Sfoglia il codice sorgente

Bridge own read status in both directions

Tulir Asokan 3 anni fa
parent
commit
a33551e487
6 ha cambiato i file con 87 aggiunte e 5 eliminazioni
  1. 3 2
      ROADMAP.md
  2. 10 0
      database/message.go
  3. 1 1
      go.mod
  4. 2 2
      go.sum
  5. 24 0
      portal.go
  6. 47 0
      user.go

+ 3 - 2
ROADMAP.md

@@ -9,10 +9,10 @@
   * [x] Message redactions
   * [x] Reactions
     * [x] Unicode emojis
-    * [ ] Custom emojis
+    * [ ] Custom emojis (re-reacting with custom emojis sent from Discord already works)
   * [ ] Presence
   * [ ] Typing notifications
-  * [ ] Own read status
+  * [x] Own read status
   * [ ] Power level
   * [ ] Membership actions
     * [ ] Invite
@@ -40,6 +40,7 @@
   * [x] Avatars
   * [ ] Presence
   * [ ] Typing notifications
+  * [x] Own read status
   * [ ] Membership actions
     * [ ] Invite
     * [ ] Join

+ 10 - 0
database/message.go

@@ -56,6 +56,16 @@ func (mq *MessageQuery) GetFirstByDiscordID(key PortalKey, discordID string) *Me
 	return mq.New().Scan(mq.db.QueryRow(query, key.ChannelID, key.Receiver, discordID))
 }
 
+func (mq *MessageQuery) GetLastByDiscordID(key PortalKey, discordID string) *Message {
+	query := messageSelect + " WHERE dc_chan_id=$1 AND dc_chan_receiver=$2 AND dcid=$3 AND dc_edit_index=0 ORDER BY dc_attachment_id DESC LIMIT 1"
+	return mq.New().Scan(mq.db.QueryRow(query, key.ChannelID, key.Receiver, discordID))
+}
+
+func (mq *MessageQuery) GetClosestBefore(key PortalKey, ts time.Time) *Message {
+	query := messageSelect + " WHERE dc_chan_id=$1 AND dc_chan_receiver=$2 AND timestamp<=$3 ORDER BY timestamp DESC, dc_attachment_id DESC LIMIT 1"
+	return mq.New().Scan(mq.db.QueryRow(query, key.ChannelID, key.Receiver, ts.UnixMilli()))
+}
+
 func (mq *MessageQuery) GetLastInThread(key PortalKey, threadID string) *Message {
 	query := messageSelect + " WHERE dc_chan_id=$1 AND dc_chan_receiver=$2 AND dc_thread_id=$3 AND dc_edit_index=0 ORDER BY timestamp DESC, dc_attachment_id DESC LIMIT 1"
 	return mq.New().Scan(mq.db.QueryRow(query, key.ChannelID, key.Receiver, threadID))

+ 1 - 1
go.mod

@@ -26,4 +26,4 @@ require (
 	maunium.net/go/mauflag v1.0.0 // indirect
 )
 
-replace github.com/bwmarrin/discordgo => gitlab.com/beeper/discordgo v0.23.3-0.20220703095519-7b2c44e4bc2f
+replace github.com/bwmarrin/discordgo => gitlab.com/beeper/discordgo v0.23.3-0.20220708085215-5150e3de5797

+ 2 - 2
go.sum

@@ -30,8 +30,8 @@ github.com/tidwall/sjson v1.2.4 h1:cuiLzLnaMeBhRmEv00Lpk3tkYrcxpmbU81tAY4Dw0tc=
 github.com/tidwall/sjson v1.2.4/go.mod h1:098SZ494YoMWPmMO6ct4dcFnqxwj9r/gF0Etp19pSNM=
 github.com/yuin/goldmark v1.4.12 h1:6hffw6vALvEDqJ19dOJvJKOoAOKe4NDaTqvd2sktGN0=
 github.com/yuin/goldmark v1.4.12/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
-gitlab.com/beeper/discordgo v0.23.3-0.20220703095519-7b2c44e4bc2f h1:Ag8rA+k9IRnEYxd0z671a7auMKoQ7DGw5FMtLpykFsA=
-gitlab.com/beeper/discordgo v0.23.3-0.20220703095519-7b2c44e4bc2f/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY=
+gitlab.com/beeper/discordgo v0.23.3-0.20220708085215-5150e3de5797 h1:G+6sujr5CBD1+GyDq9Vobj/2XhaRejXzyOVDNNWlM/E=
+gitlab.com/beeper/discordgo v0.23.3-0.20220708085215-5150e3de5797/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY=
 golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
 golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY=
 golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=

+ 24 - 0
portal.go

@@ -1451,6 +1451,30 @@ func (portal *Portal) handleMatrixRedaction(sender *User, evt *event.Event) {
 	go portal.sendMessageMetrics(evt, errTargetNotFound, "Ignoring")
 }
 
+func (portal *Portal) HandleMatrixReadReceipt(brUser bridge.User, eventID id.EventID, receiptTimestamp time.Time) {
+	sender := brUser.(*User)
+	if sender.Session == nil {
+		return
+	}
+	msg := portal.bridge.DB.Message.GetByMXID(portal.Key, eventID)
+	if msg == nil {
+		msg = portal.bridge.DB.Message.GetClosestBefore(portal.Key, receiptTimestamp)
+		if msg == nil {
+			portal.log.Debugfln("Dropping Matrix read receipt from %s for %s: no messages found", sender.MXID, eventID)
+		} else {
+			portal.log.Debugfln("Matrix read receipt target %s from %s not found, using closest message %s", eventID, sender.MXID, msg.MXID)
+		}
+	}
+	resp, err := sender.Session.ChannelMessageAckNoToken(msg.DiscordProtoChannelID(), msg.DiscordID)
+	if err != nil {
+		portal.log.Warnfln("Failed to handle read receipt for %s/%s from %s: %v", msg.MXID, msg.DiscordID, sender.MXID)
+	} else if resp.Token != nil {
+		portal.log.Debugfln("Marked %s/%s as read by %s (and got unexpected non-nil token %s)", msg.MXID, msg.DiscordID, sender.MXID, *resp.Token)
+	} else {
+		portal.log.Debugfln("Marked %s/%s as read by %s", msg.MXID, msg.DiscordID, sender.MXID)
+	}
+}
+
 func (portal *Portal) UpdateName(name string) bool {
 	if portal.Name == name && portal.NameSet {
 		return false

+ 47 - 0
user.go

@@ -479,6 +479,7 @@ func (user *User) Connect() error {
 	user.Session.AddHandler(user.messageUpdateHandler)
 	user.Session.AddHandler(user.reactionAddHandler)
 	user.Session.AddHandler(user.reactionRemoveHandler)
+	user.Session.AddHandler(user.messageAckHandler)
 
 	user.Session.Identify.Presence.Status = "online"
 
@@ -717,6 +718,52 @@ func (user *User) reactionRemoveHandler(_ *discordgo.Session, m *discordgo.Messa
 	user.pushPortalMessage(m, "reaction remove", m.ChannelID, m.GuildID)
 }
 
+type CustomReadReceipt struct {
+	Timestamp          int64  `json:"ts,omitempty"`
+	DoublePuppetSource string `json:"fi.mau.double_puppet_source,omitempty"`
+}
+
+type CustomReadMarkers struct {
+	mautrix.ReqSetReadMarkers
+	ReadExtra      CustomReadReceipt `json:"com.beeper.read.extra"`
+	FullyReadExtra CustomReadReceipt `json:"com.beeper.fully_read.extra"`
+}
+
+func (user *User) makeReadMarkerContent(eventID id.EventID) *CustomReadMarkers {
+	var extra CustomReadReceipt
+	extra.DoublePuppetSource = user.bridge.Name
+	return &CustomReadMarkers{
+		ReqSetReadMarkers: mautrix.ReqSetReadMarkers{
+			Read:      eventID,
+			FullyRead: eventID,
+		},
+		ReadExtra:      extra,
+		FullyReadExtra: extra,
+	}
+}
+
+func (user *User) messageAckHandler(_ *discordgo.Session, m *discordgo.MessageAck) {
+	portal := user.GetExistingPortalByID(m.ChannelID)
+	if portal == nil || portal.MXID == "" {
+		return
+	}
+	dp := user.GetIDoublePuppet()
+	if dp == nil {
+		return
+	}
+	msg := user.bridge.DB.Message.GetLastByDiscordID(portal.Key, m.MessageID)
+	if msg == nil {
+		user.log.Debugfln("Dropping message ack event for unknown message %s/%s", m.ChannelID, m.MessageID)
+		return
+	}
+	err := dp.CustomIntent().SetReadMarkers(portal.MXID, user.makeReadMarkerContent(msg.MXID))
+	if err != nil {
+		user.log.Warnfln("Failed to mark %s/%s as read: %v", msg.MXID, msg.DiscordID, err)
+	} else {
+		user.log.Debugfln("Marked %s/%s as read after Discord message ack event", msg.MXID, msg.DiscordID)
+	}
+}
+
 func (user *User) ensureInvited(intent *appservice.IntentAPI, roomID id.RoomID, isDirect bool) bool {
 	if intent == nil {
 		intent = user.bridge.Bot