冬休みの自由工作(?)にDiscordBotを作ってみた

こんにちは。ATOM 事業部エンジニアの和田です。

新年一発目のブログということで、あけましておめでとうございます。本年もよろしくお願いいたします。

今回は、年末の自由工作的な感じで Discord の Bot を作ってみたのですが、
思ったよりも簡単に作れたのでその紹介をしていこうと思います。

なぜ作ろうと思ったのか?

Slack の Bot は以前個人的に作っていたものがあったのですが、
元データの提供が終了してしまい、メンテすることもなくなってしまいました。

Slack の Bot はさくっと作ることができる状態ですが、他のツールで Bot 作ったことがないなと思い、一番最初に思いついた Discord で作ってみる方向に。

作ってみた

/dice と入力すると、1~6 のランダムな数字を返す Bot を試しに作ってみました。

使用したのは以下の通りです。

node.js v20.2.0
typescript v5.1.3
discord.js v14.14.1

discord の Bot を作る場合は Python または JavaScript という選択肢がライブラリに対する日本語サポートもあるため書きやすいと思います。

今回は TypeScript の勉強も兼ねて TypeScript で書いてみました。 そのため、discord.js のライブラリを利用しています。

Bot の作成

Discord の Bot を作成するには、Discord の開発者サイトにアクセスして App を作成し、適当なサーバーに Bot を招待する必要があります。

この部分は公式ドキュメント通りにやるのが一番早いです。
https://discord.com/developers/docs/intro

Bot の実装

ざっくりこんな感じになりました。一部継承していたりする部分は割愛しています。

main.ts

import { Client, Events, GatewayIntentBits, Interaction } from "discord.js";
import dotenv from "dotenv";

dotenv.config();

import slash from "@/commands/slash";
import { Dice } from "@/commands/slash/dice";

const client = new Client({
  intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMembers],
});

client.once(Events.ClientReady, () => {
  console.log(`準備OKです! ${client.user?.tag}がログインします。`);
});

client.on(Events.InteractionCreate, async (interaction: Interaction) => {
  // スラッシュコマンドの処理
  if (interaction.isChatInputCommand()) {
    let action = null;
    switch (interaction.commandName) {
      case Dice.commandName:
        action = new Dice();
        break;
      default:
        console.error(
          `${interaction.commandName}というコマンドには対応していません。`
        );
    }
    if (action != null) {
      try {
        await action.execute(interaction);
      } catch (error) {
        await interaction.followUp({
          content: "コマンド実行時にエラーになりました。[" + error + "]",
          ephemeral: true,
        });
      }
    } else {
      await interaction.reply({
        content: "コマンドが登録されていません。",
        ephemeral: true,
      });
    }
  }
});

// ログインします
client.login(process.env.DISCORDAPPBOTTOKEN);

dice.ts

import { SlashCommandBuilder, CommandInteraction } from "discord.js";
import { Slash } from "@/commands/slash/slash";

export class Dice extends Slash {
  static readonly commandName: string = "dice";
  slashCommand: SlashCommandBuilder;

  constructor() {
    super();
    this.slashCommand = new SlashCommandBuilder()
      .setName(Dice.commandName)
      .setDescription("運命のダイスロール");
  }

  async execute(interaction: CommandInteraction) {
    const result = Math.floor(Math.random() * (6 + 1 - 1)) + 1;
    const res = result.toString();
    await interaction.reply("サイコロの出目:" + res);
  }
}

これでざっくり動く部分はできたのですが、スラッシュコマンドを使っているため、事前に以下コードを実行してコマンドの登録はお忘れなく。。。。

deploy-command.ts

import dotenv from "dotenv";
import { REST } from "@discordjs/rest";
import { Routes } from "discord-api-types/v10";

dotenv.config();

import { Dice } from "@/commands/slash/dice";

// 登録コマンドを呼び出してリスト形式で登録
const dice = new Dice();
const commands = [dice.slashCommand.toJSON()];

// DiscordのAPIには現在最新のversion10を指定
const rest = new REST({ version: "10" }).setToken(
  process.env.DISCORDAPPBOTTOKEN ?? ""
);

// Discordサーバーにコマンドを登録
(async () => {
  try {
    await rest.put(
      Routes.applicationGuildCommands(
        process.env.DISCORDAPPLICATIONID ?? "",
        process.env.DISCORDGUILDID ?? ""
      ),
      { body: commands }
    );
    console.log("サーバー固有のコマンドが登録されました!");
  } catch (error) {
    console.error("コマンドの登録中にエラーが発生しました:", error);
  }
})();

Discord の Bot は常駐させる必要があるため、今回はローカル環境で試しています。
サーバーに置きたい場合は SlackBot と同様に一番小さい EC2 や GCP のインスタンスを立ててそこで動かすのが簡単かなと。

まとめ

たったこれだけでダイスをふる Bot ができるというだけで結構お手軽でした。
この状態から更に Bot の用途に合わせて色々機能を追加していけば、割となんでもできそうな気がします。
実際この Bot をどんどん拡張して他にも機能を作っています。

今回はスラッシュコマンドを使ってみましたが、モーダルを出して入力した内容を基に何かしらの処理をしたり、ボタンクリックに反応させたりできるので、
あまりコマンド等よく分からなくても、操作しやすいものは作れそうだなと思いました。

TypeScript の感想ですが、ちゃんと TypeScript で書いたのは初めてだったので、型周りで最初で苦戦しました。
ですが、慣れてきたら Promise 周りが逆にわかりやすくて普通に JavaScript で書くよりも楽に書けた気がします。 (多分普段遣いのエディタが VSCode だから説)

そして、今回は GitHub Copilot をフル活用し、生成されたコードでよくわからないなと思った部分は ChatGPT に聞きまくりました。
普段 Golang ばっかり書いているので、慣れてない言語で使ってみると、結構勉強になります。

普段の業務でも GitHub Copilot と ChatGPT は活用していますが、今回は特に使いまくったので、AI とちょっとは仲良くなれたんじゃないかなと勝手に思ってます。

ということで簡単にはなりますが、私の冬休みの自由工作でした。