From 4bb571ed30b6034ef53eab2d7a62919ddf5ddc06 Mon Sep 17 00:00:00 2001 From: Joe L <56809242+jo3-l@users.noreply.github.com> Date: Wed, 19 Jun 2024 12:08:16 -0700 Subject: [PATCH] paginatedmessages: interoperate with response autodeletion setting (#1671) Currently, commands with paginated responses do not obey the 'delete response after delay' option set using command overrides. The reason is that dcmd (and in turn the YAGPDB command system) does not know about paginated messages -- which are sent manually as opposed to using dcmd's response mechanism -- and so naturally does not delete them. This commit therefore teaches dcmd about paginated messages by introducing a new type, PaginatedResponse, which implements dcmd.Response. When returned from a command run function, dcmd will then automatically call PaginatedResponse.Send to create and return the paginated message, which interopates with the existing response deletion machinery. --- bot/paginatedmessages/paginatedcommand.go | 6 +-- .../paginatedinteractions.go | 45 ++++++++++++----- commands/help.go | 11 +---- logs/plugin_bot.go | 12 ++--- moderation/commands.go | 4 +- reputation/plugin_bot.go | 6 +-- stdcommands/define/define.go | 31 +++++------- stdcommands/dictionary/dictionary.go | 48 +++++++++---------- stdcommands/forex/forex.go | 8 +--- stdcommands/howlongtobeat/howlongtobeat.go | 13 ++--- stdcommands/inspire/inspire.go | 8 +--- timezonecompanion/plugin_bot.go | 6 +-- 12 files changed, 91 insertions(+), 107 deletions(-) diff --git a/bot/paginatedmessages/paginatedcommand.go b/bot/paginatedmessages/paginatedcommand.go index e71a05c775..6934570d16 100644 --- a/bot/paginatedmessages/paginatedcommand.go +++ b/bot/paginatedmessages/paginatedcommand.go @@ -26,10 +26,8 @@ func PaginatedCommand(pageArgIndex int, cb PaginatedCommandFunc) dcmd.RunFunc { return cb(data, nil, page) } - _, err := CreatePaginatedMessage(data.GuildData.GS.ID, data.GuildData.CS.ID, page, 0, func(p *PaginatedMessage, page int) (*discordgo.MessageEmbed, error) { + return NewPaginatedResponse(data.GuildData.GS.ID, data.GuildData.CS.ID, page, 0, func(p *PaginatedMessage, page int) (*discordgo.MessageEmbed, error) { return cb(data, p, page) - }) - - return nil, err + }), nil } } diff --git a/bot/paginatedmessages/paginatedinteractions.go b/bot/paginatedmessages/paginatedinteractions.go index ddfe7a47b3..4753258399 100644 --- a/bot/paginatedmessages/paginatedinteractions.go +++ b/bot/paginatedmessages/paginatedinteractions.go @@ -1,12 +1,14 @@ package paginatedmessages import ( + "fmt" "strconv" "sync" "time" "github.com/botlabs-gg/yagpdb/v2/bot/eventsystem" "github.com/botlabs-gg/yagpdb/v2/common" + "github.com/botlabs-gg/yagpdb/v2/lib/dcmd" "github.com/botlabs-gg/yagpdb/v2/lib/discordgo" ) @@ -93,38 +95,57 @@ func createNavigationButtons(prevDisabled bool, nextDisabled bool) []discordgo.M } } -func CreatePaginatedMessage(guildID, channelID int64, initPage, maxPages int, pagerFunc PagerFunc) (*PaginatedMessage, error) { +func NewPaginatedResponse(guildID, channelID int64, initPage, maxPages int, pagerFunc PagerFunc) *PaginatedResponse { if initPage < 1 { initPage = 1 } + return &PaginatedResponse{ + guildID: guildID, + channelID: channelID, + initPage: initPage, + maxPages: maxPages, + pagerFunc: pagerFunc, + } +} + +type PaginatedResponse struct { + guildID int64 + channelID int64 + initPage, maxPages int + pagerFunc PagerFunc +} + +var _ dcmd.Response = (*PaginatedResponse)(nil) + +func (p *PaginatedResponse) Send(*dcmd.Data) ([]*discordgo.Message, error) { pm := &PaginatedMessage{ - GuildID: guildID, - ChannelID: channelID, - CurrentPage: initPage, - MaxPage: maxPages, + GuildID: p.guildID, + ChannelID: p.channelID, + CurrentPage: p.initPage, + MaxPage: p.maxPages, lastUpdateTime: time.Now(), stopCh: make(chan bool), - Navigate: pagerFunc, + Navigate: p.pagerFunc, } - embed, err := pagerFunc(pm, initPage) + embed, err := p.pagerFunc(pm, p.initPage) if err != nil { - return nil, err + return nil, fmt.Errorf("generating first page of paginated response: %s", err) } - footer := "Page " + strconv.Itoa(initPage) + footer := "Page " + strconv.Itoa(p.initPage) nextButtonDisabled := false if pm.MaxPage > 0 { footer += "/" + strconv.Itoa(pm.MaxPage) - nextButtonDisabled = initPage >= pm.MaxPage + nextButtonDisabled = p.initPage >= pm.MaxPage } embed.Footer = &discordgo.MessageEmbedFooter{ Text: footer, } embed.Timestamp = time.Now().Format(time.RFC3339) - msg, err := common.BotSession.ChannelMessageSendComplex(channelID, &discordgo.MessageSend{ + msg, err := common.BotSession.ChannelMessageSendComplex(p.channelID, &discordgo.MessageSend{ Embeds: []*discordgo.MessageEmbed{embed}, Components: createNavigationButtons(true, nextButtonDisabled), }) @@ -140,7 +161,7 @@ func CreatePaginatedMessage(guildID, channelID int64, initPage, maxPages int, pa menusLock.Unlock() go pm.paginationTicker() - return pm, nil + return []*discordgo.Message{msg}, nil } func (p *PaginatedMessage) HandlePageButtonClick(ic *discordgo.InteractionCreate, pageMod int) { diff --git a/commands/help.go b/commands/help.go index cc29a76ae3..4e8be9df1d 100644 --- a/commands/help.go +++ b/commands/help.go @@ -119,15 +119,8 @@ For more in depth help and information you should visit https://docs.yagpdb.xyz/ } helpEmbeds = append([]*discordgo.MessageEmbed{firstPage}, helpEmbeds...) - - _, err = paginatedmessages.CreatePaginatedMessage(0, channel.ID, 1, len(helpEmbeds), func(p *paginatedmessages.PaginatedMessage, page int) (*discordgo.MessageEmbed, error) { + return paginatedmessages.NewPaginatedResponse(0, channel.ID, 1, len(helpEmbeds), func(p *paginatedmessages.PaginatedMessage, page int) (*discordgo.MessageEmbed, error) { embed := helpEmbeds[page-1] return embed, nil - }) - if err != nil { - return "Something went wrong, make sure you don't have the bot blocked or your DMs closed!", err - - } - - return nil, nil + }), nil } diff --git a/logs/plugin_bot.go b/logs/plugin_bot.go index f821f61f74..76adbc1197 100644 --- a/logs/plugin_bot.go +++ b/logs/plugin_bot.go @@ -284,7 +284,7 @@ var cmdUsernames = &commands.YAGCommand{ gID = parsed.GuildData.GS.ID } - _, err := paginatedmessages.CreatePaginatedMessage(gID, parsed.ChannelID, 1, 0, func(p *paginatedmessages.PaginatedMessage, page int) (*discordgo.MessageEmbed, error) { + return paginatedmessages.NewPaginatedResponse(gID, parsed.ChannelID, 1, 0, func(p *paginatedmessages.PaginatedMessage, page int) (*discordgo.MessageEmbed, error) { target := parsed.Author if parsed.Args[0].Value != nil { target = parsed.Args[0].Value.(*discordgo.User) @@ -317,9 +317,7 @@ var cmdUsernames = &commands.YAGCommand{ } return embed, nil - }) - - return nil, err + }), nil }, } @@ -347,7 +345,7 @@ var cmdNicknames = &commands.YAGCommand{ return "Nickname logging is disabled on this server", nil } - _, err = paginatedmessages.CreatePaginatedMessage(parsed.GuildData.GS.ID, parsed.ChannelID, 1, 0, func(p *paginatedmessages.PaginatedMessage, page int) (*discordgo.MessageEmbed, error) { + return paginatedmessages.NewPaginatedResponse(parsed.GuildData.GS.ID, parsed.ChannelID, 1, 0, func(p *paginatedmessages.PaginatedMessage, page int) (*discordgo.MessageEmbed, error) { offset := (page - 1) * 15 @@ -377,9 +375,7 @@ var cmdNicknames = &commands.YAGCommand{ } return embed, nil - }) - - return nil, err + }), nil }, } diff --git a/moderation/commands.go b/moderation/commands.go index a5176aa310..60f5230b11 100644 --- a/moderation/commands.go +++ b/moderation/commands.go @@ -896,8 +896,8 @@ var ModerationCommands = []*commands.YAGCommand{ if parsed.Context().Value(paginatedmessages.CtxKeyNoPagination) != nil { return PaginateWarnings(parsed)(nil, page) } - _, err = paginatedmessages.CreatePaginatedMessage(parsed.GuildData.GS.ID, parsed.GuildData.CS.ID, page, 0, PaginateWarnings(parsed)) - return nil, err + + return paginatedmessages.NewPaginatedResponse(parsed.GuildData.GS.ID, parsed.GuildData.CS.ID, page, 0, PaginateWarnings(parsed)), nil }, }, { diff --git a/reputation/plugin_bot.go b/reputation/plugin_bot.go index a42d2193d8..ad2903e827 100644 --- a/reputation/plugin_bot.go +++ b/reputation/plugin_bot.go @@ -427,11 +427,9 @@ var cmds = []*commands.YAGCommand{ return topRepPager(parsed.GuildData.GS.ID, nil, page) } - _, err := paginatedmessages.CreatePaginatedMessage(parsed.GuildData.GS.ID, parsed.ChannelID, page, 0, func(p *paginatedmessages.PaginatedMessage, page int) (*discordgo.MessageEmbed, error) { + return paginatedmessages.NewPaginatedResponse(parsed.GuildData.GS.ID, parsed.ChannelID, page, 0, func(p *paginatedmessages.PaginatedMessage, page int) (*discordgo.MessageEmbed, error) { return topRepPager(parsed.GuildData.GS.ID, p, page) - }) - - return nil, err + }), nil }, }, } diff --git a/stdcommands/define/define.go b/stdcommands/define/define.go index a483181c53..dd4bbef0f0 100644 --- a/stdcommands/define/define.go +++ b/stdcommands/define/define.go @@ -45,27 +45,20 @@ var Command = &commands.YAGCommand{ } if paginatedView { - _, err := paginatedmessages.CreatePaginatedMessage( - data.GuildData.GS.ID, data.ChannelID, 1, len(qResp.Results), func(p *paginatedmessages.PaginatedMessage, page int) (*discordgo.MessageEmbed, error) { - i := page - 1 - - paginatedEmbed := embedCreator(qResp.Results, i) - return paginatedEmbed, nil - }) - if err != nil { - return "Something went wrong", nil - } - } else { - result := qResp.Results[0] - - cmdResp := fmt.Sprintf("**%s**: %s\n*%s*\n*(<%s>)*", result.Word, result.Definition, result.Example, result.Permalink) - if len(qResp.Results) > 1 { - cmdResp += fmt.Sprintf(" *%d more results*", len(qResp.Results)-1) - } - return cmdResp, nil + return paginatedmessages.NewPaginatedResponse(data.GuildData.GS.ID, data.ChannelID, 1, len(qResp.Results), func(p *paginatedmessages.PaginatedMessage, page int) (*discordgo.MessageEmbed, error) { + i := page - 1 + + paginatedEmbed := embedCreator(qResp.Results, i) + return paginatedEmbed, nil + }), nil } - return nil, nil + result := qResp.Results[0] + cmdResp := fmt.Sprintf("**%s**: %s\n*%s*\n*(<%s>)*", result.Word, result.Definition, result.Example, result.Permalink) + if len(qResp.Results) > 1 { + cmdResp += fmt.Sprintf(" *%d more results*", len(qResp.Results)-1) + } + return cmdResp, nil }, } diff --git a/stdcommands/dictionary/dictionary.go b/stdcommands/dictionary/dictionary.go index 80498d527c..48c0068936 100644 --- a/stdcommands/dictionary/dictionary.go +++ b/stdcommands/dictionary/dictionary.go @@ -35,7 +35,7 @@ var Command = &commands.YAGCommand{ SlashCommandEnabled: true, RunFunc: func(data *dcmd.Data) (interface{}, error) { query := strings.ToLower(data.Args[0].Str()) - url := "https://api.dictionaryapi.dev/api/v2/entries/en/"+url.QueryEscape(query) + url := "https://api.dictionaryapi.dev/api/v2/entries/en/" + url.QueryEscape(query) req, err := http.NewRequest("GET", url, nil) if err != nil { return nil, err @@ -68,15 +68,13 @@ var Command = &commands.YAGCommand{ return createDictionaryDefinitionEmbed(dictionary, &dictionary.Meanings[0]), nil } - _, err = paginatedmessages.CreatePaginatedMessage(data.GuildData.GS.ID, data.ChannelID, 1, len(dictionary.Meanings), func(p *paginatedmessages.PaginatedMessage, page int) (*discordgo.MessageEmbed, error) { + return paginatedmessages.NewPaginatedResponse(data.GuildData.GS.ID, data.ChannelID, 1, len(dictionary.Meanings), func(p *paginatedmessages.PaginatedMessage, page int) (*discordgo.MessageEmbed, error) { if page > len(dictionary.Meanings) { return nil, paginatedmessages.ErrNoResults } return createDictionaryDefinitionEmbed(dictionary, &dictionary.Meanings[page-1]), nil - }) - - return nil, err + }), nil }, } @@ -90,18 +88,18 @@ func createDictionaryDefinitionEmbed(res *DictionaryResponse, def *Meaning) *dis Timestamp: time.Now().Format(time.RFC3339), } - if(len(res.SourceUrls) > 0) { - embed.URL = res.SourceUrls[0]; + if len(res.SourceUrls) > 0 { + embed.URL = res.SourceUrls[0] } - - var description = ""; + + var description = "" for _, d := range def.Definitions { - if(len(description) + len(d.Definition) + len(d.Example) > 2000) { + if len(description)+len(d.Definition)+len(d.Example) > 2000 { // if all definitions along with examples cannot be fit into the description, skip remaining definitions. - break; + break } - description = fmt.Sprintf("%s\n- %s", description, capitalizeSentences(normalizeOutput(d.Definition))); - if d.Example != ""{ + description = fmt.Sprintf("%s\n- %s", description, capitalizeSentences(normalizeOutput(d.Definition))) + if d.Example != "" { var example = capitalizeSentences(normalizeOutput(d.Example)) if !hasEndOfSentenceSymbol(example) { example = example + "." // add period if no other symbol that ends the sentence is present @@ -110,7 +108,7 @@ func createDictionaryDefinitionEmbed(res *DictionaryResponse, def *Meaning) *dis } } - embed.Description = common.CutStringShort(description, 2048); + embed.Description = common.CutStringShort(description, 2048) if res.Origin != "" { embed.Fields = append(embed.Fields, &discordgo.MessageEmbedField{ @@ -123,20 +121,20 @@ func createDictionaryDefinitionEmbed(res *DictionaryResponse, def *Meaning) *dis if len(res.Phonetics) != 0 { var pronunciation = &discordgo.MessageEmbedField{ Name: "Pronunciation", - Value: "", + Value: "", Inline: true, } - for _, v := range res.Phonetics { - if(v.Audio != ""){ - if(v.Text == ""){ - v.Text = res.Word; + for _, v := range res.Phonetics { + if v.Audio != "" { + if v.Text == "" { + v.Text = res.Word } pronunciation.Value = fmt.Sprintf("%s\nšŸ”Š[%s](%s)", pronunciation.Value, normalizeOutput(v.Text), v.Audio) - }else { + } else { pronunciation.Value = fmt.Sprintf("%s\n%s", pronunciation.Value, normalizeOutput(v.Text)) } } - embed.Fields = append(embed.Fields, pronunciation ) + embed.Fields = append(embed.Fields, pronunciation) } if def.PartOfSpeech != "" { @@ -208,8 +206,8 @@ func hasEndOfSentenceSymbol(s string) bool { } type Phonetic struct { - Text string `json:"text"` - Audio string `json:"audio"` + Text string `json:"text"` + Audio string `json:"audio"` } type Definition struct { @@ -227,9 +225,9 @@ type Meaning struct { } type DictionaryResponse struct { - Origin string `json:"origin,omitempty"` + Origin string `json:"origin,omitempty"` Word string `json:"word"` Phonetics []Phonetic `json:"phonetics"` Meanings []Meaning `json:"meanings"` - SourceUrls []string `json:"sourceUrls"` + SourceUrls []string `json:"sourceUrls"` } diff --git a/stdcommands/forex/forex.go b/stdcommands/forex/forex.go index 473d1ce494..edc109fa06 100644 --- a/stdcommands/forex/forex.go +++ b/stdcommands/forex/forex.go @@ -58,18 +58,14 @@ var Command = &commands.YAGCommand{ // If the currency isn't supported by API. if !toExist || !fromExist { - _, err = paginatedmessages.CreatePaginatedMessage( + return paginatedmessages.NewPaginatedResponse( data.GuildData.GS.ID, data.ChannelID, 1, maxPages, func(p *paginatedmessages.PaginatedMessage, page int) (*discordgo.MessageEmbed, error) { embed, err := errEmbed(currenciesResult, page) if err != nil { return nil, err } return embed, nil - }) - if err != nil { - return nil, err - } - return nil, nil + }), nil } err = requestAPI(fmt.Sprintf("https://api.frankfurter.app/latest?amount=%.3f&from=%s&to=%s", amount, from, to), &exchangeRateResult) diff --git a/stdcommands/howlongtobeat/howlongtobeat.go b/stdcommands/howlongtobeat/howlongtobeat.go index b15f9f8788..2073138a20 100644 --- a/stdcommands/howlongtobeat/howlongtobeat.go +++ b/stdcommands/howlongtobeat/howlongtobeat.go @@ -38,7 +38,7 @@ var ( hltbHostPath = "api/search" ) -//Command var needs a comment for lint :) +// Command var needs a comment for lint :) var Command = &commands.YAGCommand{ CmdCategory: commands.CategoryFun, Name: "HowLongToBeat", @@ -91,7 +91,7 @@ var Command = &commands.YAGCommand{ hltbEmbed := embedCreator(games, 0, paginatedView) if paginatedView { - _, err := paginatedmessages.CreatePaginatedMessage( + return paginatedmessages.NewPaginatedResponse( data.GuildData.GS.ID, data.ChannelID, 1, len(games), func(p *paginatedmessages.PaginatedMessage, page int) (*discordgo.MessageEmbed, error) { i := page - 1 sort.SliceStable(games, func(i, j int) bool { @@ -99,15 +99,10 @@ var Command = &commands.YAGCommand{ }) paginatedEmbed := embedCreator(games, i, paginatedView) return paginatedEmbed, nil - }) - if err != nil { - return "Something went wrong", nil - } - } else { - return hltbEmbed, nil + }), nil } - return nil, nil + return hltbEmbed, nil }, } diff --git a/stdcommands/inspire/inspire.go b/stdcommands/inspire/inspire.go index 64a2d64066..cf90967093 100644 --- a/stdcommands/inspire/inspire.go +++ b/stdcommands/inspire/inspire.go @@ -50,7 +50,7 @@ var Command = &commands.YAGCommand{ return nil, err } inspireArray = arrayMaker(inspireArray, result) - _, err = paginatedmessages.CreatePaginatedMessage( + return paginatedmessages.NewPaginatedResponse( data.GuildData.GS.ID, data.ChannelID, 1, 15, func(p *paginatedmessages.PaginatedMessage, page int) (*discordgo.MessageEmbed, error) { if page-1 == len(inspireArray) { result, err := inspireFromAPI(true, season) @@ -60,11 +60,7 @@ var Command = &commands.YAGCommand{ inspireArray = arrayMaker(inspireArray, result) } return createInspireEmbed(inspireArray[page-1], true), nil - }) - if err != nil { - return nil, err - } - return nil, nil + }), nil } inspData, err := inspireFromAPI(false, season) if err != nil { diff --git a/timezonecompanion/plugin_bot.go b/timezonecompanion/plugin_bot.go index c2b31101b2..1dd9dd2138 100644 --- a/timezonecompanion/plugin_bot.go +++ b/timezonecompanion/plugin_bot.go @@ -93,9 +93,9 @@ func (p *Plugin) AddCommands() { if parsed.Context().Value(paginatedmessages.CtxKeyNoPagination) != nil { return paginatedTimezones(zones)(nil, 1) } - _, err := paginatedmessages.CreatePaginatedMessage( + resp := paginatedmessages.NewPaginatedResponse( parsed.GuildData.GS.ID, parsed.ChannelID, 1, int(math.Ceil(float64(len(zones))/10)), paginatedTimezones(zones)) - return nil, err + return resp, nil } matches := "" @@ -109,7 +109,7 @@ func (p *Plugin) AddCommands() { // Check whether the requested zone has an exact match in zones found := false for n, candidate := range zones { - if strings.ToLower(candidate) == strings.ToLower(parsed.Args[0].Str()) { + if strings.EqualFold(candidate, parsed.Args[0].Str()) { found = true // Select matching zone zone = zones[n]