Spaces:
Sleeping
Sleeping
| import { loadConfig } from "../config/config.js"; | |
| import { resolveMarkdownTableMode } from "../config/markdown-tables.js"; | |
| import { convertMarkdownTables } from "../markdown/tables.js"; | |
| import { mediaKindFromMime } from "../media/constants.js"; | |
| 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"; | |
| export type IMessageSendOpts = { | |
| cliPath?: string; | |
| dbPath?: string; | |
| service?: IMessageService; | |
| region?: string; | |
| accountId?: string; | |
| mediaUrl?: string; | |
| maxBytes?: number; | |
| timeoutMs?: number; | |
| chatId?: number; | |
| client?: IMessageRpcClient; | |
| }; | |
| export type IMessageSendResult = { | |
| messageId: string; | |
| }; | |
| function resolveMessageId(result: Record<string, unknown> | null | undefined): string | null { | |
| 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 resolveAttachment( | |
| mediaUrl: string, | |
| maxBytes: number, | |
| ): Promise<{ path: string; contentType?: string }> { | |
| const media = await loadWebMedia(mediaUrl, maxBytes); | |
| const saved = await saveMediaBuffer( | |
| media.buffer, | |
| media.contentType ?? undefined, | |
| "outbound", | |
| maxBytes, | |
| ); | |
| return { path: saved.path, contentType: saved.contentType }; | |
| } | |
| export async function sendMessageIMessage( | |
| to: string, | |
| text: string, | |
| opts: IMessageSendOpts = {}, | |
| ): Promise<IMessageSendResult> { | |
| const cfg = loadConfig(); | |
| const account = resolveIMessageAccount({ | |
| 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 : undefined) ?? | |
| (account.config.service as IMessageService | undefined); | |
| 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: string | undefined; | |
| if (opts.mediaUrl?.trim()) { | |
| const resolved = await resolveAttachment(opts.mediaUrl.trim(), maxBytes); | |
| filePath = resolved.path; | |
| if (!message.trim()) { | |
| const kind = mediaKindFromMime(resolved.contentType ?? undefined); | |
| if (kind) { | |
| message = kind === "image" ? "<media:image>" : `<media:${kind}>`; | |
| } | |
| } | |
| } | |
| if (!message.trim() && !filePath) { | |
| throw new Error("iMessage send requires text or media"); | |
| } | |
| if (message.trim()) { | |
| const tableMode = resolveMarkdownTableMode({ | |
| cfg, | |
| channel: "imessage", | |
| accountId: account.accountId, | |
| }); | |
| message = convertMarkdownTables(message, tableMode); | |
| } | |
| const params: Record<string, unknown> = { | |
| 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 ?? (await createIMessageRpcClient({ cliPath, dbPath })); | |
| const shouldClose = !opts.client; | |
| try { | |
| const result = await client.request<{ ok?: string }>("send", params, { | |
| timeoutMs: opts.timeoutMs, | |
| }); | |
| const resolvedId = resolveMessageId(result); | |
| return { | |
| messageId: resolvedId ?? (result?.ok ? "ok" : "unknown"), | |
| }; | |
| } finally { | |
| if (shouldClose) { | |
| await client.stop(); | |
| } | |
| } | |
| } | |