From 15154d9f05973bb19f85d7525765c5cce877f581 Mon Sep 17 00:00:00 2001 From: biast12 Date: Sun, 12 Apr 2026 12:47:37 +0200 Subject: [PATCH] Add botpermissions package and update gdl Introduce a new botpermissions package providing permission utilities: EffectivePermissions implements Discord's permission-overwrite resolution to compute a member's channel permissions (requires caller to prefetch roles/overwrites), and MissingPermissions returns which required permissions are lacking. Add predefined permission sets (StandardPermissions, MinimalPermissions, ThreadModeRequired, ChannelModeRequired, NotifChannelRequired, TranscriptChannelRequired) used by the bot for ticket/thread/channel operations. Also bump github.com/TicketsBot-cloud/gdl to a newer version in go.mod. --- botpermissions/effective.go | 94 ++++++++++++++++++++++++++++++++++ botpermissions/requirements.go | 61 ++++++++++++++++++++++ go.mod | 2 +- go.sum | 4 +- 4 files changed, 158 insertions(+), 3 deletions(-) create mode 100644 botpermissions/effective.go create mode 100644 botpermissions/requirements.go diff --git a/botpermissions/effective.go b/botpermissions/effective.go new file mode 100644 index 0000000..8f8bf3b --- /dev/null +++ b/botpermissions/effective.go @@ -0,0 +1,94 @@ +package botpermissions + +import ( + "github.com/TicketsBot-cloud/gdl/objects/channel" + "github.com/TicketsBot-cloud/gdl/objects/guild" + "github.com/TicketsBot-cloud/gdl/permission" +) + +// EffectivePermissions computes the effective permission bitfield for a member in a +// channel following the standard Discord permission resolution algorithm: +// https://docs.discord.com/developers/topics/permissions#permission-overwrites +// +// All required data (roles, overwrites) must be pre-fetched by the caller. +func EffectivePermissions( + guildId, userId uint64, + memberRoles []uint64, + overwrites []channel.PermissionOverwrite, + roleMap map[uint64]guild.Role, +) uint64 { + // Step 1: base = @everyone permissions OR'd with all member role permissions. + // (@everyone role has the same ID as the guild.) + var base uint64 + if everyoneRole, ok := roleMap[guildId]; ok { + base = everyoneRole.Permissions + } + for _, roleId := range memberRoles { + if r, ok := roleMap[roleId]; ok { + base |= r.Permissions + } + } + + // Step 2: Administrator at guild level grants everything. + if permission.HasPermissionRaw(base, permission.Administrator) { + return ^uint64(0) + } + + // Step 3: Build overwrite lookup. + owMap := make(map[uint64]channel.PermissionOverwrite, len(overwrites)) + for _, ow := range overwrites { + owMap[ow.Id] = ow + } + + channelPerms := base + + // Step 4: Apply @everyone channel overwrite. + if ow, ok := owMap[guildId]; ok { + channelPerms &^= ow.Deny + channelPerms |= ow.Allow + } + + // Step 5: Collect and apply all role overwrites for the member's roles. + var allowBits, denyBits uint64 + for _, roleId := range memberRoles { + if ow, ok := owMap[roleId]; ok { + denyBits |= ow.Deny + allowBits |= ow.Allow + } + } + channelPerms &^= denyBits + channelPerms |= allowBits + + // Step 6: Apply member overwrite. + if ow, ok := owMap[userId]; ok { + channelPerms &^= ow.Deny + channelPerms |= ow.Allow + } + + // Step 7: Administrator via overwrites also grants everything. + if permission.HasPermissionRaw(channelPerms, permission.Administrator) { + return ^uint64(0) + } + + return channelPerms +} + +// MissingPermissions returns the subset of required permissions that the member lacks +// in the given channel. +func MissingPermissions( + guildId, userId uint64, + memberRoles []uint64, + overwrites []channel.PermissionOverwrite, + roleMap map[uint64]guild.Role, + required []permission.Permission, +) []permission.Permission { + effective := EffectivePermissions(guildId, userId, memberRoles, overwrites, roleMap) + + var missing []permission.Permission + for _, p := range required { + if !permission.HasPermissionRaw(effective, p) { + missing = append(missing, p) + } + } + return missing +} diff --git a/botpermissions/requirements.go b/botpermissions/requirements.go new file mode 100644 index 0000000..f4c335f --- /dev/null +++ b/botpermissions/requirements.go @@ -0,0 +1,61 @@ +package botpermissions + +import "github.com/TicketsBot-cloud/gdl/permission" + +// StandardPermissions are the permissions granted to users inside a ticket channel. +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, +} + +// MinimalPermissions are the base permissions the bot needs for any channel interaction. +var MinimalPermissions = []permission.Permission{ + permission.ViewChannel, + permission.SendMessages, + permission.ReadMessageHistory, + permission.UseApplicationCommands, +} + +// ThreadModeRequired are the permissions the bot needs on the panel channel in thread mode. +var ThreadModeRequired = []permission.Permission{ + permission.ViewChannel, + permission.ReadMessageHistory, + permission.EmbedLinks, + permission.AttachFiles, + permission.UseExternalEmojis, + permission.CreatePrivateThreads, + permission.SendMessagesInThreads, + permission.ManageThreads, +} + +// ChannelModeRequired are the permissions the bot needs on the ticket category in channel mode. +// It is StandardPermissions with ManageChannels prepended. +var ChannelModeRequired = func() []permission.Permission { + perms := make([]permission.Permission, 0, 1+len(StandardPermissions)) + perms = append(perms, permission.ManageChannels) + perms = append(perms, StandardPermissions...) + return perms +}() + +// NotifChannelRequired are the permissions the bot needs on the notification channel (thread mode only). +// It is MinimalPermissions with EmbedLinks and AttachFiles appended. +var NotifChannelRequired = func() []permission.Permission { + perms := make([]permission.Permission, 0, len(MinimalPermissions)+2) + perms = append(perms, MinimalPermissions...) + perms = append(perms, permission.EmbedLinks, permission.AttachFiles) + return perms +}() + +// TranscriptChannelRequired are the permissions the bot needs on the transcript channel (any mode). +// Equivalent to MinimalPermissions. +var TranscriptChannelRequired = MinimalPermissions diff --git a/go.mod b/go.mod index aebbbb5..a0442be 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ toolchain go1.24.2 require ( github.com/TicketsBot-cloud/database v0.0.0-20251018211325-3d2b9b4ad642 - github.com/TicketsBot-cloud/gdl v0.0.0-20250509054940-2045fbe19c06 + github.com/TicketsBot-cloud/gdl v0.0.0-20260306134952-cccb0116fef6 github.com/getsentry/sentry-go v0.21.0 github.com/go-redis/redis/v8 v8.11.3 github.com/google/uuid v1.6.0 diff --git a/go.sum b/go.sum index 89a8d12..0fd308e 100644 --- a/go.sum +++ b/go.sum @@ -5,8 +5,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/database v0.0.0-20251018211325-3d2b9b4ad642 h1:dTF/3+ebJfLJrq/MM7mJoo7uHdo1iec+IHnFZhVEd90= github.com/TicketsBot-cloud/database v0.0.0-20251018211325-3d2b9b4ad642/go.mod h1:LPDEn9e5wccH7rq/pUlVcL3UhyLnnwdM2dhj0tp/ljo= -github.com/TicketsBot-cloud/gdl v0.0.0-20250509054940-2045fbe19c06 h1:PzziB2S58d9agJtpaPVrYMTuBiJICr2QIGQoqL6l3z0= -github.com/TicketsBot-cloud/gdl v0.0.0-20250509054940-2045fbe19c06/go.mod h1:CdwBR2egPtxUXjD2CgC9ZwfuB8dz9HPePM8nuG6dt7Y= +github.com/TicketsBot-cloud/gdl v0.0.0-20260306134952-cccb0116fef6 h1:ucG0xLPt7xixW7/LvL0hXDBDouDRS1Nf+77qP8iJ/X0= +github.com/TicketsBot-cloud/gdl v0.0.0-20260306134952-cccb0116fef6/go.mod h1:CdwBR2egPtxUXjD2CgC9ZwfuB8dz9HPePM8nuG6dt7Y= github.com/TicketsBot/ttlcache v1.6.1-0.20200405150101-acc18e37b261 h1:NHD5GB6cjlkpZFjC76Yli2S63/J2nhr8MuE6KlYJpQM= github.com/TicketsBot/ttlcache v1.6.1-0.20200405150101-acc18e37b261/go.mod h1:2zPxDAN2TAPpxUPjxszjs3QFKreKrQh5al/R3cMXmYk= github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4=