user.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735
  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) readyHandler(s *discordgo.Session, r *discordgo.Ready) {
  286. u.log.Debugln("discord connection ready")
  287. // Update our user fields
  288. u.ID = r.User.ID
  289. // Update our guild map to match watch discord thinks we're in. This is the
  290. // only time we can get the full guild map as discordgo doesn't make it
  291. // available to us later. Also, discord might not give us the full guild
  292. // information here, so we use this to remove guilds the user left and only
  293. // add guilds whose full information we have. The are told about the
  294. // "unavailable" guilds later via the GuildCreate handler.
  295. u.guildsLock.Lock()
  296. defer u.guildsLock.Unlock()
  297. // build a list of the current guilds we're in so we can prune the old ones
  298. current := []string{}
  299. for _, guild := range r.Guilds {
  300. current = append(current, guild.ID)
  301. // If we already know about this guild, make sure we reset it's bridge
  302. // status.
  303. if val, found := u.guilds[guild.ID]; found {
  304. bridge := val.Bridge
  305. u.guilds[guild.ID].Bridge = bridge
  306. // Update the name if the guild is available
  307. if !guild.Unavailable {
  308. u.guilds[guild.ID].GuildName = guild.Name
  309. }
  310. } else {
  311. g := u.bridge.db.Guild.New()
  312. g.DiscordID = u.ID
  313. g.GuildID = guild.ID
  314. u.guilds[guild.ID] = g
  315. if !guild.Unavailable {
  316. g.GuildName = guild.Name
  317. }
  318. }
  319. }
  320. // Sync the guilds to the database.
  321. u.bridge.db.Guild.Prune(u.ID, current)
  322. // Finally reload from the database since it purged servers we're not in
  323. // anymore.
  324. u.loadGuilds()
  325. u.Update()
  326. }
  327. func (u *User) connectedHandler(s *discordgo.Session, c *discordgo.Connect) {
  328. u.log.Debugln("connected to discord")
  329. u.tryAutomaticDoublePuppeting()
  330. }
  331. func (u *User) disconnectedHandler(s *discordgo.Session, d *discordgo.Disconnect) {
  332. u.log.Debugln("disconnected from discord")
  333. }
  334. func (u *User) guildCreateHandler(s *discordgo.Session, g *discordgo.GuildCreate) {
  335. u.guildsLock.Lock()
  336. defer u.guildsLock.Unlock()
  337. // If we somehow already know about the guild, just update it's name
  338. if guild, found := u.guilds[g.ID]; found {
  339. guild.GuildName = g.Name
  340. guild.Upsert()
  341. return
  342. }
  343. // This is a brand new guild so lets get it added.
  344. guild := u.bridge.db.Guild.New()
  345. guild.DiscordID = u.ID
  346. guild.GuildID = g.ID
  347. guild.GuildName = g.Name
  348. guild.Upsert()
  349. u.guilds[g.ID] = guild
  350. }
  351. func (u *User) guildDeleteHandler(s *discordgo.Session, g *discordgo.GuildDelete) {
  352. u.guildsLock.Lock()
  353. defer u.guildsLock.Unlock()
  354. if guild, found := u.guilds[g.ID]; found {
  355. guild.Delete()
  356. delete(u.guilds, g.ID)
  357. u.log.Debugln("deleted guild", g.Guild.ID)
  358. }
  359. }
  360. func (u *User) guildUpdateHandler(s *discordgo.Session, g *discordgo.GuildUpdate) {
  361. u.guildsLock.Lock()
  362. defer u.guildsLock.Unlock()
  363. // If we somehow already know about the guild, just update it's name
  364. if guild, found := u.guilds[g.ID]; found {
  365. guild.GuildName = g.Name
  366. guild.Upsert()
  367. u.log.Debugln("updated guild", g.ID)
  368. }
  369. }
  370. func (u *User) channelCreateHandler(s *discordgo.Session, c *discordgo.ChannelCreate) {
  371. key := database.NewPortalKey(c.ID, u.User.ID)
  372. portal := u.bridge.GetPortalByID(key)
  373. if portal.MXID != "" {
  374. return
  375. }
  376. portal.Name = c.Name
  377. portal.Topic = c.Topic
  378. portal.Type = c.Type
  379. if portal.Type == discordgo.ChannelTypeDM {
  380. portal.DMUser = c.Recipients[0].ID
  381. }
  382. if c.Icon != "" {
  383. u.log.Debugln("channel icon", c.Icon)
  384. }
  385. portal.Update()
  386. portal.createMatrixRoom(u, c.Channel)
  387. }
  388. func (u *User) channelDeleteHandler(s *discordgo.Session, c *discordgo.ChannelDelete) {
  389. u.log.Debugln("channel delete handler")
  390. }
  391. func (u *User) channelPinsUpdateHandler(s *discordgo.Session, c *discordgo.ChannelPinsUpdate) {
  392. u.log.Debugln("channel pins update")
  393. }
  394. func (u *User) channelUpdateHandler(s *discordgo.Session, c *discordgo.ChannelUpdate) {
  395. key := database.NewPortalKey(c.ID, u.User.ID)
  396. portal := u.bridge.GetPortalByID(key)
  397. portal.Name = c.Name
  398. portal.Topic = c.Topic
  399. u.log.Debugln("channel icon", c.Icon)
  400. portal.Update()
  401. u.log.Debugln("channel update")
  402. }
  403. func (u *User) messageCreateHandler(s *discordgo.Session, m *discordgo.MessageCreate) {
  404. if m.GuildID != "" {
  405. u.log.Debugln("ignoring message for guild")
  406. return
  407. }
  408. key := database.NewPortalKey(m.ChannelID, u.ID)
  409. portal := u.bridge.GetPortalByID(key)
  410. msg := portalDiscordMessage{
  411. msg: m,
  412. user: u,
  413. }
  414. portal.discordMessages <- msg
  415. }
  416. func (u *User) messageDeleteHandler(s *discordgo.Session, m *discordgo.MessageDelete) {
  417. if m.GuildID != "" {
  418. u.log.Debugln("ignoring message delete for guild message")
  419. return
  420. }
  421. key := database.NewPortalKey(m.ChannelID, u.ID)
  422. portal := u.bridge.GetPortalByID(key)
  423. msg := portalDiscordMessage{
  424. msg: m,
  425. user: u,
  426. }
  427. portal.discordMessages <- msg
  428. }
  429. func (u *User) messageUpdateHandler(s *discordgo.Session, m *discordgo.MessageUpdate) {
  430. if m.GuildID != "" {
  431. u.log.Debugln("ignoring message update for guild message")
  432. return
  433. }
  434. key := database.NewPortalKey(m.ChannelID, u.ID)
  435. portal := u.bridge.GetPortalByID(key)
  436. msg := portalDiscordMessage{
  437. msg: m,
  438. user: u,
  439. }
  440. portal.discordMessages <- msg
  441. }
  442. func (u *User) reactionAddHandler(s *discordgo.Session, m *discordgo.MessageReactionAdd) {
  443. if m.GuildID != "" {
  444. u.log.Debugln("ignoring reaction for guild message")
  445. return
  446. }
  447. key := database.NewPortalKey(m.ChannelID, u.User.ID)
  448. portal := u.bridge.GetPortalByID(key)
  449. msg := portalDiscordMessage{
  450. msg: m,
  451. user: u,
  452. }
  453. portal.discordMessages <- msg
  454. }
  455. func (u *User) reactionRemoveHandler(s *discordgo.Session, m *discordgo.MessageReactionRemove) {
  456. if m.GuildID != "" {
  457. u.log.Debugln("ignoring reaction for guild message")
  458. return
  459. }
  460. key := database.NewPortalKey(m.ChannelID, u.User.ID)
  461. portal := u.bridge.GetPortalByID(key)
  462. msg := portalDiscordMessage{
  463. msg: m,
  464. user: u,
  465. }
  466. portal.discordMessages <- msg
  467. }
  468. func (u *User) ensureInvited(intent *appservice.IntentAPI, roomID id.RoomID, isDirect bool) bool {
  469. ret := false
  470. inviteContent := event.Content{
  471. Parsed: &event.MemberEventContent{
  472. Membership: event.MembershipInvite,
  473. IsDirect: isDirect,
  474. },
  475. Raw: map[string]interface{}{},
  476. }
  477. customPuppet := u.bridge.GetPuppetByCustomMXID(u.MXID)
  478. if customPuppet != nil && customPuppet.CustomIntent() != nil {
  479. inviteContent.Raw["fi.mau.will_auto_accept"] = true
  480. }
  481. _, err := intent.SendStateEvent(roomID, event.StateMember, u.MXID.String(), &inviteContent)
  482. var httpErr mautrix.HTTPError
  483. if err != nil && errors.As(err, &httpErr) && httpErr.RespError != nil && strings.Contains(httpErr.RespError.Err, "is already in the room") {
  484. u.bridge.StateStore.SetMembership(roomID, u.MXID, event.MembershipJoin)
  485. ret = true
  486. } else if err != nil {
  487. u.log.Warnfln("Failed to invite user to %s: %v", roomID, err)
  488. } else {
  489. ret = true
  490. }
  491. if customPuppet != nil && customPuppet.CustomIntent() != nil {
  492. err = customPuppet.CustomIntent().EnsureJoined(roomID, appservice.EnsureJoinedParams{IgnoreCache: true})
  493. if err != nil {
  494. u.log.Warnfln("Failed to auto-join %s: %v", roomID, err)
  495. ret = false
  496. } else {
  497. ret = true
  498. }
  499. }
  500. return ret
  501. }
  502. func (u *User) getDirectChats() map[id.UserID][]id.RoomID {
  503. chats := map[id.UserID][]id.RoomID{}
  504. privateChats := u.bridge.db.Portal.FindPrivateChats(u.ID)
  505. for _, portal := range privateChats {
  506. if portal.MXID != "" {
  507. puppetMXID := u.bridge.FormatPuppetMXID(portal.Key.Receiver)
  508. chats[puppetMXID] = []id.RoomID{portal.MXID}
  509. }
  510. }
  511. return chats
  512. }
  513. func (u *User) updateDirectChats(chats map[id.UserID][]id.RoomID) {
  514. if !u.bridge.Config.Bridge.SyncDirectChatList {
  515. return
  516. }
  517. puppet := u.bridge.GetPuppetByMXID(u.MXID)
  518. if puppet == nil {
  519. return
  520. }
  521. intent := puppet.CustomIntent()
  522. if intent == nil {
  523. return
  524. }
  525. method := http.MethodPatch
  526. if chats == nil {
  527. chats = u.getDirectChats()
  528. method = http.MethodPut
  529. }
  530. u.log.Debugln("Updating m.direct list on homeserver")
  531. var err error
  532. if u.bridge.Config.Homeserver.Asmux {
  533. urlPath := intent.BuildBaseURL("_matrix", "client", "unstable", "com.beeper.asmux", "dms")
  534. _, err = intent.MakeFullRequest(mautrix.FullRequest{
  535. Method: method,
  536. URL: urlPath,
  537. Headers: http.Header{"X-Asmux-Auth": {u.bridge.as.Registration.AppToken}},
  538. RequestJSON: chats,
  539. })
  540. } else {
  541. existingChats := map[id.UserID][]id.RoomID{}
  542. err = intent.GetAccountData(event.AccountDataDirectChats.Type, &existingChats)
  543. if err != nil {
  544. u.log.Warnln("Failed to get m.direct list to update it:", err)
  545. return
  546. }
  547. for userID, rooms := range existingChats {
  548. if _, ok := u.bridge.ParsePuppetMXID(userID); !ok {
  549. // This is not a ghost user, include it in the new list
  550. chats[userID] = rooms
  551. } else if _, ok := chats[userID]; !ok && method == http.MethodPatch {
  552. // This is a ghost user, but we're not replacing the whole list, so include it too
  553. chats[userID] = rooms
  554. }
  555. }
  556. err = intent.SetAccountData(event.AccountDataDirectChats.Type, &chats)
  557. }
  558. if err != nil {
  559. u.log.Warnln("Failed to update m.direct list:", err)
  560. }
  561. }