Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
68bde66
kb
BenHall-1 Apr 7, 2026
1f68271
Merge branch 'master' into admin-stuff
biast12 Apr 9, 2026
a9dbf37
Merge branch 'master' into admin-stuff
biast12 Apr 16, 2026
b2e3815
Remove global panel settings
biast12 Apr 23, 2026
8d50aa7
Remove ticket limit setup and references
biast12 Apr 23, 2026
ef687b9
Refactor setup command into settings
biast12 Apr 23, 2026
9515910
Merge branch 'master' into admin-stuff
biast12 Apr 23, 2026
4e98c65
analytics & central bot staff
BenHall-1 Apr 26, 2026
3f90071
Add .env.example with configuration template
biast12 May 4, 2026
593889b
Use per-panel ticket settings
biast12 May 10, 2026
3d69648
Use fixed thread archive duration
biast12 May 14, 2026
3d7b959
Use MentionBehaviour instead of DeleteMentions
biast12 May 15, 2026
2967036
Remove stray TODO comment in OpenTicket
biast12 May 15, 2026
792ccde
stats & form api
BenHall-1 May 19, 2026
6400e2e
Merge branch 'master' into admin-stuff
biast12 May 25, 2026
8f38aee
cleaned up stats cmds
BenHall-1 May 26, 2026
0b0b6cb
Merge branch 'admin-stuff' of github.com:TicketsBot-cloud/worker into…
BenHall-1 May 26, 2026
a9c22ad
Cache form API options; publish category updates
biast12 May 28, 2026
fac2674
Use permissionwrapper
biast12 Jun 9, 2026
d180e3f
feat: Kafka to Redis (#137)
BenHall-1 Jun 12, 2026
f7fb695
Merge remote-tracking branch 'origin/master' into admin-stuff
BenHall-1 Jun 20, 2026
841df79
Cache public integration owners
biast12 Jul 1, 2026
16cc13c
Refactor close request message composition
biast12 Jul 2, 2026
1d78895
Handle modal source message & response types
biast12 Jul 5, 2026
0db8f4e
Add whitelabel admin action buttons
biast12 Jul 12, 2026
a1a5ed6
Remove extra header in whitelabel data view
biast12 Jul 19, 2026
1e12bf4
Merge branch 'master' into admin-stuff
biast12 Jul 26, 2026
a8f2658
Extract panel availability check into reusable function
biast12 Jul 26, 2026
f0e8e11
Respect mention behavior in ticket content
biast12 Jul 26, 2026
c595092
Synchronize mention and welcome message order
biast12 Jul 26, 2026
b8ca07e
kb rendering & components v2 support
BenHall-1 Jul 26, 2026
bce82a3
Merge branch 'admin-stuff' of github.com:TicketsBot-cloud/worker into…
BenHall-1 Jul 26, 2026
55ef1b4
Add async claim/unclaim support via message queue
biast12 Jul 27, 2026
5143b6b
Fix ticket transfer claim reassignment
biast12 Jul 27, 2026
b38e14d
Handle intents rejection in WL resync
biast12 Aug 12, 2026
fc7e6bb
growthbook & rm kb
BenHall-1 Aug 15, 2026
ccce634
Enhance form API placeholders and request caching
biast12 Aug 15, 2026
1391f3d
feat: kill switch feature flags
BenHall-1 Aug 20, 2026
2e54aa1
Handle nil ticket channel in placeholder
biast12 Aug 21, 2026
b937120
Handle plain-text placeholders in embeds
biast12 Aug 22, 2026
271e4e6
Require guild_id for admin recache command
biast12 Aug 22, 2026
52b8b8c
go mod tidy
BenHall-1 Aug 27, 2026
298aba1
go mod tidy
BenHall-1 Aug 27, 2026
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
132 changes: 132 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
# =============================================================================
# Worker
# =============================================================================
WORKER_MODE= # required (GATEWAY or INTERACTIONS)
HTTP_ADDR= # required
WORKER_DEBUG=
WORKER_JSON_LOGS= # default: false
WORKER_LOG_LEVEL= # default: info
WORKER_PREMIUM_ONLY= # default: false

# =============================================================================
# Discord
# =============================================================================
WORKER_PUBLIC_TOKEN= # required
WORKER_PUBLIC_ID= # required
DISCORD_PROXY_URL=
DISCORD_REQUEST_TIMEOUT= # default: 15s
DISCORD_CALLBACK_TIMEOUT= # default: 2000ms
DISCORD_DEFER_HARD_TIMEOUT= # default: 2500ms
SHARDER_TOTAL= # default: 1

# =============================================================================
# Bot
# =============================================================================
WORKER_BOT_OWNER=
MONITORED_BOTS=
DASHBOARD_URL= # default: https://dashboard.tickets.bot
FRONTPAGE_URL= # default: https://tickets.bot
DOCS_URL= # default: https://docs.tickets.bot
VOTE_URL= # default: https://vote.tickets.bot
VOTE_SKU_ID=
POWEREDBY= # default: tickets.bot
ICON_URL= # default: https://tickets.bot/assets/img/logo.png
SUPPORT_SERVER_INVITE= # default: https://discord.gg/ticketsbot
INVITE_URL= # default: https://invite.tickets.bot

# =============================================================================
# Database
# =============================================================================
DATABASE_HOST= # required
DATABASE_NAME= # required
DATABASE_USER= # required
DATABASE_PASSWORD= # required
DATABASE_THREADS= # required

# =============================================================================
# Cache
# =============================================================================
CACHE_HOST= # required
CACHE_NAME= # required
CACHE_USER= # required
CACHE_PASSWORD= # required
CACHE_THREADS= # required

# =============================================================================
# Redis
# =============================================================================
WORKER_REDIS_ADDR= # required
WORKER_REDIS_PASSWD=
WORKER_REDIS_THREADS= # required

# =============================================================================
# Kafka
# =============================================================================
KAFKA_BROKERS= # required
KAFKA_EVENTS_TOPIC= # required
KAFKA_GOROUTINE_LIMIT= # default: 1000

# =============================================================================
# Premium proxy
# =============================================================================
WORKER_PROXY_URL=
WORKER_PROXY_KEY=

# =============================================================================
# Archiver
# =============================================================================
WORKER_ARCHIVER_URL=
WORKER_ARCHIVER_AES_KEY=

# =============================================================================
# Web proxy
# =============================================================================
WEB_PROXY_URL=
WEB_PROXY_AUTH_HEADER_NAME=
WEB_PROXY_AUTH_HEADER_VALUE=

# =============================================================================
# Integrations
# =============================================================================
BLOXLINK_API_KEY=
SECURE_PROXY_URL=

# =============================================================================
# Experiments
# =============================================================================
ENABLE_ALL_EXPERIMENTS=
EXPERIMENT_SERVERS=

# =============================================================================
# Observability
# =============================================================================
PROMETHEUS_SERVER_ADDR=
WORKER_STATSD_ADDR=
WORKER_STATSD_PREFIX=
WORKER_SENTRY_DSN=
WORKER_SENTRY_SAMPLE_RATE= # default: 1.0
WORKER_SENTRY_TRACING_ENABLED=
WORKER_SENTRY_TRACING_SAMPLE_RATE=
WORKER_CLOUD_PROFILER_ENABLED= # default: false
WORKER_CLOUD_PROFILER_PROJECT_ID=

# =============================================================================
# Emojis
# =============================================================================
EMOJI_ID=
EMOJI_OPEN=
EMOJI_OPENTIME=
EMOJI_CLOSE=
EMOJI_CLOSETIME=
EMOJI_REASON=
EMOJI_SUBJECT=
EMOJI_TRANSCRIPT=
EMOJI_CLAIM=
EMOJI_PANEL=
EMOJI_RATING=
EMOJI_STAFF=
EMOJI_THREAD=
EMOJI_BULLETLINE=
EMOJI_PATREON=
EMOJI_DISCORD=
EMOJI_LOGO=
36 changes: 21 additions & 15 deletions bot/button/handlers/addadmin.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,12 +129,6 @@ func (h *AddAdminHandler) Execute(ctx *context.ButtonContext) {
e := utils.BuildEmbed(ctx, customisation.Green, i18n.TitleAddAdmin, i18n.MessageAddAdminSuccess, nil, mention)
ctx.Edit(command.NewEphemeralEmbedMessageResponse(e))

settings, err := ctx.Settings()
if err != nil {
ctx.HandleError(err)
return
}

// Get member and target name once for reuse in audit reasons
member, err := ctx.Member()
hasMember := err == nil
Expand All @@ -156,22 +150,34 @@ func (h *AddAdminHandler) Execute(ctx *context.ButtonContext) {
}
}

// Add user / role to thread notification channel
if settings.TicketNotificationChannel != nil {
// Add user / role to each panel's thread notification channel
{
auditReason := "Added admin member/role"
if hasMember && targetName != "" {
auditReason = fmt.Sprintf("Added admin %s (%s) by %s", mentionableType, targetName, member.User.Username)
} else if hasMember {
auditReason = fmt.Sprintf("Added admin member/role by %s", member.User.Username)
}

reasonCtx := request.WithAuditReason(ctx, auditReason)
_ = ctx.Worker().EditChannelPermissions(reasonCtx, *settings.TicketNotificationChannel, channel.PermissionOverwrite{
Id: id,
Type: mentionableType.OverwriteType(),
Allow: permission.BuildPermissions(permission.ViewChannel, permission.UseApplicationCommands, permission.ReadMessageHistory),
Deny: 0,
})
panels, _ := dbclient.Client.Panel.GetByGuild(ctx, ctx.GuildId())
seen := make(map[uint64]struct{})
for _, p := range panels {
if p.TicketNotificationChannel == nil {
continue
}
chId := *p.TicketNotificationChannel
if _, already := seen[chId]; already {
continue
}
seen[chId] = struct{}{}
reasonCtx := request.WithAuditReason(ctx, auditReason)
_ = ctx.Worker().EditChannelPermissions(reasonCtx, chId, channel.PermissionOverwrite{
Id: id,
Type: mentionableType.OverwriteType(),
Allow: permission.BuildPermissions(permission.ViewChannel, permission.UseApplicationCommands, permission.ReadMessageHistory),
Deny: 0,
})
}
}

openTickets, err := dbclient.Client.Tickets.GetGuildOpenTicketsExcludeThreads(ctx, ctx.GuildId())
Expand Down
53 changes: 29 additions & 24 deletions bot/button/handlers/addsupport.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,12 +130,6 @@ func (h *AddSupportHandler) Execute(ctx *context.ButtonContext) {
}

func updateChannelPermissions(ctx cmdregistry.CommandContext, id uint64, mentionableType context.MentionableType) {
settings, err := ctx.Settings()
if err != nil {
ctx.HandleError(err)
return
}

// Get member and target name once for reuse in audit reasons
member, err := ctx.Member()
hasMember := err == nil
Expand All @@ -157,24 +151,6 @@ func updateChannelPermissions(ctx cmdregistry.CommandContext, id uint64, mention
}
}

// Add user / role to thread notification channel
if settings.TicketNotificationChannel != nil {
auditReason := "Added support member/role"
if hasMember && targetName != "" {
auditReason = fmt.Sprintf("Added support %s (%s) by %s", mentionableType, targetName, member.User.Username)
} else if hasMember {
auditReason = fmt.Sprintf("Added support member/role by %s", member.User.Username)
}

reasonCtx := request.WithAuditReason(ctx, auditReason)
_ = ctx.Worker().EditChannelPermissions(reasonCtx, *settings.TicketNotificationChannel, channel.PermissionOverwrite{
Id: id,
Type: mentionableType.OverwriteType(),
Allow: permission.BuildPermissions(permission.ViewChannel, permission.UseApplicationCommands, permission.ReadMessageHistory),
Deny: 0,
})
}

openTickets, err := dbclient.Client.Tickets.GetGuildOpenTicketsExcludeThreads(ctx, ctx.GuildId())
if err != nil {
ctx.HandleError(err)
Expand All @@ -187,6 +163,35 @@ func updateChannelPermissions(ctx cmdregistry.CommandContext, id uint64, mention
return
}

// Add user / role to each panel's thread notification channel
{
auditReason := "Added support member/role"
if hasMember && targetName != "" {
auditReason = fmt.Sprintf("Added support %s (%s) by %s", mentionableType, targetName, member.User.Username)
} else if hasMember {
auditReason = fmt.Sprintf("Added support member/role by %s", member.User.Username)
}

seen := make(map[uint64]struct{})
for _, p := range panels {
if p.TicketNotificationChannel == nil {
continue
}
chId := *p.TicketNotificationChannel
if _, already := seen[chId]; already {
continue
}
seen[chId] = struct{}{}
reasonCtx := request.WithAuditReason(ctx, auditReason)
_ = ctx.Worker().EditChannelPermissions(reasonCtx, chId, channel.PermissionOverwrite{
Id: id,
Type: mentionableType.OverwriteType(),
Allow: permission.BuildPermissions(permission.ViewChannel, permission.UseApplicationCommands, permission.ReadMessageHistory),
Deny: 0,
})
}
}

// Update permissions for existing tickets
for _, ticket := range openTickets {
if ticket.ChannelId == nil || ticket.IsThread {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,12 +216,7 @@ func buildPanelSettings(ctx *context.ModalContext, selectedPanel *database.Panel
}
settings = append(settings, fmt.Sprintf("**Status:** `%s`", status))

// Delete mentions
deleteMentions := "Disabled"
if selectedPanel.DeleteMentions {
deleteMentions = "Enabled"
}
settings = append(settings, fmt.Sprintf("**Delete Mentions:** `%s`", deleteMentions))
settings = append(settings, fmt.Sprintf("**Mention Behaviour:** `%s`", selectedPanel.MentionBehaviour))

return strings.Join(settings, "\n")
}
20 changes: 6 additions & 14 deletions bot/button/handlers/admindebug/server/modals/permissions.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,13 +88,6 @@ func (h *AdminDebugServerPermissionsModalSubmitHandler) Execute(ctx *context.Mod
return
}

// Get guild and settings
settings, err := dbclient.Client.Settings.Get(ctx, guildId)
if err != nil {
ctx.HandleError(err)
return
}

panels, err := dbclient.Client.Panel.GetByGuild(ctx, guildId)
if err != nil {
ctx.HandleError(err)
Expand All @@ -108,7 +101,7 @@ func (h *AdminDebugServerPermissionsModalSubmitHandler) Execute(ctx *context.Mod
}

// Process permission checks using shared logic
results, hasMissingPermissions := processPermissionChecks(selectedValues, worker, guildId, botMember, settings, panels)
results, hasMissingPermissions := processPermissionChecks(selectedValues, worker, guildId, botMember, panels)

// Choose color based on whether permissions are missing
colour := customisation.Green
Expand All @@ -126,7 +119,7 @@ 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) {
func processPermissionChecks(selectedValues []string, worker *w.Context, guildId uint64, botMember member.Member, panels []database.Panel) ([]string, bool) {
// Server-wide permissions
serverWidePermissions := append(
[]permission.Permission{
Expand Down Expand Up @@ -184,7 +177,7 @@ func processPermissionChecks(selectedValues []string, worker *w.Context, guildId
}

// Check permissions for this panel
panelResults, hasMissing := checkPanelPermissions(worker, guildId, botMember, *panel, settings)
panelResults, hasMissing := checkPanelPermissions(worker, guildId, botMember, *panel)
results = append(results, fmt.Sprintf("**Panel: %s**\n%s", panel.Title, panelResults))
if hasMissing {
hasMissingPermissions = true
Expand Down Expand Up @@ -226,7 +219,7 @@ func checkServerWidePermissions(worker *w.Context, guildId uint64, botMember mem
return result.String(), len(missing) > 0
}

func checkPanelPermissions(worker *w.Context, guildId uint64, botMember member.Member, panel database.Panel, settings database.Settings) (string, bool) {
func checkPanelPermissions(worker *w.Context, guildId uint64, botMember member.Member, panel database.Panel) (string, bool) {
var results []string
var hasMissingPermissions bool

Expand Down Expand Up @@ -289,16 +282,15 @@ func checkPanelPermissions(worker *w.Context, guildId uint64, botMember member.M
}

// Check notification channel if using thread mode
if usesThreads && settings.TicketNotificationChannel != nil {
// Notification channel needs standard permissions + embed links
if usesThreads && panel.TicketNotificationChannel != nil {
notificationPerms := append(
[]permission.Permission{
permission.EmbedLinks,
permission.AttachFiles,
},
logic.MinimalPermissions[:]...,
)
result, hasMissing := checkChannelPermissions(worker, *settings.TicketNotificationChannel, botMember, guildId, notificationPerms, "Notification Channel")
result, hasMissing := checkChannelPermissions(worker, *panel.TicketNotificationChannel, botMember, guildId, notificationPerms, "Notification Channel")
results = append(results, result)
if hasMissing {
hasMissingPermissions = true
Expand Down
2 changes: 1 addition & 1 deletion bot/button/handlers/admindebug/server/recache.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func (h *AdminDebugServerRecacheHandler) Properties() registry.Properties {
}

func (h *AdminDebugServerRecacheHandler) Execute(ctx *context.ButtonContext) {
if !utils.IsBotHelper(ctx.UserId()) {
if !utils.IsBotHelper(ctx, ctx.UserId()) {
ctx.ReplyRaw(customisation.Red, "Error", "You do not have permission to use this button.")
}

Expand Down
7 changes: 7 additions & 0 deletions bot/button/handlers/claim.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package handlers

import (
"errors"
"fmt"

"github.com/TicketsBot-cloud/common/permission"
Expand Down Expand Up @@ -56,6 +57,12 @@ func (h *ClaimHandler) Execute(ctx *context.ButtonContext) {
}

if err := logic.ClaimTicket(ctx.Context, ctx, ticket, ctx.UserId()); err != nil {
var alreadyClaimed logic.AlreadyClaimedError
if errors.As(err, &alreadyClaimed) {
ctx.Reply(customisation.Red, i18n.Error, i18n.MessageAlreadyClaimed, fmt.Sprintf("<@%d>", alreadyClaimed.ClaimerId))
return
}

ctx.HandleError(err)
return
}
Expand Down
14 changes: 10 additions & 4 deletions bot/button/handlers/close.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,16 @@ func (h *CloseHandler) Execute(ctx *cmdcontext.ButtonContext) {
return
}

closeConfirmation, err := dbclient.Client.CloseConfirmation.Get(ctx, ctx.GuildId())
if err != nil {
ctx.HandleError(err)
return
closeConfirmation := true // default: show close confirmation
if ticket.PanelId != nil {
p, err := dbclient.Client.Panel.GetById(ctx, *ticket.PanelId)
if err != nil {
ctx.HandleError(err)
return
}
if p.PanelId != 0 {
closeConfirmation = p.CloseConfirmation
}
}

if closeConfirmation {
Expand Down
Loading
Loading