Skip to content

Commit

Permalink
cc: add functions and triggers for components and modals (#1619)
Browse files Browse the repository at this point in the history
* cc: add functions and triggers for components and modals

Co-Authored-By: Shadow <[email protected]>
Co-Authored-By: Borbot33 <[email protected]>
Co-Authored-By: Luca Zeuch <[email protected]>

* cc: restructure and rename functions

creating buttons and select menus does not need context to run, and therefore should be in `general`. also renamed the functions to match convention

* cc: restructure custom id validation

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 <[email protected]>

* cc: add buttons and menus to complexMessage<Edit>

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 <[email protected]>

* cc: allow duplicate keys in complexMessage<Edit>

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 <[email protected]>

* cc: hotfix

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

* cc: upstream message unmarshal function

Implement the proper unmarshalling function from upstream discordgo to
fix improper unmarshalling of message components

* cc: sendMessageType delegated to own type

Signed-off-by: SoggySaussages <[email protected]>

* cc/web: gramatical consistency

Signed-off-by: SoggySaussages <[email protected]>

* cc: idiomatify templates prefix check

Signed-off-by: SoggySaussages <[email protected]>

* cc: add limits to functions and triggers

Signed-off-by: SoggySaussages <[email protected]>

* cc: allow sendModal to accept an sdict

Signed-off-by: SoggySaussages <[email protected]>

* cc/buttons: add more style compatibility

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 <[email protected]>

* cc/interactions: allow file in response + followup

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 <[email protected]>

* cc/interactions: improve cid parsing

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.

* cc/interactions: restructure dot context

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 <[email protected]>

* cc/interactions: add getResponse

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 <[email protected]>

* cc/interactions: fix empty message content edits

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 <[email protected]>

* dgo: use WebhookMessage

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 <[email protected]>

* Revert "dgo: use WebhookMessage"

This reverts commit 376513d.

* dgo: support string in EndpointWebhookMessage

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 <[email protected]>

* cc/interaction: fix modal cid parse

Signed-off-by: SoggySaussages <[email protected]>

* cc:clarify ccid triggers on dashboard

Clarify trigger type and add info that these triggers are in beta.

Signed-off-by: SoggySaussages <[email protected]>

* cc/interactions: move functions to separate file

Signed-off-by: SoggySaussages <[email protected]>

* cc/interactions: update js in accordance with 1646

Signed-off-by: SoggySaussages <[email protected]>

* cc/interactions: frontend fix

Signed-off-by: SoggySaussages <[email protected]>

* cc/interactions: adjust js naming

Signed-off-by: SoggySaussages <[email protected]>

* cc/interactions: pass interaction context w execCC

Signed-off-by: SoggySaussages <[email protected]>

* cc/interactions: fix non-text menus' context data

Signed-off-by: SoggySaussages <[email protected]>

* cc/interactions: fix text menus' context data

Signed-off-by: SoggySaussages <[email protected]>

* cc/interactions: allow components in DM

Allowing components in DMs allows for link buttons and disabled buttons (for cosmetic purposes) to be included. This does
not add support for triggering custom commands via DM interactions, DM component usage is still ignored by CC.

Note: This commit puts the Show Server Info button before other components.
Signed-off-by: SoggySaussages <[email protected]>

---------

Signed-off-by: SoggySaussages <[email protected]>
Signed-off-by: SoggySaussages <[email protected]>
Co-authored-by: Shadow <[email protected]>
Co-authored-by: Borbot33 <[email protected]>
Co-authored-by: Luca Zeuch <[email protected]>
  • Loading branch information
4 people authored Jun 16, 2024
1 parent 0afcbf7 commit d0bb874
Show file tree
Hide file tree
Showing 16 changed files with 1,629 additions and 86 deletions.
96 changes: 77 additions & 19 deletions common/templates/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ var (
"sdict": StringKeyDictionary,
"structToSdict": StructToSdict,
"cembed": CreateEmbed,
"cbutton": CreateButton,
"cmenu": CreateSelectMenu,
"cmodal": CreateModal,
"cslice": CreateSlice,
"complexMessage": CreateMessageSend,
"complexMessageEdit": CreateMessageEdit,
Expand Down Expand Up @@ -140,6 +143,7 @@ func RegisterSetupFunc(f ContextSetupFunc) {

func init() {
RegisterSetupFunc(baseContextFuncs)
RegisterSetupFunc(interactionContextFuncs)

msgpack.RegisterExt(1, (*SDict)(nil))
msgpack.RegisterExt(2, (*Dict)(nil))
Expand Down Expand Up @@ -193,8 +197,9 @@ type ContextFrame struct {
MentionHere bool
MentionRoles []int64

DelResponse bool
PublishResponse bool
DelResponse bool
PublishResponse bool
EphemeralResponse bool

DelResponseDelay int
EmbedsToSend []*discordgo.MessageEmbed
Expand All @@ -203,6 +208,13 @@ type ContextFrame struct {
isNestedTemplate bool
parsedTemplate *template.Template
SendResponseInDM bool

Interaction *CustomCommandInteraction
}

type CustomCommandInteraction struct {
*discordgo.Interaction
RespondedTo bool
}

func NewContext(gs *dstate.GuildSet, cs *dstate.ChannelState, ms *dstate.MemberState) *Context {
Expand Down Expand Up @@ -438,21 +450,18 @@ func (c *Context) MessageSend(content string) *discordgo.MessageSend {
}

// SendResponse sends the response and handles reactions and the like
func (c *Context) SendResponse(content string) (*discordgo.Message, error) {
func (c *Context) SendResponse(content string) (m *discordgo.Message, err error) {
channelID := int64(0)

if !c.CurrentFrame.SendResponseInDM {
if c.CurrentFrame.CS == nil {
return nil, nil
}

if hasPerms, _ := bot.BotHasPermissionGS(c.GS, c.CurrentFrame.CS.ID, discordgo.PermissionSendMessages); !hasPerms {
// don't bother sending the response if we dont have perms
return nil, nil
sendType := sendMessageGuildChannel
if c.CurrentFrame.Interaction != nil {
if c.CurrentFrame.Interaction.RespondedTo {
sendType = sendMessageInteractionFollowup
} else {
sendType = sendMessageInteractionResponse
}

channelID = c.CurrentFrame.CS.ID
} else {
} else if c.CurrentFrame.SendResponseInDM || (c.CurrentFrame.CS != nil && c.CurrentFrame.CS.IsPrivate()) {
sendType = sendMessageDM
if c.CurrentFrame.CS != nil && c.CurrentFrame.CS.Type == discordgo.ChannelTypeDM {
channelID = c.CurrentFrame.CS.ID
} else {
Expand All @@ -462,9 +471,19 @@ func (c *Context) SendResponse(content string) (*discordgo.Message, error) {
}
channelID = privChannel.ID
}
} else {
if c.CurrentFrame.CS == nil {
return nil, nil
}

if hasPerms, _ := bot.BotHasPermissionGS(c.GS, c.CurrentFrame.CS.ID, discordgo.PermissionSendMessages); !hasPerms {
// don't bother sending the response if we dont have perms
return nil, nil
}

channelID = c.CurrentFrame.CS.ID
}

isDM := c.CurrentFrame.SendResponseInDM || (c.CurrentFrame.CS != nil && c.CurrentFrame.CS.IsPrivate())
msgSend := c.MessageSend("")
var embeds []*discordgo.MessageEmbed
embeds = append(embeds, c.CurrentFrame.EmbedsToSend...)
Expand All @@ -474,7 +493,7 @@ func (c *Context) SendResponse(content string) (*discordgo.Message, error) {
// no point in sending the response if it gets deleted immedietely
return nil, nil
}
if isDM {
if sendType == sendMessageDM {
msgSend.Components = []discordgo.MessageComponent{
discordgo.ActionsRow{
Components: []discordgo.MessageComponent{
Expand All @@ -488,10 +507,40 @@ func (c *Context) SendResponse(content string) (*discordgo.Message, error) {
},
}
}
m, err := common.BotSession.ChannelMessageSendComplex(channelID, msgSend)
if c.CurrentFrame.EphemeralResponse {
msgSend.Flags |= discordgo.MessageFlagsEphemeral
}
var getErr error
switch sendType {
case sendMessageInteractionResponse:
err = common.BotSession.CreateInteractionResponse(c.CurrentFrame.Interaction.ID, c.CurrentFrame.Interaction.Token, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: msgSend.Content,
Embeds: msgSend.Embeds,
AllowedMentions: &msgSend.AllowedMentions,
Flags: msgSend.Flags,
},
})
if err == nil {
c.CurrentFrame.Interaction.RespondedTo = true
m, getErr = common.BotSession.GetOriginalInteractionResponse(common.BotApplication.ID, c.CurrentFrame.Interaction.Token)
}
case sendMessageInteractionFollowup:
m, err = common.BotSession.CreateFollowupMessage(common.BotApplication.ID, c.CurrentFrame.Interaction.Token, &discordgo.WebhookParams{
Content: msgSend.Content,
Embeds: msgSend.Embeds,
AllowedMentions: &msgSend.AllowedMentions,
Flags: int64(msgSend.Flags),
})
default:
m, err = common.BotSession.ChannelMessageSendComplex(channelID, msgSend)
}
if err != nil {
logger.WithError(err).Error("Failed sending message")
} else {
} else if getErr != nil {
logger.WithError(getErr).Error("Failed getting interaction response")
} else if !c.CurrentFrame.EphemeralResponse {
if c.CurrentFrame.DelResponse {
MaybeScheduledDeleteMessage(c.GS.ID, channelID, m.ID, c.CurrentFrame.DelResponseDelay)
}
Expand All @@ -509,9 +558,18 @@ func (c *Context) SendResponse(content string) (*discordgo.Message, error) {
}
}

return m, nil
return
}

type sendMessageType uint

const (
sendMessageGuildChannel sendMessageType = 0
sendMessageDM sendMessageType = 1
sendMessageInteractionResponse sendMessageType = 2
sendMessageInteractionFollowup sendMessageType = 3
)

// IncreaseCheckCallCounter Returns true if key is above the limit
func (c *Context) IncreaseCheckCallCounter(key string, limit int) bool {
current, ok := c.Counters[key]
Expand Down
23 changes: 18 additions & 5 deletions common/templates/context_funcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,13 @@ func (c *Context) tmplSendDM(s ...interface{}) string {
msgSend.Embeds = t
case *discordgo.MessageSend:
msgSend = t
if (len(msgSend.Embeds) == 0 && strings.TrimSpace(msgSend.Content) == "") && (msgSend.File == nil) {
if (len(msgSend.Embeds) == 0 && strings.TrimSpace(msgSend.Content) == "") && (msgSend.File == nil) && (len(msgSend.Components) == 0) {
return ""
}
default:
msgSend.Content = fmt.Sprint(s...)
}
msgSend.Components = []discordgo.MessageComponent{
serverInfo := []discordgo.MessageComponent{
discordgo.ActionsRow{
Components: []discordgo.MessageComponent{
discordgo.Button{
Expand All @@ -60,6 +60,10 @@ func (c *Context) tmplSendDM(s ...interface{}) string {
},
},
}
if len(msgSend.Components) >= 5 {
msgSend.Components = msgSend.Components[:4]
}
msgSend.Components = append(serverInfo, msgSend.Components...)

channel, err := common.BotSession.UserChannelCreate(c.MS.User.ID)
if err != nil {
Expand Down Expand Up @@ -331,12 +335,15 @@ func (c *Context) tmplSendMessage(filterSpecialMentions bool, returnID bool) fun
return ""
}

sendType := sendMessageGuildChannel
cid := c.ChannelArg(channel)
if cid == 0 {
return ""
}

isDM := cid != c.ChannelArgNoDM(channel)
if cid != c.ChannelArgNoDM(channel) {
sendType = sendMessageDM
}

var m *discordgo.Message
msgSend := &discordgo.MessageSend{
Expand All @@ -351,6 +358,7 @@ func (c *Context) tmplSendMessage(filterSpecialMentions bool, returnID bool) fun
case *discordgo.MessageEmbed:
msgSend.Embeds = []*discordgo.MessageEmbed{typedMsg}
case []*discordgo.MessageEmbed:
msgSend.Embeds = typedMsg
case *discordgo.MessageSend:
msgSend = typedMsg
if !filterSpecialMentions {
Expand All @@ -363,8 +371,8 @@ func (c *Context) tmplSendMessage(filterSpecialMentions bool, returnID bool) fun
msgSend.Content = ToString(msg)
}

if isDM {
msgSend.Components = []discordgo.MessageComponent{
if sendType == sendMessageDM {
serverInfo := []discordgo.MessageComponent{
discordgo.ActionsRow{
Components: []discordgo.MessageComponent{
discordgo.Button{
Expand All @@ -376,6 +384,10 @@ func (c *Context) tmplSendMessage(filterSpecialMentions bool, returnID bool) fun
},
},
}
if len(msgSend.Components) >= 5 {
msgSend.Components = msgSend.Components[:4]
}
msgSend.Components = append(serverInfo, msgSend.Components...)
}

m, err = common.BotSession.ChannelMessageSendComplex(cid, msgSend)
Expand Down Expand Up @@ -437,6 +449,7 @@ func (c *Context) tmplEditMessage(filterSpecialMentions bool) func(channel inter
}
msgEdit.Content = typedMsg.Content
msgEdit.Embeds = typedMsg.Embeds
msgEdit.Components = typedMsg.Components
msgEdit.AllowedMentions = typedMsg.AllowedMentions
default:
temp := fmt.Sprint(msg)
Expand Down
Loading

0 comments on commit d0bb874

Please sign in to comment.