messages_text.go 6.3 KB

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