user.go 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463
  1. package bridge
  2. import (
  3. "errors"
  4. "fmt"
  5. "strings"
  6. "sync"
  7. "github.com/bwmarrin/discordgo"
  8. "github.com/skip2/go-qrcode"
  9. log "maunium.net/go/maulogger/v2"
  10. "maunium.net/go/mautrix"
  11. "maunium.net/go/mautrix/appservice"
  12. "maunium.net/go/mautrix/event"
  13. "maunium.net/go/mautrix/id"
  14. "gitlab.com/beeper/discord/database"
  15. )
  16. var (
  17. ErrNotConnected = errors.New("not connected")
  18. ErrNotLoggedIn = errors.New("not logged in")
  19. )
  20. type User struct {
  21. *database.User
  22. sync.Mutex
  23. bridge *Bridge
  24. log log.Logger
  25. Session *discordgo.Session
  26. }
  27. func (b *Bridge) loadUser(dbUser *database.User, mxid *id.UserID) *User {
  28. // If we weren't passed in a user we attempt to create one if we were given
  29. // a matrix id.
  30. if dbUser == nil {
  31. if mxid == nil {
  32. return nil
  33. }
  34. dbUser = b.db.User.New()
  35. dbUser.MXID = *mxid
  36. dbUser.Insert()
  37. }
  38. user := b.NewUser(dbUser)
  39. // We assume the usersLock was acquired by our caller.
  40. b.usersByMXID[user.MXID] = user
  41. if user.ID != "" {
  42. b.usersByID[user.ID] = user
  43. }
  44. if user.ManagementRoom != "" {
  45. // Lock the management rooms for our update
  46. b.managementRoomsLock.Lock()
  47. b.managementRooms[user.ManagementRoom] = user
  48. b.managementRoomsLock.Unlock()
  49. }
  50. return user
  51. }
  52. func (b *Bridge) GetUserByMXID(userID id.UserID) *User {
  53. // TODO: check if puppet
  54. b.usersLock.Lock()
  55. defer b.usersLock.Unlock()
  56. user, ok := b.usersByMXID[userID]
  57. if !ok {
  58. return b.loadUser(b.db.User.GetByMXID(userID), &userID)
  59. }
  60. return user
  61. }
  62. func (b *Bridge) GetUserByID(id string) *User {
  63. b.usersLock.Lock()
  64. defer b.usersLock.Unlock()
  65. user, ok := b.usersByID[id]
  66. if !ok {
  67. return b.loadUser(b.db.User.GetByID(id), nil)
  68. }
  69. return user
  70. }
  71. func (b *Bridge) NewUser(dbUser *database.User) *User {
  72. user := &User{
  73. User: dbUser,
  74. bridge: b,
  75. log: b.log.Sub("User").Sub(string(dbUser.MXID)),
  76. }
  77. return user
  78. }
  79. func (b *Bridge) getAllUsers() []*User {
  80. b.usersLock.Lock()
  81. defer b.usersLock.Unlock()
  82. dbUsers := b.db.User.GetAll()
  83. users := make([]*User, len(dbUsers))
  84. for idx, dbUser := range dbUsers {
  85. user, ok := b.usersByMXID[dbUser.MXID]
  86. if !ok {
  87. user = b.loadUser(dbUser, nil)
  88. }
  89. users[idx] = user
  90. }
  91. return users
  92. }
  93. func (b *Bridge) startUsers() {
  94. b.log.Debugln("Starting users")
  95. for _, user := range b.getAllUsers() {
  96. // if user.ID != "" {
  97. // haveSessions = true
  98. // }
  99. go user.Connect()
  100. }
  101. }
  102. func (u *User) SetManagementRoom(roomID id.RoomID) {
  103. u.bridge.managementRoomsLock.Lock()
  104. defer u.bridge.managementRoomsLock.Unlock()
  105. existing, ok := u.bridge.managementRooms[roomID]
  106. if ok {
  107. // If there's a user already assigned to this management room, clear it
  108. // out.
  109. // I think this is due a name change or something? I dunno, leaving it
  110. // for now.
  111. existing.ManagementRoom = ""
  112. existing.Update()
  113. }
  114. u.ManagementRoom = roomID
  115. u.bridge.managementRooms[u.ManagementRoom] = u
  116. u.Update()
  117. }
  118. func (u *User) sendQRCode(bot *appservice.IntentAPI, roomID id.RoomID, code string) (id.EventID, error) {
  119. url, err := u.uploadQRCode(code)
  120. if err != nil {
  121. return "", err
  122. }
  123. content := event.MessageEventContent{
  124. MsgType: event.MsgImage,
  125. Body: code,
  126. URL: url.CUString(),
  127. }
  128. resp, err := bot.SendMessageEvent(roomID, event.EventMessage, &content)
  129. if err != nil {
  130. return "", err
  131. }
  132. return resp.EventID, nil
  133. }
  134. func (u *User) uploadQRCode(code string) (id.ContentURI, error) {
  135. qrCode, err := qrcode.Encode(code, qrcode.Low, 256)
  136. if err != nil {
  137. u.log.Errorln("Failed to encode QR code:", err)
  138. return id.ContentURI{}, err
  139. }
  140. bot := u.bridge.as.BotClient()
  141. resp, err := bot.UploadBytes(qrCode, "image/png")
  142. if err != nil {
  143. u.log.Errorln("Failed to upload QR code:", err)
  144. return id.ContentURI{}, err
  145. }
  146. return resp.ContentURI, nil
  147. }
  148. func (u *User) Login(token string) error {
  149. if token == "" {
  150. return fmt.Errorf("No token specified")
  151. }
  152. u.Token = token
  153. u.Update()
  154. return u.Connect()
  155. }
  156. func (u *User) LoggedIn() bool {
  157. u.Lock()
  158. defer u.Unlock()
  159. return u.Token != ""
  160. }
  161. func (u *User) Logout() error {
  162. u.Lock()
  163. defer u.Unlock()
  164. if u.Session == nil {
  165. return ErrNotLoggedIn
  166. }
  167. if err := u.Session.Close(); err != nil {
  168. return err
  169. }
  170. u.Session = nil
  171. u.Token = ""
  172. u.Update()
  173. return nil
  174. }
  175. func (u *User) Connected() bool {
  176. u.Lock()
  177. defer u.Unlock()
  178. return u.Session != nil
  179. }
  180. func (u *User) Connect() error {
  181. u.Lock()
  182. defer u.Unlock()
  183. if u.Token == "" {
  184. return ErrNotLoggedIn
  185. }
  186. u.log.Debugln("connecting to discord")
  187. session, err := discordgo.New(u.Token)
  188. if err != nil {
  189. return err
  190. }
  191. u.Session = session
  192. // get our user info
  193. user, err := u.Session.User("@me")
  194. if err != nil {
  195. return err
  196. }
  197. u.User.ID = user.ID
  198. // Add our event handlers
  199. u.Session.AddHandler(u.connectedHandler)
  200. u.Session.AddHandler(u.disconnectedHandler)
  201. u.Session.AddHandler(u.channelCreateHandler)
  202. u.Session.AddHandler(u.channelDeleteHandler)
  203. u.Session.AddHandler(u.channelPinsUpdateHandler)
  204. u.Session.AddHandler(u.channelUpdateHandler)
  205. u.Session.AddHandler(u.messageCreateHandler)
  206. u.Session.AddHandler(u.messageDeleteHandler)
  207. u.Session.AddHandler(u.messageUpdateHandler)
  208. u.Session.AddHandler(u.reactionAddHandler)
  209. u.Session.AddHandler(u.reactionRemoveHandler)
  210. u.Session.Identify.Presence.Status = "online"
  211. return u.Session.Open()
  212. }
  213. func (u *User) Disconnect() error {
  214. u.Lock()
  215. defer u.Unlock()
  216. if u.Session == nil {
  217. return ErrNotConnected
  218. }
  219. if err := u.Session.Close(); err != nil {
  220. return err
  221. }
  222. u.Session = nil
  223. return nil
  224. }
  225. func (u *User) connectedHandler(s *discordgo.Session, c *discordgo.Connect) {
  226. u.log.Debugln("connected to discord")
  227. }
  228. func (u *User) disconnectedHandler(s *discordgo.Session, d *discordgo.Disconnect) {
  229. u.log.Debugln("disconnected from discord")
  230. }
  231. func (u *User) channelCreateHandler(s *discordgo.Session, c *discordgo.ChannelCreate) {
  232. key := database.NewPortalKey(c.ID, u.User.ID)
  233. portal := u.bridge.GetPortalByID(key)
  234. portal.Name = c.Name
  235. portal.Topic = c.Topic
  236. portal.Type = c.Type
  237. if portal.Type == discordgo.ChannelTypeDM {
  238. portal.DMUser = c.Recipients[0].ID
  239. }
  240. if c.Icon != "" {
  241. u.log.Debugln("channel icon", c.Icon)
  242. }
  243. portal.Update()
  244. portal.createMatrixRoom(u, c.Channel)
  245. }
  246. func (u *User) channelDeleteHandler(s *discordgo.Session, c *discordgo.ChannelDelete) {
  247. u.log.Debugln("channel delete handler")
  248. }
  249. func (u *User) channelPinsUpdateHandler(s *discordgo.Session, c *discordgo.ChannelPinsUpdate) {
  250. u.log.Debugln("channel pins update")
  251. }
  252. func (u *User) channelUpdateHandler(s *discordgo.Session, c *discordgo.ChannelUpdate) {
  253. key := database.NewPortalKey(c.ID, u.User.ID)
  254. portal := u.bridge.GetPortalByID(key)
  255. portal.Name = c.Name
  256. portal.Topic = c.Topic
  257. u.log.Debugln("channel icon", c.Icon)
  258. portal.Update()
  259. u.log.Debugln("channel update")
  260. }
  261. func (u *User) messageCreateHandler(s *discordgo.Session, m *discordgo.MessageCreate) {
  262. if m.GuildID != "" {
  263. u.log.Debugln("ignoring message for guild")
  264. return
  265. }
  266. key := database.NewPortalKey(m.ChannelID, u.ID)
  267. portal := u.bridge.GetPortalByID(key)
  268. msg := portalDiscordMessage{
  269. msg: m,
  270. user: u,
  271. }
  272. portal.discordMessages <- msg
  273. }
  274. func (u *User) messageDeleteHandler(s *discordgo.Session, m *discordgo.MessageDelete) {
  275. if m.GuildID != "" {
  276. u.log.Debugln("ignoring message delete for guild message")
  277. return
  278. }
  279. key := database.NewPortalKey(m.ChannelID, u.ID)
  280. portal := u.bridge.GetPortalByID(key)
  281. msg := portalDiscordMessage{
  282. msg: m,
  283. user: u,
  284. }
  285. portal.discordMessages <- msg
  286. }
  287. func (u *User) messageUpdateHandler(s *discordgo.Session, m *discordgo.MessageUpdate) {
  288. if m.GuildID != "" {
  289. u.log.Debugln("ignoring message update for guild message")
  290. return
  291. }
  292. key := database.NewPortalKey(m.ChannelID, u.ID)
  293. portal := u.bridge.GetPortalByID(key)
  294. msg := portalDiscordMessage{
  295. msg: m,
  296. user: u,
  297. }
  298. portal.discordMessages <- msg
  299. }
  300. func (u *User) reactionAddHandler(s *discordgo.Session, m *discordgo.MessageReactionAdd) {
  301. if m.GuildID != "" {
  302. u.log.Debugln("ignoring reaction for guild message")
  303. return
  304. }
  305. key := database.NewPortalKey(m.ChannelID, u.User.ID)
  306. portal := u.bridge.GetPortalByID(key)
  307. msg := portalDiscordMessage{
  308. msg: m,
  309. user: u,
  310. }
  311. portal.discordMessages <- msg
  312. }
  313. func (u *User) reactionRemoveHandler(s *discordgo.Session, m *discordgo.MessageReactionRemove) {
  314. if m.GuildID != "" {
  315. u.log.Debugln("ignoring reaction for guild message")
  316. return
  317. }
  318. key := database.NewPortalKey(m.ChannelID, u.User.ID)
  319. portal := u.bridge.GetPortalByID(key)
  320. msg := portalDiscordMessage{
  321. msg: m,
  322. user: u,
  323. }
  324. portal.discordMessages <- msg
  325. }
  326. func (u *User) ensureInvited(intent *appservice.IntentAPI, roomID id.RoomID, isDirect bool) bool {
  327. ret := false
  328. inviteContent := event.Content{
  329. Parsed: &event.MemberEventContent{
  330. Membership: event.MembershipInvite,
  331. IsDirect: isDirect,
  332. },
  333. Raw: map[string]interface{}{},
  334. }
  335. _, err := intent.SendStateEvent(roomID, event.StateMember, u.MXID.String(), &inviteContent)
  336. var httpErr mautrix.HTTPError
  337. if err != nil && errors.As(err, &httpErr) && httpErr.RespError != nil && strings.Contains(httpErr.RespError.Err, "is already in the room") {
  338. u.bridge.StateStore.SetMembership(roomID, u.MXID, event.MembershipJoin)
  339. ret = true
  340. } else if err != nil {
  341. u.log.Warnfln("Failed to invite user to %s: %v", roomID, err)
  342. } else {
  343. ret = true
  344. }
  345. return ret
  346. }