puppet.go 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. package bridge
  2. import (
  3. "fmt"
  4. "io"
  5. "net/http"
  6. "regexp"
  7. "sync"
  8. log "maunium.net/go/maulogger/v2"
  9. "maunium.net/go/mautrix/appservice"
  10. "maunium.net/go/mautrix/id"
  11. "gitlab.com/beeper/discord/database"
  12. )
  13. type Puppet struct {
  14. *database.Puppet
  15. bridge *Bridge
  16. log log.Logger
  17. MXID id.UserID
  18. syncLock sync.Mutex
  19. }
  20. var userIDRegex *regexp.Regexp
  21. func (b *Bridge) NewPuppet(dbPuppet *database.Puppet) *Puppet {
  22. return &Puppet{
  23. Puppet: dbPuppet,
  24. bridge: b,
  25. log: b.log.Sub(fmt.Sprintf("Puppet/%s", dbPuppet.ID)),
  26. MXID: b.FormatPuppetMXID(dbPuppet.ID),
  27. }
  28. }
  29. func (b *Bridge) ParsePuppetMXID(mxid id.UserID) (string, bool) {
  30. if userIDRegex == nil {
  31. pattern := fmt.Sprintf(
  32. "^@%s:%s$",
  33. b.Config.Bridge.FormatUsername("([0-9]+)"),
  34. b.Config.Homeserver.Domain,
  35. )
  36. userIDRegex = regexp.MustCompile(pattern)
  37. }
  38. match := userIDRegex.FindStringSubmatch(string(mxid))
  39. if len(match) == 2 {
  40. return match[1], true
  41. }
  42. return "", false
  43. }
  44. func (b *Bridge) GetPuppetByMXID(mxid id.UserID) *Puppet {
  45. id, ok := b.ParsePuppetMXID(mxid)
  46. if !ok {
  47. return nil
  48. }
  49. return b.GetPuppetByID(id)
  50. }
  51. func (b *Bridge) GetPuppetByID(id string) *Puppet {
  52. b.puppetsLock.Lock()
  53. defer b.puppetsLock.Unlock()
  54. puppet, ok := b.puppets[id]
  55. if !ok {
  56. dbPuppet := b.db.Puppet.Get(id)
  57. if dbPuppet == nil {
  58. dbPuppet = b.db.Puppet.New()
  59. dbPuppet.ID = id
  60. dbPuppet.Insert()
  61. }
  62. puppet = b.NewPuppet(dbPuppet)
  63. b.puppets[puppet.ID] = puppet
  64. }
  65. return puppet
  66. }
  67. func (b *Bridge) FormatPuppetMXID(did string) id.UserID {
  68. return id.NewUserID(
  69. b.Config.Bridge.FormatUsername(did),
  70. b.Config.Homeserver.Domain,
  71. )
  72. }
  73. func (p *Puppet) DefaultIntent() *appservice.IntentAPI {
  74. return p.bridge.as.Intent(p.MXID)
  75. }
  76. func (p *Puppet) IntentFor(portal *Portal) *appservice.IntentAPI {
  77. // TODO: when we add double puppeting we need to adjust this.
  78. return p.DefaultIntent()
  79. }
  80. func (p *Puppet) updatePortalMeta(meta func(portal *Portal)) {
  81. for _, portal := range p.bridge.GetAllPortalsByID(p.ID) {
  82. meta(portal)
  83. }
  84. }
  85. func (p *Puppet) updateName(source *User) bool {
  86. user, err := source.Session.User(p.ID)
  87. if err != nil {
  88. p.log.Warnln("failed to get user from id:", err)
  89. return false
  90. }
  91. newName := p.bridge.Config.Bridge.FormatDisplayname(user)
  92. if p.DisplayName != newName {
  93. err := p.DefaultIntent().SetDisplayName(newName)
  94. if err == nil {
  95. p.DisplayName = newName
  96. go p.updatePortalName()
  97. p.Update()
  98. } else {
  99. p.log.Warnln("failed to set display name:", err)
  100. }
  101. return true
  102. }
  103. return false
  104. }
  105. func (p *Puppet) updatePortalName() {
  106. p.updatePortalMeta(func(portal *Portal) {
  107. if portal.MXID != "" {
  108. _, err := portal.MainIntent().SetRoomName(portal.MXID, p.DisplayName)
  109. if err != nil {
  110. portal.log.Warnln("Failed to set name:", err)
  111. }
  112. }
  113. portal.Name = p.DisplayName
  114. portal.Update()
  115. })
  116. }
  117. func (p *Puppet) uploadAvatar(intent *appservice.IntentAPI, url string) (id.ContentURI, error) {
  118. getResp, err := http.DefaultClient.Get(url)
  119. if err != nil {
  120. return id.ContentURI{}, fmt.Errorf("failed to download avatar: %w", err)
  121. }
  122. data, err := io.ReadAll(getResp.Body)
  123. getResp.Body.Close()
  124. if err != nil {
  125. return id.ContentURI{}, fmt.Errorf("failed to read avatar data: %w", err)
  126. }
  127. mime := http.DetectContentType(data)
  128. resp, err := intent.UploadBytes(data, mime)
  129. if err != nil {
  130. return id.ContentURI{}, fmt.Errorf("failed to upload avatar to Matrix: %w", err)
  131. }
  132. return resp.ContentURI, nil
  133. }
  134. func (p *Puppet) updateAvatar(source *User) bool {
  135. user, err := source.Session.User(p.ID)
  136. if err != nil {
  137. p.log.Warnln("Failed to get user:", err)
  138. return false
  139. }
  140. if p.Avatar == user.Avatar {
  141. return false
  142. }
  143. if user.Avatar == "" {
  144. p.log.Warnln("User does not have an avatar")
  145. return false
  146. }
  147. url, err := p.uploadAvatar(p.DefaultIntent(), user.AvatarURL(""))
  148. if err != nil {
  149. p.log.Warnln("Failed to upload user avatar:", err)
  150. return false
  151. }
  152. p.AvatarURL = url
  153. err = p.DefaultIntent().SetAvatarURL(p.AvatarURL)
  154. if err != nil {
  155. p.log.Warnln("Failed to set avatar:", err)
  156. }
  157. p.log.Debugln("Updated avatar", p.Avatar, "->", user.Avatar)
  158. p.Avatar = user.Avatar
  159. go p.updatePortalAvatar()
  160. return true
  161. }
  162. func (p *Puppet) updatePortalAvatar() {
  163. p.updatePortalMeta(func(portal *Portal) {
  164. if portal.MXID != "" {
  165. _, err := portal.MainIntent().SetRoomAvatar(portal.MXID, p.AvatarURL)
  166. if err != nil {
  167. portal.log.Warnln("Failed to set avatar:", err)
  168. }
  169. }
  170. portal.AvatarURL = p.AvatarURL
  171. portal.Avatar = p.Avatar
  172. portal.Update()
  173. })
  174. }
  175. func (p *Puppet) SyncContact(source *User) {
  176. p.syncLock.Lock()
  177. defer p.syncLock.Unlock()
  178. p.log.Debugln("syncing contact", p.DisplayName)
  179. err := p.DefaultIntent().EnsureRegistered()
  180. if err != nil {
  181. p.log.Errorln("Failed to ensure registered:", err)
  182. }
  183. update := false
  184. update = p.updateName(source) || update
  185. if p.Avatar == "" {
  186. update = p.updateAvatar(source) || update
  187. p.log.Debugln("update avatar returned", update)
  188. }
  189. if update {
  190. p.Update()
  191. }
  192. }