@elizaos/plugin-imessage 2.0.0-beta.1 → 2.0.3-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +13 -18
- package/auto-enable.ts +1 -1
- package/package.json +21 -4
- package/dist/accounts.d.ts +0 -135
- package/dist/accounts.d.ts.map +0 -1
- package/dist/accounts.js +0 -209
- package/dist/accounts.js.map +0 -1
- package/dist/api/bluebubbles-routes.d.ts +0 -10
- package/dist/api/bluebubbles-routes.d.ts.map +0 -1
- package/dist/api/bluebubbles-routes.js +0 -132
- package/dist/api/bluebubbles-routes.js.map +0 -1
- package/dist/api/imessage-routes.d.ts +0 -80
- package/dist/api/imessage-routes.d.ts.map +0 -1
- package/dist/api/imessage-routes.js +0 -230
- package/dist/api/imessage-routes.js.map +0 -1
- package/dist/chatdb-reader.d.ts +0 -240
- package/dist/chatdb-reader.d.ts.map +0 -1
- package/dist/chatdb-reader.js +0 -647
- package/dist/chatdb-reader.js.map +0 -1
- package/dist/config.d.ts +0 -60
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js +0 -8
- package/dist/config.js.map +0 -1
- package/dist/connector-account-provider.d.ts +0 -18
- package/dist/connector-account-provider.d.ts.map +0 -1
- package/dist/connector-account-provider.js +0 -83
- package/dist/connector-account-provider.js.map +0 -1
- package/dist/contacts-reader.d.ts +0 -147
- package/dist/contacts-reader.d.ts.map +0 -1
- package/dist/contacts-reader.js +0 -481
- package/dist/contacts-reader.js.map +0 -1
- package/dist/index.d.ts +0 -23
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -78
- package/dist/index.js.map +0 -1
- package/dist/providers/index.d.ts +0 -4
- package/dist/providers/index.d.ts.map +0 -1
- package/dist/providers/index.js +0 -5
- package/dist/providers/index.js.map +0 -1
- package/dist/rpc.d.ts +0 -206
- package/dist/rpc.d.ts.map +0 -1
- package/dist/rpc.js +0 -393
- package/dist/rpc.js.map +0 -1
- package/dist/service.d.ts +0 -266
- package/dist/service.d.ts.map +0 -1
- package/dist/service.js +0 -1694
- package/dist/service.js.map +0 -1
- package/dist/setup-routes.d.ts +0 -38
- package/dist/setup-routes.d.ts.map +0 -1
- package/dist/setup-routes.js +0 -322
- package/dist/setup-routes.js.map +0 -1
- package/dist/types.d.ts +0 -192
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -138
- package/dist/types.js.map +0 -1
package/dist/chatdb-reader.js
DELETED
|
@@ -1,647 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* macOS chat.db reader for @elizaos/plugin-imessage.
|
|
3
|
-
*
|
|
4
|
-
* iMessage stores every message in a SQLite database at
|
|
5
|
-
* `~/Library/Messages/chat.db`. Reading it requires Full Disk Access on
|
|
6
|
-
* whichever process hosts the plugin (the Eliza agent, typically). This
|
|
7
|
-
* module opens that file read-only and exposes a single `fetchNewMessages`
|
|
8
|
-
* method the polling loop uses to walk forward by ROWID.
|
|
9
|
-
*
|
|
10
|
-
* ---
|
|
11
|
-
*
|
|
12
|
-
* Backend: runtime SQLite built-ins. Bun exposes `bun:sqlite`; Node 22+
|
|
13
|
-
* exposes `node:sqlite`. We normalize both to the small query surface this
|
|
14
|
-
* module needs so live chat.db reads keep working in test runners and under
|
|
15
|
-
* either runtime.
|
|
16
|
-
*
|
|
17
|
-
* Prior to this module, the plugin attempted to read messages by running
|
|
18
|
-
* AppleScript against Messages.app's `get messages` verb — a verb that
|
|
19
|
-
* does not exist in Messages.app's scripting dictionary. That code path
|
|
20
|
-
* silently returned an empty list on every poll, so inbound messages
|
|
21
|
-
* never reached the agent.
|
|
22
|
-
*/
|
|
23
|
-
import { createRequire } from "node:module";
|
|
24
|
-
import { homedir } from "node:os";
|
|
25
|
-
import { join } from "node:path";
|
|
26
|
-
import { logger } from "@elizaos/core";
|
|
27
|
-
/**
|
|
28
|
-
* Default path to macOS's iMessage database. Requires Full Disk Access
|
|
29
|
-
* on whichever process opens it.
|
|
30
|
-
*/
|
|
31
|
-
export const DEFAULT_CHAT_DB_PATH = join(homedir(), "Library", "Messages", "chat.db");
|
|
32
|
-
export const MACOS_FULL_DISK_ACCESS_SETTINGS_URL = "x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles";
|
|
33
|
-
export function createFullDiskAccessAction() {
|
|
34
|
-
return {
|
|
35
|
-
type: "full_disk_access",
|
|
36
|
-
label: "Open Full Disk Access",
|
|
37
|
-
url: MACOS_FULL_DISK_ACCESS_SETTINGS_URL,
|
|
38
|
-
instructions: [
|
|
39
|
-
"Open System Settings > Privacy & Security > Full Disk Access.",
|
|
40
|
-
"Enable Eliza. If you run Eliza from a terminal, enable that terminal app too.",
|
|
41
|
-
"Quit and relaunch Eliza after changing Full Disk Access.",
|
|
42
|
-
],
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Apple Cocoa reference date: 2001-01-01T00:00:00Z. The `message.date`
|
|
47
|
-
* column stores a delta from this instant. Modern macOS stores the delta
|
|
48
|
-
* in nanoseconds; older macOS (< 10.13) stored it in seconds. We detect
|
|
49
|
-
* which by magnitude — any plausible seconds-since-2001 value fits in ~10
|
|
50
|
-
* digits, any nanoseconds-since-2001 value is at least 13 digits.
|
|
51
|
-
*/
|
|
52
|
-
const APPLE_EPOCH_MS = Date.UTC(2001, 0, 1);
|
|
53
|
-
/**
|
|
54
|
-
* Convert an Apple Cocoa date delta to JavaScript milliseconds since
|
|
55
|
-
* epoch. Handles both legacy (seconds) and modern (nanoseconds) storage.
|
|
56
|
-
*/
|
|
57
|
-
export function appleDateToJsMs(appleDate) {
|
|
58
|
-
if (!appleDate || appleDate < 0)
|
|
59
|
-
return 0;
|
|
60
|
-
// Nanosecond-scale values are enormous (> 1e15 for any date after 2002).
|
|
61
|
-
// Second-scale values top out around 1e9 for dates decades from now.
|
|
62
|
-
// Split at 1e12 to be safe.
|
|
63
|
-
const deltaMs = appleDate > 1e12 ? appleDate / 1e6 : appleDate * 1000;
|
|
64
|
-
return APPLE_EPOCH_MS + deltaMs;
|
|
65
|
-
}
|
|
66
|
-
/**
|
|
67
|
-
* Extract the plain UTF-8 text from an `attributedBody` BLOB.
|
|
68
|
-
*
|
|
69
|
-
* Modern macOS (~10.13+) stores message text as an `NSMutableAttributedString`
|
|
70
|
-
* serialised via Apple's legacy `typedstream` / NSArchiver format, because
|
|
71
|
-
* Messages.app wants to attach attributes (links, mentions, formatting)
|
|
72
|
-
* that plain text can't carry. Empirically, on a fresh macOS chat.db,
|
|
73
|
-
* ~97% of message rows have `text=NULL` and their actual readable content
|
|
74
|
-
* only exists in `attributedBody`. Reading chat.db without decoding this
|
|
75
|
-
* blob means being blind to almost every real message.
|
|
76
|
-
*
|
|
77
|
-
* Full typedstream parsing is complex (class inheritance chains, object
|
|
78
|
-
* references, multiple string encodings) and worth ~500 lines of code.
|
|
79
|
-
* The good news: Messages.app uses a narrow, stable subset for its own
|
|
80
|
-
* message text, and the text always appears after the same marker
|
|
81
|
-
* sequence: `NSString\x00\x01\x94\x84\x01\x2b` (class name, object flags,
|
|
82
|
-
* then `+` which is typedstream's "cstring" verb), followed by a length
|
|
83
|
-
* byte, followed by the UTF-8 bytes. For strings longer than 254 bytes
|
|
84
|
-
* typedstream escapes the length with `0x81` followed by a little-endian
|
|
85
|
-
* uint16 length, then the bytes. Everything else we don't care about.
|
|
86
|
-
*
|
|
87
|
-
* Verified against real blobs from a live chat.db — hit rate is ~100% on
|
|
88
|
-
* the messages checked, including short replies like "Yo" and longer
|
|
89
|
-
* messages with emoji. Returns null if no marker is found, which the
|
|
90
|
-
* caller uses as a signal to fall back to the raw `text` column or
|
|
91
|
-
* skip the row.
|
|
92
|
-
*
|
|
93
|
-
* References:
|
|
94
|
-
* - Apple's typedstream format: see darling-gnustep-base, GSTypedStream.m
|
|
95
|
-
* - imessage-exporter's Rust implementation (MIT) for the full parser
|
|
96
|
-
* - NSAttributedString serialisation in Cocoa Foundation
|
|
97
|
-
*/
|
|
98
|
-
export function decodeAttributedBody(blob) {
|
|
99
|
-
if (!blob)
|
|
100
|
-
return null;
|
|
101
|
-
const buf = blob instanceof Buffer ? blob : Buffer.from(blob);
|
|
102
|
-
if (buf.length < 20)
|
|
103
|
-
return null;
|
|
104
|
-
// Locate the NSString class marker. Messages.app uses either "NSString"
|
|
105
|
-
// or (rarely) "NSMutableString" depending on whether the attributed
|
|
106
|
-
// string wraps a mutable backing store. Try both.
|
|
107
|
-
const MARKERS = [Buffer.from("NSString", "latin1"), Buffer.from("NSMutableString", "latin1")];
|
|
108
|
-
let start = -1;
|
|
109
|
-
for (const marker of MARKERS) {
|
|
110
|
-
const idx = buf.indexOf(marker);
|
|
111
|
-
if (idx !== -1) {
|
|
112
|
-
start = idx + marker.length;
|
|
113
|
-
break;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
if (start === -1)
|
|
117
|
-
return null;
|
|
118
|
-
// After the class name is a short preamble: the byte sequence varies
|
|
119
|
-
// slightly depending on object graph position but always ends with
|
|
120
|
-
// `\x2b` (the typedstream `+` cstring verb). We scan forward a small
|
|
121
|
-
// bounded window for the `+` so the decoder doesn't drift into the
|
|
122
|
-
// attributes dictionary if Apple changes the exact preamble length.
|
|
123
|
-
const MAX_PREAMBLE = 16;
|
|
124
|
-
let plusAt = -1;
|
|
125
|
-
for (let i = start; i < Math.min(start + MAX_PREAMBLE, buf.length); i++) {
|
|
126
|
-
if (buf[i] === 0x2b) {
|
|
127
|
-
plusAt = i;
|
|
128
|
-
break;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
if (plusAt === -1)
|
|
132
|
-
return null;
|
|
133
|
-
// Read the length. Typedstream uses:
|
|
134
|
-
// - single byte length for 0..254
|
|
135
|
-
// - 0x81 + little-endian uint16 for 255..65535
|
|
136
|
-
// - 0x82 + little-endian uint32 for larger (rare in chat.db)
|
|
137
|
-
let cursor = plusAt + 1;
|
|
138
|
-
if (cursor >= buf.length)
|
|
139
|
-
return null;
|
|
140
|
-
let length;
|
|
141
|
-
const first = buf[cursor];
|
|
142
|
-
if (first < 0x80 || first === 0xff) {
|
|
143
|
-
// Direct length byte
|
|
144
|
-
length = first;
|
|
145
|
-
cursor += 1;
|
|
146
|
-
}
|
|
147
|
-
else if (first === 0x81 && cursor + 2 < buf.length) {
|
|
148
|
-
length = buf.readUInt16LE(cursor + 1);
|
|
149
|
-
cursor += 3;
|
|
150
|
-
}
|
|
151
|
-
else if (first === 0x82 && cursor + 4 < buf.length) {
|
|
152
|
-
length = buf.readUInt32LE(cursor + 1);
|
|
153
|
-
cursor += 5;
|
|
154
|
-
}
|
|
155
|
-
else {
|
|
156
|
-
// Unknown length encoding; give up.
|
|
157
|
-
return null;
|
|
158
|
-
}
|
|
159
|
-
if (length === 0)
|
|
160
|
-
return "";
|
|
161
|
-
if (cursor + length > buf.length) {
|
|
162
|
-
// Truncated blob — return what we can without overrunning.
|
|
163
|
-
length = buf.length - cursor;
|
|
164
|
-
if (length <= 0)
|
|
165
|
-
return null;
|
|
166
|
-
}
|
|
167
|
-
// The bytes are UTF-8. Buffer.toString("utf8") silently replaces
|
|
168
|
-
// invalid sequences with U+FFFD, which is the right behaviour here —
|
|
169
|
-
// the agent would rather see a slightly-mangled message than nothing.
|
|
170
|
-
return buf.slice(cursor, cursor + length).toString("utf8");
|
|
171
|
-
}
|
|
172
|
-
/**
|
|
173
|
-
* Parse the reaction fields out of a chat.db row when `associated_message_type`
|
|
174
|
-
* is in the reaction range (2000-3005). Returns null for non-reaction rows.
|
|
175
|
-
*/
|
|
176
|
-
function parseReaction(row) {
|
|
177
|
-
const type = row.associated_message_type ?? 0;
|
|
178
|
-
if (type < 2000 || type >= 4000)
|
|
179
|
-
return null;
|
|
180
|
-
const isRemove = type >= 3000;
|
|
181
|
-
const baseType = isRemove ? type - 1000 : type;
|
|
182
|
-
const kind = baseType === 2000
|
|
183
|
-
? "love"
|
|
184
|
-
: baseType === 2001
|
|
185
|
-
? "like"
|
|
186
|
-
: baseType === 2002
|
|
187
|
-
? "dislike"
|
|
188
|
-
: baseType === 2003
|
|
189
|
-
? "laugh"
|
|
190
|
-
: baseType === 2004
|
|
191
|
-
? "emphasis"
|
|
192
|
-
: baseType === 2005
|
|
193
|
-
? "question"
|
|
194
|
-
: baseType === 2006
|
|
195
|
-
? "sticker"
|
|
196
|
-
: "unknown";
|
|
197
|
-
// associated_message_guid comes back as either a plain guid or prefixed
|
|
198
|
-
// with `p:<partIndex>/<guid>` for messages that target a specific part
|
|
199
|
-
// of a multipart message. Strip the prefix so downstream handlers get
|
|
200
|
-
// a clean guid they can match against other rows.
|
|
201
|
-
let targetGuid = row.associated_message_guid ?? "";
|
|
202
|
-
const slashIdx = targetGuid.lastIndexOf("/");
|
|
203
|
-
if (slashIdx !== -1)
|
|
204
|
-
targetGuid = targetGuid.slice(slashIdx + 1);
|
|
205
|
-
return {
|
|
206
|
-
kind,
|
|
207
|
-
add: !isRemove,
|
|
208
|
-
rawType: type,
|
|
209
|
-
targetGuid,
|
|
210
|
-
emoji: row.associated_message_emoji,
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
const runtimeRequire = createRequire(import.meta.url);
|
|
214
|
-
const loggedChatDbOpenFailures = new Set();
|
|
215
|
-
const lastChatDbAccessIssues = new Map();
|
|
216
|
-
let loggedSqliteUnavailable = false;
|
|
217
|
-
export function getLastChatDbAccessIssue(dbPath = DEFAULT_CHAT_DB_PATH) {
|
|
218
|
-
return lastChatDbAccessIssues.get(dbPath) ?? null;
|
|
219
|
-
}
|
|
220
|
-
/**
|
|
221
|
-
* Dynamically resolve a SQLite backend. We keep the specifiers opaque so the
|
|
222
|
-
* module still loads under runtimes that only support one of them.
|
|
223
|
-
*/
|
|
224
|
-
async function tryLoadSqlite() {
|
|
225
|
-
try {
|
|
226
|
-
const mod = runtimeRequire("bun:sqlite");
|
|
227
|
-
const Database = mod.Database ?? mod.default;
|
|
228
|
-
if (Database) {
|
|
229
|
-
return (path, options) => new Database(path, options);
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
catch {
|
|
233
|
-
// Fall through to Node's built-in SQLite runtime.
|
|
234
|
-
}
|
|
235
|
-
try {
|
|
236
|
-
const mod = (await import("node:sqlite"));
|
|
237
|
-
const DatabaseSync = mod.DatabaseSync ?? mod.default?.DatabaseSync;
|
|
238
|
-
if (!DatabaseSync) {
|
|
239
|
-
return null;
|
|
240
|
-
}
|
|
241
|
-
return (path, options) => {
|
|
242
|
-
const db = new DatabaseSync(path, {
|
|
243
|
-
readOnly: options?.readonly ?? false,
|
|
244
|
-
});
|
|
245
|
-
return {
|
|
246
|
-
query(sql) {
|
|
247
|
-
const statement = db.prepare(sql);
|
|
248
|
-
return {
|
|
249
|
-
all(...params) {
|
|
250
|
-
return statement.all(...params);
|
|
251
|
-
},
|
|
252
|
-
};
|
|
253
|
-
},
|
|
254
|
-
close() {
|
|
255
|
-
db.close();
|
|
256
|
-
},
|
|
257
|
-
};
|
|
258
|
-
};
|
|
259
|
-
}
|
|
260
|
-
catch {
|
|
261
|
-
return null;
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
/**
|
|
265
|
-
* Open macOS chat.db read-only and return a reader bound to it.
|
|
266
|
-
*
|
|
267
|
-
* Returns `null` — and logs a human-readable reason — in every failure
|
|
268
|
-
* mode so the caller can degrade to send-only operation instead of
|
|
269
|
-
* crashing the runtime:
|
|
270
|
-
*
|
|
271
|
-
* - Not running under Bun (bun:sqlite built-in unavailable)
|
|
272
|
-
* - chat.db does not exist at the given path
|
|
273
|
-
* - chat.db exists but cannot be opened (missing Full Disk Access, etc.)
|
|
274
|
-
*/
|
|
275
|
-
export async function openChatDb(dbPath = DEFAULT_CHAT_DB_PATH, options = {}) {
|
|
276
|
-
const diagnosticsLogger = options.diagnosticsLogger ?? logger;
|
|
277
|
-
const openDatabase = await tryLoadSqlite();
|
|
278
|
-
if (!openDatabase) {
|
|
279
|
-
lastChatDbAccessIssues.set(dbPath, {
|
|
280
|
-
code: "sqlite_unavailable",
|
|
281
|
-
path: dbPath,
|
|
282
|
-
reason: "No supported SQLite runtime is available.",
|
|
283
|
-
permissionAction: null,
|
|
284
|
-
});
|
|
285
|
-
if (!loggedSqliteUnavailable) {
|
|
286
|
-
loggedSqliteUnavailable = true;
|
|
287
|
-
diagnosticsLogger.warn("[imessage] no supported SQLite runtime is available — inbound polling is disabled. " +
|
|
288
|
-
"Run the agent under Bun or Node 22+, or disable polling with IMESSAGE_POLL_INTERVAL_MS=0. " +
|
|
289
|
-
"Outbound send via AppleScript still works regardless. Further identical startup checks will log at debug.");
|
|
290
|
-
}
|
|
291
|
-
else {
|
|
292
|
-
diagnosticsLogger.debug("[imessage] SQLite runtime still unavailable; inbound polling remains disabled");
|
|
293
|
-
}
|
|
294
|
-
return null;
|
|
295
|
-
}
|
|
296
|
-
let db;
|
|
297
|
-
try {
|
|
298
|
-
db = openDatabase(dbPath, { readonly: true });
|
|
299
|
-
}
|
|
300
|
-
catch (error) {
|
|
301
|
-
const reason = error instanceof Error ? error.message : String(error);
|
|
302
|
-
lastChatDbAccessIssues.set(dbPath, {
|
|
303
|
-
code: "open_failed",
|
|
304
|
-
path: dbPath,
|
|
305
|
-
reason,
|
|
306
|
-
permissionAction: createFullDiskAccessAction(),
|
|
307
|
-
});
|
|
308
|
-
const failureKey = `${dbPath}\0${reason}`;
|
|
309
|
-
if (!loggedChatDbOpenFailures.has(failureKey)) {
|
|
310
|
-
loggedChatDbOpenFailures.add(failureKey);
|
|
311
|
-
diagnosticsLogger.warn(`[imessage] Failed to open chat.db at ${dbPath}: ${reason}. ` +
|
|
312
|
-
"Ensure the path is correct and the host process has Full Disk Access " +
|
|
313
|
-
"(macOS → System Settings → Privacy & Security → Full Disk Access). " +
|
|
314
|
-
`Open it directly with ${MACOS_FULL_DISK_ACCESS_SETTINGS_URL}. ` +
|
|
315
|
-
"Plugin will continue in send-only mode. Further identical startup failures will log at debug.");
|
|
316
|
-
}
|
|
317
|
-
else {
|
|
318
|
-
diagnosticsLogger.debug(`[imessage] chat.db at ${dbPath} is still unavailable (${reason}); continuing in send-only mode`);
|
|
319
|
-
}
|
|
320
|
-
return null;
|
|
321
|
-
}
|
|
322
|
-
lastChatDbAccessIssues.delete(dbPath);
|
|
323
|
-
// Prepared statement reused on every poll. We join `message` to
|
|
324
|
-
// `handle` (for the sender identity) and to `chat` (for the room
|
|
325
|
-
// identity and display name) via the `chat_message_join` edge table.
|
|
326
|
-
// We also pull `attributedBody` so the reader can recover the text for
|
|
327
|
-
// the ~97% of messages that store their content there instead of in
|
|
328
|
-
// the plain `text` column, and enough status columns to surface
|
|
329
|
-
// reactions, replies, edits, and read receipts to the caller.
|
|
330
|
-
const pollStmt = db.query(`
|
|
331
|
-
SELECT
|
|
332
|
-
m.ROWID AS row_id,
|
|
333
|
-
m.guid AS guid,
|
|
334
|
-
m.text AS text,
|
|
335
|
-
m.attributedBody AS attributed_body,
|
|
336
|
-
m.date AS apple_date,
|
|
337
|
-
m.date_read AS apple_date_read,
|
|
338
|
-
m.date_edited AS apple_date_edited,
|
|
339
|
-
m.date_retracted AS apple_date_retracted,
|
|
340
|
-
m.is_from_me AS is_from_me,
|
|
341
|
-
m.is_read AS is_read,
|
|
342
|
-
m.is_sent AS is_sent,
|
|
343
|
-
m.is_delivered AS is_delivered,
|
|
344
|
-
m.item_type AS item_type,
|
|
345
|
-
m.reply_to_guid AS reply_to_guid,
|
|
346
|
-
m.associated_message_guid AS associated_message_guid,
|
|
347
|
-
m.associated_message_type AS associated_message_type,
|
|
348
|
-
m.associated_message_emoji AS associated_message_emoji,
|
|
349
|
-
m.cache_has_attachments AS cache_has_attachments,
|
|
350
|
-
m.service AS message_service,
|
|
351
|
-
h.id AS handle,
|
|
352
|
-
h.service AS handle_service,
|
|
353
|
-
c.chat_identifier AS chat_identifier,
|
|
354
|
-
c.display_name AS display_name,
|
|
355
|
-
c.style AS chat_style
|
|
356
|
-
FROM message m
|
|
357
|
-
LEFT JOIN handle h ON m.handle_id = h.ROWID
|
|
358
|
-
LEFT JOIN chat_message_join cmj ON m.ROWID = cmj.message_id
|
|
359
|
-
LEFT JOIN chat c ON cmj.chat_id = c.ROWID
|
|
360
|
-
WHERE m.ROWID > ?
|
|
361
|
-
ORDER BY m.ROWID ASC
|
|
362
|
-
LIMIT ?
|
|
363
|
-
`);
|
|
364
|
-
// Secondary statement: fetch every attachment row attached to a given
|
|
365
|
-
// message ROWID. Called lazily per-message when `cache_has_attachments`
|
|
366
|
-
// is set, so zero-attachment polls pay nothing.
|
|
367
|
-
const attachmentsStmt = db.query(`
|
|
368
|
-
SELECT
|
|
369
|
-
a.guid AS guid,
|
|
370
|
-
a.transfer_name AS transfer_name,
|
|
371
|
-
a.filename AS filename,
|
|
372
|
-
a.mime_type AS mime_type,
|
|
373
|
-
a.uti AS uti,
|
|
374
|
-
a.total_bytes AS total_bytes,
|
|
375
|
-
a.is_sticker AS is_sticker
|
|
376
|
-
FROM attachment a
|
|
377
|
-
JOIN message_attachment_join maj ON a.ROWID = maj.attachment_id
|
|
378
|
-
WHERE maj.message_id = ?
|
|
379
|
-
`);
|
|
380
|
-
// Separate prepared statement for the cheap "what's the tip?" query.
|
|
381
|
-
// Used once on service start to seed the polling cursor.
|
|
382
|
-
const tipStmt = db.query("SELECT MAX(ROWID) AS max_row_id FROM message");
|
|
383
|
-
const latestOwnMessageStmt = db.query("SELECT MAX(date) AS max_apple_date FROM message WHERE is_from_me = 1");
|
|
384
|
-
const recentMessagesStmt = db.query(`
|
|
385
|
-
SELECT
|
|
386
|
-
m.ROWID AS row_id,
|
|
387
|
-
m.guid AS guid,
|
|
388
|
-
m.text AS text,
|
|
389
|
-
m.attributedBody AS attributed_body,
|
|
390
|
-
m.date AS apple_date,
|
|
391
|
-
m.date_read AS apple_date_read,
|
|
392
|
-
m.date_edited AS apple_date_edited,
|
|
393
|
-
m.date_retracted AS apple_date_retracted,
|
|
394
|
-
m.is_from_me AS is_from_me,
|
|
395
|
-
m.is_read AS is_read,
|
|
396
|
-
m.is_sent AS is_sent,
|
|
397
|
-
m.is_delivered AS is_delivered,
|
|
398
|
-
m.item_type AS item_type,
|
|
399
|
-
m.reply_to_guid AS reply_to_guid,
|
|
400
|
-
m.associated_message_guid AS associated_message_guid,
|
|
401
|
-
m.associated_message_type AS associated_message_type,
|
|
402
|
-
m.associated_message_emoji AS associated_message_emoji,
|
|
403
|
-
m.cache_has_attachments AS cache_has_attachments,
|
|
404
|
-
m.service AS message_service,
|
|
405
|
-
h.id AS handle,
|
|
406
|
-
h.service AS handle_service,
|
|
407
|
-
c.chat_identifier AS chat_identifier,
|
|
408
|
-
c.display_name AS display_name,
|
|
409
|
-
c.style AS chat_style
|
|
410
|
-
FROM message m
|
|
411
|
-
LEFT JOIN handle h ON m.handle_id = h.ROWID
|
|
412
|
-
LEFT JOIN chat_message_join cmj ON m.ROWID = cmj.message_id
|
|
413
|
-
LEFT JOIN chat c ON cmj.chat_id = c.ROWID
|
|
414
|
-
ORDER BY m.ROWID DESC
|
|
415
|
-
LIMIT ?
|
|
416
|
-
`);
|
|
417
|
-
const recentMessagesByChatStmt = db.query(`
|
|
418
|
-
SELECT
|
|
419
|
-
m.ROWID AS row_id,
|
|
420
|
-
m.guid AS guid,
|
|
421
|
-
m.text AS text,
|
|
422
|
-
m.attributedBody AS attributed_body,
|
|
423
|
-
m.date AS apple_date,
|
|
424
|
-
m.date_read AS apple_date_read,
|
|
425
|
-
m.date_edited AS apple_date_edited,
|
|
426
|
-
m.date_retracted AS apple_date_retracted,
|
|
427
|
-
m.is_from_me AS is_from_me,
|
|
428
|
-
m.is_read AS is_read,
|
|
429
|
-
m.is_sent AS is_sent,
|
|
430
|
-
m.is_delivered AS is_delivered,
|
|
431
|
-
m.item_type AS item_type,
|
|
432
|
-
m.reply_to_guid AS reply_to_guid,
|
|
433
|
-
m.associated_message_guid AS associated_message_guid,
|
|
434
|
-
m.associated_message_type AS associated_message_type,
|
|
435
|
-
m.associated_message_emoji AS associated_message_emoji,
|
|
436
|
-
m.cache_has_attachments AS cache_has_attachments,
|
|
437
|
-
m.service AS message_service,
|
|
438
|
-
h.id AS handle,
|
|
439
|
-
h.service AS handle_service,
|
|
440
|
-
c.chat_identifier AS chat_identifier,
|
|
441
|
-
c.display_name AS display_name,
|
|
442
|
-
c.style AS chat_style
|
|
443
|
-
FROM message m
|
|
444
|
-
LEFT JOIN handle h ON m.handle_id = h.ROWID
|
|
445
|
-
LEFT JOIN chat_message_join cmj ON m.ROWID = cmj.message_id
|
|
446
|
-
LEFT JOIN chat c ON cmj.chat_id = c.ROWID
|
|
447
|
-
WHERE c.chat_identifier = ?
|
|
448
|
-
ORDER BY m.ROWID DESC
|
|
449
|
-
LIMIT ?
|
|
450
|
-
`);
|
|
451
|
-
// List-chats statement: every chat joined to handles via
|
|
452
|
-
// chat_handle_join, grouped so each chat returns one row with an
|
|
453
|
-
// aggregated participant list.
|
|
454
|
-
const chatsStmt = db.query(`
|
|
455
|
-
SELECT
|
|
456
|
-
c.ROWID AS row_id,
|
|
457
|
-
c.chat_identifier AS chat_identifier,
|
|
458
|
-
c.display_name AS display_name,
|
|
459
|
-
c.service_name AS service_name,
|
|
460
|
-
c.style AS chat_style,
|
|
461
|
-
c.last_read_message_timestamp AS last_read_apple_date,
|
|
462
|
-
GROUP_CONCAT(h.id, ',') AS participant_handles
|
|
463
|
-
FROM chat c
|
|
464
|
-
LEFT JOIN chat_handle_join chj ON c.ROWID = chj.chat_id
|
|
465
|
-
LEFT JOIN handle h ON chj.handle_id = h.ROWID
|
|
466
|
-
GROUP BY c.ROWID
|
|
467
|
-
ORDER BY c.last_read_message_timestamp DESC
|
|
468
|
-
`);
|
|
469
|
-
let closed = false;
|
|
470
|
-
function materializeMessages(rows) {
|
|
471
|
-
const out = [];
|
|
472
|
-
let undecodable = 0;
|
|
473
|
-
for (const row of rows) {
|
|
474
|
-
// Resolve the visible text: prefer the plain `text` column, fall
|
|
475
|
-
// back to decoding `attributedBody`, then empty string.
|
|
476
|
-
let text = "";
|
|
477
|
-
if (row.text && row.text.length > 0) {
|
|
478
|
-
text = row.text;
|
|
479
|
-
}
|
|
480
|
-
else if (row.attributed_body) {
|
|
481
|
-
const decoded = decodeAttributedBody(row.attributed_body);
|
|
482
|
-
if (decoded != null) {
|
|
483
|
-
text = decoded;
|
|
484
|
-
}
|
|
485
|
-
else {
|
|
486
|
-
undecodable++;
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
// Classify the row. Reactions get their own kind + a parsed
|
|
490
|
-
// reaction payload; system messages (group add/remove/rename)
|
|
491
|
-
// surface as `"system"` so the caller can log or ignore.
|
|
492
|
-
const assocType = row.associated_message_type ?? 0;
|
|
493
|
-
let kind = "text";
|
|
494
|
-
let reaction = null;
|
|
495
|
-
if (assocType >= 2000 && assocType < 4000) {
|
|
496
|
-
kind = "reaction";
|
|
497
|
-
reaction = parseReaction(row);
|
|
498
|
-
}
|
|
499
|
-
else if (row.item_type != null && row.item_type !== 0) {
|
|
500
|
-
kind = "system";
|
|
501
|
-
}
|
|
502
|
-
// Attachments — only fetched when the cache bit indicates any.
|
|
503
|
-
let attachments = [];
|
|
504
|
-
if (row.cache_has_attachments === 1) {
|
|
505
|
-
try {
|
|
506
|
-
const attRows = attachmentsStmt.all(row.row_id);
|
|
507
|
-
attachments = attRows.map((a) => ({
|
|
508
|
-
guid: a.guid,
|
|
509
|
-
filename: a.transfer_name ?? a.filename ?? null,
|
|
510
|
-
uti: a.uti,
|
|
511
|
-
mimeType: a.mime_type,
|
|
512
|
-
totalBytes: a.total_bytes,
|
|
513
|
-
isSticker: a.is_sticker === 1,
|
|
514
|
-
}));
|
|
515
|
-
}
|
|
516
|
-
catch (error) {
|
|
517
|
-
logger.debug(`[imessage] attachment query failed for rowid=${row.row_id}: ${error instanceof Error ? error.message : String(error)}`);
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
// Service resolution: prefer the message row's own service field,
|
|
521
|
-
// fall back to the handle's service (stable across messages from
|
|
522
|
-
// the same sender), else unknown.
|
|
523
|
-
const service = row.message_service ?? row.handle_service ?? null;
|
|
524
|
-
out.push({
|
|
525
|
-
rowId: row.row_id,
|
|
526
|
-
guid: row.guid,
|
|
527
|
-
text,
|
|
528
|
-
kind,
|
|
529
|
-
handle: row.handle ?? "",
|
|
530
|
-
chatId: row.chat_identifier ?? "",
|
|
531
|
-
chatType: row.chat_style === 43 ? "group" : "direct",
|
|
532
|
-
displayName: row.display_name,
|
|
533
|
-
timestamp: appleDateToJsMs(row.apple_date),
|
|
534
|
-
isFromMe: row.is_from_me === 1,
|
|
535
|
-
service,
|
|
536
|
-
isSent: row.is_sent === 1,
|
|
537
|
-
isDelivered: row.is_delivered === 1,
|
|
538
|
-
isRead: row.is_read === 1,
|
|
539
|
-
dateRead: appleDateToJsMs(row.apple_date_read ?? 0),
|
|
540
|
-
dateEdited: appleDateToJsMs(row.apple_date_edited ?? 0),
|
|
541
|
-
dateRetracted: appleDateToJsMs(row.apple_date_retracted ?? 0),
|
|
542
|
-
replyToGuid: row.reply_to_guid,
|
|
543
|
-
reaction,
|
|
544
|
-
attachments,
|
|
545
|
-
});
|
|
546
|
-
}
|
|
547
|
-
if (undecodable > 0) {
|
|
548
|
-
logger.debug(`[imessage] chat.db poll: ${undecodable} row(s) had attributedBody that could not be decoded; their text is empty`);
|
|
549
|
-
}
|
|
550
|
-
return out;
|
|
551
|
-
}
|
|
552
|
-
return {
|
|
553
|
-
fetchNewMessages(sinceRowId, limit) {
|
|
554
|
-
if (closed)
|
|
555
|
-
return [];
|
|
556
|
-
let rows;
|
|
557
|
-
try {
|
|
558
|
-
rows = pollStmt.all(sinceRowId, limit);
|
|
559
|
-
}
|
|
560
|
-
catch (error) {
|
|
561
|
-
logger.error(`[imessage] chat.db query failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
562
|
-
return [];
|
|
563
|
-
}
|
|
564
|
-
return materializeMessages(rows);
|
|
565
|
-
},
|
|
566
|
-
getLatestRowId() {
|
|
567
|
-
if (closed)
|
|
568
|
-
return 0;
|
|
569
|
-
try {
|
|
570
|
-
const rows = tipStmt.all();
|
|
571
|
-
return rows[0]?.max_row_id ?? 0;
|
|
572
|
-
}
|
|
573
|
-
catch (error) {
|
|
574
|
-
logger.error(`[imessage] chat.db tip query failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
575
|
-
return 0;
|
|
576
|
-
}
|
|
577
|
-
},
|
|
578
|
-
getLatestOwnMessageTimestamp() {
|
|
579
|
-
if (closed)
|
|
580
|
-
return null;
|
|
581
|
-
try {
|
|
582
|
-
const rows = latestOwnMessageStmt.all();
|
|
583
|
-
const appleDate = rows[0]?.max_apple_date ?? null;
|
|
584
|
-
return appleDate === null ? null : appleDateToJsMs(appleDate);
|
|
585
|
-
}
|
|
586
|
-
catch (error) {
|
|
587
|
-
logger.error(`[imessage] chat.db latest own message query failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
588
|
-
return null;
|
|
589
|
-
}
|
|
590
|
-
},
|
|
591
|
-
listMessages(options = {}) {
|
|
592
|
-
if (closed)
|
|
593
|
-
return [];
|
|
594
|
-
const chatId = options.chatId?.trim();
|
|
595
|
-
const requestedLimit = typeof options.limit === "number" && Number.isFinite(options.limit) ? options.limit : 50;
|
|
596
|
-
const limit = Math.max(1, Math.trunc(requestedLimit));
|
|
597
|
-
let rows;
|
|
598
|
-
try {
|
|
599
|
-
rows = chatId
|
|
600
|
-
? recentMessagesByChatStmt.all(chatId, limit)
|
|
601
|
-
: recentMessagesStmt.all(limit);
|
|
602
|
-
}
|
|
603
|
-
catch (error) {
|
|
604
|
-
logger.error(`[imessage] chat.db listMessages query failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
605
|
-
return [];
|
|
606
|
-
}
|
|
607
|
-
// Queries run DESC for efficiency on "latest N" reads. Reverse
|
|
608
|
-
// back to chronological order so API/UI callers can render in the
|
|
609
|
-
// natural oldest→newest sequence without a second sort.
|
|
610
|
-
return materializeMessages(rows).reverse();
|
|
611
|
-
},
|
|
612
|
-
listChats() {
|
|
613
|
-
if (closed)
|
|
614
|
-
return [];
|
|
615
|
-
try {
|
|
616
|
-
const rows = chatsStmt.all();
|
|
617
|
-
return rows.map((row) => ({
|
|
618
|
-
chatId: row.chat_identifier ?? `chat-${row.row_id}`,
|
|
619
|
-
chatType: row.chat_style === 43 ? "group" : "direct",
|
|
620
|
-
displayName: row.display_name,
|
|
621
|
-
serviceName: row.service_name,
|
|
622
|
-
participants: row.participant_handles
|
|
623
|
-
? row.participant_handles.split(",").filter(Boolean)
|
|
624
|
-
: [],
|
|
625
|
-
lastReadMessageTimestamp: appleDateToJsMs(row.last_read_apple_date ?? 0),
|
|
626
|
-
}));
|
|
627
|
-
}
|
|
628
|
-
catch (error) {
|
|
629
|
-
logger.error(`[imessage] chat.db listChats query failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
630
|
-
return [];
|
|
631
|
-
}
|
|
632
|
-
},
|
|
633
|
-
close() {
|
|
634
|
-
if (closed)
|
|
635
|
-
return;
|
|
636
|
-
closed = true;
|
|
637
|
-
try {
|
|
638
|
-
db.close();
|
|
639
|
-
}
|
|
640
|
-
catch {
|
|
641
|
-
// Closing a read-only handle on a file we don't own should
|
|
642
|
-
// never throw in practice, but we swallow to stay idempotent.
|
|
643
|
-
}
|
|
644
|
-
},
|
|
645
|
-
};
|
|
646
|
-
}
|
|
647
|
-
//# sourceMappingURL=chatdb-reader.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"chatdb-reader.js","sourceRoot":"","sources":["../src/chatdb-reader.ts"],"names":[],"mappings":"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"}
|