プロジェクト名: Reaction Bot 目的: Discord上で特定のカスタム絵文字のリアクションがつけられたメッセージを指定したチャンネルに転送するbot
- 指定したカスタム絵文字のリアクションがメッセージに付けられると、そのメッセージを転送先チャンネルに転送
- リアクションが全て外されると、転送されたメッセージを削除
- カスタム絵文字IDで判定するため、サーバー固有の絵文字を使用
- 同じメッセージに複数人が同じリアクションをつけても、転送は1度だけ
- 最後のリアクションが外された時のみ、転送メッセージを削除
クリーンアーキテクチャ(entities/usecases/handlers/gateways)
reaction-bot/
├── main.go # エントリーポイント
├── go.mod # Go modules定義
├── go.sum # 依存関係のチェックサム
├── .env # 環境変数(gitignore対象)
├── .env.example # 環境変数のサンプル
├── .gitignore # Git除外設定
├── CLAUDE.md # このファイル
└── internal/ # 内部パッケージ(外部からimport不可)
├── entities/ # エンティティ層
│ └── config.go # 設定とドメインモデル
├── usecases/ # ユースケース層
│ └── transfer_message.go # ビジネスロジック + インターフェース定義
├── handlers/ # ハンドラー層(Controller)
│ └── discord_handler.go # Discordイベントハンドラー
└── gateways/ # ゲートウェイ層(外部APIとの橋渡し)
└── discord_gateway.go # Discord API実装
- アプリケーションの設定を管理
- ドメインモデルの定義
- 環境変数の読み込みとバリデーション
- 外部ライブラリに依存しない純粋なビジネスルール
- 依存: なし(何も知らない)
- メッセージ転送のビジネスロジック
- 外部APIのインターフェース定義(DiscordClient)
- ビジネスルールの実装
- 依存: entities のみ
- 重要: 外部ライブラリ(discordgo)に直接依存しない
- 外部からの入力を受け取る(Discordイベントのハンドリング)
- UseCaseの呼び出しを統制(orchestrate)
- イベントをビジネスロジックに変換
- 依存: usecases, gateways, entities
- 役割: 入力側のアダプター
- 外部APIとの橋渡し(Discord API実装)
- usecasesで定義されたインターフェースを実装
- 外部ライブラリ(discordgo)の具体的な呼び出し
- 依存: usecases(インターフェース), discordgo
- 役割: 出力側のアダプター
- アプリケーションのエントリーポイント
- 依存関係の注入(DI)
- 依存関係の組み立て順序: entities → usecases → gateway → handler
- Discord botの起動とシャットダウン処理
環境変数の設定方法は .env.example を参照してください。
このプロジェクトではMakefileを使用してビルドやテストを管理しています。
重要: ビルドやテストなどのコマンドを実行する際は、必ずMakefileの内容を確認し、定義されているターゲットを使用してください。
主要なコマンド:
make build- バイナリをビルド(開発用)make test- テストを実行make fmt- コードフォーマットmake lint- 静的解析make run- アプリケーションを実行
詳細は make help で確認できます。
- パッケージ名: 小文字、単数形、短く簡潔(
entities,usecases,handlers,gateways) - 変数名: キャメルケース、明示的で説明的な名前
- 一般的で伝わる略語はOK:
cfg,msg,ch,ctx,err,id - 伝わりにくい略語はNG:
fwdCh,rctMapなど - WHY: 変数はスコープが限定的なため、略語による可読性の低下は少ない
- 一般的で伝わる略語はOK:
- 関数名: キャメルケース、動詞で始まる、略語を使わず完全な単語を使用
- プライベート:
loadConfiguration(),validateReactionMapping() - パブリック:
TransferMessage(),HandleReactionAdd() - 良い例:
DeleteTransferredMessage()- 検索しやすく、何をする関数か明確 - 悪い例:
DeleteTransferredMsg()- 検索で引っかからない可能性がある - WHY: 関数名は外部APIとして公開され、コードベース全体で検索される。略語を使うと検索性が低下し、他の開発者が機能を見つけにくくなる
- プライベート:
- メソッド名: 関数名と同様、略語を使わず完全な単語を使用
- 良い例:
GetTriggerReactionCount(),IsTriggerReactionEmoji() - 悪い例:
GetTriggerReactionCnt(),IsTriggerReactionEmj()
- 良い例:
- 定数: 大文字スネークケース
DEFAULT_REACTION_EMOJI,MAX_RETRY_COUNT
- 構造体: パスカルケース、明示的な名前
Config,ReactionMapping,TransferMessageUseCase
- インターフェース: パスカルケース、
-erで終わる(可能な場合)MessageTransferrer,ConfigLoader
- エラーハンドリング: 全てのエラーを適切に処理、ログ出力
- コメント: 日本語で記載
- パブリック関数・構造体には必ずコメントを記載
- 関数コメント:
- パブリック関数(大文字始まり): 必須 -
// 関数名 - 説明の形式で記載// TransferMessage - 転送メッセージを作成し、転送メッセージIDを保存 func (uc *TransferMessageUseCase) TransferMessage(...)
- プライベート関数(小文字始まり): 関数名が明示的で処理が自明な場合は省略可
- ただし、複雑なロジックや初見で分かりにくい処理には記載すること
// コメント不要 - 名前で処理内容が明確 func (h *DiscordHandler) isTriggerReactionEmoji(emoji discordgo.Emoji) bool
- WHY: Goのドキュメント生成では
// 関数名[半角スペース]説明で関数とコメントを紐付けるが、半角スペースだけだと区切りが分かりにくいため、ハイフンを入れて視認性を高める
- パブリック関数(大文字始まり): 必須 -
- 初見で伝わりにくい実装には
WHY:プレフィックスをつけて理由を説明// WHY: Bot自身のリアクションに反応すると無限ループになるため除外 if r.UserID == s.State.User.ID { return }
- ログ出力: 以下のルールで統一する
- エラーログ:
log.Printf()を使用し、エラー詳細を%vで出力log.Printf("メッセージ取得に失敗: %v", err)
- 情報ログ(複数の変数を含む):
log.Printf()を使用log.Printf("メッセージ %s をチャンネル %s に転送しました", msgID, channelID)
- 情報ログ(単純な文字列のみ):
log.Println()を使用log.Println("bot起動完了")
- WHY:
Printfで統一することで、将来的な構造化ログへの移行が容易になる
- エラーログ:
- importの順序:
- 標準ライブラリ
- サードパーティライブラリ
- 内部パッケージ
- 依存関係の注入: コンストラクタパターンを使用
- エラーメッセージ: 小文字で始める、末尾にピリオドをつけない
*_test.goファイルとして各ファイルと同じディレクトリに配置- 各レイヤーごとにテストを記述
- テーブル駆動テストを活用
依存ライブラリの詳細は go.mod を参照してください。
主要な依存:
github.com/bwmarrin/discordgo- Discord API クライアントgithub.com/joho/godotenv- 環境変数の読み込み