2022-03-16

Discord Bot チュートリアル

Discord Bot チュートリアル

こんにちは。B3 の Arc です。2022 年 2 月 18 日に部内で Discord Bot の開発チュートリアルを行いました。当時の資料を元に加筆・修正を加えた物を公開してみます。

近頃の Discord Bot 界隈

最初に、近頃の Discord Bot 開発者界隈を取り巻く状況について軽く触れておきます。

むかし

昔は Bot 開発用の 2 大ライブラリとして、Python 用のdiscord.pyと Node.js 用のdiscord.jsがありました。

2021 年 8 月 28 日

この日に discord.py の開発終了がアナウンスされました。Discord 公式とのトラブルがあったようです(詳しくはググってね)。後述しますが、現在は開発が再開されたようです。

これにより、Discord 公式の REST API の仕様変更などに対応できなくなった discord.py 製 bot は死ぬ運命となりました(結果的にそうならなかったけど)。また、discord.py の後継を名乗るプロジェクトが林立する事態となりました。

Python 界隈がこの有様(だった)ので、このチュートリアルでは discord.js を使っていきます。

2022 年 3 月 6 日

……と思っていたら 3 月 6 日に discord.py の開発再開アナウンスされました何が何だか分からん 再び 2 大ライブラリ時代に戻るんじゃないかなと思っています。たぶん……

環境構築・初期設定

この章では、Discord Bot を作るための環境構築・初期設定を行います。

用語集

初めに、基本的な用語について説明しておきます。

Node.js のインストール

ダウンロードページから、Node.js の LTS 版をダウンロード・インストールしてください。

ターミナルでnode --versionを実行した際、v16.14.0のように表示されていればインストール成功です。

プロジェクトディレクトリの作成

適当な名前のディレクトリを作ってください。

次に、ターミナルでディレクトリに移動し、npm initを実行してください。色々聞かれますが、とりあえず全て Enter を押せばいいです。

package.jsonが作成されていることを確認してください。

パッケージのインストール

開発に必要なパッケージをインストールします。次のコマンドを実行してください。

npm install --save discord.js dotenv typescript @types/node@16 ts-node tsconfig-paths

TypeScript の設定

npx tsc --initを実行してください。tsconfig.jsonが生成されれば成功です。

tsconfig.jsonには、TypeScript のコンパイラ(TSC)の設定情報が含まれています。

tsconfig.jsonには設定項目が大量にありますが、中身については省略します。baseUrlの項目だけ次のように変えてください。

{
  // ...
  "baseUrl": "./src"
  // ...
}

スクリプトの設定

package.json"scripts"の箇所を次のように編集してください。

{
  // ...
  "scripts": {
    "start": "ts-node --files -r tsconfig-paths/register src/index.ts"
  }
  // ...
}

これにより、npm startnpx ts-node --files -r tsconfig-paths/register src/index.tsを実行できるようになりました。

ts-nodeは、.tsファイルを事前コンパイル無しで直接実行するためのパッケージです(正確には JIT コンパイルしているようです)。TypeScript のコードを走らせるには「tsc でコンパイル」→「Node で js を実行」 の 2 ステップを要していましたが、ts-nodeを使うことで 1 ステップに抑えることができます。

いろいろ引数がついていますが、ここでは説明を省略します。気になる方はtsconfig-pathsなどで検索してください。

Hello World

srcディレクトリを作成し、その中にindex.tsを作成してください。

そして、次のコードをコピペしてください。

console.log("Hello World!");

最後に、プロジェクトルートに居る状態でnpm startを実行してください。Hello World!と表示されれば成功です!

Bot の登録・ログイン

この章では、Discord Bot をサーバーに追加した後、Bot をサーバーにログインさせます。

概ねDiscord.js Guide v12 版に沿っていますが、現行の v13 に合わせて記述を変更している箇所もあります。

Bot をサーバーに追加する

Bot をサーバーに追加していきます。最初にテスト用のサーバーを立てておいてください。

Bot の登録

まずは Bot を Discord に登録します。

  1. Discord にログインした状態で、Discord Developer Portal のApplicationページに移動してください。
  2. 右上のNew Applicationをクリックして、いい感じの名前をつけてCreateしてください。

creat app

  1. 左のメニューからBotをクリックして、Add Bot -> Yes, do it!をクリックしてください。A wild bot has appeared!みたいなメッセージが表示されれば OK です。

add bot

Bot をサーバーに招待

次に、Bot をサーバーに招待します。

  1. 左メニューからOAuth2 -> URL Generatorを開いてください。
  2. SCOPESの中のbot, applications.commandsにチェックを入れてください。
  3. BOT PERMISSIONSの中のSend Messagesにチェックを入れてください。これにより、Bot にメッセージ送信権限が付与されます。他の権限が必要な場合は適宜チェックを増やしてください。

permission

  1. 下の方にあるGenerated URLをコピーして、Web ブラウザに貼り付けてください。事前に作成したサーバーを選択して、「はい」などのボタンを押してください。
  2. Discord クライアント上で、サーバーに Bot が追加されたことを確認してください。

Bot をサーバーにログインさせる

環境変数と dotenv

これから Bot をサーバーにログインさせますが、その前に環境変数について説明しておきます。

Bot をログインさせる際には「トークン」という値が必要になります。この値が外部に流出した場合、他の人が Bot をサーバーにログインさせて、スパムメッセージを送信するなどの悪事を働くことができてしまいます。このため、トークンはソースコードに埋め込まずに管理する必要があります(トークンを埋め込んだソースコードを Git に Push したら外部流出したことになります)。

このような機密情報は、実行環境の環境変数に設定して、ソースコードから環境変数を読み込む必要があります。しかし、環境変数を設定するのは割とだるいです。

Node では、実行時に.envというファイルから環境変数を読み込んで設定するdotenvというモジュールがあります。実はこのモジュールは前章でインストールしていました。.envファイルを.gitignoreに設定することで、機密情報を安全に、便利に扱うことができます。

トークンを.envファイルに保存しておきましょう。.envをルートディレクトリに作成して、次の内容を書き込んでください。

TOKEN=<Botのトークン>

<Botのトークン>の箇所を実際の値で置き換えてください。Bot のトークンは次のようにして得ることができます。

  1. Developer Portal から前節で作成したアプリケーションを選択し、左メニューのBotを選択します。
  2. TOKENという所にCopyボタンがあるのでクリックしてください。トークンがクリップボードにコピーされます。

ログイン処理の実装

Bot をサーバーにログインさせる処理を書いていきます。

// 1: インポート
import * as dotenv from "dotenv";
import { Client, ClientOptions } from "discord.js";
 
// 2: .envを読み込み、環境変数に登録
dotenv.config();
 
// 3: クライアントを初期化
const clientOptions: ClientOptions = {
  intents: ["GUILD_MESSAGES", "GUILDS"],
};
const client = new Client(clientOptions);
 
// 4: ログイン完了時に実行するコールバック関数を登録
client.once("ready", () => {
  console.log("I'm  ready!");
});
 
// 5: ログイン
client.login(process.env.TOKEN);

コードの中身を説明していきます。

インポート

使用するクラスなどをインポートします。

.env を読み込み、環境変数に登録

.envを読み込んで、環境変数に登録します。環境変数へはprocess.env.<変数名>でアクセスすることができます。

クライアントを初期化

ログイン完了時に実行するコールバック関数を登録

ログイン

実行してみる

ここまで書けたら実際に実行してみましょう。

npm startを実行して数秒待ってください。次のことが確認できれば成功です!

確認できたら、Ctrl+C などで Bot を止めておいてください。

スラッシュコマンド

この章では、スラッシュコマンドの実装を行います。

この記事を参考にしています: discord.js でスラッシュコマンド(Slash commands)を使う - Qiita

2 種類のスラッシュコマンド

スラッシュコマンドには「ギルドコマンド」と「グローバルコマンド」の 2 種類があります。

グローバルコマンドは、登録後実際に使えるようになるまで 1 時間ほどかかるようです。このため、本チュートリアルではギルドコマンドを使用します。

環境変数の設定

ギルドコマンドを登録するためには、対象サーバーの ID が必要です。.envに保存しておきます。

サーバー ID は次の方法で取得することができます。

  1. Discord アプリの設定画面を開き、「詳細設定」の「開発者モード」をオンにしておきます。

dev mode

  1. サーバーアイコン上で右クリックして、「ID をコピー」を選択します。サーバー ID がクリップボードにコピーされます。

copy server id

.envに次の項目を追記してください。<サーバーID>は実際の値に置き換えてください。

SERVER_ID=<サーバーID>

シンプルなコマンド

まず、pongというメッセージを送信するだけのシンプルなコマンド/pingを作成してみます。

環境変数の型定義を記述する

環境変数の型定義ファイルを作っておかないと後々面倒なので、ここで作ってしまいます。

src@typesディレクトリを作成して、中にglobal.d.tsを作り、以下の内容を入力してください。

declare namespace NodeJS {
  interface ProcessEnv {
    readonly TOKEN: string;
    readonly SERVER_ID: string;
  }
}

コマンドを実装する

実際にコマンドを作っていきます。次のコードを参考にして、readyイベントのコールバックを編集し、interactionCreateイベントのコールバックを追加してください。asyncキーワードが新しく加わっていることに注意してください。

import { ApplicationCommandData, Client, ClientOptions } from "discord.js"; // インポート部分が変わっています
 
// ...
 
// ログイン完了時に実行するコールバック関数を登録
client.once("ready", async () => {
  const commands: ApplicationCommandData[] = [
    {
      name: "ping",
      description: "pongと返します。",
    },
  ];
  await client.application?.commands.set(commands, process.env.SERVER_ID);
 
  console.log("I'm  ready!");
});
 
// コマンド受信時のコールバック関数を登録
client.on("interactionCreate", async (interaction) => {
  if (!interaction.isCommand()) {
    return;
  }
  if (interaction.commandName === "ping") {
    // pingコマンドが来たら:
    await interaction.reply("pong"); // pongとreplyする
  }
});

書けたらnpm startして、アプリ上でコマンドを使えるかどうか確認してみましょう。

引数付きコマンド

引数を受け取るコマンドを作っていきます。ここでは、引数をそのまま出力するechoコマンドを作っていきます。

コマンドの登録

次のコードを参考にして、commandsに要素を追加してください。引数はoptionsプロパティで設定します。

const commands: ApplicationCommandData[] = [
  {
    name: "ping",
    description: "pongと返します。",
  },
  {
    name: "echo",
    description: "入力された文字をそのまま返します。",
    options: [
      {
        type: "STRING",
        name: "value",
        description: "文字列",
        required: true,
      },
    ],
  },
];

コマンドの中身を実装

次のコードを参考にして、コマンドの中身を実装してください。引数はinteraction.options.getString()などで受け取ることができます。

if (interaction.commandName === "ping") {
  await interaction.reply("pong");
}
if (interaction.commandName === "echo") {
  const value = interaction.options.getString("value", true); // 引数valueを受け取る
  await interaction.reply(value);
}

書けたらnpm startして、アプリ上でコマンドを使えるかどうか確認してみましょう。

遅延応答

「コマンドを受信したら、データベースにアクセスして処理をした後に返信したい」といったケースなど、時間のかかる処理を実装したくなることがあると思います。このような処理をそのまま書いたらどうなるでしょうか?

pingコマンドを次のように編集してみます。ここでは 4000 ミリ秒後に"pong"と返信するコードを書いています。

if (interaction.commandName === "ping") {
  setTimeout(async () => {
    await interaction.reply("pong");
  }, 4000); // 4000ミリ秒後に"pong"と返信
}

pingコマンドを実際に実行してみましょう。すると、アプリケーションが応答しませんでしたなどのメッセージが表示されると思います。

実は、スラッシュコマンドは3 秒以内に返信しないとエラーになります。

これを回避するため、deferReply(), followUp()という関数を使います。

if (interaction.commandName === "ping") {
  await interaction.deferReply(); // 追加
  setTimeout(async () => {
    await interaction.followUp("pong"); // 変更
  }, 4000); // 4000ミリ秒後に"pong"と返信
}

このコードにした上でpingコマンドを実行すると、<bot名>が考え中…というメッセージが表示された 4 秒後にpongと表示されるはずです。意図通りの挙動になりました。

ということで、時間のかかる処理を行う時には、返信を遅延させるためにdeferReply()followUp()を使う必要があります。

詳しくはこのページが参考になります: reply と deferReply の違い - Discord.js Japan User Group

分からないことがあったら

分からないことがある時は次のサイトが頼りになります。

おわり

スラッシュコマンドに対応した Discord Bot を作りました。後はいろいろ工夫して頑張ってください(雑)。

← 記事一覧に戻る