From 9358940f150d8f668e9c8f709c033b51f5be7d1f Mon Sep 17 00:00:00 2001 From: Kesuaheli Date: Sun, 24 Nov 2024 00:00:07 +0100 Subject: [PATCH] feat(secret santa): added nudging match --- config.yaml | 4 + data/lang/de.yaml | 12 ++- data/lang/en.yaml | 12 ++- modules/secretsanta/handleComponentInvite.go | 106 +++++++++++++++++-- modules/secretsanta/handleModal.go | 1 + modules/secretsanta/secretsantabase.go | 15 ++- util/interaction.go | 76 +++++++++++++ 7 files changed, 213 insertions(+), 13 deletions(-) diff --git a/config.yaml b/config.yaml index 20748be..06107ea 100644 --- a/config.yaml +++ b/config.yaml @@ -85,6 +85,10 @@ event: name: 🗑️ #id: #animated: true + secretsanta.invite.nudge_match: + name: 👉 + #id: + #animated: true webserver: favicon: webserver/favicon.png diff --git a/data/lang/de.yaml b/data/lang/de.yaml index 9304531..5733840 100644 --- a/data/lang/de.yaml +++ b/data/lang/de.yaml @@ -145,13 +145,23 @@ discord.command: msg.invite.show_match.title: Dein Partner ist %s msg.invite.show_match.description: Bitte breite ein Wichtelgeschenk vor und schicke es ihm/ihr. Halte dich dabei an unsere vereinbarten Regeln. msg.invite.show_match.address: Adresse - msg.invite.show_match.address_not_set: Dein Partner hat noch keine Adresse eingetragen + msg.invite.show_match.address_not_set: Dein Partner hat noch keine Adresse eingetragen. + + Falls schon einige Tage vergangen sind, kannst du den "Anstupsen" Knopf drücken. Dein Partner bekommt daraufhin einen Hinweis die Adresse einzutragen. + msg.invite.show_match.nudge_description: Wenn du vermutest, dass die eingetragene Adresse einen Fehler enthält, kannst du unten den den "Anstupsen" Knopf drücken. Dein Partner bekommt daraufhin einen Hinweis die Adresse zu aktualisieren. msg.invite.set_address: Deine Adresse wurde eingetragen msg.invite.button.set_address: Deine Adresse eintragen msg.invite.set_address.changed: Deine Adresse wurde aktualisiert auf msg.invite.set_address.not_changed: Deine Adresse wurde nicht aktualisiert, weil es die gleiche ist wie vorher. msg.invite.set_address.match_updated: Dein Partner hat gerade seine/ihre Adresse aktualisiert. Das ist nur um dich zu informieren - Boop Boop 🤖 msg.invite.button.delete: Schließen + msg.invite.button.nudge_match: Anstupsen + msg.invite.nudge_match.confirm: Bist du sicher, dass du deinen Partner anstupsen möchtest? + + Bestätige, dass du deinen Partner anstupsen möchtest. Er/Sie bekommt dann einen Hinweis die Adresse zu aktualisieren. + msg.invite.nudge_match.success: Dein Partner wurde erfolgreich angestupst + msg.invite.nudge_match.pending: Du hast deinen Partner angestupst. Ich melde mich, sobald er/sie die Adresse aktualisiert hat. + msg.invite.nudge_received: Dein Wichtel hat dich anstupst. Bitte trage eine Adresse ein falls du es noch nicht gemacht hast oder überprüfe deine bereits eingetragene Adresse ob sie eventuell einen Tippfehler oder ähnliches enthält. msg.invite.modal.set_address.title: Deine Adresse eintragen msg.invite.modal.set_address.label: Deine Adresse wird deinem Wichtel angezeigt diff --git a/data/lang/en.yaml b/data/lang/en.yaml index e2e4540..3e0e369 100644 --- a/data/lang/en.yaml +++ b/data/lang/en.yaml @@ -145,13 +145,23 @@ discord.command: msg.invite.show_match.title: Your partner is %s msg.invite.show_match.description: Please prepare and send them a secret gift according to our agreed rules. msg.invite.show_match.address: Address - msg.invite.show_match.address_not_set: Your partner has not set an address yet + msg.invite.show_match.address_not_set: Your partner has not set an address yet. + + If some days passed, you can click the "Nudge" button below. Your partner will receive a notification saying that they enter their address. + msg.invite.show_match.nudge_description: If you think the entered address is wrong, you can click the "Nudge" button below. Your partner will receive a notification saying that they should update their address. msg.invite.set_address: Your address is set msg.invite.button.set_address: Set your address msg.invite.set_address.changed: Your address was updated to msg.invite.set_address.not_changed: Your address was not updated, because it is the same as before. msg.invite.set_address.match_updated: Your partner just updated their address. This is just to inform you - Beep Boop 🤖 msg.invite.button.delete: Close + msg.invite.button.nudge_match: Nudge + msg.invite.nudge_match.confirm: Are you sure you want to nudge your partner? + + Please confirm that you want to nudge your partner. They will receive a notification that they should update their address. + msg.invite.nudge_match.success: Your partner was nudged! + msg.invite.nudge_match.pending: You nudged your partner. I will notify you, when they updated their address. + msg.invite.nudge_received: Your santa nudged you. Please enter an address if you haven't done it yet or check your already entered address for any typos. msg.invite.modal.set_address.title: Set your address msg.invite.modal.set_address.label: Your address will be shown your secret santa diff --git a/modules/secretsanta/handleComponentInvite.go b/modules/secretsanta/handleComponentInvite.go index 3d5d90f..a182105 100644 --- a/modules/secretsanta/handleComponentInvite.go +++ b/modules/secretsanta/handleComponentInvite.go @@ -4,6 +4,7 @@ import ( "cake4everybot/data/lang" "cake4everybot/util" "fmt" + "strings" "github.com/bwmarrin/discordgo" ) @@ -16,6 +17,11 @@ func (c Component) handleInvite(ids []string) { case "set_address": c.handleInviteSetAddress(ids) return + case "nudge_match": + c.handleInviteNudgeMatch(ids) + case "confirm_nudge": + c.handleInviteConfirmNudge(ids) + return case "delete": err := c.Session.ChannelMessageDelete(c.Interaction.ChannelID, c.Interaction.Message.ID) if err != nil { @@ -51,16 +57,26 @@ func (c Component) handleInviteShowMatch(ids []string) { e := util.AuthoredEmbed(c.Session, player.Match.Member, tp+"display") e.Title = fmt.Sprintf(lang.GetDefault(tp+"msg.invite.show_match.title"), player.Match.Member.DisplayName()) e.Description = lang.GetDefault(tp + "msg.invite.show_match.description") + e.Color = 0x690042 e.Fields = append(e.Fields, &discordgo.MessageEmbedField{ Name: lang.GetDefault(tp + "msg.invite.show_match.address"), - Value: fmt.Sprintf("```\n%s\n```", player.Match.Address), + Value: fmt.Sprintf("```\n%s\n```\n%s", player.Match.Address, lang.GetDefault(tp+"msg.invite.show_match.nudge_description")), }) if player.Match.Address == "" { e.Fields[0].Value = lang.GetDefault(tp + "msg.invite.show_match.address_not_set") } util.SetEmbedFooter(c.Session, tp+"display", e) - c.ReplyHiddenEmbed(e) + c.ReplyComponentsHiddenEmbed( + []discordgo.MessageComponent{discordgo.ActionsRow{Components: []discordgo.MessageComponent{ + util.CreateButtonComponent( + fmt.Sprintf("secretsanta.invite.nudge_match.%s", c.Interaction.GuildID), + lang.GetDefault(tp+"msg.invite.button.nudge_match"), + discordgo.SecondaryButton, + util.GetConfigComponentEmoji("secretsanta.invite.nudge_match"), + ), + }}}, + e) } func (c Component) handleInviteSetAddress(ids []string) { @@ -77,13 +93,8 @@ func (c Component) handleInviteSetAddress(ids []string) { return } - var player *player - for _, p := range players { - if p.User.ID == c.Interaction.User.ID { - player = p - } - } - if player == nil { + player, ok := players[c.Interaction.User.ID] + if !ok { log.Printf("ERROR: could not find player %s in guild %s: %+v", c.Interaction.User.ID, c.Interaction.GuildID, c.Interaction.User.ID) c.ReplyError() return @@ -100,3 +111,80 @@ func (c Component) handleInviteSetAddress(ids []string) { }, }}) } + +func (c Component) handleInviteNudgeMatch(ids []string) { + c.ReplyComponentsHiddenSimpleEmbedUpdate( + []discordgo.MessageComponent{discordgo.ActionsRow{Components: []discordgo.MessageComponent{ + util.CreateButtonComponent( + "secretsanta.invite.confirm_nudge."+strings.Join(ids, "."), + lang.GetDefault(tp+"msg.invite.button.nudge_match"), + discordgo.PrimaryButton, + util.GetConfigComponentEmoji("secretsanta.invite.nudge_match"), + ), + }}}, + 0x690042, + lang.GetDefault(tp+"msg.invite.nudge_match.confirm")) +} + +func (c Component) handleInviteConfirmNudge(ids []string) { + c.Interaction.GuildID = util.ShiftL(ids) + players, err := c.getPlayers() + if err != nil { + log.Printf("ERROR: could not get players: %+v", err) + c.ReplyError() + return + } + if len(players) == 0 { + log.Printf("ERROR: no players in guild %s", c.Interaction.GuildID) + c.ReplyError() + return + } + + player, ok := players[c.Interaction.User.ID] + if !ok { + log.Printf("ERROR: could not find player %s in guild %s: %+v", c.Interaction.User.ID, c.Interaction.GuildID, c.Interaction.User.ID) + c.ReplyError() + return + } + player.Match.PendingNudge = true + + matchChannel, err := c.Session.UserChannelCreate(player.Match.User.ID) + if err != nil { + log.Printf("ERROR: could not create DM channel with user %s: %+v", player.Match.User.ID, err) + c.ReplyError() + return + } + _, err = c.Session.ChannelMessageEditEmbed(matchChannel.ID, player.Match.MessageID, player.Match.InviteEmbed(c.Session)) + if err != nil { + log.Printf("ERROR: could not edit match message embed: %+v", err) + c.ReplyError() + return + } + + data := &discordgo.MessageSend{ + Content: lang.GetDefault(tp + "msg.invite.nudge_received"), + Reference: &discordgo.MessageReference{MessageID: player.Match.MessageID}, + Components: []discordgo.MessageComponent{discordgo.ActionsRow{Components: []discordgo.MessageComponent{ + util.CreateButtonComponent( + "secretsanta.invite.delete", + lang.GetDefault(tp+"msg.invite.button.delete"), + discordgo.DangerButton, + util.GetConfigComponentEmoji("secretsanta.invite.delete"), + ), + }}}, + } + _, err = c.Session.ChannelMessageSendComplex(matchChannel.ID, data) + if err != nil { + log.Printf("ERROR: could not send nudge message: %+v", err) + c.ReplyError() + return + } + + _, err = c.Session.ChannelMessageEditEmbed(c.Interaction.ChannelID, player.MessageID, player.InviteEmbed(c.Session)) + if err != nil { + log.Printf("ERROR: could not edit invite message embed: %+v", err) + c.ReplyError() + return + } + c.ReplyHiddenSimpleEmbedUpdate(0x690042, lang.GetDefault(tp+"msg.invite.nudge_match.success")) +} diff --git a/modules/secretsanta/handleModal.go b/modules/secretsanta/handleModal.go index 8c18147..be4c7dd 100644 --- a/modules/secretsanta/handleModal.go +++ b/modules/secretsanta/handleModal.go @@ -35,6 +35,7 @@ func (c Component) handleModalSetAddress(ids []string) { } player.Address = addressFiled.Value + player.PendingNudge = false err = c.setPlayers(players) if err != nil { log.Printf("ERROR: could not set players: %+v", err) diff --git a/modules/secretsanta/secretsantabase.go b/modules/secretsanta/secretsantabase.go index f13ed48..5f1a25c 100644 --- a/modules/secretsanta/secretsantabase.go +++ b/modules/secretsanta/secretsantabase.go @@ -108,16 +108,27 @@ type player struct { Address string // MessageID is the message the bot sent to the player MessageID string + // PendingNudge is true if the player has received a nugde from their santa and they haven't changed their + // address yet i.e. the nudge is still pending. + PendingNudge bool } // InviteEmbed returns an embed for the player to be sent by the bot. func (player *player) InviteEmbed(s *discordgo.Session) (e *discordgo.MessageEmbed) { var matchValue, addressValue = "❌", "❌" if player != nil && player.Match.Address != "" { - matchValue = "✅" + if player.Match.PendingNudge { + matchValue = fmt.Sprintf("%s %s", "⌛", lang.GetDefault(tp+"msg.invite.nudge_match.pending")) + } else { + matchValue = "✅" + } } if player != nil && player.Address != "" { - addressValue = "✅" + if player.PendingNudge { + addressValue = fmt.Sprintf("%s %s", "⚠️", lang.GetDefault(tp+"msg.invite.nudge_received")) + } else { + addressValue = "✅" + } } e = &discordgo.MessageEmbed{ diff --git a/util/interaction.go b/util/interaction.go index 389a693..d4e2897 100644 --- a/util/interaction.go +++ b/util/interaction.go @@ -195,6 +195,17 @@ func (i *InteractionUtil) ReplyHiddenEmbed(embeds ...*discordgo.MessageEmbed) { i.respond() } +// ReplyHiddenEmbedUpdate is like [InteractionUtil.ReplyHiddenEmbed] but made for an update for +// components. +func (i *InteractionUtil) ReplyHiddenEmbedUpdate(embeds ...*discordgo.MessageEmbed) { + if !i.respondMessage(true, false) { + return + } + i.response.Data.Embeds = embeds + i.response.Data.Flags = discordgo.MessageFlagsEphemeral + i.respond() +} + // ReplyComponents sends a message along with the provied message components. func (i *InteractionUtil) ReplyComponents(components []discordgo.MessageComponent, message string) { i.respondMessage(false, false) @@ -259,6 +270,22 @@ func (i *InteractionUtil) ReplySimpleEmbedUpdatef(color int, format string, a .. i.ReplyEmbedUpdate(e) } +// ReplyHiddenSimpleEmbedUpdate is like [InteractionUtil.ReplyHiddenSimpleEmbed] but made for an +// update for components. +func (i *InteractionUtil) ReplyHiddenSimpleEmbedUpdate(color int, content string) { + e := &discordgo.MessageEmbed{ + Description: content, + Color: color, + } + i.ReplyHiddenEmbedUpdate(e) +} + +// ReplyHiddenSimpleEmbedUpdatef is like [InteractionUtil.ReplyHiddenSimpleEmbedf] but made for an +// update for components. +func (i *InteractionUtil) ReplyHiddenSimpleEmbedUpdatef(color int, format string, a ...any) { + i.ReplyHiddenSimpleEmbedUpdate(color, fmt.Sprintf(format, a...)) +} + // ReplyComponentsf formats according to a format specifier and sends the result along with the // provied message components. func (i *InteractionUtil) ReplyComponentsf(components []discordgo.MessageComponent, format string, a ...any) { @@ -302,6 +329,16 @@ func (i *InteractionUtil) ReplyComponentsEmbed(components []discordgo.MessageCom i.respond() } +// ReplyComponentsEmbedUpdate is like [InteractionUtil.ReplyComponentsEmbed] but made for an update for components. +func (i *InteractionUtil) ReplyComponentsEmbedUpdate(components []discordgo.MessageComponent, embeds ...*discordgo.MessageEmbed) { + if !i.respondMessage(true, false) { + return + } + i.response.Data.Embeds = embeds + i.response.Data.Components = components + i.respond() +} + // ReplyComponentsHiddenEmbed sends the given embeds as ephemeral reply along with the provided message // components. func (i *InteractionUtil) ReplyComponentsHiddenEmbed(components []discordgo.MessageComponent, embeds ...*discordgo.MessageEmbed) { @@ -312,6 +349,45 @@ func (i *InteractionUtil) ReplyComponentsHiddenEmbed(components []discordgo.Mess i.respond() } +// ReplyComponentsHiddenEmbedUpdate is like [InteractionUtil.ReplyComponentsHiddenEmbed] but made for an update for components. +func (i *InteractionUtil) ReplyComponentsHiddenEmbedUpdate(components []discordgo.MessageComponent, embeds ...*discordgo.MessageEmbed) { + if !i.respondMessage(true, false) { + return + } + i.response.Data.Embeds = embeds + i.response.Data.Components = components + i.response.Data.Flags = discordgo.MessageFlagsEphemeral + i.respond() +} + +// ReplyComponentsSimpleEmbedUpdate is like [InteractionUtil.ReplyComponentsSimpleEmbed] but made for an update for components. +func (i *InteractionUtil) ReplyComponentsSimpleEmbedUpdate(components []discordgo.MessageComponent, color int, content string) { + e := &discordgo.MessageEmbed{ + Description: content, + Color: color, + } + i.ReplyComponentsEmbedUpdate(components, e) +} + +// ReplyComponentsSimpleEmbedUpdatef is like [InteractionUtil.ReplyComponentsSimpleEmbedf] but made for an update for components. +func (i *InteractionUtil) ReplyComponentsSimpleEmbedUpdatef(components []discordgo.MessageComponent, color int, format string, a ...any) { + i.ReplyComponentsSimpleEmbedUpdate(components, color, fmt.Sprintf(format, a...)) +} + +// ReplyComponentsHiddenSimpleEmbedUpdate is like [InteractionUtil.ReplyComponentsHiddenSimpleEmbed] but made for an update for components. +func (i *InteractionUtil) ReplyComponentsHiddenSimpleEmbedUpdate(components []discordgo.MessageComponent, color int, content string) { + e := &discordgo.MessageEmbed{ + Description: content, + Color: color, + } + i.ReplyComponentsHiddenEmbedUpdate(components, e) +} + +// ReplyComponentsHiddenSimpleEmbedUpdatef is like [InteractionUtil.ReplyComponentsHiddenSimpleEmbedf] but made for an update for components. +func (i *InteractionUtil) ReplyComponentsHiddenSimpleEmbedUpdatef(components []discordgo.MessageComponent, color int, format string, a ...any) { + i.ReplyComponentsHiddenSimpleEmbedUpdate(components, color, fmt.Sprintf(format, a...)) +} + // ReplyAutocomplete returns the given choices to the user. When this is called on an interaction // type outside form an applicationCommandAutocomplete nothing will happen. func (i *InteractionUtil) ReplyAutocomplete(choices []*discordgo.ApplicationCommandOptionChoice) {