discord.py入門(2) 


# はじめに
discord.pyの非公式のサーバーを運用しています。
ここでは質問も受け付けています。
https://discord.gg/KPp2Wsu **この記事についての質問もこちらにお願いします**

# この記事について
この記事はdiscord.pyについて段階を踏んで勉強していくための記事になります。
まず始めに基本の書き方を学び、次に発展的な内容を学びます。
**【シリーズ】**

[discord.py入門(1)](https://qiita.com/sizumita/items/9d44ae7d1ce007391699)
[discord.py入門(2)](https://qiita.com/sizumita/items/b37e74fca41d98077a18)
[discord.py入門(3)](https://qiita.com/sizumita/items/cafd00fe3e114d834ce3)

# 注意
**この記事は少しでもPythonができる人向けになります。**
ただし、コピペするだけでも使えますが、その後書けなくなるので、もっと個別に機能を作りたい場合は、
本やpaizaラーニング、ドットインストール、Progate、[京大のテキスト](https://repository.kulib.kyoto-u.ac.jp/dspace/handle/2433/245698) を使って勉強することをお勧めします。
私がお勧めな本は、辻真吾さんの[Pythonスタートブック](https://www.amazon.co.jp/Pythonスタートブック-増補改訂版-辻-真吾/dp/4774196436/)です。


# 筆者の動作環境
Python 3.8.0
Mac OS Catalina 10.15.2
discord.py-1.3.2


# 様々なイベントを使う
前回discord.pyにはたくさんのイベントがあると紹介しました。前回使用した`on_ready`、`on_message`以外にも、主要なイベントを紹介します。(カッコ内に書かれているものは引数です。)
**詳しい説明は登場時に紹介します**

### 書き方について
List\[discord.Reaction]のような書き方は、discord.Reactionが入っているリストという意味です。

| イベント名                | 内容                    | 備考 |
|:------------------------|:------------------------|:------------------------|
| [on_message(message)]()     | メッセージが送信された | |
| [on_message_delete(message)]() | メッセージが削除された |  誰が削除したかはわからない. また、botが起動する前のメッセージの削除では呼ばれない |
| [on_message_edit(before, after)]() | メッセージが更新された |  botが起動する前のメッセージの更新では呼ばれない. |
| [on_reaction_add(reaction, user)]() | リアクションがつけられた | botが起動する前のメッセージへの追加では呼ばれない. |
| [on_reaction_remove(reaction, user)]() | リアクションが削除された | botが起動する前のメッセージへの削除では呼ばれない. |
| [on_member_join(member)]() | サーバーにユーザーが入った | |
| [on_member_remove(member)]() | サーバーからユーザーが退出した | |
| [on_voice_state_update(member, before, after)]() | ボイスステータスが変更された | ボイスチャンネルに入ったり出たり、ミュートしたりした時に呼ばれます |

例として、メッセージが削除された時に設定したチャンネルにその内容を送るコードを示します:

```python
import discord
client = discord.Client()


@client.event
async def on_message_delete(message):
    channel = client.get_channel(DEBUG_CHANNEL_ID)
    await channel.send(f"{message.author.name}さんのメッセージが削除されました:\n```\n{message.content}\n```")


client.run(TOKEN)
```


# サーバー設定を変更する
discord.pyでサーバーを表すのは、discord.Guildクラスです。これをClientを使い取得することで、様々な変更が可能になります。


### この章で学ぶこと

- サーバーの取得方法
- 変更方法

## サーバーを取得する
### idから取得する
Client.get_guild関数もしくはClient.fetch_guild関数を使います。普通の場合はget_guild関数を使用します。

```python
# get_guild
guild = client.get_guild(GUILD_ID)

# fetch_guild
guild = await client.fetch_guild(GUILD_ID)
```

### 名前から取得する
名前から取得するためには、discord.utilsにある、get関数を使用します。

```python
guild = discord.utils.get(client.guilds, name="名前")
```

**これは、同じ名前があっても最初のものしか返さないので注意してください**

## サーバーの設定を変える
サーバーの設定はとても長いので、メインのものだけを紹介します。

### 名前を変更する
discord.Guildで名前などを変更する場合は、edit関数を使用します。

```python
# 上の続き
await guild.edit(name="新しい名前")
```

### 説明書きを変える
descriptionを変更します。

```python
# 上の続き
await guild.edit(description="これは説明書きですこれは説明書きですこれは説明書きですこれは説明書きです")
```

### iconを変更する
iconにbytesを指定してやればいいです。

```python
# 上の続き

# ローカルにあるファイルをアイコンにする
with open("test.png", "rb") as f:
    await guild.edit(icon=f.getvalue())
```

## 変更理由をつける
これらの変更はシステムログに残りますが、その時に表示する理由を設定できます。

```python
# 上の続き

await guild.edit(name="まどマギｽｷｰの部屋", reason="気分でやった, 反省はしていない")
```

## まとめて変更する
上に書いた変更内容は、全て一回で実行できます。

```python
# 上の続き

await guild.edit(name="まどマギｽｷｰの部屋", description="まどマギが好きな人が集まるサーバー。", reason="気分でやった, 反省はしていない")
```

# チャンネル設定を変更する
discord.pyでチャンネルを表す主要なクラスは、discord.CategoryChannel, discord.TextChannel, discord.VoiceChannelです。これをClientやGuildを使い取得することで、様々な変更が可能になります。

### この章で学ぶこと

- チャンネルの取得方法
- 変更方法

## チャンネルを取得する
### idから取得する
Client.get_channel関数もしくはClient.fetch_channel, Guild.get_channel関数を使います。普通の場合はget_channel関数を使用します。

```python
channel = guild.get_channel(CHANNEL_ID)
```

### 名前から取得する
名前から取得するためには、discord.utilsにある、get関数を使用します。
この場合、チャンネルのタイプにも気を配る必要があります。`guild.channels`, `guild.text_channels`, `guild.voice_channels`, `guild.categories`を使い分けます。

#### テキストチャンネルの中から取得する場合
`guild.text_channels`を使います。

```python
channel = discord.utils.get(guild.text_channels, name="名前")
```

#### ボイスチャンネルの中から取得する場合
`guild.voice_channels`を使います。

```python
channel = discord.utils.get(guild.voice_channels, name="名前")
```

#### カテゴリーの中から取得する場合
`guild.categories`を使います。

```python
channel = discord.utils.get(guild.categories, name="名前")
```

#### 全ての中から取得する場合
`guild.channels`を使います。

```python
channel = discord.utils.get(guild.channels, name="名前")
```

## チャンネルの設定を変える
チャンネルの設定はとても長いので、メインのものだけを紹介します。

### テキストチャンネル
#### 名前を変更する
nameを使用します。

```python
await channel.edit(name="New Name")
```

#### トピックを変更する
topicを使用します。

```python
await channel.edit(topic="topictopictopictopictoping")
```

#### 場所を変える
positionを使用します

```python
await channel.edit(position=2)
```

#### カテゴリーを変える
categoryを使用します。

```python
# カテゴリーを設定する
category_channel = client.get_channel(CATEGORY_CHANNEL_ID)
await channel.edit(category=category_channel)

# カテゴリーをなしにする
await channel.edit(category=None)
```

## まとめて変更する
上に書いた変更内容は、全て一回で実行できます。

```python
await channel.edit(name="お知らせ", topic="運営からのお知らせを載せます.", category=None)
```

# 役職設定を変更する
discord.pyで役職を表すのは、discord.Roleクラスです。これをguildから取得することで、様々な変更が可能になります。
**権限変更についてはのちの章で紹介します**

### この章で学ぶこと

- 役職のいろいろな設定方法

## 役職を取得する
### idから取得する
discord.Guild.get_role関数を使用します。

```python
guild = message.guild
role = guild.get_role(ROLE_ID)
```

### 名前から取得する
名前から取得するためには、discord.utilsにある、get関数を使用します。

```python
guild = message.guild
role = discord.utils.get(guild.roles, name="名前")
```

**これは、同じ名前があっても最初のものしか返さないので注意してください**

## 名前を変更する
discord.Roleで名前や色などを変更するときは、edit関数を使用します。

```python
# 上の続き

# 名前を変更する
await role.edit(name="新しい名前")
```

## 色を変更する
色を変更するときは、discord.Colourを使用します。
discord.Colourにはそれぞれの色に対応した関数があり、それを使用することもできます。

```python
# 上の続き

# RGBを使用する場合
await role.edit(colour=discord.Colour.from_rgb(256, 256, 256))

# hsvを使用する場合 (H,S,V にはそれぞれ 0 から 1 の間の値を与えます)
await role.edit(colour=discord.Colour.from_hsv(0, 0, 1))

# 関数を使用する場合
await role.edit(colour=discord.Colour.blue())
```

## 位置を変更する
位置を変更する際は、position引数にポジションの数値を入れてやる必要があります。
0が@everyoneなので、1以上を選ぶ必要があります。1から順に上に上がっていきます。

```python
# 上の続き

await role.edit(position=1)
```

## 変更理由をつける
これらの変更はシステムログに残りますが、その時に表示する理由を設定できます。

```python
# 上の続き

await role.edit(position=1, reason="気分でやった, 反省はしていない")
```

## まとめて変更する
上に書いた変更内容は、全て一回で実行できます。

```python
await role.edit(name="青々とした緑", colour=discord.Colour.green(), position=1, reason="気分でやった, 反省はしていない")
```

# 権限を変更する
### この項で勉強すること

- discord.pyの権限について
- 役職の権限を変更する方法
- チャンネルの権限を変更する方法

## discord.pyの権限周りのクラス
discord.pyの権限周りのクラスにはdiscord.Permissionsとdiscord.PermissionOverWriteがあります。これらはそれぞれ、discordの役職の権限許可と、チャンネル毎の設定にリンクしています。

各項目に設定できる値について、Permissionsではオンオフしか設定できないのに対し、PermissionOverWriteはオンオフに加えて「設定しない」ことができます(設定しない場合、より上位の権限設定、例えばテキストチャンネルであれば所属するカテゴリチャンネルの設定を引き継ぎます)。

また、この二つの間には、Permissionsが全てを上書きする（指定しなかったもの以外も変更されてしまう）のに対し、PermissionOverWriteは指定したものだけを上書きするという違いがあります。

```python
# overwrite
overwrite = discord.PermissionOverWrite()
overwrite.read_messages = True
overwrite.manage_messages = False
overwrite.send_messages = None  # overwriteだとNoneが設定できる

# permissions
permissions = discord.Permissions()
permissions.read_messages = True
permissions.manage_messages = False
# permissions.send_messages = None # Noneは設定できない
```

## 役職の権限を変更する
discord.pyでは役職はdiscord.Roleです。まず、ギルドからこれを取得してやる必要があります。

### idから取得する
discord.Guild.get_role関数を使用します。

```python
guild = message.guild
role = guild.get_role(ROLE_ID)
```

### 名前から取得する
名前から取得するためには、discord.utilsにある、get関数を使用します。

```python
guild = message.guild
role = discord.utils.get(guild.roles, name="名前")
```

**これは、同じ名前があっても最初のものしか返さないので注意してください**


### 変更先の権限を取得する
権限を変更するには、新しくdiscord.Permissionsクラスを作る必要があります。ここでは、一度取得したroleのpermissionsを使い、無用な変更を避けます。

```python
guild = message.guild
role = discord.utils.get(guild.roles, name="everyone")
# permissionsを取得
permissions = role.permissions
```

discord.Permissionsの値を変更するには、権限の名前に対応した変数に許可=Trueもしくは禁止=Falseを入れてやる必要があります。

```python
# 一つ上の続き

# @everyoneを打てなくする
permissions.mention_everyone = False

# emojiの変更権限を追加する
permissions.manage_emojis = True
```

### 新しくPermissionsを作る
新しく作りたい場合は、`permissions = discord.Permissions()`とすることで作成できます。

### 変更する
これをdiscord.Roleに反映させるためには、discord.Role.edit関数を使用します(**コルーチン関数です**)
前回やりましたがedit関数では、他に名前・色・everyoneとは別に表示されるか・位置を設定できます。

```python
# 上の続き

await role.edit(permissions=permissions)
```

## チャンネルの権限を変更する
チャンネルの権限はset_permissionsを使用して変更します。

### パーミッションを直で設定する場合
set_permissionsコマンドを使用するとことで、ユーザーや役職の権限を変更することができます。

```python
channel = client.get_channel(CHANNEL_ID)
# ターゲットとするメンバーを取得
member = channel.guild.get_member(MEMBER_ID)

# 変更する
await channel.set_permissions(member, send_messages=False)
```

### PermissionOverWriteを使用する場合
PermissionOverWriteを使用すると、None=カテゴリー設定に準拠を設定できます。

```python
channel = client.get_channel(CHANNEL_ID)
# ターゲットとする役職を取得
role = discord.utils.get(channel.guild.roles, name="everyone")

overwrite = discord.PermissionOverWrite()
overwrite.send_messages = False
overwrite.read_messages = None

# 変更する
await channel.set_permissions(role, overwrite=overwrite)
```


# チャンネルを作成する
### この章で学ぶこと

- チャンネルの作成方法

## テキストチャンネルの作成方法
テキストチャンネルを作成するには、`discord.Guild.create_text_channel`もしくは`discord.CategoryChannel.create_text_channel`関数を使用します。
`discord.CategoryChannel.create_text_channel`を使った場合は、親のカテゴリーに自動的に所属します。`discord.Guild.create_text_channel`では、自分で設定する必要があります。
また、権限設定を一緒に行えますが、PermissionOverWriteしか使用できません。
権限設定をするときは、

```python
{
    指定する役職やメンバ- : 対応するPermissionOverWrite,
    guild.me : 対応するPermissionOverWrite
}
```

のようにして使います。

```python
# discord.Guild.create_text_channelを使用する
guild = client.get_guild(GUILD_ID)
# @everyoneは発言できないが、自分はできる
overwrites = {guild.default_role: discord.PermissionOverWrite(send_messages=False),
              guild.me: discord.PermissionOverWrite(send_messages=True)
}
new_channel = await guild.create_text_channel(name="お知らせ", overwrites=overwrites, topic="お知らせを表示します。")
print(f"#{new_channel.name} を作成しました。")
```
create_text_channelの返り値は新しく作成されたチャンネルです。

## ボイスチャンネルの作成方法
基本的にはテキストチャンネルと同じですが、トピックとスローモードの設定がありません。

```python
# discord.Guild.create_voice_channelを使用する
category = client.get_channel(CATEGORY_ID)
await category.create_voice_channel(name="雑談ボイス")
```

## カテゴリーの作成方法
こちらは、名前と権限しか設定できません。また、discord.Guild.create_categoryしかありません。

```python
guild = client.get_guild(GUILD_ID)
# @everyoneは見れないが、自分は見れる
overwrites = {guild.default_role: discord.PermissionOverWrite(read_messages=False),
              guild.me: discord.PermissionOverWrite(read_messages=True)
}
await guild.create_category(name="運営用", overwrites=overwrites)
```

# 終わりに

ここまでいかがだったでしょうか。
今回は書き方を発展的なeventの使用方法、設定の変更方法、チャンネルの作り方に分けて説明しました。
次の記事では、入力を待つ方法・ボイスチャンネルに接続して音声を流す方法について扱います。
それでは。 

次回の記事は [discord.py入門(3)](https://qiita.com/sizumita/items/cafd00fe3e114d834ce3)