Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 2 additions & 8 deletions pkg/connector/reaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,6 @@ type linePaidReactionRef struct {
Version int
}

func (ref linePaidReactionRef) networkEmojiID() networkid.EmojiID {
return networkid.EmojiID("paid:" + ref.ProductID + ":" + ref.EmojiID)
}

func (ref linePaidReactionRef) reactionType() line.ReactionType {
return line.ReactionType{
PaidReactionType: &line.PaidReactionType{
Expand Down Expand Up @@ -342,13 +338,12 @@ func (lc *LineClient) consumeSentReqSeq(reqSeq int) bool {

func (lc *LineClient) PreHandleMatrixReaction(ctx context.Context, msg *bridgev2.MatrixReaction) (bridgev2.MatrixReactionPreResponse, error) {
key := msg.Content.RelatesTo.GetAnnotationKey()
ref, ok := linePaidReactionForMatrixEmoji(key)
_, ok := linePaidReactionForMatrixEmoji(key)
if !ok {
return bridgev2.MatrixReactionPreResponse{}, unsupportedMatrixReactionError(key)
}
return bridgev2.MatrixReactionPreResponse{
SenderID: makeUserID(string(lc.UserLogin.ID)),
EmojiID: ref.networkEmojiID(),
Emoji: key,
MaxReactions: 1,
}, nil
Expand Down Expand Up @@ -378,8 +373,7 @@ func (lc *LineClient) HandleMatrixReaction(ctx context.Context, msg *bridgev2.Ma
}

return &database.Reaction{
EmojiID: ref.networkEmojiID(),
Emoji: key,
Emoji: key,
}, nil
}

Expand Down
63 changes: 44 additions & 19 deletions pkg/connector/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -1131,7 +1131,7 @@ func (lc *LineClient) handleOperation(ctx context.Context, op line.Operation) {
// Curr == nil signals a reaction removal/clear from LINE.
if param2.Curr == nil {
lc.UserLogin.Bridge.Log.Debug().Str("msg_id", op.Param1).Str("chat_mid", param2.ChatMid).Msg("Received reaction removal (self)")
lc.handleReactionRemove(op, param2.ChatMid, []networkid.UserID{makeUserID(string(lc.UserLogin.ID))})
lc.handleReactionRemove(ctx, op, param2.ChatMid, []networkid.UserID{makeUserID(string(lc.UserLogin.ID))})
return
}

Expand Down Expand Up @@ -1168,7 +1168,7 @@ func (lc *LineClient) handleOperation(ctx context.Context, op line.Operation) {
if op.Param3 != "" && op.Param3 != param2.ChatMid {
senders = append(senders, makeUserID(op.Param3))
}
lc.handleReactionRemove(op, param2.ChatMid, senders)
lc.handleReactionRemove(ctx, op, param2.ChatMid, senders)
return
}

Expand Down Expand Up @@ -1354,34 +1354,59 @@ func (lc *LineClient) handlePredefinedReaction(ctx context.Context, op line.Oper
}

// handleReactionRemove queues a RemoteEventReactionRemove for each candidate
// sender. Reactions are stored with EmojiID="" (see handlePaidReaction /
// handlePredefinedReaction), so the framework's reaction lookup finds the
// single row keyed by (target_message, sender) and redacts it. A miss is
// silently ignored by bridgev2, which lets callers safely queue multiple
// sender candidates when the previous reaction's actor is ambiguous.
// sender. LINE reaction clears don't include the previous reaction type, so we
// remove every stored reaction row for the target message and sender. This also
// handles older Matrix-origin reactions that were stored with a paid EmojiID
// before LINE clears were normalized to EmojiID="".
//
// It also evicts stale add-dedup entries for the target message so that
// re-adding the same emoji after a clear isn't silently dropped by the
// recentReactions sync.Map.
func (lc *LineClient) handleReactionRemove(op line.Operation, chatMid string, senders []networkid.UserID) {
func (lc *LineClient) handleReactionRemove(ctx context.Context, op line.Operation, chatMid string, senders []networkid.UserID) {
ts, _ := op.CreatedTime.Int64()
portalKey := networkid.PortalKey{ID: makePortalID(chatMid), Receiver: lc.UserLogin.ID}
targetMessage := networkid.MessageID(op.Param1)

for _, sender := range senders {
dedupKey := op.Param1 + "\x00remove\x00" + string(sender)
if _, loaded := lc.recentReactions.LoadOrStore(dedupKey, struct{}{}); loaded {
if _, loaded := lc.recentReactions.Load(op.Param1 + "\x00remove\x00" + string(sender) + "\x00all"); loaded {
lc.UserLogin.Bridge.Log.Debug().Str("msg_id", op.Param1).Str("sender", string(sender)).Msg("Skipping duplicate reaction removal")
continue
}
lc.UserLogin.Bridge.QueueRemoteEvent(lc.UserLogin, &simplevent.Reaction{
EventMeta: simplevent.EventMeta{
Type: bridgev2.RemoteEventReactionRemove,
PortalKey: portalKey,
Timestamp: time.UnixMilli(ts),
Sender: bridgev2.EventSender{Sender: sender},
},
TargetMessage: networkid.MessageID(op.Param1),
})

emojiIDs := []networkid.EmojiID{""}
existingReactions, err := lc.UserLogin.Bridge.DB.Reaction.GetAllToMessageBySender(ctx, lc.UserLogin.ID, targetMessage, sender)
if err != nil {
lc.UserLogin.Bridge.Log.Error().Err(err).Str("msg_id", op.Param1).Str("sender", string(sender)).Msg("Failed to look up stored reactions for removal")
} else if len(existingReactions) > 0 {
emojiIDs = emojiIDs[:0]
seenEmojiIDs := make(map[networkid.EmojiID]struct{}, len(existingReactions))
for _, reaction := range existingReactions {
if _, seen := seenEmojiIDs[reaction.EmojiID]; seen {
continue
}
seenEmojiIDs[reaction.EmojiID] = struct{}{}
emojiIDs = append(emojiIDs, reaction.EmojiID)
}
lc.recentReactions.Store(op.Param1+"\x00remove\x00"+string(sender)+"\x00all", struct{}{})
}

for _, emojiID := range emojiIDs {
dedupKey := op.Param1 + "\x00remove\x00" + string(sender) + "\x00" + string(emojiID)
if _, loaded := lc.recentReactions.LoadOrStore(dedupKey, struct{}{}); loaded {
lc.UserLogin.Bridge.Log.Debug().Str("msg_id", op.Param1).Str("sender", string(sender)).Str("emoji_id", string(emojiID)).Msg("Skipping duplicate reaction removal")
continue
}
lc.UserLogin.Bridge.QueueRemoteEvent(lc.UserLogin, &simplevent.Reaction{
EventMeta: simplevent.EventMeta{
Type: bridgev2.RemoteEventReactionRemove,
PortalKey: portalKey,
Timestamp: time.UnixMilli(ts),
Sender: bridgev2.EventSender{Sender: sender},
},
TargetMessage: targetMessage,
EmojiID: emojiID,
})
}
}

lc.clearReactionDedupEntries(op.Param1, false)
Expand Down
Loading