ui.go 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. package main
  2. import (
  3. "strings"
  4. "github.com/atotto/clipboard"
  5. "github.com/ayntgl/discordgo"
  6. "github.com/gdamore/tcell/v2"
  7. "github.com/rivo/tview"
  8. )
  9. var (
  10. app *tview.Application
  11. loginForm *tview.Form
  12. channelsTree *tview.TreeView
  13. messagesView *tview.TextView
  14. messageInputField *tview.InputField
  15. mainFlex *tview.Flex
  16. )
  17. func onAppInputCapture(e *tcell.EventKey) *tcell.EventKey {
  18. switch e.Name() {
  19. case conf.Keybindings.ChannelsTree.Focus:
  20. app.SetFocus(channelsTree)
  21. case conf.Keybindings.MessagesTextView.Focus:
  22. app.SetFocus(messagesView)
  23. case conf.Keybindings.MessageInputField.Focus:
  24. app.SetFocus(messageInputField)
  25. }
  26. return e
  27. }
  28. func onChannelsTreeSelected(n *tview.TreeNode) {
  29. selectedChannel = nil
  30. selectedMessage = nil
  31. messagesView.
  32. Clear().
  33. SetTitle("")
  34. messageInputField.SetText("")
  35. // Unhighlight the already-highlighted regions.
  36. messagesView.Highlight()
  37. if len(n.GetChildren()) != 0 || n.GetText() == "Direct Messages" {
  38. n.SetExpanded(!n.IsExpanded())
  39. } else {
  40. cID := n.GetReference().(string)
  41. c, err := session.State.Channel(cID)
  42. if err != nil {
  43. return
  44. }
  45. selectedChannel = c
  46. app.SetFocus(messageInputField)
  47. switch c.Type {
  48. case discordgo.ChannelTypeGuildText, discordgo.ChannelTypeGuildNews:
  49. title := generateChannelRepr(c)
  50. if c.Topic != "" {
  51. title += " - " + c.Topic
  52. }
  53. messagesView.SetTitle(title)
  54. case discordgo.ChannelTypeDM, discordgo.ChannelTypeGroupDM:
  55. messagesView.SetTitle(generateChannelRepr(c))
  56. }
  57. if strings.HasPrefix(n.GetText(), "[::b]") {
  58. n.SetText("[::d]" + generateChannelRepr(c) + "[::-]")
  59. }
  60. messagesView.Clear()
  61. go func() {
  62. ms, err := session.ChannelMessages(cID, conf.GetMessagesLimit, "", "", "")
  63. if err != nil {
  64. return
  65. }
  66. for i := len(ms) - 1; i >= 0; i-- {
  67. selectedChannel.Messages = append(selectedChannel.Messages, ms[i])
  68. renderMessage(ms[i])
  69. }
  70. if len(ms) != 0 && isUnread(c) {
  71. session.ChannelMessageAck(c.ID, c.LastMessageID, "")
  72. }
  73. }()
  74. }
  75. }
  76. func createTopLevelChannelsTreeNodes(
  77. n *tview.TreeNode,
  78. cs []*discordgo.Channel,
  79. ) {
  80. for _, c := range cs {
  81. if (c.Type == discordgo.ChannelTypeGuildText || c.Type == discordgo.ChannelTypeGuildNews) &&
  82. (c.ParentID == "") {
  83. p, err := session.State.UserChannelPermissions(session.State.User.ID, c.ID)
  84. if err != nil || p&discordgo.PermissionViewChannel != discordgo.PermissionViewChannel {
  85. continue
  86. }
  87. var tag string
  88. if isUnread(c) {
  89. tag = "[::b]"
  90. } else {
  91. tag = "[::d]"
  92. }
  93. cn := tview.NewTreeNode(tag + generateChannelRepr(c) + "[::-]").
  94. SetReference(c.ID)
  95. n.AddChild(cn)
  96. continue
  97. }
  98. }
  99. }
  100. func createCategoryChannelsTreeNodes(
  101. n *tview.TreeNode,
  102. cs []*discordgo.Channel,
  103. ) {
  104. CategoryLoop:
  105. for _, c := range cs {
  106. if c.Type == discordgo.ChannelTypeGuildCategory {
  107. p, err := session.State.UserChannelPermissions(session.State.User.ID, c.ID)
  108. if err != nil || p&discordgo.PermissionViewChannel != discordgo.PermissionViewChannel {
  109. continue
  110. }
  111. for _, child := range cs {
  112. if child.ParentID == c.ID {
  113. cn := tview.NewTreeNode(c.Name).
  114. SetReference(c.ID)
  115. n.AddChild(cn)
  116. continue CategoryLoop
  117. }
  118. }
  119. cn := tview.NewTreeNode(c.Name).
  120. SetReference(c.ID)
  121. n.AddChild(cn)
  122. }
  123. }
  124. }
  125. func createSecondLevelChannelsTreeNodes(cs []*discordgo.Channel) {
  126. for _, c := range cs {
  127. if (c.Type == discordgo.ChannelTypeGuildText || c.Type == discordgo.ChannelTypeGuildNews) &&
  128. (c.ParentID != "") {
  129. p, err := session.State.UserChannelPermissions(session.State.User.ID, c.ID)
  130. if err != nil || p&discordgo.PermissionViewChannel != discordgo.PermissionViewChannel {
  131. continue
  132. }
  133. var tag string
  134. if isUnread(c) {
  135. tag = "[::b]"
  136. } else {
  137. tag = "[::d]"
  138. }
  139. pn := getTreeNodeByReference(c.ParentID)
  140. if pn != nil {
  141. cn := tview.NewTreeNode(tag + generateChannelRepr(c) + "[::-]").
  142. SetReference(c.ID)
  143. pn.AddChild(cn)
  144. }
  145. }
  146. }
  147. }
  148. func getTreeNodeByReference(r interface{}) (mn *tview.TreeNode) {
  149. channelsTree.GetRoot().Walk(func(n, _ *tview.TreeNode) bool {
  150. if n.GetReference() == r {
  151. mn = n
  152. return false
  153. }
  154. return true
  155. })
  156. return
  157. }
  158. func onMessagesTextViewInputCapture(e *tcell.EventKey) *tcell.EventKey {
  159. if selectedChannel == nil {
  160. return nil
  161. }
  162. switch e.Name() {
  163. case conf.Keybindings.MessagesTextView.SelectPrevious:
  164. ms := selectedChannel.Messages
  165. if len(ms) == 0 {
  166. return nil
  167. }
  168. hs := messagesView.GetHighlights()
  169. if len(hs) == 0 {
  170. messagesView.
  171. Highlight(ms[len(ms)-1].ID).
  172. ScrollToHighlight()
  173. } else {
  174. idx, _ := findByMessageID(ms, hs[0])
  175. if idx == -1 || idx == 0 {
  176. return nil
  177. }
  178. messagesView.
  179. Highlight(ms[idx-1].ID).
  180. ScrollToHighlight()
  181. }
  182. return nil
  183. case conf.Keybindings.MessagesTextView.SelectNext:
  184. ms := selectedChannel.Messages
  185. if len(ms) == 0 {
  186. return nil
  187. }
  188. hs := messagesView.GetHighlights()
  189. if len(hs) == 0 {
  190. messagesView.
  191. Highlight(ms[len(ms)-1].ID).
  192. ScrollToHighlight()
  193. } else {
  194. idx, _ := findByMessageID(ms, hs[0])
  195. if idx == -1 || idx == len(ms)-1 {
  196. return nil
  197. }
  198. messagesView.
  199. Highlight(ms[idx+1].ID).
  200. ScrollToHighlight()
  201. }
  202. return nil
  203. case conf.Keybindings.MessagesTextView.SelectFirst:
  204. ms := selectedChannel.Messages
  205. if len(ms) == 0 {
  206. return nil
  207. }
  208. messagesView.
  209. Highlight(ms[0].ID).
  210. ScrollToHighlight()
  211. case conf.Keybindings.MessagesTextView.SelectLast:
  212. ms := selectedChannel.Messages
  213. if len(ms) == 0 {
  214. return nil
  215. }
  216. messagesView.
  217. Highlight(ms[len(ms)-1].ID).
  218. ScrollToHighlight()
  219. case conf.Keybindings.MessagesTextView.Reply:
  220. ms := selectedChannel.Messages
  221. if len(ms) == 0 {
  222. return nil
  223. }
  224. hs := messagesView.GetHighlights()
  225. if len(hs) == 0 {
  226. return nil
  227. }
  228. _, selectedMessage = findByMessageID(ms, hs[0])
  229. messageInputField.SetTitle(
  230. "Replying to " + selectedMessage.Author.Username,
  231. )
  232. app.SetFocus(messageInputField)
  233. case conf.Keybindings.MessagesTextView.ReplyMention:
  234. ms := selectedChannel.Messages
  235. if len(ms) == 0 {
  236. return nil
  237. }
  238. hs := messagesView.GetHighlights()
  239. if len(hs) == 0 {
  240. return nil
  241. }
  242. _, selectedMessage = findByMessageID(ms, hs[0])
  243. messageInputField.SetTitle("[@] Repling to " + selectedMessage.Author.Username)
  244. app.SetFocus(messageInputField)
  245. }
  246. return e
  247. }
  248. func onMessageInputFieldInputCapture(e *tcell.EventKey) *tcell.EventKey {
  249. // If the "Alt" modifier key is pressed, do not handle the event.
  250. if e.Modifiers() == tcell.ModAlt {
  251. return nil
  252. }
  253. switch e.Key() {
  254. case tcell.KeyEnter:
  255. if selectedChannel == nil {
  256. return nil
  257. }
  258. t := strings.TrimSpace(messageInputField.GetText())
  259. if t == "" {
  260. return nil
  261. }
  262. if selectedMessage != nil {
  263. d := &discordgo.MessageSend{
  264. Content: t,
  265. Reference: selectedMessage.Reference(),
  266. AllowedMentions: &discordgo.MessageAllowedMentions{RepliedUser: false},
  267. }
  268. if strings.HasPrefix(messageInputField.GetTitle(), "[@]") {
  269. d.AllowedMentions.RepliedUser = true
  270. } else {
  271. d.AllowedMentions.RepliedUser = false
  272. }
  273. go session.ChannelMessageSendComplex(selectedMessage.ChannelID, d)
  274. messageInputField.SetTitle("")
  275. selectedMessage = nil
  276. } else {
  277. go session.ChannelMessageSend(selectedChannel.ID, t)
  278. }
  279. messageInputField.SetText("")
  280. case tcell.KeyCtrlV:
  281. text, _ := clipboard.ReadAll()
  282. text = messageInputField.GetText() + text
  283. messageInputField.SetText(text)
  284. case tcell.KeyEscape:
  285. messageInputField.SetTitle("")
  286. selectedMessage = nil
  287. }
  288. return e
  289. }
  290. func newLoginForm(onLoginFormLoginButtonSelected func(), mfa bool) *tview.Form {
  291. w := tview.NewForm()
  292. w.
  293. AddButton("Login", onLoginFormLoginButtonSelected).
  294. SetButtonsAlign(tview.AlignCenter).
  295. SetBorder(true).
  296. SetBorderPadding(0, 0, 1, 0)
  297. if mfa {
  298. w.AddPasswordField("Code", "", 0, 0, nil)
  299. } else {
  300. w.
  301. AddInputField("Email", "", 0, nil, nil).
  302. AddPasswordField("Password", "", 0, 0, nil)
  303. }
  304. return w
  305. }