discord.js 基礎
Client を作って Gateway に接続、Intents で受け取りたいイベントを宣言、 Events でハンドラを書く。これが基本ループ。
Client
import { Client, GatewayIntentBits, Partials, Events } from "discord.js"
const client = new Client({
intents: [
GatewayIntentBits.Guilds, // サーバー基本情報
GatewayIntentBits.GuildMessages, // メッセージイベント
GatewayIntentBits.MessageContent, // 本文(Privileged)
GatewayIntentBits.GuildMembers, // メンバー情報(Privileged)
GatewayIntentBits.GuildVoiceStates, // 音声状態
GatewayIntentBits.GuildPresences, // オンライン状態(Privileged)
GatewayIntentBits.MessageReactions, // リアクション
GatewayIntentBits.DirectMessages, // DM
],
partials: [
Partials.Message,
Partials.Channel,
Partials.Reaction,
],
})
client.login(process.env.DISCORD_TOKEN)
Intents の選び方
必要な分だけ宣言。多すぎると無駄な帯域、少なすぎるとイベントが来ない。
Guilds— 必須レベル(サーバー基本イベント)GuildMessages— メッセージ送受信を見たい時MessageContent— 本文を読む時。Privileged。Portal でも有効化GuildMembers— メンバー一覧。PrivilegedGuildPresences— オンライン状態。PrivilegedGuildVoiceStates— VC 接続状態MessageReactions— リアクションDirectMessages— DM
Partials
Discord はキャッシュにない古いメッセージなどを「partial」として返す。
partial を扱うなら明示的に Partials を宣言する。古いメッセージのリアクション取得などで必要。
主なイベント
| イベント | 発火 |
|---|---|
ClientReady | 接続完了 |
Error | エラー |
Warn | 警告 |
Debug | デバッグログ |
InteractionCreate | スラッシュコマンド・ボタン等 |
MessageCreate | メッセージ投稿 |
MessageUpdate | 編集 |
MessageDelete | 削除 |
MessageReactionAdd | リアクション追加 |
GuildMemberAdd | メンバー参加 |
GuildMemberRemove | 退出 |
VoiceStateUpdate | VC 状態変化 |
ThreadCreate | スレッド作成 |
イベントハンドラ
client.once(Events.ClientReady, (c) => {
console.log(`Logged in as ${c.user.tag}`)
})
client.on(Events.MessageCreate, async (msg) => {
if (msg.author.bot) return // bot 同士のループ防止
if (!msg.content.startsWith("!ping")) return
await msg.reply("pong")
})
client.on(Events.Error, (err) => console.error(err))
Channel / Guild / User / Member
Discord の登場人物:
- User — Discord アカウント。グローバルな一意 ID
- GuildMember — サーバー内のユーザー。ニックネーム・ロール等を持つ
- Guild — サーバー
- Channel — テキスト/音声/カテゴリ/フォーラム/スレッド
- Role — 色・権限を持つグループ
取得
const guild = client.guilds.cache.get(GUILD_ID)
const channel = guild?.channels.cache.get(CHANNEL_ID)
const member = await guild?.members.fetch(USER_ID)
const role = guild?.roles.cache.find((r) => r.name === "Admin")
// fetch(API リクエスト・最新値)
const user = await client.users.fetch(USER_ID)
// キャッシュにあれば優先
const cached = guild?.members.cache.get(USER_ID)
cache はメモリ上、fetch は API リクエストで最新。大規模 bot ではキャッシュ多用、特殊な状況でだけ fetch。
メッセージを送る
// チャンネルから
const channel = await client.channels.fetch(CHANNEL_ID)
if (channel?.isTextBased()) {
await channel.send("こんにちは")
}
// メッセージへの返信
await msg.reply("内容")
await msg.reply({ content: "内容", ephemeral: false })
// メンションせずに返信
await msg.reply({ content: "...", allowedMentions: { repliedUser: false } })
ロールの操作
const member = await guild.members.fetch(userId)
const role = guild.roles.cache.find((r) => r.name === "Verified")
await member.roles.add(role)
await member.roles.remove(role)
if (member.roles.cache.has(role.id)) {
// 持っている
}
権限チェック
import { PermissionFlagsBits } from "discord.js"
if (member.permissions.has(PermissionFlagsBits.ManageMessages)) {
// 管理者向けの処理
}
// チャンネル単位
const perms = channel.permissionsFor(member)
if (perms?.has(PermissionFlagsBits.SendMessages)) { ... }
キャッシュとレート制限
Discord API にはレート制限がある(route ごとに違う)。 discord.js は内部でキューイング・自動再試行するので、普通の使い方では意識しなくて良い。
- キャッシュはメモリ次第で消える。fetch で取り直せる
- 大量メッセージを一度に送る場合は意図的に sleep を入れる
- レート制限に当たると
RateLimitedErrorが発生
Sharding(大規模 bot)
Discord は2,500 サーバー超になると 1 接続では扱えない。Shardに分割する必要がある。
import { ShardingManager } from "discord.js"
const manager = new ShardingManager("./bot.js", {
totalShards: "auto",
token: process.env.DISCORD_TOKEN,
})
manager.on("shardCreate", (shard) => console.log(`Launched shard ${shard.id}`))
manager.spawn()
TypeScript セットアップ
npm install -D typescript @types/node tsx
npx tsc --init
tsconfig.json の最低限:
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "Bundler",
"esModuleInterop": true,
"strict": true,
"outDir": "./dist"
},
"include": ["src"]
}
ファイル構成(実用パターン)
my-bot/
├── src/
│ ├── index.ts ← Client 起動
│ ├── events/
│ │ ├── ready.ts
│ │ ├── interactionCreate.ts
│ │ └── messageCreate.ts
│ ├── commands/
│ │ ├── ping.ts
│ │ └── info.ts
│ └── lib/
│ └── deploy-commands.ts
├── .env
├── package.json
└── tsconfig.json
コマンドをファイル単位で動的読み込みするパターンが定石(次ページで詳述)。
注意点
msg.author.botでループ防止(自分のメッセージに反応しない)- 非同期処理は必ず
await。send後に何かするなら必須 - 大きなコレクション(メッセージ履歴)はiterate で(ページング)
- 例外はイベントハンドラ内で catch(throw すると process が落ちる)