imessage-parser
Version:
Parse iMessage chat.db attributedBody NSAttributedString format in Node.js
350 lines (257 loc) ⢠11 kB
Markdown
# node-imessage-parser
A Node.js library for parsing iMessage `chat.db` database files, specifically handling the `attributedBody` field that contains NSAttributedString data in Apple's proprietary typedstream format.
## Features
- Parse iMessage `attributedBody` fields containing formatted text
- Extract plain text from NSAttributedString binary data
- Handle multi-line messages with proper formatting preservation
- Support for numbered lists and sub-items
- Fallback text extraction for corrupted or unknown formats
- Direct database access with search capabilities
- Build deep links to iMessage conversations and messages
## Installation
```bash
npm install imessage-parser
# Or install globally for CLI access
npm install -g imessage-parser
```
## CLI Usage
The package includes a comprehensive CLI for working with iMessage data:
```bash
# List all chats
imessage-parser list-chats
# Extract messages from a specific chat
imessage-parser extract "Family Chat" -o family-messages.md -f markdown
# Search for messages across all chats
imessage-parser search "vacation plans" --limit 20
# Parse a specific message by ROWID
imessage-parser parse-message 302041 --show-readable
# Show statistics
imessage-parser stats -c "Family Chat"
# Get help
imessage-parser --help
```
### CLI Commands
- **list-chats** - List all available chats with their IDs
- **extract** - Extract messages from a specific chat
- Supports text, JSON, and Markdown output formats
- Can filter by date range
- Optionally includes formatting information
- **search** - Search messages across all chats
- Supports case-sensitive and regex search
- Highlights search terms in results
- **parse-message** - Parse and analyze a specific message
- Shows hex dump of attributedBody
- Displays all readable text segments
- Extracts formatting attributes
- **stats** - Display statistics about messages
- Overall database stats or per-chat statistics
- Message counts, date ranges, and formatting info
## Programmatic Usage
### Basic Usage
```javascript
const { parseAttributedBody } = require('imessage-parser');
// Parse a Buffer containing attributedBody data
const buffer = /* Buffer from iMessage database */;
const result = parseAttributedBody(buffer);
console.log(result.text); // Extracted message text
```
### Database Access
```javascript
const { IMessageDatabase } = require('imessage-parser');
async function readMessages() {
// Connect to the iMessage database
const db = new IMessageDatabase(); // Uses default path: ~/Library/Messages/chat.db
try {
// Get all chats
const chats = await db.getChats();
console.log(`Found ${chats.length} chats`);
// Get messages from a specific chat
const messages = await db.getMessagesFromChat(chats[0].ROWID, 50);
for (const message of messages) {
const parsed = db.parseMessage(message);
console.log(`${message.is_from_me ? 'Me' : 'Them'}: ${parsed.text}`);
}
// Search for messages containing specific text
const results = await db.searchMessages('currency');
console.log(`Found ${results.length} messages containing "currency"`);
} finally {
await db.close();
}
}
```
### Advanced Parser Options
```javascript
const { AttributedStringParser } = require('imessage-parser');
const parser = new AttributedStringParser({
preserveFormatting: true, // Preserve line breaks and formatting
includeMetadata: true, // Include attributes and raw data
cleanOutput: true, // Clean up whitespace and artifacts
encoding: 'utf8' // Text encoding
});
const result = parser.parse(attributedBodyBuffer);
console.log(result.text); // Parsed text
console.log(result.attributes); // Message attributes (if any)
console.log(result.rawData); // Original buffer
```
### Extract Messages with Formatting
```javascript
const { IMessageDatabase } = require('imessage-parser');
async function extractFormattedMessages() {
const db = new IMessageDatabase();
try {
// Get messages from the last week
const startDate = new Date();
startDate.setDate(startDate.getDate() - 7);
const endDate = new Date();
const messages = await db.getAttributedMessagesInRange(
startDate,
endDate,
'Family Chat' // Optional: filter by chat name
);
for (const message of messages) {
const parsed = db.parseMessage(message);
if (parsed.text.includes('?')) {
console.log('\nQuestion found:');
console.log(parsed.text);
console.log('---');
}
}
} finally {
await db.close();
}
}
```
## API Reference
### `parseAttributedBody(buffer, options?)`
Convenience function to parse a single attributedBody buffer.
- `buffer` - Buffer containing NSAttributedString data
- `options` - Optional parser options
- Returns: `ParsedMessage` object with extracted text
### `IMessageDatabase`
Main class for accessing the iMessage database.
#### Methods:
- `constructor(dbPath?, parserOptions?)` - Create new database instance
- `getChats()` - Get all chat conversations
- `getMessagesFromChat(chatId, limit?, offset?)` - Get messages from a specific chat
- `parseMessage(message)` - Parse a message row to extract text
- `searchMessages(searchTerm, limit?)` - Search messages by content
- `getAttributedMessagesInRange(startDate, endDate, chatName?)` - Get messages with attributedBody in date range
- `close()` - Close database connection
### `AttributedStringParser`
Core parser for NSAttributedString format.
#### Methods:
- `constructor(options?)` - Create new parser instance
- `parse(buffer)` - Parse attributedBody buffer
#### Options:
- `preserveFormatting` - Preserve line breaks and formatting (default: true)
- `includeMetadata` - Include attributes and raw data in result (default: false)
- `cleanOutput` - Clean up whitespace and artifacts (default: true)
- `encoding` - Text encoding (default: 'utf8')
## Formatting Extraction
The parser can extract various formatting attributes from messages:
### Supported Formatting
- **Text Styles**: Bold, italic, underline, strikethrough
- **Links**: URLs with their display text and position
- **Mentions**: @mentions with phone numbers or emails
- **Data Detectors**: Dates, times, addresses, phone numbers, flights, measurements
- **Fonts**: Font family, size, weight, and style
- **Colors**: Text and background colors with RGB values
- **Attachments**: Attachment indicators and metadata
### Example: Extract All Formatting
```javascript
const { IMessageDatabase } = require('imessage-parser');
const db = new IMessageDatabase(undefined, { includeMetadata: true });
const messages = await db.getMessagesFromChat(chatId);
messages.forEach(msg => {
const parsed = db.parseMessage(msg);
if (parsed.attributes?.links) {
parsed.attributes.links.forEach(link => {
console.log(`Link: ${link.url} at position ${link.location}`);
});
}
if (parsed.attributes?.bold) {
parsed.attributes.bold.forEach(range => {
const boldText = parsed.text.substring(range.location, range.location + range.length);
console.log(`Bold text: "${boldText}"`);
});
}
if (parsed.attributes?.dataDetectors) {
parsed.attributes.dataDetectors.forEach(detector => {
console.log(`Detected ${detector.type}: ${detector.value}`);
});
}
});
```
## How It Works
iMessage stores formatted messages in the `attributedBody` field using Apple's NSAttributedString format, serialized with NSArchiver (typedstream format). This library:
1. Identifies NSString objects within the binary data
2. Extracts text content following the typedstream format specification
3. Parses NSDictionary structures containing formatting attributes
4. Reconstructs the original message with proper formatting
5. Falls back to readable text extraction for unknown formats
The typedstream format for NSString typically follows this pattern:
```
| "NSString" | preamble (5 bytes) | length | content |
```
Where:
- Preamble is usually `0x01 0x94 0x84 0x01 0x2b`
- Length is either 1 byte or 3 bytes (if first byte is `0x81`)
- Content is UTF-8 encoded text
Formatting attributes are stored in NSDictionary structures with range information indicating where the formatting applies in the text.
## Requirements
- Node.js 14 or higher
- macOS (for accessing iMessage database)
- Read access to `~/Library/Messages/chat.db`
### Building iMessage Deep Links
The library can automatically generate deep links to open specific messages or conversations in the Messages app.
```javascript
const { IMessageDatabase } = require('imessage-parser');
const db = new IMessageDatabase();
// Parse message - link is always included
const messages = await db.getMessagesFromChat(chatId, 1);
const parsed = db.parseMessage(messages[0]);
console.log(parsed);
// {
// text: "Hello world",
// link: "messages://open?guid=12345-67890-ABCDEF"
// }
// Get messages with full chat context for better links
const messagesWithChat = await db.getMessagesWithChatInfo(chatId);
const parsedWithChat = db.parseMessageWithChat(messagesWithChat[0]);
console.log(parsedWithChat.link);
// Automatically selects best link type:
// - "messages://open?guid=<message-guid>" (specific message)
// - "sms://+15551234567" (conversation fallback)
// - "messages://open?guid=<chat-guid>" (group chat fallback)
```
For more control over link generation:
```javascript
const { buildChatLinks, buildMessageLink, buildComposeLink } = require('imessage-parser');
// Get all possible links for a chat
const chatLinks = buildChatLinks(chat);
// Build link for specific message
const messageLink = buildMessageLink(message);
// Build compose link with pre-filled text
const composeLink = buildComposeLink('+15551234567', 'Hello!');
```
#### URL Scheme Support
- **`sms://`** - Opens conversations (universally supported)
- **`imessage://`** - Alternative for opening conversations
- **`messages://`** - Opens specific messages (requires special entitlements in some contexts)
š **[See the Deep Linking Guide](docs/deep-linking.md)** for comprehensive documentation on URL schemes, special entitlements, platform compatibility, and troubleshooting.
## Privacy & Security
When using this library, please be mindful of privacy:
- The iMessage database contains personal conversations
- Always obtain consent before accessing or processing someone else's messages
- Be careful not to commit message data to version control
- Consider anonymizing data when sharing examples or debugging
## Limitations
- Only works on macOS with local iMessage database
- Cannot decrypt messages if database is encrypted
- Some complex formatting may not be fully preserved
- Attributes (bold, italic, links) extraction is not yet implemented
- Deep links to specific messages may not work in all contexts
## Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
## License
MIT