refactor(signal): share reaction send helper

This commit is contained in:
Peter Steinberger
2026-02-15 05:41:10 +00:00
parent a14d275b2a
commit e163883fb3

View File

@@ -22,6 +22,13 @@ export type SignalReactionResult = {
timestamp?: number;
};
type SignalReactionErrorMessages = {
missingRecipient: string;
invalidTargetTimestamp: string;
missingEmoji: string;
missingTargetAuthor: string;
};
function normalizeSignalId(raw: string): string {
const trimmed = raw.trim();
if (!trimmed) {
@@ -60,6 +67,69 @@ function resolveTargetAuthorParams(params: {
return {};
}
async function sendReactionSignalCore(params: {
recipient: string;
targetTimestamp: number;
emoji: string;
remove: boolean;
opts: SignalReactionOpts;
errors: SignalReactionErrorMessages;
}): Promise<SignalReactionResult> {
const accountInfo = resolveSignalAccount({
cfg: loadConfig(),
accountId: params.opts.accountId,
});
const { baseUrl, account } = resolveSignalRpcContext(params.opts, accountInfo);
const normalizedRecipient = normalizeSignalUuid(params.recipient);
const groupId = params.opts.groupId?.trim();
if (!normalizedRecipient && !groupId) {
throw new Error(params.errors.missingRecipient);
}
if (!Number.isFinite(params.targetTimestamp) || params.targetTimestamp <= 0) {
throw new Error(params.errors.invalidTargetTimestamp);
}
const normalizedEmoji = params.emoji?.trim();
if (!normalizedEmoji) {
throw new Error(params.errors.missingEmoji);
}
const targetAuthorParams = resolveTargetAuthorParams({
targetAuthor: params.opts.targetAuthor,
targetAuthorUuid: params.opts.targetAuthorUuid,
fallback: normalizedRecipient,
});
if (groupId && !targetAuthorParams.targetAuthor) {
throw new Error(params.errors.missingTargetAuthor);
}
const requestParams: Record<string, unknown> = {
emoji: normalizedEmoji,
targetTimestamp: params.targetTimestamp,
...(params.remove ? { remove: true } : {}),
...targetAuthorParams,
};
if (normalizedRecipient) {
requestParams.recipients = [normalizedRecipient];
}
if (groupId) {
requestParams.groupIds = [groupId];
}
if (account) {
requestParams.account = account;
}
const result = await signalRpcRequest<{ timestamp?: number }>("sendReaction", requestParams, {
baseUrl,
timeoutMs: params.opts.timeoutMs,
});
return {
ok: true,
timestamp: result?.timestamp,
};
}
/**
* Send a Signal reaction to a message
* @param recipient - UUID or E.164 phone number of the message author
@@ -73,57 +143,19 @@ export async function sendReactionSignal(
emoji: string,
opts: SignalReactionOpts = {},
): Promise<SignalReactionResult> {
const accountInfo = resolveSignalAccount({
cfg: loadConfig(),
accountId: opts.accountId,
});
const { baseUrl, account } = resolveSignalRpcContext(opts, accountInfo);
const normalizedRecipient = normalizeSignalUuid(recipient);
const groupId = opts.groupId?.trim();
if (!normalizedRecipient && !groupId) {
throw new Error("Recipient or groupId is required for Signal reaction");
}
if (!Number.isFinite(targetTimestamp) || targetTimestamp <= 0) {
throw new Error("Valid targetTimestamp is required for Signal reaction");
}
if (!emoji?.trim()) {
throw new Error("Emoji is required for Signal reaction");
}
const targetAuthorParams = resolveTargetAuthorParams({
targetAuthor: opts.targetAuthor,
targetAuthorUuid: opts.targetAuthorUuid,
fallback: normalizedRecipient,
});
if (groupId && !targetAuthorParams.targetAuthor) {
throw new Error("targetAuthor is required for group reactions");
}
const params: Record<string, unknown> = {
emoji: emoji.trim(),
return await sendReactionSignalCore({
recipient,
targetTimestamp,
...targetAuthorParams,
};
if (normalizedRecipient) {
params.recipients = [normalizedRecipient];
}
if (groupId) {
params.groupIds = [groupId];
}
if (account) {
params.account = account;
}
const result = await signalRpcRequest<{ timestamp?: number }>("sendReaction", params, {
baseUrl,
timeoutMs: opts.timeoutMs,
emoji,
remove: false,
opts,
errors: {
missingRecipient: "Recipient or groupId is required for Signal reaction",
invalidTargetTimestamp: "Valid targetTimestamp is required for Signal reaction",
missingEmoji: "Emoji is required for Signal reaction",
missingTargetAuthor: "targetAuthor is required for group reactions",
},
});
return {
ok: true,
timestamp: result?.timestamp,
};
}
/**
@@ -139,56 +171,17 @@ export async function removeReactionSignal(
emoji: string,
opts: SignalReactionOpts = {},
): Promise<SignalReactionResult> {
const accountInfo = resolveSignalAccount({
cfg: loadConfig(),
accountId: opts.accountId,
});
const { baseUrl, account } = resolveSignalRpcContext(opts, accountInfo);
const normalizedRecipient = normalizeSignalUuid(recipient);
const groupId = opts.groupId?.trim();
if (!normalizedRecipient && !groupId) {
throw new Error("Recipient or groupId is required for Signal reaction removal");
}
if (!Number.isFinite(targetTimestamp) || targetTimestamp <= 0) {
throw new Error("Valid targetTimestamp is required for Signal reaction removal");
}
if (!emoji?.trim()) {
throw new Error("Emoji is required for Signal reaction removal");
}
const targetAuthorParams = resolveTargetAuthorParams({
targetAuthor: opts.targetAuthor,
targetAuthorUuid: opts.targetAuthorUuid,
fallback: normalizedRecipient,
});
if (groupId && !targetAuthorParams.targetAuthor) {
throw new Error("targetAuthor is required for group reaction removal");
}
const params: Record<string, unknown> = {
emoji: emoji.trim(),
return await sendReactionSignalCore({
recipient,
targetTimestamp,
emoji,
remove: true,
...targetAuthorParams,
};
if (normalizedRecipient) {
params.recipients = [normalizedRecipient];
}
if (groupId) {
params.groupIds = [groupId];
}
if (account) {
params.account = account;
}
const result = await signalRpcRequest<{ timestamp?: number }>("sendReaction", params, {
baseUrl,
timeoutMs: opts.timeoutMs,
opts,
errors: {
missingRecipient: "Recipient or groupId is required for Signal reaction removal",
invalidTargetTimestamp: "Valid targetTimestamp is required for Signal reaction removal",
missingEmoji: "Emoji is required for Signal reaction removal",
missingTargetAuthor: "targetAuthor is required for group reaction removal",
},
});
return {
ok: true,
timestamp: result?.timestamp,
};
}