Skip to content

fix: allow reusing LINE custom reactions - #227

Merged
highesttt merged 2 commits into
mainfrom
highest/plat-38162
Jul 28, 2026
Merged

fix: allow reusing LINE custom reactions#227
highesttt merged 2 commits into
mainfrom
highest/plat-38162

Conversation

@highesttt

Copy link
Copy Markdown
Collaborator

No description provided.

@indent-zero

indent-zero Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor
PR Summary

Enables Matrix users to re-add LINE custom (paid/predefined) reactions by persisting the LINE ReactionType alongside every reaction row so an mxc://… annotation key can be resolved back to the original LINE reaction. Also assigns stable per-reaction EmojiIDs (predefined:N / paid:P:E) and migrates live SSE 139/140 handling to RemoteEventReactionSync with per-sender HasAllReactions=true, which lets bridgev2 handle dedup and replacement in place of the removed recentReactions sync.Map.

  • Registers a Reaction metadata factory (&ReactionMetadata{}) in GetDBMetaTypes so bridgev2 decodes each row's metadata JSON.
  • Introduces lineReactionRef + newLineReactionRef with validation, cloneLineReactionType, and networkEmojiID() returning stable IDs; linePaidReactionRef.networkEmojiID is removed and callers upgrade via newLineReactionRef(paidRef.reactionType()).
  • Adds resolveMatrixReaction used by both PreHandleMatrixReaction and HandleMatrixReaction: Unicode fast-path, else Bridge.DB.Reaction.GetAllToMessagePart + storedLineReactionForMatrixKey for MXC keys; malformed / EmojiID-mismatched rows are now skipped instead of aborting the lookup (ef2373e).
  • Adds convertReaction to consolidate icon upload + BackfillReaction construction (with EmojiID/DBMetadata) across live and embedded-history paths.
  • Refactors handlePaidReaction / handlePredefinedReaction / handleReactionRemove to enqueue simplevent.ReactionSync via a new liveReactionSyncEvent helper (HasAllUsers=false, Users[sender]={reactions, HasAllReactions=true}), and deletes LineClient.recentReactions + clearReactionDedupEntries.
  • Expands reaction_test.go with coverage for ref identity/metadata round-trip, stored-key lookup edge cases (including mixed malformed+valid rows), sender-authoritative live sync payload, embedded-history metadata, and Unicode PreHandle behavior; also asserts CustomEmojiReactions stays disabled.

Issues

2 potential issues found:

  • Legacy reaction rows (written before this PR) have Metadata=nil, so storedLineReactionForMatrixKey never matches them and Matrix users can't re-react with the paid mxc://… key those rows represent until a fresh live event replaces them with a stable-ID row. → Autofix
  • resolveMatrixReaction runs twice per MXC-keyed reaction (once in PreHandleMatrixReaction, again in HandleMatrixReaction), issuing the same Bridge.DB.Reaction.GetAllToMessagePart query both times; the result could be reused via msg.PreHandleResp to avoid the extra DB round-trip. → Autofix
1 issue already resolved
  • A single corrupt reaction row aborts the whole lookup: storedLineReactionForMatrixKey returns (_, false) on the first same-MatrixKey row whose metadata fails newLineReactionRef or whose EmojiID doesn't match the recomputed stable ID, instead of skipping just that row. (fixed by commit ef2373e)

CI Checks

All CI checks passed on ef2373e.


⚡ Autofix All Issues

@linear-code

linear-code Bot commented Jul 28, 2026

Copy link
Copy Markdown

PLAT-38162

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b1d22a67-0531-4f79-81f7-a0ae663c1565

📥 Commits

Reviewing files that changed from the base of the PR and between 2963ffb and ef2373e.

📒 Files selected for processing (2)
  • pkg/connector/reaction.go
  • pkg/connector/reaction_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • pkg/connector/reaction.go
  • pkg/connector/reaction_test.go
📜 Recent review details
⏰ Context from checks skipped due to timeout. (4)
  • GitHub Check: Lint with 1.25
  • GitHub Check: build-docker
  • GitHub Check: Lint with 1.25
  • GitHub Check: build-docker

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Enhanced LINE↔Matrix reaction syncing with persisted reaction identification (including paid vs predefined icon selection and Unicode behavior).
    • Updated live reaction handling to use centralized reaction-sync payloads for additions and removals.
  • Bug Fixes

    • Fixed reaction reconstruction from stored metadata, rejecting malformed/legacy/conflicting metadata and ensuring stable identity.
    • Improved sender attribution for live reaction updates and clearer removals.
  • Tests

    • Added/strengthened reaction-focused tests for metadata stability, resolver correctness, Unicode preservation, and sender-authoritative sync behavior.
    • Explicitly verified custom emoji reactions remain disabled.

Walkthrough

Changes

Reaction handling refactor

Layer / File(s) Summary
Unified reaction references and Matrix resolution
pkg/connector/reaction.go, pkg/connector/connector.go
Adds validated reaction references and persisted metadata, centralizes historical reaction conversion, and resolves Matrix reactions using emoji mappings or stored metadata.
Authoritative live reaction synchronization
pkg/connector/sync.go, pkg/connector/client.go
Replaces individual reaction events with sender-specific reaction syncs, emits authoritative clears on removal, and removes reaction deduplication state.
Reaction behavior validation
pkg/connector/reaction_test.go
Tests identity, metadata persistence and lookup, conversion, Unicode handling, capabilities, and sender-authoritative synchronization.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant LINE as LINE SSE
  participant Connector as Reaction handlers
  participant Bridge as RemoteEventReactionSync
  LINE->>Connector: reaction add or remove
  Connector->>Bridge: enqueue sender-bound sync
  Bridge-->>Connector: reaction state update
Loading

Possibly related PRs

  • beeper/line#213: Both modify reaction capability coverage in pkg/connector/reaction_test.go.
  • beeper/line#217: Both modify LINE-to-Matrix reaction synchronization and sender resolution.
  • beeper/line#224: Both update reaction conversion, backfilling, and synchronization logic.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Description check ❓ Inconclusive No description was provided, so the PR intent cannot be meaningfully assessed from the description alone. Add a brief description of the behavior change and scope so reviewers can verify the intent.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title matches the main change: reaction handling was refactored to allow reusing LINE custom reactions.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch highest/plat-38162

Comment @coderabbitai help to get the list of available commands.

Comment thread pkg/connector/reaction.go
if err != nil {
return lineReactionRef{}, fmt.Errorf("get target message reactions: %w", err)
}
ref, ok := storedLineReactionForMatrixKey(key, reactions)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Latent: legacy EmojiID="" rows are unreachable via this lookup.

Rows persisted before this PR were written with EmojiID="" and Metadata=nil. The Reaction metadata factory now returns &ReactionMetadata{} on load, so those rows come back with an empty MatrixKey, fail the meta.MatrixKey != key filter in storedLineReactionForMatrixKey, and this call returns unsupportedMatrixReactionError.

Trigger: any user tries to re-react (from Matrix) to a paid reaction that was originally bridged before this PR shipped. It resolves itself once someone else reacts and a live 139/140 event creates a new stable-ID row (the HasAllReactions=true sweep then also redacts the legacy row), but until then the paid MXC in the picker looks broken.

Worth calling out in the changelog / considering a one-shot migration that populates metadata for existing rows from Emoji if it's an MXC URL that maps to a known LINE sticker.

Comment thread pkg/connector/reaction.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pkg/connector/reaction_test.go`:
- Around line 797-810: Strengthen the assertions around liveReactionSyncEvent in
the reaction test to verify that only Uother is present in add.Reactions.Users
and remove.Reactions.Users. Keep the existing Uother validation, and assert the
collection size or explicitly reject any additional sender entries so
regressions affecting other users fail.
- Around line 581-582: Update the response assertion in the reaction test to
require the stable resolved EmojiID value for 👍️, replacing the current
non-empty check while preserving the existing SenderID, Emoji, and MaxReactions
assertions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8bab1afb-13f0-40cf-87c3-5eea0a2c265d

📥 Commits

Reviewing files that changed from the base of the PR and between 67e653a and 2963ffb.

📒 Files selected for processing (5)
  • pkg/connector/client.go
  • pkg/connector/connector.go
  • pkg/connector/reaction.go
  • pkg/connector/reaction_test.go
  • pkg/connector/sync.go
💤 Files with no reviewable changes (1)
  • pkg/connector/client.go
📜 Review details
⏰ Context from checks skipped due to timeout. (2)
  • GitHub Check: build-docker
  • GitHub Check: Lint with 1.25
🧰 Additional context used
📓 Path-based instructions (3)
**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

**/*.go: Use go fmt for code formatting across all Go files
Use goimports with -local "github.com/highesttt/matrix-line-messenger" flag to group project-local imports correctly
Use zerolog for logging throughout the codebase
Do not use Msgf in logging; use Msg with structured fields instead
Use Stringer interface where applicable in Go code

Files:

  • pkg/connector/connector.go
  • pkg/connector/sync.go
  • pkg/connector/reaction_test.go
  • pkg/connector/reaction.go
**/!(ltsm)/**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

**/!(ltsm)/**/*.go: Run staticcheck on all Go files excluding pkg/ltsm package (transpiled WASM code)
Run go vet on all Go files excluding pkg/ltsm package (transpiled WASM code)

Files:

  • pkg/connector/connector.go
  • pkg/connector/sync.go
  • pkg/connector/reaction_test.go
  • pkg/connector/reaction.go
pkg/connector/connector.go

📄 CodeRabbit inference engine (AGENTS.md)

Implement bridgev2.NetworkConnector and bridgev2.NetworkAPI interfaces in the connector package for bridge logic

Files:

  • pkg/connector/connector.go
🔇 Additional comments (15)
pkg/connector/reaction_test.go (2)

437-503: LGTM!

Also applies to: 505-563, 720-742


34-36: 📐 Maintainability & Code Quality

No change needed here.

			> Likely an incorrect or invalid review comment.
pkg/connector/reaction.go (9)

39-46: LGTM!


59-84: LGTM!


86-95: LGTM!


107-113: LGTM!


365-394: LGTM!


412-421: LGTM!

Also applies to: 431-431


562-593: LGTM!


707-719: LGTM!

Also applies to: 721-751


97-105: 🎯 Functional Correctness

equal() may be too strict for reaction reuse. If ResourceType or Version can differ for the same visible paid reaction, storedLineReactionForMatrixKey will treat matching rows as conflicting and skip reuse.

pkg/connector/connector.go (1)

102-107: LGTM!

pkg/connector/sync.go (3)

2063-2079: LGTM!


2081-2127: LGTM!


2129-2139: LGTM!

Comment on lines +581 to +582
if resp.SenderID != "Uself" || resp.EmojiID == "" || resp.Emoji != "\U0001F44D\uFE0F" || resp.MaxReactions != 1 {
t.Fatalf("pre-handle response = %#v", resp)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n -C 3 --type go '1F44D|predefined:2|linePaidReactionForMatrixEmoji' pkg/connector pkg/line

Repository: beeper/line

Length of output: 21601


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '500,590p' pkg/connector/reaction_test.go
printf '\n----\n'
sed -n '520,590p' pkg/connector/reaction.go
printf '\n----\n'
sed -n '1,140p' pkg/line/reaction.go

Repository: beeper/line

Length of output: 8056


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,220p' pkg/connector/reaction.go
printf '\n----\n'
sed -n '220,520p' pkg/connector/reaction.go
printf '\n----\n'
sed -n '1,220p' pkg/connector/reaction_test.go

Repository: beeper/line

Length of output: 26827


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 4 'func .*PreHandleMatrixReaction|EmojiID:' pkg/connector

Repository: beeper/line

Length of output: 8873


Assert the resolved LINE emoji ID here. EmojiID should be pinned to the expected stable value for 👍️ (for example, paid:670e0cce840a8236ddd4ee4c:143) instead of only checking it is non-empty.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/connector/reaction_test.go` around lines 581 - 582, Update the response
assertion in the reaction test to require the stable resolved EmojiID value for
👍️, replacing the current non-empty check while preserving the existing
SenderID, Emoji, and MaxReactions assertions.

Comment on lines +797 to +810
t.Fatal("single-sender live sync was marked authoritative for all users")
}
userSync := add.Reactions.Users["Uother"]
if userSync == nil || !userSync.HasAllReactions || len(userSync.Reactions) != 1 || userSync.Reactions[0] != reaction {
t.Fatalf("add user sync = %#v", userSync)
}
if want := time.UnixMilli(1784930400123); !add.Timestamp.Equal(want) {
t.Fatalf("add timestamp = %s, want %s", add.Timestamp, want)
}

remove := lc.liveReactionSyncEvent(op, "Cgroup", "Uother", nil)
userSync = remove.Reactions.Users["Uother"]
if userSync == nil || !userSync.HasAllReactions || len(userSync.Reactions) != 0 {
t.Fatalf("remove user sync = %#v", userSync)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Verify no other sender is marked authoritative.

The test checks Uother's entry but not that it is the only entry. A regression that also clears another sender would still pass.

Proposed test strengthening
  if add.Reactions.HasAllUsers {
    t.Fatal("single-sender live sync was marked authoritative for all users")
  }
+ if len(add.Reactions.Users) != 1 {
+   t.Fatalf("add sync has %d authoritative users, want 1", len(add.Reactions.Users))
+ }
  userSync := add.Reactions.Users["Uother"]
  if userSync == nil || !userSync.HasAllReactions || len(userSync.Reactions) != 1 || userSync.Reactions[0] != reaction {
    t.Fatalf("add user sync = %#v", userSync)
  }
@@
  remove := lc.liveReactionSyncEvent(op, "Cgroup", "Uother", nil)
+ if len(remove.Reactions.Users) != 1 {
+   t.Fatalf("remove sync has %d authoritative users, want 1", len(remove.Reactions.Users))
+ }
  userSync = remove.Reactions.Users["Uother"]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
t.Fatal("single-sender live sync was marked authoritative for all users")
}
userSync := add.Reactions.Users["Uother"]
if userSync == nil || !userSync.HasAllReactions || len(userSync.Reactions) != 1 || userSync.Reactions[0] != reaction {
t.Fatalf("add user sync = %#v", userSync)
}
if want := time.UnixMilli(1784930400123); !add.Timestamp.Equal(want) {
t.Fatalf("add timestamp = %s, want %s", add.Timestamp, want)
}
remove := lc.liveReactionSyncEvent(op, "Cgroup", "Uother", nil)
userSync = remove.Reactions.Users["Uother"]
if userSync == nil || !userSync.HasAllReactions || len(userSync.Reactions) != 0 {
t.Fatalf("remove user sync = %#v", userSync)
t.Fatal("single-sender live sync was marked authoritative for all users")
}
if len(add.Reactions.Users) != 1 {
t.Fatalf("add sync has %d authoritative users, want 1", len(add.Reactions.Users))
}
userSync := add.Reactions.Users["Uother"]
if userSync == nil || !userSync.HasAllReactions || len(userSync.Reactions) != 1 || userSync.Reactions[0] != reaction {
t.Fatalf("add user sync = %#v", userSync)
}
if want := time.UnixMilli(1784930400123); !add.Timestamp.Equal(want) {
t.Fatalf("add timestamp = %s, want %s", add.Timestamp, want)
}
remove := lc.liveReactionSyncEvent(op, "Cgroup", "Uother", nil)
if len(remove.Reactions.Users) != 1 {
t.Fatalf("remove sync has %d authoritative users, want 1", len(remove.Reactions.Users))
}
userSync = remove.Reactions.Users["Uother"]
if userSync == nil || !userSync.HasAllReactions || len(userSync.Reactions) != 0 {
t.Fatalf("remove user sync = %#v", userSync)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/connector/reaction_test.go` around lines 797 - 810, Strengthen the
assertions around liveReactionSyncEvent in the reaction test to verify that only
Uother is present in add.Reactions.Users and remove.Reactions.Users. Keep the
existing Uother validation, and assert the collection size or explicitly reject
any additional sender entries so regressions affecting other users fail.

@highesttt
highesttt merged commit cf5e471 into main Jul 28, 2026
10 checks passed
@highesttt
highesttt deleted the highest/plat-38162 branch July 28, 2026 18:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant