discord.go 4.5 KB

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