portal.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757
  1. package bridge
  2. import (
  3. "fmt"
  4. "sync"
  5. "time"
  6. "github.com/bwmarrin/discordgo"
  7. log "maunium.net/go/maulogger/v2"
  8. "maunium.net/go/mautrix"
  9. "maunium.net/go/mautrix/appservice"
  10. "maunium.net/go/mautrix/event"
  11. "maunium.net/go/mautrix/id"
  12. "gitlab.com/beeper/discord/database"
  13. )
  14. type portalDiscordMessage struct {
  15. msg interface{}
  16. user *User
  17. }
  18. type portalMatrixMessage struct {
  19. evt *event.Event
  20. user *User
  21. }
  22. type Portal struct {
  23. *database.Portal
  24. bridge *Bridge
  25. log log.Logger
  26. roomCreateLock sync.Mutex
  27. discordMessages chan portalDiscordMessage
  28. matrixMessages chan portalMatrixMessage
  29. }
  30. var (
  31. portalCreationDummyEvent = event.Type{Type: "fi.mau.dummy.portal_created", Class: event.MessageEventType}
  32. )
  33. func (b *Bridge) loadPortal(dbPortal *database.Portal, key *database.PortalKey) *Portal {
  34. // If we weren't given a portal we'll attempt to create it if a key was
  35. // provided.
  36. if dbPortal == nil {
  37. if key == nil {
  38. return nil
  39. }
  40. dbPortal = b.db.Portal.New()
  41. dbPortal.Key = *key
  42. dbPortal.Insert()
  43. }
  44. portal := b.NewPortal(dbPortal)
  45. // No need to lock, it is assumed that our callers have already acquired
  46. // the lock.
  47. b.portalsByID[portal.Key] = portal
  48. if portal.MXID != "" {
  49. b.portalsByMXID[portal.MXID] = portal
  50. }
  51. return portal
  52. }
  53. func (b *Bridge) GetPortalByMXID(mxid id.RoomID) *Portal {
  54. b.portalsLock.Lock()
  55. defer b.portalsLock.Unlock()
  56. portal, ok := b.portalsByMXID[mxid]
  57. if !ok {
  58. return b.loadPortal(b.db.Portal.GetByMXID(mxid), nil)
  59. }
  60. return portal
  61. }
  62. func (b *Bridge) GetPortalByID(key database.PortalKey) *Portal {
  63. b.portalsLock.Lock()
  64. defer b.portalsLock.Unlock()
  65. portal, ok := b.portalsByID[key]
  66. if !ok {
  67. return b.loadPortal(b.db.Portal.GetByID(key), &key)
  68. }
  69. return portal
  70. }
  71. func (b *Bridge) GetAllPortals() []*Portal {
  72. return b.dbPortalsToPortals(b.db.Portal.GetAll())
  73. }
  74. func (b *Bridge) GetAllPortalsByID(id string) []*Portal {
  75. return b.dbPortalsToPortals(b.db.Portal.GetAllByID(id))
  76. }
  77. func (b *Bridge) dbPortalsToPortals(dbPortals []*database.Portal) []*Portal {
  78. b.portalsLock.Lock()
  79. defer b.portalsLock.Unlock()
  80. output := make([]*Portal, len(dbPortals))
  81. for index, dbPortal := range dbPortals {
  82. if dbPortal == nil {
  83. continue
  84. }
  85. portal, ok := b.portalsByID[dbPortal.Key]
  86. if !ok {
  87. portal = b.loadPortal(dbPortal, nil)
  88. }
  89. output[index] = portal
  90. }
  91. return output
  92. }
  93. func (b *Bridge) NewPortal(dbPortal *database.Portal) *Portal {
  94. portal := &Portal{
  95. Portal: dbPortal,
  96. bridge: b,
  97. log: b.log.Sub(fmt.Sprintf("Portal/%s", dbPortal.Key)),
  98. discordMessages: make(chan portalDiscordMessage, b.Config.Bridge.PortalMessageBuffer),
  99. matrixMessages: make(chan portalMatrixMessage, b.Config.Bridge.PortalMessageBuffer),
  100. }
  101. go portal.messageLoop()
  102. return portal
  103. }
  104. func (p *Portal) handleMatrixInvite(sender *User, evt *event.Event) {
  105. // Look up an existing puppet or create a new one.
  106. puppet := p.bridge.GetPuppetByMXID(id.UserID(evt.GetStateKey()))
  107. if puppet != nil {
  108. p.log.Infoln("no puppet for %v", sender)
  109. // Open a conversation on the discord side?
  110. }
  111. p.log.Infoln("puppet:", puppet)
  112. }
  113. func (p *Portal) messageLoop() {
  114. for {
  115. select {
  116. case msg := <-p.matrixMessages:
  117. p.handleMatrixMessages(msg)
  118. case msg := <-p.discordMessages:
  119. p.handleDiscordMessages(msg)
  120. }
  121. }
  122. }
  123. func (p *Portal) IsPrivateChat() bool {
  124. return p.Type == discordgo.ChannelTypeDM
  125. }
  126. func (p *Portal) MainIntent() *appservice.IntentAPI {
  127. if p.IsPrivateChat() && p.DMUser != "" {
  128. return p.bridge.GetPuppetByID(p.DMUser).DefaultIntent()
  129. }
  130. return p.bridge.bot
  131. }
  132. func (p *Portal) createMatrixRoom(user *User, channel *discordgo.Channel) error {
  133. p.Type = channel.Type
  134. if p.Type == discordgo.ChannelTypeDM {
  135. p.DMUser = channel.Recipients[0].ID
  136. }
  137. p.roomCreateLock.Lock()
  138. defer p.roomCreateLock.Unlock()
  139. // If we have a matrix id the room should exist so we have nothing to do.
  140. if p.MXID != "" {
  141. return nil
  142. }
  143. intent := p.MainIntent()
  144. if err := intent.EnsureRegistered(); err != nil {
  145. return err
  146. }
  147. // if p.IsPrivateChat() {
  148. p.Name = channel.Name
  149. p.Topic = channel.Topic
  150. // TODO: get avatars figured out
  151. // p.Avatar = puppet.Avatar
  152. // p.AvatarURL = puppet.AvatarURL
  153. // }
  154. p.log.Infoln("Creating Matrix room for channel:", p.Portal.Key.ChannelID)
  155. initialState := []*event.Event{}
  156. creationContent := make(map[string]interface{})
  157. // if !portal.bridge.Config.Bridge.FederateRooms {
  158. creationContent["m.federate"] = false
  159. // }
  160. var invite []id.UserID
  161. if p.IsPrivateChat() {
  162. invite = append(invite, p.bridge.bot.UserID)
  163. }
  164. resp, err := intent.CreateRoom(&mautrix.ReqCreateRoom{
  165. Visibility: "private",
  166. Name: p.Name,
  167. Topic: p.Topic,
  168. Preset: "private_chat",
  169. IsDirect: p.IsPrivateChat(),
  170. InitialState: initialState,
  171. CreationContent: creationContent,
  172. })
  173. if err != nil {
  174. return err
  175. }
  176. p.MXID = resp.RoomID
  177. p.Update()
  178. p.bridge.portalsLock.Lock()
  179. p.bridge.portalsByMXID[p.MXID] = p
  180. p.bridge.portalsLock.Unlock()
  181. p.log.Debugln("inviting user", user)
  182. p.ensureUserInvited(user)
  183. if p.IsPrivateChat() {
  184. p.syncParticipants(user, channel.Recipients)
  185. }
  186. firstEventResp, err := p.MainIntent().SendMessageEvent(p.MXID, portalCreationDummyEvent, struct{}{})
  187. if err != nil {
  188. p.log.Errorln("Failed to send dummy event to mark portal creation:", err)
  189. } else {
  190. p.FirstEventID = firstEventResp.EventID
  191. p.Update()
  192. }
  193. return nil
  194. }
  195. func (p *Portal) handleDiscordMessages(msg portalDiscordMessage) {
  196. if p.MXID == "" {
  197. p.log.Debugln("Creating Matrix room from incoming message")
  198. discordMsg := msg.msg.(*discordgo.MessageCreate)
  199. channel, err := msg.user.Session.Channel(discordMsg.ChannelID)
  200. if err != nil {
  201. p.log.Errorln("Failed to find channel for message:", err)
  202. return
  203. }
  204. if err := p.createMatrixRoom(msg.user, channel); err != nil {
  205. p.log.Errorln("Failed to create portal room:", err)
  206. return
  207. }
  208. }
  209. switch msg.msg.(type) {
  210. case *discordgo.MessageCreate:
  211. p.handleDiscordMessageCreate(msg.user, msg.msg.(*discordgo.MessageCreate).Message)
  212. case *discordgo.MessageUpdate:
  213. p.handleDiscordMessagesUpdate(msg.user, msg.msg.(*discordgo.MessageUpdate).Message)
  214. case *discordgo.MessageDelete:
  215. p.handleDiscordMessageDelete(msg.user, msg.msg.(*discordgo.MessageDelete).Message)
  216. case *discordgo.MessageReactionAdd:
  217. p.handleDiscordReaction(msg.user, msg.msg.(*discordgo.MessageReactionAdd).MessageReaction, true)
  218. case *discordgo.MessageReactionRemove:
  219. p.handleDiscordReaction(msg.user, msg.msg.(*discordgo.MessageReactionRemove).MessageReaction, false)
  220. default:
  221. p.log.Warnln("unknown message type")
  222. }
  223. }
  224. func (p *Portal) ensureUserInvited(user *User) bool {
  225. return user.ensureInvited(p.MainIntent(), p.MXID, p.IsPrivateChat())
  226. }
  227. func (p *Portal) markMessageHandled(msg *database.Message, discordID string, mxid id.EventID, authorID string, timestamp time.Time) *database.Message {
  228. if msg == nil {
  229. msg := p.bridge.db.Message.New()
  230. msg.Channel = p.Key
  231. msg.DiscordID = discordID
  232. msg.MatrixID = mxid
  233. msg.AuthorID = authorID
  234. msg.Timestamp = timestamp
  235. msg.Insert()
  236. } else {
  237. msg.UpdateMatrixID(mxid)
  238. }
  239. return msg
  240. }
  241. func (p *Portal) handleDiscordMessageCreate(user *User, msg *discordgo.Message) {
  242. if user.ID == msg.Author.ID {
  243. return
  244. }
  245. if p.MXID == "" {
  246. p.log.Warnln("handle message called without a valid portal")
  247. return
  248. }
  249. existing := p.bridge.db.Message.GetByDiscordID(p.Key, msg.ID)
  250. if existing != nil {
  251. p.log.Debugln("not handling duplicate message", msg.ID)
  252. return
  253. }
  254. content := &event.MessageEventContent{
  255. Body: msg.Content,
  256. MsgType: event.MsgText,
  257. }
  258. intent := p.bridge.GetPuppetByID(msg.Author.ID).IntentFor(p)
  259. resp, err := intent.SendMessageEvent(p.MXID, event.EventMessage, content)
  260. if err != nil {
  261. p.log.Warnfln("failed to send message %q to matrix: %v", msg.ID, err)
  262. return
  263. }
  264. ts, _ := msg.Timestamp.Parse()
  265. p.markMessageHandled(existing, msg.ID, resp.EventID, msg.Author.ID, ts)
  266. }
  267. func (p *Portal) handleDiscordMessagesUpdate(user *User, msg *discordgo.Message) {
  268. if user.ID == msg.Author.ID {
  269. return
  270. }
  271. if p.MXID == "" {
  272. p.log.Warnln("handle message called without a valid portal")
  273. return
  274. }
  275. existing := p.bridge.db.Message.GetByDiscordID(p.Key, msg.ID)
  276. if existing == nil {
  277. p.log.Debugln("failed to find previous message to update", msg.ID)
  278. }
  279. content := &event.MessageEventContent{
  280. Body: msg.Content,
  281. MsgType: event.MsgText,
  282. }
  283. content.SetEdit(existing.MatrixID)
  284. intent := p.bridge.GetPuppetByID(msg.Author.ID).IntentFor(p)
  285. _, err := intent.SendMessageEvent(p.MXID, event.EventMessage, content)
  286. if err != nil {
  287. p.log.Warnfln("failed to send message %q to matrix: %v", msg.ID, err)
  288. return
  289. }
  290. // It appears that matrix updates only work against the original event id
  291. // so updating it to the new one from an edit makes it so you can't update
  292. // it anyways. So we just don't update anything and we can keep updating
  293. // the message.
  294. }
  295. func (p *Portal) handleDiscordMessageDelete(user *User, msg *discordgo.Message) {
  296. // The discord delete message object is pretty empty and doesn't include
  297. // the author so we have to use the DMUser from the portal that was added
  298. // at creation time if we're a DM. We'll might have similar issues when we
  299. // add guild message support, but we'll cross that bridge when we get
  300. // there.
  301. // Find the message that we're working with.
  302. existing := p.bridge.db.Message.GetByDiscordID(p.Key, msg.ID)
  303. if existing == nil {
  304. p.log.Debugfln("failed to find message", msg.ID)
  305. return
  306. }
  307. var intent *appservice.IntentAPI
  308. if p.Type == discordgo.ChannelTypeDM {
  309. intent = p.bridge.GetPuppetByID(p.DMUser).IntentFor(p)
  310. } else {
  311. p.log.Errorfln("no guilds yet...")
  312. }
  313. _, err := intent.RedactEvent(p.MXID, existing.MatrixID)
  314. if err != nil {
  315. p.log.Warnfln("Failed to remove message %s: %v", existing.MatrixID, err)
  316. }
  317. existing.Delete()
  318. }
  319. func (p *Portal) syncParticipants(source *User, participants []*discordgo.User) {
  320. for _, participant := range participants {
  321. puppet := p.bridge.GetPuppetByID(participant.ID)
  322. puppet.SyncContact(source)
  323. user := p.bridge.GetUserByID(participant.ID)
  324. if user != nil {
  325. p.ensureUserInvited(user)
  326. }
  327. if user == nil || !puppet.IntentFor(p).IsCustomPuppet {
  328. if err := puppet.IntentFor(p).EnsureJoined(p.MXID); err != nil {
  329. p.log.Warnfln("Failed to make puppet of %s join %s: %v", participant.ID, p.MXID, err)
  330. }
  331. }
  332. }
  333. }
  334. func (p *Portal) handleMatrixMessages(msg portalMatrixMessage) {
  335. switch msg.evt.Type {
  336. case event.EventMessage:
  337. p.handleMatrixMessage(msg.user, msg.evt)
  338. default:
  339. p.log.Debugln("unknown event type", msg.evt.Type)
  340. }
  341. }
  342. func (p *Portal) handleMatrixMessage(sender *User, evt *event.Event) {
  343. if p.IsPrivateChat() && sender.ID != p.Key.Receiver {
  344. return
  345. }
  346. existing := p.bridge.db.Message.GetByMatrixID(p.Key, evt.ID)
  347. if existing != nil {
  348. p.log.Debugln("not handling duplicate message", evt.ID)
  349. return
  350. }
  351. content, ok := evt.Content.Parsed.(*event.MessageEventContent)
  352. if !ok {
  353. p.log.Debugfln("Failed to handle event %s: unexpected parsed content type %T", evt.ID, evt.Content.Parsed)
  354. return
  355. }
  356. if content.RelatesTo != nil && content.RelatesTo.Type == event.RelReplace {
  357. existing := p.bridge.db.Message.GetByMatrixID(p.Key, content.RelatesTo.EventID)
  358. if existing != nil && existing.DiscordID != "" {
  359. // we don't have anything to save for the update message right now
  360. // as we're not tracking edited timestamps.
  361. _, err := sender.Session.ChannelMessageEdit(p.Key.ChannelID,
  362. existing.DiscordID, content.NewContent.Body)
  363. if err != nil {
  364. p.log.Errorln("Failed to update message %s: %v", existing.DiscordID, err)
  365. return
  366. }
  367. }
  368. } else {
  369. msg, err := sender.Session.ChannelMessageSend(p.Key.ChannelID, content.Body)
  370. if err != nil {
  371. p.log.Errorfln("Failed to send message: %v", err)
  372. return
  373. }
  374. dbMsg := p.bridge.db.Message.New()
  375. dbMsg.Channel = p.Key
  376. dbMsg.DiscordID = msg.ID
  377. dbMsg.MatrixID = evt.ID
  378. dbMsg.AuthorID = sender.ID
  379. dbMsg.Timestamp = time.Now()
  380. dbMsg.Insert()
  381. }
  382. }
  383. func (p *Portal) handleMatrixLeave(sender *User) {
  384. if p.IsPrivateChat() {
  385. p.log.Debugln("User left private chat portal, cleaning up and deleting...")
  386. p.delete()
  387. p.cleanup(false)
  388. return
  389. }
  390. // TODO: figure out how to close a dm from the API.
  391. p.cleanupIfEmpty()
  392. }
  393. func (p *Portal) delete() {
  394. p.Portal.Delete()
  395. p.bridge.portalsLock.Lock()
  396. delete(p.bridge.portalsByID, p.Key)
  397. if p.MXID != "" {
  398. delete(p.bridge.portalsByMXID, p.MXID)
  399. }
  400. p.bridge.portalsLock.Unlock()
  401. }
  402. func (p *Portal) cleanupIfEmpty() {
  403. users, err := p.getMatrixUsers()
  404. if err != nil {
  405. p.log.Errorfln("Failed to get Matrix user list to determine if portal needs to be cleaned up: %v", err)
  406. return
  407. }
  408. if len(users) == 0 {
  409. p.log.Infoln("Room seems to be empty, cleaning up...")
  410. p.delete()
  411. p.cleanup(false)
  412. }
  413. }
  414. func (p *Portal) cleanup(puppetsOnly bool) {
  415. if p.MXID != "" {
  416. return
  417. }
  418. if p.IsPrivateChat() {
  419. _, err := p.MainIntent().LeaveRoom(p.MXID)
  420. if err != nil {
  421. p.log.Warnln("Failed to leave private chat portal with main intent:", err)
  422. }
  423. return
  424. }
  425. intent := p.MainIntent()
  426. members, err := intent.JoinedMembers(p.MXID)
  427. if err != nil {
  428. p.log.Errorln("Failed to get portal members for cleanup:", err)
  429. return
  430. }
  431. for member := range members.Joined {
  432. if member == intent.UserID {
  433. continue
  434. }
  435. puppet := p.bridge.GetPuppetByMXID(member)
  436. if p != nil {
  437. _, err = puppet.DefaultIntent().LeaveRoom(p.MXID)
  438. if err != nil {
  439. p.log.Errorln("Error leaving as puppet while cleaning up portal:", err)
  440. }
  441. } else if !puppetsOnly {
  442. _, err = intent.KickUser(p.MXID, &mautrix.ReqKickUser{UserID: member, Reason: "Deleting portal"})
  443. if err != nil {
  444. p.log.Errorln("Error kicking user while cleaning up portal:", err)
  445. }
  446. }
  447. }
  448. _, err = intent.LeaveRoom(p.MXID)
  449. if err != nil {
  450. p.log.Errorln("Error leaving with main intent while cleaning up portal:", err)
  451. }
  452. }
  453. func (p *Portal) getMatrixUsers() ([]id.UserID, error) {
  454. members, err := p.MainIntent().JoinedMembers(p.MXID)
  455. if err != nil {
  456. return nil, fmt.Errorf("failed to get member list: %w", err)
  457. }
  458. var users []id.UserID
  459. for userID := range members.Joined {
  460. _, isPuppet := p.bridge.ParsePuppetMXID(userID)
  461. if !isPuppet && userID != p.bridge.bot.UserID {
  462. users = append(users, userID)
  463. }
  464. }
  465. return users, nil
  466. }
  467. func (p *Portal) handleMatrixKick(sender *User, target *Puppet) {
  468. // TODO: need to learn how to make this happen as discordgo proper doesn't
  469. // support group dms and it looks like it's a binary blob.
  470. }
  471. func (p *Portal) handleMatrixReaction(evt *event.Event) {
  472. user := p.bridge.GetUserByMXID(evt.Sender)
  473. if user == nil {
  474. p.log.Errorf("failed to find user for %s", evt.Sender)
  475. return
  476. }
  477. if user.ID != p.Key.Receiver {
  478. return
  479. }
  480. reaction := evt.Content.AsReaction()
  481. if reaction.RelatesTo.Type != event.RelAnnotation {
  482. p.log.Errorfln("Ignoring reaction %s due to unknown m.relates_to data", evt.ID)
  483. return
  484. }
  485. msg := p.bridge.db.Message.GetByMatrixID(p.Key, reaction.RelatesTo.EventID)
  486. if msg.DiscordID == "" {
  487. p.log.Debugf("Message %s has not yet been sent to discord", reaction.RelatesTo.EventID)
  488. return
  489. }
  490. err := user.Session.MessageReactionAdd(p.Key.ChannelID, msg.DiscordID, reaction.RelatesTo.Key)
  491. if err != nil {
  492. p.log.Debugf("Failed to send reaction %s@%s: %v", p.Key, msg.DiscordID, err)
  493. return
  494. }
  495. dbReaction := p.bridge.db.Reaction.New()
  496. dbReaction.Channel.ChannelID = p.Key.ChannelID
  497. dbReaction.Channel.Receiver = p.Key.Receiver
  498. dbReaction.MatrixEventID = evt.ID
  499. dbReaction.DiscordMessageID = msg.DiscordID
  500. dbReaction.AuthorID = user.ID
  501. dbReaction.MatrixName = reaction.RelatesTo.Key
  502. dbReaction.DiscordID = reaction.RelatesTo.Key
  503. dbReaction.Insert()
  504. }
  505. func (p *Portal) handleDiscordReaction(user *User, reaction *discordgo.MessageReaction, add bool) {
  506. if user.ID == reaction.UserID {
  507. return
  508. }
  509. // This is temporary until we add support for custom emoji.
  510. if reaction.Emoji.ID != "" {
  511. p.log.Debugln("ignoring non-unicode reaction")
  512. return
  513. }
  514. emoteID := reaction.Emoji.ID
  515. if reaction.Emoji.Name != "" {
  516. emoteID = reaction.Emoji.Name
  517. }
  518. // Find the message that we're working with.
  519. message := p.bridge.db.Message.GetByDiscordID(p.Key, reaction.MessageID)
  520. if message == nil {
  521. p.log.Debugfln("failed to add reaction to message %s: message not found", reaction.MessageID)
  522. return
  523. }
  524. intent := p.bridge.GetPuppetByID(reaction.UserID).IntentFor(p)
  525. // Lookup an existing reaction
  526. existing := p.bridge.db.Reaction.GetByDiscordID(p.Key, message.DiscordID, emoteID)
  527. if !add {
  528. if existing == nil {
  529. p.log.Debugln("Failed to remove reaction for unknown message", reaction.MessageID)
  530. return
  531. }
  532. _, err := intent.RedactEvent(p.MXID, existing.MatrixEventID)
  533. if err != nil {
  534. p.log.Warnfln("Failed to remove reaction from %s: %v", p.MXID, err)
  535. }
  536. existing.Delete()
  537. return
  538. }
  539. content := event.Content{Parsed: &event.ReactionEventContent{
  540. RelatesTo: event.RelatesTo{
  541. EventID: message.MatrixID,
  542. Type: event.RelAnnotation,
  543. Key: reaction.Emoji.Name,
  544. },
  545. }}
  546. resp, err := intent.Client.SendMessageEvent(p.MXID, event.EventReaction, &content)
  547. if err != nil {
  548. p.log.Errorfln("failed to send reaction from %s: %v", reaction.MessageID, err)
  549. return
  550. }
  551. if existing == nil {
  552. dbReaction := p.bridge.db.Reaction.New()
  553. dbReaction.Channel = p.Key
  554. dbReaction.DiscordMessageID = message.DiscordID
  555. dbReaction.MatrixEventID = resp.EventID
  556. dbReaction.AuthorID = reaction.UserID
  557. dbReaction.MatrixName = reaction.Emoji.Name
  558. dbReaction.DiscordID = emoteID
  559. dbReaction.Insert()
  560. }
  561. }
  562. func (p *Portal) handleMatrixRedaction(evt *event.Event) {
  563. user := p.bridge.GetUserByMXID(evt.Sender)
  564. if user.ID != p.Key.Receiver {
  565. return
  566. }
  567. // First look if we're redacting a message
  568. message := p.bridge.db.Message.GetByMatrixID(p.Key, evt.Redacts)
  569. if message != nil {
  570. if message.DiscordID != "" {
  571. err := user.Session.ChannelMessageDelete(p.Key.ChannelID, message.DiscordID)
  572. if err != nil {
  573. p.log.Debugfln("Failed to delete discord message %s: %v", message.DiscordID, err)
  574. } else {
  575. message.Delete()
  576. }
  577. }
  578. return
  579. }
  580. // Now check if it's a reaction.
  581. reaction := p.bridge.db.Reaction.GetByMatrixID(p.Key, evt.Redacts)
  582. if reaction != nil {
  583. if reaction.DiscordID != "" {
  584. err := user.Session.MessageReactionRemove(p.Key.ChannelID, reaction.DiscordMessageID, reaction.DiscordID, reaction.AuthorID)
  585. if err != nil {
  586. p.log.Debugfln("Failed to delete reaction %s for message %s: %v", reaction.DiscordID, reaction.DiscordMessageID, err)
  587. } else {
  588. reaction.Delete()
  589. }
  590. }
  591. return
  592. }
  593. p.log.Warnfln("Failed to redact %s@%s: no event found", p.Key, evt.Redacts)
  594. }