Add Dart Docs & Sort final issues.

This commit is contained in:
HarryET 2021-02-10 16:29:42 +00:00 committed by Szymon Uglis
parent 23dfa06ac0
commit 327d14dcb5
No known key found for this signature in database
GPG key ID: 112376C5BEE91FE2
15 changed files with 136 additions and 158 deletions

2
.gitignore vendored
View file

@ -33,4 +33,4 @@ private-*.dart
test-*.dart test-*.dart
[Rr]pc* [Rr]pc*
**/doc/api/** **/doc/api/**
old.* old.*

View file

@ -1,5 +1,5 @@
name: nyxx_extensions name: nyxx_extensions
version: 1.1.0-dev.1 version: 1.1.0-dev.2
description: Extensions for Nyxx library description: Extensions for Nyxx library
homepage: https://github.com/l7ssha/nyxx homepage: https://github.com/l7ssha/nyxx
repository: https://github.com/l7ssha/nyxx repository: https://github.com/l7ssha/nyxx

View file

@ -9,20 +9,20 @@ import "package:nyxx/nyxx.dart";
part "src/Interactions.dart"; part "src/Interactions.dart";
// Models // Models
part 'src/Models/SlashCommand.dart'; part "src/Models/SlashCommand.dart";
part "src/Models/Interaction.dart"; part "src/Models/Interaction.dart";
part "src/Models/InteractionOption.dart"; part "src/Models/InteractionOption.dart";
// Command Args // Command Args
part 'src/Models/CommandArgs/ArgChoice.dart'; part "src/Models/CommandArgs/ArgChoice.dart";
part 'src/Models/CommandArgs/CommandArg.dart'; part "src/Models/CommandArgs/CommandArg.dart";
// Internal // Internal
part 'src/Internal/_EventController.dart'; part "src/Internal/_EventController.dart";
// Events // Events
part "src/Events/InteractionEvent.dart"; part "src/Events/InteractionEvent.dart";
// Exceptions // Exceptions
part "src/Exceptions/InteractionExpired.dart"; part "src/Exceptions/InteractionExpired.dart";
part "src/Exceptions/AlreadyResponded.dart"; part "src/Exceptions/AlreadyResponded.dart";

View file

@ -1,26 +1,29 @@
part of nyxx_interactions; part of nyxx_interactions;
/// The event that you receive when a user types a slash command.
class InteractionEvent { class InteractionEvent {
late final Nyxx _client; late final Nyxx _client;
/// The interaction data, includes the args, name, guild, channel, etc.
late final Interaction interaction; 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; bool hasResponded = false;
InteractionEvent._new(Nyxx client, Map<String, dynamic> rawJson) { InteractionEvent._new(Nyxx client, Map<String, dynamic> rawJson) {
this._client = client; this._client = client;
interaction = Interaction._new(client, rawJson); interaction = Interaction._new(client, rawJson);
if(interaction.type == 1) { if (interaction.type == 1) {
this._pong(); this._pong();
} }
} }
/// Should be sent when you receive a ping from interactions. Used to acknowledge a ping. /// Should be sent when you receive a ping from interactions. Used to acknowledge a ping. Internal to the InteractionEvent.
Future _pong() { Future<void> _pong() async {
if (DateTime.now() if (DateTime.now().isBefore(this.receivedAt.add(const Duration(minutes: 15)))) {
.isBefore(this.recievedAt.add(const Duration(minutes: 15)))) {
if (!hasResponded) { if (!hasResponded) {
final response = this._client.httpEndpoints.sendRawRequest( final response = await this._client.httpEndpoints.sendRawRequest(
"/interactions/${this.interaction.id.toString()}/${this.interaction.token}/callback", "/interactions/${this.interaction.id.toString()}/${this.interaction.token}/callback",
"POST", "POST",
body: { body: {
@ -36,7 +39,6 @@ class InteractionEvent {
if (!hasResponded) { if (!hasResponded) {
hasResponded = true; hasResponded = true;
} }
return Future.value(null);
} else { } else {
return Future.error(InteractionExpired()); 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. /// 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({ Future<void> acknowledge({ bool showSource = false, }) async {
bool showSource = false, if (DateTime.now().isBefore(this.receivedAt.add(const Duration(minutes: 15)))) {
}) {
if (DateTime.now()
.isBefore(this.recievedAt.add(const Duration(minutes: 15)))) {
if (hasResponded) { if (hasResponded) {
return Future.error(AlreadyResponded()); return Future.error(AlreadyResponded());
} }
final url = final url = "/interactions/${this.interaction.id.toString()}/${this.interaction.token}/callback";
"/interactions/${this.interaction.id.toString()}/${this.interaction.token}/callback";
final response = this._client.httpEndpoints.sendRawRequest( final response = await this._client.httpEndpoints.sendRawRequest(
url, url,
"POST", "POST",
body: { body: {
@ -73,29 +71,19 @@ class InteractionEvent {
if (!hasResponded) { if (!hasResponded) {
hasResponded = true; hasResponded = true;
} }
return Future.value(null);
} else { } else {
return Future.error(InteractionExpired()); return Future.error(InteractionExpired());
} }
} }
Future reply({ /// 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.
dynamic content, Future<void> reply({ dynamic content, EmbedBuilder? embed, bool? tts, AllowedMentions? allowedMentions, bool showSource = false, }) async {
EmbedBuilder? embed, if (DateTime.now().isBefore(this.receivedAt.add(const Duration(minutes: 15)))) {
bool? tts,
AllowedMentions? allowedMentions,
bool showSource = false,
}) async {
if (DateTime.now()
.isBefore(this.recievedAt.add(const Duration(minutes: 15)))) {
String url; String url;
if (hasResponded) { if (hasResponded) {
url = url = "/webhooks/${this.interaction.id.toString()}/${this.interaction.token}";
"/webhooks/${this.interaction.id.toString()}/${this.interaction.token}";
} else { } else {
url = url = "/interactions/${this.interaction.id.toString()}/${this.interaction.token}/callback";
"/interactions/${this.interaction.id.toString()}/${this.interaction.token}/callback";
} }
final response = await this._client.httpEndpoints.sendRawRequest( final response = await this._client.httpEndpoints.sendRawRequest(
url, url,
@ -104,10 +92,9 @@ class InteractionEvent {
"type": showSource ? 4 : 3, "type": showSource ? 4 : 3,
"data": { "data": {
"content": content, "content": content,
"embeds": embed != null ? [ BuilderUtility.buildRawEmbed(embed) ] : null, "embeds": embed != null ? [BuilderUtility.buildRawEmbed(embed)] : null,
"allowed_mentions": allowedMentions != null "allowed_mentions":
? BuilderUtility.buildRawAllowedMentions(allowedMentions) allowedMentions != null ? BuilderUtility.buildRawAllowedMentions(allowedMentions) : null,
: null,
"tts": content != null && tts != null && tts "tts": content != null && tts != null && tts
}, },
}, },
@ -117,12 +104,9 @@ class InteractionEvent {
return Future.error(response); return Future.error(response);
} }
print((response as HttpResponseSuccess).body);
if (!hasResponded) { if (!hasResponded) {
hasResponded = true; hasResponded = true;
} }
return Future.value(null);
} else { } else {
return Future.error(InteractionExpired()); return Future.error(InteractionExpired());
} }

View file

@ -1,7 +1,8 @@
part of nyxx_interactions; 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 { class AlreadyResponded implements Error {
/// Returns a string representation of this object. /// Returns a string representation of this object.
@override @override
String toString() => String toString() =>
@ -9,4 +10,4 @@ class AlreadyResponded implements Error {
@override @override
StackTrace? get stackTrace => StackTrace.empty; StackTrace? get stackTrace => StackTrace.empty;
} }

View file

@ -2,6 +2,7 @@ part of nyxx_interactions;
/// Thrown when 15 minutes has passed since an interaction was called. /// Thrown when 15 minutes has passed since an interaction was called.
class InteractionExpired implements Error { class InteractionExpired implements Error {
/// Returns a string representation of this object. /// Returns a string representation of this object.
@override @override
String toString() => String toString() =>
@ -9,4 +10,4 @@ class InteractionExpired implements Error {
@override @override
StackTrace? get stackTrace => StackTrace.empty; StackTrace? get stackTrace => StackTrace.empty;
} }

View file

@ -4,14 +4,14 @@ part of nyxx_interactions;
class Interactions { class Interactions {
late final Nyxx _client; late final Nyxx _client;
final Logger _logger = Logger("Interactions"); final Logger _logger = Logger("Interactions");
final List<SlashCommand> _commands = List.empty(growable: true); final List<SlashCommand> _commands = [];
late final _EventController _events; late final _EventController _events;
/// Emitted when a a slash command is sent. /// Emitted when a slash command is sent.
late Stream<InteractionEvent> onSlashCommand; late final Stream<InteractionEvent> onSlashCommand;
/// Emitted when a a slash command is sent. /// Emitted when a slash command is created by the user.
late Stream<SlashCommand> onSlashCommandCreated; late final Stream<SlashCommand> onSlashCommandCreated;
/// ///
Interactions(Nyxx client) { Interactions(Nyxx client) {
@ -22,12 +22,10 @@ class Interactions {
client.onReady.listen((event) { client.onReady.listen((event) {
client.shardManager.rawEvent.listen((event) { client.shardManager.rawEvent.listen((event) {
print(event.rawData);
if (event.rawData["op"] as int == 0) { if (event.rawData["op"] as int == 0) {
if (event.rawData["t"] as String == "INTERACTION_CREATE") { if (event.rawData["t"] as String == "INTERACTION_CREATE") {
_events.onSlashCommand.add( _events.onSlashCommand.add(
InteractionEvent._new( InteractionEvent._new(client, event.rawData["d"] as Map<String, dynamic>),
client, event.rawData["d"] as Map<String, dynamic>),
); );
} }
} }
@ -35,55 +33,55 @@ class Interactions {
}); });
} }
SlashCommand createCommand(String name, String description, List<CommandArg> args, {String? guild}) /// Creates a command that can be registered using .registerCommand or .registerCommands
=> SlashCommand._new(_client, name, description, args, guild: guild != null ? CacheUtility.createCacheableGuild(_client, Snowflake(guild)) : null); ///
/// 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<CommandArg> args, {Snowflake? guild}) =>
SlashCommand._new(_client, name, description, args,
guild: guild != null ? CacheUtility.createCacheableGuild(_client, guild) : null);
/// Registers a single command. /// Registers a single command.
/// ///
/// @param command A [SlashCommand] to register. /// The command you want to register is the [command] you create a command by using [createCommand]
void registerCommand(SlashCommand command) { void registerCommand(SlashCommand command) => _commands.add(command);
_commands.add(command);
}
/// Registers multiple commands at one. /// Registers multiple commands at one.
/// ///
/// This wraps around [Interactions.registerCommand()] running [Interactions.registerCommand] for each command in the list. /// The commands you want to register is the [commands] you create a command by using [createCommand], this just runs [registerCommand] for each command.
/// @param commands A list of [SlashCommand]s to register. void registerCommands(List<SlashCommand> commands) => commands.forEach(this.registerCommand);
void registerCommands(List<SlashCommand> commands) {
for(var i = 0; i < commands.length; i++) {
this.registerCommand(commands[i]);
}
}
/// Gets all the commands that are currently registered.
List<SlashCommand> getCommands({bool registeredOnly = false}) { List<SlashCommand> getCommands({bool registeredOnly = false}) {
if(!registeredOnly) { return _commands; } if (!registeredOnly) {
return _commands;
}
final registeredCommands = List<SlashCommand>.empty(growable: true); final registeredCommands = List<SlashCommand>.empty(growable: true);
for(var i = 0; i < _commands.length; i++) { for (final el in _commands) {
final el = _commands[i]; if (el.isRegistered) {
if(el.isRegistered) {
registeredCommands.add(el); registeredCommands.add(el);
} }
} }
return registeredCommands; return registeredCommands;
} }
/// Syncs the local commands with the discord API
Future<void> sync() async { Future<void> sync() async {
var success = 0; var success = 0;
var failed = 0; var failed = 0;
for(var i = 0; i < _commands.length; i++) { for (final el in _commands) {
final el = _commands[i]; if (!el.isRegistered) {
if(!el.isRegistered) { await el
final command = await el._register(); ._register()
if(command != null) { .catchError(() {
_commands[i] = command; failed++;
this._events.onSlashCommandCreated.add(_commands[i]); return;
success++; });
} else {
failed++; 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"}.");
} }
} }

View file

@ -20,4 +20,4 @@ class _EventController implements Disposable {
await this.onSlashCommand.close(); await this.onSlashCommand.close();
await this.onSlashCommandCreated.close(); await this.onSlashCommandCreated.close();
} }
} }

View file

@ -1,31 +1,23 @@
part of nyxx_interactions; part of nyxx_interactions;
/// A specified choice for a slash command argument.
class ArgChoice { class ArgChoice {
/// This options name. /// This options name.
late final String name; late final String name;
/// This options int value. If there is an int value there can't be a [SlashArgChoice.stringValue] /// This is the options value, must be int or string
late final int? intValue; late final dynamic value;
/// This options string value. If there is an string value there can't be a [SlashArgChoice.intValue]
late final String? stringValue;
/// A Choice for the user to input in int & string args. You can only have an int or string option. /// 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) { ArgChoice(this.name, dynamic value) {
if(value is String) { if (value is String || value is int) {
this.stringValue = value; 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<String, dynamic> _build() => { Map<String, dynamic> _build() => {"name": this.name, "value": this.value};
"name": this.name,
"value": this.stringValue ?? this.intValue
};
} }

View file

@ -2,14 +2,22 @@ part of nyxx_interactions;
/// The type that a user should input for a [SlashArg] /// The type that a user should input for a [SlashArg]
enum CommandArgType { enum CommandArgType {
SUB_COMMAND, /// Specify an arg as a sub command
SUB_COMMAND_GROUP, subCommand,
STRING, /// Specify an arg as a sub command group
INTEGER, subCommandGroup,
BOOLEAN, /// Specify an arg as a string
USER, string,
CHANNEL, /// Specify an arg as an int
ROLE, 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]. /// An argument for a [SlashCommand].
@ -39,29 +47,25 @@ class CommandArg {
/// If this argument is required /// If this argument is required
late final bool 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<ArgChoice>? choices; late final List<ArgChoice>? choices;
/// If the option is a subcommand or subcommand group type, this nested options will be the parameters /// If the option is a subcommand or subcommand group type, this nested options will be the parameters
late final List<CommandArg>? options; late final List<CommandArg>? 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, CommandArg(this.type, this.name, this.description,
{this.defaultArg = false, {this.defaultArg = false, this.required = false, this.choices, this.options});
this.required = false,
this.choices,
this.options});
Map<String, dynamic> _build() { Map<String, dynamic> _build() {
final subOptions = this.options != null final subOptions = this.options != null
? List<Map<String, dynamic>>.generate( ? this.options!.map((e) => e._build())
this.options!.length, (i) => this.options![i]._build())
: null; : null;
final rawChoices = this.choices != null final rawChoices = this.choices != null
? List<Map<String, dynamic>>.generate( ? this.choices!.map((e) => e._build())
this.choices!.length, (i) => this.choices![i]._build())
: null; : null;
return { return {
"type": (this.type.index) + 1, "type": (this.type.index) + 1,
"name": this.name, "name": this.name,

View file

@ -16,30 +16,27 @@ class Interaction extends SnowflakeEntity implements Disposable {
late final int version; late final int version;
late final String name; // TODO late final String name;
late final Map<String, InteractionOption> args; // TODO late final Map<String, InteractionOption> args;
Interaction._new( Interaction._new(
this.client, this.client,
Map<String, dynamic> raw, Map<String, dynamic> raw,
) : super(Snowflake(raw["id"])) { ) : super(Snowflake(raw["id"])) {
this.type = raw["type"] as int; this.type = raw["type"] as int;
this.guild = CacheUtility.createCacheableGuild( this.guild = CacheUtility.createCacheableGuild(
client, client,
Snowflake( Snowflake(
raw["guild_id"], raw["guild_id"],
), ),
); );
this.channel = CacheUtility.createCacheableTextChannel( this.channel = CacheUtility.createCacheableTextChannel(
client, client,
Snowflake( Snowflake(
raw["channel_id"], raw["channel_id"],
), ),
); );
this.author = EntityUtility.createGuildMember( this.author = EntityUtility.createGuildMember(
client, client,
Snowflake( Snowflake(
@ -47,26 +44,22 @@ class Interaction extends SnowflakeEntity implements Disposable {
), ),
raw["member"] as Map<String, dynamic>, raw["member"] as Map<String, dynamic>,
); );
this.token = raw["token"] as String; this.token = raw["token"] as String;
this.version = raw["version"] as int; this.version = raw["version"] as int;
this.name = raw["data"]["name"] as String; this.name = raw["data"]["name"] as String;
this.args = _generateArgs(raw["data"] as Map<String, dynamic>); this.args = _generateArgs(raw["data"] as Map<String, dynamic>);
} }
Map<String, InteractionOption> _generateArgs(Map<String, dynamic> rawData) { Map<String, InteractionOption> _generateArgs(Map<String, dynamic> rawData) {
final args = <String, InteractionOption>{}; final args = <String, InteractionOption>{};
if(rawData["options"] != null) { if (rawData["options"] != null) {
final l = rawData["options"] as List; 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]; final el = l[i];
args[el["name"] as String] = InteractionOption._new( args[el["name"] as String] = InteractionOption._new(
el["value"] as dynamic, el["value"] as dynamic,
(el["options"] ?? List<dynamic>.empty() ) as List, (el["options"] ?? List<dynamic>.empty()) as List,
); );
} }
} }
@ -76,4 +69,4 @@ class Interaction extends SnowflakeEntity implements Disposable {
@override @override
Future<void> dispose() => Future.value(null); Future<void> dispose() => Future.value(null);
} }

View file

@ -1,14 +1,18 @@
part of nyxx_interactions; part of nyxx_interactions;
/// The option given by the user when sending a command
class InteractionOption { class InteractionOption {
/// The value given by the user
final dynamic? value; final dynamic? value;
final Map<String, InteractionOption> args = <String, InteractionOption>{};
/// Any args under this as you can have sub commands
final Map<String, InteractionOption> args = {};
InteractionOption._new(this.value, List rawOptions) { InteractionOption._new(this.value, List rawOptions) {
for(var i = 0; i < rawOptions.length; i++) { for (final el in rawOptions) {
final el = rawOptions[i];
this.args[el["name"] as String] = InteractionOption._new( this.args[el["name"] as String] = InteractionOption._new(
el["value"] as dynamic, el["value"] as dynamic,
(el["options"] ?? List<dynamic>.empty() ) as List, (el["options"] ?? List<dynamic>.empty()) as List,
); );
} }
} }

View file

@ -1,12 +1,18 @@
part of nyxx_interactions; part of nyxx_interactions;
/// A slash command, can only be instantiated through a method on [Interactions]
class SlashCommand { class SlashCommand {
/// Command name to be shown to the user in the Slash Command UI
late final String name; late final String name;
/// Command description shown to the user in the Slash Command UI
late final String description; 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<Snowflake, Guild>? guild; late final Cacheable<Snowflake, Guild>? guild;
/// The arguments that the command takes
late final List<CommandArg> args; late final List<CommandArg> args;
/// If the command is a global on, false if restricted to a guild.
late final bool isGlobal; late final bool isGlobal;
/// If the command has been registered with the discord api
late bool isRegistered = false; late bool isRegistered = false;
late final Nyxx _client; late final Nyxx _client;
@ -16,24 +22,19 @@ class SlashCommand {
} }
Future<SlashCommand> _register() async { Future<SlashCommand> _register() async {
final options = List<Map<String, dynamic>>.generate( final options = args.map((e) => e._build());
this.args.length, (i) => this.args[i]._build());
final response = await this._client.httpEndpoints.sendRawRequest( final response = await this._client.httpEndpoints.sendRawRequest(
"/applications/${this._client.app.id.toString()}/commands", "/applications/${this._client.app.id.toString()}/commands",
"POST", "POST",
body: { body: {"name": this.name, "description": this.description, "options": options.isNotEmpty ? options : null},
"name": this.name,
"description": this.description,
"options": options.isNotEmpty ? options : null
},
); );
if (response is HttpResponseError) { if (response is HttpResponseError) {
return Future.error(response); return Future.error(response);
} }
this.isRegistered = true;
return Future.value(this);
}
} this.isRegistered = true;
return this;
}
}

View file

@ -1,5 +1,5 @@
name: nyxx_interactions name: nyxx_interactions
version: 1.1.0-dev.1 version: 1.1.0-dev.2
description: Interactions for Nyxx library description: Interactions for Nyxx library
homepage: https://github.com/l7ssha/nyxx homepage: https://github.com/l7ssha/nyxx
repository: 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' sdk: '>=2.12.0-51.0.dev <3.0.0'
dependencies: dependencies:
nyxx: "^1.1.0-dev.1"
logging: "^1.0.0-nullsafety.0" logging: "^1.0.0-nullsafety.0"
nyxx: "^1.1.0-dev.2"
dependency_overrides: dependency_overrides:
http: http:

View file

@ -1,5 +1,5 @@
name: nyxx name: nyxx
version: 1.1.0-dev.1 version: 1.1.0-dev.2
description: A Discord library for Dart. description: A Discord library for Dart.
homepage: https://github.com/l7ssha/nyxx homepage: https://github.com/l7ssha/nyxx
repository: https://github.com/l7ssha/nyxx repository: https://github.com/l7ssha/nyxx