🤖

docker+discordgoで自作discord Bot ~環境構築編

に公開

背景

Discordの小規模な鯖運営(身内向け)に関わる機会があったので,Botなるものにも手を出してみようと思い,調べたことをメモしてみました.また,実際に動作を確認するため,発言をそのままオウム返しするBotを作成しました.

Discord Botの作成

Discord Botは,Botというだけあって,基本的には一般ユーザアカウントと同様に振舞います.そのため,discordのディベロッパーサイトからbotを作成し,トークンによって認証を行う必要があります.以下の記事が参考になります.

https://qiita.com/RyuichiroYoshida/items/37c8b19366f03064dccd

docker環境の構築

早速本題から外れますが,dockerによる環境構築をします.dockerに興味がない人は飛ばしてください.

Dockerfile

dockerfileの記載内容は以下です.今回は簡単のため,プログラムはmain.goのみとしています.

From golang:1.24.6-alpine

WORKDIR /root/src

COPY ./main.go /root/src

RUN go mod init sample_bot
RUN go mod tidy

RUN go build -o /app/myapp ./main.go

この中で注意すべき点は go mod init <プロジェクト名>go mod tidy の2行です.

golangはビルド時に go.mod ファイルに記載されたパッケージやバージョンを参照します.このファイルを作成するのがgo mod initです.ただ,簡単な例においては少し冗長なので,プログラムファイル (main.go) でインポートしているファイルを勝手に記載してほしいです.これを実現するのが go mod tidy です.

また,dockerfile内でビルドを完了しておくことで,コンテナ起動時に実行するだけでbotを起動できるようにしています.

https://qiita.com/kbys-fumi/items/b818ef13478978c6239f

docker-compose.yml

services:
  app:
    build: .
    command: /app/myapp
    ports:
      - 8080:8080
    tty: true
    env_file:
      - ./.env

ymlファイルでは,コンテナ起動と同時にbotを実行するように記述しています.また,環境変数(botが用いるトークンなど)をまとめて反映させるようにしています.

discordgo

Discord Botをgolangで自作する場合,discordgoを使うことになります.

https://github.com/bwmarrin/discordgo

認証

discordgo.New() では,トークンを用いてBotの認証を行います.また, discord.Open()では,そこで作成したインターフェースを用いてwebsocketでセッションを構築します.

discord, err := discordgo.New("Bot " + Token)
err = discord.Open()

イベントハンドラ

イベントハンドラを実装するため,以下のコールバック関数を作成した.ここでは,セッションとイベントを表す2つの引数( sm)が必要です.セッションを表すオブジェクトは,セッション内で実行可能な関数を含んでおり,イベントを表すオブジェクトは,受信したメッセージの情報(送信者や内容など)を含んでいます.そのため, if u.ID != clientIdでは,送信者のIDがBot自身のID出ない場合にリプライを返すようにしています.

func onMessageCreate(s *discordgo.Session, m *discordgo.MessageCreate) {
   clientId := os.Getenv("CLIENT_ID")
   u := m.Author
   fmt.Printf("%20s %20s(%20s) > %s\n", m.ChannelID, u.Username, u.ID, m.Content)
   if u.ID != clientId {
   	sendReply(s, m.ChannelID, m.Content, m.Reference())
   }

}

この実装では,以下の記事が非常に参考にしました.全体の実装は最後に.

https://zenn.dev/saldra/articles/4b4dbca7b8c230

実行結果

以下のようにチャット内容を繰り返します.

まとめ

とりあえず動かすことを目的に,簡単なdiscord botをdocker+golangで作成しました.今後はbotの機能追加や,それを前提としたgolangやdiscordgoの勉強を進めたいと思います.discordgo はどうやら様々なインターフェースが構造体になっているようなので(golangってそういうもの?)そのあたり注意したいです.

参考(次回記事参考予定)

https://zenn.dev/aqyuki/articles/98aa55cc9ab7e8

付録:今回の実装

package main
import (
   "fmt"
   "log"
   "os"
   "os/signal"
   "syscall"

   "github.com/bwmarrin/discordgo"
   "github.com/joho/godotenv"
)

func main() {
   Token := os.Getenv("TOKEN")

   fmt.Println("Token",Token)

   discord, err := discordgo.New("Bot " + Token)
   if err != nil {
   	fmt.Println("ログインに失敗", err)
   }

   // Add Event Handler
   discord.AddHandler(onMessageCreate)

   err = discord.Open()
   if err != nil {
   	fmt.Println("セッションのオープンに失敗", err)
   }

   defer discord.Close()

   fmt.Println("Listening...")
   waitForExitSignal()
}

func waitForExitSignal() {
   stopBot := make(chan os.Signal, 1)
   signal.Notify(stopBot, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, os.Kill)
   <-stopBot
}

func onMessageCreate(s *discordgo.Session, m *discordgo.MessageCreate) {
   clientId := os.Getenv("CLIENT_ID")
   u := m.Author
   fmt.Printf("%20s %20s(%20s) > %s\n", m.ChannelID, u.Username, u.ID, m.Content)
   if u.ID != clientId {
   	sendReply(s, m.ChannelID, m.Content, m.Reference())
   }

}

func sendMessage(s *discordgo.Session, channelID string, msg string) {
   _, err := s.ChannelMessageSend(channelID, msg)
   log.Println(">>> " + msg)
   if err != nil {
   	log.Println("Error sending message: ", err)
   }
}

func sendReply(s *discordgo.Session, channelID string, msg string, reference *discordgo.MessageReference) {
   _, err := s.ChannelMessageSendReply(channelID, msg, reference)
   if err != nil {
   	log.Println("Error sending message: ", err)
   }
}

Discussion