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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions botpermissions/effective.go
Original file line number Diff line number Diff line change
@@ -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
}
61 changes: 61 additions & 0 deletions botpermissions/requirements.go
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down