🐭

golangを用いたdiscord bot作成入門【DiscordGo】

2021/12/27に公開

目的

goとdiscordGoを用いて簡易的なdiscordのbotを作成する。
日本語の文献が少なかったため、自分が躓いた部分で他の人が躓かないように書く。
この記事によって下記がbotでできるようになる。

  • Discordへのログイン
  • イベントハンドラの追加
  • メッセージの検知
  • ユーザーへのメンション
  • ユーザーのメッセージへのリプライ

環境構築

DiscordDeveloperPortalでのアプリ作成、BotのToken発行、サーバーへの追加までは下記記事を参考にしてください。BotのTokenとClientのID(=Application ID)を取得したらenvファイルにメモしておくと良いです。
https://qiita.com/s3pt3mb3r/items/e691c878e45235a8a9e2

ライブラリの導入

各位使っているバージョンで下記ライブラリをインストールしてください。
自分はまだ1.16を使用していたためgo getを使用しました。

go get github.com/bwmarrin/discordgo
go get github.com/joho/godotenv

実装

個々のコードブロックにわけて説明するため、全文を見たい場合は一番下を確認してください。

準備

最初にenvファイルを開きトークンとクライアントIDを読み込む。
Newの部分でセッションを開き、openでwebsocketを開く。

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

イベントハンドラ

discord.AddHandlerの引数にはコールバック関数を入れる。 これがイベントハンドラ。
コールバック関数の名前は任意で決めて良いが、コールバック関数にはSessionとイベント条件を格納するための二つの引数指定が必要。 Sessionにリプライ用の関数やsend用の関数が入っている。User型の取得はm.Authorから行える。User型からユーザー名やIDを取得する。ユーザー名はサーバー内の名前ではなく共通の名前。

今回のイベント条件にはMessageCreateを入れている。これは誰かがサーバーで発言したら発火する。

discord.AddHandler(onMessageCreate)

 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 {
		sendMessage(s, m.ChannelID, u.Mention()+"なんか喋った!")
		sendReply(s, m.ChannelID, "test", m.Reference())
	}
}

メッセージの送信

sessionのChannelMessageSendを発火させれば任意のチャンネルにメッセージを送信できる。
User型のMention関数を呼ぶと<@ID>というstringが生成される。<"@"+u.ID+">こんにちは"というようなstringを作ればMention関数を呼ばずにメンションを飛ばすことができる。

sendMessage(s, m.ChannelID, u.Mention()+"なんか喋った!")

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)
   }
}

リプライの送信

sessionのChannelMessageSendReplyを発火させれば該当するメッセージにリプライを送信できる。リプライ先であるMessageReferenceはMessageCreateのReference関数を実行することで取得する。

sendReply(s, m.ChannelID, "test", m.Reference())

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)
   }
}

botの終了

deferを使いCloseさせる。
stopBotチャンネルを使い、いい感じにreturnまでいかないように止めておく。

   // 直近の関数(main)の最後に実行される
  defer discord.Close()

  fmt.Println("Listening...")
  stopBot := make(chan os.Signal, 1)
  signal.Notify(stopBot, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, os.Kill)
  <-stopBot
  return

まとめ

下記参考zenn、Qiita以外の日本語文献が皆無に近いためDiscordGoのリファレンスとにらめっこすることが多いと思います。イベントハンドラの仕様とUserの取得方法さえ把握できれば自分の作りたいものが比較的簡単に作れると思うので、諦めず是非やってみてください。

参考

全文

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

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

func main() {
   loadEnv()
   var (
   	Token   = "Bot " + os.Getenv("APP_BOT_TOKEN")
   	BotName = "<@" + os.Getenv("CLIENT_ID") + ">"
   )

   fmt.Println(Token)
   fmt.Println(BotName)

   discord, err := discordgo.New()
   discord.Token = Token
   if err != nil {
   	fmt.Println("ログインに失敗しました")
   	fmt.Println(err)
   }
   //イベントハンドラを追加
   discord.AddHandler(onMessageCreate)
   err = discord.Open()
   if err != nil {
   	fmt.Println(err)
   }
   // 直近の関数(main)の最後に実行される
   defer discord.Close()

   fmt.Println("Listening...")
   stopBot := make(chan os.Signal, 1)
   signal.Notify(stopBot, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, os.Kill)
   <-stopBot
   return
}

func loadEnv() {
   err := godotenv.Load(".env")

   if err != nil {
   	fmt.Printf(".env読み込みエラー: %v", err)
   }
   fmt.Println(".envを読み込みました。")
}

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 {
   	sendMessage(s, m.ChannelID, u.Mention()+"なんか喋った!")
   	sendReply(s, m.ChannelID, "test", 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