diff options
Diffstat (limited to 'add_bookmark.js')
| -rw-r--r-- | add_bookmark.js | 76 |
1 files changed, 76 insertions, 0 deletions
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 <url> <title> + +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)}</title> + </bookmark> +</xbel>`; + +xml = xml.replace(/highestId :(\d+):/, `highestId :${newId}:`); +xml = xml.replace('</xbel>', 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}`); |
