user.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740
  1. package bridge
  2. import (
  3. "errors"
  4. "fmt"
  5. "net/http"
  6. "strings"
  7. "sync"
  8. "github.com/bwmarrin/discordgo"
  9. "github.com/skip2/go-qrcode"
  10. log "maunium.net/go/maulogger/v2"
  11. "maunium.net/go/mautrix"
  12. "maunium.net/go/mautrix/appservice"
  13. "maunium.net/go/mautrix/event"
  14. "maunium.net/go/mautrix/id"
  15. "gitlab.com/beeper/discord/database"
  16. )
  17. var (
  18. ErrNotConnected = errors.New("not connected")
  19. ErrNotLoggedIn = errors.New("not logged in")
  20. )
  21. type User struct {
  22. *database.User
  23. sync.Mutex
  24. bridge *Bridge
  25. log log.Logger
  26. guilds map[string]*database.Guild
  27. guildsLock sync.Mutex
  28. Session *discordgo.Session
  29. }
  30. // this assume you are holding the guilds lock!!!
  31. func (u *User) loadGuilds() {
  32. u.guilds = map[string]*database.Guild{}
  33. for _, guild := range u.bridge.db.Guild.GetAll(u.ID) {
  34. u.guilds[guild.GuildID] = guild
  35. }
  36. }
  37. func (b *Bridge) loadUser(dbUser *database.User, mxid *id.UserID) *User {
  38. // If we weren't passed in a user we attempt to create one if we were given
  39. // a matrix id.
  40. if dbUser == nil {
  41. if mxid == nil {
  42. return nil
  43. }
  44. dbUser = b.db.User.New()
  45. dbUser.MXID = *mxid
  46. dbUser.Insert()
  47. }
  48. user := b.NewUser(dbUser)
  49. // We assume the usersLock was acquired by our caller.
  50. b.usersByMXID[user.MXID] = user
  51. if user.ID != "" {
  52. b.usersByID[user.ID] = user
  53. }
  54. if user.ManagementRoom != "" {
  55. // Lock the management rooms for our update
  56. b.managementRoomsLock.Lock()
  57. b.managementRooms[user.ManagementRoom] = user
  58. b.managementRoomsLock.Unlock()
  59. }
  60. // Load our guilds state from the database and turn it into a map
  61. user.guildsLock.Lock()
  62. user.loadGuilds()
  63. user.guildsLock.Unlock()
  64. return user
  65. }
  66. func (b *Bridge) GetUserByMXID(userID id.UserID) *User {
  67. // TODO: check if puppet
  68. b.usersLock.Lock()
  69. defer b.usersLock.Unlock()
  70. user, ok := b.usersByMXID[userID]
  71. if !ok {
  72. return b.loadUser(b.db.User.GetByMXID(userID), &userID)
  73. }
  74. return user
  75. }
  76. func (b *Bridge) GetUserByID(id string) *User {
  77. b.usersLock.Lock()
  78. defer b.usersLock.Unlock()
  79. user, ok := b.usersByID[id]
  80. if !ok {
  81. return b.loadUser(b.db.User.GetByID(id), nil)
  82. }
  83. return user
  84. }
  85. func (b *Bridge) NewUser(dbUser *database.User) *User {
  86. user := &User{
  87. User: dbUser,
  88. bridge: b,
  89. log: b.log.Sub("User").Sub(string(dbUser.MXID)),
  90. guilds: map[string]*database.Guild{},
  91. }
  92. return user
  93. }
  94. func (b *Bridge) getAllUsers() []*User {
  95. b.usersLock.Lock()
  96. defer b.usersLock.Unlock()
  97. dbUsers := b.db.User.GetAll()
  98. users := make([]*User, len(dbUsers))
  99. for idx, dbUser := range dbUsers {
  100. user, ok := b.usersByMXID[dbUser.MXID]
  101. if !ok {
  102. user = b.loadUser(dbUser, nil)
  103. }
  104. users[idx] = user
  105. }
  106. return users
  107. }
  108. func (b *Bridge) startUsers() {
  109. b.log.Debugln("Starting users")
  110. for _, user := range b.getAllUsers() {
  111. go user.Connect()
  112. }
  113. b.log.Debugln("Starting custom puppets")
  114. for _, customPuppet := range b.GetAllPuppetsWithCustomMXID() {
  115. go func(puppet *Puppet) {
  116. b.log.Debugln("Starting custom puppet", puppet.CustomMXID)
  117. if err := puppet.StartCustomMXID(true); err != nil {
  118. puppet.log.Errorln("Failed to start custom puppet:", err)
  119. }
  120. }(customPuppet)
  121. }
  122. }
  123. func (u *User) SetManagementRoom(roomID id.RoomID) {
  124. u.bridge.managementRoomsLock.Lock()
  125. defer u.bridge.managementRoomsLock.Unlock()
  126. existing, ok := u.bridge.managementRooms[roomID]
  127. if ok {
  128. // If there's a user already assigned to this management room, clear it
  129. // out.
  130. // I think this is due a name change or something? I dunno, leaving it
  131. // for now.
  132. existing.ManagementRoom = ""
  133. existing.Update()
  134. }
  135. u.ManagementRoom = roomID
  136. u.bridge.managementRooms[u.ManagementRoom] = u
  137. u.Update()
  138. }
  139. func (u *User) sendQRCode(bot *appservice.IntentAPI, roomID id.RoomID, code string) (id.EventID, error) {
  140. url, err := u.uploadQRCode(code)
  141. if err != nil {
  142. return "", err
  143. }
  144. content := event.MessageEventContent{
  145. MsgType: event.MsgImage,
  146. Body: code,
  147. URL: url.CUString(),
  148. }
  149. resp, err := bot.SendMessageEvent(roomID, event.EventMessage, &content)
  150. if err != nil {
  151. return "", err
  152. }
  153. return resp.EventID, nil
  154. }
  155. func (u *User) uploadQRCode(code string) (id.ContentURI, error) {
  156. qrCode, err := qrcode.Encode(code, qrcode.Low, 256)
  157. if err != nil {
  158. u.log.Errorln("Failed to encode QR code:", err)
  159. return id.ContentURI{}, err
  160. }
  161. bot := u.bridge.as.BotClient()
  162. resp, err := bot.UploadBytes(qrCode, "image/png")
  163. if err != nil {
  164. u.log.Errorln("Failed to upload QR code:", err)
  165. return id.ContentURI{}, err
  166. }
  167. return resp.ContentURI, nil
  168. }
  169. func (u *User) tryAutomaticDoublePuppeting() {
  170. u.Lock()
  171. defer u.Unlock()
  172. if !u.bridge.Config.CanAutoDoublePuppet(u.MXID) {
  173. return
  174. }
  175. u.log.Debugln("Checking if double puppeting needs to be enabled")
  176. puppet := u.bridge.GetPuppetByID(u.ID)
  177. if puppet.CustomMXID != "" {
  178. u.log.Debugln("User already has double-puppeting enabled")
  179. return
  180. }
  181. accessToken, err := puppet.loginWithSharedSecret(u.MXID)
  182. if err != nil {
  183. u.log.Warnln("Failed to login with shared secret:", err)
  184. return
  185. }
  186. err = puppet.SwitchCustomMXID(accessToken, u.MXID)
  187. if err != nil {
  188. puppet.log.Warnln("Failed to switch to auto-logined custom puppet:", err)
  189. return
  190. }
  191. u.log.Infoln("Successfully automatically enabled custom puppet")
  192. }
  193. func (u *User) syncChatDoublePuppetDetails(portal *Portal, justCreated bool) {
  194. doublePuppet := portal.bridge.GetPuppetByCustomMXID(u.MXID)
  195. if doublePuppet == nil {
  196. return
  197. }
  198. if doublePuppet == nil || doublePuppet.CustomIntent() == nil || portal.MXID == "" {
  199. return
  200. }
  201. // TODO sync mute status
  202. }
  203. func (u *User) Login(token string) error {
  204. if token == "" {
  205. return fmt.Errorf("No token specified")
  206. }
  207. u.Token = token
  208. u.Update()
  209. return u.Connect()
  210. }
  211. func (u *User) LoggedIn() bool {
  212. u.Lock()
  213. defer u.Unlock()
  214. return u.Token != ""
  215. }
  216. func (u *User) Logout() error {
  217. u.Lock()
  218. defer u.Unlock()
  219. if u.Session == nil {
  220. return ErrNotLoggedIn
  221. }
  222. puppet := u.bridge.GetPuppetByID(u.ID)
  223. if puppet.CustomMXID != "" {
  224. err := puppet.SwitchCustomMXID("", "")
  225. if err != nil {
  226. u.log.Warnln("Failed to logout-matrix while logging out of Discord:", err)
  227. }
  228. }
  229. if err := u.Session.Close(); err != nil {
  230. return err
  231. }
  232. u.Session = nil
  233. u.Token = ""
  234. u.Update()
  235. return nil
  236. }
  237. func (u *User) Connected() bool {
  238. u.Lock()
  239. defer u.Unlock()
  240. return u.Session != nil
  241. }
  242. func (u *User) Connect() error {
  243. u.Lock()
  244. defer u.Unlock()
  245. if u.Token == "" {
  246. return ErrNotLoggedIn
  247. }
  248. u.log.Debugln("connecting to discord")
  249. session, err := discordgo.New(u.Token)
  250. if err != nil {
  251. return err
  252. }
  253. u.Session = session
  254. // Add our event handlers
  255. u.Session.AddHandler(u.readyHandler)
  256. u.Session.AddHandler(u.connectedHandler)
  257. u.Session.AddHandler(u.disconnectedHandler)
  258. u.Session.AddHandler(u.guildCreateHandler)
  259. u.Session.AddHandler(u.guildDeleteHandler)
  260. u.Session.AddHandler(u.guildUpdateHandler)
  261. u.Session.AddHandler(u.channelCreateHandler)
  262. u.Session.AddHandler(u.channelDeleteHandler)
  263. u.Session.AddHandler(u.channelPinsUpdateHandler)
  264. u.Session.AddHandler(u.channelUpdateHandler)
  265. u.Session.AddHandler(u.messageCreateHandler)
  266. u.Session.AddHandler(u.messageDeleteHandler)
  267. u.Session.AddHandler(u.messageUpdateHandler)
  268. u.Session.AddHandler(u.reactionAddHandler)
  269. u.Session.AddHandler(u.reactionRemoveHandler)
  270. u.Session.Identify.Presence.Status = "online"
  271. return u.Session.Open()
  272. }
  273. func (u *User) Disconnect() error {
  274. u.Lock()
  275. defer u.Unlock()
  276. if u.Session == nil {
  277. return ErrNotConnected
  278. }
  279. if err := u.Session.Close(); err != nil {
  280. return err
  281. }
  282. u.Session = nil
  283. return nil
  284. }
  285. func (u *User) bridgeMessage(guildID string) bool {
  286. // Non guild message always get bridged.
  287. if guildID == "" {
  288. return true
  289. }
  290. u.guildsLock.Lock()
  291. defer u.guildsLock.Unlock()
  292. if guild, found := u.guilds[guildID]; found {
  293. if guild.Bridge {
  294. return true
  295. }
  296. }
  297. u.log.Debugfln("ignoring message for non-bridged guild %s-%s", u.ID, guildID)
  298. return false
  299. }
  300. func (u *User) readyHandler(s *discordgo.Session, r *discordgo.Ready) {
  301. u.log.Debugln("discord connection ready")
  302. // Update our user fields
  303. u.ID = r.User.ID
  304. // Update our guild map to match watch discord thinks we're in. This is the
  305. // only time we can get the full guild map as discordgo doesn't make it
  306. // available to us later. Also, discord might not give us the full guild
  307. // information here, so we use this to remove guilds the user left and only
  308. // add guilds whose full information we have. The are told about the
  309. // "unavailable" guilds later via the GuildCreate handler.
  310. u.guildsLock.Lock()
  311. defer u.guildsLock.Unlock()
  312. // build a list of the current guilds we're in so we can prune the old ones
  313. current := []string{}
  314. for _, guild := range r.Guilds {
  315. current = append(current, guild.ID)
  316. // If we already know about this guild, make sure we reset it's bridge
  317. // status.
  318. if val, found := u.guilds[guild.ID]; found {
  319. bridge := val.Bridge
  320. u.guilds[guild.ID].Bridge = bridge
  321. // Update the name if the guild is available
  322. if !guild.Unavailable {
  323. u.guilds[guild.ID].GuildName = guild.Name
  324. }
  325. } else {
  326. g := u.bridge.db.Guild.New()
  327. g.DiscordID = u.ID
  328. g.GuildID = guild.ID
  329. u.guilds[guild.ID] = g
  330. if !guild.Unavailable {
  331. g.GuildName = guild.Name
  332. }
  333. }
  334. }
  335. // Sync the guilds to the database.
  336. u.bridge.db.Guild.Prune(u.ID, current)
  337. // Finally reload from the database since it purged servers we're not in
  338. // anymore.
  339. u.loadGuilds()
  340. u.Update()
  341. }
  342. func (u *User) connectedHandler(s *discordgo.Session, c *discordgo.Connect) {
  343. u.log.Debugln("connected to discord")
  344. u.tryAutomaticDoublePuppeting()
  345. }
  346. func (u *User) disconnectedHandler(s *discordgo.Session, d *discordgo.Disconnect) {
  347. u.log.Debugln("disconnected from discord")
  348. }
  349. func (u *User) guildCreateHandler(s *discordgo.Session, g *discordgo.GuildCreate) {
  350. u.guildsLock.Lock()
  351. defer u.guildsLock.Unlock()
  352. // If we somehow already know about the guild, just update it's name
  353. if guild, found := u.guilds[g.ID]; found {
  354. guild.GuildName = g.Name
  355. guild.Upsert()
  356. return
  357. }
  358. // This is a brand new guild so lets get it added.
  359. guild := u.bridge.db.Guild.New()
  360. guild.DiscordID = u.ID
  361. guild.GuildID = g.ID
  362. guild.GuildName = g.Name
  363. guild.Upsert()
  364. u.guilds[g.ID] = guild
  365. }
  366. func (u *User) guildDeleteHandler(s *discordgo.Session, g *discordgo.GuildDelete) {
  367. u.guildsLock.Lock()
  368. defer u.guildsLock.Unlock()
  369. if guild, found := u.guilds[g.ID]; found {
  370. guild.Delete()
  371. delete(u.guilds, g.ID)
  372. u.log.Debugln("deleted guild", g.Guild.ID)
  373. }
  374. }
  375. func (u *User) guildUpdateHandler(s *discordgo.Session, g *discordgo.GuildUpdate) {
  376. u.guildsLock.Lock()
  377. defer u.guildsLock.Unlock()
  378. // If we somehow already know about the guild, just update it's name
  379. if guild, found := u.guilds[g.ID]; found {
  380. guild.GuildName = g.Name
  381. guild.Upsert()
  382. u.log.Debugln("updated guild", g.ID)
  383. }
  384. }
  385. func (u *User) channelCreateHandler(s *discordgo.Session, c *discordgo.ChannelCreate) {
  386. key := database.NewPortalKey(c.ID, u.User.ID)
  387. portal := u.bridge.GetPortalByID(key)
  388. if portal.MXID != "" {
  389. return
  390. }
  391. portal.Name = c.Name
  392. portal.Topic = c.Topic
  393. portal.Type = c.Type
  394. if portal.Type == discordgo.ChannelTypeDM {
  395. portal.DMUser = c.Recipients[0].ID
  396. }
  397. if c.Icon != "" {
  398. u.log.Debugln("channel icon", c.Icon)
  399. }
  400. portal.Update()
  401. portal.createMatrixRoom(u, c.Channel)
  402. }
  403. func (u *User) channelDeleteHandler(s *discordgo.Session, c *discordgo.ChannelDelete) {
  404. u.log.Debugln("channel delete handler")
  405. }
  406. func (u *User) channelPinsUpdateHandler(s *discordgo.Session, c *discordgo.ChannelPinsUpdate) {
  407. u.log.Debugln("channel pins update")
  408. }
  409. func (u *User) channelUpdateHandler(s *discordgo.Session, c *discordgo.ChannelUpdate) {
  410. key := database.NewPortalKey(c.ID, u.User.ID)
  411. portal := u.bridge.GetPortalByID(key)
  412. portal.update(u, c.Channel)
  413. }
  414. func (u *User) messageCreateHandler(s *discordgo.Session, m *discordgo.MessageCreate) {
  415. if !u.bridgeMessage(m.GuildID) {
  416. return
  417. }
  418. key := database.NewPortalKey(m.ChannelID, u.ID)
  419. portal := u.bridge.GetPortalByID(key)
  420. msg := portalDiscordMessage{
  421. msg: m,
  422. user: u,
  423. }
  424. portal.discordMessages <- msg
  425. }
  426. func (u *User) messageDeleteHandler(s *discordgo.Session, m *discordgo.MessageDelete) {
  427. if !u.bridgeMessage(m.GuildID) {
  428. return
  429. }
  430. key := database.NewPortalKey(m.ChannelID, u.ID)
  431. portal := u.bridge.GetPortalByID(key)
  432. msg := portalDiscordMessage{
  433. msg: m,
  434. user: u,
  435. }
  436. portal.discordMessages <- msg
  437. }
  438. func (u *User) messageUpdateHandler(s *discordgo.Session, m *discordgo.MessageUpdate) {
  439. if !u.bridgeMessage(m.GuildID) {
  440. return
  441. }
  442. key := database.NewPortalKey(m.ChannelID, u.ID)
  443. portal := u.bridge.GetPortalByID(key)
  444. msg := portalDiscordMessage{
  445. msg: m,
  446. user: u,
  447. }
  448. portal.discordMessages <- msg
  449. }
  450. func (u *User) reactionAddHandler(s *discordgo.Session, m *discordgo.MessageReactionAdd) {
  451. if !u.bridgeMessage(m.MessageReaction.GuildID) {
  452. return
  453. }
  454. key := database.NewPortalKey(m.ChannelID, u.User.ID)
  455. portal := u.bridge.GetPortalByID(key)
  456. msg := portalDiscordMessage{
  457. msg: m,
  458. user: u,
  459. }
  460. portal.discordMessages <- msg
  461. }
  462. func (u *User) reactionRemoveHandler(s *discordgo.Session, m *discordgo.MessageReactionRemove) {
  463. if !u.bridgeMessage(m.MessageReaction.GuildID) {
  464. return
  465. }
  466. key := database.NewPortalKey(m.ChannelID, u.User.ID)
  467. portal := u.bridge.GetPortalByID(key)
  468. msg := portalDiscordMessage{
  469. msg: m,
  470. user: u,
  471. }
  472. portal.discordMessages <- msg
  473. }
  474. func (u *User) ensureInvited(intent *appservice.IntentAPI, roomID id.RoomID, isDirect bool) bool {
  475. ret := false
  476. inviteContent := event.Content{
  477. Parsed: &event.MemberEventContent{
  478. Membership: event.MembershipInvite,
  479. IsDirect: isDirect,
  480. },
  481. Raw: map[string]interface{}{},
  482. }
  483. customPuppet := u.bridge.GetPuppetByCustomMXID(u.MXID)
  484. if customPuppet != nil && customPuppet.CustomIntent() != nil {
  485. inviteContent.Raw["fi.mau.will_auto_accept"] = true
  486. }
  487. _, err := intent.SendStateEvent(roomID, event.StateMember, u.MXID.String(), &inviteContent)
  488. var httpErr mautrix.HTTPError
  489. if err != nil && errors.As(err, &httpErr) && httpErr.RespError != nil && strings.Contains(httpErr.RespError.Err, "is already in the room") {
  490. u.bridge.StateStore.SetMembership(roomID, u.MXID, event.MembershipJoin)
  491. ret = true
  492. } else if err != nil {
  493. u.log.Warnfln("Failed to invite user to %s: %v", roomID, err)
  494. } else {
  495. ret = true
  496. }
  497. if customPuppet != nil && customPuppet.CustomIntent() != nil {
  498. err = customPuppet.CustomIntent().EnsureJoined(roomID, appservice.EnsureJoinedParams{IgnoreCache: true})
  499. if err != nil {
  500. u.log.Warnfln("Failed to auto-join %s: %v", roomID, err)
  501. ret = false
  502. } else {
  503. ret = true
  504. }
  505. }
  506. return ret
  507. }
  508. func (u *User) getDirectChats() map[id.UserID][]id.RoomID {
  509. chats := map[id.UserID][]id.RoomID{}
  510. privateChats := u.bridge.db.Portal.FindPrivateChats(u.ID)
  511. for _, portal := range privateChats {
  512. if portal.MXID != "" {
  513. puppetMXID := u.bridge.FormatPuppetMXID(portal.Key.Receiver)
  514. chats[puppetMXID] = []id.RoomID{portal.MXID}
  515. }
  516. }
  517. return chats
  518. }
  519. func (u *User) updateDirectChats(chats map[id.UserID][]id.RoomID) {
  520. if !u.bridge.Config.Bridge.SyncDirectChatList {
  521. return
  522. }
  523. puppet := u.bridge.GetPuppetByMXID(u.MXID)
  524. if puppet == nil {
  525. return
  526. }
  527. intent := puppet.CustomIntent()
  528. if intent == nil {
  529. return
  530. }
  531. method := http.MethodPatch
  532. if chats == nil {
  533. chats = u.getDirectChats()
  534. method = http.MethodPut
  535. }
  536. u.log.Debugln("Updating m.direct list on homeserver")
  537. var err error
  538. if u.bridge.Config.Homeserver.Asmux {
  539. urlPath := intent.BuildBaseURL("_matrix", "client", "unstable", "com.beeper.asmux", "dms")
  540. _, err = intent.MakeFullRequest(mautrix.FullRequest{
  541. Method: method,
  542. URL: urlPath,
  543. Headers: http.Header{"X-Asmux-Auth": {u.bridge.as.Registration.AppToken}},
  544. RequestJSON: chats,
  545. })
  546. } else {
  547. existingChats := map[id.UserID][]id.RoomID{}
  548. err = intent.GetAccountData(event.AccountDataDirectChats.Type, &existingChats)
  549. if err != nil {
  550. u.log.Warnln("Failed to get m.direct list to update it:", err)
  551. return
  552. }
  553. for userID, rooms := range existingChats {
  554. if _, ok := u.bridge.ParsePuppetMXID(userID); !ok {
  555. // This is not a ghost user, include it in the new list
  556. chats[userID] = rooms
  557. } else if _, ok := chats[userID]; !ok && method == http.MethodPatch {
  558. // This is a ghost user, but we're not replacing the whole list, so include it too
  559. chats[userID] = rooms
  560. }
  561. }
  562. err = intent.SetAccountData(event.AccountDataDirectChats.Type, &chats)
  563. }
  564. if err != nil {
  565. u.log.Warnln("Failed to update m.direct list:", err)
  566. }
  567. }