summaryrefslogtreecommitdiff
path: root/add_bookmark_to_wishlist.js
blob: a48817409a6740aa7e263589ab8fd268f1be95a6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
#!/usr/bin/env node
// Add a bookmark to Music/Wishlist folder in Floccus bookmarks
// Usage: node add_bookmark_to_wishlist.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 WISHLIST_FOLDER_TITLE = 'Wishlist';

const url = process.argv[2];
const title = process.argv[3];

if (!url || !title) {
  console.error('Usage: node add_bookmark_to_wishlist.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();
  
  return Buffer.concat([iv, encrypted, tag]).toString('base64');
}

const escapeXml = (str) => str
  .replace(/&/g, '&amp;')
  .replace(/</g, '&lt;')
  .replace(/>/g, '&gt;')
  .replace(/"/g, '&quot;')
  .replace(/'/g, '&apos;');

try {
  const data = JSON.parse(fs.readFileSync(BOOKMARKS_PATH, 'utf8'));
  let xml = decrypt(data, PASSWORD);

  // Check if bookmark already exists
  if (xml.includes(escapeXml(url)) || xml.includes(url)) {
    console.log('Bookmark already exists');
    process.exit(0);
  }

  // Find highest ID
  const idMatch = xml.match(/highestId :(\d+):/);
  let highestId = idMatch ? parseInt(idMatch[1]) : 100;
  const newId = highestId + 1;

  // Find the Wishlist folder and add bookmark inside it
  const wishlistPattern = /<folder id="\d+">\s*<title>Wishlist<\/title>/;
  
  if (wishlistPattern.test(xml)) {
    // Add bookmark inside existing Wishlist folder
    const newBookmark = `<bookmark href="${escapeXml(url)}" id="${newId}">
      <title>${escapeXml(title)}</title>
    </bookmark>
  </folder>`;
    
    // Find Wishlist folder's closing tag and insert before it
    xml = xml.replace(
      /(<folder id="\d+">\s*<title>Wishlist<\/title>[\s\S]*?)(<\/folder>)/,
      (match, folderContent, closingTag) => folderContent + newBookmark
    );
  } else {
    console.error('Wishlist folder not found');
    process.exit(1);
  }

  // Update highest ID
  xml = xml.replace(/highestId :(\d+):/, `highestId :${newId}:`);

  // Save
  const newCiphertext = encrypt(xml, PASSWORD, data.salt);
  fs.writeFileSync(BOOKMARKS_PATH, JSON.stringify({ ciphertext: newCiphertext, salt: data.salt }));

  console.log(`Added: ${title}`);
} catch (e) {
  console.error('Error:', e.message);
  process.exit(1);
}