Help us understand the problem. What is going on with this article?

discord.py入門(1)

はじめに

discord.pyの非公式のサーバーを運用しています(開発者の方もいますが)。
ここでは質問も受け付けています。また、長期的にサポートが欲しい方向けに、個人チャンネルを作成しそこでサポートを受けることもできます。
個人サポートを受けたい方は、下の招待urlから入り、@すみどら#8931にDMをお願いします。
https://discord.gg/KPp2Wsu この記事についての質問もこちらにお願いします

この記事について

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

discord.py入門(1)
discord.py入門(2)

注意

この記事は少しでもPythonができる人向けになります。
ただし、コピペするだけでも使えますが、その後書けなくなるので、もっと個別に機能を作りたい場合は、
本やpaizaラーニング、ドットインストール、Progate、京大のテキスト を使って勉強することをお勧めします。
私がお勧めな本は、辻真吾さんのPythonスタートブックです。

筆者の動作環境

Python 3.8.0
Mac OS Catalina 10.15.2
discord.py-1.3.2

初期設定・Botを動かす

Pythonが入っていない方はpythonjpのページ から導入してください。

次に、 こちらの記事 から、アカウントを初期設定してください。

できたら、適当なエディタから新しくファイルを作り、

import discord
client = discord.Client()

@client.event
async def on_ready():
    print("on_ready")
    print(discord.__version__)

client.run("あなたのbotのトークン")

と書き、discordbot.pyなどとして保存してください。

python3 discordbot.pyなどとして実行し、コンソールにon_readyなどと表示されれば成功です。

また、Herokuを使用する場合は こちらの記事 を見て設定を行ってください。

イベントを使ってみよう

ここまでの内容ができた読者の皆さんは、まずeventの概念を覚えましょう。
上で書いた

@client.event
async def on_ready():
    print("on_ready")
    print(discord.__version__)

は覚えていますか?ここでは、普通の関数とは違い、デコレーターコルーチン関数が使われています。

デコレーターとは

デコレーターとは、関数やclassの前に書き、その関数を修飾するための仕組みです。詳しくはこちら をご覧ください。

コルーチン関数とは

次に、コルーチン関数とは、async/awaitに対応した関数です。
pythonのasync/await用のライブラリはasyncioというものがあります。これがdiscord.pyを支えています。
asyncioについては初心者の域を超えてしまうので、ここでは詳しく説明できません(私もわかっていません)。 (参考)

ここでは、関数定義の時にasync defとついているコルーチン関数はawait 関数名()として呼び出す、
awaitはコルーチン関数内でしか呼び出せない、ということを覚えておけば大丈夫だと思います。

Eventとは

discord.pyのeventとは、discord apiから送られてきた、例えばメッセージが送られたなどのイベントが発生した時に
その関数を実行してくれる仕組みです。

イベントを設定するにはon_ + イベント名を関数名につけ、また関数定義の一行前に@client.eventと書く必要があります(clientの部分は自分の定義名に変更してください)。
上の例のon_readyは、ready = Botが起動した というイベントに対応しています。on_message = メッセージが送られた、
on_reaction_add = リアクションが送られたなどといったイベントがあります。一覧は こちら

注意
たまに、このように書いている人を見かけます:

@client.event
async def on_message(message):
    処理1

@client.event
async def on_message(message):
    処理2

この様に書くと、一番最後に定義したon_messageのみが実行されます!!!
よって、関数定義は一つにする必要があります。この様に書く必要があります:

@client.event
async def on_message(message):
    処理1
    処理2

Clientについて

discord.Clientは、apiとの通信を受け持っているclassです。Clientには、様々な取得系の関数があります。
主なものを紹介します。

get_channel, get_guild, get_user, get_emoji

これらは非同期関数ではありません
channel, guild, user, emojiをそれぞれidから取得するための関数です。

get_all_members, get_all_channels

これらは非同期関数ではありません
全てのメンバー、全てのチャンネルを返すイテレーターです。ユーザーは重複する可能性があります。

簡単な応答botを作る

discord.pyについて学んだところで、簡単な応答botを作ってみましょう。

この項で勉強すること

  • メッセージへの反応のさせ方
  • よく使う変数

今回の仕様は、

  • 鳴いて と言われたらにゃーん,わん!,コケコッコーのどれかを返す です。on_messageというeventを使いますが、このeventはめちゃくちゃ多用することになるので、書き方を覚えておくといいと思います。
import discord
import random
client = discord.Client()
# ランダムで送るメッセージの一覧 ※ここに書き足すことでランダムに選ぶ内容を増やせる
random_contents = [
    "にゃーん",
    "わん!",
    "コケッコッコー",
]

@client.event
async def on_ready():
    print("on_ready")
    print(discord.__version__)


@client.event
async def on_message(message):
    # 送信者がbotである場合は弾く
    if message.author.bot:
        return 
    # メッセージの本文が 鳴いて だった場合
    if message.content == "鳴いて":
        # 送信するメッセージをランダムで決める
        content = random.choice(random_contents)
        # メッセージが送られてきたチャンネルに送る
        await message.channel.send(content)
    elif message.content == "おはよう":
        await message.channel.send("おはよう!!")


client.run("あなたのbotのトークン")

if message.content == "鳴いて":で、メッセージの本文(content)が鳴いてであるかを判定しています。
コマンドを作成する場合はもっぱらこの手法や正規表現などを使います(後で紹介するcommands.bot frameworkを使用しない場合)。
messageのクラスはdiscord.Messageです。

次に、await message.channel.send(content)で、メッセージが送られたチャンネルにメッセージを送信しています。
メッセージを送信する時は、TextChannelなどのオブジェクトにあるsendという関数を使用します。
ドキュメントを見るときも、チャンネルに対して行う動作(名前を変えるなど)はTextChannelなど、メッセージに対して行う動作(消去する、ピン留めする)はMessageにあると考えると探しやすいです。

よく使う変数一覧

以下に、MessageとGuild、TextChannel、Memberにあるよく使われる変数の一覧を示します。関数についてはそれぞれ触れたところで解説します。

書き方について

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

discord.Message

変数名 内容 クラス
id メッセージのid int
author メッセージの送信者 discord.Member
guild メッセージを送信されたサーバー discord.Guild (DMだった場合はNone)
channel メッセージが送信されたチャンネル discord.TextChannel or discord.DMChannel or discord.GroupChannel
content メッセージの本文 str
created_at メッセージが送られた時間 datetime.datetime
edited_at 最後にメッセージが編集された時間 datetime.datetime(編集されてない場合はない)
jump_url メッセージリンク str
embeds 埋め込みのlist List[discord.Embed]
attachments 一緒に送られた画像やファイルのlist List[discord.Attachment]
reactions つけられたリアクションのlist List[discord.Reaction]

discord.Guild

変数名 内容 クラス
name サーバー名 str
id サーバーのid int
owner サーバー所有者 discord.Member
channels サーバーの全てのチャンネル List[discord.TextChannel, discord.VoiceChannel, discord.CategoryChannel]
text_channels サーバーの全てのテキストチャンネル List[discord.TextChannel]
voice_channels サーバーの全てのボイスチャンネル List[discord.VoiceChannel]
categories サーバーのカテゴリーのlist List[discord.CategoryChannel]
me サーバー内でのbot discord.Member
voice_client サーバー内でのボイスクライアント discord.VoiceClient or None
members サーバーの全てのメンバー List[discord.Member]
roles サーバーの全ての役職 List[discord.Role]
icon_url サーバーアイコンのurl discord.Asset

discord.TextChannel

変数名 内容 クラス
name チャンネル名 str
id チャンネルid int
guild チャンネルがあるギルド discord.Guild
category_id チャンネルがあるカテゴリーのid int もしくは なし
topic チャンネルトピック str
members そのチャンネルを見ることができる全てのユーザー List[discord.Member]
mention チャンネルのメンション(<#チャンネルid>) str

discord.Member

変数名 内容 クラス
id ユーザーid int
name ユーザーネーム str
discriminator 俗にいうタグ str
bot ユーザーがbotか bool
joined_at サーバーに入った時間 datetime.datetime
created_at ユーザーを作った時間 datetime.datetime
guild メンバーがいるサーバー discord.Guild
nick メンバーのニックネーム str もしくは なし
roles メンバーが持っている全ての役職 List[discord.Role]
mention メンバーへのメンション(<@メンバーid>) str
display_name ニックネームがあればニックネーム、なければ名前 str

これらを見ずに使えるようになれば、ぐんとリファレンスを読むことが少なくなると思います。

画像を扱う

次に、画像を扱ってみましょう。

この項で勉強すること

  • 画像が送信されたことを判定する
  • Attachmentについて学ぶ
  • 画像を送信する

Attachmentについて

Attachmentは、メッセージと一緒に送られてきたファイルに当たります。AttachmentのurlはAttachment.urlで取得できます。
また、ファイル名はAttachment.filenameです。

Attachmentをローカルに保存する

送られてきた画像を保存したい時などには、Attachment.save関数を使うことができます。例を示します:

attachment = message.attachments[0]
# 送られてきたファイルをattachment.pngという名前で保存する
await attachment.save("attachment.png")

Attachmentの中身を取り出す

ファイルに保存しなくても、中身が欲しい時があります。その時はAttachment.read関数を使用します。この関数の返り値はbytesです。

attachment = message.attachments[0]
# 送られてきたファイルをdataに格納
data = await attachment.read()

画像が送信されたか判定する

画像があればMessage.attachmentsに格納されています。よって、画像が送信されたかはMessage.attachmentsが空ではないか、またそれの拡張子がpng,jpgなどであるかを判定すればいいことになります。

@client.event
async def on_message(message):
    # 送信者がbotである場合は弾く
    if message.author.bot:
        return 
    # ファイルがある場合
    if message.attachments:
        for attachment in message.attachments:
            # Attachmentの拡張子がpng, jpg, jpegのどれかだった場合
            if attachment.url.endswith(("png", "jpg", "jpeg")):
                await message.channel.send(attachment.url)

画像を送信する

ローカルの画像を送るためには、discord.Fileを使用します。
また、TextChannel.sendのfileという引数を使用します。

@client.event
async def on_message(message):
    # 送信者がbotである場合は弾く
    if message.author.bot:
        return 
    if message.content == "猫の画像":
        # ローカルにあるcat.pngという名前のファイルを送信する
        await message.channel.send(file=discord.File("cat.png"))

リアクションを扱う

この項で勉強すること

  • リアクションをつける
  • リアクションを削除する
  • ついているリアクションを一覧で表示する
  • 引数つきコマンドを作る

リアクションをつける

リアクションをつけるのは非常に簡単です。

# 😙というリアクションをつける
await message.add_reaction("😙")

複数リアクションをつける場合はこうします:

for reaction in ["☺️", "😙", "🚗"]:
    await message.add_reaction(reaction)

リアクションを削除する

一人がつけたリアクションを削除するにはMessage.remove_reactionを使用します。
第1引数に削除したいユーザー、第2引数に絵文字を入れます。

# 自分の😙というリアクションを消す
await message.remove_reaction(message.guild.me, "😙")

特定のリアクションを全て消したい時には、Message.clear_reactionを使用します。

# 全員の😙というリアクションを消す
await message.clear_reaction("😙")

言論統制のためにリアクションを全て消してまっさらにするためには、Message.clear_reactionsを使用します。

# 言論統制し、全てのリアクションを消す
await message.clear_reactions()

メッセージについているリアクションの一覧を表示する

まず、メッセージを選択する必要があります。選択の仕方は様々ですが、ここでは正規表現とメッセージリンクを使用してみましょう。

メッセージを取得し、リアクション一覧を手に入れます。

import re
link_regex = re.compile(
            r'^https?://(?:(ptb|canary)\.)?discordapp\.com/channels/'
            r'(?:([0-9]{15,21})|(@me))'
            r'/(?P<channel_id>[0-9]{15,21})/(?P<message_id>[0-9]{15,21})/?$'
        )

link = ""
match = link_regex.match(link)
channel = client.get_channel(int(match.group("channel_id")))
# channelからメッセージを取得する、get_messageは無いので注意!
message = await channel.fetch_message(int(match.group("message_id")))
# リアクションのリスト
reactions = message.reactions

次に、リアクションのリストから、絵文字と個数をリスト形式で表示させてみましょう。
Reaction.emojiが絵文字、Reaction.countがリアクションの個数です。

text = "絵文字 : 個数\n"
for reaction in reactions:
    text += f"{reaction.emoji} : {reaction.count}\n"

await message.channel.send(text)

これを、on_messageに当てはめるとこうなります。

import discord
import re
link_regex = re.compile(
            r'^https?://(?:(ptb|canary)\.)?discordapp\.com/channels/'
            r'(?:([0-9]{15,21})|(@me))'
            r'/(?P<channel_id>[0-9]{15,21})/(?P<message_id>[0-9]{15,21})/?$'
        )
client = discord.Client()


@client.event
async def on_message(message):
    # 送信者がbotである場合は弾く
    if message.author.bot:
        return 
    if message.content.startswith("/get_reactions "):
        link = message.content.replace("/get_reactions ", "")
        match = link_regex.match(link)
        channel = client.get_channel(int(match.group("channel_id")))
        target_message = await channel.fetch_message(int(match.group("message_id")))
        reactions = target_message.reactions
        text = "絵文字 : 個数\n"
        for reaction in reactions:
            text += f"{reaction.emoji} : {reaction.count}\n"

        await message.channel.send(text)

client.run("あなたのbotのtoken")

引数付きコマンドは、このようにreplaceを使用したり、正規表現を使ったり、空白がなければsplitを使うことが多いです。
しかし、Clientのみでコマンドを作る時は引数は一つぐらいの関数にしておくほうがいいのかなとは思っています。

Embedを送信する

URLのプレビューなどでお馴染みのEmbedを送信してみましょう。

この項で勉強すること

  • Embedの作成方法
  • フィールドの使い方・画像や時間を入れる
  • Embedの送信方法

Embedを作成する

discord.pyでのEmbedはdiscord.Embed型を使い表現します。
作成時、タイトルと説明を入れることができます(両方省略可)。

embed = discord.Embed(title="タイトル", description="説明")

情報を追加する

Embedには、フィールド、タイムスタンプ、サムネイル、画像、上側のユーザー情報、下側のアイコンとテキストを追加することができます。
フィールドは20個まで、その他は一つ追加できます。
また、フィールドはnameとvalueからなりますが、必ず両方とも空文字列("")やNone以外の、普通の文字列を入れてください
送信する際にエラーになります。
これらは設定しなくても送信は可能です。

embed = discord.Embed(title="タイトル", description="説明")

# フィールドを追加する
embed.add_field(name="名前", value="値")

# タイムスタンプを設定する
import datetime
embed.timestamp = datetime.datetime.now()

# サムネイルを追加する(URL指定なので注意!)
embed.set_thumbnail(url="https://upload.wikimedia.org/wikipedia/commons/0/07/Viscontisforzatarot.jpg")

# 画像を追加する(こちらもURL)
embed.set_image("https://upload.wikimedia.org/wikipedia/commons/0/07/Viscontisforzatarot.jpg")

# 上側のユーザー情報を追加
embed.set_author(name="タロットマン", url="https://ja.wikipedia.org/wiki/タロット", icon_url="https://upload.wikimedia.org/wikipedia/commons/6/6f/Taroky_trul.JPG")

# 下側のアイコンとテキストを追加
embed.set_footer(text="アルカナあるかな?", icon_url="https://upload.wikimedia.org/wikipedia/commons/6/6f/Taroky_trul.JPG")

送信する

discord.TextChannel.send関数の、embed引数に指定してやることで送ることができます。

# 上の続き

await channel.send(embed=embed)

終わりに

ここまでいかがだったでしょうか。
今回は書き方をメッセージ・画像・リアクション・Embedに分けて説明しました。
次の記事では、発展的なeventの使用方法、設定の変更方法、チャンネルの作り方について扱います。
それでは。 次の記事へ

sizumita
安曇野でpythonとかやってます。
proglove
国内最大級学生エンジニアオンラインコミュニティー
https://proglove.tech/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした