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
4 changes: 4 additions & 0 deletions backend/app/http/endpoints/api/channels.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ func ChannelsHandler(ctx *gin.Context) {
continue
}

if ch.IsObfuscated() {
continue
}

filtered = append(filtered, ch)
}

Expand Down
12 changes: 9 additions & 3 deletions backend/app/http/endpoints/api/panel/multipanelcreate.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,10 +246,16 @@ func (d *multiPanelCreateData) validateChannel(guildId uint64) func() error {

var valid bool
for _, ch := range channels {
if ch.Id == d.ChannelId && (ch.Type == channel.ChannelTypeGuildText || ch.Type == channel.ChannelTypeGuildNews) {
valid = true
break
if ch.Id != d.ChannelId {
continue
}

if ch.IsObfuscated() {
return errChannelNoAccess("The selected channel")
}

valid = ch.Type == channel.ChannelTypeGuildText || ch.Type == channel.ChannelTypeGuildNews
break
}

if !valid {
Expand Down
8 changes: 8 additions & 0 deletions backend/app/http/endpoints/api/panel/permcheck.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type channelCheckResult struct {
Required []string `json:"required"`
Missing []string `json:"missing"`
Deleted bool `json:"deleted"`
NoAccess bool `json:"no_access"`
Ok bool `json:"ok"`
}

Expand Down Expand Up @@ -204,6 +205,13 @@ func checkChannel(
return result
}

if ch.IsObfuscated() {
result.NoAccess = true
result.Missing = []string{permission.ViewChannel.String()}
result.Ok = false
return result
}

result.ChannelName = ch.Name

missing := botpermissions.MissingPermissions(guildId, botId, botRoles, ch.PermissionOverwrites, roleMap, required)
Expand Down
61 changes: 56 additions & 5 deletions backend/app/http/endpoints/api/panel/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,12 +114,26 @@ func validateContent(ctx PanelValidationContext) validation.ValidationFunc {
}
}

func errChannelNoAccess(what string) error {
return validation.NewInvalidInputError(fmt.Sprintf("%s is not visible to the bot - grant it the View Channel permission", what))
}

func validateChannelId(ctx PanelValidationContext) validation.ValidationFunc {
return func() error {
for _, ch := range ctx.Channels {
if ch.Id == ctx.Data.ChannelId && (ch.Type == channel.ChannelTypeGuildText || ch.Type == channel.ChannelTypeGuildNews) {
if ch.Id != ctx.Data.ChannelId {
continue
}

if ch.IsObfuscated() {
return errChannelNoAccess("Panel channel")
}

if ch.Type == channel.ChannelTypeGuildText || ch.Type == channel.ChannelTypeGuildNews {
return nil
}

break
}

return validation.NewInvalidInputError("Panel channel not found")
Expand All @@ -128,10 +142,24 @@ func validateChannelId(ctx PanelValidationContext) validation.ValidationFunc {

func validateCategory(ctx PanelValidationContext) validation.ValidationFunc {
return func() error {
if ctx.Data.UseThreads {
return nil
}

for _, ch := range ctx.Channels {
if ch.Id == ctx.Data.CategoryId && ch.Type == channel.ChannelTypeGuildCategory {
if ch.Id != ctx.Data.CategoryId {
continue
}

if ch.IsObfuscated() {
return errChannelNoAccess("Ticket category")
}

if ch.Type == channel.ChannelTypeGuildCategory {
return nil
}

break
}

return validation.NewInvalidInputError("Invalid ticket category")
Expand Down Expand Up @@ -279,9 +307,19 @@ func validatePendingCategory(ctx PanelValidationContext) validation.ValidationFu
}

for _, ch := range ctx.Channels {
if ch.Id == *ctx.Data.PendingCategory && ch.Type == channel.ChannelTypeGuildCategory {
if ch.Id != *ctx.Data.PendingCategory {
continue
}

if ch.IsObfuscated() && !ctx.Data.UseThreads {
return errChannelNoAccess("Awaiting response category")
}

if ch.Type == channel.ChannelTypeGuildCategory {
return nil
}

break
}

return validation.NewInvalidInputError("Invalid awaiting response category")
Expand Down Expand Up @@ -486,6 +524,9 @@ func validateTranscriptChannelId(ctx PanelValidationContext) validation.Validati

for _, ch := range ctx.Channels {
if ch.Id == *ctx.Data.TranscriptChannelId {
if ch.IsObfuscated() {
return errChannelNoAccess("Transcript channel")
}
if ch.Type != channel.ChannelTypeGuildText && ch.Type != channel.ChannelTypeGuildNews {
return validation.NewInvalidInputError("Transcript channel must be a text channel")
}
Expand All @@ -499,12 +540,18 @@ func validateTranscriptChannelId(ctx PanelValidationContext) validation.Validati

func validateTicketNotificationChannel(ctx PanelValidationContext) validation.ValidationFunc {
return func() error {
// Always validate the channel if provided
if !ctx.Data.UseThreads {
return nil
}

if ctx.Data.TicketNotificationChannel != nil {
channelFound := false
for _, ch := range ctx.Channels {
if ch.Id == *ctx.Data.TicketNotificationChannel {
channelFound = true
if ch.IsObfuscated() {
return errChannelNoAccess("Ticket notification channel")
}
if ch.Type != channel.ChannelTypeGuildText {
return validation.NewInvalidInputError("Ticket notification channel must be a text channel")
}
Expand All @@ -518,7 +565,7 @@ func validateTicketNotificationChannel(ctx PanelValidationContext) validation.Va
}

// Thread mode requires a per-panel notification channel
if ctx.Data.UseThreads && ctx.Data.TicketNotificationChannel == nil {
if ctx.Data.TicketNotificationChannel == nil {
return validation.NewInvalidInputError("You must select a ticket notification channel for this panel when thread mode is enabled")
}

Expand Down Expand Up @@ -548,6 +595,10 @@ func validateOverflowCategoryId(ctx PanelValidationContext) validation.Validatio

for _, ch := range ctx.Channels {
if ch.Id == *ctx.Data.OverflowCategoryId {
if ch.IsObfuscated() && !ctx.Data.UseThreads {
return errChannelNoAccess("Overflow category")
}

if ch.GuildId != ctx.GuildId {
return validation.NewInvalidInputError("Overflow category guild ID does not match")
}
Expand Down
6 changes: 4 additions & 2 deletions backend/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,16 @@ go 1.26.0

// replace github.com/TicketsBot-cloud/worker => ../../worker

// replace github.com/TicketsBot-cloud/gdl => ../../gdl

replace github.com/go-playground/validator/v10 => github.com/go-playground/validator/v10 v10.14.0

require (
github.com/BurntSushi/toml v1.2.1
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-20261002063446-01dca52a987e
github.com/TicketsBot-cloud/gdl v0.0.0-20260612070331-a3947b410d3e
github.com/TicketsBot-cloud/database v0.0.0-20260927071905-7a22c7be0e4a
github.com/TicketsBot-cloud/gdl v0.0.0-20260927072153-8dbf0b284f57
github.com/TicketsBot-cloud/logarchiver v0.0.0-20251018211319-7a7df5cacbdc
github.com/TicketsBot-cloud/worker v0.0.0-20260827073646-455b39e53841
github.com/apex/log v1.1.2
Expand Down
8 changes: 4 additions & 4 deletions backend/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ github.com/TicketsBot-cloud/archiverclient v0.0.0-20251015181023-f0b66a074704 h1
github.com/TicketsBot-cloud/archiverclient v0.0.0-20251015181023-f0b66a074704/go.mod h1:Mux1bEPpOHwRw1wo6Fa6qJLJH9Erk9qv1yAIfLi1Wmw=
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-20261002063446-01dca52a987e h1:kZS42Mp+6wBhApK7zA6F1SX8ZYYG18cVRHB2ZDTvx94=
github.com/TicketsBot-cloud/database v0.0.0-20261002063446-01dca52a987e/go.mod h1:0fKn7nPX6KeljYNQtGQiuZ5aJ/TczgXvBhnbz+4GEc8=
github.com/TicketsBot-cloud/gdl v0.0.0-20260612070331-a3947b410d3e h1:qCibZQmO2rrBFrvn6+oAchPEQ7k/0Izr3ruL5P9aVZU=
github.com/TicketsBot-cloud/gdl v0.0.0-20260612070331-a3947b410d3e/go.mod h1:CdwBR2egPtxUXjD2CgC9ZwfuB8dz9HPePM8nuG6dt7Y=
github.com/TicketsBot-cloud/database v0.0.0-20260927071905-7a22c7be0e4a h1:Y1MPuFHwMmkoGeqnUV8CuXx4yqChdvPpqy3vGWBYnZo=
github.com/TicketsBot-cloud/database v0.0.0-20260927071905-7a22c7be0e4a/go.mod h1:0fKn7nPX6KeljYNQtGQiuZ5aJ/TczgXvBhnbz+4GEc8=
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-cloud/worker v0.0.0-20260827073646-455b39e53841 h1:TBabCBrj8XiiDey2iqicOA6HfDIyqZ63akGhImheoxs=
Expand Down
10 changes: 9 additions & 1 deletion frontend/src/components/PermissionWarningBanner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ const ROLE_LABELS: Record<string, string> = {
transcript_channel: "Transcript channel",
};

// Maps a role to the appropriate noun for "no longer exists" messages
const ROLE_ENTITY: Record<string, string> = {
ticket_category: "category",
};
Expand Down Expand Up @@ -63,6 +62,15 @@ const PanelDetail: FC<{ panel: PanelPermResult; isError: boolean; guildId: strin
{" - "}
{ROLE_ENTITY[ch.role] ?? "channel"} no longer exists
</span>
) : ch.no_access ? (
<>
{" "}
<span className={nameColor}>({ch.channel_id})</span>
{" - "}
<span>
bot cannot see this {ROLE_ENTITY[ch.role] ?? "channel"}, grant it View Channel
</span>
</>
) : (
<>
{" "}
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/pages/manage/multipanels/create.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,8 @@ const MultiPanelsPage: FC = () => {
// Returns the message so the Save button can reuse the rules.
const saveBlocker = () => {
if (!multiPanel.channel_id) return "Select a panel channel before creating the multi-panel.";
if (staleChannel) return "The selected panel channel no longer exists.";
if (staleChannel)
return "The selected panel channel no longer exists or the bot can't view it.";
if (multiPanel.panels.length < 2)
return "Select at least two panels before creating the multi-panel.";
if (multiPanel.panels.length > 15) return "Multi-panels cannot contain more than 15 panels.";
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/pages/manage/multipanels/edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,8 @@ const MultiPanelsPage: FC = () => {
// Returns the message so the Save button can reuse the rules.
const saveBlocker = () => {
if (!multiPanel?.channel_id) return "Select a panel channel before saving the multi-panel.";
if (staleChannel) return "The selected panel channel no longer exists.";
if (staleChannel)
return "The selected panel channel no longer exists or the bot can't view it.";
if (tooFewPanels) return "Select at least two panels before saving the multi-panel.";
if (multiPanel.panels.length > 15) return "Multi-panels cannot contain more than 15 panels.";
if (labellessPanelCount > 0) return "Every dropdown panel needs a label.";
Expand Down
8 changes: 4 additions & 4 deletions frontend/src/pages/manage/panels/create.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -281,9 +281,9 @@ const PanelsPage: FC = () => {
missingThreadChannel ||
buttonIdentityMissing ||
stale(panel.channel_id) ||
stale(panel.category_id) ||
(!panel.use_threads && stale(panel.category_id)) ||
stale(panel.transcript_channel_id) ||
stale(panel.ticket_notification_channel) ||
(panel.use_threads && stale(panel.ticket_notification_channel)) ||
welcomeMessageEmpty ||
invalidWelcomeMessageUrls.length > 0;

Expand Down Expand Up @@ -495,7 +495,7 @@ const PanelsPage: FC = () => {
<Select
label="Ticket Category"
required
error={stale(panel.category_id)}
error={!panel.use_threads && stale(panel.category_id)}
options={
selectedGuild?.channels
?.filter((c) => c.type == 4)
Expand Down Expand Up @@ -890,7 +890,7 @@ const PanelsPage: FC = () => {
label="Thread Notification Channel"
info={THREAD_NOTIFICATION_CHANNEL_INFO}
required={panel.use_threads}
error={stale(panel.ticket_notification_channel)}
error={panel.use_threads && stale(panel.ticket_notification_channel)}
disabled={!panel.use_threads}
options={
selectedGuild?.channels
Expand Down
8 changes: 4 additions & 4 deletions frontend/src/pages/manage/panels/edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -205,9 +205,9 @@ const EditPanelsPage: FC = () => {
missingThreadChannel ||
buttonIdentityMissing ||
stale(panel.channel_id) ||
stale(panel.category_id) ||
(!panel.use_threads && stale(panel.category_id)) ||
stale(panel.transcript_channel_id) ||
stale(panel.ticket_notification_channel) ||
(panel.use_threads && stale(panel.ticket_notification_channel)) ||
welcomeMessageEmpty ||
invalidWelcomeMessageUrls.length > 0;

Expand Down Expand Up @@ -409,7 +409,7 @@ const EditPanelsPage: FC = () => {
<Select
label="Ticket Category"
required
error={stale(panel.category_id)}
error={!panel.use_threads && stale(panel.category_id)}
options={
selectedGuild?.channels
?.filter((c) => c.type == 4)
Expand Down Expand Up @@ -804,7 +804,7 @@ const EditPanelsPage: FC = () => {
label="Thread Notification Channel"
info={THREAD_NOTIFICATION_CHANNEL_INFO}
required={panel.use_threads}
error={stale(panel.ticket_notification_channel)}
error={panel.use_threads && stale(panel.ticket_notification_channel)}
disabled={!panel.use_threads}
options={
selectedGuild?.channels
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ export interface GuildChannel {
join_timestamp: string; // ISO 8601 format
flags: number; // Flags for the member
};
flags?: number;
// Additional fields can be added as needed
}

Expand Down Expand Up @@ -994,6 +995,7 @@ export interface ChannelPermCheckResult {
required: string[];
missing: string[];
deleted: boolean;
no_access: boolean;
ok: boolean;
}

Expand Down
Loading