messages_text.go 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. package main
  2. import (
  3. "bytes"
  4. "log"
  5. "time"
  6. "github.com/ayn2op/discordo/discordmd"
  7. "github.com/diamondburned/arikawa/v3/discord"
  8. "github.com/gdamore/tcell/v2"
  9. "github.com/rivo/tview"
  10. )
  11. const replyIndicator = '╭'
  12. type MessagesText struct {
  13. *tview.TextView
  14. selectedMessage int
  15. buf bytes.Buffer
  16. }
  17. func newMessagesText() *MessagesText {
  18. mt := &MessagesText{
  19. TextView: tview.NewTextView(),
  20. selectedMessage: -1,
  21. }
  22. mt.SetDynamicColors(true)
  23. mt.SetRegions(true)
  24. mt.SetWordWrap(true)
  25. mt.SetInputCapture(mt.onInputCapture)
  26. mt.ScrollToEnd()
  27. mt.SetChangedFunc(func() {
  28. app.Draw()
  29. })
  30. mt.SetBackgroundColor(tcell.GetColor(config.Theme.MessagesText.BackgroundColor))
  31. mt.SetTitle("Messages")
  32. mt.SetTitleColor(tcell.GetColor(config.Theme.MessagesText.TitleColor))
  33. mt.SetTitleAlign(tview.AlignLeft)
  34. padding := config.Theme.MessagesText.BorderPadding
  35. mt.SetBorder(config.Theme.MessagesText.Border)
  36. mt.SetBorderPadding(padding[0], padding[1], padding[2], padding[3])
  37. return mt
  38. }
  39. func (mt *MessagesText) reset() {
  40. messagesText.selectedMessage = -1
  41. mt.SetTitle("")
  42. mt.Clear()
  43. mt.Highlight()
  44. }
  45. func (mt *MessagesText) createMessage(m *discord.Message) error {
  46. switch m.Type {
  47. case discord.DefaultMessage, discord.InlinedReplyMessage:
  48. // Region tags are square brackets that contain a region ID in double quotes
  49. // https://pkg.go.dev/github.com/rivo/tview#hdr-Regions_and_Highlights
  50. mt.buf.WriteString(`["`)
  51. mt.buf.WriteString(m.ID.String())
  52. mt.buf.WriteString(`"]`)
  53. if m.ReferencedMessage != nil {
  54. mt.buf.WriteString("[::d] ")
  55. mt.buf.WriteRune(replyIndicator)
  56. mt.buf.WriteByte(' ')
  57. mt.buf.WriteByte('[')
  58. mt.buf.WriteString(config.Theme.MessagesText.AuthorColor)
  59. mt.buf.WriteByte(']')
  60. mt.buf.WriteString(m.Author.Username)
  61. mt.buf.WriteString("[-] ")
  62. mt.buf.WriteString(discordmd.Parse(tview.Escape(m.Content)))
  63. mt.buf.WriteString("[::-]\n")
  64. }
  65. mt.createHeader(m)
  66. mt.createBody(m)
  67. mt.createFooter(m)
  68. // Tags with no region ID ([""]) don't start new regions. They can therefore be used to mark the end of a region.
  69. mt.buf.WriteString(`[""]`)
  70. mt.buf.WriteByte('\n')
  71. }
  72. _, err := mt.buf.WriteTo(mt)
  73. return err
  74. }
  75. func (mt *MessagesText) createHeader(m *discord.Message) {
  76. mt.buf.WriteByte('[')
  77. mt.buf.WriteString(config.Theme.MessagesText.AuthorColor)
  78. mt.buf.WriteByte(']')
  79. mt.buf.WriteString(m.Author.Username)
  80. mt.buf.WriteString("[-] ")
  81. if config.Timestamps {
  82. mt.buf.WriteString("[::d]")
  83. mt.buf.WriteString(m.Timestamp.Format(time.Kitchen))
  84. mt.buf.WriteString("[::-] ")
  85. }
  86. }
  87. func (mt *MessagesText) createBody(m *discord.Message) {
  88. mt.buf.WriteString(discordmd.Parse(tview.Escape(m.Content)))
  89. }
  90. func (mt *MessagesText) createFooter(m *discord.Message) {
  91. for _, a := range m.Attachments {
  92. mt.buf.WriteByte('\n')
  93. mt.buf.WriteByte('[')
  94. mt.buf.WriteString(a.Filename)
  95. mt.buf.WriteString("]: ")
  96. mt.buf.WriteString(a.URL)
  97. }
  98. }
  99. func (mt *MessagesText) onInputCapture(event *tcell.EventKey) *tcell.EventKey {
  100. switch event.Name() {
  101. case config.Keys.MessagesText.Reply:
  102. mt.replyAction(false)
  103. return nil
  104. case config.Keys.MessagesText.ReplyMention:
  105. mt.replyAction(true)
  106. return nil
  107. case config.Keys.MessagesText.SelectPrevious:
  108. mt.selectPreviousAction()
  109. return nil
  110. case config.Keys.MessagesText.SelectNext:
  111. mt.selectNextAction()
  112. return nil
  113. case config.Keys.MessagesText.SelectFirst:
  114. mt.selectFirstAction()
  115. return nil
  116. case config.Keys.MessagesText.SelectLast:
  117. mt.selectLastAction()
  118. return nil
  119. case config.Keys.MessagesText.Cancel:
  120. guildsTree.selectedChannel = nil
  121. messagesText.reset()
  122. messageInput.reset()
  123. return nil
  124. }
  125. return event
  126. }
  127. func (mt *MessagesText) replyAction(mention bool) {
  128. if mt.selectedMessage == -1 {
  129. return
  130. }
  131. var title string
  132. if mention {
  133. title += "[@] Replying to "
  134. } else {
  135. title += "Replying to "
  136. }
  137. ms, err := discordState.Cabinet.Messages(guildsTree.selectedChannel.ID)
  138. if err != nil {
  139. log.Println(err)
  140. return
  141. }
  142. title += ms[mt.selectedMessage].Author.Tag()
  143. messageInput.SetTitle(title)
  144. app.SetFocus(messageInput)
  145. }
  146. func (mt *MessagesText) selectPreviousAction() {
  147. ms, err := discordState.Cabinet.Messages(guildsTree.selectedChannel.ID)
  148. if err != nil {
  149. log.Println(err)
  150. return
  151. }
  152. // If no message is currently selected, select the latest message.
  153. if len(mt.GetHighlights()) == 0 {
  154. mt.selectedMessage = 0
  155. } else {
  156. if mt.selectedMessage < len(ms)-1 {
  157. mt.selectedMessage++
  158. }
  159. }
  160. mt.Highlight(ms[mt.selectedMessage].ID.String())
  161. mt.ScrollToHighlight()
  162. }
  163. func (mt *MessagesText) selectNextAction() {
  164. ms, err := discordState.Cabinet.Messages(guildsTree.selectedChannel.ID)
  165. if err != nil {
  166. log.Println(err)
  167. return
  168. }
  169. // If no message is currently selected, select the latest message.
  170. if len(mt.GetHighlights()) == 0 {
  171. mt.selectedMessage = 0
  172. } else {
  173. if mt.selectedMessage > 0 {
  174. mt.selectedMessage--
  175. }
  176. }
  177. mt.Highlight(ms[mt.selectedMessage].ID.String())
  178. mt.ScrollToHighlight()
  179. }
  180. func (mt *MessagesText) selectFirstAction() {
  181. ms, err := discordState.Cabinet.Messages(guildsTree.selectedChannel.ID)
  182. if err != nil {
  183. log.Println(err)
  184. return
  185. }
  186. mt.selectedMessage = len(ms) - 1
  187. mt.Highlight(ms[mt.selectedMessage].ID.String())
  188. mt.ScrollToHighlight()
  189. }
  190. func (mt *MessagesText) selectLastAction() {
  191. ms, err := discordState.Cabinet.Messages(guildsTree.selectedChannel.ID)
  192. if err != nil {
  193. log.Println(err)
  194. return
  195. }
  196. mt.selectedMessage = 0
  197. mt.Highlight(ms[mt.selectedMessage].ID.String())
  198. mt.ScrollToHighlight()
  199. }