Reaction rendering
Reactions can now be rendered inline. This can be toggled via the
`ShowReactionsInline` configuration field.
MODIFY
config/config.go
config/config.go
@@ -145,6 +145,9 @@ type Config struct {
// application, as there are some childish goons that deem it funny
// to impersonate people or change their name every 5 minutes.
ShowNicknames bool
+ // ShowReactionsInline decides whether reactions are displayed below a
+ // message.
+ ShowReactionsInline bool
// FileHandlers allow registering specific file-handers for certain
FileOpenHandlers map[string]string
@@ -208,6 +211,7 @@ func createDefaultConfig() *Config {
IndicateChannelAccessRestriction: false,
ShowBottomBar: true,
ShowNicknames: true,
+ ShowReactionsInline: true,
FileOpenHandlers: make(map[string]string),
FileOpenSaveFilesPermanently: false,
FileDownloadSaveLocation: "~/Downloads",

MODIFY
discordutil/message.go
discordutil/message.go
@@ -245,3 +245,51 @@ func ReplaceMentions(message *discordgo.Message) string {
}
return strings.NewReplacer(replaceInstructions...).Replace(message.Content)
}
+
+// HandleReactionAdd adds a new reaction to a message or updates the count if
+// that message already has a reaction with that same emoji.
+func HandleReactionAdd(state *discordgo.State,
+ message *discordgo.Message,
+ newReaction *discordgo.MessageReactionAdd) {
+ for _, reaction := range message.Reactions {
+ //Only custom emojis have IDs and non custom unes have unique names.
+ if reaction.Emoji.ID == newReaction.Emoji.ID && reaction.Emoji.Name == newReaction.Emoji.Name {
+ //Match found, so we can add one to the count.
+ reaction.Count++
+ return
+ }
+ }
+
+ //FIXME Better look up emoji in cache if possible?
+ message.Reactions = append(message.Reactions, &discordgo.MessageReactions{
+ Count: 1,
+ Emoji: &newReaction.Emoji,
+ Me: newReaction.UserID == state.User.ID,
+ })
+}
+
+// HandleReactionRemove removes an existing reaction to a message or updates
+// the count if the same message still has reactions with the same emoji left.
+func HandleReactionRemove(state *discordgo.State,
+ message *discordgo.Message,
+ newReaction *discordgo.MessageReactionRemove) {
+ for index, reaction := range message.Reactions {
+ //Only custom emojis have IDs and non custom unes have unique names.
+ if reaction.Emoji.ID == newReaction.Emoji.ID && reaction.Emoji.Name == newReaction.Emoji.Name {
+ if reaction.Count <= 1 {
+ message.Reactions = append(message.Reactions[:index], message.Reactions[index+1:]...)
+ //No more reactions of that emoji would be left, therefore we remove the array entry.
+ } else {
+ //Only a single user removed his reaction, so we keep the array entry.
+ reaction.Count--
+ }
+ return
+ }
+ }
+}
+
+// HandleReactionRemoveAll removes all reactions from all users in a message.
+func HandleReactionRemoveAll(state *discordgo.State,
+ message *discordgo.Message) {
+ message.Reactions = message.Reactions[0:0]
+}

MODIFY
ui/chatview.go
ui/chatview.go
@@ -714,8 +714,32 @@ func (chatView *ChatView) formatDefaultMessageText(message *discordgo.Message) s
}
}
+ var reactionText string
+ if len(message.Reactions) > 0 {
+ var reactionBuilder strings.Builder
+ reactionBuilder.Grow(10 + len(message.Reactions)*8)
+ reactionBuilder.WriteString("\nReactions: ")
+ for rIndex, reaction := range message.Reactions {
+ if reaction.Emoji.Name != "" {
+ reactionBuilder.WriteString(tviewutil.Escape(reaction.Emoji.Name))
+ if reaction.Me {
+ reactionBuilder.WriteString("[::r]")
+ }
+ reactionBuilder.WriteRune('-')
+ reactionBuilder.WriteString(strconv.FormatInt(int64(reaction.Count), 10))
+ if reaction.Me {
+ reactionBuilder.WriteString("[::-]")
+ }
+ if rIndex != len(message.Reactions)-1 {
+ reactionBuilder.WriteRune(' ')
+ }
+ }
+ }
+ reactionText = reactionBuilder.String()
+ }
+
if !hasRichEmbed {
- return messageText
+ return messageText + reactionText
}
var messageBuffer strings.Builder
@@ -808,7 +832,7 @@ func (chatView *ChatView) formatDefaultMessageText(message *discordgo.Message) s
embedBuffer.WriteRune('\n')
}
- return messageBuffer.String()
+ return messageBuffer.String() + reactionText
}
func parseCustomEmojis(text string) string {

MODIFY
ui/window.go
ui/window.go
@@ -742,6 +742,7 @@ func NewWindow(app *tview.Application, session *discordgo.Session, readyEvent *d
window.registerMessageEventHandler(messageInputChan, messageEditChan, messageDeleteChan, messageBulkDeleteChan)
window.startMessageHandlerRoutines(messageInputChan, messageEditChan, messageDeleteChan, messageBulkDeleteChan)
+ window.registerReactionEventHandlers()
window.userList = NewUserTree(window.session.State)
@@ -1653,6 +1654,73 @@ func (window *Window) registerMessageEventHandler(input, edit, delete chan *disc
})
}
+// registerReactionEventHandlers are responsible for updating the cache if
+// reactions are added or removed and updating the chatview if needed.
+func (window *Window) registerReactionEventHandlers() {
+ window.session.AddHandler(func(s *discordgo.Session, m *discordgo.MessageReactionAdd) {
+ message, stateError := s.State.Message(m.ChannelID, m.MessageID)
+ if message != nil && stateError == nil {
+ s.State.Lock()
+ defer func() {
+ selectedChannel := window.selectedChannel
+ if selectedChannel != nil && selectedChannel.ID == m.ChannelID {
+ window.app.QueueUpdateDraw(func() {
+ window.chatView.Lock()
+ defer window.chatView.Unlock()
+
+ window.chatView.UpdateMessage(message)
+ })
+ }
+ }()
+ defer s.State.Unlock()
+
+ discordutil.HandleReactionAdd(s.State, message, m)
+ }
+ })
+
+ window.session.AddHandler(func(s *discordgo.Session, m *discordgo.MessageReactionRemove) {
+ message, stateError := s.State.Message(m.ChannelID, m.MessageID)
+ if message != nil && stateError == nil {
+ s.State.Lock()
+ defer func() {
+ selectedChannel := window.selectedChannel
+ if selectedChannel != nil && selectedChannel.ID == m.ChannelID {
+ window.app.QueueUpdateDraw(func() {
+ window.chatView.Lock()
+ defer window.chatView.Unlock()
+
+ window.chatView.UpdateMessage(message)
+ })
+ }
+ }()
+ defer s.State.Unlock()
+
+ discordutil.HandleReactionRemove(s.State, message, m)
+ }
+ })
+
+ window.session.AddHandler(func(s *discordgo.Session, m *discordgo.MessageReactionRemoveAll) {
+ message, stateError := s.State.Message(m.ChannelID, m.MessageID)
+ if message != nil && stateError == nil {
+ s.State.Lock()
+ defer func() {
+ selectedChannel := window.selectedChannel
+ if selectedChannel != nil && selectedChannel.ID == m.ChannelID {
+ window.app.QueueUpdateDraw(func() {
+ window.chatView.Lock()
+ defer window.chatView.Unlock()
+
+ window.chatView.UpdateMessage(message)
+ })
+ }
+ }()
+ defer s.State.Unlock()
+
+ discordutil.HandleReactionRemoveAll(s.State, message)
+ }
+ })
+}
+
// QueueUpdateDrawSynchronized is meant to be used by goroutines that aren't
// the main goroutine in order to wait for the UI-Thread to execute the given
// If this method is ever called from the main thread, the application will

Reaction rendering

Reactions can now be rendered inline. This can be toggled via the
`ShowReactionsInline` configuration field.