Contents
1️⃣ アプリ作成と Bot トークン取得(2026 年版)
手順概要
| 手順 | 操作内容 |
|---|---|
| ① | https://discord.com/developers に管理者アカウントでログイン |
| ② | 左サイドバー Applications → New Application をクリック |
| ③ | 名前を入力し Create |
| ④ | アプリ詳細画面左メニューの Bot タブへ移動 |
| ⑤ | Add Bot → デフォルトで PUBLIC BOT が有効になる |
| ⑥ | TOKEN セクションで Copy(※一度だけ表示) |
トークン取得時の注意点
- 「一度だけ」表示されることを忘れない
- 表示された瞬間に
.envファイルやシークレットマネージャーへ保存してください。画面上で再表示はできません。 - 漏洩防止策
- 公開リポジトリに絶対にコミットしない(
.gitignoreに必ず.envを追加)。 - 何らかの形でトークンが流出したら、Developer Portal の Bot ページから Regenerate ボタンで即座に再生成できます。
- 再生成後は全てのデプロイ環境(Docker・CI/CD シークレット)を同時に更新してください。
- 安全な保管先例
- ローカル:
.env→dotenvでロード - CI/CD:GitHub Actions の Secrets, Railway/Fly.io の Environment Variables
- クラウド:AWS Secrets Manager、GCP Secret Manager、Azure Key Vault 等
Privileged Gateway Intents の有効化
| Intent | 説明 |
|---|---|
| PRESENCE INTENT | client.user.setPresence が機能するために必須 |
| SERVER MEMBERS INTENT | メンバー情報取得が必要な場合にだけチェック |
⚠️ 公式ドキュメント確認:Intents の有効化は Developer Portal 上で行い、Bot 起動時の
GatewayIntentBitsと合わせて必ず設定してください。
2️⃣ 開発環境構築(Node.js v20・discord.js v14)
必要ソフトウェア
| ソフト | 推奨バージョン |
|---|---|
| Node.js | v20 LTS(2026 年時点) |
| npm | 10.x 系 |
| discord.js | v14(最新の API スキーマに対応) |
プロジェクト初期化
|
1 2 3 4 5 6 7 8 9 |
# 1. 作業ディレクトリ作成 mkdir discord-status-bot && cd discord-status-bot # 2. npm 初期化 npm init -y # 3. 必要パッケージをインストール(production 用) npm install discord.js@14 dotenv node-cron axios sqlite3 @google-cloud/firestore winston |
開発時にだけ必要な依存関係
| パッケージ | 用途 |
|---|---|
| eslint | コード品質チェック |
| prettier | フォーマット自動化 |
| jest | テスト実装 |
Docker で開発する場合は、マルチステージビルドを利用し
npm ci(devDependencies 含む)を最初のステージで走らせ、最終イメージでは--productionフラグに切り替えるとサイズが小さくなります。
VS Code 推奨設定
|
1 2 3 4 5 6 7 8 |
{ "editor.tabSize": 2, "files.autoSave": "onFocusChange", "eslint.enable": true, "prettier.singleQuote": true, "javascript.format.insertSpaceAfterFunctionKeywordForAnonymousFunctions": false } |
.env の例
|
1 2 |
DISCORD_TOKEN=YOUR_BOT_TOKEN_HERE |
3️⃣ ステータス設定と最新 Activity 仕様
2026 年時点の公式 API:https://discord.com/developers/docs/topics/gateway#activity-object
setPresence の基本構文(変更なし)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
const { Client, GatewayIntentBits, ActivityType } = require('discord.js'); require('dotenv').config(); const client = new Client({ intents: [ GatewayIntentBits.Guilds, GatewayIntentBits.GuildPresences, // 必須 ], }); client.once('ready', () => { console.log(`Logged in as ${client.user.tag}`); client.user.setPresence({ status: 'online', activities: [{ name: '起動中のボット', type: ActivityType.Playing }], }); }); client.login(process.env.DISCORD_TOKEN); |
Activity Type と必須フィールド(2026 年版)
| タイプ | ActivityType |
必要/推奨フィールド |
|---|---|---|
| Playing | ActivityType.Playing |
{ name: 'ゲーム名' } |
| Watching | ActivityType.Watching |
{ name: 'イベント' } |
| Listening | ActivityType.Listening |
{ name: '音楽名' } |
| Competing | ActivityType.Competing |
{ name: '大会名' } |
| Streaming | ActivityType.Streaming |
{ name: '', url: 'https://twitch.tv/...' } |
| Custom | ActivityType.Custom |
必須 type, name(空文字列可), state, emoji(任意) |
Custom アクティビティの正確な JSON 例
|
1 2 3 4 5 6 7 8 9 10 11 |
client.user.setPresence({ activities: [ { type: ActivityType.Custom, name: '', // カスタムステータスは空文字が必須 state: '🚀 デプロイ中', // 表示したいテキスト emoji: { name: '🛰️' }, // 任意のカスタム/Unicode 絵文字 }, ], }); |
注意:
custom_idフィールドは Interaction オブジェクト(ボタンや選択メニュー)で使用され、Activity ペイロードには含まれません。過去の情報に混同しないようご留意ください。
4️⃣ 動的ステータス更新ロジックとレートリミット回避策
① レートリミットの実際の上限(公式確認)
- Gateway 経由での Presence 更新は「5 回 / 20 秒」が上限です。
- 1 分間に最大 15 回という解釈は誤りです。(
PATCH /users/@me/settingsのレートリミットと混同しやすいため、実装時は公式ドキュメントの Presence Update Rate Limits を必ず参照してください)
② 安全な更新関数(Retry‑After ヘッダー+指数バックオフ併用)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
const { createLogger, transports, format } = require('winston'); const logger = createLogger({ level: 'info', format: format.combine(format.timestamp(), format.json()), transports: [new transports.Console(), new transports.File({ filename: 'bot.log' })], }); /** * Presence 更新を安全に実行 * @param {Object} options discord.js の setPresence 引数 * @param {number} attempt 再試行回数(内部使用) */ async function safeSetPresence(options, attempt = 1) { try { await client.user.setPresence(options); logger.info('Presence updated', options); } catch (err) { // 429 → Rate limited if (err.response?.status === 429 && attempt <= 5) { const retryAfterHeader = err.response.headers['retry-after']; const retryAfterMs = retryAfterHeader ? Number(retryAfterHeader) * 1000 : 0; const exponentialBackoff = Math.pow(2, attempt) * 1000; // 1s,2s,4s,... const delay = Math.max(retryAfterMs, exponentialBackoff); logger.warn(`Rate limited (attempt ${attempt}). Retrying in ${delay} ms`); setTimeout(() => safeSetPresence(options, attempt + 1), delay); } else { logger.error('Failed to update presence', { message: err.message }); } } } |
③ 定時実行(cron)例
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
const cron = require('node-cron'); const axios = require('axios'); cron.schedule('0 * * * *', async () => { // 毎正時に天気情報を取得して表示 try { const { data } = await axios.get( 'https://api.openweathermap.org/data/2.5/weather', { params: { q: 'Tokyo', appid: process.env.OPENWEATHER_KEY, lang: 'ja' } } ); const description = data.weather[0].description; await safeSetPresence({ status: 'online', activities: [{ name: `天気:${description}`, type: ActivityType.Watching }], }); } catch (e) { logger.error('Cron job failed', { error: e.message }); } }); |
④ 「非アクティブ → idle」自動遷移を上書きするロジック
client.presence は undefined(Bot ユーザーには presence プロパティが無い)ため、現在のステータスは client.user.presence?.status で取得します。
|
1 2 3 4 5 6 7 8 9 |
// 5 分ごとにステータスを強制的に online に保つ例 cron.schedule('*/5 * * * *', async () => { const currentStatus = client.user.presence?.status ?? 'offline'; if (currentStatus === 'idle') { await safeSetPresence({ status: 'online' }); logger.info('Idle → Online forced'); } }); |
⑤ レートリミット回避のベストプラクティスまとめ
| 手法 | 説明 |
|---|---|
| 更新頻度制御 | 5 回/20 秒 を超えないように cron の間隔を設計 |
| Retry‑After ヘッダー利用 | 429 応答時はヘッダーの秒数で待機 |
| 指数バックオフ | retry-after が無い場合でもバックオフで再試行 |
| エラーログ化 | winston に JSON 形式で残す → Loki/Grafana で可視化 |
5️⃣ ノーコードツール Yoom とハイブリッド連携
5‑1 外部リンクの耐久性確保
- 元リンク:
https://lp.yoom.fun/blog-posts/how-to-automatically-update-data-in-discord-bot-25w16 - 代替策
- 同内容を公式ドキュメント(GitHub リポジトリの
docs/yoom-integration.md)にミラー。 - 重要情報は Wayback Machine(例: https://web.archive.org/web/20260417000000/https://lp.yoom.fun/...)でバックアップを取得し、記事末尾に Archive URL を記載。
5‑2 Yoom の基本フロー
| ステップ | 内容 |
|---|---|
| テンプレートインポート | Yoom の「Discord ステータス自動更新」テンプレートをコピーし、Webhook URL に Bot 側エンドポイント(例:https://my-bot.example.com/webhook)を設定 |
| データ取得(Salesforce / Google Sheets) | 各サービスの OAuth2 認証 → 必要フィールドだけ抽出 → JSON へ変換 |
| Transform(JSON 化) | {{field}} を埋め込んだオブジェクト例:{ "type": "Playing", "name": "{{StageName}}" } |
| Webhook 送信 | Yoom が生成した JSON を POST → Bot が受信し safeSetPresence に渡す |
5‑3 Bot 側の Webhook エンドポイント例
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
const express = require('express'); const app = express(); app.use(express.json()); app.post('/webhook', async (req, res) => { const { type, name, state, emoji } = req.body; // Yoom が送る形式に合わせて取得 const activity = { type: ActivityType[type] ?? ActivityType.Custom, name: name ?? '', state, emoji, }; await safeSetPresence({ activities: [activity], status: 'online' }); res.status(200).send('OK'); }); app.listen(process.env.PORT || 3000, () => { console.log('Webhook server listening...'); }); |
5‑4 エラーハンドリング(Yoom 側)
- Retry:最大 3 回、バックオフ間隔 5 秒
- 通知先:失敗時は Slack または別 Discord チャンネルへ Webhook 通知
ポイント:ノーコード側でのリトライ設定と Bot の
safeSetPresenceによる再試行が二重になることを防ぐため、Yoom 側では 最大 1 回 のリトライに留め、残りは Bot がハンドリングします。
6️⃣ ローカルテスト → 本番デプロイ・運用ベストプラクティス
6‑1 Dockerfile(マルチステージビルド推奨)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
# ---------- Build Stage ---------- FROM node:20-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci # devDependencies もインストール COPY . . RUN npm run lint && npm test # 必要なら Lint/テスト実行 # ---------- Production Stage ---------- FROM node:20-alpine WORKDIR /app COPY --from=builder /app/package*.json ./ RUN npm ci --production # 本番は devDependencies を除外 COPY --from=builder /app/dist ./ # ビルド成果物がある場合 # 環境変数は実行時に渡す想定 CMD ["node", "index.js"] |
- 開発コンテナは
docker compose upでnpm run dev(watch モード)を走らせ、--productionを外したステージを利用すると便利です。
6‑2 docker-compose.yml(ローカル開発向け)
|
1 2 3 4 5 6 7 8 9 10 11 |
version: '3.9' services: bot: build: . env_file: - .env volumes: - .:/app # ソースコードのリアルタイム反映 command: ["npm", "run", "dev"] restart: unless-stopped |
6‑3 CI/CD(GitHub Actions の例)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
name: CI & Deploy on: push: branches: [main] jobs: test-and-build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up Node uses: actions/setup-node@v3 with: node-version: '20' - run: npm ci - run: npm run lint - run: npm test docker-build-push: needs: test-and-build runs-on: ubuntu-latest permissions: contents: read packages: write steps: - uses: actions/checkout@v3 - name: Log in to Docker Hub uses: docker/login-action@v2 with: username: ${{ secrets.DOCKER_USER }} password: ${{ secrets.DOCKER_PASS }} - name: Build & push image run: | IMAGE=ghcr.io/${{ github.repository }}:${{ github.sha }} docker build -t $IMAGE . docker push $IMAGE deploy: needs: docker-build-push runs-on: ubuntu-latest steps: - name: Deploy to Railway (example) run: | curl -X POST https://api.railway.app/v2/projects/${{ secrets.RAILWAY_PROJECT }}/services/bot/deploy \ -H "Authorization: Bearer ${{ secrets.RAILWAY_TOKEN }}" \ -H "Content-Type: application/json" \ -d '{"image":"ghcr.io/${{ github.repository }}:${{ github.sha }}"}' |
6‑4 監視・メトリクス
| ツール | 用途 |
|---|---|
winston + Loki/Grafana |
ログ集約・検索 |
prom-client + /metrics エンドポイント |
Prometheus が取得できる数値(アップタイム、エラー回数) |
Docker の restart: unless-stopped |
コンテナクラッシュ時の自動再起動 |
簡易 /metrics 実装例
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
const client = require('prom-client'); const express = require('express'); const app = express(); const presenceUpdates = new client.Counter({ name: 'presence_updates_total', help: 'Number of successful presence updates', }); const errorCounter = new client.Counter({ name: 'bot_errors_total', help: 'Number of errors encountered by the bot', }); app.get('/metrics', async (req, res) => { res.set('Content-Type', client.register.contentType); res.end(await client.register.metrics()); }); // 例:safeSetPresence 内でカウント async function safeSetPresence(options, attempt = 1) { try { await client.user.setPresence(options); presenceUpdates.inc(); logger.info('Presence updated', options); } catch (err) { errorCounter.inc(); // ...既存のリトライ処理... } } |
6‑5 運用上のチェックリスト
- [ ] Token ローテーション:最低 90 日ごとに再生成し、CI/CD シークレットを更新
- [ ] Intents 設定:Developer Portal とコードが一致しているか確認
- [ ] レートリミットモニタリング:429 発生回数が閾値(例: 5/hour)を超えたらアラート
- [ ] バックアップ:
.env、データベース(SQLite のstatus.db)は定期的に S3 / GCS にコピー
🎉 まとめ
- 正確なレートリミットは「5 回/20 秒」→実装時はこの上限を守る。
- Custom アクティビティは
type,name(空文字),state,emojiが必須で、custom_idは不要。 - トークン管理は「一度だけ表示」→すぐ
.envかシークレットマネージャーへ保存し、漏洩時は即再生成。 - 非アクティブ → idle の上書きは
client.user.presence?.statusを参照し、5 分ごとに強制 online に戻すロジックを実装。 - Dockerはマルチステージで devDependencies と production 依存を分離し、開発時は別ステージで
npm ci(dev)を走らせる。 - レートリミット回避は指数バックオフだけでなく、429 の
Retry-Afterヘッダーを必ず利用。 - Yoom 連携は外部リンクのアーカイブ化と代替ドキュメントへの誘導を行い、ノーコード側のリトライ回数は最小限に抑える。
- 本番デプロイは Docker → CI/CD(GitHub Actions) → Railway/Fly.io のフローで自動化し、
winstonとprom-clientによる可観測性を確保。
これらのポイントを踏まえて実装すれば、2026 年でも安定・安全に Discord ステータス自動更新ボットを運用できます。 Happy coding! 🚀