fix(tui): render discord mention names

This commit is contained in:
Vincent Koc 2026-05-03 09:21:30 -07:00
parent 39906edc3d
commit a4f5d3fdb4
No known key found for this signature in database
4 changed files with 119 additions and 6 deletions

View File

@ -237,14 +237,16 @@ func TestDiscordTUIRowsIncludePaneMetadata(t *testing.T) {
AuthorID: "u1",
AuthorName: "Peter",
Content: "hello from desktop",
DisplayContent: "hello from Vincent",
CreatedAt: time.Date(2026, 5, 2, 12, 0, 0, 0, time.UTC),
ReplyToMessage: "m0",
HasAttachments: true,
Pinned: true,
}})
require.Len(t, rows, 1)
require.Equal(t, "hello from desktop", rows[0].Title)
require.Equal(t, "hello from desktop", rows[0].Detail)
require.Equal(t, "hello from Vincent", rows[0].Title)
require.Equal(t, "hello from Vincent", rows[0].Detail)
require.Equal(t, "hello from Vincent", rows[0].Text)
require.Equal(t, "Direct messages", rows[0].Scope)
require.Equal(t, "Vincent K", rows[0].Container)
require.Contains(t, rows[0].Tags, "dm")

View File

@ -117,7 +117,8 @@ func (r *runtime) archiveSourceLocation() string {
func discordTUIRows(rows []store.MessageRow) []tui.Row {
items := make([]tui.Row, 0, len(rows))
for _, row := range rows {
title := strings.TrimSpace(row.Content)
content := discordDisplayContent(row)
title := strings.TrimSpace(content)
if title == "" {
title = row.MessageID
}
@ -137,8 +138,8 @@ func discordTUIRows(rows []store.MessageRow) []tui.Row {
Container: discordContainerLabel(row),
Author: discordAuthorLabel(row),
Title: title,
Text: row.Content,
Detail: row.Content,
Text: content,
Detail: content,
URL: discordMessageURL(row),
CreatedAt: formatTime(row.CreatedAt),
Tags: tags,
@ -156,6 +157,13 @@ func discordTUIRows(rows []store.MessageRow) []tui.Row {
return items
}
func discordDisplayContent(row store.MessageRow) string {
if content := strings.TrimSpace(row.DisplayContent); content != "" {
return content
}
return row.Content
}
func discordMessageURL(row store.MessageRow) string {
guildID := strings.TrimSpace(row.GuildID)
channelID := strings.TrimSpace(row.ChannelID)

View File

@ -116,3 +116,44 @@ func TestAttachmentTextAndMentionsAreQueryable(t *testing.T) {
require.NoError(t, err)
require.Len(t, filtered, 1)
}
func TestListMessagesResolvesMentionNamesForDisplay(t *testing.T) {
t.Parallel()
ctx := context.Background()
s, err := Open(ctx, filepath.Join(t.TempDir(), "discrawl.db"))
require.NoError(t, err)
defer func() { _ = s.Close() }()
require.NoError(t, s.UpsertChannel(ctx, ChannelRecord{ID: "c1", GuildID: "g1", Kind: "text", Name: "maintainers", RawJSON: `{}`}))
createdAt := time.Now().UTC().Format(time.RFC3339Nano)
rawContent := "ping <@u2> <@!u3> <@&r1> in <#c1>"
require.NoError(t, s.UpsertMessages(ctx, []MessageMutation{{
Record: MessageRecord{
ID: "m1",
GuildID: "g1",
ChannelID: "c1",
ChannelName: "maintainers",
AuthorID: "u1",
AuthorName: "Peter",
MessageType: 0,
CreatedAt: createdAt,
Content: rawContent,
NormalizedContent: rawContent,
RawJSON: `{}`,
},
Mentions: []MentionEventRecord{
{MessageID: "m1", GuildID: "g1", ChannelID: "c1", AuthorID: "u1", TargetType: "user", TargetID: "u2", TargetName: "Shadow", EventAt: createdAt},
{MessageID: "m1", GuildID: "g1", ChannelID: "c1", AuthorID: "u1", TargetType: "user", TargetID: "u3", TargetName: "Vincent", EventAt: createdAt},
{MessageID: "m1", GuildID: "g1", ChannelID: "c1", AuthorID: "u1", TargetType: "role", TargetID: "r1", TargetName: "Maintainers", EventAt: createdAt},
{MessageID: "m1", GuildID: "g1", ChannelID: "c1", AuthorID: "u1", TargetType: "channel", TargetID: "c1", TargetName: "maintainers", EventAt: createdAt},
},
}}))
messages, err := s.ListMessages(ctx, MessageListOptions{Channel: "maintainers", Limit: 10})
require.NoError(t, err)
require.Len(t, messages, 1)
require.Equal(t, rawContent, messages[0].Content)
require.Equal(t, "ping @Shadow @Vincent @Maintainers in #maintainers", messages[0].DisplayContent)
}

View File

@ -37,6 +37,7 @@ type MessageRow struct {
AuthorID string `json:"author_id"`
AuthorName string `json:"author_name"`
Content string `json:"content"`
DisplayContent string `json:"display_content,omitempty"`
CreatedAt time.Time `json:"created_at"`
ReplyToMessage string `json:"reply_to_message_id,omitempty"`
Source string `json:"source,omitempty"`
@ -161,11 +162,72 @@ func (s *Store) ListMessages(ctx context.Context, opts MessageListOptions) ([]Me
row.CreatedAt = parseTime(created)
row.HasAttachments = hasAttachments == 1
row.Pinned = pinned == 1
row.DisplayContent = row.Content
out = append(out, row)
}
return out, rows.Err()
if err := rows.Err(); err != nil {
return nil, err
}
return out, s.resolveMessageDisplayMentions(ctx, out)
}
func normalizeChannelFilter(raw string) string {
return strings.TrimSpace(strings.TrimPrefix(strings.TrimSpace(raw), "#"))
}
func (s *Store) resolveMessageDisplayMentions(ctx context.Context, rows []MessageRow) error {
if len(rows) == 0 {
return nil
}
ids := make([]any, 0, len(rows))
indexByID := make(map[string]int, len(rows))
for index, row := range rows {
id := strings.TrimSpace(row.MessageID)
if id == "" {
continue
}
ids = append(ids, id)
indexByID[id] = index
}
if len(ids) == 0 {
return nil
}
query := `select message_id, target_type, target_id, target_name from mention_events where message_id in (` + placeholders(len(ids)) + `)`
mentionRows, err := s.db.QueryContext(ctx, query, ids...)
if err != nil {
return err
}
defer func() { _ = mentionRows.Close() }()
for mentionRows.Next() {
var messageID, targetType, targetID, targetName string
if err := mentionRows.Scan(&messageID, &targetType, &targetID, &targetName); err != nil {
return err
}
index, ok := indexByID[messageID]
if !ok {
continue
}
rows[index].DisplayContent = replaceDiscordMention(rows[index].DisplayContent, targetType, targetID, targetName)
}
return mentionRows.Err()
}
func replaceDiscordMention(content, targetType, targetID, targetName string) string {
targetID = strings.TrimSpace(targetID)
if targetID == "" {
return content
}
label := strings.TrimSpace(targetName)
if label == "" {
label = targetID
}
switch strings.TrimSpace(targetType) {
case "role":
return strings.ReplaceAll(content, "<@&"+targetID+">", "@"+label)
case "channel":
return strings.ReplaceAll(content, "<#"+targetID+">", "#"+label)
default:
content = strings.ReplaceAll(content, "<@"+targetID+">", "@"+label)
return strings.ReplaceAll(content, "<@!"+targetID+">", "@"+label)
}
}