puppet.go 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. package main
  2. import (
  3. "fmt"
  4. "regexp"
  5. "sync"
  6. log "maunium.net/go/maulogger/v2"
  7. "maunium.net/go/mautrix/appservice"
  8. "maunium.net/go/mautrix/bridge"
  9. "maunium.net/go/mautrix/id"
  10. "go.mau.fi/mautrix-discord/database"
  11. )
  12. type Puppet struct {
  13. *database.Puppet
  14. bridge *DiscordBridge
  15. log log.Logger
  16. MXID id.UserID
  17. customIntent *appservice.IntentAPI
  18. customUser *User
  19. syncLock sync.Mutex
  20. }
  21. var _ bridge.Ghost = (*Puppet)(nil)
  22. func (puppet *Puppet) GetMXID() id.UserID {
  23. return puppet.MXID
  24. }
  25. var userIDRegex *regexp.Regexp
  26. func (br *DiscordBridge) NewPuppet(dbPuppet *database.Puppet) *Puppet {
  27. return &Puppet{
  28. Puppet: dbPuppet,
  29. bridge: br,
  30. log: br.Log.Sub(fmt.Sprintf("Puppet/%s", dbPuppet.ID)),
  31. MXID: br.FormatPuppetMXID(dbPuppet.ID),
  32. }
  33. }
  34. func (br *DiscordBridge) ParsePuppetMXID(mxid id.UserID) (string, bool) {
  35. if userIDRegex == nil {
  36. pattern := fmt.Sprintf(
  37. "^@%s:%s$",
  38. br.Config.Bridge.FormatUsername("([0-9]+)"),
  39. br.Config.Homeserver.Domain,
  40. )
  41. userIDRegex = regexp.MustCompile(pattern)
  42. }
  43. match := userIDRegex.FindStringSubmatch(string(mxid))
  44. if len(match) == 2 {
  45. return match[1], true
  46. }
  47. return "", false
  48. }
  49. func (br *DiscordBridge) GetPuppetByMXID(mxid id.UserID) *Puppet {
  50. id, ok := br.ParsePuppetMXID(mxid)
  51. if !ok {
  52. return nil
  53. }
  54. return br.GetPuppetByID(id)
  55. }
  56. func (br *DiscordBridge) GetPuppetByID(id string) *Puppet {
  57. br.puppetsLock.Lock()
  58. defer br.puppetsLock.Unlock()
  59. puppet, ok := br.puppets[id]
  60. if !ok {
  61. dbPuppet := br.DB.Puppet.Get(id)
  62. if dbPuppet == nil {
  63. dbPuppet = br.DB.Puppet.New()
  64. dbPuppet.ID = id
  65. dbPuppet.Insert()
  66. }
  67. puppet = br.NewPuppet(dbPuppet)
  68. br.puppets[puppet.ID] = puppet
  69. }
  70. return puppet
  71. }
  72. func (br *DiscordBridge) GetPuppetByCustomMXID(mxid id.UserID) *Puppet {
  73. br.puppetsLock.Lock()
  74. defer br.puppetsLock.Unlock()
  75. puppet, ok := br.puppetsByCustomMXID[mxid]
  76. if !ok {
  77. dbPuppet := br.DB.Puppet.GetByCustomMXID(mxid)
  78. if dbPuppet == nil {
  79. return nil
  80. }
  81. puppet = br.NewPuppet(dbPuppet)
  82. br.puppets[puppet.ID] = puppet
  83. br.puppetsByCustomMXID[puppet.CustomMXID] = puppet
  84. }
  85. return puppet
  86. }
  87. func (br *DiscordBridge) GetAllPuppetsWithCustomMXID() []*Puppet {
  88. return br.dbPuppetsToPuppets(br.DB.Puppet.GetAllWithCustomMXID())
  89. }
  90. func (br *DiscordBridge) GetAllPuppets() []*Puppet {
  91. return br.dbPuppetsToPuppets(br.DB.Puppet.GetAll())
  92. }
  93. func (br *DiscordBridge) dbPuppetsToPuppets(dbPuppets []*database.Puppet) []*Puppet {
  94. br.puppetsLock.Lock()
  95. defer br.puppetsLock.Unlock()
  96. output := make([]*Puppet, len(dbPuppets))
  97. for index, dbPuppet := range dbPuppets {
  98. if dbPuppet == nil {
  99. continue
  100. }
  101. puppet, ok := br.puppets[dbPuppet.ID]
  102. if !ok {
  103. puppet = br.NewPuppet(dbPuppet)
  104. br.puppets[dbPuppet.ID] = puppet
  105. if dbPuppet.CustomMXID != "" {
  106. br.puppetsByCustomMXID[dbPuppet.CustomMXID] = puppet
  107. }
  108. }
  109. output[index] = puppet
  110. }
  111. return output
  112. }
  113. func (br *DiscordBridge) FormatPuppetMXID(did string) id.UserID {
  114. return id.NewUserID(
  115. br.Config.Bridge.FormatUsername(did),
  116. br.Config.Homeserver.Domain,
  117. )
  118. }
  119. func (puppet *Puppet) DefaultIntent() *appservice.IntentAPI {
  120. return puppet.bridge.AS.Intent(puppet.MXID)
  121. }
  122. func (puppet *Puppet) IntentFor(portal *Portal) *appservice.IntentAPI {
  123. if puppet.customIntent == nil {
  124. return puppet.DefaultIntent()
  125. }
  126. return puppet.customIntent
  127. }
  128. func (puppet *Puppet) CustomIntent() *appservice.IntentAPI {
  129. return puppet.customIntent
  130. }
  131. func (puppet *Puppet) updatePortalMeta(meta func(portal *Portal)) {
  132. for _, portal := range puppet.bridge.GetAllPortalsByID(puppet.ID) {
  133. // Get room create lock to prevent races between receiving contact info and room creation.
  134. portal.roomCreateLock.Lock()
  135. meta(portal)
  136. portal.roomCreateLock.Unlock()
  137. }
  138. }
  139. func (puppet *Puppet) updateName(source *User) bool {
  140. user, err := source.Session.User(puppet.ID)
  141. if err != nil {
  142. puppet.log.Warnln("failed to get user from id:", err)
  143. return false
  144. }
  145. newName := puppet.bridge.Config.Bridge.FormatDisplayname(user)
  146. if puppet.DisplayName != newName {
  147. err := puppet.DefaultIntent().SetDisplayName(newName)
  148. if err == nil {
  149. puppet.DisplayName = newName
  150. go puppet.updatePortalName()
  151. puppet.Update()
  152. } else {
  153. puppet.log.Warnln("failed to set display name:", err)
  154. }
  155. return true
  156. }
  157. return false
  158. }
  159. func (puppet *Puppet) updatePortalName() {
  160. puppet.updatePortalMeta(func(portal *Portal) {
  161. if portal.MXID != "" {
  162. _, err := portal.MainIntent().SetRoomName(portal.MXID, puppet.DisplayName)
  163. if err != nil {
  164. portal.log.Warnln("Failed to set name:", err)
  165. }
  166. }
  167. portal.Name = puppet.DisplayName
  168. portal.Update()
  169. })
  170. }
  171. func (puppet *Puppet) updateAvatar(source *User) bool {
  172. user, err := source.Session.User(puppet.ID)
  173. if err != nil {
  174. puppet.log.Warnln("Failed to get user:", err)
  175. return false
  176. }
  177. if puppet.Avatar == user.Avatar {
  178. return false
  179. }
  180. if user.Avatar == "" {
  181. puppet.log.Warnln("User does not have an avatar")
  182. return false
  183. }
  184. url, err := uploadAvatar(puppet.DefaultIntent(), user.AvatarURL(""))
  185. if err != nil {
  186. puppet.log.Warnln("Failed to upload user avatar:", err)
  187. return false
  188. }
  189. puppet.AvatarURL = url
  190. err = puppet.DefaultIntent().SetAvatarURL(puppet.AvatarURL)
  191. if err != nil {
  192. puppet.log.Warnln("Failed to set avatar:", err)
  193. }
  194. puppet.log.Debugln("Updated avatar", puppet.Avatar, "->", user.Avatar)
  195. puppet.Avatar = user.Avatar
  196. go puppet.updatePortalAvatar()
  197. return true
  198. }
  199. func (puppet *Puppet) updatePortalAvatar() {
  200. puppet.updatePortalMeta(func(portal *Portal) {
  201. if portal.MXID != "" {
  202. _, err := portal.MainIntent().SetRoomAvatar(portal.MXID, puppet.AvatarURL)
  203. if err != nil {
  204. portal.log.Warnln("Failed to set avatar:", err)
  205. }
  206. }
  207. portal.AvatarURL = puppet.AvatarURL
  208. portal.Avatar = puppet.Avatar
  209. portal.Update()
  210. })
  211. }
  212. func (puppet *Puppet) SyncContact(source *User) {
  213. puppet.syncLock.Lock()
  214. defer puppet.syncLock.Unlock()
  215. puppet.log.Debugln("syncing contact", puppet.DisplayName)
  216. err := puppet.DefaultIntent().EnsureRegistered()
  217. if err != nil {
  218. puppet.log.Errorln("Failed to ensure registered:", err)
  219. }
  220. update := false
  221. update = puppet.updateName(source) || update
  222. if puppet.Avatar == "" {
  223. update = puppet.updateAvatar(source) || update
  224. puppet.log.Debugln("update avatar returned", update)
  225. }
  226. if update {
  227. puppet.Update()
  228. }
  229. }