From f68958f75c8dd428279768fb2c09b49167a48127 Mon Sep 17 00:00:00 2001 From: spearzificial <342299391+spearzificial@users.noreply.github.com> Date: Mon, 19 Oct 2026 14:29:40 +0530 Subject: [PATCH] feat(messageslist): add option to show user status on messages (#454) --- docs/config.md | 6 ++++++ internal/config/ui.go | 3 +++ internal/ui/chat/messageslist/message.go | 18 ++++++++++++++++++ internal/ui/chat/messageslist/model_test.go | 20 ++++++++++++++++++++ internal/ui/chat/state.go | 3 +++ 5 files changed, 50 insertions(+) diff --git a/docs/config.md b/docs/config.md index 119ba8b..593c2ef 100644 --- a/docs/config.md +++ b/docs/config.md @@ -367,6 +367,12 @@ The number of empty rows between messages, except those of a group. Default: `1` +### ui.messages_list.show_user_status + +Whether to show the author's presence status dot alongside their name. + +Default: `false` + ### ui.messages_list.reply_indicator Default: `">"` diff --git a/internal/config/ui.go b/internal/config/ui.go index 37cff52..d5815e5 100644 --- a/internal/config/ui.go +++ b/internal/config/ui.go @@ -174,6 +174,9 @@ type ( // The number of empty rows between messages, except those of a group. Spacing uint8 `toml:"spacing"` + // Whether to show the author's presence status dot alongside their name. + ShowUserStatus bool `toml:"show_user_status"` + ReplyIndicator string `toml:"reply_indicator"` ForwardedIndicator string `toml:"forwarded_indicator"` diff --git a/internal/ui/chat/messageslist/message.go b/internal/ui/chat/messageslist/message.go index 6921e0d..00e5dbc 100644 --- a/internal/ui/chat/messageslist/message.go +++ b/internal/ui/chat/messageslist/message.go @@ -175,7 +175,14 @@ func (m Model) drawCozy(builder *markdown.Builder, message discord.Message, base } } +const statusMarker = "●" + func (m Model) drawAuthor(builder *markdown.Builder, message discord.Message, baseStyle tview.Style) { + if m.cfg.UI.MessagesList.ShowUserStatus && !message.WebhookID.IsValid() { + status := m.status(message.GuildID, message.Author.ID) + builder.Write(statusMarker+" ", m.cfg.Theme.MembersTree.StatusStyle(status)) + } + name := message.Author.DisplayOrUsername() foreground := tcell.ColorDefault @@ -211,6 +218,17 @@ func (m Model) memberForMessage(message discord.Message) *discord.Member { return member } +func (m Model) status(guildID discord.GuildID, userID discord.UserID) discord.Status { + if m.state == nil || m.state.Cabinet == nil { + return discord.OfflineStatus + } + presence, err := m.state.Cabinet.Presence(guildID, userID) + if err != nil { + return discord.OfflineStatus + } + return presence.Status +} + // drawContent renders the message body. root is its parsed content, or nil to parse it here. func (m Model) drawContent(builder *markdown.Builder, message discord.Message, root ast.Node, baseStyle tview.Style) { if root == nil && m.cfg.Markdown.Enabled { diff --git a/internal/ui/chat/messageslist/model_test.go b/internal/ui/chat/messageslist/model_test.go index 1fc5c02..4c446a7 100644 --- a/internal/ui/chat/messageslist/model_test.go +++ b/internal/ui/chat/messageslist/model_test.go @@ -12,6 +12,7 @@ import ( "github.com/ayn2op/arikawa/v3/discord" "github.com/ayn2op/arikawa/v3/state" "github.com/ayn2op/discordo/internal/config" + "github.com/ayn2op/discordo/internal/markdown" "github.com/ayn2op/ningen/v3" "github.com/ayn2op/tview" tviewimage "github.com/ayn2op/tview/image" @@ -401,3 +402,22 @@ func TestModelReactionsView(t *testing.T) { } }) } + +func TestModelShowUserStatus(t *testing.T) { + cfg := testConfig(t) + cfg.UI.MessagesList.ShowUserStatus = true + ml := newTestModel(t, cfg) + user := discord.User{ID: 10, Username: "alice"} + _ = ml.state.PresenceSet(discord.NullGuildID, &discord.Presence{User: user, Status: discord.OnlineStatus}, false) + + msg := discord.Message{ID: 1, Author: user, Content: "hello"} + builder := new(markdown.Builder) + ml.drawAuthor(builder, msg, tcell.StyleDefault) + spans := builder.Finish() + if len(spans) == 0 { + t.Fatal("expected spans from drawAuthor") + } + if !strings.HasPrefix(spans[0].Text, statusMarker) { + t.Fatalf("first span text = %q, want statusMarker %q", spans[0].Text, statusMarker) + } +} diff --git a/internal/ui/chat/state.go b/internal/ui/chat/state.go index 55ae149..2b55a92 100644 --- a/internal/ui/chat/state.go +++ b/internal/ui/chat/state.go @@ -110,6 +110,9 @@ func (m *Model) applyEvent(event gateway.Event) tview.Cmd { case *gateway.MessageUpdateEvent: return m.messagesList.UpdateMessage(event.Message) case *gateway.PresenceUpdateEvent: + if m.cfg.UI.MessagesList.ShowUserStatus { + m.messagesList.InvalidateRendered() + } return tview.Batch(m.updatePane(paneGuildsTree, event), m.updatePane(paneMembersTree, event)) case *gateway.GuildMemberListUpdateEvent: return m.updatePane(paneMembersTree, event)