yagpdb icon indicating copy to clipboard operation
yagpdb copied to clipboard

cc: add functions and triggers for components and modals

Open SoggySaussages opened this issue 2 years ago • 5 comments

This PR updates YAGPDB custom commands to allow use of newer Discord features. Specifically, the use of Message Components and Modals. The ability to receive these interactions also opens up the possibility of interaction responses, which this PR also adds.

Changelog:

  • added an interaction field to context, which takes a pointer to a CustomCommandInteraction type value.
  • added a CustomCommandInteraction type value which is a pointer to a discordgo.Interaction with a RespondedTo field. This variable is set and pointer added to context upon interaction creation, allowing for the RespondedTo field to accurately reflect whether the bot has responded to an interaction yet across different cc executions from the same response. This dictates logic that decides whether the bot should send a "response" or a "followup" when asked to send a message re: an interaction.
  • Restructured the context.SendResponse function to use a new sendMessageType value instead of isDM. Since a response may now need to be sent as either a guild channel message, a dm, an interaction response, or an interaction followup, this new type covers our bases.
  • context.SendResponse can now send a response as an interaction response or a followup if the triggering CC is triggered by an interaction. The bot will decide whether to send an original response or a followup based on if the interaction has been responded to already or not. It will respond ephemerally if ephemeralResponse function is used.
  • added templating functions editResponse, and editResponseNoEscape. editResponse works similarly to editMessage, but is used to edit interaction responses. While most interaction responses and followup messages can be edited with editMessage (given the bot has the right permissions in the channel), ephemeral responses must be edited using a different endpoint, thus necessitating editResponse. It uses the following syntax: editResponse interactionToken msgID msg. interactionToken can be either a token or nil to use the current interaction token in context (the one which triggered the cc). msgID can either be a message ID, required for editing a followup message, or nil, to edit the original interaction response. msg functions the same as editMessage.
  • added function ephemeralResponse, which causes the response message to be sent ephemerally if the command was triggered by an interaction.
  • added function sendModal, which sends the provided modal as an interaction response. syntax is sendModal modal where modal takes a modal value which can be created with cmodal. Since sendModal may only be run as an interaction response, it takes no interactionToken argument, and will only respond to the interaction triggering the cc.
  • added functions sendResponse, sendResponseNoEscape, sendResponseNoEscapeRetID, and sendResponseRetID. These function similarly to their sendMessage counterparts. They will either send an original interaction response or a followup based on whether the interaction has already been responded to. They use the syntax sendMessage interactionToken msg, where interactionToken can either be a token or nil, to use the current token in context (the interaction which triggered the command). msg functions the same as sendMessage. You cannot send an original interaction response to an interaction not triggered by the current command. i.e: if interactionToken != currentFrame.Interaction.Token { send a followup }
  • added functions updateMessage and updateMessageNoEscape. These function similarly to editMessage. it edits whichever message the triggering component (such as a button) was used on. updateMessage sends an original interaction response, which differentiates it from editResponse which may only send a followup. This distinction is necessary since if an admin would like the only response to a button to be the editing of the message, using editResponse will not send an interaction response, and the triggering user will get an error saying the interaction failed regardless of whether it truly has. it follows the syntax updateMessage msg. msg functions similarly to editMessage. Since updateMessage may only be run as an interaction response, it takes no interactionToken argument, and will only respond to the interaction triggering the cc.
  • added function cbutton to create buttons which can then be added to complexMessages or complexMessageEdits. It works similarly to cembed, directly encoding and unmarshalling values into a button structure WITH THESE THREE EXCEPTIONS:
    1. the style field parses the string names of the different styles such as primary, green, secondary, link as an alternative to integer values of the button types.
    2. If a user passes a link argument to the builder, it will automatically correct this to the url field discord requires for link buttons. essentially adding an alias for the url field since the difference between link and url may be confusing to users.
    3. The function will prepend templates- to any custom id to limit them to use within templates only. This function additionally has validation, and checks for the following errors:
    • link button specified but no link provided
    • non-link button specified but no custom id provided, assigns a default id if so
    • no label AND no emoji provided
  • added function cmenu to create select menus which can then be added to complexMessages or complexMessageEdits. It works similarly to cembed, directly encoding and unmarshalling values into a button structure WITH THESE TWO EXCEPTIONS:
    1. the type field parses the string names of the different types of select menu such as user, role, or text instead of an integer value representing the type.
    2. The function will prepend templates- to any custom id to limit them to use within templates only. This function additionally has validation, and checks for the following errors:
    • no custom id provided, assigns a default id if so
    • invalid number of options provided
    • invalid minValues or maxValues argument provided
    • duplicate value fields within the provided options
  • added function cmodal to create modals which can then be sent with sendModal. Parsed manually because the developer in charge of designing modals at discord wants to see the world burn. Available fields are title, custom_id, and fields, which takes a slice of sdicts. The sdicts are encoded and unmarshalled into discordgo.TextInput objects. Assigned a default custom id if not overridden. Also prepends templates- to a custom id if it is provided.
  • edited complexMessage to add components and ephemeral fields. ephemeral adds the ephemeral flag to the message if the value is not falsey. components takes either a single message component, a slice of message components, or a slice of slices of message components. If given a single component, it appends a new action row with that single component. If given a slice of components, it automatically distributes the provided components into action rows, trimming off any components which don't fit. It also errors if duplicate custom ids for the components are used. Providing a slice of slices of components allows custom organization of the components into rows. excess is trimmed off. Also assigns default custom ids to components if one is not provided.
  • edited complexMessageEdit to take a components value working exactly the same as in complexMessage.
  • added Message Component and Modal Submission triggers, which match the provided text trigger as a regex against any component/modal submission received which is prefixed by templates-. On the website, switching to these triggers disables the message edit toggle.
  • altered the customcommands command to properly display the new types of custom command triggers
  • added . context data for components and modal submission-triggered CCs:
    • Interaction - full interaction object as provided by discord. Should be used if you need to get .Interaction.Token to be saved to database, allowing for subsequent CCs to send followup responses.
    • InteractionData - data specific to the component used, such as custom id and values submitted.
    • CustomID - custom id of the component used
    • Cmd - alias for custom id
    • StrippedID - custom ID with the matched trigger stripped off
    • StrippedMsg - alias for stripped id
    • IsButton - returns true if the used component was a button
    • IsMenu - returns true if the used component was a select menu
    • IsModal - returns true if the used component was a select menu
    • MenuType - returns a string name of the type of select menu if applicable
    • CmdArgs - returns a slice of submitted values to a modal or select menu
  • upstream changes to the discordgo library were pulled to enhance compatibility.

This is a PR which has been in the works for about 15 months, ever since I first got a functional yag host and began my journey in golang. Special thanks to the team of incredible testers that helped me make this code more user friendly, widely applicable, and bug-free* over the past few weeks: Sam Black Wolf OneBigLotus EyesEyes TOW. Ranger sado Shizuka Borbot Henri lzodd (and also to all the people on the christopaganism server who have been live-testing my prototypes for a few months)

Please see some use cases we’ve come up with:

Sado’s UNO (uses ephemeral responses, select menus, buttons)

uno1 uno2 uno3

Question of the day (uses buttons, modals, ephemeral responses)

qotd1 qotd2

Verification (uses buttons, ephemeral responses)

verification

Signed-off-by: SoggySaussages [email protected]

SoggySaussages avatar Mar 06 '24 23:03 SoggySaussages

@Ranger-4297 pointed out a really smart potential change re: adding components to messages. We've come up with a potential alternative to using "components" ( cslice ( cbutton ... ) ( cmenu ... ) ) after noting the following concerns about the current implementation:

  1. Two functions you will never use outside of complexMessage or complexMessageEdit.
  2. What the hell is a component? This may confuse new users, who may not understand why a menu and a button are the same thing.

The solution we have come up with is this:

{{ $msg := complexMessage "buttons" ( cslice ( sdict … ) ( sdict … ) ) "menus" ( cslice ( sdict … ) ( sdict … ) ) }}

Pros

  1. Clearly denotes which components are which, may make more sense to more users.
  2. Eliminates need for a new function
  3. Could still order your rows by doing `"buttons" … "menus" … "buttons" … "buttons" … "menus" …
  4. Now that I’m thinking this through, I like this option better

Cons

  1. May not be immediately apparent that you can change the order
  2. Encourages users to double up on button or menu declarations which may become confusing, and also isn’t very conventionally accepted despite it making sense in this case
  3. More difficult for conditional branching, especially for users who don’t know how to .Set an sdict. Even if the msg was made as an sdict variable and then converted to a complexMessage at the end, you cannot double up on keys, so if you are including certain buttons or menus based on conditions, you would be much more limited in the ordering and layout of the rows. You would be required to shift around your declarations and branches a lot to accommodate this quirk of "doubling up on keys is allowed and encouraged"

My proposal is thus: Keep the current functionality with buttons and menus in "components", but ADD the ability to use "buttons" and "menus" as the recommended, simpler option. Keep "components" for more advanced users. Please share thoughts as to how to move forward in this regard.

SoggySaussages avatar Mar 06 '24 23:03 SoggySaussages

This is just a suggestion, but what I would do is place all new interaction context functions to separate file, context_interactions.go for example. There's basically 1000 lines new code and this bloats general and context_funcs

mrbentarikau avatar Mar 17 '24 17:03 mrbentarikau

This is just a suggestion, but what I would do is place all new interaction context functions to separate file, context_interactions.go for example. There's basically 1000 lines new code and this bloats general and context_funcs

I don't personally love the idea of putting the general functions in an "interactions" file because they're not really related to interactions, they are just object functions which dictate features of a message. You could use disabled buttons purely for aesthetics and never call a CC interaction.

I feel that something such as context_interactions.go implies that the included functions require interaction data in context to function. While that is true for 3 of them, the others can function as intended without interaction data in context, and some could use either interaction context or not. I think it wouldn't be worthwhile to include three functions and not the others. For these reasons I am personally against having them in a separate file.

SoggySaussages avatar Mar 18 '24 04:03 SoggySaussages

For what it is worth, I don't think the naming is an issue and would prefer to see a separate file.

jo3-l avatar Mar 18 '24 05:03 jo3-l

functions: CreateComponent, CreateButton, CreateSelectMenu, CreateModal, distributeComponents, validateCustomID, validateActionRowsCustomIDs

methods: tmplEditInteractionResponse, tmplEphemeralResponse, tmplGetResponse, tmplSendModal, tmplSendInteractionResponse, tmplUpdateMessage, tokenArg

are all tied to interactions and sum up around 800 lines of code... seeing these separate makes more sense to me

also in assets/customcommands-edit.html > "Custom ID Regex" does not really say much to regular user and it should be at least "Component (Custom) ID Regex"

mrbentarikau avatar Mar 18 '24 09:03 mrbentarikau

Is anything specific pending in this @SoggySaussages ?

ashishjh-bst avatar May 30 '24 05:05 ashishjh-bst

Is anything specific pending in this @SoggySaussages ?

Apart from the needed changes given joe's recent js refactor, we were hoping to get the new documentation up and running first so we could thoroughly document the new features on release, given that they are a bit of a complex concept.

SoggySaussages avatar May 30 '24 15:05 SoggySaussages

all interaction related stuff should still be in a separate file like context_interactions.go, like I said on Mar 18th

mrbentarikau avatar May 30 '24 15:05 mrbentarikau

all interaction related stuff should still be in a separate file like context_interactions.go, like I said on Mar 18th

@SoggySaussages I agree with this it would make the code cleaner keeping all interaction related stuff in a different file.

ashishjh-bst avatar May 30 '24 16:05 ashishjh-bst

all interaction related stuff should still be in a separate file like context_interactions.go, like I said on Mar 18th

@SoggySaussages I agree with this it would make the code cleaner keeping all interaction related stuff in a different file.

Oh sorry, I could've sworn I pushed that change, I did change that locally and will push when I get home.

SoggySaussages avatar May 30 '24 22:05 SoggySaussages

Rebased and updated with separate file for new functions and js fixes in accordance with 1646 (hopefully everything fit together smoothly)

SoggySaussages avatar Jun 06 '24 18:06 SoggySaussages