messages_text.go 6.3 KB

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