gablilli commited on
Commit
4f74731
·
1 Parent(s): 8a17e92

feat: change endpoints to retrieve books for sanoma

Browse files
.gitignore CHANGED
@@ -2,4 +2,5 @@ node_modules/
2
  temp/
3
  tmp/
4
  *.pdf
5
- *.epub
 
 
2
  temp/
3
  tmp/
4
  *.pdf
5
+ *.epub
6
+ .DS_Store
package-lock.json CHANGED
@@ -11,7 +11,10 @@
11
  "dependencies": {
12
  "adm-zip": "^0.5.12",
13
  "aes-js": "^3.1.2",
 
 
14
  "better-sqlite3": "^12.4.1",
 
15
  "express": "^5.2.1",
16
  "fs-extra": "^11.2.0",
17
  "inquirer": "^8.2.7",
@@ -26,6 +29,7 @@
26
  "sanitize-filename": "^1.6.3",
27
  "svg-to-pdfkit": "^0.1.8",
28
  "sync-request": "^6.1.0",
 
29
  "ws": "^8.19.0",
30
  "xml2js": "^0.6.2",
31
  "yargs": "^17.5.1",
@@ -170,6 +174,15 @@
170
  "integrity": "sha512-e5pEa2kBnBOgR4Y/p20pskXI74UEz7de8ZGVo58asOtvSVG5YAbJeELPZxOmt+Bnz3rX753YKhfIn4X4l1PPRQ==",
171
  "license": "MIT"
172
  },
 
 
 
 
 
 
 
 
 
173
  "node_modules/ansi-escapes": {
174
  "version": "4.3.2",
175
  "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
@@ -279,6 +292,52 @@
279
  "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
280
  "license": "MIT"
281
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
282
  "node_modules/balanced-match": {
283
  "version": "4.0.4",
284
  "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
@@ -366,6 +425,12 @@
366
  "url": "https://opencollective.com/express"
367
  }
368
  },
 
 
 
 
 
 
369
  "node_modules/brace-expansion": {
370
  "version": "5.0.4",
371
  "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz",
@@ -501,6 +566,57 @@
501
  "node": "*"
502
  }
503
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
504
  "node_modules/chownr": {
505
  "version": "1.1.4",
506
  "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
@@ -864,6 +980,34 @@
864
  "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==",
865
  "license": "MIT"
866
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
867
  "node_modules/data-uri-to-buffer": {
868
  "version": "4.0.1",
869
  "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
@@ -959,6 +1103,61 @@
959
  "integrity": "sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==",
960
  "license": "MIT"
961
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
962
  "node_modules/dunder-proto": {
963
  "version": "1.0.1",
964
  "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
@@ -994,6 +1193,31 @@
994
  "node": ">= 0.8"
995
  }
996
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
997
  "node_modules/end-of-stream": {
998
  "version": "1.4.5",
999
  "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
@@ -1003,6 +1227,18 @@
1003
  "once": "^1.4.0"
1004
  }
1005
  },
 
 
 
 
 
 
 
 
 
 
 
 
1006
  "node_modules/es-define-property": {
1007
  "version": "1.0.1",
1008
  "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
@@ -1253,6 +1489,26 @@
1253
  "url": "https://opencollective.com/express"
1254
  }
1255
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1256
  "node_modules/fontkit": {
1257
  "version": "2.0.4",
1258
  "resolved": "https://registry.npmjs.org/fontkit/-/fontkit-2.0.4.tgz",
@@ -1534,6 +1790,37 @@
1534
  "node": ">= 0.4"
1535
  }
1536
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1537
  "node_modules/http-basic": {
1538
  "version": "8.1.3",
1539
  "resolved": "https://registry.npmjs.org/http-basic/-/http-basic-8.1.3.tgz",
@@ -1549,6 +1836,30 @@
1549
  "node": ">=6.0.0"
1550
  }
1551
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1552
  "node_modules/http-errors": {
1553
  "version": "2.0.1",
1554
  "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
@@ -2147,6 +2458,18 @@
2147
  "node": ">=0.10.0"
2148
  }
2149
  },
 
 
 
 
 
 
 
 
 
 
 
 
2150
  "node_modules/object-inspect": {
2151
  "version": "1.13.4",
2152
  "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
@@ -2271,6 +2594,55 @@
2271
  "resolved": "https://registry.npmjs.org/parse-cache-control/-/parse-cache-control-1.0.1.tgz",
2272
  "integrity": "sha512-60zvsJReQPX5/QP0Kzfd/VrpjScIQ7SHBW6bFCYfEP+fp0Eppr1SHhIO5nd1PjZtvclzSzES9D/p5nFJurwfWg=="
2273
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2274
  "node_modules/parseurl": {
2275
  "version": "1.3.3",
2276
  "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
@@ -2440,6 +2812,24 @@
2440
  "node": ">= 0.10"
2441
  }
2442
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2443
  "node_modules/pump": {
2444
  "version": "3.0.3",
2445
  "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz",
@@ -2450,6 +2840,15 @@
2450
  "once": "^1.3.1"
2451
  }
2452
  },
 
 
 
 
 
 
 
 
 
2453
  "node_modules/qs": {
2454
  "version": "6.15.0",
2455
  "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz",
@@ -2465,6 +2864,12 @@
2465
  "url": "https://github.com/sponsors/ljharb"
2466
  }
2467
  },
 
 
 
 
 
 
2468
  "node_modules/range-parser": {
2469
  "version": "1.2.1",
2470
  "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
@@ -2527,6 +2932,12 @@
2527
  "node": ">=0.10.0"
2528
  }
2529
  },
 
 
 
 
 
 
2530
  "node_modules/restore-cursor": {
2531
  "version": "3.1.0",
2532
  "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz",
@@ -3057,6 +3468,30 @@
3057
  "node": ">=0.6"
3058
  }
3059
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3060
  "node_modules/truncate-utf8-bytes": {
3061
  "version": "1.0.2",
3062
  "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz",
@@ -3141,12 +3576,6 @@
3141
  "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==",
3142
  "license": "MIT"
3143
  },
3144
- "node_modules/undici-types": {
3145
- "version": "7.18.2",
3146
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
3147
- "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
3148
- "optional": true
3149
- },
3150
  "node_modules/unicode-properties": {
3151
  "version": "1.4.1",
3152
  "resolved": "https://registry.npmjs.org/unicode-properties/-/unicode-properties-1.4.1.tgz",
@@ -3191,6 +3620,16 @@
3191
  "node": ">= 0.8"
3192
  }
3193
  },
 
 
 
 
 
 
 
 
 
 
3194
  "node_modules/utf8-byte-length": {
3195
  "version": "1.0.5",
3196
  "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz",
@@ -3230,6 +3669,40 @@
3230
  "node": ">= 8"
3231
  }
3232
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3233
  "node_modules/which": {
3234
  "version": "2.0.2",
3235
  "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
 
11
  "dependencies": {
12
  "adm-zip": "^0.5.12",
13
  "aes-js": "^3.1.2",
14
+ "axios": "^1.13.6",
15
+ "axios-cookiejar-support": "^5.0.5",
16
  "better-sqlite3": "^12.4.1",
17
+ "cheerio": "^1.2.0",
18
  "express": "^5.2.1",
19
  "fs-extra": "^11.2.0",
20
  "inquirer": "^8.2.7",
 
29
  "sanitize-filename": "^1.6.3",
30
  "svg-to-pdfkit": "^0.1.8",
31
  "sync-request": "^6.1.0",
32
+ "tough-cookie": "^4.1.4",
33
  "ws": "^8.19.0",
34
  "xml2js": "^0.6.2",
35
  "yargs": "^17.5.1",
 
174
  "integrity": "sha512-e5pEa2kBnBOgR4Y/p20pskXI74UEz7de8ZGVo58asOtvSVG5YAbJeELPZxOmt+Bnz3rX753YKhfIn4X4l1PPRQ==",
175
  "license": "MIT"
176
  },
177
+ "node_modules/agent-base": {
178
+ "version": "7.1.4",
179
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
180
+ "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
181
+ "license": "MIT",
182
+ "engines": {
183
+ "node": ">= 14"
184
+ }
185
+ },
186
  "node_modules/ansi-escapes": {
187
  "version": "4.3.2",
188
  "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
 
292
  "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
293
  "license": "MIT"
294
  },
295
+ "node_modules/axios": {
296
+ "version": "1.13.6",
297
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.6.tgz",
298
+ "integrity": "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==",
299
+ "license": "MIT",
300
+ "dependencies": {
301
+ "follow-redirects": "^1.15.11",
302
+ "form-data": "^4.0.5",
303
+ "proxy-from-env": "^1.1.0"
304
+ }
305
+ },
306
+ "node_modules/axios-cookiejar-support": {
307
+ "version": "5.0.5",
308
+ "resolved": "https://registry.npmjs.org/axios-cookiejar-support/-/axios-cookiejar-support-5.0.5.tgz",
309
+ "integrity": "sha512-jJG+p7JnOYxkVrYkCDKBrLqUmcpwHZTNQrEcIEKr5qe7YVTyPAD9nCsi1cO5LDmQpQApfS430czO+oceI3g/3g==",
310
+ "license": "MIT",
311
+ "dependencies": {
312
+ "http-cookie-agent": "^6.0.8"
313
+ },
314
+ "engines": {
315
+ "node": ">=18.0.0"
316
+ },
317
+ "funding": {
318
+ "url": "https://github.com/sponsors/3846masa"
319
+ },
320
+ "peerDependencies": {
321
+ "axios": ">=0.20.0",
322
+ "tough-cookie": ">=4.0.0"
323
+ }
324
+ },
325
+ "node_modules/axios/node_modules/form-data": {
326
+ "version": "4.0.5",
327
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
328
+ "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
329
+ "license": "MIT",
330
+ "dependencies": {
331
+ "asynckit": "^0.4.0",
332
+ "combined-stream": "^1.0.8",
333
+ "es-set-tostringtag": "^2.1.0",
334
+ "hasown": "^2.0.2",
335
+ "mime-types": "^2.1.12"
336
+ },
337
+ "engines": {
338
+ "node": ">= 6"
339
+ }
340
+ },
341
  "node_modules/balanced-match": {
342
  "version": "4.0.4",
343
  "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
 
425
  "url": "https://opencollective.com/express"
426
  }
427
  },
428
+ "node_modules/boolbase": {
429
+ "version": "1.0.0",
430
+ "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
431
+ "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
432
+ "license": "ISC"
433
+ },
434
  "node_modules/brace-expansion": {
435
  "version": "5.0.4",
436
  "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz",
 
566
  "node": "*"
567
  }
568
  },
569
+ "node_modules/cheerio": {
570
+ "version": "1.2.0",
571
+ "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.2.0.tgz",
572
+ "integrity": "sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg==",
573
+ "license": "MIT",
574
+ "dependencies": {
575
+ "cheerio-select": "^2.1.0",
576
+ "dom-serializer": "^2.0.0",
577
+ "domhandler": "^5.0.3",
578
+ "domutils": "^3.2.2",
579
+ "encoding-sniffer": "^0.2.1",
580
+ "htmlparser2": "^10.1.0",
581
+ "parse5": "^7.3.0",
582
+ "parse5-htmlparser2-tree-adapter": "^7.1.0",
583
+ "parse5-parser-stream": "^7.1.2",
584
+ "undici": "^7.19.0",
585
+ "whatwg-mimetype": "^4.0.0"
586
+ },
587
+ "engines": {
588
+ "node": ">=20.18.1"
589
+ },
590
+ "funding": {
591
+ "url": "https://github.com/cheeriojs/cheerio?sponsor=1"
592
+ }
593
+ },
594
+ "node_modules/cheerio-select": {
595
+ "version": "2.1.0",
596
+ "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz",
597
+ "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==",
598
+ "license": "BSD-2-Clause",
599
+ "dependencies": {
600
+ "boolbase": "^1.0.0",
601
+ "css-select": "^5.1.0",
602
+ "css-what": "^6.1.0",
603
+ "domelementtype": "^2.3.0",
604
+ "domhandler": "^5.0.3",
605
+ "domutils": "^3.0.1"
606
+ },
607
+ "funding": {
608
+ "url": "https://github.com/sponsors/fb55"
609
+ }
610
+ },
611
+ "node_modules/cheerio/node_modules/undici": {
612
+ "version": "7.24.4",
613
+ "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.4.tgz",
614
+ "integrity": "sha512-BM/JzwwaRXxrLdElV2Uo6cTLEjhSb3WXboncJamZ15NgUURmvlXvxa6xkwIOILIjPNo9i8ku136ZvWV0Uly8+w==",
615
+ "license": "MIT",
616
+ "engines": {
617
+ "node": ">=20.18.1"
618
+ }
619
+ },
620
  "node_modules/chownr": {
621
  "version": "1.1.4",
622
  "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
 
980
  "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==",
981
  "license": "MIT"
982
  },
983
+ "node_modules/css-select": {
984
+ "version": "5.2.2",
985
+ "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz",
986
+ "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==",
987
+ "license": "BSD-2-Clause",
988
+ "dependencies": {
989
+ "boolbase": "^1.0.0",
990
+ "css-what": "^6.1.0",
991
+ "domhandler": "^5.0.2",
992
+ "domutils": "^3.0.1",
993
+ "nth-check": "^2.0.1"
994
+ },
995
+ "funding": {
996
+ "url": "https://github.com/sponsors/fb55"
997
+ }
998
+ },
999
+ "node_modules/css-what": {
1000
+ "version": "6.2.2",
1001
+ "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz",
1002
+ "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==",
1003
+ "license": "BSD-2-Clause",
1004
+ "engines": {
1005
+ "node": ">= 6"
1006
+ },
1007
+ "funding": {
1008
+ "url": "https://github.com/sponsors/fb55"
1009
+ }
1010
+ },
1011
  "node_modules/data-uri-to-buffer": {
1012
  "version": "4.0.1",
1013
  "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
 
1103
  "integrity": "sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==",
1104
  "license": "MIT"
1105
  },
1106
+ "node_modules/dom-serializer": {
1107
+ "version": "2.0.0",
1108
+ "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
1109
+ "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
1110
+ "license": "MIT",
1111
+ "dependencies": {
1112
+ "domelementtype": "^2.3.0",
1113
+ "domhandler": "^5.0.2",
1114
+ "entities": "^4.2.0"
1115
+ },
1116
+ "funding": {
1117
+ "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
1118
+ }
1119
+ },
1120
+ "node_modules/domelementtype": {
1121
+ "version": "2.3.0",
1122
+ "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
1123
+ "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
1124
+ "funding": [
1125
+ {
1126
+ "type": "github",
1127
+ "url": "https://github.com/sponsors/fb55"
1128
+ }
1129
+ ],
1130
+ "license": "BSD-2-Clause"
1131
+ },
1132
+ "node_modules/domhandler": {
1133
+ "version": "5.0.3",
1134
+ "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
1135
+ "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
1136
+ "license": "BSD-2-Clause",
1137
+ "dependencies": {
1138
+ "domelementtype": "^2.3.0"
1139
+ },
1140
+ "engines": {
1141
+ "node": ">= 4"
1142
+ },
1143
+ "funding": {
1144
+ "url": "https://github.com/fb55/domhandler?sponsor=1"
1145
+ }
1146
+ },
1147
+ "node_modules/domutils": {
1148
+ "version": "3.2.2",
1149
+ "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz",
1150
+ "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==",
1151
+ "license": "BSD-2-Clause",
1152
+ "dependencies": {
1153
+ "dom-serializer": "^2.0.0",
1154
+ "domelementtype": "^2.3.0",
1155
+ "domhandler": "^5.0.3"
1156
+ },
1157
+ "funding": {
1158
+ "url": "https://github.com/fb55/domutils?sponsor=1"
1159
+ }
1160
+ },
1161
  "node_modules/dunder-proto": {
1162
  "version": "1.0.1",
1163
  "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
 
1193
  "node": ">= 0.8"
1194
  }
1195
  },
1196
+ "node_modules/encoding-sniffer": {
1197
+ "version": "0.2.1",
1198
+ "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz",
1199
+ "integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==",
1200
+ "license": "MIT",
1201
+ "dependencies": {
1202
+ "iconv-lite": "^0.6.3",
1203
+ "whatwg-encoding": "^3.1.1"
1204
+ },
1205
+ "funding": {
1206
+ "url": "https://github.com/fb55/encoding-sniffer?sponsor=1"
1207
+ }
1208
+ },
1209
+ "node_modules/encoding-sniffer/node_modules/iconv-lite": {
1210
+ "version": "0.6.3",
1211
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
1212
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
1213
+ "license": "MIT",
1214
+ "dependencies": {
1215
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
1216
+ },
1217
+ "engines": {
1218
+ "node": ">=0.10.0"
1219
+ }
1220
+ },
1221
  "node_modules/end-of-stream": {
1222
  "version": "1.4.5",
1223
  "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
 
1227
  "once": "^1.4.0"
1228
  }
1229
  },
1230
+ "node_modules/entities": {
1231
+ "version": "4.5.0",
1232
+ "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
1233
+ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
1234
+ "license": "BSD-2-Clause",
1235
+ "engines": {
1236
+ "node": ">=0.12"
1237
+ },
1238
+ "funding": {
1239
+ "url": "https://github.com/fb55/entities?sponsor=1"
1240
+ }
1241
+ },
1242
  "node_modules/es-define-property": {
1243
  "version": "1.0.1",
1244
  "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
 
1489
  "url": "https://opencollective.com/express"
1490
  }
1491
  },
1492
+ "node_modules/follow-redirects": {
1493
+ "version": "1.15.11",
1494
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
1495
+ "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
1496
+ "funding": [
1497
+ {
1498
+ "type": "individual",
1499
+ "url": "https://github.com/sponsors/RubenVerborgh"
1500
+ }
1501
+ ],
1502
+ "license": "MIT",
1503
+ "engines": {
1504
+ "node": ">=4.0"
1505
+ },
1506
+ "peerDependenciesMeta": {
1507
+ "debug": {
1508
+ "optional": true
1509
+ }
1510
+ }
1511
+ },
1512
  "node_modules/fontkit": {
1513
  "version": "2.0.4",
1514
  "resolved": "https://registry.npmjs.org/fontkit/-/fontkit-2.0.4.tgz",
 
1790
  "node": ">= 0.4"
1791
  }
1792
  },
1793
+ "node_modules/htmlparser2": {
1794
+ "version": "10.1.0",
1795
+ "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz",
1796
+ "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==",
1797
+ "funding": [
1798
+ "https://github.com/fb55/htmlparser2?sponsor=1",
1799
+ {
1800
+ "type": "github",
1801
+ "url": "https://github.com/sponsors/fb55"
1802
+ }
1803
+ ],
1804
+ "license": "MIT",
1805
+ "dependencies": {
1806
+ "domelementtype": "^2.3.0",
1807
+ "domhandler": "^5.0.3",
1808
+ "domutils": "^3.2.2",
1809
+ "entities": "^7.0.1"
1810
+ }
1811
+ },
1812
+ "node_modules/htmlparser2/node_modules/entities": {
1813
+ "version": "7.0.1",
1814
+ "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz",
1815
+ "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==",
1816
+ "license": "BSD-2-Clause",
1817
+ "engines": {
1818
+ "node": ">=0.12"
1819
+ },
1820
+ "funding": {
1821
+ "url": "https://github.com/fb55/entities?sponsor=1"
1822
+ }
1823
+ },
1824
  "node_modules/http-basic": {
1825
  "version": "8.1.3",
1826
  "resolved": "https://registry.npmjs.org/http-basic/-/http-basic-8.1.3.tgz",
 
1836
  "node": ">=6.0.0"
1837
  }
1838
  },
1839
+ "node_modules/http-cookie-agent": {
1840
+ "version": "6.0.8",
1841
+ "resolved": "https://registry.npmjs.org/http-cookie-agent/-/http-cookie-agent-6.0.8.tgz",
1842
+ "integrity": "sha512-qnYh3yLSr2jBsTYkw11elq+T361uKAJaZ2dR4cfYZChw1dt9uL5t3zSUwehoqqVb4oldk1BpkXKm2oat8zV+oA==",
1843
+ "license": "MIT",
1844
+ "dependencies": {
1845
+ "agent-base": "^7.1.3"
1846
+ },
1847
+ "engines": {
1848
+ "node": ">=18.0.0"
1849
+ },
1850
+ "funding": {
1851
+ "url": "https://github.com/sponsors/3846masa"
1852
+ },
1853
+ "peerDependencies": {
1854
+ "tough-cookie": "^4.0.0 || ^5.0.0",
1855
+ "undici": "^5.11.0 || ^6.0.0"
1856
+ },
1857
+ "peerDependenciesMeta": {
1858
+ "undici": {
1859
+ "optional": true
1860
+ }
1861
+ }
1862
+ },
1863
  "node_modules/http-errors": {
1864
  "version": "2.0.1",
1865
  "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
 
2458
  "node": ">=0.10.0"
2459
  }
2460
  },
2461
+ "node_modules/nth-check": {
2462
+ "version": "2.1.1",
2463
+ "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
2464
+ "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
2465
+ "license": "BSD-2-Clause",
2466
+ "dependencies": {
2467
+ "boolbase": "^1.0.0"
2468
+ },
2469
+ "funding": {
2470
+ "url": "https://github.com/fb55/nth-check?sponsor=1"
2471
+ }
2472
+ },
2473
  "node_modules/object-inspect": {
2474
  "version": "1.13.4",
2475
  "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
 
2594
  "resolved": "https://registry.npmjs.org/parse-cache-control/-/parse-cache-control-1.0.1.tgz",
2595
  "integrity": "sha512-60zvsJReQPX5/QP0Kzfd/VrpjScIQ7SHBW6bFCYfEP+fp0Eppr1SHhIO5nd1PjZtvclzSzES9D/p5nFJurwfWg=="
2596
  },
2597
+ "node_modules/parse5": {
2598
+ "version": "7.3.0",
2599
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
2600
+ "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
2601
+ "license": "MIT",
2602
+ "dependencies": {
2603
+ "entities": "^6.0.0"
2604
+ },
2605
+ "funding": {
2606
+ "url": "https://github.com/inikulin/parse5?sponsor=1"
2607
+ }
2608
+ },
2609
+ "node_modules/parse5-htmlparser2-tree-adapter": {
2610
+ "version": "7.1.0",
2611
+ "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz",
2612
+ "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==",
2613
+ "license": "MIT",
2614
+ "dependencies": {
2615
+ "domhandler": "^5.0.3",
2616
+ "parse5": "^7.0.0"
2617
+ },
2618
+ "funding": {
2619
+ "url": "https://github.com/inikulin/parse5?sponsor=1"
2620
+ }
2621
+ },
2622
+ "node_modules/parse5-parser-stream": {
2623
+ "version": "7.1.2",
2624
+ "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz",
2625
+ "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==",
2626
+ "license": "MIT",
2627
+ "dependencies": {
2628
+ "parse5": "^7.0.0"
2629
+ },
2630
+ "funding": {
2631
+ "url": "https://github.com/inikulin/parse5?sponsor=1"
2632
+ }
2633
+ },
2634
+ "node_modules/parse5/node_modules/entities": {
2635
+ "version": "6.0.1",
2636
+ "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
2637
+ "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
2638
+ "license": "BSD-2-Clause",
2639
+ "engines": {
2640
+ "node": ">=0.12"
2641
+ },
2642
+ "funding": {
2643
+ "url": "https://github.com/fb55/entities?sponsor=1"
2644
+ }
2645
+ },
2646
  "node_modules/parseurl": {
2647
  "version": "1.3.3",
2648
  "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
 
2812
  "node": ">= 0.10"
2813
  }
2814
  },
2815
+ "node_modules/proxy-from-env": {
2816
+ "version": "1.1.0",
2817
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
2818
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
2819
+ "license": "MIT"
2820
+ },
2821
+ "node_modules/psl": {
2822
+ "version": "1.15.0",
2823
+ "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz",
2824
+ "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==",
2825
+ "license": "MIT",
2826
+ "dependencies": {
2827
+ "punycode": "^2.3.1"
2828
+ },
2829
+ "funding": {
2830
+ "url": "https://github.com/sponsors/lupomontero"
2831
+ }
2832
+ },
2833
  "node_modules/pump": {
2834
  "version": "3.0.3",
2835
  "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz",
 
2840
  "once": "^1.3.1"
2841
  }
2842
  },
2843
+ "node_modules/punycode": {
2844
+ "version": "2.3.1",
2845
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
2846
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
2847
+ "license": "MIT",
2848
+ "engines": {
2849
+ "node": ">=6"
2850
+ }
2851
+ },
2852
  "node_modules/qs": {
2853
  "version": "6.15.0",
2854
  "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz",
 
2864
  "url": "https://github.com/sponsors/ljharb"
2865
  }
2866
  },
2867
+ "node_modules/querystringify": {
2868
+ "version": "2.2.0",
2869
+ "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
2870
+ "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==",
2871
+ "license": "MIT"
2872
+ },
2873
  "node_modules/range-parser": {
2874
  "version": "1.2.1",
2875
  "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
 
2932
  "node": ">=0.10.0"
2933
  }
2934
  },
2935
+ "node_modules/requires-port": {
2936
+ "version": "1.0.0",
2937
+ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
2938
+ "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
2939
+ "license": "MIT"
2940
+ },
2941
  "node_modules/restore-cursor": {
2942
  "version": "3.1.0",
2943
  "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz",
 
3468
  "node": ">=0.6"
3469
  }
3470
  },
3471
+ "node_modules/tough-cookie": {
3472
+ "version": "4.1.4",
3473
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz",
3474
+ "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==",
3475
+ "license": "BSD-3-Clause",
3476
+ "dependencies": {
3477
+ "psl": "^1.1.33",
3478
+ "punycode": "^2.1.1",
3479
+ "universalify": "^0.2.0",
3480
+ "url-parse": "^1.5.3"
3481
+ },
3482
+ "engines": {
3483
+ "node": ">=6"
3484
+ }
3485
+ },
3486
+ "node_modules/tough-cookie/node_modules/universalify": {
3487
+ "version": "0.2.0",
3488
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
3489
+ "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
3490
+ "license": "MIT",
3491
+ "engines": {
3492
+ "node": ">= 4.0.0"
3493
+ }
3494
+ },
3495
  "node_modules/truncate-utf8-bytes": {
3496
  "version": "1.0.2",
3497
  "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz",
 
3576
  "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==",
3577
  "license": "MIT"
3578
  },
 
 
 
 
 
 
3579
  "node_modules/unicode-properties": {
3580
  "version": "1.4.1",
3581
  "resolved": "https://registry.npmjs.org/unicode-properties/-/unicode-properties-1.4.1.tgz",
 
3620
  "node": ">= 0.8"
3621
  }
3622
  },
3623
+ "node_modules/url-parse": {
3624
+ "version": "1.5.10",
3625
+ "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
3626
+ "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
3627
+ "license": "MIT",
3628
+ "dependencies": {
3629
+ "querystringify": "^2.1.1",
3630
+ "requires-port": "^1.0.0"
3631
+ }
3632
+ },
3633
  "node_modules/utf8-byte-length": {
3634
  "version": "1.0.5",
3635
  "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz",
 
3669
  "node": ">= 8"
3670
  }
3671
  },
3672
+ "node_modules/whatwg-encoding": {
3673
+ "version": "3.1.1",
3674
+ "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
3675
+ "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==",
3676
+ "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation",
3677
+ "license": "MIT",
3678
+ "dependencies": {
3679
+ "iconv-lite": "0.6.3"
3680
+ },
3681
+ "engines": {
3682
+ "node": ">=18"
3683
+ }
3684
+ },
3685
+ "node_modules/whatwg-encoding/node_modules/iconv-lite": {
3686
+ "version": "0.6.3",
3687
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
3688
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
3689
+ "license": "MIT",
3690
+ "dependencies": {
3691
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
3692
+ },
3693
+ "engines": {
3694
+ "node": ">=0.10.0"
3695
+ }
3696
+ },
3697
+ "node_modules/whatwg-mimetype": {
3698
+ "version": "4.0.0",
3699
+ "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz",
3700
+ "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==",
3701
+ "license": "MIT",
3702
+ "engines": {
3703
+ "node": ">=18"
3704
+ }
3705
+ },
3706
  "node_modules/which": {
3707
  "version": "2.0.2",
3708
  "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
package.json CHANGED
@@ -24,7 +24,10 @@
24
  "dependencies": {
25
  "adm-zip": "^0.5.12",
26
  "aes-js": "^3.1.2",
 
 
27
  "better-sqlite3": "^12.4.1",
 
28
  "express": "^5.2.1",
29
  "fs-extra": "^11.2.0",
30
  "inquirer": "^8.2.7",
@@ -39,6 +42,7 @@
39
  "sanitize-filename": "^1.6.3",
40
  "svg-to-pdfkit": "^0.1.8",
41
  "sync-request": "^6.1.0",
 
42
  "ws": "^8.19.0",
43
  "xml2js": "^0.6.2",
44
  "yargs": "^17.5.1",
 
24
  "dependencies": {
25
  "adm-zip": "^0.5.12",
26
  "aes-js": "^3.1.2",
27
+ "axios": "^1.13.6",
28
+ "axios-cookiejar-support": "^5.0.5",
29
  "better-sqlite3": "^12.4.1",
30
+ "cheerio": "^1.2.0",
31
  "express": "^5.2.1",
32
  "fs-extra": "^11.2.0",
33
  "inquirer": "^8.2.7",
 
42
  "sanitize-filename": "^1.6.3",
43
  "svg-to-pdfkit": "^0.1.8",
44
  "sync-request": "^6.1.0",
45
+ "tough-cookie": "^4.1.4",
46
  "ws": "^8.19.0",
47
  "xml2js": "^0.6.2",
48
  "yargs": "^17.5.1",
providers/bsmart.js CHANGED
@@ -10,8 +10,8 @@ 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 { performBsmartLogin, getUserInfo, getBooks, getBookInfo, getBookResources, getResourceLinks } from './src/api.js';
15
 
16
  const prompt = PromptSync({ sigint: true });
17
 
 
10
  import path from 'path';
11
  import pLimit from 'p-limit';
12
 
13
+ import { fetchEncryptionKey, decryptFile } from './src/bsmart/crypto.js';
14
+ import { performBsmartLogin, getUserInfo, getBooks, getBookInfo, getBookResources, getResourceLinks } from './src/bsmart/api.js';
15
 
16
  const prompt = PromptSync({ sigint: true });
17
 
providers/sanoma.js CHANGED
@@ -8,6 +8,7 @@ import fsExtra from 'fs-extra';
8
  import path from 'path';
9
  import { spawn } from 'child_process';
10
  import { pipeline } from 'stream';
 
11
 
12
  const prompt = PromptSync({ sigint: true });
13
  const SANOMA_BASE_URLS = [
@@ -77,20 +78,17 @@ export async function run(options = {}) {
77
 
78
  console.log("Avvio provider Sanoma...");
79
 
80
- const sessionTmp = process.env.OURBOOKS_SESSION_TMP || 'tmp';
81
- const outputDir = process.env.OURBOOKS_OUTPUT_DIR || '.';
82
- const doOcr = (ocr || argv.ocr) === 'on';
83
 
84
- await fsExtra.ensureDir(sessionTmp);
85
-
86
- let userId = id || argv.id;
87
- let userPassword = password || argv.password;
88
- let bookGedi = gedi || argv.gedi;
89
 
90
- if (!userId) userId = prompt("Enter account email: ");
91
- if (!userPassword) userPassword = prompt("Enter account password: ", { echo: '*' });
 
92
 
93
- function promisify(api) {
94
  return function (...args) {
95
  return new Promise((resolve, reject) => {
96
  api(...args, (err, response) => {
@@ -99,11 +97,11 @@ export async function run(options = {}) {
99
  });
100
  });
101
  };
102
- }
103
 
104
- const yauzlFromFile = promisify(yauzl.open);
105
 
106
- function runOCR(inputPdf, outputPdf) {
107
  return new Promise((resolve, reject) => {
108
  const ocr = spawn('ocrmypdf', [inputPdf, outputPdf], { stdio: 'inherit' });
109
 
@@ -120,9 +118,9 @@ export async function run(options = {}) {
120
  else reject(new Error(`OCRmyPDF exited with code ${code}`));
121
  });
122
  });
123
- }
124
 
125
- async function fetchSanomaJson(pathname, init = {}) {
126
  let lastError = null;
127
 
128
  for (const base of SANOMA_BASE_URLS) {
@@ -141,17 +139,17 @@ export async function run(options = {}) {
141
  }
142
 
143
  throw lastError || new Error('Richiesta Sanoma fallita');
144
- }
145
 
146
- function getAccessToken(userAuth) {
147
  return userAuth?.result?.data?.access_token
148
  || userAuth?.data?.access_token
149
  || userAuth?.access_token
150
  || userAuth?.token
151
  || null;
152
- }
153
 
154
- function getBooksPage(payload) {
155
  const data = payload?.result?.data || payload?.data || payload?.books || [];
156
  const totalSize = payload?.result?.total_size ?? payload?.total_size ?? payload?.total ?? data.length;
157
  const rawPageSize = payload?.result?.page_size ?? payload?.page_size ?? data.length;
@@ -160,20 +158,20 @@ export async function run(options = {}) {
160
  data: Array.isArray(data) ? data : [],
161
  pages: Math.max(1, Math.ceil(totalSize / pageSize)),
162
  };
163
- }
164
 
165
- function getBookName(book) {
166
  return book?.name || book?.title || `GEDI ${book?.gedi || ''}`.trim();
167
- }
168
 
169
- function getBookDownloadUrl(book) {
170
  return book?.url_download || book?.urlDownload || book?.downloadUrl || book?.url || null;
171
- }
172
 
173
- (async () => {
174
  await fsExtra.ensureDir(sessionTmp);
175
 
176
- let book;
177
 
178
  if (argv.download) {
179
  let folder = await fs.promises.readdir(sessionTmp);
@@ -182,18 +180,43 @@ export async function run(options = {}) {
182
  process.exit(1);
183
  }
184
 
185
- let id = userId;
186
- let password = userPassword;
187
 
188
  console.log('Warning: this script might log you out of other devices');
189
 
190
- while (!id) id = prompt('Enter account email: ');
191
- while (!password) password = prompt('Enter account password: ', { echo: '*' });
 
 
 
 
 
 
 
 
 
192
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
193
  let userAuth = await fetchSanomaJson('/login', {
194
  method: 'POST',
195
  headers: { 'Content-Type': 'application/json', 'X-Timezone-Offset': '+0200' },
196
- body: JSON.stringify({ id, password }),
197
  }).catch((err) => { console.error('Failed to log in:', err.message); process.exit(1); });
198
 
199
  if (!userAuth || (userAuth.code != null && userAuth.code !== 0)) {
@@ -207,8 +230,8 @@ export async function run(options = {}) {
207
  process.exit(1);
208
  }
209
 
210
- console.log('Fetching book list');
211
- let books = {};
212
  let pages = 1;
213
  for (let i = 1; i <= pages; i++) {
214
  const newBooks = await fetchSanomaJson(`/books?app=true&page=${i}`, {
@@ -219,35 +242,41 @@ export async function run(options = {}) {
219
  pages = pageInfo.pages;
220
 
221
  for (const item of pageInfo.data) {
222
- if (!item?.gedi) continue;
223
- books[item.gedi] = item;
224
- }
 
225
  }
226
-
227
- console.log('Books:');
228
- console.table(Object.fromEntries(Object.entries(books).map(([bookId, book]) => [bookId, getBookName(book)])));
229
-
230
- let gedi = bookGedi;
231
- while (!gedi) gedi = prompt('Enter the book\'s gedi: ');
232
-
233
- book = books[gedi];
234
- if (!book) {
235
- console.error(`GEDI non trovato: ${gedi}`);
236
- process.exit(1);
237
  }
238
 
239
- const downloadUrl = getBookDownloadUrl(book);
240
  if (!downloadUrl) {
241
- console.error('URL download non trovato nel payload del libro.');
242
  process.exit(1);
243
  }
244
 
245
- console.log('Downloading "' + getBookName(book) + '"');
246
 
247
  let zip = await fetch(downloadUrl);
248
  if (!zip.ok) { console.error('Failed to download zip'); process.exit(1); }
249
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
250
  await promisify(pipeline)(zip.body, fs.createWriteStream(sessionTmp + '/book.zip'));
 
251
  } else {
252
  console.log('Skipping download');
253
  let stats = await fs.promises.stat(sessionTmp + '/book.zip');
@@ -290,7 +319,7 @@ export async function run(options = {}) {
290
  }
291
 
292
  let baseName = argv.output || options.output;
293
- if (argv.download && !baseName) baseName = getBookName(book).replace(/[\\/:*?"<>|]/g, '') + '.pdf';
294
  else if (!baseName) baseName = 'output.pdf';
295
  const outFilePath = path.join(outputDir, baseName);
296
 
@@ -319,22 +348,22 @@ export async function run(options = {}) {
319
  console.log('Done. Output:', finalOutput);
320
  console.log(`OURBOOKS_OUTPUT:${finalOutput}`);
321
  });
322
- })();
323
 
324
- async function convertPage(input, output) {
325
  try {
326
  await convertPageWithInkscape(input, output);
327
  } catch (err) {
328
  console.error('Inkscape fallito:', err.message);
329
  throw err;
330
  }
331
- }
332
 
333
- async function convertPageWithInkscape(input, output) {
334
  return new Promise((resolve, reject) => {
335
  const convert = spawn('inkscape', ['--export-filename=' + output, input]);
336
  convert.on('error', reject);
337
  convert.on('close', code => code === 0 ? resolve() : reject(new Error(`Inkscape exited with code ${code}`)));
338
  });
339
- }
340
  }
 
8
  import path from 'path';
9
  import { spawn } from 'child_process';
10
  import { pipeline } from 'stream';
11
+ import { loginSanoma, fetchBooks } from './src/sanoma/auth.js';
12
 
13
  const prompt = PromptSync({ sigint: true });
14
  const SANOMA_BASE_URLS = [
 
78
 
79
  console.log("Avvio provider Sanoma...");
80
 
81
+ const sessionTmp = process.env.OURBOOKS_SESSION_TMP || 'tmp';
82
+ const outputDir = process.env.OURBOOKS_OUTPUT_DIR || '.';
83
+ const doOcr = (ocr || argv.ocr) === 'on';
84
 
85
+ await fsExtra.ensureDir(sessionTmp);
 
 
 
 
86
 
87
+ let userId = id || argv.id;
88
+ let userPassword = password || argv.password;
89
+ let bookGedi = gedi || argv.gedi;
90
 
91
+ function promisify(api) {
92
  return function (...args) {
93
  return new Promise((resolve, reject) => {
94
  api(...args, (err, response) => {
 
97
  });
98
  });
99
  };
100
+ }
101
 
102
+ const yauzlFromFile = promisify(yauzl.open);
103
 
104
+ function runOCR(inputPdf, outputPdf) {
105
  return new Promise((resolve, reject) => {
106
  const ocr = spawn('ocrmypdf', [inputPdf, outputPdf], { stdio: 'inherit' });
107
 
 
118
  else reject(new Error(`OCRmyPDF exited with code ${code}`));
119
  });
120
  });
121
+ }
122
 
123
+ async function fetchSanomaJson(pathname, init = {}) {
124
  let lastError = null;
125
 
126
  for (const base of SANOMA_BASE_URLS) {
 
139
  }
140
 
141
  throw lastError || new Error('Richiesta Sanoma fallita');
142
+ }
143
 
144
+ function getAccessToken(userAuth) {
145
  return userAuth?.result?.data?.access_token
146
  || userAuth?.data?.access_token
147
  || userAuth?.access_token
148
  || userAuth?.token
149
  || null;
150
+ }
151
 
152
+ function getBooksPage(payload) {
153
  const data = payload?.result?.data || payload?.data || payload?.books || [];
154
  const totalSize = payload?.result?.total_size ?? payload?.total_size ?? payload?.total ?? data.length;
155
  const rawPageSize = payload?.result?.page_size ?? payload?.page_size ?? data.length;
 
158
  data: Array.isArray(data) ? data : [],
159
  pages: Math.max(1, Math.ceil(totalSize / pageSize)),
160
  };
161
+ }
162
 
163
+ function getBookName(book) {
164
  return book?.name || book?.title || `GEDI ${book?.gedi || ''}`.trim();
165
+ }
166
 
167
+ function getBookDownloadUrl(book) {
168
  return book?.url_download || book?.urlDownload || book?.downloadUrl || book?.url || null;
169
+ }
170
 
171
+ (async () => {
172
  await fsExtra.ensureDir(sessionTmp);
173
 
174
+ let targetBookName = "Sanoma Book";
175
 
176
  if (argv.download) {
177
  let folder = await fs.promises.readdir(sessionTmp);
 
180
  process.exit(1);
181
  }
182
 
183
+ let loginId = userId;
184
+ let loginPassword = userPassword;
185
 
186
  console.log('Warning: this script might log you out of other devices');
187
 
188
+ while (!loginId) loginId = prompt('Enter account email: ');
189
+ while (!loginPassword) loginPassword = prompt('Enter account password: ', { echo: '*' });
190
+
191
+ console.log('Logging in to MyPlace to retrieve books...');
192
+ const skClient = await loginSanoma(loginId, loginPassword).catch(err => {
193
+ console.error('Failed to log in via MyPlace:', err.message);
194
+ process.exit(1);
195
+ });
196
+
197
+ console.log('Fetching book list...');
198
+ const skBooks = await fetchBooks(skClient);
199
 
200
+ let tableObj = {};
201
+ for (const b of skBooks) {
202
+ for (const p of b.products) {
203
+ tableObj[p.gedi] = p.name;
204
+ }
205
+ }
206
+
207
+ console.log('Books (MyPlace graph):');
208
+ console.table(tableObj);
209
+
210
+ let gedi = bookGedi;
211
+ while (!gedi) gedi = prompt('Enter the book\'s gedi: ');
212
+
213
+ targetBookName = tableObj[gedi] || `GEDI ${gedi}`;
214
+
215
+ console.log('Logging in to offline API to retrieve download URL...');
216
  let userAuth = await fetchSanomaJson('/login', {
217
  method: 'POST',
218
  headers: { 'Content-Type': 'application/json', 'X-Timezone-Offset': '+0200' },
219
+ body: JSON.stringify({ id: loginId, password: loginPassword }),
220
  }).catch((err) => { console.error('Failed to log in:', err.message); process.exit(1); });
221
 
222
  if (!userAuth || (userAuth.code != null && userAuth.code !== 0)) {
 
230
  process.exit(1);
231
  }
232
 
233
+ console.log('Searching for download bundle...');
234
+ let downloadUrl = null;
235
  let pages = 1;
236
  for (let i = 1; i <= pages; i++) {
237
  const newBooks = await fetchSanomaJson(`/books?app=true&page=${i}`, {
 
242
  pages = pageInfo.pages;
243
 
244
  for (const item of pageInfo.data) {
245
+ if (item?.gedi == gedi) {
246
+ downloadUrl = getBookDownloadUrl(item);
247
+ break;
248
+ }
249
  }
250
+ if (downloadUrl) break;
 
 
 
 
 
 
 
 
 
 
251
  }
252
 
 
253
  if (!downloadUrl) {
254
+ console.error(`URL download non trovato per questo GEDI (${gedi}) nella offline API. Errore bundle non disponibile.`);
255
  process.exit(1);
256
  }
257
 
258
+ console.log('Downloading "' + targetBookName + '"');
259
 
260
  let zip = await fetch(downloadUrl);
261
  if (!zip.ok) { console.error('Failed to download zip'); process.exit(1); }
262
 
263
+ const totalBytes = parseInt(zip.headers.get('content-length'), 10);
264
+ let downloadedBytes = 0;
265
+ let lastLoggedPercent = 0;
266
+
267
+ zip.body.on('data', (chunk) => {
268
+ downloadedBytes += chunk.length;
269
+ if (totalBytes) {
270
+ const percent = Math.floor((downloadedBytes / totalBytes) * 100);
271
+ if (percent >= lastLoggedPercent + 10) {
272
+ process.stdout.write(`...${percent}%`);
273
+ lastLoggedPercent = percent;
274
+ }
275
+ }
276
+ });
277
+
278
  await promisify(pipeline)(zip.body, fs.createWriteStream(sessionTmp + '/book.zip'));
279
+ console.log('\nDownload completato!');
280
  } else {
281
  console.log('Skipping download');
282
  let stats = await fs.promises.stat(sessionTmp + '/book.zip');
 
319
  }
320
 
321
  let baseName = argv.output || options.output;
322
+ if (argv.download && !baseName) baseName = targetBookName.replace(/[\\/:*?"<>|]/g, '') + '.pdf';
323
  else if (!baseName) baseName = 'output.pdf';
324
  const outFilePath = path.join(outputDir, baseName);
325
 
 
348
  console.log('Done. Output:', finalOutput);
349
  console.log(`OURBOOKS_OUTPUT:${finalOutput}`);
350
  });
351
+ })();
352
 
353
+ async function convertPage(input, output) {
354
  try {
355
  await convertPageWithInkscape(input, output);
356
  } catch (err) {
357
  console.error('Inkscape fallito:', err.message);
358
  throw err;
359
  }
360
+ }
361
 
362
+ async function convertPageWithInkscape(input, output) {
363
  return new Promise((resolve, reject) => {
364
  const convert = spawn('inkscape', ['--export-filename=' + output, input]);
365
  convert.on('error', reject);
366
  convert.on('close', code => code === 0 ? resolve() : reject(new Error(`Inkscape exited with code ${code}`)));
367
  });
368
+ }
369
  }
providers/src/{api.js → bsmart/api.js} RENAMED
File without changes
providers/src/{crypto.js → bsmart/crypto.js} RENAMED
File without changes
providers/src/{pdf.js → bsmart/pdf.js} RENAMED
File without changes
providers/src/sanoma/auth.js ADDED
@@ -0,0 +1,464 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import axios from 'axios';
2
+ import { wrapper } from 'axios-cookiejar-support';
3
+ import { CookieJar } from 'tough-cookie';
4
+ import * as cheerio from 'cheerio';
5
+ import { URL } from 'url';
6
+
7
+ export async function loginSanoma(email, password) {
8
+ const jar = new CookieJar();
9
+ const client = wrapper(axios.create({
10
+ jar,
11
+ withCredentials: true,
12
+ maxRedirects: 0,
13
+ validateStatus: (status) => status >= 200 && status < 400,
14
+ headers: {
15
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
16
+ 'Accept-Language': 'it-IT,it;q=0.9,en-US;q=0.8,en;q=0.7'
17
+ }
18
+ }));
19
+
20
+ const redirectUri = 'https://place.sanoma.it/';
21
+ let authUrl = null;
22
+ let clientId = null;
23
+
24
+ try {
25
+ // init session cookies
26
+ await client.get('https://place.sanoma.it/login', {
27
+ headers: {
28
+ 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
29
+ }
30
+ });
31
+
32
+ const initParams = new URLSearchParams({
33
+ ref: 'https://place.sanoma.it/',
34
+ context: '',
35
+ text: email
36
+ });
37
+
38
+ const initRes = await client.post('https://place.sanoma.it/login?/status', initParams.toString(), {
39
+ headers: {
40
+ 'Content-Type': 'application/x-www-form-urlencoded',
41
+ 'Accept': 'application/json',
42
+ 'Origin': 'https://place.sanoma.it',
43
+ 'Referer': 'https://place.sanoma.it/login',
44
+ 'x-sveltekit-action': 'true'
45
+ }
46
+ });
47
+
48
+ let data = initRes.data;
49
+ if (typeof data === 'string') {
50
+ try { data = JSON.parse(data); } catch (e) {}
51
+ }
52
+
53
+ if (data && data.type === 'redirect' && data.location) {
54
+ authUrl = data.location;
55
+ }
56
+ } catch (err) {
57
+ if (err.response && err.response.data && err.response.data.type === 'redirect' && err.response.data.location) {
58
+ authUrl = err.response.data.location;
59
+ } else if (err.response && err.response.headers && err.response.headers.location) {
60
+ authUrl = err.response.headers.location;
61
+ } else {
62
+ throw err;
63
+ }
64
+ }
65
+
66
+ if (!authUrl) {
67
+ throw new Error('Failed to get Auth0 redirect URL from /login?/status');
68
+ }
69
+
70
+ if (!authUrl.startsWith('http')) {
71
+ authUrl = 'https://login.sanoma.it' + (authUrl.startsWith('/') ? '' : '/') + authUrl;
72
+ }
73
+
74
+ const parsedInitUrl = new URL(authUrl);
75
+ clientId = parsedInitUrl.searchParams.get('client_id');
76
+
77
+ if (!clientId) {
78
+ throw new Error('Client ID missing from SvelteKit authorization URL');
79
+ }
80
+
81
+ let authPageRes = await client.get(authUrl, {
82
+ headers: {
83
+ 'Referer': 'https://place.sanoma.it/',
84
+ 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8'
85
+ }
86
+ });
87
+
88
+ while (authPageRes.status >= 300 && authPageRes.status < 400 && authPageRes.headers.location) {
89
+ let nextUrl = authPageRes.headers.location;
90
+ if (!nextUrl.startsWith('http')) {
91
+ nextUrl = 'https://login.sanoma.it' + nextUrl;
92
+ }
93
+ authUrl = nextUrl;
94
+ authPageRes = await client.get(authUrl, {
95
+ headers: {
96
+ 'Referer': 'https://place.sanoma.it/',
97
+ 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8'
98
+ }
99
+ });
100
+ }
101
+
102
+ const $ = cheerio.load(authPageRes.data);
103
+
104
+ const cookies = await jar.getCookies('https://login.sanoma.it');
105
+ const csrfCookie = cookies.find(c => c.key === '_csrf');
106
+ const csrfToken = $('input[name="_csrf"]').val() || (csrfCookie ? csrfCookie.value : '');
107
+
108
+ const parsedUrl = new URL(authUrl);
109
+ const state = parsedUrl.searchParams.get('state');
110
+
111
+ if (!state) {
112
+ throw new Error('State parameter not found in Auth0 URL');
113
+ }
114
+
115
+ const loginPayload = {
116
+ client_id: clientId,
117
+ redirect_uri: redirectUri,
118
+ tenant: "sanoma-italy",
119
+ response_type: "code",
120
+ scope: "openid profile email",
121
+ state: state,
122
+ connection: "Sanoma-Italy-Database",
123
+ username: email,
124
+ password: password,
125
+ popup_options: {},
126
+ sso: true,
127
+ protocol: "oauth2",
128
+ _csrf: csrfToken,
129
+ _intstate: "deprecated"
130
+ };
131
+
132
+ let loginRes;
133
+ try {
134
+ loginRes = await client.post('https://login.sanoma.it/usernamepassword/login', loginPayload, {
135
+ headers: {
136
+ 'Content-Type': 'application/json',
137
+ 'Origin': 'https://login.sanoma.it',
138
+ 'Referer': authUrl,
139
+ 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
140
+ }
141
+ });
142
+ } catch (err) {
143
+ throw new Error(`Login failed: ${err.response ? err.response.status : 'Unknown'} ${err.response ? err.response.statusText : ''}`);
144
+ }
145
+
146
+ const $login = cheerio.load(loginRes.data);
147
+ const wa = $login('input[name="wa"]').val();
148
+ const wresult = $login('input[name="wresult"]').val();
149
+ const wctx = $login('input[name="wctx"]').val();
150
+
151
+ if (!wa || !wresult || !wctx) {
152
+ console.error('Callback form fields not found');
153
+ throw new Error('Login failed: callback form not found (wrong credentials?)');
154
+ }
155
+
156
+ let finalCodeUrl = null;
157
+
158
+ try {
159
+ const callbackRes = await client.post('https://login.sanoma.it/login/callback',
160
+ `wa=${encodeURIComponent(wa)}&wresult=${encodeURIComponent(wresult)}&wctx=${encodeURIComponent(wctx)}`,
161
+ {
162
+ headers: {
163
+ 'Content-Type': 'application/x-www-form-urlencoded',
164
+ 'Origin': 'https://login.sanoma.it',
165
+ 'Referer': 'https://login.sanoma.it/',
166
+ 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
167
+ }
168
+ }
169
+ );
170
+
171
+ if (callbackRes.status >= 300 && callbackRes.status < 400) {
172
+ finalCodeUrl = callbackRes.headers.location;
173
+ }
174
+ } catch(err) {
175
+ if (err.response && err.response.status >= 300 && err.response.status < 400) {
176
+ finalCodeUrl = err.response.headers.location;
177
+ } else {
178
+ throw err;
179
+ }
180
+ }
181
+
182
+ if (!finalCodeUrl) {
183
+ throw new Error('Final redirect URL not found after callback');
184
+ }
185
+
186
+ if (!finalCodeUrl.startsWith('http')) {
187
+ if (finalCodeUrl.startsWith('/authorize')) {
188
+ finalCodeUrl = 'https://login.sanoma.it' + finalCodeUrl;
189
+ } else {
190
+ finalCodeUrl = 'https://place.sanoma.it' + (finalCodeUrl.startsWith('/') ? '' : '/') + finalCodeUrl;
191
+ }
192
+ }
193
+
194
+ let currentUrl = finalCodeUrl;
195
+ let redirectCount = 0;
196
+ const maxRedirects = 15;
197
+
198
+ while (redirectCount < maxRedirects) {
199
+ try {
200
+ const res = await client.get(currentUrl, {
201
+ headers: {
202
+ 'Referer': redirectCount === 0 ? 'https://login.sanoma.it/' : currentUrl,
203
+ 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
204
+ }
205
+ });
206
+
207
+ if (res.status >= 300 && res.status < 400 && res.headers.location) {
208
+ let nextUrl = res.headers.location;
209
+
210
+ if (!nextUrl.startsWith('http')) {
211
+ if (nextUrl.startsWith('/authorize')) {
212
+ nextUrl = 'https://login.sanoma.it' + nextUrl;
213
+ } else {
214
+ nextUrl = 'https://place.sanoma.it' + (nextUrl.startsWith('/') ? '' : '/') + nextUrl;
215
+ }
216
+ }
217
+
218
+ currentUrl = nextUrl;
219
+ redirectCount++;
220
+ } else {
221
+ break;
222
+ }
223
+ } catch (err) {
224
+ if (err.response && err.response.status >= 300 && err.response.status < 400) {
225
+ let nextUrl = err.response.headers.location;
226
+
227
+ if (!nextUrl.startsWith('http')) {
228
+ if (nextUrl.startsWith('/authorize')) {
229
+ nextUrl = 'https://login.sanoma.it' + nextUrl;
230
+ } else {
231
+ nextUrl = 'https://place.sanoma.it' + (nextUrl.startsWith('/') ? '' : '/') + nextUrl;
232
+ }
233
+ }
234
+
235
+ currentUrl = nextUrl;
236
+ redirectCount++;
237
+ } else {
238
+ break;
239
+ }
240
+ }
241
+ }
242
+
243
+ return client;
244
+ }
245
+
246
+ export async function fetchBooks(client) {
247
+ const response = await client.get('https://place.sanoma.it/prodotti_digitali/__data.json', {
248
+ headers: {
249
+ 'Referer': 'https://place.sanoma.it/prodotti_digitali',
250
+ 'Accept': 'application/json',
251
+ 'X-Sveltekit-Invalidated': '01'
252
+ }
253
+ });
254
+
255
+ const lines = response.data.split('\n').filter(line => line.trim());
256
+ const jsonObjects = lines.map(line => JSON.parse(line));
257
+
258
+ let allData = [];
259
+
260
+ jsonObjects.forEach(obj => {
261
+ if (obj.data && Array.isArray(obj.data)) {
262
+ for (let i = 0; i < obj.data.length; i++) {
263
+ if (obj.data[i] !== undefined) {
264
+ allData[i] = obj.data[i];
265
+ }
266
+ }
267
+ }
268
+ if (obj.nodes) {
269
+ obj.nodes.forEach(node => {
270
+ if (node && Array.isArray(node.data)) {
271
+ for (let i = 0; i < node.data.length; i++) {
272
+ if (node.data[i] !== undefined) {
273
+ allData[i] = node.data[i];
274
+ }
275
+ }
276
+ }
277
+ });
278
+ }
279
+ });
280
+
281
+ jsonObjects.filter(obj => obj.type === 'chunk' && obj.data).forEach(chunk => {
282
+ let chunkData = chunk.data;
283
+ if (Array.isArray(chunkData[0])) chunkData = chunkData[0];
284
+ for (let i = 0; i < chunkData.length; i++) {
285
+ if (chunkData[i] !== undefined) {
286
+ allData[i] = chunkData[i];
287
+ }
288
+ }
289
+ });
290
+
291
+ const books = [];
292
+ const seenOperas = new Set();
293
+
294
+ // prevent ram out of limit (lol)
295
+ const resolved = new Map();
296
+
297
+ function decompressValue(val) {
298
+ if (typeof val === 'number') {
299
+ if (val < 0 || val >= allData.length || allData[val] === undefined) return val;
300
+ if (resolved.has(val)) return resolved.get(val);
301
+
302
+ const target = allData[val];
303
+ if (Array.isArray(target)) {
304
+ const newArr = [];
305
+ resolved.set(val, newArr); // register immediately before recurse
306
+ for (let j = 0; j < target.length; j++) {
307
+ newArr.push(decompressValue(target[j]));
308
+ }
309
+ return newArr;
310
+ } else if (target && typeof target === 'object') {
311
+ const newObj = {};
312
+ resolved.set(val, newObj); // register immediately before recurse
313
+ for (const key in target) {
314
+ newObj[key] = decompressValue(target[key]);
315
+ }
316
+ return newObj;
317
+ } else {
318
+ resolved.set(val, target);
319
+ return target;
320
+ }
321
+ }
322
+ return val;
323
+ }
324
+
325
+ for (let i = 0; i < allData.length; i++) {
326
+ const item = allData[i];
327
+
328
+ if (item && typeof item === 'object' && !Array.isArray(item) && 'opera_id' in item && 'display_name' in item) {
329
+
330
+ const fullyResolved = decompressValue(i);
331
+
332
+ if (!fullyResolved || !fullyResolved.opera_id || seenOperas.has(fullyResolved.opera_id)) {
333
+ continue;
334
+ }
335
+
336
+ seenOperas.add(fullyResolved.opera_id);
337
+
338
+ const productsMap = new Map();
339
+ const crawlVisited = new Set();
340
+
341
+ function extractProducts(node, namePath, inheritedIsbn) {
342
+ if (!node || typeof node !== 'object') return;
343
+ if (crawlVisited.has(node)) return;
344
+ crawlVisited.add(node);
345
+
346
+ let currentNames = [...namePath];
347
+ const potentialNames = [
348
+ node.display_name,
349
+ node.title,
350
+ node.name,
351
+ node.category_label,
352
+ node.category_name
353
+ ];
354
+
355
+ for (const n of potentialNames) {
356
+ const str = String(n || '').trim();
357
+ if (str && str !== 'Prodotti' && str !== 'null' && str !== 'undefined' && str !== '[object Object]' && !/^\d+$/.test(str)) {
358
+ let isRedundant = false;
359
+ for (let j = 0; j < currentNames.length; j++) {
360
+ const existing = currentNames[j];
361
+ if (existing.toLowerCase() === str.toLowerCase()) {
362
+ isRedundant = true; break;
363
+ }
364
+ if (str.toLowerCase().includes(existing.toLowerCase()) && str.length > existing.length) {
365
+ currentNames[j] = str;
366
+ isRedundant = true; break;
367
+ }
368
+ if (existing.toLowerCase().includes(str.toLowerCase())) {
369
+ isRedundant = true; break;
370
+ }
371
+ }
372
+ if (!isRedundant) {
373
+ currentNames.push(str);
374
+ }
375
+ }
376
+ }
377
+
378
+ const currentIsbn = node.isbn || node.paper_isbn || inheritedIsbn;
379
+
380
+ // search a gedi
381
+ let gediCode = null;
382
+ if (node.external_id && /^\d{5,10}$/.test(String(node.external_id))) {
383
+ gediCode = String(node.external_id);
384
+ } else if (node.id && /^\d{5,10}$/.test(String(node.id))) {
385
+ gediCode = String(node.id);
386
+ }
387
+
388
+ if (gediCode) {
389
+ // clean names
390
+ let finalParts = [];
391
+ for (let j = 0; j < currentNames.length; j++) {
392
+ let isRedundant = false;
393
+ let currFirstWord = currentNames[j].trim().split(/[\s\-_]+/)[0].toLowerCase();
394
+
395
+ for (let k = j + 1; k < currentNames.length; k++) {
396
+ let nextFirstWord = currentNames[k].trim().split(/[\s\-_]+/)[0].toLowerCase();
397
+
398
+ // redudant names
399
+ if (currFirstWord && currFirstWord === nextFirstWord) {
400
+ isRedundant = true;
401
+ break;
402
+ }
403
+ }
404
+ if (!isRedundant) {
405
+ finalParts.push(currentNames[j]);
406
+ }
407
+ }
408
+
409
+ let finalName = finalParts.join(' - ');
410
+ if (!finalName) {
411
+ finalName = `Volume (${gediCode})`;
412
+ }
413
+
414
+ if (!productsMap.has(gediCode)) {
415
+ productsMap.set(gediCode, {
416
+ isbn: currentIsbn || '',
417
+ name: finalName,
418
+ gedi: gediCode,
419
+ resources: []
420
+ });
421
+ } else {
422
+ if (finalName.length > productsMap.get(gediCode).name.length) {
423
+ productsMap.get(gediCode).name = finalName;
424
+ }
425
+ }
426
+
427
+ productsMap.get(gediCode).resources.push({
428
+ type: node.category_name || '',
429
+ category_id: node.category_id || '',
430
+ external_id: node.external_id || '',
431
+ code: node.internal_code || '',
432
+ url: node.url || ''
433
+ });
434
+ }
435
+
436
+ if (Array.isArray(node)) {
437
+ for (let k = 0; k < node.length; k++) {
438
+ if (typeof node[k] === 'object') extractProducts(node[k], currentNames, currentIsbn);
439
+ }
440
+ } else {
441
+ for (const key in node) {
442
+ if (typeof node[key] === 'object') extractProducts(node[key], currentNames, currentIsbn);
443
+ }
444
+ }
445
+ }
446
+
447
+ let initialPath = [];
448
+ if (fullyResolved.display_name) initialPath.push(fullyResolved.display_name);
449
+
450
+ extractProducts(fullyResolved.included || fullyResolved, initialPath, '');
451
+
452
+ // independent names
453
+ for (const product of productsMap.values()) {
454
+ books.push({
455
+ name: product.name,
456
+ opera_id: fullyResolved.opera_id,
457
+ products: [product]
458
+ });
459
+ }
460
+ }
461
+ }
462
+
463
+ return books;
464
+ }
server.js CHANGED
@@ -6,7 +6,7 @@ import path from 'path';
6
  import { fileURLToPath } from 'url';
7
  import { randomUUID } from 'crypto';
8
  import fs from 'fs';
9
- import { performBsmartLogin, getBooks, getUserInfo } from './providers/src/api.js';
10
 
11
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
12
 
 
6
  import { fileURLToPath } from 'url';
7
  import { randomUUID } from 'crypto';
8
  import fs from 'fs';
9
+ import { performBsmartLogin, getBooks, getUserInfo } from './providers/src/bsmart/api.js';
10
 
11
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
12