🔰

Discord botコマンド登録の個人的チートシート

に公開

友人と2人で創作をやっているDiscordサーバに入れる用のbotを開発しました。
Vide Codingで終われるはずがbotのコマンド登録でつまづき、知見が得られたので自分用にまとめておきます。
ちなみに作ったbotはこちら:
https://github.com/t-esserac-t/meetingSetBot

前提

対象

  • Node.jsでDiscord botを開発する人
    • JavaScript初心者だけどやっていく人

開発環境

プラットフォーム: Cloudflare Workers + Durable Objects
開発言語: TypeScript
Wrangler:  v4.34.0
npm: v10.9.0
Node.js: v22.12.0

botのコマンド登録って何

  • Discordのbotが受け付けるslash commandsを宣言すること

    これ

コマンド登録の流れ

  • 大まかには、「登録先情報の参照→URL作成→Discord API呼び出し→コマンド定義の送信」となります。
    • 以下のようにpackage.jsonにエイリアスを指定し、mjsファイルが実行されるようにすると便利
package.json
...
	"scripts": {
		"deploy": "wrangler deploy",
...
		"register:commands": "node ./scripts/register-commands.mjs"
	},
...

以下では、コマンド登録をスクリプトにまとめておく前提で、入れておくと便利な機能や書き方をまとめます。

スクリプトに入れておくと便利なもの一覧

ログ出力

基本のログ出力

console.log('ここまでOK');

変数のログ出力

  • 例1: 変数の中身を出力する
console.log('app id:', APP_ID, 'env global:', PURGE_GLOBAL);
  • 例2: 配列の中身を出力する
    const arr = [1, 2, 3, 4];という配列があったとき、
console.log(arr);
  • 例3: エラーが出たときエラー内容を出力する
try {
...
} catch (e) {
  console.error('Fetch error', e); 
}

重要情報をハードコード以外で追加する

環境変数を別ファイルから読み出す

  • dotenvを使ってファイルに環境変数を書き、それを読み出すことが可能
    • npm install dotenvをターミナルで実行し、.envファイル、コマンド登録スクリプトをそれぞれ以下のように編集する
.env
DUMMY_ID=thisIsFrom.envFile
register-commands.mjs
import dotenv from 'dotenv';
dotenv.config();
console.log('DUMMY_ID:',process.env.DUMMY_ID);
// 出力例: DUMMY_ID: thisIsFrom.envFile

既存の環境変数を変数に代入する

  • PowerShellのターミナル画面などで指定した環境変数を読み出して使用することも可能
$env:BOT_TOKEN="xxxxx"
...
const BOT_TOKEN = process.env.BOT_TOKEN ?? '';
  • ターミナルと.envファイルで同名の環境変数が指定されているときは、デフォルトではターミナル優先となる

  • dotenv.config({ override: true });と指定しておくと.envファイルの内容が優先される

リトライロジックを入れる

  • 40333 internal nerwork errorや500系エラー対策
  • コマンド削除の直後に登録を行うと、レートリミットに引っかかりやすい
    • 何msか待機を入れておくことで制限に引っかかりにくくした
    • 429(Too Many Requests)ではレスポンスヘッダにretry_after(再試行まで何ms待てばよいか)が書かれているので、それに従うと無駄がない
  • レートリミット時の応答サンプル
< HTTP/1.1 429 TOO MANY REQUESTS
< Content-Type: application/json
< Retry-After: 65
< X-RateLimit-Limit: 10
< X-RateLimit-Remaining: 0
< X-RateLimit-Reset: 1470173023.123
< X-RateLimit-Reset-After: 64.57
< X-RateLimit-Bucket: abcd1234
< X-RateLimit-Scope: user
{
  "message": "You are being rate limited.",
  "retry_after": 64.57,
  "global": false
}
  • リトライ処理入りリクエスト送信関数のサンプル
    • 429エラー(レートリミット)と500系などのサーバエラー時にリトライする処理を入れています
async function reqWithRetry(method, url, body, tries = 3) {
  for (let i = 0; i < tries; i++) {
    const res = await apiFetch(method, url, body);
    if (res.ok || i === tries - 1) return res;
    const text = await res.text().catch(() => '');
    const retryAfterMsDef = 1000;
    if (res.status === 429) {
      let retryAfterMs = retryAfterMsDef;
      try {
        const data = JSON.parse(text);
        if (typeof data.retry_after === 'number') retryAfterMs = Math.ceil(data.retry_after * 1000);
      } catch {}

      await sleep(retryAfterMs + 100); //一応100ms延ばしておく
      continue;
    }
    // 何秒待てばいいかわからないエラーの場合は固定値をもとにバックオフ待機して再送
    if (res.status >= 500 || text.includes('40333')) {
      await sleep(retryAfterMsDef * (i + 1) );
      continue;
    }
    const out = new Response(text, { status: res.status });
    out.debugText = text;
    return out;
  }
}

参考

公式ガイド・Q&A

https://support-dev.discord.com/hc/ja/articles/6223003921559-私のBotがレート制限されています

https://discord.com/developers/docs/topics/rate-limits
https://developers.cloudflare.com/durable-objects/reference/durable-objects-migrations/

Discord bot関連

https://zenn.dev/semapho/articles/063582c32eff32

https://qiita.com/tartar_sauce/items/91d8b7b04f3eaede553d

https://zenn.dev/drumath2237/articles/112fd0bfa7ea4f836195

https://zenn.dev/suzuesa/articles/2bf80f33013a14

https://zenn.dev/king/articles/4201f4ee821a27

https://zenn.dev/discorders/articles/discord-webhook-guide

Cloudflare関連

https://zenn.dev/moutend/articles/97c98a277f4bae

https://qiita.com/khayama/items/0787728497918f2e68d6

https://zenn.dev/ak/articles/a2bd28a258b615

https://zenn.dev/kameoncloud/articles/1fac9762aab4ec

https://zenn.dev/msy/articles/4c48d9d9e06147

Discussion