messages_text.go 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. package main
  2. import (
  3. "fmt"
  4. "log"
  5. "strings"
  6. "time"
  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. builder strings.Builder
  16. selectedMessage *discord.Message
  17. }
  18. func newMessagesText() *MessagesText {
  19. mt := &MessagesText{
  20. TextView: tview.NewTextView(),
  21. }
  22. mt.SetDynamicColors(true)
  23. mt.SetRegions(true)
  24. mt.SetWordWrap(true)
  25. mt.ScrollToEnd()
  26. mt.SetHighlightedFunc(mt.onHighlighted)
  27. mt.SetInputCapture(mt.onInputCapture)
  28. mt.SetBackgroundColor(tcell.GetColor(cfg.Theme.MessagesText.BackgroundColor))
  29. mt.SetTitle("Messages")
  30. mt.SetTitleColor(tcell.GetColor(cfg.Theme.MessagesText.TitleColor))
  31. mt.SetTitleAlign(tview.AlignLeft)
  32. padding := cfg.Theme.MessagesText.BorderPadding
  33. mt.SetBorder(cfg.Theme.MessagesText.Border)
  34. mt.SetBorderPadding(padding[0], padding[1], padding[2], padding[3])
  35. return mt
  36. }
  37. func (mt *MessagesText) reset() {
  38. messagesText.selectedMessage = nil
  39. mt.SetTitle("")
  40. mt.Clear()
  41. mt.Highlight()
  42. }
  43. func (mt *MessagesText) newMessage(m *discord.Message) error {
  44. mt.builder.Reset()
  45. switch m.Type {
  46. case discord.DefaultMessage, discord.InlinedReplyMessage:
  47. // Region tags are square brackets that contain a region ID in double quotes
  48. // https://pkg.go.dev/github.com/rivo/tview#hdr-Regions_and_Highlights
  49. mt.builder.WriteString(`["`)
  50. mt.builder.WriteString(m.ID.String())
  51. mt.builder.WriteString(`"]`)
  52. if m.ReferencedMessage != nil {
  53. mt.builder.WriteString("[::d] ")
  54. mt.builder.WriteRune(replyIndicator)
  55. mt.builder.WriteByte(' ')
  56. // Author
  57. mt.newAuthor(m.ReferencedMessage)
  58. // Content
  59. mt.newContent(m.ReferencedMessage)
  60. mt.builder.WriteString("[::-]\n")
  61. }
  62. if cfg.Timestamps {
  63. // Timestamps
  64. mt.newTimestamp(m)
  65. }
  66. // Author
  67. mt.newAuthor(m)
  68. // Content
  69. mt.newContent(m)
  70. // Attachments
  71. mt.newAttachments(m)
  72. // Tags with no region ID ([""]) don't start new regions. They can therefore be used to mark the end of a region.
  73. mt.builder.WriteString(`[""]`)
  74. mt.builder.WriteByte('\n')
  75. }
  76. _, err := fmt.Fprint(mt, mt.builder.String())
  77. return err
  78. }
  79. func (mt *MessagesText) newAuthor(m *discord.Message) {
  80. mt.builder.WriteByte('[')
  81. mt.builder.WriteString(cfg.Theme.MessagesText.AuthorColor)
  82. mt.builder.WriteByte(']')
  83. mt.builder.WriteString(m.Author.Username)
  84. mt.builder.WriteString("[-] ")
  85. }
  86. func (mt *MessagesText) newTimestamp(m *discord.Message) {
  87. mt.builder.WriteString("[::d]")
  88. mt.builder.WriteString(m.Timestamp.Format(time.Kitchen))
  89. mt.builder.WriteString("[::-] ")
  90. }
  91. func (mt *MessagesText) newContent(m *discord.Message) {
  92. mt.builder.WriteString(discordmd.Parse(tview.Escape(m.Content)))
  93. }
  94. func (mt *MessagesText) newAttachments(m *discord.Message) {
  95. for _, a := range m.Attachments {
  96. mt.builder.WriteByte('\n')
  97. mt.builder.WriteByte('[')
  98. mt.builder.WriteString(a.Filename)
  99. mt.builder.WriteString("]: ")
  100. mt.builder.WriteString(a.URL)
  101. }
  102. }
  103. func (mt *MessagesText) onHighlighted(added, removed, remaining []string) {
  104. if len(added) == 0 {
  105. return
  106. }
  107. sf, err := discord.ParseSnowflake(added[0])
  108. if err != nil {
  109. log.Println(err)
  110. return
  111. }
  112. m, err := discordState.Cabinet.Message(guildsTree.selectedChannel.ID, discord.MessageID(sf))
  113. if err != nil {
  114. log.Println(err)
  115. return
  116. }
  117. mt.selectedMessage = m
  118. }
  119. func (mt *MessagesText) onInputCapture(event *tcell.EventKey) *tcell.EventKey {
  120. switch event.Name() {
  121. case cfg.Keys.MessagesText.Reply:
  122. mt.replyAction(false)
  123. return nil
  124. case cfg.Keys.MessagesText.ReplyMention:
  125. mt.replyAction(true)
  126. return nil
  127. case cfg.Keys.MessagesText.Cancel:
  128. // TODO
  129. guildsTree.selectedChannel = nil
  130. messagesText.reset()
  131. messageInput.reset()
  132. return nil
  133. }
  134. return event
  135. }
  136. func (mt *MessagesText) replyAction(mention bool) {
  137. if mt.selectedMessage == nil {
  138. return
  139. }
  140. var title string
  141. if mention {
  142. title += "[@] Replying to "
  143. } else {
  144. title += "Replying to "
  145. }
  146. title += mt.selectedMessage.Author.Tag()
  147. messageInput.SetTitle(title)
  148. app.SetFocus(messageInput)
  149. }