From c7956ae9b228054d57897ea338ad4154cc0b7221 Mon Sep 17 00:00:00 2001 From: Caine Date: Sun, 15 Feb 2026 09:41:49 +0000 Subject: Initial commit: susan automation scripts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Overnight transcoding, music discovery/import, system health reports, stats page generator, and bookmark management. Secrets stored in /etc/automation/ — not in repo. --- add_bookmark.js | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 add_bookmark.js (limited to 'add_bookmark.js') diff --git a/add_bookmark.js b/add_bookmark.js new file mode 100644 index 0000000..9a68789 --- /dev/null +++ b/add_bookmark.js @@ -0,0 +1,76 @@ +#!/usr/bin/env node +// Add a bookmark to Floccus encrypted bookmarks +// Usage: node add_bookmark.js + +const crypto = require('crypto'); +const fs = require('fs'); + +const BOOKMARKS_PATH = '/var/www/webdav/bookmarks.xbel'; +const PASSWORD = process.env.BOOKMARKS_PASSWORD || JSON.parse(require('fs').readFileSync('/etc/automation/bookmarks.json', 'utf8')).password; + +const url = process.argv[2]; +const title = process.argv[3]; + +if (!url || !title) { + console.log('Usage: node add_bookmark.js <url> <title>'); + process.exit(1); +} + +function decrypt(data, password) { + const ciphertext = Buffer.from(data.ciphertext, 'base64'); + const salt = data.salt; + const key = crypto.pbkdf2Sync(password, salt, 250000, 32, 'sha256'); + const iv = ciphertext.slice(0, 16); + const encrypted = ciphertext.slice(16, -16); + const tag = ciphertext.slice(-16); + + const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv); + decipher.setAuthTag(tag); + return Buffer.concat([decipher.update(encrypted), decipher.final()]).toString('utf8'); +} + +function encrypt(plaintext, password, salt) { + const key = crypto.pbkdf2Sync(password, salt, 250000, 32, 'sha256'); + const iv = crypto.randomBytes(16); + + const cipher = crypto.createCipheriv('aes-256-gcm', key, iv); + const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]); + const tag = cipher.getAuthTag(); + + const combined = Buffer.concat([iv, encrypted, tag]); + return combined.toString('base64'); +} + +// Read and decrypt +const data = JSON.parse(fs.readFileSync(BOOKMARKS_PATH, 'utf8')); +let xml = decrypt(data, PASSWORD); + +// Find highest ID +const idMatch = xml.match(/highestId :(\d+):/); +let highestId = idMatch ? parseInt(idMatch[1]) : 100; +const newId = highestId + 1; + +// Escape XML entities +const escapeXml = (str) => str + .replace(/&/g, '&') + .replace(/</g, '<') + .replace(/>/g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + +// Add bookmark +const newBookmark = `<bookmark href="${escapeXml(url)}" id="${newId}"> + <title>${escapeXml(title)} + +`; + +xml = xml.replace(/highestId :(\d+):/, `highestId :${newId}:`); +xml = xml.replace('', newBookmark); + +// Encrypt and save +const newCiphertext = encrypt(xml, PASSWORD, data.salt); +fs.writeFileSync(BOOKMARKS_PATH, JSON.stringify({ ciphertext: newCiphertext, salt: data.salt })); + +console.log(`Added bookmark: ${title}`); +console.log(`URL: ${url}`); +console.log(`ID: ${newId}`); -- cgit v1.2.3