Skip to content
This repository has been archived by the owner on Nov 1, 2023. It is now read-only.

Commit

Permalink
Release 4.3.0 (#56)
Browse files Browse the repository at this point in the history
* feature: Locales (#48)

* Locales

* Fix and add deprecated notice

* Fix template

* Format

* cov: unit tests

* Add enum locale, bump to Dart 2.17

* fix: Links in pubspec

* fix: Query param

* fix: `Endpoints@fetchGuildCommand()`

* suggestions: `withLocales` to `true`

* fix: queryParams

* fix: typos

* fix: Remove `withLocales` param

Not used by discord, I'm just dumb

* feat: Add deserialize onto `Locale`

* fix: Deserialize `RawApiMap` into full `Locale`

+ add unit tests for localizations.

* feat: Add localizations on receivied options.

* Differentiate command handlers per guild

* Fixup workflows

* Format code

* Fix interaction acknowledgement 3 second timeout

acknowledged interaction tokens were expiring in 3 seconds rather than the intended 15 minutes

* Fix typo

* Revert #37

* Remove trailing whitespace in docs

* Fix crash in Locale

* Fix unit tests

* Update to nyxx 4.0.0 (#54)

* Release 4.3.0 (#55)

Co-authored-by: Rapougnac <74512338+Rapougnac@users.noreply.github.com>
Co-authored-by: Abitofevrything <mylo.fawcett@gmail.com>
Co-authored-by: Nicholas Shrefler <16249086+NDSo@users.noreply.github.com>
Co-authored-by: Abitofevrything <54505189+abitofevrything@users.noreply.github.com>
  • Loading branch information
5 people authored Jul 29, 2022
1 parent 4c21707 commit 661982c
Show file tree
Hide file tree
Showing 20 changed files with 497 additions and 152 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/unit_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ jobs:
run: dart run test --coverage="coverage" test/unit/**

- name: Format coverage
run: dart run coverage:format_coverage --lcov --in=coverage --out=coverage/coverage.lcov --packages=.packages --report-on=lib
run: dart run coverage:format_coverage --lcov --in=coverage --out=coverage/coverage.lcov --packages=.dart_tool/package_config.json --report-on=lib

- name: Generate coverage
run: genhtml coverage/coverage.lcov -o coverage/coverage_gen
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## 4.3.0
__29.07.2022__

- feature: Update to nyxx 4.0.0 (#54)
- feature: Locales (#48)
- feature: Differentiate command handlers per guild

## 4.2.1
__02.05.2022__

Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ unit-tests: ## Run unit tests with coverage

.PHONY: coverage-format
coverage-format: ## Format dart coverage output to lcov
dart run coverage:format_coverage --lcov --in=coverage --out=coverage/coverage.lcov --packages=.packages --report-on=lib
dart run coverage:format_coverage --lcov --in=coverage --out=coverage/coverage.lcov --packages=.dart_tool/package_config.json --report-on=lib

.PHONY: coverage-gen-html
coverage-gen-html: ## Generate html coverage from lcov data
Expand Down
8 changes: 2 additions & 6 deletions example/buttons_and_dropdowns.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,7 @@ Future<void> buttonHandler(IButtonInteractionEvent event) async {
await event.acknowledge(); // ack the interaction so we can send response later

// Send followup to button click with id of button
await event.sendFollowup(MessageBuilder.content(
"Button pressed with id: ${event.interaction.customId}")
);
await event.sendFollowup(MessageBuilder.content("Button pressed with id: ${event.interaction.customId}"));
}

// Handling multiselect events is no different from handling button.
Expand All @@ -55,9 +53,7 @@ Future<void> multiselectHandlerHandler(IMultiselectInteractionEvent event) async
await event.acknowledge(); // ack the interaction so we can send response later

// Send followup to button click with id of button
await event.sendFollowup(MessageBuilder.content(
"Option chosen with values: ${event.interaction.values}")
);
await event.sendFollowup(MessageBuilder.content("Option chosen with values: ${event.interaction.values}"));
}

void main() {
Expand Down
11 changes: 7 additions & 4 deletions example/example.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,13 @@ void main() {
..connect();

IInteractions.create(WebsocketInteractionBackend(bot))
..registerSlashCommand(
SlashCommandBuilder("itest", "This is test command", [
..registerSlashCommand(SlashCommandBuilder(
"itest",
"This is test command",
[
CommandOptionBuilder(CommandOptionType.subCommand, "subtest", "This is sub test")
..registerHandler((event) => event.respond(MessageBuilder.content("This is example command")))
], guild: 302360552993456135.toSnowflake())
)..syncOnReady();
],
guild: 302360552993456135.toSnowflake()))
..syncOnReady();
}
3 changes: 2 additions & 1 deletion lib/nyxx_interactions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export 'src/internal/sync/commands_sync.dart' show ICommandsSync;
export 'src/internal/sync/lock_file_command_sync.dart' show LockFileCommandSync;
export 'src/internal/sync/manual_command_sync.dart' show ManualCommandSync;
export 'src/internal/event_controller.dart' show IEventController;
export 'src/internal/interaction_endpoints.dart' show IInteractionsEndpoints;
export 'src/internal/interaction_endpoints.dart' show IInteractionsEndpoints, InteractionRouteParts;
export 'src/internal/utils.dart' show slashCommandNameRegex;
export 'src/models/arg_choice.dart' show IArgChoice;
export 'src/models/command_option.dart' show ICommandOption, CommandOptionType;
Expand All @@ -46,6 +46,7 @@ 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;
export 'src/models/slash_command_type.dart' show SlashCommandType;
export 'src/models/locale.dart' show Locale;

export 'src/interactions.dart' show IInteractions;
export 'src/typedefs.dart' show AutocompleteInteractionHandler, ButtonInteractionHandler, MultiselectInteractionHandler, SlashCommandHandler;
Expand Down
18 changes: 16 additions & 2 deletions lib/src/builders/command_option_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import 'package:nyxx/nyxx.dart';

import 'package:nyxx_interactions/src/builders/arg_choice_builder.dart';
import 'package:nyxx_interactions/src/models/command_option.dart';
import 'package:nyxx_interactions/src/models/locale.dart';
import 'package:nyxx_interactions/src/typedefs.dart';
import 'package:nyxx_interactions/src/builders/slash_command_builder.dart';

/// An argument for a [SlashCommandBuilder].
class CommandOptionBuilder extends Builder {
Expand All @@ -22,16 +24,24 @@ class CommandOptionBuilder extends Builder {
/// The name of your argument / sub-group.
final String name;

/// The localizations name of your argument / sub-group.
/// See [SlashCommandBuilder.localizationsName] for more information.
final Map<Locale, String>? localizationsName;

/// The description of your argument / sub-group.
final String description;

/// If this should be the fist required option the user picks
/// The localizations description of your argument / sub-group.
/// See [SlashCommandBuilder.localizationsDescription] for more information.
final Map<Locale, String>? localizationsDescription;

/// If this should be the first required option the user picks
bool defaultArg = false;

/// If this argument is required
bool required = false;

/// Choices for [CommandOptionType.string] and [CommandOptionType.string] types for the user to pick from
/// Choices for [CommandOptionType.string], [CommandOptionType.integer] and [CommandOptionType.number] types for the user to pick from
List<ArgChoiceBuilder>? choices;

/// If the option is a subcommand or subcommand group type, this nested options will be the parameters
Expand Down Expand Up @@ -66,6 +76,8 @@ class CommandOptionBuilder extends Builder {
this.autoComplete = false,
this.min,
this.max,
this.localizationsName,
this.localizationsDescription,
});

/// Registers handler for subcommand
Expand Down Expand Up @@ -98,6 +110,8 @@ class CommandOptionBuilder extends Builder {
if (channelTypes != null && type == CommandOptionType.channel) "channel_types": channelTypes!.map((e) => e.value).toList(),
if (min != null) "min_value": min,
if (max != null) "max_value": max,
if (localizationsName != null) "name_localizations": localizationsName!.map((k, v) => MapEntry<String, String>(k.toString(), v)),
if (localizationsDescription != null) "description_localizations": localizationsDescription!.map((k, v) => MapEntry<String, String>(k.toString(), v)),
"autocomplete": autoComplete,
};
}
6 changes: 3 additions & 3 deletions lib/src/builders/command_permission_builder.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import 'package:nyxx/nyxx.dart';

/// Used to define permissions for a particular command.
@Deprecated('Use SlashCommandBuilder.canBeUsedInDm and SlashCommandBuilder.requiresPermissions instead')
@Deprecated('Use SlashCommandBuilder.canBeUsedInDm and SlashCommandBuilder.requiredPermissions instead')
abstract class CommandPermissionBuilderAbstract extends Builder {
int get type;

Expand All @@ -21,7 +21,7 @@ abstract class CommandPermissionBuilderAbstract extends Builder {
}

/// A permission for a single role that can be used in [SlashCommandBuilder]
@Deprecated('Use SlashCommandBuilder.canBeUsedInDm and SlashCommandBuilder.requiresPermissions instead')
@Deprecated('Use SlashCommandBuilder.canBeUsedInDm and SlashCommandBuilder.requiredPermissions instead')
class RoleCommandPermissionBuilder extends CommandPermissionBuilderAbstract {
@override
late final int type = 1;
Expand All @@ -34,7 +34,7 @@ class RoleCommandPermissionBuilder extends CommandPermissionBuilderAbstract {
}

/// A permission for a single user that can be used in [SlashCommandBuilder]
@Deprecated('Use SlashCommandBuilder.canBeUsedInDm and SlashCommandBuilder.requiresPermissions instead')
@Deprecated('Use SlashCommandBuilder.canBeUsedInDm and SlashCommandBuilder.requiredPermissions instead')
class UserCommandPermissionBuilder extends CommandPermissionBuilderAbstract {
@override
late final int type = 2;
Expand Down
54 changes: 46 additions & 8 deletions lib/src/builders/slash_command_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,60 @@ import 'package:nyxx/nyxx.dart';

import 'package:nyxx_interactions/src/builders/command_option_builder.dart';
import 'package:nyxx_interactions/src/builders/command_permission_builder.dart';

import 'package:nyxx_interactions/src/models/locale.dart';
import 'package:nyxx_interactions/src/models/slash_command_type.dart';
import 'package:nyxx_interactions/src/models/command_option.dart';
import 'package:nyxx_interactions/src/interactions.dart';
import 'package:nyxx_interactions/src/internal/utils.dart';
import 'package:nyxx_interactions/src/typedefs.dart';

/// A slash command, can only be instantiated through a method on [Interactions]
/// A slash command, can only be instantiated through a method on [IInteractions]
class SlashCommandBuilder extends Builder {
/// The commands ID that is defined on registration and used for permission syncing.
late final Snowflake _id;

/// Command name to be shown to the user in the Slash Command UI
final String name;

/// The command names to be shown to the user in the Slash Command UI by specified locales.
/// See the [available locales](https://discord.com/developers/docs/reference#locales) for a list of available locales.
/// The key is the locale and the value is the name of the command in that locale.
/// Values follow the same constraints as [name] (`^[\w-]{1,32}$`).
///
/// An example:
/// {@template slashcommand.builder.example}
/// ```dart
/// final scb = SlashCommandBuilder(
/// 'hello',
/// 'Hello World!',
/// [],
/// localizationsName: {
/// Locale.french: 'salut',
/// Locale.german: 'hallo',
/// },
/// localizationsDescription: {
/// Locale.french: 'Salut le monde !',
/// Locale.german: 'Hallo Welt!',
/// },
/// );
/// ```
/// {@endtemplate}
final Map<Locale, String>? localizationsName;

/// Command description shown to the user in the Slash Command UI
final String? description;

/// The command descriptions to be shown to the user in the Slash Command UI by specified locales.
/// See the [available locales](https://discord.com/developers/docs/reference#locales) for a list of available locales.
/// The key is the locale and the value is the description of the command in that locale.
/// Values follow the same constraints as [description].
///
/// An example:
/// {@macro slashcommand.builder.example}
final Map<Locale, String>? localizationsDescription;

/// If people can use the command by default or if they need permissions to use it.
@Deprecated('Use canBeUsedInDm and requiresPermissions instead')
@Deprecated('Use canBeUsedInDm and requiredPermissions instead')
final bool defaultPermissions;

/// The guild that the slash Command is registered in. This can be null if its a global command.
Expand All @@ -31,7 +65,7 @@ class SlashCommandBuilder extends Builder {
List<CommandOptionBuilder> options;

/// Permission overrides for the command
@Deprecated('Use canBeUsedInDm and requiresPermissions instead')
@Deprecated('Use canBeUsedInDm and requiredPermissions instead')
List<CommandPermissionBuilderAbstract>? permissions;

/// Target of slash command if different that SlashCommandTarget.chat - slash command will
Expand All @@ -50,7 +84,7 @@ class SlashCommandBuilder extends Builder {
/// operator, they will be allowed to execute the command.
int? requiredPermissions;

/// A slash command, can only be instantiated through a method on [Interactions]
/// A slash command, can only be instantiated through a method on [IInteractions]
SlashCommandBuilder(
this.name,
this.description,
Expand All @@ -59,8 +93,10 @@ class SlashCommandBuilder extends Builder {
this.requiredPermissions,
this.guild,
this.type = SlashCommandType.chat,
this.defaultPermissions = true,
this.permissions,
@Deprecated('Use canBeUsedInDm and requiredPermissions instead') this.defaultPermissions = true,
@Deprecated('Use canBeUsedInDm and requiredPermissions instead') this.permissions,
this.localizationsName,
this.localizationsDescription,
}) {
if (!slashCommandNameRegex.hasMatch(name)) {
throw ArgumentError("Command name has to match regex: ${slashCommandNameRegex.pattern}");
Expand All @@ -83,6 +119,8 @@ class SlashCommandBuilder extends Builder {
"type": type.value,
"dm_permission": canBeUsedInDm,
if (requiredPermissions != null) "default_member_permissions": requiredPermissions.toString(),
if (localizationsName != null) "name_localizations": localizationsName!.map((k, v) => MapEntry<String, String>(k.toString(), v)),
if (localizationsDescription != null) "description_localizations": localizationsDescription!.map((k, v) => MapEntry<String, String>(k.toString(), v)),
"default_permission": defaultPermissions,
};

Expand All @@ -91,7 +129,7 @@ class SlashCommandBuilder extends Builder {
Snowflake get id => _id;

/// Register a permission
@Deprecated('Use canBeUsedInDm and requiresPermissions instead')
@Deprecated('Use canBeUsedInDm and requiredPermissions instead')
void addPermission(CommandPermissionBuilderAbstract permission) {
permissions ??= [];

Expand Down
10 changes: 5 additions & 5 deletions lib/src/events/interaction_event.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import 'package:nyxx_interactions/src/exceptions/already_responded.dart';
import 'package:nyxx/nyxx.dart';

abstract class IInteractionEvent<T extends IInteraction> {
/// Reference to [Nyxx]
/// Reference to [INyxx]
INyxx get client;

/// Reference to [Interactions]
Expand All @@ -27,7 +27,7 @@ abstract class IInteractionEvent<T extends IInteraction> {
}

abstract class InteractionEventAbstract<T extends IInteraction> implements IInteractionEvent<T> {
/// Reference to [Nyxx]
/// Reference to [INyxx]
@override
INyxx get client => interactions.client;

Expand All @@ -41,7 +41,7 @@ abstract class InteractionEventAbstract<T extends IInteraction> implements IInte

/// The DateTime the interaction was received by the Nyxx Client.
@override
DateTime get receivedAt => interaction.id.timestamp;
final DateTime receivedAt = DateTime.now();

final Logger logger = Logger("Interaction Event");

Expand Down Expand Up @@ -204,7 +204,7 @@ abstract class InteractionEventWithAcknowledge<T extends IInteraction> extends I
final now = DateTime.now();
if (_hasAcked && now.isAfter(receivedAt.add(const Duration(minutes: 15)))) {
return Future.error(InteractionExpiredError.fifteenMins());
} else if (now.isAfter(receivedAt.add(const Duration(seconds: 3)))) {
} else if (!_hasAcked && now.isAfter(receivedAt.add(const Duration(seconds: 3)))) {
return Future.error(InteractionExpiredError.threeSecs());
}

Expand All @@ -222,7 +222,7 @@ abstract class InteractionEventWithAcknowledge<T extends IInteraction> extends I
_hasAcked = true;
}

/// Returns [Message] object of original interaction response
/// Returns [IMessage] object of original interaction response
@override
Future<IMessage> getOriginalResponse() async =>
interactions.interactionsEndpoints.fetchOriginalResponse(interaction.token, client.appId, interaction.id.toString());
Expand Down
18 changes: 11 additions & 7 deletions lib/src/interactions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -66,15 +66,15 @@ abstract class IInteractions {
Future<void> deleteGuildCommands(List<Snowflake> guildIds);

/// Fetches all global bots command
Stream<ISlashCommand> fetchGlobalCommands();
Stream<ISlashCommand> fetchGlobalCommands({bool withLocales = true});

/// Fetches all guild commands for given guild
Stream<ISlashCommand> fetchGuildCommands(Snowflake guildId);
Stream<ISlashCommand> fetchGuildCommands(Snowflake guildId, {bool withLocales = true});

/// Returns the global overrides for commands in a guild.
Cacheable<Snowflake, ISlashCommandPermissionOverrides> getGlobalOverridesInGuild(Snowflake guildId);

static IInteractions create(InteractionBackend backend) => Interactions(backend);
factory IInteractions.create(InteractionBackend backend) => Interactions(backend);
}

/// Interaction extension for Nyxx. Allows use of: Slash Commands.
Expand Down Expand Up @@ -207,7 +207,7 @@ class Interactions implements IInteractions {

if (entry.value.any((element) => element.permissions?.isNotEmpty ?? false)) {
_logger.warning(
'Using deprecated permissions endpoint. To fix, use SlashCommandBuilder.canBeUsedInDm and SlashCommandBuilder.requiresPermissions'
'Using deprecated permissions endpoint. To fix, use SlashCommandBuilder.canBeUsedInDm and SlashCommandBuilder.requiredPermissions'
' instead of SlashCommandBuilder.permissions',
);
await interactionsEndpoints.bulkOverrideGuildCommandsPermissions(client.appId, entry.key, entry.value);
Expand Down Expand Up @@ -309,11 +309,12 @@ class Interactions implements IInteractions {

/// Fetches all global bots command
@override
Stream<ISlashCommand> fetchGlobalCommands() => interactionsEndpoints.fetchGlobalCommands(client.appId);
Stream<ISlashCommand> fetchGlobalCommands({bool withLocales = true}) => interactionsEndpoints.fetchGlobalCommands(client.appId, withLocales: withLocales);

/// Fetches all guild commands for given guild
@override
Stream<ISlashCommand> fetchGuildCommands(Snowflake guildId) => interactionsEndpoints.fetchGuildCommands(client.appId, guildId);
Stream<ISlashCommand> fetchGuildCommands(Snowflake guildId, {bool withLocales = true}) =>
interactionsEndpoints.fetchGuildCommands(client.appId, guildId, withLocales: withLocales);

@override
Cacheable<Snowflake, ISlashCommandPermissionOverrides> getGlobalOverridesInGuild(Snowflake guildId) =>
Expand All @@ -336,7 +337,10 @@ class Interactions implements IInteractions {
}

void _assignCommandToHandler(SlashCommandBuilder builder) {
final commandHashPrefix = builder.name;
String commandHashPrefix = builder.name;
if (builder.guild != null) {
commandHashPrefix = '${builder.guild}/$commandHashPrefix';
}

var allowRootHandler = true;

Expand Down
Loading

0 comments on commit 661982c

Please sign in to comment.