Discord.py の Intents について

## はじめに
Discord.py ver2.0 以降から、Bot と Discord を接続する際に必須となったパラメータ __Intents__ についてまとめてみました。

## Intents とは
Intents とは Discord.py Ver1.5 で実装され、Ver2.0 から Bot が Discord に接続する際に必須のパラメータとなりました。Intents を利用することで、"どのイベントを受信し、どのイベントを受信しないか" を Bot が選択できるようになります。不必要なイベントを取得せずに済むため Bot の負荷低減が見込めます。

## 使い方
一部の Intents には制約が設けられており、コードに記述した上で手動で有効にする必要があります。これらの Intents をまとめて __Privieged Intents__ と呼びます。
有効化の手順は以下の通りです。
1. <a href="https://discord.com/developers">Discord Developer Portal </a> にログインします。
2. アプリケーションページに移動します。
3. Privieged Intents を有効にしたい Bot を選択します。
4. 画面左側にある [Bot] タブに移動します。
![discord_bot_tab.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1115291/afe7c220-81ae-b14d-3a51-fc3d1264d20b.png)
5. `Privileged Gateway Intents` から必要なものを有効にします。よく分からなければ全て有効でも問題ありません。Bot の権限を制限したい場合、個別に有効化して下さい。
![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1115291/c2d8478e-f3a5-7604-8cc3-12be7a3c4401.png)
それぞれの Privieged Intents については以下のとおりです。

* PRESENCE INTENT
    * メンバーのステータスを追跡するかどうか
    * メンバーのアクティビティを確認するかどうか
* SERVER MEMBERS INTENT
    * メンバーの参加や退出を追跡するかどうか
    * ニックネームやロールの変更など、メンバーの更新を追跡するかどうか
    * ユーザー名、アバター、識別子などのユーザーの更新を追跡するかどうか
    * ギルドメンバーリストをリクエストするかどうか。
* MESSAGE CONTENT INTENT（Ver2.0 で追加されました）
    * メッセージの内容を確認するかどうか
    * メッセージの添付ファイルを確認するかどうか
    * メッセージの埋め込みを確認するかどうか
    * メッセージコンポーネントをチェックするかどうか

注意点として Bot を 100 以上のサーバで運用していている場合、Privieged Intents を有効化するために <a href="https://support.discord.com/hc/en-us/articles/360040720412">bot verification</a> が必要になります。Privieged Intents についての詳しい説明は <a href="https://discordpy.readthedocs.io/ja/latest/intents.html">Discord のドキュメント</a>を参考して下さい。

一例として、メッセージへのリアクションに反応してメッセージ主にメンションを送る Bot を見てみましょう。Bot の内容に関しては以下記事で解説しています。

https://qiita.com/Pumila/items/cf447c24538e13994a38

```Python:main.py
#!/usr/bin/env python3
import discord

TOKEN = 'YourAccessToken'

intents=discord.Intents.none()
intents.reactions = True
intents.guilds = True
# discord.py Ver2.0 以降は必要
#intent.message_content = True
client = discord.Client(intents=intents)

@client.event
async def on_ready():
    print('ログインしました')

@client.event
async def on_raw_reaction_add(payload):
    txt_channel = client.get_channel(payload.channel_id)
    message = await txt_channel.fetch_message(payload.message_id)
    user = payload.member
    if (message.author == user):
        return
    reaction = payload.emoji
    CHANNEL_ID = 106308992804
    channel = client.get_channel(CHANNEL_ID)
    msg = f"{message.author.mention} {reaction}\nFrom:{user.display_name} \
          \nMessage:{message.content}\n{message.jump_url}"
    await channel.send(msg)
    
client.run(TOKEN)
```
このコードではまず、5行目の `discord.Intents.none()` メソッドで全てのイベントを取得しないように制限しています。その後、個別に使用したい Intents を有効にします。どの Intents を有効にすればいいのかですが、非同期処理と client クラスで使用しているメソッドに着目します（この作業はエラーと <a href="https://discordpy.readthedocs.io/ja/latest/api.html">API リファレンス</a>とを睨めっこしながら探すことになると思います）。ここでは、<a href="https://discordpy.readthedocs.io/ja/latest/api.html#discord.on_ready">`on_ready()`</a>、<a href="https://discordpy.readthedocs.io/ja/latest/api.html#discord.on_raw_reaction_add">`on_raw_reaction_add()`</a>、<a href="https://discordpy.readthedocs.io/ja/latest/api.html#discord.Client.get_channel">`client.get_channel()`</a>、`channel.send` などが対象になります。例えば `on_raw_reaction_add()` を使うためには `Intents.reactions` を有効にする必要があります。

エラーと睨めっこしながら必要な Intents を探すのもいいですが、普通はこんな煩わしいことはしたくないと思います（Bot の処理をどうしても軽くしたい場合はしてもいいですが…）。そんな時のために Privieged Intents 以外の全ての Intents を `True` としてくれる `discord.Intents.default()` というメソッドが用意されています。以下のように使用します。
```python
client = discord.Client(intents=discord.Intents.default())
```
`default()` メソッド以外にも、Privieged Intents も含めた全ての Intents を `True` とする `all()` メソッド、Intents 全てを `False` とする `none()` メソッドがあります。基本的には `default()` メソッドを使用すれば問題ないと思います（何も考えたくなければ `all()` メソッドを使用し、Privieged Intents を全て有効化しましょう）。必要に応じて Privieged Intents を有効化する場合は、以下のようにします。
```python
intents = discord.Intents.default()
intents.message_content = True
client = discord.Client(intents=intents)
```
## 注意点
Privieged Intents を `True` とした場合や、`discord.Intents.all()` と指定した場合には、<a href="https://discord.com/developers">Discord Developer Portal</a> にて対応する Privieged Intents を有効化する必要があります。有効化しないと以下のようなエラーが吐かれます。
`discord.errors.PrivilegedIntentsRequired: Shard ID None is requesting privileged intents that have not been explicitly enabled in the developer portal.`
Privieged Intents を使用する場合は、コードの記述だけでなく手動で有効化もして下さいということですね。
また、Discord.py Ver2.0 以降から、Bot と Discord を接続する際に使用する Client クラスに Intents の指定が必須となりました。Intents を指定しない場合以下のようなエラーメッセージが出力されます。
`TypeError: __init__() missing 1 required keyword-only　argument:'intents'`
対応として以下のように変更しましょう。
```python
# 変更前
client = discord.Client()
# 変更後
client = discord.Client(intents=discord.Intents.default())
```

## 最後に
discord.py の更新により Bot の書き方が大きく変わりました。古い Bot などは注意点で挙げたエラーなどが吐かれて動かないことがあると思います。本記事の対応策が一助になれば幸いです。

## 参考
<a href="https://discordpy.readthedocs.io/ja/latest/intents.html">ゲートウェイインテント入門</a>