From ca5bc6cab94b579646b170a4e4bfc4f2f9d1bb8a Mon Sep 17 00:00:00 2001 From: Abitofevrything <54505189+abitofevrything@users.noreply.github.com> Date: Sun, 26 Nov 2023 20:10:05 +0100 Subject: [PATCH] Fix component helpers not working with ephemeral messages (#146) --- example/example.dart | 54 ++++++++++++++++++++++++++++++++++++++-- lib/src/util/mixins.dart | 47 ++++++++++++++++++++-------------- 2 files changed, 80 insertions(+), 21 deletions(-) diff --git a/example/example.dart b/example/example.dart index 5be18e5..321f07d 100644 --- a/example/example.dart +++ b/example/example.dart @@ -113,11 +113,61 @@ void main() async { // // Since a ping command doesn't have any other arguments, we don't add any other parameters to // the function. - id('ping', (ChatContext context) { + options: CommandOptions(defaultResponseLevel: ResponseLevel.hint), + id('ping', (ChatContext context) async { // For a ping command, all we need to do is respond with `pong`. // To do that, we can use the `IChatContext`'s `respond` method which responds to the command with // a message. - context.respond(MessageBuilder(content: 'pong!')); + // context.respond(MessageBuilder(content: 'pong!')); + + final level1 = ResponseLevel( + hideInteraction: true, + isDm: false, + mention: false, + preserveComponentMessages: true, + ); + final level2 = ResponseLevel( + hideInteraction: true, + isDm: false, + mention: false, + preserveComponentMessages: false, + ); + final level3 = ResponseLevel( + hideInteraction: false, + isDm: false, + mention: false, + preserveComponentMessages: true, + ); + final level4 = ResponseLevel( + hideInteraction: false, + isDm: false, + mention: false, + preserveComponentMessages: false, + ); + final levels = [level1, level2, level3, level4]; + + for (final level in levels) { + await context.getButtonSelection( + [1, 2, 3], + MessageBuilder(content: 'test'), + level: level, + ); + await context.getSelection( + [1, 2, 3], + MessageBuilder(content: 'test'), + level: level, + ); + await context.getSelection( + List.generate(50, (index) => index), + MessageBuilder(content: 'test'), + level: level, + ); + await context.getMultiSelection( + [1, 2, 3], + MessageBuilder(content: 'test'), + level: level, + ); + } }), ); diff --git a/lib/src/util/mixins.dart b/lib/src/util/mixins.dart index 1e5f729..2a5d963 100644 --- a/lib/src/util/mixins.dart +++ b/lib/src/util/mixins.dart @@ -115,6 +115,18 @@ mixin InteractiveMixin implements InteractiveContext, ContextData { return _parent!._nearestCommandContext; } + Future _updateMessage( + InteractiveContext context, + Message message, + MessageUpdateBuilder builder, + ) async { + return switch (context) { + InteractionContextData(:MessageResponse interaction) => + interaction.updateFollowup(message.id, builder), + _ => message.update(builder), + }; + } + @override Future awaitButtonPress(ComponentId componentId) async { if (delegate != null) { @@ -372,9 +384,7 @@ mixin InteractiveMixin implements InteractiveContext, ContextData { commands.eventManager.stopListeningFor(id); } - await message.update( - MessageUpdateBuilder(components: disabledComponentRows), - ); + await _updateMessage(this, message, MessageUpdateBuilder(components: disabledComponentRows)); } } @@ -462,6 +472,7 @@ mixin InteractiveMixin implements InteractiveContext, ContextData { SelectMenuBuilder? menu; Message? message; + InteractiveContext? responseContext; try { do { @@ -494,6 +505,7 @@ mixin InteractiveMixin implements InteractiveContext, ContextData { (builder.components ??= []).add(row); message = await respond(builder, level: level); + responseContext = this; } else { // On later iterations, replace the last row with our newly created one. List rows = builder.components!; @@ -505,6 +517,7 @@ mixin InteractiveMixin implements InteractiveContext, ContextData { level: (level ?? _nearestCommandContext.command.resolvedOptions.defaultResponseLevel)! .copyWith(preserveComponentMessages: false), ); + responseContext = context; } context = await commands.eventManager.nextSelectMenuEvent(menuId); @@ -537,9 +550,9 @@ mixin InteractiveMixin implements InteractiveContext, ContextData { _nearestCommandContext, )..stackTrace = s; } finally { - if (menu != null && message != null) { + if (menu != null && message != null && responseContext != null) { menu.isDisabled = true; - await message.edit(MessageCreateUpdateBuilder.fromMessageBuilder(builder)); + await _updateMessage(this, message, MessageCreateUpdateBuilder.fromMessageBuilder(builder)); } } } @@ -594,7 +607,7 @@ mixin InteractiveMixin implements InteractiveContext, ContextData { type: MessageComponentType.stringSelect, customId: menuId.toString(), options: options, - minValues: choices.length, + maxValues: choices.length, ); ActionRowBuilder row = ActionRowBuilder(components: [menu]); @@ -628,7 +641,7 @@ mixin InteractiveMixin implements InteractiveContext, ContextData { )..stackTrace = s; } finally { menu.isDisabled = true; - await message.edit(MessageCreateUpdateBuilder.fromMessageBuilder(builder)); + await _updateMessage(this, message, MessageCreateUpdateBuilder.fromMessageBuilder(builder)); } } } @@ -645,6 +658,8 @@ mixin InteractionRespondMixin @override Future respond(MessageBuilder builder, {ResponseLevel? level}) async { + builder = MessageCreateUpdateBuilder.fromMessageBuilder(builder); + await _acknowledgeLock; if (_delegate != null) { @@ -671,24 +686,18 @@ mixin InteractionRespondMixin return interaction.createFollowup(builder, isEphemeral: level.hideInteraction); } - // If we want to preserve the original message a component is attached to, we can just send a - // followup instead of a response. - // Also, if we are requested to hide interactions, also send a followup, or - // components will just edit the original message (making it public). - if (level.hideInteraction) { - await interaction.respond(builder, isEphemeral: level.hideInteraction); - return interaction.fetchOriginalResponse(); - } - - if (interaction is MessageComponentInteraction) { + // Only update the message if we don't want to preserve it and the message's ephemerality + // matches whether we want the response to be ephemeral or not. + if (interaction is MessageComponentInteraction && + !level.preserveComponentMessages && + interaction.message?.flags.isEphemeral == level.hideInteraction) { // Using interactionEvent.respond is actually the same as editing a message in the case where // the interaction is a message component. In those cases, leaving `componentRows` as `null` // would leave the existing components on the message - which likely isn't what our users // expect. Instead, we override them and set the builder to have no components. builder.components ??= []; - await (interaction as MessageComponentInteraction) - .respond(builder, updateMessage: !level.preserveComponentMessages); + await (interaction as MessageComponentInteraction).respond(builder, updateMessage: true); return interaction.fetchOriginalResponse(); }