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..afee903 100644 --- a/main.go +++ b/main.go @@ -4,10 +4,10 @@ import ( "context" "fuagfuga-2025-LinkGate/src/router" "fuagfuga-2025-LinkGate/src/usecase" + "fuagfuga-2025-LinkGate/src/usecase/discord" "log" - "time" - "os" + "time" "github.com/gin-gonic/gin" "go.mongodb.org/mongo-driver/mongo" @@ -46,8 +46,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 d9f7d22..b3873e4 100644 --- a/src/usecase/discord/init.go +++ b/src/usecase/discord/init.go @@ -1 +1,215 @@ package discord + +// このパッケージは Discord ボットへのメッセージ送信と受信を担当します。 +// LINE ボットと同様に、MongoDB に登録された新規メッセージを +// 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 トークン。 +// 環境変数 DISCORD_BOT_TOKEN に設定してください。 +var botToken = os.Getenv("DISCORD_BOT_TOKEN") + +// メッセージ送信先となる Discord チャンネル ID。 +// 環境変数 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 == "" { + 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 // その他はグレー + } + + // 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, + }, + }, + } + + body, err := json.Marshal(embedPayload) + if err != nil { + log.Printf("DiscordメッセージのJSONエンコードに失敗しました: %v", err) + return + } + + 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") + + 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送信成功 (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 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) + } } // コンソール通知