Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,4 @@ go.work.sum
# Scripts included sensitive information
scripts/build.sh
docker-compose.prod.yaml
docker-compose.yaml
2 changes: 2 additions & 0 deletions src/internal/bot/command/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package command
import (
"unibot/internal/bot/command/admin"
"unibot/internal/bot/command/general"
"unibot/internal/bot/command/server_management"

"github.com/bwmarrin/discordgo"
)
Expand All @@ -14,4 +15,5 @@ var Commands = []*discordgo.ApplicationCommand{
general.LoadTtsCommandContext(),
general.LoadHelpCommandContext(),
admin.LoadMaintenanceCommandContext(),
server_management.LoadRolepanelCommandContext(),
}
4 changes: 4 additions & 0 deletions src/internal/bot/command/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"unibot/internal"
"unibot/internal/bot/command/admin"
"unibot/internal/bot/command/general"
"unibot/internal/bot/command/server_management"

"github.com/bwmarrin/discordgo"
)
Expand Down Expand Up @@ -33,4 +34,7 @@ var Handlers = map[string]HandlerEntry{
"colorcode": {
Handler: general.ColorCode,
},
"rolepanel": {
Handler: server_management.Rolepanel,
},
Comment on lines +37 to +39
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 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/rolepanel

Repository: UniPro-tech/UniBot

Length of output: 4773


🏁 Script executed:

cat -n src/internal/bot/handler/interaction.go | head -60

Repository: UniPro-tech/UniBot

Length of output: 2271


rolepanelEphemeral: true にそろえた方がよいです。

src/internal/bot/handler/interaction.go:29-32 で deferred response にレジストリの Ephemeral フラグが反映されるため、現在の rolepanel 登録ではデファード応答が公開扱いになります。一方、rolepanel 配下のすべてのハンドラ (rolepanel.gocreate.goadd.goremove.golist.godelete.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.

Suggested change
"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.

}
65 changes: 65 additions & 0 deletions src/internal/bot/command/server_management/rolepanel.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package server_management

import (
"time"
"unibot/internal"
"unibot/internal/bot/command/server_management/rolepanel"

"github.com/bwmarrin/discordgo"
)

func LoadRolepanelCommandContext() *discordgo.ApplicationCommand {
return &discordgo.ApplicationCommand{
Name: "rolepanel",
Description: "ロールパネルを管理します",
DefaultMemberPermissions: ptrInt64(discordgo.PermissionManageRoles),
Options: []*discordgo.ApplicationCommandOption{
rolepanel.LoadCreateCommandContext(),
rolepanel.LoadDeleteCommandContext(),
rolepanel.LoadAddCommandContext(),
rolepanel.LoadRemoveCommandContext(),
rolepanel.LoadListCommandContext(),
},
}
}

func ptrInt64(i int64) *int64 {
return &i
}

var rolepanelHandler = map[string]func(ctx *internal.BotContext, s *discordgo.Session, i *discordgo.InteractionCreate){
"create": rolepanel.Create,
"delete": rolepanel.Delete,
"add": rolepanel.Add,
"remove": rolepanel.Remove,
"list": rolepanel.List,
}

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,
},
})
}
Comment on lines +38 to +65
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 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/rolepanel

Repository: 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.

187 changes: 187 additions & 0 deletions src/internal/bot/command/server_management/rolepanel/add.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
package rolepanel

import (
"fmt"
"time"
"unibot/internal"
"unibot/internal/bot/messageComponent"
"unibot/internal/repository"

"github.com/bwmarrin/discordgo"
)

func LoadAddCommandContext() *discordgo.ApplicationCommandOption {
return &discordgo.ApplicationCommandOption{
Type: discordgo.ApplicationCommandOptionSubCommand,
Name: "add",
Description: "ロールパネルにロールを追加します",
Options: []*discordgo.ApplicationCommandOption{
{
Type: discordgo.ApplicationCommandOptionRole,
Name: "role",
Description: "追加するロール",
Required: true,
},
{
Type: discordgo.ApplicationCommandOptionString,
Name: "label",
Description: "セレクトメニューに表示するラベル",
Required: true,
MaxLength: 100,
},
{
Type: discordgo.ApplicationCommandOptionString,
Name: "description",
Description: "ロールの説明",
Required: false,
MaxLength: 100,
},
{
Type: discordgo.ApplicationCommandOptionString,
Name: "emoji",
Description: "絵文字 (例: 🎮)",
Required: false,
Comment thread
ysmreg marked this conversation as resolved.
MaxLength: 100,
},
},
}
}

func Add(ctx *internal.BotContext, s *discordgo.Session, i *discordgo.InteractionCreate) {
config := ctx.Config
options := i.ApplicationCommandData().Options[0].Options

var roleID, label, description, emoji string
for _, opt := range options {
switch opt.Name {
case "role":
roleID = opt.RoleValue(s, i.GuildID).ID
case "label":
label = opt.StringValue()
case "description":
description = opt.StringValue()
case "emoji":
emoji = opt.StringValue()
}
}

repo := repository.NewRolePanelRepository(ctx.DB)

// このギルドのパネル一覧を取得
panels, err := repo.ListByGuild(i.GuildID)
if err != nil {
_ = 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,
},
})
return
}

if len(panels) == 0 {
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Embeds: []*discordgo.MessageEmbed{
{
Title: "エラー",
Description: "このサーバーにはロールパネルがありません。\n先に `/rolepanel create` でパネルを作成してください。",
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,
},
})
return
}

// パネル選択用セレクトメニューを作成
var selectOptions []discordgo.SelectMenuOption
for _, panel := range panels {
selectOptions = append(selectOptions, discordgo.SelectMenuOption{
Label: panel.Title,
Value: panel.MessageID,
Description: fmt.Sprintf("%d個のロール", len(panel.Options)),
})
}

token, err := messageComponent.SaveRolePanelPendingAdd(messageComponent.RolePanelPendingAdd{
UserID: i.Member.User.ID,
GuildID: i.GuildID,
RoleID: roleID,
Label: label,
Description: description,
Emoji: emoji,
})
if err != nil {
_ = 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,
},
})
return
}

customID := "rolepanel_add_" + token

_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Embeds: []*discordgo.MessageEmbed{
{
Title: "パネルを選択",
Description: fmt.Sprintf("ロール <@&%s> を追加するパネルを選択してください。", roleID),
Color: config.Colors.Primary,
Footer: &discordgo.MessageEmbedFooter{
Text: "Requested by " + i.Member.DisplayName(),
IconURL: i.Member.AvatarURL(""),
},
Timestamp: time.Now().Format(time.RFC3339),
},
},
Components: []discordgo.MessageComponent{
discordgo.ActionsRow{
Components: []discordgo.MessageComponent{
discordgo.SelectMenu{
CustomID: customID,
Placeholder: "パネルを選択...",
Options: selectOptions,
},
},
},
},
Flags: discordgo.MessageFlagsEphemeral,
},
})
}
Loading