-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
158 lines (112 loc) · 4.19 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
import sqlite3 from 'sqlite3'
import { open } from 'sqlite'
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
import convert from 'xml-js';
import moment from 'moment';
import { fileURLToPath } from 'url';
import { dirname } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// if necessary, change this to the path to your Kobo device. Do not use a trailing slash.
const koboPath = '/Volumes/KOBOeReader';
// First, grab highlights from the sqlite DB
// This format is more annoying re: position in book, but more universal
const db = await open({
filename: path.join(koboPath, '.kobo','KoboReader.sqlite'),
driver: sqlite3.Database
})
const bookmarks = await db.all(`SELECT b.Text as highlight, b.startContainerPath, b.DateCreated as date, b.Annotation as note, book.Title as title, book.Attribution as author
FROM Bookmark b
INNER JOIN content book ON book.ContentID = b.VolumeID
WHERE highlight IS NOT NULL
ORDER BY title, date
`);
function formatCsv(string){
if(typeof string === 'string'){
string = `"${string.replace(/"/g,'""')}"`;
}
return string;
}
function makeId(title, author){
return `${title} ${author}`;
}
function formatDate(string){
return moment(string).format('YYYY-MM-DD hh:MM:ss')
}
const dbEntries = [];
bookmarks.forEach(bookmark => {
const row = [];
row.push(formatCsv(bookmark.highlight));
row.push(formatCsv(bookmark.title));
row.push(formatCsv(bookmark.author));
row.push(formatCsv(bookmark.note));
row.push(formatCsv(formatDate(bookmark.date)));
row.push('');
dbEntries.push({
bookId: makeId(bookmark.title, bookmark.author),
row: row.join()
})
// output.push(row.join());
});
// now look for Digital Editions annotations
// not all books store annotations in this format,
// but those that do will be imported in the correct order
const getAllFiles = function(dirPath, arrayOfFiles) {
const files = fs.readdirSync(dirPath)
arrayOfFiles = arrayOfFiles || []
files.forEach(function(file) {
if (fs.statSync(dirPath + "/" + file).isDirectory()) {
arrayOfFiles = getAllFiles(path.join(dirPath, file), arrayOfFiles)
} else {
arrayOfFiles.push(path.join(dirPath, file))
}
})
return arrayOfFiles
}
const annotationFiles = getAllFiles(path.join(koboPath, 'Digital Editions')).filter(file => path.extname(file) === '.annot');
const annotationFileBookIds = [];
const annotationFileEntries = [];
annotationFiles.forEach(annotationFile => {
var xml = fs.readFileSync(path.resolve(annotationFile), 'utf8');
var document = convert.xml2js(xml, {compact: true});
const info = document.annotationSet.publication;
const title = info['dc:title']._text;
const author = info['dc:creator']._text;
annotationFileBookIds.push(makeId(title, author));
let annotations = document.annotationSet.annotation || [];
if(!Array.isArray(annotations)){
annotations = [annotations];
}
annotations.forEach(annotation => {
const target = annotation.target.fragment;
const content = annotation.content;
const date = formatDate(annotation['dc:date']._text);
const location = Math.round(parseFloat(target._attributes.progress) * 1000000000);
const text = target.text ? target.text._text : "";
const comment = content ? content.text._text: "";
if(text){
const row = [];
row.push(formatCsv(text));
row.push(formatCsv(title));
row.push(formatCsv(author));
row.push(formatCsv(comment));
row.push(formatCsv(date));
row.push(formatCsv(location));
annotationFileEntries.push(row);
}
})
})
const filename = path.join(__dirname, 'output.csv');
const output = [];
output.push(['Highlight','Title','Author','Note','Date', 'Location'].join())
dbEntries.forEach(entry => {
if(!annotationFileBookIds.includes(entry.bookId)){
output.push(entry.row);
}
})
annotationFileEntries.forEach(entry => {
output.push(entry.join());
})
fs.writeFileSync(filename, output.join(os.EOL));