Discordでレギオン運営支援Botを運用してみた(保守・今後の展望編)

本記事は下記の記事のその後の運用編になります。
導入までの経緯と初期導入についてはこちら

https://qiita.com/shamimi367/items/996612e2e845fe9d1db3

# 本記事の想定読者
・discordでなんらかのBotを使用している
・pythonでなにか作ってみたい
・アサルトリリィ Last Bullet（#ラスバレ）をプレイしている
・discordで専用サーバを運営しているレギオンの管理者の皆様

# 動作環境
Python 3.9.7
Heroku
Heroku Postgres
Google Cloud API(Drive API/Sheets API)
Googleスプレッドシート

# 使用ライブラリ
discord.py
gspread　
psycopg2
psycopg2.extras　
oauth2client

# 構成概略
![Untitled Diagram.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1138438/8fadb637-ff22-8ef8-c8b7-8ef8cd192340.png)

# E-R図
![LsbrBotReration.drawio (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1138438/1380db60-f5b0-6c88-c98e-5d090cc4a443.png)
後付けで作ったテーブルがある関係でリレーションが怪しい所もありますが…

# ディレクトリ構成図
```主要ファイル群
├── discordbot.py   [コアファイル]
├── cogs
│   ├── birthday.py [誕生日関係機能]
│   ├── event.py    [イベント関係機能]
│   ├── help.py     [ヘルプコマンド]
│   ├── match.py    [戦績管理機能]
│   ├── legion.py   [レギオン情報管理]
│   ├── reminder.py [リマインダー関係]
│   ├── skill.py    [スキル管理]
│   ├── vchat.py    [ボイスチャット関係]
│   └── test.py     [テストコード実行用]
├── functions.py    [共通処理]
├── app.json
├── Procfile
├── requirements.txt
├── runtime.txt
├── .profile
├── .gitignore
:
```

# 主要機能一覧
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1138438/9f8251ac-fffc-c2ea-de51-98a55130468b.png)


# 現状とその後の取り組み
その後、初期導入が落ち着いたこともあり、細かな改修ややりたかった機能実装などちまちま作っていたものが落ち着いて、前回の記事投稿から半年経とうとしてますので、簡単にまとめておきます。
技術的な内容も含まれますが、主にレギオンでDiscordのサーバ管理やってるけど、十分に活用出来てないなぁっていう方のアイデア出しの参考になれば幸いです。

### やったこと
1. 戦績管理機能の実装
1. リマインダー機能の実装
1. ボイスチャット利用状況の管理
1. 作戦情報投稿の自動化
1. コードのリファクタリング(Cog化)

## 1.戦績管理機能
本ゲームではレギオン(※1)同士でのマルチバトル(レギオンマッチ(※2)、以下レギマ)があり、マッチング済レギオンとの対戦履歴を確認したいという要望がメンバーから出ていました。
実装当初は取り急ぎ、相手レギオン名と勝敗結果だけをHeroku Postgresの単一テーブル内に記録する形を取っていましたが、
試験的にマッチ動画を録画して共有するようにしたところ、メンバーからの評判も良かった為、戦績管理機能に組み込めないか検討しました。

録画について、ゲーム側推奨プラットフォームのmildomとYoutubeの利用を検討したところ、mildomだと動画を全体公開状態にする必要があった為、URLの限定共有ができるYoutubeでのチャンネル内管理を選択しました。
運用開始直後は動画の撮影から戦績登録まで自分でやっていましたが、レギマの前準備からかかりっきりでやっていたので、正直面倒になってきたのでこの作業をメンバーに任せる事にしました。

1. 課題その1　投稿権限の管理について
Botの実装当初、レコードの追加、削除にあたる部分に関しては招待制のサーバであるにしてもセキュリティ上の理由で私のアカウント以外からは実行出来ないようにしていました。
その為、新たにロール「scoreer」を作成し、対象のユーザーアカウントに付与し、この作業を対象ユーザーに委任する事にしました。

1. 課題その2　レギオンリーグの新設
ゲーム開始直後は、週のうちの任意の曜日固定でレギマを開催する形だったものが、ゲームアップデートにより毎月の特定週に連続で6日間レギオンリーグ(以下レギリ)という形で別のマッチングルールの下でレギマをやるというシステムが追加されました。
従来1テーブルで戦績情報は管理していたのですが、ゲーム上レギオンランクの計算根拠となる母数集計ではレギリ中の戦績は加味されない為、通常レギマとレギリ中の戦績を分離する必要が出てきました。
本機能検討時はみだりにテーブルを増やしたくなかったので、1テーブルでレギリフラグを管理するカラムを追加する方法を検討しましたが、
今後、更にレギマとレギリで別要素が出てきた場合などに備え、match_recordsを通常レギマ用、match_records2をレギリ用に完全に分離し戦績を登録する形に変更しました。
レギリ中かどうかの判定にはスプレッドシート上のレギリ期間を参照する形で、日時を取得し判定、記録するテーブルを選択するという方式を取っています。

尚、過去の戦績を検索する際にはrecordsとrecords2を結合したテーブルからデータを取得しているのですべての期間からの戦績を確認することが出来ます。

動作イメージ
![match03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1138438/0378cb4e-36d8-2e91-f824-01fc5ce60ac5.png)


## 2.リマインダー機能
本ゲームに限った事ではないのですが、この手のゲームは何かとアイテムの使用期限やイベントの開催期間など、スケジュール管理を要求してきます。
イベントの開催期間やガチャのおまけなどの全員共通のスケジュールはスプシでまとめてテキストチャンネルへの自動投稿で今はリマインドしていますが、
個々で使用期限が違うアイテムやスケジュールの管理まではスプシでしきれないので、メモ機能+DMでお知らせするという形で実装しています。
ユーザー側で操作出来る機能はリマインダーメモの登録、自分のメモ一覧の表示、メモの削除となっていて、当然ですが他人が登録したメモの閲覧、削除は出来ないようになっています。

動作イメージ
![reminder.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1138438/13a966f5-9cc7-64dc-b7a4-c85b3753c2f6.gif)
(一覧表示(登録前)➡登録➡表示(期限前)➡表示(期限切れ)➡削除➡表示(削除後))


###### 今後の予定
現在の仕様では、リマインダー期限当日の午前9時に当日期限を迎えるメモがあればDMで対象者に通知を送る形になっています。
ただこれでは当日9時よりも前に設定されているものは期限終了後に通知が来るという欠陥状態なのでなんとかしたいです…



あと、現状では平文でメモが保存されているので、不用意にSQLツールでテーブルを開けないという問題があるので、早いところ暗号化処理させるようにしたいと思ってます。
(どうしてもテーブルを見る必要がある場合は、メモ欄カラムを抜いたSELECT文を発行しています、中身は個人に関わるものなので)

## 3.ボイスチャット利用状況の管理
私達のDiscordサーバではマルチバトル中だけ使うボイスチャンネルと雑談に使うチャンネル、ゲームを垂れ流すチャンネルなど、用途によって複数のボイスチャンネルを設置しています。
Discordのデフォルト機能にはVCに関するロギングの機能が無いので、サーバ管理の観点から管理者の不在時でも入退室状況の把握を出来るようにしました。
ついでに、1日ごとの最大利用時間を積算し、擬似的に1ヶ月の利用時間を算出し、月末に月報テーブルに出力しています。

動作イメージ
入退室ログの出力例
![スクリーンショット 2021-11-05 192708.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1138438/6aefe875-a9a9-7cc0-8b23-1a12c567ad9f.png)

月間ログの出力例
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1138438/782c9bb0-0f61-b8ef-9995-14dd60a2e8c5.png)
寝落ちた人が切断しないとその人の分がカウントされるので実際ここまでは使っていない…はず…

###### 今後の予定
たとえば、個人ごとの利用時間を算出して、月間の利用時間が所定の時間を超えたら使いすぎメッセージを送るみたいな使い方もで出来そうだなと…


## 4.作戦情報投稿の自動化
実装前はレギマ、レギリを円滑に進行するために、相手レギオンの情報やその日の作戦をテンプレートを使用してチャンネルに投稿していました。
ただ、レギリ中ともなると毎日やることになってテンプレをせっせこ直す作業が~~地味に面倒に感じた~~ので、予めスプシ上に記入したものをembedで整形してワンコマンド、もしくは指定時間に自動投稿するように改良しました。
イベント情報取得でスプシとpythonの連携は使い慣れてたので、どちらかというとシートの作成とembedを意図通りのレイアウトで表現すること、投稿条件の指定に時間を要しました。
ついでに、投稿と同時にlegionsテーブルに辞書型の作戦情報をhstore型に変換して登録するようにしています。

動作イメージ
![legion.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1138438/63083dc1-fd49-52af-e16d-3fff2b19d8f2.png)
前衛は本来4人です。


###### 躓いた点
1. embedの取り扱い
50項目近くある取得項目を適切に扱う為に、今回は辞書型に格納する形を選択したのですが、スプシが空欄の要素を取得した場合は空文字(`""`)ではなく`None`が入っていたので、全項目検査してNoneが入っている場合は空文字を入れるようにしました。

ここまでは良かったのですが、

``` region.py
embed.add_field(name="物理特化編成",
                value=f"{ss_dic['atk1']} {ss_dic['atk2']} {ss_dic['atk3']}",
                inline=True)
```

このような形でembed内に入力値を挿入しようとした時、atk1~3のうちのどれかに値が入ってれば問題無く表示されるんですが、全て空(つまり`None`から空文字`""`に変換された状態)だとエラーになりました。

原因：`embed.add_field()`した時のvalueには必ず値が必要(空文字ではダメ)

改善策
事前にatk1~3の値を検査して空文字に置換した後、embedに組み込む前に要素を結合、
結合後の値も空文字のままだった場は代替文字列`"なし"`を代入する

最初はフィールド自体を作成しないしようにしようと思ったのですが、そうするとinlineのレイアウトが崩れるので、空の箱は残すようにしました。

2.Botによるカスタム絵文字の投稿
サーバ内で登録しているカスタム絵文字をBotに使用させるにあたり、ユーザーが投稿する際に使うエイリアスだけでは上手に表示が出来なかった為、各絵文字の固有IDを調べ<:alias:固有ID>の形でスプシに格納しました。

> 参考：@Discord.py[サーバーのカスタム絵文字を使用する方法]
> https://note.com/discordpy/n/n6d4bed87691e

あとはスプシ内の入力項目をなるべく簡潔にするために、スプレッドシート関数とGASでガチガチに固めてます。お陰で手入力の項目が従来の方式から4分の1くらいになってます。

###### 今後の予定
作戦の自動投稿に関してはやりたいと思っていた事がおおよそ実現しているのですが、一点課題があるとすれば現状このコマンドを実行すると1度に50セル以上の値を取得する(のと、チェックフラグの書き込みもある)ので2回コマンドを叩くだけでGoogle Sheets APIの制限に達してしまいます。
レギマが23時で22時45分に自動投稿をセットしているので、万一取得エラーが発生するとすぐに再実行が出来ず情報伝達がその分遅れてしまうので、もう少し処理を簡潔に出来ないかは検討したいです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1138438/563dda57-88a5-b875-79ac-0cd29706eee7.png)


あと、APIを利用している点からある程度は目を瞑らないといけないのですが、手動でコマンド叩いてから作戦embedの投稿まで10秒近くかかります。
初回実行時は途中エラーで止まったかと思いました…。まとめられる値は予めシート上でまとめておいた方がいいのかもしれません。

## 5.コードのリファクタリング(Cog化)

> 参考 discord.py公式リファレンス
> https://discordpy.readthedocs.io/en/stable/ext/commands/cogs.html

開発当初は簡単な自動投稿だけ出来れば良いと思っていたので、1本目の記事を書いた時にはコアファイルのみにすべての処理を書いていました。
その後の拡張計画を検討した際に、このままではスパゲティになると思い処理をまとめ直して今の形に落ち着きました。
これをやらずに今の機能を全部盛り込んでたと思うとゾッとしますね…。

# スラッシュコマンドへの対応について
> discord.py開発終了の衝撃・BOT利用者への影響
> https://note.com/grapecolor/n/nb16071ae81bf

こちらの記事に書かれている通り、メインライブラリのdiscord.pyは現在既に開発を終了しており、2022年4月には現在の形でのコマンドが使用出来なくなる可能性が高そうです。

今後、discord.pyを踏襲した有志によるpythonライブラリが開発される可能性もありますが、
既存のスラッシュコマンド対応ライブラリを検討した方がいいような気もします。
その頃まで今のレギオンが続いてれば、年明けくらいからはリプレースも検討しないといけませんね…。
それまでには『今後やること』を片付けておかねば…。


# おまけ
このBotとは直接関係ありませんが、レギオン内での情報活用の一貫として、
ノーコードアプリですがAppSheetで編成バランス調整ツールを作成、使用しています。
将来的にはスマホネイティブでのリプレースも視野に入れてますが、
現状そこまで有効活用できてない事、メンバーのスマホにインストールしないと使用出来なくなる事などから、実現には至っていません。
<img width="300" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1138438/0ddd3197-ed9f-64b4-894b-1fea4989e0a0.png">
羅針盤のところには円グラフが入っていて、編成グループ毎の発動スキルのバランスなどが確認出来るようになってます。


素朴な疑問なんですが、他のレギオンさんはどういう風に情報共有とか管理とかやってるんですかね…



## 最後に
ラスバレ、面白いのでみんなやりましょう。

https://assaultlily.bushimo.jp/

本記事中の商標、イラスト等の著作権はすべて権利元に帰属します。

~~これ、Qiitaではなくnoteとかに書くべき内容だったかもしれないですね…。~~