Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
5 changes: 5 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -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=
Expand Down Expand Up @@ -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=
Expand Down Expand Up @@ -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=
Expand Down
8 changes: 5 additions & 3 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -46,8 +46,10 @@ func main() {

go usecase.WatchChanges(collection)

go discord.InitializeDiscordBot(collection)

// サーバーを起動
if err := r.Run(":8080"); err != nil {
log.Fatal("サーバーの起動に失敗🥺:", err)
}
}
}
214 changes: 214 additions & 0 deletions src/usecase/discord/init.go

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👁️

Original file line number Diff line number Diff line change
@@ -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()
}
11 changes: 9 additions & 2 deletions src/usecase/watch-change.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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)
}
}

// コンソール通知
Expand Down