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
[Rr]pc*
**/doc/api/**
old.*
old.*

View File

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

View File

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

View File

@ -1,26 +1,29 @@
part of nyxx_interactions;
/// The event that you receive when a user types a slash command.
class InteractionEvent {
late final Nyxx _client;
/// The interaction data, includes the args, name, guild, channel, etc.
late final Interaction interaction;
final DateTime recievedAt = DateTime.now();
/// The DateTime the interaction was received by the Nyxx Client.
final DateTime receivedAt = DateTime.now();
/// If the Client has sent a response to the Discord API. Once the API was received a response you cannot send another.
bool hasResponded = false;
InteractionEvent._new(Nyxx client, Map<String, dynamic> rawJson) {
this._client = client;
interaction = Interaction._new(client, rawJson);
if(interaction.type == 1) {
if (interaction.type == 1) {
this._pong();
}
}
/// Should be sent when you receive a ping from interactions. Used to acknowledge a ping.
Future _pong() {
if (DateTime.now()
.isBefore(this.recievedAt.add(const Duration(minutes: 15)))) {
/// Should be sent when you receive a ping from interactions. Used to acknowledge a ping. Internal to the InteractionEvent.
Future<void> _pong() async {
if (DateTime.now().isBefore(this.receivedAt.add(const Duration(minutes: 15)))) {
if (!hasResponded) {
final response = this._client.httpEndpoints.sendRawRequest(
final response = await this._client.httpEndpoints.sendRawRequest(
"/interactions/${this.interaction.id.toString()}/${this.interaction.token}/callback",
"POST",
body: {
@ -36,7 +39,6 @@ class InteractionEvent {
if (!hasResponded) {
hasResponded = true;
}
return Future.value(null);
} else {
return Future.error(InteractionExpired());
}
@ -45,19 +47,15 @@ class InteractionEvent {
}
}
/// Used to acknowledge a Interaction but not send any response yet. Once this is sent you can then only send ChannelMessages with or without source.
Future acknowledge({
bool showSource = false,
}) {
if (DateTime.now()
.isBefore(this.recievedAt.add(const Duration(minutes: 15)))) {
/// Used to acknowledge a Interaction but not send any response yet. Once this is sent you can then only send ChannelMessages. You can also set showSource to also print out the command the user entered.
Future<void> acknowledge({ bool showSource = false, }) async {
if (DateTime.now().isBefore(this.receivedAt.add(const Duration(minutes: 15)))) {
if (hasResponded) {
return Future.error(AlreadyResponded());
}
final url =
"/interactions/${this.interaction.id.toString()}/${this.interaction.token}/callback";
final url = "/interactions/${this.interaction.id.toString()}/${this.interaction.token}/callback";
final response = this._client.httpEndpoints.sendRawRequest(
final response = await this._client.httpEndpoints.sendRawRequest(
url,
"POST",
body: {
@ -73,29 +71,19 @@ class InteractionEvent {
if (!hasResponded) {
hasResponded = true;
}
return Future.value(null);
} else {
return Future.error(InteractionExpired());
}
}
Future reply({
dynamic content,
EmbedBuilder? embed,
bool? tts,
AllowedMentions? allowedMentions,
bool showSource = false,
}) async {
if (DateTime.now()
.isBefore(this.recievedAt.add(const Duration(minutes: 15)))) {
/// Used to acknowledge a Interaction and send a response. Once this is sent you can then only send ChannelMessages. You can also set showSource to also print out the command the user entered.
Future<void> reply({ dynamic content, EmbedBuilder? embed, bool? tts, AllowedMentions? allowedMentions, bool showSource = false, }) async {
if (DateTime.now().isBefore(this.receivedAt.add(const Duration(minutes: 15)))) {
String url;
if (hasResponded) {
url =
"/webhooks/${this.interaction.id.toString()}/${this.interaction.token}";
url = "/webhooks/${this.interaction.id.toString()}/${this.interaction.token}";
} else {
url =
"/interactions/${this.interaction.id.toString()}/${this.interaction.token}/callback";
url = "/interactions/${this.interaction.id.toString()}/${this.interaction.token}/callback";
}
final response = await this._client.httpEndpoints.sendRawRequest(
url,
@ -104,10 +92,9 @@ class InteractionEvent {
"type": showSource ? 4 : 3,
"data": {
"content": content,
"embeds": embed != null ? [ BuilderUtility.buildRawEmbed(embed) ] : null,
"allowed_mentions": allowedMentions != null
? BuilderUtility.buildRawAllowedMentions(allowedMentions)
: null,
"embeds": embed != null ? [BuilderUtility.buildRawEmbed(embed)] : null,
"allowed_mentions":
allowedMentions != null ? BuilderUtility.buildRawAllowedMentions(allowedMentions) : null,
"tts": content != null && tts != null && tts
},
},
@ -117,12 +104,9 @@ class InteractionEvent {
return Future.error(response);
}
print((response as HttpResponseSuccess).body);
if (!hasResponded) {
hasResponded = true;
}
return Future.value(null);
} else {
return Future.error(InteractionExpired());
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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