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

Release 4.3.0 #56

Merged
merged 17 commits into from
Jul 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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