From 8ed98eba7b69be8fcf6de81219e10a218cfc4b07 Mon Sep 17 00:00:00 2001 From: biast12 Date: Fri, 18 Jul 2025 23:51:08 +0200 Subject: [PATCH 1/4] feat(RM-51): Show panel name in close container if available Adds the panel name to the close container details if the ticket has an associated panel with a title. Also refactors claimedBy and reason formatting logic for clarity and error handling. --- bot/logic/closeembed.go | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/bot/logic/closeembed.go b/bot/logic/closeembed.go index 1dd7e02..5c2bb42 100644 --- a/bot/logic/closeembed.go +++ b/bot/logic/closeembed.go @@ -209,10 +209,8 @@ func BuildCloseContainer( rating *uint8, components [][]CloseEmbedElement, ) *component.Component { - var formattedReason string - if reason == nil { - formattedReason = "No reason specified" - } else { + var formattedReason = "No reason specified" + if reason != nil { formattedReason = *reason if len(formattedReason) > 1024 { formattedReason = formattedReason[:1024] @@ -225,24 +223,33 @@ func BuildCloseContainer( } var claimedBy string - { - claimUserId, err := dbclient.Client.TicketClaims.Get(ctx, ticket.GuildId, ticket.Id) + claimUserId, err := dbclient.Client.TicketClaims.Get(ctx, ticket.GuildId, ticket.Id) + if err != nil { + sentry.Error(err) + } else if claimUserId > 0 { + claimedBy = fmt.Sprintf("<@%d>", claimUserId) + } + + var panelName string + if ticket.PanelId != nil { + p, err := dbclient.Client.Panel.GetById(ctx, *ticket.PanelId) if err != nil { sentry.Error(err) - } - - if claimUserId == 0 { - claimedBy = "" - } else { - claimedBy = fmt.Sprintf("<@%d>", claimUserId) + } else if p.Title != "" { + panelName = p.Title } } section1Text := []string{ formatRow("Ticket ID", strconv.Itoa(ticket.Id)), + } + if panelName != "" { + section1Text = append(section1Text, formatRow("Panel", panelName)) + } + section1Text = append(section1Text, formatRow("Opened By", fmt.Sprintf("<@%d>", ticket.UserId)), formatRow("Closed By", fmt.Sprintf("<@%d>", closedBy)), - } + ) section2Text := []string{ formatRow("Open Time", message.BuildTimestamp(ticket.OpenTime, message.TimestampStyleShortDateTime)), From 4e770badbb0599b3397398fed9627aa745520275 Mon Sep 17 00:00:00 2001 From: biast12 Date: Sat, 19 Jul 2025 03:52:42 +0200 Subject: [PATCH 2/4] Converted rating/feedback/DM transcript --- bot/button/handlers/exitsurveysubmit.go | 2 +- bot/button/handlers/rate.go | 2 +- bot/logic/close.go | 45 ++++------ bot/logic/closeembed.go | 112 ++++-------------------- 4 files changed, 39 insertions(+), 122 deletions(-) diff --git a/bot/button/handlers/exitsurveysubmit.go b/bot/button/handlers/exitsurveysubmit.go index dca0b33..5fc6b5a 100644 --- a/bot/button/handlers/exitsurveysubmit.go +++ b/bot/button/handlers/exitsurveysubmit.go @@ -182,5 +182,5 @@ func addViewFeedbackButton(ctx context.Context, cmd *cmdcontext.ModalContext, ti return fmt.Errorf("exit survey was completed, but no rating was found (%d:%d)", ticket.GuildId, ticket.Id) } - return logic.EditGuildArchiveMessageIfExists(ctx, cmd.Worker(), ticket, settings, true, closedBy, reason, &rating) + return logic.EditGuildArchiveMessageIfExists(ctx, cmd, cmd.Worker(), ticket, settings, true, closedBy, reason, &rating) } diff --git a/bot/button/handlers/rate.go b/bot/button/handlers/rate.go index 2703f4e..a05756c 100644 --- a/bot/button/handlers/rate.go +++ b/bot/button/handlers/rate.go @@ -152,7 +152,7 @@ func (h *RateHandler) Execute(ctx *cmdcontext.ButtonContext) { return } - if err := logic.EditGuildArchiveMessageIfExists(ctx, ctx.Worker(), ticket, settings, hasFeedback, closedBy, reason, &rating); err != nil { + if err := logic.EditGuildArchiveMessageIfExists(ctx, ctx, ctx.Worker(), ticket, settings, hasFeedback, closedBy, reason, &rating); err != nil { ctx.HandleError(err) } } diff --git a/bot/logic/close.go b/bot/logic/close.go index 8393258..f5792a9 100644 --- a/bot/logic/close.go +++ b/bot/logic/close.go @@ -12,6 +12,7 @@ import ( "github.com/TicketsBot-cloud/common/sentry" "github.com/TicketsBot-cloud/database" "github.com/TicketsBot-cloud/gdl/objects/channel/message" + "github.com/TicketsBot-cloud/gdl/objects/interaction/component" "github.com/TicketsBot-cloud/gdl/objects/member" "github.com/TicketsBot-cloud/gdl/rest" "github.com/TicketsBot-cloud/gdl/rest/request" @@ -231,10 +232,10 @@ func CloseTicket(ctx context.Context, cmd registry.CommandContext, reason *strin } } - sendCloseEmbed(ctx, cmd, errorContext, member, settings, ticket, reason) + sendCloseContainer(ctx, cmd, errorContext, member, settings, ticket, reason) } -func sendCloseEmbed(ctx context.Context, cmd registry.CommandContext, errorContext sentry.ErrorContext, member member.Member, settings database.Settings, ticket database.Ticket, reason *string) { +func sendCloseContainer(ctx context.Context, cmd registry.CommandContext, errorContext sentry.ErrorContext, member member.Member, settings database.Settings, ticket database.Ticket, reason *string) { // Send logs to archive channel archiveChannelId, err := dbclient.Client.ArchiveChannel.Get(ctx, ticket.GuildId) if err != nil { @@ -249,14 +250,7 @@ func sendCloseEmbed(ctx context.Context, cmd registry.CommandContext, errorConte } if archiveChannelExists && archiveChannelId != nil { - componentBuilders := [][]CloseEmbedElement{ - { - TranscriptLinkElement(settings.StoreTranscripts), - ThreadLinkElement(ticket.IsThread && ticket.ChannelId != nil), - }, - } - - closeContainer := BuildCloseContainer(ctx, cmd, cmd.Worker(), ticket, member.User.Id, reason, nil, componentBuilders) + closeContainer := BuildCloseContainer(ctx, cmd, cmd.Worker(), ticket, member.User.Id, reason, nil, false) data := rest.CreateMessageData{ Flags: uint(message.FlagComponentsV2), @@ -321,33 +315,32 @@ func sendCloseEmbed(ctx context.Context, cmd registry.CommandContext, errorConte statsd.Client.IncrementKey(statsd.KeyDirectMessage) - componentBuilders := [][]CloseEmbedElement{ - { - TranscriptLinkElement(settings.StoreTranscripts), - ThreadLinkElement(ticket.IsThread && ticket.ChannelId != nil), - }, - { - FeedbackRowElement(feedbackEnabled && hasSentMessage && permLevel == permission.Everyone), - }, - } - - closeEmbed, closeComponents := BuildCloseEmbed(ctx, cmd.Worker(), ticket, member.User.Id, reason, nil, componentBuilders) - closeEmbed.SetAuthor(guild.Name, "", fmt.Sprintf("https://cdn.discordapp.com/icons/%d/%s.png", guild.Id, guild.Icon)) - // Use message content to tell users why they can't rate a ticket var content string + var viewFeedbackButton = true if feedbackEnabled { if permLevel > permission.Everyone { content = "-# " + cmd.GetMessage(i18n.MessageCloseCantRateStaff, guild.Name) + viewFeedbackButton = false } else if !hasSentMessage { content = "-# " + cmd.GetMessage(i18n.MessageCloseCantRateEmpty) + viewFeedbackButton = false + } + } + + closeContainer := BuildCloseContainer(ctx, cmd, cmd.Worker(), ticket, member.User.Id, reason, nil, viewFeedbackButton) + + components := []component.Component{*closeContainer} + if content != "" { + components = []component.Component{ + component.BuildTextDisplay(component.TextDisplay{Content: content}), + *closeContainer, } } data := rest.CreateMessageData{ - Content: content, - Embeds: utils.Slice(closeEmbed), - Components: closeComponents, + Flags: uint(message.FlagComponentsV2), + Components: components, } if _, err := cmd.Worker().CreateMessageComplex(dmChannel, data); err != nil { diff --git a/bot/logic/closeembed.go b/bot/logic/closeembed.go index 5c2bb42..925739a 100644 --- a/bot/logic/closeembed.go +++ b/bot/logic/closeembed.go @@ -9,7 +9,6 @@ import ( "github.com/TicketsBot-cloud/common/premium" "github.com/TicketsBot-cloud/common/sentry" "github.com/TicketsBot-cloud/database" - "github.com/TicketsBot-cloud/gdl/objects/channel/embed" "github.com/TicketsBot-cloud/gdl/objects/channel/message" "github.com/TicketsBot-cloud/gdl/objects/guild/emoji" "github.com/TicketsBot-cloud/gdl/objects/interaction/component" @@ -22,15 +21,15 @@ import ( "github.com/TicketsBot-cloud/worker/config" ) -type CloseEmbedElement func(worker *worker.Context, ticket database.Ticket) []component.Component +type CloseContainerElement func(worker *worker.Context, ticket database.Ticket) []component.Component -func NoopElement() CloseEmbedElement { +func NoopElement() CloseContainerElement { return func(worker *worker.Context, ticket database.Ticket) []component.Component { return nil } } -func TranscriptLinkElement(condition bool) CloseEmbedElement { +func TranscriptLinkElement(condition bool) CloseContainerElement { if !condition { return NoopElement() } @@ -52,7 +51,7 @@ func TranscriptLinkElement(condition bool) CloseEmbedElement { } } -func ThreadLinkElement(condition bool) CloseEmbedElement { +func ThreadLinkElement(condition bool) CloseContainerElement { if !condition { return NoopElement() } @@ -74,7 +73,7 @@ func ThreadLinkElement(condition bool) CloseEmbedElement { } } -func ViewFeedbackElement(condition bool) CloseEmbedElement { +func ViewFeedbackElement(condition bool) CloseContainerElement { if !condition { return NoopElement() } @@ -91,7 +90,7 @@ func ViewFeedbackElement(condition bool) CloseEmbedElement { } } -func FeedbackRowElement(condition bool) CloseEmbedElement { +func FeedbackRowElement(condition bool) CloseContainerElement { if !condition { return NoopElement() } @@ -123,82 +122,6 @@ func FeedbackRowElement(condition bool) CloseEmbedElement { } } -func BuildCloseEmbed( - ctx context.Context, - worker *worker.Context, - ticket database.Ticket, - closedBy uint64, - reason *string, - rating *uint8, - components [][]CloseEmbedElement, -) (*embed.Embed, []component.Component) { - var formattedReason string - if reason == nil { - formattedReason = "No reason specified" - } else { - formattedReason = *reason - if len(formattedReason) > 1024 { - formattedReason = formattedReason[:1024] - } - } - - var claimedBy string - { - claimUserId, err := dbclient.Client.TicketClaims.Get(ctx, ticket.GuildId, ticket.Id) - if err != nil { - sentry.Error(err) - } - - if claimUserId == 0 { - claimedBy = "Not claimed" - } else { - claimedBy = fmt.Sprintf("<@%d>", claimUserId) - } - } - - colour, err := utils.GetColourForGuild(ctx, worker, customisation.Green, ticket.GuildId) - if err != nil { - sentry.Error(err) - colour = customisation.Green.Default() - } - - // TODO: Translate titles - closeEmbed := embed.NewEmbed(). - SetTitle("Ticket Closed"). - SetColor(colour). - AddField(formatTitle("Ticket ID", customisation.EmojiId, worker.IsWhitelabel), strconv.Itoa(ticket.Id), true). - AddField(formatTitle("Opened By", customisation.EmojiOpen, worker.IsWhitelabel), fmt.Sprintf("<@%d>", ticket.UserId), true). - AddField(formatTitle("Closed By", customisation.EmojiClose, worker.IsWhitelabel), fmt.Sprintf("<@%d>", closedBy), true). - AddField(formatTitle("Open Time", customisation.EmojiOpenTime, worker.IsWhitelabel), message.BuildTimestamp(ticket.OpenTime, message.TimestampStyleShortDateTime), true). - AddField(formatTitle("Claimed By", customisation.EmojiClaim, worker.IsWhitelabel), claimedBy, true) - - if ticket.CloseTime != nil { - closeEmbed.SetTimestamp(*ticket.CloseTime) - } - - if rating == nil { - closeEmbed = closeEmbed.AddBlankField(true) - } else { - closeEmbed = closeEmbed.AddField(formatTitle("Rating", customisation.EmojiRating, worker.IsWhitelabel), fmt.Sprintf("%d ⭐", *rating), true) - } - - closeEmbed = closeEmbed.AddField(formatTitle("Reason", customisation.EmojiReason, worker.IsWhitelabel), formattedReason, false) - - var rows []component.Component - for _, row := range components { - var rowElements []component.Component - for _, element := range row { - rowElements = append(rowElements, element(worker, ticket)...) - } - - if len(rowElements) > 0 { - rows = append(rows, component.BuildActionRow(rowElements...)) - } - } - - return closeEmbed, rows -} - func BuildCloseContainer( ctx context.Context, cmd registry.CommandContext, @@ -207,7 +130,7 @@ func BuildCloseContainer( closedBy uint64, reason *string, rating *uint8, - components [][]CloseEmbedElement, + viewFeedbackButton bool, ) *component.Component { var formattedReason = "No reason specified" if reason != nil { @@ -289,7 +212,14 @@ func BuildCloseContainer( component.BuildTextDisplay(component.TextDisplay{Content: strings.Join(section2Text, "\n")}), } + if viewFeedbackButton { + innerComponents = append(innerComponents, component.BuildActionRow( + FeedbackRowElement(viewFeedbackButton)(worker, ticket)..., + )) + } + if cmd.PremiumTier() == premium.None { + innerComponents = append(innerComponents, component.BuildSeparator(component.Separator{})) innerComponents = utils.AddPremiumFooter(innerComponents) } @@ -315,6 +245,7 @@ func formatTitle(s string, emoji customisation.CustomEmoji, isWhitelabel bool) s func EditGuildArchiveMessageIfExists( ctx context.Context, + cmd registry.CommandContext, worker *worker.Context, ticket database.Ticket, settings database.Settings, @@ -332,18 +263,11 @@ func EditGuildArchiveMessageIfExists( return nil } - componentBuilders := [][]CloseEmbedElement{ - { - TranscriptLinkElement(settings.StoreTranscripts), - ThreadLinkElement(ticket.IsThread && ticket.ChannelId != nil), - ViewFeedbackElement(viewFeedbackButton), - }, - } + closeContainer := BuildCloseContainer(ctx, cmd, worker, ticket, closedBy, reason, rating, viewFeedbackButton) - embed, components := BuildCloseEmbed(ctx, worker, ticket, closedBy, reason, rating, componentBuilders) _, err = worker.EditMessage(archiveMessage.ChannelId, archiveMessage.MessageId, rest.EditMessageData{ - Embeds: utils.Slice(embed), - Components: components, + Flags: uint(message.FlagComponentsV2), + Components: utils.Slice(*closeContainer), }) return err From 34bf12558e8163b520b68724294c6c229c1033c8 Mon Sep 17 00:00:00 2001 From: biast12 Date: Sat, 19 Jul 2025 16:33:16 +0200 Subject: [PATCH 3/4] Add server info and icon to close container UI The BuildCloseContainer function now accepts a guild parameter to display the server name and icon in the ticket close UI (DMs only). --- bot/logic/close.go | 4 ++-- bot/logic/closeembed.go | 30 +++++++++++++++++++++++++----- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/bot/logic/close.go b/bot/logic/close.go index f5792a9..e5051ac 100644 --- a/bot/logic/close.go +++ b/bot/logic/close.go @@ -250,7 +250,7 @@ func sendCloseContainer(ctx context.Context, cmd registry.CommandContext, errorC } if archiveChannelExists && archiveChannelId != nil { - closeContainer := BuildCloseContainer(ctx, cmd, cmd.Worker(), ticket, member.User.Id, reason, nil, false) + closeContainer := BuildCloseContainer(ctx, cmd, cmd.Worker(), ticket, nil, member.User.Id, reason, nil, false) data := rest.CreateMessageData{ Flags: uint(message.FlagComponentsV2), @@ -328,7 +328,7 @@ func sendCloseContainer(ctx context.Context, cmd registry.CommandContext, errorC } } - closeContainer := BuildCloseContainer(ctx, cmd, cmd.Worker(), ticket, member.User.Id, reason, nil, viewFeedbackButton) + closeContainer := BuildCloseContainer(ctx, cmd, cmd.Worker(), ticket, &guild, member.User.Id, reason, nil, viewFeedbackButton) components := []component.Component{*closeContainer} if content != "" { diff --git a/bot/logic/closeembed.go b/bot/logic/closeembed.go index 925739a..e318561 100644 --- a/bot/logic/closeembed.go +++ b/bot/logic/closeembed.go @@ -10,6 +10,7 @@ import ( "github.com/TicketsBot-cloud/common/sentry" "github.com/TicketsBot-cloud/database" "github.com/TicketsBot-cloud/gdl/objects/channel/message" + "github.com/TicketsBot-cloud/gdl/objects/guild" "github.com/TicketsBot-cloud/gdl/objects/guild/emoji" "github.com/TicketsBot-cloud/gdl/objects/interaction/component" "github.com/TicketsBot-cloud/gdl/rest" @@ -127,6 +128,7 @@ func BuildCloseContainer( cmd registry.CommandContext, worker *worker.Context, ticket database.Ticket, + guild *guild.Guild, closedBy uint64, reason *string, rating *uint8, @@ -163,9 +165,11 @@ func BuildCloseContainer( } } - section1Text := []string{ - formatRow("Ticket ID", strconv.Itoa(ticket.Id)), + section1Text := []string{} + if guild != nil { + section1Text = append(section1Text, formatRow("Server", guild.Name)) } + section1Text = append(section1Text, formatRow("Ticket ID", strconv.Itoa(ticket.Id))) if panelName != "" { section1Text = append(section1Text, formatRow("Panel", panelName)) } @@ -207,10 +211,26 @@ func BuildCloseContainer( Content: "## Ticket Closed", })), }), - component.BuildTextDisplay(component.TextDisplay{Content: strings.Join(section1Text, "\n")}), + } + + section := component.Section{ + Components: utils.Slice(component.BuildTextDisplay(component.TextDisplay{ + Content: strings.Join(section1Text, "\n"), + })), + } + if guild != nil && guild.Icon != "" { + section.Accessory = component.BuildThumbnail(component.Thumbnail{ + Media: component.UnfurledMediaItem{ + Url: fmt.Sprintf("https://cdn.discordapp.com/icons/%d/%s.png", guild.Id, guild.Icon), + }, + }) + } + innerComponents = append(innerComponents, component.BuildSection(section)) + + innerComponents = append(innerComponents, component.BuildSeparator(component.Separator{}), component.BuildTextDisplay(component.TextDisplay{Content: strings.Join(section2Text, "\n")}), - } + ) if viewFeedbackButton { innerComponents = append(innerComponents, component.BuildActionRow( @@ -263,7 +283,7 @@ func EditGuildArchiveMessageIfExists( return nil } - closeContainer := BuildCloseContainer(ctx, cmd, worker, ticket, closedBy, reason, rating, viewFeedbackButton) + closeContainer := BuildCloseContainer(ctx, cmd, worker, ticket, nil, closedBy, reason, rating, viewFeedbackButton) _, err = worker.EditMessage(archiveMessage.ChannelId, archiveMessage.MessageId, rest.EditMessageData{ Flags: uint(message.FlagComponentsV2), From 28bf5d184fa75bc4d40505266f5d2d62bcab2615 Mon Sep 17 00:00:00 2001 From: biast12 Date: Tue, 22 Jul 2025 02:24:56 +0200 Subject: [PATCH 4/4] Fix section accessory rendering in close embed Refactors the logic for building the section with a guild icon accessory in the close embed. Now only adds the accessory if the guild and icon are present, otherwise appends the text display directly. This prevents empty or incorrect accessory fields. --- bot/logic/closeembed.go | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/bot/logic/closeembed.go b/bot/logic/closeembed.go index e318561..e48eeb7 100644 --- a/bot/logic/closeembed.go +++ b/bot/logic/closeembed.go @@ -213,19 +213,23 @@ func BuildCloseContainer( }), } - section := component.Section{ - Components: utils.Slice(component.BuildTextDisplay(component.TextDisplay{ - Content: strings.Join(section1Text, "\n"), - })), - } + sectionContent := component.BuildTextDisplay(component.TextDisplay{ + Content: strings.Join(section1Text, "\n"), + }) + if guild != nil && guild.Icon != "" { - section.Accessory = component.BuildThumbnail(component.Thumbnail{ - Media: component.UnfurledMediaItem{ - Url: fmt.Sprintf("https://cdn.discordapp.com/icons/%d/%s.png", guild.Id, guild.Icon), - }, - }) + section := component.Section{ + Components: utils.Slice(sectionContent), + Accessory: component.BuildThumbnail(component.Thumbnail{ + Media: component.UnfurledMediaItem{ + Url: fmt.Sprintf("https://cdn.discordapp.com/icons/%d/%s.png", guild.Id, guild.Icon), + }, + }), + } + innerComponents = append(innerComponents, component.BuildSection(section)) + } else { + innerComponents = append(innerComponents, sectionContent) } - innerComponents = append(innerComponents, component.BuildSection(section)) innerComponents = append(innerComponents, component.BuildSeparator(component.Separator{}),