From 32294932265b765d70fcdb647795f50194dab8ad Mon Sep 17 00:00:00 2001 From: Siwoo Jeon Date: Mon, 31 Mar 2025 21:57:00 +0900 Subject: [PATCH] feat: Add deleteLearnedData command --- Dockerfile | 1 + commands/dataLength.go | 2 +- commands/deleteLearnedData.go | 174 ++++++++++++++++++++++++++++++++ commands/discommand.go | 52 ++++++++-- commands/help.go | 2 +- commands/information.go | 2 +- commands/learn.go | 2 +- commands/learnedDataList.go | 2 +- components/deleteLearnedData.go | 77 ++++++++++++++ handler/interactionCreate.go | 2 + main.go | 4 + utils/customIds.go | 7 ++ utils/regexp.go | 1 + 13 files changed, 313 insertions(+), 15 deletions(-) create mode 100644 commands/deleteLearnedData.go create mode 100644 components/deleteLearnedData.go create mode 100644 utils/customIds.go diff --git a/Dockerfile b/Dockerfile index 8d047fc..751b695 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,6 +8,7 @@ COPY ./configs . COPY ./databases . COPY ./handler . COPY ./utils . +COPY ./components . COPY go.mod . COPY go.sum . COPY main.go . diff --git a/commands/dataLength.go b/commands/dataLength.go index 588ba8b..f27823d 100644 --- a/commands/dataLength.go +++ b/commands/dataLength.go @@ -43,7 +43,7 @@ var DataLengthCommand *Command = &Command{ MessageRun: func(ctx *MsgContext) { dataLengthRun(ctx.Session, ctx.Msg) }, - ChatInputRun: func(ctx *InterContext) { + ChatInputRun: func(ctx *ChatInputContext) { dataLengthRun(ctx.Session, ctx.Inter) }, } diff --git a/commands/deleteLearnedData.go b/commands/deleteLearnedData.go new file mode 100644 index 0000000..e27f3e0 --- /dev/null +++ b/commands/deleteLearnedData.go @@ -0,0 +1,174 @@ +package commands + +import ( + "context" + "strconv" + "strings" + + "git.wh64.net/muffin/goMuffin/databases" + "git.wh64.net/muffin/goMuffin/utils" + "github.com/bwmarrin/discordgo" + "go.mongodb.org/mongo-driver/v2/bson" + "go.mongodb.org/mongo-driver/v2/mongo" +) + +var DeleteLearnedDataCommand *Command = &Command{ + ApplicationCommand: &discordgo.ApplicationCommand{ + Name: "삭제", + Description: "당신이 가르쳐준 단ㅇ어를 삭제해요.", + Options: []*discordgo.ApplicationCommandOption{ + { + Name: "단어", + Description: "삭제할 단어를 입ㄹ력해주세요.", + Required: true, + }, + }, + }, + Aliases: []string{"잊어", "지워"}, + DetailedDescription: &DetailedDescription{ + Usage: "머핀아 삭제 (삭제할 단어)", + Examples: []string{"머핀아 삭제 머핀"}, + }, + Category: Chattings, + MessageRun: func(ctx *MsgContext) { + deleteLearnedDataRun(ctx.Command, ctx.Session, ctx.Msg, &ctx.Args) + }, + ChatInputRun: func(ctx *ChatInputContext) { + var args *[]string + deleteLearnedDataRun(ctx.Command, ctx.Session, ctx.Inter, args) + }, +} + +func deleteLearnedDataRun(c *Command, s *discordgo.Session, m any, args *[]string) { + var command, userId, description string + var datas []databases.Learn + var options []discordgo.SelectMenuOption + + switch m := m.(type) { + case *discordgo.MessageCreate: + command = strings.Join(*args, " ") + userId = m.Author.ID + + if command == "" { + s.ChannelMessageSendEmbedReply(m.ChannelID, &discordgo.MessageEmbed{ + Title: "❌ 오류", + Description: "올바르지 않ㅇ은 용법이에요.", + Fields: []*discordgo.MessageEmbedField{ + { + Name: "사용법", + Value: utils.InlineCode(c.DetailedDescription.Usage), + }, + { + Name: "예시", + Value: utils.InlineCode(strings.Join(addPrefix(c.DetailedDescription.Examples), "\n")), + }, + }, + Color: int(utils.EFail), + }, m.Reference()) + } + case *discordgo.InteractionCreate: + s.InteractionRespond(m.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseDeferredChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Flags: discordgo.MessageFlagsEphemeral, + }, + }) + + optsMap := map[string]*discordgo.ApplicationCommandInteractionDataOption{} + for _, opt := range m.ApplicationCommandData().Options { + optsMap[opt.Name] = opt + } + if opt, ok := optsMap["단어"]; ok { + command = opt.StringValue() + } + userId = m.Member.User.ID + } + + cur, err := databases.Learns.Find(context.TODO(), bson.M{"user_id": userId, "command": command}) + if err != nil { + embed := &discordgo.MessageEmbed{ + Title: "❌ 오류", + Color: int(utils.EFail), + } + if err == mongo.ErrNoDocuments { + embed.Description = "해당 하는 지식ㅇ을 찾을 수 없어요." + switch m := m.(type) { + case *discordgo.MessageCreate: + s.ChannelMessageSendEmbedReply(m.ChannelID, embed, m.Reference()) + case *discordgo.InteractionCreate: + s.InteractionResponseEdit(m.Interaction, &discordgo.WebhookEdit{ + Embeds: &[]*discordgo.MessageEmbed{embed}, + }) + } + return + } + + embed.Description = "데이터를 가져오는데 실패했어요." + switch m := m.(type) { + case *discordgo.MessageCreate: + s.ChannelMessageSendEmbedReply(m.ChannelID, embed, m.Reference()) + case *discordgo.InteractionCreate: + s.InteractionResponseEdit(m.Interaction, &discordgo.WebhookEdit{ + Embeds: &[]*discordgo.MessageEmbed{embed}, + }) + } + return + } + + cur.All(context.TODO(), &datas) + + for i := range len(datas) { + data := datas[i] + + options = append(options, discordgo.SelectMenuOption{ + Label: strconv.Itoa(i+1) + "번 지식", + Description: data.Result, + Value: utils.DeleteLearnedData + data.Id.Hex() + `&No.` + strconv.Itoa(i+1), + }) + description += strconv.Itoa(i+1) + ". " + data.Result + "\n" + } + + embed := &discordgo.MessageEmbed{ + Title: command + " 삭제", + Description: command + " 에 대한 대답 중 하나를 선ㅌ택하여 삭제해주세요.\n" + + utils.CodeBlockWithLanguage("md", description), + Color: int(utils.EDefault), + } + + components := []discordgo.MessageComponent{ + discordgo.ActionsRow{ + Components: []discordgo.MessageComponent{ + discordgo.SelectMenu{ + MenuType: discordgo.StringSelectMenu, + CustomID: utils.DeleteLearnedDataUserId + userId, + Options: options, + Placeholder: "ㅈ지울 응답을 선택해주세요.", + }, + }, + }, + discordgo.ActionsRow{ + Components: []discordgo.MessageComponent{ + discordgo.Button{ + CustomID: utils.DeleteLearnedDataCancel + userId, + Label: "취소하기", + Style: discordgo.DangerButton, + Disabled: false, + }, + }, + }, + } + + switch m := m.(type) { + case *discordgo.MessageCreate: + s.ChannelMessageSendComplex(m.ChannelID, &discordgo.MessageSend{ + Embeds: []*discordgo.MessageEmbed{embed}, + Components: components, + Reference: m.Reference(), + }) + case *discordgo.InteractionCreate: + s.InteractionResponseEdit(m.Interaction, &discordgo.WebhookEdit{ + Embeds: &[]*discordgo.MessageEmbed{embed}, + Components: &components, + }) + } +} diff --git a/commands/discommand.go b/commands/discommand.go index b3c56ed..a04d5ad 100644 --- a/commands/discommand.go +++ b/commands/discommand.go @@ -7,7 +7,9 @@ import ( ) type messageRun func(ctx *MsgContext) -type chatInputRun func(ctx *InterContext) +type chatInputRun func(ctx *ChatInputContext) +type componentRun func(ctx *ComponentContext) +type componentParse func(ctx *ComponentContext) bool type Category string @@ -26,8 +28,9 @@ type Command struct { } type DiscommandStruct struct { - Commands map[string]*Command - Aliases map[string]string + Commands map[string]*Command + Components []*Component + Aliases map[string]string } type MsgContext struct { @@ -37,36 +40,55 @@ type MsgContext struct { Command *Command } -type InterContext struct { +type ChatInputContext struct { Session *discordgo.Session Inter *discordgo.InteractionCreate Command *Command } +type ComponentContext struct { + Session *discordgo.Session + Inter *discordgo.InteractionCreate + Component *Component +} + +type Component struct { + Parse componentParse + Run componentRun +} + const ( Chattings Category = "채팅" Generals Category = "일반" ) -var mutex *sync.Mutex = &sync.Mutex{} +var mutex1 *sync.Mutex = &sync.Mutex{} +var mutex2 *sync.Mutex = &sync.Mutex{} func new() *DiscommandStruct { discommand := DiscommandStruct{ - Commands: map[string]*Command{}, - Aliases: map[string]string{}, + Commands: map[string]*Command{}, + Aliases: map[string]string{}, + Components: []*Component{}, } return &discommand } func (d *DiscommandStruct) LoadCommand(c *Command) { - mutex.Lock() + mutex1.Lock() d.Commands[c.Name] = c d.Aliases[c.Name] = c.Name for _, alias := range c.Aliases { d.Aliases[alias] = c.Name } - mutex.Unlock() + mutex1.Unlock() +} + +func (d *DiscommandStruct) LoadComponent(c *Component) { + mutex2.Lock() + d.Components = append(d.Components, c) + mutex2.Unlock() } func (d *DiscommandStruct) MessageRun(name string, s *discordgo.Session, m *discordgo.MessageCreate, args []string) { @@ -82,7 +104,17 @@ func (d *DiscommandStruct) ChatInputRun(name string, s *discordgo.Session, i *di if command == nil { return } - command.ChatInputRun(&InterContext{s, i, command}) + command.ChatInputRun(&ChatInputContext{s, i, command}) +} + +func (d *DiscommandStruct) ComponentRun(s *discordgo.Session, i *discordgo.InteractionCreate) { + for _, c := range d.Components { + if (!c.Parse(&ComponentContext{s, i, c})) { + return + } + + c.Run(&ComponentContext{s, i, c}) + } } var Discommand *DiscommandStruct = new() diff --git a/commands/help.go b/commands/help.go index 6a5c18a..00fae4d 100644 --- a/commands/help.go +++ b/commands/help.go @@ -40,7 +40,7 @@ var HelpCommand *Command = &Command{ MessageRun: func(ctx *MsgContext) { helpRun(ctx.Command, ctx.Session, ctx.Msg, &ctx.Args) }, - ChatInputRun: func(ctx *InterContext) { + ChatInputRun: func(ctx *ChatInputContext) { var args *[]string helpRun(ctx.Command, ctx.Session, ctx.Inter, args) }, diff --git a/commands/information.go b/commands/information.go index 30bc14d..9005b62 100644 --- a/commands/information.go +++ b/commands/information.go @@ -20,7 +20,7 @@ var InformationCommand *Command = &Command{ MessageRun: func(ctx *MsgContext) { informationRun(ctx.Session, ctx.Msg) }, - ChatInputRun: func(ctx *InterContext) { + ChatInputRun: func(ctx *ChatInputContext) { informationRun(ctx.Session, ctx.Inter) }, } diff --git a/commands/learn.go b/commands/learn.go index 6052dc4..19b7409 100644 --- a/commands/learn.go +++ b/commands/learn.go @@ -45,7 +45,7 @@ var LearnCommand *Command = &Command{ MessageRun: func(ctx *MsgContext) { learnRun(ctx.Command, ctx.Session, ctx.Msg, &ctx.Args) }, - ChatInputRun: func(ctx *InterContext) { + ChatInputRun: func(ctx *ChatInputContext) { var args *[]string learnRun(ctx.Command, ctx.Session, ctx.Inter, args) }, diff --git a/commands/learnedDataList.go b/commands/learnedDataList.go index 8b40787..beb2ce8 100644 --- a/commands/learnedDataList.go +++ b/commands/learnedDataList.go @@ -27,7 +27,7 @@ var LearnedDataListCommand *Command = &Command{ MessageRun: func(ctx *MsgContext) { learnedDataListRun(ctx.Session, ctx.Msg) }, - ChatInputRun: func(ctx *InterContext) { + ChatInputRun: func(ctx *ChatInputContext) { learnedDataListRun(ctx.Session, ctx.Inter) }, } diff --git a/components/deleteLearnedData.go b/components/deleteLearnedData.go new file mode 100644 index 0000000..8e6bb90 --- /dev/null +++ b/components/deleteLearnedData.go @@ -0,0 +1,77 @@ +package components + +import ( + "context" + "strings" + + "git.wh64.net/muffin/goMuffin/commands" + "git.wh64.net/muffin/goMuffin/databases" + "git.wh64.net/muffin/goMuffin/utils" + "github.com/bwmarrin/discordgo" + "go.mongodb.org/mongo-driver/v2/bson" +) + +var DeleteLearnedDataComponent *commands.Component = &commands.Component{ + Parse: func(ctx *commands.ComponentContext) bool { + var userId string + i := ctx.Inter + s := ctx.Session + customId := i.MessageComponentData().CustomID + + if i.MessageComponentData().ComponentType == discordgo.ButtonComponent { + if !strings.HasPrefix(customId, utils.DeleteLearnedDataCancel) { + return false + } + + userId = customId[len(utils.DeleteLearnedDataCancel):] + } else { + if !strings.HasPrefix(customId, utils.DeleteLearnedDataUserId) { + return false + } + + userId = customId[len(utils.DeleteLearnedDataUserId):] + } + + if i.Member.User.ID != userId { + s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Flags: discordgo.MessageFlagsEphemeral, + Embeds: []*discordgo.MessageEmbed{ + { + Title: "❌ 오류", + Description: "당신은 해당 권한이 없ㅇ어요.", + Color: int(utils.EFail), + }, + }, + Components: []discordgo.MessageComponent{}, + }, + }) + return false + } + return true + }, + Run: func(ctx *commands.ComponentContext) { + i := ctx.Inter + s := ctx.Session + + s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseDeferredMessageUpdate, + }) + + id, _ := bson.ObjectIDFromHex(strings.ReplaceAll(utils.ItemIdRegexp.ReplaceAllString(i.MessageComponentData().Values[0][len(utils.DeleteLearnedData):], ""), "&", "")) + itemId := strings.ReplaceAll(utils.ItemIdRegexp.FindAllString(i.MessageComponentData().Values[0], 1)[0], "No.", "") + + databases.Learns.DeleteOne(context.TODO(), bson.D{{Key: "_id", Value: id}}) + + s.InteractionResponseEdit(i.Interaction, &discordgo.WebhookEdit{ + Embeds: &[]*discordgo.MessageEmbed{ + { + Title: "✅ 삭제 완료", + Description: itemId + "번을 삭ㅈ제했어요.", + }, + }, + Components: &[]discordgo.MessageComponent{}, + }) + }, +} diff --git a/handler/interactionCreate.go b/handler/interactionCreate.go index e24c51d..2152bd8 100644 --- a/handler/interactionCreate.go +++ b/handler/interactionCreate.go @@ -9,5 +9,7 @@ func InteractionCreate(s *discordgo.Session, i *discordgo.InteractionCreate) { if i.Type == discordgo.InteractionApplicationCommand { commands.Discommand.ChatInputRun(i.ApplicationCommandData().Name, s, i) return + } else if i.Type == discordgo.InteractionMessageComponent { + commands.Discommand.ComponentRun(s, i) } } diff --git a/main.go b/main.go index b03ec27..6f24788 100644 --- a/main.go +++ b/main.go @@ -9,6 +9,7 @@ import ( "syscall" "git.wh64.net/muffin/goMuffin/commands" + "git.wh64.net/muffin/goMuffin/components" "git.wh64.net/muffin/goMuffin/configs" "git.wh64.net/muffin/goMuffin/databases" "git.wh64.net/muffin/goMuffin/handler" @@ -30,6 +31,9 @@ func main() { go commands.Discommand.LoadCommand(commands.LearnCommand) go commands.Discommand.LoadCommand(commands.LearnedDataListCommand) go commands.Discommand.LoadCommand(commands.InformationCommand) + go commands.Discommand.LoadCommand(commands.DeleteLearnedDataCommand) + + go commands.Discommand.LoadComponent(components.DeleteLearnedDataComponent) go dg.AddHandler(handler.MessageCreate) go dg.AddHandler(handler.InteractionCreate) diff --git a/utils/customIds.go b/utils/customIds.go new file mode 100644 index 0000000..6544d26 --- /dev/null +++ b/utils/customIds.go @@ -0,0 +1,7 @@ +package utils + +const ( + DeleteLearnedData = "#muffin/deleteLearnedData$" + DeleteLearnedDataUserId = "#muffin/deleteLearnedData@" + DeleteLearnedDataCancel = "#muffin/deleteLearnedData/cancel@" +) diff --git a/utils/regexp.go b/utils/regexp.go index 1cd2002..d2279e3 100644 --- a/utils/regexp.go +++ b/utils/regexp.go @@ -4,3 +4,4 @@ import "regexp" var FlexibleStringParser *regexp.Regexp = regexp.MustCompile("[^\\s\"'「」«»]+|\"([^\"]*)\"|'([^']*)'|「([^」]*)」|«([^»]*)»") var Decimals *regexp.Regexp = regexp.MustCompile(`\d+`) +var ItemIdRegexp *regexp.Regexp = regexp.MustCompile(`No.\d+`)