diff --git a/.gitignore b/.gitignore index de985f7..21e5bdd 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ pubspec.lock .packages private_test.dart .vscode/ +doc/** diff --git a/CHANGELOG.md b/CHANGELOG.md index 99eeafa..7fe515d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 4.4.0 +__14.11.022__ + +- feature: Add support for new select menus components (#62) + ## 4.3.2 __09.11.2022__ diff --git a/example/buttons_and_dropdowns.dart b/example/buttons_and_dropdowns.dart index fe936ad..5a10d95 100644 --- a/example/buttons_and_dropdowns.dart +++ b/example/buttons_and_dropdowns.dart @@ -18,8 +18,8 @@ final singleCommand = SlashCommandBuilder("help", "This is example help command" // Adding selects is as easy as adding buttons. Use MultiselectBuilder with custom id // and list of multiselect options. final firstRow = ComponentRowBuilder() - ..addComponent(ButtonBuilder("This is button label", "thisisid", ComponentStyle.success)) - ..addComponent(ButtonBuilder("This is another button label", "thisisid2", ComponentStyle.success)); + ..addComponent(ButtonBuilder("This is button label", "thisisid", ButtonStyle.success)) + ..addComponent(ButtonBuilder("This is another button label", "thisisid2", ButtonStyle.primary)); final secondRow = ComponentRowBuilder() ..addComponent(MultiselectBuilder("customId", [ MultiselectOptionBuilder("example option 1", "option1"), diff --git a/lib/nyxx_interactions.dart b/lib/nyxx_interactions.dart index dbca9fc..d52ec79 100644 --- a/lib/nyxx_interactions.dart +++ b/lib/nyxx_interactions.dart @@ -12,7 +12,11 @@ export 'src/builders/component_builder.dart' MultiselectBuilder, MultiselectOptionBuilder, TextInputBuilder, - TextInputStyle; + TextInputStyle, + UserMultiSelectBuilder, + RoleMultiSelectBuilder, + MentionableMultiSelectBuilder, + ChannelMultiSelectBuilder; export 'src/builders/modal_builder.dart' show ModalBuilder; export 'src/builders/slash_command_builder.dart' show SlashCommandBuilder; export 'src/events/interaction_event.dart' @@ -27,7 +31,11 @@ export 'src/events/interaction_event.dart' InteractionEventWithAcknowledge, ISlashCommandInteractionEvent, IModalResponseMixin, - IModalInteractionEvent; + IModalInteractionEvent, + IUserMultiSelectInteractionEvent, + IRoleMultiSelectInteractionEvent, + IMentionableMultiSelectInteractionEvent, + IChannelMultiSelectInteractionEvent; export 'src/exceptions/already_responded.dart' show AlreadyRespondedError; export 'src/exceptions/interaction_expired.dart' show InteractionExpiredError; export 'src/exceptions/response_required.dart' show ResponseRequiredError; @@ -40,8 +48,19 @@ export 'src/internal/utils.dart' show slashCommandNameRegex; export 'src/models/arg_choice.dart' show IArgChoice; export 'src/models/command_option.dart' show ICommandOption, CommandOptionType; export 'src/models/interaction.dart' - show IComponentInteraction, IInteraction, IButtonInteraction, IMultiselectInteraction, ISlashCommandInteraction, IModalInteraction; -export 'src/models/interaction_data_resolved.dart' show IInteractionDataResolved, IPartialChannel; + show + IComponentInteraction, + IInteraction, + IButtonInteraction, + IMultiselectInteraction, + ISlashCommandInteraction, + IModalInteraction, + IUserMultiSelectInteraction, + IRoleMultiSelectInteraction, + IMentionableMultiSelectInteraction, + IChannelMultiSelectInteraction, + IResolvedSelectInteraction; +export 'src/models/interaction_data_resolved.dart' show IInteractionDataResolved, IPartialChannel, IInteractionSlashDataResolved; export 'src/models/interaction_option.dart' show IInteractionOption; export 'src/models/slash_command_permission.dart' show ISlashCommandPermissionOverride, ISlashCommandPermissionOverrides, SlashCommandPermissionType; export 'src/models/slash_command.dart' show ISlashCommand; diff --git a/lib/src/builders/component_builder.dart b/lib/src/builders/component_builder.dart index e6e13fc..1311b44 100644 --- a/lib/src/builders/component_builder.dart +++ b/lib/src/builders/component_builder.dart @@ -11,7 +11,41 @@ abstract class ComponentBuilderAbstract extends Builder { }; } -/// Allows to create multi select option for [MultiselectBuilder] +/// Abstract base class that represents any multi select builer. +abstract class MultiSelectBuilderAbstract extends ComponentBuilderAbstract { + /// Id for the select menu; max 100 characters. + final String customId; + + /// Placeholder text if nothing is selected; max 150 characters. + String? placeholder; + + /// Minimum number of items that must be chosen (defaults to 1); min 0, max 25. + int? minValues; + + /// Maximum number of items that can be chosen (defaults to 1); max 25. + int? maxValues; + + /// Whether select menu is disabled (defaults to `false`). + bool? disabled; + + MultiSelectBuilderAbstract(this.customId) { + if (customId.length > 100) { + throw ArgumentError("Custom Id for Select cannot have more than 100 characters"); + } + } + + @override + Map build() => { + ...super.build(), + 'custom_id': customId, + if (placeholder != null) 'placeholder': placeholder, + if (minValues != null) 'min_values': minValues, + if (maxValues != null) 'max_values': maxValues, + if (disabled != null) 'disabled': disabled, + }; +} + +/// Allows to create multi select options for [MultiselectBuilder]. class MultiselectOptionBuilder extends Builder { /// User-facing name of the option final String label; @@ -47,36 +81,15 @@ class MultiselectOptionBuilder extends Builder { } /// Allows to create multi select interactive components. -class MultiselectBuilder extends ComponentBuilderAbstract { +class MultiselectBuilder extends MultiSelectBuilderAbstract { @override - ComponentType get type => ComponentType.select; - - /// Max: 100 characters - final String customId; + ComponentType get type => ComponentType.multiSelect; /// Max: 25 final List options = []; - /// Custom placeholder when nothing selected - String? placeholder; - - /// Minimum number of options that can be chosen. - /// Default: 1, min: 1, max: 25 - int? minValues; - - /// Maximum numbers of options that can be chosen - /// Default: 1, min: 1, max: 25 - int? maxValues; - - /// Whether disable the select menu. - bool? disabled; - /// Creates instance of [MultiselectBuilder] - MultiselectBuilder(this.customId, [Iterable? options]) { - if (customId.length > 100) { - throw ArgumentError("Custom Id for Select cannot have more than 100 characters"); - } - + MultiselectBuilder(super.customId, [Iterable? options]) { if (options != null) { this.options.addAll(options); } @@ -88,12 +101,47 @@ class MultiselectBuilder extends ComponentBuilderAbstract { @override Map build() => { ...super.build(), - "custom_id": customId, "options": [for (final optionBuilder in options) optionBuilder.build()], - if (placeholder != null) "placeholder": placeholder, - if (minValues != null) "min_values": minValues, - if (maxValues != null) "max_values": maxValues, - if (disabled != null) "disabled": disabled, + }; +} + +/// Builder to create select menu with [IUser]s inside of it. +class UserMultiSelectBuilder extends MultiSelectBuilderAbstract { + @override + ComponentType get type => ComponentType.userMultiSelect; + + UserMultiSelectBuilder(super.customId); +} + +/// Builder to create select menu with [IRole]s inside of it. +class RoleMultiSelectBuilder extends MultiSelectBuilderAbstract { + @override + ComponentType get type => ComponentType.roleMultiSelect; + + RoleMultiSelectBuilder(super.customId); +} + +/// Builder to create select menu with mentionables ([IRole]s & [IUser]s) inside of it. +class MentionableMultiSelectBuilder extends MultiSelectBuilderAbstract { + @override + ComponentType get type => ComponentType.mentionableMultiSelect; + + MentionableMultiSelectBuilder(super.customId); +} + +/// Builder to create select menu with [IChannel]s inside of it. +class ChannelMultiSelectBuilder extends MultiSelectBuilderAbstract { + @override + ComponentType get type => ComponentType.channelMultiSelect; + + List? channelTypes; + + ChannelMultiSelectBuilder(super.customId, [this.channelTypes]); + + @override + Map build() => { + ...super.build(), + if (channelTypes != null) 'channel_types': channelTypes!.map((e) => e.value).toList(), }; } diff --git a/lib/src/events/interaction_event.dart b/lib/src/events/interaction_event.dart index fa4ad56..75a6c72 100644 --- a/lib/src/events/interaction_event.dart +++ b/lib/src/events/interaction_event.dart @@ -312,6 +312,51 @@ class MultiselectInteractionEvent extends ComponentInteractionEvent {} + +class UserMultiSelectInteractionEvent extends ComponentInteractionEvent implements IUserMultiSelectInteractionEvent { + @override + late final IUserMultiSelectInteraction interaction; + + UserMultiSelectInteractionEvent(Interactions interactions, RawApiMap raw) : super(interactions, raw) { + interaction = UserMultiSelectInteraction(client, raw); + } +} + +abstract class IRoleMultiSelectInteractionEvent implements ComponentInteractionEvent {} + +class RoleMultiSelectInteractionEvent extends ComponentInteractionEvent implements IRoleMultiSelectInteractionEvent { + @override + late final IRoleMultiSelectInteraction interaction; + + RoleMultiSelectInteractionEvent(Interactions interactions, RawApiMap raw) : super(interactions, raw) { + interaction = RoleMultiSelectInteraction(client, raw); + } +} + +abstract class IMentionableMultiSelectInteractionEvent implements ComponentInteractionEvent {} + +class MentionableMultiSelectInteractionEvent extends ComponentInteractionEvent + implements IMentionableMultiSelectInteractionEvent { + @override + late final IMentionableMultiSelectInteraction interaction; + + MentionableMultiSelectInteractionEvent(Interactions interactions, RawApiMap raw) : super(interactions, raw) { + interaction = MentionableMultiSelectInteraction(client, raw); + } +} + +abstract class IChannelMultiSelectInteractionEvent implements ComponentInteractionEvent {} + +class ChannelMultiSelectInteractionEvent extends ComponentInteractionEvent implements IChannelMultiSelectInteractionEvent { + @override + late final IChannelMultiSelectInteraction interaction; + + ChannelMultiSelectInteractionEvent(Interactions interactions, RawApiMap raw) : super(interactions, raw) { + interaction = ChannelMultiSelectInteraction(client, raw); + } +} + mixin IModalResponseMixin { IInteractions get interactions; IInteraction get interaction; diff --git a/lib/src/interactions.dart b/lib/src/interactions.dart index 8b9a0ab..5d32f6f 100644 --- a/lib/src/interactions.dart +++ b/lib/src/interactions.dart @@ -47,6 +47,18 @@ abstract class IInteractions { /// Register callback for dropdown event for given [id] void registerMultiselectHandler(String id, MultiselectInteractionHandler handler); + /// Register callback for user dropdown event for given [id]. + void registerUserMultiSelectHandler(String id, UserMultiSelectInteractionHandler handler); + + /// Register callback for role dropdown event for given [id]. + void registerRoleMultiSelectHandler(String id, RoleMultiSelectInteractionHandler handler); + + /// Register callback for mentionable dropdown event for given [id]. + void registerMentionableMultiSelectHandler(String id, MentionableMultiSelectInteractionHandler handler); + + /// Register callback for channel dropdown event for given [id]. + void registerChannelMultiSelectHandler(String id, ChannelMultiSelectInteractionHandler handler); + /// Allows to register new [SlashCommandBuilder] void registerSlashCommand(SlashCommandBuilder slashCommandBuilder); @@ -89,6 +101,10 @@ class Interactions implements IInteractions { final _buttonHandlers = {}; final _autocompleteHandlers = {}; final _multiselectHandlers = {}; + final _userMultiSelectHandlers = {}; + final _roleMultiSelectHandlers = {}; + final _mentionableMultiSelectHandlers = {}; + final _channelMultiSelectHandlers = {}; final permissionOverridesCache = >{}; @@ -133,12 +149,30 @@ class Interactions implements IInteractions { final componentType = rawData["d"]["data"]["component_type"] as int; switch (componentType) { + // ComponentType.button case 2: (events as EventController).onButtonEventController.add(ButtonInteractionEvent(this, rawData["d"] as Map)); break; + // ComponentType.select case 3: (events as EventController).onMultiselectEventController.add(MultiselectInteractionEvent(this, rawData["d"] as Map)); break; + // ComponentType.userMultiSelect + case 5: + (events as EventController).onUserMultiSelectController.add(UserMultiSelectInteractionEvent(this, rawData['d'] as RawApiMap)); + break; + // ComponentType.roleMultiSelect + case 6: + (events as EventController).onRoleMultiSelectController.add(RoleMultiSelectInteractionEvent(this, rawData['d'] as RawApiMap)); + break; + // ComponentType.mentionableMultiSelect + case 7: + (events as EventController).onMentionableMultiSelectController.add(MentionableMultiSelectInteractionEvent(this, rawData['d'] as RawApiMap)); + break; + // ComponentType.channelMultiSelect + case 8: + (events as EventController).onChannelMultiSelectController.add(ChannelMultiSelectInteractionEvent(this, rawData['d'] as RawApiMap)); + break; default: _logger.warning("Unknown componentType type: [$componentType]; Payload: ${jsonEncode(rawData)}"); } @@ -254,6 +288,50 @@ class Interactions implements IInteractions { }); } + if (_userMultiSelectHandlers.isNotEmpty) { + events.onUserMultiSelect.listen((event) { + if (_userMultiSelectHandlers.containsKey(event.interaction.customId)) { + _logger.info("Executing user select with id [${event.interaction.customId}]"); + _userMultiSelectHandlers[event.interaction.customId]!(event); + } else { + _logger.warning("Received event for unknown user select: ${event.interaction.customId}"); + } + }); + } + + if (_roleMultiSelectHandlers.isNotEmpty) { + events.onRoleMultiSelect.listen((event) { + if (_roleMultiSelectHandlers.containsKey(event.interaction.customId)) { + _logger.info("Executing role select with id [${event.interaction.customId}]"); + _roleMultiSelectHandlers[event.interaction.customId]!(event); + } else { + _logger.warning("Received event for unknown role select: ${event.interaction.customId}"); + } + }); + } + + if (_mentionableMultiSelectHandlers.isNotEmpty) { + events.onMentionableMultiSelect.listen((event) { + if (_mentionableMultiSelectHandlers.containsKey(event.interaction.customId)) { + _logger.info("Executing mentionable select with id [${event.interaction.customId}]"); + _mentionableMultiSelectHandlers[event.interaction.customId]!(event); + } else { + _logger.warning("Received event for unknown mentionable select: ${event.interaction.customId}"); + } + }); + } + + if (_channelMultiSelectHandlers.isNotEmpty) { + events.onChannelMultiSelect.listen((event) { + if (_channelMultiSelectHandlers.containsKey(event.interaction.customId)) { + _logger.info("Executing channel select with id [${event.interaction.customId}]"); + _channelMultiSelectHandlers[event.interaction.customId]!(event); + } else { + _logger.warning("Received event for unknown channel select: ${event.interaction.customId}"); + } + }); + } + if (_autocompleteHandlers.isNotEmpty) { events.onAutocompleteEvent.listen((event) { final name = event.focusedOption.name; @@ -289,6 +367,18 @@ class Interactions implements IInteractions { @override void registerSlashCommandHandler(String id, SlashCommandHandler handler) => _commandHandlers[id] = handler; + @override + void registerRoleMultiSelectHandler(String id, RoleMultiSelectInteractionHandler handler) => _roleMultiSelectHandlers[id] = handler; + + @override + void registerUserMultiSelectHandler(String id, UserMultiSelectInteractionHandler handler) => _userMultiSelectHandlers[id] = handler; + + @override + void registerMentionableMultiSelectHandler(String id, MentionableMultiSelectInteractionHandler handler) => _mentionableMultiSelectHandlers[id] = handler; + + @override + void registerChannelMultiSelectHandler(String id, ChannelMultiSelectInteractionHandler handler) => _channelMultiSelectHandlers[id] = handler; + /// Deletes global command @override Future deleteGlobalCommand(Snowflake commandId) => interactionsEndpoints.deleteGlobalCommand(client.appId, commandId); diff --git a/lib/src/internal/event_controller.dart b/lib/src/internal/event_controller.dart index 2e704b1..734d649 100644 --- a/lib/src/internal/event_controller.dart +++ b/lib/src/internal/event_controller.dart @@ -15,6 +15,18 @@ abstract class IEventController implements Disposable { /// Emitted when a dropdown interaction is received. Stream get onMultiselectEvent; + /// Emitted when a user interaction multi select is received. + Stream get onUserMultiSelect; + + /// Emitted when a role interaction multi select is received. + Stream get onRoleMultiSelect; + + /// Emitted when a mentionable interaction multi select is received. + Stream get onMentionableMultiSelect; + + /// Emitted when a channel interaction multi select is received. + Stream get onChannelMultiSelect; + /// Emitted when a slash command is created by the user. Stream get onSlashCommandCreated; @@ -38,6 +50,22 @@ class EventController implements IEventController { @override late final Stream onMultiselectEvent; + /// Emitted when a user interaction multi select is received + @override + late final Stream onUserMultiSelect; + + /// Emitted when a role interaction multi select is received. + @override + late final Stream onRoleMultiSelect; + + /// Emitted when a mentionable interaction multi select is received. + @override + late final Stream onMentionableMultiSelect; + + /// Emitted when a channel interaction multi select is received. + @override + late final Stream onChannelMultiSelect; + /// Emitted when a slash command is created by the user. @override late final Stream onSlashCommandCreated; @@ -53,10 +81,14 @@ class EventController implements IEventController { late final StreamController onSlashCommandCreatedController; late final StreamController onButtonEventController; late final StreamController onMultiselectEventController; + late final StreamController onUserMultiSelectController; + late final StreamController onRoleMultiSelectController; + late final StreamController onMentionableMultiSelectController; + late final StreamController onChannelMultiSelectController; late final StreamController onAutocompleteEventController; late final StreamController onModalEventController; - /// Creates na instance of [EventController] + /// Creates an instance of [EventController] EventController() { onSlashCommandController = StreamController.broadcast(); onSlashCommand = onSlashCommandController.stream; @@ -69,6 +101,14 @@ class EventController implements IEventController { onMultiselectEventController = StreamController.broadcast(); onMultiselectEvent = onMultiselectEventController.stream; + onUserMultiSelectController = StreamController.broadcast(); + onUserMultiSelect = onUserMultiSelectController.stream; + onRoleMultiSelectController = StreamController.broadcast(); + onRoleMultiSelect = onRoleMultiSelectController.stream; + onMentionableMultiSelectController = StreamController.broadcast(); + onMentionableMultiSelect = onMentionableMultiSelectController.stream; + onChannelMultiSelectController = StreamController.broadcast(); + onChannelMultiSelect = onChannelMultiSelectController.stream; onAutocompleteEventController = StreamController.broadcast(); onAutocompleteEvent = onAutocompleteEventController.stream; @@ -84,5 +124,9 @@ class EventController implements IEventController { await onButtonEventController.close(); await onMultiselectEventController.close(); await onAutocompleteEventController.close(); + await onUserMultiSelectController.close(); + await onRoleMultiSelectController.close(); + await onMentionableMultiSelectController.close(); + await onChannelMultiSelectController.close(); } } diff --git a/lib/src/internal/utils.dart b/lib/src/internal/utils.dart index 80525ab..ce43961 100644 --- a/lib/src/internal/utils.dart +++ b/lib/src/internal/utils.dart @@ -8,7 +8,7 @@ import 'package:nyxx_interactions/src/models/interaction.dart'; import 'package:nyxx_interactions/src/models/slash_command.dart'; /// Slash command names and subcommands names have to match this regex -final RegExp slashCommandNameRegex = RegExp(r"^[\w-]{1,32}$"); +final RegExp slashCommandNameRegex = RegExp(r"^[-_\p{L}\p{N}\p{sc=Deva}\p{sc=Thai}]{1,32}$", unicode: true); Iterable> partition(Iterable list, bool Function(T) predicate) { final matches = []; diff --git a/lib/src/models/interaction.dart b/lib/src/models/interaction.dart index cc14e76..4083c12 100644 --- a/lib/src/models/interaction.dart +++ b/lib/src/models/interaction.dart @@ -2,7 +2,6 @@ import 'package:nyxx/nyxx.dart'; import 'package:nyxx/src/core/permissions/permissions.dart'; import 'package:nyxx/src/core/user/user.dart'; import 'package:nyxx/src/core/user/member.dart'; -import 'package:nyxx/src/core/guild/guild.dart'; import 'package:nyxx/src/internal/cache/cacheable.dart'; import 'package:nyxx/src/core/channel/cacheable_text_channel.dart'; import 'package:nyxx/src/core/message/message.dart'; @@ -174,7 +173,7 @@ abstract class ISlashCommandInteraction implements IInteraction { late final Snowflake commandId; /// Additional data for command - late final IInteractionDataResolved? resolved; + late final IInteractionSlashDataResolved? resolved; /// Id of the target entity (only present in message or user interactions) Snowflake? get targetId; @@ -196,7 +195,7 @@ class SlashCommandInteraction extends Interaction implements ISlashCommandIntera /// Additional data for command @override - late final IInteractionDataResolved? resolved; + late final IInteractionSlashDataResolved? resolved; @override late final Snowflake? targetId; @@ -210,7 +209,7 @@ class SlashCommandInteraction extends Interaction implements ISlashCommandIntera ]; commandId = Snowflake(raw["data"]["id"]); - resolved = raw["data"]["resolved"] != null ? InteractionDataResolved(raw["data"]["resolved"] as RawApiMap, guild?.id, client) : null; + resolved = raw["data"]["resolved"] != null ? InteractionSlashDataResolved(raw["data"]["resolved"] as RawApiMap, guild?.id, client) : null; targetId = raw["data"]["target_id"] != null ? Snowflake(raw["data"]["target_id"]) : null; } @@ -275,3 +274,87 @@ class MultiselectInteraction extends ComponentInteraction implements IMultiselec values = (raw["data"]["values"] as List).cast(); } } + +abstract class IResolvedSelectInteraction implements IComponentInteraction { + /// Iterable of all ids selectionned. + Iterable get values; +} + +abstract class ResolvedSelectInteraction extends ComponentInteraction implements IResolvedSelectInteraction { + @override + late final Iterable values; + + ResolvedSelectInteraction(INyxx client, RawApiMap raw) : super(client, raw) { + values = (raw['data']['values'] as List).cast().map(Snowflake.new); + } +} + +abstract class IUserMultiSelectInteraction implements IResolvedSelectInteraction { + /// The users that were selected. + Iterable get users; + + /// The [IMember]s attached to the [users]. + Iterable get members; +} + +class UserMultiSelectInteraction extends ResolvedSelectInteraction implements IUserMultiSelectInteraction { + @override + late final Iterable users; + @override + late final Iterable members; + + UserMultiSelectInteraction(INyxx client, RawApiMap raw) : super(client, raw) { + final resolved = InteractionDataResolved(raw['data']['resolved'] as RawApiMap, guild?.id, client); + users = resolved.users; + members = resolved.members; + } +} + +abstract class IRoleMultiSelectInteraction implements IResolvedSelectInteraction { + /// The roles that were selected. + Iterable get roles; +} + +class RoleMultiSelectInteraction extends ResolvedSelectInteraction implements IRoleMultiSelectInteraction { + @override + late final Iterable roles; + + RoleMultiSelectInteraction(INyxx client, RawApiMap raw) : super(client, raw) { + final resolved = InteractionDataResolved(raw['data']['resolved'] as RawApiMap, guild?.id, client); + roles = resolved.roles; + } +} + +abstract class IMentionableMultiSelectInteraction implements IResolvedSelectInteraction { + /// The mentionables that were selected. + Iterable get mentionables; +} + +class MentionableMultiSelectInteraction extends ResolvedSelectInteraction implements IMentionableMultiSelectInteraction { + @override + late final Iterable mentionables; + + MentionableMultiSelectInteraction(INyxx client, RawApiMap raw) : super(client, raw) { + final resolved = InteractionDataResolved(raw['data']['resolved'] as RawApiMap, guild?.id, client); + mentionables = [ + ...resolved.users, + ...resolved.members, + ...resolved.roles, + ]; + } +} + +abstract class IChannelMultiSelectInteraction implements IResolvedSelectInteraction { + /// The channels that were selected. + Iterable get channels; +} + +class ChannelMultiSelectInteraction extends ResolvedSelectInteraction implements IChannelMultiSelectInteraction { + @override + late final Iterable channels; + + ChannelMultiSelectInteraction(INyxx client, RawApiMap raw) : super(client, raw) { + final resolved = InteractionDataResolved(raw['data']['resolved'] as RawApiMap, guild?.id, client); + channels = resolved.channels; + } +} diff --git a/lib/src/models/interaction_data_resolved.dart b/lib/src/models/interaction_data_resolved.dart index 3e97062..9a35ca5 100644 --- a/lib/src/models/interaction_data_resolved.dart +++ b/lib/src/models/interaction_data_resolved.dart @@ -6,11 +6,12 @@ import 'package:nyxx/src/core/guild/role.dart'; import 'package:nyxx/src/core/message/message.dart'; import 'package:nyxx/src/core/message/attachment.dart'; -abstract class IPartialChannel implements SnowflakeEntity { +abstract class IPartialChannel implements SnowflakeEntity, IChannel { /// Channel name String get name; /// Type of channel + @Deprecated('Use "channelType" instead') ChannelType get type; /// Permissions of user in channel @@ -25,33 +26,47 @@ class PartialChannel extends SnowflakeEntity implements IPartialChannel { /// Type of channel @override - late final ChannelType type; + late final ChannelType channelType; + + @override + late final ChannelType type = channelType; /// Permissions of user in channel @override late final IPermissions permissions; - /// Creates na instance of [PartialChannel] - PartialChannel(RawApiMap raw) : super(Snowflake(raw["id"])) { + @override + final INyxx client; + + /// Creates an instance of [PartialChannel] + PartialChannel(RawApiMap raw, this.client) : super(Snowflake(raw["id"])) { name = raw["name"] as String; - type = ChannelType.from(raw["type"] as int); + channelType = ChannelType.from(raw["type"] as int); permissions = Permissions(int.parse(raw["permissions"].toString())); } + + @override + Future delete() => client.httpEndpoints.deleteChannel(id); + + @override + Future dispose() async {} } abstract class IInteractionDataResolved { - /// Resolved [User]s + /// Resolved [IUser]s Iterable get users; - /// Resolved [Member]s + /// Resolved [IMember]s Iterable get members; - /// Resolved [Role]s + /// Resolved [IRole]s Iterable get roles; - /// Resolved [PartialChannel]s + /// Resolved [IPartialChannel]s Iterable get channels; +} +abstract class IInteractionSlashDataResolved implements IInteractionDataResolved { /// Resolved [IMessage] objects Iterable get messages; @@ -59,10 +74,9 @@ abstract class IInteractionDataResolved { Iterable get attachments; } -/// Additional data for slash command class InteractionDataResolved implements IInteractionDataResolved { @override - late final Iterable users; + late final Iterable channels; @override late final Iterable members; @@ -71,43 +85,78 @@ class InteractionDataResolved implements IInteractionDataResolved { late final Iterable roles; @override - late final Iterable channels; - - @override - late final Iterable messages; - - @override - late final Iterable attachments; + late final Iterable users; - /// Creates na instance of [InteractionDataResolved] InteractionDataResolved(RawApiMap raw, Snowflake? guildId, INyxx client) { users = [ if (raw["users"] != null) - for (final rawUserEntry in (raw["users"] as RawApiMap).entries) User(client, rawUserEntry.value as RawApiMap) + for (final rawUserEntry in (raw["users"] as RawApiMap).entries) + if (client.cacheOptions.userCachePolicyLocation.objectConstructor) + client.users.putIfAbsent(Snowflake(rawUserEntry.value['id']), () => User(client, rawUserEntry.value as RawApiMap)) + else + User(client, rawUserEntry.value as RawApiMap) ]; - members = [ - if (raw["members"] != null) - for (final rawMemberEntry in (raw["members"] as RawApiMap).entries) - Member( - client, - { - ...rawMemberEntry.value as RawApiMap, - "user": {"id": rawMemberEntry.key} - }, - guildId!) - ]; + members = []; + + if (raw["members"] != null) { + for (final rawMemberEntry in (raw["members"] as RawApiMap).entries) { + final member = Member( + client, + { + ...rawMemberEntry.value as RawApiMap, + "user": {"id": rawMemberEntry.key} + }, + guildId!, + ); + if (client.cacheOptions.memberCachePolicyLocation.objectConstructor && client.cacheOptions.memberCachePolicy.canCache(member)) { + client.guilds[guildId]?.members.putIfAbsent(member.id, () => member); + (members as List).add(member); + } else { + (members as List).add(member); + } + } + } + + roles = []; + + if (raw["roles"] != null) { + for (final rawRoleEntry in (raw["roles"] as RawApiMap).entries) { + final role = Role(client, rawRoleEntry.value as RawApiMap, guildId!); + + client.guilds[guildId]?.roles.putIfAbsent(role.id, () => role); + + (roles as List).add(role); + } + } + + channels = []; + + if (raw["channels"] != null) { + for (final rawChannelEntry in (raw["channels"] as RawApiMap).entries) { + final channel = PartialChannel(rawChannelEntry.value as RawApiMap, client); + + if (client.cacheOptions.channelCachePolicyLocation.objectConstructor && client.cacheOptions.channelCachePolicy.canCache(channel)) { + client.channels.putIfAbsent(channel.id, () => channel); + (channels as List).add(channel); + } else { + (channels as List).add(channel); + } + } + } + } +} - roles = [ - if (raw["roles"] != null) - for (final rawRoleEntry in (raw["roles"] as RawApiMap).entries) Role(client, rawRoleEntry.value as RawApiMap, guildId!) - ]; +/// Additional data for slash command +class InteractionSlashDataResolved extends InteractionDataResolved implements IInteractionSlashDataResolved { + @override + late final Iterable messages; - channels = [ - if (raw["channels"] != null) - for (final rawChannelEntry in (raw["channels"] as RawApiMap).entries) PartialChannel(rawChannelEntry.value as RawApiMap) - ]; + @override + late final Iterable attachments; + /// Creates na instance of [InteractionDataResolved] + InteractionSlashDataResolved(RawApiMap raw, Snowflake? guildId, INyxx client) : super(raw, guildId, client) { messages = [ if (raw['messages'] != null) for (final rawMessageEntry in (raw['messages'] as RawApiMap).entries) Message(client, rawMessageEntry.value as RawApiMap) diff --git a/lib/src/typedefs.dart b/lib/src/typedefs.dart index 736907f..7cd3e77 100644 --- a/lib/src/typedefs.dart +++ b/lib/src/typedefs.dart @@ -13,3 +13,15 @@ typedef MultiselectInteractionHandler = FutureOr Function(IMultiselectInte /// Function that will handle execution of button interaction event typedef AutocompleteInteractionHandler = FutureOr Function(IAutocompleteInteractionEvent); + +/// Function that will handle execution of user dropdown event +typedef UserMultiSelectInteractionHandler = FutureOr Function(IUserMultiSelectInteractionEvent); + +/// Function that will handle execution of role dropdown event +typedef RoleMultiSelectInteractionHandler = FutureOr Function(IRoleMultiSelectInteractionEvent); + +/// Function that will handle execution of mentionable dropdown event +typedef MentionableMultiSelectInteractionHandler = FutureOr Function(IMentionableMultiSelectInteractionEvent); + +/// Function that will handle execution of channel dropdown event +typedef ChannelMultiSelectInteractionHandler = FutureOr Function(IChannelMultiSelectInteractionEvent); diff --git a/pubspec.yaml b/pubspec.yaml index a5589d3..015a7b3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: nyxx_interactions -version: 4.3.2 +version: 4.4.0 description: Nyxx Interactions Module. Discord library for Dart. Simple, robust framework for creating discord bots for Dart language. homepage: https://github.com/nyxx-discord/nyxx_interactions repository: https://github.com/nyxx-discord/nyxx_interactions @@ -12,7 +12,7 @@ environment: dependencies: crypto: ^3.0.1 logging: ^1.0.1 - nyxx: ^4.0.0 + nyxx: ^4.2.0 dev_dependencies: test: ^1.19.0 diff --git a/test/unit/builder.dart b/test/unit/builder_test.dart old mode 100755 new mode 100644 similarity index 100% rename from test/unit/builder.dart rename to test/unit/builder_test.dart diff --git a/test/unit/command_option_builder.dart b/test/unit/command_option_builder_test.dart old mode 100755 new mode 100644 similarity index 100% rename from test/unit/command_option_builder.dart rename to test/unit/command_option_builder_test.dart diff --git a/test/unit/command_permission_builder.dart b/test/unit/command_permission_builder_test.dart similarity index 100% rename from test/unit/command_permission_builder.dart rename to test/unit/command_permission_builder_test.dart diff --git a/test/unit/command_sync.dart b/test/unit/command_sync_test.dart old mode 100755 new mode 100644 similarity index 100% rename from test/unit/command_sync.dart rename to test/unit/command_sync_test.dart diff --git a/test/unit/component_builder.dart b/test/unit/component_builder_test.dart similarity index 88% rename from test/unit/component_builder.dart rename to test/unit/component_builder_test.dart index 9a46669..95902fe 100644 --- a/test/unit/component_builder.dart +++ b/test/unit/component_builder_test.dart @@ -7,12 +7,20 @@ main() { final customButton = ButtonBuilder("label", "customId", ButtonStyle.secondary); final linkButton = LinkButtonBuilder("label2", "discord://-/"); final multiselect = MultiselectBuilder("customId2", [MultiselectOptionBuilder("label1", "value1", true)]); + final multiSelectUser = ChannelMultiSelectBuilder('userCustomId') + ..disabled = true + ..minValues = 1 + ..maxValues = 3 + ..placeholder = 'A placeholder here' + ..channelTypes = [ChannelType.text, ChannelType.voice]; final componentRow = ComponentRowBuilder() ..addComponent(customButton) ..addComponent(linkButton); - final secondComponentRow = ComponentRowBuilder()..addComponent(multiselect); + final secondComponentRow = ComponentRowBuilder() + ..addComponent(multiselect) + ..addComponent(multiSelectUser); final messageBuilder = ComponentMessageBuilder() ..addComponentRow(componentRow) @@ -38,6 +46,15 @@ main() { 'options': [ {'label': 'label1', 'value': 'value1', 'default': true} ] + }, + { + 'type': 8, + 'custom_id': 'userCustomId', + 'placeholder': 'A placeholder here', + 'min_values': 1, + 'max_values': 3, + 'disabled': true, + 'channel_types': [0, 2] } ] } diff --git a/test/unit/event_controller.dart b/test/unit/event_controller_test.dart old mode 100755 new mode 100644 similarity index 100% rename from test/unit/event_controller.dart rename to test/unit/event_controller_test.dart diff --git a/test/unit/model.dart b/test/unit/model_test.dart similarity index 100% rename from test/unit/model.dart rename to test/unit/model_test.dart diff --git a/test/unit/nyxx_backend.dart b/test/unit/nyxx_backend_test.dart similarity index 100% rename from test/unit/nyxx_backend.dart rename to test/unit/nyxx_backend_test.dart diff --git a/test/unit/slash_command_builder.dart b/test/unit/slash_command_builder_test.dart old mode 100755 new mode 100644 similarity index 100% rename from test/unit/slash_command_builder.dart rename to test/unit/slash_command_builder_test.dart diff --git a/test/unit/utils.dart b/test/unit/utils_test.dart old mode 100755 new mode 100644 similarity index 100% rename from test/unit/utils.dart rename to test/unit/utils_test.dart