Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cc: add functions and triggers for components and modals #1619

Draft
wants to merge 24 commits into
base: dev
Choose a base branch
from

Conversation

SoggySaussages
Copy link
Contributor

@SoggySaussages SoggySaussages commented Mar 6, 2024

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 vmdmaharaj@gmail.com

Co-Authored-By: Shadow <66721908+Shadow21AR@users.noreply.github.com>
Co-Authored-By: Borbot33 <58076174+borbot33@users.noreply.github.com>
Co-Authored-By: Luca Zeuch <l-zeuch@email.de>
@SoggySaussages
Copy link
Contributor Author

SoggySaussages commented Mar 6, 2024

@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.

creating buttons and select menus does not need context to run, and therefore should be in `general`. also renamed the functions to match convention
Move all custom id validation, including setting link button cids to
empty strings, to one function executed at the end of a complexMessage
or complexMessageEdit run. also moves the setting of default custom ids
to this function.

Signed-off-by: SoggySaussages <vmdmaharaj@gmail.com>
add fields to complexMessage allowing users to pass slices of buttons
and menus to complexMessage builders, in any order, as many times as
they'd like. they can be slices of buttons/modals or sdicts.

Signed-off-by: SoggySaussages <vmdmaharaj@gmail.com>
allow multiple of the same key to be passed to complexMessage so that
embeds and components may be declared multiple times, each case
appending the input to the message.
illustrated here: https://github.com/botlabs-gg/yagpdb/assets/110698921/c2e1e9fa-8f36-4ddd-8fd0-d45f4ad945d6
this is mainly implemented for buttons
and menus since embed already uses a
slice of embeds and this can be done
even better using that method. this
is not as simple with buttons and menus
because we need to be able to customize
the layout of the component rows.
only keeping it the same for embed
field for consistency.

Signed-off-by: SoggySaussages <vmdmaharaj@gmail.com>
SoggySaussages and others added 2 commits March 7, 2024 15:35
Fix an issue re: "cc: restructure custom id validation" where components were no longer prepending templates- to custom ids

Reverted an upstream pull in lib/discordgo.components.go, upstream discordgo uses strings whilst yagpdb uses int64
Implement the proper unmarshalling function from upstream discordgo to
fix improper unmarshalling of message components
customcommands/bot.go Show resolved Hide resolved
customcommands/bot.go Outdated Show resolved Hide resolved
customcommands/assets/customcommands-editcmd.html Outdated Show resolved Hide resolved
customcommands/assets/customcommands-editcmd.html Outdated Show resolved Hide resolved
common/templates/context.go Outdated Show resolved Hide resolved
SoggySaussages and others added 11 commits March 7, 2024 20:52
Signed-off-by: SoggySaussages <vmdmaharaj@gmail.com>
Signed-off-by: SoggySaussages <vmdmaharaj@gmail.com>
Signed-off-by: SoggySaussages <vmdmaharaj@gmail.com>
Signed-off-by: SoggySaussages <vmdmaharaj@gmail.com>
Signed-off-by: SoggySaussages <vmdmaharaj@gmail.com>
adds aliases for the styles to be their numerical value or a
discordgo.ButtonStyle, useful if using structToSdict so you don't need
to manually parse the styles

Signed-off-by: SoggySaussages <vmdmaharaj@gmail.com>
add the ability for a file specified in complexMessage to be sent in an
interaction response or followup. Technically a fix because I thought it
would already work but never tested it. Feauring a slightly altered
upstream pull of discordgo for (s *Session) CreateInteractionResponse.
It still diverges from upstream slightly, however only enough to avoid
rebasing the entirety of restapi.go.

Signed-off-by: SoggySaussages <vmdmaharaj@gmail.com>
use cutPrefix in modal handler. do not prepend templates- if it is already prefixxed, either from a structToSdict or a user thinking they have to. also validate cid length.
CustomID has custom ID with prefix templates- trimmed off. Cmd now has
Cmd as matched by the regex pattern instead of the full custom ID.
CmdArgs has the proper CmdArgs as would usually be parsed, Values has
any values submitted in a menu or modal instead.

Signed off by SoggySaussages <vmdmaharaj@gmail.com>
getResponse allows the user to get an interaction response or followup.
It functions similarly to getMessage, and takes an interaction token
(nil to use token in context) and a message id (nil to get original
response). This is necessary for getting ephemeral messages.
Syntax: getResponse interactionToken mID

Signed-off-by: SoggySaussages <vmdmaharaj@gmail.com>
fixes a bug where editing a message to have no content would error. Also
allows for messages without content or embeds if they have components.

Signed-off-by: SoggySaussages <vmdmaharaj@gmail.com>
SoggySaussages and others added 4 commits March 14, 2024 21:02
Change from using GetOriginalInteractionResponse to WebhookMessage. This
is similar to upstream which uses WebhookMessage with a message ID
"@original" as opposed to a separate function. WebhookMessage message ID
is a string which may be a message ID or "@original."

Signed-off by SoggySaussages <vmdmaharaj@gmail.com>
Add support for EndpointWebhookMessage to use a string value for the
message ID. This is necessary because the endpoint supports use of
either an int64 message ID or @original.

Signed-off-by: SoggySaussages <vmdmaharaj@gmail.com>
@mrbentarikau
Copy link
Contributor

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

@SoggySaussages
Copy link
Contributor Author

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.

@jo3-l
Copy link
Contributor

jo3-l commented Mar 18, 2024

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

@mrbentarikau
Copy link
Contributor

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"

Signed-off-by: SoggySaussages <vmdmaharaj@gmail.com>
Clarify trigger type and add info that these triggers are in beta.

Signed-off-by: SoggySaussages <vmdmaharaj@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
4 participants