Parcourir la source

feat: add nicknames and role colors to usernames (#531)

ludmila-lovelace il y a 1 an
Parent
commit
edd3523514
4 fichiers modifiés avec 120 ajouts et 7 suppressions
  1. 104 6
      cmd/messages_text.go
  2. 5 0
      cmd/state.go
  3. 6 0
      internal/config/theme.go
  4. 5 1
      internal/markdown/renderer.go

+ 104 - 6
cmd/messages_text.go

@@ -1,12 +1,14 @@
 package cmd
 
 import (
+	"context"
 	"errors"
 	"fmt"
 	"io"
 	"log/slog"
 	"slices"
 	"strconv"
+	"sync"
 	"time"
 
 	"github.com/atotto/clipboard"
@@ -14,6 +16,7 @@ import (
 	"github.com/ayn2op/discordo/internal/markdown"
 	"github.com/ayn2op/discordo/internal/ui"
 	"github.com/diamondburned/arikawa/v3/discord"
+	"github.com/diamondburned/arikawa/v3/gateway"
 	"github.com/diamondburned/ningen/v3/discordmd"
 	"github.com/gdamore/tcell/v2"
 	"github.com/rivo/tview"
@@ -29,6 +32,11 @@ type MessagesText struct {
 	cfg               *config.Config
 	app               *tview.Application
 	selectedMessageID discord.MessageID
+	fetchingMembers   struct {
+		value bool
+		done  chan struct{}
+		mu    sync.Mutex
+	}
 }
 
 func newMessagesText(app *tview.Application, cfg *config.Config) *MessagesText {
@@ -57,6 +65,7 @@ func newMessagesText(app *tview.Application, cfg *config.Config) *MessagesText {
 	markdown.DefaultRenderer.AddOptions(
 		renderer.WithOption("emojiColor", t.MessagesText.EmojiColor),
 		renderer.WithOption("linkColor", t.MessagesText.LinkColor),
+		renderer.WithOption("showNicknames", t.MessagesText.ShowNicknames),
 	)
 
 	return mt
@@ -69,6 +78,12 @@ func (mt *MessagesText) drawMsgs(cID discord.ChannelID) {
 		return
 	}
 
+	if app.cfg.Theme.MessagesText.ShowNicknames || app.cfg.Theme.MessagesText.ShowUsernameColors {
+		if ch, _ := discordState.Cabinet.Channel(cID); ch.GuildID.IsValid() {
+			mt.fetchMembers(ch.GuildID, ms)
+		}
+	}
+
 	for _, m := range slices.Backward(ms) {
 		app.messagesText.createMessage(m)
 	}
@@ -111,23 +126,23 @@ func (mt *MessagesText) createMessage(m discord.Message) {
 		fmt.Fprint(mt, "["+mt.cfg.Theme.MessagesText.ContentColor+"]"+m.Author.Username+" pinned a message"+"[-:-:-]")
 	case discord.DefaultMessage, discord.InlinedReplyMessage:
 		if m.ReferencedMessage != nil {
-			mt.createHeader(mt, *m.ReferencedMessage, true)
+			mt.createHeader(mt, *m.ReferencedMessage, m.GuildID, true)
 			mt.createBody(mt, *m.ReferencedMessage, true)
 
 			fmt.Fprint(mt, "[::-]\n")
 		}
 
-		mt.createHeader(mt, m, false)
+		mt.createHeader(mt, m, m.GuildID, false)
 		mt.createBody(mt, m, false)
 		mt.createFooter(mt, m)
 	default:
-		mt.createHeader(mt, m, false)
+		mt.createHeader(mt, m, m.GuildID, false)
 	}
 
 	fmt.Fprintln(mt)
 }
 
-func (mt *MessagesText) createHeader(w io.Writer, m discord.Message, isReply bool) {
+func (mt *MessagesText) createHeader(w io.Writer, m discord.Message, gID discord.GuildID, isReply bool) {
 	if mt.cfg.Timestamps.Enabled {
 		time := m.Timestamp.Time().In(time.Local).Format(mt.cfg.Timestamps.Format)
 		fmt.Fprintf(w, "[::d]%s[::-] ", time)
@@ -137,7 +152,10 @@ func (mt *MessagesText) createHeader(w io.Writer, m discord.Message, isReply boo
 		fmt.Fprintf(mt, "[::d]%s", mt.cfg.Theme.MessagesText.ReplyIndicator)
 	}
 
-	fmt.Fprintf(w, "[%s]%s[-:-:-] ", mt.cfg.Theme.MessagesText.AuthorColor, m.Author.Username)
+	displayName := mt.getAuthorDisplayName(m, gID)
+	displayColor := mt.getAuthorDisplayColor(m, gID)
+
+	fmt.Fprintf(w, "[%s]%s[-:-:-] ", displayColor, displayName)
 }
 
 func (mt *MessagesText) createBody(w io.Writer, m discord.Message, isReply bool) {
@@ -193,6 +211,32 @@ func (mt *MessagesText) getSelectedMessageIndex() (int, error) {
 	return -1, nil
 }
 
+func (mt *MessagesText) getAuthorDisplayName(m discord.Message, gID discord.GuildID) string {
+	name := m.Author.DisplayOrUsername()
+
+	if app.cfg.Theme.MessagesText.ShowNicknames && gID.IsValid() {
+		// Use guild nickname if present
+		if member, _ := discordState.Cabinet.Member(gID, m.Author.ID); member != nil && member.Nick != "" {
+			name = member.Nick
+		}
+	}
+
+	return name
+}
+
+func (mt *MessagesText) getAuthorDisplayColor(m discord.Message, gID discord.GuildID) string {
+	color := mt.cfg.Theme.MessagesText.AuthorColor
+
+	if app.cfg.Theme.MessagesText.ShowUsernameColors && gID.IsValid() {
+		// Use color from highest role in guild
+		if c, ok := discordState.MemberColor(gID, m.Author.ID); ok {
+			color = c.String()
+		}
+	}
+
+	return color
+}
+
 func (mt *MessagesText) onInputCapture(event *tcell.EventKey) *tcell.EventKey {
 	switch event.Name() {
 	case mt.cfg.Keys.MessagesText.Cancel:
@@ -460,7 +504,7 @@ func (mt *MessagesText) reply(mention bool) {
 		return
 	}
 
-	title += msg.Author.Tag()
+	title += mt.getAuthorDisplayName(*msg, msg.GuildID)
 	app.messageInput.SetTitle(title)
 	app.messageInput.replyMessageID = mt.selectedMessageID
 	mt.app.SetFocus(app.messageInput)
@@ -511,3 +555,57 @@ func (mt *MessagesText) delete() {
 		app.messagesText.createMessage(m)
 	}
 }
+
+func (mt *MessagesText) fetchMembers(gID discord.GuildID, ms []discord.Message) {
+	var usersToFetch []discord.UserID
+	for _, m := range ms {
+		if member, _ := discordState.Cabinet.Member(gID, m.Author.ID); member == nil {
+			usersToFetch = append(usersToFetch, m.Author.ID)
+		}
+	}
+
+	if usersToFetch != nil {
+		err := discordState.Gateway().Send(context.Background(), &gateway.RequestGuildMembersCommand{
+			GuildIDs: []discord.GuildID{gID},
+			UserIDs:  slices.Compact(usersToFetch),
+		})
+		if err != nil {
+			slog.Error("Failed to request guild members", "err", err)
+			return
+		}
+
+		mt.setFetchingChunk(true)
+		mt.waitForChunkEvent()
+	}
+}
+
+func (mt *MessagesText) setFetchingChunk(value bool) {
+	mt.fetchingMembers.mu.Lock()
+	if mt.fetchingMembers.value == value {
+		mt.fetchingMembers.mu.Unlock()
+		return
+	}
+	mt.fetchingMembers.value = value
+	mt.fetchingMembers.mu.Unlock()
+
+	if value {
+		mt.fetchingMembers.done = make(chan struct{})
+	} else {
+		close(mt.fetchingMembers.done)
+	}
+}
+
+func (mt *MessagesText) waitForChunkEvent() {
+	mt.fetchingMembers.mu.Lock()
+	if !mt.fetchingMembers.value {
+		mt.fetchingMembers.mu.Unlock()
+		return
+	}
+	mt.fetchingMembers.mu.Unlock()
+
+	select {
+	case <-mt.fetchingMembers.done:
+	default:
+		<-mt.fetchingMembers.done
+	}
+}

+ 5 - 0
cmd/state.go

@@ -43,6 +43,7 @@ func openState(token string) error {
 	discordState.AddHandler(discordState.onReady)
 	discordState.AddHandler(discordState.onMessageCreate)
 	discordState.AddHandler(discordState.onMessageDelete)
+	discordState.AddHandler(discordState.onGetMemberChunk)
 
 	discordState.OnRequest = append(discordState.OnRequest, discordState.onRequest)
 	return discordState.Open(context.TODO())
@@ -106,3 +107,7 @@ func (s *State) onMessageDelete(m *gateway.MessageDeleteEvent) {
 		app.messagesText.drawMsgs(m.ChannelID)
 	}
 }
+
+func (s *State) onGetMemberChunk(g *gateway.GuildMembersChunkEvent) {
+	app.messagesText.setFetchingChunk(false)
+}

+ 6 - 0
internal/config/theme.go

@@ -55,6 +55,9 @@ type (
 	}
 
 	MessagesTextTheme struct {
+		ShowNicknames      bool `toml:"show_user_nicks"`
+		ShowUsernameColors bool `toml:"show_user_colors"`
+
 		ReplyIndicator string `toml:"reply_indicator"`
 
 		AuthorColor     string `toml:"author_color"`
@@ -92,6 +95,9 @@ func defaultTheme() Theme {
 			PrivateChannelColor: tview.Styles.PrimaryTextColor.String(),
 		},
 		MessagesText: MessagesTextTheme{
+			ShowNicknames:      true,
+			ShowUsernameColors: true,
+
 			ReplyIndicator: string(tview.BoxDrawingsLightArcDownAndRight) + " ",
 
 			AuthorColor:     "aqua",

+ 5 - 1
internal/markdown/renderer.go

@@ -96,7 +96,11 @@ func (r *renderer) Render(w io.Writer, source []byte, n ast.Node) error {
 				case n.Channel != nil:
 					io.WriteString(w, "#"+n.Channel.Name)
 				case n.GuildUser != nil:
-					io.WriteString(w, "@"+n.GuildUser.Username)
+					username := n.GuildUser.DisplayOrUsername()
+					if r.config.Options["showNicknames"].(bool) && n.GuildUser.Member != nil && n.GuildUser.Member.Nick != "" {
+						username = n.GuildUser.Member.Nick
+					}
+					io.WriteString(w, "@"+username)
 				case n.GuildRole != nil:
 					io.WriteString(w, "@"+n.GuildRole.Name)
 				}