util.go 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. package ui
  2. import (
  3. "fmt"
  4. "regexp"
  5. "strings"
  6. "github.com/ayntgl/discordgo"
  7. )
  8. var (
  9. boldRegex = regexp.MustCompile(`(?m)\*\*(.*?)\*\*`)
  10. italicRegex = regexp.MustCompile(`(?m)\*(.*?)\*`)
  11. underlineRegex = regexp.MustCompile(`(?m)__(.*?)__`)
  12. strikeThroughRegex = regexp.MustCompile(`(?m)~~(.*?)~~`)
  13. )
  14. func channelToString(c *discordgo.Channel) string {
  15. var repr string
  16. if c.Name != "" {
  17. repr = "#" + c.Name
  18. } else if len(c.Recipients) == 1 {
  19. rp := c.Recipients[0]
  20. repr = rp.Username + "#" + rp.Discriminator
  21. } else {
  22. rps := make([]string, len(c.Recipients))
  23. for i, r := range c.Recipients {
  24. rps[i] = r.Username + "#" + r.Discriminator
  25. }
  26. repr = strings.Join(rps, ", ")
  27. }
  28. return repr
  29. }
  30. func hasKeybinding(ks []string, k string) bool {
  31. for _, repr := range ks {
  32. if repr == k {
  33. return true
  34. }
  35. }
  36. return false
  37. }
  38. func buildMessage(app *App, m *discordgo.Message) []byte {
  39. var b strings.Builder
  40. switch m.Type {
  41. case discordgo.MessageTypeDefault, discordgo.MessageTypeReply:
  42. // Define a new region and assign message ID as the region ID.
  43. // Learn more:
  44. // https://pkg.go.dev/github.com/rivo/tview#hdr-Regions_and_Highlights
  45. b.WriteString("[\"")
  46. b.WriteString(m.ID)
  47. b.WriteString("\"]")
  48. // Build the message associated with crosspost, channel follow add, pin, or a reply.
  49. buildReferencedMessage(&b, m.ReferencedMessage, app.Session.State.User.ID)
  50. // Build the author of this message.
  51. buildAuthor(&b, m.Author, app.Session.State.User.ID)
  52. // Build the contents of the message.
  53. buildContent(&b, m, app.Session.State.User.ID)
  54. if m.EditedTimestamp != "" {
  55. b.WriteString(" [::d](edited)[::-]")
  56. }
  57. // Build the embeds associated with the message.
  58. buildEmbeds(&b, m.Embeds)
  59. // Build the message attachments (attached files to the message).
  60. buildAttachments(&b, m.Attachments)
  61. // Tags with no region ID ([""]) do not start new regions. They can
  62. // therefore be used to mark the end of a region.
  63. b.WriteString("[\"\"]")
  64. b.WriteByte('\n')
  65. case discordgo.MessageTypeGuildMemberJoin:
  66. b.WriteString("[#5865F2]")
  67. b.WriteString(m.Author.Username)
  68. b.WriteString("[-] joined the server.")
  69. b.WriteByte('\n')
  70. case discordgo.MessageTypeCall:
  71. b.WriteString("[#5865F2]")
  72. b.WriteString(m.Author.Username)
  73. b.WriteString("[-] started a call.")
  74. b.WriteByte('\n')
  75. case discordgo.MessageTypeChannelPinnedMessage:
  76. b.WriteString("[#5865F2]")
  77. b.WriteString(m.Author.Username)
  78. b.WriteString("[-] pinned a message.")
  79. b.WriteByte('\n')
  80. }
  81. if str := b.String(); str != "" {
  82. b := make([]byte, len(str)+1)
  83. copy(b, str)
  84. return b
  85. }
  86. return nil
  87. }
  88. func buildReferencedMessage(b *strings.Builder, rm *discordgo.Message, clientID string) {
  89. if rm != nil {
  90. b.WriteString(" ╭ ")
  91. b.WriteString("[::d]")
  92. buildAuthor(b, rm.Author, clientID)
  93. if rm.Content != "" {
  94. rm.Content = buildMentions(rm.Content, rm.Mentions, clientID)
  95. b.WriteString(parseMarkdown(rm.Content))
  96. }
  97. b.WriteString("[::-]")
  98. b.WriteByte('\n')
  99. }
  100. }
  101. func buildContent(b *strings.Builder, m *discordgo.Message, clientID string) {
  102. if m.Content != "" {
  103. m.Content = buildMentions(m.Content, m.Mentions, clientID)
  104. b.WriteString(parseMarkdown(m.Content))
  105. }
  106. }
  107. func buildEmbeds(b *strings.Builder, es []*discordgo.MessageEmbed) {
  108. for _, e := range es {
  109. if e.Type != discordgo.EmbedTypeRich {
  110. continue
  111. }
  112. var embedBuilder strings.Builder
  113. var hasHeading bool
  114. prefix := fmt.Sprintf("[#%06X]▐[-] ", e.Color)
  115. b.WriteByte('\n')
  116. embedBuilder.WriteString(prefix)
  117. if e.Author != nil {
  118. hasHeading = true
  119. embedBuilder.WriteString("[::u]")
  120. embedBuilder.WriteString(e.Author.Name)
  121. embedBuilder.WriteString("[::-]")
  122. }
  123. if e.Title != "" {
  124. hasHeading = true
  125. embedBuilder.WriteString("[::b]")
  126. embedBuilder.WriteString(e.Title)
  127. embedBuilder.WriteString("[::-]")
  128. }
  129. if e.Description != "" {
  130. if hasHeading {
  131. embedBuilder.WriteString("\n\n")
  132. }
  133. embedBuilder.WriteString(parseMarkdown(e.Description))
  134. }
  135. if len(e.Fields) != 0 {
  136. if hasHeading || e.Description != "" {
  137. embedBuilder.WriteString("\n\n")
  138. }
  139. for i, ef := range e.Fields {
  140. embedBuilder.WriteString("[::b]")
  141. embedBuilder.WriteString(ef.Name)
  142. embedBuilder.WriteString("[::-]")
  143. embedBuilder.WriteByte('\n')
  144. embedBuilder.WriteString(parseMarkdown(ef.Value))
  145. if i != len(e.Fields)-1 {
  146. embedBuilder.WriteString("\n\n")
  147. }
  148. }
  149. }
  150. if e.Footer != nil {
  151. if hasHeading {
  152. embedBuilder.WriteString("\n\n")
  153. }
  154. embedBuilder.WriteString(e.Footer.Text)
  155. }
  156. b.WriteString(strings.Replace(embedBuilder.String(), "\n", "\n"+prefix, -1))
  157. }
  158. }
  159. func buildAttachments(b *strings.Builder, as []*discordgo.MessageAttachment) {
  160. for _, a := range as {
  161. b.WriteByte('\n')
  162. b.WriteByte('[')
  163. b.WriteString(a.Filename)
  164. b.WriteString("]: ")
  165. b.WriteString(a.URL)
  166. }
  167. }
  168. func buildMentions(content string, mentions []*discordgo.User, clientID string) string {
  169. for _, mUser := range mentions {
  170. var color string
  171. if mUser.ID == clientID {
  172. color = "[:#5865F2]"
  173. } else {
  174. color = "[#EB459E]"
  175. }
  176. content = strings.NewReplacer(
  177. // <@USER_ID>
  178. "<@"+mUser.ID+">",
  179. color+"@"+mUser.Username+"[-:-]",
  180. // <@!USER_ID>
  181. "<@!"+mUser.ID+">",
  182. color+"@"+mUser.Username+"[-:-]",
  183. ).Replace(content)
  184. }
  185. return content
  186. }
  187. func buildAuthor(b *strings.Builder, u *discordgo.User, clientID string) {
  188. if u.ID == clientID {
  189. b.WriteString("[#57F287]")
  190. } else {
  191. b.WriteString("[#ED4245]")
  192. }
  193. b.WriteString(u.Username)
  194. b.WriteString("[-] ")
  195. // If the message author is a bot account, render the message with bot label
  196. // for distinction.
  197. if u.Bot {
  198. b.WriteString("[#EB459E]BOT[-] ")
  199. }
  200. }
  201. func parseMarkdown(md string) string {
  202. var res string
  203. res = boldRegex.ReplaceAllString(md, "[::b]$1[::-]")
  204. res = italicRegex.ReplaceAllString(res, "[::i]$1[::-]")
  205. res = underlineRegex.ReplaceAllString(res, "[::u]$1[::-]")
  206. res = strikeThroughRegex.ReplaceAllString(res, "[::s]$1[::-]")
  207. return res
  208. }