涼しい風

Discord.pyでdiscordbotを作っていた人のブログです。開発のお役に立てれば幸いです。2019/11/10を持って更新は終了しました。

Discord.pyのコンバータを使ってみる

Discord公式APIのラッパー、Discord.py 、およびそのフレームワーク Bot Commands Framework の機能である「コンバーター」を使ってみます。 ※この記事では、説明のため、Bot Commands Frameworkを使用することを前提にしています。

コンバータって何?

コンバータは、コマンド実行時に受け取った文字列をいい感じに変換してくれる機能です。 具体的には、数値とか、memberとかに変換してくれるのですが、実際に使ってみましょう。

コマンドの引数のコンバーター

普通、コマンドの仮引数は以下のように指定すると思います。 (この例では受け取った引数を数値に変換しています。)

@bot.command()
async def example_1( ctx , number ):

    number = int(number)
    number += 5

    await ctx.send( str(number) + " Hello World" )

しかし、コンバーターを使うと、

@bot.command()
async def example_2( ctx , number : int ):
  
    number += 5

    await ctx.send( str(number) + " Hello World")

int()が消えただけの些細な違いに見えますが、受け取った文字列を他の型に変換することって割とよくあると思います。


↑の例では整数に変換しましたが、memberchannel などのDiscordモデルのオブジェクトにも簡単に変換できるのがこの機能の強みです。

@bot.command()
async def example_3( ctx , member : discord.Member ):
    
    await ctx.send( member.name + " のニックネームは " + member.display_name )

@bot.command()
async def example_4( ctx , channel : discord.TextChannel ):

    await ctx.send( channel.name + " のIDは " + channel.id )

なお、memberの場合はメンションやID、名前、channelの場合はチャンネル名やチャンネルへのメンションが変換の対象となります。

コンバータを自作しよう

自作と言っても難しいことはしません。 上記の例だと可変長引数などで使いづらいと思いませんか?私は思います

コンバータのインスタンスを作成し、convert()します。

converter = commands.MemberConverter()
member = await converter.convert(ctx, string)

この場合、stringに格納されたmember名やメンションをmemberオブジェクトに変換しています。

[Discord.py] よくあるやりたい事とその解決策

Discord公式APIのラッパー、Discord.py を使い始めた時に躓きそうな点をまとめました。

その多くはドキュメントを精読すればわかりますが、初めはその読み方もわからないものです・・・

基本的な操作

メッセージに反応させたい(基本)

import discord

token = "" 

client = discord.Client()

# ↑ " と " の間にあなたのbotのトークンを書き込みます。

@client.event
async def on_message(message):
    
    await message.channel.send( "メッセージが送信されました、こんにちは" )

client.run(token)

メッセージが送信されたらasync def on_message(message):以下の処理が働きます。 そのとき、botメッセージの情報を受け取る事ができます。 そのメッセージから送信元のチャンネルを取得して、そこへ新たなメッセージを送信します。

githubなどにソースコードを公開する場合、tokenをそのまま書くのは乗っ取りの危険があります。 環境変数を設定可能なら、import discordの次の行にimport osと書き込み、 token = ""token = os.environ[ '環境変数名' ]と書き換えてください。


if文を使えば、ある内容のメッセージのみに反応させることもできます。 メッセージの内容は、messageに含まれる「content」から取得できます。

import discord

token = ""

client = discord.Client()

@client.event
async def on_message(message):

    if message.content.startswith("こんにちは"):

        await message.channel.send("こんにちは")
        # 内容が こんにちは で始まるメッセージにのみ反応します。
        # startwith(" ")の中身を書き換えると、その内容で始まるメッセージに反応するようになります。

    if message.content.endswith("おやすみ"):

        await message.channel.send("おやすみ")
        # 先ほどとは逆に、内容が おやすみ で「終わる」メッセージにのみ反応します。

    if message.content == "おはよう":

         await message.channel.send("おはよう")
         # メッセージ内容が「おはよう」の場合にのみ反応します。この書き方をすると、反応は完全一致のときのみになります。

    if "こんばんは" in message.content:
          await message.channel.send("こんばんは")
          # メッセージ内容に「こんばんは」がどこかに含まれていたら反応します。含まれているならどこに書かれていても反応します。

client.run(token)

メッセージ主の情報を取得したい・メンションしたい

messageにはさまざまな情報が含まれています。 先ほどの例に出てきた、channelcontentもその一つですね。

送信者の情報はmessage.authorで取得できます。また、member.mentionでメンションを作成することができます。

import discord

token = ""

client = discord.Client()

@client.event
async def on_message(message):
    reply = message.author.mention +"\n" 
    reply += "送信者名は" + message.author.name + "\n"
    reply += "送信されたチャンネル名は" + message.channel.name + "\n"
    reply += "送信されたサーバー名は" + message.guild.name + "\n"

    await message.channel.send(reply)

余談ですが、DMはサーバーに属していないので、もしこのコードのbotにDMを送信したら、nameが分からないのでエラーが返ってきます。 また、メンションは<@ID>と書くだけでも実現できます。特定のアカウントのみにメンションを送る場合は試してみてください。

botのプロフィールを編集したい

import discord

token = ""

client = discord.Client()

@client.event
async def on_message(message):
    if message.content == "!edit":

        payload = { "username" : "my_bot" }
        await client.user.edit( payload )

client.run(token)

このコードを実行すると、!editと送信したときに、botの名前が「my_bot」に変更されます。

アイコンを変更する場合は、

import discord

token = ""

client = discord.Client()

@client.event
async def on_message(message):
    if message.content == "!edit":

        with open( "ファイルのパス" ,"rb" ) as icon:
            payload = { "username" : "my_bot" , "avatar" : icon.read() }
            await client.user.edit( payload )

client.run(token)

とします。

これがうまく行かない場合は、ファイルのパスが間違っていないか確認してください。


自分自身のプロフィールを編集させたい場合はこれで問題ありませんが、他人のプロフィールを編集させる場合は、

payload = { "username" : "new_username" , "password" : "編集したいアカウントのパスワード"}

とします。しかし、パスワードで編集させるのはセキュリティを考えると現実的ではありませんね・・・

サーバーの内容を編集したい

サーバーの場合は、上記の方法を応用できます。

サーバーに対して行える編集の種類は非常に多いのでリストにしました、使いたいものをpayloadに加えてください。

(認証などがなされていない一般のサーバーで行える操作のみを書いています)

  • name → サーバー名。""で囲います。
  • icon → サーバーのアイコン。書き方は上の見出しでbotのアイコンを設定するコードを参考にしてください。
  • region → サーバーの音声チャンネルの地域設定。discord.VoiceRegion.地域名と書きます。地域名の一覧はここを参照してください。
  • afk_channel → サーバーのafkチャンネル設定。client.get_channel( 新しいAFKチャンネルのID )と書きます。
  • afk_timeout → afkチャンネルに隔離されるまでの時間。""で囲わずに直接秒数を書いてください。
  • owner → サーバーの所有権を変更する。guild.get_member( 新しい所有者のID )と書きます。
  • default_notifications → サーバーの通知設定。discord.NotificationLevel.レベルと書きます。レベルの一覧はここ
  • explicit_content_filter → サーバーのフィルター設定。discord.ContentFilter.フィルター対象と書きます。対象の一覧はここ
  • system_channel → サーバーの通知チャンネル。client.get_channel( 新しく設定したいチャンネルのID )と書きます。
  • reason → サーバーを編集する理由。""で囲います。

ファイルを送信したい

基本的な送信方法はこの記事で解説しています。

coolwind0202.hatenadiary.jp

「プレイ中」を変えたい・オンライン表示を変えたい

await client.change_presence()を使います。

引数にはactivity(プレイ中)、status(オンライン)、afk(AFKチャンネルに入るか)を指定できます。

import discord

token = ""

client = discord.Client()

@client.event
async def on_ready(ready):
    await client.change_presence( activity = discord.Game( name = "プレイ中欄の文字列" ) , status =discord.Status.ステータス名 )

client.run(token)

と書くと、○○をプレイ中、オンライン表示も指定したステータス名になっているはずです。

使用できるステータス名の一覧はここを参照してください。

処理を一時停止したい・定時に処理したい

この記事で解説しています。

coolwind0202.hatenadiary.jp

チャンネルの履歴を取得したい

この記事で解説しています。

coolwind0202.hatenadiary.jp

サーバー関連

チャンネルを作りたい(+カテゴリ指定)

カテゴリを取得しその下にチャンネルを作成することで実現します。

import discord

client = discord.Client()

token = ""

@client.event
async def on_message(message):
    if message.content == "!create":
        message.guild.create_text_channel("New Channel") #作成

    if message.content == "!create_say":
        ch = message.guild.create_text_channel("New Channel") #チャンネルを保持
        await ch.send("おはようございます。") #保持したチャンネルに送信

client.run(token)

このコードは、!createでNew Channelという名のチャンネルを作成します。また、!create_sayで、作成したチャンネルに発言します。

カテゴリを指定して作成する場合は、以下のように書き換えます。

@client.event
async def on_message(message):
    if message.content == "!create":
        category = client.get_channel(message.channel.category_id)
        ch = await category.create_text_channel("Category - New Channel")
        await ch.send("こんばんは") #チャンネルに送信

監査ログを取得したい

監査ログを取得するにはguild.audit_logs()を使います。

当然、botには監査ログの閲覧権限が与えられている必要があります。

guild.audit_logs()には、

  • limit → 何個のログを取得するか。""で囲わずに数値を書きます。limit=Noneなら取得可能な全てのログを取得します。
  • oldest_frist → 古い順に取得するか。真偽値を書きます。
  • user → メンバーでフィルターをかけます、guild.get_member( メンバーのID )と書きます。
  • beforeafter → 指定した時刻の範囲のログを取得します。datetimeオブジェクトを指定します。
import discord

token = ""

client = discord.Client()

@client.event
async def on_message(message):

    if message.content == "!audit":

        if message.guild is None:
            # メッセージがギルドに属していない = DMチャンネルの場合
            return
        if not message.guild.me.guild_permissions.view_audit_log:
            # 監査ログの閲覧権限が無い場合
            return

        audit_list = [ i.action for i in await message.guild.audit_logs( limit = None ).flatten() ]

        print(audit_list)

client.run(token)

beforeafterを指定するには(補足)
直接コードには書きませんが、以下のように指定します(この場合2010年の1/1、1時1分より後のログを取得します)

tmp = datetime.datetime(2015, 1, 1, 1, 1)
audit_list = [ i.action for i in await message.guild.audit_logs( limit = None , after = tmp ).flatten() ]

guild.audit_logs()は非同期イテレータという形でログを返します。

これだと少し扱いにくいのですが、上記のようにawait guild.audit_logs.flatten()とすると簡単に扱えるようになります。

async for i in message.guild.audit_logs( limit = None )
    print( i.action )

このように非同期のループで処理しても同様です(一つ一つのログに処理をするときに使うと分かりやすいかもしれないです)

メンバーをキック・BANする

botは荒らしに使う物じゃないよ。

import discord

client = discord.Client()

@client.event
async def on_message(message):
    if message.content == "!kick":
        await message.author.kick()
    if message.content == "!ban":
        await message.author.ban()

client.run(token)

!kickと送信すると送信者がキックされ、!banと送信すると送信者がBANされる。理不尽!

特定のワードを送信したらキックするようなNGワードbotを作るなら、

client.ng_list = ["from","the","far","east"] #NGワードのリスト

@client.event
async def on_message(message):
    if [word for word in client.ng_list if word in message.content]:
        await message.author.send("メッセージに不適切な内容が含まれているのでキックします。")
        await message.author.kick()

メッセージ関連

メッセージを埋め込みたい

この記事で解説しています。

coolwind0202.hatenadiary.jp

メッセージを編集・削除したい

この記事で解説しています。

coolwind0202.hatenadiary.jp

メッセージをピン留めしたい

import discord

token = ""

client = discord.Client()

@client.event
async def on_message(message):
    if message.content == "!pin":
        if not message.guild.me.guild_permissions.manage_messages:
            return
        await message.pin()

client.run(token)

特定のメッセージをピン留め・ピン留めを解除するコードも載せておきます。(Bot Commands Frameworkを使用)

import discord
from discord.ext import commands

token = ""

bot = commands.Bot(command_prefix="!")

@bot.command()
async def pin( ctx , message : discord.Message ):
    await message.pin()

@bot.command()
async def unpin( ctx , message : discord.Message ):
    await message.unpin()

bot.run(token)

メッセージにリアクションを付けたい

カスタムでない標準の絵文字を探すには、このサイトがおすすめです

emojipedia.org

カスタム絵文字でリアクションを付けるには、絵文字のIDが必要です。 絵文字の前にバックスラッシュ('\')を付けて送信するとIDが表示されると思います。

import discord

token = ""
client = discord.Client()

@client.event
async def on_message(message):
    if message.content == "!thinking":
        await message.add_reaction("🤔")
    if message.content == "!custom_emoji":
        await message.add_reaction("<:絵文字名:絵文字ID>")

client.run(token)

メッセージがピン留めされているか・メッセージに付けられたリアクションを取得したい

message.pinnedmessage.reactionsを使用します。

on_message(message)で取得した所でピン留めはされていないので、コード例ではBot Commands Frameworkを使用します。

import discord
from discord.ext import commands

token = ""

bot = commands.Bot( command_prefix="!" )

@bot.command()
async def message_pinned( ctx , message : discord.Message ):
    await ctx.send( message.pinned )

@bot.command()
async def message_reaction( ctx , message : discord.Message):
    await ctx.send( "**reactions**\n" + "\n".join( [ i.emoji for i in message.reactions ] ) )
    # message.reactionsはreactionのリスト。そのままだと送信できないので、emoji属性のみのリストを作って送信。

bot.run(token)

Discordモデル取得、リスト生成系のTips

Discordモデル

共通

discord.utils.get( 検索したいイテラブルオブジェクト , name = "名前" )
discord.utils.get( 検索したいイテラブルオブジェクト , id = ID )

特定のチャンネルを取得したい

client.get_channel( チャンネルID )

特定のメッセージを取得したい

client.fetch_message( メッセージID )

特定のサーバーを取得したい

client.get_guild( サーバーID )

サーバー内の特定のメンバーを取得したい

guild.get_member( メンバーID )

特定のユーザーを取得したい

client.get_user( ユーザーID )

特定の役職を取得したい

guild.get_role( 役職ID )

特定のカスタム絵文字を取得したい

client.get_emoji( 絵文字ID ) 絵文字IDは、バックスラッシュの後に絵文字(\絵文字)と送信することで表示される。

リスト

(便利っぽくて変に長く書かなくてもいいリストをたまーに追加していきたい)

サーバー内のメンバー名の一覧を取得したい

  • 通常

[member.name for member in guild.members]

  • botを除くパターン)

[member.name for member in guild.members if not member.bot]

botのいる全サーバーから特定の名前のWebhookのみを取得したい

[discord.utils.get(await guild.webhooks(),name="webhook名") for guild in client.guilds ]

なお、各サーバーから1つずつ取得されます。

同名のWebhookが複数存在するとき、それを全て取得するコードには二重ループを使ってみました。

[webhook for guild in client.guilds for webhook in await guild.webhooks() if webhook.name=="webhook名"]

メンバーの持っている役職名のリスト

[role.name for role in member.roles]

サーバーに存在するけど、メンバーが持っていない役職

リストといいましたがこういうのは集合型でやった方が簡潔なので変換してしまいましょう。

guild_roles = set(member.guild.roles)
my_roles = set(member.roles)

list(guild_roles ^ my_roles) #結果

コマンド

以下の見出しはBot commands frameworkを使用していることが前提です。

コマンド作成やループ管理を楽にしてくれるフレームワークで、試してみる場合は以下のように書くと楽です。

client = discord.Client()
client.run(token)

の二行を削除し、

import discord
from discord.ext import commands

token = ""

bot = commands.Bot( command_prefix="!") # !は他の記号でも大丈夫です

bot.run(token)

このように書けばOKです。インスタンス名は自由です。

また、commands.Botクラスを継承した新たなbot用のクラスを作成することも可能ですが、ここでは省略します。 継承や、機能をまとめるコグという機能の利用については以下が詳しいです。

qiita.com

コマンドの作り方

import discord
from discord.ext import commands

token = ""

bot = command.Bot( command_prefix = "!" )

@bot.command()
async def new_command(ctx):
    await ctx.send( "コマンドが送信されました。" )

bot.run(token)

ctxはコマンドが送信された際に受け取る事ができます、messageと違い直接sendできます(内部的には省略してるだけですが)

messageと同様に、サーバーやチャンネル、送信者の情報を取得できます。

await ctx.send( "サーバー:"+ ctx.guild + "\n" + "チャンネル" + ctx.channel + "\n" + "送信者" + ctx.author)

コマンドの引数

Bot Commands Frameworkを使えばコマンドに簡単に引数を設定できます。

import discord
from discord.ext import commands

token = ""

bot = commands.Bot( command_prefix = "!" )

@bot.command()
async def arg_example_1(ctx,string):
    await ctx.send(string)

@bot.command()
async def arg_example_2(ctx,*strings):
    print( len(strings) )
    
    if len(strings) > 0:
        await ctx.send( "\n".join(strings) )

@bot.command()
async def arg_example_3(ctx,*,sentence):
    await ctx.send("入力された文章:" + sentence )


@bot.command()
async def arg_example_4(ctx,num:int,member:discord.Member):
    await ctx.send(member.name*num)


bot.run(token)

コマンドの詳細は以下の記事で解説しているので参照してください。 coolwind0202.hatenadiary.jp

f:id:coolwind0202:20190831141630p:plainf:id:coolwind0202:20190831141613p:plain
コードの実行例

画像では都合上prefixが!から&になっていますが、前述したとおり!限定ではなく、コード内で設定したprefixを使ってください。

コマンドを役職・権限で制限

import discord
from discord.ext import commands

token = ""

bot = commands.Bot( command_prefix = "!")

@bot.command()
@commands.is_owner()
async def owner_only(ctx):
    await ctx.send( "botの所有者のみが実行できるコマンド。" )

@bot.command()
@commands.has_role( "役職Aの名前" )
@commands.has_role( 役職BのID )
async def role_only(ctx):
    await ctx.send( "指定した役職を持っているメンバーのみが実行できるコマンド。" )

@bot.command()
@commands.has_permissions( 権限名 = True )
async def permission_only(ctx):
    await ctx.send( "指定した権限を持っているメンバーのみが実行できるコマンド。" )
    # 権限名 = True はカンマ区切りで複数指定することができます。

bot.run(token)

permission_onlyで設定した権限の一覧はここを参照してください。

そのほか、コマンド実行時に簡単にチェックできるものです。

@commands.has_any_role( 複数の役職をカンマ区切り ) #送信者が指定された役職を一つでも持っているときに実行されます。
@commands.is_nsfw() #nsfwチャンネルだった場合に実行されます。
@commands.guild_only() #サーバー内のテキストチャンネルでのみ実行されます。
@commands.dm_only() #DMチャンネルでのみ実行されます。
@commands.bot_has_role( 役職名 or 役職ID ) #指定された役職をbotが持っている場合にのみ実行されます。
@commands.bot_has_permissions( 権限名 = True ) #指定された権限をbotが持っている場合にのみ実行されます。
@commands.bot_has_any_role( 複数の役職をカンマ区切り) #指定された役職をbotが一つでも持っている場合に実行されます。

補足ですが、チェック用の関数を作ってチェックを自作することもできます。

import discord
from discord.ext import commands

token = ""

bot = commands.Bot( command_prefix = "!" )

def example_check(ctx):
    return ctx.message.author.id == チェックしたいID

@bot.command()
@commands.check( example_check )
async def checked_command(ctx):
    await ctx.send( "IDのチェックを通ったコマンドのみ実行されます。" )

bot.run(token)

チェックの自由度が広がると思います。

コマンドのクールダウン

クールダウンというのは、コマンドが過剰に働かないようにおやすみさせるものです( ˘ω˘ ) スヤァ…

@commands.cooldown( クールダウンが発動するまでのコマンド実行回数 , クールダウンの時間 ,クールダウンの対象)

import discord
from discord.ext import commands

token = ""

bot = commands.Bot( command_prefix = "!" )

@bot.command()
@commands.cooldown( 5 , 30.0 , BucketType.guild )
async def cooldown_example(ctx):
    await ctx.send( "5回使用するとそのサーバーでは30秒間実行できません。" )

bot.run(token)

クールダウンまでのコマンド使用回数をカウントする対象は、BucketType.対象名と指定します。

  • default Discord全体でカウント
  • guild サーバーごとにカウント
  • category サーバーのチャンネルカテゴリごとにカウント
  • channel サーバーのチャンネルごとにカウント
  • role サーバーの役職ごとにカウント
  • member サーバーのメンバーごとにカウント
  • user Discord全体のユーザーごとにカウント

コマンドにエイリアスを付けたい・使用時は別の名前のコマンドにしたい

import discord
from discord.ext import commands

@bot.command( name = "test" )
async def name_example(ctx):
    await ctx.send( "!testなら実行できるが、!name_exampleでは実行できない。" )

@bot.command( aliases = [ "test1" , "test2" ])
async def aliases_example(ctx):
    await ctx.send( "!test1、!test2、!aliases_exampleのどれでも実行できる。" )

Discord.pyでメンバーの役職を管理

Discord公式APIのラッパー、Discord.py を利用して、メンバーの役職を管理する方法を紹介します。

操作したい役職を取得する(前提)

役職を取得するには、discord.utils.get( guild.roles , name="役職の名前" )と書きます。

実装

役職を付与する場合には、await member.add_roles(role)を使います。

剥奪する場合には、await member.remove_roles(role)

名前の「test」の役職が送信者になかったときは付与、既にあったときは剥奪するコードです。

@client.event
async def on_message(message):

    if message.content == "!role":
        role = discord.utils.get( message.guild.roles , name = "test" )

        if role not in message.author.roles:

            await message.author.add_roles(role)
            await message.channel.send("役職を付与しました")
            # member.rolesは、メンバーの持っている役職の一覧を返すので、その中に目的の役職がないか探しています。

        else:

            await message.author.remove_roles(role)
            await message.channel.send("役職を剥奪しました")

f:id:coolwind0202:20190830182731p:plainf:id:coolwind0202:20190830182728p:plain
コードの実行例

実行例のメッセージ内容がコードと異なっていますがご容赦ください

役職操作関連について、注意点

役職管理の権限が与えられているか?

これが無いと始まりません。

botに役職管理の権限が与えられているかは、以下のように取得できます。

# 役職の取得等は省略

@client.event
async def on_message(message):
    if message.content == "!role":
        if message.guild.me.guild_permissions.manage_roles:
            await message.author.add_roles(role)

            # 役職管理権限を持っているときにここ
        else:
            await message.channel.send("権限がありません")
            # 役職管理権限がないときにここが動く

一応コードを解説すると、 meは、サーバーの中でのbot自分自身を表します。

guild_permissionsはメンバーがサーバー内でどの権限があってどの権限が無いかをまとめたものです。

この中にmanage_roles(役職管理)があった場合には上の処理、無かったら下の処理が走るようになっています。

自分より位が上の役職を操作していないか?

# 役職の取得等は省略
@client.event
async def on_message(message):
    if message.content == "!role":
        try:
            await message.author.add_roles(role)
        except discord.Forbidden:
             await message.channel.send("権限がありません。")

実際は上記のmanage_rolesの問題もこれで解決できるんですが、上記の方法は他の権限取得に応用できるので方法を分けました。

存在しない名前の役職をutils.getしていないか?

discord.utils.getbotが取得できるオブジェクトから、指定されたモノをAND検索するメソッドですが、当然何もマッチしない場合もあります。

何もマッチしなかったのに参照しようとするとTypeError: NoneType.....などと怒られます。

# 色々と省略

tmp = discord.utils.get( message.guild.roles , name = "hoge")

if tmp is None:
    await message.channel.send("役職が見つかりませんでした。")
    return

としてあげると問題無いです。

Discord.pyで特定のメッセージを編集/削除

Discord公式APIのラッパー、Discord.py で特定のメッセージを編集したり削除したりする方法を紹介します。

メッセージの取得について (前提)

※もしon_message(message) などでメッセージを取得する場合は読み飛ばしていただいて構いません

Discord.pyではawait fetch_message()で特定のメッセージを取得できます。

# サーバーから(基本)
guild = client.get_guild( サーバーID )
msg = await guild.fetch_message( メッセージID )

# チャンネルから(サーバー取得済みが前提)
channel = guild.get_channel( チャンネルID )
msg = await guild.fetch_message( メッセージID )

# メンバーから(サーバー取得済みが前提)
member = guild.get_member( メンバーID )
msg = await guild.fetch_message( メッセージID )

コード例で出てくるmessageはmsgに読み替えてください。

編集

メッセージ管理の権限がないとエラーになります・編集できるのはbot自分自身のメッセージだけです

await message.edit()を使用します。

@client.event
async def on_message(message):
    if message.content == "!edit":
        await message.edit( content = "編集しました" )

削除

メッセージ管理の権限が無いとエラーになります

await message.delete()を使用します。

@client.event
async def on_message(message):
    if message.content == "!delete":
        await message.delete()
        await message.channel.send( "削除しました" )

簡単ですね。

botに自分自身のメッセージを編集させる

@client.event
async def on_message(message):
    if message.content == "!edit":
        tmp = await message.channel.send("編集前メッセージ") # 編集するメッセージを保持
        await tmp.edit( content = "編集しました" )
    
    if message.content == "!delete":
        tmp = await message.channel.send("削除前メッセージ") # 削除するメッセージを保持
        await tmp.delete()
        await message.channel.send( "削除しました" )

await send()は、送信されたMessageそのものを返すので、それを保持することにより、bot自身が送信したメッセージの操作が可能になります。

f:id:coolwind0202:20190829180054p:plain
実行例

事後なので分かりにくいですが実際に編集/削除されています。

Discord.pyでメッセージの履歴を取得する

Discord公式APIのラッパー、Discord.py を利用して、テキストチャンネルの履歴を取得する方法をかんたんに紹介します。

概説

メッセージ履歴の取得には、history()を使います。

@client.event
async def on_message(message):

    if message.content == "!history":
        tmp = await message.channel.history( limit = 5 ).flatten()
        await message.channel.send( "\n".join( [ i.content for i in tmp] ) )

f:id:coolwind0202:20190829171955p:plain
実行例

history()はメッセージ履歴を非同期イテレータという形で返します。

引数には、

  • limit (取得したいメッセージの限度数/整数値)
  • before (指定された時刻より前のメッセージを取得する/datetime)
  • after (指定された時刻より後のメッセージを取得する/datetime)
  • around (指定された時刻の付近のメッセージを取得する/datetime)
  • oldest_first (メッセージを古い順に取得する/真偽値)

を渡すことができます。

非同期イテレータはそのまま扱おうとすると少し複雑です(後述)。

説明用なので、履歴を扱いやすくするために、await flatten()でメッセージのリストにしています。

補足

ここではawait flatten()で扱いやすくしてから処理しましたが、async forを使うことでそのまま処理することもできます。 特に目的が無ければさっきのやり方でも問題ないと思いますけどね。

@client.event
async def on_message(message):
    if message.content == "!history":

        async for i in message.channel.history( limit = 3 ):
            await message.channel.send(i.author.display_name+"\t"+i.content)

f:id:coolwind0202:20190829172858p:plain
実行例(async forを使った場合)

このように、async forのループで一つ一つに処理を行えます。

この例では、i が取り出したメッセージを表しているので、contentやauthorを取得できています。

なおdisplay_nameはmemberのサーバーでの表示名です。


この記事ではメッセージ履歴の取得方法を紹介しました。

oldest_firstとbeforeを使えば過去のメッセージも遡りやすくなりますね。

Discord.pyでメンバーのVCの情報を取得

Discord公式APIのラッパー、Discord.py で、VCの情報を取得する方法を紹介します。

VoiceState

メンバーの場合、member.voiceVoiceStateオブジェクトを取得できます。

VoiceStateは、そのメンバーのボイスチャンネルでの状況を表すものです。

これは、メンバーがボイスチャンネルにいないときはNoneを返し、いるときはチャンネルの情報などを返します。

@client.event
async def on_message(message):
    if message.content == "!vc":
        state = message.author.voice

        if state is None:
            await message.channel.send( "ボイスチャンネルに入っていません。" )

        else:
            await message.channel.send( message.author.name + "さんは、 " + state.channel.name + " に入っています。" )

f:id:coolwind0202:20190828225036p:plain
コードの実行例

このとき、state.channelは送信者のいるVoiceChannelオブジェクトと言えます。

VoiceStateで取得できる情報

真偽値

  • deaf 出力をオフにしているか?
  • mute サーバー側の設定で強制的にミュートされているか?
  • self_mute 自分でミュートしたか?
  • self_video ボイスチャンネルで動画を配信しているか?
  • afk AFKチャンネル(無音状態で時間が経過したメンバーが強制的に移動させられるチャンネル)に入っているか?

VoiceChannel

  • channel 上記の内容で説明した通り、メンバーがサーバーのどのチャンネルに入っているかを表します。

Discord.pyでヘルプコマンドを実装する方法

Discord公式APIのラッパー、Discord.py を利用して、botにヘルプコマンドを実装する方法を紹介します。

ただし記事の前提として、Bot Commands Frameworkを利用している必要があります。

1.デフォルトのヘルプコマンドについて

以下のようなかんたんなコードを想定します。コグを利用していたり、bot用のクラスを作成している場合は適宜読み替えてください。

import discord
from discord.ext import commands

bot = commands.Bot( command_prefix = "!" )

@bot.command()
async def example1( ctx ):
    await ctx.send( "例1" )

@bot.command()
async def hogehoge( ctx ):
    await ctx.send( "ほげほげ" )

bot.run("Your token") 

このコードを実行すると、

f:id:coolwind0202:20190827203237p:plain
デフォルトのヘルプコマンド

もしあなたがヘルプコマンドについて何も指定していない場合、「prefix+help」でこのメッセージを送信するはずです(!help、など)

f:id:coolwind0202:20190827204902p:plain
デフォルトでコマンドの詳細を表示させた例
「prefix+help command」とすると、画像のようにそのコマンドの書き方を送信するはずです。

このヘルプでは、各コマンドに説明を書き加えることができます。

f:id:coolwind0202:20190827211540p:plain
コマンドに説明を書き足した例
その場合、

@bot.command( help = "ここにコマンドの説明を設定します" )
async def test(ctx):
    await ctx.send( "テストです" )

このように書きます。

2.自分好みのヘルプに

これだとコマンドしか表示させられず自由度が足りないですね・・・

そこで、次のように書くと、自分でヘルプコマンドを実装できます。

import discord
from discord.ext import commands

bot = commands.Bot( command_prefix = "!" , help_command = None )

@bot.command()
async def example1( ctx ):
    await ctx.send( "例1" )

@bot.command()
async def hogehoge( ctx ):
    await ctx.send( "ほげほげ" )

@bot.command()
async def help( ctx ):
    await ctx.send( "helpに書きたい内容" )

bot.run("Your token") 

f:id:coolwind0202:20190827211716p:plain
ヘルプコマンドの実装例

これで自由にbotの説明を書くことができます。