Go言語で作るDiscord Bot

# Go言語で作ったDiscord Botのお話

## 開発経緯
当時、スマホMMOをやっていた時ですが
血盟（チームのようなもの）でいろいろアンケート？参加集計？等を取る際に
Discordを使っていました。

それに特化したBotが欲しいなと思い作成しました。

## システム構成
今回も、いつも放置で使っている自分のPCでBotを動かしました。
というのも、永続的に動作されるサーバーの用意が結構面倒な状況に現在なっている為
致し方なくな構成ではあります。

ローカルの場合には、外部定義ファイルにデータ保管するのもお手軽であり
Googleスプレットシート等で十分にお手軽なDBのような事ができました。

今回は、外部定義の絡みが多いため
プログラム一式の公開はちょっと面倒なので避けます。

## 開発した機能
・メッセージのリアクション集計
・リアクションを集計する為のリストをBOTに作成させる
※実際には18機能ぐらいありますが、どれも組み合わせみたいなものなので・・・・

# プログラムのお話
なかなかきれいに作るのが難しく、過去に作成したプログラムから情報を抽出しています。
その為、誤記載などあったらすみません。


## Discord接続
これは、多々情報がありますが
簡潔に記載すると以下の通りです。
```golang
// discordの初期化
discord, err := discordgo.New()
// botのtokenをセット
discord.Token = "BotのTokenをセット"
// Discord上にアクションがあった場合に実行する関数
discord.AddHandler(※1　XXXXXXX) // メッセージ受信処理用
discord.AddHandler(※1　XXXXXXX) // リアクション追加時処理用
discord.AddHandler(※1　XXXXXXX) // リアクション削除時処理用
// Discord Botの開始
err = discord.Open()
```
※1セットする関数
Discordにて発生したアクションによって、受信先が変わるので
どのようなことをしたいかで設定するHandler情報をセットします。


## 例1：メッセージの受信
```
// メッセージを取得したい場合
// Session:これはどの関数でも必須
// MessageCreate:メッセージが作成された事をトリガーとする
func 任意の名前 (s *discordgo.Session, m *discordgo.MessageCreate){

}
```
### 主に使う情報
- ChannelID(m.ChangelID)
どこのチャンネルから取得した情報かを取得する。  
基本的には受信したチャネルにメッセージを返す等が多いと思います。  
- Message.ID (m.ID)
受信したメッセージのIDを取得します。
受信したメッセージに何かしたい場合には、このメッセージIDに対して
アクションを起こすようにします。
- Content(m.Content)
受信したメッセージ本文
基本的にここから情報を取得します
多分Botとしてコマンドを用意することになると思いますので
strings.Contains等で含まれてるかどうかとかで多様すると思います。

- メッセージを返却する
Botが生きてるかどうかを確認するためのコマンドを
!ok?としていたので、
m.Content(受信したメッセージ)に!ok?が含まれているかどうかを確認して
メッセージを返却しています。

```golang
	if strings.Contains(m.Message.Content, "!ok?") {
		_, err := s.ChannelMessageSend(channelID, "動作中")
	}
```

- リアクションを付ける
以下のような実装をすると、受信したメッセージにBotがリアクションとして
⭕❌🔺をつけてくれます。
```golang
// MessageReactionAdd: メッセージにリアクションを付ける提供された関数
s.MessageReactionAdd(m.ChannelID, m.Message.ID, "⭕")
s.MessageReactionAdd(m.ChannelID, m.Message.ID, "❌")
s.MessageReactionAdd(m.ChannelID, m.Message.ID, "🔺")
```

- Discord参加者のID一覧
当初、メンバーの名前で作ってしまい微妙にマッチしなかったりと
面倒な事があったので、何かするならユーザーIDを利用することがおすすめです。
表示名が欲しい場合には、ユーザーから日本語名を取得することもできますし
ニックネームから取得することも可能です。

```golang
ss := s.State // セッションから情報を取得
ls := ss.Guild(m.GuildID) // 参加してるDiscordの情報を取得
members := ls.Members // メンバー情報リストを取得
for _,member := range members{
   id := member.User.Id // メンバーのユーザーIDを取得
   userName:= member.User.UserName // ユーザー名
   userNickName:=  member.Nick // つけられたニックネーム
}
```

- Messageからリアクションを取得
この情報には、チャンネルとMessageのIDが必要です。
実際にリアクションの集計を行うときはこの関数を使っていました。
リアクションの集計を行いたいメッセージについては、
Discord上のメッセージからメッセージIDを取得できるので
botが反応するメッセージに、メッセージのリンクを付ける事で
集計したいメッセージを特定するといった仕組みで実装しました。
```golang
// 先にメッセージの情報を取得する必要がある
// messageの中にスライスで付いているアクションを取得できる。
message, _ := s.ChannelMessage(chanID, targetMessageID)
```

// 指定メッセージから指定の絵文字にリアクションをした
// ユーザー情報を取得する。
```golang
actionCount := len(message.Reactions) // アクションの数をカウント
    for i := range actionCount{
        users, _ := s.MessageReactions(targetChannelID, targetMessageID, 
        message.Reactions.[i].Emoji.Name, 100, "", "")
        for _,user := range users{
            // ここでユーザー情報を個人単位で取得できる
        }
    }
```

- リアクション者の集計と比較
これに関しては、取得したメンバー情報リストと
リアクションから取得したメンバー情報とを比較し
リアクションA：Aさん、Bさん、Cさん
リアクションB：Dさん、Eさん、Fさん
リアクション押していない人：Gさん、Hさん
等を分けることができます。
この時、Discordの部屋にサブアカウントとかで複数入ってる人がいる場合には
別途メンバーIDを保存しておき、対象から省いてあげると親切です。
※私はローカルのiniファイルに除外者リスト等を作っていました。
この除外リストももちろん自分で作ったBotへの命令コマンドで実施します。

- リアクションして欲しい人へのメンション
*discordgo.Memberから、Mention()を実行すると
メンションする為の文字列を取得できます。
この文字列をそのままメッセージ送信で送るとメンションになります。
この機能は、アンケート漏れしてる人を調べてやるより
かなり楽にプログラム内で実行できる
```golang
   mentionMessage :=  member.Mention()
_, err := s.ChannelMessageSend(m.ChannelID, mentionMessage)
```


## 例2：アクションの受信と削除
リアクションの追加と削除は別イベントで連携されますが
基本やりたいことが一緒だったりすると、中身はコピペでよかったりします。
```golang
// リアクション追加の場合
func 任意の名前 (s *discordgo.Session, r *discordgo.MessageReactionAdd) {
// ここに実行したい内容を記載
}
// リアクション削除の場合
func 任意の名前 (s *discordgo.Session, r *discordgo.MessageReactionRemove) {
// ここに実行したい内容を記載
}

```

### 主に使う情報
私の使った方法ですが
特定の集計したいメッセージの下に、リアクションをした人の
リアルタイム集計の表を作っていました。
リアクションが発生するメッセージと集計に使うメッセージを別に用意していたので
リアクションが発生→すぐ下にある集計メッセージを自動で編集して更新
する動作を行わせていました。

```
// リアクションが発生した一個後のメッセージを取得する。
message, err := s.ChannelMessages(r.ChannelID, 1, "", r.MessageID, "")
s.ChannelMessageEdit(r.ChannelID,message[0].ID,"編集後の文字列全文")
```

# まとめ
今回やりたかったことに関しては
ChannelID、MessageID、メンバー一覧等を駆使して
実現することが可能でした。

心残りは、音声チャットへの接続は何度か試行しましたが
うまくできませんでした。
読み上げBot、無償で使うと結構、使えなかったりしますよね？
文字列を音声変換して音声ファイルを出力する行為はできましたが
ボイスチャットにBotを呼んで、その音声ファイルを再生させるのが
難しかったです。

いつか再度チャレンジしてみようと思います。