Compare commits

...

71 commits

Author SHA1 Message Date
0c8950819b
feat: Add delete chat 2025-07-14 21:36:18 +09:00
c7b548a9c5
feat: Add canceled container 2025-07-14 21:34:53 +09:00
d83823ffa6
fix: customId 2025-07-14 21:34:28 +09:00
ff32a29bc4
typo: remove wrong space 2025-07-14 21:08:17 +09:00
b911c006c6
fix: button style 2025-07-14 19:23:44 +09:00
0dc264c547
fix: fucking moron code 2025-07-14 19:19:03 +09:00
1b9e4714f7
rename: dev commands 2025-07-13 16:03:33 +09:00
18a373f083
feat: add component select chat 2025-07-12 23:59:27 +09:00
26c96e5637
format: fix code style 2025-07-12 23:55:12 +09:00
6a78106136
feat: add chat command 2025-07-12 17:24:58 +09:00
76cacdd660
fix: database filter 2025-07-08 20:50:40 +09:00
ef7ab396b5
fix: add load (un)block command 2025-07-07 18:59:55 +09:00
cbe77be8d3
rename: fix developer only commands' file name prefix (_ -> dev) 2025-07-07 18:58:39 +09:00
2d4a28e1e2
rename: developer only commands' file name 2025-07-07 18:50:10 +09:00
48c9d643c7
feat: blocked user 2025-07-07 18:49:16 +09:00
3dbc32ee7b
feat: add unblock command 2025-07-05 14:52:56 +09:00
ce6a242a24
feat: block command 2025-07-03 23:06:07 +09:00
2f655a1384
refactor: use omitempty 2025-07-02 14:08:56 +09:00
5f5c14df7e
fix: error handling (maybe) 2025-07-02 13:59:44 +09:00
ee6b2712c3
feat: add learn response in ai response 2025-06-21 16:30:34 +09:00
4e98143c61
fix: muffin response 2025-06-21 16:21:20 +09:00
11241231c9
fix: block unregistered user's use muffin bot 2025-06-21 10:05:18 +09:00
3997617519
feat:Add ephemeral 2025-06-21 10:04:03 +09:00
294511cf46
refactor: use switch 2025-06-21 09:59:28 +09:00
631111165a
docs!: move tos to https://github.com/Muffin-laboratory/terms 2025-06-20 19:32:39 +09:00
9cdd55d29a
feat: add privacy policy and ToS 2025-06-20 19:23:02 +09:00
5f20680997
fix: stupid code 2025-06-20 18:40:51 +09:00
c98dcce7b2
feat: Add deregister command 2025-06-20 18:37:26 +09:00
eb991797d7
fix: Add ephemeral 2025-06-19 22:39:29 +09:00
05f7bd07b7
feat: Add register command 2025-06-19 21:12:07 +09:00
71fa0b506a
feat: Add command flags 2025-06-11 18:53:45 +09:00
c34f88f5bc
Merge branch 'feature/muffinAI' into canary 2025-06-11 18:06:09 +09:00
d0ff7bcccf
fix: prompt 2025-06-05 22:45:08 +09:00
4228adf7f3
fix: memory 2025-06-04 23:07:02 +09:00
3755e58ec7
feat: Add detect user 2025-06-04 22:43:14 +09:00
a933939e45
fix: parse error 2025-06-04 22:40:49 +09:00
52f183e449
feat: Add chat with slash command 2025-06-02 22:47:13 +09:00
7b0a35fbf6
feat: memory 2025-06-01 17:03:28 +09:00
afbab98a6b
fix: help command error 2025-06-01 13:58:42 +09:00
ec429b5c6e
feat: Add switch mode command 2025-06-01 13:52:12 +09:00
bd2f1dfc06
feat: Add reload prompt command 2025-06-01 13:23:05 +09:00
a394624ac5
fix: print err 2025-06-01 12:31:31 +09:00
8205d85303
feat: Add response with ai 2025-05-31 22:53:44 +09:00
4ee2f4fe70
fix: change ui for list command 2025-05-31 19:11:04 +09:00
3dc47791b4
feat: add AI answer 2025-05-30 17:13:56 +09:00
3ddc933abc
fix: config parsing error 2025-05-27 22:01:57 +09:00
08a1e82125
fix: reply chat 2025-05-26 22:57:14 +09:00
7b64c3eea0
Merge branch 'hotfix/train' into feature/muffinAI 2025-05-26 22:53:22 +09:00
ec410b9f81
feat: rewrite chatbot 2025-05-26 22:46:37 +09:00
3fcc446cce
feat: add file type fine tune 2025-05-25 17:32:44 +09:00
b6e9350e73
fix: list error container 2025-05-25 17:11:50 +09:00
9bfc53bf37
fix: delete error container 2025-05-25 16:49:20 +09:00
d41e74bb55
feat: recreated delete learn data command 2025-05-24 23:44:37 +09:00
fbef008f4f
fix: pagination embed 2025-05-24 23:33:02 +09:00
4cf4b42989
feat: learn command componentsV2 2025-05-24 16:31:49 +09:00
3a5cfd6a58
feat: data length command componentsV2 2025-05-24 16:10:51 +09:00
6468521ee1
feat: Add help command componentsV2 2025-05-24 15:52:27 +09:00
6fd272ef5f
chore: add AddPrefix method 2025-05-24 15:52:16 +09:00
35347edb85
feat: information command componentsV2 2025-05-24 14:33:28 +09:00
b6ac950f2e
feat!: change to PaginationEmbed embed to ComponentsV2's container 2025-05-23 23:33:19 +09:00
218c019989
fix: edit type deferReply argument 2025-05-23 23:31:34 +09:00
0fdfd1cbd7
fix: edit type editReply argument 2025-05-23 23:28:37 +09:00
aaf5246218
fix: showModal 2025-05-23 23:22:49 +09:00
15fe432feb
chore: remove unused dependencies 2025-05-21 19:45:54 +09:00
0f911f58be
remove: database migrate 2025-05-19 19:53:20 +09:00
fb58599991
fix: edit next, prev 2025-05-19 19:44:45 +09:00
7390cc31ed
feat: add AllowedMentions 2025-05-18 17:57:32 +09:00
20c0debf4a
chore: use init 2025-05-18 15:02:51 +09:00
797bd56b3d
fix!: remove AddEmbed, AddComponent and add AddEmbeds, AddComponents 2025-05-17 22:04:05 +09:00
5f0bb32e9b
fix!: remove deprecated value in MuffinConfig 2025-05-17 21:12:14 +09:00
63a820f946
feat: MessageSender 2025-05-17 15:09:42 +09:00
51 changed files with 2802 additions and 1381 deletions

View file

@ -12,13 +12,18 @@ DATABASE_AUTH_SOURCE= # 기본 값: admin
## 필수 값
DATABASE_NAME=
## 데이터베이스 마이그레이션용
PREVIOUS_DATABASE_URL=
# 봇
BOT_TOKEN=
BOT_PREFIX=
BOT_OWNER_ID=
# 학습 (필수 아님)
TRAIN_USER_ID=
# 서비스
SERVICE_PRIVACY_POLICY_URL=
SERVICE_TERM_OF_SERVICE_URL=
# 챗봇
CHATBOT_GEMINI_TOKEN=
CHATBOT_GEMINI_PROMPT_PATH=
CHATBOT_GEMINI_MODEL= # 기본 값은 gemini-2.5-flash
CHATBOT_TRAIN_USER_ID= # 필수값 아님

4
.gitignore vendored
View file

@ -127,4 +127,6 @@ $RECYCLE.BIN/
!.env.example
export/
data/
data/
*prompt.txt

201
chatbot/chatbot.go Normal file
View file

@ -0,0 +1,201 @@
package chatbot
import (
"context"
"fmt"
"log"
"math/rand"
"git.wh64.net/muffin/goMuffin/configs"
"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"
"google.golang.org/genai"
)
type Chatbot struct {
Mode ChatbotMode
Gemini *genai.Client
systemPrompt string
s *discordgo.Session
}
var ChatBot *Chatbot
func New(s *discordgo.Session) error {
gemini, err := genai.NewClient(context.TODO(), &genai.ClientConfig{
APIKey: configs.Config.Chatbot.Gemini.Token,
Backend: genai.BackendGeminiAPI,
})
if err != nil {
return err
}
ChatBot = &Chatbot{
Mode: ChatbotAI,
Gemini: gemini,
s: s,
}
prompt, err := loadPrompt()
if err != nil {
return err
}
ChatBot.systemPrompt = prompt
return nil
}
func (c *Chatbot) SetMode(mode ChatbotMode) *Chatbot {
c.Mode = mode
return c
}
func (c *Chatbot) SwitchMode() *Chatbot {
switch c.Mode {
case ChatbotAI:
c.SetMode(ChatbotMuffin)
case ChatbotMuffin:
c.SetMode(ChatbotAI)
}
return c
}
func (c *Chatbot) ModeString() string {
switch c.Mode {
case ChatbotAI:
return "AI모드"
case ChatbotMuffin:
return "머핀 모드"
default:
return "알 수 없음"
}
}
func (c *Chatbot) ReloadPrompt() error {
prompt, err := loadPrompt()
if err != nil {
return err
}
c.systemPrompt = prompt
return nil
}
func getMuffinResponse(s *discordgo.Session, question string) (string, error) {
var learnData []databases.Learn
var data []databases.Text
var result string
x := rand.Intn(10)
muffinCur, err := databases.Database.Texts.Find(context.TODO(), bson.D{{Key: "persona", Value: "muffin"}})
if err != nil {
return "살려주ㅅ세요", err
}
learnCur, err := databases.Database.Learns.Find(context.TODO(), bson.D{{Key: "command", Value: question}})
if err != nil {
return "살려주ㅅ세요", err
}
defer muffinCur.Close(context.TODO())
defer learnCur.Close(context.TODO())
err = muffinCur.All(context.TODO(), &data)
if err != nil {
return "살려주ㅅ세요", err
}
err = learnCur.All(context.TODO(), &learnData)
if err != nil {
return "살려주ㅅ세요", err
}
if x > 2 && len(learnData) != 0 {
data := learnData[rand.Intn(len(learnData))]
user, _ := s.User(data.UserId)
result =
fmt.Sprintf("%s\n%s", data.Result, utils.InlineCode(fmt.Sprintf("%s님이 알려주셨어요.", user.Username)))
} else {
result = data[rand.Intn(len(data))].Text
}
return result, nil
}
func getAIResponse(s *discordgo.Session, c *Chatbot, user *discordgo.User, question string) (string, error) {
var data []databases.Learn
var dbUser databases.User
x := rand.Intn(10)
cur, err := databases.Database.Learns.Find(context.TODO(), bson.D{{Key: "command", Value: question}})
if err != nil {
return "살려주ㅅ세요", err
}
defer cur.Close(context.TODO())
cur.All(context.TODO(), &data)
if x == 10 && len(data) != 0 {
data := data[rand.Intn(len(data))]
user, _ := s.User(data.UserId)
return fmt.Sprintf("%s\n%s", data.Result, utils.InlineCode(fmt.Sprintf("%s님이 알려주셨어요.", user.Username))), nil
}
err = databases.Database.Users.FindOne(context.TODO(), databases.User{UserId: user.ID}).Decode(&dbUser)
if err != nil {
return "살려주ㅅ세요", err
}
err = databases.Database.Chats.FindOne(context.TODO(), databases.Chat{UserId: user.ID}).Err()
if err != nil {
if err == mongo.ErrNoDocuments {
_, err = databases.CreateChat(user.ID, "새로운 채팅")
if err != nil {
return "살려주ㅅ세요", err
}
}
return "살려주ㅅ세요", err
}
contents, err := GetMemory(dbUser.ChatId)
if err != nil {
ChatBot.Mode = ChatbotMuffin
return "AI에 문제가 생겼ㅇ어요.", err
}
contents = append(contents, genai.NewContentFromText(question, genai.RoleUser))
result, err := ChatBot.Gemini.Models.GenerateContent(context.TODO(), configs.Config.Chatbot.Gemini.Model, contents, &genai.GenerateContentConfig{
SystemInstruction: genai.NewContentFromText(makePrompt(c.systemPrompt, user), genai.RoleUser),
})
if err != nil {
ChatBot.Mode = ChatbotMuffin
return "AI에 문제가 생겼ㅇ어요.", err
}
resultText := result.Text()
err = SaveMemory(&databases.Memory{
UserId: user.ID,
Content: question,
Answer: resultText,
ChatId: dbUser.ChatId,
})
if err != nil {
return "살려주ㅅ세요", err
}
log.Printf("%s TOKEN: %d", user.ID, result.UsageMetadata.PromptTokenCount)
return resultText, nil
}
func (c *Chatbot) GetResponse(user *discordgo.User, question string) (string, error) {
switch c.Mode {
case ChatbotMuffin:
return getMuffinResponse(c.s, question)
default:
return getAIResponse(c.s, c, user, question)
}
}

8
chatbot/enum.go Normal file
View file

@ -0,0 +1,8 @@
package chatbot
type ChatbotMode int
const (
ChatbotAI ChatbotMode = iota
ChatbotMuffin
)

39
chatbot/memory.go Normal file
View file

@ -0,0 +1,39 @@
package chatbot
import (
"context"
"git.wh64.net/muffin/goMuffin/databases"
"go.mongodb.org/mongo-driver/v2/bson"
"google.golang.org/genai"
)
func SaveMemory(data *databases.Memory) error {
_, err := databases.Database.Memory.InsertOne(context.TODO(), *data)
return err
}
func GetMemory(chatId bson.ObjectID) ([]*genai.Content, error) {
var data []databases.Memory
memory := []*genai.Content{}
cur, err := databases.Database.Memory.Find(context.TODO(), databases.User{ChatId: chatId})
if err != nil {
return memory, err
}
err = cur.All(context.TODO(), &data)
if err != nil {
return memory, err
}
for _, data := range data {
memory = append(memory,
genai.NewContentFromText(data.Content, genai.RoleUser),
genai.NewContentFromText(data.Answer, genai.RoleModel),
)
}
return memory, nil
}

42
chatbot/parser.go Normal file
View file

@ -0,0 +1,42 @@
package chatbot
import (
"strings"
"time"
"git.wh64.net/muffin/goMuffin/configs"
"git.wh64.net/muffin/goMuffin/utils"
"github.com/bwmarrin/discordgo"
)
func ParseResult(content string, s *discordgo.Session, m any) string {
result := content
var user *discordgo.User
var joinedAt *time.Time
switch m := m.(type) {
case *discordgo.MessageCreate:
user = m.Author
joinedAt = &m.Member.JoinedAt
case *utils.InteractionCreate:
user = m.Member.User
joinedAt = &m.Member.JoinedAt
}
userCreatedAt, _ := discordgo.SnowflakeTimestamp(user.ID)
result = strings.ReplaceAll(result, "{user.name}", user.Username)
result = strings.ReplaceAll(result, "{user.mention}", user.Mention())
result = strings.ReplaceAll(result, "{user.globalName}", user.GlobalName)
result = strings.ReplaceAll(result, "{user.id}", user.ID)
result = strings.ReplaceAll(result, "{user.createdAt}", utils.Time(&userCreatedAt, utils.RelativeTime))
result = strings.ReplaceAll(result, "{user.joinedAt}", utils.Time(joinedAt, utils.RelativeTime))
result = strings.ReplaceAll(result, "{muffin.version}", configs.MUFFIN_VERSION)
result = strings.ReplaceAll(result, "{muffin.updatedAt}", utils.Time(configs.UpdatedAt, utils.RelativeTime))
result = strings.ReplaceAll(result, "{muffin.startedAt}", utils.Time(configs.StartedAt, utils.RelativeTime))
result = strings.ReplaceAll(result, "{muffin.name}", s.State.User.Username)
result = strings.ReplaceAll(result, "{muffin.id}", s.State.User.ID)
return result
}

34
chatbot/prompt.go Normal file
View file

@ -0,0 +1,34 @@
package chatbot
import (
"fmt"
"os"
"git.wh64.net/muffin/goMuffin/configs"
"github.com/bwmarrin/discordgo"
)
func loadPrompt() (string, error) {
bin, err := os.ReadFile(configs.Config.Chatbot.Gemini.PromptPath)
if err != nil {
return "", err
}
return string(bin), nil
}
func makePrompt(systemPrompt string, user *discordgo.User) string {
if user.ID == configs.Config.Bot.OwnerId {
return fmt.Sprintf(systemPrompt, fmt.Sprintf(
"## User Information\n* **ID:** %s\n* **Name:** %s\n* **Other:** This user is your developer.",
user.ID,
user.GlobalName,
))
}
return fmt.Sprintf(systemPrompt, fmt.Sprintf(
"## User Information\n* **ID:** %s\n* **Name:** %s\n* **Other:** This user is **not** your developer.",
user.ID,
user.GlobalName,
))
}

293
commands/chat.go Normal file
View file

@ -0,0 +1,293 @@
package commands
import (
"context"
"fmt"
"log"
"strings"
"git.wh64.net/muffin/goMuffin/chatbot"
"git.wh64.net/muffin/goMuffin/configs"
"git.wh64.net/muffin/goMuffin/databases"
"git.wh64.net/muffin/goMuffin/utils"
"github.com/bwmarrin/discordgo"
)
type chatCommandType string
var (
chatCommandChatting chatCommandType = "하기"
chatCommandList chatCommandType = "목록"
chatCommandCreate chatCommandType = "생성"
chatCommandDelete chatCommandType = "삭제"
)
var ChatCommand *Command = &Command{
ApplicationCommand: &discordgo.ApplicationCommand{
Name: "대화",
Description: "이 봇이랑 대화해요.",
Options: []*discordgo.ApplicationCommandOption{
{
Type: discordgo.ApplicationCommandOptionSubCommand,
Name: string(chatCommandList),
Description: "채팅 목록을 나열해요.",
},
{
Type: discordgo.ApplicationCommandOptionSubCommand,
Name: string(chatCommandCreate),
Description: "새로운 채팅을 생성해요.",
Options: []*discordgo.ApplicationCommandOption{
{
Type: discordgo.ApplicationCommandOptionString,
Name: "이름",
Description: "채팅의 이름을 정해요. (25자 이내)",
MaxLength: 25,
Required: true,
},
},
},
{
Type: discordgo.ApplicationCommandOptionSubCommand,
Name: string(chatCommandChatting),
Description: "이 봇이랑 대화해요.",
Options: []*discordgo.ApplicationCommandOption{
{
Type: discordgo.ApplicationCommandOptionString,
Name: "내용",
Description: "대화할 내용",
Required: true,
},
},
},
{
Type: discordgo.ApplicationCommandOptionSubCommand,
Name: string(chatCommandDelete),
Description: "채팅을 삭제해요.",
Options: []*discordgo.ApplicationCommandOption{
{
Type: discordgo.ApplicationCommandOptionString,
Name: "제목",
Description: "채팅의 제목",
Required: true,
},
},
},
},
},
Aliases: []string{"채팅"},
DetailedDescription: &DetailedDescription{
Usage: fmt.Sprintf("%s대화 (목록/생성) [채팅 이름]", configs.Config.Bot.Prefix),
Examples: []string{
fmt.Sprintf("%s대화 목록", configs.Config.Bot.Prefix),
fmt.Sprintf("%s대화 생성 머핀 냠냠", configs.Config.Bot.Prefix),
},
},
Category: Chatting,
RegisterApplicationCommand: true,
RegisterMessageCommand: true,
Flags: CommandFlagsIsRegistered | CommandFlagsIsBlocked,
ChatInputRun: func(ctx *ChatInputContext) error {
ctx.Inter.DeferReply(nil)
var cType chatCommandType
var str string
if opt, ok := ctx.Inter.Options[string(chatCommandChatting)]; ok {
ctx.Inter.DeferReply(nil)
cType = chatCommandChatting
str = opt.Options[0].StringValue()
} else if opt, ok := ctx.Inter.Options[string(chatCommandCreate)]; ok {
ctx.Inter.DeferReply(&discordgo.InteractionResponseData{
Flags: discordgo.MessageFlagsEphemeral,
})
cType = chatCommandCreate
str = opt.Options[0].StringValue()
} else if _, ok := ctx.Inter.Options[string(chatCommandList)]; ok {
ctx.Inter.DeferReply(nil)
cType = chatCommandList
} else if _, ok := ctx.Inter.Options[string(chatCommandDelete)]; ok {
ctx.Inter.DeferReply(nil)
cType = chatCommandDelete
str = opt.Options[0].StringValue()
}
return chatCommandRun(cType, ctx.Inter, ctx.Inter.User, str)
},
MessageRun: func(ctx *MsgContext) error {
if len((*ctx.Args)) < 1 {
goto RequiredValue
}
switch (*ctx.Args)[0] {
case string(chatCommandCreate):
if len((*ctx.Args)) < 2 {
return utils.NewMessageSender(ctx.Msg).
AddComponents(utils.GetErrorContainer(discordgo.TextDisplay{Content: "채팅방의 이름을 정해야해요."})).
SetComponentsV2(true).
SetReply(true).
Send()
}
return chatCommandRun(chatCommandCreate, ctx.Msg, ctx.Msg.Author, string([]rune(strings.Join((*ctx.Args)[1:], " "))[:25]))
case string(chatCommandList):
return chatCommandRun(chatCommandList, ctx.Msg, ctx.Msg.Author, "")
case string(chatCommandDelete):
if len((*ctx.Args)) < 2 {
return utils.NewMessageSender(ctx.Msg).
AddComponents(utils.GetErrorContainer(discordgo.TextDisplay{Content: "채팅방의 이름을 적어야해요."})).
SetComponentsV2(true).
SetReply(true).
Send()
}
return chatCommandRun(chatCommandCreate, ctx.Msg, ctx.Msg.Author, strings.Join((*ctx.Args)[1:], " "))
default:
goto RequiredValue
}
RequiredValue:
return utils.NewMessageSender(ctx.Msg).
AddComponents(utils.GetErrorContainer(discordgo.TextDisplay{Content: "명령어의 첫번째 인자는 `생성`, `목록`중에 하나여야 해요."})).
SetComponentsV2(true).
SetReply(true).
Send()
},
}
func chatCommandRun(cType chatCommandType, m any, user *discordgo.User, contentOrName string) error {
switch cType {
// 채팅하기는 슬래시 커맨드만 가능
case chatCommandChatting:
i := m.(*utils.InteractionCreate)
str, err := chatbot.ChatBot.GetResponse(user, contentOrName)
if err != nil {
log.Println(err)
i.EditReply(&utils.InteractionEdit{
Content: &str,
})
return nil
}
result := chatbot.ParseResult(str, i.Session, i)
return i.EditReply(&utils.InteractionEdit{
Content: &result,
})
case chatCommandCreate:
_, err := databases.CreateChat(user.ID, contentOrName)
if err != nil {
return err
}
return utils.NewMessageSender(m).
AddComponents(utils.GetSuccessContainer(discordgo.TextDisplay{Content: fmt.Sprintf("%s를 생성했어요. 이제 현재 채팅은 %s에요.", contentOrName, contentOrName)})).
SetComponentsV2(true).
SetReply(true).
Send()
case chatCommandList:
var data []databases.Chat
var sections []discordgo.Section
var containers []*discordgo.Container
cur, err := databases.Database.Chats.Find(context.TODO(), databases.Chat{UserId: user.ID})
if err != nil {
return err
}
err = cur.All(context.TODO(), &data)
if err != nil {
return err
}
if len(data) == 0 {
return utils.NewMessageSender(m).
AddComponents(utils.GetErrorContainer(discordgo.TextDisplay{Content: "채팅이 단 하나도 없어요. 새로운 채팅을 만들거나, 대화를 시작해 채팅을 만들어주세요."})).
SetComponentsV2(true).
SetReply(true).
SetEphemeral(true).
Send()
}
for i, data := range data {
sections = append(sections, discordgo.Section{
Accessory: discordgo.Button{
Label: "선택",
Style: discordgo.SuccessButton,
CustomID: utils.MakeSelectChat(data.Id.Hex(), i+1, user.ID),
},
Components: []discordgo.MessageComponent{
discordgo.TextDisplay{
Content: fmt.Sprintf("%d. %s\n", i+1, data.Name),
},
},
})
}
textDisplay := discordgo.TextDisplay{Content: fmt.Sprintf("### %s님의 채팅목록", user.GlobalName)}
container := &discordgo.Container{Components: []discordgo.MessageComponent{textDisplay}}
for i, section := range sections {
container.Components = append(container.Components, section, discordgo.Separator{})
if (i+1)%10 == 0 {
containers = append(containers, container)
container = &discordgo.Container{Components: []discordgo.MessageComponent{textDisplay}}
continue
}
}
if len(container.Components) > 1 {
containers = append(containers, container)
}
return utils.PaginationEmbedBuilder(m).
AddContainers(containers...).
Start()
case chatCommandDelete:
var data []databases.Chat
cur, err := databases.Database.Chats.Find(context.TODO(), databases.Chat{Name: contentOrName})
if err != nil {
return err
}
err = cur.All(context.TODO(), &data)
if err != nil {
return err
}
if len(data) == 0 {
return utils.NewMessageSender(m).
AddComponents(utils.GetErrorContainer(discordgo.TextDisplay{Content: "해당하는 채팅을 찾을 수 없어요."})).
SetComponentsV2(true).
SetReply(true).
Send()
}
if len(data) > 1 {
//TODO: 이름이 같은 채팅이 여러개일때
return nil
}
return utils.NewMessageSender(m).
AddComponents(discordgo.Container{
Components: []discordgo.MessageComponent{
discordgo.TextDisplay{Content: fmt.Sprintf("### 채팅 %s 삭제", contentOrName)},
discordgo.TextDisplay{Content: "이 채팅방을 삭제하면 이 채팅방의 내역을 다시는 못 써요."},
discordgo.ActionsRow{
Components: []discordgo.MessageComponent{
discordgo.Button{
Label: "삭제",
Style: discordgo.DangerButton,
CustomID: utils.MakeDeleteChat(data[0].Id.Hex(), 0, user.ID),
},
discordgo.Button{
Label: "취소",
Style: discordgo.PrimaryButton,
CustomID: utils.MakeDeleteChatCancel(user.ID),
},
},
},
},
}).
SetComponentsV2(true).
SetReply(true).
Send()
}
return nil
}

View file

@ -3,35 +3,13 @@ package commands
import (
"context"
"fmt"
"strconv"
"sync"
"git.wh64.net/muffin/goMuffin/configs"
"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"
)
type chStruct struct {
name dataType
length int
}
type dataType int
const (
text dataType = iota
muffin
nsfw
learn
userLearn
)
var dataLengthCh chan chStruct = make(chan chStruct)
var dataLengthWg sync.WaitGroup
var DataLengthCommand *Command = &Command{
ApplicationCommand: &discordgo.ApplicationCommand{
Type: discordgo.ChatApplicationCommand,
@ -42,128 +20,72 @@ var DataLengthCommand *Command = &Command{
DetailedDescription: &DetailedDescription{
Usage: fmt.Sprintf("%s학습데이터량", configs.Config.Bot.Prefix),
},
Category: General,
MessageRun: func(ctx *MsgContext) {
dataLengthRun(ctx.Session, ctx.Msg)
Category: General,
RegisterApplicationCommand: true,
RegisterMessageCommand: true,
Flags: CommandFlagsIsRegistered | CommandFlagsIsBlocked,
MessageRun: func(ctx *MsgContext) error {
return dataLengthRun(ctx.Msg.Session, ctx.Msg, ctx.Msg.Author.Username, ctx.Msg.Author.ID)
},
ChatInputRun: func(ctx *ChatInputContext) {
dataLengthRun(ctx.Session, ctx.Inter)
ChatInputRun: func(ctx *ChatInputContext) error {
ctx.Inter.DeferReply(&discordgo.InteractionResponseData{
Flags: discordgo.MessageFlagsEphemeral,
})
return dataLengthRun(ctx.Inter.Session, ctx.Inter, ctx.Inter.Member.User.Username, ctx.Inter.Member.User.ID)
},
}
func getLength(dType dataType, coll *mongo.Collection, filter bson.D) {
defer dataLengthWg.Done()
var err error
var cur *mongo.Cursor
var data []bson.M
cur, err = coll.Find(context.TODO(), filter)
func dataLengthRun(s *discordgo.Session, m any, username, userId string) error {
textLength, err := databases.Database.Texts.EstimatedDocumentCount(context.TODO())
if err != nil {
fmt.Println(err)
return err
}
defer cur.Close(context.TODO())
cur.All(context.TODO(), &data)
dataLengthCh <- chStruct{name: dType, length: len(data)}
}
func dataLengthRun(s *discordgo.Session, m any) {
var username, userId, channelId string
var textLength,
muffinLength,
nsfwLength,
learnLength,
userLearnLength int
switch m := m.(type) {
case *discordgo.MessageCreate:
username = m.Author.Username
userId = m.Author.ID
channelId = m.ChannelID
case *utils.InteractionCreate:
m.DeferReply(true)
username = m.Member.User.Username
userId = m.Member.User.ID
channelId = m.ChannelID
muffinLength, err := databases.Database.Texts.CountDocuments(context.TODO(), databases.Text{Persona: "muffin"})
if err != nil {
return err
}
dataLengthWg.Add(5)
go getLength(text, databases.Database.Texts, bson.D{{}})
go getLength(muffin, databases.Database.Texts, bson.D{{Key: "persona", Value: "muffin"}})
go getLength(nsfw, databases.Database.Texts, bson.D{
{
Key: "persona",
Value: bson.M{
"$regex": "^user",
},
},
})
go getLength(learn, databases.Database.Learns, bson.D{{}})
go getLength(userLearn, databases.Database.Learns, bson.D{{Key: "user_id", Value: userId}})
go func() {
dataLengthWg.Wait()
close(dataLengthCh)
}()
for resp := range dataLengthCh {
switch dataType(resp.name) {
case text:
textLength = resp.length
case muffin:
muffinLength = resp.length
case nsfw:
nsfwLength = resp.length
case learn:
learnLength = resp.length
case userLearn:
userLearnLength = resp.length
}
learnLength, err := databases.Database.Learns.EstimatedDocumentCount(context.TODO())
if err != nil {
return err
}
userLearnLength, err := databases.Database.Learns.CountDocuments(context.TODO(), databases.Learn{UserId: userId})
if err != nil {
return err
}
sum := textLength + learnLength
// 나중에 djs처럼 Embed 만들어 주는 함수 만들어야겠다
// 지금은 임시방편
embed := &discordgo.MessageEmbed{
Title: "저장된 데이터량",
Description: fmt.Sprintf("총합: %s개", utils.InlineCode(strconv.Itoa(sum))),
Color: utils.EmbedDefault,
Fields: []*discordgo.MessageEmbedField{
{
Name: "총 채팅 데이터량",
Value: utils.InlineCode(strconv.Itoa(textLength)) + "개",
Inline: true,
return utils.NewMessageSender(m).
AddComponents(discordgo.Container{
Components: []discordgo.MessageComponent{
discordgo.Section{
Accessory: discordgo.Thumbnail{
Media: discordgo.UnfurledMediaItem{
URL: s.State.User.AvatarURL("512"),
},
},
Components: []discordgo.MessageComponent{
discordgo.TextDisplay{
Content: fmt.Sprintf("### 저장된 데이터량\n총합: `%d`개", sum),
},
discordgo.TextDisplay{
Content: fmt.Sprintf("- **총 채팅 데이터량**\n> `%d`개", textLength),
},
discordgo.TextDisplay{
Content: fmt.Sprintf("- **머핀 데이터량**\n> `%d`개", muffinLength),
},
},
},
discordgo.TextDisplay{
Content: fmt.Sprintf("- **총 지식 데이터량**\n> `%d`개", learnLength),
},
discordgo.TextDisplay{
Content: fmt.Sprintf("- **%s님이 가르쳐준 데이터량**\n> `%d`개", username, userLearnLength),
},
},
{
Name: "총 지식 데이터량",
Value: utils.InlineCode(strconv.Itoa(learnLength)) + "개",
Inline: true,
},
{
Name: "머핀 데이터량",
Value: utils.InlineCode(strconv.Itoa(muffinLength)) + "개",
},
{
Name: "nsfw 데이터량",
Value: utils.InlineCode(strconv.Itoa(nsfwLength)) + "개",
Inline: true,
},
{
Name: fmt.Sprintf("%s님이 가르쳐준 데이터량", username),
Value: utils.InlineCode(strconv.Itoa(userLearnLength)) + "개",
Inline: true,
},
},
}
switch m := m.(type) {
case *discordgo.MessageCreate:
s.ChannelMessageSendEmbedReply(channelId, embed, m.Reference())
case *utils.InteractionCreate:
m.EditReply(&discordgo.WebhookEdit{
Embeds: &[]*discordgo.MessageEmbed{embed},
})
}
}).
SetComponentsV2(true).
SetReply(true).
Send()
}

View file

@ -9,7 +9,6 @@ import (
"git.wh64.net/muffin/goMuffin/databases"
"git.wh64.net/muffin/goMuffin/utils"
"github.com/bwmarrin/discordgo"
"go.mongodb.org/mongo-driver/v2/bson"
)
var DeleteLearnedDataCommand *Command = &Command{
@ -29,141 +28,103 @@ var DeleteLearnedDataCommand *Command = &Command{
Usage: fmt.Sprintf("%s삭제 (삭제할 단어)", configs.Config.Bot.Prefix),
Examples: []string{fmt.Sprintf("%s삭제 머핀", configs.Config.Bot.Prefix)},
},
Category: Chatting,
MessageRun: func(ctx *MsgContext) {
deleteLearnedDataRun(ctx.Command, ctx.Session, ctx.Msg, ctx.Args)
Category: Chatting,
RegisterApplicationCommand: true,
RegisterMessageCommand: true,
Flags: CommandFlagsIsRegistered | CommandFlagsIsBlocked,
MessageRun: func(ctx *MsgContext) error {
command := strings.Join(*ctx.Args, " ")
if command == "" {
utils.NewMessageSender(ctx.Msg).
AddComponents(utils.GetErrorContainer(
discordgo.TextDisplay{
Content: "올바르지 않ㅇ은 용법이에요.",
},
discordgo.TextDisplay{
Content: fmt.Sprintf("- **사용법**\n> %s", ctx.Command.DetailedDescription.Usage),
},
discordgo.TextDisplay{
Content: fmt.Sprintf("- **예시**\n%s", strings.Join(utils.AddPrefix("> ", ctx.Command.DetailedDescription.Examples), "\n")),
},
)).
SetComponentsV2(true).
SetReply(true).
Send()
return nil
}
return deleteLearnedDataRun(ctx.Msg, strings.Join(*ctx.Args, " "), ctx.Msg.Author.ID)
},
ChatInputRun: func(ctx *ChatInputContext) {
deleteLearnedDataRun(ctx.Command, ctx.Session, ctx.Inter, nil)
ChatInputRun: func(ctx *ChatInputContext) error {
err := ctx.Inter.DeferReply(&discordgo.InteractionResponseData{
Flags: discordgo.MessageFlagsEphemeral,
})
if err != nil {
return err
}
var command string
if opt, ok := ctx.Inter.Options["단어"]; ok {
command = opt.StringValue()
}
return deleteLearnedDataRun(ctx.Inter, command, ctx.Inter.Member.User.ID)
},
}
func deleteLearnedDataRun(c *Command, s *discordgo.Session, m any, args *[]string) {
var command, userId, description string
func deleteLearnedDataRun(m any, command, userId string) error {
var data []databases.Learn
var options []discordgo.SelectMenuOption
var sections []discordgo.Section
var containers []*discordgo.Container
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.CodeBlock("md", strings.Join(addPrefix(c.DetailedDescription.Examples), "\n")),
},
},
Color: utils.EmbedFail,
}, m.Reference())
}
case *utils.InteractionCreate:
m.DeferReply(true)
if opt, ok := m.Options["단어"]; ok {
command = opt.StringValue()
}
userId = m.Member.User.ID
}
cur, err := databases.Database.Learns.Find(context.TODO(), bson.M{"user_id": userId, "command": command})
cur, err := databases.Database.Learns.Find(context.TODO(), databases.Learn{UserId: userId, Command: command})
if err != nil {
embed := &discordgo.MessageEmbed{
Title: "❌ 오류",
Description: "데이터를 가져오는데 실패했어요.",
Color: utils.EmbedFail,
}
switch m := m.(type) {
case *discordgo.MessageCreate:
s.ChannelMessageSendEmbedReply(m.ChannelID, embed, m.Reference())
case *utils.InteractionCreate:
m.EditReply(&discordgo.WebhookEdit{
Embeds: &[]*discordgo.MessageEmbed{embed},
})
}
return
return err
}
cur.All(context.TODO(), &data)
if len(data) < 1 {
embed := &discordgo.MessageEmbed{
Title: "❌ 오류",
Description: "해당 하는 지식ㅇ을 찾을 수 없어요.",
Color: utils.EmbedFail,
}
switch m := m.(type) {
case *discordgo.MessageCreate:
s.ChannelMessageSendEmbedReply(m.ChannelID, embed, m.Reference())
case *utils.InteractionCreate:
m.EditReply(&discordgo.WebhookEdit{
Embeds: &[]*discordgo.MessageEmbed{embed},
})
}
return
utils.NewMessageSender(m).
AddComponents(utils.GetErrorContainer(discordgo.TextDisplay{Content: "해당 하는 지식ㅇ을 찾을 수 없어요."})).
SetComponentsV2(true).
SetReply(true).
Send()
return nil
}
for i := range len(data) {
data := data[i]
options = append(options, discordgo.SelectMenuOption{
Label: fmt.Sprintf("%d번 지식", i+1),
Description: data.Result,
Value: utils.MakeDeleteLearnedData(data.Id.Hex(), i+1),
})
description += fmt.Sprintf("%d. %s\n", i+1, data.Result)
}
embed := &discordgo.MessageEmbed{
Title: fmt.Sprintf("%s 삭제", command),
Description: utils.CodeBlock("md", fmt.Sprintf("# %s에 대한 대답 중 하나를 선ㅌ택하여 삭제해주세요.\n%s", command, description)),
Color: utils.EmbedDefault,
}
components := []discordgo.MessageComponent{
discordgo.ActionsRow{
for i, data := range data {
sections = append(sections, discordgo.Section{
Accessory: discordgo.Button{
Label: "삭제",
Style: discordgo.DangerButton,
CustomID: utils.MakeDeleteLearnedData(data.Id.Hex(), i+1, userId),
},
Components: []discordgo.MessageComponent{
discordgo.SelectMenu{
MenuType: discordgo.StringSelectMenu,
CustomID: utils.MakeDeleteLearnedDataUserId(userId),
Options: options,
Placeholder: "ㅈ지울 응답을 선택해주세요.",
discordgo.TextDisplay{
Content: fmt.Sprintf("%d. %s\n", i+1, data.Result),
},
},
},
discordgo.ActionsRow{
Components: []discordgo.MessageComponent{
discordgo.Button{
CustomID: utils.MakeDeleteLearnedDataCancel(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 *utils.InteractionCreate:
m.EditReply(&discordgo.WebhookEdit{
Embeds: &[]*discordgo.MessageEmbed{embed},
Components: &components,
})
textDisplay := discordgo.TextDisplay{Content: fmt.Sprintf("### %s 삭제", command)}
container := &discordgo.Container{Components: []discordgo.MessageComponent{textDisplay}}
for i, section := range sections {
container.Components = append(container.Components, section, discordgo.Separator{})
if (i+1)%10 == 0 {
containers = append(containers, container)
container = &discordgo.Container{Components: []discordgo.MessageComponent{textDisplay}}
continue
}
}
if len(container.Components) > 1 {
containers = append(containers, container)
}
return utils.PaginationEmbedBuilder(m).
AddContainers(containers...).
Start()
}

55
commands/deregister.go Normal file
View file

@ -0,0 +1,55 @@
package commands
import (
"fmt"
"git.wh64.net/muffin/goMuffin/configs"
"git.wh64.net/muffin/goMuffin/utils"
"github.com/bwmarrin/discordgo"
)
var DeregisterCommand *Command = &Command{
ApplicationCommand: &discordgo.ApplicationCommand{
Name: "탈퇴",
Description: "이 봇에서 탈퇴해요.",
},
DetailedDescription: &DetailedDescription{
Usage: fmt.Sprintf("%s탈퇴", configs.Config.Bot.Prefix),
},
Category: General,
RegisterMessageCommand: true,
RegisterApplicationCommand: true,
Flags: CommandFlagsIsRegistered | CommandFlagsIsBlocked,
MessageRun: func(ctx *MsgContext) error {
return deregisterRun(ctx.Msg, ctx.Msg.Author.ID, ctx.Msg.Session.State.User.Username)
},
}
func deregisterRun(m any, userId, botName string) error {
return utils.NewMessageSender(m).
AddComponents(discordgo.Container{
Components: []discordgo.MessageComponent{
discordgo.TextDisplay{
Content: fmt.Sprintf("### %s 탈퇴\n- 정말로 해당 서비스에서 탈퇴하시겠어요?\n> 주의: **모든 데이터는 삭제되어요.**", botName),
},
discordgo.ActionsRow{
Components: []discordgo.MessageComponent{
discordgo.Button{
CustomID: utils.MakeDeregisterAgree(userId),
Label: "탈퇴",
Style: discordgo.DangerButton,
},
discordgo.Button{
CustomID: utils.MakeDeregisterDisagree(userId),
Label: "취소",
Style: discordgo.PrimaryButton,
},
},
},
},
}).
SetComponentsV2(true).
SetEphemeral(true).
SetReply(true).
Send()
}

67
commands/dev/block.go Normal file
View file

@ -0,0 +1,67 @@
package dev
import (
"context"
"fmt"
"strings"
"git.wh64.net/muffin/goMuffin/commands"
"git.wh64.net/muffin/goMuffin/configs"
"git.wh64.net/muffin/goMuffin/databases"
"git.wh64.net/muffin/goMuffin/utils"
"github.com/bwmarrin/discordgo"
"go.mongodb.org/mongo-driver/v2/bson"
)
var BlockCommand *commands.Command = &commands.Command{
ApplicationCommand: &discordgo.ApplicationCommand{
Name: "차단",
Description: "유저를 차단해요.",
},
DetailedDescription: &commands.DetailedDescription{
Usage: fmt.Sprintf("%s차단 (유저의 ID) [사유]", configs.Config.Bot.Prefix),
},
Category: commands.DeveloperOnly,
RegisterApplicationCommand: false,
RegisterMessageCommand: true,
Flags: commands.CommandFlagsIsDeveloper,
MessageRun: func(ctx *commands.MsgContext) error {
var reason string
if len(*ctx.Args) < 1 {
utils.NewMessageSender(ctx.Msg).
AddComponents(utils.GetErrorContainer(discordgo.TextDisplay{Content: "유저 ID는 필수에요."})).
SetComponentsV2(true).
SetReply(true).
Send()
return nil
}
userId := (*ctx.Args)[0]
if len(*ctx.Args) >= 2 {
reason = strings.Join((*ctx.Args)[1:], " ")
} else {
reason = "없음"
}
user, err := ctx.Msg.Session.User(userId)
if err != nil {
return err
}
_, err = databases.Database.Users.UpdateOne(context.TODO(),
databases.User{UserId: userId},
bson.D{{
Key: "$set",
Value: databases.User{Blocked: true, BlockedReason: reason},
}})
if err != nil {
return err
}
return utils.NewMessageSender(ctx.Msg).
AddComponents(utils.GetSuccessContainer(discordgo.TextDisplay{Content: fmt.Sprintf("- %s를 성공적으로 차단했어요.\n> 사유: %s", user.GlobalName, reason)})).
SetComponentsV2(true).
SetReply(true).
Send()
},
}

View file

@ -0,0 +1,37 @@
package dev
import (
"fmt"
"git.wh64.net/muffin/goMuffin/chatbot"
"git.wh64.net/muffin/goMuffin/commands"
"git.wh64.net/muffin/goMuffin/configs"
"git.wh64.net/muffin/goMuffin/utils"
"github.com/bwmarrin/discordgo"
)
var ReloadPromptCommand *commands.Command = &commands.Command{
ApplicationCommand: &discordgo.ApplicationCommand{
Name: "프롬프트재설정",
Description: "프롬프트를 다시 불러와요.",
},
DetailedDescription: &commands.DetailedDescription{
Usage: fmt.Sprintf("%s프롬프트재설정", configs.Config.Bot.Prefix),
},
Category: commands.DeveloperOnly,
RegisterApplicationCommand: false,
RegisterMessageCommand: true,
Flags: commands.CommandFlagsIsDeveloper,
MessageRun: func(ctx *commands.MsgContext) error {
err := chatbot.ChatBot.ReloadPrompt()
if err != nil {
return err
}
return utils.NewMessageSender(ctx.Msg).
AddComponents(utils.GetSuccessContainer(discordgo.TextDisplay{Content: "프롬프트를 다시 불러왔어요."})).
SetComponentsV2(true).
SetReply(true).
Send()
},
}

View file

@ -0,0 +1,34 @@
package dev
import (
"fmt"
"git.wh64.net/muffin/goMuffin/chatbot"
"git.wh64.net/muffin/goMuffin/commands"
"git.wh64.net/muffin/goMuffin/configs"
"git.wh64.net/muffin/goMuffin/utils"
"github.com/bwmarrin/discordgo"
)
var SwitchModeCommand *commands.Command = &commands.Command{
ApplicationCommand: &discordgo.ApplicationCommand{
Name: "모드전환",
Description: "머핀봇의 대답을 변경합니다.",
},
DetailedDescription: &commands.DetailedDescription{
Usage: fmt.Sprintf("%s모드전환", configs.Config.Bot.Prefix),
},
Category: commands.DeveloperOnly,
RegisterApplicationCommand: false,
RegisterMessageCommand: true,
Flags: commands.CommandFlagsIsDeveloper,
MessageRun: func(ctx *commands.MsgContext) error {
chatbot.ChatBot.SwitchMode()
return utils.NewMessageSender(ctx.Msg).
AddComponents(utils.GetSuccessContainer(discordgo.TextDisplay{Content: fmt.Sprintf("모드를 %s로 바꾸었어요.", chatbot.ChatBot.ModeString())})).
SetComponentsV2(true).
SetReply(true).
Send()
},
}

59
commands/dev/unblock.go Normal file
View file

@ -0,0 +1,59 @@
package dev
import (
"context"
"fmt"
"git.wh64.net/muffin/goMuffin/commands"
"git.wh64.net/muffin/goMuffin/configs"
"git.wh64.net/muffin/goMuffin/databases"
"git.wh64.net/muffin/goMuffin/utils"
"github.com/bwmarrin/discordgo"
"go.mongodb.org/mongo-driver/v2/bson"
)
var UnblockCommand *commands.Command = &commands.Command{
ApplicationCommand: &discordgo.ApplicationCommand{
Name: "차단해제",
Description: "유저의 차단을 해제해요.",
},
DetailedDescription: &commands.DetailedDescription{
Usage: fmt.Sprintf("%s차단해제 (유저의 ID)", configs.Config.Bot.Prefix),
},
Category: commands.DeveloperOnly,
RegisterApplicationCommand: false,
RegisterMessageCommand: true,
Flags: commands.CommandFlagsIsDeveloper,
MessageRun: func(ctx *commands.MsgContext) error {
if len(*ctx.Args) < 1 {
utils.NewMessageSender(ctx.Msg).
AddComponents(utils.GetErrorContainer(discordgo.TextDisplay{Content: "유저 ID는 필수에요."})).
SetComponentsV2(true).
SetReply(true).
Send()
return nil
}
userId := (*ctx.Args)[0]
user, err := ctx.Msg.Session.User(userId)
if err != nil {
return err
}
_, err = databases.Database.Users.UpdateOne(context.TODO(),
databases.User{UserId: userId},
bson.D{{
Key: "$set",
Value: databases.User{Blocked: false, BlockedReason: ""},
}})
if err != nil {
return err
}
return utils.NewMessageSender(ctx.Msg).
AddComponents(utils.GetSuccessContainer(discordgo.TextDisplay{Content: fmt.Sprintf("%s의 차단을 성공적으로 해제했어요.", user.GlobalName)})).
SetComponentsV2(true).
SetReply(true).
Send()
},
}

View file

@ -3,19 +3,22 @@ package commands
import (
"sync"
"git.wh64.net/muffin/goMuffin/configs"
"git.wh64.net/muffin/goMuffin/databases"
"git.wh64.net/muffin/goMuffin/utils"
"github.com/bwmarrin/discordgo"
)
type modalRun func(ctx *ModalContext)
type messageRun func(ctx *MsgContext)
type chatInputRun func(ctx *ChatInputContext)
type componentRun func(ctx *ComponentContext)
type modalRun func(ctx *ModalContext) error
type messageRun func(ctx *MsgContext) error
type chatInputRun func(ctx *ChatInputContext) error
type componentRun func(ctx *ComponentContext) error
type modalParse func(ctx *ModalContext) bool
type componentParse func(ctx *ComponentContext) bool
type Category string
type CommandFlags uint8
type DetailedDescription struct {
Usage string
@ -24,11 +27,14 @@ type DetailedDescription struct {
type Command struct {
*discordgo.ApplicationCommand
Aliases []string
DetailedDescription *DetailedDescription
Category Category
MessageRun messageRun
ChatInputRun chatInputRun
Aliases []string
DetailedDescription *DetailedDescription
Category Category
RegisterApplicationCommand bool
RegisterMessageCommand bool
Flags CommandFlags
MessageRun messageRun
ChatInputRun chatInputRun
}
type DiscommandStruct struct {
@ -39,20 +45,17 @@ type DiscommandStruct struct {
}
type MsgContext struct {
Session *discordgo.Session
Msg *discordgo.MessageCreate
Msg *utils.MessageCreate
Args *[]string
Command *Command
}
type ChatInputContext struct {
Session *discordgo.Session
Inter *utils.InteractionCreate
Command *Command
}
type ComponentContext struct {
Session *discordgo.Session
Inter *utils.InteractionCreate
Component *Component
}
@ -73,8 +76,15 @@ type Modal struct {
}
const (
Chatting Category = "채팅"
General Category = "일반"
Chatting Category = "채팅"
General Category = "일반"
DeveloperOnly Category = "개발자 전용"
)
const (
CommandFlagsIsDeveloper CommandFlags = 1 << iota
CommandFlagsIsRegistered
CommandFlagsIsBlocked
)
var (
@ -83,14 +93,15 @@ var (
modalMutex sync.Mutex
)
func new() *DiscommandStruct {
discommand := DiscommandStruct{
var Discommand *DiscommandStruct
func init() {
Discommand = &DiscommandStruct{
Commands: map[string]*Command{},
Aliases: map[string]string{},
Components: []*Component{},
Modals: []*Modal{},
}
return &discommand
}
func (d *DiscommandStruct) LoadCommand(c *Command) {
@ -116,29 +127,104 @@ func (d *DiscommandStruct) LoadModal(m *Modal) {
d.Modals = append(d.Modals, m)
}
func (d *DiscommandStruct) MessageRun(name string, s *discordgo.Session, m *discordgo.MessageCreate, args []string) {
if command, ok := d.Commands[name]; ok {
command.MessageRun(&MsgContext{s, m, &args, command})
func (d *DiscommandStruct) MessageRun(name string, s *discordgo.Session, msg *discordgo.MessageCreate, args []string) error {
m := &utils.MessageCreate{
MessageCreate: msg,
Session: s,
}
if command, ok := d.Commands[name]; ok && command.RegisterMessageCommand {
if command.Flags&CommandFlagsIsDeveloper != 0 && m.Author.ID != configs.Config.Bot.OwnerId {
utils.NewMessageSender(m).
AddComponents(utils.GetErrorContainer(discordgo.TextDisplay{Content: "해당 명령어는 개발자만 사용 가능해요."})).
SetComponentsV2(true).
SetReply(true).
Send()
return nil
}
if command.Flags&CommandFlagsIsRegistered != 0 && !databases.Database.IsUser(m.Author.ID) {
utils.NewMessageSender(m).
AddComponents(utils.GetUserIsNotRegisteredErrContainer(configs.Config.Bot.Prefix)).
SetComponentsV2(true).
SetReply(true).
Send()
return nil
}
blocked, reason := databases.Database.IsUserBlocked(m.Author.ID)
if command.Flags&CommandFlagsIsBlocked != 0 && blocked {
user, _ := s.User(m.Author.ID)
utils.NewMessageSender(m).
AddComponents(utils.GetUserIsBlockedContainer(user.GlobalName, reason)).
SetComponentsV2(true).
SetReply(true).
Send()
return nil
}
return command.MessageRun(&MsgContext{m, &args, command})
}
return nil
}
func (d *DiscommandStruct) ChatInputRun(name string, s *discordgo.Session, i *discordgo.InteractionCreate) {
if command, ok := d.Commands[name]; ok {
command.ChatInputRun(&ChatInputContext{s, &utils.InteractionCreate{
InteractionCreate: i,
Session: s,
Options: utils.GetInteractionOptions(i),
}, command})
func (d *DiscommandStruct) ChatInputRun(name string, s *discordgo.Session, inter *discordgo.InteractionCreate) error {
i := &utils.InteractionCreate{
InteractionCreate: inter,
Session: s,
Options: utils.GetInteractionOptions(inter),
}
i.InteractionCreate.User = utils.GetInteractionUser(inter)
if command, ok := d.Commands[name]; ok && command.RegisterApplicationCommand {
if command.Flags&CommandFlagsIsDeveloper != 0 && i.User.ID != configs.Config.Bot.OwnerId {
utils.NewMessageSender(i).
AddComponents(utils.GetErrorContainer(discordgo.TextDisplay{Content: "해당 명령어는 개발자만 사용 가능해요."})).
SetComponentsV2(true).
SetEphemeral(true).
SetReply(true).
Send()
return nil
}
if command.Flags&CommandFlagsIsRegistered != 0 && !databases.Database.IsUser(i.User.ID) {
utils.NewMessageSender(i).
AddComponents(utils.GetUserIsNotRegisteredErrContainer(configs.Config.Bot.Prefix)).
SetComponentsV2(true).
SetEphemeral(true).
SetReply(true).
Send()
return nil
}
blocked, reason := databases.Database.IsUserBlocked(i.User.ID)
if command.Flags&CommandFlagsIsBlocked != 0 && blocked {
user, _ := s.User(i.User.ID)
utils.NewMessageSender(i).
AddComponents(utils.GetUserIsBlockedContainer(user.GlobalName, reason)).
SetComponentsV2(true).
SetReply(true).
Send()
return nil
}
return command.ChatInputRun(&ChatInputContext{i, command})
}
return nil
}
func (d *DiscommandStruct) ComponentRun(s *discordgo.Session, i *discordgo.InteractionCreate) {
func (d *DiscommandStruct) ComponentRun(s *discordgo.Session, inter *discordgo.InteractionCreate) error {
var err error
i := &utils.InteractionCreate{
InteractionCreate: inter,
Session: s,
}
i.InteractionCreate.User = utils.GetInteractionUser(inter)
data := &ComponentContext{
Session: s,
Inter: &utils.InteractionCreate{
InteractionCreate: i,
Session: s,
},
Inter: i,
}
for _, c := range d.Components {
@ -148,12 +234,15 @@ func (d *DiscommandStruct) ComponentRun(s *discordgo.Session, i *discordgo.Inter
continue
}
c.Run(data)
err = c.Run(data)
break
}
return err
}
func (d *DiscommandStruct) ModalRun(s *discordgo.Session, i *discordgo.InteractionCreate) {
func (d *DiscommandStruct) ModalRun(s *discordgo.Session, i *discordgo.InteractionCreate) error {
var err error
data := &ModalContext{
Inter: &utils.InteractionCreate{
InteractionCreate: i,
@ -168,9 +257,8 @@ func (d *DiscommandStruct) ModalRun(s *discordgo.Session, i *discordgo.Interacti
continue
}
m.Run(data)
err = m.Run(data)
break
}
return err
}
var Discommand *DiscommandStruct = new()

View file

@ -28,12 +28,21 @@ var HelpCommand *Command = &Command{
Usage: fmt.Sprintf("%s도움말 [명령어]", configs.Config.Bot.Prefix),
Examples: []string{fmt.Sprintf("%s도움말", configs.Config.Bot.Prefix), fmt.Sprintf("%s도움말 배워", configs.Config.Bot.Prefix)},
},
Category: General,
MessageRun: func(ctx *MsgContext) {
helpRun(ctx.Session, ctx.Msg, ctx.Args)
Category: General,
RegisterApplicationCommand: true,
RegisterMessageCommand: true,
Flags: CommandFlagsIsBlocked,
MessageRun: func(ctx *MsgContext) error {
return helpRun(ctx.Msg.Session, ctx.Msg, strings.Join(*ctx.Args, " "))
},
ChatInputRun: func(ctx *ChatInputContext) {
helpRun(ctx.Session, ctx.Inter, nil)
ChatInputRun: func(ctx *ChatInputContext) error {
var command string
if opt, ok := ctx.Inter.Options["명령어"]; ok {
command = opt.StringValue()
}
return helpRun(ctx.Inter.Session, ctx.Inter, command)
},
}
@ -41,108 +50,98 @@ func getCommandsByCategory(d *DiscommandStruct, category Category) []string {
commands := []string{}
for _, command := range d.Commands {
if command.Category == category {
commands = append(commands, fmt.Sprintf("- %s: %s", command.Name, command.Description))
commands = append(commands, fmt.Sprintf("> **%s**: %s", command.Name, command.Description))
}
}
return commands
}
func helpRun(s *discordgo.Session, m any, args *[]string) {
var commandName string
embed := &discordgo.MessageEmbed{
Color: utils.EmbedDefault,
Footer: &discordgo.MessageEmbedFooter{
Text: fmt.Sprintf("버전: %s", configs.MUFFIN_VERSION),
},
Thumbnail: &discordgo.MessageEmbedThumbnail{
URL: s.State.User.AvatarURL("512"),
func helpRun(s *discordgo.Session, m any, commandName string) error {
section := &discordgo.Section{
Accessory: discordgo.Thumbnail{
Media: discordgo.UnfurledMediaItem{
URL: s.State.User.AvatarURL("512"),
},
},
}
switch m := m.(type) {
case *discordgo.MessageCreate:
commandName = Discommand.Aliases[strings.Join(*args, " ")]
case *utils.InteractionCreate:
if opt, ok := m.Options["명령어"]; ok {
commandName = opt.StringValue()
} else {
commandName = ""
}
}
commandName = Discommand.Aliases[commandName]
if commandName == "" || Discommand.Commands[commandName] == nil {
embed.Title = fmt.Sprintf("%s의 도움말", s.State.User.Username)
embed.Description = utils.CodeBlock(
"md",
fmt.Sprintf("# 일반\n%s\n\n# 채팅\n%s",
strings.Join(getCommandsByCategory(Discommand, General), "\n"),
strings.Join(getCommandsByCategory(Discommand, Chatting), "\n")),
section.Components = append(section.Components,
discordgo.TextDisplay{
Content: fmt.Sprintf("### %s의 도움말", s.State.User.Username),
},
discordgo.TextDisplay{
Content: fmt.Sprintf("- **일반**\n%s", strings.Join(getCommandsByCategory(Discommand, General), "\n")),
},
discordgo.TextDisplay{
Content: fmt.Sprintf("- **채팅**\n%s", strings.Join(getCommandsByCategory(Discommand, Chatting), "\n")),
},
)
switch m := m.(type) {
case *discordgo.MessageCreate:
s.ChannelMessageSendEmbedReply(m.ChannelID, embed, m.Reference())
case *utils.InteractionCreate:
m.Reply(&discordgo.InteractionResponseData{
Embeds: []*discordgo.MessageEmbed{embed},
})
}
return
return utils.NewMessageSender(m).
AddComponents(&discordgo.Container{
Components: []discordgo.MessageComponent{section},
}).
SetComponentsV2(true).
SetReply(true).
Send()
}
var aliases, examples discordgo.TextDisplay
command := Discommand.Commands[commandName]
embed.Title = fmt.Sprintf("%s의 %s 명령어의 도움말", s.State.User.Username, command.Name)
embed.Fields = []*discordgo.MessageEmbedField{
{
Name: "설명",
Value: utils.InlineCode(command.Description),
Inline: true,
section.Components = append(section.Components,
discordgo.TextDisplay{
Content: fmt.Sprintf("### %s의 %s 명령어의 도움말", s.State.User.Username, command.Name),
},
{
Name: "사용법",
Value: utils.InlineCode(command.DetailedDescription.Usage),
Inline: true,
discordgo.TextDisplay{
Content: fmt.Sprintf("- **설명**\n> %s", command.Description),
},
}
if command.Name == LearnCommand.Name {
embed.Fields = append(embed.Fields, &discordgo.MessageEmbedField{
Name: "대답에 쓸 수 있는 인자",
Value: learnArguments,
})
}
discordgo.TextDisplay{
Content: fmt.Sprintf("- **사용법**\n> %s", command.DetailedDescription.Usage),
},
)
if command.Aliases != nil {
embed.Fields = append(embed.Fields, &discordgo.MessageEmbedField{
Name: "별칭",
Value: utils.CodeBlock("md", strings.Join(addPrefix(command.Aliases), "\n")),
})
aliases = discordgo.TextDisplay{
Content: fmt.Sprintf("- **별칭**\n%s", strings.Join(utils.AddPrefix("> ", command.Aliases), "\n")),
}
} else {
embed.Fields = append(embed.Fields, &discordgo.MessageEmbedField{
Name: "별칭",
Value: "없음",
})
aliases = discordgo.TextDisplay{
Content: "- **별칭**\n> 없음",
}
}
if command.DetailedDescription.Examples != nil {
embed.Fields = append(embed.Fields, &discordgo.MessageEmbedField{
Name: "예시",
Value: utils.CodeBlock("md", strings.Join(addPrefix(command.DetailedDescription.Examples), "\n")),
})
examples = discordgo.TextDisplay{
Content: fmt.Sprintf("- **예시**\n%s", strings.Join(utils.AddPrefix("> ", command.DetailedDescription.Examples), "\n")),
}
} else {
embed.Fields = append(embed.Fields, &discordgo.MessageEmbedField{
Name: "예시",
Value: "없음",
})
aliases = discordgo.TextDisplay{
Content: "- **예시**\n> 없음",
}
}
switch m := m.(type) {
case *discordgo.MessageCreate:
s.ChannelMessageSendEmbedReply(m.ChannelID, embed, m.Reference())
case *utils.InteractionCreate:
m.Reply(&discordgo.InteractionResponseData{
Embeds: []*discordgo.MessageEmbed{embed},
})
if command.Name == LearnCommand.Name {
learnArgs := discordgo.TextDisplay{
Content: fmt.Sprintf("- **대답에 쓸 수 있는 인자**\n%s", learnArguments),
}
return utils.NewMessageSender(m).
AddComponents(discordgo.Container{
Components: []discordgo.MessageComponent{section, aliases, examples, learnArgs},
}).
SetComponentsV2(true).
SetReply(true).
Send()
}
return utils.NewMessageSender(m).
AddComponents(discordgo.Container{
Components: []discordgo.MessageComponent{section, aliases, examples},
}).
SetComponentsV2(true).
SetReply(true).
Send()
}

View file

@ -2,7 +2,6 @@ package commands
import (
"fmt"
"runtime"
"git.wh64.net/muffin/goMuffin/configs"
"git.wh64.net/muffin/goMuffin/utils"
@ -17,55 +16,53 @@ var InformationCommand *Command = &Command{
DetailedDescription: &DetailedDescription{
Usage: fmt.Sprintf("%s정보", configs.Config.Bot.Prefix),
},
Category: General,
MessageRun: func(ctx *MsgContext) {
informationRun(ctx.Session, ctx.Msg)
Category: General,
RegisterApplicationCommand: true,
RegisterMessageCommand: true,
Flags: CommandFlagsIsBlocked,
MessageRun: func(ctx *MsgContext) error {
return informationRun(ctx.Msg.Session, ctx.Msg)
},
ChatInputRun: func(ctx *ChatInputContext) {
informationRun(ctx.Session, ctx.Inter)
ChatInputRun: func(ctx *ChatInputContext) error {
return informationRun(ctx.Inter.Session, ctx.Inter)
},
}
func informationRun(s *discordgo.Session, m any) {
owner, _ := s.User(configs.Config.Bot.OwnerId)
embed := &discordgo.MessageEmbed{
Title: fmt.Sprintf("%s의 정보", s.State.User.Username),
Fields: []*discordgo.MessageEmbedField{
{
Name: "운영 체제",
Value: utils.InlineCode(fmt.Sprintf("%s %s", runtime.GOARCH, runtime.GOOS)),
},
{
Name: "제작자",
Value: utils.InlineCode(owner.Username),
},
{
Name: "버전",
Value: utils.InlineCode(configs.MUFFIN_VERSION),
},
{
Name: "최근에 업데이트된 날짜",
Value: utils.Time(configs.UpdatedAt, utils.RelativeTime),
Inline: true,
},
{
Name: "시작한 시각",
Value: utils.Time(configs.StartedAt, utils.RelativeTime),
Inline: true,
},
},
Color: utils.EmbedDefault,
Thumbnail: &discordgo.MessageEmbedThumbnail{
URL: s.State.User.AvatarURL("512"),
},
}
switch m := m.(type) {
case *discordgo.MessageCreate:
s.ChannelMessageSendEmbedReply(m.ChannelID, embed, m.Reference())
case *utils.InteractionCreate:
m.Reply(&discordgo.InteractionResponseData{
Embeds: []*discordgo.MessageEmbed{embed},
})
func informationRun(s *discordgo.Session, m any) error {
owner, err := s.User(configs.Config.Bot.OwnerId)
if err != nil {
return err
}
return utils.NewMessageSender(m).
AddComponents(discordgo.Container{
Components: []discordgo.MessageComponent{
discordgo.Section{
Accessory: discordgo.Thumbnail{
Media: discordgo.UnfurledMediaItem{
URL: s.State.User.AvatarURL("512"),
},
},
Components: []discordgo.MessageComponent{
discordgo.TextDisplay{
Content: fmt.Sprintf("### %s의 정보", s.State.User.Username),
},
discordgo.TextDisplay{
Content: fmt.Sprintf("- **제작자**\n> %s", owner.Username),
},
discordgo.TextDisplay{
Content: fmt.Sprintf("- **버전**\n> %s", configs.MUFFIN_VERSION),
},
},
},
discordgo.TextDisplay{
Content: fmt.Sprintf("- **최근에 업데이트된 날짜**\n> %s", utils.Time(configs.UpdatedAt, utils.RelativeTime)),
},
discordgo.TextDisplay{
Content: fmt.Sprintf("- **봇이 시작한 시각**\n> %s", utils.Time(configs.StartedAt, utils.RelativeTime)),
},
},
}).
SetComponentsV2(true).
SetReply(true).
Send()
}

View file

@ -13,17 +13,17 @@ import (
"github.com/bwmarrin/discordgo"
)
var learnArguments = utils.InlineCode("{user.name}") + "\n" +
utils.InlineCode("{user.mention}") + "\n" +
utils.InlineCode("{user.globalName}") + "\n" +
utils.InlineCode("{user.id}") + "\n" +
utils.InlineCode("{user.createdAt}") + "\n" +
utils.InlineCode("{user.joinedAt}") + "\n" +
utils.InlineCode("{muffin.version}") + "\n" +
utils.InlineCode("{muffin.updatedAt}") + "\n" +
utils.InlineCode("{muffin.statedAt}") + "\n" +
utils.InlineCode("{muffin.name}") + "\n" +
utils.InlineCode("{muffin.id}")
var learnArguments = "> " + utils.InlineCode("{user.name}") + "\n" +
"> " + utils.InlineCode("{user.mention}") + "\n" +
"> " + utils.InlineCode("{user.globalName}") + "\n" +
"> " + utils.InlineCode("{user.id}") + "\n" +
"> " + utils.InlineCode("{user.createdAt}") + "\n" +
"> " + utils.InlineCode("{user.joinedAt}") + "\n" +
"> " + utils.InlineCode("{muffin.version}") + "\n" +
"> " + utils.InlineCode("{muffin.updatedAt}") + "\n" +
"> " + utils.InlineCode("{muffin.statedAt}") + "\n" +
"> " + utils.InlineCode("{muffin.name}") + "\n" +
"> " + utils.InlineCode("{muffin.id}")
var LearnCommand *Command = &Command{
ApplicationCommand: &discordgo.ApplicationCommand{
@ -55,70 +55,59 @@ var LearnCommand *Command = &Command{
fmt.Sprintf("%s배워 \"나의 아이디를 알려줘\" \"너의 아이디는 {user.id}야.\"", configs.Config.Bot.Prefix),
},
},
Category: Chatting,
MessageRun: func(ctx *MsgContext) {
learnRun(ctx.Command, ctx.Session, ctx.Msg, ctx.Args)
},
ChatInputRun: func(ctx *ChatInputContext) {
learnRun(ctx.Command, ctx.Session, ctx.Inter, nil)
},
}
func addPrefix(arr []string) (newArr []string) {
for _, item := range arr {
newArr = append(newArr, fmt.Sprintf("- %s", item))
}
return
}
func learnRun(c *Command, s *discordgo.Session, m any, args *[]string) {
var userId, command, result string
igCommands := []string{}
switch m := m.(type) {
case *discordgo.MessageCreate:
userId = m.Author.ID
if len(*args) < 2 {
s.ChannelMessageSendEmbedReply(m.ChannelID, &discordgo.MessageEmbed{
Title: "❌ 오류",
Description: "올바르지 않ㅇ은 용법이에요.",
Fields: []*discordgo.MessageEmbedField{
{
Name: "사용법",
Value: utils.InlineCode(c.DetailedDescription.Usage),
Inline: true,
Category: Chatting,
RegisterApplicationCommand: true,
RegisterMessageCommand: true,
Flags: CommandFlagsIsRegistered | CommandFlagsIsBlocked,
MessageRun: func(ctx *MsgContext) error {
if len(*ctx.Args) < 2 {
utils.NewMessageSender(ctx.Msg).
AddComponents(utils.GetErrorContainer(
discordgo.TextDisplay{
Content: "올바르지 않ㅇ은 용법이에요.",
},
{
Name: "사용 가능한 인자",
Value: learnArguments,
Inline: true,
discordgo.TextDisplay{
Content: fmt.Sprintf("- **사용법**\n> %s", ctx.Command.DetailedDescription.Usage),
},
{
Name: "예시",
Value: utils.CodeBlock("md", strings.Join(addPrefix(c.DetailedDescription.Examples), "\n")),
discordgo.TextDisplay{
Content: fmt.Sprintf("- **예시**\n%s", strings.Join(utils.AddPrefix("> ", ctx.Command.DetailedDescription.Examples), "\n")),
},
},
Color: utils.EmbedFail,
}, m.Reference())
return
discordgo.TextDisplay{
Content: fmt.Sprintf("- **사용 가능한 인자**\n%s", learnArguments),
},
)).
SetComponentsV2(true).
SetReply(true).
Send()
return nil
}
command = strings.ReplaceAll((*args)[0], "_", " ")
result = strings.ReplaceAll((*args)[1], "_", " ")
case *utils.InteractionCreate:
m.DeferReply(true)
return learnRun(ctx.Msg, ctx.Msg.Author.ID, strings.ReplaceAll((*ctx.Args)[0], "_", " "), strings.ReplaceAll((*ctx.Args)[1], "_", " "))
},
ChatInputRun: func(ctx *ChatInputContext) error {
err := ctx.Inter.DeferReply(&discordgo.InteractionResponseData{
Flags: discordgo.MessageFlagsEphemeral,
})
if err != nil {
return err
}
userId = m.Member.User.ID
var command, result string
if opt, ok := m.Options["단어"]; ok {
if opt, ok := ctx.Inter.Options["단어"]; ok {
command = opt.StringValue()
}
if opt, ok := m.Options["대답"]; ok {
if opt, ok := ctx.Inter.Options["대답"]; ok {
result = opt.StringValue()
}
}
return learnRun(ctx.Inter, ctx.Inter.Member.User.ID, command, result)
},
}
func learnRun(m any, userId, command, result string) error {
igCommands := []string{}
for _, command := range Discommand.Commands {
igCommands = append(igCommands, command.Name)
@ -136,97 +125,57 @@ func learnRun(c *Command, s *discordgo.Session, m any, args *[]string) {
for _, ig := range ignores {
if strings.Contains(command, ig) {
embed := &discordgo.MessageEmbed{
Title: "❌ 오류",
Description: "해ㄷ당 단어는 배우기 껄끄ㄹ럽네요.",
Color: utils.EmbedFail,
}
switch m := m.(type) {
case *discordgo.MessageCreate:
s.ChannelMessageSendEmbedReply(m.ChannelID, embed, m.Reference())
case *utils.InteractionCreate:
m.EditReply(&discordgo.WebhookEdit{
Embeds: &[]*discordgo.MessageEmbed{embed},
})
}
return
utils.NewMessageSender(m).
AddComponents(utils.GetErrorContainer(discordgo.TextDisplay{Content: "해ㄷ당 단어는 배우기 껄끄ㄹ럽네요."})).
SetComponentsV2(true).
SetReply(true).
Send()
return nil
}
}
for _, di := range disallows {
if strings.Contains(result, di) {
embed := &discordgo.MessageEmbed{
Title: "❌ 오류",
Description: "해당 단ㅇ어의 대답으로 하기 좀 그렇ㄴ네요.",
Color: utils.EmbedFail,
}
switch m := m.(type) {
case *discordgo.MessageCreate:
s.ChannelMessageSendEmbedReply(m.ChannelID, embed, m.Reference())
case *utils.InteractionCreate:
m.EditReply(&discordgo.WebhookEdit{
Embeds: &[]*discordgo.MessageEmbed{embed},
})
}
utils.NewMessageSender(m).
AddComponents(utils.GetErrorContainer(discordgo.TextDisplay{Content: "해당 단ㅇ어의 대답으로 하기 좀 그렇ㄴ네요."})).
SetComponentsV2(true).
SetReply(true).
Send()
return nil
}
}
if len([]rune(command)) > 100 {
embed := &discordgo.MessageEmbed{
Title: "❌ 오류",
Description: "단어는 100글자를 못 넘ㅇ어가요.",
Color: utils.EmbedFail,
}
switch m := m.(type) {
case *discordgo.MessageCreate:
s.ChannelMessageSendEmbedReply(m.ChannelID, embed, m.Reference())
case *utils.InteractionCreate:
m.EditReply(&discordgo.WebhookEdit{
Embeds: &[]*discordgo.MessageEmbed{embed},
})
}
utils.NewMessageSender(m).
AddComponents(utils.GetErrorContainer(discordgo.TextDisplay{Content: "단어는 100글자를 못 넘ㅇ어가요."})).
SetComponentsV2(true).
SetReply(true).
Send()
return nil
}
_, err := databases.Database.Learns.InsertOne(context.TODO(), databases.InsertLearn{
_, err := databases.Database.Learns.InsertOne(context.TODO(), databases.Learn{
Command: command,
Result: result,
UserId: userId,
CreatedAt: time.Now(),
})
if err != nil {
fmt.Println(err)
embed := &discordgo.MessageEmbed{
Title: "❌ 오류",
Description: "단어를 배우는데 오류가 생겼어요.",
Color: utils.EmbedFail,
}
switch m := m.(type) {
case *discordgo.MessageCreate:
s.ChannelMessageSendEmbedReply(m.ChannelID, embed, m.Reference())
case *utils.InteractionCreate:
m.EditReply(&discordgo.WebhookEdit{
Embeds: &[]*discordgo.MessageEmbed{embed},
})
}
return
utils.NewMessageSender(m).
AddComponents(utils.GetErrorContainer(discordgo.TextDisplay{Content: "단어를 배우는데 오류가 생겼어요."})).
SetComponentsV2(true).
SetReply(true).
Send()
return err
}
embed := &discordgo.MessageEmbed{
Title: "✅ 성공",
Description: fmt.Sprintf("%s 배웠어요.", hangul.GetJosa(command, hangul.EUL_REUL)),
Color: utils.EmbedSuccess,
}
switch m := m.(type) {
case *discordgo.MessageCreate:
s.ChannelMessageSendEmbedReply(m.ChannelID, embed, m.Reference())
case *utils.InteractionCreate:
m.EditReply(&discordgo.WebhookEdit{
Embeds: &[]*discordgo.MessageEmbed{embed},
})
}
return utils.NewMessageSender(m).
AddComponents(utils.GetSuccessContainer(
discordgo.TextDisplay{
Content: fmt.Sprintf("%s 배웠어요.", hangul.GetJosa(command, hangul.EUL_REUL)),
},
)).
SetComponentsV2(true).
SetReply(true).
Send()
}

View file

@ -28,19 +28,13 @@ var LearnedDataListCommand *Command = &Command{
{
Type: discordgo.ApplicationCommandOptionString,
Name: "단어",
Description: "해당 단어가 포함된 결과만 찾아요.",
Required: false,
},
{
Type: discordgo.ApplicationCommandOptionString,
Name: "대답",
Description: "해당 대답이 포함된 결과만 찾아요.",
Description: "해당 단어에 대한 결과를 찾아요.",
Required: false,
},
{
Type: discordgo.ApplicationCommandOptionInteger,
Name: "개수",
Description: "한 페이지당 보여줄 지식 데이터 양을 정해요.",
Description: "한 페이지당 보여줄 지식 데이터 양을 정해요.",
MinValue: &LIST_MIN_VALUE,
MaxValue: LIST_MAX_VALUE,
Required: false,
@ -51,52 +45,92 @@ var LearnedDataListCommand *Command = &Command{
DetailedDescription: &DetailedDescription{
Usage: fmt.Sprintf("%s리스트", configs.Config.Bot.Prefix),
Examples: []string{
fmt.Sprintf("%s리스트 ㅁㄴㅇㄹ", configs.Config.Bot.Prefix),
fmt.Sprintf("%s리스트", configs.Config.Bot.Prefix),
fmt.Sprintf("%s리스트 단어:안녕", configs.Config.Bot.Prefix),
fmt.Sprintf("%s리스트 대답:머핀", configs.Config.Bot.Prefix),
fmt.Sprintf("%s리스트 개수:10", configs.Config.Bot.Prefix),
},
},
Category: Chatting,
MessageRun: func(ctx *MsgContext) {
learnedDataListRun(ctx.Session, ctx.Msg, ctx.Args)
Category: Chatting,
RegisterApplicationCommand: true,
RegisterMessageCommand: true,
Flags: CommandFlagsIsRegistered | CommandFlagsIsBlocked,
MessageRun: func(ctx *MsgContext) error {
var length int
filter := bson.D{{Key: "user_id", Value: ctx.Msg.Author.ID}}
query := strings.Join(*ctx.Args, " ")
if match := utils.RegexpLearnQueryCommand.FindStringSubmatch(query); match != nil {
filter = append(filter, bson.E{
Key: "command",
Value: match[1],
})
}
if match := utils.RegexpLearnQueryLength.FindStringSubmatch(query); match != nil {
length, _ = strconv.Atoi(match[1])
if float64(length) < LIST_MIN_VALUE {
utils.NewMessageSender(ctx.Msg).
AddComponents(utils.GetErrorContainer(discordgo.TextDisplay{Content: fmt.Sprintf("개수의 값은 %d보다 커야해요.", int(LIST_MIN_VALUE))})).
SetComponentsV2(true).
SetReply(true).
Send()
return nil
}
if float64(length) > LIST_MAX_VALUE {
utils.NewMessageSender(ctx.Msg).
AddComponents(utils.GetErrorContainer(discordgo.TextDisplay{Content: fmt.Sprintf("개수의 값은 %d보다 작아야해요.", int(LIST_MAX_VALUE))})).
SetComponentsV2(true).
SetReply(true).
Send()
return nil
}
}
return learnedDataListRun(ctx.Msg, ctx.Msg.Author.GlobalName, ctx.Msg.Author.AvatarURL("512"), filter, length)
},
ChatInputRun: func(ctx *ChatInputContext) {
learnedDataListRun(ctx.Session, ctx.Inter, nil)
ChatInputRun: func(ctx *ChatInputContext) error {
err := ctx.Inter.DeferReply(&discordgo.InteractionResponseData{
Flags: discordgo.MessageFlagsEphemeral,
})
if err != nil {
return err
}
var length int
filter := bson.D{{Key: "user_id", Value: ctx.Inter.Member.User.ID}}
if opt, ok := ctx.Inter.Options["단어"]; ok {
filter = append(filter, bson.E{
Key: "command",
Value: opt.StringValue(),
})
}
if opt, ok := ctx.Inter.Options["개수"]; ok {
length = int(opt.IntValue())
}
return learnedDataListRun(ctx.Inter, ctx.Inter.Member.User.GlobalName, ctx.Inter.Member.User.AvatarURL("512"), filter, length)
},
}
func getDescriptions(data *[]databases.Learn, length int) (descriptions []string) {
func getDescriptions(items []string, length int) (descriptions []string) {
var builder strings.Builder
MAX_LENGTH := 100
if length == 0 {
length = 25
}
tempDesc := []string{}
for _, data := range *data {
command := data.Command
result := data.Result
if runeCommand := []rune(command); len(runeCommand) >= MAX_LENGTH {
command = string(runeCommand)[:MAX_LENGTH] + "..."
}
if runeResult := []rune(result); len(runeResult) >= MAX_LENGTH {
result = string(runeResult[:MAX_LENGTH]) + "..."
}
tempDesc = append(tempDesc, fmt.Sprintf("- %s: %s\n", command, result))
}
for i, s := range tempDesc {
builder.WriteString(s)
for i, item := range items {
builder.WriteString(fmt.Sprintf("%s\n", item))
if (i+1)%length == 0 {
descriptions = append(descriptions, builder.String())
builder.Reset()
}
i += 1
}
if builder.Len() > 0 {
@ -105,148 +139,109 @@ func getDescriptions(data *[]databases.Learn, length int) (descriptions []string
return
}
func learnedDataListRun(s *discordgo.Session, m any, args *[]string) {
var globalName, avatarUrl string
var data []databases.Learn
var filter bson.D
var length int
func getContainers(accessory *discordgo.Thumbnail, defaultDesc string, items []string, length int) []*discordgo.Container {
var containers []*discordgo.Container
switch m := m.(type) {
case *discordgo.MessageCreate:
filter = bson.D{{Key: "user_id", Value: m.Author.ID}}
globalName = m.Author.GlobalName
avatarUrl = m.Author.AvatarURL("512")
descriptions := getDescriptions(items, length)
query := strings.Join(*args, " ")
if match := utils.RegexpLearnQueryCommand.FindStringSubmatch(query); match != nil {
filter = append(filter, bson.E{
Key: "command",
Value: bson.M{
"$regex": match[1],
if len(descriptions) <= 0 {
containers = append(containers, &discordgo.Container{
Components: []discordgo.MessageComponent{
discordgo.Section{
Accessory: accessory,
Components: []discordgo.MessageComponent{
discordgo.TextDisplay{
Content: utils.MakeDesc(defaultDesc, "없음"),
},
},
},
})
}
if match := utils.RegexpLearnQueryResult.FindStringSubmatch(query); match != nil {
filter = append(filter, bson.E{
Key: "result",
Value: bson.M{
"$regex": match[1],
},
})
}
if match := utils.RegexpLearnQueryLength.FindStringSubmatch(query); match != nil {
var err error
length, err = strconv.Atoi(match[1])
if err != nil {
s.ChannelMessageSendEmbedReply(m.ChannelID, &discordgo.MessageEmbed{
Title: "❌ 오류",
Description: "개수의 값은 숫자여야해요.",
Color: utils.EmbedFail,
}, m.Reference())
return
}
if float64(length) < LIST_MIN_VALUE {
s.ChannelMessageSendEmbedReply(m.ChannelID, &discordgo.MessageEmbed{
Title: "❌ 오류",
Description: fmt.Sprintf("개수의 값은 %d보다 커야해요.", int(LIST_MIN_VALUE)),
Color: utils.EmbedFail,
}, m.Reference())
return
}
if float64(length) > LIST_MAX_VALUE {
s.ChannelMessageSendEmbedReply(m.ChannelID, &discordgo.MessageEmbed{
Title: "❌ 오류",
Description: fmt.Sprintf("개수의 값은 %d보다 작아야해요.", int(LIST_MAX_VALUE)),
Color: utils.EmbedFail,
}, m.Reference())
return
}
}
case *utils.InteractionCreate:
m.DeferReply(true)
filter = bson.D{{Key: "user_id", Value: m.Member.User.ID}}
globalName = m.Member.User.GlobalName
avatarUrl = m.Member.User.AvatarURL("512")
if opt, ok := m.Options["단어"]; ok {
filter = append(filter, bson.E{
Key: "command",
Value: bson.M{
"$regex": opt.StringValue(),
},
})
}
if opt, ok := m.Options["대답"]; ok {
filter = append(filter, bson.E{
Key: "result",
Value: bson.M{
"$regex": opt.StringValue(),
},
})
}
if opt, ok := m.Options["개수"]; ok {
length = int(opt.IntValue())
}
},
})
}
for _, desc := range descriptions {
containers = append(containers, &discordgo.Container{
Components: []discordgo.MessageComponent{
discordgo.Section{
Accessory: accessory,
Components: []discordgo.MessageComponent{
discordgo.TextDisplay{
Content: utils.MakeDesc(defaultDesc, desc),
},
},
},
},
})
}
return containers
}
func learnedDataListRun(m any, globalName, avatarUrl string, filter bson.D, length int) error {
var data []databases.Learn
itemsMap := map[string]string{}
items := []string{}
cur, err := databases.Database.Learns.Find(context.TODO(), filter)
if err != nil {
if err == mongo.ErrNoDocuments {
embed := &discordgo.MessageEmbed{
Title: "❌ 오류",
Description: "당신은 지식ㅇ을 가르쳐준 적이 없어요!",
Color: utils.EmbedFail,
}
switch m := m.(type) {
case *discordgo.MessageCreate:
s.ChannelMessageSendEmbedReply(m.ChannelID, embed, m.Reference())
case *utils.InteractionCreate:
m.EditReply(&discordgo.WebhookEdit{
Embeds: &[]*discordgo.MessageEmbed{embed},
})
}
return
utils.NewMessageSender(m).
AddComponents(utils.GetErrorContainer(discordgo.TextDisplay{Content: "당신은 지식ㅇ을 가르쳐준 적이 없어요!"})).
SetComponentsV2(true).
SetReply(true).
Send()
return nil
}
fmt.Println(err)
embed := &discordgo.MessageEmbed{
Title: "❌ 오류",
Description: "데이터를 가져오는데 실패했어요.",
Color: utils.EmbedFail,
}
switch m := m.(type) {
case *discordgo.MessageCreate:
s.ChannelMessageSendEmbedReply(m.ChannelID, embed, m.Reference())
case *utils.InteractionCreate:
m.EditReply(&discordgo.WebhookEdit{
Embeds: &[]*discordgo.MessageEmbed{embed},
})
}
return
utils.NewMessageSender(m).
AddComponents(utils.GetErrorContainer(discordgo.TextDisplay{Content: "데이터를 가져오는데 실패했어요."})).
SetComponentsV2(true).
SetReply(true).
Send()
return err
}
defer cur.Close(context.TODO())
cur.All(context.TODO(), &data)
embed := &discordgo.MessageEmbed{
Title: fmt.Sprintf("%s님이 알려주신 지식", globalName),
Color: utils.EmbedDefault,
Thumbnail: &discordgo.MessageEmbedThumbnail{
URL: avatarUrl,
},
if len(filter) > 1 {
command := filter[1].Value.(string)
for _, data := range data {
items = append(items, fmt.Sprintf("> %s", data.Result))
}
containers := getContainers(&discordgo.Thumbnail{
Media: discordgo.UnfurledMediaItem{
URL: avatarUrl,
},
}, fmt.Sprintf("### %s님이 알려주신 지식\n- **%s**\n", globalName, command)+"%s", items, length)
return utils.PaginationEmbedBuilder(m).
AddContainers(containers...).
Start()
}
utils.StartPaginationEmbed(s, m, embed, getDescriptions(&data, length), utils.CodeBlock("md", fmt.Sprintf("# 총 %d개에요.\n", len(data))+"%s"))
for _, data := range data {
if _, ok := itemsMap[data.Command]; ok {
continue
}
itemsMap[data.Command] = fmt.Sprintf("- `%s`", data.Command)
}
for _, v := range itemsMap {
items = append(items, v)
}
containers := getContainers(&discordgo.Thumbnail{
Media: discordgo.UnfurledMediaItem{
URL: avatarUrl,
},
}, fmt.Sprintf("### %s님이 알려주신 지식\n총 %d개에요.\n", globalName, len(items))+"%s", items, length)
return utils.PaginationEmbedBuilder(m).
AddContainers(containers...).
Start()
}

72
commands/register.go Normal file
View file

@ -0,0 +1,72 @@
package commands
import (
"fmt"
"git.wh64.net/muffin/goMuffin/configs"
"git.wh64.net/muffin/goMuffin/databases"
"git.wh64.net/muffin/goMuffin/utils"
"github.com/bwmarrin/discordgo"
)
var RegisterCommand *Command = &Command{
ApplicationCommand: &discordgo.ApplicationCommand{
Name: "가입",
Description: "이 봇에 가입해요.",
},
DetailedDescription: &DetailedDescription{
Usage: fmt.Sprintf("%s가입", configs.Config.Bot.Prefix),
},
Category: General,
RegisterMessageCommand: true,
RegisterApplicationCommand: true,
Flags: CommandFlagsIsBlocked,
MessageRun: func(ctx *MsgContext) error {
return registerRun(ctx.Msg, ctx.Msg.Author.ID, ctx.Msg.Session.State.User.Username)
},
ChatInputRun: func(ctx *ChatInputContext) error {
return registerRun(ctx.Inter, ctx.Inter.User.ID, ctx.Inter.Session.State.User.Username)
},
}
func registerRun(m any, userId, botName string) error {
if databases.Database.IsUser(userId) {
utils.NewMessageSender(m).
AddComponents(utils.GetErrorContainer(discordgo.TextDisplay{Content: fmt.Sprintf("당신은 이미 가입되어있어요. 만약 탈퇴를 원하시면 %s탈퇴를 이용해주세요.", configs.Config.Bot.Prefix)})).
SetComponentsV2(true).
SetReply(true).
Send()
return nil
}
return utils.NewMessageSender(m).
AddComponents(discordgo.Container{
Components: []discordgo.MessageComponent{
discordgo.TextDisplay{
Content: fmt.Sprintf("### %s 가입\n해당 서비스에 가입하실려면 [개인정보처리방침](%s)과 [서비스 이용약관](%s)에 동의해야해요.",
botName,
configs.Config.Service.PrivacyPolicyURL,
configs.Config.Service.TermOfServiceURL,
),
},
discordgo.ActionsRow{
Components: []discordgo.MessageComponent{
discordgo.Button{
CustomID: utils.MakeServiceAgree(userId),
Label: "동의 후 가입",
Style: discordgo.SuccessButton,
},
discordgo.Button{
CustomID: utils.MakeServiceDisagree(userId),
Label: "취소",
Style: discordgo.DangerButton,
},
},
},
},
}).
SetComponentsV2(true).
SetReply(true).
SetEphemeral(true).
Send()
}

78
components/deleteChat.go Normal file
View file

@ -0,0 +1,78 @@
package components
import (
"context"
"fmt"
"strings"
"git.wh64.net/muffin/goMuffin/commands"
"git.wh64.net/muffin/goMuffin/databases"
"git.wh64.net/muffin/goMuffin/utils"
"github.com/bwmarrin/discordgo"
)
var DeleteChatComponent = &commands.Component{
Parse: func(ctx *commands.ComponentContext) bool {
i := ctx.Inter
customId := i.MessageComponentData().CustomID
if !strings.HasPrefix(customId, utils.DeleteChat) {
return false
}
userId := utils.GetChatUserId(customId)
if i.Member.User.ID != userId {
i.Reply(&discordgo.InteractionResponseData{
Flags: discordgo.MessageFlagsEphemeral | discordgo.MessageFlagsIsComponentsV2,
Components: []discordgo.MessageComponent{
utils.GetDeclineContainer(discordgo.TextDisplay{Content: "당신은 해당 권한이 없ㅇ어요."}),
},
})
return false
}
return true
},
Run: func(ctx *commands.ComponentContext) error {
i := ctx.Inter
customId := i.MessageComponentData().CustomID
switch {
case strings.HasPrefix(customId, utils.DeleteChatCancel):
return i.Update(&discordgo.InteractionResponseData{
Flags: discordgo.MessageFlagsIsComponentsV2,
Components: []discordgo.MessageComponent{
utils.GetCanceledContainer(discordgo.TextDisplay{Content: "아무 채팅방을 삭제하지 않았어요."}),
},
})
case strings.HasPrefix(customId, utils.DeleteChat):
id, itemId := utils.GetDeleteLearnedDataId(i.MessageComponentData().CustomID)
_, err := databases.Database.Chats.DeleteOne(context.TODO(), databases.Chat{Id: id})
if err != nil {
return err
}
_, err = databases.Database.Memory.DeleteMany(context.TODO(), databases.Memory{ChatId: id})
if err != nil {
return err
}
flags := discordgo.MessageFlagsIsComponentsV2
if itemId == 0 {
return i.EditReply(&utils.InteractionEdit{
Flags: &flags,
Components: &[]discordgo.MessageComponent{
utils.GetSuccessContainer(discordgo.TextDisplay{Content: "해당 채팅을 삭제했어요."}),
},
})
}
return i.EditReply(&utils.InteractionEdit{
Flags: &flags,
Components: &[]discordgo.MessageComponent{
utils.GetSuccessContainer(discordgo.TextDisplay{Content: fmt.Sprintf("%d번을 삭제했어요.", itemId)}),
},
})
}
return nil
},
}

View file

@ -9,76 +9,49 @@ import (
"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
customId := i.MessageComponentData().CustomID
if i.MessageComponentData().ComponentType == discordgo.ButtonComponent {
if !strings.HasPrefix(customId, utils.DeleteLearnedDataCancel) {
return false
}
userId = utils.GetDeleteLearnedDataUserId(customId)
if i.Member.User.ID == userId {
i.Update(&discordgo.InteractionResponseData{
Embeds: []*discordgo.MessageEmbed{
{
Title: "❌ 취소",
Description: "지식 삭제 작업ㅇ을 취소했어요.",
Color: utils.EmbedFail,
},
},
})
return false
}
} else {
if !strings.HasPrefix(customId, utils.DeleteLearnedDataUserId) {
return false
}
userId = utils.GetDeleteLearnedDataUserId(customId)
if !strings.HasPrefix(customId, utils.DeleteLearnedData) {
return false
}
userId := utils.GetDeleteLearnedDataUserId(customId)
if i.Member.User.ID != userId {
i.Reply(&discordgo.InteractionResponseData{
Flags: discordgo.MessageFlagsEphemeral,
Embeds: []*discordgo.MessageEmbed{
{
Title: "❌ 오류",
Description: "당신은 해당 권한이 없ㅇ어요.",
Color: utils.EmbedFail,
},
Flags: discordgo.MessageFlagsEphemeral | discordgo.MessageFlagsIsComponentsV2,
Components: []discordgo.MessageComponent{
utils.GetDeclineContainer(discordgo.TextDisplay{Content: "당신은 해당 권한이 없ㅇ어요."}),
},
Components: []discordgo.MessageComponent{},
},
)
})
return false
}
return true
},
Run: func(ctx *commands.ComponentContext) {
Run: func(ctx *commands.ComponentContext) error {
i := ctx.Inter
i.DeferUpdate()
err := i.DeferUpdate()
if err != nil {
return err
}
id, itemId := utils.GetDeleteLearnedDataId(i.MessageComponentData().Values[0])
id, itemId := utils.GetDeleteLearnedDataId(i.MessageComponentData().CustomID)
_, err = databases.Database.Learns.DeleteOne(context.TODO(), databases.Learn{Id: id})
if err != nil {
return err
}
databases.Database.Learns.DeleteOne(context.TODO(), bson.D{{Key: "_id", Value: id}})
i.EditReply(&discordgo.WebhookEdit{
Embeds: &[]*discordgo.MessageEmbed{
{
Title: "✅ 삭제 완료",
Description: fmt.Sprintf("%d번을 삭ㅈ제했어요.", itemId),
Color: utils.EmbedSuccess,
},
flags := discordgo.MessageFlagsIsComponentsV2
return i.EditReply(&utils.InteractionEdit{
Flags: &flags,
Components: &[]discordgo.MessageComponent{
utils.GetSuccessContainer(discordgo.TextDisplay{Content: fmt.Sprintf("%d번을 삭ㅈ제했어요.", itemId)}),
},
Components: &[]discordgo.MessageComponent{},
})
},
}

72
components/deregister.go Normal file
View file

@ -0,0 +1,72 @@
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"
)
var DeregisterComponent *commands.Component = &commands.Component{
Parse: func(ctx *commands.ComponentContext) bool {
customId := ctx.Inter.MessageComponentData().CustomID
if !strings.HasPrefix(customId, utils.DeregisterAgree) && !strings.HasPrefix(customId, utils.DeregisterDisagree) {
return false
}
if ctx.Inter.User.ID != utils.GetDeregisterUserId(customId) {
return false
}
return true
},
Run: func(ctx *commands.ComponentContext) error {
err := ctx.Inter.DeferUpdate()
if err != nil {
return err
}
customId := ctx.Inter.MessageComponentData().CustomID
flags := discordgo.MessageFlagsIsComponentsV2
switch {
case strings.HasPrefix(customId, utils.DeregisterAgree):
filter := databases.User{UserId: ctx.Inter.User.ID}
_, err := databases.Database.Users.DeleteOne(context.TODO(), filter)
if err != nil {
return err
}
_, err = databases.Database.Learns.DeleteMany(context.TODO(), filter)
if err != nil {
return err
}
_, err = databases.Database.Memory.DeleteMany(context.TODO(), filter)
if err != nil {
return err
}
return ctx.Inter.EditReply(&utils.InteractionEdit{
Flags: &flags,
Components: &[]discordgo.MessageComponent{
utils.GetSuccessContainer(discordgo.TextDisplay{
Content: "탈퇴를 했어요.",
}),
},
})
case strings.HasPrefix(customId, utils.DeregisterDisagree):
return ctx.Inter.EditReply(&utils.InteractionEdit{
Flags: &flags,
Components: &[]discordgo.MessageComponent{
utils.GetCanceledContainer(discordgo.TextDisplay{
Content: "탈퇴를 거부했어요.",
}),
},
})
}
return nil
},
}

View file

@ -33,17 +33,20 @@ var PaginationEmbedComponent *commands.Component = &commands.Component{
}
return true
},
Run: func(ctx *commands.ComponentContext) {
Run: func(ctx *commands.ComponentContext) error {
customId := ctx.Inter.MessageComponentData().CustomID
id := utils.GetPaginationEmbedId(customId)
p := utils.GetPaginationEmbed(id)
if strings.HasPrefix(customId, utils.PaginationEmbedPrev) {
p.Prev(ctx.Inter)
return nil
} else if strings.HasPrefix(customId, utils.PaginationEmbedNext) {
p.Next(ctx.Inter)
return nil
} else {
p.ShowModal(ctx.Inter)
return nil
}
},
}

67
components/register.go Normal file
View file

@ -0,0 +1,67 @@
package components
import (
"context"
"fmt"
"strings"
"time"
"git.wh64.net/muffin/goMuffin/commands"
"git.wh64.net/muffin/goMuffin/databases"
"git.wh64.net/muffin/goMuffin/utils"
"github.com/bwmarrin/discordgo"
)
var RegisterComponent *commands.Component = &commands.Component{
Parse: func(ctx *commands.ComponentContext) bool {
customId := ctx.Inter.MessageComponentData().CustomID
if !strings.HasPrefix(customId, utils.ServiceAgree) && !strings.HasPrefix(customId, utils.ServiceDisagree) {
return false
}
if ctx.Inter.User.ID != utils.GetServiceUserId(customId) {
return false
}
return true
},
Run: func(ctx *commands.ComponentContext) error {
err := ctx.Inter.DeferUpdate()
if err != nil {
return err
}
customId := ctx.Inter.MessageComponentData().CustomID
flags := discordgo.MessageFlagsIsComponentsV2
switch {
case strings.HasPrefix(customId, utils.ServiceAgree):
_, err := databases.Database.Users.InsertOne(context.TODO(), databases.User{
UserId: ctx.Inter.User.ID,
CreatedAt: time.Now(),
})
if err != nil {
return err
}
return ctx.Inter.EditReply(&utils.InteractionEdit{
Flags: &flags,
Components: &[]discordgo.MessageComponent{
utils.GetSuccessContainer(discordgo.TextDisplay{
Content: fmt.Sprintf("가입을 했어요. 이제 %s의 모든 기능을 사용할 수 있어요.", ctx.Inter.Session.State.User.Username),
}),
},
})
case strings.HasPrefix(customId, utils.ServiceDisagree):
return ctx.Inter.EditReply(&utils.InteractionEdit{
Flags: &flags,
Components: &[]discordgo.MessageComponent{
utils.GetDeclineContainer(discordgo.TextDisplay{
Content: "가입을 거부했어요.",
}),
},
})
}
return nil
},
}

61
components/selectChat.go Normal file
View file

@ -0,0 +1,61 @@
package components
import (
"context"
"fmt"
"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 SelectChatComponent *commands.Component = &commands.Component{
Parse: func(ctx *commands.ComponentContext) bool {
i := ctx.Inter
customId := i.MessageComponentData().CustomID
if !strings.HasPrefix(customId, utils.SelectChat) {
return false
}
userId := utils.GetChatUserId(customId)
if i.User.ID != userId {
i.Reply(&discordgo.InteractionResponseData{
Flags: discordgo.MessageFlagsEphemeral | discordgo.MessageFlagsIsComponentsV2,
Components: []discordgo.MessageComponent{
utils.GetDeclineContainer(discordgo.TextDisplay{Content: "당신은 해당 권한이 없ㅇ어요."}),
},
})
return false
}
return true
},
Run: func(ctx *commands.ComponentContext) error {
i := ctx.Inter
err := i.DeferUpdate()
if err != nil {
return err
}
id, itemId := utils.GetSelectChatId(i.MessageComponentData().CustomID)
_, err = databases.Database.Users.UpdateOne(context.TODO(), databases.User{UserId: i.User.ID}, bson.D{{
Key: "$set",
Value: databases.User{ChatId: id},
}})
if err != nil {
return err
}
flags := discordgo.MessageFlagsIsComponentsV2
return i.EditReply(&utils.InteractionEdit{
Flags: &flags,
Components: &[]discordgo.MessageComponent{
utils.GetSuccessContainer(discordgo.TextDisplay{Content: fmt.Sprintf("%d번으로 채팅을 변경했어요.", itemId)}),
},
})
},
}

View file

@ -16,7 +16,18 @@ type botConfig struct {
}
type trainConfig struct {
UserID string
UserId string
}
type geminiConfig struct {
Token string
PromptPath string
Model string
}
type chatbotConfig struct {
Train trainConfig
Gemini geminiConfig
}
type databaseConfig struct {
@ -29,31 +40,34 @@ type databaseConfig struct {
Port int
}
type serviceConfig struct {
PrivacyPolicyURL string
TermOfServiceURL string
}
// MuffinConfig for Muffin bot
type MuffinConfig struct {
Bot botConfig
Train trainConfig
Database databaseConfig
Chatbot chatbotConfig
Service serviceConfig
// Deprecated: Use Database.URL
DatabaseURL string
// Deprecated: Use Database.Name
DatabaseName string
// Deprecated: Use Chatbot.Train
Train trainConfig
}
var Config *MuffinConfig
func init() {
godotenv.Load()
Config = &MuffinConfig{Bot: botConfig{}, Train: trainConfig{}, Database: databaseConfig{}}
Config = &MuffinConfig{}
setConfig(Config)
}
func getRequiredValue(key string) string {
value := os.Getenv(key)
if value == "" {
log.Fatalln(fmt.Sprintf("[goMuffin] .env 파일에서 필요한 '%s'값이 없어요.", key))
log.Fatalf("[goMuffin] .env 파일에서 필요한 '%s'값이 없어요.", key)
}
return value
}
@ -63,20 +77,22 @@ func getValue(key string) string {
}
func setConfig(config *MuffinConfig) {
config.Bot.Prefix = getRequiredValue("BOT_PREFIX")
config.Bot.Token = getRequiredValue("BOT_TOKEN")
config.Bot.OwnerId = getRequiredValue("BOT_OWNER_ID")
config.Bot = botConfig{
Prefix: getRequiredValue("BOT_PREFIX"),
Token: getRequiredValue("BOT_TOKEN"),
OwnerId: getRequiredValue("BOT_OWNER_ID"),
}
config.Train.UserID = getValue("TRAIN_USER_ID")
config.Database.URL = getValue("DATABASE_URL")
config.Database.HostName = getValue("DATABASE_HOSTNAME")
config.Database.Password = getValue("DATABASE_PASSWORD")
config.Database.Username = getValue("DATABASE_USERNAME")
config.Database.AuthSource = getValue("DATABASE_AUTH_SOURCE")
config.Database.Name = getRequiredValue("DATABASE_NAME")
config.Database = databaseConfig{
URL: getValue("DATABASE_URL"),
HostName: getValue("DATABASE_HOSTNAME"),
Password: getValue("DATABASE_PASSWORD"),
Username: getValue("DATABASE_USERNAME"),
AuthSource: getValue("DATABASE_AUTH_SOURCE"),
Name: getRequiredValue("DATABASE_NAME"),
}
port, err := strconv.Atoi(getValue("DATABASE_PORT"))
if err != nil {
if getValue("DATABASE_PORT") != "" && err != nil {
log.Println("[goMuffin] 'DATABASE_PORT'값을 int로 파싱할 수 없어요.")
log.Fatalln(err)
}
@ -91,7 +107,19 @@ func setConfig(config *MuffinConfig) {
config.Database.URL = fmt.Sprintf("mongodb://%s:%s@%s:%d/?authSource=%s", config.Database.Username, config.Database.Password, config.Database.HostName, config.Database.Port, config.Database.AuthSource)
}
// Deprecated된 Value
config.DatabaseURL = config.Database.URL
config.DatabaseName = config.Database.Name
config.Chatbot = chatbotConfig{
Gemini: geminiConfig{Token: getValue("CHATBOT_GEMINI_TOKEN"), PromptPath: getValue("CHATBOT_GEMINI_PROMPT_PATH"), Model: getValue("CHATBOT_GEMINI_MODEL")},
Train: trainConfig{UserId: getValue("CHATBOT_TRAIN_USER_ID")},
}
if config.Chatbot.Gemini.Model == "" {
config.Chatbot.Gemini.Model = "gemini-2.0-flash"
}
config.Train = config.Chatbot.Train
config.Service = serviceConfig{
PrivacyPolicyURL: getRequiredValue("SERVICE_PRIVACY_POLICY_URL"),
TermOfServiceURL: getRequiredValue("SERVICE_TERM_OF_SERVICE_URL"),
}
}

View file

@ -7,7 +7,7 @@ import (
"git.wh64.net/muffin/goMuffin/utils"
)
const MUFFIN_VERSION = "5.1.0-gopher_release.250524a"
const MUFFIN_VERSION = "6.0.0-madeleine_develop.250712a"
var updatedString string = utils.RegexpDecimals.FindAllStringSubmatch(MUFFIN_VERSION, -1)[3][0]

32
databases/Chat.go Normal file
View file

@ -0,0 +1,32 @@
package databases
import (
"context"
"time"
"go.mongodb.org/mongo-driver/v2/bson"
"go.mongodb.org/mongo-driver/v2/mongo"
)
type Chat struct {
Id bson.ObjectID `bson:"_id,omitempty"`
Name string `bson:"name,omitempty"`
UserId string `bson:"user_id,omitempty"`
CreatedAt time.Time `bson:"created_at,omitempty"`
}
func CreateChat(userId, name string) (*mongo.InsertOneResult, error) {
createdChat, err := Database.Chats.InsertOne(context.TODO(), Chat{UserId: userId, Name: name, CreatedAt: time.Now()})
if err != nil {
return nil, err
}
_, err = Database.Users.UpdateOne(context.TODO(), User{UserId: userId}, bson.D{{
Key: "$set",
Value: User{ChatId: createdChat.InsertedID.(bson.ObjectID)},
}})
if err != nil {
return nil, err
}
return createdChat, nil
}

View file

@ -6,17 +6,10 @@ import (
"go.mongodb.org/mongo-driver/v2/bson"
)
type InsertLearn struct {
Command string `bson:"command"`
Result string `bson:"result"`
UserId string `bson:"user_id"`
CreatedAt time.Time `bson:"created_at"`
}
type Learn struct {
Id bson.ObjectID `bson:"_id" json:"id"`
Command string `bson:"command" json:"command"`
Result string `bson:"result" json:"result"`
UserId string `bson:"user_id" json:"user_id"`
CreatedAt time.Time `bson:"created_at" json:"created_at"`
Id bson.ObjectID `bson:"_id,omitempty" json:"id"`
Command string `bson:"command,omitempty"`
Result string `bson:"result,omitempty"`
UserId string `bson:"user_id,omitempty"`
CreatedAt time.Time `bson:"created_at,omitempty"`
}

11
databases/Memory.go Normal file
View file

@ -0,0 +1,11 @@
package databases
import "go.mongodb.org/mongo-driver/v2/bson"
type Memory struct {
Id bson.ObjectID `bson:"_id,omitempty"`
UserId string `bson:"user_id,omitempty"`
Content string `bson:"content,omitempty"`
Answer string `bson:"answer,omitempty"`
ChatId bson.ObjectID `bson:"chat_id,omitempty"`
}

View file

@ -6,15 +6,9 @@ import (
"go.mongodb.org/mongo-driver/v2/bson"
)
type InsertText struct {
Text string `bson:"text" json:"text"`
Persona string `bson:"persona" json:"persona"`
CreatedAt time.Time `bson:"created_at"`
}
type Text struct {
Id bson.ObjectID `bson:"_id" json:"id"`
Text string `bson:"text" json:"text"`
Persona string `bson:"persona" json:"persona"`
CreatedAt time.Time `bson:"created_at" json:"created_at"`
Id bson.ObjectID `bson:"_id,omitempty" json:"id"`
Text string `bson:"text,omitempty" json:"text"`
Persona string `bson:"persona,omitempty" json:"persona"`
CreatedAt time.Time `bson:"created_at,omitempty"`
}

29
databases/User.go Normal file
View file

@ -0,0 +1,29 @@
package databases
import (
"context"
"time"
"go.mongodb.org/mongo-driver/v2/bson"
)
type User struct {
Id bson.ObjectID `bson:"_id,omitempty"`
UserId string `bson:"user_id,omitempty"`
Blocked bool `bson:"blocked,omitempty"`
BlockedReason string `bson:"blocked_reason,omitempty"`
ChatId bson.ObjectID `bson:"chat_id,omitempty"`
CreatedAt time.Time `bson:"created_at,omitempty"`
}
func (d *MuffinDatabase) IsUser(userId string) bool {
var user *User
d.Users.FindOne(context.TODO(), bson.D{{Key: "user_id", Value: userId}}).Decode(&user)
return user != nil
}
func (d *MuffinDatabase) IsUserBlocked(userId string) (bool, string) {
var user User
d.Users.FindOne(context.TODO(), bson.D{{Key: "user_id", Value: userId}}).Decode(&user)
return user.Blocked, user.BlockedReason
}

View file

@ -12,6 +12,9 @@ type MuffinDatabase struct {
Client *mongo.Client
Learns *mongo.Collection
Texts *mongo.Collection
Memory *mongo.Collection
Users *mongo.Collection
Chats *mongo.Collection
}
var Database *MuffinDatabase
@ -26,13 +29,16 @@ func init() {
}
func Connect() (*MuffinDatabase, error) {
client, err := mongo.Connect(options.Client().ApplyURI(configs.Config.DatabaseURL))
client, err := mongo.Connect(options.Client().ApplyURI(configs.Config.Database.URL))
if err != nil {
return nil, err
}
return &MuffinDatabase{
Client: client,
Learns: client.Database(configs.Config.DatabaseName).Collection("learn"),
Texts: client.Database(configs.Config.DatabaseName).Collection("text"),
Learns: client.Database(configs.Config.Database.Name).Collection("learn"),
Texts: client.Database(configs.Config.Database.Name).Collection("text"),
Memory: client.Database(configs.Config.Database.Name).Collection("memory"),
Users: client.Database(configs.Config.Database.Name).Collection("user"),
Chats: client.Database(configs.Config.Database.Name).Collection("chat"),
}, nil
}

17
go.mod
View file

@ -4,24 +4,35 @@ go 1.24.1
require (
github.com/LoperLee/golang-hangul-toolkit v1.1.0
github.com/bwmarrin/discordgo v0.28.1
github.com/bwmarrin/discordgo v0.28.2-0.20250520184322-b9883c495955
github.com/devproje/commando v0.1.0-alpha.1
github.com/go-sql-driver/mysql v1.9.2
github.com/joho/godotenv v1.5.1
go.mongodb.org/mongo-driver/v2 v2.1.0
google.golang.org/genai v1.6.0
)
require (
filippo.io/edwards25519 v1.1.0 // indirect
cloud.google.com/go v0.116.0 // indirect
cloud.google.com/go/auth v0.9.3 // indirect
cloud.google.com/go/compute/metadata v0.5.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/snappy v1.0.0 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/s2a-go v0.1.8 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/scram v1.1.2 // indirect
github.com/xdg-go/stringprep v1.0.4 // indirect
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
go.opencensus.io v0.24.0 // indirect
golang.org/x/crypto v0.36.0 // indirect
golang.org/x/net v0.29.0 // indirect
golang.org/x/sync v0.12.0 // indirect
golang.org/x/sys v0.31.0 // indirect
golang.org/x/text v0.23.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect
google.golang.org/grpc v1.66.2 // indirect
google.golang.org/protobuf v1.34.2 // indirect
)

115
go.sum
View file

@ -1,19 +1,56 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE=
cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U=
cloud.google.com/go/auth v0.9.3 h1:VOEUIAADkkLtyfr3BLa3R8Ed/j6w1jTBmARx+wb5w5U=
cloud.google.com/go/auth v0.9.3/go.mod h1:7z6VY+7h3KUdRov5F1i8NDP5ZzWKYmEPO842BgCsmTk=
cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY=
cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/LoperLee/golang-hangul-toolkit v1.1.0 h1:JEyLpLyA2hDQwWY9oCprHClnKIdkYVOSJzAat2uFX/A=
github.com/LoperLee/golang-hangul-toolkit v1.1.0/go.mod h1:CDbZ23/IL4v2ovWIOb7xDEiFcSc0pIIbbYTpg+gP+Sk=
github.com/bwmarrin/discordgo v0.28.1 h1:gXsuo2GBO7NbR6uqmrrBDplPUx2T3nzu775q/Rd1aG4=
github.com/bwmarrin/discordgo v0.28.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY=
github.com/bwmarrin/discordgo v0.28.2-0.20250520184322-b9883c495955 h1:ReUA/wL53HdO2jlzwwl5kVa2UJInBtHzqrvf3eVTEvk=
github.com/bwmarrin/discordgo v0.28.2-0.20250520184322-b9883c495955/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/devproje/commando v0.1.0-alpha.1 h1:JU6CKIdt1otjUKh+asCJC0yTzwVj+4Yh8KoTdzaKAkU=
github.com/devproje/commando v0.1.0-alpha.1/go.mod h1:OhrPX3mZUGSyEX/E7d1o0vaQIYkjG/N5rk6Nqwgyc7k=
github.com/go-sql-driver/mysql v1.9.2 h1:4cNKDYQ1I84SXslGddlsrMhc8k4LeDVj6Ad6WRjiHuU=
github.com/go-sql-driver/mysql v1.9.2/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM=
github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw=
github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
@ -21,6 +58,14 @@ github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
@ -32,20 +77,41 @@ github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfS
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.mongodb.org/mongo-driver/v2 v2.1.0 h1:/ELnVNjmfUKDsoBisXxuJL0noR9CfeUIrP7Yt3R+egg=
go.mongodb.org/mongo-driver/v2 v2.1.0/go.mod h1:AWiLRShSrk5RHQS3AEn3RL19rqOzVq49MCpWQ3x/huI=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -61,6 +127,43 @@ golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genai v1.6.0 h1:aG0J3QF/Ad2GsjHvY8LjRp9hiDl4hvLJN98YwkLDqFE=
google.golang.org/genai v1.6.0/go.mod h1:TyfOKRz/QyCaj6f/ZDt505x+YreXnY40l2I6k8TvgqY=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.66.2 h1:3QdXkuq3Bkh7w+ywLdLvM56cmGvQHUMZpiCzt6Rqaoo=
google.golang.org/grpc v1.66.2/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View file

@ -1,17 +1,43 @@
package handler
import (
"fmt"
"git.wh64.net/muffin/goMuffin/commands"
"git.wh64.net/muffin/goMuffin/configs"
"git.wh64.net/muffin/goMuffin/utils"
"github.com/bwmarrin/discordgo"
)
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)
} else if i.Type == discordgo.InteractionModalSubmit {
commands.Discommand.ModalRun(s, i)
var err error
switch i.Type {
case discordgo.InteractionApplicationCommand:
err = commands.Discommand.ChatInputRun(i.ApplicationCommandData().Name, s, i)
if err != nil {
goto ErrMsg
}
case discordgo.InteractionMessageComponent:
err = commands.Discommand.ComponentRun(s, i)
if err != nil {
goto ErrMsg
}
case discordgo.InteractionModalSubmit:
err = commands.Discommand.ModalRun(s, i)
if err != nil {
goto ErrMsg
}
}
// 아 몰라 goto 쓸래
ErrMsg:
owner, _ := s.User(configs.Config.Bot.OwnerId)
utils.NewMessageSender(&utils.InteractionCreate{
InteractionCreate: i,
Session: s,
}).
AddComponents(utils.GetErrorContainer(discordgo.TextDisplay{Content: fmt.Sprintf("오류가 발생하였어요. 만약 계속 발생한다면, %s으로 연락해주세요.", utils.InlineCode(owner.Username))})).
SetComponentsV2(true).
SetReply(true).
Send()
}

View file

@ -2,19 +2,16 @@ package handler
import (
"context"
"fmt"
"log"
"math/rand"
"strings"
"time"
"git.wh64.net/muffin/goMuffin/chatbot"
"git.wh64.net/muffin/goMuffin/commands"
"git.wh64.net/muffin/goMuffin/configs"
"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"
)
func argParser(content string) (args []string) {
@ -28,25 +25,6 @@ func argParser(content string) (args []string) {
return
}
func resultParser(content string, s *discordgo.Session, m *discordgo.MessageCreate) string {
result := content
userCreatedAt, _ := discordgo.SnowflakeTimestamp(m.Author.ID)
result = strings.ReplaceAll(result, "{user.name}", m.Author.Username)
result = strings.ReplaceAll(result, "{user.mention}", m.Author.Mention())
result = strings.ReplaceAll(result, "{user.globalName}", m.Author.GlobalName)
result = strings.ReplaceAll(result, "{user.id}", m.Author.ID)
result = strings.ReplaceAll(result, "{user.createdAt}", utils.Time(&userCreatedAt, utils.RelativeTime))
result = strings.ReplaceAll(result, "{user.joinedAt}", utils.Time(&m.Member.JoinedAt, utils.RelativeTime))
result = strings.ReplaceAll(result, "{muffin.version}", configs.MUFFIN_VERSION)
result = strings.ReplaceAll(result, "{muffin.updatedAt}", utils.Time(configs.UpdatedAt, utils.RelativeTime))
result = strings.ReplaceAll(result, "{muffin.startedAt}", utils.Time(configs.StartedAt, utils.RelativeTime))
result = strings.ReplaceAll(result, "{muffin.name}", s.State.User.Username)
result = strings.ReplaceAll(result, "{muffin.id}", s.State.User.ID)
return result
}
// MessageCreate is handlers of messageCreate event
func MessageCreate(s *discordgo.Session, m *discordgo.MessageCreate) {
config := configs.Config
@ -59,97 +37,73 @@ func MessageCreate(s *discordgo.Session, m *discordgo.MessageCreate) {
args := argParser(content)
command := commands.Discommand.Aliases[args[0]]
if command == "" {
s.ChannelTyping(m.ChannelID)
var data []databases.Text
var learnData []databases.Learn
var filter bson.D
ch := make(chan int)
x := rand.Intn(10)
channel, _ := s.Channel(m.ChannelID)
if channel.NSFW {
filter = bson.D{{}}
if _, err := databases.Database.Texts.InsertOne(context.TODO(), databases.InsertText{
Text: content,
Persona: fmt.Sprintf("user:%s", m.Author.Username),
CreatedAt: time.Now(),
}); err != nil {
log.Fatalln(err)
}
} else {
filter = bson.D{{Key: "persona", Value: "muffin"}}
}
go func() {
cur, err := databases.Database.Texts.Find(context.TODO(), filter)
if err != nil {
log.Fatalln(err)
}
defer cur.Close(context.TODO())
cur.All(context.TODO(), &data)
ch <- 1
}()
go func() {
cur, err := databases.Database.Learns.Find(context.TODO(), bson.D{{Key: "command", Value: content}})
if err != nil {
if err == mongo.ErrNilDocument {
learnData = []databases.Learn{}
}
log.Fatalln(err)
}
defer cur.Close(context.TODO())
cur.All(context.TODO(), &learnData)
ch <- 1
}()
for range 2 {
<-ch
}
close(ch)
if x > 2 && len(learnData) != 0 {
data := learnData[rand.Intn(len(learnData))]
user, _ := s.User(data.UserId)
result := resultParser(data.Result, s, m)
s.ChannelMessageSendComplex(m.ChannelID, &discordgo.MessageSend{
Reference: m.Reference(),
Content: fmt.Sprintf("%s\n%s", result, utils.InlineCode(fmt.Sprintf("%s님이 알려주셨어요.", user.Username))),
AllowedMentions: &discordgo.MessageAllowedMentions{
Roles: []string{},
Parse: []discordgo.AllowedMentionType{},
Users: []string{},
},
})
if command == "" || command == "대화" {
if !databases.Database.IsUser(m.Author.ID) {
utils.NewMessageSender(&utils.MessageCreate{
MessageCreate: m,
Session: s,
}).
AddComponents(utils.GetUserIsNotRegisteredErrContainer(configs.Config.Bot.Prefix)).
SetComponentsV2(true).
SetReply(true).
Send()
return
}
s.ChannelMessageSendComplex(m.ChannelID, &discordgo.MessageSend{
Reference: m.Reference(),
Content: data[rand.Intn(len(data))].Text,
AllowedMentions: &discordgo.MessageAllowedMentions{
Roles: []string{},
Parse: []discordgo.AllowedMentionType{},
Users: []string{},
},
})
blocked, reason := databases.Database.IsUserBlocked(m.Author.ID)
if blocked {
user, _ := s.User(m.Author.ID)
utils.NewMessageSender(m).
AddComponents(utils.GetUserIsBlockedContainer(user.GlobalName, reason)).
SetComponentsV2(true).
SetReply(true).
Send()
return
}
s.ChannelTyping(m.ChannelID)
str, err := chatbot.ChatBot.GetResponse(m.Author, strings.TrimPrefix(content, "대화 "))
if err != nil {
log.Println(err)
utils.NewMessageSender(&utils.MessageCreate{
MessageCreate: m,
Session: s,
}).
SetContent(str).
SetReply(true).
Send()
return
}
result := chatbot.ParseResult(str, s, m)
utils.NewMessageSender(&utils.MessageCreate{
MessageCreate: m,
Session: s,
}).
SetContent(result).
SetReply(true).
Send()
return
}
commands.Discommand.MessageRun(command, s, m, args[1:])
err := commands.Discommand.MessageRun(command, s, m, args[1:])
if err != nil {
log.Println(err)
utils.NewMessageSender(&utils.MessageCreate{
MessageCreate: m,
Session: s,
}).
AddComponents(utils.GetErrorContainer(discordgo.TextDisplay{Content: "오류가 발생하였어요. 만약 계속 발생한다면, `migan.`으로 연락해주세요."})).
SetComponentsV2(true).
SetReply(true).
Send()
return
}
return
} else {
if m.Author.ID == config.Train.UserID {
if _, err := databases.Database.Texts.InsertOne(context.TODO(), databases.InsertText{
if m.Author.ID == config.Chatbot.Train.UserId {
if _, err := databases.Database.Texts.InsertOne(context.TODO(), databases.Text{
Text: m.Content,
Persona: "muffin",
CreatedAt: time.Now(),

48
main.go
View file

@ -9,7 +9,9 @@ import (
"syscall"
"time"
"git.wh64.net/muffin/goMuffin/chatbot"
"git.wh64.net/muffin/goMuffin/commands"
"git.wh64.net/muffin/goMuffin/commands/dev"
"git.wh64.net/muffin/goMuffin/components"
"git.wh64.net/muffin/goMuffin/configs"
"git.wh64.net/muffin/goMuffin/databases"
@ -21,12 +23,36 @@ import (
"github.com/devproje/commando/types"
)
func init() {
go commands.Discommand.LoadCommand(commands.HelpCommand)
go commands.Discommand.LoadCommand(commands.DataLengthCommand)
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.LoadCommand(dev.ReloadPromptCommand)
go commands.Discommand.LoadCommand(dev.SwitchModeCommand)
go commands.Discommand.LoadCommand(commands.ChatCommand)
go commands.Discommand.LoadCommand(commands.RegisterCommand)
go commands.Discommand.LoadCommand(commands.DeregisterCommand)
go commands.Discommand.LoadCommand(dev.BlockCommand)
go commands.Discommand.LoadCommand(dev.UnblockCommand)
go commands.Discommand.LoadComponent(components.DeleteLearnedDataComponent)
go commands.Discommand.LoadComponent(components.PaginationEmbedComponent)
go commands.Discommand.LoadComponent(components.RegisterComponent)
go commands.Discommand.LoadComponent(components.DeregisterComponent)
go commands.Discommand.LoadComponent(components.SelectChatComponent)
go commands.Discommand.LoadComponent(components.DeleteChatComponent)
go commands.Discommand.LoadModal(modals.PaginationEmbedModal)
}
func main() {
command := commando.NewCommando(os.Args[1:])
config := configs.Config
if len(os.Args) > 1 {
command.Root("db-migrate", "봇의 데이터를 MariaDB에서 MongoDB로 옮깁니다.", scripts.DBMigrate)
command.Root("delete-all-commands", "봇의 모든 슬래시 커맨드를 삭제합니다.", scripts.DeleteAllCommands,
types.OptionData{
Name: "id",
@ -43,7 +69,7 @@ func main() {
command.Root("export", "머핀봇의 데이터를 추출합니다.", scripts.ExportData,
types.OptionData{
Name: "type",
Desc: "파일형식을 지정합니다. (json, txt(txt는 머핀 데이터만 적용))",
Desc: "파일형식을 지정합니다. (json, jsonl, finetune)",
Type: types.STRING,
},
types.OptionData{
@ -68,18 +94,6 @@ func main() {
dg, _ := discordgo.New("Bot " + config.Bot.Token)
go commands.Discommand.LoadCommand(commands.HelpCommand)
go commands.Discommand.LoadCommand(commands.DataLengthCommand)
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 commands.Discommand.LoadComponent(components.PaginationEmbedComponent)
go commands.Discommand.LoadModal(modals.PaginationEmbedModal)
go dg.AddHandler(handler.MessageCreate)
go dg.AddHandler(handler.InteractionCreate)
@ -89,6 +103,8 @@ func main() {
log.Fatalln(err)
}
chatbot.New(dg)
defer dg.Close()
// 봇의 상태메세지 변경
@ -111,6 +127,10 @@ func main() {
}
}
if !cmd.RegisterApplicationCommand {
continue
}
go dg.ApplicationCommandCreate(dg.State.User.ID, "", cmd.ApplicationCommand)
}

View file

@ -52,7 +52,7 @@ var PaginationEmbedModal *commands.Modal = &commands.Modal{
return true
},
Run: func(ctx *commands.ModalContext) {
Run: func(ctx *commands.ModalContext) error {
data := ctx.Inter.ModalSubmitData()
customId := data.CustomID
id := utils.GetPaginationEmbedId(customId)
@ -61,6 +61,6 @@ var PaginationEmbedModal *commands.Modal = &commands.Modal{
page, _ := strconv.Atoi(cmp.Value)
p.Set(ctx.Inter, page)
return p.Set(ctx.Inter, page)
},
}

View file

@ -1,196 +0,0 @@
package scripts
import (
"context"
"database/sql"
"fmt"
"os"
"sync"
"time"
"git.wh64.net/muffin/goMuffin/configs"
"github.com/devproje/commando"
_ "github.com/go-sql-driver/mysql"
"go.mongodb.org/mongo-driver/v2/bson"
"go.mongodb.org/mongo-driver/v2/mongo"
"go.mongodb.org/mongo-driver/v2/mongo/options"
)
var wg sync.WaitGroup
// 이 스크립트는 MariaDB -> MongoDB로의 전환을 위해 만들었음.
func DBMigrate(n *commando.Node) error {
mariaURL := os.Getenv("PREVIOUS_DATABASE_URL")
mongoURL := configs.Config.DatabaseURL
dbName := configs.Config.DatabaseName
dbConnectionQuery := "?parseTime=true"
wg.Add(3)
fmt.Println("[경고] 해당 명령어는 다음 버전에서 사라져요.")
// statement -> text
go func() {
defer wg.Done()
newDataList := []any{}
mariaDB, err := sql.Open("mysql", mariaURL+dbConnectionQuery)
if err != nil {
panic(err)
}
mongoDB, err := mongo.Connect(options.Client().ApplyURI(mongoURL))
if err != nil {
panic(err)
}
defer mongoDB.Disconnect(context.TODO())
defer mariaDB.Close()
rows, err := mariaDB.Query("select text, persona, created_at from statement;")
if err != nil {
panic(err)
}
defer rows.Close()
i := 1
for rows.Next() {
var text, persona string
var createdAt time.Time
fmt.Printf("statement %d\n", i)
err = rows.Scan(&text, &persona, &createdAt)
if err != nil {
panic(err)
}
if text == "" {
text = "살ㄹ려주세요"
}
newDataList = append(newDataList, bson.M{
"text": text,
"persona": persona,
"created_at": createdAt,
})
i++
}
_, err = mongoDB.Database(dbName).Collection("text").InsertMany(context.TODO(), newDataList)
if err != nil {
panic(err)
}
}()
// nsfw_content -> text
go func() {
defer wg.Done()
newDataList := []any{}
mariaDB, err := sql.Open("mysql", mariaURL+dbConnectionQuery)
if err != nil {
panic(err)
}
mongoDB, err := mongo.Connect(options.Client().ApplyURI(mongoURL))
if err != nil {
panic(err)
}
defer mongoDB.Disconnect(context.TODO())
defer mariaDB.Close()
rows, err := mariaDB.Query("select text, persona, created_at from nsfw_content;")
if err != nil {
panic(err)
}
defer rows.Close()
i := 1
for rows.Next() {
var text, persona string
var createdAt time.Time
fmt.Printf("nsfw_content %d\n", i)
err = rows.Scan(&text, &persona, &createdAt)
if err != nil {
panic(err)
}
if text == "" {
text = "살ㄹ려주세요"
}
newDataList = append(newDataList, bson.M{
"text": text,
"persona": persona,
"created_at": createdAt,
})
i++
}
_, err = mongoDB.Database(dbName).Collection("text").InsertMany(context.TODO(), newDataList)
if err != nil {
panic(err)
}
}()
// learn -> learn
go func() {
defer wg.Done()
newDataList := []any{}
mariaDB, err := sql.Open("mysql", mariaURL+dbConnectionQuery)
if err != nil {
panic(err)
}
mongoDB, err := mongo.Connect(options.Client().ApplyURI(mongoURL))
if err != nil {
panic(err)
}
defer mongoDB.Disconnect(context.TODO())
defer mariaDB.Close()
rows, err := mariaDB.Query("select command, result, user_id, created_at from learn;")
if err != nil {
panic(err)
}
defer rows.Close()
i := 1
for rows.Next() {
var command, result, userId string
var createdAt time.Time
fmt.Printf("learn %d\n", i)
err = rows.Scan(&command, &result, &userId, &createdAt)
if err != nil {
panic(err)
}
newDataList = append(newDataList, bson.M{
"command": command,
"result": result,
"user_id": userId,
"created_at": createdAt,
})
i++
}
_, err = mongoDB.Database(dbName).Collection("learn").InsertMany(context.TODO(), newDataList)
if err != nil {
panic(err)
}
}()
// 모든 고루틴이 끝날 떄 까지 대기
wg.Wait()
fmt.Println("데이터 마이그레이션이 끝났어요.")
return nil
}

View file

@ -17,7 +17,7 @@ import (
"go.mongodb.org/mongo-driver/v2/bson"
)
var date time.Time = time.Now()
type role string
type textJSONLData struct {
Text string `json:"text"`
@ -29,6 +29,26 @@ type learnJSONLData struct {
Result string `json:"result"`
}
type fineTuneMessageData struct {
Role role `json:"role"`
Content string `json:"content"`
}
type fineTuneJSONLData struct {
Messages []fineTuneMessageData `json:"messages"`
}
var date time.Time = time.Now()
var (
system role = "system"
user role = "user"
assistant role = "assistant"
)
const SYSTEM_PROMPT = "당신은 머핀AI입니다. 질문을 최대한 분석하지 말고, 간단히하며, 고급 개념은 대답할 수 없습니다. " +
"모르면 모른다고 말해도 괜찮습니다. 말투는 친근하되 존댓말을 사용하여야 합니다. 그리고 대답을 길게 하지 말아야 합니다. 그리고 약간 엉뚱한 면이 있어야 합니다."
func getDate() string {
year := strconv.Itoa(date.Year())
month := strconv.Itoa(int(date.Month()))
@ -90,26 +110,15 @@ func saveFileToJSON(path, name string, data any) error {
return nil
}
func saveFileToJSONL(path, name string, data any) error {
func saveFileToJSONL[T any](path, name string, data []T) error {
var content string
switch data := data.(type) {
case []textJSONLData:
for _, data := range data {
bytes, err := json.Marshal(data)
if err != nil {
return err
}
content += string(bytes) + "\n"
}
case []learnJSONLData:
for _, data := range data {
bytes, err := json.Marshal(data)
if err != nil {
return err
}
content += string(bytes) + "\n"
for _, data := range data {
bytes, err := json.Marshal(data)
if err != nil {
return err
}
content += string(bytes) + "\n"
}
f, err := os.Create(fmt.Sprintf("%s/%s.jsonl", path, name))
@ -137,8 +146,8 @@ func ExportData(n *commando.Node) error {
return err
}
if fileType != "json" && fileType != "jsonl" {
return fmt.Errorf("파일 형식은 txt또는 json또는 jsonl이여야 해요")
if fileType != "json" && fileType != "jsonl" && fileType != "finetune" {
return fmt.Errorf("파일 형식은 txt또는 json또는 jsonl, finetune이여야 해요")
}
refined, err := option.ParseBool(*n.MustGetOpt("refined"), n)
@ -213,6 +222,33 @@ func ExportData(n *commando.Node) error {
ch <- err
return
}
} else if fileType == "finetune" {
var newData []fineTuneJSONLData
for _, data := range data {
newData = append(newData, fineTuneJSONLData{
[]fineTuneMessageData{
{
Role: system,
Content: SYSTEM_PROMPT,
},
{
Role: user,
Content: "",
},
{
Role: assistant,
Content: data.Text,
},
},
})
}
err = saveFileToJSONL(path, "muffin-fine-tune", newData)
if err != nil {
ch <- err
return
}
}
fmt.Println("머핀 데이터 추출 완료")
@ -222,6 +258,10 @@ func ExportData(n *commando.Node) error {
go func() {
defer wg.Done()
if fileType == "finetune" {
return
}
var data []databases.Text
cur, err := databases.Database.Texts.Find(context.TODO(), bson.D{
@ -272,6 +312,10 @@ func ExportData(n *commando.Node) error {
go func() {
defer wg.Done()
if fileType == "finetune" {
return
}
var data []databases.Learn
cur, err := databases.Database.Learns.Find(context.TODO(), bson.D{{}})

View file

@ -9,42 +9,38 @@ import (
)
const (
DeleteLearnedData = "#muffin/deleteLearnedData@"
DeleteLearnedDataUserId = "#muffin/deleteLearnedData@"
DeleteLearnedDataCancel = "#muffin/deleteLearnedData/cancel@"
DeleteLearnedData = "#muffin/deleteLearnedData$"
PaginationEmbedPrev = "#muffin-pages/prev$"
PaginationEmbedPages = "#muffin-pages/pages$"
PaginationEmbedNext = "#muffin-pages/next$"
PaginationEmbedModal = "#muffin-pages/modal$"
PaginationEmbedSetPage = "#muffin-pages/modal/set$"
ServiceAgree = "#muffin/service/agree@"
ServiceDisagree = "#muffin/service/disagree@"
DeregisterAgree = "#muffin/deregister/agree@"
DeregisterDisagree = "#muffin/deregister/disagree@"
SelectChat = "#muffin/chat/select$"
DeleteChat = "#muffin/chat/delete$"
DeleteChatCancel = "#muffin/chat/delete/cancel@"
)
func MakeDeleteLearnedData(id string, number int) string {
return fmt.Sprintf("%s%s&No.%d", DeleteLearnedData, id, number)
}
func MakeDeleteLearnedDataUserId(userId string) string {
return fmt.Sprintf("%s%s", DeleteLearnedDataUserId, userId)
}
func MakeDeleteLearnedDataCancel(id string) string {
return fmt.Sprintf("%s%s", DeleteLearnedDataCancel, id)
func MakeDeleteLearnedData(id string, number int, userId string) string {
return fmt.Sprintf("%sid=%s&no=%d&user_id=%s", DeleteLearnedData, id, number, userId)
}
func GetDeleteLearnedDataId(customId string) (id bson.ObjectID, itemId int) {
id, _ = bson.ObjectIDFromHex(strings.ReplaceAll(RegexpItemId.ReplaceAllString(customId[len(DeleteLearnedData):], ""), "&", ""))
stringItemId := strings.ReplaceAll(RegexpItemId.FindAllString(customId, 1)[0], "No.", "")
id, _ = bson.ObjectIDFromHex(strings.ReplaceAll(RegexpDLDId.FindAllString(customId, 1)[0], "id=", ""))
stringItemId := strings.ReplaceAll(RegexpDLDItemId.FindAllString(customId, 1)[0], "no=", "")
itemId, _ = strconv.Atoi(stringItemId)
return
}
func GetDeleteLearnedDataUserId(customId string) string {
if strings.HasPrefix(customId, DeleteLearnedDataCancel) {
return customId[len(DeleteLearnedDataCancel):]
} else {
return customId[len(DeleteLearnedDataUserId):]
}
return strings.ReplaceAll(RegexpDLDUserId.FindAllString(customId, 1)[0], "user_id=", "")
}
func MakePaginationEmbedPrev(id string) string {
@ -87,3 +83,79 @@ func GetPaginationEmbedId(customId string) string {
func GetPaginationEmbedUserId(id string) string {
return RegexpPaginationEmbedId.FindAllStringSubmatch(id, 1)[0][1]
}
func MakeServiceAgree(userId string) string {
return fmt.Sprintf("%s%s", ServiceAgree, userId)
}
func MakeServiceDisagree(userId string) string {
return fmt.Sprintf("%s%s", ServiceDisagree, userId)
}
func GetServiceUserId(customId string) string {
switch {
case strings.HasPrefix(customId, ServiceAgree):
return customId[len(ServiceAgree):]
case strings.HasPrefix(customId, ServiceDisagree):
return customId[len(ServiceDisagree):]
default:
return customId
}
}
func MakeDeregisterAgree(userId string) string {
return fmt.Sprintf("%s%s", DeregisterAgree, userId)
}
func MakeDeregisterDisagree(userId string) string {
return fmt.Sprintf("%s%s", DeregisterDisagree, userId)
}
func GetDeregisterUserId(customId string) string {
switch {
case strings.HasPrefix(customId, DeregisterAgree):
return customId[len(DeregisterAgree):]
case strings.HasPrefix(customId, DeregisterDisagree):
return customId[len(DeregisterDisagree):]
default:
return customId
}
}
func MakeSelectChat(id string, number int, userId string) string {
return fmt.Sprintf("%sid=%s&no=%d&user_id=%s", DeleteLearnedData, id, number, userId)
}
func GetSelectChatId(customId string) (id bson.ObjectID, itemId int) {
id, _ = bson.ObjectIDFromHex(strings.ReplaceAll(RegexpDLDId.FindAllString(customId, 1)[0], "id=", ""))
stringItemId := strings.ReplaceAll(RegexpDLDItemId.FindAllString(customId, 1)[0], "no=", "")
itemId, _ = strconv.Atoi(stringItemId)
return
}
func GetChatUserId(customId string) string {
switch {
case strings.HasPrefix(customId, SelectChat),
strings.HasPrefix(customId, DeleteChat):
return strings.ReplaceAll(RegexpDLDUserId.FindAllString(customId, 1)[0], "user_id=", "")
case strings.HasPrefix(customId, DeleteChatCancel):
return customId[len(DeleteChatCancel):]
default:
return ""
}
}
func MakeDeleteChat(id string, number int, userId string) string {
return fmt.Sprintf("%sid=%s&no=%d&user_id=%s", DeleteLearnedData, id, number, userId)
}
func GetDeleteChatId(customId string) (id bson.ObjectID, itemId int) {
id, _ = bson.ObjectIDFromHex(strings.ReplaceAll(RegexpDLDId.FindAllString(customId, 1)[0], "id=", ""))
stringItemId := strings.ReplaceAll(RegexpDLDItemId.FindAllString(customId, 1)[0], "no=", "")
itemId, _ = strconv.Atoi(stringItemId)
return
}
func MakeDeleteChatCancel(userId string) string {
return fmt.Sprintf("%s%s", DeleteChatCancel, userId)
}

77
utils/embed.go Normal file
View file

@ -0,0 +1,77 @@
package utils
import (
"fmt"
"github.com/bwmarrin/discordgo"
)
const (
EmbedDefault int = 0xaddb87
EmbedFail int = 0xff0000
EmbedSuccess int = 0x00ff00
)
func GetErrorContainer(components ...discordgo.MessageComponent) *discordgo.Container {
c := &discordgo.Container{
Components: []discordgo.MessageComponent{
discordgo.TextDisplay{
Content: "### ❌ 오류",
},
},
}
c.Components = append(c.Components, components...)
return c
}
func GetDeclineContainer(components ...discordgo.MessageComponent) *discordgo.Container {
c := &discordgo.Container{
Components: []discordgo.MessageComponent{
discordgo.TextDisplay{
Content: "### ❌ 거부",
},
},
}
c.Components = append(c.Components, components...)
return c
}
func GetCanceledContainer(components ...discordgo.MessageComponent) *discordgo.Container {
c := &discordgo.Container{
Components: []discordgo.MessageComponent{
discordgo.TextDisplay{
Content: "### ❌ 취소",
},
},
}
c.Components = append(c.Components, components...)
return c
}
func GetSuccessContainer(components ...discordgo.MessageComponent) *discordgo.Container {
c := &discordgo.Container{
Components: []discordgo.MessageComponent{
discordgo.TextDisplay{
Content: "### ✅ 성공",
},
},
}
c.Components = append(c.Components, components...)
return c
}
func GetUserIsNotRegisteredErrContainer(prefix string) *discordgo.Container {
return GetErrorContainer(discordgo.TextDisplay{
Content: fmt.Sprintf("해당 기능은 등록된 사용자만 쓸 수 있어요. `%s가입`으로 가입해주새요.", prefix),
})
}
func GetUserIsBlockedContainer(globalName, reason string) *discordgo.Container {
return GetDeclineContainer(discordgo.TextDisplay{
Content: fmt.Sprintf("- %s님은 서비스에서 차단되었어요.\n> 사유: %s", globalName, reason),
})
}

View file

@ -1,7 +0,0 @@
package utils
const (
EmbedDefault int = 0xaddb87
EmbedFail int = 0xff0000
EmbedSuccess int = 0x00ff00
)

View file

@ -1,12 +1,6 @@
package utils
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"github.com/bwmarrin/discordgo"
)
@ -16,20 +10,37 @@ type ModalData struct {
Components []discordgo.MessageComponent `json:"components"`
}
type InteractionEdit struct {
Content *string `json:"content,omitempty"`
Components *[]discordgo.MessageComponent `json:"components,omitempty"`
Embeds *[]*discordgo.MessageEmbed `json:"embeds,omitempty"`
Flags *discordgo.MessageFlags `json:"flags,omitempty"`
Attachments *[]*discordgo.MessageAttachment `json:"attachments,omitempty"`
AllowedMentions *discordgo.MessageAllowedMentions `json:"allowed_mentions,omitempty"`
}
// 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
Options map[string]*discordgo.ApplicationCommandInteractionDataOption
Deferred bool
Replied bool
}
// Reply to this interaction.
func (i *InteractionCreate) Reply(data *discordgo.InteractionResponseData) {
i.Session.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
func (i *InteractionCreate) Reply(data *discordgo.InteractionResponseData) error {
err := i.Session.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: data,
})
if err != nil {
return err
}
i.Replied = true
return nil
}
// GetInteractionOptions to this interaction.
@ -42,39 +53,67 @@ func GetInteractionOptions(i *discordgo.InteractionCreate) map[string]*discordgo
return optsMap
}
// DeferReply to this interaction.
func (i *InteractionCreate) DeferReply(ephemeral bool) {
var flags discordgo.MessageFlags
if ephemeral {
flags = discordgo.MessageFlagsEphemeral
// NOTE: It's only can ApplicationCommand
func GetInteractionUser(i *discordgo.InteractionCreate) *discordgo.User {
if i.Member != nil {
return i.Member.User
}
i.Session.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
if i.User != nil {
return i.User
}
return nil
}
// DeferReply to this interaction.
func (i *InteractionCreate) DeferReply(data *discordgo.InteractionResponseData) error {
err := i.Session.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseDeferredChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Flags: flags,
},
Data: data,
})
if err != nil {
return err
}
i.Deferred = true
return err
}
// DeferUpdate to this interaction.
func (i *InteractionCreate) DeferUpdate() {
i.Session.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
func (i *InteractionCreate) DeferUpdate() error {
err := i.Session.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseDeferredMessageUpdate,
})
if err != nil {
return err
}
i.Deferred = true
return err
}
// EditReply to this interaction.
func (i *InteractionCreate) EditReply(data *discordgo.WebhookEdit) {
i.Session.InteractionResponseEdit(i.Interaction, data)
func (i *InteractionCreate) EditReply(data *InteractionEdit) error {
endpoint := discordgo.EndpointWebhookMessage(i.AppID, i.Token, "@original")
_, err := i.Session.RequestWithBucketID("PATCH", endpoint, *data, discordgo.EndpointWebhookToken("", ""))
i.Replied = true
return err
}
// Update to this interaction.
func (i *InteractionCreate) Update(data *discordgo.InteractionResponseData) {
i.Session.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
func (i *InteractionCreate) Update(data *discordgo.InteractionResponseData) error {
err := i.Session.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseUpdateMessage,
Data: data,
})
if err != nil {
return err
}
i.Replied = true
return err
}
func (i *InteractionCreate) ShowModal(data *ModalData) error {
@ -85,35 +124,8 @@ func (i *InteractionCreate) ShowModal(data *ModalData) error {
reqData.Type = discordgo.InteractionResponseModal
reqData.Data = *data
bin, err := json.Marshal(reqData)
if err != nil {
return err
}
buf := bytes.NewBuffer(bin)
req, err := http.NewRequest("POST", discordgo.EndpointInteractionResponse(i.ID, i.Token), buf)
if err != nil {
return err
}
req.Header.Add("Authorization", i.Session.Identify.Token)
req.Header.Add("Content-Type", "application/json")
resp, err := i.Session.Client.Do(req)
if err != nil {
return err
}
respBin, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
if resp.StatusCode != 200 {
return fmt.Errorf("%s", string(respBin))
}
defer resp.Body.Close()
return nil
endpoint := discordgo.EndpointInteractionResponse(i.ID, i.Token)
_, err := i.Session.RequestWithBucketID("POST", endpoint, reqData, endpoint)
return err
}

110
utils/messageBuilder.go Normal file
View file

@ -0,0 +1,110 @@
package utils
import (
"github.com/bwmarrin/discordgo"
)
type MessageCreate struct {
*discordgo.MessageCreate
Session *discordgo.Session
}
type MessageSender struct {
Embeds []*discordgo.MessageEmbed
Content string
Components []discordgo.MessageComponent
Ephemeral bool
Reply bool
ComponentsV2 bool
AllowedMentions *discordgo.MessageAllowedMentions
m any
}
func NewMessageSender(m any) *MessageSender {
return &MessageSender{m: m}
}
func (s *MessageSender) AddEmbeds(embeds ...*discordgo.MessageEmbed) *MessageSender {
s.Embeds = append(s.Embeds, embeds...)
return s
}
func (s *MessageSender) AddComponents(components ...discordgo.MessageComponent) *MessageSender {
s.Components = append(s.Components, components...)
return s
}
func (s *MessageSender) SetContent(content string) *MessageSender {
s.Content = content
return s
}
func (s *MessageSender) SetEphemeral(ephemeral bool) *MessageSender {
s.Ephemeral = ephemeral
return s
}
func (s *MessageSender) SetReply(reply bool) *MessageSender {
s.Reply = reply
return s
}
func (s *MessageSender) SetAllowedMentions(allowedMentions discordgo.MessageAllowedMentions) *MessageSender {
s.AllowedMentions = &allowedMentions
return s
}
func (s *MessageSender) SetComponentsV2(componentsV2 bool) *MessageSender {
s.ComponentsV2 = componentsV2
return s
}
func (s *MessageSender) Send() error {
var flags discordgo.MessageFlags
if s.ComponentsV2 {
flags |= discordgo.MessageFlagsIsComponentsV2
}
switch m := s.m.(type) {
case *MessageCreate:
var reference *discordgo.MessageReference = nil
if s.Reply {
reference = m.Reference()
}
_, err := m.Session.ChannelMessageSendComplex(m.ChannelID, &discordgo.MessageSend{
Content: s.Content,
Embeds: s.Embeds,
Components: s.Components,
AllowedMentions: s.AllowedMentions,
Flags: flags,
Reference: reference,
})
return err
case *InteractionCreate:
if s.Ephemeral {
flags |= discordgo.MessageFlagsEphemeral
}
if m.Replied || m.Deferred {
err := m.EditReply(&InteractionEdit{
Content: &s.Content,
Embeds: &s.Embeds,
Components: &s.Components,
Flags: &flags,
})
return err
}
err := m.Reply(&discordgo.InteractionResponseData{
Content: s.Content,
Embeds: s.Embeds,
Components: s.Components,
Flags: flags,
})
return err
}
return nil
}

View file

@ -9,50 +9,81 @@ import (
// PaginationEmbed is embed with page
type PaginationEmbed struct {
Embed *discordgo.MessageEmbed
Data []string
Current int
Total int
id string
desc string
Container *discordgo.Container
Containers []*discordgo.Container
Current int
Total int
Id string
m any
}
var PaginationEmbeds = make(map[string]*PaginationEmbed)
func makeComponents(id string, current, total int) *[]discordgo.MessageComponent {
func PaginationEmbedBuilder(m any) *PaginationEmbed {
var userId string
switch m := m.(type) {
case *MessageCreate:
userId = m.Author.ID
case *InteractionCreate:
userId = m.Member.User.ID
}
id := fmt.Sprintf("%s/%d", userId, rand.Intn(100))
return &PaginationEmbed{
Current: 1,
Id: id,
m: m,
}
}
func (p *PaginationEmbed) SetContainer(container discordgo.Container) *PaginationEmbed {
p.Container = &container
return p
}
func (p *PaginationEmbed) AddContainers(container ...*discordgo.Container) *PaginationEmbed {
p.Total += len(container)
p.Containers = append(p.Containers, container...)
return p
}
func (p *PaginationEmbed) Start() error {
return startPaginationEmbed(p)
}
func makeComponents(id string, current, total int) *discordgo.ActionsRow {
disabled := false
if total == 1 {
disabled = true
}
return &[]discordgo.MessageComponent{
discordgo.ActionsRow{
Components: []discordgo.MessageComponent{
discordgo.Button{
Style: discordgo.PrimaryButton,
Label: "이전",
CustomID: MakePaginationEmbedPrev(id),
Disabled: disabled,
},
discordgo.Button{
Style: discordgo.SecondaryButton,
Label: fmt.Sprintf("(%d/%d)", current, total),
CustomID: MakePaginationEmbedPages(id),
Disabled: disabled,
},
discordgo.Button{
Style: discordgo.PrimaryButton,
Label: "다음",
CustomID: MakePaginationEmbedNext(id),
Disabled: disabled,
},
return &discordgo.ActionsRow{
Components: []discordgo.MessageComponent{
discordgo.Button{
Style: discordgo.PrimaryButton,
Label: "이전",
CustomID: MakePaginationEmbedPrev(id),
Disabled: disabled,
},
discordgo.Button{
Style: discordgo.SecondaryButton,
Label: fmt.Sprintf("(%d/%d)", current, total),
CustomID: MakePaginationEmbedPages(id),
Disabled: disabled,
},
discordgo.Button{
Style: discordgo.PrimaryButton,
Label: "다음",
CustomID: MakePaginationEmbedNext(id),
Disabled: disabled,
},
},
}
}
func makeDesc(desc, item string) string {
func MakeDesc(desc, item string) string {
var newDesc string
if desc == "" {
@ -63,49 +94,19 @@ func makeDesc(desc, item string) string {
return newDesc
}
// StartPaginationEmbed starts new PaginationEmbed struct
func StartPaginationEmbed(s *discordgo.Session, m any, e *discordgo.MessageEmbed, data []string, defaultDesc string) {
var userId string
func startPaginationEmbed(p *PaginationEmbed) error {
container := *p.Containers[0]
container.Components = append(container.Components, makeComponents(p.Id, p.Current, p.Total))
switch m := m.(type) {
case *discordgo.MessageCreate:
userId = m.Author.ID
case *InteractionCreate:
userId = m.Member.User.ID
}
PaginationEmbeds[p.Id] = p
id := fmt.Sprintf("%s/%d", userId, rand.Intn(12))
p := &PaginationEmbed{
Embed: e,
Data: data,
Current: 1,
Total: len(data),
id: id,
desc: defaultDesc,
}
if len(data) <= 0 {
p.Embed.Description = makeDesc(p.desc, "없음")
p.Total = 1
} else {
p.Embed.Description = makeDesc(p.desc, data[0])
}
switch m := m.(type) {
case *discordgo.MessageCreate:
s.ChannelMessageSendComplex(m.ChannelID, &discordgo.MessageSend{
Reference: m.Reference(),
Embeds: []*discordgo.MessageEmbed{p.Embed},
Components: *makeComponents(id, p.Current, p.Total),
})
case *InteractionCreate:
m.EditReply(&discordgo.WebhookEdit{
Embeds: &[]*discordgo.MessageEmbed{p.Embed},
Components: makeComponents(id, p.Current, p.Total),
})
}
PaginationEmbeds[id] = p
err := NewMessageSender(p.m).
AddComponents(container).
SetReply(true).
SetEphemeral(true).
SetComponentsV2(true).
Send()
return err
}
func GetPaginationEmbed(id string) *PaginationEmbed {
@ -118,101 +119,77 @@ func GetPaginationEmbed(id string) *PaginationEmbed {
func (p *PaginationEmbed) Prev(i *InteractionCreate) {
if p.Current == 1 {
i.Reply(&discordgo.InteractionResponseData{
Embeds: []*discordgo.MessageEmbed{
{
Title: "❌ 오류",
Description: "해당 페이지가 처음ㅇ이에요.",
Color: EmbedFail,
},
Components: []discordgo.MessageComponent{
GetErrorContainer(discordgo.TextDisplay{Content: "해당 페이지가 처음ㅇ이에요."}),
},
Flags: discordgo.MessageFlagsEphemeral,
Flags: discordgo.MessageFlagsEphemeral | discordgo.MessageFlagsIsComponentsV2,
})
return
}
p.Current -= 1
p.Embed.Description = makeDesc(p.desc, p.Data[p.Current-1])
i.Update(&discordgo.InteractionResponseData{
Embeds: []*discordgo.MessageEmbed{p.Embed},
Components: *makeComponents(p.id, p.Current, p.Total),
})
p.Set(i, p.Current)
}
func (p *PaginationEmbed) Next(i *InteractionCreate) {
if p.Current >= p.Total {
i.Reply(&discordgo.InteractionResponseData{
Embeds: []*discordgo.MessageEmbed{
{
Title: "❌ 오류",
Description: "해당 페이지가 마지막ㅇ이에요.",
Color: EmbedFail,
},
Components: []discordgo.MessageComponent{
GetErrorContainer(discordgo.TextDisplay{Content: "해당 페이지가 마지막ㅇ이에요."}),
},
Flags: discordgo.MessageFlagsEphemeral,
Flags: discordgo.MessageFlagsEphemeral | discordgo.MessageFlagsIsComponentsV2,
})
return
}
p.Current += 1
p.Embed.Description = makeDesc(p.desc, p.Data[p.Current-1])
i.Update(&discordgo.InteractionResponseData{
Embeds: []*discordgo.MessageEmbed{p.Embed},
Components: *makeComponents(p.id, p.Current, p.Total),
})
p.Set(i, p.Current)
}
func (p *PaginationEmbed) Set(i *InteractionCreate, page int) {
func (p *PaginationEmbed) Set(i *InteractionCreate, page int) error {
if page <= 0 {
i.Reply(&discordgo.InteractionResponseData{
Embeds: []*discordgo.MessageEmbed{
{
Title: "❌ 오류",
Description: "해당 값은 0보다 커야해요.",
Color: EmbedFail,
},
Components: []discordgo.MessageComponent{
GetErrorContainer(discordgo.TextDisplay{Content: "해당 값은 0보다 커야해요."}),
},
Flags: discordgo.MessageFlagsEphemeral,
Flags: discordgo.MessageFlagsEphemeral | discordgo.MessageFlagsIsComponentsV2,
})
return
return nil
}
if page >= p.Total {
if page > p.Total {
i.Reply(&discordgo.InteractionResponseData{
Embeds: []*discordgo.MessageEmbed{
{
Title: "❌ 오류",
Description: "해당 값은 총 페이지의 수보다 작아야해요.",
Color: EmbedFail,
},
Components: []discordgo.MessageComponent{
GetErrorContainer(discordgo.TextDisplay{Content: "해당 값은 총 페이지의 수보다 작아야해요."}),
},
Flags: discordgo.MessageFlagsEphemeral,
Flags: discordgo.MessageFlagsEphemeral | discordgo.MessageFlagsIsComponentsV2,
})
return
return nil
}
p.Current = page
p.Embed.Description = makeDesc(p.desc, p.Data[p.Current-1])
container := *p.Containers[p.Current-1]
container.Components = append(container.Components, makeComponents(p.Id, p.Current, p.Total))
i.Update(&discordgo.InteractionResponseData{
Embeds: []*discordgo.MessageEmbed{p.Embed},
Components: *makeComponents(p.id, p.Current, p.Total),
err := i.Update(&discordgo.InteractionResponseData{
Flags: discordgo.MessageFlagsIsComponentsV2,
Components: []discordgo.MessageComponent{container},
})
return err
}
func (p *PaginationEmbed) ShowModal(i *InteractionCreate) {
i.ShowModal(&ModalData{
CustomId: MakePaginationEmbedModal(p.id),
CustomId: MakePaginationEmbedModal(p.Id),
Title: fmt.Sprintf("%s의 리스트", i.Session.State.User.Username),
Components: []discordgo.MessageComponent{
discordgo.ActionsRow{
Components: []discordgo.MessageComponent{
discordgo.TextInput{
CustomID: MakePaginationEmbedSetPage(p.id),
CustomID: MakePaginationEmbedSetPage(p.Id),
Label: "페이지",
Style: discordgo.TextInputShort,
Placeholder: "이동할 페이지를 여기에 적어주세요.",

View file

@ -5,7 +5,9 @@ import "regexp"
var (
RegexpFlexibleString = regexp.MustCompile(`[^\s"'「」«»]+|"([^"]*)"|'([^']*)'|「([^」]*)」|«([^»]*)»`)
RegexpDecimals = regexp.MustCompile(`\d+`)
RegexpItemId = regexp.MustCompile(`No.\d+`)
RegexpDLDItemId = regexp.MustCompile(`no=\d+`)
RegexpDLDUserId = regexp.MustCompile(`user_id=\d+`)
RegexpDLDId = regexp.MustCompile(`id=[^&]*`)
RegexpEmoji = regexp.MustCompile(`<a?:\w+:\d+>`)
RegexpLearnQueryCommand = regexp.MustCompile(`단어:([^\n대답개수:]*)`)
RegexpLearnQueryResult = regexp.MustCompile(`대답:([^\n단어개수:]*)`)

10
utils/strings.go Normal file
View file

@ -0,0 +1,10 @@
package utils
import "fmt"
func AddPrefix(prefix string, arr []string) (newArr []string) {
for _, item := range arr {
newArr = append(newArr, fmt.Sprintf("%s%s", prefix, item))
}
return
}