"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.a = isAllowedIMessageSender;exports.c = createAllowedChatSenderMatcher;exports.d = resolveServicePrefixedAllowTarget;exports.f = resolveServicePrefixedChatTarget;exports.i = formatIMessageChatTarget;exports.l = parseChatAllowTargetPrefixes;exports.m = resolveServicePrefixedTarget;exports.n = createIMessageRpcClient;exports.o = normalizeIMessageHandle;exports.p = resolveServicePrefixedOrChatAllowTarget;exports.r = void 0;exports.s = parseIMessageTarget;exports.t = sendMessageIMessage;exports.u = parseChatTargetPrefixesOrThrow;var _configDiiPndBn = require("./config-DiiPndBn.js"); var _loggerU3s76KST = require("./logger-U3s76KST.js"); var _imageOpsZjRT9yvG = require("./image-ops-ZjRT9yvG.js"); var _pluginsBhm3N6Y = require("./plugins-Bhm3N6Y-.js"); var _allowFromCfbQs9EO = require("./allow-from-CfbQs9EO.js"); var _irKp5uANes = require("./ir-Kp5uANes.js"); var _tablesDkBUhlLj = require("./tables-DkBUhlLj.js"); var _outboundAttachmentCflAwcGk = require("./outbound-attachment-CflAwcGk.js"); var _nodeChild_process = require("node:child_process"); var _nodeReadline = require("node:readline"); //#region src/imessage/target-parsing-helpers.ts function stripPrefix(value, prefix) { return value.slice(prefix.length).trim(); } function startsWithAnyPrefix(value, prefixes) { return prefixes.some((prefix) => value.startsWith(prefix)); } function resolveServicePrefixedTarget(params) { for (const { prefix, service } of params.servicePrefixes) { if (!params.lower.startsWith(prefix)) continue; const remainder = stripPrefix(params.trimmed, prefix); if (!remainder) throw new Error(`${prefix} target is required`); const remainderLower = remainder.toLowerCase(); if (params.isChatTarget(remainderLower)) return params.parseTarget(remainder); return { kind: "handle", to: remainder, service }; } return null; } function resolveServicePrefixedChatTarget(params) { const chatPrefixes = [ ...params.chatIdPrefixes, ...params.chatGuidPrefixes, ...params.chatIdentifierPrefixes, ...(params.extraChatPrefixes ?? [])]; return resolveServicePrefixedTarget({ trimmed: params.trimmed, lower: params.lower, servicePrefixes: params.servicePrefixes, isChatTarget: (remainderLower) => startsWithAnyPrefix(remainderLower, chatPrefixes), parseTarget: params.parseTarget }); } function parseChatTargetPrefixesOrThrow(params) { for (const prefix of params.chatIdPrefixes) if (params.lower.startsWith(prefix)) { const value = stripPrefix(params.trimmed, prefix); const chatId = Number.parseInt(value, 10); if (!Number.isFinite(chatId)) throw new Error(`Invalid chat_id: ${value}`); return { kind: "chat_id", chatId }; } for (const prefix of params.chatGuidPrefixes) if (params.lower.startsWith(prefix)) { const value = stripPrefix(params.trimmed, prefix); if (!value) throw new Error("chat_guid is required"); return { kind: "chat_guid", chatGuid: value }; } for (const prefix of params.chatIdentifierPrefixes) if (params.lower.startsWith(prefix)) { const value = stripPrefix(params.trimmed, prefix); if (!value) throw new Error("chat_identifier is required"); return { kind: "chat_identifier", chatIdentifier: value }; } return null; } function resolveServicePrefixedAllowTarget(params) { for (const { prefix } of params.servicePrefixes) { if (!params.lower.startsWith(prefix)) continue; const remainder = stripPrefix(params.trimmed, prefix); if (!remainder) return { kind: "handle", handle: "" }; return params.parseAllowTarget(remainder); } return null; } function resolveServicePrefixedOrChatAllowTarget(params) { const servicePrefixed = resolveServicePrefixedAllowTarget({ trimmed: params.trimmed, lower: params.lower, servicePrefixes: params.servicePrefixes, parseAllowTarget: params.parseAllowTarget }); if (servicePrefixed) return servicePrefixed; const chatTarget = parseChatAllowTargetPrefixes({ trimmed: params.trimmed, lower: params.lower, chatIdPrefixes: params.chatIdPrefixes, chatGuidPrefixes: params.chatGuidPrefixes, chatIdentifierPrefixes: params.chatIdentifierPrefixes }); if (chatTarget) return chatTarget; return null; } function createAllowedChatSenderMatcher(params) { return (input) => (0, _allowFromCfbQs9EO.r)({ allowFrom: input.allowFrom, sender: input.sender, chatId: input.chatId, chatGuid: input.chatGuid, chatIdentifier: input.chatIdentifier, normalizeSender: params.normalizeSender, parseAllowTarget: params.parseAllowTarget }); } function parseChatAllowTargetPrefixes(params) { for (const prefix of params.chatIdPrefixes) if (params.lower.startsWith(prefix)) { const value = stripPrefix(params.trimmed, prefix); const chatId = Number.parseInt(value, 10); if (Number.isFinite(chatId)) return { kind: "chat_id", chatId }; } for (const prefix of params.chatGuidPrefixes) if (params.lower.startsWith(prefix)) { const value = stripPrefix(params.trimmed, prefix); if (value) return { kind: "chat_guid", chatGuid: value }; } for (const prefix of params.chatIdentifierPrefixes) if (params.lower.startsWith(prefix)) { const value = stripPrefix(params.trimmed, prefix); if (value) return { kind: "chat_identifier", chatIdentifier: value }; } return null; } //#endregion //#region src/imessage/targets.ts 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 SERVICE_PREFIXES = [ { prefix: "imessage:", service: "imessage" }, { prefix: "sms:", service: "sms" }, { prefix: "auto:", service: "auto" }]; function normalizeIMessageHandle(raw) { 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)); for (const prefix of CHAT_ID_PREFIXES) if (lowered.startsWith(prefix)) return `chat_id:${trimmed.slice(prefix.length).trim()}`; for (const prefix of CHAT_GUID_PREFIXES) if (lowered.startsWith(prefix)) return `chat_guid:${trimmed.slice(prefix.length).trim()}`; for (const prefix of CHAT_IDENTIFIER_PREFIXES) if (lowered.startsWith(prefix)) return `chat_identifier:${trimmed.slice(prefix.length).trim()}`; if (trimmed.includes("@")) return trimmed.toLowerCase(); const normalized = (0, _loggerU3s76KST.C)(trimmed); if (normalized) return normalized; return trimmed.replace(/\s+/g, ""); } function parseIMessageTarget(raw) { const trimmed = raw.trim(); if (!trimmed) throw new Error("iMessage target is required"); const lower = trimmed.toLowerCase(); const servicePrefixed = resolveServicePrefixedChatTarget({ trimmed, lower, servicePrefixes: SERVICE_PREFIXES, chatIdPrefixes: CHAT_ID_PREFIXES, chatGuidPrefixes: CHAT_GUID_PREFIXES, chatIdentifierPrefixes: CHAT_IDENTIFIER_PREFIXES, parseTarget: parseIMessageTarget }); if (servicePrefixed) return servicePrefixed; const chatTarget = parseChatTargetPrefixesOrThrow({ trimmed, lower, chatIdPrefixes: CHAT_ID_PREFIXES, chatGuidPrefixes: CHAT_GUID_PREFIXES, chatIdentifierPrefixes: CHAT_IDENTIFIER_PREFIXES }); if (chatTarget) return chatTarget; return { kind: "handle", to: trimmed, service: "auto" }; } function parseIMessageAllowTarget(raw) { const trimmed = raw.trim(); if (!trimmed) return { kind: "handle", handle: "" }; const servicePrefixed = resolveServicePrefixedOrChatAllowTarget({ trimmed, lower: trimmed.toLowerCase(), servicePrefixes: SERVICE_PREFIXES, parseAllowTarget: parseIMessageAllowTarget, chatIdPrefixes: CHAT_ID_PREFIXES, chatGuidPrefixes: CHAT_GUID_PREFIXES, chatIdentifierPrefixes: CHAT_IDENTIFIER_PREFIXES }); if (servicePrefixed) return servicePrefixed; return { kind: "handle", handle: normalizeIMessageHandle(trimmed) }; } const isAllowedIMessageSenderMatcher = createAllowedChatSenderMatcher({ normalizeSender: normalizeIMessageHandle, parseAllowTarget: parseIMessageAllowTarget }); function isAllowedIMessageSender(params) { return isAllowedIMessageSenderMatcher(params); } function formatIMessageChatTarget(chatId) { if (!chatId || !Number.isFinite(chatId)) return ""; return `chat_id:${chatId}`; } //#endregion //#region src/imessage/constants.ts /** Default timeout for iMessage probe/RPC operations (10 seconds). */ const DEFAULT_IMESSAGE_PROBE_TIMEOUT_MS = exports.r = 1e4; //#endregion //#region src/imessage/client.ts function isTestEnv() { const vitest = process.env.VITEST?.trim().toLowerCase(); return Boolean(vitest); } var IMessageRpcClient = class { constructor(opts = {}) { this.pending = /* @__PURE__ */new Map(); this.closedResolve = null; this.child = null; this.reader = null; this.nextId = 1; this.cliPath = opts.cliPath?.trim() || "imsg"; this.dbPath = opts.dbPath?.trim() ? (0, _loggerU3s76KST.D)(opts.dbPath) : void 0; this.runtime = opts.runtime; this.onNotification = opts.onNotification; this.closed = new Promise((resolve) => { this.closedResolve = resolve; }); } async start() { if (this.child) return; if (isTestEnv()) throw new Error("Refusing to start imsg rpc in test environment; mock iMessage RPC client"); const args = ["rpc"]; if (this.dbPath) args.push("--db", this.dbPath); const child = (0, _nodeChild_process.spawn)(this.cliPath, args, { stdio: [ "pipe", "pipe", "pipe"] }); this.child = child; this.reader = (0, _nodeReadline.createInterface)({ input: child.stdout }); this.reader.on("line", (line) => { const trimmed = line.trim(); if (!trimmed) return; this.handleLine(trimmed); }); child.stderr?.on("data", (chunk) => { const lines = chunk.toString().split(/\r?\n/); for (const line of lines) { if (!line.trim()) continue; this.runtime?.error?.(`imsg rpc: ${line.trim()}`); } }); child.on("error", (err) => { this.failAll(err instanceof Error ? err : new Error(String(err))); this.closedResolve?.(); }); child.on("close", (code, signal) => { if (code !== 0 && code !== null) { const reason = signal ? `signal ${signal}` : `code ${code}`; this.failAll(/* @__PURE__ */new Error(`imsg rpc exited (${reason})`)); } else this.failAll(/* @__PURE__ */new Error("imsg rpc closed")); this.closedResolve?.(); }); } async stop() { if (!this.child) return; this.reader?.close(); this.reader = null; this.child.stdin?.end(); const child = this.child; this.child = null; await Promise.race([this.closed, new Promise((resolve) => { setTimeout(() => { if (!child.killed) child.kill("SIGTERM"); resolve(); }, 500); })]); } async waitForClose() { await this.closed; } async request(method, params, opts) { if (!this.child || !this.child.stdin) throw new Error("imsg rpc not running"); const id = this.nextId++; const payload = { jsonrpc: "2.0", id, method, params: params ?? {} }; const line = `${JSON.stringify(payload)}\n`; const timeoutMs = opts?.timeoutMs ?? 1e4; const response = new Promise((resolve, reject) => { const key = String(id); const timer = timeoutMs > 0 ? setTimeout(() => { this.pending.delete(key); reject(/* @__PURE__ */new Error(`imsg rpc timeout (${method})`)); }, timeoutMs) : void 0; this.pending.set(key, { resolve: (value) => resolve(value), reject, timer }); }); this.child.stdin.write(line); return await response; } handleLine(line) { let parsed; try { parsed = JSON.parse(line); } catch (err) { const detail = err instanceof Error ? err.message : String(err); this.runtime?.error?.(`imsg rpc: failed to parse ${line}: ${detail}`); return; } if (parsed.id !== void 0 && parsed.id !== null) { const key = String(parsed.id); const pending = this.pending.get(key); if (!pending) return; if (pending.timer) clearTimeout(pending.timer); this.pending.delete(key); if (parsed.error) { const baseMessage = parsed.error.message ?? "imsg rpc error"; const details = parsed.error.data; const code = parsed.error.code; const suffixes = []; if (typeof code === "number") suffixes.push(`code=${code}`); if (details !== void 0) { const detailText = typeof details === "string" ? details : JSON.stringify(details, null, 2); if (detailText) suffixes.push(detailText); } const msg = suffixes.length > 0 ? `${baseMessage}: ${suffixes.join(" ")}` : baseMessage; pending.reject(new Error(msg)); return; } pending.resolve(parsed.result); return; } if (parsed.method) this.onNotification?.({ method: parsed.method, params: parsed.params }); } failAll(err) { for (const [key, pending] of this.pending.entries()) { if (pending.timer) clearTimeout(pending.timer); pending.reject(err); this.pending.delete(key); } } }; async function createIMessageRpcClient(opts = {}) { const client = new IMessageRpcClient(opts); await client.start(); return client; } //#endregion //#region src/imessage/send.ts const LEADING_REPLY_TAG_RE = /^\s*\[\[\s*reply_to\s*:\s*([^\]\n]+)\s*\]\]\s*/i; const MAX_REPLY_TO_ID_LENGTH = 256; function stripUnsafeReplyTagChars(value) { let next = ""; for (const ch of value) { const code = ch.charCodeAt(0); if (code >= 0 && code <= 31 || code === 127 || ch === "[" || ch === "]") continue; next += ch; } return next; } function sanitizeReplyToId(rawReplyToId) { const trimmed = rawReplyToId?.trim(); if (!trimmed) return; const sanitized = stripUnsafeReplyTagChars(trimmed).trim(); if (!sanitized) return; if (sanitized.length > MAX_REPLY_TO_ID_LENGTH) return sanitized.slice(0, MAX_REPLY_TO_ID_LENGTH); return sanitized; } function prependReplyTagIfNeeded(message, replyToId) { const resolvedReplyToId = sanitizeReplyToId(replyToId); if (!resolvedReplyToId) return message; const replyTag = `[[reply_to:${resolvedReplyToId}]]`; const existingLeadingTag = message.match(LEADING_REPLY_TAG_RE); if (existingLeadingTag) { const remainder = message.slice(existingLeadingTag[0].length).trimStart(); return remainder ? `${replyTag} ${remainder}` : replyTag; } const trimmedMessage = message.trimStart(); return trimmedMessage ? `${replyTag} ${trimmedMessage}` : replyTag; } function resolveMessageId(result) { if (!result) return null; const raw = typeof result.messageId === "string" && result.messageId.trim() || typeof result.message_id === "string" && result.message_id.trim() || typeof result.id === "string" && result.id.trim() || typeof result.guid === "string" && result.guid.trim() || (typeof result.message_id === "number" ? String(result.message_id) : null) || (typeof result.id === "number" ? String(result.id) : null); return raw ? String(raw).trim() : null; } async function sendMessageIMessage(to, text, opts = {}) { const cfg = opts.config ?? (0, _configDiiPndBn.i)(); const account = opts.account ?? (0, _pluginsBhm3N6Y.Q)({ cfg, accountId: opts.accountId }); 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 service = opts.service ?? (target.kind === "handle" ? target.service : void 0) ?? account.config.service; const region = opts.region?.trim() || account.config.region?.trim() || "US"; const maxBytes = typeof opts.maxBytes === "number" ? opts.maxBytes : typeof account.config.mediaMaxMb === "number" ? account.config.mediaMaxMb * 1024 * 1024 : 16 * 1024 * 1024; let message = text ?? ""; let filePath; if (opts.mediaUrl?.trim()) { const resolved = await (opts.resolveAttachmentImpl ?? _outboundAttachmentCflAwcGk.t)(opts.mediaUrl.trim(), maxBytes, { localRoots: opts.mediaLocalRoots }); filePath = resolved.path; if (!message.trim()) { const kind = (0, _imageOpsZjRT9yvG.m)(resolved.contentType ?? void 0); if (kind) message = kind === "image" ? "" : ``; } } if (!message.trim() && !filePath) throw new Error("iMessage send requires text or media"); if (message.trim()) { const tableMode = (0, _irKp5uANes.i)({ cfg, channel: "imessage", accountId: account.accountId }); message = (0, _tablesDkBUhlLj.t)(message, tableMode); } message = prependReplyTagIfNeeded(message, opts.replyToId); const params = { text: message, service: service || "auto", region }; if (filePath) params.file = filePath; if (target.kind === "chat_id") params.chat_id = target.chatId;else if (target.kind === "chat_guid") params.chat_guid = target.chatGuid;else if (target.kind === "chat_identifier") params.chat_identifier = target.chatIdentifier;else params.to = target.to; const client = opts.client ?? (opts.createClient ? await opts.createClient({ cliPath, dbPath }) : await createIMessageRpcClient({ cliPath, dbPath })); const shouldClose = !opts.client; try { const result = await client.request("send", params, { timeoutMs: opts.timeoutMs }); return { messageId: resolveMessageId(result) ?? (result?.ok ? "ok" : "unknown") }; } finally { if (shouldClose) await client.stop(); } } //#endregion /* v9-f710649fa247a0ff */