mirror of
https://github.com/moltbot/moltbot.git
synced 2026-03-07 22:44:16 +00:00
refactor(outbound): reuse signal uuid detection and payload types
This commit is contained in:
@@ -25,9 +25,7 @@ type DeliveryMirrorPayload = {
|
||||
mediaUrls?: string[];
|
||||
};
|
||||
|
||||
export interface QueuedDelivery {
|
||||
id: string;
|
||||
enqueuedAt: number;
|
||||
type QueuedDeliveryPayload = {
|
||||
channel: Exclude<OutboundChannel, "none">;
|
||||
to: string;
|
||||
accountId?: string;
|
||||
@@ -43,6 +41,11 @@ export interface QueuedDelivery {
|
||||
gifPlayback?: boolean;
|
||||
silent?: boolean;
|
||||
mirror?: DeliveryMirrorPayload;
|
||||
};
|
||||
|
||||
export interface QueuedDelivery extends QueuedDeliveryPayload {
|
||||
id: string;
|
||||
enqueuedAt: number;
|
||||
retryCount: number;
|
||||
lastError?: string;
|
||||
}
|
||||
@@ -65,18 +68,7 @@ export async function ensureQueueDir(stateDir?: string): Promise<string> {
|
||||
}
|
||||
|
||||
/** Persist a delivery entry to disk before attempting send. Returns the entry ID. */
|
||||
type QueuedDeliveryParams = {
|
||||
channel: Exclude<OutboundChannel, "none">;
|
||||
to: string;
|
||||
accountId?: string;
|
||||
payloads: ReplyPayload[];
|
||||
threadId?: string | number | null;
|
||||
replyToId?: string | null;
|
||||
bestEffort?: boolean;
|
||||
gifPlayback?: boolean;
|
||||
silent?: boolean;
|
||||
mirror?: DeliveryMirrorPayload;
|
||||
};
|
||||
type QueuedDeliveryParams = QueuedDeliveryPayload;
|
||||
|
||||
export async function enqueueDelivery(
|
||||
params: QueuedDeliveryParams,
|
||||
|
||||
@@ -9,6 +9,7 @@ import { parseIMessageTarget, normalizeIMessageHandle } from "../../imessage/tar
|
||||
import { buildAgentSessionKey, type RoutePeer } from "../../routing/resolve-route.js";
|
||||
import { resolveThreadSessionKeys } from "../../routing/session-key.js";
|
||||
import {
|
||||
looksLikeUuid,
|
||||
resolveSignalPeerId,
|
||||
resolveSignalRecipient,
|
||||
resolveSignalSender,
|
||||
@@ -44,22 +45,9 @@ export type ResolveOutboundSessionRouteParams = {
|
||||
threadId?: string | number | null;
|
||||
};
|
||||
|
||||
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
||||
const UUID_COMPACT_RE = /^[0-9a-f]{32}$/i;
|
||||
// Cache Slack channel type lookups to avoid repeated API calls.
|
||||
const SLACK_CHANNEL_TYPE_CACHE = new Map<string, "channel" | "group" | "dm" | "unknown">();
|
||||
|
||||
function looksLikeUuid(value: string): boolean {
|
||||
if (UUID_RE.test(value) || UUID_COMPACT_RE.test(value)) {
|
||||
return true;
|
||||
}
|
||||
const compact = value.replace(/-/g, "");
|
||||
if (!/^[0-9a-f]+$/i.test(compact)) {
|
||||
return false;
|
||||
}
|
||||
return /[a-f]/i.test(compact);
|
||||
}
|
||||
|
||||
function normalizeThreadId(value?: string | number | null): string | undefined {
|
||||
if (value == null) {
|
||||
return undefined;
|
||||
@@ -669,9 +657,15 @@ function resolveNextcloudTalkSession(
|
||||
function resolveZaloSession(
|
||||
params: ResolveOutboundSessionRouteParams,
|
||||
): OutboundSessionRoute | null {
|
||||
const trimmed = stripProviderPrefix(params.target, "zalo")
|
||||
.replace(/^(zl):/i, "")
|
||||
.trim();
|
||||
return resolveZaloLikeSession(params, "zalo", /^(zl):/i);
|
||||
}
|
||||
|
||||
function resolveZaloLikeSession(
|
||||
params: ResolveOutboundSessionRouteParams,
|
||||
channel: "zalo" | "zalouser",
|
||||
aliasPrefix: RegExp,
|
||||
): OutboundSessionRoute | null {
|
||||
const trimmed = stripProviderPrefix(params.target, channel).replace(aliasPrefix, "").trim();
|
||||
if (!trimmed) {
|
||||
return null;
|
||||
}
|
||||
@@ -681,7 +675,7 @@ function resolveZaloSession(
|
||||
const baseSessionKey = buildBaseSessionKey({
|
||||
cfg: params.cfg,
|
||||
agentId: params.agentId,
|
||||
channel: "zalo",
|
||||
channel,
|
||||
accountId: params.accountId,
|
||||
peer,
|
||||
});
|
||||
@@ -690,39 +684,16 @@ function resolveZaloSession(
|
||||
baseSessionKey,
|
||||
peer,
|
||||
chatType: isGroup ? "group" : "direct",
|
||||
from: isGroup ? `zalo:group:${peerId}` : `zalo:${peerId}`,
|
||||
to: `zalo:${peerId}`,
|
||||
from: isGroup ? `${channel}:group:${peerId}` : `${channel}:${peerId}`,
|
||||
to: `${channel}:${peerId}`,
|
||||
};
|
||||
}
|
||||
|
||||
function resolveZalouserSession(
|
||||
params: ResolveOutboundSessionRouteParams,
|
||||
): OutboundSessionRoute | null {
|
||||
const trimmed = stripProviderPrefix(params.target, "zalouser")
|
||||
.replace(/^(zlu):/i, "")
|
||||
.trim();
|
||||
if (!trimmed) {
|
||||
return null;
|
||||
}
|
||||
const isGroup = trimmed.toLowerCase().startsWith("group:");
|
||||
const peerId = stripKindPrefix(trimmed);
|
||||
// Keep DM vs group aligned with inbound sessions for Zalo Personal.
|
||||
const peer: RoutePeer = { kind: isGroup ? "group" : "direct", id: peerId };
|
||||
const baseSessionKey = buildBaseSessionKey({
|
||||
cfg: params.cfg,
|
||||
agentId: params.agentId,
|
||||
channel: "zalouser",
|
||||
accountId: params.accountId,
|
||||
peer,
|
||||
});
|
||||
return {
|
||||
sessionKey: baseSessionKey,
|
||||
baseSessionKey,
|
||||
peer,
|
||||
chatType: isGroup ? "group" : "direct",
|
||||
from: isGroup ? `zalouser:group:${peerId}` : `zalouser:${peerId}`,
|
||||
to: `zalouser:${peerId}`,
|
||||
};
|
||||
return resolveZaloLikeSession(params, "zalouser", /^(zlu):/i);
|
||||
}
|
||||
|
||||
function resolveNostrSession(
|
||||
|
||||
56
src/signal/identity.test.ts
Normal file
56
src/signal/identity.test.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
looksLikeUuid,
|
||||
resolveSignalPeerId,
|
||||
resolveSignalRecipient,
|
||||
resolveSignalSender,
|
||||
} from "./identity.js";
|
||||
|
||||
describe("looksLikeUuid", () => {
|
||||
it("accepts hyphenated UUIDs", () => {
|
||||
expect(looksLikeUuid("123e4567-e89b-12d3-a456-426614174000")).toBe(true);
|
||||
});
|
||||
|
||||
it("accepts compact UUIDs", () => {
|
||||
expect(looksLikeUuid("123e4567e89b12d3a456426614174000")).toBe(true);
|
||||
});
|
||||
|
||||
it("accepts uuid-like hex values with letters", () => {
|
||||
expect(looksLikeUuid("abcd-1234")).toBe(true);
|
||||
});
|
||||
|
||||
it("rejects numeric ids and phone-like values", () => {
|
||||
expect(looksLikeUuid("1234567890")).toBe(false);
|
||||
expect(looksLikeUuid("+15555551212")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("signal sender identity", () => {
|
||||
it("prefers sourceNumber over sourceUuid", () => {
|
||||
const sender = resolveSignalSender({
|
||||
sourceNumber: " +15550001111 ",
|
||||
sourceUuid: "123e4567-e89b-12d3-a456-426614174000",
|
||||
});
|
||||
expect(sender).toEqual({
|
||||
kind: "phone",
|
||||
raw: "+15550001111",
|
||||
e164: "+15550001111",
|
||||
});
|
||||
});
|
||||
|
||||
it("uses sourceUuid when sourceNumber is missing", () => {
|
||||
const sender = resolveSignalSender({
|
||||
sourceUuid: "123e4567-e89b-12d3-a456-426614174000",
|
||||
});
|
||||
expect(sender).toEqual({
|
||||
kind: "uuid",
|
||||
raw: "123e4567-e89b-12d3-a456-426614174000",
|
||||
});
|
||||
});
|
||||
|
||||
it("maps uuid senders to recipient and peer ids", () => {
|
||||
const sender = { kind: "uuid", raw: "123e4567-e89b-12d3-a456-426614174000" } as const;
|
||||
expect(resolveSignalRecipient(sender)).toBe("123e4567-e89b-12d3-a456-426614174000");
|
||||
expect(resolveSignalPeerId(sender)).toBe("uuid:123e4567-e89b-12d3-a456-426614174000");
|
||||
});
|
||||
});
|
||||
@@ -12,7 +12,7 @@ type SignalAllowEntry =
|
||||
const UUID_HYPHENATED_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
||||
const UUID_COMPACT_RE = /^[0-9a-f]{32}$/i;
|
||||
|
||||
function looksLikeUuid(value: string): boolean {
|
||||
export function looksLikeUuid(value: string): boolean {
|
||||
if (UUID_HYPHENATED_RE.test(value) || UUID_COMPACT_RE.test(value)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user