├── package.json ├── README.md └── index.mjs /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hypothesis-to-bullet", 3 | "type": "module", 4 | "version": "1.0.0", 5 | "main": "index.js", 6 | "license": "MIT", 7 | "dependencies": { 8 | "isomorphic-fetch": "^2.2.1", 9 | "lodash": "^4.17.15", 10 | "query-string": "^6.9.0" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kindle-clippings-to-bullet 2 | 3 | Script to convert "My Clippings.txt" file from Kindle to Markdown that is suitable for Roam 4 | 5 | ## Installation Instructions 6 | 7 | 1. Ensure that you are running Node v.13.5.0 or greater. You can check from the command line by typing in `node -v`. 8 | 1. Clone or download the repository. 9 | 1. From the command line, enter the directory. 10 | 1. Run `node index.mjs "" ""` 11 | 12 | This assumes that only a single book matches your query, and will generate a Markdown output with that book title, the dates you read the book (assuming you read it linearly), and one line each for highlights and annotations (annotations are highlighted in yellow). 13 | 14 | To get a list of all book titles, run `node index.mjs "" listTitles`. 15 | -------------------------------------------------------------------------------- /index.mjs: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import os from "os"; 3 | import lodash from "lodash"; 4 | 5 | const months = [ 6 | "January", 7 | "February", 8 | "March", 9 | "April", 10 | "May", 11 | "June", 12 | "July", 13 | "August", 14 | "September", 15 | "October", 16 | "November", 17 | "December" 18 | ]; 19 | 20 | const nth = function(d) { 21 | if (d > 3 && d < 21) return "th"; 22 | switch (d % 10) { 23 | case 1: 24 | return "st"; 25 | case 2: 26 | return "nd"; 27 | case 3: 28 | return "rd"; 29 | default: 30 | return "th"; 31 | } 32 | }; 33 | 34 | const getRoamDate = dateString => { 35 | const d = new Date(dateString); 36 | const year = d.getFullYear(); 37 | const date = d.getDate(); 38 | const month = months[d.getMonth()]; 39 | const nthStr = nth(date); 40 | return `${month} ${date}${nthStr}, ${year}`; 41 | }; 42 | 43 | const clippingsFile = process.argv[2]; 44 | const titleSearch = process.argv[3]; 45 | const clippingsRaw = fs.readFileSync(clippingsFile, "utf-8"); 46 | 47 | const clippings = clippingsRaw.split("==========").map(x => x.trim()); 48 | let clipStr = ""; 49 | let retainTitle = ""; 50 | let firstDate = undefined; 51 | let lastDate = undefined; 52 | const getDate = meta => { 53 | const dateMatch = meta.match(/Added on (.+)\r/); 54 | if (dateMatch.length > 1) { 55 | return dateMatch[1]; 56 | } 57 | }; 58 | 59 | const titles = []; 60 | clippings.forEach(clip => { 61 | const [title, meta, ...rest] = clip.split("\n"); 62 | titles.push(title); 63 | if (title.match(titleSearch)) { 64 | retainTitle = title; 65 | if (!firstDate) { 66 | firstDate = getDate(meta); 67 | } 68 | lastDate = getDate(meta); 69 | 70 | let pageStr = ""; 71 | if (meta.includes("page")) { 72 | const m = meta.match(/page (\d+) /); 73 | if (m.length > 1) { 74 | pageStr = ` (p. ${m[1]})`; 75 | } 76 | } 77 | 78 | const noteStr = meta.includes("Note") ? "^^" : ""; 79 | clipStr += ` - ${noteStr}${rest.join("").trim()}${noteStr} ${pageStr}\n`; 80 | } 81 | }); 82 | 83 | if (titleSearch === "listTitles") { 84 | console.log([...new Set(titles)].map((x => `- ${x}`)).join("\n")); 85 | process.exit(0); 86 | } 87 | 88 | let dateStr; 89 | if (firstDate && lastDate && firstDate !== lastDate) { 90 | dateStr = ` - Read from [[${getRoamDate(firstDate)}]] to [[${getRoamDate( 91 | lastDate 92 | )}]]\n`; 93 | } else if (firstDate) { 94 | dateStr = ` - Read on [[${getRoamDate(firstDate)}]]\n`; 95 | } 96 | 97 | console.log(`- ${retainTitle}\n${dateStr} - Clippings:\n${clipStr}`); 98 | --------------------------------------------------------------------------------