浏览代码

feat: mark unread channels (#65)

* feat: distinguish unread messages, channels, and guilds

* feat: add UI acknowledgement functionality

* fix: return if tree node by reference not found

* feat: mark pre-unread messages with bold tag
ayntgl 4 年之前
父节点
当前提交
14f93ee1c0
共有 3 个文件被更改,包括 80 次插入38 次删除
  1. 36 12
      discord.go
  2. 0 12
      renderer.go
  3. 44 14
      ui.go

+ 36 - 12
discord.go

@@ -48,7 +48,15 @@ func onSessionReady(_ *discordgo.Session, r *discordgo.Ready) {
 	})
 
 	for _, c := range r.PrivateChannels {
-		cn := tview.NewTreeNode(generateChannelRepr(c)).SetReference(c.ID)
+		var tag string
+		if isUnread(c) {
+			tag = "[::b]"
+		} else {
+			tag = "[::d]"
+		}
+
+		cn := tview.NewTreeNode(tag + generateChannelRepr(c) + "[::-]").
+			SetReference(c.ID)
 		dmNode.AddChild(cn)
 	}
 
@@ -89,12 +97,27 @@ func onSessionReady(_ *discordgo.Session, r *discordgo.Ready) {
 	channelsTree.SetCurrentNode(n)
 }
 
+func isUnread(c *discordgo.Channel) bool {
+	if c.LastMessageID == "" {
+		return false
+	}
+
+	for _, rs := range session.State.ReadState {
+		if c.ID == rs.ID {
+			return c.LastMessageID != rs.LastMessageID
+		}
+	}
+
+	return false
+}
+
 func onSessionMessageCreate(_ *discordgo.Session, m *discordgo.MessageCreate) {
-	if selectedChannel == nil {
-		selectedChannel = &discordgo.Channel{ID: ""}
+	c, err := session.State.Channel(m.ChannelID)
+	if err != nil {
+		return
 	}
 
-	if selectedChannel.ID != m.ChannelID {
+	if selectedChannel == nil || selectedChannel.ID != m.ChannelID {
 		if conf.Notifications {
 			for _, u := range m.Mentions {
 				if u.ID == session.State.User.ID {
@@ -102,10 +125,6 @@ func onSessionMessageCreate(_ *discordgo.Session, m *discordgo.MessageCreate) {
 					if err != nil {
 						return
 					}
-					c, err := session.State.Channel(m.ChannelID)
-					if err != nil {
-						return
-					}
 
 					go beeep.Alert(fmt.Sprintf("%s (#%s)", g.Name, c.Name), m.ContentWithMentionsReplaced(), "")
 					return
@@ -113,11 +132,16 @@ func onSessionMessageCreate(_ *discordgo.Session, m *discordgo.MessageCreate) {
 			}
 		}
 
-		return
+		cn := getTreeNodeByReference(c.ID)
+		if cn == nil {
+			return
+		}
+		cn.SetText("[::b]" + generateChannelRepr(c) + "[::-]")
+		app.Draw()
+	} else {
+		selectedChannel.Messages = append(selectedChannel.Messages, m.Message)
+		renderMessage(m.Message)
 	}
-
-	selectedChannel.Messages = append(selectedChannel.Messages, m.Message)
-	renderMessage(m.Message)
 }
 
 type loginResponse struct {

+ 0 - 12
renderer.go

@@ -13,18 +13,6 @@ var italicRegex = regexp.MustCompile(`(?m)\*(.*?)\*`)
 var underlineRegex = regexp.MustCompile(`(?m)__(.*?)__`)
 var strikeThroughRegex = regexp.MustCompile(`(?m)~~(.*?)~~`)
 
-func renderMessages(cID string) {
-	ms, err := session.ChannelMessages(cID, conf.GetMessagesLimit, "", "", "")
-	if err != nil {
-		return
-	}
-
-	for i := len(ms) - 1; i >= 0; i-- {
-		selectedChannel.Messages = append(selectedChannel.Messages, ms[i])
-		renderMessage(ms[i])
-	}
-}
-
 func renderMessage(m *discordgo.Message) {
 	var b strings.Builder
 

+ 44 - 14
ui.go

@@ -62,7 +62,7 @@ func onChannelsTreeSelected(n *tview.TreeNode) {
 	// Unhighlight the already-highlighted regions.
 	messagesTextView.Highlight()
 
-	if len(n.GetChildren()) != 0 {
+	if len(n.GetChildren()) != 0 || n.GetText() == "Direct Messages" {
 		n.SetExpanded(!n.IsExpanded())
 	} else {
 		cID := n.GetReference().(string)
@@ -86,16 +86,26 @@ func onChannelsTreeSelected(n *tview.TreeNode) {
 			messagesTextView.SetTitle(generateChannelRepr(c))
 		}
 
+		if strings.HasPrefix(n.GetText(), "[::b]") {
+			n.SetText("[::d]" + generateChannelRepr(c) + "[::-]")
+		}
+
 		messagesTextView.Clear()
-		go renderMessages(c.ID)
-	}
-}
 
-func newTextChannelTreeNode(c *discordgo.Channel) *tview.TreeNode {
-	n := tview.NewTreeNode("[::d]" + generateChannelRepr(c) + "[::-]").
-		SetReference(c.ID)
+		ms, err := session.ChannelMessages(cID, conf.GetMessagesLimit, "", "", "")
+		if err != nil {
+			return
+		}
+
+		for i := len(ms) - 1; i >= 0; i-- {
+			selectedChannel.Messages = append(selectedChannel.Messages, ms[i])
+			go renderMessage(ms[i])
+		}
 
-	return n
+		if len(ms) != 0 && isUnread(c) {
+			go session.ChannelMessageAck(c.ID, c.LastMessageID, "")
+		}
+	}
 }
 
 func createTopLevelChannelsTreeNodes(
@@ -105,11 +115,20 @@ func createTopLevelChannelsTreeNodes(
 	for _, c := range cs {
 		if (c.Type == discordgo.ChannelTypeGuildText || c.Type == discordgo.ChannelTypeGuildNews) &&
 			(c.ParentID == "") {
-			if p, err := session.State.UserChannelPermissions(session.State.User.ID, c.ID); err != nil || p&discordgo.PermissionViewChannel != discordgo.PermissionViewChannel {
+			p, err := session.State.UserChannelPermissions(session.State.User.ID, c.ID)
+			if err != nil || p&discordgo.PermissionViewChannel != discordgo.PermissionViewChannel {
 				continue
 			}
 
-			cn := newTextChannelTreeNode(c)
+			var tag string
+			if isUnread(c) {
+				tag = "[::b]"
+			} else {
+				tag = "[::d]"
+			}
+
+			cn := tview.NewTreeNode(tag + generateChannelRepr(c) + "[::-]").
+				SetReference(c.ID)
 			n.AddChild(cn)
 			continue
 		}
@@ -123,7 +142,8 @@ func createCategoryChannelsTreeNodes(
 CategoryLoop:
 	for _, c := range cs {
 		if c.Type == discordgo.ChannelTypeGuildCategory {
-			if p, err := session.State.UserChannelPermissions(session.State.User.ID, c.ID); err != nil || p&discordgo.PermissionViewChannel != discordgo.PermissionViewChannel {
+			p, err := session.State.UserChannelPermissions(session.State.User.ID, c.ID)
+			if err != nil || p&discordgo.PermissionViewChannel != discordgo.PermissionViewChannel {
 				continue
 			}
 
@@ -147,12 +167,22 @@ func createSecondLevelChannelsTreeNodes(cs []*discordgo.Channel) {
 	for _, c := range cs {
 		if (c.Type == discordgo.ChannelTypeGuildText || c.Type == discordgo.ChannelTypeGuildNews) &&
 			(c.ParentID != "") {
-			if p, err := session.State.UserChannelPermissions(session.State.User.ID, c.ID); err != nil || p&discordgo.PermissionViewChannel != discordgo.PermissionViewChannel {
+			p, err := session.State.UserChannelPermissions(session.State.User.ID, c.ID)
+			if err != nil || p&discordgo.PermissionViewChannel != discordgo.PermissionViewChannel {
 				continue
 			}
 
-			if pn := getTreeNodeByReference(c.ParentID); pn != nil {
-				cn := newTextChannelTreeNode(c)
+			var tag string
+			if isUnread(c) {
+				tag = "[::b]"
+			} else {
+				tag = "[::d]"
+			}
+
+			pn := getTreeNodeByReference(c.ParentID)
+			if pn != nil {
+				cn := tview.NewTreeNode(tag + generateChannelRepr(c) + "[::-]").
+					SetReference(c.ID)
 				pn.AddChild(cn)
 			}
 		}