ui.go 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. package main
  2. import (
  3. "strings"
  4. "github.com/atotto/clipboard"
  5. "github.com/ayntgl/discordgo"
  6. "github.com/ayntgl/discordo/util"
  7. "github.com/gdamore/tcell/v2"
  8. "github.com/rivo/tview"
  9. )
  10. var (
  11. selectedChannel *discordgo.Channel
  12. selectedMessage int = -1
  13. )
  14. func newApp() *tview.Application {
  15. a := tview.NewApplication()
  16. a.
  17. EnableMouse(conf.Mouse).
  18. SetInputCapture(onAppInputCapture)
  19. return a
  20. }
  21. func onAppInputCapture(e *tcell.EventKey) *tcell.EventKey {
  22. switch e.Name() {
  23. case conf.Keybindings.FocusChannelsTree:
  24. app.SetFocus(channelsTree)
  25. return nil
  26. case conf.Keybindings.FocusMessagesView:
  27. app.SetFocus(messagesView)
  28. return nil
  29. case conf.Keybindings.FocusMessageInputField:
  30. app.SetFocus(messageInputField)
  31. return nil
  32. }
  33. return e
  34. }
  35. func newChannelsTree() *tview.TreeView {
  36. treeView := tview.NewTreeView()
  37. treeView.
  38. SetSelectedFunc(onChannelsTreeSelected).
  39. SetTopLevel(1).
  40. SetRoot(tview.NewTreeNode("")).
  41. SetBorder(true).
  42. SetBorderPadding(0, 0, 1, 0)
  43. return treeView
  44. }
  45. func onChannelsTreeSelected(n *tview.TreeNode) {
  46. selectedChannel = nil
  47. selectedMessage = 0
  48. messagesView.
  49. Clear().
  50. SetTitle("")
  51. messageInputField.SetText("")
  52. // Unhighlight the already-highlighted regions.
  53. messagesView.Highlight()
  54. ref := n.GetReference()
  55. // If the node's reference is nil, the selected node is a guild or direct messages node; expand or collapse accordingly.
  56. if ref == nil {
  57. n.SetExpanded(!n.IsExpanded())
  58. } else {
  59. c, err := session.State.Channel(ref.(string))
  60. if err != nil {
  61. return
  62. }
  63. selectedChannel = c
  64. app.SetFocus(messageInputField)
  65. switch c.Type {
  66. case discordgo.ChannelTypeGuildText, discordgo.ChannelTypeGuildNews:
  67. title := util.ChannelToString(c)
  68. if c.Topic != "" {
  69. title += " - " + c.Topic
  70. }
  71. messagesView.SetTitle(title)
  72. case discordgo.ChannelTypeDM, discordgo.ChannelTypeGroupDM:
  73. messagesView.SetTitle(util.ChannelToString(c))
  74. }
  75. if strings.HasPrefix(n.GetText(), "[::b]") {
  76. n.SetText("[::d]" + util.ChannelToString(c) + "[::-]")
  77. }
  78. go func() {
  79. ms, err := session.ChannelMessages(c.ID, conf.GetMessagesLimit, "", "", "")
  80. if err != nil {
  81. return
  82. }
  83. for i := len(ms) - 1; i >= 0; i-- {
  84. selectedChannel.Messages = append(selectedChannel.Messages, ms[i])
  85. messagesView.Write(buildMessage(ms[i]))
  86. }
  87. // Scroll to the end of the text after the messages have been written to the TextView.
  88. messagesView.ScrollToEnd()
  89. if len(ms) != 0 && util.ChannelIsUnread(session.State, c) {
  90. session.ChannelMessageAck(c.ID, c.LastMessageID, "")
  91. }
  92. }()
  93. }
  94. }
  95. func newMessagesView() *tview.TextView {
  96. textView := tview.NewTextView()
  97. textView.
  98. SetRegions(true).
  99. SetDynamicColors(true).
  100. SetWordWrap(true).
  101. SetChangedFunc(func() {
  102. app.Draw()
  103. }).
  104. SetInputCapture(onMessagesViewInputCapture).
  105. SetBorder(true).
  106. SetBorderPadding(0, 0, 1, 0).
  107. SetTitleAlign(tview.AlignLeft)
  108. return textView
  109. }
  110. func onMessagesViewInputCapture(e *tcell.EventKey) *tcell.EventKey {
  111. if selectedChannel == nil {
  112. return nil
  113. }
  114. ms := selectedChannel.Messages
  115. if len(ms) == 0 {
  116. return nil
  117. }
  118. switch e.Name() {
  119. case conf.Keybindings.SelectPreviousMessage:
  120. if len(messagesView.GetHighlights()) == 0 {
  121. selectedMessage = len(ms) - 1
  122. } else {
  123. selectedMessage--
  124. if selectedMessage < 0 {
  125. selectedMessage = 0
  126. }
  127. }
  128. messagesView.
  129. Highlight(ms[selectedMessage].ID).
  130. ScrollToHighlight()
  131. return nil
  132. case conf.Keybindings.SelectNextMessage:
  133. if len(messagesView.GetHighlights()) == 0 {
  134. selectedMessage = len(ms) - 1
  135. } else {
  136. selectedMessage++
  137. if selectedMessage >= len(ms) {
  138. selectedMessage = len(ms) - 1
  139. }
  140. }
  141. messagesView.
  142. Highlight(ms[selectedMessage].ID).
  143. ScrollToHighlight()
  144. return nil
  145. case conf.Keybindings.SelectFirstMessage:
  146. selectedMessage = 0
  147. messagesView.
  148. Highlight(ms[selectedMessage].ID).
  149. ScrollToHighlight()
  150. return nil
  151. case conf.Keybindings.SelectLastMessage:
  152. selectedMessage = len(ms) - 1
  153. messagesView.
  154. Highlight(ms[selectedMessage].ID).
  155. ScrollToHighlight()
  156. return nil
  157. case conf.Keybindings.SelectMessageReference:
  158. hs := messagesView.GetHighlights()
  159. if len(hs) == 0 {
  160. return nil
  161. }
  162. _, m := util.FindMessageByID(selectedChannel.Messages, hs[0])
  163. if m.ReferencedMessage != nil {
  164. selectedMessage, _ = util.FindMessageByID(selectedChannel.Messages, m.ReferencedMessage.ID)
  165. messagesView.
  166. Highlight(m.ReferencedMessage.ID).
  167. ScrollToHighlight()
  168. }
  169. return nil
  170. case conf.Keybindings.ReplySelectedMessage:
  171. hs := messagesView.GetHighlights()
  172. if len(hs) == 0 {
  173. return nil
  174. }
  175. _, m := util.FindMessageByID(selectedChannel.Messages, hs[0])
  176. messageInputField.SetTitle("Replying to " + m.Author.String())
  177. app.SetFocus(messageInputField)
  178. return nil
  179. case conf.Keybindings.MentionReplySelectedMessage:
  180. hs := messagesView.GetHighlights()
  181. if len(hs) == 0 {
  182. return nil
  183. }
  184. _, m := util.FindMessageByID(selectedChannel.Messages, hs[0])
  185. messageInputField.SetTitle("[@] Replying to " + m.Author.String())
  186. app.SetFocus(messageInputField)
  187. return nil
  188. case conf.Keybindings.CopySelectedMessage:
  189. hs := messagesView.GetHighlights()
  190. if len(hs) == 0 {
  191. return nil
  192. }
  193. _, m := util.FindMessageByID(selectedChannel.Messages, hs[0])
  194. err := clipboard.WriteAll(m.Content)
  195. if err != nil {
  196. return nil
  197. }
  198. }
  199. return e
  200. }
  201. func newMessageInputField() *tview.InputField {
  202. inputField := tview.NewInputField()
  203. inputField.
  204. SetPlaceholder("Message...").
  205. SetPlaceholderTextColor(tcell.ColorWhite).
  206. SetFieldBackgroundColor(tview.Styles.PrimitiveBackgroundColor).
  207. SetInputCapture(onMessageInputFieldInputCapture).
  208. SetBorder(true).
  209. SetBorderPadding(0, 0, 1, 0).
  210. SetTitleAlign(tview.AlignLeft)
  211. return inputField
  212. }
  213. func onMessageInputFieldInputCapture(e *tcell.EventKey) *tcell.EventKey {
  214. switch e.Key() {
  215. case tcell.KeyEnter:
  216. if selectedChannel == nil {
  217. return nil
  218. }
  219. t := strings.TrimSpace(messageInputField.GetText())
  220. if t == "" {
  221. return nil
  222. }
  223. if len(messagesView.GetHighlights()) != 0 {
  224. m := selectedChannel.Messages[selectedMessage]
  225. d := &discordgo.MessageSend{
  226. Content: t,
  227. Reference: m.Reference(),
  228. AllowedMentions: &discordgo.MessageAllowedMentions{RepliedUser: false},
  229. }
  230. if strings.HasPrefix(messageInputField.GetTitle(), "[@]") {
  231. d.AllowedMentions.RepliedUser = true
  232. } else {
  233. d.AllowedMentions.RepliedUser = false
  234. }
  235. go session.ChannelMessageSendComplex(m.ChannelID, d)
  236. messageInputField.SetTitle("")
  237. } else {
  238. go session.ChannelMessageSend(selectedChannel.ID, t)
  239. }
  240. messageInputField.SetText("")
  241. return nil
  242. case tcell.KeyCtrlV:
  243. text, _ := clipboard.ReadAll()
  244. text = messageInputField.GetText() + text
  245. messageInputField.SetText(text)
  246. return nil
  247. case tcell.KeyEscape:
  248. messageInputField.SetText("")
  249. messageInputField.SetTitle("")
  250. selectedMessage = -1
  251. messagesView.Highlight()
  252. return nil
  253. }
  254. return e
  255. }
  256. func newLoginForm(onLoginFormLoginButtonSelected func(), mfa bool) *tview.Form {
  257. w := tview.NewForm()
  258. w.
  259. AddButton("Login", onLoginFormLoginButtonSelected).
  260. SetButtonsAlign(tview.AlignCenter).
  261. SetBorder(true).
  262. SetBorderPadding(0, 0, 1, 0)
  263. if mfa {
  264. w.AddPasswordField("Code", "", 0, 0, nil)
  265. } else {
  266. w.
  267. AddInputField("Email", "", 0, nil, nil).
  268. AddPasswordField("Password", "", 0, 0, nil)
  269. }
  270. return w
  271. }