@@ -36,8 +36,13 @@ type linePaidReactionRef struct {
3636 Version int
3737}
3838
39- func (ref linePaidReactionRef ) networkEmojiID () networkid.EmojiID {
40- return networkid .EmojiID ("paid:" + ref .ProductID + ":" + ref .EmojiID )
39+ type lineReactionRef struct {
40+ typ line.ReactionType
41+ }
42+
43+ type ReactionMetadata struct {
44+ MatrixKey string `json:"matrix_key,omitempty"`
45+ ReactionType line.ReactionType `json:"reaction_type"`
4146}
4247
4348func (ref linePaidReactionRef ) reactionType () line.ReactionType {
@@ -51,6 +56,61 @@ func (ref linePaidReactionRef) reactionType() line.ReactionType {
5156 }
5257}
5358
59+ func cloneLineReactionType (typ line.ReactionType ) line.ReactionType {
60+ cloned := line.ReactionType {
61+ PredefinedReactionType : typ .PredefinedReactionType ,
62+ }
63+ if typ .PaidReactionType != nil {
64+ paid := * typ .PaidReactionType
65+ cloned .PaidReactionType = & paid
66+ }
67+ return cloned
68+ }
69+
70+ func newLineReactionRef (typ line.ReactionType ) (lineReactionRef , error ) {
71+ hasPredefined := typ .PredefinedReactionType != 0
72+ hasPaid := typ .PaidReactionType != nil
73+ if hasPredefined == hasPaid {
74+ return lineReactionRef {}, errors .New ("reaction type must contain exactly one predefined or paid reaction" )
75+ }
76+ if hasPredefined {
77+ if _ , ok := line .PredefinedReactionEmoji [typ .PredefinedReactionType ]; ! ok {
78+ return lineReactionRef {}, fmt .Errorf ("unknown predefined reaction type %d" , typ .PredefinedReactionType )
79+ }
80+ } else if typ .PaidReactionType .ProductID == "" || typ .PaidReactionType .EmojiID == "" {
81+ return lineReactionRef {}, errors .New ("paid reaction is missing product or emoji ID" )
82+ }
83+ return lineReactionRef {typ : cloneLineReactionType (typ )}, nil
84+ }
85+
86+ func (ref lineReactionRef ) reactionType () line.ReactionType {
87+ return cloneLineReactionType (ref .typ )
88+ }
89+
90+ func (ref lineReactionRef ) networkEmojiID () networkid.EmojiID {
91+ if ref .typ .PaidReactionType != nil {
92+ return networkid .EmojiID ("paid:" + ref .typ .PaidReactionType .ProductID + ":" + ref .typ .PaidReactionType .EmojiID )
93+ }
94+ return networkid .EmojiID ("predefined:" + strconv .Itoa (ref .typ .PredefinedReactionType ))
95+ }
96+
97+ func (ref lineReactionRef ) equal (other lineReactionRef ) bool {
98+ if ref .typ .PredefinedReactionType != other .typ .PredefinedReactionType {
99+ return false
100+ }
101+ if ref .typ .PaidReactionType == nil || other .typ .PaidReactionType == nil {
102+ return ref .typ .PaidReactionType == nil && other .typ .PaidReactionType == nil
103+ }
104+ return * ref .typ .PaidReactionType == * other .typ .PaidReactionType
105+ }
106+
107+ func (ref lineReactionRef ) metadata (matrixKey string ) * ReactionMetadata {
108+ return & ReactionMetadata {
109+ MatrixKey : matrixKey ,
110+ ReactionType : ref .reactionType (),
111+ }
112+ }
113+
54114// These are the LINE emoji/sticon URLs from the issue's pack-based reaction
55115// set. Add more entries here as more Matrix emoji -> LINE CDN URL mappings are
56116// captured.
@@ -302,6 +362,36 @@ func (lc *LineClient) getPaidReactionMXC(ctx context.Context, prt *line.PaidReac
302362 return mxc , nil
303363}
304364
365+ func (lc * LineClient ) convertReaction (
366+ ctx context.Context ,
367+ typ line.ReactionType ,
368+ sender bridgev2.EventSender ,
369+ timestamp time.Time ,
370+ ) (* bridgev2.BackfillReaction , error ) {
371+ ref , err := newLineReactionRef (typ )
372+ if err != nil {
373+ return nil , err
374+ }
375+
376+ var mxc string
377+ if ref .typ .PaidReactionType != nil {
378+ mxc , err = lc .getPaidReactionMXC (ctx , ref .typ .PaidReactionType )
379+ } else {
380+ mxc , err = lc .getPredefinedReactionMXC (ctx , ref .typ .PredefinedReactionType )
381+ }
382+ if err != nil {
383+ return nil , err
384+ }
385+
386+ return & bridgev2.BackfillReaction {
387+ Timestamp : timestamp ,
388+ Sender : sender ,
389+ EmojiID : ref .networkEmojiID (),
390+ Emoji : mxc ,
391+ DBMetadata : ref .metadata (mxc ),
392+ }, nil
393+ }
394+
305395func (lc * LineClient ) convertMessageReactions (ctx context.Context , msg * line.Message ) ([]* bridgev2.BackfillReaction , bool ) {
306396 if msg == nil || msg .Reactions == nil {
307397 return nil , false
@@ -319,18 +409,16 @@ func (lc *LineClient) convertMessageReactions(ctx context.Context, msg *line.Mes
319409 continue
320410 }
321411
322- var (
323- mxc string
324- err error
325- )
326- switch {
327- case reaction .ReactionType .PaidReactionType != nil :
328- mxc , err = lc .getPaidReactionMXC (ctx , reaction .ReactionType .PaidReactionType )
329- case reaction .ReactionType .PredefinedReactionType != 0 :
330- mxc , err = lc .getPredefinedReactionMXC (ctx , reaction .ReactionType .PredefinedReactionType )
331- default :
332- err = errors .New ("reaction type is missing" )
412+ var timestamp time.Time
413+ if timestampMillis , err := reaction .AtMillis .Int64 (); err == nil && timestampMillis > 0 {
414+ timestamp = time .UnixMilli (timestampMillis )
333415 }
416+ convertedReaction , err := lc .convertReaction (
417+ ctx ,
418+ reaction .ReactionType ,
419+ lc .eventSenderForMID (reaction .FromUserMID ),
420+ timestamp ,
421+ )
334422 if err != nil {
335423 complete = false
336424 lc .UserLogin .Bridge .Log .Warn ().
@@ -340,16 +428,7 @@ func (lc *LineClient) convertMessageReactions(ctx context.Context, msg *line.Mes
340428 Msg ("Skipping unsupported historical reaction" )
341429 continue
342430 }
343-
344- var timestamp time.Time
345- if timestampMillis , err := reaction .AtMillis .Int64 (); err == nil && timestampMillis > 0 {
346- timestamp = time .UnixMilli (timestampMillis )
347- }
348- converted = append (converted , & bridgev2.BackfillReaction {
349- Timestamp : timestamp ,
350- Sender : lc .eventSenderForMID (reaction .FromUserMID ),
351- Emoji : mxc ,
352- })
431+ converted = append (converted , convertedReaction )
353432 }
354433 return converted , complete
355434}
@@ -457,6 +536,61 @@ func linePaidReactionForMatrixEmoji(key string) (linePaidReactionRef, bool) {
457536 return ref , true
458537}
459538
539+ func storedLineReactionForMatrixKey (key string , reactions []* database.Reaction ) (lineReactionRef , bool ) {
540+ var (
541+ found lineReactionRef
542+ hasFound bool
543+ )
544+ for _ , reaction := range reactions {
545+ meta , ok := reaction .Metadata .(* ReactionMetadata )
546+ if ! ok || meta == nil || meta .MatrixKey != key {
547+ continue
548+ }
549+ ref , err := newLineReactionRef (meta .ReactionType )
550+ if err != nil || (reaction .EmojiID != "" && reaction .EmojiID != ref .networkEmojiID ()) {
551+ continue
552+ }
553+ if hasFound && ! found .equal (ref ) {
554+ return lineReactionRef {}, false
555+ }
556+ found = ref
557+ hasFound = true
558+ }
559+ return found , hasFound
560+ }
561+
562+ func (lc * LineClient ) resolveMatrixReaction (ctx context.Context , msg * bridgev2.MatrixReaction ) (lineReactionRef , error ) {
563+ key := msg .Content .RelatesTo .GetAnnotationKey ()
564+ if paidRef , ok := linePaidReactionForMatrixEmoji (key ); ok {
565+ ref , err := newLineReactionRef (paidRef .reactionType ())
566+ if err != nil {
567+ return lineReactionRef {}, err
568+ }
569+ return ref , nil
570+ }
571+ if ! strings .HasPrefix (key , "mxc://" ) {
572+ return lineReactionRef {}, unsupportedMatrixReactionError (key )
573+ }
574+ if msg .TargetMessage == nil || msg .Portal == nil || msg .Portal .Bridge == nil || msg .Portal .Bridge .DB == nil {
575+ return lineReactionRef {}, errors .New ("reaction target database context is missing" )
576+ }
577+
578+ reactions , err := msg .Portal .Bridge .DB .Reaction .GetAllToMessagePart (
579+ ctx ,
580+ msg .Portal .Receiver ,
581+ msg .TargetMessage .ID ,
582+ msg .TargetMessage .PartID ,
583+ )
584+ if err != nil {
585+ return lineReactionRef {}, fmt .Errorf ("get target message reactions: %w" , err )
586+ }
587+ ref , ok := storedLineReactionForMatrixKey (key , reactions )
588+ if ! ok {
589+ return lineReactionRef {}, unsupportedMatrixReactionError (key )
590+ }
591+ return ref , nil
592+ }
593+
460594func unsupportedMatrixReactionError (key string ) error {
461595 return bridgev2 .WrapErrorInStatus (fmt .Errorf ("LINE does not support Matrix reaction %q" , key )).
462596 WithStatus (event .MessageStatusFail ).
@@ -572,9 +706,9 @@ func (lc *LineClient) consumeSentReqSeq(reqSeq int) bool {
572706
573707func (lc * LineClient ) PreHandleMatrixReaction (ctx context.Context , msg * bridgev2.MatrixReaction ) (bridgev2.MatrixReactionPreResponse , error ) {
574708 key := msg .Content .RelatesTo .GetAnnotationKey ()
575- ref , ok := linePaidReactionForMatrixEmoji ( key )
576- if ! ok {
577- return bridgev2.MatrixReactionPreResponse {}, unsupportedMatrixReactionError ( key )
709+ ref , err := lc . resolveMatrixReaction ( ctx , msg )
710+ if err != nil {
711+ return bridgev2.MatrixReactionPreResponse {}, err
578712 }
579713 return bridgev2.MatrixReactionPreResponse {
580714 SenderID : makeUserID (string (lc .UserLogin .ID )),
@@ -586,9 +720,9 @@ func (lc *LineClient) PreHandleMatrixReaction(ctx context.Context, msg *bridgev2
586720
587721func (lc * LineClient ) HandleMatrixReaction (ctx context.Context , msg * bridgev2.MatrixReaction ) (* database.Reaction , error ) {
588722 key := msg .Content .RelatesTo .GetAnnotationKey ()
589- ref , ok := linePaidReactionForMatrixEmoji ( key )
590- if ! ok {
591- return nil , unsupportedMatrixReactionError ( key )
723+ ref , err := lc . resolveMatrixReaction ( ctx , msg )
724+ if err != nil {
725+ return nil , err
592726 }
593727 targetID , err := parseReactionTargetMessageID (msg .TargetMessage .ID )
594728 if err != nil {
@@ -610,8 +744,9 @@ func (lc *LineClient) HandleMatrixReaction(ctx context.Context, msg *bridgev2.Ma
610744 }
611745
612746 return & database.Reaction {
613- EmojiID : ref .networkEmojiID (),
614- Emoji : key ,
747+ EmojiID : ref .networkEmojiID (),
748+ Emoji : key ,
749+ Metadata : ref .metadata (key ),
615750 }, nil
616751}
617752
0 commit comments