VRフォトコン管理Botを作った

以前、ぶいあーる写真部という Discord サーバーを立ち上げた。
VRChat でバーチャルフォトを楽しむ人たちのコミュニティで、現在は200名ほどが参加している。

立ち上げた動機はいくつかあったけれど、一番大きかったのは「VRフォトコンの開催情報をひとつにまとめたい」という気持ちだった。VRChatのフォトコンは各ワールドや主催者ごとにばらばらに告知されていて、見逃してしまうことが多い。そういう情報をGoogleカレンダーにまとめて共有できればいいな、というのが最初のアイデアだった。

ただ、忙しさや撮影に関する悩みなど色々な理由が重なって、正直あまりサーバーを動かしてこられていなかった。カレンダーへの記入も全部手動で、誰かに申請を受けて内容を確認して、承認したらカレンダーに入力して……という作業が地味に積み上がっていた。

これをBotに任せよう、と思い立って開発したのが CalenderBot(仮称) だ。

何をするBotか

イベントの申請から告知まで、一連の流れをDiscord上で完結させる。大まかな動きはこうだ。

  1. ユーザーが /event request またはパネルのボタンからフォームを送信
  2. 管理者チャンネルに承認ボタン付きの申請が届く
  3. 管理者が承認すると、告知チャンネルへ自動投稿 + Google カレンダーへ同期
  4. 締め切り前にリマインダーDMを購読者全員に送信
  5. イベント終了後、告知メッセージを自動で「終了」表示に書き換え

地味にうれしいのがリマインダーの仕組みで、告知メッセージへの 🔔 リアクションで購読登録できる。締め切り何時間前に知らせてほしいかはサーバーごとに設定できて、当サーバーでは3日前と3時間前の2回にしているが、管理用コマンドで簡単に修正できる。  

技術スタック

Python 3.11 + discord.py で書いた。DBはSQLiteをWALモードで使っていて、スケジューラーはAPScheduler。Google Calendar連携はサービスアカウント認証で動かしている。

SQLiteを選んだのは単純に規模感に見合っているから。200人規模のサーバーで、同時書き込みが爆発的に増えることはまず考えられないので、WALモードで並行性を確保すれば十分だと判断した。

作っていて難しかったところ

一番ハマったのは Persistent View(永続ビュー)の扱いだった。

discord.py では、承認ボタンや申請パネルのボタンは Bot を再起動すると消えてしまう。再起動後もボタンが機能し続けるためには、起動時にすべての「生きているView」を add_view() で再登録しなければいけない。DBに approval_message_id を保存しておいて、起動時に pending なイベントを全件取得して再登録する実装にした。

もうひとつはAPSchedulerのリマインダーキャッチアップ。DateTrigger で登録したジョブは Bot 再起動で消えるので、1分ごとに「未送信のリマインダーがないか」チェックして再スケジュールするジョブを常時回している。これがないと、Botが落ちていた時間に送るはずだったリマインダーが丸ごと消える。

重複申請の自動却下

地味だけれど重要な仕組みとして、重複申請の自動却下がある。

告知URL・開始日時・終了日時がすべて既存の pending または approved イベントと完全一致する場合のみ、自動で rejected にする。URLだけの一致では却下しない設計にしたのは、同じURLを使う定期開催イベントを誤って弾かないためだ。

動かしてみて

稼働したばかりなので、使い勝手の細かい部分はこれから調整していくことになる。特に重複申請の部分なんかはかなり雑な条件だと認識している。運用の中で微調整が必要だと思う。
ただ、今まで手でやっていたカレンダー記入や確認作業が全部なくなるのは、単純にだいぶ楽になりそうだ。

コードは全部で約2,200行。素人がAIに力を借りながらひとりで書くには程よいサイズで、「もう少しで終わる」という感覚を保ちながら作れたのがよかった。

ぶいあーる写真部は正直活発とは言い難い現状だが、フォトコン情報の共有機能を使ってみたいと思った方はこちらのリンクから参加してみてほしい。