Discord server 内の特定チャンネルに投稿したユーザーのメッセージを自動的に個人用の times-<username> チャンネルに転送し、SQLite で永続化する Bot です。
開発フロー・ブランチ戦略・バージョニング方針は DEVELOPMENT.md、機能アイディアは IDEAS.md を参照してください。
- 自動ユーザー登録:
sourceChannelIdに投稿したユーザーは自動的に登録されます - 自動チャンネル作成: ユーザー初回投稿時に
times-<username>という専用チャンネルが自動作成されます - メッセージ永続化: すべてのメッセージを SQLite に保存し、編集・削除の履歴も記録します
- ユーザー情報スナップショット: username や displayName の変更を追跡しつつ、チャンネル名は固定に保ちます
- タイムライン集約: 各 times チャンネルへの投稿を1つの集約チャンネルにまとめて表示します(オプション)
- リアクション同期: 監視元チャンネルのリアクションを転送先
times-<username>に反映します。さらに集約チャンネルを使う場合は、times チャンネルと集約チャンネルの間でリアクションを双方向に同期します - ロールベースカテゴリ振り分け: ユーザーの Discord ロールに応じて、チャンネルを配置するカテゴリを動的に切り替えられます(オプション)
env.exampleをコピーして.envを作るroutes.example.yamlをコピーしてroutes.yamlを作る- 各ID、トークン、カテゴリを埋める
- Docker Compose が使える環境で
docker compose up --buildを実行する
cp env.example .env
cp routes.example.yaml routes.yaml
docker compose up --buildCompose は手元の .env を env_file で読み込み、routes.yaml を /app/routes.yaml に read-only mount して起動します。SQLite ファイル(デフォルト data/times.sqlite)は volume を通じて永続化されます。秘密情報はイメージに焼き込まれません。
実際に動かすには、次の情報を自分で用意します。
DISCORD_TOKEN- Discord Developer Portal で作成した Bot のトークン
GUILD_ID- Bot を動かす Discord サーバーのID
TIMES_CATEGORY_ID- 自動作成される
times-<username>チャンネルを配置するカテゴリID - カテゴリに対して以下の権限が必要です
- Manage Channels (チャンネル作成)
- View Channels (チャンネル閲覧)
- Send Messages (メッセージ送信)
- Attach Files (ファイル添付)
- 自動作成される
DISCORD_ENABLE_MESSAGE_CONTENT_INTENT(デフォルト:true)- メッセージ本文を転送するかどうか
- SQLite による永続化を行う都合上、
trueが必須です falseを設定すると起動エラーになります
TIMES_DB_PATH(デフォルト:data/times.sqlite)- SQLite ファイルの保存先パス
TIMES_AGGREGATE_CHANNEL_ID(省略可)- 全 times チャンネルの投稿を集約して表示するチャンネルのID
- 設定すると、各
times-<username>チャンネルへの投稿が Embed 形式でこのチャンネルにも転送されます - Embed にはアバター・表示名・投稿元チャンネルへのリンク・タイムスタンプが含まれます
- 未設定の場合、集約機能は無効になります
sourceChannelId: 監視対象チャンネルの ID- テキストチャンネルまたはスレッドのみ対応
- forum/media 親チャンネルは非対応です(起動時にエラーになります)
roleCategoryMappings(省略可): ロール別のカテゴリ振り分け設定roleId: 振り分けに使う Discord ロールの IDcategoryId: 振り分け先カテゴリの ID- リスト上位のエントリが優先されます(ユーザーが複数の対象ロールを持つ場合、最初に一致したエントリが使われます)
- 一致するロールがない場合は
TIMES_CATEGORY_IDにフォールバックします - 省略した場合はすべてのユーザーが
TIMES_CATEGORY_IDに配置されます(従来どおり)
IDを調べるには、Discord の 詳細設定 > 開発者モード をONにして、サーバー・チャンネル・ユーザーを右クリックし IDをコピー を使います。
.env の例:
DISCORD_TOKEN=your_bot_token_here
GUILD_ID=123456789012345678
TIMES_CATEGORY_ID=987654321098765432
DISCORD_ENABLE_MESSAGE_CONTENT_INTENT=true
TIMES_DB_PATH=data/times.sqlite
# 集約チャンネルを使う場合のみ設定
TIMES_AGGREGATE_CHANNEL_ID=111222333444555666routes.yaml の例(ロール振り分けなし):
sourceChannelId: "123456789012345678"routes.yaml の例(ロール振り分けあり):
sourceChannelId: "123456789012345678"
roleCategoryMappings:
- roleId: "111111111111111111" # Engineer ロール
categoryId: "222222222222222222" # Engineering カテゴリ
- roleId: "333333333333333333" # Designer ロール
categoryId: "444444444444444444" # Design カテゴリ雛形として env.example と routes.example.yaml があります。
- ユーザーが
sourceChannelIdのチャンネルにメッセージを投稿します - Bot は投稿を検出し、DB にユーザー情報を記録します
- ユーザーのロールと
roleCategoryMappingsを照合してカテゴリを決定します(一致するロールがない場合はTIMES_CATEGORY_IDを使用)。決定したカテゴリ配下にtimes-<sanitized_username>という新しいテキストチャンネルを作成します - 元のメッセージを転送先チャンネルに送信し、DB に記録します
- 以降、同じユーザーの投稿はすべて同じ転送先チャンネルに送信されます
routes.yaml に roleCategoryMappings を設定すると、ユーザーが持つ Discord ロールに応じて times-<username> チャンネルの配置先カテゴリを自動的に振り分けられます。
- ユーザーの初回投稿時に、Bot がそのメンバーのロール一覧を取得します
roleCategoryMappingsのエントリを上から順に照合します- 最初に一致したエントリの
categoryIdを振り分け先カテゴリとして使います - どのロールも一致しない場合は
TIMES_CATEGORY_IDにフォールバックします
ユーザーが roleCategoryMappings に登録された複数のロールを同時に持っている場合、リスト上位のエントリが優先されます。エントリの並び順で振り分け先を制御できます。
roleCategoryMappings:
- roleId: "111111111111111111" # このロールが最優先
categoryId: "222222222222222222"
- roleId: "333333333333333333" # 上記ロールを持たない場合に適用
categoryId: "444444444444444444"roleCategoryMappings を省略した場合、すべてのユーザーのチャンネルが TIMES_CATEGORY_ID に配置されます。既存の設定に変更は不要です。
roleCategoryMappings を使う場合、TIMES_CATEGORY_ID に加えて振り分け先カテゴリすべてに以下の権限を付与してください。
- Manage Channels: チャンネル作成に必須
- View Channels: チャンネル閲覧に必須
- Send Messages: メッセージ送信に必須
- Attach Files: ファイル転送が必要な場合に必須
- 作成: メッセージが投稿されるたびに DB に記録されます
- 編集: Source channel でメッセージが編集されると、DB の
source_edited_atとcontentが更新されます(転送先メッセージは更新されません) - 削除: Source channel でメッセージが削除されると、DB に
source_deleted_atが記録されます(転送先メッセージは削除されません)
添付ファイルは転送時に Discord CDN からダウンロードされて転送先に添付されますが、DB には本文のみ保存されます。has_attachments フラグで「このメッセージには添付があった」ことを追跡できます。
- ユーザー初回投稿時に
times-<sanitized_username>という形式でチャンネルが作成されます - Username に特殊文字が含まれる場合は、英数字とハイフンのみの形式に sanitize されます
- Sanitize 後に空になるか、既に同じ名前のチャンネルが存在する場合は
times-<username>-<userId末尾6桁>にフォールバックします - ユーザーが Discord 上で username を変更しても、チャンネル名は変わりません (既に作成されたチャンネルを rename しないため)
- DB 上の username と displayName は最新の値に更新されます
正規の実行導線は docker compose up --build です。Docker Engine と Compose v2 が使える環境で実行してください。
docker compose up --buildよく使うコマンド:
docker compose logs -f
docker compose restart
docker compose down初回起動時はイメージのビルドが走ります。正常に起動するとログに bot_ready が出ます。
Botを作成したら、少なくとも次を確認してください。
- Developer Portalの
Bot設定でMessage Content Intentを有効化する - Botを対象サーバーに招待する(権限には
Manage Channels,View Channels,Send Messages,Attach Files,Add Reactionsを含める) - 監視元チャンネル(
sourceChannelId)でBotがメッセージを読めるようにする - TIMES_CATEGORY_ID カテゴリで以下の権限を付与する
- Manage Channels: 自動チャンネル作成に必須
- View Channels: チャンネル閲覧に必須
- Send Messages: メッセージ送信に必須
- Attach Files: ファイル転送が必要な場合に必須
- Add Reactions: リアクション同期に必須
- 集約チャンネル(
TIMES_AGGREGATE_CHANNEL_ID)を使う場合は、そのチャンネルでもView Channels,Send Messages,Add Reactionsを付与する(双方向リアクション同期のため) - Bot のロールがカテゴリの権限設定より上位にあることを確認(ロールの順序が重要)
roleCategoryMappingsを使う場合は、TIMES_CATEGORY_IDに加えて振り分け先カテゴリすべてに上記と同じ権限を付与する
このBotはコード上で次のIntentを使っています。
GuildsGuildMessagesGuildMessageReactionsMessageContent
.devcontainer/ は開発作業用です。実行だけなら Compose を使い、コード修正やテストをしたいときだけ devcontainer やローカル Node.js 環境を使います。Compose 実行は Docker が使えるホスト環境を前提にしています。
ローカルで直接動かす場合は Node.js 22系と pnpm を使います。
pnpm install
pnpm test
pnpm build
pnpm startsourceChannelIdのチャンネルにメッセージを投稿すると、未登録ユーザーは自動登録される- BotやWebhookの投稿は転送されない
- メンションは転送時に除去される
- 本文が空でも添付ファイルがあれば転送される
DISCORD_ENABLE_MESSAGE_CONTENT_INTENT=falseの場合、Botは起動エラーになるTIMES_AGGREGATE_CHANNEL_IDを設定した場合、各 times チャンネルへの投稿が集約チャンネルに Embed で表示される- Embed 内のチャンネルリンクをクリックすると投稿元チャンネルに飛べる
- ユーザーごとに異なる色で表示される
- times チャンネル側・集約チャンネル側どちらにリアクションを付けても、もう一方に同期される(双方向)
- 監視元チャンネルのメッセージに付けたリアクションは、転送先
times-<username>のメッセージにも反映される roleCategoryMappingsを設定した場合- 対象ロールを持つユーザーの初回投稿で、対応カテゴリにチャンネルが作成される
- どのロールも持たないユーザーは
TIMES_CATEGORY_IDにフォールバックされる - ロールを複数持つ場合はリスト上位のエントリが優先される
DISCORD_TOKEN is required..envがないか、値が空ですGUILD_ID must be a valid Discord snowflake.IDの形式が不正ですUsed disallowed intentsDeveloper PortalでMessage Content Intentを有効化するか、暫定確認だけなら.envにDISCORD_ENABLE_MESSAGE_CONTENT_INTENT=falseを設定してくださいUnknown GuildGUILD_IDが違うか、Botがそのサーバーに招待されていませんsourceChannelId ... is not a text-based channelフォーラムや対象外チャンネルを指定している可能性がありますdestinationChannelId ... is not a sendable text channel転送先が送信可能なテキストチャンネルではないか、Bot権限が不足していますCould not locate the bindings file.better-sqlite3のネイティブバインディングが見つかりません。以下を確認してください- Docker イメージを再ビルドしてください:
docker compose down && docker compose up --build - ホスト環境の Node.js バージョンと Docker の Node.js バージョン(22.x)が一致しているか確認してください
- ローカルで実行する場合は、
pnpm rebuildを実行してネイティブモジュールを再構築してください pnpm install直後にpnpm buildを実行してください
- Docker イメージを再ビルドしてください:
- チャンネルが作成されない
times-<username>チャンネルが自動作成されない場合、ログを確認してくださいevent: "channel_creation_starting"ログで、channel name と category ID が正しいか確認event: "channel_created"ログがない場合は、以下の権限を確認してください:- Bot に対して:
Manage Channels,View Channels権限 - TIMES_CATEGORY_ID カテゴリに対して: 上記と同じ権限、さらに
Send Messages権限
- Bot に対して:
- Bot のロール が category の権限より上にあるか確認してください
event: "user_registration_failed"ログに詳細なエラーが出ている場合、その内容を確認
roleCategoryMappingsを設定したのにデフォルトカテゴリに作成されるroleIdが正しいか確認してください(開発者モードでロールを右クリック → ID をコピー)- ユーザーが該当ロールを実際に持っているか確認してください
- リスト上位のエントリが先に一致していないか確認してください
- ロール振り分け先カテゴリにチャンネルが作成されない
- 振り分け先カテゴリに
Manage Channels,View Channels,Send Messages権限が付与されているか確認してください categoryIdが有効なカテゴリチャンネルのIDか確認してください
- 振り分け先カテゴリに