DiscordのBotをdiscord.jsで作ってみた

こんにちは。最近映画ベイマックスを久しぶりに見ました。日本が舞台らしいですけどサイバーパンク感マシマシになってました。あんな感じの日本になったら面白そうですね、

最近DiscordのBotをdiscord.jsを使って作る機会があったので同じような人の参考になるようにまとめておきます。

やりたいことは、Discordのボイスチャンネルの状況(誰が何人ボイスチャンネルに現在参加しているか)をslackに通知することでした。

slackへの通知はincomingWebhookを使うだけで終わりです。対して、Discorのボイスチャンネルの状況を取得するにはBotが必要で、そのBotを使ってボイスチャンネルの状況を取得することができるようです。

DiscrodのBotはこちらが参考になります。

そうして作成したBotを操作するためのライブラリは、Pythonのdiscord.pyやJavaScriptのdiscord.jsが主流なようです。

Pythonに馴染みないのでdiscord.jsを利用することにしました。 discord.jsではプッシュ型(コードの実行により発火する)でもプル型(リスナーを設定して状況が変わったら自動で発火する)でも発火できましたが、常時サーバーを設けると費用が高くなりそうなのでプッシュ型にします。AWSでLambdaをcronを使って15分に一度発火させるようにします。

デプロイはCDKを使いました。コードはこちらのリポジトリで公開してます。

その中のLambdaのコードはこんな感じです。CDKがTypescriptなのでLambdaのコードもTypescriptにしたかったんですが、Lambdaにコードを渡すときにはJSにビルドさせる必要が出てきて、ぱっとその方法がググっても出なかったので諦めました 😅

const Discord = require('discord.js');
const { IncomingWebhook } = require('@slack/webhook');

const client = new Discord.Client();

const discordToken = process.env.DISCORD_TOKEN;
const url = process.env.SLACK_WEBHOOK_URL;

const webhook = new IncomingWebhook(url);

const init = () => new Promise((resolve, reject) => {
    client.on('ready', () => {
        resolve()
    });
})

exports.handler = async function (event) {
    await client.login(discordToken);
    await init()

    const channels = client.channels.cache.array();

    const members = []
    channels.filter(c=>c.type==="voice").forEach(c=>{
        const membersInChannel = c.members.values()        
        members.push(...membersInChannel)
    })

    if (members.length === 0) {
        return {
            statusCode: 200,
            headers: { "Content-Type": "text/plain" },
            body: message
        };
    }

    // 適当な一人選出
    const someone = members[Math.floor(Math.random() * members.length)].displayName

    let message = `いま、${someone}さん他${members.length - 1}人がdiscoにいるよ👀`

    if (members.length === 1) {
        message = `${someone}さんがdiscoでさみしそうにしてるよ👀`
    }

    await webhook.send({
        text: message,
    });

    return {
        statusCode: 200,
        headers: { "Content-Type": "text/plain" },
        body: message
    };
};

ちなみに、プル型の場合は、このイベントをlistenして実装する感じになりそうです。

discord.jsが型を提供していなかったので、最初は実装が辛かったんですが、VSCodeでデバッグして試行錯誤するとスムーズに実装ができましたのでおすすめです


See also