ui.go 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. package main
  2. import (
  3. "sort"
  4. "strings"
  5. "github.com/atotto/clipboard"
  6. "github.com/ayntgl/discordgo"
  7. "github.com/ayntgl/discordo/util"
  8. "github.com/gdamore/tcell/v2"
  9. "github.com/rivo/tview"
  10. )
  11. func onAppInputCapture(e *tcell.EventKey) *tcell.EventKey {
  12. if util.HasKeybinding(conf.Keybindings.FocusChannelsTree, e.Name()) {
  13. app.SetFocus(channelsTreeView)
  14. return nil
  15. } else if util.HasKeybinding(conf.Keybindings.FocusMessagesView, e.Name()) {
  16. app.SetFocus(messagesTextView)
  17. return nil
  18. } else if util.HasKeybinding(conf.Keybindings.FocusMessageInputField, e.Name()) {
  19. app.SetFocus(messageInputField)
  20. return nil
  21. }
  22. return e
  23. }
  24. func onChannelsTreeSelected(n *tview.TreeNode) {
  25. selectedChannel = nil
  26. selectedMessage = 0
  27. messagesTextView.
  28. Clear().
  29. SetTitle("")
  30. messageInputField.SetText("")
  31. // Unhighlight the already-highlighted regions.
  32. messagesTextView.Highlight()
  33. id := n.GetReference()
  34. switch n.GetLevel() {
  35. case 1: // Guilds or Direct Messages
  36. if len(n.GetChildren()) == 0 {
  37. // If the reference of the selected `*TreeNode` is `nil`, it is the direct messages `*TreeNode`.
  38. if id == nil {
  39. cs := session.State.PrivateChannels
  40. sort.Slice(cs, func(i, j int) bool {
  41. return cs[i].LastMessageID > cs[j].LastMessageID
  42. })
  43. for _, c := range cs {
  44. tag := "[::d]"
  45. if util.ChannelIsUnread(session.State, c) {
  46. tag = "[::b]"
  47. }
  48. cn := tview.NewTreeNode(tag + util.ChannelToString(c) + "[::-]").
  49. SetReference(c.ID).
  50. Collapse()
  51. n.AddChild(cn)
  52. }
  53. } else {
  54. g, err := session.State.Guild(id.(string))
  55. if err != nil {
  56. return
  57. }
  58. sort.Slice(g.Channels, func(i, j int) bool {
  59. return g.Channels[i].Position < g.Channels[j].Position
  60. })
  61. // Top-level channels
  62. createTopLevelChannelsNodes(channelsTreeView, session.State, n, g.Channels)
  63. // Category channels
  64. createCategoryChannelsNodes(channelsTreeView, session.State, n, g.Channels)
  65. // Second-level channels
  66. createSecondLevelChannelsNodes(channelsTreeView, session.State, g.Channels)
  67. }
  68. }
  69. n.SetExpanded(!n.IsExpanded())
  70. default: // Channels
  71. c, err := session.State.Channel(id.(string))
  72. if err != nil {
  73. return
  74. }
  75. selectedChannel = c
  76. app.SetFocus(messageInputField)
  77. switch c.Type {
  78. case discordgo.ChannelTypeGuildText, discordgo.ChannelTypeGuildNews:
  79. title := util.ChannelToString(c)
  80. if c.Topic != "" {
  81. title += " - " + c.Topic
  82. }
  83. messagesTextView.SetTitle(title)
  84. case discordgo.ChannelTypeDM, discordgo.ChannelTypeGroupDM:
  85. messagesTextView.SetTitle(util.ChannelToString(c))
  86. }
  87. if strings.HasPrefix(n.GetText(), "[::b]") {
  88. n.SetText("[::d]" + util.ChannelToString(c) + "[::-]")
  89. }
  90. go func() {
  91. ms, err := session.ChannelMessages(c.ID, conf.GetMessagesLimit, "", "", "")
  92. if err != nil {
  93. return
  94. }
  95. for i := len(ms) - 1; i >= 0; i-- {
  96. selectedChannel.Messages = append(selectedChannel.Messages, ms[i])
  97. messagesTextView.Write(buildMessage(ms[i]))
  98. }
  99. // Scroll to the end of the text after the messages have been written to the TextView.
  100. messagesTextView.ScrollToEnd()
  101. if len(ms) != 0 && util.ChannelIsUnread(session.State, c) {
  102. session.ChannelMessageAck(c.ID, c.LastMessageID, "")
  103. }
  104. }()
  105. }
  106. }
  107. // createTopLevelChannelsNodes builds and creates `*tview.TreeNode`s for top-level (channels that have an empty parent ID and of type GUILD_TEXT, GUILD_NEWS) channels. If the client user does not have the VIEW_CHANNEL permission for a channel, the channel is excluded from the parent.
  108. func createTopLevelChannelsNodes(treeView *tview.TreeView, s *discordgo.State, n *tview.TreeNode, cs []*discordgo.Channel) {
  109. for _, c := range cs {
  110. if (c.Type == discordgo.ChannelTypeGuildText || c.Type == discordgo.ChannelTypeGuildNews) &&
  111. (c.ParentID == "") {
  112. if !util.HasPermission(s, c.ID, discordgo.PermissionViewChannel) {
  113. continue
  114. }
  115. n.AddChild(util.CreateChannelNode(s, c))
  116. continue
  117. }
  118. }
  119. }
  120. // createCategoryChannelsNodes builds and creates `*tview.TreeNode`s for category (type: GUILD_CATEGORY) channels. If the client user does not have the VIEW_CHANNEL permission for a channel, the channel is excluded from the parent.
  121. func createCategoryChannelsNodes(treeView *tview.TreeView, s *discordgo.State, n *tview.TreeNode, cs []*discordgo.Channel) {
  122. CategoryLoop:
  123. for _, c := range cs {
  124. if c.Type == discordgo.ChannelTypeGuildCategory {
  125. if !util.HasPermission(s, c.ID, discordgo.PermissionViewChannel) {
  126. continue
  127. }
  128. for _, child := range cs {
  129. if child.ParentID == c.ID {
  130. n.AddChild(util.CreateChannelNode(s, c))
  131. continue CategoryLoop
  132. }
  133. }
  134. n.AddChild(util.CreateChannelNode(s, c))
  135. }
  136. }
  137. }
  138. // createSecondLevelChannelsNodes builds and creates `*tview.TreeNode`s for second-level (channels that have a non-empty parent ID and of type GUILD_TEXT, GUILD_NEWS) channels. If the client user does not have the VIEW_CHANNEL permission for a channel, the channel is excluded from the parent.
  139. func createSecondLevelChannelsNodes(treeView *tview.TreeView, s *discordgo.State, cs []*discordgo.Channel) {
  140. for _, c := range cs {
  141. if (c.Type == discordgo.ChannelTypeGuildText || c.Type == discordgo.ChannelTypeGuildNews) &&
  142. (c.ParentID != "") {
  143. if !util.HasPermission(s, c.ID, discordgo.PermissionViewChannel) {
  144. continue
  145. }
  146. pn := util.GetNodeByReference(treeView, c.ParentID)
  147. if pn != nil {
  148. pn.AddChild(util.CreateChannelNode(s, c))
  149. }
  150. }
  151. }
  152. }
  153. func onMessagesViewInputCapture(e *tcell.EventKey) *tcell.EventKey {
  154. if selectedChannel == nil {
  155. return nil
  156. }
  157. ms := selectedChannel.Messages
  158. if len(ms) == 0 {
  159. return nil
  160. }
  161. if util.HasKeybinding(conf.Keybindings.SelectPreviousMessage, e.Name()) {
  162. if len(messagesTextView.GetHighlights()) == 0 {
  163. selectedMessage = len(ms) - 1
  164. } else {
  165. selectedMessage--
  166. if selectedMessage < 0 {
  167. selectedMessage = 0
  168. }
  169. }
  170. messagesTextView.
  171. Highlight(ms[selectedMessage].ID).
  172. ScrollToHighlight()
  173. return nil
  174. } else if util.HasKeybinding(conf.Keybindings.SelectNextMessage, e.Name()) {
  175. if len(messagesTextView.GetHighlights()) == 0 {
  176. selectedMessage = len(ms) - 1
  177. } else {
  178. selectedMessage++
  179. if selectedMessage >= len(ms) {
  180. selectedMessage = len(ms) - 1
  181. }
  182. }
  183. messagesTextView.
  184. Highlight(ms[selectedMessage].ID).
  185. ScrollToHighlight()
  186. return nil
  187. } else if util.HasKeybinding(conf.Keybindings.SelectFirstMessage, e.Name()) {
  188. selectedMessage = 0
  189. messagesTextView.
  190. Highlight(ms[selectedMessage].ID).
  191. ScrollToHighlight()
  192. return nil
  193. } else if util.HasKeybinding(conf.Keybindings.SelectLastMessage, e.Name()) {
  194. selectedMessage = len(ms) - 1
  195. messagesTextView.
  196. Highlight(ms[selectedMessage].ID).
  197. ScrollToHighlight()
  198. return nil
  199. } else if util.HasKeybinding(conf.Keybindings.SelectMessageReference, e.Name()) {
  200. hs := messagesTextView.GetHighlights()
  201. if len(hs) == 0 {
  202. return nil
  203. }
  204. _, m := util.FindMessageByID(selectedChannel.Messages, hs[0])
  205. if m.ReferencedMessage != nil {
  206. selectedMessage, _ = util.FindMessageByID(selectedChannel.Messages, m.ReferencedMessage.ID)
  207. messagesTextView.
  208. Highlight(m.ReferencedMessage.ID).
  209. ScrollToHighlight()
  210. }
  211. return nil
  212. } else if util.HasKeybinding(conf.Keybindings.ReplySelectedMessage, e.Name()) {
  213. hs := messagesTextView.GetHighlights()
  214. if len(hs) == 0 {
  215. return nil
  216. }
  217. _, m := util.FindMessageByID(selectedChannel.Messages, hs[0])
  218. messageInputField.SetTitle("Replying to " + m.Author.String())
  219. app.SetFocus(messageInputField)
  220. return nil
  221. } else if util.HasKeybinding(conf.Keybindings.MentionReplySelectedMessage, e.Name()) {
  222. hs := messagesTextView.GetHighlights()
  223. if len(hs) == 0 {
  224. return nil
  225. }
  226. _, m := util.FindMessageByID(selectedChannel.Messages, hs[0])
  227. messageInputField.SetTitle("[@] Replying to " + m.Author.String())
  228. app.SetFocus(messageInputField)
  229. return nil
  230. } else if util.HasKeybinding(conf.Keybindings.CopySelectedMessage, e.Name()) {
  231. hs := messagesTextView.GetHighlights()
  232. if len(hs) == 0 {
  233. return nil
  234. }
  235. _, m := util.FindMessageByID(selectedChannel.Messages, hs[0])
  236. err := clipboard.WriteAll(m.Content)
  237. if err != nil {
  238. return nil
  239. }
  240. return nil
  241. }
  242. return e
  243. }
  244. func onMessageInputFieldInputCapture(e *tcell.EventKey) *tcell.EventKey {
  245. switch e.Key() {
  246. case tcell.KeyEnter:
  247. if selectedChannel == nil {
  248. return nil
  249. }
  250. t := strings.TrimSpace(messageInputField.GetText())
  251. if t == "" {
  252. return nil
  253. }
  254. if len(messagesTextView.GetHighlights()) != 0 {
  255. m := selectedChannel.Messages[selectedMessage]
  256. d := &discordgo.MessageSend{
  257. Content: t,
  258. Reference: m.Reference(),
  259. AllowedMentions: &discordgo.MessageAllowedMentions{RepliedUser: false},
  260. }
  261. if strings.HasPrefix(messageInputField.GetTitle(), "[@]") {
  262. d.AllowedMentions.RepliedUser = true
  263. } else {
  264. d.AllowedMentions.RepliedUser = false
  265. }
  266. go session.ChannelMessageSendComplex(m.ChannelID, d)
  267. selectedMessage = -1
  268. messagesTextView.Highlight()
  269. messageInputField.SetTitle("")
  270. } else {
  271. go session.ChannelMessageSend(selectedChannel.ID, t)
  272. }
  273. messageInputField.SetText("")
  274. return nil
  275. case tcell.KeyCtrlV:
  276. text, _ := clipboard.ReadAll()
  277. text = messageInputField.GetText() + text
  278. messageInputField.SetText(text)
  279. return nil
  280. case tcell.KeyEscape:
  281. messageInputField.SetText("")
  282. messageInputField.SetTitle("")
  283. selectedMessage = -1
  284. messagesTextView.Highlight()
  285. return nil
  286. }
  287. return e
  288. }