Contents
Discord Developer Portal でのアプリ作成と Bot トークン取得
Discord と外部サービスを連携させて自動通知を実装する第一歩は、Bot 用のアプリケーションを作成しトークンを取得することです。この記事では、2026 年時点の Discord Developer Portal の操作フローと、最低限必要な権限設定・Intents の有効化手順を具体的に解説します。
アプリ登録手順
Discord 開発者向けポータルでアプリケーションを作成し、Bot トークンを安全に管理できるようになるまでの流れです。
- Discord Developer Portal にログイン
-
https://discord.com/developers/applications にアクセスし、Discord アカウントでサインインします。
-
「New Application」ボタンをクリック
-
任意のアプリ名(例:
AutoNotifyBot)を入力し、「Create」を選択します。 -
左メニューの「Bot」タブへ移動
-
「Add Bot」→「Yes, do it!」で Bot を有効化します。
-
トークン取得
- 「TOKEN」欄にある「Copy」ボタンでトークンをコピーし、
.env等安全な場所に保存します(決してリポジトリにコミットしない)。
必要権限・Privileged Intents の設定方法
Bot が実行すべき機能だけを許可することでセキュリティリスクを低減できます。ここでは 最小権限 と Server Members Intent の有効化手順を紹介します。
- スコープ:
botとapplications.commandsを必ず選択してください。これにより Bot がサーバーへ招待でき、スラッシュコマンドが使用可能になります。 - Bot Permissions(権限): 自動通知だけなら最低限
Send Messages (0x00000800)とRead Message History (0x00000100)を付与します。不要な権限は外すほど安全です。 -
Privileged Intents(重要度が高いもの):
-
Message Content Intent(メッセージ内容へのアクセス) Server Members Intent(サーバーメンバー情報へのアクセス)
上記のチェックボックスをオンにし、画面下部の Save Changes を忘れずにクリックしてください。
ポイント:権限は最小限に抑えるほど安全です。不足したらその都度追加する方針で運用しましょう。
Node.js 環境と discord.js(最新版)の導入
Node.js と公式ライブラリ discord.js を組み合わせることで、最新の Discord API にシームレスにアクセスできます。ここではプロジェクトの初期化から基本コードまでを手順化します。
プロジェクト初期化と依存パッケージインストール
以下のコマンドで作業ディレクトリを作成し、必要なパッケージをインストールします。
|
1 2 3 4 5 6 7 8 9 10 11 12 |
# 作業ディレクトリ作成 mkdir auto-notify-bot && cd auto-notify-bot # npm 初期化(デフォルト設定で問題なし) npm init -y # 本番に必要な依存パッケージをインストール npm install discord.js@^14 dotenv express node-schedule # 開発支援ツールは任意でインストール npm install -D nodemon eslint prettier typescript @types/node |
TypeScript プロジェクトの雛形作成
TypeScript を利用する場合は tsconfig.json を生成し、ビルド時にコンパイルが走るようにします。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
npx tsc --init # 重要な設定例(抜粋) cat > tsconfig.json <<'EOF' { "compilerOptions": { "target": "ES2022", "module": "commonjs", "outDir": "./dist", "rootDir": "./src", "strict": true, "esModuleInterop": true, "skipLibCheck": true }, "include": ["src"] } EOF |
基本的な Bot コード構造(TypeScript)
src/index.ts に以下のコードを配置します。
|
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 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
// src/index.ts import 'dotenv/config'; import { Client, GatewayIntentBits, Partials } from 'discord.js'; import express from 'express'; import schedule from 'node-schedule'; import verifySignature from './middleware/verifySignature'; // 必要なインテントだけ有効化(MessageContent が必須) const client = new Client({ intents: [ GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent, ], partials: [Partials.Channel], }); client.once('ready', () => { console.log(`✅ Bot logged in as ${client.user?.tag}`); }); /** 指定チャンネルへメッセージを送信するユーティリティ */ async function sendMessage(channelId: string, content: string) { try { const channel = await client.channels.fetch(channelId); // `as` アサーションで TextBasedChannel に絞る await (channel as any).send(content); } catch (err) { console.error('❌ Send failed:', err); } } /* ------------------------------------------------- Express(Webhook 受信用)設定 --------------------------------------------------- */ const app = express(); app.use(express.json()); app.post('/webhook/github', verifySignature, (req, res) => { const { action, repository } = req.body; if (action === 'opened') { sendMessage( process.env.NOTIFY_CHANNEL_ID!, `🆕 New issue opened in **${repository.full_name}**` ); } res.status(200).send('ok'); }); /* ヘルスチェックエンドポイント(K8s 等で利用) */ app.get('/health', (_req, res) => { const status = client.ws.status === 0 ? 'online' : 'offline'; res.json({ uptime: process.uptime(), discordStatus: status }); }); const PORT = Number(process.env.PORT) || 3000; app.listen(PORT, () => console.log(`🚀 Webhook server listening on ${PORT}`)); /* ------------------------------------------------- 定期通知(毎日 09:00 JST に実行) --------------------------------------------------- */ schedule.scheduleJob('0 0 9 * * *', () => { sendMessage( process.env.NOTIFY_CHANNEL_ID!, `⏰ 本日の自動リマインドです。` ); }); client.login(process.env.BOT_TOKEN); |
.env のサンプルは次の通りです。
|
1 2 3 4 5 |
BOT_TOKEN=YOUR_BOT_TOKEN_HERE NOTIFY_CHANNEL_ID=123456789012345678 PORT=3000 GITHUB_WEBHOOK_SECRET=your_secret_here |
ポイント:
discord.jsはインテントを明示的に指定しないと Bot が起動できません。特にMessageContentを忘れずに有効化してください。
自動通知ロジック実装:メッセージ送信と外部サービス連携
自動通知は「Discord へメッセージを送る」だけでなく、GitHub や CI/CD の Webhook を受け取ってトリガーするケースが多いです。ここではチャンネル ID の取得方法、Webhook 検証・署名チェック、そして定期実行までの流れを具体的に示します。
チャンネル ID の取得手順
- Discord クライアントで対象サーバーを開く
- 設定 > アピアランス > 開発者モード を有効化
- 目的のテキストチャンネルを右クリック → 「ID をコピー」
取得した ID を .env の NOTIFY_CHANNEL_ID に保存すれば、先ほど実装した sendMessage 関数で即座に利用できます。
Webhook 受信と署名検証(Express ミドルウェア)
外部サービスからの POST リクエストは偽装されやすいため、X‑Hub‑Signature‑256(GitHub)等で HMAC を検証します。以下は TypeScript 版ミドルウェアです。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// src/middleware/verifySignature.ts import { Request, Response, NextFunction } from 'express'; import crypto from 'crypto'; export default function verifySignature(req: Request, res: Response, next: NextFunction) { const signature = req.headers['x-hub-signature-256'] as string | undefined; if (!signature) return res.status(400).send('Missing signature'); const hmac = crypto.createHmac('sha256', process.env.GITHUB_WEBHOOK_SECRET!); const digest = `sha256=${hmac.update(JSON.stringify(req.body)).digest('hex')}`; // timingSafeEqual で安全比較 if (crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(digest))) { return next(); } return res.status(401).send('Invalid signature'); } |
GitHub 側の設定は「Payload URL」に https://your-domain.com/webhook/github、Content type は application/json、Secret に .env.GITHUB_WEBHOOK_SECRET を入力し、必要なイベント(例:Issues, Pull Requests)を選択してください。
cron / node‑schedule を用いた定期通知
node-schedule の Cron 形式は 秒 分 時 日 月 曜日 の 6 フィールドです。以下は平日の 18:30 に業務終了リマインドを送る例です。
|
1 2 3 4 5 6 7 8 |
// 毎週月〜金の18:30に実行 schedule.scheduleJob('0 30 18 * * 1-5', () => { sendMessage( process.env.NOTIFY_CHANNEL_ID!, '📅 今日の業務はここまでです。明日も頑張りましょう!' ); }); |
ポイント:Cron 表記はコンテナのタイムゾーンに依存します。Docker コンテナやサーバーレス環境では
TZ=Asia/Tokyoを設定しておくと安心です。
Docker と CI/CD/サーバーレスでのデプロイ手順
本番運用ではコンテナ化と自動デプロイが不可欠です。ここでは マルチステージビルド による軽量イメージ作成、GitHub Actions でのパイプライン構築、そしてサーバーレスオプション(Vercel・Cloudflare Workers)について解説します。
Dockerfile 作成とローカルビルド
以下の Dockerfile は TypeScript プロジェクトを前提にしています。ビルドステージで tsc によるコンパイル を行い、実行イメージにはコンパイル済みの JavaScript と本番依存だけを残します。
|
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 |
# ---------- Build Stage ---------- FROM node:20-alpine AS builder WORKDIR /app # パッケージ情報だけ先にコピーし、devDeps もインストール COPY package*.json ./ RUN npm ci # devDependencies も含めてインストール # ソースコード全体をコピーして TypeScript をコンパイル COPY . . RUN npx tsc # ./dist に出力される前提 # ---------- Run Stage ---------- FROM node:20-alpine AS runner ENV NODE_ENV=production TZ=Asia/Tokyo WORKDIR /app # ビルドステージから実行に必要なファイルだけコピー COPY --from=builder /app/dist ./dist COPY --from=builder /app/package*.json ./ # 本番依存だけを再インストール(devDeps は除外) RUN npm ci --only=production EXPOSE 3000 CMD ["node", "dist/index.js"] |
ビルド・実行手順
|
1 2 3 |
docker build -t auto-notify-bot . docker run -d -p 3000:3000 --env-file .env auto-notify-bot |
注意:公式ドキュメントで提示されているレートリミットは随時変更されます。Docker イメージのビルド自体はレートリミットに影響しませんが、
npm install時に API 呼び出し回数が増える可能性があるため、CI 環境ではキャッシュを活用してください。
GitHub Actions で CI/CD パイプライン構築
.github/workflows/deploy.yml の例です。ビルド・テスト・Docker イメージのプッシュ・デプロイまでを自動化します。
|
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 |
name: Docker Deploy on: push: branches: [ main ] jobs: build-and-deploy: runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 - name: Set up Node.js uses: actions/setup-node@v3 with: node-version: '20' - name: Install dependencies (including dev) run: npm ci - name: Run lint & test run: | npx eslint . # テストコマンドがあればここに追加 - name: Login to Docker Hub uses: docker/login-action@v2 with: username: ${{ secrets.DOCKERHUB_USER }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build and push Docker image uses: docker/build-push-action@v5 with: context: . push: true tags: ${{ secrets.DOCKERHUB_USER }}/auto-notify-bot:${{ github.sha }} - name: Deploy to Fly.io (例) if: success() run: | flyctl deploy --image ${{ secrets.DOCKERHUB_USER }}/auto-notify-bot:${{ github.sha }} env: FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} |
- シークレット管理は GitHub の
Settings > Secretsにすべて保存し、コード上に平文を書かないよう徹底してください。
Vercel または Cloudflare Workers へのサーバーレスデプロイ
Vercel(Node.js Function)
- Vercel CLI をインストール
npm i -g vercel - プロジェクトルートで
vercelコマンドを実行し、api/webhook.tsにハンドラを書きます。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// api/webhook.ts import express from 'express'; import verifySignature from '../src/middleware/verifySignature'; import { sendMessage } from '../src/utils'; // 事前に作成した送信関数 const app = express(); app.use(express.json()); app.post('/github', verifySignature, (req, res) => { const { action, repository } = req.body; if (action === 'opened') { sendMessage(process.env.NOTIFY_CHANNEL_ID!, `🆕 New issue: ${repository.full_name}`); } res.status(200).send('ok'); }); export default app; |
Vercel のダッシュボードで環境変数(BOT_TOKEN、NOTIFY_CHANNEL_ID 等)を登録すれば完了です。
Cloudflare Workers(Durable Objects 推奨)
- wrangler CLI をインストール
npm i -g @cloudflare/wrangler wrangler.tomlに必要な変数を設定します。
|
1 2 3 4 5 6 7 8 9 10 |
name = "auto-notify-bot" type = "javascript" account_id = "<YOUR_ACCOUNT_ID>" workers_dev = true [vars] BOT_TOKEN = "$BOT_TOKEN" NOTIFY_CHANNEL_ID = "$CHANNEL_ID" GITHUB_WEBHOOK_SECRET = "$WEBHOOK_SECRET" |
src/worker.tsにリクエストハンドラを書き、Discord への POST はfetchを使って非同期に実行します(Workers は長時間接続できないため)。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
export default { async fetch(request: Request, env: Env) { const url = new URL(request.url); if (url.pathname === '/github' && request.method === 'POST') { // 署名検証は同様に実装(省略) const body = await request.json(); if (body.action === 'opened') { // Discord に即時送信 await fetch('https://discord.com/api/v10/channels/' + env.NOTIFY_CHANNEL_ID + '/messages', { method: 'POST', headers: { Authorization: `Bot ${env.BOT_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ content: `🆕 New issue: ${body.repository.full_name}` }), }); } return new Response('ok'); } return new Response('Not Found', { status: 404 }); }, }; |
ポイント:Docker コンテナは常駐 Bot に適し、サーバーレスは「イベント駆動」だけを扱う場合にコスト効率が高いです。要件に合わせて選択してください。
運用・監視とベストプラクティス
自動通知システムは 24/7 稼働が前提です。レートリミットやエラー、シークレット漏洩を防ぐための具体的対策をまとめます。
レートリミット対策(最新公式情報へのリンク必須)
Discord の公式ドキュメント(2026 年版)によると グローバルレートリミットは 120 リクエスト/分、各エンドポイントには個別の上限が設定されており多くの場合 1 秒間に 5〜10 リクエスト が上限です。実装時は以下を守ります。
discord.jsは内部で自動リトライとバックオフを行いますが、カスタム API(例:Webhook 受信側)では自前の 指数バックオフ を実装してください。- レートリミットエラー (
429) が返ってきたらRetry-Afterヘッダーを参照し、指定秒数だけ待機します。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
async function safeSend(channelId: string, content: string, attempt = 1): Promise<void> { try { await sendMessage(channelId, content); } catch (err: any) { if (err.status === 429 && attempt <= 5) { const waitMs = (err.headers?.['retry-after'] ?? Math.pow(2, attempt) * 1000); console.warn(`⏳ Rate limited. Retry in ${waitMs} ms`); await new Promise(r => setTimeout(r, waitMs)); return safeSend(channelId, content, attempt + 1); } console.error('❗️ Final send failure:', err); } } |
重要:レートリミットの数値は随時変更されるため、デプロイ前に必ず公式ドキュメント(https://discord.com/developers/docs/topics/rate-limits)を確認してください。
環境変数・シークレット管理のベストプラクティス
| 方法 | 用途 | メリット |
|---|---|---|
.env + dotenv |
ローカル開発 | 手軽に設定可能 |
| Docker Secrets / Kubernetes Secret | コンテナ運用 | OS レベルで暗号化、ロールベース制御 |
| Cloud Secret Manager (GCP, AWS, Azure) | サーバーレス・CI | 自動ローテーション・アクセス監査 |
- 絶対にコード内にハードコーディングしないこと。
process.env.VARIABLE_NAMEで参照し、リポジトリは必ず.gitignoreに.envを追加してください。 - ビルドイメージにシークレットが残らないよう、
docker historyやdocker run --rm -it <image> sh -c 'cat /app/.env'で確認し、漏れがあれば Dockerfile の COPY 節を見直します。
ロギング・ヘルスチェック・トラブルシューティング
Winston による構造化ロギング
|
1 2 3 4 5 6 7 8 9 |
// src/logger.ts import { createLogger, transports, format } from 'winston'; export default createLogger({ level: 'info', format: format.combine(format.timestamp(), format.json()), transports: [new transports.Console(), new transports.File({ filename: 'logs/bot.log' })], }); |
使用例:
|
1 2 3 |
import logger from './logger'; logger.info('Message sent', { channelId, content }); |
ヘルスチェックエンドポイント(K8s 等で活用)
|
1 2 3 4 5 |
app.get('/health', (_req, res) => { const discordStatus = client.ws.status === 0 ? 'online' : 'offline'; res.json({ uptime: process.uptime(), discordStatus }); }); |
Dockerfile にも HEALTHCHECK を追加し、コンテナオーケストレータが自動復旧できるようにします。
|
1 2 3 |
HEALTHCHECK --interval=30s --timeout=5s \ CMD curl -f http://localhost:3000/health || exit 1 |
トラブルシューティングチェックリスト
| 項目 | 確認手順 |
|---|---|
| Bot がオンラインでない | コンテナログに Bot logged in as … が出ているか |
| メッセージ未送信 | 429 エラーや safeSend のバックオフが機能しているか |
| Webhook が届かない | GitHub の Delivery 履歴で 200 応答があるか、署名検証に失敗していないか |
| 環境変数漏洩 | Docker イメージ内やリポジトリ履歴に BOT_TOKEN が残っていないか |
| メモリリーク | process.memoryUsage() を定期的に監視し、急激な増加が無いか |
ポイント:障害発生時は「ヘルスチェック → ログ → レートリミット」の順で切り分けると迅速に原因特定できます。
まとめ
- Discord Developer Portal で Bot アプリを作成し、最小権限・
Server Members Intentを有効化したトークンを安全に管理する。 - Node.js + discord.js v14 + TypeScript により、インテント指定と環境変数の読み込みだけで基本的な通知ロジックが完成する。
- 外部サービスからの Webhook は署名検証を必ず入れ、Express 経由で受信→Discord へ転送すればリアルタイム通知が実現できる。
- Docker のマルチステージビルド と GitHub Actions による CI/CD パイプラインで、本番デプロイを数クリックに自動化する。サーバーレス(Vercel・Cloudflare Workers)も用途に合わせて選択可能。
- 運用面では、公式ドキュメントのレートリミット情報を随時確認しつつ指数バックオフ実装、シークレットは外部管理、
winstonで構造化ログ、ヘルスチェックエンドポイントで可観測性を確保する。
これらの手順とベストプラクティスに従えば、業務ツールや CI/CD と連携した安全・高速な自動通知システムを安定的に構築・運用できます。ぜひ本稿をテンプレートとして活用し、プロジェクトごとの要件に合わせてカスタマイズしてください。