|
1 | | -import { spawn } from "node:child_process"; |
2 | 1 | import { |
3 | 2 | createMessageReceiptFromOutboundResults, |
4 | 3 | type MessageReceipt, |
@@ -53,7 +52,6 @@ type IMessageSendOpts = { |
53 | 52 | }, |
54 | 53 | ) => Promise<{ path: string; contentType?: string }>; |
55 | 54 | createClient?: (params: { cliPath: string; dbPath?: string }) => Promise<IMessageRpcClient>; |
56 | | - runCliJson?: (args: readonly string[]) => Promise<Record<string, unknown>>; |
57 | 55 | }; |
58 | 56 |
|
59 | 57 | export type IMessageSendResult = { |
@@ -212,110 +210,6 @@ function resolveOutboundEchoScope(params: { |
212 | 210 | return `${params.accountId}:imessage:${params.target.to}`; |
213 | 211 | } |
214 | 212 |
|
215 | | -function buildIMessageCliJsonArgs(args: readonly string[], dbPath?: string): string[] { |
216 | | - const trimmedDbPath = dbPath?.trim(); |
217 | | - return [...args, ...(trimmedDbPath ? ["--db", trimmedDbPath] : []), "--json"]; |
218 | | -} |
219 | | - |
220 | | -async function runIMessageCliJson( |
221 | | - cliPath: string, |
222 | | - dbPath: string | undefined, |
223 | | - args: readonly string[], |
224 | | - timeoutMs?: number, |
225 | | -): Promise<Record<string, unknown>> { |
226 | | - return await new Promise((resolve, reject) => { |
227 | | - const child = spawn(cliPath, buildIMessageCliJsonArgs(args, dbPath), { |
228 | | - stdio: ["ignore", "pipe", "pipe"], |
229 | | - }); |
230 | | - let stdout = ""; |
231 | | - let stderr = ""; |
232 | | - let killEscalation: ReturnType<typeof setTimeout> | null = null; |
233 | | - const timer = |
234 | | - timeoutMs && timeoutMs > 0 |
235 | | - ? setTimeout(() => { |
236 | | - child.kill("SIGTERM"); |
237 | | - killEscalation = setTimeout(() => { |
238 | | - try { |
239 | | - child.kill("SIGKILL"); |
240 | | - } catch { |
241 | | - // best-effort |
242 | | - } |
243 | | - }, 2000); |
244 | | - reject(new Error(`iMessage action timed out after ${timeoutMs}ms`)); |
245 | | - }, timeoutMs) |
246 | | - : null; |
247 | | - child.stdout.setEncoding("utf8"); |
248 | | - child.stderr.setEncoding("utf8"); |
249 | | - child.stdout.on("data", (chunk) => { |
250 | | - stdout += chunk; |
251 | | - }); |
252 | | - child.stderr.on("data", (chunk) => { |
253 | | - stderr += chunk; |
254 | | - }); |
255 | | - child.on("error", (error) => { |
256 | | - if (timer) { |
257 | | - clearTimeout(timer); |
258 | | - } |
259 | | - if (killEscalation) { |
260 | | - clearTimeout(killEscalation); |
261 | | - } |
262 | | - reject(error); |
263 | | - }); |
264 | | - child.on("close", (code) => { |
265 | | - if (timer) { |
266 | | - clearTimeout(timer); |
267 | | - } |
268 | | - if (killEscalation) { |
269 | | - clearTimeout(killEscalation); |
270 | | - } |
271 | | - const lines = stdout |
272 | | - .split(/\r?\n/u) |
273 | | - .map((line) => line.trim()) |
274 | | - .filter(Boolean); |
275 | | - const last = lines.at(-1); |
276 | | - let parsed: Record<string, unknown> | null = null; |
277 | | - if (last) { |
278 | | - try { |
279 | | - const json = JSON.parse(last) as unknown; |
280 | | - if (json && typeof json === "object" && !Array.isArray(json)) { |
281 | | - parsed = json as Record<string, unknown>; |
282 | | - } |
283 | | - } catch { |
284 | | - // handled below |
285 | | - } |
286 | | - } |
287 | | - if (code === 0 && parsed) { |
288 | | - resolve(parsed); |
289 | | - return; |
290 | | - } |
291 | | - if (parsed && typeof parsed.error === "string" && parsed.error.trim()) { |
292 | | - reject(new Error(parsed.error.trim())); |
293 | | - return; |
294 | | - } |
295 | | - const detail = stderr.trim() || stdout.trim() || `imsg exited with code ${code}`; |
296 | | - reject(new Error(detail)); |
297 | | - }); |
298 | | - }); |
299 | | -} |
300 | | - |
301 | | -function stringValue(value: unknown): string | undefined { |
302 | | - return typeof value === "string" && value.trim() ? value.trim() : undefined; |
303 | | -} |
304 | | - |
305 | | -async function resolveAttachmentChatGuid(params: { |
306 | | - target: ReturnType<typeof parseIMessageTarget>; |
307 | | - runCliJson: (args: readonly string[]) => Promise<Record<string, unknown>>; |
308 | | -}): Promise<string | null> { |
309 | | - if (params.target.kind === "chat_guid") { |
310 | | - return params.target.chatGuid; |
311 | | - } |
312 | | - if (params.target.kind !== "chat_id") { |
313 | | - return null; |
314 | | - } |
315 | | - const result = await params.runCliJson(["group", "--chat-id", String(params.target.chatId)]); |
316 | | - return stringValue(result.guid) ?? stringValue(result.chat_guid) ?? null; |
317 | | -} |
318 | | - |
319 | 213 | export async function sendMessageIMessage( |
320 | 214 | to: string, |
321 | 215 | text: string, |
@@ -384,56 +278,6 @@ export async function sendMessageIMessage( |
384 | 278 | } |
385 | 279 | const echoText = resolveOutboundEchoText(message, filePath ? mediaContentType : undefined); |
386 | 280 | const resolvedReplyToId = sanitizeReplyToId(opts.replyToId); |
387 | | - const runCliJson = |
388 | | - opts.runCliJson ?? |
389 | | - ((args: readonly string[]) => runIMessageCliJson(cliPath, dbPath, args, opts.timeoutMs)); |
390 | | - |
391 | | - if (filePath && !message.trim() && !resolvedReplyToId) { |
392 | | - const attachmentChatGuid = await resolveAttachmentChatGuid({ target, runCliJson }); |
393 | | - if (attachmentChatGuid) { |
394 | | - const result = await runCliJson([ |
395 | | - "send-attachment", |
396 | | - "--chat", |
397 | | - attachmentChatGuid, |
398 | | - "--file", |
399 | | - filePath, |
400 | | - "--transport", |
401 | | - "auto", |
402 | | - ]); |
403 | | - const resolvedId = resolveMessageId(result); |
404 | | - const approvalBindingMessageId = resolveOutboundMessageGuid(result); |
405 | | - const messageId = resolvedId ?? (result?.ok || result?.success ? "ok" : "unknown"); |
406 | | - const echoScope = resolveOutboundEchoScope({ accountId: account.accountId, target }); |
407 | | - if (echoScope) { |
408 | | - rememberPersistedIMessageEcho({ |
409 | | - scope: echoScope, |
410 | | - text: echoText, |
411 | | - messageId: resolvedId ?? undefined, |
412 | | - }); |
413 | | - } |
414 | | - if (resolvedId) { |
415 | | - rememberIMessageReplyCache({ |
416 | | - accountId: account.accountId, |
417 | | - messageId: resolvedId, |
418 | | - chatGuid: target.kind === "chat_guid" ? target.chatGuid : attachmentChatGuid, |
419 | | - chatId: target.kind === "chat_id" ? target.chatId : undefined, |
420 | | - timestamp: Date.now(), |
421 | | - isFromMe: true, |
422 | | - }); |
423 | | - } |
424 | | - return { |
425 | | - messageId, |
426 | | - ...(approvalBindingMessageId ? { guid: approvalBindingMessageId } : {}), |
427 | | - sentText: message, |
428 | | - ...(echoText ? { echoText } : {}), |
429 | | - receipt: createIMessageSendReceipt({ |
430 | | - messageId, |
431 | | - target, |
432 | | - kind: "media", |
433 | | - }), |
434 | | - }; |
435 | | - } |
436 | | - } |
437 | 281 | const params: Record<string, unknown> = { |
438 | 282 | text: message, |
439 | 283 | service: service || "auto", |
|
0 commit comments