* 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
196 lines
3.9 KiB
Go
196 lines
3.9 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"sort"
|
|
|
|
"github.com/ayntgl/discordgo"
|
|
"github.com/gen2brain/beeep"
|
|
"github.com/rivo/tview"
|
|
)
|
|
|
|
var (
|
|
session *discordgo.Session
|
|
selectedChannel *discordgo.Channel
|
|
selectedMessage *discordgo.Message
|
|
)
|
|
|
|
func newSession() *discordgo.Session {
|
|
s, err := discordgo.New()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
s.UserAgent = conf.UserAgent
|
|
s.Identify.Compress = false
|
|
s.Identify.Intents = 0
|
|
s.Identify.LargeThreshold = 0
|
|
s.Identify.Properties.Device = ""
|
|
s.Identify.Properties.Browser = "Chrome"
|
|
s.Identify.Properties.OS = "Linux"
|
|
|
|
s.AddHandlerOnce(onSessionReady)
|
|
s.AddHandler(onSessionMessageCreate)
|
|
|
|
return s
|
|
}
|
|
|
|
func onSessionReady(_ *discordgo.Session, r *discordgo.Ready) {
|
|
dmNode := tview.NewTreeNode("Direct Messages").
|
|
Collapse()
|
|
|
|
n := channelsTree.GetRoot()
|
|
n.AddChild(dmNode)
|
|
|
|
sort.Slice(r.PrivateChannels, func(i, j int) bool {
|
|
return r.PrivateChannels[i].LastMessageID > r.PrivateChannels[j].LastMessageID
|
|
})
|
|
|
|
for _, c := range r.PrivateChannels {
|
|
var tag string
|
|
if isUnread(c) {
|
|
tag = "[::b]"
|
|
} else {
|
|
tag = "[::d]"
|
|
}
|
|
|
|
cn := tview.NewTreeNode(tag + generateChannelRepr(c) + "[::-]").
|
|
SetReference(c.ID)
|
|
dmNode.AddChild(cn)
|
|
}
|
|
|
|
sort.Slice(r.Guilds, func(a, b int) bool {
|
|
found := false
|
|
for _, gID := range r.Settings.GuildPositions {
|
|
if found {
|
|
if gID == r.Guilds[b].ID {
|
|
return true
|
|
}
|
|
} else {
|
|
if gID == r.Guilds[a].ID {
|
|
found = true
|
|
}
|
|
}
|
|
}
|
|
|
|
return false
|
|
})
|
|
|
|
for _, g := range r.Guilds {
|
|
gn := tview.NewTreeNode(g.Name).Collapse()
|
|
n.AddChild(gn)
|
|
|
|
cs := g.Channels
|
|
sort.Slice(cs, func(i, j int) bool {
|
|
return cs[i].Position < cs[j].Position
|
|
})
|
|
|
|
// Top-level channels
|
|
createTopLevelChannelsTreeNodes(gn, cs)
|
|
// Category channels
|
|
createCategoryChannelsTreeNodes(gn, cs)
|
|
// Second-level channels
|
|
createSecondLevelChannelsTreeNodes(cs)
|
|
}
|
|
|
|
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) {
|
|
c, err := session.State.Channel(m.ChannelID)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
if selectedChannel == nil || selectedChannel.ID != m.ChannelID {
|
|
if conf.Notifications {
|
|
for _, u := range m.Mentions {
|
|
if u.ID == session.State.User.ID {
|
|
g, err := session.State.Guild(m.GuildID)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
go beeep.Alert(fmt.Sprintf("%s (#%s)", g.Name, c.Name), m.ContentWithMentionsReplaced(), "")
|
|
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)
|
|
}
|
|
}
|
|
|
|
type loginResponse struct {
|
|
MFA bool `json:"mfa"`
|
|
SMS bool `json:"sms"`
|
|
Ticket string `json:"ticket"`
|
|
Token string `json:"token"`
|
|
}
|
|
|
|
func login(email, password string) (*loginResponse, error) {
|
|
data := struct {
|
|
Email string `json:"email"`
|
|
Password string `json:"password"`
|
|
}{email, password}
|
|
resp, err := session.RequestWithBucketID(
|
|
"POST",
|
|
discordgo.EndpointLogin,
|
|
data,
|
|
discordgo.EndpointLogin,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var lr loginResponse
|
|
err = json.Unmarshal(resp, &lr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &lr, nil
|
|
}
|
|
|
|
func totp(code, ticket string) (*loginResponse, error) {
|
|
data := struct {
|
|
Code string `json:"code"`
|
|
Ticket string `json:"ticket"`
|
|
}{code, ticket}
|
|
e := discordgo.EndpointAuth + "mfa/totp"
|
|
resp, err := session.RequestWithBucketID("POST", e, data, e)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var lr loginResponse
|
|
err = json.Unmarshal(resp, &lr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &lr, nil
|
|
}
|