Skip to content

Instantly share code, notes, and snippets.

@lamchau
Last active April 17, 2025 12:48
Show Gist options
  • Save lamchau/718aa36a6e78c2a744ff017709efcf58 to your computer and use it in GitHub Desktop.
Save lamchau/718aa36a6e78c2a744ff017709efcf58 to your computer and use it in GitHub Desktop.
[Chrome/Tampermonkey] Extract *.user.js scripts from LevelDB backup

Restoring Tampermonkey scripts

Chrome stores .user.js scripts in a .ldb, which isn't in a user accessible format to recovery. On some versions of macOS the provided python script can't compile the leveldb package. As a workaround, we can use the node level package to recover our userscripts.

Prerequisite

brew install node
npm install level

Usage

node extract.js "/Users/$(whoami)/Library/Application Support/Google/Chrome Canary/backup/Default/Default/Local Extension Settings/dhdgffkkebhmkfjojejmpbldmpobfkfo"
const fs = require('fs');
const path = require('path');
const level = require('level');
const directory = path.resolve(process.argv.length > 2
? process.argv[2]
: 'dhdgffkkebhmkfjojejmpbldmpobfkfo');
const db = level(directory);
const stream = db.createReadStream();
console.log(`Loading: ${directory}`);
const date = new Date().toISOString();
const outputDirectory = path.resolve(
path.join(
__dirname,
date.slice(0, date.indexOf('T'))
)
);
if (!fs.existsSync(outputDirectory)) {
fs.mkdirSync(outputDirectory, true);
}
console.log(`Saving to: ${outputDirectory}`);
stream.on('data', data => {
const { key, value } = data;
try {
if (value.includes('UserScript')) {
const uuid = key.slice(key.indexOf('#') + 1);
const parsed = JSON.parse(value).value;
const filename = path.join(outputDirectory, `${uuid}.user.js`);
fs.writeFile(filename, parsed, () => {
console.log(`Saved: ${path.basename(filename)}`);
});
}
} catch (err) {
}
});
@XCanG
Copy link

XCanG commented Nov 23, 2024

I updated script again to extract values, originally it store it in JSON like: {"origin": "normal", "value": ...} and I also extracted metadata with a name instead of using UUID as a name.

Here is updated version:

console.log(`--> LOG - INIT --------------------`);
const fs = require("fs");
const path = require("path");

const directory = path.resolve(process.argv.length > 2
	? process.argv[2]
	: "bhmmomiinigofkjcapegjjndpbikblnp");

const { Level } = require("level");
const db = new Level(directory, { valueEncoding: "json" });
console.log(`--> LOG - Opened DB`);
db.open(main);

const { EntryStream } = require("level-read-stream");
const red = new RegExp(`^@source#(.*)$`), rem = new RegExp(`^@meta#(.*)$`);

function main(e) {
	if (e) {
		console.log(`--> LOG - ERROR: ${e}`);
		return;
	}

	console.log(`--> LOG - db status: ${db.status}`);

	console.log(`--> LOG - Loading: ${directory}`);
	const date = new Date().toISOString();
	const output_directory = path.resolve(
		path.join(
			__dirname,
			date.slice(0, date.indexOf("T"))
		)
	);

	if (!fs.existsSync(output_directory)) {
		fs.mkdirSync(output_directory, true);
	}

	const meta = {}, content = {};

	var stream = new EntryStream(db);

	stream.on("data", ({ key, value }) => {
		const mm = key.match(rem), md = key.match(red);
		if (mm) {
			meta[mm[1]] = value.value;
		} else if (md) {
			content[md[1]] = value.value;
		}
	});

	stream.on("end", () => {
		console.log(`--> LOG - Saving to: ${output_directory}`);
		// console.log(meta, content);

		for (const [uuid, value] of Object.entries(content)) {
			try {
				console.log(`--> LOG ->> key: ${uuid} - value ${value}`);

				const meta_obj = meta?.[uuid],
					name = (meta_obj?.name ?? uuid).replace(/[\\\/$|<>:\*\?]/g, "_"),
					filename = path.join(output_directory, `${name}.user.js`),
					meta_file = path.join(output_directory, `${name}.user.json`);

				fs.writeFile(filename, value, () => {
					console.log(`--> LOG - Saved: ${path.basename(filename)}`);
				});
				fs.writeFile(meta_file, JSON.stringify(meta_obj), () => {});
			} catch (err) {
				console.log(`--> LOG - ERROR: ${err}`);
			}
		};

		console.log(`--> LOG - FIN --------------------`);
	});
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment