gablilli commited on
Commit
71e8598
·
1 Parent(s): 52855e7

feat: bsmart with annotations

Browse files
cli.js CHANGED
@@ -22,7 +22,8 @@ async function selectProvider() {
22
  "sanoma",
23
  "hubscuola",
24
  "dibooklaterza",
25
- "zanichelli"
 
26
  // there'll be more providers in the future, but I currently don't have books to test em so if you want to contribute feel free to open a PR with your provider implementation
27
  ]
28
  }
@@ -33,6 +34,11 @@ async function selectProvider() {
33
 
34
  async function main() {
35
  const provider = await selectProvider();
 
 
 
 
 
36
 
37
  try {
38
  const module = await import(`./providers/${provider}.js`);
@@ -42,7 +48,7 @@ async function main() {
42
  process.exit(1);
43
  }
44
 
45
- await module.run({});
46
  } catch (err) {
47
  console.error("Errore caricando il provider:", err.message);
48
  process.exit(1);
 
22
  "sanoma",
23
  "hubscuola",
24
  "dibooklaterza",
25
+ "zanichelli",
26
+ "bsmart"
27
  // there'll be more providers in the future, but I currently don't have books to test em so if you want to contribute feel free to open a PR with your provider implementation
28
  ]
29
  }
 
34
 
35
  async function main() {
36
  const provider = await selectProvider();
37
+ const providerOptions = { ...argv };
38
+ delete providerOptions._;
39
+ delete providerOptions.$0;
40
+ delete providerOptions.provider;
41
+ delete providerOptions.p;
42
 
43
  try {
44
  const module = await import(`./providers/${provider}.js`);
 
48
  process.exit(1);
49
  }
50
 
51
+ await module.run(providerOptions);
52
  } catch (err) {
53
  console.error("Errore caricando il provider:", err.message);
54
  process.exit(1);
package-lock.json CHANGED
@@ -14,11 +14,15 @@
14
  "better-sqlite3": "^12.4.1",
15
  "fs-extra": "^11.2.0",
16
  "inquirer": "^8.2.7",
 
 
17
  "node-fetch": "^3.3.2",
18
  "node-forge": "^1.3.3",
 
19
  "pdf-lib": "^1.17.1",
20
  "pdf-merger-js": "^5.1.1",
21
  "prompt-sync": "^4.2.0",
 
22
  "svg-to-pdfkit": "^0.1.8",
23
  "sync-request": "^6.1.0",
24
  "xml2js": "^0.6.2",
@@ -314,6 +318,15 @@
314
  "integrity": "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==",
315
  "license": "MIT"
316
  },
 
 
 
 
 
 
 
 
 
317
  "node_modules/chownr": {
318
  "version": "1.1.4",
319
  "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
@@ -487,6 +500,15 @@
487
  "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
488
  "license": "MIT"
489
  },
 
 
 
 
 
 
 
 
 
490
  "node_modules/crypto-js": {
491
  "version": "4.2.0",
492
  "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz",
@@ -654,6 +676,12 @@
654
  "node": ">=0.8.0"
655
  }
656
  },
 
 
 
 
 
 
657
  "node_modules/expand-template": {
658
  "version": "2.0.3",
659
  "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
@@ -1043,16 +1071,6 @@
1043
  }
1044
  }
1045
  },
1046
- "node_modules/inquirer/node_modules/@types/node": {
1047
- "version": "25.3.3",
1048
- "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.3.tgz",
1049
- "integrity": "sha512-DpzbrH7wIcBaJibpKo9nnSQL0MTRdnWttGyE5haGwK86xgMOkFLp7vEyfQPGLOJh5wNYiJ3V9PmUMDhV9u8kkQ==",
1050
- "optional": true,
1051
- "peer": true,
1052
- "dependencies": {
1053
- "undici-types": "~7.18.0"
1054
- }
1055
- },
1056
  "node_modules/inquirer/node_modules/ansi-regex": {
1057
  "version": "5.0.1",
1058
  "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
@@ -1088,6 +1106,18 @@
1088
  "node": ">=8"
1089
  }
1090
  },
 
 
 
 
 
 
 
 
 
 
 
 
1091
  "node_modules/is-fullwidth-code-point": {
1092
  "version": "3.0.0",
1093
  "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
@@ -1193,6 +1223,17 @@
1193
  "node": ">= 0.4"
1194
  }
1195
  },
 
 
 
 
 
 
 
 
 
 
 
1196
  "node_modules/mime-db": {
1197
  "version": "1.52.0",
1198
  "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
@@ -1250,6 +1291,21 @@
1250
  "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
1251
  "license": "MIT"
1252
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1253
  "node_modules/mute-stream": {
1254
  "version": "0.0.8",
1255
  "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
@@ -1401,6 +1457,21 @@
1401
  "node": ">=8"
1402
  }
1403
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1404
  "node_modules/pako": {
1405
  "version": "1.0.11",
1406
  "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
@@ -1647,6 +1718,15 @@
1647
  "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
1648
  "license": "MIT"
1649
  },
 
 
 
 
 
 
 
 
 
1650
  "node_modules/sax": {
1651
  "version": "1.4.4",
1652
  "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.4.tgz",
@@ -1968,6 +2048,15 @@
1968
  "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==",
1969
  "license": "MIT"
1970
  },
 
 
 
 
 
 
 
 
 
1971
  "node_modules/tslib": {
1972
  "version": "1.14.1",
1973
  "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
@@ -2009,8 +2098,7 @@
2009
  "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
2010
  "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
2011
  "license": "MIT",
2012
- "optional": true,
2013
- "peer": true
2014
  },
2015
  "node_modules/unicode-properties": {
2016
  "version": "1.4.1",
@@ -2047,6 +2135,12 @@
2047
  "node": ">= 10.0.0"
2048
  }
2049
  },
 
 
 
 
 
 
2050
  "node_modules/util-deprecate": {
2051
  "version": "1.0.2",
2052
  "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@@ -2185,6 +2279,18 @@
2185
  "engines": {
2186
  "node": ">=12"
2187
  }
 
 
 
 
 
 
 
 
 
 
 
 
2188
  }
2189
  }
2190
  }
 
14
  "better-sqlite3": "^12.4.1",
15
  "fs-extra": "^11.2.0",
16
  "inquirer": "^8.2.7",
17
+ "md5": "^2.3.0",
18
+ "msgpack-lite": "^0.1.26",
19
  "node-fetch": "^3.3.2",
20
  "node-forge": "^1.3.3",
21
+ "p-limit": "^6.2.0",
22
  "pdf-lib": "^1.17.1",
23
  "pdf-merger-js": "^5.1.1",
24
  "prompt-sync": "^4.2.0",
25
+ "sanitize-filename": "^1.6.3",
26
  "svg-to-pdfkit": "^0.1.8",
27
  "sync-request": "^6.1.0",
28
  "xml2js": "^0.6.2",
 
318
  "integrity": "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==",
319
  "license": "MIT"
320
  },
321
+ "node_modules/charenc": {
322
+ "version": "0.0.2",
323
+ "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz",
324
+ "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==",
325
+ "license": "BSD-3-Clause",
326
+ "engines": {
327
+ "node": "*"
328
+ }
329
+ },
330
  "node_modules/chownr": {
331
  "version": "1.1.4",
332
  "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
 
500
  "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
501
  "license": "MIT"
502
  },
503
+ "node_modules/crypt": {
504
+ "version": "0.0.2",
505
+ "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz",
506
+ "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==",
507
+ "license": "BSD-3-Clause",
508
+ "engines": {
509
+ "node": "*"
510
+ }
511
+ },
512
  "node_modules/crypto-js": {
513
  "version": "4.2.0",
514
  "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz",
 
676
  "node": ">=0.8.0"
677
  }
678
  },
679
+ "node_modules/event-lite": {
680
+ "version": "0.1.3",
681
+ "resolved": "https://registry.npmjs.org/event-lite/-/event-lite-0.1.3.tgz",
682
+ "integrity": "sha512-8qz9nOz5VeD2z96elrEKD2U433+L3DWdUdDkOINLGOJvx1GsMBbMn0aCeu28y8/e85A6mCigBiFlYMnTBEGlSw==",
683
+ "license": "MIT"
684
+ },
685
  "node_modules/expand-template": {
686
  "version": "2.0.3",
687
  "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
 
1071
  }
1072
  }
1073
  },
 
 
 
 
 
 
 
 
 
 
1074
  "node_modules/inquirer/node_modules/ansi-regex": {
1075
  "version": "5.0.1",
1076
  "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
 
1106
  "node": ">=8"
1107
  }
1108
  },
1109
+ "node_modules/int64-buffer": {
1110
+ "version": "0.1.10",
1111
+ "resolved": "https://registry.npmjs.org/int64-buffer/-/int64-buffer-0.1.10.tgz",
1112
+ "integrity": "sha512-v7cSY1J8ydZ0GyjUHqF+1bshJ6cnEVLo9EnjB8p+4HDRPZc9N5jjmvUV7NvEsqQOKyH0pmIBFWXVQbiS0+OBbA==",
1113
+ "license": "MIT"
1114
+ },
1115
+ "node_modules/is-buffer": {
1116
+ "version": "1.1.6",
1117
+ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
1118
+ "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
1119
+ "license": "MIT"
1120
+ },
1121
  "node_modules/is-fullwidth-code-point": {
1122
  "version": "3.0.0",
1123
  "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
 
1223
  "node": ">= 0.4"
1224
  }
1225
  },
1226
+ "node_modules/md5": {
1227
+ "version": "2.3.0",
1228
+ "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz",
1229
+ "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==",
1230
+ "license": "BSD-3-Clause",
1231
+ "dependencies": {
1232
+ "charenc": "0.0.2",
1233
+ "crypt": "0.0.2",
1234
+ "is-buffer": "~1.1.6"
1235
+ }
1236
+ },
1237
  "node_modules/mime-db": {
1238
  "version": "1.52.0",
1239
  "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
 
1291
  "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
1292
  "license": "MIT"
1293
  },
1294
+ "node_modules/msgpack-lite": {
1295
+ "version": "0.1.26",
1296
+ "resolved": "https://registry.npmjs.org/msgpack-lite/-/msgpack-lite-0.1.26.tgz",
1297
+ "integrity": "sha512-SZ2IxeqZ1oRFGo0xFGbvBJWMp3yLIY9rlIJyxy8CGrwZn1f0ZK4r6jV/AM1r0FZMDUkWkglOk/eeKIL9g77Nxw==",
1298
+ "license": "MIT",
1299
+ "dependencies": {
1300
+ "event-lite": "^0.1.1",
1301
+ "ieee754": "^1.1.8",
1302
+ "int64-buffer": "^0.1.9",
1303
+ "isarray": "^1.0.0"
1304
+ },
1305
+ "bin": {
1306
+ "msgpack": "bin/msgpack"
1307
+ }
1308
+ },
1309
  "node_modules/mute-stream": {
1310
  "version": "0.0.8",
1311
  "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
 
1457
  "node": ">=8"
1458
  }
1459
  },
1460
+ "node_modules/p-limit": {
1461
+ "version": "6.2.0",
1462
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-6.2.0.tgz",
1463
+ "integrity": "sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA==",
1464
+ "license": "MIT",
1465
+ "dependencies": {
1466
+ "yocto-queue": "^1.1.1"
1467
+ },
1468
+ "engines": {
1469
+ "node": ">=18"
1470
+ },
1471
+ "funding": {
1472
+ "url": "https://github.com/sponsors/sindresorhus"
1473
+ }
1474
+ },
1475
  "node_modules/pako": {
1476
  "version": "1.0.11",
1477
  "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
 
1718
  "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
1719
  "license": "MIT"
1720
  },
1721
+ "node_modules/sanitize-filename": {
1722
+ "version": "1.6.3",
1723
+ "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.3.tgz",
1724
+ "integrity": "sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==",
1725
+ "license": "WTFPL OR ISC",
1726
+ "dependencies": {
1727
+ "truncate-utf8-bytes": "^1.0.0"
1728
+ }
1729
+ },
1730
  "node_modules/sax": {
1731
  "version": "1.4.4",
1732
  "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.4.tgz",
 
2048
  "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==",
2049
  "license": "MIT"
2050
  },
2051
+ "node_modules/truncate-utf8-bytes": {
2052
+ "version": "1.0.2",
2053
+ "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz",
2054
+ "integrity": "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==",
2055
+ "license": "WTFPL",
2056
+ "dependencies": {
2057
+ "utf8-byte-length": "^1.0.1"
2058
+ }
2059
+ },
2060
  "node_modules/tslib": {
2061
  "version": "1.14.1",
2062
  "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
 
2098
  "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
2099
  "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
2100
  "license": "MIT",
2101
+ "optional": true
 
2102
  },
2103
  "node_modules/unicode-properties": {
2104
  "version": "1.4.1",
 
2135
  "node": ">= 10.0.0"
2136
  }
2137
  },
2138
+ "node_modules/utf8-byte-length": {
2139
+ "version": "1.0.5",
2140
+ "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz",
2141
+ "integrity": "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==",
2142
+ "license": "(WTFPL OR MIT)"
2143
+ },
2144
  "node_modules/util-deprecate": {
2145
  "version": "1.0.2",
2146
  "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
 
2279
  "engines": {
2280
  "node": ">=12"
2281
  }
2282
+ },
2283
+ "node_modules/yocto-queue": {
2284
+ "version": "1.2.2",
2285
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz",
2286
+ "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==",
2287
+ "license": "MIT",
2288
+ "engines": {
2289
+ "node": ">=12.20"
2290
+ },
2291
+ "funding": {
2292
+ "url": "https://github.com/sponsors/sindresorhus"
2293
+ }
2294
  }
2295
  }
2296
  }
package.json CHANGED
@@ -25,11 +25,15 @@
25
  "better-sqlite3": "^12.4.1",
26
  "fs-extra": "^11.2.0",
27
  "inquirer": "^8.2.7",
 
 
28
  "node-fetch": "^3.3.2",
29
  "node-forge": "^1.3.3",
30
  "pdf-lib": "^1.17.1",
31
  "pdf-merger-js": "^5.1.1",
 
32
  "prompt-sync": "^4.2.0",
 
33
  "svg-to-pdfkit": "^0.1.8",
34
  "sync-request": "^6.1.0",
35
  "xml2js": "^0.6.2",
 
25
  "better-sqlite3": "^12.4.1",
26
  "fs-extra": "^11.2.0",
27
  "inquirer": "^8.2.7",
28
+ "md5": "^2.3.0",
29
+ "msgpack-lite": "^0.1.26",
30
  "node-fetch": "^3.3.2",
31
  "node-forge": "^1.3.3",
32
  "pdf-lib": "^1.17.1",
33
  "pdf-merger-js": "^5.1.1",
34
+ "p-limit": "^6.2.0",
35
  "prompt-sync": "^4.2.0",
36
+ "sanitize-filename": "^1.6.3",
37
  "svg-to-pdfkit": "^0.1.8",
38
  "sync-request": "^6.1.0",
39
  "xml2js": "^0.6.2",
providers/bsmart.js ADDED
@@ -0,0 +1,583 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import PromptSync from 'prompt-sync';
2
+ import fetch from 'node-fetch';
3
+ import { PDFDocument, rgb } from 'pdf-lib';
4
+ import fs from 'fs';
5
+ import sanitize from 'sanitize-filename';
6
+ import yargs from 'yargs';
7
+ import { hideBin } from 'yargs/helpers';
8
+ import md5 from 'md5';
9
+ import { spawn } from 'child_process';
10
+ import path from 'path';
11
+ import pLimit from 'p-limit';
12
+
13
+ import { fetchEncryptionKey, decryptFile } from './src/crypto.js';
14
+ import { getUserInfo, getBooks, getBookInfo, getBookResources, getResourceLinks } from './src/api.js';
15
+
16
+ const prompt = PromptSync({ sigint: true });
17
+
18
+ function parseAnnotationValue(value) {
19
+ if (!Array.isArray(value)) {
20
+ return [];
21
+ }
22
+
23
+ return value
24
+ .map(element => {
25
+ if (typeof element !== 'string') {
26
+ return null;
27
+ }
28
+
29
+ try {
30
+ return JSON.parse(element);
31
+ } catch {
32
+ return null;
33
+ }
34
+ })
35
+ .filter(Boolean);
36
+ }
37
+
38
+ function extractAnnotationsFromLinks(links) {
39
+ const annotations = [];
40
+
41
+ for (const link of links) {
42
+ const resource = link?.resource;
43
+ const parsed = parseAnnotationValue(resource?.content?.value);
44
+
45
+ if (parsed.length === 0) {
46
+ continue;
47
+ }
48
+
49
+ annotations.push({
50
+ linkId: link?.id ?? null,
51
+ resourceId: resource?.id ?? link?.resource_id ?? null,
52
+ resourceTypeId: resource?.resource_type_id ?? link?.resource_type_id ?? null,
53
+ targetId: link?.target_id ?? null,
54
+ targetTypeId: link?.target_type_id ?? null,
55
+ items: parsed
56
+ });
57
+ }
58
+
59
+ return annotations;
60
+ }
61
+
62
+ function clamp(value, min, max) {
63
+ return Math.min(max, Math.max(min, value));
64
+ }
65
+
66
+ function parseHexColor(color) {
67
+ if (typeof color !== 'string') {
68
+ return rgb(0, 0, 0);
69
+ }
70
+
71
+ const normalized = color.trim().replace('#', '');
72
+
73
+ if (!/^[0-9a-fA-F]{6}$/.test(normalized)) {
74
+ return rgb(0, 0, 0);
75
+ }
76
+
77
+ const red = parseInt(normalized.slice(0, 2), 16) / 255;
78
+ const green = parseInt(normalized.slice(2, 4), 16) / 255;
79
+ const blue = parseInt(normalized.slice(4, 6), 16) / 255;
80
+ return rgb(red, green, blue);
81
+ }
82
+
83
+ function toPdfRect(pageHeight, rect) {
84
+ if (!Array.isArray(rect) || rect.length < 4) {
85
+ return null;
86
+ }
87
+
88
+ const x = Number(rect[0]);
89
+ const y = Number(rect[1]);
90
+ const width = Number(rect[2]);
91
+ const height = Number(rect[3]);
92
+
93
+ if (![x, y, width, height].every(Number.isFinite)) {
94
+ return null;
95
+ }
96
+
97
+ return {
98
+ x,
99
+ y: pageHeight - y - height,
100
+ width,
101
+ height
102
+ };
103
+ }
104
+
105
+ function drawInkAnnotationOnPage(pdfPage, annotationItem) {
106
+ const pointsByStroke = annotationItem?.lines?.points;
107
+
108
+ if (!Array.isArray(pointsByStroke)) {
109
+ return;
110
+ }
111
+
112
+ const pageHeight = pdfPage.getHeight();
113
+ const strokeColor = parseHexColor(annotationItem?.strokeColor);
114
+ const thickness = Math.max(1, Number(annotationItem?.lineWidth) || 1);
115
+ const opacity = clamp(Number(annotationItem?.opacity) || 1, 0, 1);
116
+
117
+ for (const stroke of pointsByStroke) {
118
+ if (!Array.isArray(stroke) || stroke.length === 0) {
119
+ continue;
120
+ }
121
+
122
+ if (stroke.length === 1) {
123
+ const point = stroke[0];
124
+ if (Array.isArray(point) && point.length >= 2) {
125
+ const x = Number(point[0]);
126
+ const y = Number(point[1]);
127
+ if (Number.isFinite(x) && Number.isFinite(y)) {
128
+ pdfPage.drawCircle({
129
+ x,
130
+ y: pageHeight - y,
131
+ size: Math.max(0.5, thickness / 2),
132
+ color: strokeColor,
133
+ opacity
134
+ });
135
+ }
136
+ }
137
+ continue;
138
+ }
139
+
140
+ for (let i = 0; i < stroke.length - 1; i++) {
141
+ const from = stroke[i];
142
+ const to = stroke[i + 1];
143
+
144
+ if (!Array.isArray(from) || !Array.isArray(to) || from.length < 2 || to.length < 2) {
145
+ continue;
146
+ }
147
+
148
+ const x1 = Number(from[0]);
149
+ const y1 = Number(from[1]);
150
+ const x2 = Number(to[0]);
151
+ const y2 = Number(to[1]);
152
+
153
+ if (![x1, y1, x2, y2].every(Number.isFinite)) {
154
+ continue;
155
+ }
156
+
157
+ pdfPage.drawLine({
158
+ start: { x: x1, y: pageHeight - y1 },
159
+ end: { x: x2, y: pageHeight - y2 },
160
+ thickness,
161
+ color: strokeColor,
162
+ opacity
163
+ });
164
+ }
165
+ }
166
+ }
167
+
168
+ function drawHighlightAnnotationOnPage(pdfPage, annotationItem) {
169
+ const pageHeight = pdfPage.getHeight();
170
+ const color = parseHexColor(annotationItem?.color || '#FAFC00');
171
+ const opacity = clamp(Number(annotationItem?.opacity) || 0.6, 0, 1);
172
+ const rects = Array.isArray(annotationItem?.rects) && annotationItem.rects.length > 0
173
+ ? annotationItem.rects
174
+ : [annotationItem?.bbox];
175
+
176
+ for (const rect of rects) {
177
+ const normalized = toPdfRect(pageHeight, rect);
178
+ if (!normalized) {
179
+ continue;
180
+ }
181
+
182
+ pdfPage.drawRectangle({
183
+ x: normalized.x,
184
+ y: normalized.y,
185
+ width: normalized.width,
186
+ height: normalized.height,
187
+ color,
188
+ opacity
189
+ });
190
+ }
191
+ }
192
+
193
+ function drawLinkAnnotationOnPage(pdfPage, annotationItem) {
194
+ const pageHeight = pdfPage.getHeight();
195
+ const rect = toPdfRect(pageHeight, annotationItem?.bbox);
196
+
197
+ if (!rect) {
198
+ return;
199
+ }
200
+
201
+ const borderWidth = Math.max(0, Number(annotationItem?.borderWidth) || 0);
202
+ if (borderWidth === 0) {
203
+ return;
204
+ }
205
+
206
+ const borderColor = parseHexColor(annotationItem?.color || '#1A73E8');
207
+ const opacity = clamp(Number(annotationItem?.opacity) || 1, 0, 1);
208
+
209
+ pdfPage.drawRectangle({
210
+ x: rect.x,
211
+ y: rect.y,
212
+ width: rect.width,
213
+ height: rect.height,
214
+ borderColor,
215
+ borderWidth,
216
+ borderOpacity: opacity
217
+ });
218
+ }
219
+
220
+ function drawFallbackRectAnnotationOnPage(pdfPage, annotationItem) {
221
+ const pageHeight = pdfPage.getHeight();
222
+ const rects = Array.isArray(annotationItem?.rects) && annotationItem.rects.length > 0
223
+ ? annotationItem.rects
224
+ : [annotationItem?.bbox];
225
+
226
+ const borderColor = parseHexColor(annotationItem?.strokeColor || annotationItem?.color || '#000000');
227
+ const fillColor = annotationItem?.fillColor ? parseHexColor(annotationItem.fillColor) : null;
228
+ const opacity = clamp(Number(annotationItem?.opacity) || 1, 0, 1);
229
+ const borderWidth = Math.max(0.5, Number(annotationItem?.lineWidth) || Number(annotationItem?.borderWidth) || 1);
230
+
231
+ for (const rect of rects) {
232
+ const normalized = toPdfRect(pageHeight, rect);
233
+ if (!normalized) {
234
+ continue;
235
+ }
236
+
237
+ pdfPage.drawRectangle({
238
+ x: normalized.x,
239
+ y: normalized.y,
240
+ width: normalized.width,
241
+ height: normalized.height,
242
+ ...(fillColor ? { color: fillColor, opacity } : {}),
243
+ borderColor,
244
+ borderWidth,
245
+ borderOpacity: opacity
246
+ });
247
+ }
248
+ }
249
+
250
+ function drawAnnotationsOnPage(pdfPage, annotations) {
251
+ const stats = {};
252
+
253
+ for (const annotation of annotations) {
254
+ for (const item of annotation.items || []) {
255
+ const type = item?.type || 'unknown';
256
+
257
+ if (type === 'pspdfkit/ink') {
258
+ drawInkAnnotationOnPage(pdfPage, item);
259
+ stats[type] = (stats[type] || 0) + 1;
260
+ continue;
261
+ }
262
+
263
+ if (type === 'pspdfkit/markup/highlight') {
264
+ drawHighlightAnnotationOnPage(pdfPage, item);
265
+ stats[type] = (stats[type] || 0) + 1;
266
+ continue;
267
+ }
268
+
269
+ if (type === 'pspdfkit/link') {
270
+ drawLinkAnnotationOnPage(pdfPage, item);
271
+ stats[type] = (stats[type] || 0) + 1;
272
+ continue;
273
+ }
274
+
275
+ if (item?.bbox || (Array.isArray(item?.rects) && item.rects.length > 0)) {
276
+ drawFallbackRectAnnotationOnPage(pdfPage, item);
277
+ stats[type] = (stats[type] || 0) + 1;
278
+ }
279
+ }
280
+ }
281
+
282
+ return stats;
283
+ }
284
+
285
+ export async function run(options = {}) {
286
+ const argv = yargs(hideBin(process.argv))
287
+ .option('site', {
288
+ describe: 'The site to download from, currently either bsmart or digibook24',
289
+ type: 'string',
290
+ default: null
291
+ })
292
+ .option('siteUrl', {
293
+ describe: 'This overwrites the base url for the site, useful in case a new platform is added',
294
+ type: 'string',
295
+ default: null
296
+ })
297
+ .option('cookie', {
298
+ describe: 'Input "_bsw_session_v1_production" cookie',
299
+ type: 'string',
300
+ default: null
301
+ })
302
+ .option('bookId', {
303
+ describe: 'Book id',
304
+ type: 'string',
305
+ default: null
306
+ })
307
+ .option('downloadOnly', {
308
+ describe: 'Downloads the pages as individual pdfs and will provide a command that can be used to merge them with pdftk',
309
+ type: 'boolean',
310
+ default: false
311
+ })
312
+ .option('pdftk', {
313
+ describe: 'Downloads the pages as individual pdfs and merges them with pdftk',
314
+ type: 'boolean',
315
+ default: false
316
+ })
317
+ .option('pdftkPath', {
318
+ describe: 'Path to pdftk executable',
319
+ type: 'string',
320
+ default: 'pdftk'
321
+ })
322
+ .option('checkMd5', {
323
+ describe: 'Checks the md5 hash of the downloaded pages',
324
+ type: 'boolean',
325
+ default: false
326
+ })
327
+ .option('output', {
328
+ describe: 'Output filename',
329
+ type: 'string',
330
+ default: null
331
+ })
332
+ .option('resources', {
333
+ describe: 'Download resources of the book instead of the book itself',
334
+ type: 'boolean',
335
+ default: false
336
+ })
337
+ .option('concurrency', {
338
+ describe: 'Number of parallel downloads',
339
+ type: 'number',
340
+ default: 4
341
+ })
342
+ .option('annotations', {
343
+ describe: 'Draw page annotations on merged PDF',
344
+ type: 'boolean',
345
+ default: false
346
+ })
347
+ .help()
348
+ .argv;
349
+
350
+ const runtime = { ...argv, ...options };
351
+
352
+ if (runtime.downloadOnly && runtime.pdftk) {
353
+ console.log("Can't use --download-only and --pdftk at the same time");
354
+ return;
355
+ }
356
+
357
+ if (runtime.annotations && (runtime.downloadOnly || runtime.pdftk)) {
358
+ console.log('Annotations drawing requires merged PDF mode (without --downloadOnly/--pdftk)');
359
+ }
360
+
361
+ if ((runtime.downloadOnly || runtime.pdftk) && !fs.existsSync('temp')) {
362
+ fs.mkdirSync('temp');
363
+ }
364
+
365
+ if ((runtime.downloadOnly || runtime.pdftk) && fs.readdirSync('temp').length > 0) {
366
+ console.log('Files already in temp folder, please manually delete them if you want to download a new book');
367
+ return;
368
+ }
369
+
370
+ let baseSite = runtime.siteUrl;
371
+
372
+ if (!baseSite) {
373
+ let platform = runtime.site;
374
+ while (!platform) {
375
+ platform = prompt('Input site (bsmart or digibook24):');
376
+ if (platform !== 'bsmart' && platform !== 'digibook24') {
377
+ platform = null;
378
+ console.log('Invalid site');
379
+ }
380
+ }
381
+
382
+ baseSite = platform === 'bsmart' ? 'www.bsmart.it' : 'web.digibook24.com';
383
+ }
384
+
385
+ let cookie = runtime.cookie;
386
+ while (!cookie) {
387
+ cookie = prompt('Input "_bsw_session_v1_production" cookie:');
388
+ }
389
+
390
+ let user;
391
+ try {
392
+ const cookieHeaders = { cookie: `_bsw_session_v1_production=${cookie}` };
393
+ user = await getUserInfo(baseSite, cookieHeaders);
394
+ } catch (error) {
395
+ console.log('Error fetching user info:', error);
396
+ return;
397
+ }
398
+
399
+ const headers = { auth_token: user.auth_token };
400
+
401
+ let books;
402
+ try {
403
+ books = await getBooks(baseSite, headers);
404
+ } catch (error) {
405
+ console.log('Error fetching books:', error);
406
+ return;
407
+ }
408
+
409
+ if (books.length === 0) {
410
+ console.log('No books in your library!');
411
+ } else {
412
+ console.log('Book list:');
413
+ console.table(books.map(book => ({ id: book.id, title: book.title })));
414
+ }
415
+
416
+ let bookId = runtime.bookId;
417
+ while (!bookId) {
418
+ bookId = prompt(`Please input book id${books.length === 0 ? ' manually' : ''}:`);
419
+ }
420
+
421
+ console.log('Fetching book info');
422
+
423
+ let book;
424
+ try {
425
+ book = await getBookInfo(baseSite, bookId, headers);
426
+ } catch (error) {
427
+ console.log('Error fetching book info:', error.message);
428
+ return;
429
+ }
430
+
431
+ let info;
432
+ try {
433
+ info = await getBookResources(baseSite, book, headers);
434
+ } catch (error) {
435
+ console.log('Error fetching book resources:', error);
436
+ return;
437
+ }
438
+
439
+ const outputPdf = await PDFDocument.create();
440
+ const filenames = [];
441
+ const outputname = runtime.output || sanitize(`${book.id} - ${book.title}`);
442
+
443
+ let assets = info
444
+ .flatMap(resource =>
445
+ (resource.assets || []).map(asset => ({
446
+ ...asset,
447
+ pageResourceId: resource.id
448
+ }))
449
+ );
450
+
451
+ console.log('Fetching encryption key');
452
+
453
+ let encryptionKey;
454
+ try {
455
+ encryptionKey = await fetchEncryptionKey();
456
+ } catch (error) {
457
+ console.log('Error fetching encryption key:', error);
458
+ return;
459
+ }
460
+
461
+ if (runtime.resources) {
462
+ assets = assets.filter(element => element.use === 'launch_file');
463
+ if (!fs.existsSync(outputname)) {
464
+ fs.mkdirSync(outputname);
465
+ }
466
+ console.log('Downloading resources');
467
+ } else {
468
+ assets = assets.filter(element => element.use === 'page_pdf');
469
+ console.log('Downloading pages');
470
+ }
471
+
472
+ const limit = pLimit(runtime.concurrency);
473
+ let pagesWithAnnotations = 0;
474
+ let totalAnnotationItems = 0;
475
+ const renderedByType = {};
476
+
477
+ const downloadTasks = assets.map((asset, index) =>
478
+ limit(async () => {
479
+ try {
480
+ let data = await fetch(asset.url).then(response => response.buffer());
481
+ if (asset.encrypted !== false) {
482
+ data = await decryptFile(data, encryptionKey);
483
+ }
484
+ if (runtime.checkMd5 && md5(data) !== asset.url) {
485
+ console.log(`\nMismatching md5 hash for asset ${index}: ${asset.url}`);
486
+ }
487
+ return data;
488
+ } catch (error) {
489
+ console.log(`\nError downloading asset ${index}: ${error.message}`);
490
+ throw error;
491
+ }
492
+ })
493
+ );
494
+
495
+ for (let index = 0; index < assets.length; index++) {
496
+ const asset = assets[index];
497
+ let data;
498
+
499
+ try {
500
+ data = await downloadTasks[index];
501
+ } catch {
502
+ return;
503
+ }
504
+
505
+ process.stdout.write(`\rProgress ${((index + 1) / assets.length * 100).toFixed(2)}% (${index + 1}/${assets.length})`);
506
+
507
+ if (runtime.resources) {
508
+ const filename = path.basename(asset.filename);
509
+ await fs.promises.writeFile(`${outputname}/${filename}`, data);
510
+ continue;
511
+ }
512
+
513
+ if (runtime.downloadOnly || runtime.pdftk) {
514
+ const filename = path.basename(asset.filename, '.pdf');
515
+ const filePath = `temp/${filename}.pdf`;
516
+ await fs.promises.writeFile(filePath, data);
517
+ filenames.push(filePath);
518
+ continue;
519
+ }
520
+
521
+ const page = await PDFDocument.load(data);
522
+ const [firstDonorPage] = await outputPdf.copyPages(page, [0]);
523
+
524
+ if (runtime.annotations && asset?.pageResourceId) {
525
+ try {
526
+ const links = await getResourceLinks(baseSite, asset.pageResourceId, headers);
527
+ const annotations = extractAnnotationsFromLinks(links);
528
+
529
+ if (annotations.length > 0) {
530
+ const pageStats = drawAnnotationsOnPage(firstDonorPage, annotations);
531
+ pagesWithAnnotations++;
532
+ totalAnnotationItems += annotations.reduce((sum, group) => sum + (group.items?.length || 0), 0);
533
+ for (const [type, count] of Object.entries(pageStats)) {
534
+ renderedByType[type] = (renderedByType[type] || 0) + count;
535
+ }
536
+ }
537
+ } catch (error) {
538
+ console.log(`\nError fetching annotations for page ${index + 1}: ${error.message}`);
539
+ }
540
+ }
541
+
542
+ outputPdf.addPage(firstDonorPage);
543
+ }
544
+
545
+ console.log();
546
+
547
+ if (!runtime.resources && !runtime.downloadOnly && !runtime.pdftk) {
548
+ await fs.promises.writeFile(`${outputname}.pdf`, await outputPdf.save());
549
+
550
+ if (runtime.annotations) {
551
+ console.log(`Applied ${totalAnnotationItems} annotation item(s) on ${pagesWithAnnotations} page(s)`);
552
+ console.log(`Rendered by type: ${JSON.stringify(renderedByType)}`);
553
+ }
554
+
555
+ console.log('Done');
556
+ return;
557
+ }
558
+
559
+ if (!runtime.resources) {
560
+ const pdftkCommand = `${runtime.pdftkPath} ${filenames.join(' ')} cat output "${outputname}.pdf"`;
561
+ console.log('Run this command to merge the pages with pdftk:');
562
+ console.log(pdftkCommand);
563
+
564
+ if (runtime.pdftk) {
565
+ console.log('Merging pages with pdftk');
566
+ await new Promise(resolve => {
567
+ const pdftk = spawn(runtime.pdftkPath, [...filenames, 'cat', 'output', `${outputname}.pdf`]);
568
+ pdftk.stdout.on('data', data => {
569
+ console.log(`stdout: ${data}`);
570
+ });
571
+ pdftk.stderr.on('data', data => {
572
+ console.log(`stderr: ${data}`);
573
+ });
574
+ pdftk.on('close', code => {
575
+ console.log(`child process exited with code ${code}`);
576
+ resolve();
577
+ });
578
+ });
579
+ }
580
+ }
581
+
582
+ console.log('Done');
583
+ }
providers/src/api.js ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import fetch from 'node-fetch';
2
+
3
+ /**
4
+ * Gets user information from the API
5
+ * @param {string} baseSite - The base site URL
6
+ * @param {Object} headers - Complete headers object including auth_token
7
+ * @returns {Promise<Object>} User information
8
+ */
9
+ export async function getUserInfo(baseSite, headers) {
10
+ const response = await fetch(`https://${baseSite}/api/v5/user`, { headers });
11
+
12
+ if (response.status != 200) {
13
+ throw new Error('Bad cookie');
14
+ }
15
+
16
+ return await response.json();
17
+ }
18
+
19
+ /**
20
+ * Gets the list of books from the API
21
+ * @param {string} baseSite - The base site URL
22
+ * @param {Object} headers - Complete headers object including auth_token
23
+ * @returns {Promise<Array>} Array of books
24
+ */
25
+ export async function getBooks(baseSite, headers) {
26
+ const books = await fetch(`https://${baseSite}/api/v6/books?page_thumb_size=medium&per_page=25000`, { headers }).then(res => res.json());
27
+
28
+ const preactivations = await fetch(`https://${baseSite}/api/v5/books/preactivations`, { headers }).then(res => res.json());
29
+
30
+ preactivations.forEach(preactivation => {
31
+ if (preactivation.no_bsmart === false) {
32
+ books.push(...preactivation.books);
33
+ }
34
+ });
35
+
36
+ return books;
37
+ }
38
+
39
+ /**
40
+ * Gets detailed information about a specific book
41
+ * @param {string} baseSite - The base site URL
42
+ * @param {string} bookId - The book ID
43
+ * @param {Object} headers - Complete headers object including auth_token
44
+ * @returns {Promise<Object>} Book information
45
+ */
46
+ export async function getBookInfo(baseSite, bookId, headers) {
47
+ const response = await fetch(`https://${baseSite}/api/v6/books/by_book_id/${bookId}`, { headers });
48
+
49
+ if (response.status != 200) {
50
+ throw new Error('Invalid book id');
51
+ }
52
+
53
+ return await response.json();
54
+ }
55
+
56
+ /**
57
+ * Gets all resources for a book
58
+ * @param {string} baseSite - The base site URL
59
+ * @param {Object} book - The book object
60
+ * @param {Object} headers - Complete headers object including auth_token
61
+ * @returns {Promise<Array>} Array of resources
62
+ */
63
+ export async function getBookResources(baseSite, book, headers) {
64
+ let info = [];
65
+ let page = 1;
66
+
67
+ while (true) {
68
+ const tempInfo = await fetch(`https://${baseSite}/api/v5/books/${book.id}/${book.current_edition.revision}/resources?per_page=500&page=${page}`, { headers }).then(res => res.json());
69
+ info = info.concat(tempInfo);
70
+ if (tempInfo.length < 500) break;
71
+ page++;
72
+ }
73
+
74
+ return info;
75
+ }
76
+
77
+ /**
78
+ * Gets links attached to a specific resource (used for annotations)
79
+ * @param {string} baseSite - The base site URL
80
+ * @param {number|string} resourceId - The page resource ID
81
+ * @param {Object} headers - Complete headers object including auth_token
82
+ * @returns {Promise<Array>} Array of links
83
+ */
84
+ export async function getResourceLinks(baseSite, resourceId, headers) {
85
+ let links = [];
86
+ let page = 1;
87
+
88
+ while (true) {
89
+ const pageLinks = await fetch(
90
+ `https://${baseSite}/api/v5/resources/${resourceId}/links?per_page=50&page=${page}`,
91
+ { headers }
92
+ ).then(res => res.json());
93
+
94
+ if (!Array.isArray(pageLinks) || pageLinks.length === 0) {
95
+ break;
96
+ }
97
+
98
+ links = links.concat(pageLinks);
99
+
100
+ if (pageLinks.length < 50) {
101
+ break;
102
+ }
103
+
104
+ page++;
105
+ }
106
+
107
+ return links;
108
+ }
providers/src/crypto.js ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import fetch from 'node-fetch';
2
+ import msgpack from 'msgpack-lite';
3
+ import aesjs from 'aes-js';
4
+
5
+ /**
6
+ * Fetches the encryption key from the bSmart website
7
+ * @returns {Promise<Buffer>} The encryption key
8
+ */
9
+ export async function fetchEncryptionKey() {
10
+ const page = await fetch('https://my.bsmart.it/');
11
+ const text = await page.text();
12
+ const script = text.match(/<script src="(\/scripts\/.*.min.js)">/)[1];
13
+ const scriptText = await fetch('https://my.bsmart.it' + script).then(res => res.text());
14
+ let keyScript = scriptText.slice(scriptText.indexOf('var i=String.fromCharCode'));
15
+ keyScript = keyScript.slice(0, keyScript.indexOf('()'));
16
+ const sourceCharacters = keyScript.match(/var i=String.fromCharCode\((((\d+),)+(\d+))\)/)[1].split(',').map(e => parseInt(e)).map(e => String.fromCharCode(e));
17
+ const map = keyScript.match(/i\[\d+\]/g).map(e => parseInt(e.slice(2, -1)));
18
+ const snippet = map.map(e => sourceCharacters[e]).join('');
19
+ const key = Buffer.from(snippet.match(/'((?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?)'/)[1], 'base64');
20
+ return key;
21
+ }
22
+
23
+ /**
24
+ * Decrypts an encrypted file using the provided encryption key
25
+ * @param {Buffer} file - The encrypted file data
26
+ * @param {Buffer} key - The encryption key
27
+ * @returns {Promise<Uint8Array>} The decrypted file data
28
+ */
29
+ export async function decryptFile(file, key) {
30
+ return new Promise((resolve, reject) => {
31
+ try {
32
+ const header = msgpack.decode(file.slice(0, 256));
33
+
34
+ const firstPart = file.slice(256, header.start);
35
+ const secondPart = new Uint8Array(file.slice(header.start));
36
+
37
+ const aesCbc = new aesjs.ModeOfOperation.cbc(key, firstPart.slice(0, 16));
38
+ let decryptedFirstPart = aesCbc.decrypt(firstPart.slice(16));
39
+
40
+ for (let i = 16; i > 0; i--) {
41
+ if (decryptedFirstPart.slice(decryptedFirstPart.length - i).every(e => e == i)) {
42
+ decryptedFirstPart = decryptedFirstPart.slice(0, decryptedFirstPart.length - i);
43
+ break;
44
+ }
45
+ }
46
+
47
+ const result = new Uint8Array(decryptedFirstPart.length + secondPart.length);
48
+ result.set(decryptedFirstPart);
49
+ result.set(secondPart, decryptedFirstPart.length);
50
+ resolve(result);
51
+ } catch (e) {
52
+ reject({ e, file });
53
+ }
54
+ });
55
+ }
providers/src/pdf.js ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { PDFDocument } from 'pdf-lib';
2
+
3
+ /**
4
+ * Merges multiple PDF pages into a single PDF document
5
+ * @param {Array<Buffer>} pages - Array of PDF page data
6
+ * @returns {Promise<PDFDocument>} The merged PDF document
7
+ */
8
+ export async function mergePdfPages(pages) {
9
+ const outputPdf = await PDFDocument.create();
10
+
11
+ for (const pageData of pages) {
12
+ const page = await PDFDocument.load(pageData);
13
+ const [firstDonorPage] = await outputPdf.copyPages(page, [0]);
14
+ outputPdf.addPage(firstDonorPage);
15
+ }
16
+
17
+ return outputPdf;
18
+ }
19
+
20
+ /**
21
+ * Saves a PDF document to a buffer
22
+ * @param {PDFDocument} pdfDoc - The PDF document to save
23
+ * @returns {Promise<Uint8Array>} The PDF data as a buffer
24
+ */
25
+ export async function savePdf(pdfDoc) {
26
+ return await pdfDoc.save();
27
+ }