chore: migrate to oxlint and oxfmt
Co-authored-by: Christoph Nakazawa <christoph.pojer@gmail.com>
This commit is contained in:
@@ -1,9 +1,6 @@
|
||||
import type { ClawdbotConfig } from "../config/config.js";
|
||||
import type { IMessageAccountConfig } from "../config/types.js";
|
||||
import {
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
normalizeAccountId,
|
||||
} from "../routing/session-key.js";
|
||||
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../routing/session-key.js";
|
||||
|
||||
export type ResolvedIMessageAccount = {
|
||||
accountId: string;
|
||||
@@ -40,10 +37,7 @@ function resolveAccountConfig(
|
||||
return accounts[accountId] as IMessageAccountConfig | undefined;
|
||||
}
|
||||
|
||||
function mergeIMessageAccountConfig(
|
||||
cfg: ClawdbotConfig,
|
||||
accountId: string,
|
||||
): IMessageAccountConfig {
|
||||
function mergeIMessageAccountConfig(cfg: ClawdbotConfig, accountId: string): IMessageAccountConfig {
|
||||
const { accounts: _ignored, ...base } = (cfg.channels?.imessage ??
|
||||
{}) as IMessageAccountConfig & { accounts?: unknown };
|
||||
const account = resolveAccountConfig(cfg, accountId) ?? {};
|
||||
@@ -60,17 +54,17 @@ export function resolveIMessageAccount(params: {
|
||||
const accountEnabled = merged.enabled !== false;
|
||||
const configured = Boolean(
|
||||
merged.cliPath?.trim() ||
|
||||
merged.dbPath?.trim() ||
|
||||
merged.service ||
|
||||
merged.region?.trim() ||
|
||||
(merged.allowFrom && merged.allowFrom.length > 0) ||
|
||||
(merged.groupAllowFrom && merged.groupAllowFrom.length > 0) ||
|
||||
merged.dmPolicy ||
|
||||
merged.groupPolicy ||
|
||||
typeof merged.includeAttachments === "boolean" ||
|
||||
typeof merged.mediaMaxMb === "number" ||
|
||||
typeof merged.textChunkLimit === "number" ||
|
||||
(merged.groups && Object.keys(merged.groups).length > 0),
|
||||
merged.dbPath?.trim() ||
|
||||
merged.service ||
|
||||
merged.region?.trim() ||
|
||||
(merged.allowFrom && merged.allowFrom.length > 0) ||
|
||||
(merged.groupAllowFrom && merged.groupAllowFrom.length > 0) ||
|
||||
merged.dmPolicy ||
|
||||
merged.groupPolicy ||
|
||||
typeof merged.includeAttachments === "boolean" ||
|
||||
typeof merged.mediaMaxMb === "number" ||
|
||||
typeof merged.textChunkLimit === "number" ||
|
||||
(merged.groups && Object.keys(merged.groups).length > 0),
|
||||
);
|
||||
return {
|
||||
accountId,
|
||||
@@ -81,9 +75,7 @@ export function resolveIMessageAccount(params: {
|
||||
};
|
||||
}
|
||||
|
||||
export function listEnabledIMessageAccounts(
|
||||
cfg: ClawdbotConfig,
|
||||
): ResolvedIMessageAccount[] {
|
||||
export function listEnabledIMessageAccounts(cfg: ClawdbotConfig): ResolvedIMessageAccount[] {
|
||||
return listIMessageAccountIds(cfg)
|
||||
.map((accountId) => resolveIMessageAccount({ cfg, accountId }))
|
||||
.filter((account) => account.enabled);
|
||||
|
||||
@@ -51,9 +51,7 @@ export class IMessageRpcClient {
|
||||
|
||||
constructor(opts: IMessageRpcClientOptions = {}) {
|
||||
this.cliPath = opts.cliPath?.trim() || "imsg";
|
||||
this.dbPath = opts.dbPath?.trim()
|
||||
? resolveUserPath(opts.dbPath)
|
||||
: undefined;
|
||||
this.dbPath = opts.dbPath?.trim() ? resolveUserPath(opts.dbPath) : undefined;
|
||||
this.runtime = opts.runtime;
|
||||
this.onNotification = opts.onNotification;
|
||||
this.closed = new Promise((resolve) => {
|
||||
|
||||
@@ -11,9 +11,7 @@ const readAllowFromStoreMock = vi.fn();
|
||||
const upsertPairingRequestMock = vi.fn();
|
||||
|
||||
let config: Record<string, unknown> = {};
|
||||
let notificationHandler:
|
||||
| ((msg: { method: string; params?: unknown }) => void)
|
||||
| undefined;
|
||||
let notificationHandler: ((msg: { method: string; params?: unknown }) => void) | undefined;
|
||||
let closeResolve: (() => void) | undefined;
|
||||
|
||||
vi.mock("../config/config.js", async (importOriginal) => {
|
||||
@@ -33,10 +31,8 @@ vi.mock("./send.js", () => ({
|
||||
}));
|
||||
|
||||
vi.mock("../pairing/pairing-store.js", () => ({
|
||||
readChannelAllowFromStore: (...args: unknown[]) =>
|
||||
readAllowFromStoreMock(...args),
|
||||
upsertChannelPairingRequest: (...args: unknown[]) =>
|
||||
upsertPairingRequestMock(...args),
|
||||
readChannelAllowFromStore: (...args: unknown[]) => readAllowFromStoreMock(...args),
|
||||
upsertChannelPairingRequest: (...args: unknown[]) => upsertPairingRequestMock(...args),
|
||||
}));
|
||||
|
||||
vi.mock("../config/sessions.js", () => ({
|
||||
@@ -45,27 +41,24 @@ vi.mock("../config/sessions.js", () => ({
|
||||
}));
|
||||
|
||||
vi.mock("./client.js", () => ({
|
||||
createIMessageRpcClient: vi.fn(
|
||||
async (opts: { onNotification?: typeof notificationHandler }) => {
|
||||
notificationHandler = opts.onNotification;
|
||||
return {
|
||||
request: (...args: unknown[]) => requestMock(...args),
|
||||
waitForClose: () =>
|
||||
new Promise<void>((resolve) => {
|
||||
closeResolve = resolve;
|
||||
}),
|
||||
stop: (...args: unknown[]) => stopMock(...args),
|
||||
};
|
||||
},
|
||||
),
|
||||
createIMessageRpcClient: vi.fn(async (opts: { onNotification?: typeof notificationHandler }) => {
|
||||
notificationHandler = opts.onNotification;
|
||||
return {
|
||||
request: (...args: unknown[]) => requestMock(...args),
|
||||
waitForClose: () =>
|
||||
new Promise<void>((resolve) => {
|
||||
closeResolve = resolve;
|
||||
}),
|
||||
stop: (...args: unknown[]) => stopMock(...args),
|
||||
};
|
||||
}),
|
||||
}));
|
||||
|
||||
const flush = () => new Promise((resolve) => setTimeout(resolve, 0));
|
||||
|
||||
async function waitForSubscribe() {
|
||||
for (let i = 0; i < 5; i += 1) {
|
||||
if (requestMock.mock.calls.some((call) => call[0] === "watch.subscribe"))
|
||||
return;
|
||||
if (requestMock.mock.calls.some((call) => call[0] === "watch.subscribe")) return;
|
||||
await flush();
|
||||
}
|
||||
}
|
||||
@@ -85,8 +78,7 @@ beforeEach(() => {
|
||||
},
|
||||
};
|
||||
requestMock.mockReset().mockImplementation((method: string) => {
|
||||
if (method === "watch.subscribe")
|
||||
return Promise.resolve({ subscription: 1 });
|
||||
if (method === "watch.subscribe") return Promise.resolve({ subscription: 1 });
|
||||
return Promise.resolve({});
|
||||
});
|
||||
stopMock.mockReset().mockResolvedValue(undefined);
|
||||
@@ -94,9 +86,7 @@ beforeEach(() => {
|
||||
replyMock.mockReset().mockResolvedValue({ text: "ok" });
|
||||
updateLastRouteMock.mockReset();
|
||||
readAllowFromStoreMock.mockReset().mockResolvedValue([]);
|
||||
upsertPairingRequestMock
|
||||
.mockReset()
|
||||
.mockResolvedValue({ code: "PAIRCODE", created: true });
|
||||
upsertPairingRequestMock.mockReset().mockResolvedValue({ code: "PAIRCODE", created: true });
|
||||
notificationHandler = undefined;
|
||||
closeResolve = undefined;
|
||||
});
|
||||
@@ -356,9 +346,7 @@ describe("monitorIMessageProvider", () => {
|
||||
expect(String(sendMock.mock.calls[0]?.[1] ?? "")).toContain(
|
||||
"Your iMessage sender id: +15550001111",
|
||||
);
|
||||
expect(String(sendMock.mock.calls[0]?.[1] ?? "")).toContain(
|
||||
"Pairing code: PAIRCODE",
|
||||
);
|
||||
expect(String(sendMock.mock.calls[0]?.[1] ?? "")).toContain("Pairing code: PAIRCODE");
|
||||
});
|
||||
|
||||
it("delivers group replies when mentioned", async () => {
|
||||
|
||||
@@ -11,9 +11,7 @@ const readAllowFromStoreMock = vi.fn();
|
||||
const upsertPairingRequestMock = vi.fn();
|
||||
|
||||
let config: Record<string, unknown> = {};
|
||||
let notificationHandler:
|
||||
| ((msg: { method: string; params?: unknown }) => void)
|
||||
| undefined;
|
||||
let notificationHandler: ((msg: { method: string; params?: unknown }) => void) | undefined;
|
||||
let closeResolve: (() => void) | undefined;
|
||||
|
||||
vi.mock("../config/config.js", async (importOriginal) => {
|
||||
@@ -33,10 +31,8 @@ vi.mock("./send.js", () => ({
|
||||
}));
|
||||
|
||||
vi.mock("../pairing/pairing-store.js", () => ({
|
||||
readChannelAllowFromStore: (...args: unknown[]) =>
|
||||
readAllowFromStoreMock(...args),
|
||||
upsertChannelPairingRequest: (...args: unknown[]) =>
|
||||
upsertPairingRequestMock(...args),
|
||||
readChannelAllowFromStore: (...args: unknown[]) => readAllowFromStoreMock(...args),
|
||||
upsertChannelPairingRequest: (...args: unknown[]) => upsertPairingRequestMock(...args),
|
||||
}));
|
||||
|
||||
vi.mock("../config/sessions.js", () => ({
|
||||
@@ -45,27 +41,24 @@ vi.mock("../config/sessions.js", () => ({
|
||||
}));
|
||||
|
||||
vi.mock("./client.js", () => ({
|
||||
createIMessageRpcClient: vi.fn(
|
||||
async (opts: { onNotification?: typeof notificationHandler }) => {
|
||||
notificationHandler = opts.onNotification;
|
||||
return {
|
||||
request: (...args: unknown[]) => requestMock(...args),
|
||||
waitForClose: () =>
|
||||
new Promise<void>((resolve) => {
|
||||
closeResolve = resolve;
|
||||
}),
|
||||
stop: (...args: unknown[]) => stopMock(...args),
|
||||
};
|
||||
},
|
||||
),
|
||||
createIMessageRpcClient: vi.fn(async (opts: { onNotification?: typeof notificationHandler }) => {
|
||||
notificationHandler = opts.onNotification;
|
||||
return {
|
||||
request: (...args: unknown[]) => requestMock(...args),
|
||||
waitForClose: () =>
|
||||
new Promise<void>((resolve) => {
|
||||
closeResolve = resolve;
|
||||
}),
|
||||
stop: (...args: unknown[]) => stopMock(...args),
|
||||
};
|
||||
}),
|
||||
}));
|
||||
|
||||
const flush = () => new Promise((resolve) => setTimeout(resolve, 0));
|
||||
|
||||
async function waitForSubscribe() {
|
||||
for (let i = 0; i < 5; i += 1) {
|
||||
if (requestMock.mock.calls.some((call) => call[0] === "watch.subscribe"))
|
||||
return;
|
||||
if (requestMock.mock.calls.some((call) => call[0] === "watch.subscribe")) return;
|
||||
await flush();
|
||||
}
|
||||
}
|
||||
@@ -85,8 +78,7 @@ beforeEach(() => {
|
||||
},
|
||||
};
|
||||
requestMock.mockReset().mockImplementation((method: string) => {
|
||||
if (method === "watch.subscribe")
|
||||
return Promise.resolve({ subscription: 1 });
|
||||
if (method === "watch.subscribe") return Promise.resolve({ subscription: 1 });
|
||||
return Promise.resolve({});
|
||||
});
|
||||
stopMock.mockReset().mockResolvedValue(undefined);
|
||||
@@ -94,9 +86,7 @@ beforeEach(() => {
|
||||
replyMock.mockReset().mockResolvedValue({ text: "ok" });
|
||||
updateLastRouteMock.mockReset();
|
||||
readAllowFromStoreMock.mockReset().mockResolvedValue([]);
|
||||
upsertPairingRequestMock
|
||||
.mockReset()
|
||||
.mockResolvedValue({ code: "PAIRCODE", created: true });
|
||||
upsertPairingRequestMock.mockReset().mockResolvedValue({ code: "PAIRCODE", created: true });
|
||||
notificationHandler = undefined;
|
||||
closeResolve = undefined;
|
||||
});
|
||||
@@ -135,10 +125,8 @@ describe("monitorIMessageProvider", () => {
|
||||
|
||||
it("does not trigger unhandledRejection when aborting during shutdown", async () => {
|
||||
requestMock.mockImplementation((method: string) => {
|
||||
if (method === "watch.subscribe")
|
||||
return Promise.resolve({ subscription: 1 });
|
||||
if (method === "watch.unsubscribe")
|
||||
return Promise.reject(new Error("imsg rpc closed"));
|
||||
if (method === "watch.subscribe") return Promise.resolve({ subscription: 1 });
|
||||
if (method === "watch.unsubscribe") return Promise.reject(new Error("imsg rpc closed"));
|
||||
return Promise.resolve({});
|
||||
});
|
||||
|
||||
|
||||
@@ -13,11 +13,9 @@ export async function deliverReplies(params: {
|
||||
maxBytes: number;
|
||||
textLimit: number;
|
||||
}) {
|
||||
const { replies, target, client, runtime, maxBytes, textLimit, accountId } =
|
||||
params;
|
||||
const { replies, target, client, runtime, maxBytes, textLimit, accountId } = params;
|
||||
for (const payload of replies) {
|
||||
const mediaList =
|
||||
payload.mediaUrls ?? (payload.mediaUrl ? [payload.mediaUrl] : []);
|
||||
const mediaList = payload.mediaUrls ?? (payload.mediaUrl ? [payload.mediaUrl] : []);
|
||||
const text = payload.text ?? "";
|
||||
if (!text && mediaList.length === 0) continue;
|
||||
if (mediaList.length === 0) {
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import {
|
||||
resolveEffectiveMessagesConfig,
|
||||
resolveHumanDelayConfig,
|
||||
} from "../../agents/identity.js";
|
||||
import { resolveEffectiveMessagesConfig, resolveHumanDelayConfig } from "../../agents/identity.js";
|
||||
import { resolveTextChunkLimit } from "../../auto-reply/chunk.js";
|
||||
import { hasControlCommand } from "../../auto-reply/command-detection.js";
|
||||
import { formatAgentEnvelope } from "../../auto-reply/envelope.js";
|
||||
@@ -12,10 +9,7 @@ import {
|
||||
DEFAULT_GROUP_HISTORY_LIMIT,
|
||||
type HistoryEntry,
|
||||
} from "../../auto-reply/reply/history.js";
|
||||
import {
|
||||
buildMentionRegexes,
|
||||
matchesMentionPatterns,
|
||||
} from "../../auto-reply/reply/mentions.js";
|
||||
import { buildMentionRegexes, matchesMentionPatterns } from "../../auto-reply/reply/mentions.js";
|
||||
import { createReplyDispatcher } from "../../auto-reply/reply/reply-dispatcher.js";
|
||||
import { loadConfig } from "../../config/config.js";
|
||||
import {
|
||||
@@ -44,9 +38,7 @@ import { deliverReplies } from "./deliver.js";
|
||||
import { normalizeAllowList, resolveRuntime } from "./runtime.js";
|
||||
import type { IMessagePayload, MonitorIMessageOpts } from "./types.js";
|
||||
|
||||
export async function monitorIMessageProvider(
|
||||
opts: MonitorIMessageOpts = {},
|
||||
): Promise<void> {
|
||||
export async function monitorIMessageProvider(opts: MonitorIMessageOpts = {}): Promise<void> {
|
||||
const runtime = resolveRuntime(opts);
|
||||
const cfg = opts.config ?? loadConfig();
|
||||
const accountInfo = resolveIMessageAccount({
|
||||
@@ -61,25 +53,17 @@ export async function monitorIMessageProvider(
|
||||
DEFAULT_GROUP_HISTORY_LIMIT,
|
||||
);
|
||||
const groupHistories = new Map<string, HistoryEntry[]>();
|
||||
const textLimit = resolveTextChunkLimit(
|
||||
cfg,
|
||||
"imessage",
|
||||
accountInfo.accountId,
|
||||
);
|
||||
const textLimit = resolveTextChunkLimit(cfg, "imessage", accountInfo.accountId);
|
||||
const allowFrom = normalizeAllowList(opts.allowFrom ?? imessageCfg.allowFrom);
|
||||
const groupAllowFrom = normalizeAllowList(
|
||||
opts.groupAllowFrom ??
|
||||
imessageCfg.groupAllowFrom ??
|
||||
(imessageCfg.allowFrom && imessageCfg.allowFrom.length > 0
|
||||
? imessageCfg.allowFrom
|
||||
: []),
|
||||
(imessageCfg.allowFrom && imessageCfg.allowFrom.length > 0 ? imessageCfg.allowFrom : []),
|
||||
);
|
||||
const groupPolicy = imessageCfg.groupPolicy ?? "open";
|
||||
const dmPolicy = imessageCfg.dmPolicy ?? "pairing";
|
||||
const includeAttachments =
|
||||
opts.includeAttachments ?? imessageCfg.includeAttachments ?? false;
|
||||
const mediaMaxBytes =
|
||||
(opts.mediaMaxMb ?? imessageCfg.mediaMaxMb ?? 16) * 1024 * 1024;
|
||||
const includeAttachments = opts.includeAttachments ?? imessageCfg.includeAttachments ?? false;
|
||||
const mediaMaxBytes = (opts.mediaMaxMb ?? imessageCfg.mediaMaxMb ?? 16) * 1024 * 1024;
|
||||
|
||||
const handleMessage = async (raw: unknown) => {
|
||||
const params = raw as { message?: IMessagePayload | null };
|
||||
@@ -115,26 +99,18 @@ export async function monitorIMessageProvider(
|
||||
// If the owner explicitly configures a chat_id under imessage.groups, treat
|
||||
// that thread as a "group" for permission gating and session isolation.
|
||||
const treatAsGroupByConfig = Boolean(
|
||||
groupIdCandidate &&
|
||||
groupListPolicy.allowlistEnabled &&
|
||||
groupListPolicy.groupConfig,
|
||||
groupIdCandidate && groupListPolicy.allowlistEnabled && groupListPolicy.groupConfig,
|
||||
);
|
||||
|
||||
const isGroup = Boolean(message.is_group) || treatAsGroupByConfig;
|
||||
if (isGroup && !chatId) return;
|
||||
|
||||
const groupId = isGroup ? groupIdCandidate : undefined;
|
||||
const storeAllowFrom = await readChannelAllowFromStore("imessage").catch(
|
||||
() => [],
|
||||
);
|
||||
const effectiveDmAllowFrom = Array.from(
|
||||
new Set([...allowFrom, ...storeAllowFrom]),
|
||||
)
|
||||
const storeAllowFrom = await readChannelAllowFromStore("imessage").catch(() => []);
|
||||
const effectiveDmAllowFrom = Array.from(new Set([...allowFrom, ...storeAllowFrom]))
|
||||
.map((v) => String(v).trim())
|
||||
.filter(Boolean);
|
||||
const effectiveGroupAllowFrom = Array.from(
|
||||
new Set([...groupAllowFrom, ...storeAllowFrom]),
|
||||
)
|
||||
const effectiveGroupAllowFrom = Array.from(new Set([...groupAllowFrom, ...storeAllowFrom]))
|
||||
.map((v) => String(v).trim())
|
||||
.filter(Boolean);
|
||||
|
||||
@@ -145,9 +121,7 @@ export async function monitorIMessageProvider(
|
||||
}
|
||||
if (groupPolicy === "allowlist") {
|
||||
if (effectiveGroupAllowFrom.length === 0) {
|
||||
logVerbose(
|
||||
"Blocked iMessage group message (groupPolicy: allowlist, no groupAllowFrom)",
|
||||
);
|
||||
logVerbose("Blocked iMessage group message (groupPolicy: allowlist, no groupAllowFrom)");
|
||||
return;
|
||||
}
|
||||
const allowed = isAllowedIMessageSender({
|
||||
@@ -158,16 +132,12 @@ export async function monitorIMessageProvider(
|
||||
chatIdentifier,
|
||||
});
|
||||
if (!allowed) {
|
||||
logVerbose(
|
||||
`Blocked iMessage sender ${sender} (not in groupAllowFrom)`,
|
||||
);
|
||||
logVerbose(`Blocked iMessage sender ${sender} (not in groupAllowFrom)`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (groupListPolicy.allowlistEnabled && !groupListPolicy.allowed) {
|
||||
logVerbose(
|
||||
`imessage: skipping group message (${groupId ?? "unknown"}) not in allowlist`,
|
||||
);
|
||||
logVerbose(`imessage: skipping group message (${groupId ?? "unknown"}) not in allowlist`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -216,15 +186,11 @@ export async function monitorIMessageProvider(
|
||||
},
|
||||
);
|
||||
} catch (err) {
|
||||
logVerbose(
|
||||
`imessage pairing reply failed for ${senderId}: ${String(err)}`,
|
||||
);
|
||||
logVerbose(`imessage pairing reply failed for ${senderId}: ${String(err)}`);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logVerbose(
|
||||
`Blocked iMessage sender ${sender} (dmPolicy=${dmPolicy})`,
|
||||
);
|
||||
logVerbose(`Blocked iMessage sender ${sender} (dmPolicy=${dmPolicy})`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -236,16 +202,12 @@ export async function monitorIMessageProvider(
|
||||
accountId: accountInfo.accountId,
|
||||
peer: {
|
||||
kind: isGroup ? "group" : "dm",
|
||||
id: isGroup
|
||||
? String(chatId ?? "unknown")
|
||||
: normalizeIMessageHandle(sender),
|
||||
id: isGroup ? String(chatId ?? "unknown") : normalizeIMessageHandle(sender),
|
||||
},
|
||||
});
|
||||
const mentionRegexes = buildMentionRegexes(cfg, route.agentId);
|
||||
const messageText = (message.text ?? "").trim();
|
||||
const mentioned = isGroup
|
||||
? matchesMentionPatterns(messageText, mentionRegexes)
|
||||
: true;
|
||||
const mentioned = isGroup ? matchesMentionPatterns(messageText, mentionRegexes) : true;
|
||||
const requireMention = resolveChannelGroupRequireMention({
|
||||
cfg,
|
||||
channel: "imessage",
|
||||
@@ -273,29 +235,17 @@ export async function monitorIMessageProvider(
|
||||
commandAuthorized &&
|
||||
hasControlCommand(messageText);
|
||||
const effectiveWasMentioned = mentioned || shouldBypassMention;
|
||||
if (
|
||||
isGroup &&
|
||||
requireMention &&
|
||||
canDetectMention &&
|
||||
!mentioned &&
|
||||
!shouldBypassMention
|
||||
) {
|
||||
if (isGroup && requireMention && canDetectMention && !mentioned && !shouldBypassMention) {
|
||||
logVerbose(`imessage: skipping group message (no mention)`);
|
||||
return;
|
||||
}
|
||||
|
||||
const attachments = includeAttachments ? (message.attachments ?? []) : [];
|
||||
const firstAttachment = attachments?.find(
|
||||
(entry) => entry?.original_path && !entry?.missing,
|
||||
);
|
||||
const firstAttachment = attachments?.find((entry) => entry?.original_path && !entry?.missing);
|
||||
const mediaPath = firstAttachment?.original_path ?? undefined;
|
||||
const mediaType = firstAttachment?.mime_type ?? undefined;
|
||||
const kind = mediaKindFromMime(mediaType ?? undefined);
|
||||
const placeholder = kind
|
||||
? `<media:${kind}>`
|
||||
: attachments?.length
|
||||
? "<media:attachment>"
|
||||
: "";
|
||||
const placeholder = kind ? `<media:${kind}>` : attachments?.length ? "<media:attachment>" : "";
|
||||
const bodyText = messageText || placeholder;
|
||||
if (!bodyText) return;
|
||||
|
||||
@@ -303,9 +253,7 @@ export async function monitorIMessageProvider(
|
||||
const fromLabel = isGroup
|
||||
? `${message.chat_name || "iMessage Group"} id:${chatId ?? "unknown"}`
|
||||
: `${normalizeIMessageHandle(sender)} id:${sender}`;
|
||||
const createdAt = message.created_at
|
||||
? Date.parse(message.created_at)
|
||||
: undefined;
|
||||
const createdAt = message.created_at ? Date.parse(message.created_at) : undefined;
|
||||
const body = formatAgentEnvelope({
|
||||
channel: "iMessage",
|
||||
from: fromLabel,
|
||||
@@ -351,9 +299,7 @@ export async function monitorIMessageProvider(
|
||||
AccountId: route.accountId,
|
||||
ChatType: isGroup ? "group" : "direct",
|
||||
GroupSubject: isGroup ? (message.chat_name ?? undefined) : undefined,
|
||||
GroupMembers: isGroup
|
||||
? (message.participants ?? []).filter(Boolean).join(", ")
|
||||
: undefined,
|
||||
GroupMembers: isGroup ? (message.participants ?? []).filter(Boolean).join(", ") : undefined,
|
||||
SenderName: sender,
|
||||
SenderId: sender,
|
||||
Provider: "imessage",
|
||||
@@ -396,8 +342,7 @@ export async function monitorIMessageProvider(
|
||||
|
||||
let didSendReply = false;
|
||||
const dispatcher = createReplyDispatcher({
|
||||
responsePrefix: resolveEffectiveMessagesConfig(cfg, route.agentId)
|
||||
.responsePrefix,
|
||||
responsePrefix: resolveEffectiveMessagesConfig(cfg, route.agentId).responsePrefix,
|
||||
humanDelay: resolveHumanDelayConfig(cfg, route.agentId),
|
||||
deliver: async (payload) => {
|
||||
await deliverReplies({
|
||||
@@ -412,9 +357,7 @@ export async function monitorIMessageProvider(
|
||||
didSendReply = true;
|
||||
},
|
||||
onError: (err, info) => {
|
||||
runtime.error?.(
|
||||
danger(`imessage ${info.kind} reply failed: ${String(err)}`),
|
||||
);
|
||||
runtime.error?.(danger(`imessage ${info.kind} reply failed: ${String(err)}`));
|
||||
},
|
||||
});
|
||||
|
||||
@@ -474,10 +417,9 @@ export async function monitorIMessageProvider(
|
||||
abort?.addEventListener("abort", onAbort, { once: true });
|
||||
|
||||
try {
|
||||
const result = await client.request<{ subscription?: number }>(
|
||||
"watch.subscribe",
|
||||
{ attachments: includeAttachments },
|
||||
);
|
||||
const result = await client.request<{ subscription?: number }>("watch.subscribe", {
|
||||
attachments: includeAttachments,
|
||||
});
|
||||
subscriptionId = result?.subscription ?? null;
|
||||
await client.waitForClose();
|
||||
} catch (err) {
|
||||
|
||||
@@ -43,11 +43,7 @@ describe("sendMessageIMessage", () => {
|
||||
it("sends to chat_id targets", async () => {
|
||||
await sendMessageIMessage("chat_id:123", "hi");
|
||||
const params = requestMock.mock.calls[0]?.[1] as Record<string, unknown>;
|
||||
expect(requestMock).toHaveBeenCalledWith(
|
||||
"send",
|
||||
expect.any(Object),
|
||||
expect.any(Object),
|
||||
);
|
||||
expect(requestMock).toHaveBeenCalledWith("send", expect.any(Object), expect.any(Object));
|
||||
expect(params.chat_id).toBe(123);
|
||||
expect(params.text).toBe("hi");
|
||||
});
|
||||
|
||||
@@ -4,11 +4,7 @@ import { saveMediaBuffer } from "../media/store.js";
|
||||
import { loadWebMedia } from "../web/media.js";
|
||||
import { resolveIMessageAccount } from "./accounts.js";
|
||||
import { createIMessageRpcClient, type IMessageRpcClient } from "./client.js";
|
||||
import {
|
||||
formatIMessageChatTarget,
|
||||
type IMessageService,
|
||||
parseIMessageTarget,
|
||||
} from "./targets.js";
|
||||
import { formatIMessageChatTarget, type IMessageService, parseIMessageTarget } from "./targets.js";
|
||||
|
||||
export type IMessageSendOpts = {
|
||||
cliPath?: string;
|
||||
@@ -51,12 +47,9 @@ export async function sendMessageIMessage(
|
||||
cfg,
|
||||
accountId: opts.accountId,
|
||||
});
|
||||
const cliPath =
|
||||
opts.cliPath?.trim() || account.config.cliPath?.trim() || "imsg";
|
||||
const cliPath = opts.cliPath?.trim() || account.config.cliPath?.trim() || "imsg";
|
||||
const dbPath = opts.dbPath?.trim() || account.config.dbPath?.trim();
|
||||
const target = parseIMessageTarget(
|
||||
opts.chatId ? formatIMessageChatTarget(opts.chatId) : to,
|
||||
);
|
||||
const target = parseIMessageTarget(opts.chatId ? formatIMessageChatTarget(opts.chatId) : to);
|
||||
const service =
|
||||
opts.service ??
|
||||
(target.kind === "handle" ? target.service : undefined) ??
|
||||
@@ -76,8 +69,7 @@ export async function sendMessageIMessage(
|
||||
filePath = resolved.path;
|
||||
if (!message.trim()) {
|
||||
const kind = mediaKindFromMime(resolved.contentType ?? undefined);
|
||||
if (kind)
|
||||
message = kind === "image" ? "<media:image>" : `<media:${kind}>`;
|
||||
if (kind) message = kind === "image" ? "<media:image>" : `<media:${kind}>`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,8 +94,7 @@ export async function sendMessageIMessage(
|
||||
params.to = target.to;
|
||||
}
|
||||
|
||||
const client =
|
||||
opts.client ?? (await createIMessageRpcClient({ cliPath, dbPath }));
|
||||
const client = opts.client ?? (await createIMessageRpcClient({ cliPath, dbPath }));
|
||||
const shouldClose = !opts.client;
|
||||
try {
|
||||
const result = await client.request<{ ok?: boolean }>("send", params, {
|
||||
|
||||
@@ -24,9 +24,7 @@ describe("imessage targets", () => {
|
||||
});
|
||||
|
||||
it("normalizes handles", () => {
|
||||
expect(normalizeIMessageHandle("Name@Example.com")).toBe(
|
||||
"name@example.com",
|
||||
);
|
||||
expect(normalizeIMessageHandle("Name@Example.com")).toBe("name@example.com");
|
||||
expect(normalizeIMessageHandle(" +1 (555) 222-3333 ")).toBe("+15552223333");
|
||||
});
|
||||
|
||||
|
||||
@@ -16,11 +16,7 @@ export type IMessageAllowTarget =
|
||||
|
||||
const CHAT_ID_PREFIXES = ["chat_id:", "chatid:", "chat:"];
|
||||
const CHAT_GUID_PREFIXES = ["chat_guid:", "chatguid:", "guid:"];
|
||||
const CHAT_IDENTIFIER_PREFIXES = [
|
||||
"chat_identifier:",
|
||||
"chatidentifier:",
|
||||
"chatident:",
|
||||
];
|
||||
const CHAT_IDENTIFIER_PREFIXES = ["chat_identifier:", "chatidentifier:", "chatident:"];
|
||||
const SERVICE_PREFIXES: Array<{ prefix: string; service: IMessageService }> = [
|
||||
{ prefix: "imessage:", service: "imessage" },
|
||||
{ prefix: "sms:", service: "sms" },
|
||||
@@ -35,12 +31,9 @@ export function normalizeIMessageHandle(raw: string): string {
|
||||
const trimmed = raw.trim();
|
||||
if (!trimmed) return "";
|
||||
const lowered = trimmed.toLowerCase();
|
||||
if (lowered.startsWith("imessage:"))
|
||||
return normalizeIMessageHandle(trimmed.slice(9));
|
||||
if (lowered.startsWith("sms:"))
|
||||
return normalizeIMessageHandle(trimmed.slice(4));
|
||||
if (lowered.startsWith("auto:"))
|
||||
return normalizeIMessageHandle(trimmed.slice(5));
|
||||
if (lowered.startsWith("imessage:")) return normalizeIMessageHandle(trimmed.slice(9));
|
||||
if (lowered.startsWith("sms:")) return normalizeIMessageHandle(trimmed.slice(4));
|
||||
if (lowered.startsWith("auto:")) return normalizeIMessageHandle(trimmed.slice(5));
|
||||
if (trimmed.includes("@")) return trimmed.toLowerCase();
|
||||
const normalized = normalizeE164(trimmed);
|
||||
if (normalized) return normalized;
|
||||
|
||||
Reference in New Issue
Block a user