cc: add functions and triggers for components and modals
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
CustomCommandInteractiontype value. - added a
CustomCommandInteractiontype 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 theRespondedTofield 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.SendResponsefunction to use a newsendMessageTypevalue instead ofisDM. 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.SendResponsecan 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 ifephemeralResponsefunction is used. - added templating functions
editResponse,andeditResponseNoEscape.editResponseworks similarly toeditMessage, but is used to edit interaction responses. While most interaction responses and followup messages can be edited witheditMessage(given the bot has the right permissions in the channel), ephemeral responses must be edited using a different endpoint, thus necessitatingeditResponse. It uses the following syntax:editResponse interactionToken msgID msg.interactionTokencan be either a token ornilto use the current interaction token in context (the one which triggered the cc).msgIDcan either be a message ID, required for editing a followup message, ornil, to edit the original interaction response.msgfunctions the same aseditMessage. - 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 issendModal modalwheremodaltakes a modal value which can be created withcmodal. SincesendModalmay only be run as an interaction response, it takes nointeractionTokenargument, and will only respond to the interaction triggering the cc. - added functions
sendResponse,sendResponseNoEscape,sendResponseNoEscapeRetID, andsendResponseRetID. These function similarly to theirsendMessagecounterparts. They will either send an original interaction response or a followup based on whether the interaction has already been responded to. They use the syntaxsendMessage interactionToken msg, whereinteractionTokencan either be a token ornil, to use the current token in context (the interaction which triggered the command).msgfunctions the same assendMessage. 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
updateMessageandupdateMessageNoEscape. These function similarly toeditMessage. it edits whichever message the triggering component (such as a button) was used on.updateMessagesends an original interaction response, which differentiates it fromeditResponsewhich 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, usingeditResponsewill 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 syntaxupdateMessage msg.msgfunctions similarly toeditMessage. SinceupdateMessagemay only be run as an interaction response, it takes nointeractionTokenargument, and will only respond to the interaction triggering the cc. - added function
cbuttonto create buttons which can then be added tocomplexMessages orcomplexMessageEdits. It works similarly tocembed, directly encoding and unmarshalling values into a button structure WITH THESE THREE EXCEPTIONS:- the
stylefield parses the string names of the different styles such asprimary,green,secondary,linkas an alternative to integer values of the button types. - If a user passes a
linkargument to the builder, it will automatically correct this to theurlfield discord requires for link buttons. essentially adding an alias for theurlfield since the difference betweenlinkandurlmay be confusing to users. - 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
- the
- added function
cmenuto create select menus which can then be added tocomplexMessages orcomplexMessageEdits. It works similarly tocembed, directly encoding and unmarshalling values into a button structure WITH THESE TWO EXCEPTIONS:- the
typefield parses the string names of the different types of select menu such asuser,role, ortextinstead of an integer value representing the type. - 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
minValuesormaxValuesargument provided - duplicate
valuefields within the provided options
- the
- added function
cmodalto create modals which can then be sent withsendModal. Parsed manually because the developer in charge of designing modals at discord wants to see the world burn. Available fields aretitle,custom_id, andfields, which takes asliceofsdicts. The sdicts are encoded and unmarshalled intodiscordgo.TextInputobjects. Assigned a default custom id if not overridden. Also prependstemplates-to a custom id if it is provided. - edited
complexMessageto addcomponentsandephemeralfields.ephemeraladds the ephemeral flag to the message if the value is not falsey.componentstakes 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
complexMessageEditto take acomponentsvalue working exactly the same as incomplexMessage. - 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.Tokento 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
- Interaction - full interaction object as provided by discord. Should be used if you need to get
- 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)
Question of the day (uses buttons, modals, ephemeral responses)
Verification (uses buttons, ephemeral responses)
Signed-off-by: SoggySaussages [email protected]
@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:
- Two functions you will never use outside of
complexMessageorcomplexMessageEdit. - 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
- Clearly denotes which components are which, may make more sense to more users.
- Eliminates need for a new function
- Could still order your rows by doing `"buttons" … "menus" … "buttons" … "buttons" … "menus" …
- Now that I’m thinking this through, I like this option better
Cons
- May not be immediately apparent that you can change the order
- 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
- More difficult for conditional branching, especially for users who don’t know how to
.Setan sdict. Even if the msg was made as an sdict variable and then converted to acomplexMessageat 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.
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
This is just a suggestion, but what I would do is place all new interaction context functions to separate file,
context_interactions.gofor 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.
For what it is worth, I don't think the naming is an issue and would prefer to see a separate file.
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"
Is anything specific pending in this @SoggySaussages ?
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.
all interaction related stuff should still be in a separate file like context_interactions.go, like I said on Mar 18th
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.
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.
Rebased and updated with separate file for new functions and js fixes in accordance with 1646 (hopefully everything fit together smoothly)