From 7fd76ad7429c7ee1c113490bc45f0f9a18a8422e Mon Sep 17 00:00:00 2001 From: hitugihane Date: Wed, 27 Aug 2025 06:37:13 +0900 Subject: [PATCH 1/4] =?UTF-8?q?discord=5Fbot=E3=81=AE=E5=A4=A7=E6=9E=A0?= =?UTF-8?q?=E5=87=A6=E7=90=86=E3=81=AE=E8=BF=BD=E5=8A=A0=EF=BC=88=E8=A6=81?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/usecase/discord/init.go | 88 +++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/src/usecase/discord/init.go b/src/usecase/discord/init.go index d9f7d22..512eda8 100644 --- a/src/usecase/discord/init.go +++ b/src/usecase/discord/init.go @@ -1 +1,89 @@ package discord + +// このパッケージは Discord ボットへのメッセージ送信を担当します。 +// LINE ボットと同様に、MongoDB に登録された新規メッセージを +// Discord のチャンネルへ転送する用途を想定しています。 + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "log" + "net/http" + "os" + "time" + + "fuagfuga-2025-LinkGate/src/model" +) + +// Discord API への認証に使用する Bot トークン。 +var botToken = os.Getenv("DISCORD_BOT_TOKEN") + +// メッセージ送信先となる Discord チャンネル ID。 +var channelID = os.Getenv("DISCORD_CHANNEL_ID") + +// discordMessage は Discord API へ送信する JSON ペイロードの形式です。 +// 必要に応じてフィールドを拡張してください。 +type discordMessage struct { + Content string `json:"content"` +} + +// CreateDiscordMessage は受け取ったメッセージを Discord の指定チャンネルに送信します。 +// +// 例: +// from: 山田太郎さん +// +// こんにちは! +// +// (Platform: Slack) +// +// この関数は MongoDB への新規挿入イベントをフックして呼び出されることを想定しています。 +func CreateDiscordMessage(msg model.Message) { + // 認証情報が設定されていない場合は処理を中断 + if botToken == "" || channelID == "" { + log.Println("DiscordのBotトークンまたはチャンネルIDが設定されていません") + return + } + + // 送信内容を作成 + content := fmt.Sprintf("from: %sさん\n\n%s\n\n(Platform: %s)", msg.User.Name, msg.Content.Text, msg.User.Platform) + + payload := discordMessage{ + Content: content, + } + body, err := json.Marshal(payload) + if err != nil { + log.Printf("DiscordメッセージのJSONエンコードに失敗しました: %v", err) + return + } + + // Discord API エンドポイント + url := fmt.Sprintf("https://discord.com/api/v10/channels/%s/messages", channelID) + + req, err := http.NewRequest("POST", url, bytes.NewBuffer(body)) + if err != nil { + log.Printf("Discordリクエストの生成に失敗しました: %v", err) + return + } + // 認証トークンをヘッダーに設定 + req.Header.Set("Authorization", "Bot "+botToken) + req.Header.Set("Content-Type", "application/json") + + // HTTP クライアントを用意 + client := &http.Client{Timeout: 10 * time.Second} + resp, err := client.Do(req) + if err != nil { + log.Printf("Discord API 呼び出しでエラーが発生しました: %v", err) + return + } + defer resp.Body.Close() + // ステータスコードチェック + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + // 失敗時はレスポンスボディを読み取りログ出力 + respBody, _ := io.ReadAll(resp.Body) + log.Printf("Discord API からエラーコード %d が返されました: %s", resp.StatusCode, string(respBody)) + return + } + log.Println("Discord送信成功") +} From d2ec3716fc85a4abedcdd3b66ce78c954f89c475 Mon Sep 17 00:00:00 2001 From: hitugihane Date: Wed, 27 Aug 2025 21:59:27 +0000 Subject: [PATCH 2/4] =?UTF-8?q?add:=20discord=E3=81=A7=E3=81=AE=E9=80=81?= =?UTF-8?q?=E4=BF=A1=E5=87=A6=E7=90=86=E3=81=AE=E5=AE=9F=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/usecase/discord/init.go | 138 +++++++++++++++++++----------------- src/usecase/watch-change.go | 11 ++- 2 files changed, 81 insertions(+), 68 deletions(-) diff --git a/src/usecase/discord/init.go b/src/usecase/discord/init.go index 512eda8..72a0d9a 100644 --- a/src/usecase/discord/init.go +++ b/src/usecase/discord/init.go @@ -5,85 +5,91 @@ package discord // Discord のチャンネルへ転送する用途を想定しています。 import ( - "bytes" - "encoding/json" - "fmt" - "io" - "log" - "net/http" - "os" - "time" + "bytes" + "encoding/json" + "fmt" + "io" + "log" + "net/http" + "os" + "time" - "fuagfuga-2025-LinkGate/src/model" + "fuagfuga-2025-LinkGate/src/model" ) // Discord API への認証に使用する Bot トークン。 +// 環境変数 DISCORD_BOT_TOKEN に設定してください。 var botToken = os.Getenv("DISCORD_BOT_TOKEN") // メッセージ送信先となる Discord チャンネル ID。 +// 環境変数 DISCORD_CHANNEL_ID に設定してください。 var channelID = os.Getenv("DISCORD_CHANNEL_ID") -// discordMessage は Discord API へ送信する JSON ペイロードの形式です。 -// 必要に応じてフィールドを拡張してください。 -type discordMessage struct { - Content string `json:"content"` -} - -// CreateDiscordMessage は受け取ったメッセージを Discord の指定チャンネルに送信します。 -// -// 例: -// from: 山田太郎さん -// -// こんにちは! -// -// (Platform: Slack) -// -// この関数は MongoDB への新規挿入イベントをフックして呼び出されることを想定しています。 +// CreateDiscordMessage はMongoDBに新規追加されたメッセージをDiscordへ転送します。 func CreateDiscordMessage(msg model.Message) { - // 認証情報が設定されていない場合は処理を中断 - if botToken == "" || channelID == "" { - log.Println("DiscordのBotトークンまたはチャンネルIDが設定されていません") - return - } + if botToken == "" { + log.Println("DiscordのBotトークンが設定されていません") + return + } + if channelID == "" { + log.Println("DiscordのチャンネルIDが設定されていません") + return + } + + // プラットフォームごとにEmbedカラーを設定 + var colorInt int + switch msg.User.Platform { + case model.PlatformDiscord: + colorInt = 0x5865F2 // Discordブランドカラー(紫) + case model.PlatformLINE: + colorInt = 0x00C300 // LINEブランドカラー(ライトグリーン) + case model.PlatformSlack: + colorInt = 0xFFFFFF // Slackは白 + default: + colorInt = 0xCCCCCC // その他はグレー + } - // 送信内容を作成 - content := fmt.Sprintf("from: %sさん\n\n%s\n\n(Platform: %s)", msg.User.Name, msg.Content.Text, msg.User.Platform) + // Embedペイロードを作成 + embedPayload := map[string]interface{}{ + "embeds": []map[string]interface{}{ + { + "color": colorInt, + "author": map[string]interface{}{ + "name": msg.User.Name, + "icon_url": msg.User.IconUrl, + }, + "description": msg.Content.Text, + }, + }, + } - payload := discordMessage{ - Content: content, - } - body, err := json.Marshal(payload) - if err != nil { - log.Printf("DiscordメッセージのJSONエンコードに失敗しました: %v", err) - return - } + body, err := json.Marshal(embedPayload) + if err != nil { + log.Printf("DiscordメッセージのJSONエンコードに失敗しました: %v", err) + return + } - // Discord API エンドポイント - url := fmt.Sprintf("https://discord.com/api/v10/channels/%s/messages", channelID) + url := fmt.Sprintf("https://discord.com/api/v10/channels/%s/messages", channelID) + req, err := http.NewRequest("POST", url, bytes.NewBuffer(body)) + if err != nil { + log.Printf("Discordリクエストの生成に失敗しました: %v", err) + return + } + req.Header.Set("Authorization", "Bot "+botToken) + req.Header.Set("Content-Type", "application/json") - req, err := http.NewRequest("POST", url, bytes.NewBuffer(body)) - if err != nil { - log.Printf("Discordリクエストの生成に失敗しました: %v", err) - return - } - // 認証トークンをヘッダーに設定 - req.Header.Set("Authorization", "Bot "+botToken) - req.Header.Set("Content-Type", "application/json") + client := &http.Client{Timeout: 10 * time.Second} + resp, err := client.Do(req) + if err != nil { + log.Printf("Discord API 呼び出しでエラーが発生しました: %v", err) + return + } + defer resp.Body.Close() - // HTTP クライアントを用意 - client := &http.Client{Timeout: 10 * time.Second} - resp, err := client.Do(req) - if err != nil { - log.Printf("Discord API 呼び出しでエラーが発生しました: %v", err) - return - } - defer resp.Body.Close() - // ステータスコードチェック - if resp.StatusCode < 200 || resp.StatusCode >= 300 { - // 失敗時はレスポンスボディを読み取りログ出力 - respBody, _ := io.ReadAll(resp.Body) - log.Printf("Discord API からエラーコード %d が返されました: %s", resp.StatusCode, string(respBody)) - return - } - log.Println("Discord送信成功") + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + respBody, _ := io.ReadAll(resp.Body) + log.Printf("Discord API からエラーコード %d が返されました: %s", resp.StatusCode, string(respBody)) + return + } + log.Println("Discord送信成功 (Embed形式)") } diff --git a/src/usecase/watch-change.go b/src/usecase/watch-change.go index 15d9f17..eb60f33 100644 --- a/src/usecase/watch-change.go +++ b/src/usecase/watch-change.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "fuagfuga-2025-LinkGate/src/model" + "fuagfuga-2025-LinkGate/src/usecase/discord" "fuagfuga-2025-LinkGate/src/usecase/line" "log" @@ -38,10 +39,16 @@ func WatchChanges(coll *mongo.Collection) { fullDoc := event.FullDocument platform := fullDoc.User.Platform - if platform != model.PlatformLINE { - if event.OperationType == "insert" { + // 新規メッセージ挿入時に各プラットフォームへ転送します。 + if event.OperationType == "insert" { + // 元プラットフォームがLINEでなければLINEへ送信 + if platform != model.PlatformLINE { line.CreateLINEMessage(fullDoc) } + // 元プラットフォームがDiscordでなければDiscordへ送信 + if platform != model.PlatformDiscord { + discord.CreateDiscordMessage(fullDoc) + } } // コンソール通知 From 7d397f3ae45bfbe792d67cbe51b5184fb0002d92 Mon Sep 17 00:00:00 2001 From: hitugihane Date: Sun, 31 Aug 2025 15:54:26 +0900 Subject: [PATCH 3/4] =?UTF-8?q?add:=20Disocrd=E3=81=AE=E3=83=81=E3=83=A3?= =?UTF-8?q?=E3=83=B3=E3=83=8D=E3=83=AB=E3=82=92=E7=9B=A3=E8=A6=96=E3=81=97?= =?UTF-8?q?=E3=81=A6=E3=83=A1=E3=83=83=E3=82=BB=E3=83=BC=E3=82=B8=E3=82=92?= =?UTF-8?q?DB=E7=99=BB=E9=8C=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- go.mod | 2 + go.sum | 5 ++ main.go | 7 +- src/usecase/discord/init.go | 124 +++++++++++++++++++++++++++++++++++- 4 files changed, 133 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 7ba7d51..3aeae7f 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( ) require ( + github.com/bwmarrin/discordgo v0.29.0 // indirect github.com/bytedance/sonic v1.11.6 // indirect github.com/bytedance/sonic/loader v0.1.1 // indirect github.com/cloudwego/base64x v0.1.4 // indirect @@ -19,6 +20,7 @@ require ( github.com/go-playground/validator/v10 v10.20.0 // indirect github.com/goccy/go-json v0.10.2 // indirect github.com/golang/snappy v0.0.4 // indirect + github.com/gorilla/websocket v1.4.2 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.16.7 // indirect github.com/klauspost/cpuid/v2 v2.2.7 // indirect diff --git a/go.sum b/go.sum index 61eca92..aca9edb 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/bwmarrin/discordgo v0.29.0 h1:FmWeXFaKUwrcL3Cx65c20bTRW+vOb6k8AnaP+EgjDno= +github.com/bwmarrin/discordgo v0.29.0/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= @@ -31,6 +33,8 @@ github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW 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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= @@ -95,6 +99,7 @@ golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUu golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +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.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= diff --git a/main.go b/main.go index d7630e7..03a6cb1 100644 --- a/main.go +++ b/main.go @@ -4,11 +4,10 @@ import ( "context" "fuagfuga-2025-LinkGate/src/router" "fuagfuga-2025-LinkGate/src/usecase" + "fuagfuga-2025-LinkGate/src/usecase/discord" "log" "time" - "os" - "github.com/gin-gonic/gin" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" @@ -46,8 +45,10 @@ func main() { go usecase.WatchChanges(collection) + go discord.InitializeDiscordBot(collection) + // サーバーを起動 if err := r.Run(":8080"); err != nil { log.Fatal("サーバーの起動に失敗🥺:", err) } -} +} \ No newline at end of file diff --git a/src/usecase/discord/init.go b/src/usecase/discord/init.go index 72a0d9a..b3873e4 100644 --- a/src/usecase/discord/init.go +++ b/src/usecase/discord/init.go @@ -1,20 +1,27 @@ package discord -// このパッケージは Discord ボットへのメッセージ送信を担当します。 +// このパッケージは Discord ボットへのメッセージ送信と受信を担当します。 // LINE ボットと同様に、MongoDB に登録された新規メッセージを -// Discord のチャンネルへ転送する用途を想定しています。 +// Discord のチャンネルへ転送し、Discord からのメッセージを監視してDBに保存します。 import ( "bytes" + "context" "encoding/json" "fmt" "io" "log" "net/http" "os" + "os/signal" + "strings" + "syscall" "time" "fuagfuga-2025-LinkGate/src/model" + "github.com/bwmarrin/discordgo" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo" ) // Discord API への認証に使用する Bot トークン。 @@ -25,6 +32,11 @@ var botToken = os.Getenv("DISCORD_BOT_TOKEN") // 環境変数 DISCORD_CHANNEL_ID に設定してください。 var channelID = os.Getenv("DISCORD_CHANNEL_ID") +// Discord セッション +var session *discordgo.Session + +var mongoCollection *mongo.Collection + // CreateDiscordMessage はMongoDBに新規追加されたメッセージをDiscordへ転送します。 func CreateDiscordMessage(msg model.Message) { if botToken == "" { @@ -93,3 +105,111 @@ func CreateDiscordMessage(msg model.Message) { } log.Println("Discord送信成功 (Embed形式)") } + +func StartDiscordBot(collection *mongo.Collection) error { + if botToken == "" { + log.Println("DiscordのBotトークンが設定されていません") + return fmt.Errorf("DISCORD_BOT_TOKEN is not set") + } + if channelID == "" { + log.Println("DiscordのチャンネルIDが設定されていません") + return fmt.Errorf("DISCORD_CHANNEL_ID is not set") + } + + mongoCollection = collection + + var err error + session, err = discordgo.New("Bot " + botToken) + if err != nil { + log.Printf("Discordセッションの作成に失敗しました: %v", err) + return err + } + + session.AddHandler(messageCreate) + + session.Identify.Intents = discordgo.IntentsGuildMessages + + err = session.Open() + if err != nil { + log.Printf("Discordセッションのオープンに失敗しました: %v", err) + return err + } + + log.Println("🔍 Discord bot started and monitoring messages...") + return nil +} + +func CloseDiscordBot() { + if session != nil { + session.Close() + } +} + +func messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) { + if m.Author.ID == s.State.User.ID { + return + } + + if m.ChannelID != channelID { + return + } + + if m.Type != discordgo.MessageTypeDefault { + return + } + + SaveDiscordMessageToMongoDB(s, m) +} + +func SaveDiscordMessageToMongoDB(s *discordgo.Session, m *discordgo.MessageCreate) { + if mongoCollection == nil { + log.Println("MongoDB collection is not initialized") + return + } + + userName := m.Author.Username + if m.Member != nil && m.Member.Nick != "" { + userName = m.Member.Nick + } + + iconURL := m.Author.AvatarURL("64") + + var message model.Message + + // Message構造体に保存内容を格納 + message.ID = primitive.NewObjectID() + message.User.ID = primitive.NewObjectID() + message.User.UserID = m.Author.ID + message.User.Platform = model.PlatformDiscord + message.User.Name = userName + message.User.IconUrl = iconURL + message.Content.ID = primitive.NewObjectID() + message.Content.Text = strings.TrimSpace(m.Content) + message.CreatedAt = time.Now() + + // MongoDB にドキュメントを挿入 + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + if _, err := mongoCollection.InsertOne(ctx, message); err != nil { + log.Printf("Discordメッセージのデータ登録に失敗しました: %v", err) + return + } + + log.Printf("Discord message saved: %s from %s", message.Content.Text, userName) +} + +func InitializeDiscordBot(collection *mongo.Collection) { + if err := StartDiscordBot(collection); err != nil { + log.Printf("Discord ボットの起動に失敗しました: %v", err) + return + } + + log.Println("🔍 Discord bot initialized successfully") + + stop := make(chan os.Signal, 1) + signal.Notify(stop, os.Interrupt, syscall.SIGTERM) + + <-stop + log.Println("🛑 Discord bot shutting down gracefully...") + CloseDiscordBot() +} \ No newline at end of file From a582cb948c022a51727b3be20047438b77514c17 Mon Sep 17 00:00:00 2001 From: hitugihane Date: Sun, 31 Aug 2025 16:01:13 +0900 Subject: [PATCH 4/4] =?UTF-8?q?fix:=20=E3=82=A8=E3=83=A9=E3=83=BC=E5=AF=BE?= =?UTF-8?q?=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.go | 1 + 1 file changed, 1 insertion(+) diff --git a/main.go b/main.go index 03a6cb1..afee903 100644 --- a/main.go +++ b/main.go @@ -6,6 +6,7 @@ import ( "fuagfuga-2025-LinkGate/src/usecase" "fuagfuga-2025-LinkGate/src/usecase/discord" "log" + "os" "time" "github.com/gin-gonic/gin"