Conversation
There was a problem hiding this comment.
Pull request overview
This pull request implements a role panel feature for Discord server management, allowing admins to create interactive role selection panels using Discord's select menu UI. Users can self-assign roles through these panels, improving the bot's role management capabilities.
Changes:
- Added database models (RolePanel and RolePanelOption) with GORM auto-migration support for storing panel configurations
- Implemented a RolePanelRepository with full CRUD operations for panels and their role options
- Created a
/rolepanelcommand with five subcommands (create, delete, add, remove, list) for panel administration - Developed message component handlers for interactive panel functionality, including role selection and administrative actions
Reviewed changes
Copilot reviewed 12 out of 13 changed files in this pull request and generated 14 comments.
Show a summary per file
| File | Description |
|---|---|
| src/internal/model/rolepanel.go | Defines database models for role panels and their selectable role options |
| src/internal/db/db.go | Adds new models to database auto-migration |
| src/internal/repository/rolepanel.go | Implements repository pattern with CRUD operations for role panels |
| src/internal/bot/command/server_management/rolepanel.go | Main command handler with subcommand routing |
| src/internal/bot/command/server_management/rolepanel/create.go | Subcommand to create new role panels |
| src/internal/bot/command/server_management/rolepanel/delete.go | Subcommand to delete existing panels |
| src/internal/bot/command/server_management/rolepanel/add.go | Subcommand to add roles to panels |
| src/internal/bot/command/server_management/rolepanel/remove.go | Subcommand to remove roles from panels |
| src/internal/bot/command/server_management/rolepanel/list.go | Subcommand to list all panels in a server |
| src/internal/bot/messageComponent/rolepanel.go | Handles interactive select menu interactions for role assignment and panel management |
| src/internal/bot/command/commands.go | Registers the rolepanel command |
| src/internal/bot/command/registry.go | Maps the rolepanel command to its handler |
| .gitignore | Adds docker-compose.yaml to ignored files |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: ysmreg <33682244+ysmreg@users.noreply.github.com>
|
@ysmreg コパイロットに生成させているのだろうけど、
|
📝 WalkthroughWalkthrough新しいロールパネル機能をDiscordボットに実装しました。ユーザーが作成・削除・追加・削除・リストのサブコマンドを使用してロールパネルを管理でき、対応するデータモデル、リポジトリ、メッセージコンポーネントハンドラーが含まれます。 Changes
Sequence Diagram(s)sequenceDiagram
actor User as ユーザー
participant Discord as Discord API
participant Bot as Bot Command Handler
participant DB as Database/Repository
participant MC as Message Component Handler
User->>Discord: /rolepanel create コマンド実行
Discord->>Bot: InteractionCreate イベント送信
Bot->>Bot: オプション抽出 (title, description)
Bot->>Discord: パネル埋め込みメッセージ送信
Discord->>User: チャネルにパネル表示
Bot->>DB: RolePanel レコード作成・保存
DB-->>Bot: 成功/失敗応答
Bot->>Discord: エフェメラルレスポンス送信
Discord->>User: 成功/エラーメッセージ表示
User->>Discord: パネルのロール選択ボタン/メニュー操作
Discord->>MC: MessageComponentInteractionCreate イベント送信
MC->>DB: パネルメタデータ取得
DB-->>MC: RolePanel 情報返却
MC->>MC: 現在のロール vs 希望ロール比較
MC->>Discord: ロール追加/削除実行
MC->>Discord: パネルメッセージ更新
Discord->>User: ロール変更反映・パネル更新
MC->>Discord: エフェメラル確認メッセージ送信
Discord->>User: 操作結果表示
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes 🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
📝 Coding Plan
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment Tip CodeRabbit can generate a title for your PR based on the changes.Add |
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/internal/db/db.go (1)
18-45:⚠️ Potential issue | 🟠 Majorマイグレーション失敗を成功扱いにしないでください。
AutoMigrateが失敗しても後続の cleanup を続けていますし、各DropColumnの error も返していません。ここで失敗を握りつぶすと、起動だけ成功して schema だけ半端な状態が残ります。AutoMigrateは即 return、DropColumnも失敗時に return する形にそろえたいです。🔧 修正例
func SetupDB(db *gorm.DB) error { - err := db.AutoMigrate( + if err := db.AutoMigrate( &model.Guild{}, &model.Member{}, &model.AuditLogSetting{}, &model.BotSystemSetting{}, &model.PinSetting{}, &model.RSSSetting{}, &model.ScheduleSetting{}, &model.TTSConnection{}, &model.TTSPersonalSetting{}, &model.TTSDictionary{}, &model.RolePanel{}, &model.RolePanelOption{}, - ) + ); err != nil { + return err + } migrator := db.Migrator() if migrator.HasColumn(&model.TTSPersonalSetting{}, "speaker_seed") { - migrator.DropColumn(&model.TTSPersonalSetting{}, "speaker_seed") + if err := migrator.DropColumn(&model.TTSPersonalSetting{}, "speaker_seed"); err != nil { + return err + } } if migrator.HasColumn(&model.TTSPersonalSetting{}, "speaker_pitch") { - migrator.DropColumn(&model.TTSPersonalSetting{}, "speaker_pitch") + if err := migrator.DropColumn(&model.TTSPersonalSetting{}, "speaker_pitch"); err != nil { + return err + } } if migrator.HasColumn(&model.TTSPersonalSetting{}, "speed_scale") { - migrator.DropColumn(&model.TTSPersonalSetting{}, "speed_scale") + if err := migrator.DropColumn(&model.TTSPersonalSetting{}, "speed_scale"); err != nil { + return err + } } if migrator.HasColumn(&model.RolePanelOption{}, "role_id") { - migrator.DropColumn(&model.RolePanelOption{}, "role_id") + if err := migrator.DropColumn(&model.RolePanelOption{}, "role_id"); err != nil { + return err + } } - return err + return nil }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/internal/db/db.go` around lines 18 - 45, The migration currently swallows errors: call to db.AutoMigrate should immediately return the error if non-nil (i.e., if err := db.AutoMigrate(...) != nil { return err }) instead of continuing to cleanup, and each migration cleanup using migrator.HasColumn/migrator.DropColumn must check the error returned by DropColumn and return it when non-nil; update the logic around migrator.HasColumn, migrator.DropColumn and the final return so every DropColumn call's error is propagated (reuse a local err variable or assign/return the DropColumn result) rather than always returning the original err.
🧹 Nitpick comments (3)
src/internal/bot/command/server_management/rolepanel/create.go (1)
37-49:Options[0]へのアクセスでパニックの可能性Line 39で
i.ApplicationCommandData().Options[0].Optionsにアクセスしていますが、Optionsが空の場合にパニックが発生します。サブコマンド経由で呼ばれる想定なので通常は問題ありませんが、防御的にチェックを追加することを推奨します。🛡️ 防御的なnilチェックの追加
func Create(ctx *internal.BotContext, s *discordgo.Session, i *discordgo.InteractionCreate) { config := ctx.Config - options := i.ApplicationCommandData().Options[0].Options + cmdOptions := i.ApplicationCommandData().Options + if len(cmdOptions) == 0 || cmdOptions[0].Options == nil { + return + } + options := cmdOptions[0].Options var title, description string🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/internal/bot/command/server_management/rolepanel/create.go` around lines 37 - 49, The code in Create accesses i.ApplicationCommandData().Options[0].Options without checking for empty or nil, which can panic; update Create to defensively validate i.ApplicationCommandData() and its Options slice length before indexing (e.g., check that ApplicationCommandData() != nil and len(Options) > 0 and Options[0] != nil) and handle the error path by returning early or sending a user-facing error/ephemeral response rather than indexing; refer to Create, the local variable options, and the switch over opt.Name when adding the guard.src/internal/bot/messageComponent/rolepanel.go (2)
188-203: ロール操作失敗時のログ出力を改善できます
fmt.Printfでエラーをログ出力していますが、構造化ロギングを使用することでより良い運用監視が可能になります。また、ユーザーに一部のロール操作が失敗したことを通知することも検討してください。📝 エラーハンドリングの改善案
+var failedRoles []string for _, roleID := range panelRoleIDs { hasRole := currentRoles[roleID] shouldHaveRole := selectedRoleMap[roleID] if shouldHaveRole && !hasRole { if err := s.GuildMemberRoleAdd(i.GuildID, i.Member.User.ID, roleID); err != nil { - fmt.Printf("failed to add role %s to user %s in guild %s: %v\n", roleID, i.Member.User.ID, i.GuildID, err) + // TODO: 構造化ロギングの使用を検討 + failedRoles = append(failedRoles, roleID) continue }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/internal/bot/messageComponent/rolepanel.go` around lines 188 - 203, Replace the ad-hoc fmt.Printf error prints inside the role add/remove handling with structured logging and surface failures to the user; specifically, in the block using s.GuildMemberRoleAdd and s.GuildMemberRoleRemove (where addedRoles and removedRoles are appended) remove fmt.Printf and call the project's logger with contextual fields (guild ID, user ID, roleID, and the error) e.g., logger.WithFields(...).Errorf(...) or the equivalent logging helper used in this package, and optionally queue a concise ephemeral/user-facing notification indicating that some role operations failed so the caller sees partial failures.
36-41: インメモリストレージはボット再起動時にデータが失われます
rolePanelPendingAddsはインメモリのマップで管理されているため、ボット再起動時に保留中の追加操作がすべて失われます。TTLが10分と短いため許容範囲かもしれませんが、この動作を認識しておく必要があります。将来的に永続化が必要な場合は、Redisやデータベースへの移行を検討してください。
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/internal/bot/messageComponent/rolepanel.go` around lines 36 - 41, rolePanelPendingAdds(型 RolePanelPendingAdd を保持するインメモリ map)はボット再起動でデータを失うため、永続化ストレージに移す必要があります。修正方法:rolePanelPendingAdds を直接使っている箇所(参照する関数やメソッド)を抽象化してインターフェース(例: GetPendingAdd, SetPendingAdd, DeletePendingAdd, ListPendingAdds)を導入し、既存の sync.Mutex + map 実装をそのままの振る舞いでラップしたローカル実装と、Redis(または既存のDB)を使った永続実装の2つを用意して切り替え可能にすること。Redis 実装ではキーに TTL(10分)を設定し、RolePanelPendingAdd をシリアライズして保存・復元するように実装してください。最終的に rolePanelPendingAdds の直接参照を削除して新しいインターフェース経由でアクセスするように変更してください。
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/internal/bot/command/registry.go`:
- Around line 37-39: The registry entry for "rolepanel" currently lacks the
Ephemeral flag, which causes deferred responses to be public; update the
registry map entry for "rolepanel" (the map key "rolepanel" with Handler:
server_management.Rolepanel) to include Ephemeral: true so it matches the
handlers (rolepanel.go, create.go, add.go, remove.go, list.go, delete.go) that
set MessageFlagsEphemeral and ensures deferred responses are private.
In `@src/internal/bot/command/server_management/rolepanel.go`:
- Around line 38-65: The handlers are calling InteractionRespond although the
dispatcher already ACKed the interaction, causing double-response errors;
replace all calls to InteractionRespond in Rolepanel and the rolepanel/*
handlers (rolepanel.go and rolepanel/{add,delete,remove,create,list}.go) with a
post-ACK approach: either edit the original response using
s.InteractionResponseEdit(i.Interaction, ...) when you intend to update the
deferred response, or create a follow-up using
s.FollowupMessageCreate(i.Interaction, ...) for additional messages; ensure you
preserve the same InteractionResponseData content (embeds, flags, etc.) when
converting each InteractionRespond call.
In `@src/internal/bot/command/server_management/rolepanel/list.go`:
- Around line 72-102: The embed fields are built from panels into the local
variable fields and may exceed Discord's 25-field limit; update the loop in
list.go (where panels are iterated and fields appended) to enforce a maximum of
25 fields: stop appending after 25 and either add a final MessageEmbedField
noting "表示は最初の25件までです" (or similar) or implement pagination by returning only
the first page and providing controls to fetch subsequent pages; ensure the code
references the existing variables panels, fields, and the embed construction so
the truncation/notice logic happens before the embed is sent.
In `@src/internal/bot/messageComponent/rolepanel.go`:
- Around line 846-847: Handle the ignored error from repo.GetByMessageID in the
same way as HandleRolePanelAdd: capture the error returned when calling
repo.GetByMessageID(messageID), check if err != nil (or if panel == nil) and
return/log the error before calling UpdatePanelMessage(s, panel, config, nil) to
avoid a panic; update the code around the call to use the returned error value
(instead of discarding it with _), and ensure you exit early (or handle
recovery) when repo.GetByMessageID fails.
- Around line 501-504: repo.GetByMessageID's error is currently ignored which
can lead to a panic when panel is nil; update the call site to capture and
handle the returned error and ensure panel is non-nil before calling
UpdatePanelMessage (e.g., check err from repo.GetByMessageID and
return/log/handle it, and if panel == nil return an appropriate error or log and
skip UpdatePanelMessage), referencing the repo.GetByMessageID call, the panel
variable, messageID, and the subsequent UpdatePanelMessage invocation so the
code path safely aborts instead of dereferencing a nil panel.
- Around line 622-641: Deletion order bug: deleting the Discord message via
s.ChannelMessageDelete before removing the DB record with repo.DeleteByID can
leave an orphaned DB record if the DB delete fails. Change the sequence in the
handler around panelTitle/panel to call repo.DeleteByID(panel.ID) first and only
if that succeeds call s.ChannelMessageDelete(panel.ChannelID, panel.MessageID);
on DB failure respond via s.InteractionRespond with the existing error embed and
do not delete the message; preserve existing error handling and return early on
repo.DeleteByID error.
---
Outside diff comments:
In `@src/internal/db/db.go`:
- Around line 18-45: The migration currently swallows errors: call to
db.AutoMigrate should immediately return the error if non-nil (i.e., if err :=
db.AutoMigrate(...) != nil { return err }) instead of continuing to cleanup, and
each migration cleanup using migrator.HasColumn/migrator.DropColumn must check
the error returned by DropColumn and return it when non-nil; update the logic
around migrator.HasColumn, migrator.DropColumn and the final return so every
DropColumn call's error is propagated (reuse a local err variable or
assign/return the DropColumn result) rather than always returning the original
err.
---
Nitpick comments:
In `@src/internal/bot/command/server_management/rolepanel/create.go`:
- Around line 37-49: The code in Create accesses
i.ApplicationCommandData().Options[0].Options without checking for empty or nil,
which can panic; update Create to defensively validate
i.ApplicationCommandData() and its Options slice length before indexing (e.g.,
check that ApplicationCommandData() != nil and len(Options) > 0 and Options[0]
!= nil) and handle the error path by returning early or sending a user-facing
error/ephemeral response rather than indexing; refer to Create, the local
variable options, and the switch over opt.Name when adding the guard.
In `@src/internal/bot/messageComponent/rolepanel.go`:
- Around line 188-203: Replace the ad-hoc fmt.Printf error prints inside the
role add/remove handling with structured logging and surface failures to the
user; specifically, in the block using s.GuildMemberRoleAdd and
s.GuildMemberRoleRemove (where addedRoles and removedRoles are appended) remove
fmt.Printf and call the project's logger with contextual fields (guild ID, user
ID, roleID, and the error) e.g., logger.WithFields(...).Errorf(...) or the
equivalent logging helper used in this package, and optionally queue a concise
ephemeral/user-facing notification indicating that some role operations failed
so the caller sees partial failures.
- Around line 36-41: rolePanelPendingAdds(型 RolePanelPendingAdd を保持するインメモリ
map)はボット再起動でデータを失うため、永続化ストレージに移す必要があります。修正方法:rolePanelPendingAdds
を直接使っている箇所(参照する関数やメソッド)を抽象化してインターフェース(例: GetPendingAdd, SetPendingAdd,
DeletePendingAdd, ListPendingAdds)を導入し、既存の sync.Mutex + map
実装をそのままの振る舞いでラップしたローカル実装と、Redis(または既存のDB)を使った永続実装の2つを用意して切り替え可能にすること。Redis
実装ではキーに TTL(10分)を設定し、RolePanelPendingAdd をシリアライズして保存・復元するように実装してください。最終的に
rolePanelPendingAdds の直接参照を削除して新しいインターフェース経由でアクセスするように変更してください。
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 810d1c7b-a41a-4682-ae3b-ac3cd1f8a5ea
📒 Files selected for processing (13)
.gitignoresrc/internal/bot/command/commands.gosrc/internal/bot/command/registry.gosrc/internal/bot/command/server_management/rolepanel.gosrc/internal/bot/command/server_management/rolepanel/add.gosrc/internal/bot/command/server_management/rolepanel/create.gosrc/internal/bot/command/server_management/rolepanel/delete.gosrc/internal/bot/command/server_management/rolepanel/list.gosrc/internal/bot/command/server_management/rolepanel/remove.gosrc/internal/bot/messageComponent/rolepanel.gosrc/internal/db/db.gosrc/internal/model/rolepanel.gosrc/internal/repository/rolepanel.go
| "rolepanel": { | ||
| Handler: server_management.Rolepanel, | ||
| }, |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n[レジストリ]\n'
rg -n -C2 '"rolepanel":|Ephemeral' src/internal/bot/command/registry.go
printf '\n[rolepanel 側のエフェメラル指定]\n'
rg -n -C1 'MessageFlagsEphemeral' \
src/internal/bot/command/server_management/rolepanel.go \
src/internal/bot/command/server_management/rolepanelRepository: UniPro-tech/UniBot
Length of output: 4773
🏁 Script executed:
cat -n src/internal/bot/handler/interaction.go | head -60Repository: UniPro-tech/UniBot
Length of output: 2271
rolepanel は Ephemeral: true にそろえた方がよいです。
src/internal/bot/handler/interaction.go:29-32 で deferred response にレジストリの Ephemeral フラグが反映されるため、現在の rolepanel 登録ではデファード応答が公開扱いになります。一方、rolepanel 配下のすべてのハンドラ (rolepanel.go、create.go、add.go、remove.go、list.go、delete.go) は応答時に MessageFlagsEphemeral を指定しており、プライベート前提の運用です。登録側と実装側のエフェメラル指定を統一するため、レジストリ側に Ephemeral: true を追加してください。
🔧 修正例
"rolepanel": {
- Handler: server_management.Rolepanel,
+ Handler: server_management.Rolepanel,
+ Ephemeral: true,
},📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "rolepanel": { | |
| Handler: server_management.Rolepanel, | |
| }, | |
| "rolepanel": { | |
| Handler: server_management.Rolepanel, | |
| Ephemeral: true, | |
| }, |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/internal/bot/command/registry.go` around lines 37 - 39, The registry
entry for "rolepanel" currently lacks the Ephemeral flag, which causes deferred
responses to be public; update the registry map entry for "rolepanel" (the map
key "rolepanel" with Handler: server_management.Rolepanel) to include Ephemeral:
true so it matches the handlers (rolepanel.go, create.go, add.go, remove.go,
list.go, delete.go) that set MessageFlagsEphemeral and ensures deferred
responses are private.
| func Rolepanel(ctx *internal.BotContext, s *discordgo.Session, i *discordgo.InteractionCreate) { | ||
| config := ctx.Config | ||
| subCommand := i.ApplicationCommandData().Options[0] | ||
|
|
||
| if handler, exists := rolepanelHandler[subCommand.Name]; exists { | ||
| handler(ctx, s, i) | ||
| return | ||
| } | ||
|
|
||
| _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ | ||
| Type: discordgo.InteractionResponseChannelMessageWithSource, | ||
| Data: &discordgo.InteractionResponseData{ | ||
| Embeds: []*discordgo.MessageEmbed{ | ||
| { | ||
| Title: "エラー", | ||
| Description: "不明なサブコマンドです。", | ||
| Color: config.Colors.Error, | ||
| Footer: &discordgo.MessageEmbedFooter{ | ||
| Text: "Requested by " + i.Member.DisplayName(), | ||
| IconURL: i.Member.AvatarURL(""), | ||
| }, | ||
| Timestamp: time.Now().Format(time.RFC3339), | ||
| }, | ||
| }, | ||
| Flags: discordgo.MessageFlagsEphemeral, | ||
| }, | ||
| }) | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n[dispatcher の ACK 順序]\n'
rg -n -C3 'InteractionRespond|entry\.Handler\(ctx, s, i\)' \
src/internal/bot/handler/interaction.go
printf '\n[rolepanel 配下の InteractionRespond 呼び出し]\n'
rg -n -C1 'InteractionRespond\s*\(' \
src/internal/bot/command/server_management/rolepanel.go \
src/internal/bot/command/server_management/rolepanelRepository: UniPro-tech/UniBot
Length of output: 6411
rolepanel コマンド群は dispatcher による ACK 後に InteractionRespond を呼び出しており、双重応答エラーが発生します。
src/internal/bot/handler/interaction.go:38 で既に ACK を返してから entry.Handler(ctx, s, i) で handler を呼んでいるため、rolepanel.go:47 および rolepanel/{add,delete,remove,create,list}.go 内の InteractionRespond 呼び出しはすべて双重応答になります。Discord API は同一 interaction に対する応答は 1 回のみ許可しているため、このままでは成功系・エラー系ともに応答失敗となります。original response の edit または followup message へ統一してください。
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/internal/bot/command/server_management/rolepanel.go` around lines 38 -
65, The handlers are calling InteractionRespond although the dispatcher already
ACKed the interaction, causing double-response errors; replace all calls to
InteractionRespond in Rolepanel and the rolepanel/* handlers (rolepanel.go and
rolepanel/{add,delete,remove,create,list}.go) with a post-ACK approach: either
edit the original response using s.InteractionResponseEdit(i.Interaction, ...)
when you intend to update the deferred response, or create a follow-up using
s.FollowupMessageCreate(i.Interaction, ...) for additional messages; ensure you
preserve the same InteractionResponseData content (embeds, flags, etc.) when
converting each InteractionRespond call.
| var fields []*discordgo.MessageEmbedField | ||
| for _, panel := range panels { | ||
| var roles []string | ||
| roleIDsByKey, err := loadPanelRoleIDs(s, panel) | ||
| if err != nil { | ||
| fields = append(fields, &discordgo.MessageEmbedField{ | ||
| Name: fmt.Sprintf("%s (ID: %s)", panel.Title, panel.MessageID), | ||
| Value: fmt.Sprintf("チャンネル: <#%s>\nロール: 取得失敗", panel.ChannelID), | ||
| Inline: false, | ||
| }) | ||
| continue | ||
| } | ||
|
|
||
| for _, opt := range panel.Options { | ||
| roleID, ok := roleIDsByKey[opt.OptionKey] | ||
| if ok { | ||
| roles = append(roles, fmt.Sprintf("<@&%s>", roleID)) | ||
| } | ||
| } | ||
|
|
||
| roleList := "なし" | ||
| if len(roles) > 0 { | ||
| roleList = strings.Join(roles, ", ") | ||
| } | ||
|
|
||
| fields = append(fields, &discordgo.MessageEmbedField{ | ||
| Name: fmt.Sprintf("%s (ID: %s)", panel.Title, panel.MessageID), | ||
| Value: fmt.Sprintf("チャンネル: <#%s>\nロール: %s", panel.ChannelID, roleList), | ||
| Inline: false, | ||
| }) | ||
| } |
There was a problem hiding this comment.
Discord埋め込みのフィールド数上限に注意
Discordの埋め込みは最大25フィールドまでという制限があります。サーバーに25個以上のロールパネルがある場合、一部のパネルが表示されない可能性があります。ページネーションの実装、または最初の25件のみ表示する旨のメッセージを追加することを検討してください。
🔧 フィールド数の上限チェック追加案
var fields []*discordgo.MessageEmbedField
+ const maxFields = 25
for _, panel := range panels {
+ if len(fields) >= maxFields {
+ break
+ }
var roles []string
roleIDsByKey, err := loadPanelRoleIDs(s, panel)Description部分に超過した場合のメッセージを追加:
- Description: fmt.Sprintf("このサーバーには %d 個のロールパネルがあります。", len(panels)),
+ description := fmt.Sprintf("このサーバーには %d 個のロールパネルがあります。", len(panels))
+ if len(panels) > maxFields {
+ description += fmt.Sprintf("\n(最初の %d 件を表示しています)", maxFields)
+ }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/internal/bot/command/server_management/rolepanel/list.go` around lines 72
- 102, The embed fields are built from panels into the local variable fields and
may exceed Discord's 25-field limit; update the loop in list.go (where panels
are iterated and fields appended) to enforce a maximum of 25 fields: stop
appending after 25 and either add a final MessageEmbedField noting
"表示は最初の25件までです" (or similar) or implement pagination by returning only the first
page and providing controls to fetch subsequent pages; ensure the code
references the existing variables panels, fields, and the embed construction so
the truncation/notice logic happens before the embed is sent.
| panel, _ = repo.GetByMessageID(messageID) | ||
| if err := UpdatePanelMessage(s, panel, config, map[string]string{ | ||
| optionKey: pendingAdd.RoleID, | ||
| }); err != nil { |
There was a problem hiding this comment.
エラーが無視されています
repo.GetByMessageID のエラーが無視されており、panel が nil の場合、続く UpdatePanelMessage でパニックが発生する可能性があります。
🐛 エラーハンドリングの追加
- panel, _ = repo.GetByMessageID(messageID)
+ panel, err = repo.GetByMessageID(messageID)
+ if err != nil || panel == nil {
+ _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
+ Type: discordgo.InteractionResponseUpdateMessage,
+ Data: &discordgo.InteractionResponseData{
+ Embeds: []*discordgo.MessageEmbed{
+ {
+ Title: "エラー",
+ Description: "パネル情報の再取得に失敗しました。",
+ Color: config.Colors.Error,
+ Timestamp: time.Now().Format(time.RFC3339),
+ },
+ },
+ Components: []discordgo.MessageComponent{},
+ },
+ })
+ return
+ }
if err := UpdatePanelMessage(s, panel, config, map[string]string{📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| panel, _ = repo.GetByMessageID(messageID) | |
| if err := UpdatePanelMessage(s, panel, config, map[string]string{ | |
| optionKey: pendingAdd.RoleID, | |
| }); err != nil { | |
| panel, err = repo.GetByMessageID(messageID) | |
| if err != nil || panel == nil { | |
| _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ | |
| Type: discordgo.InteractionResponseUpdateMessage, | |
| Data: &discordgo.InteractionResponseData{ | |
| Embeds: []*discordgo.MessageEmbed{ | |
| { | |
| Title: "エラー", | |
| Description: "パネル情報の再取得に失敗しました。", | |
| Color: config.Colors.Error, | |
| Timestamp: time.Now().Format(time.RFC3339), | |
| }, | |
| }, | |
| Components: []discordgo.MessageComponent{}, | |
| }, | |
| }) | |
| return | |
| } | |
| if err := UpdatePanelMessage(s, panel, config, map[string]string{ | |
| optionKey: pendingAdd.RoleID, | |
| }); err != nil { |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/internal/bot/messageComponent/rolepanel.go` around lines 501 - 504,
repo.GetByMessageID's error is currently ignored which can lead to a panic when
panel is nil; update the call site to capture and handle the returned error and
ensure panel is non-nil before calling UpdatePanelMessage (e.g., check err from
repo.GetByMessageID and return/log/handle it, and if panel == nil return an
appropriate error or log and skip UpdatePanelMessage), referencing the
repo.GetByMessageID call, the panel variable, messageID, and the subsequent
UpdatePanelMessage invocation so the code path safely aborts instead of
dereferencing a nil panel.
| panelTitle := panel.Title | ||
| _ = s.ChannelMessageDelete(panel.ChannelID, panel.MessageID) | ||
|
|
||
| if err := repo.DeleteByID(panel.ID); err != nil { | ||
| _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ | ||
| Type: discordgo.InteractionResponseUpdateMessage, | ||
| Data: &discordgo.InteractionResponseData{ | ||
| Embeds: []*discordgo.MessageEmbed{ | ||
| { | ||
| Title: "エラー", | ||
| Description: "パネルの削除中にエラーが発生しました。", | ||
| Color: config.Colors.Error, | ||
| Timestamp: time.Now().Format(time.RFC3339), | ||
| }, | ||
| }, | ||
| Components: []discordgo.MessageComponent{}, | ||
| }, | ||
| }) | ||
| return | ||
| } |
There was a problem hiding this comment.
削除順序により不整合が発生する可能性
Line 623でメッセージを削除した後、Line 625でDBレコードを削除しています。DB削除が失敗した場合、メッセージは既に削除されているため、孤立したDBレコードが残ります。
順序を逆にする(先にDBを削除、その後メッセージを削除)ことで、DBレコードが残る方向の一貫性を保つことができます。
💡 削除順序の変更案
panelTitle := panel.Title
- _ = s.ChannelMessageDelete(panel.ChannelID, panel.MessageID)
if err := repo.DeleteByID(panel.ID); err != nil {
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
// ... error response
})
return
}
+ // DBレコード削除成功後にメッセージを削除
+ _ = s.ChannelMessageDelete(panel.ChannelID, panel.MessageID)
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/internal/bot/messageComponent/rolepanel.go` around lines 622 - 641,
Deletion order bug: deleting the Discord message via s.ChannelMessageDelete
before removing the DB record with repo.DeleteByID can leave an orphaned DB
record if the DB delete fails. Change the sequence in the handler around
panelTitle/panel to call repo.DeleteByID(panel.ID) first and only if that
succeeds call s.ChannelMessageDelete(panel.ChannelID, panel.MessageID); on DB
failure respond via s.InteractionRespond with the existing error embed and do
not delete the message; preserve existing error handling and return early on
repo.DeleteByID error.
| panel, _ = repo.GetByMessageID(messageID) | ||
| if err := UpdatePanelMessage(s, panel, config, nil); err != nil { |
There was a problem hiding this comment.
同様のエラー無視の問題
HandleRolePanelAdd と同様に、repo.GetByMessageID のエラーが無視されています。panel が nil の場合に UpdatePanelMessage でパニックが発生します。
🐛 エラーハンドリングの追加
- panel, _ = repo.GetByMessageID(messageID)
+ panel, err = repo.GetByMessageID(messageID)
+ if err != nil || panel == nil {
+ _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
+ Type: discordgo.InteractionResponseUpdateMessage,
+ Data: &discordgo.InteractionResponseData{
+ Embeds: []*discordgo.MessageEmbed{
+ {
+ Title: "エラー",
+ Description: "パネル情報の再取得に失敗しました。",
+ Color: config.Colors.Error,
+ Timestamp: time.Now().Format(time.RFC3339),
+ },
+ },
+ Components: []discordgo.MessageComponent{},
+ },
+ })
+ return
+ }
if err := UpdatePanelMessage(s, panel, config, nil); err != nil {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| panel, _ = repo.GetByMessageID(messageID) | |
| if err := UpdatePanelMessage(s, panel, config, nil); err != nil { | |
| panel, err = repo.GetByMessageID(messageID) | |
| if err != nil || panel == nil { | |
| _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ | |
| Type: discordgo.InteractionResponseUpdateMessage, | |
| Data: &discordgo.InteractionResponseData{ | |
| Embeds: []*discordgo.MessageEmbed{ | |
| { | |
| Title: "エラー", | |
| Description: "パネル情報の再取得に失敗しました。", | |
| Color: config.Colors.Error, | |
| Timestamp: time.Now().Format(time.RFC3339), | |
| }, | |
| }, | |
| Components: []discordgo.MessageComponent{}, | |
| }, | |
| }) | |
| return | |
| } | |
| if err := UpdatePanelMessage(s, panel, config, nil); err != nil { |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/internal/bot/messageComponent/rolepanel.go` around lines 846 - 847,
Handle the ignored error from repo.GetByMessageID in the same way as
HandleRolePanelAdd: capture the error returned when calling
repo.GetByMessageID(messageID), check if err != nil (or if panel == nil) and
return/log the error before calling UpdatePanelMessage(s, panel, config, nil) to
avoid a panic; update the code around the call to use the returned error value
(instead of discarding it with _), and ensure you exit early (or handle
recovery) when repo.GetByMessageID fails.
This pull request introduces a new "role panel" feature for server management in the Discord bot, allowing admins to create, list, add roles to, remove roles from, and delete role panels via the
/rolepanelcommand. It adds the necessary command registration, handler logic, database models, and subcommand implementations to support interactive role management through Discord's UI.The most important changes are:
Role Panel Feature Implementation:
rolepanelcommand under server management, with subcommands for creating, deleting, adding, removing, and listing role panels. This includes command registration incommands.goand handler mapping inregistry.go. [1] [2] [3] [4] [5]create.gofor panel creation,delete.gofor deletion,add.gofor adding roles,remove.gofor removing roles, andlist.gofor listing all panels in a server. Each subcommand provides user feedback and interactive selection via Discord embeds and select menus. [1] [2] [3] [4] [5]Database Model and Migration:
RolePanelandRolePanelOptionto represent panels and their selectable roles, and updated the database setup to auto-migrate these models. [1] [2]These changes enable server admins to manage self-assignable roles through a user-friendly panel system, improving the bot's role management capabilities.
Summary by CodeRabbit
New Features
Chores