From 004e63bf619050b97f2064f2c499ab2ce89b54f0 Mon Sep 17 00:00:00 2001 From: Tyler Hawkins <3319104+tyzbit@users.noreply.github.com> Date: Sat, 15 Jul 2023 22:35:54 -0400 Subject: [PATCH] feat: (interactions) add message application command --- bot/functions.go | 91 +++++++++++++++++++++++++++++++++++++++++++--- bot/handlers.go | 90 ++++++++++++++++++++++++++++++++++++++++++++- globals/globals.go | 16 +++++--- 3 files changed, 186 insertions(+), 11 deletions(-) diff --git a/bot/functions.go b/bot/functions.go index c2f29e9..24a9a77 100644 --- a/bot/functions.go +++ b/bot/functions.go @@ -22,8 +22,8 @@ const ( ) // handleArchiveRequest takes a Discord session and a message reaction and -// calls go-archiver with a []string of URLs parsed from the message -// It then sends an embed with the resulting archived URLs +// calls go-archiver with a []string of URLs parsed from the message. +// It then returns an embed with the resulting archived URLs, func (bot *ArchiverBot) handleArchiveRequest(r *discordgo.MessageReactionAdd, newSnapshot bool) ( embeds []*discordgo.MessageEmbed, errs []error) { @@ -35,7 +35,7 @@ func (bot *ArchiverBot) handleArchiveRequest(r *discordgo.MessageReactionAdd, ne typingStop <- true return []*discordgo.MessageEmbed{ { - Description: "Use `/archive` instead of adding a reaction to a message.", + Description: "Use `/archive` or the `Get snapshots` menu item on the message instead of adding a reaction.", }, }, errs } @@ -132,8 +132,8 @@ func (bot *ArchiverBot) handleArchiveRequest(r *discordgo.MessageReactionAdd, ne } // handleArchiveCommand takes a discordgo.InteractionCreate and -// calls go-archiver with a []string of URLs parsed from the message -// It then sends an embed with the resulting archived URLs +// calls go-archiver with a []string of URLs parsed from the message. +// It then returns an embed with the resulting archived URLs, func (bot *ArchiverBot) handleArchiveCommand(i *discordgo.InteractionCreate) ( embeds []*discordgo.MessageEmbed, errs []error) { @@ -210,6 +210,87 @@ func (bot *ArchiverBot) handleArchiveCommand(i *discordgo.InteractionCreate) ( return embeds, errs } +// handleArchiveMessage takes a discordgo.InteractionCreate and +// calls go-archiver with a []string of URLs parsed from the message. +// It then returns an embed with the resulting archived URLs, +func (bot *ArchiverBot) handleArchiveMessage(i *discordgo.InteractionCreate) ( + embeds []*discordgo.MessageEmbed, errs []error) { + + message := &discordgo.Message{} + var archives []ArchiveEvent + commandData := i.Interaction.ApplicationCommandData() + if len(commandData.Options) > 1 { + embeds = append(embeds, &discordgo.MessageEmbed{ + Title: "Too many options submitted", + }) + } else { + for _, message := range commandData.Resolved.Messages { + messageUrls, errs := bot.extractMessageUrls(message.Content) + for index, err := range errs { + if err != nil { + log.Errorf("unable to extract message url: %s, err: %w", messageUrls[index], err) + } + } + + archives, errs := bot.populateArchiveEventCache(messageUrls, true, discordgo.Guild{ID: "", Name: "ArchiveCommand"}) + for _, err := range errs { + if err != nil { + log.Error("error populating archive cache: ", err) + } + } + + sc := bot.getServerConfig(i.GuildID) + + archivedLinks, errs := bot.executeArchiveEventRequest(&archives, sc, + true) + for _, err := range errs { + if err != nil { + log.Error("error populating archive cache: ", err) + } + } + + if len(archivedLinks) < len(messageUrls) { + log.Errorf("did not receive the same number of archived links as submitted URLs") + if len(archivedLinks) == 0 { + log.Errorf("did not receive any Archive.org links") + archivedLinks = []string{"I was unable to get any Wayback Machine URLs. " + + "Most of the time, this is " + + "due to rate-limiting by Archive.org. " + + "Please try again"} + } + } + + embeds, errs = bot.buildArchiveReply(archivedLinks, messageUrls, sc) + + for _, err := range errs { + if err != nil { + log.Error("error building archive reply: ", err) + } + } + } + } + + // Don't create an event if there were no archives + if len(archives) > 0 { + // Create a call to Archiver API event + tx := bot.DB.Create(&ArchiveEventEvent{ + UUID: archives[0].ArchiveEventEventUUID, + AuthorId: message.Author.ID, + AuthorUsername: message.Author.Username, + ChannelId: message.ChannelID, + MessageId: message.ID, + ServerID: "DirectMessage", + ArchiveEvents: archives, + }) + + if tx.RowsAffected != 1 { + errs = append(errs, fmt.Errorf("unexpected number of rows affected inserting archive event: %v", tx.RowsAffected)) + } + } + + return embeds, errs +} + // extractMessageUrls takes a string and returns a slice of URLs parsed from the string func (bot *ArchiverBot) extractMessageUrls(message string) (messageUrls []string, errs []error) { xurlsStrict := xurls.Strict diff --git a/bot/handlers.go b/bot/handlers.go index 7c4c2c5..fc257ed 100644 --- a/bot/handlers.go +++ b/bot/handlers.go @@ -219,6 +219,93 @@ func (bot *ArchiverBot) InteractionHandler(s *discordgo.Session, i *discordgo.In }, globals.Archive: func(s *discordgo.Session, i *discordgo.InteractionCreate) { log.Debug("handling archive command request") + // Send a response immediately that says the bot is thinking + bot.DG.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseDeferredChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Flags: uint64(discordgo.MessageFlagsEphemeral), + }, + }) + guild := &discordgo.Guild{} + var err error + if i.GuildID == "" { + guild.Name = "DirectMessage" + } else { + guild, err = bot.DG.Guild(i.Interaction.GuildID) + if err != nil { + guild.Name = "GuildLookupError" + } + } + + // If i.Interaction.User is not nil, then this is a DM + if i.Interaction.User != nil { + bot.createInteractionEvent(InteractionEvent{ + UserID: i.Interaction.User.ID, + Username: i.Interaction.User.Username, + InteractionId: i.ID, + ChannelId: i.ChannelID, + ServerID: i.GuildID, + ServerName: guild.Name, + }) + } else { + bot.createInteractionEvent(InteractionEvent{ + UserID: i.Interaction.Member.User.ID, + Username: i.Interaction.Member.User.Username, + InteractionId: i.ID, + ChannelId: i.ChannelID, + ServerID: i.GuildID, + ServerName: guild.Name, + }) + } + + embeds, errs := bot.handleArchiveCommand(i) + for _, err := range errs { + if err != nil { + log.Errorf("problem handling archive command request: %v", err) + } + } + + if len(embeds) == 0 { + log.Warn("no embeds were generated") + return + } + + for index, embed := range embeds { + if len(errs) > 0 { + if errs[index] != nil { + guild.Name = "None" + guild.ID = "0" + } + } + + if i.Interaction.User != nil { + bot.createMessageEvent(MessageEvent{ + AuthorId: i.Interaction.User.ID, + AuthorUsername: i.Interaction.User.Username, + MessageId: "", + ChannelId: i.Interaction.ChannelID, + ServerID: guild.ID, + ServerName: guild.Name, + }) + } else { + bot.createMessageEvent(MessageEvent{ + AuthorId: i.Interaction.Member.User.ID, + AuthorUsername: i.Interaction.Member.User.Username, + MessageId: "", + ChannelId: i.Interaction.ChannelID, + ServerID: guild.ID, + ServerName: guild.Name, + }) + } + + err := bot.sendArchiveCommandResponse(i.Interaction, embed) + if err != nil { + log.Errorf("problem sending message: %v", err) + } + } + }, + globals.ArchiveMessage: func(s *discordgo.Session, i *discordgo.InteractionCreate) { + log.Debug("handling archive message request") response := &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseDeferredChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ @@ -237,6 +324,7 @@ func (bot *ArchiverBot) InteractionHandler(s *discordgo.Session, i *discordgo.In } } + // If i.Interaction.User is not nil, then this is a DM if i.Interaction.User != nil { bot.createInteractionEvent(InteractionEvent{ UserID: i.Interaction.User.ID, @@ -257,7 +345,7 @@ func (bot *ArchiverBot) InteractionHandler(s *discordgo.Session, i *discordgo.In }) } - embeds, errs := bot.handleArchiveCommand(i) + embeds, errs := bot.handleArchiveMessage(i) for _, err := range errs { if err != nil { log.Errorf("problem handling archive command request: %v", err) diff --git a/globals/globals.go b/globals/globals.go index 7a2f26a..4e3c836 100644 --- a/globals/globals.go +++ b/globals/globals.go @@ -7,10 +7,11 @@ const ( Retry = "retry" // Commands - Stats = "stats" - Settings = "settings" - Archive = "archive" - Help = "help" + Stats = "stats" + Settings = "settings" + Archive = "archive" + ArchiveMessage = "Get snapshots" + Help = "help" // Bot settings unique handler names // Booleans @@ -33,7 +34,8 @@ const ( // Shown to the user when `/help` is called BotHelpText = `**Usage** - React to a message that has links with 🏛 (The "classical building" emoji) and the bot will respond in the channel with an archive.org link for the link(s). It saves the page to archive.org if needed. + React to a message that has links with 🏛 (The "classical building" emoji) and the bot will respond in the channel with an archive.org link for the link(s). It saves the page to archive.org if needed. +You can also right-click (or long press) a message and use "Get snapshots" to get a message with snapshots for any link that only you can see. **This is a pretty good way to get around paywalls to read articles for free.** @@ -88,6 +90,10 @@ var ( }, }, }, + { + Name: ArchiveMessage, + Type: discordgo.MessageApplicationCommand, + }, { Name: Stats, Description: "Show bot stats",