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 }