Spaces:
Paused
Paused
File size: 4,496 Bytes
fb4d8fe | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 | 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();
}
}
}
|