discordo.go 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. package main
  2. import (
  3. "sort"
  4. "strings"
  5. "github.com/atotto/clipboard"
  6. "github.com/gdamore/tcell/v2"
  7. "github.com/rigormorrtiss/discordgo"
  8. "github.com/rigormorrtiss/discordo/ui"
  9. "github.com/rigormorrtiss/discordo/util"
  10. "github.com/rivo/tview"
  11. )
  12. var (
  13. app *tview.Application
  14. loginForm *tview.Form
  15. guildsTreeView *tview.TreeView
  16. channelsTreeView *tview.TreeView
  17. messagesTextView *tview.TextView
  18. messageInputField *tview.InputField
  19. mainFlex *tview.Flex
  20. conf *util.Config
  21. session *discordgo.Session
  22. channel *discordgo.Channel
  23. )
  24. func main() {
  25. conf = util.NewConfig()
  26. tview.Styles.PrimitiveBackgroundColor = tcell.GetColor(conf.Theme.Background)
  27. if !conf.Theme.Borders {
  28. tview.Borders = struct {
  29. Horizontal rune
  30. Vertical rune
  31. TopLeft rune
  32. TopRight rune
  33. BottomLeft rune
  34. BottomRight rune
  35. LeftT rune
  36. RightT rune
  37. TopT rune
  38. BottomT rune
  39. Cross rune
  40. HorizontalFocus rune
  41. VerticalFocus rune
  42. TopLeftFocus rune
  43. TopRightFocus rune
  44. BottomLeftFocus rune
  45. BottomRightFocus rune
  46. }{}
  47. }
  48. app = tview.NewApplication().
  49. EnableMouse(conf.Mouse).
  50. SetInputCapture(onAppInputCapture)
  51. guildsTreeView = ui.NewGuildsTreeView(onGuildsTreeViewSelected)
  52. channelsTreeView = ui.NewChannelsTreeView(onChannelsTreeViewSelected)
  53. messagesTextView = ui.NewMessagesTextView(app)
  54. messageInputField = ui.NewMessageInputField(onMessageInputFieldInputCapture, conf.Theme)
  55. mainFlex = ui.NewMainFlex(guildsTreeView, channelsTreeView, messagesTextView, messageInputField)
  56. token := conf.Token
  57. if t := util.GetPassword("token"); t != "" {
  58. token = t
  59. }
  60. if token != "" {
  61. app.
  62. SetRoot(mainFlex, true).
  63. SetFocus(guildsTreeView)
  64. session = newSession()
  65. session.Token = token
  66. session.Identify.Token = token
  67. if err := session.Open(); err != nil {
  68. panic(err)
  69. }
  70. } else {
  71. loginForm = ui.NewLoginForm(onLoginFormLoginButtonSelected)
  72. app.SetRoot(loginForm, true)
  73. }
  74. if err := app.Run(); err != nil {
  75. panic(err)
  76. }
  77. }
  78. func onAppInputCapture(e *tcell.EventKey) *tcell.EventKey {
  79. switch e.Name() {
  80. case "Alt+Rune[1]":
  81. app.SetFocus(guildsTreeView)
  82. case "Alt+Rune[2]":
  83. app.SetFocus(channelsTreeView)
  84. case "Alt+Rune[3]":
  85. app.SetFocus(messagesTextView)
  86. case "Alt+Rune[4]":
  87. app.SetFocus(messageInputField)
  88. }
  89. return e
  90. }
  91. func onMessageInputFieldInputCapture(e *tcell.EventKey) *tcell.EventKey {
  92. switch e.Key() {
  93. case tcell.KeyEnter:
  94. t := strings.TrimSpace(messageInputField.GetText())
  95. if t == "" {
  96. return nil
  97. }
  98. session.ChannelMessageSend(channel.ID, t)
  99. messageInputField.SetText("")
  100. case tcell.KeyCtrlV:
  101. text, _ := clipboard.ReadAll()
  102. text = messageInputField.GetText() + text
  103. messageInputField.SetText(text)
  104. }
  105. return e
  106. }
  107. func newSession() *discordgo.Session {
  108. s, err := discordgo.New()
  109. if err != nil {
  110. panic(err)
  111. }
  112. s.UserAgent = "" +
  113. "Mozilla/5.0 (X11; Linux x86_64) " +
  114. "AppleWebKit/537.36 (KHTML, like Gecko) " +
  115. "Chrome/92.0.4515.131 Safari/537.36"
  116. s.Identify.Compress = false
  117. s.Identify.Intents = 0
  118. s.Identify.LargeThreshold = 0
  119. s.Identify.Properties.Device = ""
  120. s.Identify.Properties.Browser = "Chrome"
  121. s.Identify.Properties.OS = "Linux"
  122. s.AddHandlerOnce(onSessionReady)
  123. s.AddHandler(onSessionMessageCreate)
  124. return s
  125. }
  126. func onSessionReady(_ *discordgo.Session, r *discordgo.Ready) {
  127. rootN := guildsTreeView.GetRoot()
  128. for _, g := range r.Guilds {
  129. gn := tview.NewTreeNode(g.Name).
  130. SetReference(g.ID)
  131. rootN.AddChild(gn)
  132. }
  133. guildsTreeView.SetCurrentNode(rootN)
  134. }
  135. func onSessionMessageCreate(_ *discordgo.Session, m *discordgo.MessageCreate) {
  136. if channel != nil && channel.ID == m.ChannelID {
  137. util.WriteMessage(messagesTextView, m.Message, session.State.Ready.User.ID)
  138. }
  139. }
  140. func onGuildsTreeViewSelected(gn *tview.TreeNode) {
  141. app.SetFocus(channelsTreeView)
  142. messagesTextView.SetTitle("")
  143. messagesTextView.Clear()
  144. gID := gn.GetReference().(string)
  145. g, _ := session.State.Guild(gID)
  146. cs := g.Channels
  147. sort.Slice(cs, func(i, j int) bool {
  148. return cs[i].Position < cs[j].Position
  149. })
  150. rootN := channelsTreeView.GetRoot()
  151. rootN.ClearChildren()
  152. // Top-level channels
  153. ui.CreateTopLevelTreeNodes(rootN, cs)
  154. // Category channels
  155. CategoryLoop:
  156. for _, c := range cs {
  157. if c.Type == discordgo.ChannelTypeGuildCategory {
  158. for _, child := range cs {
  159. if child.ParentID == c.ID {
  160. cn := tview.NewTreeNode(c.Name).
  161. SetReference(c.ID)
  162. rootN.AddChild(cn)
  163. continue CategoryLoop
  164. }
  165. }
  166. cn := tview.NewTreeNode(c.Name).
  167. SetReference(c.ID)
  168. rootN.AddChild(cn)
  169. }
  170. }
  171. // Second-level channels
  172. ui.CreateSecondLevelTreeNodes(channelsTreeView, rootN, cs)
  173. channelsTreeView.SetCurrentNode(rootN)
  174. }
  175. func onChannelsTreeViewSelected(n *tview.TreeNode) {
  176. cID := n.GetReference().(string)
  177. c, _ := session.State.Channel(cID)
  178. switch c.Type {
  179. case discordgo.ChannelTypeGuildCategory:
  180. n.SetExpanded(!n.IsExpanded())
  181. case discordgo.ChannelTypeGuildText, discordgo.ChannelTypeGuildNews:
  182. if len(n.GetChildren()) == 0 {
  183. channel = c
  184. app.SetFocus(messageInputField)
  185. messagesTextView.Clear()
  186. title := "#" + c.Name
  187. if c.Topic != "" {
  188. title += " - " + c.Topic
  189. }
  190. messagesTextView.SetTitle(title)
  191. go writeMessages(c.ID)
  192. } else {
  193. n.SetExpanded(!n.IsExpanded())
  194. }
  195. }
  196. }
  197. func writeMessages(cID string) {
  198. msgs, _ := session.ChannelMessages(cID, conf.GetMessagesLimit, "", "", "")
  199. for i := len(msgs) - 1; i >= 0; i-- {
  200. util.WriteMessage(messagesTextView, msgs[i], session.State.Ready.User.ID)
  201. }
  202. }
  203. func onLoginFormLoginButtonSelected() {
  204. email := loginForm.GetFormItem(0).(*tview.InputField).GetText()
  205. password := loginForm.GetFormItem(1).(*tview.InputField).GetText()
  206. if email == "" || password == "" {
  207. return
  208. }
  209. session = newSession()
  210. // Try to login without TOTP
  211. lr, err := util.Login(session, email, password)
  212. if err != nil {
  213. panic(err)
  214. }
  215. if lr.Token != "" && !lr.MFA {
  216. app.
  217. SetRoot(mainFlex, true).
  218. SetFocus(guildsTreeView)
  219. session.Token = lr.Token
  220. session.Identify.Token = lr.Token
  221. if err = session.Open(); err != nil {
  222. panic(err)
  223. }
  224. go util.SetPassword("token", lr.Token)
  225. } else if lr.MFA {
  226. loginForm = ui.NewMfaLoginForm(func() {
  227. code := loginForm.GetFormItem(0).(*tview.InputField).GetText()
  228. if code == "" {
  229. return
  230. }
  231. lr, err = util.TOTP(session, code, lr.Ticket)
  232. if err != nil {
  233. panic(err)
  234. }
  235. app.
  236. SetRoot(mainFlex, true).
  237. SetFocus(guildsTreeView)
  238. session.Token = lr.Token
  239. session.Identify.Token = lr.Token
  240. if err = session.Open(); err != nil {
  241. panic(err)
  242. }
  243. go util.SetPassword("token", lr.Token)
  244. })
  245. app.SetRoot(loginForm, true)
  246. }
  247. }