This commit is contained in:
lfirmin
2026-05-02 03:23:51 +02:00
parent aeb9b3d96f
commit 181b8ed454
12 changed files with 508 additions and 73 deletions
+101 -5
View File
@@ -23,6 +23,7 @@ type Message struct {
Content template.HTML
Timestamp string
Attachments []Attachment
Embeds []RenderedEmbed
}
type Attachment struct {
@@ -30,6 +31,29 @@ type Attachment struct {
URL string
}
type RenderedEmbed struct {
Color template.CSS // CSS hex e.g. "#5865f2"
AuthorName string
AuthorIcon string
AuthorURL string
Title string
TitleURL string
Description template.HTML
Fields []EmbedField
ImageURL string
ThumbURL string
FooterText string
FooterIcon string
Timestamp string
Empty bool // true if nothing visible to show
}
type EmbedField struct {
Name string
Value template.HTML
Inline bool
}
type Generator struct {
OutputDir string
Session *discordgo.Session
@@ -39,8 +63,6 @@ func NewGenerator(outputDir string, s *discordgo.Session) *Generator {
return &Generator{OutputDir: outputDir, Session: s}
}
// Generate fetches all messages from the ticket channel and writes the HTML transcript.
// Returns the output file path.
func (g *Generator) Generate(ticket *db.Ticket) (string, error) {
messages, err := g.fetchAll(ticket.ChannelID)
if err != nil {
@@ -103,7 +125,6 @@ func (g *Generator) fetchAll(channelID string) ([]*discordgo.Message, error) {
}
before = batch[len(batch)-1].ID
}
// Reverse to chronological order
for i, j := 0, len(all)-1; i < j; i, j = i+1, j-1 {
all[i], all[j] = all[j], all[i]
}
@@ -111,6 +132,7 @@ func (g *Generator) fetchAll(channelID string) ([]*discordgo.Message, error) {
}
func renderMessage(m *discordgo.Message) Message {
// Webhook messages use WebhookID — author is the impersonated user
name := m.Author.Username
avatar := m.Author.AvatarURL("64")
initial := "?"
@@ -118,9 +140,7 @@ func renderMessage(m *discordgo.Message) Message {
initial = strings.ToUpper(string([]rune(name)[0]))
}
// Escape all user content — XSS protection
content := template.HTMLEscapeString(m.Content)
// Preserve newlines as <br>
contentHTML := template.HTML(strings.ReplaceAll(content, "\n", "<br>"))
ts := ""
@@ -136,6 +156,11 @@ func renderMessage(m *discordgo.Message) Message {
})
}
var embeds []RenderedEmbed
for _, e := range m.Embeds {
embeds = append(embeds, renderEmbed(e))
}
return Message{
AuthorName: template.HTMLEscapeString(name),
AuthorAvatar: avatar,
@@ -143,5 +168,76 @@ func renderMessage(m *discordgo.Message) Message {
Content: contentHTML,
Timestamp: ts,
Attachments: attachments,
Embeds: embeds,
}
}
func renderEmbed(e *discordgo.MessageEmbed) RenderedEmbed {
re := RenderedEmbed{}
// Color
if e.Color != 0 {
re.Color = template.CSS(fmt.Sprintf("#%06x", e.Color))
} else {
re.Color = "#4f545c"
}
// Author
if e.Author != nil {
re.AuthorName = template.HTMLEscapeString(e.Author.Name)
re.AuthorIcon = e.Author.IconURL
re.AuthorURL = e.Author.URL
}
// Title
if e.Title != "" {
re.Title = template.HTMLEscapeString(e.Title)
re.TitleURL = e.URL
}
// Description
if e.Description != "" {
escaped := template.HTMLEscapeString(e.Description)
re.Description = template.HTML(strings.ReplaceAll(escaped, "\n", "<br>"))
}
// Fields
for _, f := range e.Fields {
val := template.HTMLEscapeString(f.Value)
re.Fields = append(re.Fields, EmbedField{
Name: template.HTMLEscapeString(f.Name),
Value: template.HTML(strings.ReplaceAll(val, "\n", "<br>")),
Inline: f.Inline,
})
}
// Image
if e.Image != nil {
re.ImageURL = e.Image.URL
}
// Thumbnail
if e.Thumbnail != nil {
re.ThumbURL = e.Thumbnail.URL
}
// Footer
if e.Footer != nil {
re.FooterText = template.HTMLEscapeString(e.Footer.Text)
re.FooterIcon = e.Footer.IconURL
}
// Timestamp
if e.Timestamp != "" {
if t, err := time.Parse(time.RFC3339, e.Timestamp); err == nil {
re.Timestamp = t.UTC().Format("02/01/2006 15:04")
}
}
// Detect empty embed (nothing visible)
re.Empty = re.AuthorName == "" && re.Title == "" &&
re.Description == "" && len(re.Fields) == 0 &&
re.ImageURL == "" && re.FooterText == ""
return re
}