DiscordBot - 設定を無視して一斉Banしてしまい色々大変なことになった話

この記事は [本番環境でやらかしちゃった人 Advent Calendar 2021](https://qiita.com/advent-calendar/2021/yarakashi-production) 7日目の記事です。

## 最初に

こんにちは。とあるDiscordBotを作っているちょっとした学生です。その時に起こったやらかしを書きました。個人開発かつ幼稚すぎる話ですがお許しください :bow: 

:::note warn
この記事はネタ要素満載です。
:::
### 用語解説
#### ギルドについて
Discord公式クライアントでは「サーバー」と表記されているもの。
Slackのワークスペースのようなもの。
この記事では誤解を防ぐため、開発者用ドキュメントと揃えて「ギルド」と表記。
#### Banについて
特定のユーザーを強制退出＆入室不可にする機能。
#### GBanについて
Botが、設定されたギルドで特定ユーザーを一斉Banすること、またはその機能。ほとんどの場合Bot管理者しか実行できない。
ほとんどの場合荒らしなどに実行される。

## 結論

このことで学んだ教訓。

- 心を落ち着かせて作業する
- デバッグは大事
- 人<<<エディタ

## とある日のこと

自分『あ、GBan申請が来てる。えーっと？』
ユーザー「（スクショ）」
自分『あー、コマンド使った荒らしは対象だからな。前にGBanのオンオフ設定できるようにしたし大丈夫でしょ。』
自分［人数分GBanコマンド実行］
自分「対応しましたー。」

### そして異変は起こる

別ギルド（人ごとにチャンネルを作れるギルド）にて…

A「あれ、なんかBotが勝手にBanし始めたんだけどGBanなんてあったっけ？」
B「一応メンションしときましょ。@（自分へのメンション）」
自分「！？ちょっと設定見てくる」
MongoDB「GBan設定はオフやで」
自分『じゃあ何故！？』

### そして異変に気がつく

自分『うーん、変更が反映されてない？』
自分［デバッグツール（jishaku、自分の場合起動時にしかロードされない）がロードされた時刻を見る］
自分『いや、コミットの日時からしてしっかりロードされてるよな…』
自分『GBanのコードは…あっ』

:::note warn
昔から（Python初心者の頃から）のソースを継ぎ足しながら使ってるのでクソコードです。
:::


```python:admin.py
ga = []
for g in self.bot.guilds:
    if not Guild_settings[g.id]["gban_enabled"]:
        next
    try:
        ga.append(
            g.ban(
                user,
                reason="...によりGBanされました。\n理由：" + reason,
            )
        )
    except Forbidden:
        pass
try:
    await asyncio.gather(*ga)
except Forbidden:
    pass
```

実際のコードです。お分かりいただけたでしょうか。

そう、問題はnextです。コードはPythonです。Rubyではありません。
しかもPythonの組み込み関数にnextがあるのでNameErrorも発生しません。
なのでエラーも吐かず設定がオフでも無視してそのままBanをしてしまっていたのです :innocent:

### 惨劇は続く

自分『とりあえずお知らせしよう…』
自分「コードミスによりGBan設定を無視して…」
自分『さて、お知らせもしたことだしBan解除するかー。とりあえずエラーログを…あっ』
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/659894/5fcd3653-bf14-6798-5b2f-ce3a2c2373ab.png)

asyncio.gatherでほぼ同時に実行してしまっていたため、Discord（CloudFlare）の1時間IPBanのレートリミットにかかっていたようです。それによってすぐにBan解除できなくなってしまいました:innocent:（追加で色々ありましたが伏せます）。
自分『明日学校もあるんだよな…とりあえず開発PCで立ち上げて寝よう、昔動かしてたときのデータがあるはず…明日の朝にはBanも解除されてるはずだし…』
自分［`.venv/scripts/activate.ps1;py main.py`］
自分『寝よう、明日起きれるかな…』

### そして翌朝

自分『ふぅ、ひどい夜だった。さて、本番環境を…あっ』
コンソール「`discord.errors.LoginFailure: Improper token has been passed.`」
自分『トークン更新してなかったああああ:innocent:』
自分『とりあえずssh繋いで本番環境動かそう…』
そのあとBotを起動してしっかりasyncio.sleepも使って設定がオフのギルドでBan解除をゆっくりしました。

## 惨劇のすべて

何が起こってしまったかまとめると、

- 設定を無視して全ギルドでGBanしてしまった
- 1時間レートリミットにかかりBan解除できなかった
- 急いで開発環境で起動させようとしたができていなかった

こうして大体700ギルドの方々に迷惑をかけてしまいました。

## 惨劇はなぜおこってしまったのか

> - 設定を無視して全ギルドでBanしてしまった

デバッグ忘れです。「まぁスキップくらいならバグることもないだろ！」という心の緩みです。
また、当時の自分は「まぁたまにシンタックスハイライトもバグるし多分大丈夫だろ！ヨシ！」って思っていたのでしょう。怪しいところを放置していたのも原因です。
あと複数の言語を短期間で触っていたのも原因かと思いました。

> - 1時間レートリミットにかかりBan解除できなかった

レートリミットの考慮忘れです。レートリミットのことを片隅に入れておくことも重要だと思いました。

> - 起動できていないことに気が付かなかった

翌朝学校だったので焦っていたのでしょう。起動はたったの数分です。心の余裕、大事。

## 二度と惨劇を起こさないためにどうしたのか 

小さな機能でもすぐデバッグできるように、デバッグ用Botを簡単に立ち上げられるようにしました。
~~そもそも本番環境のデータを開発PCに入れっぱなしってのもどうかと思いますが。~~
デバッグなし、危険。
また、1時間レートリミットにかかったら本当にどうしようもできないことを知ったので、既存機能のレートリミットの見直しもしました。
この間違いだけの対策ですが、VSCodeの[Highlight](https://marketplace.visualstudio.com/items?itemName=fabiospampinato.vscode-highlight)拡張機能でPythonでnextだけを書いたときに目立つようにしました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/659894/9eaf32f4-a2ef-206b-77a6-d44958c830d5.png)




### 惨劇を起こさないためにどうしたいか

色々なもの、特に言語が違うものを短期間で進めるときは間を置くようにしていきたいです。

## 最後に

> 言語違いなnext こことは違う世界に転生
ある日突然デバッグ無しにニョキっと生えて来た

> 新天地はPython continueに間違えられ
懸命に関数を参照するよ

> Devの怠惰が数多のバグ生み出し
GBanの進行を止まらせることのないコード

> 加速しすぎたそのリクエストに
止まれと告げる

> やがてCloudflareはBotをBanした
待つしか解除される事無い1時間のBan

> リクエストは止まる 何度再送信しても
共に見ていよう sentryの叫びを

（曲わかる人いるかな...）
ありがとうございました。