Merge pull request #637 from neist/main
fix(signal): handle reactions in dataMessage.reaction format
This commit is contained in:
@@ -35,6 +35,7 @@
|
|||||||
- iOS/Android: enable stricter concurrency/lint checks; fix Swift 6 strict concurrency issues + Android lint errors (ExifInterface, obsolete SDK check). (#662) — thanks @KristijanJovanovski.
|
- iOS/Android: enable stricter concurrency/lint checks; fix Swift 6 strict concurrency issues + Android lint errors (ExifInterface, obsolete SDK check). (#662) — thanks @KristijanJovanovski.
|
||||||
- iOS/macOS: share `AsyncTimeout`, require explicit `bridgeStableID` on connect, and harden tool display defaults (avoids missing-resource label fallbacks).
|
- iOS/macOS: share `AsyncTimeout`, require explicit `bridgeStableID` on connect, and harden tool display defaults (avoids missing-resource label fallbacks).
|
||||||
- Telegram: serialize media-group processing to avoid missed albums under load.
|
- Telegram: serialize media-group processing to avoid missed albums under load.
|
||||||
|
- Signal: handle `dataMessage.reaction` events (signal-cli SSE) to avoid broken attachment errors. (#637) — thanks @neist.
|
||||||
- Docs: showcase entries for ParentPay, R2 Upload, iOS TestFlight, and Oura Health. (#650) — thanks @henrino3.
|
- Docs: showcase entries for ParentPay, R2 Upload, iOS TestFlight, and Oura Health. (#650) — thanks @henrino3.
|
||||||
|
|
||||||
## 2026.1.9
|
## 2026.1.9
|
||||||
|
|||||||
@@ -197,6 +197,45 @@ describe("monitorSignalProvider tool results", () => {
|
|||||||
expect(updateLastRouteMock).not.toHaveBeenCalled();
|
expect(updateLastRouteMock).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("ignores reaction-only messages when reactions live in dataMessage", async () => {
|
||||||
|
const abortController = new AbortController();
|
||||||
|
|
||||||
|
streamMock.mockImplementation(async ({ onEvent }) => {
|
||||||
|
const payload = {
|
||||||
|
envelope: {
|
||||||
|
sourceNumber: "+15550001111",
|
||||||
|
sourceName: "Ada",
|
||||||
|
timestamp: 1,
|
||||||
|
dataMessage: {
|
||||||
|
reaction: {
|
||||||
|
emoji: "👍",
|
||||||
|
targetAuthor: "+15550002222",
|
||||||
|
targetSentTimestamp: 2,
|
||||||
|
},
|
||||||
|
attachments: [{}],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
await onEvent({
|
||||||
|
event: "receive",
|
||||||
|
data: JSON.stringify(payload),
|
||||||
|
});
|
||||||
|
abortController.abort();
|
||||||
|
});
|
||||||
|
|
||||||
|
await monitorSignalProvider({
|
||||||
|
autoStart: false,
|
||||||
|
baseUrl: "http://127.0.0.1:8080",
|
||||||
|
abortSignal: abortController.signal,
|
||||||
|
});
|
||||||
|
|
||||||
|
await flush();
|
||||||
|
|
||||||
|
expect(replyMock).not.toHaveBeenCalled();
|
||||||
|
expect(sendMock).not.toHaveBeenCalled();
|
||||||
|
expect(updateLastRouteMock).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
it("enqueues system events for reaction notifications", async () => {
|
it("enqueues system events for reaction notifications", async () => {
|
||||||
config = {
|
config = {
|
||||||
...config,
|
...config,
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ type SignalDataMessage = {
|
|||||||
groupName?: string | null;
|
groupName?: string | null;
|
||||||
} | null;
|
} | null;
|
||||||
quote?: { text?: string | null } | null;
|
quote?: { text?: string | null } | null;
|
||||||
|
reaction?: SignalReactionMessage | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
type SignalAttachment = {
|
type SignalAttachment = {
|
||||||
@@ -143,6 +144,20 @@ function resolveSignalReactionTargets(
|
|||||||
return targets;
|
return targets;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isSignalReactionMessage(
|
||||||
|
reaction: SignalReactionMessage | null | undefined,
|
||||||
|
): reaction is SignalReactionMessage {
|
||||||
|
if (!reaction) return false;
|
||||||
|
const emoji = reaction.emoji?.trim();
|
||||||
|
const timestamp = reaction.targetSentTimestamp;
|
||||||
|
const hasTarget = Boolean(
|
||||||
|
reaction.targetAuthor?.trim() || reaction.targetAuthorUuid?.trim(),
|
||||||
|
);
|
||||||
|
return Boolean(
|
||||||
|
emoji && typeof timestamp === "number" && timestamp > 0 && hasTarget,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function shouldEmitSignalReactionNotification(params: {
|
function shouldEmitSignalReactionNotification(params: {
|
||||||
mode?: SignalReactionNotificationMode;
|
mode?: SignalReactionNotificationMode;
|
||||||
account?: string | null;
|
account?: string | null;
|
||||||
@@ -403,8 +418,17 @@ export async function monitorSignalProvider(
|
|||||||
}
|
}
|
||||||
const dataMessage =
|
const dataMessage =
|
||||||
envelope.dataMessage ?? envelope.editMessage?.dataMessage;
|
envelope.dataMessage ?? envelope.editMessage?.dataMessage;
|
||||||
if (envelope.reactionMessage && !dataMessage) {
|
const reaction = isSignalReactionMessage(envelope.reactionMessage)
|
||||||
const reaction = envelope.reactionMessage;
|
? envelope.reactionMessage
|
||||||
|
: isSignalReactionMessage(dataMessage?.reaction)
|
||||||
|
? dataMessage?.reaction
|
||||||
|
: null;
|
||||||
|
const messageText = (dataMessage?.message ?? "").trim();
|
||||||
|
const quoteText = dataMessage?.quote?.text?.trim() ?? "";
|
||||||
|
const hasBodyContent =
|
||||||
|
Boolean(messageText || quoteText) ||
|
||||||
|
Boolean(!reaction && dataMessage?.attachments?.length);
|
||||||
|
if (reaction && !hasBodyContent) {
|
||||||
if (reaction.isRemove) return; // Ignore reaction removals
|
if (reaction.isRemove) return; // Ignore reaction removals
|
||||||
const emojiLabel = reaction.emoji?.trim() || "emoji";
|
const emojiLabel = reaction.emoji?.trim() || "emoji";
|
||||||
const senderDisplay = formatSignalSenderDisplay(sender);
|
const senderDisplay = formatSignalSenderDisplay(sender);
|
||||||
@@ -550,7 +574,6 @@ export async function monitorSignalProvider(
|
|||||||
? isSignalSenderAllowed(sender, effectiveGroupAllow)
|
? isSignalSenderAllowed(sender, effectiveGroupAllow)
|
||||||
: true
|
: true
|
||||||
: dmAllowed;
|
: dmAllowed;
|
||||||
const messageText = (dataMessage.message ?? "").trim();
|
|
||||||
|
|
||||||
let mediaPath: string | undefined;
|
let mediaPath: string | undefined;
|
||||||
let mediaType: string | undefined;
|
let mediaType: string | undefined;
|
||||||
|
|||||||
Reference in New Issue
Block a user