discord.go 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. package util
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "strings"
  6. "github.com/rigormorrtiss/discordgo"
  7. "github.com/rivo/tview"
  8. )
  9. // WriteMessage parses, renders, and writes a message to the given TextView.
  10. func WriteMessage(v *tview.TextView, m *discordgo.Message, clientID string) {
  11. var b strings.Builder
  12. switch m.Type {
  13. case discordgo.MessageTypeDefault, discordgo.MessageTypeReply:
  14. // Define a new region and assign message ID as the region ID.
  15. // Learn more:
  16. // https://pkg.go.dev/github.com/rivo/tview#hdr-Regions_and_Highlights
  17. b.WriteString("[\"")
  18. b.WriteString(m.ID)
  19. b.WriteString("\"]")
  20. // Render the message associated with crosspost, channel follow add,
  21. // pin, or a reply.
  22. if rm := m.ReferencedMessage; rm != nil {
  23. b.WriteString(" ╭ ")
  24. b.WriteString("[::d]")
  25. parseAuthor(&b, rm.Author, clientID)
  26. if rm.Content != "" {
  27. rm.Content = parseMessageMentions(
  28. rm.Content,
  29. rm.Mentions,
  30. clientID,
  31. )
  32. b.WriteString(rm.Content)
  33. }
  34. b.WriteString("[::-]\n")
  35. }
  36. // Render the author of the message.
  37. parseAuthor(&b, m.Author, clientID)
  38. // If the message content is not empty, parse the message mentions
  39. // (users mentioned in the message) and render the message content.
  40. if m.Content != "" {
  41. m.Content = parseMessageMentions(m.Content, m.Mentions, clientID)
  42. b.WriteString(m.Content)
  43. }
  44. // If the edited timestamp of the message is not empty; it implies that
  45. // the message has been edited, hence render the message with edited
  46. // label for distinction
  47. if m.EditedTimestamp != "" {
  48. b.WriteString(" [::d](edited)[::-]")
  49. }
  50. // TODO: render message embeds
  51. for range m.Embeds {
  52. b.WriteString("\n<EMBED>")
  53. }
  54. // Render the message attachments (attached files to the message).
  55. for _, a := range m.Attachments {
  56. b.WriteString("\n[")
  57. b.WriteString(a.Filename)
  58. b.WriteString("]: ")
  59. b.WriteString(a.URL)
  60. }
  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.WriteString("\"]")
  65. fmt.Fprintln(v, b.String())
  66. case discordgo.MessageTypeGuildMemberJoin:
  67. b.WriteString("[#5865F2]")
  68. b.WriteString(m.Author.Username)
  69. b.WriteString("[-]")
  70. b.WriteString(" joined the server")
  71. fmt.Fprintln(v, b.String())
  72. }
  73. }
  74. func parseMessageMentions(
  75. content string,
  76. mentions []*discordgo.User,
  77. clientID string,
  78. ) string {
  79. for _, mUser := range mentions {
  80. var color string
  81. if mUser.ID == clientID {
  82. color = "[:#5865F2]"
  83. } else {
  84. color = "[#EB459E]"
  85. }
  86. content = strings.NewReplacer(
  87. // <@USER_ID>
  88. "<@"+mUser.ID+">",
  89. color+"@"+mUser.Username+"[-:-]",
  90. // <@!USER_ID>
  91. "<@!"+mUser.ID+">",
  92. color+"@"+mUser.Username+"[-:-]",
  93. ).Replace(content)
  94. }
  95. return content
  96. }
  97. func parseAuthor(b *strings.Builder, u *discordgo.User, clientID string) {
  98. // If the message author is the client, modify the text color for
  99. // distinction.
  100. if u.ID == clientID {
  101. b.WriteString("[#57F287]")
  102. } else {
  103. b.WriteString("[#ED4245]")
  104. }
  105. b.WriteString(u.Username)
  106. b.WriteString("[-] ")
  107. // If the message author is a bot account, render the message with bot label
  108. // for distinction.
  109. if u.Bot {
  110. b.WriteString("[#EB459E]BOT[-] ")
  111. }
  112. }
  113. type loginResponse struct {
  114. MFA bool `json:"mfa"`
  115. SMS bool `json:"sms"`
  116. Ticket string `json:"ticket"`
  117. Token string `json:"token"`
  118. }
  119. // Login creates a new request to the `/login` endpoint for essential login
  120. // information.
  121. func Login(
  122. s *discordgo.Session,
  123. email, password string,
  124. ) (*loginResponse, error) {
  125. data := struct {
  126. Email string `json:"email"`
  127. Password string `json:"password"`
  128. }{email, password}
  129. resp, err := s.RequestWithBucketID(
  130. "POST",
  131. discordgo.EndpointLogin,
  132. data,
  133. discordgo.EndpointLogin,
  134. )
  135. if err != nil {
  136. return nil, err
  137. }
  138. var lr loginResponse
  139. err = json.Unmarshal(resp, &lr)
  140. if err != nil {
  141. return nil, err
  142. }
  143. return &lr, nil
  144. }
  145. // TOTP creates a new request to `/mfa/totp` endpoint for time-based one-time
  146. // passcode for essential login information
  147. func TOTP(s *discordgo.Session, code, ticket string) (*loginResponse, error) {
  148. data := struct {
  149. Code string `json:"code"`
  150. Ticket string `json:"ticket"`
  151. }{code, ticket}
  152. e := discordgo.EndpointAuth + "mfa/totp"
  153. resp, err := s.RequestWithBucketID("POST", e, data, e)
  154. if err != nil {
  155. return nil, err
  156. }
  157. var lr loginResponse
  158. err = json.Unmarshal(resp, &lr)
  159. if err != nil {
  160. return nil, err
  161. }
  162. return &lr, nil
  163. }
  164. // HasPermission returns a boolean representing whether the provided user has
  165. // given permissions or not.
  166. func HasPermission(
  167. s *discordgo.State,
  168. uID string,
  169. cID string,
  170. perm int64,
  171. ) bool {
  172. p, err := s.UserChannelPermissions(uID, cID)
  173. if err != nil {
  174. return false
  175. }
  176. return p&perm == perm
  177. }