Discord の Bot に定時実行をさせてみよう

**要点**

* [discord.py](https://discordpy.readthedocs.io/ja/latest/index.html) を使って python で開発する。
  * [discord.ext.tasks](https://discordpy.readthedocs.io/ja/latest/ext/tasks/) でバックグラウンドループを実現する。[Code](https://github.com/coleyon/discord-partypoll/blob/665f81bd89964fa061ca964d0c3e70c6133b5f01/cogs/cron.py#L123)
  * [croniter](https://pypi.org/project/croniter/) で各ループ（タイミング）におけるジョブの発火要否判定を実現する。[Code](https://github.com/coleyon/discord-partypoll/blob/665f81bd89964fa061ca964d0c3e70c6133b5f01/cogs/cron.py#L127)
  * [Command.invoke()](https://discordpy.readthedocs.io/ja/latest/ext/commands/api.html#discord.ext.commands.Bot.after_invoke) でBotによるBotコマンドの実行を実現する。[Code](https://github.com/coleyon/discord-partypoll/blob/665f81bd89964fa061ca964d0c3e70c6133b5f01/cogs/cron.py#L167)

----

**やってみた**

[discord.py](https://pypi.org/project/discord.py/) を使って、オンラインゲームのクエストや対戦マッチング相手を募集する時に使える Bot を作り、1か月ほど実際に使ってみましたので、記事にしてみます。

----

# 概略仕様

----

## コード一式
論よりコードの方はこちら。
https://github.com/coleyon/discord-partypoll

----

**機能**

ざっくり言えば、以下2つの機能から成るBotです。

* `PartyPoll` - [SimplePoll](https://top.gg/bot/simplepoll) に似た、イベントの周知と参加者募集ができる機能。
* `Cron` - PartyPoll のコマンドをBot自身に、 [cron](https://linuxjm.osdn.jp/html/cron/man5/crontab.5.html#lbAD) ライクな設定書式でスケジューリング・定時実行させる機能

----

**環境**

PythonプログラムとしてのBotが、Dockerコンテナ上で実行される構成です。

![aws_vpc_design-Page-3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157638/36472964-5eb6-d57d-1b48-2c3e282c021a.png)

----

# 環境構築

----

**サーバとOSを用意する**

例えばEC2上に、`Ubuntu 18.04 LTS x86-64` を立てます。無料利用枠内で十分です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157638/94b4e0d5-26f5-c0e1-9fc2-5994161e4df4.png)

----

**Dockerサーバ環境をインストールする**

docker と compose をインストールします。

* https://docs.docker.com/engine/install/ubuntu/
* https://docs.docker.com/compose/install/

----

**Dockerサーバ環境をインストールする**

こんな風になったと思います。

```bash
$ docker --version
Docker version 19.03.12, build 48a66213fe
$ docker-compose --version
docker-compose version 1.27.2, build 18f557f9
$ id
uid=1000(ubuntu) gid=1000(ubuntu),
# 中略...
,999(docker)
$ 
```

----

**Dockerコンテナをビルドする**

```bash
$ git clone  https://github.com/coleyon/discord-partypoll.git
Cloning into 'discord-partypoll'...
...
Resolving deltas: 100% (136/136), done.
$ cd discord-partypoll/
$ docker-compose build
$ docker images | grep partypoll
discord-partypoll_discord-bot  latest ...
$ 
```

----

# BotをDiscordサーバに誘う

----

**Discordアプリを作る**

[Developer Portal](https://discord.com/developers/applications) 上でアプリケーションを作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157638/97622b57-8c29-5ad8-5dde-713fff3d33cc.png)

----

**BotのOAuth2 TokenをBotプログラムにセットする**

アプリの `General Information` メニューから `Client Secret` を得て、`docker-compose.yml` の `DISCORD_BOT_TOKEN` にセットします。

```yaml
version: "3"
services:
  discord-bot:
    restart: always
    build: .
    environment: 
      DISCORD_BOT_TOKEN: "yourtoken"
```

----

**BotをDiscordサーバに誘う**

アプリの `OAuth2` メニューから Scopes が BOT となる OAuth2 URL を得ますPermissions は `Send Messages`, `Manage Messages`, `Attach Files`, `Mention Everyone`, `Add Reactions` を与えてください。

----

**BotをDiscordサーバに誘う**

OAuth2 URL にブラウザでアクセスし、Bot を Discord サーバに招きます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157638/623f09b4-c8fd-8daf-231f-2932efdf0160.png)


----

# Botプログラムを起動する

----

**Botプログラムの依存ライブラリを生成する**

`Pipfile` から `requirements.yml` を生成します。

```bash
$ pipenv lock -r > requirements.txt
```

----

**Botプログラムを起動する**

Bot プログラム（の実行環境としての Docker コンテナ）を起動します。

```bash
$ docker-compose up -d
$ docker-compose logs -f
Attaching to discord-partypoll_discord-bot_1
discord-bot_1  | -----Logged in info-----
discord-bot_1  | {BOT_NAME}
discord-bot_1  | {BOT_ID}
discord-bot_1  | {DISCORDPY_VERSION}
discord-bot_1  | ------------------------
discord-bot_1  | Poll Extension Enabled.
discord-bot_1  | Cron Extension Enabled.
^CERROR: Aborting.
$ 
```

Bot プログラムを起動すると、Discord上の Bot がオンラインとなり、使用可能な状態になります。

----

# パーティ募集コマンド ppoll を使ってみる

----

**募集全体の人数制限が可能な募集**

チーム全体で計20名までを上限とするパーティの募集をするコマンドです。 `/ppoll total 全20名3チーム 20 TeamA TeamB TeamC`
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157638/85449354-6658-7212-79af-1d3ce7756590.png)

----

**個々の募集項目毎に人数制限が可能な募集**

個々のチームで計4名までを上限とするパーティの募集をするコマンドです。 `/ppoll each 各4名3チーム [4]TeamA [4]TeamB [4]TeamC`
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157638/2df6c263-bc3f-8e5e-10e4-a8a1de198277.png)

----

**ヘルプコマンド**

詳しくは `/ppoll help` コマンドの説明か、Gitリポジトリの [readme.md](https://github.com/coleyon/discord-partypoll/blob/master/README.md) をご覧ください。

----

# 定時実行コマンド cron を使ってみる

----

**実行タイムゾーン指定**

実行Timezoneを設定できます。デフォルトは日本時間(Asia/Tokyo)ですが、米国東部時間(EST)などに設定することもできます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157638/fa184513-450a-198d-74de-394433dbdf31.png)

----

**ジョブの定義と有効化**

* Jobを定義します。
  * `/cron add "Job A" */1 * * * mon-fri /ppoll each 各4名3チームパーティ({{1.days}}開催!) [4]TeamA [4]TeamB [4]TeamC`
* cron を稼働状態にします(2020/10/30 23:11 に実行したとします)
  * `/cron enable`

----

**ジョブの稼働例**

こうなります。詳しくは `/cron help` コマンドの説明か、Gitリポジトリの [readme.md](https://github.com/coleyon/discord-partypoll/blob/master/README.md) をご覧ください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157638/5bb3dfbf-90d3-6c17-5666-025f746d63d7.png)

----

# 参考資料

----

　基本的に[公式Docs](https://discordpy.readthedocs.io/en/latest/)がバイブルですが、作っている途中でオンラインゲームのメンバーさんより、以下の本をご紹介頂きました。

読んだところとても分かりやすく公式マニュアルでは把握できなかった内容が多々あったりして有用でしたので、DiscordのBot作りに興味がある方にはオススメです。

* Pythonで作る Discord Bot開発実践入門
  * https://techbookfest.org/product/5755158612934656?productVariantID=5633838124367872