From d3ba8fbbf1c85140684a83765ca134442cb027b1 Mon Sep 17 00:00:00 2001
From: Siwoo Jeon <me@migan.co.kr>
Date: Sat, 5 Apr 2025 14:04:54 +0900
Subject: [PATCH] feat: Add interaction's reply method

---
 commands/dataLength.go          | 22 ++++---------
 commands/deleteLearnedData.go   | 27 ++++++----------
 commands/discommand.go          | 21 ++++++++++---
 commands/help.go                | 28 ++++++-----------
 commands/information.go         |  9 ++----
 commands/learn.go               | 34 +++++++-------------
 commands/learnedDataList.go     | 21 +++++--------
 components/deleteLearnedData.go | 30 +++++++-----------
 configs/version.go              |  2 +-
 utils/interactions.go           | 56 +++++++++++++++++++++++++++++++++
 10 files changed, 133 insertions(+), 117 deletions(-)
 create mode 100644 utils/interactions.go

diff --git a/commands/dataLength.go b/commands/dataLength.go
index d5870a2..6b025fe 100644
--- a/commands/dataLength.go
+++ b/commands/dataLength.go
@@ -66,8 +66,6 @@ func getLength(data dataType, coll *mongo.Collection, filter bson.D) {
 }
 
 func dataLengthRun(s *discordgo.Session, m any) {
-	var i *discordgo.Interaction
-	var referance *discordgo.MessageReference
 	var username, userId, channelId string
 	var textLength,
 		muffinLength,
@@ -80,19 +78,11 @@ func dataLengthRun(s *discordgo.Session, m any) {
 		username = m.Author.Username
 		userId = m.Author.ID
 		channelId = m.ChannelID
-		referance = m.Reference()
-	case *discordgo.InteractionCreate:
+	case *utils.InteractionCreate:
+		m.DeferReply(true)
 		username = m.Member.User.Username
 		userId = m.Member.User.ID
 		channelId = m.ChannelID
-		i = m.Interaction
-		s.InteractionRespond(i,
-			&discordgo.InteractionResponse{
-				Type: discordgo.InteractionResponseDeferredChannelMessageWithSource,
-				Data: &discordgo.InteractionResponseData{
-					Flags: discordgo.MessageFlagsEphemeral,
-				},
-			})
 	}
 
 	go getLength(text, databases.Texts, bson.D{{}})
@@ -161,11 +151,11 @@ func dataLengthRun(s *discordgo.Session, m any) {
 		},
 	}
 
-	switch m.(type) {
+	switch m := m.(type) {
 	case *discordgo.MessageCreate:
-		s.ChannelMessageSendEmbedReply(channelId, embed, referance)
-	case *discordgo.InteractionCreate:
-		s.InteractionResponseEdit(i, &discordgo.WebhookEdit{
+		s.ChannelMessageSendEmbedReply(channelId, embed, m.Reference())
+	case *utils.InteractionCreate:
+		m.EditReply(&discordgo.WebhookEdit{
 			Embeds: &[]*discordgo.MessageEmbed{embed},
 		})
 	}
diff --git a/commands/deleteLearnedData.go b/commands/deleteLearnedData.go
index 533d67e..465861e 100644
--- a/commands/deleteLearnedData.go
+++ b/commands/deleteLearnedData.go
@@ -67,19 +67,10 @@ func deleteLearnedDataRun(c *Command, s *discordgo.Session, m any, args *[]strin
 				Color: utils.EmbedFail,
 			}, m.Reference())
 		}
-	case *discordgo.InteractionCreate:
-		s.InteractionRespond(m.Interaction, &discordgo.InteractionResponse{
-			Type: discordgo.InteractionResponseDeferredChannelMessageWithSource,
-			Data: &discordgo.InteractionResponseData{
-				Flags: discordgo.MessageFlagsEphemeral,
-			},
-		})
+	case *utils.InteractionCreate:
+		m.DeferReply(true)
 
-		optsMap := map[string]*discordgo.ApplicationCommandInteractionDataOption{}
-		for _, opt := range m.ApplicationCommandData().Options {
-			optsMap[opt.Name] = opt
-		}
-		if opt, ok := optsMap["단어"]; ok {
+		if opt, ok := m.Options["단어"]; ok {
 			command = opt.StringValue()
 		}
 		userId = m.Member.User.ID
@@ -96,8 +87,8 @@ func deleteLearnedDataRun(c *Command, s *discordgo.Session, m any, args *[]strin
 			switch m := m.(type) {
 			case *discordgo.MessageCreate:
 				s.ChannelMessageSendEmbedReply(m.ChannelID, embed, m.Reference())
-			case *discordgo.InteractionCreate:
-				s.InteractionResponseEdit(m.Interaction, &discordgo.WebhookEdit{
+			case *utils.InteractionCreate:
+				m.EditReply(&discordgo.WebhookEdit{
 					Embeds: &[]*discordgo.MessageEmbed{embed},
 				})
 			}
@@ -108,8 +99,8 @@ func deleteLearnedDataRun(c *Command, s *discordgo.Session, m any, args *[]strin
 		switch m := m.(type) {
 		case *discordgo.MessageCreate:
 			s.ChannelMessageSendEmbedReply(m.ChannelID, embed, m.Reference())
-		case *discordgo.InteractionCreate:
-			s.InteractionResponseEdit(m.Interaction, &discordgo.WebhookEdit{
+		case *utils.InteractionCreate:
+			m.EditReply(&discordgo.WebhookEdit{
 				Embeds: &[]*discordgo.MessageEmbed{embed},
 			})
 		}
@@ -165,8 +156,8 @@ func deleteLearnedDataRun(c *Command, s *discordgo.Session, m any, args *[]strin
 			Components: components,
 			Reference:  m.Reference(),
 		})
-	case *discordgo.InteractionCreate:
-		s.InteractionResponseEdit(m.Interaction, &discordgo.WebhookEdit{
+	case *utils.InteractionCreate:
+		m.EditReply(&discordgo.WebhookEdit{
 			Embeds:     &[]*discordgo.MessageEmbed{embed},
 			Components: &components,
 		})
diff --git a/commands/discommand.go b/commands/discommand.go
index 9685068..b17e5d7 100644
--- a/commands/discommand.go
+++ b/commands/discommand.go
@@ -3,6 +3,7 @@ package commands
 import (
 	"sync"
 
+	"git.wh64.net/muffin/goMuffin/utils"
 	"github.com/bwmarrin/discordgo"
 )
 
@@ -42,13 +43,13 @@ type MsgContext struct {
 
 type ChatInputContext struct {
 	Session *discordgo.Session
-	Inter   *discordgo.InteractionCreate
+	Inter   *utils.InteractionCreate
 	Command *Command
 }
 
 type ComponentContext struct {
 	Session   *discordgo.Session
-	Inter     *discordgo.InteractionCreate
+	Inter     *utils.InteractionCreate
 	Component *Component
 }
 
@@ -104,16 +105,26 @@ func (d *DiscommandStruct) ChatInputRun(name string, s *discordgo.Session, i *di
 	if command == nil {
 		return
 	}
-	command.ChatInputRun(&ChatInputContext{s, i, command})
+	command.ChatInputRun(&ChatInputContext{s, &utils.InteractionCreate{
+		InteractionCreate: i,
+		Session:           s,
+		Options:           utils.GetInteractionOptions(i),
+	}, command})
 }
 
 func (d *DiscommandStruct) ComponentRun(s *discordgo.Session, i *discordgo.InteractionCreate) {
 	for _, c := range d.Components {
-		if (!c.Parse(&ComponentContext{s, i, c})) {
+		if (!c.Parse(&ComponentContext{s, &utils.InteractionCreate{
+			InteractionCreate: i,
+			Session:           s,
+		}, c})) {
 			continue
 		}
 
-		c.Run(&ComponentContext{s, i, c})
+		c.Run(&ComponentContext{s, &utils.InteractionCreate{
+			InteractionCreate: i,
+			Session:           s,
+		}, c})
 		break
 	}
 }
diff --git a/commands/help.go b/commands/help.go
index 7a35c68..f80a52e 100644
--- a/commands/help.go
+++ b/commands/help.go
@@ -72,13 +72,11 @@ func helpRun(c *Command, s *discordgo.Session, m any, args *[]string) {
 	switch m := m.(type) {
 	case *discordgo.MessageCreate:
 		commandName = Discommand.Aliases[strings.Join(*args, " ")]
-	case *discordgo.InteractionCreate:
-		optsMap := map[string]*discordgo.ApplicationCommandInteractionDataOption{}
-		for _, opt := range m.ApplicationCommandData().Options {
-			optsMap[opt.Name] = opt
-		}
-		if opt, ok := optsMap["명령어"]; ok {
+	case *utils.InteractionCreate:
+		if opt, ok := m.Options["도움말"]; ok {
 			commandName = opt.StringValue()
+		} else {
+			commandName = ""
 		}
 	}
 
@@ -94,12 +92,9 @@ func helpRun(c *Command, s *discordgo.Session, m any, args *[]string) {
 		switch m := m.(type) {
 		case *discordgo.MessageCreate:
 			s.ChannelMessageSendEmbedReply(m.ChannelID, embed, m.Reference())
-		case *discordgo.InteractionCreate:
-			s.InteractionRespond(m.Interaction, &discordgo.InteractionResponse{
-				Type: discordgo.InteractionResponseChannelMessageWithSource,
-				Data: &discordgo.InteractionResponseData{
-					Embeds: []*discordgo.MessageEmbed{embed},
-				},
+		case *utils.InteractionCreate:
+			m.Reply(&discordgo.InteractionResponseData{
+				Embeds: []*discordgo.MessageEmbed{embed},
 			})
 		}
 		return
@@ -148,12 +143,9 @@ func helpRun(c *Command, s *discordgo.Session, m any, args *[]string) {
 	switch m := m.(type) {
 	case *discordgo.MessageCreate:
 		s.ChannelMessageSendEmbedReply(m.ChannelID, embed, m.Reference())
-	case *discordgo.InteractionCreate:
-		s.InteractionRespond(m.Interaction, &discordgo.InteractionResponse{
-			Type: discordgo.InteractionResponseChannelMessageWithSource,
-			Data: &discordgo.InteractionResponseData{
-				Embeds: []*discordgo.MessageEmbed{embed},
-			},
+	case *utils.InteractionCreate:
+		m.Reply(&discordgo.InteractionResponseData{
+			Embeds: []*discordgo.MessageEmbed{embed},
 		})
 	}
 }
diff --git a/commands/information.go b/commands/information.go
index c1a12fb..598737c 100644
--- a/commands/information.go
+++ b/commands/information.go
@@ -63,12 +63,9 @@ func informationRun(s *discordgo.Session, m any) {
 	switch m := m.(type) {
 	case *discordgo.MessageCreate:
 		s.ChannelMessageSendEmbedReply(m.ChannelID, embed, m.Reference())
-	case *discordgo.InteractionCreate:
-		s.InteractionRespond(m.Interaction, &discordgo.InteractionResponse{
-			Type: discordgo.InteractionResponseChannelMessageWithSource,
-			Data: &discordgo.InteractionResponseData{
-				Embeds: []*discordgo.MessageEmbed{embed},
-			},
+	case *utils.InteractionCreate:
+		m.Reply(&discordgo.InteractionResponseData{
+			Embeds: []*discordgo.MessageEmbed{embed},
 		})
 	}
 }
diff --git a/commands/learn.go b/commands/learn.go
index 7c98133..6003e25 100644
--- a/commands/learn.go
+++ b/commands/learn.go
@@ -107,26 +107,16 @@ func learnRun(c *Command, s *discordgo.Session, m any, args *[]string) {
 
 		command = strings.ReplaceAll((*args)[0], "_", " ")
 		result = strings.ReplaceAll((*args)[1], "_", " ")
-	case *discordgo.InteractionCreate:
-		s.InteractionRespond(m.Interaction, &discordgo.InteractionResponse{
-			Type: discordgo.InteractionResponseDeferredChannelMessageWithSource,
-			Data: &discordgo.InteractionResponseData{
-				Flags: discordgo.MessageFlagsEphemeral,
-			},
-		})
+	case *utils.InteractionCreate:
+		m.DeferReply(true)
 
 		userId = m.Member.User.ID
 
-		optsMap := map[string]*discordgo.ApplicationCommandInteractionDataOption{}
-		for _, opt := range m.ApplicationCommandData().Options {
-			optsMap[opt.Name] = opt
-		}
-
-		if opt, ok := optsMap["단어"]; ok {
+		if opt, ok := m.Options["단어"]; ok {
 			command = opt.StringValue()
 		}
 
-		if opt, ok := optsMap["대답"]; ok {
+		if opt, ok := m.Options["대답"]; ok {
 			result = opt.StringValue()
 		}
 	}
@@ -156,8 +146,8 @@ func learnRun(c *Command, s *discordgo.Session, m any, args *[]string) {
 			switch m := m.(type) {
 			case *discordgo.MessageCreate:
 				s.ChannelMessageSendEmbedReply(m.ChannelID, embed, m.Reference())
-			case *discordgo.InteractionCreate:
-				s.InteractionResponseEdit(m.Interaction, &discordgo.WebhookEdit{
+			case *utils.InteractionCreate:
+				m.EditReply(&discordgo.WebhookEdit{
 					Embeds: &[]*discordgo.MessageEmbed{embed},
 				})
 			}
@@ -176,8 +166,8 @@ func learnRun(c *Command, s *discordgo.Session, m any, args *[]string) {
 			switch m := m.(type) {
 			case *discordgo.MessageCreate:
 				s.ChannelMessageSendEmbedReply(m.ChannelID, embed, m.Reference())
-			case *discordgo.InteractionCreate:
-				s.InteractionResponseEdit(m.Interaction, &discordgo.WebhookEdit{
+			case *utils.InteractionCreate:
+				m.EditReply(&discordgo.WebhookEdit{
 					Embeds: &[]*discordgo.MessageEmbed{embed},
 				})
 			}
@@ -201,8 +191,8 @@ func learnRun(c *Command, s *discordgo.Session, m any, args *[]string) {
 		switch m := m.(type) {
 		case *discordgo.MessageCreate:
 			s.ChannelMessageSendEmbedReply(m.ChannelID, embed, m.Reference())
-		case *discordgo.InteractionCreate:
-			s.InteractionResponseEdit(m.Interaction, &discordgo.WebhookEdit{
+		case *utils.InteractionCreate:
+			m.EditReply(&discordgo.WebhookEdit{
 				Embeds: &[]*discordgo.MessageEmbed{embed},
 			})
 		}
@@ -218,8 +208,8 @@ func learnRun(c *Command, s *discordgo.Session, m any, args *[]string) {
 	switch m := m.(type) {
 	case *discordgo.MessageCreate:
 		s.ChannelMessageSendEmbedReply(m.ChannelID, embed, m.Reference())
-	case *discordgo.InteractionCreate:
-		s.InteractionResponseEdit(m.Interaction, &discordgo.WebhookEdit{
+	case *utils.InteractionCreate:
+		m.EditReply(&discordgo.WebhookEdit{
 			Embeds: &[]*discordgo.MessageEmbed{embed},
 		})
 	}
diff --git a/commands/learnedDataList.go b/commands/learnedDataList.go
index cecde6b..6558203 100644
--- a/commands/learnedDataList.go
+++ b/commands/learnedDataList.go
@@ -47,13 +47,8 @@ func learnedDataListRun(s *discordgo.Session, m any) {
 		userId = m.Author.ID
 		globalName = m.Author.GlobalName
 		avatarUrl = m.Author.AvatarURL("512")
-	case *discordgo.InteractionCreate:
-		s.InteractionRespond(m.Interaction, &discordgo.InteractionResponse{
-			Type: discordgo.InteractionResponseDeferredChannelMessageWithSource,
-			Data: &discordgo.InteractionResponseData{
-				Flags: discordgo.MessageFlagsEphemeral,
-			},
-		})
+	case *utils.InteractionCreate:
+		m.DeferReply(true)
 
 		userId = m.Member.User.ID
 		globalName = m.Member.User.GlobalName
@@ -72,8 +67,8 @@ func learnedDataListRun(s *discordgo.Session, m any) {
 			switch m := m.(type) {
 			case *discordgo.MessageCreate:
 				s.ChannelMessageSendEmbedReply(m.ChannelID, embed, m.Reference())
-			case *discordgo.InteractionCreate:
-				s.InteractionResponseEdit(m.Interaction, &discordgo.WebhookEdit{
+			case *utils.InteractionCreate:
+				m.EditReply(&discordgo.WebhookEdit{
 					Embeds: &[]*discordgo.MessageEmbed{embed},
 				})
 			}
@@ -90,8 +85,8 @@ func learnedDataListRun(s *discordgo.Session, m any) {
 		switch m := m.(type) {
 		case *discordgo.MessageCreate:
 			s.ChannelMessageSendEmbedReply(m.ChannelID, embed, m.Reference())
-		case *discordgo.InteractionCreate:
-			s.InteractionResponseEdit(m.Interaction, &discordgo.WebhookEdit{
+		case *utils.InteractionCreate:
+			m.EditReply(&discordgo.WebhookEdit{
 				Embeds: &[]*discordgo.MessageEmbed{embed},
 			})
 		}
@@ -114,8 +109,8 @@ func learnedDataListRun(s *discordgo.Session, m any) {
 	switch m := m.(type) {
 	case *discordgo.MessageCreate:
 		s.ChannelMessageSendEmbedReply(m.ChannelID, embed, m.Reference())
-	case *discordgo.InteractionCreate:
-		s.InteractionResponseEdit(m.Interaction, &discordgo.WebhookEdit{
+	case *utils.InteractionCreate:
+		m.EditReply(&discordgo.WebhookEdit{
 			Embeds: &[]*discordgo.MessageEmbed{embed},
 		})
 	}
diff --git a/components/deleteLearnedData.go b/components/deleteLearnedData.go
index 16855ac..0d4db28 100644
--- a/components/deleteLearnedData.go
+++ b/components/deleteLearnedData.go
@@ -16,7 +16,6 @@ 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 {
@@ -34,38 +33,33 @@ var DeleteLearnedDataComponent *commands.Component = &commands.Component{
 		}
 
 		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.EmbedFail),
-						},
+			i.Reply(&discordgo.InteractionResponseData{
+				Flags: discordgo.MessageFlagsEphemeral,
+				Embeds: []*discordgo.MessageEmbed{
+					{
+						Title:       "❌ 오류",
+						Description: "당신은 해당 권한이 없ㅇ어요.",
+						Color:       int(utils.EmbedFail),
 					},
-					Components: []discordgo.MessageComponent{},
 				},
-			})
+				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,
-		})
+		i.DeferUpdate()
 
 		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{
+		i.EditReply(&discordgo.WebhookEdit{
 			Embeds: &[]*discordgo.MessageEmbed{
 				{
 					Title:       "✅ 삭제 완료",
diff --git a/configs/version.go b/configs/version.go
index 23e7391..87d1ce4 100644
--- a/configs/version.go
+++ b/configs/version.go
@@ -7,7 +7,7 @@ import (
 	"git.wh64.net/muffin/goMuffin/utils"
 )
 
-const MUFFIN_VERSION = "5.0.0-gopher_dev.250404a"
+const MUFFIN_VERSION = "5.0.0-gopher_preview.250405a"
 
 var updatedString string = utils.Decimals.FindAllStringSubmatch(MUFFIN_VERSION, -1)[3][0]
 
diff --git a/utils/interactions.go b/utils/interactions.go
new file mode 100644
index 0000000..69c1955
--- /dev/null
+++ b/utils/interactions.go
@@ -0,0 +1,56 @@
+package utils
+
+import "github.com/bwmarrin/discordgo"
+
+// InteractionCreate custom data of discordgo.InteractionCreate
+type InteractionCreate struct {
+	*discordgo.InteractionCreate
+	Session *discordgo.Session
+	// NOTE: It's only can ApplicationCommand
+	Options map[string]*discordgo.ApplicationCommandInteractionDataOption
+}
+
+// Reply to this interaction.
+func (i *InteractionCreate) Reply(data *discordgo.InteractionResponseData) {
+	i.Session.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
+		Type: discordgo.InteractionResponseChannelMessageWithSource,
+		Data: data,
+	})
+}
+
+// GetInteractionOptions to this interaction.
+// NOTE: It's only can ApplicationCommand
+func GetInteractionOptions(i *discordgo.InteractionCreate) map[string]*discordgo.ApplicationCommandInteractionDataOption {
+	optsMap := map[string]*discordgo.ApplicationCommandInteractionDataOption{}
+	for _, opt := range i.ApplicationCommandData().Options {
+		optsMap[opt.Name] = opt
+	}
+	return optsMap
+}
+
+// DeferReply to this interaction.
+func (i *InteractionCreate) DeferReply(ephemeral bool) {
+	var flags discordgo.MessageFlags
+	if ephemeral {
+		flags = discordgo.MessageFlagsEphemeral
+	}
+
+	i.Session.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
+		Type: discordgo.InteractionResponseDeferredChannelMessageWithSource,
+		Data: &discordgo.InteractionResponseData{
+			Flags: flags,
+		},
+	})
+}
+
+// DeferUpdate to this interaction.
+func (i *InteractionCreate) DeferUpdate() {
+	i.Session.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
+		Type: discordgo.InteractionResponseDeferredMessageUpdate,
+	})
+}
+
+// EditReply to this interaction.
+func (i *InteractionCreate) EditReply(data *discordgo.WebhookEdit) {
+	i.Session.InteractionResponseEdit(i.Interaction, data)
+}