天色グラフィティ

機械学習やプログラミングでいろいろ作って遊ぶブログ

【discord.py】Among Usのためにランダムに人を選ぶDiscord Botを作りました

現在このbotは公開を停止しています。申し訳ありません。

f:id:ejinote:20210226173153p:plain

discord.pyを使ってDiscordのbotを2時間くらいで作った話を書きます。

なんで作ったか

Among Usはクルーメイト(村人)とインポスター(人狼)が争うゲームなのですが、MODを利用して「狂人」「てるてる」などの新役職を入れることができます。

ただし、MODを利用できるのはsteam経由だけなので、iOSやSwitchから参加している人は利用することができません。

そこで、ゲーム内の機能として利用するのではなく、DiscordのDMで通知を送ることでゲーム外で新役職を利用しようと考えました。

仕様

Botの仕様

  • Discordのコマンドとして .darts コマンドを実装する
  • .darts [channel_name]channel_nameボイスチャット)に入っているメンバーからランダムに1人選ぶ
  • 選ばれた人にDMを送る

デプロイ

  • Google Compute Engineのf1-microインスタンスにデプロイする
    • vCPU×1、メモリ600MBなのでザコだが、無料で使えるので文句は言わない

実装

Botの用意

Discord Developer PortalからApplicationを作成します。

discord.com

詳しくはこちらの記事が参考になります(感謝!)

qiita.com

discord.pyでの実装

discord.pyを使います。

discord.Client を使うパターンと、discord.ext.commands を使うパターンがあります。 今回のように引数を取るコマンドの場合、discord.ext.commands を使うのが扱いやすいです。

@bot.command() デコレータをつけることで、その関数をコマンドとして扱うことができます。

第1引数は commands.Context が入ってきており、コマンドが叩かれたチャンネルやその文章などの内容が含まれています。 第2引数以降を定義した場合、そのままコマンドの引数になります。

@bot.command()
async def darts(ctx: commands.Context, channel_name: str):
    if ctx.author.bot:
        return

    await ctx.message.delete()

    channel_name = channel_name.lstrip("#")
    channels = ctx.guild.voice_channels  # ボイスチャンネルリストを取得
    for ch in channels:
        if ch.name == channel_name:
            if not ch.members:
                await ctx.send(f"#{channel_name}には誰もいません")
                return
            member = random.choice(ch.members)
            await member.send("あなたが選ばれました!")
            return

    await ctx.send(f"#{channel_name}が存在しません")

ハマったポイント

ボイスチャットに入っているメンバーを取得するには、Bot側とスクリプト側の両方に設定が必要でした。

Bot

botの設定ページから SERVER MEMBERS INTENT をオンにします。

f:id:ejinote:20210226152449p:plain

スクリプト

import discord
from discord.ext import commands

intents = discord.Intents.default()
intents.members = True
bot = commands.Bot(command_prefix=".", intents=intents)

コード全体

import os
import random
import discord
from discord.ext import commands
from typing import Optional

intents = discord.Intents.default()
intents.members = True
bot = commands.Bot(command_prefix=".", intents=intents)


@bot.command()
async def darts(ctx: commands.Context, channel_name: str, name: Optional[str] = None):
    if ctx.author.bot:
        return
    await ctx.message.delete()

    channel_name = channel_name.lstrip("#")
    channels = ctx.guild.voice_channels
    for ch in channels:
        if ch.name == channel_name:
            if not ch.members:
                await ctx.send(f"#{channel_name}には誰もいません")
                return
            member = random.choice(ch.members)
            msg = f"{name}に選ばれました!" if name is not None else "あなたが選ばれました!"
            await member.send(msg)
            return

    await ctx.send(f"#{channel_name}が存在しません")


TOKEN = os.environ.get("DISCORD_BOT_TOKEN")
bot.run(TOKEN)

まとめ

discord.pyは手軽に使えて便利。slack関係のライブラリも見習ってほしい

ソースコードはこちらです

github.com

参考文献