DiscordでPythonベースのBotを運用する



#本記事の趣旨
初投稿です。Discordのサーバー上で動かすBotの作成メモ、詰まったことなどの備忘録がメインです。最近安定してきたので、いちどこの辺でまとめておきます。

各PaaSへの登録、デプロイ手順は先人皆さんの秀逸な記事を参考にさせて頂きましたので割愛しています。
>Discord Bot 最速チュートリアル【Python&Heroku&GitHub】
>https://qiita.com/1ntegrale9/items/aa4b373e8895273875a8

#動作環境
Python 3.x
Heroku
Heroku Postgres
Google Cloud API(DriveAPI/SheetsAPI)
Googleスプレッドシート

#使用パッケージ
discord.py　(これが無いと始まりません)
gspread　(pythonからGoogleスプレッドシートを操作)
psycopg2　(PythonからPostgresSQLを操作)
oauth2client　(Google Cloud APIの認証に使用)


#なぜ作ろうと思ったか
現在プレイしている[ソーシャルゲーム](https://assaultlily.bushimo.jp/)でチーム(レギオン)を組んでおり、
公式サイドでDiscordの使用を推奨している為、それに乗っかって連絡用にメンバー専用サーバを作成。
利便性を向上させたいと思い補助ツールの導入を検討したところ、簡単な自動投稿などは既存の広く知られているbotサービスで実現できそうだけど、
将来的な拡張性を考えた結果、せっかくなので自分で作ってみようということになりました。

https://assaultlily.bushimo.jp/

#おおまかな仕組み
Heroku上にdiscordbot(以下チャーミィ)をデプロイした上で、Google Cloud APIを利用してGoogleスプレッドシートの情報を参照、およびHeroku Postgresから必要な情報を取得しています。
チャーミィにプレフィックス付きのコマンドを投げると内容に応じて情報を参照し、整理して返答をする仕組みです。
![Untitled Diagram.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1138438/8fadb637-ff22-8ef8-c8b7-8ef8cd192340.png)

ちなみにIDEはPyCharm(ぱいちゃーむ)です。

#現時点で実装できている機能
##自動投稿
[毎日]
定時投稿(朝もしくは夜)
(マルチバトルコンテンツの予定を投稿、曜日別に案内内容を変えたかった為曜日はリストで管理)
[条件を満たした場合]
スプレッドシート内の情報を参照して
　　　・ゲーム内イベントの終了日当日
　　　・定期メンテナンス当日
　　　・ゲーム内アイテム(イベントメダル/ガチャメダル)交換期限日当日
データベースに登録されている情報を参照して
　　　・キャラクターの誕生日当日の朝
にリマインダーとして自動投稿で告知

##コマンド(プレフィックス'/'を付けた任意のもの)
![charmy.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1138438/e89bd733-6130-e0c5-631b-8bdae1c4fe19.png)



#当初からの変更点
実装最初期は外征とレギマのリマインダーのみで、次のバージョンでイベント情報の通知を追加、参照する情報は全て直接コードに記述していました。

```python:discordbot.py
# イベント開催情報
event_name = 'アーセナルジェラシー'
event_date = '2021-04-30 16:59:59'
# メンテナンス情報
maintenance_date = '2021-04-21 14:00:00'
######################################

```
その為、イベントの切り替わりや定期メンテの度にコードを書き換えてデプロイし直す必要があった為、Googleスプレッドシートを参照する方式に変更しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1138438/fd919b0c-0702-8b21-ea28-9c0328f5d5cb.png)


```python:discordbot.py
# イベント開催情報
event1_name = worksheet.acell('B2').value
event1_date = dt.strptime(worksheet.acell('B3').value, '%Y-%m-%d %H:%M:%S')
event1_medal = dt.strptime(worksheet.acell('B4').value, '%Y-%m-%d %H:%M:%S')
event2_name = worksheet.acell('D2').value
event2_date = dt.strptime(worksheet.acell('D3').value, '%Y-%m-%d %H:%M:%S')
event2_medal = dt.strptime(worksheet.acell('D4').value, '%Y-%m-%d %H:%M:%S')
# ガチャメダル引き換え期限
gacha1_name = worksheet.acell('B6').value
gacha1_expiry = dt.strptime(worksheet.acell('B7').value, '%Y-%m-%d %H:%M:%S')
gacha2_name = worksheet.acell('D6').value
gacha2_expiry = dt.strptime(worksheet.acell('D7').value, '%Y-%m-%d %H:%M:%S')

```

その結果、外出先でスマホからでも参照する情報を直接書き換えられるようになって、
メンテナンス性が大幅に向上したので、とても楽に管理出来るようになりました。

Pythonファイルからスプレッドシートの操作方法はこちらを参考にさせて頂きました。
>【もう迷わない】Pythonでスプレッドシートに読み書きする初期設定まとめ｜たぬハック
> https://tanuhack.com/operate-spreadsheet/ 

スプレッドシートから取ってきた日付情報はstr型で格納されているので、
予めstrptime関数でdatetime型に変更してあります。

event1とevent2はどちらに開催中イベントが入ってても開催中イベントを判定して
適切なイベントを表示させています。

#データベースの導入
今回、スケジュールの管理の他、ゲーム内の情報を整理して表示する為にデータベースを作成しました。
データのボリューム的にスプレッドシートだけで出来なくも無さそうでしたが、ゲームリリースからまだそんなに経っていない事もあり、今後のキャラ追加にも柔軟に対応できるようにという狙いです。
MySQLの方が馴染みがあったのですが、Herokuが標準でサポートしているものがPostgreSQLという事で、今回初めて触ってみました。

##DB導入にあたっての懸念
1.セキュリティ面での不安
コマンドで特定のクエリを走らせるだけであれば、大きな不安は無かったのですが、
LIKE句による検索を受け付ける予定をしていたのでSQLインジェクション対策は必須でした。

と、思いきゃpsycopg2はデフォルトでプレースホルダを使って変数を渡せる(エスケープもされる)という事で、この点は杞憂でした。流石…

2.コスト面での不安
自身の勉強も兼ねているとはいえ、出来ればお金はかけたくない…という事で、調べたところ、ログ監査やバックアップを気にしなければ『hobby-dev』プランなら費用はかかららずに済みそうです。
今の所、チャーミィ側からDBに記録するような運用は想定していないので、レコード行数についても心配する必要は無さそうかなと。
今後、マルチコンテンツの戦績を記録するなどの機能拡張があった場合はその限りではないですが…。

#Google Cloud APIの鍵を適切に管理する
このBotではgspreadを使ってGoogleスプレッドシートの値を参照しているので、
GCPの認証鍵となるjsonファイルをHerokuのコンテナ(Dyno)上に置く必要があります。
最初うっかりでプライベートリポジトリに上げてしまって、そのまま使ってたのですが、
GCPの[ベストプラクティス](https://cloud.google.com/blog/ja/products/gcp/help-keep-your-google-cloud-service-account-keys-safe)からかけ離れた運用になっていた為、修正しました。(猛省)

Dynoは24時間に1回以上自動的に再起動するようにスケジュールされている為、最初どうすればリポジトリに載せずに安全に鍵を管理出来るのかという部分で詰まってました。

>Heroku で Google Cloud API の認証を通す方法 
>http://disq.us/t/3qed9l5 

上記のページを参考にさせて頂きました。
具体的な手順は

1. Herokuの環境変数にjsonファイル(認証鍵)の中身を格納する
1. ホームディレクトリに.profileファイルを作成する
1. Herokuの環境変数に2.内で指定したjsonファイルのパスを指定する
1. ソース内で環境変数を呼び出す

ターミナルから`heroku config:set`でもコンソールの「Config Vars」からの
設定でも問題なく行けました。
Dyno再起動時、自動的に`.profile`が読まれてjsonファイルが作成され、
環境変数から呼び出せるようになるという流れらしい。


#今後やりたいこと
大体現時点でメンバー側に提供したい機能(自己満足)はおおよそ実装できたので、
今後は、ゲーム内のマルチコンテンツの戦績記録や編成情報の管理に手を出していければ面白いかなーと。
たとえば、バトルの結果画面のスクショを投げたら、OpenCVなりTesseractを使って文字認識、戦績をデータベースに登録みたいな感じで自動化出来たら便利かなと思ってみたり
もしくは、有志で公開されている雑談APIみたいなものを組み込んで会話機能を拡張するなり、会話を成立させるようにするのも面白いかなと。

ゲーム内のバトルパラメーターなんかも公式Discord内で情報解析が進んできているので、その辺りの情報を組み込んだら更に活用していけそうな雰囲気があります。(バトルにガチなところではないですが)

リポジトリは、セキュリティ的に上げちゃいけないファイルはもう無い状態になってますが、
当面はプライベートリポジトリにしておきます。
ここまで色々やっても全部無料枠内で出来るんだから凄いですよね…。

最後に…
ポ○ラボさん…レギオンマッチのバトルログを秒単位まで細かく見れるのなら、
もういっそのことcsvでダウンロードできるようになりませんか…?