devusman commited on
Commit
158a13b
Β·
1 Parent(s): 96620a1

feat : fixing timeout

Browse files
Files changed (4) hide show
  1. package-lock.json +683 -1
  2. package.json +3 -1
  3. server copy.js +233 -330
  4. server.js +22 -35
package-lock.json CHANGED
@@ -11,9 +11,11 @@
11
  "axios": "^1.11.0",
12
  "cors": "^2.8.5",
13
  "express": "^5.1.0",
 
14
  "puppeteer": "^24.16.2",
15
  "puppeteer-extra": "^3.3.6",
16
- "puppeteer-extra-plugin-stealth": "^2.11.2"
 
17
  },
18
  "devDependencies": {
19
  "nodemon": "^3.1.10",
@@ -44,6 +46,434 @@
44
  "node": ">=6.9.0"
45
  }
46
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
  "node_modules/@puppeteer/browsers": {
48
  "version": "2.10.6",
49
  "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.10.6.tgz",
@@ -65,6 +495,15 @@
65
  "node": ">=18"
66
  }
67
  },
 
 
 
 
 
 
 
 
 
68
  "node_modules/@tootallnate/quickjs-emscripten": {
69
  "version": "0.23.0",
70
  "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz",
@@ -294,6 +733,26 @@
294
  }
295
  }
296
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
297
  "node_modules/basic-ftp": {
298
  "version": "5.0.5",
299
  "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz",
@@ -359,6 +818,15 @@
359
  "node": ">=8"
360
  }
361
  },
 
 
 
 
 
 
 
 
 
362
  "node_modules/buffer-crc32": {
363
  "version": "0.2.13",
364
  "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
@@ -467,6 +935,15 @@
467
  "node": ">=12"
468
  }
469
  },
 
 
 
 
 
 
 
 
 
470
  "node_modules/clone-deep": {
471
  "version": "0.2.4",
472
  "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-0.2.4.tgz",
@@ -483,6 +960,19 @@
483
  "node": ">=0.10.0"
484
  }
485
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
486
  "node_modules/color-convert": {
487
  "version": "2.0.1",
488
  "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -501,6 +991,16 @@
501
  "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
502
  "license": "MIT"
503
  },
 
 
 
 
 
 
 
 
 
 
504
  "node_modules/combined-stream": {
505
  "version": "1.0.8",
506
  "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
@@ -612,6 +1112,12 @@
612
  "node": ">= 8"
613
  }
614
  },
 
 
 
 
 
 
615
  "node_modules/data-uri-to-buffer": {
616
  "version": "6.0.2",
617
  "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz",
@@ -679,12 +1185,27 @@
679
  "node": ">= 0.8"
680
  }
681
  },
 
 
 
 
 
 
 
 
 
682
  "node_modules/devtools-protocol": {
683
  "version": "0.0.1475386",
684
  "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1475386.tgz",
685
  "integrity": "sha512-RQ809ykTfJ+dgj9bftdeL2vRVxASAuGU+I9LEx9Ij5TXU5HrgAQVmzi72VA+mkzscE12uzlRv5/tWWv9R9J1SA==",
686
  "license": "BSD-3-Clause"
687
  },
 
 
 
 
 
 
688
  "node_modules/dunder-proto": {
689
  "version": "1.0.1",
690
  "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
@@ -953,6 +1474,12 @@
953
  "@types/yauzl": "^2.9.1"
954
  }
955
  },
 
 
 
 
 
 
956
  "node_modules/fast-fifo": {
957
  "version": "1.3.2",
958
  "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz",
@@ -1018,6 +1545,23 @@
1018
  }
1019
  }
1020
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1021
  "node_modules/for-in": {
1022
  "version": "1.0.2",
1023
  "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
@@ -1552,6 +2096,12 @@
1552
  "node": ">=0.10.0"
1553
  }
1554
  },
 
 
 
 
 
 
1555
  "node_modules/js-tokens": {
1556
  "version": "4.0.0",
1557
  "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -1609,6 +2159,25 @@
1609
  "node": ">=0.10.0"
1610
  }
1611
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1612
  "node_modules/lines-and-columns": {
1613
  "version": "1.2.4",
1614
  "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
@@ -1879,6 +2448,12 @@
1879
  "node": ">= 14"
1880
  }
1881
  },
 
 
 
 
 
 
1882
  "node_modules/parent-module": {
1883
  "version": "1.0.1",
1884
  "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -1959,6 +2534,19 @@
1959
  "through": "~2.3"
1960
  }
1961
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
1962
  "node_modules/pend": {
1963
  "version": "1.2.0",
1964
  "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
@@ -1984,6 +2572,11 @@
1984
  "url": "https://github.com/sponsors/jonschlinkert"
1985
  }
1986
  },
 
 
 
 
 
1987
  "node_modules/progress": {
1988
  "version": "2.0.3",
1989
  "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
@@ -2308,6 +2901,12 @@
2308
  "node": ">=4"
2309
  }
2310
  },
 
 
 
 
 
 
2311
  "node_modules/rimraf": {
2312
  "version": "3.0.2",
2313
  "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
@@ -2457,6 +3056,48 @@
2457
  "node": ">=0.10.0"
2458
  }
2459
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2460
  "node_modules/shebang-command": {
2461
  "version": "2.0.0",
2462
  "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -2552,6 +3193,21 @@
2552
  "url": "https://github.com/sponsors/ljharb"
2553
  }
2554
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2555
  "node_modules/simple-update-notifier": {
2556
  "version": "2.0.0",
2557
  "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
@@ -2748,6 +3404,12 @@
2748
  "dev": true,
2749
  "license": "MIT"
2750
  },
 
 
 
 
 
 
2751
  "node_modules/to-regex-range": {
2752
  "version": "5.0.1",
2753
  "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -2856,6 +3518,26 @@
2856
  "license": "MIT",
2857
  "optional": true
2858
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2859
  "node_modules/universalify": {
2860
  "version": "2.0.1",
2861
  "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
 
11
  "axios": "^1.11.0",
12
  "cors": "^2.8.5",
13
  "express": "^5.1.0",
14
+ "pdfkit": "^0.17.1",
15
  "puppeteer": "^24.16.2",
16
  "puppeteer-extra": "^3.3.6",
17
+ "puppeteer-extra-plugin-stealth": "^2.11.2",
18
+ "sharp": "^0.34.3"
19
  },
20
  "devDependencies": {
21
  "nodemon": "^3.1.10",
 
46
  "node": ">=6.9.0"
47
  }
48
  },
49
+ "node_modules/@emnapi/runtime": {
50
+ "version": "1.4.5",
51
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.5.tgz",
52
+ "integrity": "sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==",
53
+ "license": "MIT",
54
+ "optional": true,
55
+ "dependencies": {
56
+ "tslib": "^2.4.0"
57
+ }
58
+ },
59
+ "node_modules/@img/sharp-darwin-arm64": {
60
+ "version": "0.34.3",
61
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.3.tgz",
62
+ "integrity": "sha512-ryFMfvxxpQRsgZJqBd4wsttYQbCxsJksrv9Lw/v798JcQ8+w84mBWuXwl+TT0WJ/WrYOLaYpwQXi3sA9nTIaIg==",
63
+ "cpu": [
64
+ "arm64"
65
+ ],
66
+ "license": "Apache-2.0",
67
+ "optional": true,
68
+ "os": [
69
+ "darwin"
70
+ ],
71
+ "engines": {
72
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
73
+ },
74
+ "funding": {
75
+ "url": "https://opencollective.com/libvips"
76
+ },
77
+ "optionalDependencies": {
78
+ "@img/sharp-libvips-darwin-arm64": "1.2.0"
79
+ }
80
+ },
81
+ "node_modules/@img/sharp-darwin-x64": {
82
+ "version": "0.34.3",
83
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.3.tgz",
84
+ "integrity": "sha512-yHpJYynROAj12TA6qil58hmPmAwxKKC7reUqtGLzsOHfP7/rniNGTL8tjWX6L3CTV4+5P4ypcS7Pp+7OB+8ihA==",
85
+ "cpu": [
86
+ "x64"
87
+ ],
88
+ "license": "Apache-2.0",
89
+ "optional": true,
90
+ "os": [
91
+ "darwin"
92
+ ],
93
+ "engines": {
94
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
95
+ },
96
+ "funding": {
97
+ "url": "https://opencollective.com/libvips"
98
+ },
99
+ "optionalDependencies": {
100
+ "@img/sharp-libvips-darwin-x64": "1.2.0"
101
+ }
102
+ },
103
+ "node_modules/@img/sharp-libvips-darwin-arm64": {
104
+ "version": "1.2.0",
105
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.0.tgz",
106
+ "integrity": "sha512-sBZmpwmxqwlqG9ueWFXtockhsxefaV6O84BMOrhtg/YqbTaRdqDE7hxraVE3y6gVM4eExmfzW4a8el9ArLeEiQ==",
107
+ "cpu": [
108
+ "arm64"
109
+ ],
110
+ "license": "LGPL-3.0-or-later",
111
+ "optional": true,
112
+ "os": [
113
+ "darwin"
114
+ ],
115
+ "funding": {
116
+ "url": "https://opencollective.com/libvips"
117
+ }
118
+ },
119
+ "node_modules/@img/sharp-libvips-darwin-x64": {
120
+ "version": "1.2.0",
121
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.0.tgz",
122
+ "integrity": "sha512-M64XVuL94OgiNHa5/m2YvEQI5q2cl9d/wk0qFTDVXcYzi43lxuiFTftMR1tOnFQovVXNZJ5TURSDK2pNe9Yzqg==",
123
+ "cpu": [
124
+ "x64"
125
+ ],
126
+ "license": "LGPL-3.0-or-later",
127
+ "optional": true,
128
+ "os": [
129
+ "darwin"
130
+ ],
131
+ "funding": {
132
+ "url": "https://opencollective.com/libvips"
133
+ }
134
+ },
135
+ "node_modules/@img/sharp-libvips-linux-arm": {
136
+ "version": "1.2.0",
137
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.0.tgz",
138
+ "integrity": "sha512-mWd2uWvDtL/nvIzThLq3fr2nnGfyr/XMXlq8ZJ9WMR6PXijHlC3ksp0IpuhK6bougvQrchUAfzRLnbsen0Cqvw==",
139
+ "cpu": [
140
+ "arm"
141
+ ],
142
+ "license": "LGPL-3.0-or-later",
143
+ "optional": true,
144
+ "os": [
145
+ "linux"
146
+ ],
147
+ "funding": {
148
+ "url": "https://opencollective.com/libvips"
149
+ }
150
+ },
151
+ "node_modules/@img/sharp-libvips-linux-arm64": {
152
+ "version": "1.2.0",
153
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.0.tgz",
154
+ "integrity": "sha512-RXwd0CgG+uPRX5YYrkzKyalt2OJYRiJQ8ED/fi1tq9WQW2jsQIn0tqrlR5l5dr/rjqq6AHAxURhj2DVjyQWSOA==",
155
+ "cpu": [
156
+ "arm64"
157
+ ],
158
+ "license": "LGPL-3.0-or-later",
159
+ "optional": true,
160
+ "os": [
161
+ "linux"
162
+ ],
163
+ "funding": {
164
+ "url": "https://opencollective.com/libvips"
165
+ }
166
+ },
167
+ "node_modules/@img/sharp-libvips-linux-ppc64": {
168
+ "version": "1.2.0",
169
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.0.tgz",
170
+ "integrity": "sha512-Xod/7KaDDHkYu2phxxfeEPXfVXFKx70EAFZ0qyUdOjCcxbjqyJOEUpDe6RIyaunGxT34Anf9ue/wuWOqBW2WcQ==",
171
+ "cpu": [
172
+ "ppc64"
173
+ ],
174
+ "license": "LGPL-3.0-or-later",
175
+ "optional": true,
176
+ "os": [
177
+ "linux"
178
+ ],
179
+ "funding": {
180
+ "url": "https://opencollective.com/libvips"
181
+ }
182
+ },
183
+ "node_modules/@img/sharp-libvips-linux-s390x": {
184
+ "version": "1.2.0",
185
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.0.tgz",
186
+ "integrity": "sha512-eMKfzDxLGT8mnmPJTNMcjfO33fLiTDsrMlUVcp6b96ETbnJmd4uvZxVJSKPQfS+odwfVaGifhsB07J1LynFehw==",
187
+ "cpu": [
188
+ "s390x"
189
+ ],
190
+ "license": "LGPL-3.0-or-later",
191
+ "optional": true,
192
+ "os": [
193
+ "linux"
194
+ ],
195
+ "funding": {
196
+ "url": "https://opencollective.com/libvips"
197
+ }
198
+ },
199
+ "node_modules/@img/sharp-libvips-linux-x64": {
200
+ "version": "1.2.0",
201
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.0.tgz",
202
+ "integrity": "sha512-ZW3FPWIc7K1sH9E3nxIGB3y3dZkpJlMnkk7z5tu1nSkBoCgw2nSRTFHI5pB/3CQaJM0pdzMF3paf9ckKMSE9Tg==",
203
+ "cpu": [
204
+ "x64"
205
+ ],
206
+ "license": "LGPL-3.0-or-later",
207
+ "optional": true,
208
+ "os": [
209
+ "linux"
210
+ ],
211
+ "funding": {
212
+ "url": "https://opencollective.com/libvips"
213
+ }
214
+ },
215
+ "node_modules/@img/sharp-libvips-linuxmusl-arm64": {
216
+ "version": "1.2.0",
217
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.0.tgz",
218
+ "integrity": "sha512-UG+LqQJbf5VJ8NWJ5Z3tdIe/HXjuIdo4JeVNADXBFuG7z9zjoegpzzGIyV5zQKi4zaJjnAd2+g2nna8TZvuW9Q==",
219
+ "cpu": [
220
+ "arm64"
221
+ ],
222
+ "license": "LGPL-3.0-or-later",
223
+ "optional": true,
224
+ "os": [
225
+ "linux"
226
+ ],
227
+ "funding": {
228
+ "url": "https://opencollective.com/libvips"
229
+ }
230
+ },
231
+ "node_modules/@img/sharp-libvips-linuxmusl-x64": {
232
+ "version": "1.2.0",
233
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.0.tgz",
234
+ "integrity": "sha512-SRYOLR7CXPgNze8akZwjoGBoN1ThNZoqpOgfnOxmWsklTGVfJiGJoC/Lod7aNMGA1jSsKWM1+HRX43OP6p9+6Q==",
235
+ "cpu": [
236
+ "x64"
237
+ ],
238
+ "license": "LGPL-3.0-or-later",
239
+ "optional": true,
240
+ "os": [
241
+ "linux"
242
+ ],
243
+ "funding": {
244
+ "url": "https://opencollective.com/libvips"
245
+ }
246
+ },
247
+ "node_modules/@img/sharp-linux-arm": {
248
+ "version": "0.34.3",
249
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.3.tgz",
250
+ "integrity": "sha512-oBK9l+h6KBN0i3dC8rYntLiVfW8D8wH+NPNT3O/WBHeW0OQWCjfWksLUaPidsrDKpJgXp3G3/hkmhptAW0I3+A==",
251
+ "cpu": [
252
+ "arm"
253
+ ],
254
+ "license": "Apache-2.0",
255
+ "optional": true,
256
+ "os": [
257
+ "linux"
258
+ ],
259
+ "engines": {
260
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
261
+ },
262
+ "funding": {
263
+ "url": "https://opencollective.com/libvips"
264
+ },
265
+ "optionalDependencies": {
266
+ "@img/sharp-libvips-linux-arm": "1.2.0"
267
+ }
268
+ },
269
+ "node_modules/@img/sharp-linux-arm64": {
270
+ "version": "0.34.3",
271
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.3.tgz",
272
+ "integrity": "sha512-QdrKe3EvQrqwkDrtuTIjI0bu6YEJHTgEeqdzI3uWJOH6G1O8Nl1iEeVYRGdj1h5I21CqxSvQp1Yv7xeU3ZewbA==",
273
+ "cpu": [
274
+ "arm64"
275
+ ],
276
+ "license": "Apache-2.0",
277
+ "optional": true,
278
+ "os": [
279
+ "linux"
280
+ ],
281
+ "engines": {
282
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
283
+ },
284
+ "funding": {
285
+ "url": "https://opencollective.com/libvips"
286
+ },
287
+ "optionalDependencies": {
288
+ "@img/sharp-libvips-linux-arm64": "1.2.0"
289
+ }
290
+ },
291
+ "node_modules/@img/sharp-linux-ppc64": {
292
+ "version": "0.34.3",
293
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.3.tgz",
294
+ "integrity": "sha512-GLtbLQMCNC5nxuImPR2+RgrviwKwVql28FWZIW1zWruy6zLgA5/x2ZXk3mxj58X/tszVF69KK0Is83V8YgWhLA==",
295
+ "cpu": [
296
+ "ppc64"
297
+ ],
298
+ "license": "Apache-2.0",
299
+ "optional": true,
300
+ "os": [
301
+ "linux"
302
+ ],
303
+ "engines": {
304
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
305
+ },
306
+ "funding": {
307
+ "url": "https://opencollective.com/libvips"
308
+ },
309
+ "optionalDependencies": {
310
+ "@img/sharp-libvips-linux-ppc64": "1.2.0"
311
+ }
312
+ },
313
+ "node_modules/@img/sharp-linux-s390x": {
314
+ "version": "0.34.3",
315
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.3.tgz",
316
+ "integrity": "sha512-3gahT+A6c4cdc2edhsLHmIOXMb17ltffJlxR0aC2VPZfwKoTGZec6u5GrFgdR7ciJSsHT27BD3TIuGcuRT0KmQ==",
317
+ "cpu": [
318
+ "s390x"
319
+ ],
320
+ "license": "Apache-2.0",
321
+ "optional": true,
322
+ "os": [
323
+ "linux"
324
+ ],
325
+ "engines": {
326
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
327
+ },
328
+ "funding": {
329
+ "url": "https://opencollective.com/libvips"
330
+ },
331
+ "optionalDependencies": {
332
+ "@img/sharp-libvips-linux-s390x": "1.2.0"
333
+ }
334
+ },
335
+ "node_modules/@img/sharp-linux-x64": {
336
+ "version": "0.34.3",
337
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.3.tgz",
338
+ "integrity": "sha512-8kYso8d806ypnSq3/Ly0QEw90V5ZoHh10yH0HnrzOCr6DKAPI6QVHvwleqMkVQ0m+fc7EH8ah0BB0QPuWY6zJQ==",
339
+ "cpu": [
340
+ "x64"
341
+ ],
342
+ "license": "Apache-2.0",
343
+ "optional": true,
344
+ "os": [
345
+ "linux"
346
+ ],
347
+ "engines": {
348
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
349
+ },
350
+ "funding": {
351
+ "url": "https://opencollective.com/libvips"
352
+ },
353
+ "optionalDependencies": {
354
+ "@img/sharp-libvips-linux-x64": "1.2.0"
355
+ }
356
+ },
357
+ "node_modules/@img/sharp-linuxmusl-arm64": {
358
+ "version": "0.34.3",
359
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.3.tgz",
360
+ "integrity": "sha512-vAjbHDlr4izEiXM1OTggpCcPg9tn4YriK5vAjowJsHwdBIdx0fYRsURkxLG2RLm9gyBq66gwtWI8Gx0/ov+JKQ==",
361
+ "cpu": [
362
+ "arm64"
363
+ ],
364
+ "license": "Apache-2.0",
365
+ "optional": true,
366
+ "os": [
367
+ "linux"
368
+ ],
369
+ "engines": {
370
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
371
+ },
372
+ "funding": {
373
+ "url": "https://opencollective.com/libvips"
374
+ },
375
+ "optionalDependencies": {
376
+ "@img/sharp-libvips-linuxmusl-arm64": "1.2.0"
377
+ }
378
+ },
379
+ "node_modules/@img/sharp-linuxmusl-x64": {
380
+ "version": "0.34.3",
381
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.3.tgz",
382
+ "integrity": "sha512-gCWUn9547K5bwvOn9l5XGAEjVTTRji4aPTqLzGXHvIr6bIDZKNTA34seMPgM0WmSf+RYBH411VavCejp3PkOeQ==",
383
+ "cpu": [
384
+ "x64"
385
+ ],
386
+ "license": "Apache-2.0",
387
+ "optional": true,
388
+ "os": [
389
+ "linux"
390
+ ],
391
+ "engines": {
392
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
393
+ },
394
+ "funding": {
395
+ "url": "https://opencollective.com/libvips"
396
+ },
397
+ "optionalDependencies": {
398
+ "@img/sharp-libvips-linuxmusl-x64": "1.2.0"
399
+ }
400
+ },
401
+ "node_modules/@img/sharp-wasm32": {
402
+ "version": "0.34.3",
403
+ "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.3.tgz",
404
+ "integrity": "sha512-+CyRcpagHMGteySaWos8IbnXcHgfDn7pO2fiC2slJxvNq9gDipYBN42/RagzctVRKgxATmfqOSulgZv5e1RdMg==",
405
+ "cpu": [
406
+ "wasm32"
407
+ ],
408
+ "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
409
+ "optional": true,
410
+ "dependencies": {
411
+ "@emnapi/runtime": "^1.4.4"
412
+ },
413
+ "engines": {
414
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
415
+ },
416
+ "funding": {
417
+ "url": "https://opencollective.com/libvips"
418
+ }
419
+ },
420
+ "node_modules/@img/sharp-win32-arm64": {
421
+ "version": "0.34.3",
422
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.3.tgz",
423
+ "integrity": "sha512-MjnHPnbqMXNC2UgeLJtX4XqoVHHlZNd+nPt1kRPmj63wURegwBhZlApELdtxM2OIZDRv/DFtLcNhVbd1z8GYXQ==",
424
+ "cpu": [
425
+ "arm64"
426
+ ],
427
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
428
+ "optional": true,
429
+ "os": [
430
+ "win32"
431
+ ],
432
+ "engines": {
433
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
434
+ },
435
+ "funding": {
436
+ "url": "https://opencollective.com/libvips"
437
+ }
438
+ },
439
+ "node_modules/@img/sharp-win32-ia32": {
440
+ "version": "0.34.3",
441
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.3.tgz",
442
+ "integrity": "sha512-xuCdhH44WxuXgOM714hn4amodJMZl3OEvf0GVTm0BEyMeA2to+8HEdRPShH0SLYptJY1uBw+SCFP9WVQi1Q/cw==",
443
+ "cpu": [
444
+ "ia32"
445
+ ],
446
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
447
+ "optional": true,
448
+ "os": [
449
+ "win32"
450
+ ],
451
+ "engines": {
452
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
453
+ },
454
+ "funding": {
455
+ "url": "https://opencollective.com/libvips"
456
+ }
457
+ },
458
+ "node_modules/@img/sharp-win32-x64": {
459
+ "version": "0.34.3",
460
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.3.tgz",
461
+ "integrity": "sha512-OWwz05d++TxzLEv4VnsTz5CmZ6mI6S05sfQGEMrNrQcOEERbX46332IvE7pO/EUiw7jUrrS40z/M7kPyjfl04g==",
462
+ "cpu": [
463
+ "x64"
464
+ ],
465
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
466
+ "optional": true,
467
+ "os": [
468
+ "win32"
469
+ ],
470
+ "engines": {
471
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
472
+ },
473
+ "funding": {
474
+ "url": "https://opencollective.com/libvips"
475
+ }
476
+ },
477
  "node_modules/@puppeteer/browsers": {
478
  "version": "2.10.6",
479
  "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.10.6.tgz",
 
495
  "node": ">=18"
496
  }
497
  },
498
+ "node_modules/@swc/helpers": {
499
+ "version": "0.5.17",
500
+ "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz",
501
+ "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==",
502
+ "license": "Apache-2.0",
503
+ "dependencies": {
504
+ "tslib": "^2.8.0"
505
+ }
506
+ },
507
  "node_modules/@tootallnate/quickjs-emscripten": {
508
  "version": "0.23.0",
509
  "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz",
 
733
  }
734
  }
735
  },
736
+ "node_modules/base64-js": {
737
+ "version": "1.5.1",
738
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
739
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
740
+ "funding": [
741
+ {
742
+ "type": "github",
743
+ "url": "https://github.com/sponsors/feross"
744
+ },
745
+ {
746
+ "type": "patreon",
747
+ "url": "https://www.patreon.com/feross"
748
+ },
749
+ {
750
+ "type": "consulting",
751
+ "url": "https://feross.org/support"
752
+ }
753
+ ],
754
+ "license": "MIT"
755
+ },
756
  "node_modules/basic-ftp": {
757
  "version": "5.0.5",
758
  "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz",
 
818
  "node": ">=8"
819
  }
820
  },
821
+ "node_modules/brotli": {
822
+ "version": "1.3.3",
823
+ "resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.3.tgz",
824
+ "integrity": "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==",
825
+ "license": "MIT",
826
+ "dependencies": {
827
+ "base64-js": "^1.1.2"
828
+ }
829
+ },
830
  "node_modules/buffer-crc32": {
831
  "version": "0.2.13",
832
  "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
 
935
  "node": ">=12"
936
  }
937
  },
938
+ "node_modules/clone": {
939
+ "version": "2.1.2",
940
+ "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
941
+ "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==",
942
+ "license": "MIT",
943
+ "engines": {
944
+ "node": ">=0.8"
945
+ }
946
+ },
947
  "node_modules/clone-deep": {
948
  "version": "0.2.4",
949
  "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-0.2.4.tgz",
 
960
  "node": ">=0.10.0"
961
  }
962
  },
963
+ "node_modules/color": {
964
+ "version": "4.2.3",
965
+ "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
966
+ "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
967
+ "license": "MIT",
968
+ "dependencies": {
969
+ "color-convert": "^2.0.1",
970
+ "color-string": "^1.9.0"
971
+ },
972
+ "engines": {
973
+ "node": ">=12.5.0"
974
+ }
975
+ },
976
  "node_modules/color-convert": {
977
  "version": "2.0.1",
978
  "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
 
991
  "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
992
  "license": "MIT"
993
  },
994
+ "node_modules/color-string": {
995
+ "version": "1.9.1",
996
+ "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
997
+ "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
998
+ "license": "MIT",
999
+ "dependencies": {
1000
+ "color-name": "^1.0.0",
1001
+ "simple-swizzle": "^0.2.2"
1002
+ }
1003
+ },
1004
  "node_modules/combined-stream": {
1005
  "version": "1.0.8",
1006
  "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
 
1112
  "node": ">= 8"
1113
  }
1114
  },
1115
+ "node_modules/crypto-js": {
1116
+ "version": "4.2.0",
1117
+ "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz",
1118
+ "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==",
1119
+ "license": "MIT"
1120
+ },
1121
  "node_modules/data-uri-to-buffer": {
1122
  "version": "6.0.2",
1123
  "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz",
 
1185
  "node": ">= 0.8"
1186
  }
1187
  },
1188
+ "node_modules/detect-libc": {
1189
+ "version": "2.0.4",
1190
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
1191
+ "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==",
1192
+ "license": "Apache-2.0",
1193
+ "engines": {
1194
+ "node": ">=8"
1195
+ }
1196
+ },
1197
  "node_modules/devtools-protocol": {
1198
  "version": "0.0.1475386",
1199
  "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1475386.tgz",
1200
  "integrity": "sha512-RQ809ykTfJ+dgj9bftdeL2vRVxASAuGU+I9LEx9Ij5TXU5HrgAQVmzi72VA+mkzscE12uzlRv5/tWWv9R9J1SA==",
1201
  "license": "BSD-3-Clause"
1202
  },
1203
+ "node_modules/dfa": {
1204
+ "version": "1.2.0",
1205
+ "resolved": "https://registry.npmjs.org/dfa/-/dfa-1.2.0.tgz",
1206
+ "integrity": "sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==",
1207
+ "license": "MIT"
1208
+ },
1209
  "node_modules/dunder-proto": {
1210
  "version": "1.0.1",
1211
  "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
 
1474
  "@types/yauzl": "^2.9.1"
1475
  }
1476
  },
1477
+ "node_modules/fast-deep-equal": {
1478
+ "version": "3.1.3",
1479
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
1480
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
1481
+ "license": "MIT"
1482
+ },
1483
  "node_modules/fast-fifo": {
1484
  "version": "1.3.2",
1485
  "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz",
 
1545
  }
1546
  }
1547
  },
1548
+ "node_modules/fontkit": {
1549
+ "version": "2.0.4",
1550
+ "resolved": "https://registry.npmjs.org/fontkit/-/fontkit-2.0.4.tgz",
1551
+ "integrity": "sha512-syetQadaUEDNdxdugga9CpEYVaQIxOwk7GlwZWWZ19//qW4zE5bknOKeMBDYAASwnpaSHKJITRLMF9m1fp3s6g==",
1552
+ "license": "MIT",
1553
+ "dependencies": {
1554
+ "@swc/helpers": "^0.5.12",
1555
+ "brotli": "^1.3.2",
1556
+ "clone": "^2.1.2",
1557
+ "dfa": "^1.2.0",
1558
+ "fast-deep-equal": "^3.1.3",
1559
+ "restructure": "^3.0.0",
1560
+ "tiny-inflate": "^1.0.3",
1561
+ "unicode-properties": "^1.4.0",
1562
+ "unicode-trie": "^2.0.0"
1563
+ }
1564
+ },
1565
  "node_modules/for-in": {
1566
  "version": "1.0.2",
1567
  "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
 
2096
  "node": ">=0.10.0"
2097
  }
2098
  },
2099
+ "node_modules/jpeg-exif": {
2100
+ "version": "1.1.4",
2101
+ "resolved": "https://registry.npmjs.org/jpeg-exif/-/jpeg-exif-1.1.4.tgz",
2102
+ "integrity": "sha512-a+bKEcCjtuW5WTdgeXFzswSrdqi0jk4XlEtZlx5A94wCoBpFjfFTbo/Tra5SpNCl/YFZPvcV1dJc+TAYeg6ROQ==",
2103
+ "license": "MIT"
2104
+ },
2105
  "node_modules/js-tokens": {
2106
  "version": "4.0.0",
2107
  "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
 
2159
  "node": ">=0.10.0"
2160
  }
2161
  },
2162
+ "node_modules/linebreak": {
2163
+ "version": "1.1.0",
2164
+ "resolved": "https://registry.npmjs.org/linebreak/-/linebreak-1.1.0.tgz",
2165
+ "integrity": "sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==",
2166
+ "license": "MIT",
2167
+ "dependencies": {
2168
+ "base64-js": "0.0.8",
2169
+ "unicode-trie": "^2.0.0"
2170
+ }
2171
+ },
2172
+ "node_modules/linebreak/node_modules/base64-js": {
2173
+ "version": "0.0.8",
2174
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz",
2175
+ "integrity": "sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==",
2176
+ "license": "MIT",
2177
+ "engines": {
2178
+ "node": ">= 0.4"
2179
+ }
2180
+ },
2181
  "node_modules/lines-and-columns": {
2182
  "version": "1.2.4",
2183
  "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
 
2448
  "node": ">= 14"
2449
  }
2450
  },
2451
+ "node_modules/pako": {
2452
+ "version": "0.2.9",
2453
+ "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz",
2454
+ "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==",
2455
+ "license": "MIT"
2456
+ },
2457
  "node_modules/parent-module": {
2458
  "version": "1.0.1",
2459
  "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
 
2534
  "through": "~2.3"
2535
  }
2536
  },
2537
+ "node_modules/pdfkit": {
2538
+ "version": "0.17.1",
2539
+ "resolved": "https://registry.npmjs.org/pdfkit/-/pdfkit-0.17.1.tgz",
2540
+ "integrity": "sha512-Kkf1I9no14O/uo593DYph5u3QwiMfby7JsBSErN1WqeyTgCBNJE3K4pXBn3TgkdKUIVu+buSl4bYUNC+8Up4xg==",
2541
+ "license": "MIT",
2542
+ "dependencies": {
2543
+ "crypto-js": "^4.2.0",
2544
+ "fontkit": "^2.0.4",
2545
+ "jpeg-exif": "^1.1.4",
2546
+ "linebreak": "^1.1.0",
2547
+ "png-js": "^1.0.0"
2548
+ }
2549
+ },
2550
  "node_modules/pend": {
2551
  "version": "1.2.0",
2552
  "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
 
2572
  "url": "https://github.com/sponsors/jonschlinkert"
2573
  }
2574
  },
2575
+ "node_modules/png-js": {
2576
+ "version": "1.0.0",
2577
+ "resolved": "https://registry.npmjs.org/png-js/-/png-js-1.0.0.tgz",
2578
+ "integrity": "sha512-k+YsbhpA9e+EFfKjTCH3VW6aoKlyNYI6NYdTfDL4CIvFnvsuO84ttonmZE7rc+v23SLTH8XX+5w/Ak9v0xGY4g=="
2579
+ },
2580
  "node_modules/progress": {
2581
  "version": "2.0.3",
2582
  "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
 
2901
  "node": ">=4"
2902
  }
2903
  },
2904
+ "node_modules/restructure": {
2905
+ "version": "3.0.2",
2906
+ "resolved": "https://registry.npmjs.org/restructure/-/restructure-3.0.2.tgz",
2907
+ "integrity": "sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw==",
2908
+ "license": "MIT"
2909
+ },
2910
  "node_modules/rimraf": {
2911
  "version": "3.0.2",
2912
  "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
 
3056
  "node": ">=0.10.0"
3057
  }
3058
  },
3059
+ "node_modules/sharp": {
3060
+ "version": "0.34.3",
3061
+ "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.3.tgz",
3062
+ "integrity": "sha512-eX2IQ6nFohW4DbvHIOLRB3MHFpYqaqvXd3Tp5e/T/dSH83fxaNJQRvDMhASmkNTsNTVF2/OOopzRCt7xokgPfg==",
3063
+ "hasInstallScript": true,
3064
+ "license": "Apache-2.0",
3065
+ "dependencies": {
3066
+ "color": "^4.2.3",
3067
+ "detect-libc": "^2.0.4",
3068
+ "semver": "^7.7.2"
3069
+ },
3070
+ "engines": {
3071
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
3072
+ },
3073
+ "funding": {
3074
+ "url": "https://opencollective.com/libvips"
3075
+ },
3076
+ "optionalDependencies": {
3077
+ "@img/sharp-darwin-arm64": "0.34.3",
3078
+ "@img/sharp-darwin-x64": "0.34.3",
3079
+ "@img/sharp-libvips-darwin-arm64": "1.2.0",
3080
+ "@img/sharp-libvips-darwin-x64": "1.2.0",
3081
+ "@img/sharp-libvips-linux-arm": "1.2.0",
3082
+ "@img/sharp-libvips-linux-arm64": "1.2.0",
3083
+ "@img/sharp-libvips-linux-ppc64": "1.2.0",
3084
+ "@img/sharp-libvips-linux-s390x": "1.2.0",
3085
+ "@img/sharp-libvips-linux-x64": "1.2.0",
3086
+ "@img/sharp-libvips-linuxmusl-arm64": "1.2.0",
3087
+ "@img/sharp-libvips-linuxmusl-x64": "1.2.0",
3088
+ "@img/sharp-linux-arm": "0.34.3",
3089
+ "@img/sharp-linux-arm64": "0.34.3",
3090
+ "@img/sharp-linux-ppc64": "0.34.3",
3091
+ "@img/sharp-linux-s390x": "0.34.3",
3092
+ "@img/sharp-linux-x64": "0.34.3",
3093
+ "@img/sharp-linuxmusl-arm64": "0.34.3",
3094
+ "@img/sharp-linuxmusl-x64": "0.34.3",
3095
+ "@img/sharp-wasm32": "0.34.3",
3096
+ "@img/sharp-win32-arm64": "0.34.3",
3097
+ "@img/sharp-win32-ia32": "0.34.3",
3098
+ "@img/sharp-win32-x64": "0.34.3"
3099
+ }
3100
+ },
3101
  "node_modules/shebang-command": {
3102
  "version": "2.0.0",
3103
  "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
 
3193
  "url": "https://github.com/sponsors/ljharb"
3194
  }
3195
  },
3196
+ "node_modules/simple-swizzle": {
3197
+ "version": "0.2.2",
3198
+ "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
3199
+ "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
3200
+ "license": "MIT",
3201
+ "dependencies": {
3202
+ "is-arrayish": "^0.3.1"
3203
+ }
3204
+ },
3205
+ "node_modules/simple-swizzle/node_modules/is-arrayish": {
3206
+ "version": "0.3.2",
3207
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
3208
+ "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==",
3209
+ "license": "MIT"
3210
+ },
3211
  "node_modules/simple-update-notifier": {
3212
  "version": "2.0.0",
3213
  "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
 
3404
  "dev": true,
3405
  "license": "MIT"
3406
  },
3407
+ "node_modules/tiny-inflate": {
3408
+ "version": "1.0.3",
3409
+ "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz",
3410
+ "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==",
3411
+ "license": "MIT"
3412
+ },
3413
  "node_modules/to-regex-range": {
3414
  "version": "5.0.1",
3415
  "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
 
3518
  "license": "MIT",
3519
  "optional": true
3520
  },
3521
+ "node_modules/unicode-properties": {
3522
+ "version": "1.4.1",
3523
+ "resolved": "https://registry.npmjs.org/unicode-properties/-/unicode-properties-1.4.1.tgz",
3524
+ "integrity": "sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==",
3525
+ "license": "MIT",
3526
+ "dependencies": {
3527
+ "base64-js": "^1.3.0",
3528
+ "unicode-trie": "^2.0.0"
3529
+ }
3530
+ },
3531
+ "node_modules/unicode-trie": {
3532
+ "version": "2.0.0",
3533
+ "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz",
3534
+ "integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==",
3535
+ "license": "MIT",
3536
+ "dependencies": {
3537
+ "pako": "^0.2.5",
3538
+ "tiny-inflate": "^1.0.0"
3539
+ }
3540
+ },
3541
  "node_modules/universalify": {
3542
  "version": "2.0.1",
3543
  "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
package.json CHANGED
@@ -7,9 +7,11 @@
7
  "axios": "^1.11.0",
8
  "cors": "^2.8.5",
9
  "express": "^5.1.0",
 
10
  "puppeteer": "^24.16.2",
11
  "puppeteer-extra": "^3.3.6",
12
- "puppeteer-extra-plugin-stealth": "^2.11.2"
 
13
  },
14
  "devDependencies": {
15
  "nodemon": "^3.1.10",
 
7
  "axios": "^1.11.0",
8
  "cors": "^2.8.5",
9
  "express": "^5.1.0",
10
+ "pdfkit": "^0.17.1",
11
  "puppeteer": "^24.16.2",
12
  "puppeteer-extra": "^3.3.6",
13
+ "puppeteer-extra-plugin-stealth": "^2.11.2",
14
+ "sharp": "^0.34.3"
15
  },
16
  "devDependencies": {
17
  "nodemon": "^3.1.10",
server copy.js CHANGED
@@ -1,16 +1,50 @@
1
  const express = require('express');
2
- const puppeteer = require('puppeteer');
 
3
  const cors = require('cors');
 
 
 
 
4
  const app = express();
5
  const port = 7860;
6
 
7
  app.use(cors());
8
  app.use(express.json());
9
 
10
- /**
11
- * Advanced cookie banner and content bypass for StuDocu
12
- */
13
- const bypassCookiesAndRestrictions = async (page) => {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  console.log("πŸͺ Starting comprehensive cookie and restriction bypass...");
15
  // Step 1: Set cookies before page load
16
  const preCookies = [
@@ -31,7 +65,7 @@ const bypassCookiesAndRestrictions = async (page) => {
31
  }
32
  }
33
 
34
- // Step 2: Inject CSS to hide cookie banners immediately
35
  await page.addStyleTag({
36
  content: `
37
  /* Hide all possible cookie banners */
@@ -72,7 +106,7 @@ const bypassCookiesAndRestrictions = async (page) => {
72
  `
73
  });
74
 
75
- // Step 3: Inject JavaScript to handle dynamic cookie banners
76
  await page.evaluateOnNewDocument(() => {
77
  // Override common cookie consent functions
78
  window.cookieConsent = { accepted: true };
@@ -123,36 +157,26 @@ const bypassCookiesAndRestrictions = async (page) => {
123
  }, 1000);
124
  });
125
 
 
126
  return true;
127
  };
128
 
129
- /**
130
- * Enhanced content unblurring and premium bypass (integrated from extension script)
131
- */
132
- const unblurContent = async (page) => {
133
  console.log("πŸ”“ Unblurring content and bypassing premium restrictions...");
134
  await page.evaluate(() => {
135
- // Function to remove all visual restrictions
136
  const removeRestrictions = () => {
137
  const removeBySelector = (selector) => {
138
  document.querySelectorAll(selector).forEach(el => el.remove());
139
  };
140
 
141
- // Remove ads by known class or ID
142
- removeBySelector("#adbox");
143
- removeBySelector(".adsbox");
144
- removeBySelector(".ad-box");
145
- removeBySelector(".banner-ads");
146
- removeBySelector(".advert");
147
-
148
- // Remove premium banner container
149
  removeBySelector(".PremiumBannerBlobWrapper_overflow-wrapper__xsaS8");
150
 
151
- // Enhanced blur removal
152
  const removeBlur = (element = document) => {
153
  element.querySelectorAll("*").forEach(el => {
154
  const style = window.getComputedStyle(el);
155
- // Check for blur via filter, backdrop-filter, or class names
156
  if (
157
  style.filter?.includes("blur") ||
158
  style.backdropFilter?.includes("blur") ||
@@ -167,41 +191,12 @@ const unblurContent = async (page) => {
167
  el.classList.remove("blur", "blurred", "premium-blur");
168
  }
169
  }
170
- // Check parent elements for blur-inducing styles
171
- const parent = el.parentElement;
172
- if (parent) {
173
- const parentStyle = window.getComputedStyle(parent);
174
- if (
175
- parentStyle.filter?.includes("blur") ||
176
- parentStyle.backdropFilter?.includes("blur") ||
177
- parseFloat(parentStyle.opacity) < 1
178
- ) {
179
- parent.style.filter = "none !important";
180
- parent.style.backdropFilter = "none !important";
181
- parent.style.opacity = "1 !important";
182
- }
183
- }
184
  });
185
  };
186
 
187
- // Remove dark overlays and paywall-like elements
188
- document.querySelectorAll("div, section, aside").forEach(el => {
189
- const style = window.getComputedStyle(el);
190
- if (
191
- (style.backgroundColor.includes("rgba") && (style.backgroundColor.includes("0.5") || parseFloat(style.zIndex) > 1000)) ||
192
- (el.className && el.className.toString().toLowerCase().includes("overlay")) ||
193
- (el.className && el.className.toString().toLowerCase().includes("paywall"))
194
- ) {
195
- el.remove();
196
- }
197
- });
198
-
199
  removeBlur();
 
200
 
201
- // Remove other restrictions
202
- removeBySelector('[class*="blur" i], [class*="premium" i], [class*="paywall" i], [class*="sample-preview-blur" i]');
203
-
204
- // Ensure document content is visible
205
  const contentSelectors = [
206
  '.document-content', '.page-content', '.content', '[data-page]', '[data-testid*="document"]',
207
  '[data-testid*="page"]', '.page', '.document-page', 'main', 'article'
@@ -215,53 +210,44 @@ const unblurContent = async (page) => {
215
  el.style.setProperty('pointer-events', 'auto', 'important');
216
  });
217
  });
218
-
219
- // Remove overlay divs that might be blocking content
220
- const overlays = document.querySelectorAll(`
221
- [class*="overlay" i], [class*="modal" i], [class*="popup" i], [class*="banner" i],
222
- [style*="position: fixed"], [style*="position: absolute"][style*="z-index"]
223
- `);
224
- overlays.forEach(overlay => {
225
- const text = overlay.textContent || '';
226
- if (text.includes('premium') || text.includes('unlock') || text.includes('subscribe') || text.includes('cookie') || text.includes('consent') || text.includes('login')) {
227
- overlay.remove();
228
- }
229
- });
230
  };
231
 
232
- // Run immediately
233
  removeRestrictions();
234
-
235
- // Run periodically
236
  const intervalId = setInterval(removeRestrictions, 2000);
237
-
238
- // Clean up after 60 seconds
239
- setTimeout(() => {
240
- clearInterval(intervalId);
241
- }, 60000);
242
  });
 
 
243
  };
244
 
245
- /**
246
- * Apply print styles for clean PDF output (integrated from extension script with improvements)
247
- */
248
- const applyPrintStyles = async (page) => {
249
  console.log("πŸ–¨οΈ Applying print styles for clean PDF...");
250
  await page.evaluate(() => {
251
  const style = document.createElement("style");
252
  style.id = "print-style-extension";
253
  style.innerHTML = `
254
  @page {
 
255
  size: A4 portrait;
256
- margin: 5mm;
257
  }
258
  @media print {
259
  html, body {
 
 
 
260
  margin: 0 !important;
261
  padding: 0 !important;
262
  overflow: visible !important;
 
 
263
  }
 
 
264
  header, footer, nav, aside, .no-print, .ads, .sidebar, .premium-banner,
 
265
  .ViewerToolbar, .Layout_info-bar-wrapper__He0Ho, .Sidebar_sidebar-scrollable__kqeBZ,
266
  .HeaderWrapper_header-wrapper__mCmf3, .Layout_visible-content-bottom-wrapper-sticky__yaaAB,
267
  .Layout_bottom-section-wrapper__yBWWk, .Layout_footer-wrapper__bheJQ,
@@ -269,21 +255,32 @@ const applyPrintStyles = async (page) => {
269
  .Layout_sidebar-wrapper__unavM, .Layout_is-open__9DQr4 {
270
  display: none !important;
271
  }
272
- body {
273
- background: white !important;
274
- color: black !important;
275
- }
276
  * {
277
  box-shadow: none !important;
278
  background: transparent !important;
 
279
  }
280
- .Viewer_document-wrapper__JPBWQ, .Viewer_document-wrapper__LXzoQ, .Viewer_document-wrapper__XsO4j, .page-content {
281
- display: flex !important;
282
- flex-direction: column !important;
 
 
 
 
 
 
 
283
  width: 100% !important;
284
- max-width: 210mm !important;
285
- margin: 0 auto !important;
 
 
 
286
  }
 
 
287
  [data-page], .page, .document-page, img {
288
  page-break-after: always !important;
289
  page-break-inside: avoid !important;
@@ -291,21 +288,25 @@ const applyPrintStyles = async (page) => {
291
  width: 100% !important;
292
  max-width: 100% !important;
293
  height: auto !important;
 
 
 
294
  }
295
  }
296
  `;
297
  document.head.appendChild(style);
298
  });
 
 
299
  };
300
 
301
- /**
302
- * Enhanced StuDocu downloader with comprehensive bypasses and login support
303
- */
304
- const studocuDownloader = async (url, options = {}) => {
305
  let browser;
306
  try {
307
- console.log("πŸš€ Launching browser with stealth configuration...");
308
- browser = await puppeteer.launch({
 
 
309
  headless: true,
310
  args: [
311
  '--no-sandbox',
@@ -324,33 +325,44 @@ const studocuDownloader = async (url, options = {}) => {
324
  '--disable-web-security',
325
  '--disable-features=site-per-process',
326
  '--disable-blink-features=AutomationControlled',
327
- '--disable-extensions'
 
328
  ],
 
329
  timeout: 300000,
 
 
330
  });
331
 
332
  const page = await browser.newPage();
333
 
334
- // Set realistic browser characteristics
335
- await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36');
336
- await page.setViewport({ width: 794, height: 1122 });
 
 
 
 
 
337
 
338
- // Hide webdriver property
339
  await page.evaluateOnNewDocument(() => {
340
  Object.defineProperty(navigator, 'webdriver', { get: () => undefined });
341
  Object.defineProperty(navigator, 'languages', { get: () => ['en-US', 'en'] });
342
  Object.defineProperty(navigator, 'plugins', { get: () => [1, 2, 3, 4, 5] });
343
  });
344
 
345
- // Set up cookie and content bypass
346
- await bypassCookiesAndRestrictions(page);
347
 
348
- // Block unnecessary resources
349
  await page.setRequestInterception(true);
350
  page.on('request', (req) => {
351
  const resourceType = req.resourceType();
352
- const reqUrl = req.url();
353
- // Block trackers, ads, and analytics
 
 
 
 
 
354
  if (
355
  reqUrl.includes('doubleclick') ||
356
  reqUrl.includes('googletagmanager') ||
@@ -362,7 +374,7 @@ const studocuDownloader = async (url, options = {}) => {
362
  reqUrl.includes('mixpanel') ||
363
  reqUrl.includes('onetrust') ||
364
  reqUrl.includes('cookielaw') ||
365
- (resourceType === 'other' && reqUrl.includes('track'))
366
  ) {
367
  req.abort();
368
  } else {
@@ -370,53 +382,59 @@ const studocuDownloader = async (url, options = {}) => {
370
  }
371
  });
372
 
373
- // Login if credentials provided (for premium content)
374
  if (options.email && options.password) {
 
 
375
  console.log("πŸ”‘ Logging in to StuDocu...");
376
- await page.goto('https://www.studocu.com/en-us/login', { waitUntil: 'domcontentloaded', timeout: 60000 });
377
- await page.waitForSelector('#email', { timeout: 15000 });
378
  await page.type('#email', options.email);
379
  await page.type('#password', options.password);
380
  await page.click('button[type="submit"]');
381
  try {
382
- await page.waitForNavigation({ waitUntil: 'networkidle2', timeout: 30000 });
383
- // Additional check for successful login
384
- await page.waitForSelector('.user-profile, [data-testid="user-menu"]', { timeout: 10000 });
385
  console.log("βœ… Login successful.");
 
386
  } catch (e) {
387
  console.error("❌ Login failed:", e.message);
388
- throw new Error("Login failed. Check credentials, if CAPTCHA is present, or try again.");
389
  }
390
- } else {
391
- console.log("⚠️ No login credentials provided. Full unblurred content requires premium account.");
392
  }
393
 
 
 
 
 
 
 
394
  console.log(`πŸ“„ Navigating to ${url}...`);
395
- // Navigate with retry logic
396
  let navigationSuccess = false;
397
  let attempts = 0;
398
- const maxAttempts = 3;
399
  while (!navigationSuccess && attempts < maxAttempts) {
400
  try {
401
  attempts++;
 
402
  console.log(`Navigation attempt ${attempts}/${maxAttempts}`);
403
- await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 60000 });
404
  navigationSuccess = true;
405
  } catch (e) {
406
  console.log(`Navigation attempt ${attempts} failed:`, e.message);
407
  if (attempts >= maxAttempts) throw e;
408
- await new Promise(resolve => setTimeout(resolve, 5000));
409
  }
410
  }
411
 
412
- // Wait for initial load
413
  await new Promise(resolve => setTimeout(resolve, 5000));
414
 
415
- // Apply content unblurring
416
- await unblurContent(page);
417
 
418
- // Wait for document content with multiple selectors
419
  console.log("⏳ Waiting for document content to load...");
 
420
  const contentSelectors = [
421
  '.document-content', '.page-content', '[data-page]', '[data-testid*="document"]',
422
  'img[src*="document"]', 'img[src*="page"]', '.page', 'main img', 'article img'
@@ -424,7 +442,7 @@ const studocuDownloader = async (url, options = {}) => {
424
  let contentFound = false;
425
  for (const selector of contentSelectors) {
426
  try {
427
- await page.waitForSelector(selector, { timeout: 20000 });
428
  console.log(`βœ… Found content with selector: ${selector}`);
429
  contentFound = true;
430
  break;
@@ -437,8 +455,9 @@ const studocuDownloader = async (url, options = {}) => {
437
  console.log("⚠️ No specific content selector found, proceeding with page content...");
438
  }
439
 
440
- // Enhanced scrolling to load all content with loop for stability
441
  console.log("πŸ“œ Loading all document pages with enhanced slow scroll...");
 
442
  await page.evaluate(async () => {
443
  const delay = (ms) => new Promise((res) => setTimeout(res, ms));
444
  let scrollHeight = document.body.scrollHeight;
@@ -448,23 +467,24 @@ const studocuDownloader = async (url, options = {}) => {
448
  while (totalHeight < scrollHeight) {
449
  window.scrollBy(0, distance);
450
  totalHeight += distance;
451
- await delay(500); // Increased delay for better loading
452
  }
453
- await delay(2000); // Extra wait after reaching bottom
454
  const newHeight = document.body.scrollHeight;
455
  if (newHeight === scrollHeight) break;
456
  scrollHeight = newHeight;
457
  }
458
- // Scroll to top
459
  window.scrollTo({ top: 0, behavior: "smooth" });
460
  await delay(1000);
461
  });
462
 
463
- // Re-apply unblur after loading new content
464
- await unblurContent(page);
465
 
466
- // Wait for all images to load
 
 
467
  console.log("πŸ–ΌοΈ Waiting for all images to load...");
 
468
  await page.evaluate(async () => {
469
  const images = Array.from(document.querySelectorAll('img'));
470
  await Promise.all(images.map(img => {
@@ -477,11 +497,9 @@ const studocuDownloader = async (url, options = {}) => {
477
  }));
478
  });
479
 
480
- // Additional wait for any lazy loading
481
- await new Promise(resolve => setTimeout(resolve, 10000));
482
 
483
- // Set exact height to avoid extra blank pages
484
- console.log("πŸ“ Setting exact document height...");
485
  await page.evaluate(() => {
486
  const getDocumentHeight = () => Math.max(
487
  document.body.scrollHeight, document.body.offsetHeight,
@@ -493,7 +511,6 @@ const studocuDownloader = async (url, options = {}) => {
493
  document.body.style.overflow = 'hidden !important';
494
  });
495
 
496
- // Final content verification
497
  const contentCheck = await page.evaluate(() => {
498
  const textContent = document.body.textContent || '';
499
  const images = document.querySelectorAll('img');
@@ -505,10 +522,10 @@ const studocuDownloader = async (url, options = {}) => {
505
  totalText: textContent.length,
506
  totalImages: images.length,
507
  documentImages: documentImages.length,
508
- hasDocumentContent: documentImages.length > 0 || textContent.length > 1000,
509
- sampleText: textContent.substring(0, 300)
510
  };
511
  });
 
512
  console.log("πŸ“Š Content verification:", {
513
  textLength: contentCheck.totalText,
514
  images: contentCheck.totalImages,
@@ -517,40 +534,33 @@ const studocuDownloader = async (url, options = {}) => {
517
  });
518
 
519
  if (!contentCheck.hasDocumentContent) {
520
- console.warn("⚠️ Warning: Limited document content detected. Use premium credentials for full access.");
521
  }
522
 
523
- // Apply print styles
524
- await applyPrintStyles(page);
525
-
526
- // Emulate print media
527
  await page.emulateMediaType('print');
528
 
529
- // Generate PDF
530
  console.log("πŸ”„ Generating PDF...");
 
531
  const pdfBuffer = await page.pdf({
532
  printBackground: true,
533
  preferCSSPageSize: true,
534
  displayHeaderFooter: false,
535
- timeout: 180000,
 
536
  scale: 1,
537
  omitBackground: false
538
  });
539
 
 
540
  console.log(`βœ… PDF generated successfully! Size: ${(pdfBuffer.length / 1024 / 1024).toFixed(2)} MB`);
541
  return pdfBuffer;
542
 
543
  } catch (error) {
 
544
  console.error("❌ Error during PDF generation:", error);
545
- if (error.message.includes('timeout')) {
546
- throw new Error("Request timed out. The document may be taking too long to load. Please try again.");
547
- } else if (error.message.includes('net::')) {
548
- throw new Error("Network error. Please check the URL and your internet connection.");
549
- } else if (error.message.includes('ERR_BLOCKED')) {
550
- throw new Error("Access blocked. Try again or check if the document is publicly accessible.");
551
- } else {
552
- throw new Error(`Failed to generate PDF: ${error.message}`);
553
- }
554
  } finally {
555
  if (browser) {
556
  console.log("πŸ”’ Closing browser...");
@@ -563,221 +573,114 @@ const studocuDownloader = async (url, options = {}) => {
563
  }
564
  };
565
 
566
- /**
567
- * NEW: StuDocu downloader with page-by-page progress streaming
568
- */
569
- const studocuDownloaderStreamed = async (url, options, res) => {
570
- let browser;
571
- try {
572
- console.log("πŸš€ Launching browser for streaming with stealth configuration...");
573
- browser = await puppeteer.launch({
574
- headless: true,
575
- args: [
576
- '--no-sandbox',
577
- '--disable-setuid-sandbox',
578
- '--disable-dev-shm-usage',
579
- '--disable-accelerated-2d-canvas',
580
- '--no-first-run',
581
- '--no-zygote',
582
- '--disable-gpu'
583
- ],
584
- timeout: 300000,
585
- });
586
-
587
- const page = await browser.newPage();
588
- await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36');
589
- await page.setViewport({ width: 794, height: 1122 });
590
-
591
- await page.evaluateOnNewDocument(() => {
592
- Object.defineProperty(navigator, 'webdriver', { get: () => undefined });
593
- });
594
-
595
- await bypassCookiesAndRestrictions(page);
596
-
597
- await page.setRequestInterception(true);
598
- page.on('request', (req) => {
599
- if (['image', 'stylesheet', 'font', 'other'].includes(req.resourceType()) && !req.url().includes('studocu.com')) {
600
- req.abort();
601
- } else {
602
- req.continue();
603
- }
604
- });
605
 
606
- if (options.email && options.password) {
607
- console.log("πŸ”‘ Logging in for streaming...");
608
- await page.goto('https://www.studocu.com/en-us/login', { waitUntil: 'domcontentloaded' });
609
- await page.waitForSelector('#email');
610
- await page.type('#email', options.email);
611
- await page.type('#password', options.password);
612
- await page.click('button[type="submit"]');
613
- await page.waitForNavigation({ waitUntil: 'networkidle2' });
614
- console.log("βœ… Login successful for streaming.");
615
- }
616
 
617
- console.log(`πŸ“„ Navigating to ${url} for streaming...`);
618
- await page.goto(url, { waitUntil: 'domcontentloaded' });
619
- await new Promise(resolve => setTimeout(resolve, 5000));
620
 
621
- await unblurContent(page);
622
 
623
- console.log("⏳ Waiting for document pages to load...");
624
- await page.waitForSelector('[data-page]', { timeout: 30000 });
625
 
626
- console.log("πŸ“œ Scrolling to load all pages for streaming...");
627
- await page.evaluate(async () => {
628
- await new Promise(resolve => {
629
- let totalHeight = 0;
630
- const distance = 100;
631
- const timer = setInterval(() => {
632
- const scrollHeight = document.body.scrollHeight;
633
- window.scrollBy(0, distance);
634
- totalHeight += distance;
635
- if (totalHeight >= scrollHeight) {
636
- clearInterval(timer);
637
- resolve();
638
- }
639
- }, 100);
640
- });
641
  });
 
642
 
643
- await unblurContent(page);
644
- await new Promise(resolve => setTimeout(resolve, 5000));
645
-
646
- const pageElements = await page.$$('[data-page]');
647
- const totalPages = pageElements.length;
648
- console.log(`πŸ“„ Found ${totalPages} pages to stream.`);
649
-
650
- if (totalPages === 0) {
651
- throw new Error("No document pages found to stream. The content might be protected or not loaded correctly.");
652
- }
653
-
654
- // Set headers for streaming
655
- res.setHeader('Content-Type', 'application/json');
656
- res.setHeader('Transfer-Encoding', 'chunked');
657
-
658
- for (let i = 0; i < totalPages; i++) {
659
- console.log(`🎨 Rendering page ${i + 1} of ${totalPages}...`);
660
- const pageElement = pageElements[i];
661
- const imageData = await pageElement.screenshot({ type: 'png', encoding: 'base64' });
662
-
663
- const progressUpdate = {
664
- pageNumber: i + 1,
665
- totalPages: totalPages,
666
- imageData: `data:image/png;base64,${imageData}`
667
- };
668
-
669
- res.write(JSON.stringify(progressUpdate) + '\n'); // Send as a new line delimited JSON
670
- }
671
-
672
- console.log("βœ… All pages have been rendered and sent.");
673
 
674
- } catch (error) {
675
- console.error("❌ Error during streamed download:", error);
676
- const errorResponse = {
677
- error: `Failed to generate streamed PDF: ${error.message}`
678
- };
679
- if (!res.headersSent) {
680
- res.status(500).json(errorResponse);
681
- } else {
682
- res.write(JSON.stringify(errorResponse) + '\n');
683
  }
684
- } finally {
685
- if (browser) {
686
- console.log("πŸ”’ Closing browser for streaming...");
687
- await browser.close();
688
- }
689
- if (!res.writableEnded) {
690
- res.end(); // End the stream
691
  }
692
  }
693
- };
694
 
 
 
695
 
696
- // API Routes
 
 
697
 
698
- // Original endpoint for downloading the full PDF at once
699
- app.post('/api/download', async (req, res) => {
700
- const { url, filename, email, password } = req.body;
701
- if (!url) {
702
- return res.status(400).json({ error: 'URL is required.' });
703
  }
704
- if (!url.includes('studocu.com')) {
705
- return res.status(400).json({ error: 'Please provide a valid StuDocu URL.' });
 
706
  }
707
 
708
- let normalizedUrl = url.trim();
709
- if (!normalizedUrl.startsWith('http')) {
710
- normalizedUrl = 'https://' + normalizedUrl;
711
  }
712
 
713
- console.log(`🎯 Processing request for: ${normalizedUrl}`);
714
- try {
715
- const startTime = Date.now();
716
- const pdfBuffer = await studocuDownloader(normalizedUrl, { filename, email, password });
717
- const processingTime = ((Date.now() - startTime) / 1000).toFixed(2);
718
  res.setHeader('Content-Type', 'application/pdf');
719
  res.setHeader('Content-Disposition', 'attachment; filename=studocu-document.pdf');
720
- res.setHeader('Content-Length', pdfBuffer.length);
721
- res.send(pdfBuffer);
722
- console.log(`πŸŽ‰ Request completed successfully in ${processingTime}s`);
723
- } catch (error) {
724
- console.error(`❌ Failed to process ${normalizedUrl}:`, error.message);
725
- res.status(500).json({ error: error.message });
726
- }
727
- });
728
-
729
- // NEW: Endpoint for streaming the document page by page
730
- app.post('/api/download-stream', async (req, res) => {
731
- const { url, email, password } = req.body;
732
- if (!url) {
733
- return res.status(400).json({ error: 'URL is required.' });
734
- }
735
- if (!url.includes('studocu.com')) {
736
- return res.status(400).json({ error: 'Please provide a valid StuDocu URL.' });
737
- }
738
-
739
- let normalizedUrl = url.trim();
740
- if (!normalizedUrl.startsWith('http')) {
741
- normalizedUrl = 'https://' + normalizedUrl;
742
- }
743
-
744
- console.log(`🎯 Processing stream request for: ${normalizedUrl}`);
745
- try {
746
- await studocuDownloaderStreamed(normalizedUrl, { email, password }, res);
747
- console.log(`πŸŽ‰ Stream request completed for ${normalizedUrl}`);
748
- } catch (error) {
749
- console.error(`❌ Failed to process stream for ${normalizedUrl}:`, error.message);
750
- // Error is handled within the downloader function to ensure proper response closure
751
  }
752
  });
753
 
754
-
755
  app.get('/health', (req, res) => {
756
  res.json({
757
  status: 'healthy',
758
  timestamp: new Date().toISOString(),
759
- uptime: process.uptime()
 
760
  });
761
  });
762
 
763
  app.get('/', (req, res) => {
764
  res.json({
765
- message: 'πŸš€ Enhanced StuDocu Downloader API v5.3 - Advanced Bypass with Print Styles and Streaming',
766
- version: '5.3.1',
767
  features: [
768
  'πŸͺ Advanced cookie banner bypass',
769
- 'πŸ”“ Premium content unblurring (client-side only; server-side blur requires premium login)',
770
- 'πŸ”‘ Login support for full unblurred content access',
771
- 'πŸ€– Anti-bot detection evasion',
772
- 'πŸ“„ Full document content extraction with print styles for clean PDF',
773
- 'πŸ”„ Real-time page rendering and streaming to the frontend'
774
  ],
775
  endpoints: {
776
- download: 'POST /api/download (body: {url, filename?, email?, password?})',
777
- download_stream: 'POST /api/download-stream (body: {url, email?, password?})',
 
778
  health: 'GET /health'
779
- },
780
- note: 'For full unblurred content, provide premium email and password. Blurring is often server-side, so CSS bypass may not suffice without login.'
781
  });
782
  });
783
 
@@ -792,6 +695,6 @@ process.on('SIGINT', () => {
792
  });
793
 
794
  app.listen(port, () => {
795
- console.log(`πŸš€ Enhanced StuDocu Downloader v5.3.1 running on http://localhost:${port}`);
796
- console.log(`✨ Features: Advanced cookie bypass, content unblurring, login support, print styles, anti-detection, and real-time page streaming`);
797
  });
 
1
  const express = require('express');
2
+ const puppeteerExtra = require('puppeteer-extra'); // NEW: For stealth
3
+ const StealthPlugin = require('puppeteer-extra-plugin-stealth'); // NEW: Stealth plugin
4
  const cors = require('cors');
5
+ const { EventEmitter } = require('events');
6
+
7
+ puppeteerExtra.use(StealthPlugin()); // NEW: Enable stealth plugin
8
+
9
  const app = express();
10
  const port = 7860;
11
 
12
  app.use(cors());
13
  app.use(express.json());
14
 
15
+ // --- Progress Tracking and Job Storage --- (Unchanged)
16
+ const progressTrackers = new Map();
17
+ const downloadJobs = new Map();
18
+
19
+ class ProgressTracker extends EventEmitter {
20
+ constructor(sessionId) {
21
+ super();
22
+ this.sessionId = sessionId;
23
+ this.progress = 0;
24
+ this.status = 'initializing';
25
+ this.message = '';
26
+ }
27
+
28
+ updateProgress(progress, status, message) {
29
+ this.progress = progress;
30
+ this.status = status;
31
+ this.message = message;
32
+ const update = {
33
+ sessionId: this.sessionId,
34
+ progress,
35
+ status,
36
+ message,
37
+ timestamp: new Date().toISOString()
38
+ };
39
+ this.emit('progress', update);
40
+ console.log(`πŸ“Š [${this.sessionId}] ${progress}% - ${status}: ${message}`);
41
+ }
42
+ }
43
+
44
+ // --- Puppeteer Logic (Updated for Stealth and Reliability) ---
45
+ const bypassCookiesAndRestrictions = async (page, progressTracker) => {
46
+ progressTracker?.updateProgress(5, 'bypassing', 'Setting up cookie bypass...');
47
+
48
  console.log("πŸͺ Starting comprehensive cookie and restriction bypass...");
49
  // Step 1: Set cookies before page load
50
  const preCookies = [
 
65
  }
66
  }
67
 
68
+ // Step 2: Inject CSS to hide cookie banners immediately (Unchanged)
69
  await page.addStyleTag({
70
  content: `
71
  /* Hide all possible cookie banners */
 
106
  `
107
  });
108
 
109
+ // Step 3: Inject JavaScript to handle dynamic cookie banners (Unchanged)
110
  await page.evaluateOnNewDocument(() => {
111
  // Override common cookie consent functions
112
  window.cookieConsent = { accepted: true };
 
157
  }, 1000);
158
  });
159
 
160
+ progressTracker?.updateProgress(10, 'bypassing', 'Cookie bypass configured successfully');
161
  return true;
162
  };
163
 
164
+ const unblurContent = async (page, progressTracker) => {
165
+ progressTracker?.updateProgress(15, 'unblurring', 'Removing content restrictions...');
166
+
 
167
  console.log("πŸ”“ Unblurring content and bypassing premium restrictions...");
168
  await page.evaluate(() => {
 
169
  const removeRestrictions = () => {
170
  const removeBySelector = (selector) => {
171
  document.querySelectorAll(selector).forEach(el => el.remove());
172
  };
173
 
174
+ removeBySelector("#adbox, .adsbox, .ad-box, .banner-ads, .advert");
 
 
 
 
 
 
 
175
  removeBySelector(".PremiumBannerBlobWrapper_overflow-wrapper__xsaS8");
176
 
 
177
  const removeBlur = (element = document) => {
178
  element.querySelectorAll("*").forEach(el => {
179
  const style = window.getComputedStyle(el);
 
180
  if (
181
  style.filter?.includes("blur") ||
182
  style.backdropFilter?.includes("blur") ||
 
191
  el.classList.remove("blur", "blurred", "premium-blur");
192
  }
193
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
194
  });
195
  };
196
 
 
 
 
 
 
 
 
 
 
 
 
 
197
  removeBlur();
198
+ removeBySelector('[class*="blur" i], [class*="premium" i], [class*="paywall" i]');
199
 
 
 
 
 
200
  const contentSelectors = [
201
  '.document-content', '.page-content', '.content', '[data-page]', '[data-testid*="document"]',
202
  '[data-testid*="page"]', '.page', '.document-page', 'main', 'article'
 
210
  el.style.setProperty('pointer-events', 'auto', 'important');
211
  });
212
  });
 
 
 
 
 
 
 
 
 
 
 
 
213
  };
214
 
 
215
  removeRestrictions();
 
 
216
  const intervalId = setInterval(removeRestrictions, 2000);
217
+ setTimeout(() => clearInterval(intervalId), 60000);
 
 
 
 
218
  });
219
+
220
+ progressTracker?.updateProgress(20, 'unblurring', 'Content restrictions removed');
221
  };
222
 
223
+ const applyPrintStyles = async (page, progressTracker) => {
224
+ progressTracker?.updateProgress(85, 'styling', 'Applying print styles...');
225
+
 
226
  console.log("πŸ–¨οΈ Applying print styles for clean PDF...");
227
  await page.evaluate(() => {
228
  const style = document.createElement("style");
229
  style.id = "print-style-extension";
230
  style.innerHTML = `
231
  @page {
232
+ /* Set page size to A4 and remove default margins */
233
  size: A4 portrait;
234
+ margin: 0mm;
235
  }
236
  @media print {
237
  html, body {
238
+ /* Ensure the body takes the full width and has no extra padding/margin */
239
+ width: 210mm !important;
240
+ height: auto !important;
241
  margin: 0 !important;
242
  padding: 0 !important;
243
  overflow: visible !important;
244
+ background: white !important;
245
+ color: black !important;
246
  }
247
+
248
+ /* Remove all unwanted elements like headers, footers, sidebars, etc. */
249
  header, footer, nav, aside, .no-print, .ads, .sidebar, .premium-banner,
250
+ [class*="Header"], [class*="Footer"], [class*="Sidebar"], [id*="Header"],
251
  .ViewerToolbar, .Layout_info-bar-wrapper__He0Ho, .Sidebar_sidebar-scrollable__kqeBZ,
252
  .HeaderWrapper_header-wrapper__mCmf3, .Layout_visible-content-bottom-wrapper-sticky__yaaAB,
253
  .Layout_bottom-section-wrapper__yBWWk, .Layout_footer-wrapper__bheJQ,
 
255
  .Layout_sidebar-wrapper__unavM, .Layout_is-open__9DQr4 {
256
  display: none !important;
257
  }
258
+
259
+ /* Force all elements to have a transparent background and no shadow */
 
 
260
  * {
261
  box-shadow: none !important;
262
  background: transparent !important;
263
+ color: inherit !important;
264
  }
265
+
266
+ /*
267
+ * KEY FIX: Target the main document container.
268
+ * Force it to be a block element, remove any transforms or max-widths,
269
+ * and center it perfectly within the page.
270
+ */
271
+ .Viewer_document-wrapper__JPBWQ, .Viewer_document-wrapper__LXzoQ,
272
+ .Viewer_document-wrapper__XsO4j, .page-content, .document-viewer, #page-container {
273
+ position: static !important;
274
+ display: block !important;
275
  width: 100% !important;
276
+ max-width: none !important;
277
+ margin: 0 !important;
278
+ padding: 0 !important;
279
+ box-sizing: border-box; /* Include padding in width calculation */
280
+ transform: none !important;
281
  }
282
+
283
+ /* Ensure individual pages and images within the document use the full width */
284
  [data-page], .page, .document-page, img {
285
  page-break-after: always !important;
286
  page-break-inside: avoid !important;
 
288
  width: 100% !important;
289
  max-width: 100% !important;
290
  height: auto !important;
291
+ display: block !important;
292
+ margin: 0 !important;
293
+ padding: 0 !important;
294
  }
295
  }
296
  `;
297
  document.head.appendChild(style);
298
  });
299
+
300
+ progressTracker?.updateProgress(88, 'styling', 'Print styles applied successfully');
301
  };
302
 
303
+ const studocuDownloader = async (url, options = {}, progressTracker = null) => {
 
 
 
304
  let browser;
305
  try {
306
+ progressTracker?.updateProgress(0, 'initializing', 'Starting browser...');
307
+
308
+ console.log("πŸš€ Launching browser with enhanced stealth configuration...");
309
+ browser = await puppeteerExtra.launch({
310
  headless: true,
311
  args: [
312
  '--no-sandbox',
 
325
  '--disable-web-security',
326
  '--disable-features=site-per-process',
327
  '--disable-blink-features=AutomationControlled',
328
+ '--disable-extensions',
329
+ '--ignore-certificate-errors'
330
  ],
331
+ ignoreHTTPSErrors: true,
332
  timeout: 300000,
333
+ // --- FIX: Increased protocolTimeout for long-running operations ---
334
+ protocolTimeout: 600000 // 10 minutes, increased from the default 30 seconds
335
  });
336
 
337
  const page = await browser.newPage();
338
 
339
+ // --- FIX: Increase default navigation and action timeouts ---
340
+ page.setDefaultNavigationTimeout(180000); // 3 minutes for navigation
341
+ page.setDefaultTimeout(180000); // 3 minutes for other actions (e.g., waitForSelector)
342
+
343
+ progressTracker?.updateProgress(2, 'initializing', 'Configuring browser settings...');
344
+
345
+ await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36');
346
+ await page.setViewport({ width: 794, height: 1122 }); // A4 size in pixels at 96 DPI
347
 
 
348
  await page.evaluateOnNewDocument(() => {
349
  Object.defineProperty(navigator, 'webdriver', { get: () => undefined });
350
  Object.defineProperty(navigator, 'languages', { get: () => ['en-US', 'en'] });
351
  Object.defineProperty(navigator, 'plugins', { get: () => [1, 2, 3, 4, 5] });
352
  });
353
 
354
+ await bypassCookiesAndRestrictions(page, progressTracker);
 
355
 
 
356
  await page.setRequestInterception(true);
357
  page.on('request', (req) => {
358
  const resourceType = req.resourceType();
359
+ const reqUrl = req.url().toLowerCase();
360
+
361
+ if (resourceType === 'document') {
362
+ req.continue();
363
+ return;
364
+ }
365
+
366
  if (
367
  reqUrl.includes('doubleclick') ||
368
  reqUrl.includes('googletagmanager') ||
 
374
  reqUrl.includes('mixpanel') ||
375
  reqUrl.includes('onetrust') ||
376
  reqUrl.includes('cookielaw') ||
377
+ (resourceType === 'other' && reqUrl.includes('/track/'))
378
  ) {
379
  req.abort();
380
  } else {
 
382
  }
383
  });
384
 
 
385
  if (options.email && options.password) {
386
+ progressTracker?.updateProgress(12, 'authenticating', 'Logging into StuDocu...');
387
+
388
  console.log("πŸ”‘ Logging in to StuDocu...");
389
+ await page.goto('https://www.studocu.com/en-us/login', { waitUntil: 'domcontentloaded' });
390
+ await page.waitForSelector('#email');
391
  await page.type('#email', options.email);
392
  await page.type('#password', options.password);
393
  await page.click('button[type="submit"]');
394
  try {
395
+ await page.waitForNavigation({ waitUntil: 'networkidle2' });
396
+ await page.waitForSelector('.user-profile, [data-testid="user-menu"]');
 
397
  console.log("βœ… Login successful.");
398
+ progressTracker?.updateProgress(18, 'authenticated', 'Login successful');
399
  } catch (e) {
400
  console.error("❌ Login failed:", e.message);
401
+ throw new Error("Login failed. Check credentials or try again.");
402
  }
 
 
403
  }
404
 
405
+ progressTracker?.updateProgress(25, 'navigating', 'Navigating to homepage first for session setup...');
406
+ console.log(`πŸ“„ Navigating to homepage to simulate natural session...`);
407
+ await page.goto('https://www.studocu.com/en-us', { waitUntil: 'domcontentloaded' });
408
+ await new Promise(resolve => setTimeout(resolve, 3000));
409
+
410
+ progressTracker?.updateProgress(30, 'navigating', 'Navigating to document...');
411
  console.log(`πŸ“„ Navigating to ${url}...`);
412
+
413
  let navigationSuccess = false;
414
  let attempts = 0;
415
+ const maxAttempts = 5;
416
  while (!navigationSuccess && attempts < maxAttempts) {
417
  try {
418
  attempts++;
419
+ progressTracker?.updateProgress(30 + (attempts * 5), 'navigating', `Navigation attempt ${attempts}/${maxAttempts}`);
420
  console.log(`Navigation attempt ${attempts}/${maxAttempts}`);
421
+ await page.goto(url, { waitUntil: 'domcontentloaded' });
422
  navigationSuccess = true;
423
  } catch (e) {
424
  console.log(`Navigation attempt ${attempts} failed:`, e.message);
425
  if (attempts >= maxAttempts) throw e;
426
+ await new Promise(resolve => setTimeout(resolve, 15000));
427
  }
428
  }
429
 
430
+ progressTracker?.updateProgress(40, 'loading', 'Page loaded, waiting for content...');
431
  await new Promise(resolve => setTimeout(resolve, 5000));
432
 
433
+ await unblurContent(page, progressTracker);
 
434
 
435
+ progressTracker?.updateProgress(45, 'loading', 'Waiting for document content...');
436
  console.log("⏳ Waiting for document content to load...");
437
+
438
  const contentSelectors = [
439
  '.document-content', '.page-content', '[data-page]', '[data-testid*="document"]',
440
  'img[src*="document"]', 'img[src*="page"]', '.page', 'main img', 'article img'
 
442
  let contentFound = false;
443
  for (const selector of contentSelectors) {
444
  try {
445
+ await page.waitForSelector(selector);
446
  console.log(`βœ… Found content with selector: ${selector}`);
447
  contentFound = true;
448
  break;
 
455
  console.log("⚠️ No specific content selector found, proceeding with page content...");
456
  }
457
 
458
+ progressTracker?.updateProgress(50, 'scrolling', 'Loading all document pages...');
459
  console.log("πŸ“œ Loading all document pages with enhanced slow scroll...");
460
+
461
  await page.evaluate(async () => {
462
  const delay = (ms) => new Promise((res) => setTimeout(res, ms));
463
  let scrollHeight = document.body.scrollHeight;
 
467
  while (totalHeight < scrollHeight) {
468
  window.scrollBy(0, distance);
469
  totalHeight += distance;
470
+ await delay(500);
471
  }
472
+ await delay(2000);
473
  const newHeight = document.body.scrollHeight;
474
  if (newHeight === scrollHeight) break;
475
  scrollHeight = newHeight;
476
  }
 
477
  window.scrollTo({ top: 0, behavior: "smooth" });
478
  await delay(1000);
479
  });
480
 
481
+ progressTracker?.updateProgress(70, 'processing', 'Processing loaded content...');
 
482
 
483
+ await unblurContent(page, progressTracker);
484
+
485
+ progressTracker?.updateProgress(75, 'loading_images', 'Loading images...');
486
  console.log("πŸ–ΌοΈ Waiting for all images to load...");
487
+
488
  await page.evaluate(async () => {
489
  const images = Array.from(document.querySelectorAll('img'));
490
  await Promise.all(images.map(img => {
 
497
  }));
498
  });
499
 
500
+ await new Promise(resolve => setTimeout(resolve, 5000));
501
+ progressTracker?.updateProgress(80, 'finalizing', 'Preparing document for PDF generation...');
502
 
 
 
503
  await page.evaluate(() => {
504
  const getDocumentHeight = () => Math.max(
505
  document.body.scrollHeight, document.body.offsetHeight,
 
511
  document.body.style.overflow = 'hidden !important';
512
  });
513
 
 
514
  const contentCheck = await page.evaluate(() => {
515
  const textContent = document.body.textContent || '';
516
  const images = document.querySelectorAll('img');
 
522
  totalText: textContent.length,
523
  totalImages: images.length,
524
  documentImages: documentImages.length,
525
+ hasDocumentContent: documentImages.length > 0 || textContent.length > 1000
 
526
  };
527
  });
528
+
529
  console.log("πŸ“Š Content verification:", {
530
  textLength: contentCheck.totalText,
531
  images: contentCheck.totalImages,
 
534
  });
535
 
536
  if (!contentCheck.hasDocumentContent) {
537
+ console.warn("⚠️ Warning: Limited document content detected.");
538
  }
539
 
540
+ await applyPrintStyles(page, progressTracker);
 
 
 
541
  await page.emulateMediaType('print');
542
 
543
+ progressTracker?.updateProgress(90, 'generating', 'Generating PDF...');
544
  console.log("πŸ”„ Generating PDF...");
545
+
546
  const pdfBuffer = await page.pdf({
547
  printBackground: true,
548
  preferCSSPageSize: true,
549
  displayHeaderFooter: false,
550
+ // --- FIX: Increased PDF generation timeout ---
551
+ timeout: 600000, // 10 minutes, increased from the default 30 seconds
552
  scale: 1,
553
  omitBackground: false
554
  });
555
 
556
+ progressTracker?.updateProgress(100, 'completed', 'PDF generated successfully!');
557
  console.log(`βœ… PDF generated successfully! Size: ${(pdfBuffer.length / 1024 / 1024).toFixed(2)} MB`);
558
  return pdfBuffer;
559
 
560
  } catch (error) {
561
+ progressTracker?.updateProgress(-1, 'error', error.message);
562
  console.error("❌ Error during PDF generation:", error);
563
+ throw error;
 
 
 
 
 
 
 
 
564
  } finally {
565
  if (browser) {
566
  console.log("πŸ”’ Closing browser...");
 
573
  }
574
  };
575
 
576
+ // --- API Routes --- (Unchanged)
577
+ app.post('/api/request-download', (req, res) => {
578
+ const { url, email, password } = req.body;
579
+ if (!url || !url.includes('studocu.com')) {
580
+ return res.status(400).json({ error: 'Please provide a valid StuDocu URL.' });
581
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
582
 
583
+ const sessionId = Date.now().toString();
584
+ const progressTracker = new ProgressTracker(sessionId);
 
 
 
 
 
 
 
 
585
 
586
+ progressTrackers.set(sessionId, progressTracker);
587
+ downloadJobs.set(sessionId, { status: 'processing' });
 
588
 
589
+ console.log(`🎯 Processing request for: ${url} [Session: ${sessionId}]`);
590
 
591
+ res.json({ sessionId });
 
592
 
593
+ studocuDownloader(url, { email, password }, progressTracker)
594
+ .then(pdfBuffer => {
595
+ downloadJobs.set(sessionId, { status: 'completed', buffer: pdfBuffer });
596
+ progressTrackers.delete(sessionId);
597
+ })
598
+ .catch(error => {
599
+ downloadJobs.set(sessionId, { status: 'error', message: error.message });
600
+ progressTrackers.delete(sessionId);
 
 
 
 
 
 
 
601
  });
602
+ });
603
 
604
+ app.get('/api/progress/:sessionId', (req, res) => {
605
+ const { sessionId } = req.params;
606
+ const tracker = progressTrackers.get(sessionId);
607
+
608
+ if (tracker) {
609
+ return res.json({
610
+ sessionId,
611
+ progress: tracker.progress,
612
+ status: tracker.status,
613
+ message: tracker.message,
614
+ timestamp: new Date().toISOString()
615
+ });
616
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
617
 
618
+ const job = downloadJobs.get(sessionId);
619
+ if (job) {
620
+ if (job.status === 'completed') {
621
+ return res.json({ sessionId, progress: 100, status: 'completed', message: 'PDF generated successfully!' });
 
 
 
 
 
622
  }
623
+ if (job.status === 'error') {
624
+ return res.json({ sessionId, progress: -1, status: 'error', message: job.message });
 
 
 
 
 
625
  }
626
  }
 
627
 
628
+ return res.status(404).json({ error: 'Session not found' });
629
+ });
630
 
631
+ app.get('/api/download/:sessionId', (req, res) => {
632
+ const { sessionId } = req.params;
633
+ const job = downloadJobs.get(sessionId);
634
 
635
+ if (!job) {
636
+ return res.status(404).json({ error: 'Download session not found or expired.' });
 
 
 
637
  }
638
+
639
+ if (job.status === 'processing') {
640
+ return res.status(400).json({ error: 'Download is still processing.' });
641
  }
642
 
643
+ if (job.status === 'error') {
644
+ return res.status(500).json({ error: `Failed to generate PDF: ${job.message}` });
 
645
  }
646
 
647
+ if (job.status === 'completed' && job.buffer) {
 
 
 
 
648
  res.setHeader('Content-Type', 'application/pdf');
649
  res.setHeader('Content-Disposition', 'attachment; filename=studocu-document.pdf');
650
+ res.send(job.buffer);
651
+ } else {
652
+ res.status(500).json({ error: 'An unknown error occurred.' });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
653
  }
654
  });
655
 
656
+ // --- Health and Info Endpoints (Unchanged) ---
657
  app.get('/health', (req, res) => {
658
  res.json({
659
  status: 'healthy',
660
  timestamp: new Date().toISOString(),
661
+ uptime: process.uptime(),
662
+ activeDownloads: progressTrackers.size
663
  });
664
  });
665
 
666
  app.get('/', (req, res) => {
667
  res.json({
668
+ message: 'πŸš€ Enhanced StuDocu Downloader API v5.2 - Real-time Progress Tracking with Stealth',
669
+ version: '5.2.0',
670
  features: [
671
  'πŸͺ Advanced cookie banner bypass',
672
+ 'πŸ”“ Premium content unblurring',
673
+ 'πŸ”‘ Login support for full access',
674
+ 'πŸ“Š Real-time progress tracking via polling',
675
+ 'πŸ“„ Clean PDF generation with print styles',
676
+ 'πŸ•΅οΈ Enhanced stealth to evade bot detection'
677
  ],
678
  endpoints: {
679
+ request: 'POST /api/request-download (body: {url, filename?, email?, password?})',
680
+ progress: 'GET /api/progress/:sessionId',
681
+ download: 'GET /api/download/:sessionId',
682
  health: 'GET /health'
683
+ }
 
684
  });
685
  });
686
 
 
695
  });
696
 
697
  app.listen(port, () => {
698
+ console.log(`πŸš€ Enhanced StuDocu Downloader v5.2.0 running on http://localhost:${port}`);
699
+ console.log(`✨ Features: Real-time progress tracking, enhanced stealth, and user feedback`);
700
  });
server.js CHANGED
@@ -306,7 +306,7 @@ const studocuDownloader = async (url, options = {}, progressTracker = null) => {
306
  progressTracker?.updateProgress(0, 'initializing', 'Starting browser...');
307
 
308
  console.log("πŸš€ Launching browser with enhanced stealth configuration...");
309
- browser = await puppeteerExtra.launch({ // UPDATED: Use puppeteerExtra
310
  headless: true,
311
  args: [
312
  '--no-sandbox',
@@ -330,26 +330,29 @@ const studocuDownloader = async (url, options = {}, progressTracker = null) => {
330
  ],
331
  ignoreHTTPSErrors: true,
332
  timeout: 300000,
 
 
333
  });
334
 
335
  const page = await browser.newPage();
336
 
 
 
 
 
337
  progressTracker?.updateProgress(2, 'initializing', 'Configuring browser settings...');
338
 
339
  await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36');
340
  await page.setViewport({ width: 794, height: 1122 }); // A4 size in pixels at 96 DPI
341
 
342
- // NOTE: Stealth plugin handles most of this, but keeping for extra safety
343
  await page.evaluateOnNewDocument(() => {
344
  Object.defineProperty(navigator, 'webdriver', { get: () => undefined });
345
  Object.defineProperty(navigator, 'languages', { get: () => ['en-US', 'en'] });
346
  Object.defineProperty(navigator, 'plugins', { get: () => [1, 2, 3, 4, 5] });
347
  });
348
 
349
- // Set up cookie and content bypass
350
  await bypassCookiesAndRestrictions(page, progressTracker);
351
 
352
- // Block unnecessary resources (UPDATED: Always continue for 'document' to prevent navigation failures)
353
  await page.setRequestInterception(true);
354
  page.on('request', (req) => {
355
  const resourceType = req.resourceType();
@@ -371,7 +374,7 @@ const studocuDownloader = async (url, options = {}, progressTracker = null) => {
371
  reqUrl.includes('mixpanel') ||
372
  reqUrl.includes('onetrust') ||
373
  reqUrl.includes('cookielaw') ||
374
- (resourceType === 'other' && reqUrl.includes('/track/')) // UPDATED: More specific to avoid over-blocking
375
  ) {
376
  req.abort();
377
  } else {
@@ -379,19 +382,18 @@ const studocuDownloader = async (url, options = {}, progressTracker = null) => {
379
  }
380
  });
381
 
382
- // Login if credentials provided
383
  if (options.email && options.password) {
384
  progressTracker?.updateProgress(12, 'authenticating', 'Logging into StuDocu...');
385
 
386
  console.log("πŸ”‘ Logging in to StuDocu...");
387
- await page.goto('https://www.studocu.com/en-us/login', { waitUntil: 'domcontentloaded', timeout: 120000 });
388
- await page.waitForSelector('#email', { timeout: 15000 });
389
  await page.type('#email', options.email);
390
  await page.type('#password', options.password);
391
  await page.click('button[type="submit"]');
392
  try {
393
- await page.waitForNavigation({ waitUntil: 'networkidle2', timeout: 30000 });
394
- await page.waitForSelector('.user-profile, [data-testid="user-menu"]', { timeout: 10000 });
395
  console.log("βœ… Login successful.");
396
  progressTracker?.updateProgress(18, 'authenticated', 'Login successful');
397
  } catch (e) {
@@ -402,8 +404,8 @@ const studocuDownloader = async (url, options = {}, progressTracker = null) => {
402
 
403
  progressTracker?.updateProgress(25, 'navigating', 'Navigating to homepage first for session setup...');
404
  console.log(`πŸ“„ Navigating to homepage to simulate natural session...`);
405
- await page.goto('https://www.studocu.com/en-us', { waitUntil: 'domcontentloaded', timeout: 150000 }); // NEW: Preliminary homepage visit
406
- await new Promise(resolve => setTimeout(resolve, 3000)); // Short delay for session stabilization
407
 
408
  progressTracker?.updateProgress(30, 'navigating', 'Navigating to document...');
409
  console.log(`πŸ“„ Navigating to ${url}...`);
@@ -416,22 +418,20 @@ const studocuDownloader = async (url, options = {}, progressTracker = null) => {
416
  attempts++;
417
  progressTracker?.updateProgress(30 + (attempts * 5), 'navigating', `Navigation attempt ${attempts}/${maxAttempts}`);
418
  console.log(`Navigation attempt ${attempts}/${maxAttempts}`);
419
- await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 150000 }); // UPDATED: Increased timeout to 2.5 min
420
  navigationSuccess = true;
421
  } catch (e) {
422
  console.log(`Navigation attempt ${attempts} failed:`, e.message);
423
  if (attempts >= maxAttempts) throw e;
424
- await new Promise(resolve => setTimeout(resolve, 15000)); // UPDATED: Increased retry delay to 15s
425
  }
426
  }
427
 
428
  progressTracker?.updateProgress(40, 'loading', 'Page loaded, waiting for content...');
429
  await new Promise(resolve => setTimeout(resolve, 5000));
430
 
431
- // Apply content unblurring
432
  await unblurContent(page, progressTracker);
433
 
434
- // Wait for document content
435
  progressTracker?.updateProgress(45, 'loading', 'Waiting for document content...');
436
  console.log("⏳ Waiting for document content to load...");
437
 
@@ -442,7 +442,7 @@ const studocuDownloader = async (url, options = {}, progressTracker = null) => {
442
  let contentFound = false;
443
  for (const selector of contentSelectors) {
444
  try {
445
- await page.waitForSelector(selector, { timeout: 20000 });
446
  console.log(`βœ… Found content with selector: ${selector}`);
447
  contentFound = true;
448
  break;
@@ -455,7 +455,6 @@ const studocuDownloader = async (url, options = {}, progressTracker = null) => {
455
  console.log("⚠️ No specific content selector found, proceeding with page content...");
456
  }
457
 
458
- // Enhanced scrolling to load all content
459
  progressTracker?.updateProgress(50, 'scrolling', 'Loading all document pages...');
460
  console.log("πŸ“œ Loading all document pages with enhanced slow scroll...");
461
 
@@ -481,10 +480,8 @@ const studocuDownloader = async (url, options = {}, progressTracker = null) => {
481
 
482
  progressTracker?.updateProgress(70, 'processing', 'Processing loaded content...');
483
 
484
- // Re-apply unblur after loading new content
485
  await unblurContent(page, progressTracker);
486
 
487
- // Wait for all images to load
488
  progressTracker?.updateProgress(75, 'loading_images', 'Loading images...');
489
  console.log("πŸ–ΌοΈ Waiting for all images to load...");
490
 
@@ -503,7 +500,6 @@ const studocuDownloader = async (url, options = {}, progressTracker = null) => {
503
  await new Promise(resolve => setTimeout(resolve, 5000));
504
  progressTracker?.updateProgress(80, 'finalizing', 'Preparing document for PDF generation...');
505
 
506
- // Set exact height
507
  await page.evaluate(() => {
508
  const getDocumentHeight = () => Math.max(
509
  document.body.scrollHeight, document.body.offsetHeight,
@@ -515,7 +511,6 @@ const studocuDownloader = async (url, options = {}, progressTracker = null) => {
515
  document.body.style.overflow = 'hidden !important';
516
  });
517
 
518
- // Content verification
519
  const contentCheck = await page.evaluate(() => {
520
  const textContent = document.body.textContent || '';
521
  const images = document.querySelectorAll('img');
@@ -542,7 +537,6 @@ const studocuDownloader = async (url, options = {}, progressTracker = null) => {
542
  console.warn("⚠️ Warning: Limited document content detected.");
543
  }
544
 
545
- // Apply print styles and generate PDF
546
  await applyPrintStyles(page, progressTracker);
547
  await page.emulateMediaType('print');
548
 
@@ -551,9 +545,10 @@ const studocuDownloader = async (url, options = {}, progressTracker = null) => {
551
 
552
  const pdfBuffer = await page.pdf({
553
  printBackground: true,
554
- preferCSSPageSize: true, // Use the @page size
555
  displayHeaderFooter: false,
556
- timeout: 180000,
 
557
  scale: 1,
558
  omitBackground: false
559
  });
@@ -593,20 +588,16 @@ app.post('/api/request-download', (req, res) => {
593
 
594
  console.log(`🎯 Processing request for: ${url} [Session: ${sessionId}]`);
595
 
596
- // Respond to the client immediately with the session ID
597
  res.json({ sessionId });
598
 
599
- // --- Start the PDF generation in the background ---
600
  studocuDownloader(url, { email, password }, progressTracker)
601
  .then(pdfBuffer => {
602
- // Store the successful result
603
  downloadJobs.set(sessionId, { status: 'completed', buffer: pdfBuffer });
604
- progressTrackers.delete(sessionId); // Clean up live tracker
605
  })
606
  .catch(error => {
607
- // Store the error
608
  downloadJobs.set(sessionId, { status: 'error', message: error.message });
609
- progressTrackers.delete(sessionId); // Clean up live tracker
610
  });
611
  });
612
 
@@ -615,7 +606,6 @@ app.get('/api/progress/:sessionId', (req, res) => {
615
  const tracker = progressTrackers.get(sessionId);
616
 
617
  if (tracker) {
618
- // Job is in progress, return live data
619
  return res.json({
620
  sessionId,
621
  progress: tracker.progress,
@@ -627,7 +617,6 @@ app.get('/api/progress/:sessionId', (req, res) => {
627
 
628
  const job = downloadJobs.get(sessionId);
629
  if (job) {
630
- // Job is finished, return final state
631
  if (job.status === 'completed') {
632
  return res.json({ sessionId, progress: 100, status: 'completed', message: 'PDF generated successfully!' });
633
  }
@@ -659,8 +648,6 @@ app.get('/api/download/:sessionId', (req, res) => {
659
  res.setHeader('Content-Type', 'application/pdf');
660
  res.setHeader('Content-Disposition', 'attachment; filename=studocu-document.pdf');
661
  res.send(job.buffer);
662
- // Optional: Clean up the job after download to save memory
663
- // downloadJobs.delete(sessionId);
664
  } else {
665
  res.status(500).json({ error: 'An unknown error occurred.' });
666
  }
 
306
  progressTracker?.updateProgress(0, 'initializing', 'Starting browser...');
307
 
308
  console.log("πŸš€ Launching browser with enhanced stealth configuration...");
309
+ browser = await puppeteerExtra.launch({
310
  headless: true,
311
  args: [
312
  '--no-sandbox',
 
330
  ],
331
  ignoreHTTPSErrors: true,
332
  timeout: 300000,
333
+ // --- FIX: Increased protocolTimeout for long-running operations ---
334
+ protocolTimeout: 600000 // 10 minutes, increased from the default 30 seconds
335
  });
336
 
337
  const page = await browser.newPage();
338
 
339
+ // --- FIX: Increase default navigation and action timeouts ---
340
+ page.setDefaultNavigationTimeout(180000); // 3 minutes for navigation
341
+ page.setDefaultTimeout(180000); // 3 minutes for other actions (e.g., waitForSelector)
342
+
343
  progressTracker?.updateProgress(2, 'initializing', 'Configuring browser settings...');
344
 
345
  await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36');
346
  await page.setViewport({ width: 794, height: 1122 }); // A4 size in pixels at 96 DPI
347
 
 
348
  await page.evaluateOnNewDocument(() => {
349
  Object.defineProperty(navigator, 'webdriver', { get: () => undefined });
350
  Object.defineProperty(navigator, 'languages', { get: () => ['en-US', 'en'] });
351
  Object.defineProperty(navigator, 'plugins', { get: () => [1, 2, 3, 4, 5] });
352
  });
353
 
 
354
  await bypassCookiesAndRestrictions(page, progressTracker);
355
 
 
356
  await page.setRequestInterception(true);
357
  page.on('request', (req) => {
358
  const resourceType = req.resourceType();
 
374
  reqUrl.includes('mixpanel') ||
375
  reqUrl.includes('onetrust') ||
376
  reqUrl.includes('cookielaw') ||
377
+ (resourceType === 'other' && reqUrl.includes('/track/'))
378
  ) {
379
  req.abort();
380
  } else {
 
382
  }
383
  });
384
 
 
385
  if (options.email && options.password) {
386
  progressTracker?.updateProgress(12, 'authenticating', 'Logging into StuDocu...');
387
 
388
  console.log("πŸ”‘ Logging in to StuDocu...");
389
+ await page.goto('https://www.studocu.com/en-us/login', { waitUntil: 'domcontentloaded' });
390
+ await page.waitForSelector('#email');
391
  await page.type('#email', options.email);
392
  await page.type('#password', options.password);
393
  await page.click('button[type="submit"]');
394
  try {
395
+ await page.waitForNavigation({ waitUntil: 'networkidle2' });
396
+ await page.waitForSelector('.user-profile, [data-testid="user-menu"]');
397
  console.log("βœ… Login successful.");
398
  progressTracker?.updateProgress(18, 'authenticated', 'Login successful');
399
  } catch (e) {
 
404
 
405
  progressTracker?.updateProgress(25, 'navigating', 'Navigating to homepage first for session setup...');
406
  console.log(`πŸ“„ Navigating to homepage to simulate natural session...`);
407
+ await page.goto('https://www.studocu.com/en-us', { waitUntil: 'domcontentloaded' });
408
+ await new Promise(resolve => setTimeout(resolve, 3000));
409
 
410
  progressTracker?.updateProgress(30, 'navigating', 'Navigating to document...');
411
  console.log(`πŸ“„ Navigating to ${url}...`);
 
418
  attempts++;
419
  progressTracker?.updateProgress(30 + (attempts * 5), 'navigating', `Navigation attempt ${attempts}/${maxAttempts}`);
420
  console.log(`Navigation attempt ${attempts}/${maxAttempts}`);
421
+ await page.goto(url, { waitUntil: 'domcontentloaded' });
422
  navigationSuccess = true;
423
  } catch (e) {
424
  console.log(`Navigation attempt ${attempts} failed:`, e.message);
425
  if (attempts >= maxAttempts) throw e;
426
+ await new Promise(resolve => setTimeout(resolve, 15000));
427
  }
428
  }
429
 
430
  progressTracker?.updateProgress(40, 'loading', 'Page loaded, waiting for content...');
431
  await new Promise(resolve => setTimeout(resolve, 5000));
432
 
 
433
  await unblurContent(page, progressTracker);
434
 
 
435
  progressTracker?.updateProgress(45, 'loading', 'Waiting for document content...');
436
  console.log("⏳ Waiting for document content to load...");
437
 
 
442
  let contentFound = false;
443
  for (const selector of contentSelectors) {
444
  try {
445
+ await page.waitForSelector(selector);
446
  console.log(`βœ… Found content with selector: ${selector}`);
447
  contentFound = true;
448
  break;
 
455
  console.log("⚠️ No specific content selector found, proceeding with page content...");
456
  }
457
 
 
458
  progressTracker?.updateProgress(50, 'scrolling', 'Loading all document pages...');
459
  console.log("πŸ“œ Loading all document pages with enhanced slow scroll...");
460
 
 
480
 
481
  progressTracker?.updateProgress(70, 'processing', 'Processing loaded content...');
482
 
 
483
  await unblurContent(page, progressTracker);
484
 
 
485
  progressTracker?.updateProgress(75, 'loading_images', 'Loading images...');
486
  console.log("πŸ–ΌοΈ Waiting for all images to load...");
487
 
 
500
  await new Promise(resolve => setTimeout(resolve, 5000));
501
  progressTracker?.updateProgress(80, 'finalizing', 'Preparing document for PDF generation...');
502
 
 
503
  await page.evaluate(() => {
504
  const getDocumentHeight = () => Math.max(
505
  document.body.scrollHeight, document.body.offsetHeight,
 
511
  document.body.style.overflow = 'hidden !important';
512
  });
513
 
 
514
  const contentCheck = await page.evaluate(() => {
515
  const textContent = document.body.textContent || '';
516
  const images = document.querySelectorAll('img');
 
537
  console.warn("⚠️ Warning: Limited document content detected.");
538
  }
539
 
 
540
  await applyPrintStyles(page, progressTracker);
541
  await page.emulateMediaType('print');
542
 
 
545
 
546
  const pdfBuffer = await page.pdf({
547
  printBackground: true,
548
+ preferCSSPageSize: true,
549
  displayHeaderFooter: false,
550
+ // --- FIX: Increased PDF generation timeout ---
551
+ timeout: 600000, // 10 minutes, increased from the default 30 seconds
552
  scale: 1,
553
  omitBackground: false
554
  });
 
588
 
589
  console.log(`🎯 Processing request for: ${url} [Session: ${sessionId}]`);
590
 
 
591
  res.json({ sessionId });
592
 
 
593
  studocuDownloader(url, { email, password }, progressTracker)
594
  .then(pdfBuffer => {
 
595
  downloadJobs.set(sessionId, { status: 'completed', buffer: pdfBuffer });
596
+ progressTrackers.delete(sessionId);
597
  })
598
  .catch(error => {
 
599
  downloadJobs.set(sessionId, { status: 'error', message: error.message });
600
+ progressTrackers.delete(sessionId);
601
  });
602
  });
603
 
 
606
  const tracker = progressTrackers.get(sessionId);
607
 
608
  if (tracker) {
 
609
  return res.json({
610
  sessionId,
611
  progress: tracker.progress,
 
617
 
618
  const job = downloadJobs.get(sessionId);
619
  if (job) {
 
620
  if (job.status === 'completed') {
621
  return res.json({ sessionId, progress: 100, status: 'completed', message: 'PDF generated successfully!' });
622
  }
 
648
  res.setHeader('Content-Type', 'application/pdf');
649
  res.setHeader('Content-Disposition', 'attachment; filename=studocu-document.pdf');
650
  res.send(job.buffer);
 
 
651
  } else {
652
  res.status(500).json({ error: 'An unknown error occurred.' });
653
  }