Discord botで犬の画像を送信するコマンドを作成しました

f:id:nafuka11:20211027162218p:plain:h400

はじめに

先日、Discordサーバのユーザから、自作のDiscord botに犬の画像を送信するコマンドを追加してほしいと要望があり、実装することにしました。

この記事では、API選定、実装について簡単に紹介します。

API選定

Dog API にしました。

候補

Dog API

  • 良いところ:画像を PullRequest で受け付けている。フォーマットが誤ってる場合Closeされるようなので、画像の質が保たれていると思う。
  • 気になるところ:Rate limitについて書かれていない。

The Dog API

  • 良いところ:リクエストをどれだけ受け付けるか書いてある(Freeで10000リクエスト/月)。
  • 気になるところ:画像を気軽にアップロードできるので、Dog APIより質が低そう。
    • 系列?のサービス The Cat API を使っているが時々、色を反転しただけの外れ画像があったりする。

Rate limitについては、Discord側でも連投制限があるので多分大丈夫だろう……ということで、Dog APIを使ってみることにしました。

実装

自作Discord botPython + discord.pyで作られています(discord.pyは 開発を停止 していますが、一旦様子見でdiscord.pyを使っています)。

犬画像を送信するコマンドを作成するため、discord.py に犬画像送信コマンド用の Cog を追加しました。

既存のコマンドに猫画像を送信するコマンドがあり、動物くくりでCogをまとめることも可能でした。
しかし、個別でコマンドをdisableしたい可能性を考え、Cogを別にしています。

犬画像も猫画像も、やることはほぼ同じなため、BaseとなるCogを作成し、それぞれ継承しました。

コード例

BaseとなるCogは以下のような感じです。message.send_image は指定チャンネルに画像を送信する関数です。

src/utils/message.py にメッセージ関係の関数をまとめることで、一括でフォーマットを変更しやすくしています。

import aiohttp
from typing import Any
import discord
from discord.ext import commands
from src.utils import message


class RandomImageBaseCog(commands.Cog):
    def __init__(self, bot: commands.Bot):
        self.bot = bot

    async def send_image(self, channel: discord.channel, url: str, **kwargs: Any) -> None:
        """APIから画像URLを取得しchannelに送信する"""
        json = await self.fetch_url(url, **kwargs)
        image_url = self.get_image_url_from_json(json)
        await message.send_image(channel, image_url)

    async def fetch_url(self, url: str, **kwargs: Any) -> Any:
        """URLをfetchしjsonを返す"""
        async with aiohttp.ClientSession(raise_for_status=True) as session:
            async with session.get(url, **kwargs) as response:
                json = await response.json()
                return json

    def get_image_url_from_json(self, json: Any) -> str:
        """レスポンスのjsonから画像URLを返す"""
        raise NotImplementedError

RandomImageBaseCog を継承する DogCog はこんな感じです。

from typing import Any
import discord
from discord.ext import commands
from src.cogs.random_image_base import RandomImageBaseCog


class DogCog(RandomImageBaseCog):
    ENDPOINT_URL = "https://dog.ceo/api/breeds/image/random"

    @commands.command()
    async def dog(self, ctx: commands.Context) -> None:
        """犬の画像を表示します

        [Dog API](https://dog.ceo/dog-api/) から画像のURLを取得し、表示します"""
        if ctx.author == ctx.bot:
            return
        await self.execute_command(ctx.channel)

    async def execute_command(self, channel: discord.Channel) -> None:
        await self.send_image(channel, self.ENDPOINT_URL)

    def get_image_url_from_json(self, json: Any) -> str:
        return json.get("message")

終わりに

今までは猫画像だけだったのが、犬画像も見られるようになって、より癒されるようになりました。

コマンドを追加して良かったです。