diff --git a/.gitignore b/.gitignore index 2a5e2d0e..11e65028 100644 --- a/.gitignore +++ b/.gitignore @@ -33,4 +33,4 @@ private-*.dart test-*.dart [Rr]pc* **/doc/api/** -old.* \ No newline at end of file +old.* diff --git a/nyxx.extensions/pubspec.yaml b/nyxx.extensions/pubspec.yaml index 81c23625..5a812cc5 100644 --- a/nyxx.extensions/pubspec.yaml +++ b/nyxx.extensions/pubspec.yaml @@ -1,5 +1,5 @@ name: nyxx_extensions -version: 1.1.0-dev.1 +version: 1.1.0-dev.2 description: Extensions for Nyxx library homepage: https://github.com/l7ssha/nyxx repository: https://github.com/l7ssha/nyxx diff --git a/nyxx.interactions/lib/interactions.dart b/nyxx.interactions/lib/interactions.dart index f166f7ad..d9da628d 100644 --- a/nyxx.interactions/lib/interactions.dart +++ b/nyxx.interactions/lib/interactions.dart @@ -9,20 +9,20 @@ import "package:nyxx/nyxx.dart"; part "src/Interactions.dart"; // Models -part 'src/Models/SlashCommand.dart'; +part "src/Models/SlashCommand.dart"; part "src/Models/Interaction.dart"; part "src/Models/InteractionOption.dart"; // Command Args -part 'src/Models/CommandArgs/ArgChoice.dart'; -part 'src/Models/CommandArgs/CommandArg.dart'; +part "src/Models/CommandArgs/ArgChoice.dart"; +part "src/Models/CommandArgs/CommandArg.dart"; // Internal -part 'src/Internal/_EventController.dart'; +part "src/Internal/_EventController.dart"; // Events part "src/Events/InteractionEvent.dart"; // Exceptions part "src/Exceptions/InteractionExpired.dart"; -part "src/Exceptions/AlreadyResponded.dart"; \ No newline at end of file +part "src/Exceptions/AlreadyResponded.dart"; diff --git a/nyxx.interactions/lib/src/Events/InteractionEvent.dart b/nyxx.interactions/lib/src/Events/InteractionEvent.dart index efcb8143..33c3c36f 100644 --- a/nyxx.interactions/lib/src/Events/InteractionEvent.dart +++ b/nyxx.interactions/lib/src/Events/InteractionEvent.dart @@ -1,26 +1,29 @@ part of nyxx_interactions; +/// The event that you receive when a user types a slash command. class InteractionEvent { late final Nyxx _client; + /// The interaction data, includes the args, name, guild, channel, etc. late final Interaction interaction; - final DateTime recievedAt = DateTime.now(); + /// The DateTime the interaction was received by the Nyxx Client. + final DateTime receivedAt = DateTime.now(); + /// If the Client has sent a response to the Discord API. Once the API was received a response you cannot send another. bool hasResponded = false; InteractionEvent._new(Nyxx client, Map rawJson) { this._client = client; interaction = Interaction._new(client, rawJson); - if(interaction.type == 1) { + if (interaction.type == 1) { this._pong(); } } - /// Should be sent when you receive a ping from interactions. Used to acknowledge a ping. - Future _pong() { - if (DateTime.now() - .isBefore(this.recievedAt.add(const Duration(minutes: 15)))) { + /// Should be sent when you receive a ping from interactions. Used to acknowledge a ping. Internal to the InteractionEvent. + Future _pong() async { + if (DateTime.now().isBefore(this.receivedAt.add(const Duration(minutes: 15)))) { if (!hasResponded) { - final response = this._client.httpEndpoints.sendRawRequest( + final response = await this._client.httpEndpoints.sendRawRequest( "/interactions/${this.interaction.id.toString()}/${this.interaction.token}/callback", "POST", body: { @@ -36,7 +39,6 @@ class InteractionEvent { if (!hasResponded) { hasResponded = true; } - return Future.value(null); } else { return Future.error(InteractionExpired()); } @@ -45,19 +47,15 @@ class InteractionEvent { } } - /// Used to acknowledge a Interaction but not send any response yet. Once this is sent you can then only send ChannelMessages with or without source. - Future acknowledge({ - bool showSource = false, - }) { - if (DateTime.now() - .isBefore(this.recievedAt.add(const Duration(minutes: 15)))) { + /// Used to acknowledge a Interaction but not send any response yet. Once this is sent you can then only send ChannelMessages. You can also set showSource to also print out the command the user entered. + Future acknowledge({ bool showSource = false, }) async { + if (DateTime.now().isBefore(this.receivedAt.add(const Duration(minutes: 15)))) { if (hasResponded) { return Future.error(AlreadyResponded()); } - final url = - "/interactions/${this.interaction.id.toString()}/${this.interaction.token}/callback"; + final url = "/interactions/${this.interaction.id.toString()}/${this.interaction.token}/callback"; - final response = this._client.httpEndpoints.sendRawRequest( + final response = await this._client.httpEndpoints.sendRawRequest( url, "POST", body: { @@ -73,29 +71,19 @@ class InteractionEvent { if (!hasResponded) { hasResponded = true; } - - return Future.value(null); } else { return Future.error(InteractionExpired()); } } - Future reply({ - dynamic content, - EmbedBuilder? embed, - bool? tts, - AllowedMentions? allowedMentions, - bool showSource = false, - }) async { - if (DateTime.now() - .isBefore(this.recievedAt.add(const Duration(minutes: 15)))) { + /// Used to acknowledge a Interaction and send a response. Once this is sent you can then only send ChannelMessages. You can also set showSource to also print out the command the user entered. + Future reply({ dynamic content, EmbedBuilder? embed, bool? tts, AllowedMentions? allowedMentions, bool showSource = false, }) async { + if (DateTime.now().isBefore(this.receivedAt.add(const Duration(minutes: 15)))) { String url; if (hasResponded) { - url = - "/webhooks/${this.interaction.id.toString()}/${this.interaction.token}"; + url = "/webhooks/${this.interaction.id.toString()}/${this.interaction.token}"; } else { - url = - "/interactions/${this.interaction.id.toString()}/${this.interaction.token}/callback"; + url = "/interactions/${this.interaction.id.toString()}/${this.interaction.token}/callback"; } final response = await this._client.httpEndpoints.sendRawRequest( url, @@ -104,10 +92,9 @@ class InteractionEvent { "type": showSource ? 4 : 3, "data": { "content": content, - "embeds": embed != null ? [ BuilderUtility.buildRawEmbed(embed) ] : null, - "allowed_mentions": allowedMentions != null - ? BuilderUtility.buildRawAllowedMentions(allowedMentions) - : null, + "embeds": embed != null ? [BuilderUtility.buildRawEmbed(embed)] : null, + "allowed_mentions": + allowedMentions != null ? BuilderUtility.buildRawAllowedMentions(allowedMentions) : null, "tts": content != null && tts != null && tts }, }, @@ -117,12 +104,9 @@ class InteractionEvent { return Future.error(response); } - print((response as HttpResponseSuccess).body); - if (!hasResponded) { hasResponded = true; } - return Future.value(null); } else { return Future.error(InteractionExpired()); } diff --git a/nyxx.interactions/lib/src/Exceptions/AlreadyResponded.dart b/nyxx.interactions/lib/src/Exceptions/AlreadyResponded.dart index 274eb693..ed5cc4b8 100644 --- a/nyxx.interactions/lib/src/Exceptions/AlreadyResponded.dart +++ b/nyxx.interactions/lib/src/Exceptions/AlreadyResponded.dart @@ -1,7 +1,8 @@ part of nyxx_interactions; -/// Thrown when 15 minutes has passed since an interaction was called. +/// Thrown when you have already responded to an interaction class AlreadyResponded implements Error { + /// Returns a string representation of this object. @override String toString() => @@ -9,4 +10,4 @@ class AlreadyResponded implements Error { @override StackTrace? get stackTrace => StackTrace.empty; -} \ No newline at end of file +} diff --git a/nyxx.interactions/lib/src/Exceptions/InteractionExpired.dart b/nyxx.interactions/lib/src/Exceptions/InteractionExpired.dart index 8ae9b53e..c56118d2 100644 --- a/nyxx.interactions/lib/src/Exceptions/InteractionExpired.dart +++ b/nyxx.interactions/lib/src/Exceptions/InteractionExpired.dart @@ -2,6 +2,7 @@ part of nyxx_interactions; /// Thrown when 15 minutes has passed since an interaction was called. class InteractionExpired implements Error { + /// Returns a string representation of this object. @override String toString() => @@ -9,4 +10,4 @@ class InteractionExpired implements Error { @override StackTrace? get stackTrace => StackTrace.empty; -} \ No newline at end of file +} diff --git a/nyxx.interactions/lib/src/Interactions.dart b/nyxx.interactions/lib/src/Interactions.dart index e60daf87..9ff896e9 100644 --- a/nyxx.interactions/lib/src/Interactions.dart +++ b/nyxx.interactions/lib/src/Interactions.dart @@ -4,14 +4,14 @@ part of nyxx_interactions; class Interactions { late final Nyxx _client; final Logger _logger = Logger("Interactions"); - final List _commands = List.empty(growable: true); + final List _commands = []; late final _EventController _events; - /// Emitted when a a slash command is sent. - late Stream onSlashCommand; + /// Emitted when a slash command is sent. + late final Stream onSlashCommand; - /// Emitted when a a slash command is sent. - late Stream onSlashCommandCreated; + /// Emitted when a slash command is created by the user. + late final Stream onSlashCommandCreated; /// Interactions(Nyxx client) { @@ -22,12 +22,10 @@ class Interactions { client.onReady.listen((event) { client.shardManager.rawEvent.listen((event) { - print(event.rawData); if (event.rawData["op"] as int == 0) { if (event.rawData["t"] as String == "INTERACTION_CREATE") { _events.onSlashCommand.add( - InteractionEvent._new( - client, event.rawData["d"] as Map), + InteractionEvent._new(client, event.rawData["d"] as Map), ); } } @@ -35,55 +33,55 @@ class Interactions { }); } - SlashCommand createCommand(String name, String description, List args, {String? guild}) - => SlashCommand._new(_client, name, description, args, guild: guild != null ? CacheUtility.createCacheableGuild(_client, Snowflake(guild)) : null); + /// Creates a command that can be registered using .registerCommand or .registerCommands + /// + /// The [name] is the name that the user can see when typing /, the [description] can also be seen in this same place. [args] are any arguments you want the user to type, you can put an empty list here is you require no arguments. If you want this to be specific to a guild you can set the [guild] param with the ID of a guild, when testing its recommended to use this as it propagates immediately while global commands can take some time. + SlashCommand createCommand(String name, String description, List args, {Snowflake? guild}) => + SlashCommand._new(_client, name, description, args, + guild: guild != null ? CacheUtility.createCacheableGuild(_client, guild) : null); /// Registers a single command. /// - /// @param command A [SlashCommand] to register. - void registerCommand(SlashCommand command) { - _commands.add(command); - } + /// The command you want to register is the [command] you create a command by using [createCommand] + void registerCommand(SlashCommand command) => _commands.add(command); /// Registers multiple commands at one. /// - /// This wraps around [Interactions.registerCommand()] running [Interactions.registerCommand] for each command in the list. - /// @param commands A list of [SlashCommand]s to register. - void registerCommands(List commands) { - for(var i = 0; i < commands.length; i++) { - this.registerCommand(commands[i]); - } - } + /// The commands you want to register is the [commands] you create a command by using [createCommand], this just runs [registerCommand] for each command. + void registerCommands(List commands) => commands.forEach(this.registerCommand); + /// Gets all the commands that are currently registered. List getCommands({bool registeredOnly = false}) { - if(!registeredOnly) { return _commands; } + if (!registeredOnly) { + return _commands; + } final registeredCommands = List.empty(growable: true); - for(var i = 0; i < _commands.length; i++) { - final el = _commands[i]; - if(el.isRegistered) { + for (final el in _commands) { + if (el.isRegistered) { registeredCommands.add(el); } } return registeredCommands; } + /// Syncs the local commands with the discord API Future sync() async { var success = 0; var failed = 0; - for(var i = 0; i < _commands.length; i++) { - final el = _commands[i]; - if(!el.isRegistered) { - final command = await el._register(); - if(command != null) { - _commands[i] = command; - this._events.onSlashCommandCreated.add(_commands[i]); - success++; - } else { - failed++; - } + for (final el in _commands) { + if (!el.isRegistered) { + await el + ._register() + .catchError(() { + failed++; + return; + }); + + this._events.onSlashCommandCreated.add(el); + success++; } } - _logger.info("Successfully registered $success ${success > 1 ? "commands" : "command"}. Failed registering $failed ${failed > 1 ? "commands" : "command"}."); + _logger.info( + "Successfully registered $success ${success > 1 ? "commands" : "command"}. Failed registering $failed ${failed > 1 ? "commands" : "command"}."); } - } diff --git a/nyxx.interactions/lib/src/Internal/_EventController.dart b/nyxx.interactions/lib/src/Internal/_EventController.dart index 4ca4d4f8..04861ec1 100644 --- a/nyxx.interactions/lib/src/Internal/_EventController.dart +++ b/nyxx.interactions/lib/src/Internal/_EventController.dart @@ -20,4 +20,4 @@ class _EventController implements Disposable { await this.onSlashCommand.close(); await this.onSlashCommandCreated.close(); } -} \ No newline at end of file +} diff --git a/nyxx.interactions/lib/src/Models/CommandArgs/ArgChoice.dart b/nyxx.interactions/lib/src/Models/CommandArgs/ArgChoice.dart index a1427a83..7c76cab7 100644 --- a/nyxx.interactions/lib/src/Models/CommandArgs/ArgChoice.dart +++ b/nyxx.interactions/lib/src/Models/CommandArgs/ArgChoice.dart @@ -1,31 +1,23 @@ part of nyxx_interactions; +/// A specified choice for a slash command argument. class ArgChoice { - /// This options name. late final String name; - /// This options int value. If there is an int value there can't be a [SlashArgChoice.stringValue] - late final int? intValue; - - /// This options string value. If there is an string value there can't be a [SlashArgChoice.intValue] - late final String? stringValue; + /// This is the options value, must be int or string + late final dynamic value; /// A Choice for the user to input in int & string args. You can only have an int or string option. ArgChoice(this.name, dynamic value) { - if(value is String) { - this.stringValue = value; + if (value is String || value is int) { + this.value = value; } - if(value is int) { - this.intValue = value; - } - if(value is! int && value is! String) { - throw 'InvalidParamTypeError: Please send a string if its a string arg or an int if its an int arg.'; + + if (value is! int && value is! String) { + throw "InvalidParamTypeError: Please send a string if its a string arg or an int if its an int arg."; } } - Map _build() => { - "name": this.name, - "value": this.stringValue ?? this.intValue - }; + Map _build() => {"name": this.name, "value": this.value}; } diff --git a/nyxx.interactions/lib/src/Models/CommandArgs/CommandArg.dart b/nyxx.interactions/lib/src/Models/CommandArgs/CommandArg.dart index 11ad7cb8..21669167 100644 --- a/nyxx.interactions/lib/src/Models/CommandArgs/CommandArg.dart +++ b/nyxx.interactions/lib/src/Models/CommandArgs/CommandArg.dart @@ -2,14 +2,22 @@ part of nyxx_interactions; /// The type that a user should input for a [SlashArg] enum CommandArgType { - SUB_COMMAND, - SUB_COMMAND_GROUP, - STRING, - INTEGER, - BOOLEAN, - USER, - CHANNEL, - ROLE, + /// Specify an arg as a sub command + subCommand, + /// Specify an arg as a sub command group + subCommandGroup, + /// Specify an arg as a string + string, + /// Specify an arg as an int + integer, + /// Specify an arg as a bool + boolean, + /// Specify an arg as a user e.g @HarryET#2954 + user, + /// Specify an arg as a channel e.g. #Help + channel, + /// Specify an arg as a role e.g. @RoleName + role, } /// An argument for a [SlashCommand]. @@ -39,29 +47,25 @@ class CommandArg { /// If this argument is required late final bool required; - /// Choices for [SlashArgType.STRING] and [SlashArgType.INTEGER] types for the user to pick from + /// Choices for [CommandArgType.string] and [CommandArgType.string] types for the user to pick from late final List? choices; /// If the option is a subcommand or subcommand group type, this nested options will be the parameters late final List? options; - /// Used to create an argument for a [SlashCommand]. Thease are used in [Interactions.registerSlashGlobal] and [Interactions.registerSlashGuild] + /// Used to create an argument for a [SlashCommand]. Tease are used in [Interactions.registerCommand] CommandArg(this.type, this.name, this.description, - {this.defaultArg = false, - this.required = false, - this.choices, - this.options}); + {this.defaultArg = false, this.required = false, this.choices, this.options}); Map _build() { final subOptions = this.options != null - ? List>.generate( - this.options!.length, (i) => this.options![i]._build()) + ? this.options!.map((e) => e._build()) : null; final rawChoices = this.choices != null - ? List>.generate( - this.choices!.length, (i) => this.choices![i]._build()) + ? this.choices!.map((e) => e._build()) : null; + return { "type": (this.type.index) + 1, "name": this.name, diff --git a/nyxx.interactions/lib/src/Models/Interaction.dart b/nyxx.interactions/lib/src/Models/Interaction.dart index ebd04b99..f016db6b 100644 --- a/nyxx.interactions/lib/src/Models/Interaction.dart +++ b/nyxx.interactions/lib/src/Models/Interaction.dart @@ -16,30 +16,27 @@ class Interaction extends SnowflakeEntity implements Disposable { late final int version; - late final String name; // TODO + late final String name; - late final Map args; // TODO + late final Map args; Interaction._new( - this.client, - Map raw, - ) : super(Snowflake(raw["id"])) { + this.client, + Map raw, + ) : super(Snowflake(raw["id"])) { this.type = raw["type"] as int; - this.guild = CacheUtility.createCacheableGuild( client, Snowflake( raw["guild_id"], ), ); - this.channel = CacheUtility.createCacheableTextChannel( client, Snowflake( raw["channel_id"], ), ); - this.author = EntityUtility.createGuildMember( client, Snowflake( @@ -47,26 +44,22 @@ class Interaction extends SnowflakeEntity implements Disposable { ), raw["member"] as Map, ); - this.token = raw["token"] as String; - this.version = raw["version"] as int; - this.name = raw["data"]["name"] as String; - this.args = _generateArgs(raw["data"] as Map); } Map _generateArgs(Map rawData) { final args = {}; - if(rawData["options"] != null) { + if (rawData["options"] != null) { final l = rawData["options"] as List; - for(var i = 0; i < l.length; i++) { + for (var i = 0; i < l.length; i++) { final el = l[i]; args[el["name"] as String] = InteractionOption._new( el["value"] as dynamic, - (el["options"] ?? List.empty() ) as List, + (el["options"] ?? List.empty()) as List, ); } } @@ -76,4 +69,4 @@ class Interaction extends SnowflakeEntity implements Disposable { @override Future dispose() => Future.value(null); -} \ No newline at end of file +} diff --git a/nyxx.interactions/lib/src/Models/InteractionOption.dart b/nyxx.interactions/lib/src/Models/InteractionOption.dart index 24eea24a..5a058df3 100644 --- a/nyxx.interactions/lib/src/Models/InteractionOption.dart +++ b/nyxx.interactions/lib/src/Models/InteractionOption.dart @@ -1,14 +1,18 @@ part of nyxx_interactions; +/// The option given by the user when sending a command class InteractionOption { + /// The value given by the user final dynamic? value; - final Map args = {}; + + /// Any args under this as you can have sub commands + final Map args = {}; + InteractionOption._new(this.value, List rawOptions) { - for(var i = 0; i < rawOptions.length; i++) { - final el = rawOptions[i]; + for (final el in rawOptions) { this.args[el["name"] as String] = InteractionOption._new( el["value"] as dynamic, - (el["options"] ?? List.empty() ) as List, + (el["options"] ?? List.empty()) as List, ); } } diff --git a/nyxx.interactions/lib/src/Models/SlashCommand.dart b/nyxx.interactions/lib/src/Models/SlashCommand.dart index d0e8f275..52449fba 100644 --- a/nyxx.interactions/lib/src/Models/SlashCommand.dart +++ b/nyxx.interactions/lib/src/Models/SlashCommand.dart @@ -1,12 +1,18 @@ part of nyxx_interactions; +/// A slash command, can only be instantiated through a method on [Interactions] class SlashCommand { - + /// Command name to be shown to the user in the Slash Command UI late final String name; + /// Command description shown to the user in the Slash Command UI late final String description; + /// The guild that the slash Command is registered in. This can be null if its a global command. late final Cacheable? guild; + /// The arguments that the command takes late final List args; + /// If the command is a global on, false if restricted to a guild. late final bool isGlobal; + /// If the command has been registered with the discord api late bool isRegistered = false; late final Nyxx _client; @@ -16,24 +22,19 @@ class SlashCommand { } Future _register() async { - final options = List>.generate( - this.args.length, (i) => this.args[i]._build()); + final options = args.map((e) => e._build()); final response = await this._client.httpEndpoints.sendRawRequest( "/applications/${this._client.app.id.toString()}/commands", "POST", - body: { - "name": this.name, - "description": this.description, - "options": options.isNotEmpty ? options : null - }, + body: {"name": this.name, "description": this.description, "options": options.isNotEmpty ? options : null}, ); if (response is HttpResponseError) { return Future.error(response); } - this.isRegistered = true; - return Future.value(this); - } -} \ No newline at end of file + this.isRegistered = true; + return this; + } +} diff --git a/nyxx.interactions/pubspec.yaml b/nyxx.interactions/pubspec.yaml index a2192a0e..16d7ff74 100644 --- a/nyxx.interactions/pubspec.yaml +++ b/nyxx.interactions/pubspec.yaml @@ -1,5 +1,5 @@ name: nyxx_interactions -version: 1.1.0-dev.1 +version: 1.1.0-dev.2 description: Interactions for Nyxx library homepage: https://github.com/l7ssha/nyxx repository: https://github.com/l7ssha/nyxx @@ -10,8 +10,8 @@ environment: sdk: '>=2.12.0-51.0.dev <3.0.0' dependencies: - nyxx: "^1.1.0-dev.1" logging: "^1.0.0-nullsafety.0" + nyxx: "^1.1.0-dev.2" dependency_overrides: http: diff --git a/nyxx/pubspec.yaml b/nyxx/pubspec.yaml index b2adca5f..75410486 100644 --- a/nyxx/pubspec.yaml +++ b/nyxx/pubspec.yaml @@ -1,5 +1,5 @@ name: nyxx -version: 1.1.0-dev.1 +version: 1.1.0-dev.2 description: A Discord library for Dart. homepage: https://github.com/l7ssha/nyxx repository: https://github.com/l7ssha/nyxx