Fix and implement new 4 and 5 interaction response type; Implement interaction followup

This commit is contained in:
Szymon Uglis 2021-04-10 14:54:13 +02:00
parent 29894ee523
commit 14219dc017
No known key found for this signature in database
GPG key ID: AA55C76D0E11A7E3
5 changed files with 138 additions and 89 deletions

View file

@ -20,7 +20,7 @@ void main() {
// Check if the name of the command is echo
if (event.interaction.name == "echo") {
// Reply with the message the user sent, showSource makes discord show the command the user sent in the channel.
await event.reply(content: event.interaction.args["message"]!.value, showSource: true);
await event.respond(content: event.interaction.getArg("message"));
}
});
});

View file

@ -7,11 +7,11 @@ void main() {
interactions
..registerHandler("test", "This is test comamnd", [], handler: (event) async {
// Acknowledge about event so you can send reply later. You have 3 second to either ack command
// or send response
await event.acknowledge(showSource: true);
// Acknowledge about event so you can send reply later.
// You have 3 second to either ack command or send response
await event.acknowledge();
// After long running task, send response
await event.reply(content: "This is example message result");
await event.respond(content: "This is example message result");
});
}

View file

@ -34,93 +34,133 @@ class InteractionEvent {
}
}
/// Should be sent when you receive a ping from interactions. Used to acknowledge a ping. Internal to the InteractionEvent.
/// 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() async {
if (DateTime.now().isAfter(this.receivedAt.add(const Duration(minutes: 15)))) {
return Future.error(InteractionExpiredError());
}
if (hasResponded) {
return Future.error(AlreadyRespondedError());
}
final url = "/interactions/${this.interaction.id.toString()}/${this.interaction.token}/callback";
final response = await this._client.httpEndpoints.sendRawRequest(
url,
"POST",
body: {
"type": 5,
"data": null,
},
);
if (response is HttpResponseError) {
return Future.error(response);
}
hasResponded = true;
}
/// 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> respond({ dynamic content, EmbedBuilder? embed, bool? tts, AllowedMentions? allowedMentions, bool hidden = false}) async {
if (DateTime.now().isAfter(this.receivedAt.add(const Duration(minutes: 15)))) {
return Future.error(InteractionExpiredError());
}
late String url;
late Map<String, dynamic> body;
late String method;
if (hasResponded) {
url = "/webhooks/${this._client.app.id.toString()}/${this.interaction.token}/messages/@original";
body = <String, dynamic> {
"content": content,
"embeds": embed != null ? [BuilderUtility.buildRawEmbed(embed)] : null,
"allowed_mentions":
allowedMentions != null ? BuilderUtility.buildRawAllowedMentions(allowedMentions) : null,
"tts": content != null && tts != null && tts
};
method = "PATCH";
} else {
url = "/interactions/${this.interaction.id.toString()}/${this.interaction.token}/callback";
body = <String, dynamic>{
"type": 4,
"data": {
if (hidden) "flags": 1 << 6,
"content": content,
"embeds": embed != null ? [BuilderUtility.buildRawEmbed(embed)] : null,
"allowed_mentions":
allowedMentions != null ? BuilderUtility.buildRawAllowedMentions(allowedMentions) : null,
"tts": content != null && tts != null && tts
},
};
method = "POST";
}
final response = await this._client.httpEndpoints.sendRawRequest(
url,
method,
body: body,
);
if (response is HttpResponseError) {
return Future.error(response);
}
hasResponded = true;
}
/// Create a followup message for an Interaction
Future<void> sendFollowup({ dynamic content, EmbedBuilder? embed, bool? tts, AllowedMentions? allowedMentions, bool hidden = false}) async {
final url = "/webhooks/${this._client.app.id.toString()}/${this.interaction.token}";
final body = <String, dynamic> {
"content": content,
"embeds": embed != null ? [BuilderUtility.buildRawEmbed(embed)] : null,
"allowed_mentions":
allowedMentions != null ? BuilderUtility.buildRawAllowedMentions(allowedMentions) : null,
"tts": content != null && tts != null && tts
};
final response = await this._client.httpEndpoints.sendRawRequest(
url,
"POST",
body: body,
);
if (response is HttpResponseError) {
return Future.error(response);
}
}
/// 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 = await this._client.httpEndpoints.sendRawRequest(
"/interactions/${this.interaction.id.toString()}/${this.interaction.token}/callback",
"POST",
body: {
"type": 1,
"data": null,
},
);
if (response is HttpResponseError) {
return Future.error(response);
}
hasResponded = true;
} else {
return Future.error(InteractionExpiredError());
}
} else {
if (DateTime.now().isAfter(this.receivedAt.add(const Duration(minutes: 15)))) {
return Future.error(InteractionExpiredError());
}
}
/// 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(AlreadyRespondedError());
}
final url = "/interactions/${this.interaction.id.toString()}/${this.interaction.token}/callback";
final response = await this._client.httpEndpoints.sendRawRequest(
url,
"POST",
body: {
"type": showSource ? 5 : 2,
"data": null,
},
);
if (response is HttpResponseError) {
return Future.error(response);
}
hasResponded = true;
} else {
if (hasResponded) {
return Future.error(InteractionExpiredError());
}
}
/// 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, bool hidden = 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}";
} else {
url = "/interactions/${this.interaction.id.toString()}/${this.interaction.token}/callback";
}
final response = await this._client.httpEndpoints.sendRawRequest(
url,
"POST",
body: {
"type": showSource ? 4 : 3,
"data": {
if (hidden) "flags": 1 << 6,
"content": content,
"embeds": embed != null ? [BuilderUtility.buildRawEmbed(embed)] : null,
"allowed_mentions":
allowedMentions != null ? BuilderUtility.buildRawAllowedMentions(allowedMentions) : null,
"tts": content != null && tts != null && tts
},
},
);
final response = await this._client.httpEndpoints.sendRawRequest(
"/interactions/${this.interaction.id.toString()}/${this.interaction.token}/callback",
"POST",
body: {
"type": 1,
"data": null,
},
);
if (response is HttpResponseError) {
return Future.error(response);
}
if (!hasResponded) {
hasResponded = true;
}
} else {
return Future.error(InteractionExpiredError());
if (response is HttpResponseError) {
return Future.error(response);
}
hasResponded = true;
}
}

View file

@ -58,6 +58,15 @@ class Interaction extends SnowflakeEntity {
this.commandId = Snowflake(raw["data"]["id"]);
}
/// Allows to fetch argument value by argument name
dynamic? getArg(String name) {
try {
return this.args.firstWhere((element) => element.name == name);
} on Error {
return null;
}
}
Iterable<InteractionOption> _generateArgs(Map<String, dynamic> rawData) sync* {
if (rawData["options"] == null) {
return;

View file

@ -2,6 +2,8 @@ part of nyxx_interactions;
/// A slash command, can only be instantiated through a method on [Interactions]
class SlashCommand {
final Nyxx _client;
Snowflake? _id;
Snowflake get id {
@ -13,16 +15,16 @@ class SlashCommand {
}
/// Command name to be shown to the user in the Slash Command UI
late final String name;
final String name;
/// Command description shown to the user in the Slash Command UI
late final String description;
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;
final List<CommandArg> args;
/// If the command is a global on, false if restricted to a guild.
bool get isGlobal => this.guild == null;
@ -30,8 +32,6 @@ class SlashCommand {
/// If the command has been registered with the discord api
late bool isRegistered = false;
late final Nyxx _client;
SlashCommand._new(this._client, this.name, this.description, this.args, {this.guild});
Future<SlashCommand> _register() async {