Guilds and channels readstate now checks the muted state first. Notifications
and the readstate in private channels are still shown.
MODIFY
app/app.go
app/app.go
@@ -92,7 +92,7 @@ func Run() {
readyEvent := <-readyChan
- readstate.Load(readyEvent.ReadState)
+ readstate.Load(discord.State)
app.QueueUpdateDraw(func() {
window, createError := ui.NewWindow(runNext, app, discord, readyEvent)

MODIFY
discordutil/channel.go
discordutil/channel.go
@@ -84,3 +84,14 @@ func SortPrivateChannels(channels []*discordgo.Channel) {
return CompareChannels(channels[a], channels[b])
})
}
+
+// HasReadMessagesPermission checks if the user has permission to view a
+// specific channel.
+func HasReadMessagesPermission(channelID string, state *discordgo.State) bool {
+ userPermissions, err := state.UserChannelPermissions(state.User.ID, channelID)
+ if err != nil {
+ // Unable to access channel permissions.
+ return false
+ }
+ return (userPermissions & discordgo.PermissionReadMessages) > 0
+}

MODIFY
readstate/readmarkers.go
readstate/readmarkers.go
@@ -5,6 +5,7 @@ import (
"sync"
"time"
+ "github.com/Bios-Marcel/cordless/discordutil"
"github.com/Bios-Marcel/discordgo"
)
@@ -12,11 +13,12 @@ var (
data = make(map[string]uint64)
timerMutex = &sync.Mutex{}
ackTimers = make(map[string]*time.Timer)
+ state *discordgo.State
)
// Load loads the locally saved readmarkers returing an error if this failed.
-func Load(readState []*discordgo.ReadState) {
- for _, channelState := range readState {
+func Load(sessionState *discordgo.State) {
+ for _, channelState := range sessionState.ReadState {
lastMessageID := channelState.GetLastMessageID()
if lastMessageID == "" {
continue
@@ -29,6 +31,8 @@ func Load(readState []*discordgo.ReadState) {
data[channelState.ID] = parsed
}
+
+ state = sessionState
}
// ClearReadStateFor clears all entries for the given Channel.
@@ -60,9 +64,9 @@ func UpdateReadLocal(channelID string, lastMessageID string) bool {
// UpdateRead tells the discord server that a channel has been read. If the
// channel has already been read and this method was called needlessly, then
// this will be a No-OP.
-func UpdateRead(session *discordgo.Session, channelID string, lastMessageID string) error {
+func UpdateRead(session *discordgo.Session, channel *discordgo.Channel, lastMessageID string) error {
// Avoid unnecessary traffic
- if HasBeenRead(channelID, lastMessageID) {
+ if HasBeenRead(channel, lastMessageID) {
return nil
}
@@ -71,25 +75,25 @@ func UpdateRead(session *discordgo.Session, channelID string, lastMessageID stri
return parseError
}
- data[channelID] = parsed
+ data[channel.ID] = parsed
- _, ackError := session.ChannelMessageAck(channelID, lastMessageID, "")
+ _, ackError := session.ChannelMessageAck(channel.ID, lastMessageID, "")
return ackError
}
// UpdateReadBuffered triggers an acknowledgement after a certain amount of
// seconds. If this message is called again during that time, the timer will
// be reset. This avoid unnecessarily many calls to the Discord servers.
-func UpdateReadBuffered(session *discordgo.Session, channelID string, lastMessageID string) {
+func UpdateReadBuffered(session *discordgo.Session, channel *discordgo.Channel, lastMessageID string) {
timerMutex.Lock()
- ackTimer := ackTimers[channelID]
+ ackTimer := ackTimers[channel.ID]
if ackTimer == nil {
newTimer := time.NewTimer(4 * time.Second)
- ackTimers[channelID] = newTimer
+ ackTimers[channel.ID] = newTimer
go func() {
<-newTimer.C
- ackTimers[channelID] = nil
- UpdateRead(session, channelID, lastMessageID)
+ ackTimers[channel.ID] = nil
+ UpdateRead(session, channel, lastMessageID)
}()
} else {
ackTimer.Reset(4 * time.Second)
@@ -97,13 +101,67 @@ func UpdateReadBuffered(session *discordgo.Session, channelID string, lastMessag
timerMutex.Unlock()
}
+// IsGuildMuted returns whether the user muted the given guild.
+func IsGuildMuted(guildID string) bool {
+ for _, settings := range state.UserGuildSettings {
+ if settings.GuildID == guildID {
+ if settings.Muted {
+ return true
+ }
+
+ break
+ }
+ }
+
+ return false
+}
+
+// HasGuildBeenRead returns true if the guild has no unread messages or is
+// muted.
+func HasGuildBeenRead(guildID string) bool {
+ if IsGuildMuted(guildID) {
+ return true
+ }
+
+ realGuild, cacheError := state.Guild(guildID)
+ if cacheError == nil {
+ for _, channel := range realGuild.Channels {
+ if !discordutil.HasReadMessagesPermission(channel.ID, state) {
+ continue
+ }
+
+ if !HasBeenRead(channel, channel.LastMessageID) {
+ return false
+ }
+ }
+ }
+
+ return true
+}
+
// HasBeenRead checks whether the passed channel has an unread Message or not.
-func HasBeenRead(channelID string, lastMessageID string) bool {
+func HasBeenRead(channel *discordgo.Channel, lastMessageID string) bool {
if lastMessageID == "" {
return true
}
- data, present := data[channelID]
+ if channel.GuildID != "" {
+ for _, settings := range state.UserGuildSettings {
+ if settings.GuildID == channel.GuildID {
+ for _, override := range settings.ChannelOverrides {
+ if override.ChannelID == channel.ID {
+ if override.Muted {
+ return true
+ }
+ }
+ }
+
+ break
+ }
+ }
+ }
+
+ data, present := data[channel.ID]
if !present {
return false
}

MODIFY
ui/channeltree.go
ui/channeltree.go
@@ -78,21 +78,24 @@ func (channelTree *ChannelTree) LoadGuild(guildID string) error {
// Top level channel
state := channelTree.state
for _, channel := range channels {
- if channel.Type != discordgo.ChannelTypeGuildText || channel.ParentID != "" || !hasReadMessagesPermission(channel, state) {
+ if channel.Type != discordgo.ChannelTypeGuildText || channel.ParentID != "" ||
+ !discordutil.HasReadMessagesPermission(channel.ID, state) {
continue
}
createTopLevelChannelNodes(channelTree, channel)
}
// Categories
for _, channel := range channels {
- if channel.Type != discordgo.ChannelTypeGuildCategory || channel.ParentID != "" || !hasReadMessagesPermission(channel, state) {
+ if channel.Type != discordgo.ChannelTypeGuildCategory || channel.ParentID != "" ||
+ !discordutil.HasReadMessagesPermission(channel.ID, state) {
continue
}
createChannelCategoryNodes(channelTree, channel)
}
// Second level channel
for _, channel := range channels {
- if channel.Type != discordgo.ChannelTypeGuildText || channel.ParentID == "" || !hasReadMessagesPermission(channel, state) {
+ if channel.Type != discordgo.ChannelTypeGuildText || channel.ParentID == "" ||
+ !discordutil.HasReadMessagesPermission(channel.ID, state) {
continue
}
createSecondLevelChannelNodes(channelTree, channel)
@@ -103,7 +106,7 @@ func (channelTree *ChannelTree) LoadGuild(guildID string) error {
func createTopLevelChannelNodes(channelTree *ChannelTree, channel *discordgo.Channel) {
channelNode := createChannelNode(channel)
- if !readstate.HasBeenRead(channel.ID, channel.LastMessageID) {
+ if !readstate.HasBeenRead(channel, channel.LastMessageID) {
channelTree.channelStates[channelNode] = channelUnread
channelNode.SetColor(tcell.ColorRed)
}
@@ -121,7 +124,7 @@ func createSecondLevelChannelNodes(channelTree *ChannelTree, channel *discordgo.
for _, node := range channelTree.internalTreeView.GetRoot().GetChildren() {
channelID, ok := node.GetReference().(string)
if ok && channelID == channel.ParentID {
- if !readstate.HasBeenRead(channel.ID, channel.LastMessageID) {
+ if !readstate.HasBeenRead(channel, channel.LastMessageID) {
channelTree.channelStates[channelNode] = channelUnread
channelNode.SetColor(tcell.ColorRed)
}
@@ -132,16 +135,6 @@ func createSecondLevelChannelNodes(channelTree *ChannelTree, channel *discordgo.
}
}
-// Checks if the user has permission to view a specific channel.
-func hasReadMessagesPermission(channel *discordgo.Channel, state *discordgo.State) bool {
- userPermissions, err := state.UserChannelPermissions(state.User.ID, channel.ID)
- if err != nil {
- // Unable to access channel permissions.
- return false
- }
- return (userPermissions & discordgo.PermissionReadMessages) > 0
-}
-
func createChannelNode(channel *discordgo.Channel) *tview.TreeNode {
channelNode := tview.NewTreeNode(discordutil.GetChannelNameForTree(channel))
channelNode.SetReference(channel.ID)

MODIFY
ui/privatechats.go
ui/privatechats.go
@@ -112,7 +112,7 @@ func (privateList *PrivateChatList) prependChannel(channel *discordgo.Channel) {
func (privateList *PrivateChatList) addChannel(channel *discordgo.Channel) {
newNode := createPrivateChannelNode(channel)
- if !readstate.HasBeenRead(channel.ID, channel.LastMessageID) {
+ if !readstate.HasBeenRead(channel, channel.LastMessageID) {
privateList.privateChannelStates[newNode] = unread
newNode.SetColor(tcell.ColorRed)
}

MODIFY
ui/window.go
ui/window.go
@@ -797,10 +797,19 @@ func NewWindow(doRestart chan bool, app *tview.Application, session *discordgo.S
if readstate.UpdateReadLocal(event.ChannelID, event.MessageID) {
channel, stateError := s.State.Channel(event.ChannelID)
if stateError == nil && event.MessageID == channel.LastMessageID {
+ if channel.GuildID == "" {
+ window.privateList.MarkChannelAsRead(channel.ID)
+ } else {
if window.selectedGuild != nil && channel.GuildID == window.selectedGuild.ID {
window.channelTree.MarkChannelAsRead(channel.ID)
- } else if channel.GuildID == "" {
- window.privateList.MarkChannelAsRead(channel.ID)
+ } else {
+ for _, guildNode := range window.guildList.GetRoot().GetChildren() {
+ if guildNode.GetReference() == channel.GuildID {
+ window.updateServerReadStatus(channel.GuildID, guildNode, false)
+ break
+ }
+ }
+ }
}
}
}
@@ -945,19 +954,13 @@ func (window *Window) updateServerReadStatus(guildID string, guildNode *tview.Tr
if isSelected {
guildNode.SetColor(tview.Styles.ContrastBackgroundColor)
} else {
- realGuild, cacheError := window.session.State.Guild(guildID)
- if cacheError == nil {
- for _, channel := range realGuild.Channels {
- if !readstate.HasBeenRead(channel.ID, channel.LastMessageID) {
+ if !readstate.HasGuildBeenRead(guildID) {
guildNode.SetColor(tcell.ColorRed)
- return
- }
- }
- }
-
+ } else {
guildNode.SetColor(tview.Styles.PrimaryTextColor)
}
}
+}
func (window *Window) prepareMessage(inputText string) string {
output := codeBlockRegex.ReplaceAllStringFunc(inputText, func(input string) string {
@@ -1185,10 +1188,14 @@ func (window *Window) startMessageHandlerRoutines(input, edit, delete chan *disc
tempMessage := message
window.session.State.MessageAdd(tempMessage)
- if window.selectedChannel != nil && tempMessage.ChannelID == window.selectedChannel.ID {
+ channel, stateError := window.session.State.Channel(tempMessage.ChannelID)
+ if stateError != nil {
+ continue
+ }
+ if window.selectedChannel != nil && tempMessage.ChannelID == window.selectedChannel.ID {
if tempMessage.Author.ID != window.session.State.User.ID {
- readstate.UpdateReadBuffered(window.session, tempMessage.ChannelID, tempMessage.ID)
+ readstate.UpdateReadBuffered(window.session, channel, tempMessage.ID)
}
window.app.QueueUpdateDraw(func() {
@@ -1196,15 +1203,13 @@ func (window *Window) startMessageHandlerRoutines(input, edit, delete chan *disc
})
}
- channel, stateError := window.session.State.Channel(tempMessage.ChannelID)
- if stateError != nil {
- continue
- }
-
if channel.Type == discordgo.ChannelTypeGuildText {
for _, guildNode := range window.guildList.GetRoot().GetChildren() {
- if guildNode.GetReference() != channel.GuildID {
+ if guildNode.GetReference() == channel.GuildID &&
+ (window.selectedGuild == nil || window.selectedGuild.ID != channel.GuildID) {
+ window.app.QueueUpdateDraw(func() {
window.updateServerReadStatus(channel.GuildID, guildNode, false)
+ })
break
}
}
@@ -1237,7 +1242,6 @@ func (window *Window) startMessageHandlerRoutines(input, edit, delete chan *disc
if config.GetConfig().DesktopNotifications {
if !mentionsYou {
- //TODO Check if channel is muted.
if channel.Type == discordgo.ChannelTypeDM || channel.Type == discordgo.ChannelTypeGroupDM {
mentionsYou = true
}
@@ -1793,7 +1797,7 @@ func (window *Window) LoadChannel(channel *discordgo.Channel) error {
}
go func() {
- readstate.UpdateRead(window.session, channel.ID, channel.LastMessageID)
+ readstate.UpdateRead(window.session, channel, channel.LastMessageID)
// Here we make the assumption that the channel we are loading must be part
// of the currently loaded guild, since we don't allow loading a channel of
@@ -1804,9 +1808,8 @@ func (window *Window) LoadChannel(channel *discordgo.Channel) error {
window.selectedGuild = guild
window.app.QueueUpdateDraw(func() {
for _, guildNode := range window.guildList.GetRoot().GetChildren() {
- if guildNode.GetReference() != channel.GuildID {
+ if guildNode.GetReference() == channel.GuildID {
window.selectedGuildNode = guildNode
- window.updateServerReadStatus(window.selectedGuild.ID, window.selectedGuildNode, true)
break
}
}

Guilds and channels readstate now checks the muted state first. Notifications
and the readstate in private channels are still shown.