diff --git a/bot/button/handlers/addadmin.go b/bot/button/handlers/addadmin.go index 13b31c2..d3bb911 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 39b3d84..c3949ef 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 1bd2609..5b524ef 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" ) @@ -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, botpermissions.StandardPermissions...) var results []string var hasMissingPermissions bool @@ -256,26 +258,21 @@ 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[:]..., + 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) @@ -293,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) @@ -305,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 { @@ -321,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 3c37347..407cbf3 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 0e14f07..cc473e0 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" @@ -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 = botpermissions.ThreadModeRequired } else { - // Channel mode - check permissions in the ticket category + primaryLabel = "Ticket category" + primaryRequired = botpermissions.ChannelModeRequired 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 := botpermissions.NotifChannelRequired + 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", botpermissions.TranscriptChannelRequired) + 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/command/impl/tickets/remove.go b/bot/command/impl/tickets/remove.go index c21fe39..40d74be 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 dd7906c..e5971e6 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 a580ad1..3d14698 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 619a434..2657a6d 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,31 +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, -} - func BuildUserOverwrite(userId uint64, additionalPermissions database.TicketPermissions) channel.PermissionOverwrite { - allow := MinimalPermissions[:] + allow := append([]permission.Permission{}, botpermissions.MinimalPermissions...) var deny []permission.Permission if additionalPermissions.AddReactions { @@ -92,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 ccdb3aa..fcf41cd 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/bot/permissionwrapper/permission.go b/bot/permissionwrapper/permission.go index cf66095..4c96f9f 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 } diff --git a/go.mod b/go.mod index f3bc8de..f7473bc 100644 --- a/go.mod +++ b/go.mod @@ -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 380325f..e2f102b 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=