bookworms
Version:
A cli tool for centralising and generating bookmarks
157 lines (139 loc) • 4.41 kB
JavaScript
// TODO this is a bit messy - will rewrite when I'm not feeling sick
// This is a utility to convert existing browser bookmarks into YAML
// It can't pull descriptions as they are no required in browsers
import events from "events";
import readline from "readline";
import stringify from "json-stringify-safe";
import { createReadStream } from "fs";
import { v4 as uuidv4 } from "uuid";
import { xml2json } from "xml2json-light";
import yaml from "js-yaml";
import { writeBookmark } from "./save-bookmarks.js";
const parseHTMLtoJSON = async (path) => {
let bookmarks = {};
let currentNode = bookmarks;
let latestNode = bookmarks;
let idQueue = [];
let subId = uuidv4().replace(/-/g, "");
try {
const rl = readline.createInterface({
input: createReadStream(path),
crlfDelay: Infinity,
});
// rewrite this to make it cleaner and more testable
// This was heavily borrowed from https://github.com/CCharlieLi/bookmark-parser
rl.on("line", (line) => {
line = line
.replace(/(<DT>.+)/g, "$1</DT>")
.replace(/(<!DOCTYPE.+)/g, "")
.replace(/(<META.+)/g, "")
.replace(/<p>/g, "")
.replace(/<HR>/g, "");
let str;
if (line.match(/^<TITLE>(.+)<\/TITLE>$/)) {
str = line.match(/^<TITLE>(.+)<\/TITLE>$/);
latestNode = currentNode[str[1]] = {};
}
if (/<DL>/.test(line)) {
const prevNode = currentNode;
currentNode = latestNode;
currentNode.prevNode = prevNode;
currentNode.children = [];
idQueue.push(subId);
subId = 0;
}
if (/<\/DL>/.test(line)) {
currentNode = currentNode.prevNode;
subId = parseInt(idQueue[idQueue.length - 1], 10);
idQueue.pop();
}
if (line.match(/<DT>(<H3.+<\/H3>)<\/DT>/)) {
str = line.match(/<DT>(<H3.+<\/H3>)<\/DT>/);
const tmp = xml2json(str[1]);
currentNode.children.push({
label: tmp["H3"]["_@ttribute"],
type: "folder",
});
latestNode = currentNode.children[currentNode.children.length - 1];
}
if (line.match(/<DT>(<A.+<\/A>)<\/DT>/)) {
str = line.match(/<DT>(<A.+<\/A>)<\/DT>/);
const tmp = xml2json(str[1].replace(/&/g, "%26"));
currentNode.children.push({
label: tmp["A"]["_@ttribute"],
type: "bookmark",
href: tmp["A"]["HREF"],
});
latestNode = currentNode.children[currentNode.children.length - 1];
}
if (line.match(/<DD>(.+)/)) {
str = line.match(/<DD>(.+)/);
latestNode.description = str[1];
}
});
await events.once(rl, "close");
// this returns the entire bookmarks object
// so far only really tested chrome
return JSON.parse(stringify(bookmarks)).Bookmarks.children[0].children;
} catch (err) {
// need to write tests here
console.error(err);
}
};
const generateBookwormJSON = (json) => {
return {
label: "Bookworms",
description: "These bookmarks were generated by a browser export",
...traverseStructure(json),
};
};
// I need to clean this up but it was so gross making it work
// will come back to it
const traverseStructure = (json) => {
const arr = {};
json.forEach((entry) => {
const { type, label } = entry;
if (type === "folder") {
if (!arr.folders) {
arr.folders = [];
}
const response = traverseStructure(entry.children);
const folder = {
label,
};
if (response.folders) {
folder.folders = response.folders;
}
if (response.bookmarks) {
folder.bookmarks = response.bookmarks;
}
arr.folders.push(folder);
}
if (type === "bookmark") {
if (!arr.bookmarks) {
arr.bookmarks = [];
}
arr.bookmarks.push({
label,
href: entry.href,
});
}
});
return arr;
};
const convertHTMLtoYAML = async (path) => {
const parsedJSON = await parseHTMLtoJSON(path);
const bookwormJSON = generateBookwormJSON(parsedJSON);
return yaml.dump(bookwormJSON);
};
const createBookmarks = async (path, directory, filename) => {
console.log(`Writing ${filename} to ${directory}`);
writeBookmark(await convertHTMLtoYAML(path), directory, filename);
};
export {
parseHTMLtoJSON,
generateBookwormJSON,
traverseStructure,
convertHTMLtoYAML,
createBookmarks,
};