From bf68ed555fc7019dc17ef523c7998a1385351f47 Mon Sep 17 00:00:00 2001 From: Abitofevrything Date: Fri, 25 Nov 2022 20:14:32 +0100 Subject: [PATCH 1/6] Handle HTTP error response in acknowledge() --- lib/src/events/interaction_event.dart | 12 +++++++++++- lib/src/exceptions/already_responded.dart | 5 +---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/lib/src/events/interaction_event.dart b/lib/src/events/interaction_event.dart index 75a6c72..a3e72b7 100644 --- a/lib/src/events/interaction_event.dart +++ b/lib/src/events/interaction_event.dart @@ -190,7 +190,17 @@ abstract class InteractionEventWithAcknowledge extends I return Future.error(InteractionExpiredError.threeSecs()); } - await interactions.interactionsEndpoints.acknowledge(interaction.token, interaction.id.toString(), hidden, _acknowledgeOpCode); + try { + await interactions.interactionsEndpoints.acknowledge(interaction.token, interaction.id.toString(), hidden, _acknowledgeOpCode); + } on IHttpResponseError catch (response) { + // 40060 - Interaction has already been acknowledged + // Catch in case of a desync between server and _hasAcked + if (response.code == 40060) { + throw AlreadyRespondedError(); + } + + rethrow; + } logger.fine("Sending acknowledge for for interaction: ${interaction.id}"); diff --git a/lib/src/exceptions/already_responded.dart b/lib/src/exceptions/already_responded.dart index 58edec6..b99007f 100644 --- a/lib/src/exceptions/already_responded.dart +++ b/lib/src/exceptions/already_responded.dart @@ -1,9 +1,6 @@ /// Thrown when you have already responded to an interaction -class AlreadyRespondedError implements Error { +class AlreadyRespondedError extends Error { /// Returns a string representation of this object. @override String toString() => "AlreadyRespondedError: Interaction has already been acknowledged, you can now only send channel messages (with/without source)"; - - @override - StackTrace? get stackTrace => StackTrace.empty; } From 927e5589e8ba5e036a71438717bd394c0b872b06 Mon Sep 17 00:00:00 2001 From: Abitofevrything <54505189+abitofevrything@users.noreply.github.com> Date: Fri, 25 Nov 2022 22:59:09 +0100 Subject: [PATCH 2/6] Update nyxx_interactions to work with the new logging system in nyxx (#69) * Use code field instead of decoding message to find error code * Prefer throw over Future.error * Remove unused catch block --- lib/src/events/interaction_event.dart | 12 +- lib/src/internal/interaction_endpoints.dart | 290 ++++++++------------ 2 files changed, 118 insertions(+), 184 deletions(-) diff --git a/lib/src/events/interaction_event.dart b/lib/src/events/interaction_event.dart index a3e72b7..d29c29c 100644 --- a/lib/src/events/interaction_event.dart +++ b/lib/src/events/interaction_event.dart @@ -152,7 +152,7 @@ abstract class InteractionEventWithAcknowledge extends I @override Future sendFollowup(MessageBuilder builder, {bool hidden = false}) async { if (!_hasAcked) { - return Future.error(ResponseRequiredError()); + throw ResponseRequiredError(); } logger.fine("Sending followup for interaction: ${interaction.id}"); @@ -183,11 +183,11 @@ abstract class InteractionEventWithAcknowledge extends I @override Future acknowledge({bool hidden = false}) async { if (_hasAcked) { - return Future.error(AlreadyRespondedError()); + throw AlreadyRespondedError(); } if (DateTime.now().isAfter(receivedAt.add(const Duration(seconds: 3)))) { - return Future.error(InteractionExpiredError.threeSecs()); + throw InteractionExpiredError.threeSecs(); } try { @@ -213,9 +213,9 @@ abstract class InteractionEventWithAcknowledge extends I Future respond(MessageBuilder builder, {bool hidden = false}) async { final now = DateTime.now(); if (_hasAcked && now.isAfter(receivedAt.add(const Duration(minutes: 15)))) { - return Future.error(InteractionExpiredError.fifteenMins()); + throw InteractionExpiredError.fifteenMins(); } else if (!_hasAcked && now.isAfter(receivedAt.add(const Duration(seconds: 3)))) { - return Future.error(InteractionExpiredError.threeSecs()); + throw InteractionExpiredError.threeSecs(); } logger.fine("Sending respond for for interaction: ${interaction.id}"); @@ -223,7 +223,7 @@ abstract class InteractionEventWithAcknowledge extends I await interactions.interactionsEndpoints.respondEditOriginal(interaction.token, client.appId, builder, hidden); } else { if (!builder.canBeUsedAsNewMessage()) { - return Future.error(ArgumentError("Cannot sent message when MessageBuilder doesn't have set either content, embed or files")); + throw ArgumentError("Cannot sent message when MessageBuilder doesn't have set either content, embed or files"); } await interactions.interactionsEndpoints.respondCreateResponse(interaction.token, interaction.id.toString(), builder, hidden, _respondOpcode); diff --git a/lib/src/internal/interaction_endpoints.dart b/lib/src/internal/interaction_endpoints.dart index 694e7a6..c1de37a 100644 --- a/lib/src/internal/interaction_endpoints.dart +++ b/lib/src/internal/interaction_endpoints.dart @@ -113,28 +113,47 @@ class InteractionsEndpoints implements IInteractionsEndpoints { InteractionsEndpoints(this._client, this._interactions); - @override - Future acknowledge(String token, String interactionId, bool hidden, int opCode) async { + // TODO: Make this public in nyxx to avoid duplication? + Future executeSafe( + IHttpRoute route, + String method, { + dynamic body, + bool auth = false, + List files = const [], + Map? queryParams, + }) async { final response = await _client.httpEndpoints.sendRawRequest( - IHttpRoute() - ..interactions(id: interactionId, token: token) - ..callback(), - "POST", - body: { - "type": opCode, - "data": { - if (hidden) "flags": 1 << 6, - } - }, + route, + method, + body: body, + auth: auth, + files: files, + queryParams: queryParams, ); - if (response is IHttpResponseError) { - return Future.error(response); + if (response is! IHttpResponseSuccess) { + return Future.error(response, StackTrace.current); } + + return response; } @override - Future deleteFollowup(String token, Snowflake applicationId, Snowflake messageId) => _client.httpEndpoints.sendRawRequest( + Future acknowledge(String token, String interactionId, bool hidden, int opCode) => executeSafe( + IHttpRoute() + ..interactions(id: interactionId, token: token) + ..callback(), + "POST", + body: { + "type": opCode, + "data": { + if (hidden) "flags": 1 << 6, + } + }, + ); + + @override + Future deleteFollowup(String token, Snowflake applicationId, Snowflake messageId) => executeSafe( IHttpRoute() ..webhooks(id: applicationId.id.toString(), token: token) ..messages(id: messageId.id.toString()), @@ -142,24 +161,18 @@ class InteractionsEndpoints implements IInteractionsEndpoints { ); @override - Future deleteOriginalResponse(String token, Snowflake applicationId, String interactionId) async { - final response = await _client.httpEndpoints.sendRawRequest( - IHttpRoute() - ..webhooks(id: applicationId.id.toString(), token: token) - ..messages(id: '@original'), - "DELETE", - ); - - if (response is IHttpResponseError) { - return Future.error(response); - } - } + Future deleteOriginalResponse(String token, Snowflake applicationId, String interactionId) => executeSafe( + IHttpRoute() + ..webhooks(id: applicationId.id.toString(), token: token) + ..messages(id: '@original'), + "DELETE", + ); @override Future editFollowup(String token, Snowflake applicationId, Snowflake messageId, MessageBuilder builder) async { final body = builder.build(_client.options.allowedMentions); - final response = await _client.httpEndpoints.sendRawRequest( + final response = await executeSafe( IHttpRoute() ..webhooks(id: applicationId.id.toString(), token: token) ..messages(id: messageId.id.toString()), @@ -167,16 +180,12 @@ class InteractionsEndpoints implements IInteractionsEndpoints { body: body, ); - if (response is IHttpResponseError) { - return Future.error(response); - } - - return Message(_client, (response as IHttpResponseSuccess).jsonBody as RawApiMap); + return Message(_client, response.jsonBody as RawApiMap); } @override Future editOriginalResponse(String token, Snowflake applicationId, MessageBuilder builder) async { - final response = await _client.httpEndpoints.sendRawRequest( + final response = await executeSafe( IHttpRoute() ..webhooks(id: applicationId.id.toString(), token: token) ..messages(id: '@original'), @@ -184,70 +193,50 @@ class InteractionsEndpoints implements IInteractionsEndpoints { body: builder.build(_client.options.allowedMentions), ); - if (response is IHttpResponseError) { - return Future.error(response); - } - - return Message(_client, (response as IHttpResponseSuccess).jsonBody as RawApiMap); + return Message(_client, response.jsonBody as RawApiMap); } @override Future fetchOriginalResponse(String token, Snowflake applicationId, String interactionId) async { - final response = await _client.httpEndpoints.sendRawRequest( + final response = await executeSafe( IHttpRoute() ..webhooks(id: applicationId.id.toString(), token: token) ..messages(id: '@original'), "GET", ); - if (response is IHttpResponseError) { - return Future.error(response); - } - - return Message(_client, (response as IHttpResponseSuccess).jsonBody as RawApiMap); + return Message(_client, response.jsonBody as RawApiMap); } @override - Future respondEditOriginal(String token, Snowflake applicationId, MessageBuilder builder, bool hidden) async { - final response = await _client.httpEndpoints.sendRawRequest( - IHttpRoute() - ..webhooks(id: applicationId.id.toString(), token: token) - ..messages(id: '@original'), - "PATCH", - body: {if (hidden) "flags": 1 << 6, ...builder.build(_client.options.allowedMentions)}, - files: builder.files ?? [], - ); - - if (response is IHttpResponseError) { - return Future.error(response); - } - } + Future respondEditOriginal(String token, Snowflake applicationId, MessageBuilder builder, bool hidden) => executeSafe( + IHttpRoute() + ..webhooks(id: applicationId.id.toString(), token: token) + ..messages(id: '@original'), + "PATCH", + body: {if (hidden) "flags": 1 << 6, ...builder.build(_client.options.allowedMentions)}, + files: builder.files ?? [], + ); @override - Future respondCreateResponse(String token, String interactionId, MessageBuilder builder, bool hidden, int respondOpCode) async { - final response = await _client.httpEndpoints.sendRawRequest( - IHttpRoute() - ..interactions(id: interactionId, token: token) - ..callback(), - "POST", - body: { - "type": respondOpCode, - "data": { - if (hidden) "flags": 1 << 6, - ...builder.build(_client.options.allowedMentions), + Future respondCreateResponse(String token, String interactionId, MessageBuilder builder, bool hidden, int respondOpCode) => executeSafe( + IHttpRoute() + ..interactions(id: interactionId, token: token) + ..callback(), + "POST", + body: { + "type": respondOpCode, + "data": { + if (hidden) "flags": 1 << 6, + ...builder.build(_client.options.allowedMentions), + }, }, - }, - files: builder.files ?? [], - ); - - if (response is IHttpResponseError) { - return Future.error(response); - } - } + files: builder.files ?? [], + ); @override Future sendFollowup(String token, Snowflake applicationId, MessageBuilder builder, {bool hidden = false}) async { - final response = await _client.httpEndpoints.sendRawRequest( + final response = await executeSafe( IHttpRoute()..webhooks(id: applicationId.id.toString(), token: token), "POST", body: { @@ -257,16 +246,12 @@ class InteractionsEndpoints implements IInteractionsEndpoints { files: builder.files ?? [], ); - if (response is IHttpResponseError) { - return Future.error(response); - } - - return Message(_client, (response as IHttpResponseSuccess).jsonBody as RawApiMap); + return Message(_client, response.jsonBody as RawApiMap); } @override Stream bulkOverrideGlobalCommands(Snowflake applicationId, Iterable builders) async* { - final response = await _client.httpEndpoints.sendRawRequest( + final response = await executeSafe( IHttpRoute() ..applications(id: applicationId.id.toString()) ..commands(), @@ -275,18 +260,14 @@ class InteractionsEndpoints implements IInteractionsEndpoints { auth: true, ); - if (response is IHttpResponseError) { - yield* Stream.error(response); - } - - for (final rawRes in (response as IHttpResponseSuccess).jsonBody as List) { + for (final rawRes in response.jsonBody as List) { yield SlashCommand(rawRes as RawApiMap, _interactions); } } @override Stream bulkOverrideGuildCommands(Snowflake applicationId, Snowflake guildId, Iterable builders) async* { - final response = await _client.httpEndpoints.sendRawRequest( + final response = await executeSafe( IHttpRoute() ..applications(id: applicationId.id.toString()) ..guilds(id: guildId.id.toString()) @@ -296,17 +277,13 @@ class InteractionsEndpoints implements IInteractionsEndpoints { auth: true, ); - if (response is IHttpResponseError) { - yield* Stream.error(response); - } - - for (final rawRes in (response as IHttpResponseSuccess).jsonBody as List) { + for (final rawRes in response.jsonBody as List) { yield SlashCommand(rawRes as RawApiMap, _interactions); } } @override - Future deleteGlobalCommand(Snowflake applicationId, Snowflake commandId) => _client.httpEndpoints.sendRawRequest( + Future deleteGlobalCommand(Snowflake applicationId, Snowflake commandId) => executeSafe( IHttpRoute() ..applications(id: applicationId.id.toString()) ..commands(id: commandId.id.toString()), @@ -315,7 +292,7 @@ class InteractionsEndpoints implements IInteractionsEndpoints { ); @override - Future deleteGuildCommand(Snowflake applicationId, Snowflake commandId, Snowflake guildId) => _client.httpEndpoints.sendRawRequest( + Future deleteGuildCommand(Snowflake applicationId, Snowflake commandId, Snowflake guildId) => executeSafe( IHttpRoute() ..applications(id: applicationId.id.toString()) ..guilds(id: guildId.id.toString()) @@ -326,7 +303,7 @@ class InteractionsEndpoints implements IInteractionsEndpoints { @override Future editGlobalCommand(Snowflake applicationId, Snowflake commandId, SlashCommandBuilder builder) async { - final response = await _client.httpEndpoints.sendRawRequest( + final response = await executeSafe( IHttpRoute() ..applications(id: applicationId.id.toString()) ..commands(id: commandId.id.toString()), @@ -335,16 +312,12 @@ class InteractionsEndpoints implements IInteractionsEndpoints { auth: true, ); - if (response is IHttpResponseError) { - return Future.error(response); - } - - return SlashCommand((response as IHttpResponseSuccess).jsonBody as RawApiMap, _interactions); + return SlashCommand(response.jsonBody as RawApiMap, _interactions); } @override Future editGuildCommand(Snowflake applicationId, Snowflake commandId, Snowflake guildId, SlashCommandBuilder builder) async { - final response = await _client.httpEndpoints.sendRawRequest( + final response = await executeSafe( IHttpRoute() ..applications(id: applicationId.id.toString()) ..guilds(id: guildId.id.toString()) @@ -354,16 +327,12 @@ class InteractionsEndpoints implements IInteractionsEndpoints { auth: true, ); - if (response is IHttpResponseError) { - return Future.error(response); - } - - return SlashCommand((response as IHttpResponseSuccess).jsonBody as RawApiMap, _interactions); + return SlashCommand(response.jsonBody as RawApiMap, _interactions); } @override Future fetchGlobalCommand(Snowflake applicationId, Snowflake commandId) async { - final response = await _client.httpEndpoints.sendRawRequest( + final response = await executeSafe( IHttpRoute() ..applications(id: applicationId.id.toString()) ..commands(id: commandId.id.toString()), @@ -371,16 +340,12 @@ class InteractionsEndpoints implements IInteractionsEndpoints { auth: true, ); - if (response is IHttpResponseError) { - return Future.error(response); - } - - return SlashCommand((response as IHttpResponseSuccess).jsonBody as RawApiMap, _interactions); + return SlashCommand(response.jsonBody as RawApiMap, _interactions); } @override Stream fetchGlobalCommands(Snowflake applicationId, {bool withLocales = true}) async* { - final response = await _client.httpEndpoints.sendRawRequest( + final response = await executeSafe( IHttpRoute() ..applications(id: applicationId.id.toString()) ..commands(), @@ -389,18 +354,14 @@ class InteractionsEndpoints implements IInteractionsEndpoints { queryParams: withLocales ? {'with_localizations': withLocales.toString()} : {}, ); - if (response is IHttpResponseError) { - yield* Stream.error(response); - } - - for (final commandSlash in (response as IHttpResponseSuccess).jsonBody as List) { + for (final commandSlash in response.jsonBody as List) { yield SlashCommand(commandSlash as RawApiMap, _interactions); } } @override Future fetchGuildCommand(Snowflake applicationId, Snowflake commandId, Snowflake guildId) async { - final response = await _client.httpEndpoints.sendRawRequest( + final response = await executeSafe( IHttpRoute() ..applications(id: applicationId.id.toString()) ..guilds(id: guildId.id.toString()) @@ -409,16 +370,12 @@ class InteractionsEndpoints implements IInteractionsEndpoints { auth: true, ); - if (response is IHttpResponseError) { - return Future.error(response); - } - - return SlashCommand((response as IHttpResponseSuccess).jsonBody as RawApiMap, _interactions); + return SlashCommand(response.jsonBody as RawApiMap, _interactions); } @override Stream fetchGuildCommands(Snowflake applicationId, Snowflake guildId, {bool withLocales = true}) async* { - final response = await _client.httpEndpoints.sendRawRequest( + final response = await executeSafe( IHttpRoute() ..applications(id: applicationId.id.toString()) ..guilds(id: guildId.id.toString()) @@ -428,11 +385,7 @@ class InteractionsEndpoints implements IInteractionsEndpoints { queryParams: withLocales ? {'with_localizations': withLocales.toString()} : {}, ); - if (response is IHttpResponseError) { - yield* Stream.error(response); - } - - for (final commandSlash in (response as IHttpResponseSuccess).jsonBody as List) { + for (final commandSlash in response.jsonBody as List) { yield SlashCommand(commandSlash as RawApiMap, _interactions); } } @@ -448,7 +401,7 @@ class InteractionsEndpoints implements IInteractionsEndpoints { }) .toList(); - await _client.httpEndpoints.sendRawRequest( + await executeSafe( IHttpRoute() ..applications(id: applicationId.id.toString()) ..commands() @@ -471,7 +424,7 @@ class InteractionsEndpoints implements IInteractionsEndpoints { }) .toList(); - await _client.httpEndpoints.sendRawRequest( + await executeSafe( IHttpRoute() ..applications(id: applicationId.id.toString()) ..guilds(id: guildId.id.toString()) @@ -485,7 +438,7 @@ class InteractionsEndpoints implements IInteractionsEndpoints { @override Future fetchFollowup(String token, Snowflake applicationId, Snowflake messageId) async { - final result = await _client.httpEndpoints.sendRawRequest( + final response = await executeSafe( IHttpRoute() ..webhooks(id: applicationId.id.toString(), token: token) ..messages(id: messageId.id.toString()), @@ -493,16 +446,12 @@ class InteractionsEndpoints implements IInteractionsEndpoints { auth: true, ); - if (result is IHttpResponseError) { - return Future.error(result); - } - - return Message(_client, (result as IHttpResponseSuccess).jsonBody as RawApiMap); + return Message(_client, response.jsonBody as RawApiMap); } @override Future respondToAutocomplete(Snowflake interactionId, String token, List builders) async { - final result = await _client.httpEndpoints.sendRawRequest( + final result = await executeSafe( IHttpRoute() ..interactions(id: interactionId.id.toString(), token: token) ..callback(), @@ -521,27 +470,21 @@ class InteractionsEndpoints implements IInteractionsEndpoints { } @override - Future respondModal(String token, String interactionId, ModalBuilder builder) async { - final response = await _client.httpEndpoints.sendRawRequest( - IHttpRoute() - ..interactions(id: interactionId, token: token) - ..callback(), - "POST", - body: { - "type": 9, - "data": {...builder.build()}, - }, - ); - - if (response is IHttpResponseError) { - return Future.error(response); - } - } + Future respondModal(String token, String interactionId, ModalBuilder builder) => executeSafe( + IHttpRoute() + ..interactions(id: interactionId, token: token) + ..callback(), + "POST", + body: { + "type": 9, + "data": {...builder.build()}, + }, + ); @override Future fetchCommandOverrides(Snowflake commandId, Snowflake guildId) async { try { - final response = await _client.httpEndpoints.sendRawRequest( + final response = await executeSafe( IHttpRoute() ..applications(id: _client.appId.id.toString()) ..guilds(id: guildId.id.toString()) @@ -551,27 +494,22 @@ class InteractionsEndpoints implements IInteractionsEndpoints { auth: true, ); - return SlashCommandPermissionOverrides((response as IHttpResponseSuccess).jsonBody as Map, _client); + return SlashCommandPermissionOverrides(response.jsonBody as Map, _client); } on IHttpResponseError catch (response) { - try { - // 10066 = Unknown application command permissions - // Means there are no overrides for this command... why is this an error, Discord? - if (jsonDecode(response.message)['code'] == 10066) { - _logger.finest('Got error code 10066 on permissions for command $commandId in guild $guildId, returning empty permission overrides.'); - return SlashCommandPermissionOverrides.empty(commandId, _client); - } - } on Exception { - // We got invalid JSON. The request is probably invalid, so we ignore it and return an error. - _logger.warning('Invalid JSON in response: ${response.message}'); + // 10066 = Unknown application command permissions + // Means there are no overrides for this command... why is this an error, Discord? + if (response.code == 10066) { + _logger.finest('Got error code 10066 on permissions for command $commandId in guild $guildId, returning empty permission overrides.'); + return SlashCommandPermissionOverrides.empty(commandId, _client); } - return Future.error(response); + rethrow; } } @override Future> fetchPermissionOverrides(Snowflake guildId) async { - final response = await _client.httpEndpoints.sendRawRequest( + final response = await executeSafe( IHttpRoute() ..applications(id: _client.appId.id.toString()) ..guilds(id: guildId.id.toString()) @@ -581,12 +519,8 @@ class InteractionsEndpoints implements IInteractionsEndpoints { auth: true, ); - if (response is IHttpResponseError) { - return Future.error(response); - } - List overrides = - ((response as IHttpResponseSuccess).jsonBody as List).cast().map((d) => SlashCommandPermissionOverrides(d, _client)).toList(); + (response.jsonBody as List).cast().map((d) => SlashCommandPermissionOverrides(d, _client)).toList(); for (final override in overrides) { _interactions.permissionOverridesCache[guildId] ??= {}; From f4f423f7766e6a70827373be6c5f1a7dcec2240c Mon Sep 17 00:00:00 2001 From: Rapougnac <74512338+Rapougnac@users.noreply.github.com> Date: Fri, 25 Nov 2022 22:59:17 +0100 Subject: [PATCH 3/6] feat: Add support for nsfw commands (#66) * Merge branch 'origin/main' into 'dev' * feat: Add `isNsfw` property * fix: nullable type --- lib/src/builders/slash_command_builder.dart | 5 +++++ lib/src/models/slash_command.dart | 8 ++++++++ test/unit/slash_command_builder_test.dart | 2 ++ 3 files changed, 15 insertions(+) diff --git a/lib/src/builders/slash_command_builder.dart b/lib/src/builders/slash_command_builder.dart index 9c6c5e6..4cd4ccc 100644 --- a/lib/src/builders/slash_command_builder.dart +++ b/lib/src/builders/slash_command_builder.dart @@ -84,6 +84,9 @@ class SlashCommandBuilder extends Builder { /// operator, they will be allowed to execute the command. int? requiredPermissions; + /// Indicates whether the command is age-restricted, defaults to `false`. + bool? isNsfw; + /// A slash command, can only be instantiated through a method on [IInteractions] SlashCommandBuilder( this.name, @@ -97,6 +100,7 @@ class SlashCommandBuilder extends Builder { @Deprecated('Use canBeUsedInDm and requiredPermissions instead') this.permissions, this.localizationsName, this.localizationsDescription, + this.isNsfw, }) { if (!slashCommandNameRegex.hasMatch(name)) { throw ArgumentError("Command name has to match regex: ${slashCommandNameRegex.pattern}"); @@ -122,6 +126,7 @@ class SlashCommandBuilder extends Builder { if (localizationsName != null) "name_localizations": localizationsName!.map((k, v) => MapEntry(k.toString(), v)), if (localizationsDescription != null) "description_localizations": localizationsDescription!.map((k, v) => MapEntry(k.toString(), v)), "default_permission": defaultPermissions, + if (isNsfw != null) 'nsfw': isNsfw, }; void setId(Snowflake id) => _id = id; diff --git a/lib/src/models/slash_command.dart b/lib/src/models/slash_command.dart index 728a458..346ea57 100644 --- a/lib/src/models/slash_command.dart +++ b/lib/src/models/slash_command.dart @@ -47,6 +47,9 @@ abstract class ISlashCommand implements SnowflakeEntity { /// The localized descriptions of the command. Map? get localizationsDescription; + /// Indicates whether the command is age-restricted. + bool get isNsfw; + /// Get the permission overrides for this command in a specific guild. Cacheable getPermissionOverridesInGuild(Snowflake guildId); } @@ -102,6 +105,9 @@ class SlashCommand extends SnowflakeEntity implements ISlashCommand { @override late final Cacheable? permissionOverrides; + @override + late final bool isNsfw; + final Interactions _interactions; /// Creates an instance of [SlashCommand] @@ -126,6 +132,8 @@ class SlashCommand extends SnowflakeEntity implements ISlashCommand { if (raw["options"] != null) for (final optionRaw in raw["options"]) CommandOption(optionRaw as RawApiMap) ]; + + isNsfw = raw['nsfw'] as bool; } @override diff --git a/test/unit/slash_command_builder_test.dart b/test/unit/slash_command_builder_test.dart index c9d7d21..54193fd 100644 --- a/test/unit/slash_command_builder_test.dart +++ b/test/unit/slash_command_builder_test.dart @@ -53,6 +53,7 @@ void main() { Locale.french: 'tester', Locale.german: 'testen', }, + isNsfw: true, ); final expectedResult = { @@ -70,6 +71,7 @@ void main() { 'fr': 'tester', 'de': 'testen', }, + 'nsfw': true, }; expect(slashCommandBuilder.build(), equals(expectedResult)); From 5368f2060627dcbde2fb42d7a65ec8ea81f2a7e8 Mon Sep 17 00:00:00 2001 From: Rapougnac <74512338+Rapougnac@users.noreply.github.com> Date: Sun, 27 Nov 2022 15:42:30 +0100 Subject: [PATCH 4/6] fix slash command model failing with nsfw field (#71) --- test/unit/model_test.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/unit/model_test.dart b/test/unit/model_test.dart index 785805c..aecca2f 100644 --- a/test/unit/model_test.dart +++ b/test/unit/model_test.dart @@ -54,6 +54,7 @@ main() { ], 'default_member_permissions': '123', 'dm_permission': false, + 'nsfw': true, }, interactions); expect(entity.id, equals(Snowflake(123))); @@ -65,6 +66,7 @@ main() { expect(entity.guild, isNull); expect(entity.requiredPermissions, equals(123)); expect(entity.canBeUsedInDm, isFalse); + expect(entity.isNsfw, isTrue); }); test('InteractionOption options not empty', () { From 980e46252fed330c0dd2a60c9f3bf512f70193b5 Mon Sep 17 00:00:00 2001 From: Rapougnac <74512338+Rapougnac@users.noreply.github.com> Date: Fri, 9 Dec 2022 19:01:47 +0100 Subject: [PATCH 5/6] Fix nsfw field not present in payload (#73) --- lib/src/models/slash_command.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/models/slash_command.dart b/lib/src/models/slash_command.dart index 346ea57..9214d22 100644 --- a/lib/src/models/slash_command.dart +++ b/lib/src/models/slash_command.dart @@ -133,7 +133,7 @@ class SlashCommand extends SnowflakeEntity implements ISlashCommand { for (final optionRaw in raw["options"]) CommandOption(optionRaw as RawApiMap) ]; - isNsfw = raw['nsfw'] as bool; + isNsfw = raw['nsfw'] as bool? ?? false; } @override From d0dc776f6cff6b5736ba5ac125640c76e1d888ed Mon Sep 17 00:00:00 2001 From: Szymon Uglis Date: Mon, 12 Dec 2022 20:07:33 +0100 Subject: [PATCH 6/6] Release 4.5.0 --- CHANGELOG.md | 9 +++++++++ pubspec.yaml | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fe515d..a3e9778 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## 4.5.0 +__12.12.022__ + +- feature: Handle HTTP error response in acknowledge() +- feature: Update nyxx_interactions to work with the new logging system in nyxx (#69) +- feature: Add support for nsfw commands (#66) +- bug: fix slash command model failing with nsfw field (#71) +- bug: Fix nsfw field not present in payload (#73) + ## 4.4.0 __14.11.022__ diff --git a/pubspec.yaml b/pubspec.yaml index 015a7b3..3dfebd7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: nyxx_interactions -version: 4.4.0 +version: 4.5.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