discord.go 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. package main
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "sort"
  6. "github.com/ayntgl/discordgo"
  7. "github.com/gen2brain/beeep"
  8. "github.com/rivo/tview"
  9. )
  10. var (
  11. session *discordgo.Session
  12. selectedChannel *discordgo.Channel
  13. selectedMessage *discordgo.Message
  14. )
  15. func newSession() *discordgo.Session {
  16. s, err := discordgo.New()
  17. if err != nil {
  18. panic(err)
  19. }
  20. s.UserAgent = conf.UserAgent
  21. s.Identify.Compress = false
  22. s.Identify.Intents = 0
  23. s.Identify.LargeThreshold = 0
  24. s.Identify.Properties.Device = ""
  25. s.Identify.Properties.Browser = "Chrome"
  26. s.Identify.Properties.OS = "Linux"
  27. s.AddHandlerOnce(onSessionReady)
  28. s.AddHandler(onSessionMessageCreate)
  29. return s
  30. }
  31. func onSessionReady(_ *discordgo.Session, r *discordgo.Ready) {
  32. dmNode := tview.NewTreeNode("Direct Messages").
  33. Collapse()
  34. n := channelsTree.GetRoot()
  35. n.AddChild(dmNode)
  36. sort.Slice(r.PrivateChannels, func(i, j int) bool {
  37. return r.PrivateChannels[i].LastMessageID > r.PrivateChannels[j].LastMessageID
  38. })
  39. for _, c := range r.PrivateChannels {
  40. var tag string
  41. if isUnread(c) {
  42. tag = "[::b]"
  43. } else {
  44. tag = "[::d]"
  45. }
  46. cn := tview.NewTreeNode(tag + generateChannelRepr(c) + "[::-]").
  47. SetReference(c.ID)
  48. dmNode.AddChild(cn)
  49. }
  50. sort.Slice(r.Guilds, func(a, b int) bool {
  51. found := false
  52. for _, gID := range r.Settings.GuildPositions {
  53. if found {
  54. if gID == r.Guilds[b].ID {
  55. return true
  56. }
  57. } else {
  58. if gID == r.Guilds[a].ID {
  59. found = true
  60. }
  61. }
  62. }
  63. return false
  64. })
  65. for _, g := range r.Guilds {
  66. gn := tview.NewTreeNode(g.Name).Collapse()
  67. n.AddChild(gn)
  68. cs := g.Channels
  69. sort.Slice(cs, func(i, j int) bool {
  70. return cs[i].Position < cs[j].Position
  71. })
  72. // Top-level channels
  73. createTopLevelChannelsTreeNodes(gn, cs)
  74. // Category channels
  75. createCategoryChannelsTreeNodes(gn, cs)
  76. // Second-level channels
  77. createSecondLevelChannelsTreeNodes(cs)
  78. }
  79. channelsTree.SetCurrentNode(n)
  80. }
  81. func isUnread(c *discordgo.Channel) bool {
  82. if c.LastMessageID == "" {
  83. return false
  84. }
  85. for _, rs := range session.State.ReadState {
  86. if c.ID == rs.ID {
  87. return c.LastMessageID != rs.LastMessageID
  88. }
  89. }
  90. return false
  91. }
  92. func onSessionMessageCreate(_ *discordgo.Session, m *discordgo.MessageCreate) {
  93. c, err := session.State.Channel(m.ChannelID)
  94. if err != nil {
  95. return
  96. }
  97. if selectedChannel == nil || selectedChannel.ID != m.ChannelID {
  98. if conf.Notifications {
  99. for _, u := range m.Mentions {
  100. if u.ID == session.State.User.ID {
  101. g, err := session.State.Guild(m.GuildID)
  102. if err != nil {
  103. return
  104. }
  105. go beeep.Alert(fmt.Sprintf("%s (#%s)", g.Name, c.Name), m.ContentWithMentionsReplaced(), "")
  106. return
  107. }
  108. }
  109. }
  110. cn := getTreeNodeByReference(c.ID)
  111. if cn == nil {
  112. return
  113. }
  114. cn.SetText("[::b]" + generateChannelRepr(c) + "[::-]")
  115. app.Draw()
  116. } else {
  117. selectedChannel.Messages = append(selectedChannel.Messages, m.Message)
  118. renderMessage(m.Message)
  119. }
  120. }
  121. type loginResponse struct {
  122. MFA bool `json:"mfa"`
  123. SMS bool `json:"sms"`
  124. Ticket string `json:"ticket"`
  125. Token string `json:"token"`
  126. }
  127. func login(email, password string) (*loginResponse, error) {
  128. data := struct {
  129. Email string `json:"email"`
  130. Password string `json:"password"`
  131. }{email, password}
  132. resp, err := session.RequestWithBucketID(
  133. "POST",
  134. discordgo.EndpointLogin,
  135. data,
  136. discordgo.EndpointLogin,
  137. )
  138. if err != nil {
  139. return nil, err
  140. }
  141. var lr loginResponse
  142. err = json.Unmarshal(resp, &lr)
  143. if err != nil {
  144. return nil, err
  145. }
  146. return &lr, nil
  147. }
  148. func totp(code, ticket string) (*loginResponse, error) {
  149. data := struct {
  150. Code string `json:"code"`
  151. Ticket string `json:"ticket"`
  152. }{code, ticket}
  153. e := discordgo.EndpointAuth + "mfa/totp"
  154. resp, err := session.RequestWithBucketID("POST", e, data, e)
  155. if err != nil {
  156. return nil, err
  157. }
  158. var lr loginResponse
  159. err = json.Unmarshal(resp, &lr)
  160. if err != nil {
  161. return nil, err
  162. }
  163. return &lr, nil
  164. }