From f573f8870107e538eca2324692f303a358b4f60a Mon Sep 17 00:00:00 2001 From: biast12 Date: Sat, 11 Apr 2026 17:10:23 +0200 Subject: [PATCH 01/19] Refactor permission checks for thread/channel modes Make permission checking mode-aware and group missing perms by location. - Adjust server-wide permission list to depend on settings.UseThreads to avoid reporting irrelevant missing permissions. Append standard permissions after mode-specific ones. - In panel permission checks, treat a panel as using threads if either global settings or the panel enable threads. Use logic.ThreadPermissions for thread-mode channel checks and simplify assembled permission slices. - Add ThreadPermissions constant in bot/logic/discordpermissions.go for reuse. - Rewrite findMissingPermissions to return labeled missing-permission groups (panel channel, notification channel, transcript channel) instead of a flat list; check primary channel, optional notification channel (thread mode), and transcript channel, using channel-level and guild-level checks as appropriate. - Update reply formatting to render missing permissions grouped by location with a title and footer linking docs. These changes reduce false positives, centralize thread-mode permissions, and provide clearer error messages to users about where permissions are missing. --- .../admindebug/server/modals/permissions.go | 45 ++++--- bot/command/context/replyable.go | 114 +++++++++++------- bot/logic/discordpermissions.go | 12 ++ 3 files changed, 105 insertions(+), 66 deletions(-) diff --git a/bot/button/handlers/admindebug/server/modals/permissions.go b/bot/button/handlers/admindebug/server/modals/permissions.go index 1bd26099..c6250387 100644 --- a/bot/button/handlers/admindebug/server/modals/permissions.go +++ b/bot/button/handlers/admindebug/server/modals/permissions.go @@ -152,23 +152,25 @@ func (h *AdminDebugServerPermissionsModalSubmitHandler) Execute(ctx *context.Mod } func processPermissionChecks(selectedValues []string, worker *w.Context, guildId uint64, botMember member.Member, settings database.Settings, panels []database.Panel) ([]string, bool) { - // Server-wide permissions - serverWidePermissions := append( - []permission.Permission{ - // Thread mode specific + // Server-wide permissions depend on the active mode so we don't report + // false failures for permissions that are irrelevant to the current mode + serverWidePermissions := []permission.Permission{ + // Required in both modes + permission.ManageWebhooks, + permission.PinMessages, + // Server-wide only + permission.ManageRoles, + } + if settings.UseThreads { + serverWidePermissions = append(serverWidePermissions, permission.CreatePrivateThreads, permission.SendMessagesInThreads, permission.ManageThreads, - // Channel mode specific - permission.ManageChannels, - // Both modes - permission.ManageWebhooks, - permission.PinMessages, - // Server-wide only - permission.ManageRoles, - }, - logic.StandardPermissions[:]..., - ) + ) + } else { + serverWidePermissions = append(serverWidePermissions, permission.ManageChannels) + } + serverWidePermissions = append(serverWidePermissions, logic.StandardPermissions[:]...) var results []string var hasMissingPermissions bool @@ -256,22 +258,17 @@ func checkPanelPermissions(worker *w.Context, guildId uint64, botMember member.M var hasMissingPermissions bool // Determine if this panel uses threads or channels - usesThreads := panel.UseThreads + usesThreads := settings.UseThreads || panel.UseThreads // Check panel channel permissions (if panel has a channel) if panel.ChannelId != 0 { var panelChannelPerms []permission.Permission if usesThreads { - // Thread mode: specific thread permissions + standard permissions + // Thread mode: permissions needed in the panel channel where threads are created. panelChannelPerms = append( - []permission.Permission{ - permission.CreatePrivateThreads, - permission.SendMessagesInThreads, - permission.ManageThreads, - permission.ManageWebhooks, - permission.PinMessages, - }, - logic.StandardPermissions[:]..., + logic.ThreadPermissions[:], + permission.ManageWebhooks, + permission.PinMessages, ) } else { // Channel mode: just standard permissions (no special ones needed for panel channel in channel mode) diff --git a/bot/command/context/replyable.go b/bot/command/context/replyable.go index 0e14f07f..88a56593 100644 --- a/bot/command/context/replyable.go +++ b/bot/command/context/replyable.go @@ -212,15 +212,21 @@ func (r *Replyable) buildErrorResponse(err error, eventId string, includeInviteL interactionCtx, ok := r.ctx.(registry.InteractionContext) docsUrl := fmt.Sprintf("%s/miscellaneous/permissions-explained", config.Conf.Bot.DocsUrl) if ok { - missingPermissions, err := findMissingPermissions(interactionCtx) + missingByLocation, err := findMissingPermissions(interactionCtx) if err == nil { - if len(missingPermissions) > 0 { - message = r.GetMessage(i18n.MessageErrorMissingPermissionsTitle) + ":\n" - for _, perm := range missingPermissions { - message += fmt.Sprintf("* `%s`\n", perm.String()) + if len(missingByLocation) > 0 { + title := r.GetMessage(i18n.MessageErrorMissingPermissionsTitle) + ":\n" + footer := "\n" + r.GetMessage(i18n.MessageErrorMissingPermissionsBody, docsUrl) + + var locationMsg string + for _, loc := range missingByLocation { + locationMsg += fmt.Sprintf("**%s:**\n", loc.label) + for _, perm := range loc.missing { + locationMsg += fmt.Sprintf("* `%s`\n", perm.String()) + } } - message += "\n" + r.GetMessage(i18n.MessageErrorMissingPermissionsBody, docsUrl) + message = title + locationMsg + footer } else { message = r.GetMessage(i18n.MessageErrorMissingAccess, docsUrl) } @@ -308,16 +314,19 @@ func (r *Replyable) formatDiscordError(restError request.RestError, eventId stri r.GetMessage(i18n.MessageErrorId) + ": `" + eventId + "`" } -func findMissingPermissions(ctx registry.InteractionContext) ([]permission.Permission, error) { +type missingPermLocation struct { + label string + missing []permission.Permission +} + +func findMissingPermissions(ctx registry.InteractionContext) ([]missingPermLocation, error) { if permission.HasPermissionRaw(ctx.InteractionMetadata().AppPermissions, permission.Administrator) { return nil, nil } var useThreads bool - var targetChannelId uint64 - - settings, err := ctx.Settings() - if err == nil { + settings, settingsErr := ctx.Settings() + if settingsErr == nil { useThreads = settings.UseThreads } @@ -326,55 +335,76 @@ func findMissingPermissions(ctx registry.InteractionContext) ([]permission.Permi p, panelExists, err := dbclient.Client.Panel.GetByCustomId(context.Background(), ctx.GuildId(), btnCtx.InteractionData.CustomId) if err == nil && panelExists { panel = &p - // Panel can enable threads if global setting is disabled if !useThreads { useThreads = panel.UseThreads } } } + checkChannel := func(channelId uint64, label string, required []permission.Permission) missingPermLocation { + missing := permissionwrapper.GetMissingPermissionsChannel(ctx.Worker(), ctx.GuildId(), ctx.Worker().BotId, channelId, required...) + return missingPermLocation{label: label, missing: missing} + } + + var locations []missingPermLocation + + // 1. Primary location: panel channel (thread mode) or ticket category (channel mode) + var primaryChannelId uint64 + var primaryLabel string + var primaryRequired []permission.Permission + if useThreads { - // Thread mode - check permissions in the current channel - targetChannelId = ctx.ChannelId() + primaryChannelId = ctx.ChannelId() + primaryLabel = "Panel channel" + primaryRequired = logic.ThreadPermissions[:] } else { - // Channel mode - check permissions in the ticket category + primaryLabel = "Ticket category" + primaryRequired = append([]permission.Permission{permission.ManageChannels}, logic.StandardPermissions[:]...) if panel != nil && panel.TargetCategory != 0 { - // Use panel's target category - targetChannelId = panel.TargetCategory + primaryChannelId = panel.TargetCategory } else { - // Fall back to guild default category - targetChannelId, _ = dbclient.Client.ChannelCategory.Get(context.Background(), ctx.GuildId()) + primaryChannelId, _ = dbclient.Client.ChannelCategory.Get(context.Background(), ctx.GuildId()) } } - // Build required permissions based on mode - var requiredPermissions []permission.Permission - if useThreads { - // Thread mode permissions - requiredPermissions = append( - []permission.Permission{ - permission.CreatePrivateThreads, - permission.SendMessagesInThreads, - permission.ManageThreads, - }, - logic.StandardPermissions[:]..., - ) + if primaryChannelId != 0 { + loc := checkChannel(primaryChannelId, primaryLabel, primaryRequired) + if len(loc.missing) > 0 { + locations = append(locations, loc) + } } else { - // Channel mode permissions - requiredPermissions = append( - []permission.Permission{ - permission.ManageChannels, - }, - logic.StandardPermissions[:]..., - ) + missing := permissionwrapper.GetMissingPermissions(ctx.Worker(), ctx.GuildId(), ctx.Worker().BotId, primaryRequired...) + if len(missing) > 0 { + locations = append(locations, missingPermLocation{label: primaryLabel, missing: missing}) + } + } + + // 2. Notification channel (thread mode only) + if useThreads { + var notifChannelId *uint64 + if panel != nil && panel.TicketNotificationChannel != nil { + notifChannelId = panel.TicketNotificationChannel + } else if settingsErr == nil && settings.TicketNotificationChannel != nil { + notifChannelId = settings.TicketNotificationChannel + } + if notifChannelId != nil { + notifRequired := append(logic.MinimalPermissions[:], permission.EmbedLinks, permission.AttachFiles) + loc := checkChannel(*notifChannelId, "Notification channel", notifRequired) + if len(loc.missing) > 0 { + locations = append(locations, loc) + } + } } - if targetChannelId != 0 { - return permissionwrapper.GetMissingPermissionsChannel(ctx.Worker(), ctx.GuildId(), ctx.Worker().BotId, targetChannelId, requiredPermissions...), nil + // 3. Transcript channel (panel-level, both modes) + if panel != nil && panel.TranscriptChannelId != nil { + loc := checkChannel(*panel.TranscriptChannelId, "Transcript channel", logic.MinimalPermissions[:]) + if len(loc.missing) > 0 { + locations = append(locations, loc) + } } - // If no target channel, just return guild-level missing permissions - return permissionwrapper.GetMissingPermissions(ctx.Worker(), ctx.GuildId(), ctx.Worker().BotId, requiredPermissions...), nil + return locations, nil } func formatTimestamp(seconds float64) string { diff --git a/bot/logic/discordpermissions.go b/bot/logic/discordpermissions.go index 619a4349..fd22fb70 100644 --- a/bot/logic/discordpermissions.go +++ b/bot/logic/discordpermissions.go @@ -37,6 +37,18 @@ var MinimalPermissions = [...]permission.Permission{ permission.UseApplicationCommands, } +// ThreadPermissions are the permissions the bot needs in the panel channel when operating in thread mode +var ThreadPermissions = [...]permission.Permission{ + permission.ViewChannel, + permission.ReadMessageHistory, + permission.EmbedLinks, + permission.AttachFiles, + permission.UseExternalEmojis, + permission.CreatePrivateThreads, + permission.SendMessagesInThreads, + permission.ManageThreads, +} + func BuildUserOverwrite(userId uint64, additionalPermissions database.TicketPermissions) channel.PermissionOverwrite { allow := MinimalPermissions[:] var deny []permission.Permission From baf5436de303e27b8fd57e5b53014e77d1b23237 Mon Sep 17 00:00:00 2001 From: biast12 Date: Sun, 12 Apr 2026 12:48:43 +0200 Subject: [PATCH 02/19] Switch to common botpermissions package Replace local permission constants/usages with the centralized github.com/TicketsBot-cloud/common/botpermissions package across the codebase. Updated imports and calls to BuildPermissions to use botpermissions.StandardPermissions, MinimalPermissions, ThreadModeRequired, NotifChannelRequired, TranscriptChannelRequired, and ChannelModeRequired where appropriate. Removed duplicate permission arrays from bot/logic/discordpermissions.go and adjusted code to copy/append permission slices safely. Also enabled a local replace for the common module in go.mod. This centralizes permission definitions and reduces duplication. --- bot/button/handlers/addadmin.go | 4 +- bot/button/handlers/addsupport.go | 4 +- .../admindebug/server/modals/permissions.go | 14 +++---- bot/button/handlers/unclaim.go | 3 +- bot/command/context/replyable.go | 10 ++--- bot/command/impl/tickets/remove.go | 6 +-- bot/command/impl/tickets/unclaim.go | 3 +- bot/logic/claim.go | 9 +++-- bot/logic/discordpermissions.go | 40 ++----------------- bot/logic/open.go | 9 ++--- go.mod | 2 +- 11 files changed, 36 insertions(+), 68 deletions(-) diff --git a/bot/button/handlers/addadmin.go b/bot/button/handlers/addadmin.go index 13b31c2c..d3bb9110 100644 --- a/bot/button/handlers/addadmin.go +++ b/bot/button/handlers/addadmin.go @@ -19,7 +19,7 @@ import ( "github.com/TicketsBot-cloud/worker/bot/command/context" "github.com/TicketsBot-cloud/worker/bot/customisation" "github.com/TicketsBot-cloud/worker/bot/dbclient" - "github.com/TicketsBot-cloud/worker/bot/logic" + "github.com/TicketsBot-cloud/common/botpermissions" "github.com/TicketsBot-cloud/worker/bot/utils" "github.com/TicketsBot-cloud/worker/i18n" ) @@ -210,7 +210,7 @@ func (h *AddAdminHandler) Execute(ctx *context.ButtonContext) { overwrites := append(ch.PermissionOverwrites, channel.PermissionOverwrite{ Id: id, Type: mentionableType.OverwriteType(), - Allow: permission.BuildPermissions(logic.StandardPermissions[:]...), + Allow: permission.BuildPermissions(botpermissions.StandardPermissions...), Deny: 0, }) diff --git a/bot/button/handlers/addsupport.go b/bot/button/handlers/addsupport.go index 39b3d84d..c3949ef0 100644 --- a/bot/button/handlers/addsupport.go +++ b/bot/button/handlers/addsupport.go @@ -21,7 +21,7 @@ import ( cmdregistry "github.com/TicketsBot-cloud/worker/bot/command/registry" "github.com/TicketsBot-cloud/worker/bot/customisation" "github.com/TicketsBot-cloud/worker/bot/dbclient" - "github.com/TicketsBot-cloud/worker/bot/logic" + "github.com/TicketsBot-cloud/common/botpermissions" "github.com/TicketsBot-cloud/worker/bot/utils" "github.com/TicketsBot-cloud/worker/i18n" ) @@ -235,7 +235,7 @@ func updateChannelPermissions(ctx cmdregistry.CommandContext, id uint64, mention overwrites := append(ch.PermissionOverwrites, channel.PermissionOverwrite{ Id: id, Type: mentionableType.OverwriteType(), - Allow: permission.BuildPermissions(logic.StandardPermissions[:]...), + Allow: permission.BuildPermissions(botpermissions.StandardPermissions...), Deny: 0, }) diff --git a/bot/button/handlers/admindebug/server/modals/permissions.go b/bot/button/handlers/admindebug/server/modals/permissions.go index c6250387..5b524ef2 100644 --- a/bot/button/handlers/admindebug/server/modals/permissions.go +++ b/bot/button/handlers/admindebug/server/modals/permissions.go @@ -7,6 +7,7 @@ import ( "strings" "time" + "github.com/TicketsBot-cloud/common/botpermissions" permcache "github.com/TicketsBot-cloud/common/permission" "github.com/TicketsBot-cloud/database" "github.com/TicketsBot-cloud/gdl/objects/interaction" @@ -20,7 +21,6 @@ import ( "github.com/TicketsBot-cloud/worker/bot/command/context" "github.com/TicketsBot-cloud/worker/bot/customisation" "github.com/TicketsBot-cloud/worker/bot/dbclient" - "github.com/TicketsBot-cloud/worker/bot/logic" "github.com/TicketsBot-cloud/worker/bot/permissionwrapper" "github.com/TicketsBot-cloud/worker/bot/utils" ) @@ -170,7 +170,7 @@ func processPermissionChecks(selectedValues []string, worker *w.Context, guildId } else { serverWidePermissions = append(serverWidePermissions, permission.ManageChannels) } - serverWidePermissions = append(serverWidePermissions, logic.StandardPermissions[:]...) + serverWidePermissions = append(serverWidePermissions, botpermissions.StandardPermissions...) var results []string var hasMissingPermissions bool @@ -266,13 +266,13 @@ func checkPanelPermissions(worker *w.Context, guildId uint64, botMember member.M if usesThreads { // Thread mode: permissions needed in the panel channel where threads are created. panelChannelPerms = append( - logic.ThreadPermissions[:], + botpermissions.ThreadModeRequired, permission.ManageWebhooks, permission.PinMessages, ) } else { // Channel mode: just standard permissions (no special ones needed for panel channel in channel mode) - panelChannelPerms = append([]permission.Permission{}, logic.StandardPermissions[:]...) + panelChannelPerms = append([]permission.Permission{}, botpermissions.StandardPermissions...) } result, hasMissing := checkChannelPermissions(worker, panel.ChannelId, botMember, guildId, panelChannelPerms, "Panel Channel") results = append(results, result) @@ -290,7 +290,7 @@ func checkPanelPermissions(worker *w.Context, guildId uint64, botMember member.M permission.ManageWebhooks, permission.PinMessages, }, - logic.StandardPermissions[:]..., + botpermissions.StandardPermissions..., ) result, hasMissing := checkChannelPermissions(worker, panel.TargetCategory, botMember, guildId, categoryPerms, "Category") results = append(results, result) @@ -302,7 +302,7 @@ func checkPanelPermissions(worker *w.Context, guildId uint64, botMember member.M // Check transcript channel if enabled for this panel if panel.TranscriptChannelId != nil { // Transcript channel needs minimal message permissions - transcriptPerms := append([]permission.Permission{}, logic.MinimalPermissions[:]...) + transcriptPerms := append([]permission.Permission{}, botpermissions.MinimalPermissions...) result, hasMissing := checkChannelPermissions(worker, *panel.TranscriptChannelId, botMember, guildId, transcriptPerms, "Transcript Channel") results = append(results, result) if hasMissing { @@ -318,7 +318,7 @@ func checkPanelPermissions(worker *w.Context, guildId uint64, botMember member.M permission.EmbedLinks, permission.AttachFiles, }, - logic.MinimalPermissions[:]..., + botpermissions.MinimalPermissions..., ) result, hasMissing := checkChannelPermissions(worker, *settings.TicketNotificationChannel, botMember, guildId, notificationPerms, "Notification Channel") results = append(results, result) diff --git a/bot/button/handlers/unclaim.go b/bot/button/handlers/unclaim.go index 3c37347f..407cbf32 100644 --- a/bot/button/handlers/unclaim.go +++ b/bot/button/handlers/unclaim.go @@ -3,6 +3,7 @@ package handlers import ( "fmt" + "github.com/TicketsBot-cloud/common/botpermissions" "github.com/TicketsBot-cloud/common/permission" "github.com/TicketsBot-cloud/database" "github.com/TicketsBot-cloud/gdl/objects/channel" @@ -148,7 +149,7 @@ func (h *UnclaimHandler) Execute(ctx *context.ButtonContext) { overwrites = append(overwrites, channel.PermissionOverwrite{ Id: whoClaimed, Type: channel.PermissionTypeMember, - Allow: discordpermission.BuildPermissions(logic.StandardPermissions[:]...), + Allow: discordpermission.BuildPermissions(botpermissions.StandardPermissions...), Deny: 0, }) case database.SwitchPanelRemoveOnUnclaim: diff --git a/bot/command/context/replyable.go b/bot/command/context/replyable.go index 88a56593..cc473e0d 100644 --- a/bot/command/context/replyable.go +++ b/bot/command/context/replyable.go @@ -9,6 +9,7 @@ import ( "strings" "time" + "github.com/TicketsBot-cloud/common/botpermissions" permcache "github.com/TicketsBot-cloud/common/permission" "github.com/TicketsBot-cloud/common/premium" "github.com/TicketsBot-cloud/common/sentry" @@ -22,7 +23,6 @@ import ( "github.com/TicketsBot-cloud/worker/bot/command/registry" "github.com/TicketsBot-cloud/worker/bot/customisation" "github.com/TicketsBot-cloud/worker/bot/dbclient" - "github.com/TicketsBot-cloud/worker/bot/logic" "github.com/TicketsBot-cloud/worker/bot/permissionwrapper" "github.com/TicketsBot-cloud/worker/bot/utils" "github.com/TicketsBot-cloud/worker/config" @@ -356,10 +356,10 @@ func findMissingPermissions(ctx registry.InteractionContext) ([]missingPermLocat if useThreads { primaryChannelId = ctx.ChannelId() primaryLabel = "Panel channel" - primaryRequired = logic.ThreadPermissions[:] + primaryRequired = botpermissions.ThreadModeRequired } else { primaryLabel = "Ticket category" - primaryRequired = append([]permission.Permission{permission.ManageChannels}, logic.StandardPermissions[:]...) + primaryRequired = botpermissions.ChannelModeRequired if panel != nil && panel.TargetCategory != 0 { primaryChannelId = panel.TargetCategory } else { @@ -388,7 +388,7 @@ func findMissingPermissions(ctx registry.InteractionContext) ([]missingPermLocat notifChannelId = settings.TicketNotificationChannel } if notifChannelId != nil { - notifRequired := append(logic.MinimalPermissions[:], permission.EmbedLinks, permission.AttachFiles) + notifRequired := botpermissions.NotifChannelRequired loc := checkChannel(*notifChannelId, "Notification channel", notifRequired) if len(loc.missing) > 0 { locations = append(locations, loc) @@ -398,7 +398,7 @@ func findMissingPermissions(ctx registry.InteractionContext) ([]missingPermLocat // 3. Transcript channel (panel-level, both modes) if panel != nil && panel.TranscriptChannelId != nil { - loc := checkChannel(*panel.TranscriptChannelId, "Transcript channel", logic.MinimalPermissions[:]) + loc := checkChannel(*panel.TranscriptChannelId, "Transcript channel", botpermissions.TranscriptChannelRequired) if len(loc.missing) > 0 { locations = append(locations, loc) } diff --git a/bot/command/impl/tickets/remove.go b/bot/command/impl/tickets/remove.go index c21fe39b..40d74be9 100644 --- a/bot/command/impl/tickets/remove.go +++ b/bot/command/impl/tickets/remove.go @@ -14,7 +14,7 @@ import ( "github.com/TicketsBot-cloud/worker/bot/command/registry" "github.com/TicketsBot-cloud/worker/bot/customisation" "github.com/TicketsBot-cloud/worker/bot/dbclient" - "github.com/TicketsBot-cloud/worker/bot/logic" + "github.com/TicketsBot-cloud/common/botpermissions" "github.com/TicketsBot-cloud/worker/bot/utils" "github.com/TicketsBot-cloud/worker/i18n" ) @@ -238,7 +238,7 @@ func (RemoveCommand) Execute(ctx registry.CommandContext, id uint64) { Id: id, Type: channel.PermissionTypeMember, Allow: 0, - Deny: permission.BuildPermissions(logic.StandardPermissions[:]...), + Deny: permission.BuildPermissions(botpermissions.StandardPermissions...), } if err := ctx.Worker().EditChannelPermissions(reasonCtx, ticketChannelId, data); err != nil { @@ -322,7 +322,7 @@ func (RemoveCommand) Execute(ctx registry.CommandContext, id uint64) { Id: id, Type: channel.PermissionTypeRole, Allow: 0, - Deny: permission.BuildPermissions(logic.StandardPermissions[:]...), + Deny: permission.BuildPermissions(botpermissions.StandardPermissions...), } executor, err := ctx.Member() diff --git a/bot/command/impl/tickets/unclaim.go b/bot/command/impl/tickets/unclaim.go index dd7906c6..e5971e6e 100644 --- a/bot/command/impl/tickets/unclaim.go +++ b/bot/command/impl/tickets/unclaim.go @@ -3,6 +3,7 @@ package tickets import ( "fmt" + "github.com/TicketsBot-cloud/common/botpermissions" "github.com/TicketsBot-cloud/common/permission" "github.com/TicketsBot-cloud/database" "github.com/TicketsBot-cloud/gdl/objects/channel" @@ -144,7 +145,7 @@ func (UnclaimCommand) Execute(ctx *context.SlashCommandContext) { overwrites = append(overwrites, channel.PermissionOverwrite{ Id: whoClaimed, Type: channel.PermissionTypeMember, - Allow: discordpermission.BuildPermissions(logic.StandardPermissions[:]...), + Allow: discordpermission.BuildPermissions(botpermissions.StandardPermissions...), Deny: 0, }) case database.SwitchPanelRemoveOnUnclaim: diff --git a/bot/logic/claim.go b/bot/logic/claim.go index a580ad1f..3d14698b 100644 --- a/bot/logic/claim.go +++ b/bot/logic/claim.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" + "github.com/TicketsBot-cloud/common/botpermissions" "github.com/TicketsBot-cloud/database" "github.com/TicketsBot-cloud/gdl/objects/channel" "github.com/TicketsBot-cloud/gdl/objects/channel/embed" @@ -219,7 +220,7 @@ func overwritesCantView(claimer, selfId, openerId, guildId uint64, adminUsers, a overwrites = append(overwrites, channel.PermissionOverwrite{ Id: userId, Type: channel.PermissionTypeMember, - Allow: permission.BuildPermissions(StandardPermissions[:]...), + Allow: permission.BuildPermissions(botpermissions.StandardPermissions...), Deny: 0, }) } @@ -228,7 +229,7 @@ func overwritesCantView(claimer, selfId, openerId, guildId uint64, adminUsers, a overwrites = append(overwrites, channel.PermissionOverwrite{ Id: roleId, Type: channel.PermissionTypeRole, - Allow: permission.BuildPermissions(StandardPermissions[:]...), + Allow: permission.BuildPermissions(botpermissions.StandardPermissions...), Deny: 0, }) } @@ -269,7 +270,7 @@ func overwritesCantType(claimerId, selfId, openerId, guildId uint64, supportUser overwrites = append(overwrites, channel.PermissionOverwrite{ Id: userId, Type: channel.PermissionTypeMember, - Allow: permission.BuildPermissions(StandardPermissions[:]...), + Allow: permission.BuildPermissions(botpermissions.StandardPermissions...), Deny: 0, }) } @@ -278,7 +279,7 @@ func overwritesCantType(claimerId, selfId, openerId, guildId uint64, supportUser overwrites = append(overwrites, channel.PermissionOverwrite{ Id: roleId, Type: channel.PermissionTypeRole, - Allow: permission.BuildPermissions(StandardPermissions[:]...), + Allow: permission.BuildPermissions(botpermissions.StandardPermissions...), Deny: 0, }) } diff --git a/bot/logic/discordpermissions.go b/bot/logic/discordpermissions.go index fd22fb70..2657a6d5 100644 --- a/bot/logic/discordpermissions.go +++ b/bot/logic/discordpermissions.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + "github.com/TicketsBot-cloud/common/botpermissions" "github.com/TicketsBot-cloud/database" "github.com/TicketsBot-cloud/gdl/objects/channel" "github.com/TicketsBot-cloud/gdl/permission" @@ -14,43 +15,8 @@ import ( "github.com/TicketsBot-cloud/worker/bot/utils" ) -// StandardPermissions Returns the standard permissions that users are given in a ticket -var StandardPermissions = [...]permission.Permission{ - permission.AddReactions, - permission.ViewChannel, - permission.SendMessages, - permission.SendTTSMessages, - permission.EmbedLinks, - permission.AttachFiles, - permission.MentionEveryone, - permission.UseExternalEmojis, - permission.ReadMessageHistory, - permission.UseApplicationCommands, - permission.UseExternalStickers, - permission.SendVoiceMessages, -} - -var MinimalPermissions = [...]permission.Permission{ - permission.ViewChannel, - permission.SendMessages, - permission.ReadMessageHistory, - permission.UseApplicationCommands, -} - -// ThreadPermissions are the permissions the bot needs in the panel channel when operating in thread mode -var ThreadPermissions = [...]permission.Permission{ - permission.ViewChannel, - permission.ReadMessageHistory, - permission.EmbedLinks, - permission.AttachFiles, - permission.UseExternalEmojis, - permission.CreatePrivateThreads, - permission.SendMessagesInThreads, - permission.ManageThreads, -} - func BuildUserOverwrite(userId uint64, additionalPermissions database.TicketPermissions) channel.PermissionOverwrite { - allow := MinimalPermissions[:] + allow := append([]permission.Permission{}, botpermissions.MinimalPermissions...) var deny []permission.Permission if additionalPermissions.AddReactions { @@ -104,7 +70,7 @@ func BuildUserOverwrite(userId uint64, additionalPermissions database.TicketPerm } func BuildRoleOverwrite(roleId uint64, additionalPermissions database.TicketPermissions) channel.PermissionOverwrite { - allow := MinimalPermissions[:] + allow := append([]permission.Permission{}, botpermissions.MinimalPermissions...) var deny []permission.Permission if additionalPermissions.AddReactions { diff --git a/bot/logic/open.go b/bot/logic/open.go index ccdb3aa5..fcf41cd2 100644 --- a/bot/logic/open.go +++ b/bot/logic/open.go @@ -9,6 +9,7 @@ import ( "time" "unicode" + "github.com/TicketsBot-cloud/common/botpermissions" permcache "github.com/TicketsBot-cloud/common/permission" "github.com/TicketsBot-cloud/common/premium" "github.com/TicketsBot-cloud/common/sentry" @@ -941,8 +942,7 @@ func CreateOverwrites(ctx context.Context, cmd registry.InteractionContext, user } // Add the bot to the overwrites - selfAllow := make([]permission.Permission, len(StandardPermissions), len(StandardPermissions)+2) - copy(selfAllow, StandardPermissions[:]) // Do not append to StandardPermissions + selfAllow := append([]permission.Permission{}, botpermissions.StandardPermissions...) selfAllow = append(selfAllow, permission.ManageChannels) @@ -998,8 +998,7 @@ func CreateOverwrites(ctx context.Context, cmd registry.InteractionContext, user continue // Already added overwrite above } - allow := make([]permission.Permission, len(StandardPermissions)) - copy(allow, StandardPermissions[:]) + allow := append([]permission.Permission{}, botpermissions.StandardPermissions...) overwrites = append(overwrites, channel.PermissionOverwrite{ Id: member, @@ -1013,7 +1012,7 @@ func CreateOverwrites(ctx context.Context, cmd registry.InteractionContext, user overwrites = append(overwrites, channel.PermissionOverwrite{ Id: role, Type: channel.PermissionTypeRole, - Allow: permission.BuildPermissions(StandardPermissions[:]...), + Allow: permission.BuildPermissions(botpermissions.StandardPermissions...), Deny: 0, }) } diff --git a/go.mod b/go.mod index f3bc8dea..fdb55743 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ toolchain go1.24.2 //replace github.com/TicketsBot-cloud/database => ../database -//replace github.com/TicketsBot-cloud/common => ../common +replace github.com/TicketsBot-cloud/common => ../common //replace github.com/TicketsBot-cloud/gdl => ../gdl From c758476d5f9e5906e53ef53276c14e93481386e2 Mon Sep 17 00:00:00 2001 From: biast12 Date: Sun, 12 Apr 2026 20:40:24 +0200 Subject: [PATCH 03/19] Bump common --- go.mod | 4 ++-- go.sum | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index fdb55743..f7473bc2 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ toolchain go1.24.2 //replace github.com/TicketsBot-cloud/database => ../database -replace github.com/TicketsBot-cloud/common => ../common +//replace github.com/TicketsBot-cloud/common => ../common //replace github.com/TicketsBot-cloud/gdl => ../gdl @@ -18,7 +18,7 @@ require ( cloud.google.com/go/profiler v0.4.2 github.com/TicketsBot-cloud/analytics-client v0.0.0-20250604180646-6606dfc8fc8c github.com/TicketsBot-cloud/archiverclient v0.0.0-20251015181023-f0b66a074704 - github.com/TicketsBot-cloud/common v0.0.0-20260210203202-54154661338e + github.com/TicketsBot-cloud/common v0.0.0-20260412182419-83b9a6ea08e7 github.com/TicketsBot-cloud/database v0.0.0-20260308193919-30a698fefa8b github.com/TicketsBot-cloud/gdl v0.0.0-20260306134952-cccb0116fef6 github.com/caarlos0/env/v10 v10.0.0 diff --git a/go.sum b/go.sum index 380325f3..e2f102b0 100644 --- a/go.sum +++ b/go.sum @@ -33,8 +33,8 @@ github.com/TicketsBot-cloud/analytics-client v0.0.0-20250604180646-6606dfc8fc8c github.com/TicketsBot-cloud/analytics-client v0.0.0-20250604180646-6606dfc8fc8c/go.mod h1:zecIz09jVDSHyhV6NYgTko0NEN0QJGiZbzcxHRjQLzc= github.com/TicketsBot-cloud/archiverclient v0.0.0-20251015181023-f0b66a074704 h1:liLfvCrzoJ89DXFHzsd1iK3cyP8s4i0CnZPRFEj53zg= github.com/TicketsBot-cloud/archiverclient v0.0.0-20251015181023-f0b66a074704/go.mod h1:Mux1bEPpOHwRw1wo6Fa6qJLJH9Erk9qv1yAIfLi1Wmw= -github.com/TicketsBot-cloud/common v0.0.0-20260210203202-54154661338e h1:nFKV7yEm8MWbCP7dtsJ88+agcxDUD0YKIotVHMVvytw= -github.com/TicketsBot-cloud/common v0.0.0-20260210203202-54154661338e/go.mod h1:tGrTHFz09OM3eDWF+62hIi9ELpT4igCFi868FKSvKBg= +github.com/TicketsBot-cloud/common v0.0.0-20260412182419-83b9a6ea08e7 h1:dFmPLk9KXGRVSfiuKK6kusVhRKG7nGfiGld+WRuiX7w= +github.com/TicketsBot-cloud/common v0.0.0-20260412182419-83b9a6ea08e7/go.mod h1:jXGcmAuRvv92YqITskvClgoCpFVqYw14CKJdYhiLtVU= github.com/TicketsBot-cloud/database v0.0.0-20260308193919-30a698fefa8b h1:bHkfJWo8T/9TiHuYHxaOz8GAILIiKPugC1k3CzdOq/A= github.com/TicketsBot-cloud/database v0.0.0-20260308193919-30a698fefa8b/go.mod h1:HQXAgmNSm7/FmBYwcsa6qpZqMrDhbLoEl+AyqFQ+RwY= github.com/TicketsBot-cloud/gdl v0.0.0-20260306134952-cccb0116fef6 h1:ucG0xLPt7xixW7/LvL0hXDBDouDRS1Nf+77qP8iJ/X0= From 7bad15e8ba596c516ed66d17a0af69315e469f63 Mon Sep 17 00:00:00 2001 From: biast12 Date: Sun, 12 Apr 2026 22:06:46 +0200 Subject: [PATCH 04/19] Refactor permission checks to botpermissions Replace manual permission aggregation with botpermissions.EffectivePermissions and consolidate data fetching into fetchGuildData/fetchChannelData/buildRoleMap helpers. Simplifies HasPermissions/HasPermissionsChannel and GetMissingPermissions* by delegating to centralized logic, removes many in-file permission helpers, and updates imports accordingly. Keeps existing error handling (sentry) and preserves administrator short-circuit behavior. --- bot/permissionwrapper/permission.go | 284 ++++------------------------ 1 file changed, 35 insertions(+), 249 deletions(-) diff --git a/bot/permissionwrapper/permission.go b/bot/permissionwrapper/permission.go index cf66095e..4c96f9f6 100644 --- a/bot/permissionwrapper/permission.go +++ b/bot/permissionwrapper/permission.go @@ -1,310 +1,96 @@ package permissionwrapper import ( - "errors" - + "github.com/TicketsBot-cloud/common/botpermissions" "github.com/TicketsBot-cloud/common/sentry" "github.com/TicketsBot-cloud/gdl/objects/channel" "github.com/TicketsBot-cloud/gdl/objects/guild" + "github.com/TicketsBot-cloud/gdl/objects/member" "github.com/TicketsBot-cloud/gdl/permission" "github.com/TicketsBot-cloud/worker" ) func HasPermissionsChannel(ctx *worker.Context, guildId, userId, channelId uint64, permissions ...permission.Permission) bool { - sum, err := getEffectivePermissionsChannel(ctx, guildId, userId, channelId) + m, roles, ch, err := fetchChannelData(ctx, guildId, userId, channelId) if err != nil { return false } - - if permission.HasPermissionRaw(sum, permission.Administrator) { + effective := botpermissions.EffectivePermissions(guildId, userId, []uint64(m.Roles), ch.PermissionOverwrites, buildRoleMap(roles)) + if permission.HasPermissionRaw(effective, permission.Administrator) { return true } - - hasPermission := true - - for _, perm := range permissions { - if !permission.HasPermissionRaw(sum, perm) { - hasPermission = false - break + for _, p := range permissions { + if !permission.HasPermissionRaw(effective, p) { + return false } } - - return hasPermission + return true } func HasPermissions(ctx *worker.Context, guildId, userId uint64, permissions ...permission.Permission) bool { - sum, err := getEffectivePermissions(ctx, guildId, userId) + m, roles, err := fetchGuildData(ctx, guildId, userId) if err != nil { sentry.Error(err) return false } - - if permission.HasPermissionRaw(sum, permission.Administrator) { + effective := botpermissions.EffectivePermissions(guildId, userId, []uint64(m.Roles), nil, buildRoleMap(roles)) + if permission.HasPermissionRaw(effective, permission.Administrator) { return true } - - hasPermission := true - - for _, perm := range permissions { - if !permission.HasPermissionRaw(sum, perm) { - hasPermission = false - break - } - } - - return hasPermission -} - -func getAllPermissionsChannel(ctx *worker.Context, guildId, userId, channelId uint64) []permission.Permission { - permissions := make([]permission.Permission, 0) - - sum, err := getEffectivePermissionsChannel(ctx, guildId, userId, channelId) - if err != nil { - sentry.Error(err) - return permissions - } - - for _, perm := range permission.AllPermissions { - if permission.HasPermissionRaw(sum, perm) { - permissions = append(permissions, perm) + for _, p := range permissions { + if !permission.HasPermissionRaw(effective, p) { + return false } } - - return permissions -} - -func getAllPermissions(ctx *worker.Context, guildId, userId uint64) []permission.Permission { - permissions := make([]permission.Permission, 0) - - sum, err := getEffectivePermissions(ctx, guildId, userId) - if err != nil { - sentry.Error(err) - return permissions - } - - for _, perm := range permission.AllPermissions { - if permission.HasPermissionRaw(sum, perm) { - permissions = append(permissions, perm) - } - } - - return permissions + return true } func GetMissingPermissions(ctx *worker.Context, guildId, userId uint64, required ...permission.Permission) []permission.Permission { - missing := make([]permission.Permission, 0) - - sum, err := getEffectivePermissions(ctx, guildId, userId) + m, roles, err := fetchGuildData(ctx, guildId, userId) if err != nil { sentry.Error(err) return required } - - if permission.HasPermissionRaw(sum, permission.Administrator) { - return missing - } - - for _, perm := range required { - if !permission.HasPermissionRaw(sum, perm) { - missing = append(missing, perm) - } - } - - return missing + return botpermissions.MissingPermissions(guildId, userId, []uint64(m.Roles), nil, buildRoleMap(roles), required) } func GetMissingPermissionsChannel(ctx *worker.Context, guildId, userId, channelId uint64, required ...permission.Permission) []permission.Permission { - missing := make([]permission.Permission, 0) - - sum, err := getEffectivePermissionsChannel(ctx, guildId, userId, channelId) + m, roles, ch, err := fetchChannelData(ctx, guildId, userId, channelId) if err != nil { sentry.Error(err) return required } - - if permission.HasPermissionRaw(sum, permission.Administrator) { - return missing - } - - for _, perm := range required { - if !permission.HasPermissionRaw(sum, perm) { - missing = append(missing, perm) - } - } - - return missing -} - -func getEffectivePermissionsChannel(ctx *worker.Context, guildId, userId, channelId uint64) (uint64, error) { - permissions, err := getBasePermissions(ctx, guildId) - if err != nil { - return 0, err - } - - permissions, err = getGuildTotalRolePermissions(ctx, guildId, userId, permissions) - if err != nil { - return 0, err - } - - permissions, err = getChannelBasePermissions(ctx, guildId, channelId, permissions) - if err != nil { - return 0, err - } - - permissions, err = getChannelTotalRolePermissions(ctx, guildId, userId, channelId, permissions) - if err != nil { - return 0, err - } - - permissions, err = getChannelMemberPermissions(ctx, userId, channelId, permissions) - if err != nil { - return 0, err - } - - return permissions, nil + return botpermissions.MissingPermissions(guildId, userId, []uint64(m.Roles), ch.PermissionOverwrites, buildRoleMap(roles), required) } -func getEffectivePermissions(ctx *worker.Context, guildId, userId uint64) (uint64, error) { - permissions, err := getBasePermissions(ctx, guildId) +func fetchGuildData(ctx *worker.Context, guildId, userId uint64) (member.Member, []guild.Role, error) { + m, err := ctx.GetGuildMember(guildId, userId) if err != nil { - return 0, err + return member.Member{}, nil, err } - - permissions, err = getGuildTotalRolePermissions(ctx, guildId, userId, permissions) - if err != nil { - return 0, err - } - - return permissions, nil -} - -func getChannelMemberPermissions(ctx *worker.Context, userId, channelId uint64, initialPermissions uint64) (uint64, error) { - ch, err := ctx.GetChannel(channelId) - if err != nil { - return 0, err - } - - for _, overwrite := range ch.PermissionOverwrites { - if overwrite.Type == channel.PermissionTypeMember && overwrite.Id == userId { - initialPermissions &= ^overwrite.Deny - initialPermissions |= overwrite.Allow - } - } - - return initialPermissions, nil -} - -func getChannelTotalRolePermissions(ctx *worker.Context, guildId, userId, channelId uint64, initialPermissions uint64) (uint64, error) { - member, err := ctx.GetGuildMember(guildId, userId) - if err != nil { - return 0, err - } - roles, err := ctx.GetGuildRoles(guildId) if err != nil { - return 0, err - } - - ch, err := ctx.GetChannel(channelId) - if err != nil { - return 0, err + return member.Member{}, nil, err } - - var allow, deny uint64 - - for _, memberRole := range member.Roles { - for _, role := range roles { - if memberRole == role.Id { - for _, overwrite := range ch.PermissionOverwrites { - if overwrite.Type == channel.PermissionTypeRole && overwrite.Id == role.Id { - allow |= overwrite.Allow - deny |= overwrite.Deny - break - } - } - } - } - } - - initialPermissions &= ^deny - initialPermissions |= allow - - return initialPermissions, nil + return m, roles, nil } -func getChannelBasePermissions(ctx *worker.Context, guildId, channelId uint64, initialPermissions uint64) (uint64, error) { - roles, err := ctx.GetGuildRoles(guildId) +func fetchChannelData(ctx *worker.Context, guildId, userId, channelId uint64) (member.Member, []guild.Role, channel.Channel, error) { + m, roles, err := fetchGuildData(ctx, guildId, userId) if err != nil { - return 0, err + return member.Member{}, nil, channel.Channel{}, err } - - var publicRole *guild.Role - for _, role := range roles { - if role.Id == guildId { - publicRole = &role - break - } - } - - if publicRole == nil { - return 0, errors.New("couldn't find public role") - } - ch, err := ctx.GetChannel(channelId) if err != nil { - return 0, err - } - - for _, overwrite := range ch.PermissionOverwrites { - if overwrite.Type == channel.PermissionTypeRole && overwrite.Id == publicRole.Id { - initialPermissions &= ^overwrite.Deny - initialPermissions |= overwrite.Allow - break - } + return member.Member{}, nil, channel.Channel{}, err } - - return initialPermissions, nil + return m, roles, ch, nil } -func getGuildTotalRolePermissions(ctx *worker.Context, guildId, userId uint64, initialPermissions uint64) (uint64, error) { - member, err := ctx.GetGuildMember(guildId, userId) - if err != nil { - return 0, err +func buildRoleMap(roles []guild.Role) map[uint64]guild.Role { + m := make(map[uint64]guild.Role, len(roles)) + for _, r := range roles { + m[r.Id] = r } - - roles, err := ctx.GetGuildRoles(guildId) - if err != nil { - return 0, err - } - - for _, memberRole := range member.Roles { - for _, role := range roles { - if memberRole == role.Id { - initialPermissions |= role.Permissions - } - } - } - - return initialPermissions, nil -} - -func getBasePermissions(ctx *worker.Context, guildId uint64) (uint64, error) { - roles, err := ctx.GetGuildRoles(guildId) - if err != nil { - return 0, err - } - - var publicRole *guild.Role - for _, role := range roles { - if role.Id == guildId { - publicRole = &role - break - } - } - - if publicRole == nil { - return 0, errors.New("couldn't find public role") - } - - return publicRole.Permissions, nil + return m } From 19dfc4cfe0570a261e26f085bb924ff3ae98baa4 Mon Sep 17 00:00:00 2001 From: biast12 Date: Sat, 25 Apr 2026 16:47:13 +0200 Subject: [PATCH 05/19] Enable local replace for common module Uncomment the replace directive for github.com/TicketsBot-cloud/common in go.mod to point to the local ../common module. This allows building and testing against the local copy of the common package during development; other replace directives remain unchanged. --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 4afdfd2d..e5d3b06a 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.25.0 //replace github.com/TicketsBot-cloud/database => ../database -//replace github.com/TicketsBot-cloud/common => ../common +replace github.com/TicketsBot-cloud/common => ../common //replace github.com/TicketsBot-cloud/gdl => ../gdl From 955f0d5aa400dc56c8709e3f29f59bff95b0f556 Mon Sep 17 00:00:00 2001 From: biast12 Date: Sun, 26 Apr 2026 13:04:31 +0200 Subject: [PATCH 06/19] Bump common --- go.mod | 2 +- go.sum | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/go.mod b/go.mod index e5d3b06a..c2ea3567 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( cloud.google.com/go/profiler v0.4.2 github.com/TicketsBot-cloud/analytics-client v0.0.0-20250604180646-6606dfc8fc8c github.com/TicketsBot-cloud/archiverclient v0.0.0-20251015181023-f0b66a074704 - github.com/TicketsBot-cloud/common v0.0.0-20260412182419-83b9a6ea08e7 + github.com/TicketsBot-cloud/common v0.0.0-20260425075748-0a4c9adb5e2d github.com/TicketsBot-cloud/database v0.0.0-20260423165031-495c2e8a5bc7 github.com/TicketsBot-cloud/gdl v0.0.0-20260306134952-cccb0116fef6 github.com/caarlos0/env/v10 v10.0.0 diff --git a/go.sum b/go.sum index c7085405..51247df5 100644 --- a/go.sum +++ b/go.sum @@ -33,8 +33,6 @@ github.com/TicketsBot-cloud/analytics-client v0.0.0-20250604180646-6606dfc8fc8c github.com/TicketsBot-cloud/analytics-client v0.0.0-20250604180646-6606dfc8fc8c/go.mod h1:zecIz09jVDSHyhV6NYgTko0NEN0QJGiZbzcxHRjQLzc= github.com/TicketsBot-cloud/archiverclient v0.0.0-20251015181023-f0b66a074704 h1:liLfvCrzoJ89DXFHzsd1iK3cyP8s4i0CnZPRFEj53zg= github.com/TicketsBot-cloud/archiverclient v0.0.0-20251015181023-f0b66a074704/go.mod h1:Mux1bEPpOHwRw1wo6Fa6qJLJH9Erk9qv1yAIfLi1Wmw= -github.com/TicketsBot-cloud/common v0.0.0-20260412182419-83b9a6ea08e7 h1:dFmPLk9KXGRVSfiuKK6kusVhRKG7nGfiGld+WRuiX7w= -github.com/TicketsBot-cloud/common v0.0.0-20260412182419-83b9a6ea08e7/go.mod h1:jXGcmAuRvv92YqITskvClgoCpFVqYw14CKJdYhiLtVU= github.com/TicketsBot-cloud/database v0.0.0-20260423165031-495c2e8a5bc7 h1:35PmSSlrSN+DOLNEnkRw4vSg1qxnnlEKtMlOaOUjMOQ= github.com/TicketsBot-cloud/database v0.0.0-20260423165031-495c2e8a5bc7/go.mod h1:HQXAgmNSm7/FmBYwcsa6qpZqMrDhbLoEl+AyqFQ+RwY= github.com/TicketsBot-cloud/gdl v0.0.0-20260306134952-cccb0116fef6 h1:ucG0xLPt7xixW7/LvL0hXDBDouDRS1Nf+77qP8iJ/X0= From a79abdd5e5bce443b3de25c57405cff097eb80fd Mon Sep 17 00:00:00 2001 From: biast12 Date: Sun, 26 Apr 2026 13:04:54 +0200 Subject: [PATCH 07/19] . --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index c2ea3567..fe3dcb1e 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.25.0 //replace github.com/TicketsBot-cloud/database => ../database -replace github.com/TicketsBot-cloud/common => ../common +//replace github.com/TicketsBot-cloud/common => ../common //replace github.com/TicketsBot-cloud/gdl => ../gdl From de49c2347318f08052dd40c954c8a16e35f9d38e Mon Sep 17 00:00:00 2001 From: biast12 <53872542+biast12@users.noreply.github.com> Date: Sun, 21 Jun 2026 21:39:53 +0200 Subject: [PATCH 08/19] bump common --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 3995fc75..9a44e6b1 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( cloud.google.com/go/profiler v0.4.2 github.com/TicketsBot-cloud/analytics-client v0.0.0-20250604180646-6606dfc8fc8c github.com/TicketsBot-cloud/archiverclient v0.0.0-20251015181023-f0b66a074704 - github.com/TicketsBot-cloud/common v0.0.0-20260425075748-0a4c9adb5e2d + github.com/TicketsBot-cloud/common v0.0.0-20260620182815-55fda9a14c01 github.com/TicketsBot-cloud/database v0.0.0-20260423165031-495c2e8a5bc7 github.com/TicketsBot-cloud/gdl v0.0.0-20260426095953-999472e6e538 github.com/caarlos0/env/v10 v10.0.0 diff --git a/go.sum b/go.sum index d543affa..3fcf4548 100644 --- a/go.sum +++ b/go.sum @@ -33,6 +33,8 @@ github.com/TicketsBot-cloud/analytics-client v0.0.0-20250604180646-6606dfc8fc8c github.com/TicketsBot-cloud/analytics-client v0.0.0-20250604180646-6606dfc8fc8c/go.mod h1:zecIz09jVDSHyhV6NYgTko0NEN0QJGiZbzcxHRjQLzc= github.com/TicketsBot-cloud/archiverclient v0.0.0-20251015181023-f0b66a074704 h1:liLfvCrzoJ89DXFHzsd1iK3cyP8s4i0CnZPRFEj53zg= github.com/TicketsBot-cloud/archiverclient v0.0.0-20251015181023-f0b66a074704/go.mod h1:Mux1bEPpOHwRw1wo6Fa6qJLJH9Erk9qv1yAIfLi1Wmw= +github.com/TicketsBot-cloud/common v0.0.0-20260620182815-55fda9a14c01 h1:BTBphQxBilbCjiNUYHd67kG5yCdEQ9YRAvzRBxOgjkA= +github.com/TicketsBot-cloud/common v0.0.0-20260620182815-55fda9a14c01/go.mod h1:IA3watwJMLySOt5tsO5GPwAsHAFw6x9aqchxNWUqlog= github.com/TicketsBot-cloud/database v0.0.0-20260423165031-495c2e8a5bc7 h1:35PmSSlrSN+DOLNEnkRw4vSg1qxnnlEKtMlOaOUjMOQ= github.com/TicketsBot-cloud/database v0.0.0-20260423165031-495c2e8a5bc7/go.mod h1:HQXAgmNSm7/FmBYwcsa6qpZqMrDhbLoEl+AyqFQ+RwY= github.com/TicketsBot-cloud/gdl v0.0.0-20260426095953-999472e6e538 h1:ewKw1Wv1x/yi8h1IH7EofcYnPUxWZaVTIPFkT97nhn0= From 5c91ada5b5af63a3299669bcc485833062b5e70e Mon Sep 17 00:00:00 2001 From: biast12 <53872542+biast12@users.noreply.github.com> Date: Mon, 21 Sep 2026 23:02:25 +0200 Subject: [PATCH 09/19] Use bot standard permissions for claimers Update ticket claim and unclaim permission overwrites to use the shared botpermissions.StandardPermissions definition. --- bot/logic/claim.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/logic/claim.go b/bot/logic/claim.go index e76d9c3c..7f6730c1 100644 --- a/bot/logic/claim.go +++ b/bot/logic/claim.go @@ -206,7 +206,7 @@ func UnclaimTicket(ctx context.Context, cmd registry.CommandContext, ticket data overwrites = append(overwrites, channel.PermissionOverwrite{ Id: whoClaimed, Type: channel.PermissionTypeMember, - Allow: permission.BuildPermissions(StandardPermissions[:]...), + Allow: permission.BuildPermissions(botpermissions.StandardPermissions...), Deny: 0, }) } @@ -365,7 +365,7 @@ func BuildClaimerOverwrite(ctx context.Context, worker *worker.Context, ticket d standard := channel.PermissionOverwrite{ Id: claimerId, Type: channel.PermissionTypeMember, - Allow: permission.BuildPermissions(StandardPermissions[:]...), + Allow: permission.BuildPermissions(botpermissions.StandardPermissions...), Deny: 0, } From 2057875f4e6265f23e09fa421a665b3c020486f3 Mon Sep 17 00:00:00 2001 From: biast12 <53872542+biast12@users.noreply.github.com> Date: Mon, 21 Sep 2026 23:10:57 +0200 Subject: [PATCH 10/19] Report channel permission fetch errors Capture fetchChannelData failures in Sentry before returning false from HasPermissionsChannel, improving visibility into permission-check failures. --- bot/permissionwrapper/permission.go | 1 + 1 file changed, 1 insertion(+) diff --git a/bot/permissionwrapper/permission.go b/bot/permissionwrapper/permission.go index 4c96f9f6..d4964e58 100644 --- a/bot/permissionwrapper/permission.go +++ b/bot/permissionwrapper/permission.go @@ -13,6 +13,7 @@ import ( func HasPermissionsChannel(ctx *worker.Context, guildId, userId, channelId uint64, permissions ...permission.Permission) bool { m, roles, ch, err := fetchChannelData(ctx, guildId, userId, channelId) if err != nil { + sentry.Error(err) return false } effective := botpermissions.EffectivePermissions(guildId, userId, []uint64(m.Roles), ch.PermissionOverwrites, buildRoleMap(roles)) From 090ccfd4cd15763b4afa5f9bede9b1fbf88af06a Mon Sep 17 00:00:00 2001 From: biast12 <53872542+biast12@users.noreply.github.com> Date: Mon, 21 Sep 2026 23:14:33 +0200 Subject: [PATCH 11/19] Fix overflow and transcript permission checks Add channel permission validation for configured overflow categories, require EmbedLinks for transcript channels, and ensure channel-mode permissions are checked for panel-less opens. --- .../admindebug/server/modals/permissions.go | 28 ++++++++++--------- bot/command/context/replyable.go | 12 ++++++-- bot/permissionwrapper/requirements.go | 13 +++++++++ 3 files changed, 38 insertions(+), 15 deletions(-) create mode 100644 bot/permissionwrapper/requirements.go diff --git a/bot/button/handlers/admindebug/server/modals/permissions.go b/bot/button/handlers/admindebug/server/modals/permissions.go index 42b7a099..a65808cb 100644 --- a/bot/button/handlers/admindebug/server/modals/permissions.go +++ b/bot/button/handlers/admindebug/server/modals/permissions.go @@ -120,8 +120,6 @@ func (h *AdminDebugServerPermissionsModalSubmitHandler) Execute(ctx *context.Mod } func processPermissionChecks(selectedValues []string, worker *w.Context, guildId uint64, botMember member.Member, panels []database.Panel) ([]string, bool) { - // Only require the permissions for modes the guild's panels actually use, so we - // don't report false failures for a mode the server never opens tickets in serverWidePermissions := []permission.Permission{ // Required in both modes permission.ManageWebhooks, @@ -130,17 +128,14 @@ func processPermissionChecks(selectedValues []string, worker *w.Context, guildId permission.ManageRoles, } - anyThread, anyChannel := false, false + // No panels to judge by: report thread mode rather than claim the guild is fine + anyThread := len(panels) == 0 for _, p := range panels { if p.UseThreads { anyThread = true - } else { - anyChannel = true + break } } - if len(panels) == 0 { - anyThread, anyChannel = true, true - } if anyThread { serverWidePermissions = append(serverWidePermissions, @@ -149,9 +144,9 @@ func processPermissionChecks(selectedValues []string, worker *w.Context, guildId permission.ManageThreads, ) } - if anyChannel { - serverWidePermissions = append(serverWidePermissions, permission.ManageChannels) - } + + // Panel-less opens (message context menu) are always channel mode + serverWidePermissions = append(serverWidePermissions, permission.ManageChannels) serverWidePermissions = append(serverWidePermissions, botpermissions.StandardPermissions...) var results []string @@ -279,12 +274,19 @@ func checkPanelPermissions(worker *w.Context, guildId uint64, botMember member.M if hasMissing { hasMissingPermissions = true } + + if panel.OverflowEnabled && panel.OverflowCategoryId != nil { + result, hasMissing := checkChannelPermissions(worker, *panel.OverflowCategoryId, botMember, guildId, categoryPerms, "Overflow Category") + results = append(results, result) + if hasMissing { + hasMissingPermissions = true + } + } } // Check transcript channel if enabled for this panel if panel.TranscriptChannelId != nil { - // Transcript channel needs minimal message permissions - transcriptPerms := append([]permission.Permission{}, botpermissions.MinimalPermissions...) + transcriptPerms := permissionwrapper.TranscriptChannelRequired result, hasMissing := checkChannelPermissions(worker, *panel.TranscriptChannelId, botMember, guildId, transcriptPerms, "Transcript Channel") results = append(results, result) if hasMissing { diff --git a/bot/command/context/replyable.go b/bot/command/context/replyable.go index 2c6342c1..c11f55b3 100644 --- a/bot/command/context/replyable.go +++ b/bot/command/context/replyable.go @@ -380,9 +380,17 @@ func findMissingPermissions(ctx registry.InteractionContext) ([]missingPermLocat } } - // 3. Transcript channel (panel-level, both modes) + // 3. Overflow category (channel mode only) - a nil id means the server root + if !useThreads && panel != nil && panel.OverflowEnabled && panel.OverflowCategoryId != nil { + loc := checkChannel(*panel.OverflowCategoryId, "Overflow category", botpermissions.ChannelModeRequired) + if len(loc.missing) > 0 { + locations = append(locations, loc) + } + } + + // 4. Transcript channel (panel-level, both modes) if panel != nil && panel.TranscriptChannelId != nil { - loc := checkChannel(*panel.TranscriptChannelId, "Transcript channel", botpermissions.TranscriptChannelRequired) + loc := checkChannel(*panel.TranscriptChannelId, "Transcript channel", permissionwrapper.TranscriptChannelRequired) if len(loc.missing) > 0 { locations = append(locations, loc) } diff --git a/bot/permissionwrapper/requirements.go b/bot/permissionwrapper/requirements.go new file mode 100644 index 00000000..0598cc18 --- /dev/null +++ b/bot/permissionwrapper/requirements.go @@ -0,0 +1,13 @@ +package permissionwrapper + +import ( + "github.com/TicketsBot-cloud/common/botpermissions" + "github.com/TicketsBot-cloud/gdl/permission" +) + +// Adds EmbedLinks, which upstream omits although close.go posts an embed to the +// archive channel. Drop once common/botpermissions covers it. +var TranscriptChannelRequired = append( + append([]permission.Permission{}, botpermissions.TranscriptChannelRequired...), + permission.EmbedLinks, +) From 8097f075593f01ac9cf01bdabc696bb3b526579f Mon Sep 17 00:00:00 2001 From: biast12 <53872542+biast12@users.noreply.github.com> Date: Mon, 21 Sep 2026 23:53:32 +0200 Subject: [PATCH 12/19] Unify panel permission checks Apply channel, thread, notification, pending-category, and transcript permissions consistently across admin diagnostics and command replies. Resolve panels from buttons, select menus, and modals, and replace the obsolete permission wrapper with shared bot permission requirements. --- .../admindebug/server/modals/permissions.go | 49 ++++++------------- bot/command/context/replyable.go | 31 +++++++++--- bot/permissionwrapper/requirements.go | 13 ----- go.mod | 2 +- 4 files changed, 39 insertions(+), 56 deletions(-) delete mode 100644 bot/permissionwrapper/requirements.go diff --git a/bot/button/handlers/admindebug/server/modals/permissions.go b/bot/button/handlers/admindebug/server/modals/permissions.go index a65808cb..ea264cf1 100644 --- a/bot/button/handlers/admindebug/server/modals/permissions.go +++ b/bot/button/handlers/admindebug/server/modals/permissions.go @@ -121,32 +121,14 @@ func (h *AdminDebugServerPermissionsModalSubmitHandler) Execute(ctx *context.Mod func processPermissionChecks(selectedValues []string, worker *w.Context, guildId uint64, botMember member.Member, panels []database.Panel) ([]string, bool) { serverWidePermissions := []permission.Permission{ - // Required in both modes permission.ManageWebhooks, permission.PinMessages, - // Server-wide only permission.ManageRoles, + permission.ManageChannels, + permission.CreatePrivateThreads, + permission.SendMessagesInThreads, + permission.ManageThreads, } - - // No panels to judge by: report thread mode rather than claim the guild is fine - anyThread := len(panels) == 0 - for _, p := range panels { - if p.UseThreads { - anyThread = true - break - } - } - - if anyThread { - serverWidePermissions = append(serverWidePermissions, - permission.CreatePrivateThreads, - permission.SendMessagesInThreads, - permission.ManageThreads, - ) - } - - // Panel-less opens (message context menu) are always channel mode - serverWidePermissions = append(serverWidePermissions, permission.ManageChannels) serverWidePermissions = append(serverWidePermissions, botpermissions.StandardPermissions...) var results []string @@ -265,7 +247,6 @@ func checkPanelPermissions(worker *w.Context, guildId uint64, botMember member.M []permission.Permission{ permission.ManageChannels, permission.ManageWebhooks, - permission.PinMessages, }, botpermissions.StandardPermissions..., ) @@ -282,11 +263,19 @@ func checkPanelPermissions(worker *w.Context, guildId uint64, botMember member.M hasMissingPermissions = true } } + + if panel.PendingCategory != nil { + result, hasMissing := checkChannelPermissions(worker, *panel.PendingCategory, botMember, guildId, categoryPerms, "Pending Category") + results = append(results, result) + if hasMissing { + hasMissingPermissions = true + } + } } // Check transcript channel if enabled for this panel if panel.TranscriptChannelId != nil { - transcriptPerms := permissionwrapper.TranscriptChannelRequired + transcriptPerms := botpermissions.TranscriptChannelRequired result, hasMissing := checkChannelPermissions(worker, *panel.TranscriptChannelId, botMember, guildId, transcriptPerms, "Transcript Channel") results = append(results, result) if hasMissing { @@ -294,16 +283,8 @@ func checkPanelPermissions(worker *w.Context, guildId uint64, botMember member.M } } - // Check notification channel if using thread mode - if usesThreads && panel.TicketNotificationChannel != nil { - notificationPerms := append( - []permission.Permission{ - permission.EmbedLinks, - permission.AttachFiles, - }, - botpermissions.MinimalPermissions..., - ) - result, hasMissing := checkChannelPermissions(worker, *panel.TicketNotificationChannel, botMember, guildId, notificationPerms, "Notification Channel") + if panel.TicketNotificationChannel != nil { + result, hasMissing := checkChannelPermissions(worker, *panel.TicketNotificationChannel, botMember, guildId, botpermissions.NotifChannelRequired, "Notification Channel") results = append(results, result) if hasMissing { hasMissingPermissions = true diff --git a/bot/command/context/replyable.go b/bot/command/context/replyable.go index c11f55b3..0bbe2600 100644 --- a/bot/command/context/replyable.go +++ b/bot/command/context/replyable.go @@ -327,9 +327,21 @@ func findMissingPermissions(ctx registry.InteractionContext) ([]missingPermLocat var useThreads bool + var panelCustomId string + switch c := ctx.(type) { + case *ButtonContext: + panelCustomId = c.InteractionData.CustomId + case *SelectMenuContext: + if len(c.InteractionData.Values) > 0 { + panelCustomId = c.InteractionData.Values[0] + } + case *ModalContext: + panelCustomId = strings.TrimPrefix(c.Interaction.Data.CustomId, "form_") + } + var panel *database.Panel - if btnCtx, ok := ctx.(*ButtonContext); ok { - p, panelExists, err := dbclient.Client.Panel.GetByCustomId(context.Background(), ctx.GuildId(), btnCtx.InteractionData.CustomId) + if panelCustomId != "" { + p, panelExists, err := dbclient.Client.Panel.GetByCustomId(context.Background(), ctx.GuildId(), panelCustomId) if err == nil && panelExists { panel = &p useThreads = panel.UseThreads @@ -343,7 +355,6 @@ func findMissingPermissions(ctx registry.InteractionContext) ([]missingPermLocat var locations []missingPermLocation - // 1. Primary location: panel channel (thread mode) or ticket category (channel mode) var primaryChannelId uint64 var primaryLabel string var primaryRequired []permission.Permission @@ -372,15 +383,13 @@ func findMissingPermissions(ctx registry.InteractionContext) ([]missingPermLocat } } - // 2. Notification channel (thread mode only) - if useThreads && panel != nil && panel.TicketNotificationChannel != nil { + if panel != nil && panel.TicketNotificationChannel != nil { loc := checkChannel(*panel.TicketNotificationChannel, "Notification channel", botpermissions.NotifChannelRequired) if len(loc.missing) > 0 { locations = append(locations, loc) } } - // 3. Overflow category (channel mode only) - a nil id means the server root if !useThreads && panel != nil && panel.OverflowEnabled && panel.OverflowCategoryId != nil { loc := checkChannel(*panel.OverflowCategoryId, "Overflow category", botpermissions.ChannelModeRequired) if len(loc.missing) > 0 { @@ -388,9 +397,15 @@ func findMissingPermissions(ctx registry.InteractionContext) ([]missingPermLocat } } - // 4. Transcript channel (panel-level, both modes) + if !useThreads && panel != nil && panel.PendingCategory != nil { + loc := checkChannel(*panel.PendingCategory, "Pending category", botpermissions.ChannelModeRequired) + if len(loc.missing) > 0 { + locations = append(locations, loc) + } + } + if panel != nil && panel.TranscriptChannelId != nil { - loc := checkChannel(*panel.TranscriptChannelId, "Transcript channel", permissionwrapper.TranscriptChannelRequired) + loc := checkChannel(*panel.TranscriptChannelId, "Transcript channel", botpermissions.TranscriptChannelRequired) if len(loc.missing) > 0 { locations = append(locations, loc) } diff --git a/bot/permissionwrapper/requirements.go b/bot/permissionwrapper/requirements.go deleted file mode 100644 index 0598cc18..00000000 --- a/bot/permissionwrapper/requirements.go +++ /dev/null @@ -1,13 +0,0 @@ -package permissionwrapper - -import ( - "github.com/TicketsBot-cloud/common/botpermissions" - "github.com/TicketsBot-cloud/gdl/permission" -) - -// Adds EmbedLinks, which upstream omits although close.go posts an embed to the -// archive channel. Drop once common/botpermissions covers it. -var TranscriptChannelRequired = append( - append([]permission.Permission{}, botpermissions.TranscriptChannelRequired...), - permission.EmbedLinks, -) diff --git a/go.mod b/go.mod index 855b27a1..ec7de7db 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.26.0 // replace github.com/TicketsBot-cloud/database => ../database -// replace github.com/TicketsBot-cloud/common => ../common +replace github.com/TicketsBot-cloud/common => ../common // replace github.com/TicketsBot-cloud/gdl => ../gdl From d22d0b6d5154f92b47b64607f99be3ea9e77c9c6 Mon Sep 17 00:00:00 2001 From: biast12 <53872542+biast12@users.noreply.github.com> Date: Tue, 22 Sep 2026 00:10:41 +0200 Subject: [PATCH 13/19] Use unbranded containers for admin responses Adds admin-specific container builders without premium branding and updates admin commands and debug handlers to use them. Splits permission checks into separate containers and batches responses to stay within Discord component limits. --- .../admindebug/server/blacklistreason.go | 2 +- .../admindebug/server/entitlements.go | 2 +- .../admindebug/server/modals/panelsettings.go | 2 +- .../admindebug/server/modals/permissions.go | 84 ++++++++++++------- .../server/modals/ticketpermissions.go | 2 +- .../admindebug/server/modals/usertickets.go | 2 +- .../admindebug/server/monitoredbots.go | 2 +- .../handlers/admindebug/server/recache.go | 4 +- bot/command/impl/admin/adminblacklist.go | 4 +- bot/command/impl/admin/admingenpremium.go | 2 +- .../impl/admin/adminlistuserentitlements.go | 2 +- bot/command/impl/admin/adminrecache.go | 4 +- .../impl/admin/adminwhitelabelassignguild.go | 2 +- bot/command/impl/admin/adminwhitelabeldata.go | 2 +- bot/command/impl/admin/debug/debugserver.go | 4 +- bot/utils/component_utils.go | 27 ++++-- 16 files changed, 93 insertions(+), 54 deletions(-) diff --git a/bot/button/handlers/admindebug/server/blacklistreason.go b/bot/button/handlers/admindebug/server/blacklistreason.go index f399a598..2b923786 100644 --- a/bot/button/handlers/admindebug/server/blacklistreason.go +++ b/bot/button/handlers/admindebug/server/blacklistreason.go @@ -127,7 +127,7 @@ func (h *AdminDebugServerBlacklistReasonHandler) Execute(ctx *context.ButtonCont } ctx.ReplyWith(command.NewEphemeralMessageResponseWithComponents([]component.Component{ - utils.BuildContainerRaw( + utils.BuildAdminContainerRaw( ctx, customisation.Red, "Admin - Debug Server - Blacklist Reason", diff --git a/bot/button/handlers/admindebug/server/entitlements.go b/bot/button/handlers/admindebug/server/entitlements.go index f4c8cab7..b17f1500 100644 --- a/bot/button/handlers/admindebug/server/entitlements.go +++ b/bot/button/handlers/admindebug/server/entitlements.go @@ -103,7 +103,7 @@ func (h *AdminDebugServerEntitlementsHandler) Execute(ctx *context.ButtonContext } ctx.ReplyWith(command.NewEphemeralMessageResponseWithComponents([]component.Component{ - utils.BuildContainerRaw( + utils.BuildAdminContainerRaw( ctx, customisation.Green, "Admin - Debug Server - Entitlements", diff --git a/bot/button/handlers/admindebug/server/modals/panelsettings.go b/bot/button/handlers/admindebug/server/modals/panelsettings.go index a0e90da5..08c4df3e 100644 --- a/bot/button/handlers/admindebug/server/modals/panelsettings.go +++ b/bot/button/handlers/admindebug/server/modals/panelsettings.go @@ -118,7 +118,7 @@ func (h *AdminDebugServerPanelSettingsModalHandler) Execute(ctx *context.ModalCo } ctx.ReplyWith(command.NewEphemeralMessageResponseWithComponents([]component.Component{ - utils.BuildContainerRaw( + utils.BuildAdminContainerRaw( ctx, customisation.Orange, "Admin - Debug Server - Panel Settings", diff --git a/bot/button/handlers/admindebug/server/modals/permissions.go b/bot/button/handlers/admindebug/server/modals/permissions.go index ea264cf1..5ff11af8 100644 --- a/bot/button/handlers/admindebug/server/modals/permissions.go +++ b/bot/button/handlers/admindebug/server/modals/permissions.go @@ -100,26 +100,45 @@ func (h *AdminDebugServerPermissionsModalSubmitHandler) Execute(ctx *context.Mod return } - // Process permission checks using shared logic - results, hasMissingPermissions := processPermissionChecks(selectedValues, worker, guildId, botMember, panels) + sections := processPermissionChecks(selectedValues, worker, guildId, botMember, panels) - // Choose color based on whether permissions are missing - colour := customisation.Green - if hasMissingPermissions { - colour = customisation.Orange + containers := make([]component.Component, 0, len(sections)) + for _, section := range sections { + colour := customisation.Green + if section.hasMissing { + colour = customisation.Orange + } + + containers = append(containers, utils.BuildAdminContainerRaw(ctx, colour, section.title, section.body)) } - ctx.ReplyWith(command.NewEphemeralMessageResponseWithComponents([]component.Component{ - utils.BuildContainerRaw( - ctx, - colour, - "Admin - Debug Server - Permissions Check", - strings.Join(results, "\n\n"), - ), - })) + for len(containers) > 0 { + chunk := containers + if len(chunk) > maxContainersPerMessage { + chunk = chunk[:maxContainersPerMessage] + } + containers = containers[len(chunk):] + + if _, err := ctx.ReplyWith(command.NewEphemeralMessageResponseWithComponents(chunk)); err != nil { + ctx.HandleError(err) + return + } + } +} + +const ( + maxComponentsPerMessage = 40 + componentsPerContainer = 4 + maxContainersPerMessage = maxComponentsPerMessage / componentsPerContainer +) + +type permissionSection struct { + title string + body string + hasMissing bool } -func processPermissionChecks(selectedValues []string, worker *w.Context, guildId uint64, botMember member.Member, panels []database.Panel) ([]string, bool) { +func processPermissionChecks(selectedValues []string, worker *w.Context, guildId uint64, botMember member.Member, panels []database.Panel) []permissionSection { serverWidePermissions := []permission.Permission{ permission.ManageWebhooks, permission.PinMessages, @@ -131,8 +150,7 @@ func processPermissionChecks(selectedValues []string, worker *w.Context, guildId } serverWidePermissions = append(serverWidePermissions, botpermissions.StandardPermissions...) - var results []string - var hasMissingPermissions bool + var sections []permissionSection for _, value := range selectedValues { parts := strings.Split(value, "_") @@ -140,11 +158,12 @@ func processPermissionChecks(selectedValues []string, worker *w.Context, guildId switch checkType { case "server": - result, hasMissing := checkServerWidePermissions(worker, guildId, botMember, serverWidePermissions) - results = append(results, fmt.Sprintf("**Server Wide Permissions**\n%s", result)) - if hasMissing { - hasMissingPermissions = true - } + body, hasMissing := checkServerWidePermissions(worker, guildId, botMember, serverWidePermissions) + sections = append(sections, permissionSection{ + title: "Server Wide Permissions", + body: body, + hasMissing: hasMissing, + }) case "panel": if len(parts) < 2 { @@ -155,7 +174,6 @@ func processPermissionChecks(selectedValues []string, worker *w.Context, guildId continue } - // Find the panel var panel *database.Panel for i := range panels { if panels[i].MessageId == panelMessageId { @@ -165,20 +183,24 @@ func processPermissionChecks(selectedValues []string, worker *w.Context, guildId } if panel == nil { - results = append(results, fmt.Sprintf("**Panel (ID: %d)**\nPanel not found", panelMessageId)) + sections = append(sections, permissionSection{ + title: fmt.Sprintf("Panel (ID: %d)", panelMessageId), + body: "Panel not found", + hasMissing: true, + }) continue } - // Check permissions for this panel - panelResults, hasMissing := checkPanelPermissions(worker, guildId, botMember, *panel) - results = append(results, fmt.Sprintf("**Panel: %s**\n%s", panel.Title, panelResults)) - if hasMissing { - hasMissingPermissions = true - } + body, hasMissing := checkPanelPermissions(worker, guildId, botMember, *panel) + sections = append(sections, permissionSection{ + title: fmt.Sprintf("Panel: %s", panel.Title), + body: body, + hasMissing: hasMissing, + }) } } - return results, hasMissingPermissions + return sections } func checkServerWidePermissions(worker *w.Context, guildId uint64, botMember member.Member, requiredPermissions []permission.Permission) (string, bool) { diff --git a/bot/button/handlers/admindebug/server/modals/ticketpermissions.go b/bot/button/handlers/admindebug/server/modals/ticketpermissions.go index fe9b5f77..28ca672f 100644 --- a/bot/button/handlers/admindebug/server/modals/ticketpermissions.go +++ b/bot/button/handlers/admindebug/server/modals/ticketpermissions.go @@ -171,7 +171,7 @@ func (h *AdminDebugServerTicketPermissionsModalSubmitHandler) Execute(ctx *conte } ctx.ReplyWith(command.NewEphemeralMessageResponseWithComponents([]component.Component{ - utils.BuildContainerRaw( + utils.BuildAdminContainerRaw( ctx, customisation.Green, "Admin - Debug Server - User Permissions Check", diff --git a/bot/button/handlers/admindebug/server/modals/usertickets.go b/bot/button/handlers/admindebug/server/modals/usertickets.go index 98b95302..35a9ea06 100644 --- a/bot/button/handlers/admindebug/server/modals/usertickets.go +++ b/bot/button/handlers/admindebug/server/modals/usertickets.go @@ -121,7 +121,7 @@ func (h *AdminDebugServerUserTicketsModalSubmitHandler) Execute(ctx *context.Mod } ctx.ReplyWith(command.NewEphemeralMessageResponseWithComponents([]component.Component{ - utils.BuildContainerRaw( + utils.BuildAdminContainerRaw( ctx, customisation.Green, "Admin - Debug Server - User Tickets", diff --git a/bot/button/handlers/admindebug/server/monitoredbots.go b/bot/button/handlers/admindebug/server/monitoredbots.go index 614acc22..6111ba12 100644 --- a/bot/button/handlers/admindebug/server/monitoredbots.go +++ b/bot/button/handlers/admindebug/server/monitoredbots.go @@ -72,7 +72,7 @@ func (h *AdminDebugServerMonitoredBotsHandler) Execute(ctx *context.ButtonContex } ctx.ReplyWith(command.NewEphemeralMessageResponseWithComponents([]component.Component{ - utils.BuildContainerRaw( + utils.BuildAdminContainerRaw( ctx, customisation.Orange, "Admin - Debug Server - Monitored Bots", diff --git a/bot/button/handlers/admindebug/server/recache.go b/bot/button/handlers/admindebug/server/recache.go index 9bf99c4c..94f6bb8c 100644 --- a/bot/button/handlers/admindebug/server/recache.go +++ b/bot/button/handlers/admindebug/server/recache.go @@ -43,7 +43,7 @@ func (h *AdminDebugServerRecacheHandler) Execute(ctx *context.ButtonContext) { if onCooldown, cooldownTime := redis.GetRecacheCooldown(guildId); onCooldown { ctx.ReplyWith(command.NewMessageResponseWithComponents([]component.Component{ - utils.BuildContainerWithComponents( + utils.BuildAdminContainerWithComponents( ctx, customisation.Red, "Admin - Recache", @@ -87,7 +87,7 @@ func (h *AdminDebugServerRecacheHandler) Execute(ctx *context.ButtonContext) { } ctx.ReplyWith(command.NewMessageResponseWithComponents([]component.Component{ - utils.BuildContainerWithComponents( + utils.BuildAdminContainerWithComponents( ctx, customisation.Orange, "Admin - Recache", diff --git a/bot/command/impl/admin/adminblacklist.go b/bot/command/impl/admin/adminblacklist.go index ec168574..59538747 100644 --- a/bot/command/impl/admin/adminblacklist.go +++ b/bot/command/impl/admin/adminblacklist.go @@ -62,7 +62,7 @@ func (AdminBlacklistCommand) Execute(ctx registry.CommandContext, guildIdRaw str if isBlacklisted, blacklistReason, _ := dbclient.Client.ServerBlacklist.IsBlacklisted(ctx, guildId); isBlacklisted { ctx.ReplyWith(command.NewEphemeralMessageResponseWithComponents([]component.Component{ - utils.BuildContainerRaw( + utils.BuildAdminContainerRaw( ctx, customisation.Orange, "Admin - Blacklist", @@ -112,7 +112,7 @@ func (AdminBlacklistCommand) Execute(ctx registry.CommandContext, guildIdRaw str } ctx.ReplyWith(command.NewMessageResponseWithComponents([]component.Component{ - utils.BuildContainerRaw( + utils.BuildAdminContainerRaw( ctx, customisation.Orange, "Admin - Blacklist", diff --git a/bot/command/impl/admin/admingenpremium.go b/bot/command/impl/admin/admingenpremium.go index 9907721c..e23eb669 100644 --- a/bot/command/impl/admin/admingenpremium.go +++ b/bot/command/impl/admin/admingenpremium.go @@ -110,7 +110,7 @@ func (AdminGenPremiumCommand) Execute(ctx registry.CommandContext, skuIdRaw stri } ctx.ReplyWith(command.NewMessageResponseWithComponents(utils.Slice( - utils.BuildContainerRaw( + utils.BuildAdminContainerRaw( ctx, customisation.Orange, "Admin - Premium Key Generation", diff --git a/bot/command/impl/admin/adminlistuserentitlements.go b/bot/command/impl/admin/adminlistuserentitlements.go index 7792059d..282b7c28 100644 --- a/bot/command/impl/admin/adminlistuserentitlements.go +++ b/bot/command/impl/admin/adminlistuserentitlements.go @@ -77,7 +77,7 @@ func (AdminListUserEntitlementsCommand) Execute(ctx registry.CommandContext, use } ctx.ReplyWith(command.NewMessageResponseWithComponents([]component.Component{ - utils.BuildContainerWithComponents( + utils.BuildAdminContainerWithComponents( ctx, customisation.Orange, "Admin - User Entitlements", diff --git a/bot/command/impl/admin/adminrecache.go b/bot/command/impl/admin/adminrecache.go index 267752dd..10fecf31 100644 --- a/bot/command/impl/admin/adminrecache.go +++ b/bot/command/impl/admin/adminrecache.go @@ -47,7 +47,7 @@ func (AdminRecacheCommand) Execute(ctx registry.CommandContext, guildIdRaw strin if onCooldown, cooldownTime := redis.GetRecacheCooldown(guildId); onCooldown { ctx.ReplyWith(command.NewMessageResponseWithComponents([]component.Component{ - utils.BuildContainerWithComponents( + utils.BuildAdminContainerWithComponents( ctx, customisation.Red, "Admin - Recache", @@ -92,7 +92,7 @@ func (AdminRecacheCommand) Execute(ctx registry.CommandContext, guildIdRaw strin } ctx.ReplyWith(command.NewMessageResponseWithComponents([]component.Component{ - utils.BuildContainerWithComponents( + utils.BuildAdminContainerWithComponents( ctx, customisation.Orange, "Admin - Recache", diff --git a/bot/command/impl/admin/adminwhitelabelassignguild.go b/bot/command/impl/admin/adminwhitelabelassignguild.go index 3d7b266d..36877fba 100644 --- a/bot/command/impl/admin/adminwhitelabelassignguild.go +++ b/bot/command/impl/admin/adminwhitelabelassignguild.go @@ -69,7 +69,7 @@ func (AdminWhitelabelAssignGuildCommand) Execute(ctx registry.CommandContext, bo } ctx.ReplyWith(command.NewMessageResponseWithComponents([]component.Component{ - utils.BuildContainerRaw( + utils.BuildAdminContainerRaw( ctx, customisation.Orange, "Admin - Whitelabel Assign Guild", diff --git a/bot/command/impl/admin/adminwhitelabeldata.go b/bot/command/impl/admin/adminwhitelabeldata.go index bfeef989..d7bded05 100644 --- a/bot/command/impl/admin/adminwhitelabeldata.go +++ b/bot/command/impl/admin/adminwhitelabeldata.go @@ -140,7 +140,7 @@ func (AdminWhitelabelDataCommand) Execute(ctx registry.CommandContext, userId ui } components := []component.Component{ - utils.BuildContainerWithComponents(ctx, customisation.Green, "Admin - Whitelabel Data", innerComponents), + utils.BuildAdminContainerWithComponents(ctx, customisation.Green, "Admin - Whitelabel Data", innerComponents), } if data.BotId != 0 { diff --git a/bot/command/impl/admin/debug/debugserver.go b/bot/command/impl/admin/debug/debugserver.go index 34a860fd..f243b7fe 100644 --- a/bot/command/impl/admin/debug/debugserver.go +++ b/bot/command/impl/admin/debug/debugserver.go @@ -133,7 +133,7 @@ func (AdminDebugServerCommand) Execute(ctx registry.CommandContext, raw string) } } ctx.ReplyWith(command.NewEphemeralMessageResponseWithComponents([]component.Component{ - utils.BuildContainerRaw( + utils.BuildAdminContainerRaw( ctx, customisation.Red, "Admin - This server is blacklisted", @@ -398,7 +398,7 @@ func (AdminDebugServerCommand) Execute(ctx registry.CommandContext, raw string) // Build component list components := []component.Component{ - utils.BuildContainerRaw( + utils.BuildAdminContainerRaw( ctx, colour, "Admin - Debug Server", diff --git a/bot/utils/component_utils.go b/bot/utils/component_utils.go index a836ecb8..d947eabd 100644 --- a/bot/utils/component_utils.go +++ b/bot/utils/component_utils.go @@ -29,22 +29,39 @@ func BuildContainerRaw(ctx registry.CommandContext, colour customisation.Colour, } func BuildContainerWithComponents[T string | i18n.MessageId](ctx registry.CommandContext, colour customisation.Colour, title T, innerComponents []component.Component) component.Component { - var titleStr string + return buildContainer(ctx, colour, containerTitle(ctx, title), innerComponents, true) +} + +func BuildAdminContainerRaw(ctx registry.CommandContext, colour customisation.Colour, title, content string) component.Component { + return BuildAdminContainerWithComponents(ctx, colour, title, Slice(component.BuildTextDisplay(component.TextDisplay{ + Content: content, + }))) +} + +func BuildAdminContainerWithComponents[T string | i18n.MessageId](ctx registry.CommandContext, colour customisation.Colour, title T, innerComponents []component.Component) component.Component { + return buildContainer(ctx, colour, containerTitle(ctx, title), innerComponents, false) +} + +func containerTitle[T string | i18n.MessageId](ctx registry.CommandContext, title T) string { switch t := any(title).(type) { case string: - titleStr = t + return t case i18n.MessageId: - titleStr = ctx.GetMessage(t) + return ctx.GetMessage(t) } + return "" +} + +func buildContainer(ctx registry.CommandContext, colour customisation.Colour, title string, innerComponents []component.Component, branded bool) component.Component { components := append(Slice( component.BuildTextDisplay(component.TextDisplay{ - Content: fmt.Sprintf("### %s", titleStr), + Content: fmt.Sprintf("### %s", title), }), component.BuildSeparator(component.Separator{}), ), innerComponents...) - if ctx.PremiumTier() == premium.None && !ctx.Worker().IsWhitelabel { + if branded && ctx.PremiumTier() == premium.None && !ctx.Worker().IsWhitelabel { components = addPremiumFooter(components) } From e14b1e124feb555b8304d806c03b39b95a826f71 Mon Sep 17 00:00:00 2001 From: biast12 <53872542+biast12@users.noreply.github.com> Date: Tue, 22 Sep 2026 16:49:04 +0200 Subject: [PATCH 14/19] Fix permission checks and response chunking Ensure permission diagnostics acknowledge interactions before sending follow-ups, split responses by component and text limits, and apply thread and pending-category permissions correctly. Also fix pin permission checks to use the ticket category. --- .../admindebug/server/modals/permissions.go | 82 +++++++++++-------- bot/command/context/replyable.go | 18 ++-- bot/logic/open.go | 2 +- 3 files changed, 64 insertions(+), 38 deletions(-) diff --git a/bot/button/handlers/admindebug/server/modals/permissions.go b/bot/button/handlers/admindebug/server/modals/permissions.go index 5ff11af8..e2e1ee5b 100644 --- a/bot/button/handlers/admindebug/server/modals/permissions.go +++ b/bot/button/handlers/admindebug/server/modals/permissions.go @@ -102,36 +102,55 @@ func (h *AdminDebugServerPermissionsModalSubmitHandler) Execute(ctx *context.Mod sections := processPermissionChecks(selectedValues, worker, guildId, botMember, panels) - containers := make([]component.Component, 0, len(sections)) - for _, section := range sections { - colour := customisation.Green - if section.hasMissing { - colour = customisation.Orange - } + // Ack first so every chunk is a follow-up; the initial reply is sent by another goroutine + ctx.Ack() - containers = append(containers, utils.BuildAdminContainerRaw(ctx, colour, section.title, section.body)) - } + var chunk []component.Component + var chunkText int - for len(containers) > 0 { - chunk := containers - if len(chunk) > maxContainersPerMessage { - chunk = chunk[:maxContainersPerMessage] + flush := func() bool { + if len(chunk) == 0 { + return true } - containers = containers[len(chunk):] if _, err := ctx.ReplyWith(command.NewEphemeralMessageResponseWithComponents(chunk)); err != nil { ctx.HandleError(err) - return + return false } + + chunk, chunkText = nil, 0 + return true } + + for _, section := range sections { + cost := len(section.title) + len(section.body) + if len(chunk) >= maxContainersPerMessage || (len(chunk) > 0 && chunkText+cost > maxTextPerMessage) { + if !flush() { + return + } + } + + colour := customisation.Green + if section.hasMissing { + colour = customisation.Orange + } + + chunk = append(chunk, utils.BuildAdminContainerRaw(ctx, colour, section.title, section.body)) + chunkText += cost + } + + flush() } const ( maxComponentsPerMessage = 40 componentsPerContainer = 4 maxContainersPerMessage = maxComponentsPerMessage / componentsPerContainer + maxTextPerMessage = 3900 ) +var pendingCategoryPerms = []permission.Permission{permission.ViewChannel, permission.ManageChannels} + type permissionSection struct { title string body string @@ -144,10 +163,22 @@ func processPermissionChecks(selectedValues []string, worker *w.Context, guildId permission.PinMessages, permission.ManageRoles, permission.ManageChannels, + // /notes opens a private thread inside a channel-mode ticket permission.CreatePrivateThreads, permission.SendMessagesInThreads, - permission.ManageThreads, } + + anyThread := len(panels) == 0 + for _, p := range panels { + if p.UseThreads { + anyThread = true + break + } + } + if anyThread { + serverWidePermissions = append(serverWidePermissions, permission.ManageThreads) + } + serverWidePermissions = append(serverWidePermissions, botpermissions.StandardPermissions...) var sections []permissionSection @@ -245,15 +276,9 @@ func checkPanelPermissions(worker *w.Context, guildId uint64, botMember member.M if panel.ChannelId != 0 { var panelChannelPerms []permission.Permission if usesThreads { - // Thread mode: permissions needed in the panel channel where threads are created. - panelChannelPerms = append( - botpermissions.ThreadModeRequired, - permission.ManageWebhooks, - permission.PinMessages, - ) + panelChannelPerms = botpermissions.ThreadModeRequired } else { - // Channel mode: just standard permissions (no special ones needed for panel channel in channel mode) - panelChannelPerms = append([]permission.Permission{}, botpermissions.StandardPermissions...) + panelChannelPerms = botpermissions.StandardPermissions } result, hasMissing := checkChannelPermissions(worker, panel.ChannelId, botMember, guildId, panelChannelPerms, "Panel Channel") results = append(results, result) @@ -264,14 +289,7 @@ func checkPanelPermissions(worker *w.Context, guildId uint64, botMember member.M // Check category permissions if using channel mode if !usesThreads && panel.TargetCategory != 0 { - // Category needs channel management permissions + standard permissions - categoryPerms := append( - []permission.Permission{ - permission.ManageChannels, - permission.ManageWebhooks, - }, - botpermissions.StandardPermissions..., - ) + categoryPerms := botpermissions.ChannelModeRequired result, hasMissing := checkChannelPermissions(worker, panel.TargetCategory, botMember, guildId, categoryPerms, "Category") results = append(results, result) if hasMissing { @@ -287,7 +305,7 @@ func checkPanelPermissions(worker *w.Context, guildId uint64, botMember member.M } if panel.PendingCategory != nil { - result, hasMissing := checkChannelPermissions(worker, *panel.PendingCategory, botMember, guildId, categoryPerms, "Pending Category") + result, hasMissing := checkChannelPermissions(worker, *panel.PendingCategory, botMember, guildId, pendingCategoryPerms, "Pending Category") results = append(results, result) if hasMissing { hasMissingPermissions = true diff --git a/bot/command/context/replyable.go b/bot/command/context/replyable.go index 0bbe2600..ebd35bb2 100644 --- a/bot/command/context/replyable.go +++ b/bot/command/context/replyable.go @@ -390,15 +390,23 @@ func findMissingPermissions(ctx registry.InteractionContext) ([]missingPermLocat } } - if !useThreads && panel != nil && panel.OverflowEnabled && panel.OverflowCategoryId != nil { - loc := checkChannel(*panel.OverflowCategoryId, "Overflow category", botpermissions.ChannelModeRequired) - if len(loc.missing) > 0 { - locations = append(locations, loc) + if !useThreads && panel != nil && panel.OverflowEnabled { + if panel.OverflowCategoryId != nil { + loc := checkChannel(*panel.OverflowCategoryId, "Overflow category", botpermissions.ChannelModeRequired) + if len(loc.missing) > 0 { + locations = append(locations, loc) + } + } else if primaryChannelId != 0 { + // Skipped when the primary check was already guild-level + missing := permissionwrapper.GetMissingPermissions(ctx.Worker(), ctx.GuildId(), ctx.Worker().BotId, botpermissions.ChannelModeRequired...) + if len(missing) > 0 { + locations = append(locations, missingPermLocation{label: "Overflow (no category)", missing: missing}) + } } } if !useThreads && panel != nil && panel.PendingCategory != nil { - loc := checkChannel(*panel.PendingCategory, "Pending category", botpermissions.ChannelModeRequired) + loc := checkChannel(*panel.PendingCategory, "Pending category", []permission.Permission{permission.ViewChannel, permission.ManageChannels}) if len(loc.missing) > 0 { locations = append(locations, loc) } diff --git a/bot/logic/open.go b/bot/logic/open.go index ba9d28c5..f7cd1869 100644 --- a/bot/logic/open.go +++ b/bot/logic/open.go @@ -964,7 +964,7 @@ func CreateOverwrites(ctx context.Context, cmd registry.CommandContext, userId u // Only add PinMessages if the bot has the permission if permissionwrapper.HasPermissions(cmd.Worker(), cmd.GuildId(), cmd.Worker().BotId, permission.PinMessages) { selfAllow = append(selfAllow, permission.PinMessages) - } else if permissionwrapper.HasPermissionsChannel(cmd.Worker(), cmd.GuildId(), cmd.ChannelId(), cmd.Worker().BotId, permission.PinMessages) { + } else if permissionwrapper.HasPermissionsChannel(cmd.Worker(), cmd.GuildId(), cmd.Worker().BotId, categoryId, permission.PinMessages) { selfAllow = append(selfAllow, permission.PinMessages) } From 6b6bee2432486b6b2898128e61823d31cc67d5bb Mon Sep 17 00:00:00 2001 From: biast12 <53872542+biast12@users.noreply.github.com> Date: Sun, 27 Sep 2026 12:28:17 +0200 Subject: [PATCH 15/19] Bump common Use the latest published common module version and disable the local replacement so builds resolve the remote dependency. --- go.mod | 4 ++-- go.sum | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index ec7de7db..3db2a677 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.26.0 // replace github.com/TicketsBot-cloud/database => ../database -replace github.com/TicketsBot-cloud/common => ../common +// replace github.com/TicketsBot-cloud/common => ../common // replace github.com/TicketsBot-cloud/gdl => ../gdl @@ -15,7 +15,7 @@ replace github.com/TicketsBot-cloud/common => ../common require ( cloud.google.com/go/profiler v0.4.2 github.com/TicketsBot-cloud/archiverclient v0.0.0-20251015181023-f0b66a074704 - github.com/TicketsBot-cloud/common v0.0.0-20260905165836-38e4090764a4 + github.com/TicketsBot-cloud/common v0.0.0-20260927072029-424264fc4be2 github.com/TicketsBot-cloud/database v0.0.0-20260913165941-c2ecc7191b02 github.com/TicketsBot-cloud/gdl v0.0.0-20260426095953-999472e6e538 github.com/caarlos0/env/v10 v10.0.0 diff --git a/go.sum b/go.sum index df93d4ec..86d78f1d 100644 --- a/go.sum +++ b/go.sum @@ -27,8 +27,8 @@ github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0 github.com/ReneKroon/ttlcache v1.6.0/go.mod h1:DG6nbhXKUQhrExfwwLuZUdH7UnRDDRA1IW+nBuCssvs= github.com/TicketsBot-cloud/archiverclient v0.0.0-20251015181023-f0b66a074704 h1:liLfvCrzoJ89DXFHzsd1iK3cyP8s4i0CnZPRFEj53zg= github.com/TicketsBot-cloud/archiverclient v0.0.0-20251015181023-f0b66a074704/go.mod h1:Mux1bEPpOHwRw1wo6Fa6qJLJH9Erk9qv1yAIfLi1Wmw= -github.com/TicketsBot-cloud/common v0.0.0-20260905165836-38e4090764a4 h1:zcPcJ+03xW9qalqzqllM6j0ZPdhuL+mrtGw8woLrAMo= -github.com/TicketsBot-cloud/common v0.0.0-20260905165836-38e4090764a4/go.mod h1:yL+VPSYNVK5gxUkA+fbb0WdTjGfWXnr7ScfL0rwk29g= +github.com/TicketsBot-cloud/common v0.0.0-20260927072029-424264fc4be2 h1:qeebeGGdrsDXrngOVrKMDt9G3JUTJwT/IEPTB61d46s= +github.com/TicketsBot-cloud/common v0.0.0-20260927072029-424264fc4be2/go.mod h1:yL+VPSYNVK5gxUkA+fbb0WdTjGfWXnr7ScfL0rwk29g= github.com/TicketsBot-cloud/database v0.0.0-20260913165941-c2ecc7191b02 h1:3Z8PpXBUbdYn2srMRg3Nj0MDxnTB53CzIEAOUBIubhM= github.com/TicketsBot-cloud/database v0.0.0-20260913165941-c2ecc7191b02/go.mod h1:0fKn7nPX6KeljYNQtGQiuZ5aJ/TczgXvBhnbz+4GEc8= github.com/TicketsBot-cloud/gdl v0.0.0-20260426095953-999472e6e538 h1:ewKw1Wv1x/yi8h1IH7EofcYnPUxWZaVTIPFkT97nhn0= From d47da77286ff90bf191a8eec9eef01e655389c82 Mon Sep 17 00:00:00 2001 From: biast12 <53872542+biast12@users.noreply.github.com> Date: Sun, 4 Oct 2026 20:00:37 +0200 Subject: [PATCH 16/19] Use channel-aware permission names --- .../admindebug/server/modals/permissions.go | 9 +++---- bot/command/context/replyable.go | 24 +++++++++++++++---- bot/logic/open.go | 3 +-- bot/permissionwrapper/permission.go | 7 +++--- go.mod | 2 +- go.sum | 4 ++-- 6 files changed, 30 insertions(+), 19 deletions(-) diff --git a/bot/button/handlers/admindebug/server/modals/permissions.go b/bot/button/handlers/admindebug/server/modals/permissions.go index e2e1ee5b..7e985cd6 100644 --- a/bot/button/handlers/admindebug/server/modals/permissions.go +++ b/bot/button/handlers/admindebug/server/modals/permissions.go @@ -339,14 +339,11 @@ func checkPanelPermissions(worker *w.Context, guildId uint64, botMember member.M } func checkChannelPermissions(worker *w.Context, channelId uint64, botMember member.Member, guildId uint64, requiredPermissions []permission.Permission, label string) (string, bool) { - channel, err := worker.GetChannel(channelId) - if err != nil { + missingPerms, channel, ok := permissionwrapper.GetMissingPermissionsChannel(worker, guildId, botMember.User.Id, channelId, requiredPermissions...) + if !ok { return fmt.Sprintf("**%s**\nError: Could not fetch channel", label), false } - // Use permissionwrapper to get missing permissions - missingPerms := permissionwrapper.GetMissingPermissionsChannel(worker, guildId, botMember.User.Id, channelId, requiredPermissions...) - // Create a map for quick lookup of missing permissions missingMap := make(map[permission.Permission]bool) for _, perm := range missingPerms { @@ -357,7 +354,7 @@ func checkChannelPermissions(worker *w.Context, channelId uint64, botMember memb var missing []string for _, perm := range requiredPermissions { if missingMap[perm] { - missing = append(missing, perm.String()) + missing = append(missing, perm.ChannelName(channel.Type)) } } diff --git a/bot/command/context/replyable.go b/bot/command/context/replyable.go index ebd35bb2..592b99b6 100644 --- a/bot/command/context/replyable.go +++ b/bot/command/context/replyable.go @@ -14,6 +14,7 @@ import ( "github.com/TicketsBot-cloud/common/premium" "github.com/TicketsBot-cloud/common/sentry" "github.com/TicketsBot-cloud/database" + "github.com/TicketsBot-cloud/gdl/objects/channel" "github.com/TicketsBot-cloud/gdl/objects/channel/embed" "github.com/TicketsBot-cloud/gdl/objects/guild/emoji" "github.com/TicketsBot-cloud/gdl/objects/interaction/component" @@ -223,7 +224,7 @@ func (r *Replyable) buildErrorResponse(err error, eventId string, includeInviteL for _, loc := range missingByLocation { locationMsg += fmt.Sprintf("**%s:**\n", loc.label) for _, perm := range loc.missing { - locationMsg += fmt.Sprintf("* `%s`\n", perm.String()) + locationMsg += fmt.Sprintf("* `%s`\n", loc.permissionName(perm)) } } @@ -316,8 +317,17 @@ func (r *Replyable) formatDiscordError(restError request.RestError, eventId stri } type missingPermLocation struct { - label string - missing []permission.Permission + label string + missing []permission.Permission + channelType *channel.ChannelType // nil when guild-level or the channel couldn't be fetched (ChannelTypeGuildText is 0) +} + +func (l missingPermLocation) permissionName(p permission.Permission) string { + if l.channelType == nil { + return p.String() + } + + return p.ChannelName(*l.channelType) } func findMissingPermissions(ctx registry.InteractionContext) ([]missingPermLocation, error) { @@ -349,8 +359,12 @@ func findMissingPermissions(ctx registry.InteractionContext) ([]missingPermLocat } checkChannel := func(channelId uint64, label string, required []permission.Permission) missingPermLocation { - missing := permissionwrapper.GetMissingPermissionsChannel(ctx.Worker(), ctx.GuildId(), ctx.Worker().BotId, channelId, required...) - return missingPermLocation{label: label, missing: missing} + missing, ch, ok := permissionwrapper.GetMissingPermissionsChannel(ctx.Worker(), ctx.GuildId(), ctx.Worker().BotId, channelId, required...) + loc := missingPermLocation{label: label, missing: missing} + if ok { + loc.channelType = &ch.Type + } + return loc } var locations []missingPermLocation diff --git a/bot/logic/open.go b/bot/logic/open.go index f7cd1869..40625d76 100644 --- a/bot/logic/open.go +++ b/bot/logic/open.go @@ -1377,8 +1377,7 @@ func countRealChannels(channels []channel.Channel, parentId uint64) int { var count int for _, ch := range channels { - // Ignore threads - if ch.Type == channel.ChannelTypeGuildPublicThread || ch.Type == channel.ChannelTypeGuildPrivateThread || ch.Type == channel.ChannelTypeGuildNewsThread { + if ch.Type.IsThread() { continue } diff --git a/bot/permissionwrapper/permission.go b/bot/permissionwrapper/permission.go index d4964e58..807d5d83 100644 --- a/bot/permissionwrapper/permission.go +++ b/bot/permissionwrapper/permission.go @@ -55,13 +55,14 @@ func GetMissingPermissions(ctx *worker.Context, guildId, userId uint64, required return botpermissions.MissingPermissions(guildId, userId, []uint64(m.Roles), nil, buildRoleMap(roles), required) } -func GetMissingPermissionsChannel(ctx *worker.Context, guildId, userId, channelId uint64, required ...permission.Permission) []permission.Permission { +func GetMissingPermissionsChannel(ctx *worker.Context, guildId, userId, channelId uint64, required ...permission.Permission) ([]permission.Permission, channel.Channel, bool) { m, roles, ch, err := fetchChannelData(ctx, guildId, userId, channelId) if err != nil { sentry.Error(err) - return required + return required, channel.Channel{}, false } - return botpermissions.MissingPermissions(guildId, userId, []uint64(m.Roles), ch.PermissionOverwrites, buildRoleMap(roles), required) + missing := botpermissions.MissingPermissions(guildId, userId, []uint64(m.Roles), ch.PermissionOverwrites, buildRoleMap(roles), required) + return missing, ch, true } func fetchGuildData(ctx *worker.Context, guildId, userId uint64) (member.Member, []guild.Role, error) { diff --git a/go.mod b/go.mod index 3db2a677..62004a53 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/TicketsBot-cloud/archiverclient v0.0.0-20251015181023-f0b66a074704 github.com/TicketsBot-cloud/common v0.0.0-20260927072029-424264fc4be2 github.com/TicketsBot-cloud/database v0.0.0-20260913165941-c2ecc7191b02 - github.com/TicketsBot-cloud/gdl v0.0.0-20260426095953-999472e6e538 + github.com/TicketsBot-cloud/gdl v0.0.0-20260927072153-8dbf0b284f57 github.com/caarlos0/env/v10 v10.0.0 github.com/elliotchance/orderedmap v1.8.0 github.com/getsentry/sentry-go v0.32.0 diff --git a/go.sum b/go.sum index 86d78f1d..5e05d692 100644 --- a/go.sum +++ b/go.sum @@ -31,8 +31,8 @@ github.com/TicketsBot-cloud/common v0.0.0-20260927072029-424264fc4be2 h1:qeebeGG github.com/TicketsBot-cloud/common v0.0.0-20260927072029-424264fc4be2/go.mod h1:yL+VPSYNVK5gxUkA+fbb0WdTjGfWXnr7ScfL0rwk29g= github.com/TicketsBot-cloud/database v0.0.0-20260913165941-c2ecc7191b02 h1:3Z8PpXBUbdYn2srMRg3Nj0MDxnTB53CzIEAOUBIubhM= github.com/TicketsBot-cloud/database v0.0.0-20260913165941-c2ecc7191b02/go.mod h1:0fKn7nPX6KeljYNQtGQiuZ5aJ/TczgXvBhnbz+4GEc8= -github.com/TicketsBot-cloud/gdl v0.0.0-20260426095953-999472e6e538 h1:ewKw1Wv1x/yi8h1IH7EofcYnPUxWZaVTIPFkT97nhn0= -github.com/TicketsBot-cloud/gdl v0.0.0-20260426095953-999472e6e538/go.mod h1:CdwBR2egPtxUXjD2CgC9ZwfuB8dz9HPePM8nuG6dt7Y= +github.com/TicketsBot-cloud/gdl v0.0.0-20260927072153-8dbf0b284f57 h1:cFlQplQOlJCb+JQfxny+MJTg4dMKyEAvzB50MrWRa1E= +github.com/TicketsBot-cloud/gdl v0.0.0-20260927072153-8dbf0b284f57/go.mod h1:CdwBR2egPtxUXjD2CgC9ZwfuB8dz9HPePM8nuG6dt7Y= github.com/TicketsBot-cloud/logarchiver v0.0.0-20251018211319-7a7df5cacbdc h1:qTLNpCvIqM7UwZ6MdWQ9EztcDsIJfHh+VJdG+ULLEaA= github.com/TicketsBot-cloud/logarchiver v0.0.0-20251018211319-7a7df5cacbdc/go.mod h1:pZqkzPNNTqnwKZvCT8kCaTHxrG7HJbxZV83S0p7mmzM= github.com/TicketsBot/common v0.0.0-20240613013221-1e27eb8bfe37 h1:NC5fn+uAup0JxLiX85+bjVdyfUUXW3pKZYmp72/29hw= From 2158427b4acea7410b214cc05e0c3a6a05cad84b Mon Sep 17 00:00:00 2001 From: biast12 <53872542+biast12@users.noreply.github.com> Date: Sun, 4 Oct 2026 20:05:13 +0200 Subject: [PATCH 17/19] Format missing permissions as code Wrap missing permission names in backticks in server-wide and channel permission diagnostics for clearer Markdown rendering. --- bot/button/handlers/admindebug/server/modals/permissions.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/button/handlers/admindebug/server/modals/permissions.go b/bot/button/handlers/admindebug/server/modals/permissions.go index 7e985cd6..1d9da414 100644 --- a/bot/button/handlers/admindebug/server/modals/permissions.go +++ b/bot/button/handlers/admindebug/server/modals/permissions.go @@ -256,7 +256,7 @@ func checkServerWidePermissions(worker *w.Context, guildId uint64, botMember mem if len(missing) > 0 { result.WriteString("**Missing Permissions:**\n") for _, p := range missing { - result.WriteString(fmt.Sprintf("- %s\n", p)) + result.WriteString(fmt.Sprintf("- `%s`\n", p)) } } else { result.WriteString("All required permissions are present\n") @@ -363,7 +363,7 @@ func checkChannelPermissions(worker *w.Context, channelId uint64, botMember memb if len(missing) > 0 { result.WriteString("**Missing Permissions:**\n") for _, p := range missing { - result.WriteString(fmt.Sprintf("- %s\n", p)) + result.WriteString(fmt.Sprintf("- `%s`\n", p)) } } else { result.WriteString("All required permissions are present\n") From 18086b6cd31856074fa78006e05dee5f436c86b2 Mon Sep 17 00:00:00 2001 From: biast12 <53872542+biast12@users.noreply.github.com> Date: Thu, 15 Oct 2026 00:05:16 +0200 Subject: [PATCH 18/19] Use channel ID to detect permission target Update missing-permission location detection to rely on the returned channel ID instead of the success flag, while ignoring the unused boolean result. --- bot/command/context/replyable.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/command/context/replyable.go b/bot/command/context/replyable.go index 3696c049..544bdaf7 100644 --- a/bot/command/context/replyable.go +++ b/bot/command/context/replyable.go @@ -396,9 +396,9 @@ func findMissingPermissions(ctx registry.InteractionContext) ([]missingPermLocat } checkChannel := func(channelId uint64, label string, required []permission.Permission) missingPermLocation { - missing, ch, ok := permissionwrapper.GetMissingPermissionsChannel(ctx.Worker(), ctx.GuildId(), ctx.Worker().BotId, channelId, required...) + missing, ch, _ := permissionwrapper.GetMissingPermissionsChannel(ctx.Worker(), ctx.GuildId(), ctx.Worker().BotId, channelId, required...) loc := missingPermLocation{label: label, missing: missing} - if ok { + if ch.Id != 0 { loc.channelType = &ch.Type } return loc From 0c330ed147988f25f123be4511dd0127f4dae6e1 Mon Sep 17 00:00:00 2001 From: biast12 <53872542+biast12@users.noreply.github.com> Date: Thu, 15 Oct 2026 00:39:54 +0200 Subject: [PATCH 19/19] Show channel-specific view permissions Use the channel type when displaying missing View Channel permissions in admin diagnostics and ticket opening errors. Also normalize botpermissions import ordering in related handlers. --- bot/button/handlers/addadmin.go | 2 +- bot/button/handlers/addsupport.go | 2 +- bot/button/handlers/admindebug/server/modals/permissions.go | 2 +- bot/command/impl/tickets/remove.go | 2 +- bot/logic/open.go | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/bot/button/handlers/addadmin.go b/bot/button/handlers/addadmin.go index f0f7176c..138430f5 100644 --- a/bot/button/handlers/addadmin.go +++ b/bot/button/handlers/addadmin.go @@ -8,6 +8,7 @@ import ( "strings" "time" + "github.com/TicketsBot-cloud/common/botpermissions" permcache "github.com/TicketsBot-cloud/common/permission" "github.com/TicketsBot-cloud/gdl/objects/channel" "github.com/TicketsBot-cloud/gdl/permission" @@ -19,7 +20,6 @@ import ( "github.com/TicketsBot-cloud/worker/bot/command/context" "github.com/TicketsBot-cloud/worker/bot/customisation" "github.com/TicketsBot-cloud/worker/bot/dbclient" - "github.com/TicketsBot-cloud/common/botpermissions" "github.com/TicketsBot-cloud/worker/bot/utils" "github.com/TicketsBot-cloud/worker/i18n" ) diff --git a/bot/button/handlers/addsupport.go b/bot/button/handlers/addsupport.go index c6d74160..c7162815 100644 --- a/bot/button/handlers/addsupport.go +++ b/bot/button/handlers/addsupport.go @@ -8,6 +8,7 @@ import ( "strings" "time" + "github.com/TicketsBot-cloud/common/botpermissions" permcache "github.com/TicketsBot-cloud/common/permission" "github.com/TicketsBot-cloud/database" "github.com/TicketsBot-cloud/gdl/objects/channel" @@ -21,7 +22,6 @@ import ( cmdregistry "github.com/TicketsBot-cloud/worker/bot/command/registry" "github.com/TicketsBot-cloud/worker/bot/customisation" "github.com/TicketsBot-cloud/worker/bot/dbclient" - "github.com/TicketsBot-cloud/common/botpermissions" "github.com/TicketsBot-cloud/worker/bot/utils" "github.com/TicketsBot-cloud/worker/i18n" ) diff --git a/bot/button/handlers/admindebug/server/modals/permissions.go b/bot/button/handlers/admindebug/server/modals/permissions.go index 3df5635e..1c1ea967 100644 --- a/bot/button/handlers/admindebug/server/modals/permissions.go +++ b/bot/button/handlers/admindebug/server/modals/permissions.go @@ -341,7 +341,7 @@ func checkPanelPermissions(worker *w.Context, guildId uint64, botMember member.M func checkChannelPermissions(worker *w.Context, channelId uint64, botMember member.Member, guildId uint64, requiredPermissions []permission.Permission, label string) (string, bool) { missingPerms, channel, err := permissionwrapper.GetMissingPermissionsChannel(worker, guildId, botMember.User.Id, channelId, requiredPermissions...) if errors.Is(err, permissionwrapper.ErrChannelObfuscated) { - return fmt.Sprintf("**%s** (`%d`)\n**Missing Permissions:**\n- View Channel\n", label, channelId), true + return fmt.Sprintf("**%s** (`%d`)\n**Missing Permissions:**\n- `%s`\n", label, channelId, permission.ViewChannel.ChannelName(channel.Type)), true } if err != nil { diff --git a/bot/command/impl/tickets/remove.go b/bot/command/impl/tickets/remove.go index 40d74be9..42a42f48 100644 --- a/bot/command/impl/tickets/remove.go +++ b/bot/command/impl/tickets/remove.go @@ -4,6 +4,7 @@ import ( "fmt" "time" + "github.com/TicketsBot-cloud/common/botpermissions" permcache "github.com/TicketsBot-cloud/common/permission" "github.com/TicketsBot-cloud/gdl/objects/channel" "github.com/TicketsBot-cloud/gdl/objects/interaction" @@ -14,7 +15,6 @@ import ( "github.com/TicketsBot-cloud/worker/bot/command/registry" "github.com/TicketsBot-cloud/worker/bot/customisation" "github.com/TicketsBot-cloud/worker/bot/dbclient" - "github.com/TicketsBot-cloud/common/botpermissions" "github.com/TicketsBot-cloud/worker/bot/utils" "github.com/TicketsBot-cloud/worker/i18n" ) diff --git a/bot/logic/open.go b/bot/logic/open.go index e329e4e2..22d492ed 100644 --- a/bot/logic/open.go +++ b/bot/logic/open.go @@ -171,7 +171,7 @@ func OpenTicket(ctx context.Context, cmd registry.InteractionContext, panel *dat if appPermissions != 0 && !permission.HasPermissionRaw(appPermissions, permission.Administrator) && !permission.HasPermissionRaw(appPermissions, permission.ViewChannel) { docsUrl := fmt.Sprintf("%s/miscellaneous/permissions-explained", config.Conf.Bot.DocsUrl) message := cmd.GetMessage(i18n.MessageErrorMissingPermissionsTitle) + ":\n" - message += "* `View Channel`\n" + message += fmt.Sprintf("* `%s`\n", permission.ViewChannel.ChannelName(panelChannel.Type)) message += "\n" + cmd.GetMessage(i18n.MessageErrorMissingPermissionsBody, docsUrl) cmd.ReplyRaw(customisation.Red, cmd.GetMessage(i18n.Error), message)