UNPKG

imessage-ts

Version:

TypeScript library for interacting with iMessage on macOS - send messages, monitor chats, and automate responses

244 lines 8.45 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.exploreDatabase = exploreDatabase; exports.getDatabaseSchema = getDatabaseSchema; exports.getTableColumns = getTableColumns; exports.getRecentMessages = getRecentMessages; exports.searchMessages = searchMessages; exports.getMessageByGuid = getMessageByGuid; exports.getAttributedBodyExamples = getAttributedBodyExamples; const sqlite3_1 = __importDefault(require("sqlite3")); const path_1 = __importDefault(require("path")); const os_1 = __importDefault(require("os")); /** * Utility script to directly query the iMessage database and explore its structure */ const DEFAULT_DB_PATH = path_1.default.join(os_1.default.homedir(), 'Library/Messages/chat.db'); /** * Connect directly to the iMessage database and run queries */ async function exploreDatabase(query) { return new Promise((resolve, reject) => { // Open a direct connection to the database const db = new sqlite3_1.default.Database(DEFAULT_DB_PATH, sqlite3_1.default.OPEN_READONLY, (err) => { if (err) { reject(`Error opening database: ${err.message}`); return; } console.log('Connected to iMessage database'); // Execute the query db.all(query, [], (err, rows) => { // Close the database connection db.close(); if (err) { reject(`Error executing query: ${err.message}`); return; } resolve(rows); }); }); }); } /** * Helper function to clean up extracted text by removing binary data */ function cleanMessageText(text) { // Find the index of the first non-printable or binary character const firstBinaryIndex = text.search(/[^\x20-\x7E]/); if (firstBinaryIndex !== -1) { text = text.substring(0, firstBinaryIndex); } // The attributed string format appears to include a single character prefix // (N or 6 in observed examples) that is not part of the message. // Remove this first character if the text is long enough if (text.length > 1) { return text.substring(1); } return text; } /** * Get database schema information */ async function getDatabaseSchema() { return exploreDatabase(` SELECT name as table_name, sql as create_statement FROM sqlite_master WHERE type='table' ORDER BY name `); } /** * Get table columns */ async function getTableColumns(tableName) { return exploreDatabase(`PRAGMA table_info(${tableName})`); } /** * Get the most recent messages with all columns */ async function getRecentMessages(limit = 10) { const messages = await exploreDatabase(` SELECT m.*, datetime(m.date / 1000000000 + 978307200, 'unixepoch', 'localtime') as formatted_date, h.id as handle_address FROM message m LEFT JOIN handle h ON m.handle_id = h.ROWID ORDER BY m.date DESC LIMIT ${limit} `); // Process messages to extract text from attributedBody when text is null return messages.map(msg => { // If text is null but attributedBody exists, try to extract text if (msg.text === null && msg.attributedBody) { try { // Check if it's a Buffer or string const attributedBodyStr = Buffer.isBuffer(msg.attributedBody) ? msg.attributedBody.toString('utf8') : String(msg.attributedBody); // Extract text after the NSString pattern const nsStringMatch = attributedBodyStr.match(/NSString[^\+]+\+([^\n\r]+)/); if (nsStringMatch && nsStringMatch[1]) { msg.text = cleanMessageText(nsStringMatch[1]); } else { // Try just taking any text after a plus sign const textMatch = attributedBodyStr.match(/\+([^\n\r]+)/); if (textMatch && textMatch[1]) { msg.text = cleanMessageText(textMatch[1]); } } } catch (e) { // Silently fail } } return msg; }); } /** * Search for a specific string in message columns */ async function searchMessages(searchString) { // Escape single quotes for SQL const escapedSearch = searchString.replace(/'/g, "''"); const messages = await exploreDatabase(` SELECT m.*, datetime(m.date / 1000000000 + 978307200, 'unixepoch', 'localtime') as formatted_date, h.id as handle_address FROM message m LEFT JOIN handle h ON m.handle_id = h.ROWID WHERE m.text LIKE '%${escapedSearch}%' OR m.attributedBody LIKE '%${escapedSearch}%' OR m.associated_message_guid LIKE '%${escapedSearch}%' OR m.message_summary_info LIKE '%${escapedSearch}%' ORDER BY m.date DESC LIMIT 20 `); // Process messages to extract text from attributedBody when text is null return messages.map(msg => { // If text is null but attributedBody exists, try to extract text if (msg.text === null && msg.attributedBody) { try { // Check if it's a Buffer or string const attributedBodyStr = Buffer.isBuffer(msg.attributedBody) ? msg.attributedBody.toString('utf8') : String(msg.attributedBody); // Extract text after the NSString pattern const nsStringMatch = attributedBodyStr.match(/NSString[^\+]+\+([^\n\r]+)/); if (nsStringMatch && nsStringMatch[1]) { msg.text = cleanMessageText(nsStringMatch[1]); } else { // Try just taking any text after a plus sign const textMatch = attributedBodyStr.match(/\+([^\n\r]+)/); if (textMatch && textMatch[1]) { msg.text = cleanMessageText(textMatch[1]); } } } catch (e) { // Silently fail } } return msg; }); } /** * Get data for a specific message GUID */ async function getMessageByGuid(guid) { const messages = await exploreDatabase(` SELECT m.*, datetime(m.date / 1000000000 + 978307200, 'unixepoch', 'localtime') as formatted_date, h.id as handle_address FROM message m LEFT JOIN handle h ON m.handle_id = h.ROWID WHERE m.guid = '${guid}' `); // Process messages to extract text from attributedBody when text is null return messages.map(msg => { // If text is null but attributedBody exists, try to extract text if (msg.text === null && msg.attributedBody) { try { // Check if it's a Buffer or string const attributedBodyStr = Buffer.isBuffer(msg.attributedBody) ? msg.attributedBody.toString('utf8') : String(msg.attributedBody); // Extract text after the NSString pattern const nsStringMatch = attributedBodyStr.match(/NSString[^\+]+\+([^\n\r]+)/); if (nsStringMatch && nsStringMatch[1]) { msg.text = cleanMessageText(nsStringMatch[1]); } else { // Try just taking any text after a plus sign const textMatch = attributedBodyStr.match(/\+([^\n\r]+)/); if (textMatch && textMatch[1]) { msg.text = cleanMessageText(textMatch[1]); } } } catch (e) { // Silently fail } } return msg; }); } // Functions to explore message data structures async function getAttributedBodyExamples() { return exploreDatabase(` SELECT ROWID, attributedBody, text FROM message WHERE attributedBody IS NOT NULL ORDER BY date DESC LIMIT 5 `); } //# sourceMappingURL=database-explorer.js.map