Cloudflare Workers 上で動作する TypeScript 製 Discord Bot です。指定したサーバーで /issue を実行すると、登録済みの正式氏名、サーバー参加日、最上位ロールを載せた日本語PDFを本人の DM に送信します。
発行履歴および発行済みPDFは保存しません。D1 には Discord ユーザーIDと正式氏名だけを保存し、PDF生成時には R2 内の日本語フォントを読み取ります。
| コマンド | 利用者 | 動作 |
|---|---|---|
/issue |
全メンバー | 本人の活動証明書PDFをDMへ送信 |
/register user:@ユーザー name:氏名 |
Administrator | 証明書に使う正式氏名を登録・更新 |
/issue は、@everyone を除く最上位ロールを役職欄に表示します。氏名が未登録、または DM が許可されていない場合、PDFは送られず本人だけに案内が表示されます。
Botは GUILD_MEMBER_REMOVE をDiscord Gatewayで受信し、/register または /issue の実行時点で保存した参加日・最終ロールのスナップショットを使って、退出時にも本人へ証明書をDMします。退出・キック・BANはいずれも同じDiscordイベントとして扱われます。
| 値 | 入手先 | 用途 |
|---|---|---|
DISCORD_BOT_TOKEN |
Discord Developer Portal → Application → Bot → Reset Token | DM送信、ロール取得、コマンド登録 |
DISCORD_PUBLIC_KEY |
Discord Developer Portal → Application → General Information → Public Key | Interaction リクエストの Ed25519 署名検証 |
DISCORD_APPLICATION_ID |
Discord Developer Portal → Application → General Information → Application ID | ローカルでのコマンド登録 |
GUILD_ID |
Discordで開発者モードを有効化 → 対象サーバーを右クリック → サーバーIDをコピー | 利用サーバーの固定、コマンド登録 |
ORGANIZATION_NAME / ORGANIZATION_ADDRESS |
運用者が決定 | PDF上の団体名・住所 |
ISSUER_TITLE / ISSUER_NAME / CONTACT_EMAIL |
運用者が決定 | PDF上の発行者・連絡先 |
このプロジェクトでは、上表のうち Worker が使用する全値(DISCORD_APPLICATION_ID を除く)を Cloudflare Worker Secret として保存します。公開鍵・サーバーIDは本来は公開情報ですが、運用情報をGitに残さないという方針に合わせて Secret 化しています。Discord の Client Secret は不要です。Cloudflare はローカルからのデプロイなら npx wrangler login で認証するため、Cloudflare API Token は不要です。CI/CDからデプロイする場合にだけ、Cloudflareで最小権限の API Token を別途発行してください。
-
Discord Developer Portal で Application を作成し、Bot を追加します。Installation では
botとapplications.commandsを有効にして、対象サーバーへ招待します。Bot ページの Privileged Gateway Intents で Server Members Intent も有効にしてください。 -
Bot token、Application ID、Public Key、対象サーバーの Guild ID を控えます。Bot はサーバーのロール一覧を読み取れる必要があります。
-
依存関係を入れ、Cloudflare にログインします。
npm install npx wrangler login
-
D1 と R2 を作成します。
npx wrangler d1 create zuikaku-volunteer-profiles npx wrangler r2 bucket create zuikaku-certificate-assets
出力された D1 の
database_idをwrangler.jsoncのdatabase_idに設定します。wrangler.jsoncには秘密情報・団体情報・Discord IDを記載しません。 -
D1 マイグレーションを反映します。
npx wrangler d1 migrations apply zuikaku-volunteer-profiles --remote
-
Noto Sans JP Regular の TTF または OTF を取得し、指定のキーにアップロードします。
npx wrangler r2 object put zuikaku-certificate-assets/fonts/NotoSansJP-Regular.ttf --file ./NotoSansJP-Regular.ttf --remote
-
Worker が使用する値をすべて Secret として設定します。各コマンドは値を求める対話入力を開くため、値をコマンドライン履歴に残しません。
npx wrangler secret put DISCORD_BOT_TOKEN npx wrangler secret put DISCORD_PUBLIC_KEY npx wrangler secret put GUILD_ID npx wrangler secret put ORGANIZATION_NAME npx wrangler secret put ORGANIZATION_ADDRESS npx wrangler secret put ISSUER_TITLE npx wrangler secret put ISSUER_NAME npx wrangler secret put CONTACT_EMAIL
.dev.varsの例:DISCORD_BOT_TOKEN=your-bot-token DISCORD_PUBLIC_KEY=your-discord-public-key GUILD_ID=your-discord-guild-id ORGANIZATION_NAME=your-organization-name ORGANIZATION_ADDRESS=your-organization-address ISSUER_TITLE=your-issuer-title ISSUER_NAME=your-issuer-name CONTACT_EMAIL=your-contact-email
-
Worker をデプロイし、出力された URL を Discord Developer Portal の Interactions Endpoint URL に設定します。Discord は PING と Ed25519署名検証を行うため、先にデプロイしてください。
npm run deploy
-
Guild command を登録します。環境変数を渡して実行してください。
DISCORD_APPLICATION_ID=... GUILD_ID=... DISCORD_BOT_TOKEN=... npm run register-commands
Guild command のため、指定サーバー内では即時に反映されます。
npm run dev
npm run check
npm testnpm run check は TypeScript 型検証と Worker 型定義の最新性を検証します。wrangler.jsonc を変更した場合は npx wrangler types を実行して worker-configuration.d.ts を更新してください。
証明書発行Workerとは別に、recorder/がVC録音を担当します。先にこの章を通読し、日常運用では「コマンド」と「障害時」を必要に応じて参照してください。音声を受信するには常駐プロセス、Opusデコーダ、FFmpegが必要なため、Cloudflare Workersには配置しません。
-
R2に録音専用バケットを作成し、
recordings/を30日後に削除するライフサイクルルールと公開ドメインを設定します。証明書用バケットは使いません。 -
Renderで
render.recorder.yamlをBlueprintとして登録します。Dockerのビルド対象はrecorder/です。常時接続が必要なため、スリープするFree Web Serviceは使わず、常駐するプランを選びます。 -
recorder/.env.exampleにある値をRenderのSecretとして設定します。RECORDING_RESULT_CHANNEL_IDには結果Embedを送る専用テキストチャンネルのIDを指定します。 -
Renderの公開URLを控え、Workerへ次のSecretを設定します。
RECORDER_SHARED_SECRETはRender側と同じ十分に長いランダム値にします。npx wrangler secret put RECORDER_SERVICE_URL npx wrangler secret put RECORDER_SHARED_SECRET
-
Workerを再デプロイし、コマンドを登録します。
npm run deploy DISCORD_APPLICATION_ID=... GUILD_ID=... DISCORD_BOT_TOKEN=... npm run register-commands
Botには対象VCの閲覧・接続・発言、および専用結果チャンネルへのメッセージ送信権限が必要です。Discord Developer Portalでは Server Members Intent に加え、Voice States Intent を有効にしてください。
/startは実行者が参加中のVCを録音します。開始音が流れた時点から、発話者ごとの音声をMP3へ合成します。/stopは録音対象VCにいる参加者なら誰でも実行できます。VCから人がいなくなった場合も録音を停止します。
停止後は開始者へDMを送り、専用結果チャンネルのEmbedに公開URLを表示します。URLを知る人は再生でき、録音は30日後にR2から自動削除されます。DMが受信拒否でも録音処理は続きます。
/healthzがHTTP 200なら、録音サービスはDiscord接続済みです。503の場合はRenderログでdiscord_login_failedを確認し、Bot TokenとVoice States Intentを見直してください。保存失敗は結果Embedに表示されるため、R2の認証情報、公開URL、FFmpegのログを確認します。
- Bot token はソース、
wrangler.jsonc、ログに絶対に含めないでください。 - 構造化ログには個人名を出力せず、DiscordユーザーIDと発行結果だけを記録します。
- Discord の API 制限や DM 拒否により送信できない場合があります。その場合はユーザーにDM許可後の再実行を案内します。