Skip to content

Instantly share code, notes, and snippets.

@peterjmag
Forked from mems/icloudtabs2md.js
Created July 15, 2019 11:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save peterjmag/31648b818a0f9e8bcc59e5157ee33214 to your computer and use it in GitHub Desktop.
Save peterjmag/31648b818a0f9e8bcc59e5157ee33214 to your computer and use it in GitHub Desktop.
Generate a markdown links list from iCloud tabs
#!/usr/bin/env node
/*
Generate a markdown links list from iCloud tabs, on macOS
Usage:
./icloudtabs2md.js > icloudtabs.md
./icloudtabs2md.js | pbcopy
Require:
- nodejs v8+
- nodejs sqlite3 package, installed globally or locally (./node_modules)
- Safari macOS with iCloud tabs synchronized
From https://gist.github.com/mems/2c96233708c6b5b44ed1a26cb0ec5a0e
*/
const os = require('os');
const util = require('util');
const inflate = util.promisify(require('zlib').inflate);
const sqlite = require('sqlite3');
const dbFile = os.homedir() + "/Library/Safari/CloudTabs.db";
const db = new sqlite.Database(dbFile);
function tabSortFunc(tabA, tabB){
return tabA.position.sortValues[0].sortValue - tabB.position.sortValues[0].sortValue;
}
(async () => {
const [devices, tabs] = await Promise.all([
util.promisify(db.all.bind(db))("SELECT device_uuid AS uuid, device_name AS name FROM cloud_tab_devices;"),
util.promisify(db.all.bind(db))("SELECT title, url, position, device_uuid AS device FROM cloud_tabs;"),
]);
if(tabs.length === 0){
console.info("No tabs available");
return;
}
const positionsResolutions = [];
for(const tab of tabs){
tab.device = devices.find(device => device.uuid === tab.device);
const bytes = tab.position;
tab.position = null;
const promise = inflate(bytes).then(bytes => tab.position = JSON.parse(bytes.toString("utf8")));
positionsResolutions.push(promise);
}
await Promise.all(positionsResolutions);
let firstDevice = true;
for(const device of devices){
const deviceTabs = tabs.filter(tab => tab.device === device).sort(tabSortFunc);
if(deviceTabs.length === 0){
continue;
}
console.log(`${firstDevice ? "" : "\n"}${device.name}:\n`);
for(const tab of deviceTabs){
const url = tab.url.replace(/\(/g,"%2528").replace(/\)/g,"%2529");// escape "(" an ")", just in case
const escapedTitle = tab.title.replace(/([\\<>\[\]])/g,"\\$1");// escape some chars for the link title
console.log(`- [${escapedTitle}](${url})`);
}
firstDevice = false;
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment