ui.go 8.4 KB

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