apple-books-export 1.0.0
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 +83 -0
- package/dist/database.d.ts +37 -0
- package/dist/database.d.ts.map +1 -0
- package/dist/database.js +187 -0
- package/dist/db-adapter.d.ts +36 -0
- package/dist/db-adapter.d.ts.map +1 -0
- package/dist/db-adapter.js +72 -0
- package/dist/exporters/csv.d.ts +6 -0
- package/dist/exporters/csv.d.ts.map +1 -0
- package/dist/exporters/csv.js +70 -0
- package/dist/exporters/html.d.ts +3 -0
- package/dist/exporters/html.d.ts.map +1 -0
- package/dist/exporters/html.js +775 -0
- package/dist/exporters/json.d.ts +6 -0
- package/dist/exporters/json.d.ts.map +1 -0
- package/dist/exporters/json.js +103 -0
- package/dist/exporters/markdown.d.ts +14 -0
- package/dist/exporters/markdown.d.ts.map +1 -0
- package/dist/exporters/markdown.js +145 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +232 -0
- package/dist/types.d.ts +56 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/package.json +48 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../../src/exporters/json.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAExC;;GAEG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE,UAAU,GAAE,OAAc,GAAG,MAAM,GAAG,MAAM,EAAE,CAM7G"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { writeFileSync, mkdirSync } from "fs";
|
|
2
|
+
import { join, dirname } from "path";
|
|
3
|
+
/**
|
|
4
|
+
* Export all books to a single JSON file
|
|
5
|
+
*/
|
|
6
|
+
export function exportToJson(books, outputPath, singleFile = true) {
|
|
7
|
+
if (singleFile) {
|
|
8
|
+
return exportToSingleJson(books, outputPath);
|
|
9
|
+
}
|
|
10
|
+
else {
|
|
11
|
+
return exportToMultipleJson(books, outputPath);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Export all books to a single JSON file
|
|
16
|
+
*/
|
|
17
|
+
function exportToSingleJson(books, outputPath) {
|
|
18
|
+
// Ensure parent directory exists
|
|
19
|
+
mkdirSync(dirname(outputPath), { recursive: true });
|
|
20
|
+
const totalAnnotations = books.reduce((sum, book) => sum + book.annotations.length, 0);
|
|
21
|
+
const totalHighlights = books.reduce((sum, book) => sum + book.annotations.filter(a => a.type === 'highlight').length, 0);
|
|
22
|
+
const totalBookmarks = books.reduce((sum, book) => sum + book.annotations.filter(a => a.type === 'bookmark').length, 0);
|
|
23
|
+
const totalNotes = books.reduce((sum, book) => sum + book.annotations.filter(a => a.type === 'note').length, 0);
|
|
24
|
+
const exportData = {
|
|
25
|
+
exported: new Date().toISOString(),
|
|
26
|
+
totalBooks: books.length,
|
|
27
|
+
totalAnnotations,
|
|
28
|
+
statistics: {
|
|
29
|
+
highlights: totalHighlights,
|
|
30
|
+
bookmarks: totalBookmarks,
|
|
31
|
+
notes: totalNotes,
|
|
32
|
+
},
|
|
33
|
+
books: books.map(book => ({
|
|
34
|
+
assetId: book.assetId,
|
|
35
|
+
title: book.title,
|
|
36
|
+
author: book.author,
|
|
37
|
+
genre: book.genre,
|
|
38
|
+
annotationCount: book.annotations.length,
|
|
39
|
+
annotations: book.annotations.map(ann => ({
|
|
40
|
+
id: ann.id,
|
|
41
|
+
type: ann.type,
|
|
42
|
+
color: ann.color,
|
|
43
|
+
text: ann.text,
|
|
44
|
+
note: ann.note,
|
|
45
|
+
location: ann.location,
|
|
46
|
+
chapter: ann.chapter,
|
|
47
|
+
createdAt: ann.createdAt.toISOString(),
|
|
48
|
+
modifiedAt: ann.modifiedAt.toISOString(),
|
|
49
|
+
})),
|
|
50
|
+
})),
|
|
51
|
+
};
|
|
52
|
+
const json = JSON.stringify(exportData, null, 2);
|
|
53
|
+
writeFileSync(outputPath, json, 'utf-8');
|
|
54
|
+
return outputPath;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Sanitize filename by removing/replacing invalid characters
|
|
58
|
+
*/
|
|
59
|
+
function sanitizeFilename(filename) {
|
|
60
|
+
return filename
|
|
61
|
+
.replace(/[/\\?%*:|"<>]/g, '-')
|
|
62
|
+
.replace(/\s+/g, ' ')
|
|
63
|
+
.trim()
|
|
64
|
+
.substring(0, 200);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Export each book to separate JSON files
|
|
68
|
+
*/
|
|
69
|
+
function exportToMultipleJson(books, outputDir) {
|
|
70
|
+
// Create output directory
|
|
71
|
+
mkdirSync(outputDir, { recursive: true });
|
|
72
|
+
const filepaths = [];
|
|
73
|
+
for (const book of books) {
|
|
74
|
+
if (book.annotations.length === 0)
|
|
75
|
+
continue;
|
|
76
|
+
const bookTitle = book.title || `Unknown Book (${book.assetId.substring(0, 8)})`;
|
|
77
|
+
const filename = sanitizeFilename(`${bookTitle}.json`);
|
|
78
|
+
const filepath = join(outputDir, filename);
|
|
79
|
+
const bookData = {
|
|
80
|
+
assetId: book.assetId,
|
|
81
|
+
title: book.title,
|
|
82
|
+
author: book.author,
|
|
83
|
+
genre: book.genre,
|
|
84
|
+
exported: new Date().toISOString(),
|
|
85
|
+
annotationCount: book.annotations.length,
|
|
86
|
+
annotations: book.annotations.map(ann => ({
|
|
87
|
+
id: ann.id,
|
|
88
|
+
type: ann.type,
|
|
89
|
+
color: ann.color,
|
|
90
|
+
text: ann.text,
|
|
91
|
+
note: ann.note,
|
|
92
|
+
location: ann.location,
|
|
93
|
+
chapter: ann.chapter,
|
|
94
|
+
createdAt: ann.createdAt.toISOString(),
|
|
95
|
+
modifiedAt: ann.modifiedAt.toISOString(),
|
|
96
|
+
})),
|
|
97
|
+
};
|
|
98
|
+
const json = JSON.stringify(bookData, null, 2);
|
|
99
|
+
writeFileSync(filepath, json, 'utf-8');
|
|
100
|
+
filepaths.push(filepath);
|
|
101
|
+
}
|
|
102
|
+
return filepaths;
|
|
103
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Book } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Export a single book to markdown file
|
|
4
|
+
*/
|
|
5
|
+
export declare function exportBookToMarkdown(book: Book, outputDir: string): string;
|
|
6
|
+
/**
|
|
7
|
+
* Export all books to markdown files
|
|
8
|
+
*/
|
|
9
|
+
export declare function exportToMarkdown(books: Book[], outputPath: string): string[];
|
|
10
|
+
/**
|
|
11
|
+
* Export all books to a single markdown file
|
|
12
|
+
*/
|
|
13
|
+
export declare function exportToSingleMarkdown(books: Book[], outputPath: string): string;
|
|
14
|
+
//# sourceMappingURL=markdown.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../src/exporters/markdown.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,IAAI,EAAc,MAAM,aAAa,CAAC;AA8DpD;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CA6C1E;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,CAW5E;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAmDhF"}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { writeFileSync, mkdirSync } from "fs";
|
|
2
|
+
import { join, dirname } from "path";
|
|
3
|
+
const COLOR_EMOJI = {
|
|
4
|
+
yellow: '🟡',
|
|
5
|
+
green: '🟢',
|
|
6
|
+
blue: '🔵',
|
|
7
|
+
pink: '🔴',
|
|
8
|
+
purple: '🟣',
|
|
9
|
+
underline: '➖',
|
|
10
|
+
};
|
|
11
|
+
const TYPE_EMOJI = {
|
|
12
|
+
highlight: '✨',
|
|
13
|
+
bookmark: '📌',
|
|
14
|
+
note: '📝',
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Sanitize filename by removing/replacing invalid characters
|
|
18
|
+
*/
|
|
19
|
+
function sanitizeFilename(filename) {
|
|
20
|
+
return filename
|
|
21
|
+
.replace(/[/\\?%*:|"<>]/g, '-')
|
|
22
|
+
.replace(/\s+/g, ' ')
|
|
23
|
+
.trim()
|
|
24
|
+
.substring(0, 200); // Limit length
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Format a single annotation as markdown
|
|
28
|
+
*/
|
|
29
|
+
function formatAnnotation(annotation, index) {
|
|
30
|
+
const colorEmoji = COLOR_EMOJI[annotation.color] || '⚪';
|
|
31
|
+
const typeEmoji = TYPE_EMOJI[annotation.type] || '📄';
|
|
32
|
+
let markdown = `\n## ${colorEmoji} ${typeEmoji} ${annotation.type.charAt(0).toUpperCase() + annotation.type.slice(1)}`;
|
|
33
|
+
if (annotation.location) {
|
|
34
|
+
markdown += ` - ${annotation.location}`;
|
|
35
|
+
}
|
|
36
|
+
markdown += `\n**Created:** ${annotation.createdAt.toLocaleDateString('en-US', {
|
|
37
|
+
year: 'numeric',
|
|
38
|
+
month: 'long',
|
|
39
|
+
day: 'numeric',
|
|
40
|
+
hour: '2-digit',
|
|
41
|
+
minute: '2-digit'
|
|
42
|
+
})}\n`;
|
|
43
|
+
if (annotation.text) {
|
|
44
|
+
markdown += `\n> ${annotation.text.split('\n').join('\n> ')}\n`;
|
|
45
|
+
}
|
|
46
|
+
if (annotation.note) {
|
|
47
|
+
markdown += `\n**Note:** *${annotation.note}*\n`;
|
|
48
|
+
}
|
|
49
|
+
markdown += '\n---\n';
|
|
50
|
+
return markdown;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Export a single book to markdown file
|
|
54
|
+
*/
|
|
55
|
+
export function exportBookToMarkdown(book, outputDir) {
|
|
56
|
+
// Create output directory if it doesn't exist
|
|
57
|
+
mkdirSync(outputDir, { recursive: true });
|
|
58
|
+
// Generate filename
|
|
59
|
+
const bookTitle = book.title || `Unknown Book (${book.assetId.substring(0, 8)})`;
|
|
60
|
+
const filename = sanitizeFilename(`${bookTitle}.md`);
|
|
61
|
+
const filepath = join(outputDir, filename);
|
|
62
|
+
// Count annotation types
|
|
63
|
+
const highlights = book.annotations.filter(a => a.type === 'highlight').length;
|
|
64
|
+
const bookmarks = book.annotations.filter(a => a.type === 'bookmark').length;
|
|
65
|
+
const notes = book.annotations.filter(a => a.type === 'note').length;
|
|
66
|
+
// Build YAML frontmatter
|
|
67
|
+
let content = '---\n';
|
|
68
|
+
content += `title: "${book.title || 'Unknown'}"\n`;
|
|
69
|
+
content += `author: "${book.author || 'Unknown'}"\n`;
|
|
70
|
+
if (book.genre) {
|
|
71
|
+
content += `genre: "${book.genre}"\n`;
|
|
72
|
+
}
|
|
73
|
+
content += `assetId: "${book.assetId}"\n`;
|
|
74
|
+
content += `exported: "${new Date().toISOString()}"\n`;
|
|
75
|
+
content += `totalAnnotations: ${book.annotations.length}\n`;
|
|
76
|
+
content += `highlights: ${highlights}\n`;
|
|
77
|
+
content += `bookmarks: ${bookmarks}\n`;
|
|
78
|
+
content += `notes: ${notes}\n`;
|
|
79
|
+
content += '---\n\n';
|
|
80
|
+
// Book header
|
|
81
|
+
content += `# ${book.title || 'Unknown Book'}\n`;
|
|
82
|
+
if (book.author) {
|
|
83
|
+
content += `**by ${book.author}**\n`;
|
|
84
|
+
}
|
|
85
|
+
content += `\n*${book.annotations.length} annotations*\n`;
|
|
86
|
+
// Add annotations
|
|
87
|
+
book.annotations.forEach((annotation, index) => {
|
|
88
|
+
content += formatAnnotation(annotation, index);
|
|
89
|
+
});
|
|
90
|
+
// Write file
|
|
91
|
+
writeFileSync(filepath, content, 'utf-8');
|
|
92
|
+
return filepath;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Export all books to markdown files
|
|
96
|
+
*/
|
|
97
|
+
export function exportToMarkdown(books, outputPath) {
|
|
98
|
+
const filepaths = [];
|
|
99
|
+
for (const book of books) {
|
|
100
|
+
if (book.annotations.length === 0)
|
|
101
|
+
continue;
|
|
102
|
+
const filepath = exportBookToMarkdown(book, outputPath);
|
|
103
|
+
filepaths.push(filepath);
|
|
104
|
+
}
|
|
105
|
+
return filepaths;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Export all books to a single markdown file
|
|
109
|
+
*/
|
|
110
|
+
export function exportToSingleMarkdown(books, outputPath) {
|
|
111
|
+
// Ensure parent directory exists
|
|
112
|
+
mkdirSync(dirname(outputPath), { recursive: true });
|
|
113
|
+
const totalAnnotations = books.reduce((sum, book) => sum + book.annotations.length, 0);
|
|
114
|
+
const totalHighlights = books.reduce((sum, book) => sum + book.annotations.filter(a => a.type === 'highlight').length, 0);
|
|
115
|
+
const totalBookmarks = books.reduce((sum, book) => sum + book.annotations.filter(a => a.type === 'bookmark').length, 0);
|
|
116
|
+
const totalNotes = books.reduce((sum, book) => sum + book.annotations.filter(a => a.type === 'note').length, 0);
|
|
117
|
+
let content = '---\n';
|
|
118
|
+
content += `title: "Apple Books Export"\n`;
|
|
119
|
+
content += `exported: "${new Date().toISOString()}"\n`;
|
|
120
|
+
content += `totalBooks: ${books.length}\n`;
|
|
121
|
+
content += `totalAnnotations: ${totalAnnotations}\n`;
|
|
122
|
+
content += `highlights: ${totalHighlights}\n`;
|
|
123
|
+
content += `bookmarks: ${totalBookmarks}\n`;
|
|
124
|
+
content += `notes: ${totalNotes}\n`;
|
|
125
|
+
content += '---\n\n';
|
|
126
|
+
content += `# Apple Books Export\n\n`;
|
|
127
|
+
content += `Exported ${totalAnnotations} annotations from ${books.length} books\n\n`;
|
|
128
|
+
content += '---\n\n';
|
|
129
|
+
// Add each book
|
|
130
|
+
for (const book of books) {
|
|
131
|
+
if (book.annotations.length === 0)
|
|
132
|
+
continue;
|
|
133
|
+
content += `# ${book.title || 'Unknown Book'}\n`;
|
|
134
|
+
if (book.author) {
|
|
135
|
+
content += `**by ${book.author}**\n`;
|
|
136
|
+
}
|
|
137
|
+
content += `\n*${book.annotations.length} annotations*\n`;
|
|
138
|
+
book.annotations.forEach((annotation, index) => {
|
|
139
|
+
content += formatAnnotation(annotation, index);
|
|
140
|
+
});
|
|
141
|
+
content += '\n\n';
|
|
142
|
+
}
|
|
143
|
+
writeFileSync(outputPath, content, 'utf-8');
|
|
144
|
+
return outputPath;
|
|
145
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { findDatabases, queryAnnotations, groupByBook, filterAnnotations } from "./database.js";
|
|
3
|
+
import { exportToMarkdown, exportToSingleMarkdown } from "./exporters/markdown.js";
|
|
4
|
+
import { exportToJson } from "./exporters/json.js";
|
|
5
|
+
import { exportToCsv } from "./exporters/csv.js";
|
|
6
|
+
import { exportToHtml } from "./exporters/html.js";
|
|
7
|
+
function printHelp() {
|
|
8
|
+
console.log(`
|
|
9
|
+
Apple Books Export Tool
|
|
10
|
+
Export highlights, bookmarks, and notes from Apple Books
|
|
11
|
+
|
|
12
|
+
USAGE:
|
|
13
|
+
npx apple-books-export [FORMAT] [OPTIONS]
|
|
14
|
+
bunx apple-books-export [FORMAT] [OPTIONS] # Faster with Bun
|
|
15
|
+
|
|
16
|
+
FORMATS:
|
|
17
|
+
html Interactive HTML (default)
|
|
18
|
+
markdown Markdown files
|
|
19
|
+
json JSON export
|
|
20
|
+
csv CSV export
|
|
21
|
+
|
|
22
|
+
OPTIONS:
|
|
23
|
+
--output <path> Output file path
|
|
24
|
+
--single-file Export all books to a single file (markdown/json only)
|
|
25
|
+
--highlights-only Export only highlights
|
|
26
|
+
--bookmarks-only Export only bookmarks
|
|
27
|
+
--notes-only Export only notes
|
|
28
|
+
--include-bookmarks Include bookmarks (excluded by default)
|
|
29
|
+
--colors <colors> Filter by colors: yellow,green,blue,pink,purple,underline
|
|
30
|
+
--annotations-db <path> Custom annotations database path
|
|
31
|
+
--library-db <path> Custom library database path
|
|
32
|
+
--help Show this help message
|
|
33
|
+
|
|
34
|
+
EXAMPLES:
|
|
35
|
+
# Export to HTML (default)
|
|
36
|
+
npx apple-books-export
|
|
37
|
+
npx apple-books-export html
|
|
38
|
+
|
|
39
|
+
# Export to Markdown (one file per book)
|
|
40
|
+
npx apple-books-export markdown --output ./exports
|
|
41
|
+
|
|
42
|
+
# Export to single JSON file
|
|
43
|
+
npx apple-books-export json --output ./all.json --single-file
|
|
44
|
+
|
|
45
|
+
# Export only yellow and green highlights
|
|
46
|
+
npx apple-books-export html --highlights-only --colors yellow,green
|
|
47
|
+
|
|
48
|
+
# Include bookmarks in export
|
|
49
|
+
npx apple-books-export html --include-bookmarks
|
|
50
|
+
`);
|
|
51
|
+
}
|
|
52
|
+
function parseArgs() {
|
|
53
|
+
const args = process.argv.slice(2);
|
|
54
|
+
const options = {
|
|
55
|
+
format: 'html',
|
|
56
|
+
output: '',
|
|
57
|
+
singleFile: false,
|
|
58
|
+
highlightsOnly: false,
|
|
59
|
+
bookmarksOnly: false,
|
|
60
|
+
notesOnly: false,
|
|
61
|
+
includeBookmarks: false,
|
|
62
|
+
help: false,
|
|
63
|
+
};
|
|
64
|
+
// Check for positional format argument (first arg without --)
|
|
65
|
+
if (args.length > 0 && !args[0].startsWith('--')) {
|
|
66
|
+
const formatArg = args[0].toLowerCase();
|
|
67
|
+
if (['html', 'markdown', 'json', 'csv'].includes(formatArg)) {
|
|
68
|
+
options.format = formatArg;
|
|
69
|
+
args.shift(); // Remove the format argument
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
for (let i = 0; i < args.length; i++) {
|
|
73
|
+
const arg = args[i];
|
|
74
|
+
switch (arg) {
|
|
75
|
+
case '--help':
|
|
76
|
+
case '-h':
|
|
77
|
+
options.help = true;
|
|
78
|
+
break;
|
|
79
|
+
case '--format':
|
|
80
|
+
case '-f':
|
|
81
|
+
options.format = args[++i];
|
|
82
|
+
break;
|
|
83
|
+
case '--output':
|
|
84
|
+
case '-o':
|
|
85
|
+
options.output = args[++i];
|
|
86
|
+
break;
|
|
87
|
+
case '--single-file':
|
|
88
|
+
options.singleFile = true;
|
|
89
|
+
break;
|
|
90
|
+
case '--highlights-only':
|
|
91
|
+
options.highlightsOnly = true;
|
|
92
|
+
break;
|
|
93
|
+
case '--bookmarks-only':
|
|
94
|
+
options.bookmarksOnly = true;
|
|
95
|
+
break;
|
|
96
|
+
case '--notes-only':
|
|
97
|
+
options.notesOnly = true;
|
|
98
|
+
break;
|
|
99
|
+
case '--include-bookmarks':
|
|
100
|
+
options.includeBookmarks = true;
|
|
101
|
+
break;
|
|
102
|
+
case '--colors':
|
|
103
|
+
options.colors = args[++i].split(',');
|
|
104
|
+
break;
|
|
105
|
+
case '--annotations-db':
|
|
106
|
+
options.annotationsDb = args[++i];
|
|
107
|
+
break;
|
|
108
|
+
case '--library-db':
|
|
109
|
+
options.libraryDb = args[++i];
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// Set default output paths based on format if not specified
|
|
114
|
+
if (!options.output) {
|
|
115
|
+
switch (options.format) {
|
|
116
|
+
case 'html':
|
|
117
|
+
options.output = './apple-books-export.html';
|
|
118
|
+
break;
|
|
119
|
+
case 'json':
|
|
120
|
+
options.output = './apple-books-export.json';
|
|
121
|
+
break;
|
|
122
|
+
case 'csv':
|
|
123
|
+
options.output = './apple-books-export.csv';
|
|
124
|
+
break;
|
|
125
|
+
case 'markdown':
|
|
126
|
+
options.output = './exports';
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return options;
|
|
131
|
+
}
|
|
132
|
+
async function main() {
|
|
133
|
+
const options = parseArgs();
|
|
134
|
+
if (options.help) {
|
|
135
|
+
printHelp();
|
|
136
|
+
process.exit(0);
|
|
137
|
+
}
|
|
138
|
+
console.log('🍎 Apple Books Export Tool\n');
|
|
139
|
+
try {
|
|
140
|
+
// Find databases
|
|
141
|
+
console.log('📚 Finding Apple Books databases...');
|
|
142
|
+
const { annotationsDb, libraryDb } = findDatabases({
|
|
143
|
+
annotations: options.annotationsDb,
|
|
144
|
+
library: options.libraryDb,
|
|
145
|
+
});
|
|
146
|
+
console.log(` ✓ Annotations: ${annotationsDb}`);
|
|
147
|
+
console.log(` ✓ Library: ${libraryDb}\n`);
|
|
148
|
+
// Query annotations
|
|
149
|
+
console.log('🔍 Reading annotations...');
|
|
150
|
+
const rawAnnotations = await queryAnnotations(annotationsDb, libraryDb);
|
|
151
|
+
console.log(` ✓ Found ${rawAnnotations.length} annotations\n`);
|
|
152
|
+
// Group by book
|
|
153
|
+
console.log('📖 Grouping by book...');
|
|
154
|
+
const allBooks = groupByBook(rawAnnotations);
|
|
155
|
+
console.log(` ✓ Found ${allBooks.length} books\n`);
|
|
156
|
+
// Filter annotations based on options
|
|
157
|
+
const includeHighlights = !options.bookmarksOnly && !options.notesOnly;
|
|
158
|
+
const includeBookmarks = options.includeBookmarks || options.bookmarksOnly;
|
|
159
|
+
const includeNotes = !options.highlightsOnly && !options.bookmarksOnly;
|
|
160
|
+
const filteredBooks = allBooks.map(book => ({
|
|
161
|
+
...book,
|
|
162
|
+
annotations: filterAnnotations(book.annotations, {
|
|
163
|
+
includeHighlights,
|
|
164
|
+
includeBookmarks,
|
|
165
|
+
includeNotes,
|
|
166
|
+
colorFilters: options.colors,
|
|
167
|
+
}),
|
|
168
|
+
})).filter(book => book.annotations.length > 0);
|
|
169
|
+
if (filteredBooks.length === 0) {
|
|
170
|
+
console.log('⚠️ No annotations match the specified filters');
|
|
171
|
+
process.exit(0);
|
|
172
|
+
}
|
|
173
|
+
const totalAnnotations = filteredBooks.reduce((sum, book) => sum + book.annotations.length, 0);
|
|
174
|
+
console.log(`📊 Export statistics:`);
|
|
175
|
+
console.log(` • Books: ${filteredBooks.length}`);
|
|
176
|
+
console.log(` • Annotations: ${totalAnnotations}`);
|
|
177
|
+
const highlights = filteredBooks.reduce((sum, book) => sum + book.annotations.filter(a => a.type === 'highlight').length, 0);
|
|
178
|
+
const bookmarks = filteredBooks.reduce((sum, book) => sum + book.annotations.filter(a => a.type === 'bookmark').length, 0);
|
|
179
|
+
const notes = filteredBooks.reduce((sum, book) => sum + book.annotations.filter(a => a.type === 'note').length, 0);
|
|
180
|
+
if (highlights > 0)
|
|
181
|
+
console.log(` - Highlights: ${highlights}`);
|
|
182
|
+
if (bookmarks > 0)
|
|
183
|
+
console.log(` - Bookmarks: ${bookmarks}`);
|
|
184
|
+
if (notes > 0)
|
|
185
|
+
console.log(` - Notes: ${notes}`);
|
|
186
|
+
const orphaned = allBooks.filter(book => !book.title).length;
|
|
187
|
+
if (orphaned > 0) {
|
|
188
|
+
console.log(` - Orphaned annotations: ${orphaned} books`);
|
|
189
|
+
}
|
|
190
|
+
console.log();
|
|
191
|
+
// Export
|
|
192
|
+
console.log(`💾 Exporting to ${options.format}...`);
|
|
193
|
+
let result;
|
|
194
|
+
switch (options.format) {
|
|
195
|
+
case 'html':
|
|
196
|
+
result = exportToHtml(filteredBooks, options.output);
|
|
197
|
+
console.log(` ✓ Exported to: ${result}`);
|
|
198
|
+
break;
|
|
199
|
+
case 'markdown':
|
|
200
|
+
if (options.singleFile) {
|
|
201
|
+
result = exportToSingleMarkdown(filteredBooks, options.output);
|
|
202
|
+
console.log(` ✓ Exported to: ${result}`);
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
result = exportToMarkdown(filteredBooks, options.output);
|
|
206
|
+
console.log(` ✓ Exported ${result.length} files to: ${options.output}`);
|
|
207
|
+
}
|
|
208
|
+
break;
|
|
209
|
+
case 'json':
|
|
210
|
+
result = exportToJson(filteredBooks, options.output, options.singleFile);
|
|
211
|
+
if (Array.isArray(result)) {
|
|
212
|
+
console.log(` ✓ Exported ${result.length} files to: ${options.output}`);
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
console.log(` ✓ Exported to: ${result}`);
|
|
216
|
+
}
|
|
217
|
+
break;
|
|
218
|
+
case 'csv':
|
|
219
|
+
result = exportToCsv(filteredBooks, options.output);
|
|
220
|
+
console.log(` ✓ Exported to: ${result}`);
|
|
221
|
+
break;
|
|
222
|
+
default:
|
|
223
|
+
throw new Error(`Unknown format: ${options.format}`);
|
|
224
|
+
}
|
|
225
|
+
console.log('\n✅ Export complete!\n');
|
|
226
|
+
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
console.error('\n❌ Error:', error instanceof Error ? error.message : error);
|
|
229
|
+
process.exit(1);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
main();
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export type AnnotationType = 'highlight' | 'bookmark' | 'note';
|
|
2
|
+
export type AnnotationColor = 'yellow' | 'green' | 'blue' | 'pink' | 'purple' | 'underline';
|
|
3
|
+
export interface Annotation {
|
|
4
|
+
id: number;
|
|
5
|
+
type: AnnotationType;
|
|
6
|
+
color: AnnotationColor;
|
|
7
|
+
text: string | null;
|
|
8
|
+
note: string | null;
|
|
9
|
+
location: string | null;
|
|
10
|
+
chapter: string | null;
|
|
11
|
+
createdAt: Date;
|
|
12
|
+
modifiedAt: Date;
|
|
13
|
+
}
|
|
14
|
+
export interface Book {
|
|
15
|
+
assetId: string;
|
|
16
|
+
title: string | null;
|
|
17
|
+
author: string | null;
|
|
18
|
+
genre: string | null;
|
|
19
|
+
annotations: Annotation[];
|
|
20
|
+
}
|
|
21
|
+
export type ExportFormat = 'html' | 'markdown' | 'json' | 'csv';
|
|
22
|
+
export interface ExportOptions {
|
|
23
|
+
format: ExportFormat;
|
|
24
|
+
outputPath: string;
|
|
25
|
+
groupByBook: boolean;
|
|
26
|
+
includeBookmarks: boolean;
|
|
27
|
+
includeNotes: boolean;
|
|
28
|
+
includeHighlights: boolean;
|
|
29
|
+
colorFilters?: AnnotationColor[];
|
|
30
|
+
singleFile?: boolean;
|
|
31
|
+
annotationsDbPath?: string;
|
|
32
|
+
libraryDbPath?: string;
|
|
33
|
+
}
|
|
34
|
+
export interface RawAnnotation {
|
|
35
|
+
id: number;
|
|
36
|
+
assetId: string;
|
|
37
|
+
text: string | null;
|
|
38
|
+
note: string | null;
|
|
39
|
+
style: number;
|
|
40
|
+
location: string | null;
|
|
41
|
+
createdAt: number;
|
|
42
|
+
modifiedAt: number;
|
|
43
|
+
deleted: number;
|
|
44
|
+
title: string | null;
|
|
45
|
+
author: string | null;
|
|
46
|
+
genre: string | null;
|
|
47
|
+
}
|
|
48
|
+
export interface ExportStats {
|
|
49
|
+
totalBooks: number;
|
|
50
|
+
totalAnnotations: number;
|
|
51
|
+
highlights: number;
|
|
52
|
+
bookmarks: number;
|
|
53
|
+
notes: number;
|
|
54
|
+
orphaned: number;
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,cAAc,GAAG,WAAW,GAAG,UAAU,GAAG,MAAM,CAAC;AAC/D,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,QAAQ,GAAG,WAAW,CAAC;AAE5F,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,cAAc,CAAC;IACrB,KAAK,EAAE,eAAe,CAAC;IACvB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,SAAS,EAAE,IAAI,CAAC;IAChB,UAAU,EAAE,IAAI,CAAC;CAClB;AAED,MAAM,WAAW,IAAI;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,WAAW,EAAE,UAAU,EAAE,CAAC;CAC3B;AAED,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,UAAU,GAAG,MAAM,GAAG,KAAK,CAAC;AAEhE,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,YAAY,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,OAAO,CAAC;IACrB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,YAAY,EAAE,OAAO,CAAC;IACtB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,YAAY,CAAC,EAAE,eAAe,EAAE,CAAC;IACjC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "apple-books-export",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Export highlights, bookmarks, and notes from Apple Books",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"apple-books-export": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"README.md",
|
|
12
|
+
"LICENSE"
|
|
13
|
+
],
|
|
14
|
+
"main": "./dist/index.js",
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"scripts": {
|
|
17
|
+
"export": "bun run src/index.ts",
|
|
18
|
+
"dev": "bun run src/index.ts",
|
|
19
|
+
"build": "tsc",
|
|
20
|
+
"prepublishOnly": "npm run build",
|
|
21
|
+
"test:local": "npm run build && node ./dist/index.js --help",
|
|
22
|
+
"test:bun": "npm run build && bun ./dist/index.js --help"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"apple-books",
|
|
26
|
+
"export",
|
|
27
|
+
"highlights",
|
|
28
|
+
"notes",
|
|
29
|
+
"bookmarks",
|
|
30
|
+
"cli"
|
|
31
|
+
],
|
|
32
|
+
"author": "",
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"better-sqlite3": "^11.10.0"
|
|
36
|
+
},
|
|
37
|
+
"engines": {
|
|
38
|
+
"node": ">=18.0.0"
|
|
39
|
+
},
|
|
40
|
+
"os": [
|
|
41
|
+
"darwin"
|
|
42
|
+
],
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
45
|
+
"@types/bun": "latest",
|
|
46
|
+
"typescript": "^5.9.3"
|
|
47
|
+
}
|
|
48
|
+
}
|