From 5290e1de2f5aed6b086dd0f4145dc1adfa6def75 Mon Sep 17 00:00:00 2001 From: Natalie Date: Thu, 21 May 2026 20:24:45 -0700 Subject: [PATCH] =?UTF-8?q?feat(imessage):=20=E2=9C=A8=20improve=20iMessag?= =?UTF-8?q?e=20service=20detection=20and=20error=20handling?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Lilith Autocommit --- .../Sources/IMessageSync/Sender.swift | 48 ++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/@packages/imessage/Sources/IMessageSync/Sender.swift b/@packages/imessage/Sources/IMessageSync/Sender.swift index 8da4736..4083f13 100644 --- a/@packages/imessage/Sources/IMessageSync/Sender.swift +++ b/@packages/imessage/Sources/IMessageSync/Sender.swift @@ -88,9 +88,23 @@ class SendService { .replacingOccurrences(of: "\\", with: "\\\\") .replacingOccurrences(of: "\"", with: "\\\"") + // Resolve the iMessage service by iterating with a per-service `try`. + // A plain `1st service whose service type = iMessage` throws (-1728) + // when any stale service errors on `service type`, and it would also + // pick a *disabled* service. Require an enabled iMessage service; if + // none, fail with a clear reason instead of a cryptic delivery error. let script = """ tell application "Messages" - set targetService to 1st service whose service type = iMessage + set targetService to missing value + repeat with s in services + try + if (service type of s) is iMessage and (enabled of s) then + set targetService to s + exit repeat + end if + end try + end repeat + if targetService is missing value then error "no enabled iMessage service" set targetBuddy to buddy "\(sanitizedBuddy)" of targetService send "\(sanitizedBody)" to targetBuddy end tell @@ -143,6 +157,17 @@ class SendService { return (false, nil, errorMessage) } + // osascript exiting 0 only means Messages accepted the command — NOT + // that the message transmitted. Verify against chat.db: a disabled + // iMessage service still creates a message row, but with error != 0 + // (e.g. error 33). Never report success the ground truth contradicts. + Thread.sleep(forTimeInterval: 4) + if let sendError = Self.chatDBSendError(buddyId: buddyId), sendError != 0 { + osLog.error("send: chat.db reports error=\(sendError, privacy: .public) for \(logPrefix, privacy: .public) — NOT delivered") + activityLog.error("iMessage send failed to \(logPrefix)...: Messages error \(sendError)") + return (false, nil, "Messages reported error \(sendError) — not delivered (iMessage service may be disabled)") + } + dailySendCount += 1 hourlySendCount += 1 @@ -192,6 +217,27 @@ class SendService { } } + /// Most-recent outbound `message.error` for `buddyId` from chat.db, or nil + /// if no row is found. A non-zero value means Messages failed the send + /// even when `osascript` exited 0 (e.g. disabled iMessage service → 33). + nonisolated static func chatDBSendError(buddyId: String) -> Int? { + guard let dbQueue = iMessageReader.shared.getDatabaseQueue() else { return nil } + let suffix = String(buddyId.suffix(7)) + let value: Int? = (try? dbQueue.read { db -> Int? in + guard let row = try Row.fetchOne(db, sql: """ + SELECT m.error AS err + FROM message m + JOIN handle h ON h.ROWID = m.handle_id + WHERE m.is_from_me = 1 AND h.id LIKE ? + ORDER BY m.date DESC + LIMIT 1 + """, arguments: ["%\(suffix)"]) + else { return nil } + return (row["err"] as? Int64).map(Int.init) + }) ?? nil + return value + } + func getRateLimitStatus() -> (dailyRemaining: Int, hourlyRemaining: Int) { resetCountersIfNeeded() return (max(0, maxDailyMessages - dailySendCount), max(0, maxHourlyMessages - hourlySendCount))