framer-export 4.4.3 → 5.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -8
- package/dist/cli/index.js +2241 -443
- package/dist/cli/index.js.map +1 -1
- package/package.json +13 -5
package/dist/cli/index.js
CHANGED
|
@@ -15,15 +15,15 @@ var init_package = __esm({
|
|
|
15
15
|
"package.json"() {
|
|
16
16
|
package_default = {
|
|
17
17
|
name: "framer-export",
|
|
18
|
-
version: "
|
|
19
|
-
description: "Export
|
|
18
|
+
version: "5.0.0-beta.1",
|
|
19
|
+
description: "Export 25+ website platforms (Framer, Webflow, Wix, Shopify, Notion, WordPress, Squarespace, Ghost, and more) into a fully working local mirror. Downloads all assets, strips badges, rewrites URLs, and pretty-prints JS.",
|
|
20
20
|
type: "module",
|
|
21
21
|
main: "dist/cli/index.js",
|
|
22
22
|
types: "dist/cli/index.d.ts",
|
|
23
23
|
bin: {
|
|
24
|
-
"framer-export": "
|
|
25
|
-
fexport: "
|
|
26
|
-
framerexport: "
|
|
24
|
+
"framer-export": "bin/framer-export.js",
|
|
25
|
+
fexport: "bin/fexport.js",
|
|
26
|
+
framerexport: "bin/framerexport.js"
|
|
27
27
|
},
|
|
28
28
|
files: [
|
|
29
29
|
"dist/",
|
|
@@ -44,6 +44,14 @@ var init_package = __esm({
|
|
|
44
44
|
"framer",
|
|
45
45
|
"webflow",
|
|
46
46
|
"wix",
|
|
47
|
+
"shopify",
|
|
48
|
+
"notion",
|
|
49
|
+
"wordpress",
|
|
50
|
+
"squarespace",
|
|
51
|
+
"ghost",
|
|
52
|
+
"carrd",
|
|
53
|
+
"bubble",
|
|
54
|
+
"gumroad",
|
|
47
55
|
"exporter",
|
|
48
56
|
"export",
|
|
49
57
|
"scraper",
|
|
@@ -164,7 +172,7 @@ function showBanner() {
|
|
|
164
172
|
`
|
|
165
173
|
${ui.primary.bold("f-export")} ${ui.muted(`v${package_default.version}`)} ${chip("beta ui")}`
|
|
166
174
|
);
|
|
167
|
-
console.log(` ${ui.text.bold("Framer Export")} ${ui.muted("
|
|
175
|
+
console.log(` ${ui.text.bold("Framer Export")} ${ui.muted("25+ platforms \xB7 local mirror")}
|
|
168
176
|
`);
|
|
169
177
|
return;
|
|
170
178
|
}
|
|
@@ -177,7 +185,7 @@ function showBanner() {
|
|
|
177
185
|
` ${ui.muted(`v${package_default.version}`)} ${ui.text.bold("Framer Export")} ${chip("fexport")} ${ui.muted("local mirror exporter")}`
|
|
178
186
|
);
|
|
179
187
|
console.log(
|
|
180
|
-
` ${ui.muted("Framer")} ${ui.border("/")} ${ui.muted("Webflow")} ${ui.border("/")} ${ui.muted("Wix")} ${ui.border("\xB7")} ${ui.primary("clean assets")} ${ui.border("\xB7")} ${ui.secondary("local serve")}
|
|
188
|
+
` ${ui.muted("Framer")} ${ui.border("/")} ${ui.muted("Webflow")} ${ui.border("/")} ${ui.muted("Wix")} ${ui.border("/")} ${ui.muted("+22 more")} ${ui.border("\xB7")} ${ui.primary("clean assets")} ${ui.border("\xB7")} ${ui.secondary("local serve")}
|
|
181
189
|
`
|
|
182
190
|
);
|
|
183
191
|
}
|
|
@@ -204,6 +212,1756 @@ var init_banner = __esm({
|
|
|
204
212
|
}
|
|
205
213
|
});
|
|
206
214
|
|
|
215
|
+
// src/platforms/framer.ts
|
|
216
|
+
var FONT_EXTS, framer;
|
|
217
|
+
var init_framer = __esm({
|
|
218
|
+
"src/platforms/framer.ts"() {
|
|
219
|
+
"use strict";
|
|
220
|
+
FONT_EXTS = [".woff2", ".woff", ".ttf", ".otf"];
|
|
221
|
+
framer = {
|
|
222
|
+
name: "framer",
|
|
223
|
+
displayName: "Framer",
|
|
224
|
+
category: "builder",
|
|
225
|
+
priority: 95,
|
|
226
|
+
detectByUrl(url) {
|
|
227
|
+
return /\.framer\.(app|website)|framercanvas\.com/.test(url);
|
|
228
|
+
},
|
|
229
|
+
detectByHtml(html) {
|
|
230
|
+
return html.includes('id="main"') && (html.includes("framerstatic.com") || html.includes("framerusercontent.com"));
|
|
231
|
+
},
|
|
232
|
+
stripDomains: ["events.framer.com", "api.framer.com", "collect.frameranalytics.com"],
|
|
233
|
+
stripSelectors: [
|
|
234
|
+
'script[src*="events.framer.com"]',
|
|
235
|
+
"#__framer-badge-container",
|
|
236
|
+
"#__framer-badge",
|
|
237
|
+
'link[href*="canvas-sandbox"]',
|
|
238
|
+
'script[src*="framer.com/bootstrap"]'
|
|
239
|
+
],
|
|
240
|
+
stripPatterns: [
|
|
241
|
+
/<div id="__framer-badge-container"[^>]*><\/div>/g,
|
|
242
|
+
/<script>try\{if\(localStorage\.get\("__framer_force_showing_editorbar_since"\)\)[^<]*<\/script>/g
|
|
243
|
+
],
|
|
244
|
+
hydrationTimeout: 1e4,
|
|
245
|
+
needsHydrationCheck: true,
|
|
246
|
+
mapAssetDir(host, pathname, ext) {
|
|
247
|
+
if (host.includes("framerusercontent.com")) {
|
|
248
|
+
if (pathname.startsWith("/images/")) return "assets/images";
|
|
249
|
+
if (pathname.startsWith("/assets/")) {
|
|
250
|
+
return FONT_EXTS.includes(ext) ? "assets/fonts" : "assets/misc";
|
|
251
|
+
}
|
|
252
|
+
if (pathname.startsWith("/sites/")) {
|
|
253
|
+
if (ext === ".mjs" || ext === ".js") return "scripts/vendor";
|
|
254
|
+
if (ext === ".json") return "data";
|
|
255
|
+
if (ext === ".css") return "styles";
|
|
256
|
+
if (ext === ".framercms") return "data";
|
|
257
|
+
return "assets/misc";
|
|
258
|
+
}
|
|
259
|
+
if (pathname.startsWith("/modules/")) {
|
|
260
|
+
return ext === ".framercms" ? "data" : "scripts/modules";
|
|
261
|
+
}
|
|
262
|
+
return "assets/misc";
|
|
263
|
+
}
|
|
264
|
+
if (host.includes("app.framerstatic.com")) {
|
|
265
|
+
if (ext === ".css") return "styles";
|
|
266
|
+
if (ext === ".mjs" || ext === ".js") return "scripts/vendor";
|
|
267
|
+
if (ext === ".woff2" || ext === ".woff") return "assets/fonts";
|
|
268
|
+
if (ext === ".png" || ext === ".svg") return "assets/images";
|
|
269
|
+
return "assets/misc";
|
|
270
|
+
}
|
|
271
|
+
if (host.includes("framercanvas.com") || host.includes("framer.com")) {
|
|
272
|
+
if (ext === ".mjs" || ext === ".js") return "scripts/vendor";
|
|
273
|
+
if (ext === ".css") return "styles";
|
|
274
|
+
return "assets/misc";
|
|
275
|
+
}
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
// src/platforms/webflow.ts
|
|
283
|
+
var IMG_EXTS, FONT_EXTS2, VIDEO_EXTS, webflow;
|
|
284
|
+
var init_webflow = __esm({
|
|
285
|
+
"src/platforms/webflow.ts"() {
|
|
286
|
+
"use strict";
|
|
287
|
+
IMG_EXTS = [".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp", ".avif"];
|
|
288
|
+
FONT_EXTS2 = [".woff2", ".woff", ".ttf", ".otf", ".eot"];
|
|
289
|
+
VIDEO_EXTS = [".mp4", ".webm", ".ogg"];
|
|
290
|
+
webflow = {
|
|
291
|
+
name: "webflow",
|
|
292
|
+
displayName: "Webflow",
|
|
293
|
+
category: "builder",
|
|
294
|
+
priority: 90,
|
|
295
|
+
detectByUrl(url) {
|
|
296
|
+
return /\.webflow\.(io|com)/.test(url);
|
|
297
|
+
},
|
|
298
|
+
detectByHtml(html) {
|
|
299
|
+
return html.includes("data-wf-site") || html.includes("w-webflow-badge") || html.includes("Webflow") || html.includes("webflow.js");
|
|
300
|
+
},
|
|
301
|
+
stripDomains: ["js-agent.newrelic.com", "cdn.heapanalytics.com", "tr.snapchat.com"],
|
|
302
|
+
stripSelectors: [".w-webflow-badge", 'link[href*="editorbar"]'],
|
|
303
|
+
stripPatterns: [
|
|
304
|
+
/<a[^>]*class="[^"]*w-webflow-badge[^"]*"[^>]*>[\s\S]*?<\/a>/g,
|
|
305
|
+
/<a[^>]*href="[^"]*webflow\.com\?utm_campaign=brandjs[^"]*"[^>]*>[\s\S]*?<\/a>/g,
|
|
306
|
+
/<style>[^<]*\.w-webflow-badge[^<]*<\/style>/g,
|
|
307
|
+
/Powered by <a[^>]*href="[^"]*webflow\.com"[^>]*>[^<]*<\/a>/g,
|
|
308
|
+
/<!-- This site was created in Webflow\.[^>]*-->/g,
|
|
309
|
+
/<html([^>]*) data-wf-domain="[^"]*"/g,
|
|
310
|
+
/<html([^>]*) data-wf-page="[^"]*"/g,
|
|
311
|
+
/<html([^>]*) data-wf-site="[^"]*"/g,
|
|
312
|
+
/<html([^>]*) data-wf-status="[^"]*"/g,
|
|
313
|
+
/<meta[^>]*content="Webflow"[^>]*>/g
|
|
314
|
+
],
|
|
315
|
+
hydrationTimeout: 2e3,
|
|
316
|
+
needsHydrationCheck: false,
|
|
317
|
+
mapAssetDir(host, pathname, ext) {
|
|
318
|
+
if (host.includes("website-files.com") || host.includes("webflow.com") || host.includes("uploads-ssl.webflow.com")) {
|
|
319
|
+
if (VIDEO_EXTS.includes(ext)) return "assets/videos";
|
|
320
|
+
if (pathname.includes("/images/") || IMG_EXTS.includes(ext)) return "assets/images";
|
|
321
|
+
if (pathname.includes("/css/") || ext === ".css") return "styles";
|
|
322
|
+
if (pathname.includes("/js/") || ext === ".js") return "scripts/vendor";
|
|
323
|
+
if (pathname.includes("/gsap/") || pathname.includes("/plugins/")) return "scripts/vendor";
|
|
324
|
+
if (FONT_EXTS2.includes(ext)) return "assets/fonts";
|
|
325
|
+
return "assets/misc";
|
|
326
|
+
}
|
|
327
|
+
if (host.includes("d3e54v103j8qbb.cloudfront.net")) {
|
|
328
|
+
if (ext === ".js") return "scripts/vendor";
|
|
329
|
+
if (ext === ".css") return "styles";
|
|
330
|
+
if (IMG_EXTS.includes(ext)) return "assets/images";
|
|
331
|
+
return "assets/misc";
|
|
332
|
+
}
|
|
333
|
+
return null;
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
// src/platforms/wix.ts
|
|
340
|
+
var IMG_EXTS2, FONT_EXTS3, VIDEO_EXTS2, wix;
|
|
341
|
+
var init_wix = __esm({
|
|
342
|
+
"src/platforms/wix.ts"() {
|
|
343
|
+
"use strict";
|
|
344
|
+
IMG_EXTS2 = [".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp", ".avif", ".ico"];
|
|
345
|
+
FONT_EXTS3 = [".woff2", ".woff", ".ttf", ".otf", ".eot"];
|
|
346
|
+
VIDEO_EXTS2 = [".mp4", ".webm", ".ogg"];
|
|
347
|
+
wix = {
|
|
348
|
+
name: "wix",
|
|
349
|
+
displayName: "Wix",
|
|
350
|
+
category: "builder",
|
|
351
|
+
priority: 88,
|
|
352
|
+
detectByUrl(url) {
|
|
353
|
+
return /\.wixsite\.com|\.wix\.com/.test(url);
|
|
354
|
+
},
|
|
355
|
+
detectByHtml(html) {
|
|
356
|
+
return html.includes("wix-viewer-model") || html.includes('id="WIX_ADS"') || html.includes("X-Wix-") || html.includes("Wix.com") || html.includes("wixcode-sdk") || html.includes("data-wix") || html.includes("wix-code") || html.includes("WixCode") || /\bwix\.com\b/.test(html) || /meta\s+name=["']generator["']\s+content=["'][^"']*Wix[^"']*["']/i.test(html) || /_wix_/i.test(html) || /wix-ecom/i.test(html);
|
|
357
|
+
},
|
|
358
|
+
stripDomains: [
|
|
359
|
+
"frog.wix.com",
|
|
360
|
+
"bi.wixapi.com",
|
|
361
|
+
"fed.wixcodescheduler.com",
|
|
362
|
+
"editor.wix.com",
|
|
363
|
+
"panorama.wixapps.net"
|
|
364
|
+
],
|
|
365
|
+
stripSelectors: ["#WIX_ADS", ".wix-ads", "#wix-badge", "#SCROLL_TO_TOP"],
|
|
366
|
+
stripPatterns: [
|
|
367
|
+
/<div[^>]*class="[^"]*wix-ads[^"]*"[^>]*>[\s\S]*?<\/div>/g
|
|
368
|
+
],
|
|
369
|
+
hydrationTimeout: 3e3,
|
|
370
|
+
needsHydrationCheck: false,
|
|
371
|
+
mapAssetDir(host, pathname, ext) {
|
|
372
|
+
if (host === "video.wixstatic.com") {
|
|
373
|
+
return "assets/videos";
|
|
374
|
+
}
|
|
375
|
+
if (host.includes("wixstatic.com")) {
|
|
376
|
+
if (VIDEO_EXTS2.includes(ext)) return "assets/videos";
|
|
377
|
+
if (pathname.includes("/images/") || IMG_EXTS2.includes(ext)) return "assets/images";
|
|
378
|
+
if (ext === ".css") return "styles";
|
|
379
|
+
if (ext === ".js") return "scripts/vendor";
|
|
380
|
+
if (FONT_EXTS3.includes(ext)) return "assets/fonts";
|
|
381
|
+
return "assets/misc";
|
|
382
|
+
}
|
|
383
|
+
if (host.includes("parastorage.com")) {
|
|
384
|
+
if (ext === ".js") return "scripts/vendor";
|
|
385
|
+
if (ext === ".css") return "styles";
|
|
386
|
+
if (FONT_EXTS3.includes(ext)) return "assets/fonts";
|
|
387
|
+
if (IMG_EXTS2.includes(ext)) return "assets/images";
|
|
388
|
+
return "assets/misc";
|
|
389
|
+
}
|
|
390
|
+
return null;
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
// src/platforms/builder/bubble.ts
|
|
397
|
+
var IMG_EXTS3, FONT_EXTS4, VIDEO_EXTS3, bubble;
|
|
398
|
+
var init_bubble = __esm({
|
|
399
|
+
"src/platforms/builder/bubble.ts"() {
|
|
400
|
+
"use strict";
|
|
401
|
+
IMG_EXTS3 = [".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp", ".avif", ".ico"];
|
|
402
|
+
FONT_EXTS4 = [".woff2", ".woff", ".ttf", ".otf", ".eot"];
|
|
403
|
+
VIDEO_EXTS3 = [".mp4", ".webm", ".ogg"];
|
|
404
|
+
bubble = {
|
|
405
|
+
name: "bubble",
|
|
406
|
+
displayName: "Bubble",
|
|
407
|
+
category: "builder",
|
|
408
|
+
priority: 85,
|
|
409
|
+
detectByUrl(url) {
|
|
410
|
+
return /\.bubbleapps\.io/i.test(url);
|
|
411
|
+
},
|
|
412
|
+
detectByHtml(html) {
|
|
413
|
+
return html.includes("window.appquery = make_proxy(") || html.includes("window._bubble_page_load_data") || html.includes("window.bubble_session_uid") || html.includes("/package/pre_run_jquery_js/");
|
|
414
|
+
},
|
|
415
|
+
// Telemetry only (Plausible-based, on standard-plan apps). NEVER the asset CDN.
|
|
416
|
+
stripDomains: ["pluginpul.se"],
|
|
417
|
+
// Post-process removal of any telemetry <script> that survived into the DOM.
|
|
418
|
+
stripSelectors: ['script[src*="pluginpul.se"]'],
|
|
419
|
+
stripPatterns: [
|
|
420
|
+
// "Made with Bubble" badge — injected at runtime by run.js and gated by
|
|
421
|
+
// window._p.no_branding, so it never appears in the raw SSR HTML (only in
|
|
422
|
+
// the captured rendered DOM). Best-effort strip anchored on its bubble.io
|
|
423
|
+
// target: matches an <a> pointing at the bubble.io ROOT (optionally with a
|
|
424
|
+
// tracking query), which is tight enough to spare normal deep-links to
|
|
425
|
+
// bubble.io content pages.
|
|
426
|
+
/<a\b[^>]*href="https?:\/\/(?:www\.)?bubble\.io\/?(?:\?[^"]*)?"[^>]*>[\s\S]*?<\/a>/gi
|
|
427
|
+
],
|
|
428
|
+
needsHydrationCheck: true,
|
|
429
|
+
hydrationTimeout: 8e3,
|
|
430
|
+
hydrationSelector: "#main-page",
|
|
431
|
+
captureRenderedDom: true,
|
|
432
|
+
mapAssetDir(host, pathname, ext) {
|
|
433
|
+
if (host.includes("meta-fonts.cdn.bubble.io")) {
|
|
434
|
+
if (FONT_EXTS4.includes(ext)) return "assets/fonts";
|
|
435
|
+
if (ext === ".css") return "styles";
|
|
436
|
+
if (IMG_EXTS3.includes(ext)) return "assets/images";
|
|
437
|
+
return "assets/misc";
|
|
438
|
+
}
|
|
439
|
+
if (host.includes("cdn.bubble.io")) {
|
|
440
|
+
if (VIDEO_EXTS3.includes(ext)) return "assets/videos";
|
|
441
|
+
if (pathname.includes("/images/") || IMG_EXTS3.includes(ext)) return "assets/images";
|
|
442
|
+
if (ext === ".css") return "styles";
|
|
443
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
444
|
+
if (FONT_EXTS4.includes(ext)) return "assets/fonts";
|
|
445
|
+
if (ext === ".json") return "data";
|
|
446
|
+
return "assets/misc";
|
|
447
|
+
}
|
|
448
|
+
if (host.includes("d3dqmih97rcqmh.cloudfront.net") || host.includes("d1muf25xaso8hp.cloudfront.net")) {
|
|
449
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
450
|
+
if (ext === ".css") return "styles";
|
|
451
|
+
if (FONT_EXTS4.includes(ext)) return "assets/fonts";
|
|
452
|
+
if (IMG_EXTS3.includes(ext)) return "assets/images";
|
|
453
|
+
if (ext === ".json") return "data";
|
|
454
|
+
return "assets/misc";
|
|
455
|
+
}
|
|
456
|
+
return null;
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
// src/platforms/builder/carrd.ts
|
|
463
|
+
var IMG_EXTS4, FONT_EXTS5, VIDEO_EXTS4, carrd;
|
|
464
|
+
var init_carrd = __esm({
|
|
465
|
+
"src/platforms/builder/carrd.ts"() {
|
|
466
|
+
"use strict";
|
|
467
|
+
IMG_EXTS4 = [".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp", ".avif", ".ico"];
|
|
468
|
+
FONT_EXTS5 = [".woff2", ".woff", ".ttf", ".otf", ".eot"];
|
|
469
|
+
VIDEO_EXTS4 = [".mp4", ".webm", ".ogg"];
|
|
470
|
+
carrd = {
|
|
471
|
+
name: "carrd",
|
|
472
|
+
displayName: "Carrd",
|
|
473
|
+
category: "builder",
|
|
474
|
+
priority: 85,
|
|
475
|
+
detectByUrl(url) {
|
|
476
|
+
return /\.carrd\.co/i.test(url);
|
|
477
|
+
},
|
|
478
|
+
detectByHtml(html) {
|
|
479
|
+
return (
|
|
480
|
+
// Hallmark inline runtime IIFE, present on every Carrd site (new and old).
|
|
481
|
+
html.includes("$inner = $('.inner')") || // Free-plan "( Made with Carrd )" badge anchor.
|
|
482
|
+
html.includes('href="https://carrd.co"') || // Carrd's component-class scheme (…-component). Webflow uses w-* prefixes and
|
|
483
|
+
// Wix uses random ids, so this family does not collide with the frozen trio.
|
|
484
|
+
/class="[^"]*\b(?:text|image|container|buttons|icons|divider|links)-component\b/.test(html) || // Current-gen structural nesting: .site-wrapper > .site-main (both required, so
|
|
485
|
+
// neither generic-sounding class triggers detection on its own).
|
|
486
|
+
html.includes('class="site-wrapper"') && html.includes('class="site-main"')
|
|
487
|
+
);
|
|
488
|
+
},
|
|
489
|
+
// No third-party analytics/telemetry is injected by default (profile
|
|
490
|
+
// analyticsScripts is empty), and fonts.googleapis.com is a needed asset host,
|
|
491
|
+
// so nothing is blocked during capture.
|
|
492
|
+
stripDomains: [],
|
|
493
|
+
// The badge id sits directly on the <p> (there is no wrapper div and no
|
|
494
|
+
// class="credits"); removing #credits drops the whole "Made with Carrd" line.
|
|
495
|
+
stripSelectors: ["#credits"],
|
|
496
|
+
stripPatterns: [
|
|
497
|
+
// Free-plan badge paragraph: <p id="credits"><a href="https://carrd.co">…</a></p>
|
|
498
|
+
/<p[^>]*id="credits"[^>]*>[\s\S]*?<\/p>/gi,
|
|
499
|
+
// Belt-and-suspenders: the branded badge anchor itself (anchored on the exact
|
|
500
|
+
// "( Made with Carrd )" text so a genuine user link to carrd.co is never removed).
|
|
501
|
+
/<a[^>]*href="https:\/\/carrd\.co"[^>]*>\s*\(\s*Made with Carrd\s*\)\s*<\/a>/gi
|
|
502
|
+
],
|
|
503
|
+
// ssr-static: the raw SSR HTML is complete, so no hydration wait is required
|
|
504
|
+
// (profile needsHydrationCheck=false, hydrationTimeoutMs=0). antiBot is Cloudflare
|
|
505
|
+
// but the pages fetched cleanly in research; per the authoring guide we keep a
|
|
506
|
+
// small capture buffer instead of the literal 0 ms.
|
|
507
|
+
hydrationTimeout: 2e3,
|
|
508
|
+
needsHydrationCheck: false,
|
|
509
|
+
mapAssetDir(host, pathname, ext) {
|
|
510
|
+
if (host.includes("fonts.googleapis.com")) {
|
|
511
|
+
return "styles";
|
|
512
|
+
}
|
|
513
|
+
if (host.includes("carrd.co")) {
|
|
514
|
+
if (VIDEO_EXTS4.includes(ext)) return "assets/videos";
|
|
515
|
+
if (pathname.includes("/assets/images/") || IMG_EXTS4.includes(ext)) return "assets/images";
|
|
516
|
+
if (pathname.includes("/assets/css/") || ext === ".css") return "styles";
|
|
517
|
+
if (pathname.includes("/assets/js/") || ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
518
|
+
if (FONT_EXTS5.includes(ext)) return "assets/fonts";
|
|
519
|
+
return "assets/misc";
|
|
520
|
+
}
|
|
521
|
+
return null;
|
|
522
|
+
}
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
// src/platforms/builder/duda.ts
|
|
528
|
+
var IMG_EXTS5, FONT_EXTS6, VIDEO_EXTS5, duda;
|
|
529
|
+
var init_duda = __esm({
|
|
530
|
+
"src/platforms/builder/duda.ts"() {
|
|
531
|
+
"use strict";
|
|
532
|
+
IMG_EXTS5 = [".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp", ".avif", ".ico"];
|
|
533
|
+
FONT_EXTS6 = [".woff2", ".woff", ".ttf", ".otf", ".eot"];
|
|
534
|
+
VIDEO_EXTS5 = [".mp4", ".webm", ".ogg"];
|
|
535
|
+
duda = {
|
|
536
|
+
name: "duda",
|
|
537
|
+
displayName: "Duda",
|
|
538
|
+
category: "builder",
|
|
539
|
+
priority: 82,
|
|
540
|
+
detectByUrl(url) {
|
|
541
|
+
return /\.(?:multiscreensite|dudaone)\.com/i.test(url);
|
|
542
|
+
},
|
|
543
|
+
detectByHtml(html) {
|
|
544
|
+
return html.includes("atob('RFVEQU9ORQ==')") || // window.Parameters SiteType → 'DUDAONE'
|
|
545
|
+
html.includes('id="dmRoot"') || html.includes("DM_DIRECT") || // window.Parameters productId
|
|
546
|
+
html.includes("d_track_campaign");
|
|
547
|
+
},
|
|
548
|
+
// No external telemetry/analytics hosts on Duda (tracking is inline, first-party).
|
|
549
|
+
stripDomains: [],
|
|
550
|
+
// Inline first-party tracking <script id="..."> elements (removed by id).
|
|
551
|
+
stripSelectors: ["#d_track_campaign", "#d_track_personalization", "#d_track_sp"],
|
|
552
|
+
stripPatterns: [
|
|
553
|
+
// "Made with Duda" badge → https://www.duda.co/inspiration
|
|
554
|
+
/<a[^>]*href="[^"]*duda\.co\/inspiration[^"]*"[^>]*>[\s\S]*?<\/a>/g
|
|
555
|
+
],
|
|
556
|
+
hydrationTimeout: 0,
|
|
557
|
+
needsHydrationCheck: false,
|
|
558
|
+
mapAssetDir(host, pathname, ext) {
|
|
559
|
+
const isCdnWebsite = host.endsWith(".cdn-website.com");
|
|
560
|
+
const isMsCdn = host === "ms-cdn.multiscreensite.com" || host === "irp-cdn.multiscreensite.com";
|
|
561
|
+
if (!isCdnWebsite && !isMsCdn) return null;
|
|
562
|
+
if (VIDEO_EXTS5.includes(ext)) return "assets/videos";
|
|
563
|
+
if (IMG_EXTS5.includes(ext) || pathname.includes("/images/")) return "assets/images";
|
|
564
|
+
if (ext === ".css") return "styles";
|
|
565
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
566
|
+
if (FONT_EXTS6.includes(ext)) return "assets/fonts";
|
|
567
|
+
const isResizeProxy = host.startsWith("irp") || host.startsWith("lirp");
|
|
568
|
+
return isResizeProxy ? "assets/images" : "assets/misc";
|
|
569
|
+
}
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
// src/platforms/builder/squarespace.ts
|
|
575
|
+
var IMG_EXTS6, FONT_EXTS7, VIDEO_EXTS6, STATIC_EDGE_RE, squarespace;
|
|
576
|
+
var init_squarespace = __esm({
|
|
577
|
+
"src/platforms/builder/squarespace.ts"() {
|
|
578
|
+
"use strict";
|
|
579
|
+
IMG_EXTS6 = [".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp", ".avif", ".ico"];
|
|
580
|
+
FONT_EXTS7 = [".woff2", ".woff", ".ttf", ".otf", ".eot"];
|
|
581
|
+
VIDEO_EXTS6 = [".mp4", ".webm", ".ogg"];
|
|
582
|
+
STATIC_EDGE_RE = /^static\d+\.squarespace\.com$/i;
|
|
583
|
+
squarespace = {
|
|
584
|
+
name: "squarespace",
|
|
585
|
+
displayName: "Squarespace",
|
|
586
|
+
category: "builder",
|
|
587
|
+
priority: 82,
|
|
588
|
+
detectByUrl(url) {
|
|
589
|
+
return /\.squarespace\.com/i.test(url);
|
|
590
|
+
},
|
|
591
|
+
detectByHtml(html) {
|
|
592
|
+
return html.includes("<!-- This is Squarespace. -->") || html.includes("Static.SQUARESPACE_CONTEXT") || html.includes("<!-- End of Squarespace Headers -->") || html.includes("images.squarespace-cdn.com");
|
|
593
|
+
},
|
|
594
|
+
// First-party analytics only; no default third-party telemetry host to block.
|
|
595
|
+
stripDomains: [],
|
|
596
|
+
// No editor chrome / badge in public SSR HTML on paid or template sites.
|
|
597
|
+
stripSelectors: [],
|
|
598
|
+
stripPatterns: [],
|
|
599
|
+
hydrationTimeout: 3e3,
|
|
600
|
+
needsHydrationCheck: false,
|
|
601
|
+
mapAssetDir(host, pathname, ext) {
|
|
602
|
+
if (host.includes("images.squarespace-cdn.com")) {
|
|
603
|
+
if (VIDEO_EXTS6.includes(ext)) return "assets/videos";
|
|
604
|
+
if (ext === ".css") return "styles";
|
|
605
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
606
|
+
if (FONT_EXTS7.includes(ext)) return "assets/fonts";
|
|
607
|
+
if (ext === ".json") return "data";
|
|
608
|
+
return "assets/images";
|
|
609
|
+
}
|
|
610
|
+
if (host.includes("file.squarespace-cdn.com")) {
|
|
611
|
+
if (VIDEO_EXTS6.includes(ext)) return "assets/videos";
|
|
612
|
+
if (IMG_EXTS6.includes(ext)) return "assets/images";
|
|
613
|
+
if (ext === ".css") return "styles";
|
|
614
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
615
|
+
if (FONT_EXTS7.includes(ext)) return "assets/fonts";
|
|
616
|
+
if (ext === ".json") return "data";
|
|
617
|
+
return "assets/misc";
|
|
618
|
+
}
|
|
619
|
+
if (host.includes("assets.squarespace.com") || STATIC_EDGE_RE.test(host)) {
|
|
620
|
+
if (VIDEO_EXTS6.includes(ext)) return "assets/videos";
|
|
621
|
+
if (ext === ".css") return "styles";
|
|
622
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
623
|
+
if (FONT_EXTS7.includes(ext)) return "assets/fonts";
|
|
624
|
+
if (ext === ".json") return "data";
|
|
625
|
+
if (IMG_EXTS6.includes(ext) || pathname.includes("/images/")) return "assets/images";
|
|
626
|
+
return "assets/misc";
|
|
627
|
+
}
|
|
628
|
+
if (host.includes("definitions.sqspcdn.com")) {
|
|
629
|
+
if (ext === ".json") return "data";
|
|
630
|
+
if (ext === ".css") return "styles";
|
|
631
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
632
|
+
return "assets/misc";
|
|
633
|
+
}
|
|
634
|
+
return null;
|
|
635
|
+
}
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
});
|
|
639
|
+
|
|
640
|
+
// src/platforms/builder/strikingly.ts
|
|
641
|
+
var IMG_EXTS7, FONT_EXTS8, VIDEO_EXTS7, strikingly;
|
|
642
|
+
var init_strikingly = __esm({
|
|
643
|
+
"src/platforms/builder/strikingly.ts"() {
|
|
644
|
+
"use strict";
|
|
645
|
+
IMG_EXTS7 = [".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp", ".avif", ".ico"];
|
|
646
|
+
FONT_EXTS8 = [".woff2", ".woff", ".ttf", ".otf", ".eot"];
|
|
647
|
+
VIDEO_EXTS7 = [".mp4", ".webm", ".ogg"];
|
|
648
|
+
strikingly = {
|
|
649
|
+
name: "strikingly",
|
|
650
|
+
displayName: "Strikingly",
|
|
651
|
+
category: "builder",
|
|
652
|
+
priority: 85,
|
|
653
|
+
detectByUrl(url) {
|
|
654
|
+
return /\.mystrikingly\.com/i.test(url);
|
|
655
|
+
},
|
|
656
|
+
detectByHtml(html) {
|
|
657
|
+
return html.includes("strikingly-host-suffix") || html.includes('data-hypernova-key="SiteBootstrapper"') || html.includes('id="s-page-client-container"') || html.includes("strikinglycdn.com");
|
|
658
|
+
},
|
|
659
|
+
// Telemetry / error-monitoring only — NEVER the strikinglycdn asset CDN.
|
|
660
|
+
stripDomains: [
|
|
661
|
+
"google-analytics.com",
|
|
662
|
+
"www.google-analytics.com",
|
|
663
|
+
"notify.bugsnag.com",
|
|
664
|
+
"sessions.bugsnag.com"
|
|
665
|
+
],
|
|
666
|
+
// The "Powered by Strikingly" footer section carries the unique class
|
|
667
|
+
// `.show-strikingly-logo` (inside #s-footer-section-container).
|
|
668
|
+
stripSelectors: [".show-strikingly-logo"],
|
|
669
|
+
stripPatterns: [
|
|
670
|
+
// Hydration-rendered badge anchor → https://www.strikingly.com/?ref=logo…&utm_campaign=footer_pbs
|
|
671
|
+
/<a[^>]*href="[^"]*strikingly\.com\/\?ref=logo[^"]*"[^>]*>[\s\S]*?<\/a>/gi
|
|
672
|
+
],
|
|
673
|
+
// renderStrategy: react-hydration → use the rendered DOM as the HTML source.
|
|
674
|
+
hydrationTimeout: 3e3,
|
|
675
|
+
needsHydrationCheck: false,
|
|
676
|
+
hydrationSelector: "#s-page-client-container",
|
|
677
|
+
captureRenderedDom: true,
|
|
678
|
+
mapAssetDir(host, pathname, ext) {
|
|
679
|
+
if (host.includes("strikinglycdn.com")) {
|
|
680
|
+
if (VIDEO_EXTS7.includes(ext)) return "assets/videos";
|
|
681
|
+
if (host.includes("static-fonts-css")) {
|
|
682
|
+
if (FONT_EXTS8.includes(ext)) return "assets/fonts";
|
|
683
|
+
if (ext === ".css") return "styles";
|
|
684
|
+
}
|
|
685
|
+
if (host.includes("static-assets")) {
|
|
686
|
+
if (ext === ".css") return "styles";
|
|
687
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
688
|
+
if (FONT_EXTS8.includes(ext)) return "assets/fonts";
|
|
689
|
+
if (IMG_EXTS7.includes(ext) || pathname.includes("/images/")) return "assets/images";
|
|
690
|
+
return "assets/misc";
|
|
691
|
+
}
|
|
692
|
+
if (IMG_EXTS7.includes(ext) || pathname.includes("/images/")) return "assets/images";
|
|
693
|
+
if (FONT_EXTS8.includes(ext)) return "assets/fonts";
|
|
694
|
+
if (ext === ".css") return "styles";
|
|
695
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
696
|
+
return "assets/misc";
|
|
697
|
+
}
|
|
698
|
+
if (host.includes("cdnjs.cloudflare.com")) {
|
|
699
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
700
|
+
if (ext === ".css") return "styles";
|
|
701
|
+
if (FONT_EXTS8.includes(ext)) return "assets/fonts";
|
|
702
|
+
if (IMG_EXTS7.includes(ext)) return "assets/images";
|
|
703
|
+
return "assets/misc";
|
|
704
|
+
}
|
|
705
|
+
return null;
|
|
706
|
+
}
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
});
|
|
710
|
+
|
|
711
|
+
// src/platforms/builder/tilda.ts
|
|
712
|
+
var IMG_EXTS8, FONT_EXTS9, VIDEO_EXTS8, tilda;
|
|
713
|
+
var init_tilda = __esm({
|
|
714
|
+
"src/platforms/builder/tilda.ts"() {
|
|
715
|
+
"use strict";
|
|
716
|
+
IMG_EXTS8 = [".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp", ".avif", ".ico"];
|
|
717
|
+
FONT_EXTS9 = [".woff2", ".woff", ".ttf", ".otf", ".eot"];
|
|
718
|
+
VIDEO_EXTS8 = [".mp4", ".webm", ".ogg"];
|
|
719
|
+
tilda = {
|
|
720
|
+
name: "tilda",
|
|
721
|
+
displayName: "Tilda",
|
|
722
|
+
category: "builder",
|
|
723
|
+
priority: 80,
|
|
724
|
+
detectByUrl(url) {
|
|
725
|
+
return /\.tilda\.ws/i.test(url);
|
|
726
|
+
},
|
|
727
|
+
detectByHtml(html) {
|
|
728
|
+
return html.includes("data-tilda-project-id") || html.includes("data-tilda-page-id") || html.includes("tildacdn") || html.includes('id="allrecords"');
|
|
729
|
+
},
|
|
730
|
+
// Third-party telemetry only. NEVER the tildacdn asset hosts (they carry the
|
|
731
|
+
// page's real images/CSS/JS); the Tilda stat script is stripped by selector.
|
|
732
|
+
stripDomains: ["www.google-analytics.com", "google-analytics.com"],
|
|
733
|
+
stripSelectors: ["#tildacopy", ".t-tildalabel", 'script[src*="tilda-stat"]'],
|
|
734
|
+
stripPatterns: [
|
|
735
|
+
// "Made on Tilda" badge link (free-plan only). No nested <a>, so the
|
|
736
|
+
// non-greedy </a> match is safe even though the anchor wraps inner <div>s.
|
|
737
|
+
/<a[^>]*href="[^"]*tilda\.cc[^"]*"[^>]*>[\s\S]*?<\/a>/g
|
|
738
|
+
],
|
|
739
|
+
hydrationTimeout: 0,
|
|
740
|
+
needsHydrationCheck: false,
|
|
741
|
+
mapAssetDir(host, pathname, ext) {
|
|
742
|
+
if (host.includes("tildacdn")) {
|
|
743
|
+
if (host.startsWith("thb.")) return "assets/images";
|
|
744
|
+
if (VIDEO_EXTS8.includes(ext)) return "assets/videos";
|
|
745
|
+
if (ext === ".css" || pathname.includes("/css/")) return "styles";
|
|
746
|
+
if (ext === ".js" || ext === ".mjs" || pathname.includes("/js/")) return "scripts/vendor";
|
|
747
|
+
if (FONT_EXTS9.includes(ext) || pathname.includes("/fonts/")) return "assets/fonts";
|
|
748
|
+
if (ext === ".json") return "data";
|
|
749
|
+
if (IMG_EXTS8.includes(ext) || pathname.includes("/img/") || pathname.includes("/images/") || pathname.includes("/tild")) {
|
|
750
|
+
return "assets/images";
|
|
751
|
+
}
|
|
752
|
+
return "assets/misc";
|
|
753
|
+
}
|
|
754
|
+
return null;
|
|
755
|
+
}
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
});
|
|
759
|
+
|
|
760
|
+
// src/platforms/builder/weebly.ts
|
|
761
|
+
var IMG_EXTS9, FONT_EXTS10, VIDEO_EXTS9, weebly;
|
|
762
|
+
var init_weebly = __esm({
|
|
763
|
+
"src/platforms/builder/weebly.ts"() {
|
|
764
|
+
"use strict";
|
|
765
|
+
IMG_EXTS9 = [".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp", ".avif", ".ico"];
|
|
766
|
+
FONT_EXTS10 = [".woff2", ".woff", ".ttf", ".otf", ".eot"];
|
|
767
|
+
VIDEO_EXTS9 = [".mp4", ".webm", ".ogg"];
|
|
768
|
+
weebly = {
|
|
769
|
+
name: "weebly",
|
|
770
|
+
displayName: "Weebly",
|
|
771
|
+
category: "builder",
|
|
772
|
+
priority: 82,
|
|
773
|
+
detectByUrl(url) {
|
|
774
|
+
return /\.weebly\.com/i.test(url);
|
|
775
|
+
},
|
|
776
|
+
detectByHtml(html) {
|
|
777
|
+
return html.includes("Weebly.footer.setupContainer") || html.includes("weebly-footer-signup-container-v3") || html.includes("wsite-content") || html.includes("window._W");
|
|
778
|
+
},
|
|
779
|
+
// Third-party telemetry only. NEVER list editmysite.com here — that host serves
|
|
780
|
+
// the platform CSS/JS/images (cdn1/cdn2/cdn11) and blocking it deletes assets.
|
|
781
|
+
// The Weebly _W.Analytics trackers (main.js, stl.js) also live on that CDN, so
|
|
782
|
+
// they are intentionally not blocked (blocking the host would kill assets too).
|
|
783
|
+
stripDomains: ["google-analytics.com"],
|
|
784
|
+
// Footer "Made with Weebly" signup badge: unique container id (empty in SSR
|
|
785
|
+
// HTML, populated client-side) plus the script that injects it.
|
|
786
|
+
stripSelectors: ["#weebly-footer-signup-container-v3", 'script[src*="footerSignup.js"]'],
|
|
787
|
+
// Best-effort removal of the rendered badge anchor should it appear inline.
|
|
788
|
+
stripPatterns: [/<a[^>]*href="[^"]*weebly\.com\/signup[^"]*"[^>]*>[\s\S]*?<\/a>/g],
|
|
789
|
+
hydrationTimeout: 0,
|
|
790
|
+
needsHydrationCheck: false,
|
|
791
|
+
mapAssetDir(host, pathname, ext) {
|
|
792
|
+
if (host.includes("editmysite.com")) {
|
|
793
|
+
if (VIDEO_EXTS9.includes(ext)) return "assets/videos";
|
|
794
|
+
if (pathname.includes("/images/") || IMG_EXTS9.includes(ext)) return "assets/images";
|
|
795
|
+
if (pathname.includes("/css/") || ext === ".css") return "styles";
|
|
796
|
+
if (pathname.includes("/js/") || ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
797
|
+
if (FONT_EXTS10.includes(ext)) return "assets/fonts";
|
|
798
|
+
return "assets/misc";
|
|
799
|
+
}
|
|
800
|
+
return null;
|
|
801
|
+
}
|
|
802
|
+
};
|
|
803
|
+
}
|
|
804
|
+
});
|
|
805
|
+
|
|
806
|
+
// src/platforms/landing/clickfunnels.ts
|
|
807
|
+
var IMG_EXTS10, FONT_EXTS11, VIDEO_EXTS10, clickfunnels;
|
|
808
|
+
var init_clickfunnels = __esm({
|
|
809
|
+
"src/platforms/landing/clickfunnels.ts"() {
|
|
810
|
+
"use strict";
|
|
811
|
+
IMG_EXTS10 = [".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp", ".avif", ".ico"];
|
|
812
|
+
FONT_EXTS11 = [".woff2", ".woff", ".ttf", ".otf", ".eot"];
|
|
813
|
+
VIDEO_EXTS10 = [".mp4", ".webm", ".ogg"];
|
|
814
|
+
clickfunnels = {
|
|
815
|
+
name: "clickfunnels",
|
|
816
|
+
displayName: 'ClickFunnels (2.0 / "CF2")',
|
|
817
|
+
category: "landing",
|
|
818
|
+
priority: 82,
|
|
819
|
+
detectByUrl(url) {
|
|
820
|
+
return /\.myclickfunnels\.com/i.test(url);
|
|
821
|
+
},
|
|
822
|
+
detectByHtml(html) {
|
|
823
|
+
return html.includes("cf-head-scripts") || html.includes("window.cfRootDomain") || html.includes("cf-lander-serialized-custom-fonts") || html.includes("data-page-element");
|
|
824
|
+
},
|
|
825
|
+
// Telemetry / analytics hosts blocked during capture. NOT the asset CDNs
|
|
826
|
+
// (statics.myclickfunnels.com / images.clickfunnels.com) and NOT the functional
|
|
827
|
+
// third-party libs (cdnjs / code.jquery.com / fontawesome) or Turnstile.
|
|
828
|
+
stripDomains: ["bam.nr-data.net", "js-agent.newrelic.com", "events.myclickfunnels.com"],
|
|
829
|
+
// No CF-specific badge/editor container has a static id/class in the delivered
|
|
830
|
+
// HTML, so there is nothing selector-strippable here.
|
|
831
|
+
stripSelectors: [],
|
|
832
|
+
// BADGE: The "Powered by ClickFunnels" badge is default-on per docs but was
|
|
833
|
+
// toggled OFF/HIDDEN on both verified samples (0 occurrences in the SSR HTML),
|
|
834
|
+
// and this handler does not capture the rendered DOM. No reliable static markup
|
|
835
|
+
// exists to anchor a regex on, so no badge stripPattern is emitted (a loose match
|
|
836
|
+
// on clickfunnels.com would risk removing legitimate links). If a future sample
|
|
837
|
+
// exposes the badge anchor, add a tight /<a[^>]*href="[^"]*clickfunnels\.com[^"]*"...>/ here.
|
|
838
|
+
stripPatterns: [],
|
|
839
|
+
// ssr-static: SSR body is complete, no hydration wait needed.
|
|
840
|
+
hydrationTimeout: 0,
|
|
841
|
+
needsHydrationCheck: false,
|
|
842
|
+
mapAssetDir(host, pathname, ext) {
|
|
843
|
+
if (host.includes("statics.myclickfunnels.com")) {
|
|
844
|
+
if (VIDEO_EXTS10.includes(ext)) return "assets/videos";
|
|
845
|
+
if (pathname.includes("/image/") || pathname.includes("/images/") || IMG_EXTS10.includes(ext))
|
|
846
|
+
return "assets/images";
|
|
847
|
+
if (ext === ".css") return "styles";
|
|
848
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
849
|
+
if (FONT_EXTS11.includes(ext)) return "assets/fonts";
|
|
850
|
+
return "assets/misc";
|
|
851
|
+
}
|
|
852
|
+
if (host.includes("images.clickfunnels.com")) {
|
|
853
|
+
if (VIDEO_EXTS10.includes(ext)) return "assets/videos";
|
|
854
|
+
if (FONT_EXTS11.includes(ext)) return "assets/fonts";
|
|
855
|
+
return "assets/images";
|
|
856
|
+
}
|
|
857
|
+
return null;
|
|
858
|
+
}
|
|
859
|
+
};
|
|
860
|
+
}
|
|
861
|
+
});
|
|
862
|
+
|
|
863
|
+
// src/platforms/landing/elementor.ts
|
|
864
|
+
var IMG_EXTS11, FONT_EXTS12, VIDEO_EXTS11, elementor;
|
|
865
|
+
var init_elementor = __esm({
|
|
866
|
+
"src/platforms/landing/elementor.ts"() {
|
|
867
|
+
"use strict";
|
|
868
|
+
IMG_EXTS11 = [".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp", ".avif", ".ico"];
|
|
869
|
+
FONT_EXTS12 = [".woff2", ".woff", ".ttf", ".otf", ".eot"];
|
|
870
|
+
VIDEO_EXTS11 = [".mp4", ".webm", ".ogg"];
|
|
871
|
+
elementor = {
|
|
872
|
+
name: "elementor",
|
|
873
|
+
displayName: "Elementor",
|
|
874
|
+
category: "landing",
|
|
875
|
+
priority: 42,
|
|
876
|
+
// Only Elementor Cloud/Hosting uses a platform subdomain; self-hosted sites live
|
|
877
|
+
// on the owner's own domain and are caught by detectByHtml / detectGenerator.
|
|
878
|
+
detectByUrl(url) {
|
|
879
|
+
return /\.elementor\.cloud/i.test(url);
|
|
880
|
+
},
|
|
881
|
+
// OR of four independent, Elementor-specific markers (none match Framer/Webflow/Wix,
|
|
882
|
+
// and #2/#3 survive optimization plugins that strip the generator meta):
|
|
883
|
+
// - Elementor plugin asset directory
|
|
884
|
+
// - Elementor "kit" body class
|
|
885
|
+
// - root wrapper data-attribute
|
|
886
|
+
// - frontend bootstrap JS global
|
|
887
|
+
detectByHtml(html) {
|
|
888
|
+
return html.includes("/wp-content/plugins/elementor/") || html.includes("elementor-kit-") || html.includes("data-elementor-type=") || html.includes("elementorFrontendConfig");
|
|
889
|
+
},
|
|
890
|
+
// Matched against the <meta name="generator"> content only, e.g.
|
|
891
|
+
// "Elementor 3.31.3; features: ...". Anchored so it fires solely on Elementor's own
|
|
892
|
+
// generator string, never on the "WordPress 6.7.5" / "Site Kit by Google" siblings.
|
|
893
|
+
detectGenerator: /^Elementor\s+\d+\.\d+/i,
|
|
894
|
+
// No default third-party telemetry: googletagmanager / cdn.usefathom.com hosts seen
|
|
895
|
+
// on sample sites are site-owner additions, not an Elementor platform default.
|
|
896
|
+
stripDomains: [],
|
|
897
|
+
// No front-end editor chrome or badge element to remove.
|
|
898
|
+
stripSelectors: [],
|
|
899
|
+
// Verified: no "Made with Elementor" badge in the front-end DOM → nothing to strip.
|
|
900
|
+
stripPatterns: [],
|
|
901
|
+
hydrationTimeout: 0,
|
|
902
|
+
needsHydrationCheck: false,
|
|
903
|
+
mapAssetDir(host, pathname, ext) {
|
|
904
|
+
if (!pathname.includes("/wp-content/") && !pathname.includes("/wp-includes/")) {
|
|
905
|
+
return null;
|
|
906
|
+
}
|
|
907
|
+
if (VIDEO_EXTS11.includes(ext)) return "assets/videos";
|
|
908
|
+
if (ext === ".css") return "styles";
|
|
909
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
910
|
+
if (FONT_EXTS12.includes(ext)) return "assets/fonts";
|
|
911
|
+
if (IMG_EXTS11.includes(ext)) return "assets/images";
|
|
912
|
+
if (ext === ".json") return "data";
|
|
913
|
+
return "assets/misc";
|
|
914
|
+
}
|
|
915
|
+
};
|
|
916
|
+
}
|
|
917
|
+
});
|
|
918
|
+
|
|
919
|
+
// src/platforms/landing/instapage.ts
|
|
920
|
+
var IMG_EXTS12, FONT_EXTS13, VIDEO_EXTS12, instapage;
|
|
921
|
+
var init_instapage = __esm({
|
|
922
|
+
"src/platforms/landing/instapage.ts"() {
|
|
923
|
+
"use strict";
|
|
924
|
+
IMG_EXTS12 = [".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp", ".avif", ".ico"];
|
|
925
|
+
FONT_EXTS13 = [".woff2", ".woff", ".ttf", ".otf", ".eot"];
|
|
926
|
+
VIDEO_EXTS12 = [".mp4", ".webm", ".ogg"];
|
|
927
|
+
instapage = {
|
|
928
|
+
name: "instapage",
|
|
929
|
+
displayName: "Instapage",
|
|
930
|
+
category: "landing",
|
|
931
|
+
priority: 82,
|
|
932
|
+
detectByUrl(url) {
|
|
933
|
+
return /\.(?:pagedemo|pageserve)\.co/i.test(url);
|
|
934
|
+
},
|
|
935
|
+
detectByHtml(html) {
|
|
936
|
+
return html.includes("instapageSp") || html.includes("_instapageSnowplow") || html.includes("anthill.instapage.com") || html.includes("pageserver.page2");
|
|
937
|
+
},
|
|
938
|
+
// Telemetry / analytics hosts only — blocked during capture. The fastcdn.co
|
|
939
|
+
// asset CDN is deliberately NOT listed here (those assets are mirrored).
|
|
940
|
+
stripDomains: [
|
|
941
|
+
"anthill.instapage.com",
|
|
942
|
+
"cdn.instapagemetrics.com",
|
|
943
|
+
"heatmap-events-collector.instapage.com",
|
|
944
|
+
"heatmap.services"
|
|
945
|
+
],
|
|
946
|
+
stripSelectors: [
|
|
947
|
+
'script[src*="instapagemetrics.com"]',
|
|
948
|
+
'script[src*="anthill.instapage.com"]',
|
|
949
|
+
'script[src*="heatmap.services"]'
|
|
950
|
+
],
|
|
951
|
+
stripPatterns: [
|
|
952
|
+
// Hidden local-storage helper iframe (runtime-injected on live pages; stripped
|
|
953
|
+
// here best-effort in case it appears in the captured markup).
|
|
954
|
+
/<iframe[^>]*d3mwhxgzltpnyp\.cloudfront\.net[^>]*>[\s\S]*?<\/iframe>/g
|
|
955
|
+
],
|
|
956
|
+
hydrationTimeout: 0,
|
|
957
|
+
needsHydrationCheck: false,
|
|
958
|
+
mapAssetDir(host, pathname, ext) {
|
|
959
|
+
if (host.includes("fastcdn.co")) {
|
|
960
|
+
if (VIDEO_EXTS12.includes(ext)) return "assets/videos";
|
|
961
|
+
if (IMG_EXTS12.includes(ext) || pathname.includes("/u/")) return "assets/images";
|
|
962
|
+
if (ext === ".css") return "styles";
|
|
963
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
964
|
+
if (FONT_EXTS13.includes(ext)) return "assets/fonts";
|
|
965
|
+
if (ext === ".json") return "data";
|
|
966
|
+
return "assets/misc";
|
|
967
|
+
}
|
|
968
|
+
if (host.includes("d3mwhxgzltpnyp.cloudfront.net")) {
|
|
969
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
970
|
+
if (ext === ".css") return "styles";
|
|
971
|
+
if (IMG_EXTS12.includes(ext)) return "assets/images";
|
|
972
|
+
if (FONT_EXTS13.includes(ext)) return "assets/fonts";
|
|
973
|
+
return "assets/misc";
|
|
974
|
+
}
|
|
975
|
+
return null;
|
|
976
|
+
}
|
|
977
|
+
};
|
|
978
|
+
}
|
|
979
|
+
});
|
|
980
|
+
|
|
981
|
+
// src/platforms/landing/systemeio.ts
|
|
982
|
+
var IMG_EXTS13, FONT_EXTS14, VIDEO_EXTS13, systemeio;
|
|
983
|
+
var init_systemeio = __esm({
|
|
984
|
+
"src/platforms/landing/systemeio.ts"() {
|
|
985
|
+
"use strict";
|
|
986
|
+
IMG_EXTS13 = [".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp", ".avif", ".ico"];
|
|
987
|
+
FONT_EXTS14 = [".woff2", ".woff", ".ttf", ".otf", ".eot"];
|
|
988
|
+
VIDEO_EXTS13 = [".mp4", ".webm", ".ogg"];
|
|
989
|
+
systemeio = {
|
|
990
|
+
name: "systemeio",
|
|
991
|
+
displayName: "Systeme.io",
|
|
992
|
+
category: "landing",
|
|
993
|
+
priority: 80,
|
|
994
|
+
detectByUrl(url) {
|
|
995
|
+
return /\.systeme\.io/i.test(url);
|
|
996
|
+
},
|
|
997
|
+
detectByHtml(html) {
|
|
998
|
+
return html.includes("<!-- Created with https://systeme.io -->") || html.includes("d3fit27i5nzkqh.cloudfront.net") || html.includes("systeme.io/?sa=") || html.includes("d3syewzhvzylbl.cloudfront.net");
|
|
999
|
+
},
|
|
1000
|
+
// First-party click/funnel telemetry — blocked during capture, never downloaded.
|
|
1001
|
+
stripDomains: ["log.systeme.io"],
|
|
1002
|
+
stripSelectors: ['script[src*="log.systeme.io"]'],
|
|
1003
|
+
stripPatterns: [
|
|
1004
|
+
// Footer credit/affiliate badge: <a href="https://systeme.io/?sa=<code>" ...>SITE CREDIT</a>.
|
|
1005
|
+
// Match on the `systeme.io/?sa=` href affiliate param (visible label is owner-editable).
|
|
1006
|
+
/<a[^>]*href="[^"]*systeme\.io\/\?sa=[^"]*"[^>]*>[\s\S]*?<\/a>/g,
|
|
1007
|
+
// Branding comment injected right after <!DOCTYPE html>.
|
|
1008
|
+
/<!--\s*Created with https:\/\/systeme\.io\s*-->/g
|
|
1009
|
+
],
|
|
1010
|
+
hydrationTimeout: 0,
|
|
1011
|
+
needsHydrationCheck: false,
|
|
1012
|
+
mapAssetDir(host, pathname, ext) {
|
|
1013
|
+
if (host.includes("d3fit27i5nzkqh.cloudfront.net")) {
|
|
1014
|
+
if (ext === ".css") return "styles";
|
|
1015
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
1016
|
+
if (FONT_EXTS14.includes(ext)) return "assets/fonts";
|
|
1017
|
+
if (VIDEO_EXTS13.includes(ext)) return "assets/videos";
|
|
1018
|
+
if (IMG_EXTS13.includes(ext)) return "assets/images";
|
|
1019
|
+
if (ext === ".json") return "data";
|
|
1020
|
+
return "assets/misc";
|
|
1021
|
+
}
|
|
1022
|
+
if (host.includes("d3syewzhvzylbl.cloudfront.net")) {
|
|
1023
|
+
if (FONT_EXTS14.includes(ext) || pathname.includes("/fonts/")) return "assets/fonts";
|
|
1024
|
+
if (ext === ".css") return "styles";
|
|
1025
|
+
if (IMG_EXTS13.includes(ext)) return "assets/images";
|
|
1026
|
+
return "assets/misc";
|
|
1027
|
+
}
|
|
1028
|
+
if (host.includes("d1yei2z3i6k35z.cloudfront.net")) {
|
|
1029
|
+
if (VIDEO_EXTS13.includes(ext)) return "assets/videos";
|
|
1030
|
+
if (IMG_EXTS13.includes(ext) || pathname.includes("/images/")) return "assets/images";
|
|
1031
|
+
if (FONT_EXTS14.includes(ext)) return "assets/fonts";
|
|
1032
|
+
if (ext === ".css") return "styles";
|
|
1033
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
1034
|
+
return "assets/misc";
|
|
1035
|
+
}
|
|
1036
|
+
if (host.includes("d2543nuuc0wvdg.cloudfront.net")) {
|
|
1037
|
+
if (VIDEO_EXTS13.includes(ext)) return "assets/videos";
|
|
1038
|
+
if (IMG_EXTS13.includes(ext)) return "assets/images";
|
|
1039
|
+
if (FONT_EXTS14.includes(ext)) return "assets/fonts";
|
|
1040
|
+
if (ext === ".css") return "styles";
|
|
1041
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
1042
|
+
return "assets/misc";
|
|
1043
|
+
}
|
|
1044
|
+
return null;
|
|
1045
|
+
}
|
|
1046
|
+
};
|
|
1047
|
+
}
|
|
1048
|
+
});
|
|
1049
|
+
|
|
1050
|
+
// src/platforms/landing/unbounce.ts
|
|
1051
|
+
var IMG_EXTS14, FONT_EXTS15, VIDEO_EXTS14, unbounce;
|
|
1052
|
+
var init_unbounce = __esm({
|
|
1053
|
+
"src/platforms/landing/unbounce.ts"() {
|
|
1054
|
+
"use strict";
|
|
1055
|
+
IMG_EXTS14 = [".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp", ".avif", ".ico"];
|
|
1056
|
+
FONT_EXTS15 = [".woff2", ".woff", ".ttf", ".otf", ".eot"];
|
|
1057
|
+
VIDEO_EXTS14 = [".mp4", ".webm", ".ogg"];
|
|
1058
|
+
unbounce = {
|
|
1059
|
+
name: "unbounce",
|
|
1060
|
+
displayName: "Unbounce",
|
|
1061
|
+
category: "landing",
|
|
1062
|
+
priority: 82,
|
|
1063
|
+
detectByUrl(url) {
|
|
1064
|
+
return /\.(?:unbouncepages|ubpages)\.com/i.test(url);
|
|
1065
|
+
},
|
|
1066
|
+
detectByHtml(html) {
|
|
1067
|
+
return html.includes("lp-pom-root") || html.includes("window.ub.page") || html.includes("builder-assets.unbounce.com") || html.includes("ubembed.com");
|
|
1068
|
+
},
|
|
1069
|
+
// No first-party telemetry/analytics hosts to block (profile analyticsScripts: []).
|
|
1070
|
+
// The asset CDNs (builder-assets / cloudfront / ubembed) must NOT be listed here.
|
|
1071
|
+
stripDomains: [],
|
|
1072
|
+
// The badge selector (#lp-pom-root a[href*="unbounce.com"]) is a descendant
|
|
1073
|
+
// combinator, unsupported by stripSelectors — handled via stripPatterns below.
|
|
1074
|
+
stripSelectors: [],
|
|
1075
|
+
stripPatterns: [
|
|
1076
|
+
// Best-effort removal of the "Made with Unbounce" branded footer link.
|
|
1077
|
+
// Anchored on the exact brand host so it never matches *.unbouncepages.com /
|
|
1078
|
+
// *.ubpages.com hosting links. Markup is approximate (badge markup UNVERIFIED).
|
|
1079
|
+
/<a[^>]*href="https?:\/\/(?:www\.)?unbounce\.com[^"]*"[^>]*>[\s\S]*?<\/a>/gi
|
|
1080
|
+
],
|
|
1081
|
+
hydrationTimeout: 0,
|
|
1082
|
+
needsHydrationCheck: false,
|
|
1083
|
+
mapAssetDir(host, pathname, ext) {
|
|
1084
|
+
if (host.includes("builder-assets.unbounce.com") || host.includes("assets.unbounce.com")) {
|
|
1085
|
+
if (VIDEO_EXTS14.includes(ext)) return "assets/videos";
|
|
1086
|
+
if (IMG_EXTS14.includes(ext) || pathname.includes("/lp-image")) return "assets/images";
|
|
1087
|
+
if (ext === ".css") return "styles";
|
|
1088
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
1089
|
+
if (FONT_EXTS15.includes(ext)) return "assets/fonts";
|
|
1090
|
+
if (ext === ".json") return "data";
|
|
1091
|
+
return "assets/misc";
|
|
1092
|
+
}
|
|
1093
|
+
if (host.includes("ubembed.com")) {
|
|
1094
|
+
if (ext === ".css") return "styles";
|
|
1095
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
1096
|
+
if (VIDEO_EXTS14.includes(ext)) return "assets/videos";
|
|
1097
|
+
if (IMG_EXTS14.includes(ext)) return "assets/images";
|
|
1098
|
+
if (FONT_EXTS15.includes(ext)) return "assets/fonts";
|
|
1099
|
+
return "assets/misc";
|
|
1100
|
+
}
|
|
1101
|
+
if (host.includes("cloudfront.net")) {
|
|
1102
|
+
if (VIDEO_EXTS14.includes(ext)) return "assets/videos";
|
|
1103
|
+
if (IMG_EXTS14.includes(ext) || pathname.includes("/images/")) return "assets/images";
|
|
1104
|
+
if (ext === ".css") return "styles";
|
|
1105
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
1106
|
+
if (FONT_EXTS15.includes(ext)) return "assets/fonts";
|
|
1107
|
+
return "assets/misc";
|
|
1108
|
+
}
|
|
1109
|
+
return null;
|
|
1110
|
+
}
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
1113
|
+
});
|
|
1114
|
+
|
|
1115
|
+
// src/platforms/cms/ghost.ts
|
|
1116
|
+
var IMG_EXTS15, FONT_EXTS16, VIDEO_EXTS15, ghost;
|
|
1117
|
+
var init_ghost = __esm({
|
|
1118
|
+
"src/platforms/cms/ghost.ts"() {
|
|
1119
|
+
"use strict";
|
|
1120
|
+
IMG_EXTS15 = [".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp", ".avif", ".ico"];
|
|
1121
|
+
FONT_EXTS16 = [".woff2", ".woff", ".ttf", ".otf", ".eot"];
|
|
1122
|
+
VIDEO_EXTS15 = [".mp4", ".webm", ".ogg"];
|
|
1123
|
+
ghost = {
|
|
1124
|
+
name: "ghost",
|
|
1125
|
+
displayName: "Ghost",
|
|
1126
|
+
category: "cms",
|
|
1127
|
+
priority: 78,
|
|
1128
|
+
detectByUrl(url) {
|
|
1129
|
+
return /\.ghost\.io/i.test(url);
|
|
1130
|
+
},
|
|
1131
|
+
detectByHtml(html) {
|
|
1132
|
+
return html.includes("/assets/built/screen.css") || html.includes("/public/cards.min.css") || html.includes("cdn.jsdelivr.net/ghost/") || html.includes("gh-powered-by");
|
|
1133
|
+
},
|
|
1134
|
+
// Matched against <meta name="generator"> content only. Version drifts per site
|
|
1135
|
+
// (observed 6.51 and 6.39), so match the pattern rather than a literal version.
|
|
1136
|
+
detectGenerator: /Ghost [0-9.]+/i,
|
|
1137
|
+
// First-party ghost-stats is the only analytics; it POSTs to the site's own
|
|
1138
|
+
// origin, so there is no third-party telemetry host to block during capture.
|
|
1139
|
+
stripDomains: [],
|
|
1140
|
+
// The "Powered by Ghost" badge's unique class sits on the WRAPPER <div>, whose
|
|
1141
|
+
// only child is the <a>. The post-processor's naive `.class` stripper is
|
|
1142
|
+
// non-greedy to the first close tag, so `.gh-powered-by` would delete only up to
|
|
1143
|
+
// the inner </a> and orphan a dangling </div>. Since selectors run before
|
|
1144
|
+
// patterns, that orphan cannot be cleaned up afterwards. The badge is therefore
|
|
1145
|
+
// removed cleanly (both tags) by the wrapper-div stripPattern below instead.
|
|
1146
|
+
stripSelectors: [],
|
|
1147
|
+
stripPatterns: [
|
|
1148
|
+
// <div class="gh-powered-by"><a href="https://ghost.org/" ...>Powered by Ghost</a></div>
|
|
1149
|
+
/<div[^>]*class="[^"]*gh-powered-by[^"]*"[^>]*>[\s\S]*?<\/div>/g,
|
|
1150
|
+
// Bare <a href="…ghost.org…">Powered by Ghost</a> fallback if the wrapper drops.
|
|
1151
|
+
/<a[^>]*href="[^"]*ghost\.org[^"]*"[^>]*>[\s\S]*?<\/a>/g,
|
|
1152
|
+
// Identifying <meta name="generator" content="Ghost X.Y">.
|
|
1153
|
+
/<meta[^>]*name="generator"[^>]*content="Ghost[^"]*"[^>]*>/gi
|
|
1154
|
+
],
|
|
1155
|
+
hydrationTimeout: 0,
|
|
1156
|
+
needsHydrationCheck: false,
|
|
1157
|
+
scrollStrategy: "paginated",
|
|
1158
|
+
mapAssetDir(host, pathname, ext) {
|
|
1159
|
+
if (host.includes("storage.ghost.io")) {
|
|
1160
|
+
if (VIDEO_EXTS15.includes(ext)) return "assets/videos";
|
|
1161
|
+
if (pathname.includes("/images/") || IMG_EXTS15.includes(ext)) return "assets/images";
|
|
1162
|
+
if (FONT_EXTS16.includes(ext)) return "assets/fonts";
|
|
1163
|
+
if (ext === ".css") return "styles";
|
|
1164
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
1165
|
+
return "assets/misc";
|
|
1166
|
+
}
|
|
1167
|
+
if (host.includes("cdn.jsdelivr.net")) {
|
|
1168
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
1169
|
+
if (ext === ".css") return "styles";
|
|
1170
|
+
if (FONT_EXTS16.includes(ext)) return "assets/fonts";
|
|
1171
|
+
if (IMG_EXTS15.includes(ext)) return "assets/images";
|
|
1172
|
+
return "assets/misc";
|
|
1173
|
+
}
|
|
1174
|
+
return null;
|
|
1175
|
+
}
|
|
1176
|
+
};
|
|
1177
|
+
}
|
|
1178
|
+
});
|
|
1179
|
+
|
|
1180
|
+
// src/platforms/cms/notion.ts
|
|
1181
|
+
var IMG_EXTS16, FONT_EXTS17, VIDEO_EXTS16, notion;
|
|
1182
|
+
var init_notion = __esm({
|
|
1183
|
+
"src/platforms/cms/notion.ts"() {
|
|
1184
|
+
"use strict";
|
|
1185
|
+
IMG_EXTS16 = [".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp", ".avif", ".ico"];
|
|
1186
|
+
FONT_EXTS17 = [".woff2", ".woff", ".ttf", ".otf", ".eot"];
|
|
1187
|
+
VIDEO_EXTS16 = [".mp4", ".webm", ".ogg"];
|
|
1188
|
+
notion = {
|
|
1189
|
+
name: "notion",
|
|
1190
|
+
displayName: "Notion (Notion Sites)",
|
|
1191
|
+
category: "cms",
|
|
1192
|
+
priority: 85,
|
|
1193
|
+
detectByUrl(url) {
|
|
1194
|
+
return /\.notion\.site/i.test(url);
|
|
1195
|
+
},
|
|
1196
|
+
detectByHtml(html) {
|
|
1197
|
+
return html.includes('data-notion-html="web"') || html.includes('id="notion-app"') || html.includes("__notion_html_async") || html.includes("window.__notion_boot_data");
|
|
1198
|
+
},
|
|
1199
|
+
// Notion's own Splunk telemetry collector — blocked during capture. NEVER add
|
|
1200
|
+
// the asset hosts (www.notion.so / *.amazonaws.com) here; they serve real files.
|
|
1201
|
+
stripDomains: ["http-inputs-notion.splunkcloud.com"],
|
|
1202
|
+
// Pre-hydration loading chrome. captureRenderedDom normally replaces these once
|
|
1203
|
+
// #notion-app hydrates; stripping them is a defensive no-content cleanup in case
|
|
1204
|
+
// hydration is partial.
|
|
1205
|
+
stripSelectors: ["#initial-loading-spinner", "#skeleton", "#skeleton-sidebar"],
|
|
1206
|
+
stripPatterns: [
|
|
1207
|
+
// "Built with Notion" watermark is React-injected post-hydration (ABSENT from
|
|
1208
|
+
// raw HTML — selector/href unverified). Best-effort removal from the captured
|
|
1209
|
+
// rendered DOM: an <a> pointing at notion.so/notion.com that also carries the
|
|
1210
|
+
// badge's signature text. Both conditions keep this from matching real content.
|
|
1211
|
+
/<a[^>]*href="[^"]*notion\.(so|com)[^"]*"[^>]*>[\s\S]*?Built with Notion[\s\S]*?<\/a>/gi
|
|
1212
|
+
],
|
|
1213
|
+
hydrationTimeout: 8e3,
|
|
1214
|
+
needsHydrationCheck: true,
|
|
1215
|
+
hydrationSelector: "#notion-app",
|
|
1216
|
+
captureRenderedDom: true,
|
|
1217
|
+
mapAssetDir(host, pathname, ext) {
|
|
1218
|
+
if (host.includes("img.notionusercontent.com") || host.includes("app.notion.com")) {
|
|
1219
|
+
if (VIDEO_EXTS16.includes(ext)) return "assets/videos";
|
|
1220
|
+
if (FONT_EXTS17.includes(ext)) return "assets/fonts";
|
|
1221
|
+
if (IMG_EXTS16.includes(ext) || pathname.includes("/image")) return "assets/images";
|
|
1222
|
+
return "assets/images";
|
|
1223
|
+
}
|
|
1224
|
+
if (host.includes("prod-files-secure.s3.us-west-2.amazonaws.com") || host === "s3.us-west-2.amazonaws.com") {
|
|
1225
|
+
if (VIDEO_EXTS16.includes(ext)) return "assets/videos";
|
|
1226
|
+
if (IMG_EXTS16.includes(ext)) return "assets/images";
|
|
1227
|
+
if (FONT_EXTS17.includes(ext)) return "assets/fonts";
|
|
1228
|
+
if (ext === ".css") return "styles";
|
|
1229
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
1230
|
+
return "assets/misc";
|
|
1231
|
+
}
|
|
1232
|
+
if (host.includes("www.notion.so") || host.includes("notion.so")) {
|
|
1233
|
+
if (ext === ".css") return "styles";
|
|
1234
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
1235
|
+
if (FONT_EXTS17.includes(ext)) return "assets/fonts";
|
|
1236
|
+
if (IMG_EXTS16.includes(ext)) return "assets/images";
|
|
1237
|
+
if (ext === ".json") return "data";
|
|
1238
|
+
return "assets/misc";
|
|
1239
|
+
}
|
|
1240
|
+
return null;
|
|
1241
|
+
}
|
|
1242
|
+
};
|
|
1243
|
+
}
|
|
1244
|
+
});
|
|
1245
|
+
|
|
1246
|
+
// src/platforms/cms/wordpress.ts
|
|
1247
|
+
var IMG_EXTS17, FONT_EXTS18, VIDEO_EXTS17, wordpress;
|
|
1248
|
+
var init_wordpress = __esm({
|
|
1249
|
+
"src/platforms/cms/wordpress.ts"() {
|
|
1250
|
+
"use strict";
|
|
1251
|
+
IMG_EXTS17 = [".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp", ".avif", ".ico"];
|
|
1252
|
+
FONT_EXTS18 = [".woff2", ".woff", ".ttf", ".otf", ".eot"];
|
|
1253
|
+
VIDEO_EXTS17 = [".mp4", ".webm", ".ogg"];
|
|
1254
|
+
wordpress = {
|
|
1255
|
+
name: "wordpress",
|
|
1256
|
+
displayName: "WordPress",
|
|
1257
|
+
category: "cms",
|
|
1258
|
+
priority: 40,
|
|
1259
|
+
detectByUrl(url) {
|
|
1260
|
+
return /\.wordpress\.com|\/wp-(content|includes|json)\//i.test(url);
|
|
1261
|
+
},
|
|
1262
|
+
detectByHtml(html) {
|
|
1263
|
+
return html.includes("/wp-includes/") || html.includes("/wp-content/") || html.includes("api.w.org") || html.includes("_wpemojiSettings");
|
|
1264
|
+
},
|
|
1265
|
+
// Matched against the <meta name="generator"> content only. Covers both
|
|
1266
|
+
// "WordPress <version>" (self-hosted) and exact "WordPress.com" (hosted).
|
|
1267
|
+
detectGenerator: /WordPress/i,
|
|
1268
|
+
// Jetpack / WP.com Stats trackers only — never the asset CDN.
|
|
1269
|
+
stripDomains: ["stats.wp.com", "pixel.wp.com"],
|
|
1270
|
+
// Remove the leftover tracker <script> tags after their requests are blocked.
|
|
1271
|
+
stripSelectors: ['script[src*="stats.wp.com"]', 'script[src*="pixel.wp.com"]'],
|
|
1272
|
+
// No badge to strip; drop the generator meta as an attribution cleanup
|
|
1273
|
+
// (shape mirrors webflow.ts). Matches "WordPress 6.9.4" and "WordPress.com".
|
|
1274
|
+
stripPatterns: [
|
|
1275
|
+
/<meta[^>]*name=["']generator["'][^>]*content=["'][^"']*WordPress[^"']*["'][^>]*>/gi
|
|
1276
|
+
],
|
|
1277
|
+
hydrationTimeout: 0,
|
|
1278
|
+
needsHydrationCheck: false,
|
|
1279
|
+
mapAssetDir(host, pathname, ext) {
|
|
1280
|
+
if (host.includes("i0.wp.com") || host.includes("i1.wp.com") || host.includes("i2.wp.com")) {
|
|
1281
|
+
return "assets/images";
|
|
1282
|
+
}
|
|
1283
|
+
if (host.includes("gravatar.com")) {
|
|
1284
|
+
return "assets/images";
|
|
1285
|
+
}
|
|
1286
|
+
if (host.includes("fonts.wp.com")) {
|
|
1287
|
+
if (ext === ".css") return "styles";
|
|
1288
|
+
if (IMG_EXTS17.includes(ext)) return "assets/images";
|
|
1289
|
+
return "assets/fonts";
|
|
1290
|
+
}
|
|
1291
|
+
if (host.includes("s0.wp.com") || host.includes("s1.wp.com") || host.includes("s2.wp.com") || host.includes("c0.wp.com") || host.includes("s.w.org")) {
|
|
1292
|
+
if (VIDEO_EXTS17.includes(ext)) return "assets/videos";
|
|
1293
|
+
if (IMG_EXTS17.includes(ext) || pathname.includes("/images/")) return "assets/images";
|
|
1294
|
+
if (ext === ".css") return "styles";
|
|
1295
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
1296
|
+
if (FONT_EXTS18.includes(ext)) return "assets/fonts";
|
|
1297
|
+
return "assets/misc";
|
|
1298
|
+
}
|
|
1299
|
+
return null;
|
|
1300
|
+
}
|
|
1301
|
+
};
|
|
1302
|
+
}
|
|
1303
|
+
});
|
|
1304
|
+
|
|
1305
|
+
// src/platforms/course/kajabi.ts
|
|
1306
|
+
var IMG_EXTS18, FONT_EXTS19, VIDEO_EXTS18, kajabi;
|
|
1307
|
+
var init_kajabi = __esm({
|
|
1308
|
+
"src/platforms/course/kajabi.ts"() {
|
|
1309
|
+
"use strict";
|
|
1310
|
+
IMG_EXTS18 = [".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp", ".avif", ".ico"];
|
|
1311
|
+
FONT_EXTS19 = [".woff2", ".woff", ".ttf", ".otf", ".eot"];
|
|
1312
|
+
VIDEO_EXTS18 = [".mp4", ".webm", ".ogg"];
|
|
1313
|
+
kajabi = {
|
|
1314
|
+
name: "kajabi",
|
|
1315
|
+
displayName: "Kajabi",
|
|
1316
|
+
category: "course",
|
|
1317
|
+
priority: 85,
|
|
1318
|
+
detectByUrl(url) {
|
|
1319
|
+
return /\.mykajabi\.com/i.test(url);
|
|
1320
|
+
},
|
|
1321
|
+
// Four Kajabi-only, tier-independent tells. Badge markers are deliberately NOT
|
|
1322
|
+
// used here (the "Powered by Kajabi" badge is absent on paid plans). None of
|
|
1323
|
+
// these strings appear on Framer / Webflow / Wix output.
|
|
1324
|
+
detectByHtml(html) {
|
|
1325
|
+
return html.includes("var Kajabi = Kajabi") || // inline bootstrap: `var Kajabi = Kajabi || {};`
|
|
1326
|
+
html.includes("Kajabi.currentSiteUser") || // inline theme bootstrap global
|
|
1327
|
+
html.includes("kjb-settings-id=") || // Kajabi theme-editor data attribute
|
|
1328
|
+
html.includes("kajabi-cdn.com");
|
|
1329
|
+
},
|
|
1330
|
+
// Telemetry / analytics hosts only — NEVER the kajabi-cdn.com asset hosts.
|
|
1331
|
+
stripDomains: ["rs-dp.kajabi.com", "cdn.rudderlabs.com"],
|
|
1332
|
+
// The "Powered by Kajabi" badge wrapper carries a unique class — remove it via
|
|
1333
|
+
// selector (first-match). stripPatterns below covers repeats / rendered markup.
|
|
1334
|
+
stripSelectors: [".footer__powered-by"],
|
|
1335
|
+
stripPatterns: [
|
|
1336
|
+
// Whole badge wrapper: <div class="footer__powered-by"> … </div>
|
|
1337
|
+
// (real markup has indentation/newlines between the div and the anchor).
|
|
1338
|
+
/<div[^>]*class="[^"]*footer__powered-by[^"]*"[^>]*>[\s\S]*?<\/div>/g,
|
|
1339
|
+
// Belt-and-suspenders: the branded referral anchor itself.
|
|
1340
|
+
/<a[^>]*href="[^"]*app\.kajabi\.com\/r\/[^"]*powered_by=true[^"]*"[^>]*>[\s\S]*?<\/a>/g
|
|
1341
|
+
],
|
|
1342
|
+
// ssr-static: the raw SSR HTML is complete, so no hydration wait is needed
|
|
1343
|
+
// (profile needsHydrationCheck=false, hydrationTimeoutMs=0). antiBot is
|
|
1344
|
+
// Cloudflare but the pages fetched cleanly in research; per the authoring
|
|
1345
|
+
// guide we keep a small capture buffer instead of the literal 0 ms.
|
|
1346
|
+
hydrationTimeout: 2e3,
|
|
1347
|
+
needsHydrationCheck: false,
|
|
1348
|
+
// Both cdnDomains live under *.kajabi-cdn.com
|
|
1349
|
+
// - kajabi-storefronts-production.kajabi-cdn.com
|
|
1350
|
+
// - kajabi-app-assets.kajabi-cdn.com
|
|
1351
|
+
// so a single host match covers every declared CDN host; classify by ext/path.
|
|
1352
|
+
mapAssetDir(host, pathname, ext) {
|
|
1353
|
+
if (host.includes("kajabi-cdn.com")) {
|
|
1354
|
+
if (VIDEO_EXTS18.includes(ext)) return "assets/videos";
|
|
1355
|
+
if (pathname.includes("/images/") || IMG_EXTS18.includes(ext)) return "assets/images";
|
|
1356
|
+
if (ext === ".css") return "styles";
|
|
1357
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
1358
|
+
if (FONT_EXTS19.includes(ext)) return "assets/fonts";
|
|
1359
|
+
if (ext === ".json") return "data";
|
|
1360
|
+
return "assets/misc";
|
|
1361
|
+
}
|
|
1362
|
+
return null;
|
|
1363
|
+
}
|
|
1364
|
+
};
|
|
1365
|
+
}
|
|
1366
|
+
});
|
|
1367
|
+
|
|
1368
|
+
// src/platforms/course/podia.ts
|
|
1369
|
+
var IMG_EXTS19, FONT_EXTS20, VIDEO_EXTS19, podia;
|
|
1370
|
+
var init_podia = __esm({
|
|
1371
|
+
"src/platforms/course/podia.ts"() {
|
|
1372
|
+
"use strict";
|
|
1373
|
+
IMG_EXTS19 = [".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp", ".avif", ".ico"];
|
|
1374
|
+
FONT_EXTS20 = [".woff2", ".woff", ".ttf", ".otf", ".eot"];
|
|
1375
|
+
VIDEO_EXTS19 = [".mp4", ".webm", ".ogg"];
|
|
1376
|
+
podia = {
|
|
1377
|
+
name: "podia",
|
|
1378
|
+
displayName: "Podia",
|
|
1379
|
+
category: "course",
|
|
1380
|
+
priority: 80,
|
|
1381
|
+
detectByUrl(url) {
|
|
1382
|
+
return /\.podia\.com/i.test(url);
|
|
1383
|
+
},
|
|
1384
|
+
detectByHtml(html) {
|
|
1385
|
+
return (
|
|
1386
|
+
// Constant Turnstile site-key present on every Podia storefront (incl. custom domains).
|
|
1387
|
+
html.includes("0x4AAAAAAAJ5kwYW6AH1ybLx") || // Shared framework asset host (storefront-<hash>.css/.js, user-site-<hash>.js).
|
|
1388
|
+
html.includes("cdn.podia.com") || // React section-adapter islands unique to Podia's creator UI.
|
|
1389
|
+
html.includes('data-react-component="creator_ui/section_adapters/') || // Stimulus controller wiring the editable page sections.
|
|
1390
|
+
html.includes('data-controller="editor--page-section"')
|
|
1391
|
+
);
|
|
1392
|
+
},
|
|
1393
|
+
// Telemetry only — NEVER the asset CDN. Podia's default analytics is Google Tag Manager.
|
|
1394
|
+
stripDomains: ["www.googletagmanager.com"],
|
|
1395
|
+
stripSelectors: ['script[src*="googletagmanager.com"]'],
|
|
1396
|
+
stripPatterns: [
|
|
1397
|
+
// "Powered by Podia" footer badge — React-rendered anchor in the captured DOM.
|
|
1398
|
+
// Its href is https://www.podia.com?utm_medium=poweredby&utm_source=footer
|
|
1399
|
+
// (the "&" may serialize as "&"). Anchor on the stable brand host + the
|
|
1400
|
+
// badge-unique utm_medium=poweredby token; tolerate single/double quotes and any
|
|
1401
|
+
// chars between host and token (e.g. a "/" before the query string).
|
|
1402
|
+
/<a[^>]*href=['"][^'"]*podia\.com[^'"]*utm_medium=poweredby[^'"]*['"][^>]*>[\s\S]*?<\/a>/gi
|
|
1403
|
+
],
|
|
1404
|
+
hydrationTimeout: 4e3,
|
|
1405
|
+
needsHydrationCheck: true,
|
|
1406
|
+
// Content lives in these React islands rather than a #main root.
|
|
1407
|
+
hydrationSelector: ".react-page-section",
|
|
1408
|
+
captureRenderedDom: true,
|
|
1409
|
+
mapAssetDir(host, pathname, ext) {
|
|
1410
|
+
if (host.includes("cdn.podia.com")) {
|
|
1411
|
+
if (VIDEO_EXTS19.includes(ext)) return "assets/videos";
|
|
1412
|
+
if (ext === ".css") return "styles";
|
|
1413
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
1414
|
+
if (FONT_EXTS20.includes(ext)) return "assets/fonts";
|
|
1415
|
+
if (IMG_EXTS19.includes(ext) || pathname.includes("/images/")) return "assets/images";
|
|
1416
|
+
return "assets/misc";
|
|
1417
|
+
}
|
|
1418
|
+
if (host.includes(".podia.com") && pathname.includes("/content-assets/")) {
|
|
1419
|
+
if (VIDEO_EXTS19.includes(ext)) return "assets/videos";
|
|
1420
|
+
if (IMG_EXTS19.includes(ext)) return "assets/images";
|
|
1421
|
+
if (FONT_EXTS20.includes(ext)) return "assets/fonts";
|
|
1422
|
+
return "assets/misc";
|
|
1423
|
+
}
|
|
1424
|
+
return null;
|
|
1425
|
+
}
|
|
1426
|
+
};
|
|
1427
|
+
}
|
|
1428
|
+
});
|
|
1429
|
+
|
|
1430
|
+
// src/platforms/course/teachable.ts
|
|
1431
|
+
var IMG_EXTS20, FONT_EXTS21, VIDEO_EXTS20, teachable;
|
|
1432
|
+
var init_teachable = __esm({
|
|
1433
|
+
"src/platforms/course/teachable.ts"() {
|
|
1434
|
+
"use strict";
|
|
1435
|
+
IMG_EXTS20 = [".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp", ".avif", ".ico"];
|
|
1436
|
+
FONT_EXTS21 = [".woff2", ".woff", ".ttf", ".otf", ".eot"];
|
|
1437
|
+
VIDEO_EXTS20 = [".mp4", ".webm", ".ogg"];
|
|
1438
|
+
teachable = {
|
|
1439
|
+
name: "teachable",
|
|
1440
|
+
displayName: "Teachable",
|
|
1441
|
+
category: "course",
|
|
1442
|
+
priority: 82,
|
|
1443
|
+
detectByUrl(url) {
|
|
1444
|
+
return /\.teachable\.com/i.test(url);
|
|
1445
|
+
},
|
|
1446
|
+
detectByHtml(html) {
|
|
1447
|
+
return html.includes("teachablecdn.com") || html.includes("eventable.internal.teachable.com") || html.includes("Teach Online with") || html.includes("src=school_footer");
|
|
1448
|
+
},
|
|
1449
|
+
stripDomains: [
|
|
1450
|
+
"cdn.heapanalytics.com",
|
|
1451
|
+
"bam.nr-data.net",
|
|
1452
|
+
"www.recaptcha.net",
|
|
1453
|
+
"fast.wistia.com"
|
|
1454
|
+
],
|
|
1455
|
+
stripSelectors: [
|
|
1456
|
+
// Analytics <script src> tags left in the HTML (hosts also blocked at capture
|
|
1457
|
+
// via stripDomains). The powered-by badge is handled by stripPatterns instead
|
|
1458
|
+
// — a `.class` selector can't match its single-quoted, multi-child anchor.
|
|
1459
|
+
'script[src*="heapanalytics.com"]',
|
|
1460
|
+
'script[src*="nr-data.net"]'
|
|
1461
|
+
],
|
|
1462
|
+
stripPatterns: [
|
|
1463
|
+
// Footer "Teach Online with" badge — anchored on its unique powered-by class.
|
|
1464
|
+
/<a[^>]*class=['"][^'"]*powered-by[^'"]*['"][^>]*>[\s\S]*?<\/a>/gi,
|
|
1465
|
+
// Fallback: anchored on the teachable.com ?src=school_footer badge href.
|
|
1466
|
+
/<a[^>]*href=['"][^'"]*teachable\.com[^'"]*src=school_footer[^'"]*['"][^>]*>[\s\S]*?<\/a>/gi
|
|
1467
|
+
],
|
|
1468
|
+
hydrationTimeout: 0,
|
|
1469
|
+
needsHydrationCheck: false,
|
|
1470
|
+
mapAssetDir(host, pathname, ext) {
|
|
1471
|
+
if (host.includes("teachablecdn.com")) {
|
|
1472
|
+
if (VIDEO_EXTS20.includes(ext)) return "assets/videos";
|
|
1473
|
+
if (IMG_EXTS20.includes(ext)) return "assets/images";
|
|
1474
|
+
if (FONT_EXTS21.includes(ext)) return "assets/fonts";
|
|
1475
|
+
if (ext === ".css") return "styles";
|
|
1476
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
1477
|
+
if (ext === ".json") return "data";
|
|
1478
|
+
if (pathname.includes("/images/")) return "assets/images";
|
|
1479
|
+
if (pathname.includes("/packs/")) return "scripts/vendor";
|
|
1480
|
+
return "assets/misc";
|
|
1481
|
+
}
|
|
1482
|
+
return null;
|
|
1483
|
+
}
|
|
1484
|
+
};
|
|
1485
|
+
}
|
|
1486
|
+
});
|
|
1487
|
+
|
|
1488
|
+
// src/platforms/course/thinkific.ts
|
|
1489
|
+
var IMG_EXTS21, FONT_EXTS22, VIDEO_EXTS21, thinkific;
|
|
1490
|
+
var init_thinkific = __esm({
|
|
1491
|
+
"src/platforms/course/thinkific.ts"() {
|
|
1492
|
+
"use strict";
|
|
1493
|
+
IMG_EXTS21 = [".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp", ".avif", ".ico"];
|
|
1494
|
+
FONT_EXTS22 = [".woff2", ".woff", ".ttf", ".otf", ".eot"];
|
|
1495
|
+
VIDEO_EXTS21 = [".mp4", ".webm", ".ogg"];
|
|
1496
|
+
thinkific = {
|
|
1497
|
+
name: "thinkific",
|
|
1498
|
+
displayName: "Thinkific",
|
|
1499
|
+
category: "course",
|
|
1500
|
+
priority: 82,
|
|
1501
|
+
detectByUrl(url) {
|
|
1502
|
+
return /\.thinkific\.com/i.test(url);
|
|
1503
|
+
},
|
|
1504
|
+
// Four Thinkific-only tells that hold on both subdomain and custom-domain
|
|
1505
|
+
// sites. The "Powered by Thinkific" badge is deliberately NOT used here (it is
|
|
1506
|
+
// removable on paid plans). None of these strings appear on Framer / Webflow /
|
|
1507
|
+
// Wix output.
|
|
1508
|
+
detectByHtml(html) {
|
|
1509
|
+
return html.includes("window.Thinkific") || // inline bootstrap: `window.Thinkific = window.Thinkific || {}`
|
|
1510
|
+
html.includes("assets.thinkific.com") || // theme CSS/JS bundle host
|
|
1511
|
+
html.includes("cdn.thinkific.com") || // toga-css icon-font host
|
|
1512
|
+
html.includes("thnc.current_user-initialized");
|
|
1513
|
+
},
|
|
1514
|
+
// Telemetry / third-party embed hosts only (profile.analyticsScripts) — NEVER
|
|
1515
|
+
// the *.thinkific.com asset CDN. These are matched against the request HOSTNAME
|
|
1516
|
+
// only (capture.ts: `host.includes(d)`), so each entry MUST be a bare hostname —
|
|
1517
|
+
// a path such as "www.google.com/recaptcha" can never match and would be dead.
|
|
1518
|
+
// `fast.wistia.net` = Wistia video player; `www.google.com` = sign-in reCAPTCHA
|
|
1519
|
+
// (api.js served from www.google.com/recaptcha; the whole host is safe to block
|
|
1520
|
+
// on a static mirror — no legit Thinkific asset is served from it).
|
|
1521
|
+
stripDomains: ["fast.wistia.net", "www.google.com"],
|
|
1522
|
+
// The badge is removed via stripPatterns (below), NOT a `.class` stripSelector.
|
|
1523
|
+
// The class-based stripper (output.ts) is a non-greedy `…>[\s\S]*?<\/[^>]*>` that
|
|
1524
|
+
// stops at the FIRST closing tag, so on the nested badge markup
|
|
1525
|
+
// (<div class="footer__white-label"><a><svg><title>…</title>…</svg></a></div>) it
|
|
1526
|
+
// would cut at </title>, leaving orphan </svg></a></div> AND deleting the opening
|
|
1527
|
+
// <div>/<a> tags that the stripPatterns regexes anchor on (stripSelectors run
|
|
1528
|
+
// before stripPatterns). So no stripSelectors are used here.
|
|
1529
|
+
stripSelectors: [],
|
|
1530
|
+
stripPatterns: [
|
|
1531
|
+
// Whole badge wrapper: <div class="footer__white-label"> … </div>
|
|
1532
|
+
// (inner anchor + inline SVG titled "Teach online with Thinkific").
|
|
1533
|
+
/<div[^>]*class="[^"]*footer__white-label[^"]*"[^>]*>[\s\S]*?<\/div>/g,
|
|
1534
|
+
// Belt-and-suspenders: the branded powered-by referral anchor itself.
|
|
1535
|
+
/<a[^>]*href="[^"]*thinkific\.com[^"]*utm_medium=powered-by[^"]*"[^>]*>[\s\S]*?<\/a>/g
|
|
1536
|
+
],
|
|
1537
|
+
// ssr-static: the raw SSR HTML is complete, so no hydration wait is needed
|
|
1538
|
+
// (profile needsHydrationCheck=false, hydrationTimeoutMs=0). antiBot is
|
|
1539
|
+
// Cloudflare (rocket-loader observed) but pages fetched cleanly in research;
|
|
1540
|
+
// per the authoring guide we keep a small capture buffer instead of literal 0.
|
|
1541
|
+
hydrationTimeout: 2e3,
|
|
1542
|
+
needsHydrationCheck: false,
|
|
1543
|
+
// cdnDomains: assets.thinkific.com, cdn.thinkific.com, cdn-themes.thinkific.com,
|
|
1544
|
+
// import.cdn.thinkific.com, files.cdn.thinkific.com (all *.thinkific.com), plus
|
|
1545
|
+
// the bare s3.amazonaws.com upload host. Theme bundles/fonts live on the
|
|
1546
|
+
// thinkific.com hosts; uploaded media on import.cdn / files.cdn (front S3).
|
|
1547
|
+
mapAssetDir(host, pathname, ext) {
|
|
1548
|
+
if (host.includes("thinkific.com")) {
|
|
1549
|
+
if (VIDEO_EXTS21.includes(ext)) return "assets/videos";
|
|
1550
|
+
if (pathname.includes("/images/") || IMG_EXTS21.includes(ext)) return "assets/images";
|
|
1551
|
+
if (ext === ".css") return "styles";
|
|
1552
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
1553
|
+
if (FONT_EXTS22.includes(ext)) return "assets/fonts";
|
|
1554
|
+
if (ext === ".json") return "data";
|
|
1555
|
+
return "assets/misc";
|
|
1556
|
+
}
|
|
1557
|
+
if (host.includes("s3.amazonaws.com")) {
|
|
1558
|
+
if (VIDEO_EXTS21.includes(ext)) return "assets/videos";
|
|
1559
|
+
if (IMG_EXTS21.includes(ext)) return "assets/images";
|
|
1560
|
+
if (FONT_EXTS22.includes(ext)) return "assets/fonts";
|
|
1561
|
+
if (ext === ".css") return "styles";
|
|
1562
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
1563
|
+
return "assets/misc";
|
|
1564
|
+
}
|
|
1565
|
+
return null;
|
|
1566
|
+
}
|
|
1567
|
+
};
|
|
1568
|
+
}
|
|
1569
|
+
});
|
|
1570
|
+
|
|
1571
|
+
// src/platforms/ecommerce/gumroad.ts
|
|
1572
|
+
var IMG_EXTS22, FONT_EXTS23, VIDEO_EXTS22, gumroad;
|
|
1573
|
+
var init_gumroad = __esm({
|
|
1574
|
+
"src/platforms/ecommerce/gumroad.ts"() {
|
|
1575
|
+
"use strict";
|
|
1576
|
+
IMG_EXTS22 = [".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp", ".avif", ".ico"];
|
|
1577
|
+
FONT_EXTS23 = [".woff2", ".woff", ".ttf", ".otf", ".eot"];
|
|
1578
|
+
VIDEO_EXTS22 = [".mp4", ".webm", ".ogg"];
|
|
1579
|
+
gumroad = {
|
|
1580
|
+
name: "gumroad",
|
|
1581
|
+
displayName: "Gumroad",
|
|
1582
|
+
category: "ecommerce",
|
|
1583
|
+
priority: 80,
|
|
1584
|
+
detectByUrl(url) {
|
|
1585
|
+
return /\.gumroad\.com|\bgum\.co\b/i.test(url);
|
|
1586
|
+
},
|
|
1587
|
+
detectByHtml(html) {
|
|
1588
|
+
return html.includes("cable.gumroad.com") || html.includes("assets.gumroad.com") || html.includes("ns/fb/gumroad#") || html.includes("gumroad:product");
|
|
1589
|
+
},
|
|
1590
|
+
// Telemetry / analytics hosts only — never the asset CDN.
|
|
1591
|
+
// gumroad-analytics.com is Gumroad's first-party analytics proxy
|
|
1592
|
+
// (domain_settings.third_party_analytics_domain); GA / FB Pixel / TikTok Pixel
|
|
1593
|
+
// are injected client-side only when the creator enables them.
|
|
1594
|
+
stripDomains: [
|
|
1595
|
+
"gumroad-analytics.com",
|
|
1596
|
+
"www.googletagmanager.com",
|
|
1597
|
+
"connect.facebook.net",
|
|
1598
|
+
"analytics.tiktok.com"
|
|
1599
|
+
],
|
|
1600
|
+
// Defensively drop any surviving analytics <script> tags from the rendered DOM
|
|
1601
|
+
// (stripDomains already blocks them at the network layer during capture).
|
|
1602
|
+
stripSelectors: [
|
|
1603
|
+
'script[src*="gumroad-analytics.com"]',
|
|
1604
|
+
'script[src*="googletagmanager.com"]',
|
|
1605
|
+
'script[src*="connect.facebook.net"]',
|
|
1606
|
+
'script[src*="analytics.tiktok.com"]'
|
|
1607
|
+
],
|
|
1608
|
+
// No server-rendered badge to remove (badgePresent = false, confirmed).
|
|
1609
|
+
stripPatterns: [],
|
|
1610
|
+
hydrationTimeout: 8e3,
|
|
1611
|
+
needsHydrationCheck: true,
|
|
1612
|
+
hydrationSelector: "#app",
|
|
1613
|
+
captureRenderedDom: true,
|
|
1614
|
+
mapAssetDir(host, pathname, ext) {
|
|
1615
|
+
if (host.includes("assets.gumroad.com") || host.includes("public-files.gumroad.com") || host.includes("static-2.gumroad.com")) {
|
|
1616
|
+
if (VIDEO_EXTS22.includes(ext)) return "assets/videos";
|
|
1617
|
+
if (pathname.includes("/fonts/") || FONT_EXTS23.includes(ext)) return "assets/fonts";
|
|
1618
|
+
if (pathname.includes("/stylesheets/") || ext === ".css") return "styles";
|
|
1619
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
1620
|
+
if (pathname.includes("/images/") || IMG_EXTS22.includes(ext)) return "assets/images";
|
|
1621
|
+
if (ext === ".json") return "data";
|
|
1622
|
+
return "assets/misc";
|
|
1623
|
+
}
|
|
1624
|
+
return null;
|
|
1625
|
+
}
|
|
1626
|
+
};
|
|
1627
|
+
}
|
|
1628
|
+
});
|
|
1629
|
+
|
|
1630
|
+
// src/platforms/ecommerce/shopify.ts
|
|
1631
|
+
var IMG_EXTS23, FONT_EXTS24, VIDEO_EXTS23, shopify;
|
|
1632
|
+
var init_shopify = __esm({
|
|
1633
|
+
"src/platforms/ecommerce/shopify.ts"() {
|
|
1634
|
+
"use strict";
|
|
1635
|
+
IMG_EXTS23 = [".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp", ".avif", ".ico"];
|
|
1636
|
+
FONT_EXTS24 = [".woff2", ".woff", ".ttf", ".otf", ".eot"];
|
|
1637
|
+
VIDEO_EXTS23 = [".mp4", ".webm", ".ogg"];
|
|
1638
|
+
shopify = {
|
|
1639
|
+
name: "shopify",
|
|
1640
|
+
displayName: "Shopify",
|
|
1641
|
+
category: "ecommerce",
|
|
1642
|
+
priority: 80,
|
|
1643
|
+
detectByUrl(url) {
|
|
1644
|
+
return /\.myshopify\.com/i.test(url);
|
|
1645
|
+
},
|
|
1646
|
+
detectByHtml(html) {
|
|
1647
|
+
return html.includes("cdn.shopify.com") || html.includes("/cdn/shop/") || html.includes("Shopify.shop") || html.includes("ShopifyAnalytics");
|
|
1648
|
+
},
|
|
1649
|
+
// Telemetry only — never the asset CDN.
|
|
1650
|
+
stripDomains: ["monorail-edge.shopifysvc.com"],
|
|
1651
|
+
// The "Powered by Shopify" badge is a NESTED <span class="shopify-powered-by">
|
|
1652
|
+
// <a …>…</a></span>. The post-processor's `.class` strip only deletes up to the
|
|
1653
|
+
// FIRST closing tag (the inner </a>), which would leave an orphan </span>; worse,
|
|
1654
|
+
// stripSelectors run BEFORE stripPatterns, so it would consume the opening <span>
|
|
1655
|
+
// and stop the clean span regex below from matching. So the badge is removed
|
|
1656
|
+
// purely via stripPatterns — no `.class` selector.
|
|
1657
|
+
stripSelectors: [],
|
|
1658
|
+
stripPatterns: [
|
|
1659
|
+
// Whole badge container: <span class="shopify-powered-by" data-shopify="powered-by"><a href="…shopify.com?utm_campaign=poweredby…">Powered by Shopify</a></span>
|
|
1660
|
+
/<span[^>]*class="[^"]*shopify-powered-by[^"]*"[^>]*>[\s\S]*?<\/span>/gi,
|
|
1661
|
+
// Fallback: the brand anchor itself (href carries ?utm_campaign=poweredby), in case the <span> wrapper is absent or customised.
|
|
1662
|
+
/<a[^>]*href="[^"]*shopify\.com\?utm_campaign=poweredby[^"]*"[^>]*>[\s\S]*?<\/a>/gi
|
|
1663
|
+
],
|
|
1664
|
+
hydrationTimeout: 3500,
|
|
1665
|
+
needsHydrationCheck: true,
|
|
1666
|
+
mapAssetDir(host, pathname, ext) {
|
|
1667
|
+
const isShopifyAssetPath = pathname.includes("/cdn/shop/");
|
|
1668
|
+
if (host.includes("cdn.shopify.com") || isShopifyAssetPath) {
|
|
1669
|
+
if (VIDEO_EXTS23.includes(ext)) return "assets/videos";
|
|
1670
|
+
if (IMG_EXTS23.includes(ext)) return "assets/images";
|
|
1671
|
+
if (ext === ".css") return "styles";
|
|
1672
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
1673
|
+
if (FONT_EXTS24.includes(ext)) return "assets/fonts";
|
|
1674
|
+
if (ext === ".json") return "data";
|
|
1675
|
+
return "assets/misc";
|
|
1676
|
+
}
|
|
1677
|
+
if (host.includes("fonts.shopifycdn.com")) {
|
|
1678
|
+
if (FONT_EXTS24.includes(ext)) return "assets/fonts";
|
|
1679
|
+
return "assets/misc";
|
|
1680
|
+
}
|
|
1681
|
+
if (host.includes("extensions.shopifycdn.com")) {
|
|
1682
|
+
if (VIDEO_EXTS23.includes(ext)) return "assets/videos";
|
|
1683
|
+
if (IMG_EXTS23.includes(ext)) return "assets/images";
|
|
1684
|
+
if (ext === ".css") return "styles";
|
|
1685
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
1686
|
+
if (FONT_EXTS24.includes(ext)) return "assets/fonts";
|
|
1687
|
+
return "assets/misc";
|
|
1688
|
+
}
|
|
1689
|
+
return null;
|
|
1690
|
+
}
|
|
1691
|
+
};
|
|
1692
|
+
}
|
|
1693
|
+
});
|
|
1694
|
+
|
|
1695
|
+
// src/platforms/ai/gamma.ts
|
|
1696
|
+
var IMG_EXTS24, FONT_EXTS25, VIDEO_EXTS24, gamma;
|
|
1697
|
+
var init_gamma = __esm({
|
|
1698
|
+
"src/platforms/ai/gamma.ts"() {
|
|
1699
|
+
"use strict";
|
|
1700
|
+
IMG_EXTS24 = [".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp", ".avif", ".ico"];
|
|
1701
|
+
FONT_EXTS25 = [".woff2", ".woff", ".ttf", ".otf", ".eot"];
|
|
1702
|
+
VIDEO_EXTS24 = [".mp4", ".webm", ".ogg"];
|
|
1703
|
+
gamma = {
|
|
1704
|
+
name: "gamma",
|
|
1705
|
+
displayName: "Gamma",
|
|
1706
|
+
category: "ai",
|
|
1707
|
+
priority: 85,
|
|
1708
|
+
detectByUrl(url) {
|
|
1709
|
+
return /\.gamma\.site/i.test(url);
|
|
1710
|
+
},
|
|
1711
|
+
detectByHtml(html) {
|
|
1712
|
+
return html.includes("assets.gammahosted.com") || html.includes("imgproxy.gamma.app") || html.includes("cdn.gamma.app") || html.includes("gamma-moveable-wrapper");
|
|
1713
|
+
},
|
|
1714
|
+
// No telemetry/analytics hosts observed in the profile.
|
|
1715
|
+
stripDomains: [],
|
|
1716
|
+
// No site badge and no editor chrome to strip on published Gamma sites.
|
|
1717
|
+
stripSelectors: [],
|
|
1718
|
+
stripPatterns: [],
|
|
1719
|
+
hydrationTimeout: 8e3,
|
|
1720
|
+
needsHydrationCheck: true,
|
|
1721
|
+
hydrationSelector: "#__next",
|
|
1722
|
+
scrollStrategy: "standard",
|
|
1723
|
+
mapAssetDir(host, pathname, ext) {
|
|
1724
|
+
if (host.includes("assets.gammahosted.com")) {
|
|
1725
|
+
if (ext === ".css") return "styles";
|
|
1726
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
1727
|
+
if (ext === ".json") return "data";
|
|
1728
|
+
if (FONT_EXTS25.includes(ext)) return "assets/fonts";
|
|
1729
|
+
if (VIDEO_EXTS24.includes(ext)) return "assets/videos";
|
|
1730
|
+
if (IMG_EXTS24.includes(ext)) return "assets/images";
|
|
1731
|
+
return "assets/misc";
|
|
1732
|
+
}
|
|
1733
|
+
if (host.includes("imgproxy.gamma.app")) {
|
|
1734
|
+
return "assets/images";
|
|
1735
|
+
}
|
|
1736
|
+
if (host.includes("assets.api.gamma.app")) {
|
|
1737
|
+
return "assets/images";
|
|
1738
|
+
}
|
|
1739
|
+
if (host.includes("cdn.gamma.app")) {
|
|
1740
|
+
if (VIDEO_EXTS24.includes(ext)) return "assets/videos";
|
|
1741
|
+
if (FONT_EXTS25.includes(ext)) return "assets/fonts";
|
|
1742
|
+
if (ext === ".css") return "styles";
|
|
1743
|
+
if (ext === ".js" || ext === ".mjs") return "scripts/vendor";
|
|
1744
|
+
if (IMG_EXTS24.includes(ext)) return "assets/images";
|
|
1745
|
+
return "assets/images";
|
|
1746
|
+
}
|
|
1747
|
+
return null;
|
|
1748
|
+
}
|
|
1749
|
+
};
|
|
1750
|
+
}
|
|
1751
|
+
});
|
|
1752
|
+
|
|
1753
|
+
// src/platforms/registry.ts
|
|
1754
|
+
function sortedByPriority() {
|
|
1755
|
+
return [...PLATFORM_REGISTRY].sort((a, b) => b.priority - a.priority);
|
|
1756
|
+
}
|
|
1757
|
+
function platformsByCategory() {
|
|
1758
|
+
const out = {};
|
|
1759
|
+
for (const cat of CATEGORY_ORDER) out[cat] = [];
|
|
1760
|
+
for (const handler of PLATFORM_REGISTRY) {
|
|
1761
|
+
if (!out[handler.category]) out[handler.category] = [];
|
|
1762
|
+
out[handler.category].push(handler);
|
|
1763
|
+
}
|
|
1764
|
+
for (const cat of CATEGORY_ORDER) {
|
|
1765
|
+
out[cat].sort((a, b) => b.priority - a.priority);
|
|
1766
|
+
}
|
|
1767
|
+
return out;
|
|
1768
|
+
}
|
|
1769
|
+
var PLATFORM_REGISTRY, CATEGORY_LABELS, CATEGORY_ORDER;
|
|
1770
|
+
var init_registry = __esm({
|
|
1771
|
+
"src/platforms/registry.ts"() {
|
|
1772
|
+
"use strict";
|
|
1773
|
+
init_framer();
|
|
1774
|
+
init_webflow();
|
|
1775
|
+
init_wix();
|
|
1776
|
+
init_bubble();
|
|
1777
|
+
init_carrd();
|
|
1778
|
+
init_duda();
|
|
1779
|
+
init_squarespace();
|
|
1780
|
+
init_strikingly();
|
|
1781
|
+
init_tilda();
|
|
1782
|
+
init_weebly();
|
|
1783
|
+
init_clickfunnels();
|
|
1784
|
+
init_elementor();
|
|
1785
|
+
init_instapage();
|
|
1786
|
+
init_systemeio();
|
|
1787
|
+
init_unbounce();
|
|
1788
|
+
init_ghost();
|
|
1789
|
+
init_notion();
|
|
1790
|
+
init_wordpress();
|
|
1791
|
+
init_kajabi();
|
|
1792
|
+
init_podia();
|
|
1793
|
+
init_teachable();
|
|
1794
|
+
init_thinkific();
|
|
1795
|
+
init_gumroad();
|
|
1796
|
+
init_shopify();
|
|
1797
|
+
init_gamma();
|
|
1798
|
+
PLATFORM_REGISTRY = [
|
|
1799
|
+
// frozen trio first (highest priority, unchanged behaviour)
|
|
1800
|
+
framer,
|
|
1801
|
+
webflow,
|
|
1802
|
+
wix,
|
|
1803
|
+
// builders
|
|
1804
|
+
bubble,
|
|
1805
|
+
carrd,
|
|
1806
|
+
duda,
|
|
1807
|
+
squarespace,
|
|
1808
|
+
strikingly,
|
|
1809
|
+
tilda,
|
|
1810
|
+
weebly,
|
|
1811
|
+
// landing
|
|
1812
|
+
clickfunnels,
|
|
1813
|
+
elementor,
|
|
1814
|
+
instapage,
|
|
1815
|
+
systemeio,
|
|
1816
|
+
unbounce,
|
|
1817
|
+
// cms
|
|
1818
|
+
ghost,
|
|
1819
|
+
notion,
|
|
1820
|
+
wordpress,
|
|
1821
|
+
// course
|
|
1822
|
+
kajabi,
|
|
1823
|
+
podia,
|
|
1824
|
+
teachable,
|
|
1825
|
+
thinkific,
|
|
1826
|
+
// ecommerce
|
|
1827
|
+
gumroad,
|
|
1828
|
+
shopify,
|
|
1829
|
+
// ai
|
|
1830
|
+
gamma
|
|
1831
|
+
];
|
|
1832
|
+
CATEGORY_LABELS = {
|
|
1833
|
+
builder: "Website Builders",
|
|
1834
|
+
landing: "Landing Pages & Funnels",
|
|
1835
|
+
cms: "CMS & Blogs",
|
|
1836
|
+
course: "Courses & Membership",
|
|
1837
|
+
ecommerce: "E-commerce",
|
|
1838
|
+
ai: "AI & Presentations"
|
|
1839
|
+
};
|
|
1840
|
+
CATEGORY_ORDER = [
|
|
1841
|
+
"builder",
|
|
1842
|
+
"landing",
|
|
1843
|
+
"cms",
|
|
1844
|
+
"course",
|
|
1845
|
+
"ecommerce",
|
|
1846
|
+
"ai"
|
|
1847
|
+
];
|
|
1848
|
+
}
|
|
1849
|
+
});
|
|
1850
|
+
|
|
1851
|
+
// src/platforms/detect.ts
|
|
1852
|
+
function readGenerator(html) {
|
|
1853
|
+
const m = html.match(/<meta[^>]+name=["']generator["'][^>]*content=["']([^"']*)["']/i) || html.match(/<meta[^>]+content=["']([^"']*)["'][^>]+name=["']generator["']/i);
|
|
1854
|
+
return m ? m[1] : "";
|
|
1855
|
+
}
|
|
1856
|
+
function detectByUrl(url) {
|
|
1857
|
+
for (const platform of sortedByPriority()) {
|
|
1858
|
+
try {
|
|
1859
|
+
if (platform.detectByUrl(url)) return platform;
|
|
1860
|
+
} catch {
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1863
|
+
return null;
|
|
1864
|
+
}
|
|
1865
|
+
function detectByGenerator(html) {
|
|
1866
|
+
const gen = readGenerator(html);
|
|
1867
|
+
if (!gen) return null;
|
|
1868
|
+
for (const platform of sortedByPriority()) {
|
|
1869
|
+
if (platform.detectGenerator && platform.detectGenerator.test(gen)) return platform;
|
|
1870
|
+
}
|
|
1871
|
+
return null;
|
|
1872
|
+
}
|
|
1873
|
+
function detectByHtml(html) {
|
|
1874
|
+
const byGen = detectByGenerator(html);
|
|
1875
|
+
if (byGen) return byGen;
|
|
1876
|
+
for (const platform of sortedByPriority()) {
|
|
1877
|
+
try {
|
|
1878
|
+
if (platform.detectByHtml(html)) return platform;
|
|
1879
|
+
} catch {
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
return null;
|
|
1883
|
+
}
|
|
1884
|
+
function detectPlatform(url, html) {
|
|
1885
|
+
const byUrl = detectByUrl(url);
|
|
1886
|
+
if (byUrl) return byUrl;
|
|
1887
|
+
if (html) {
|
|
1888
|
+
const byHtml = detectByHtml(html);
|
|
1889
|
+
if (byHtml) return byHtml;
|
|
1890
|
+
}
|
|
1891
|
+
return framer;
|
|
1892
|
+
}
|
|
1893
|
+
async function detectByDom(page) {
|
|
1894
|
+
try {
|
|
1895
|
+
const signal = await page.evaluate(() => {
|
|
1896
|
+
const html = document.documentElement.outerHTML || "";
|
|
1897
|
+
if (/wix-viewer-model|_wix_|wix-ecom/i.test(html)) return "wix";
|
|
1898
|
+
if (/data-wf-|w-webflow-badge|webflow\.js/i.test(html)) return "webflow";
|
|
1899
|
+
if (/framerusercontent|framercanvas|framerstatic/i.test(html)) return "framer";
|
|
1900
|
+
const srcs = [];
|
|
1901
|
+
const scripts = document.querySelectorAll("script[src]");
|
|
1902
|
+
scripts.forEach((s) => {
|
|
1903
|
+
if (s.src) srcs.push(s.src);
|
|
1904
|
+
});
|
|
1905
|
+
const allSrcs = srcs.join(" ");
|
|
1906
|
+
if (/wixstatic\.com|parastorage\.com/.test(allSrcs)) return "wix";
|
|
1907
|
+
if (/website-files\.com|webflow\.com/.test(allSrcs)) return "webflow";
|
|
1908
|
+
if (/framerusercontent\.com|framerstatic\.com|framer\.app/.test(allSrcs)) return "framer";
|
|
1909
|
+
return "";
|
|
1910
|
+
});
|
|
1911
|
+
if (signal) return getPlatformByName(signal);
|
|
1912
|
+
} catch {
|
|
1913
|
+
}
|
|
1914
|
+
for (const platform of sortedByPriority()) {
|
|
1915
|
+
if (!platform.detectByDom) continue;
|
|
1916
|
+
try {
|
|
1917
|
+
if (await platform.detectByDom(page)) return platform;
|
|
1918
|
+
} catch {
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
return null;
|
|
1922
|
+
}
|
|
1923
|
+
function getPlatformByName(name) {
|
|
1924
|
+
const found = PLATFORM_REGISTRY.find((platform) => platform.name === name);
|
|
1925
|
+
return found || framer;
|
|
1926
|
+
}
|
|
1927
|
+
var init_detect = __esm({
|
|
1928
|
+
"src/platforms/detect.ts"() {
|
|
1929
|
+
"use strict";
|
|
1930
|
+
init_registry();
|
|
1931
|
+
init_framer();
|
|
1932
|
+
}
|
|
1933
|
+
});
|
|
1934
|
+
|
|
1935
|
+
// src/platforms/index.ts
|
|
1936
|
+
var platforms_exports = {};
|
|
1937
|
+
__export(platforms_exports, {
|
|
1938
|
+
CATEGORY_LABELS: () => CATEGORY_LABELS,
|
|
1939
|
+
CATEGORY_ORDER: () => CATEGORY_ORDER,
|
|
1940
|
+
PLATFORM_REGISTRY: () => PLATFORM_REGISTRY,
|
|
1941
|
+
detectByDom: () => detectByDom,
|
|
1942
|
+
detectByGenerator: () => detectByGenerator,
|
|
1943
|
+
detectByHtml: () => detectByHtml,
|
|
1944
|
+
detectByUrl: () => detectByUrl,
|
|
1945
|
+
detectPlatform: () => detectPlatform,
|
|
1946
|
+
framer: () => framer,
|
|
1947
|
+
getPlatformByName: () => getPlatformByName,
|
|
1948
|
+
platformsByCategory: () => platformsByCategory,
|
|
1949
|
+
readGenerator: () => readGenerator,
|
|
1950
|
+
sortedByPriority: () => sortedByPriority,
|
|
1951
|
+
webflow: () => webflow,
|
|
1952
|
+
wix: () => wix
|
|
1953
|
+
});
|
|
1954
|
+
var init_platforms = __esm({
|
|
1955
|
+
"src/platforms/index.ts"() {
|
|
1956
|
+
"use strict";
|
|
1957
|
+
init_framer();
|
|
1958
|
+
init_webflow();
|
|
1959
|
+
init_wix();
|
|
1960
|
+
init_registry();
|
|
1961
|
+
init_detect();
|
|
1962
|
+
}
|
|
1963
|
+
});
|
|
1964
|
+
|
|
207
1965
|
// src/cli/cooking.ts
|
|
208
1966
|
import chalk2 from "chalk";
|
|
209
1967
|
async function showLoadingIntro(version) {
|
|
@@ -1067,318 +2825,140 @@ function output(line) {
|
|
|
1067
2825
|
var _cooking, T, LOG_PALETTE, _li, log, INFO_PALETTE, _ii, info, warn, success;
|
|
1068
2826
|
var init_logger = __esm({
|
|
1069
2827
|
"src/logger/index.ts"() {
|
|
1070
|
-
"use strict";
|
|
1071
|
-
init_theme();
|
|
1072
|
-
_cooking = null;
|
|
1073
|
-
T = () => (/* @__PURE__ */ new Date()).toISOString().slice(11, 19);
|
|
1074
|
-
LOG_PALETTE = [
|
|
1075
|
-
(s) => chalk4.hex(THEME.primary)(s),
|
|
1076
|
-
(s) => chalk4.hex(THEME.primarySoft)(s),
|
|
1077
|
-
(s) => chalk4.hex(THEME.secondary)(s),
|
|
1078
|
-
(s) => chalk4.hex(THEME.accent)(s),
|
|
1079
|
-
(s) => chalk4.hex(THEME.info)(s),
|
|
1080
|
-
(s) => chalk4.hex(THEME.text)(s)
|
|
1081
|
-
];
|
|
1082
|
-
_li = 0;
|
|
1083
|
-
log = (m) => {
|
|
1084
|
-
_li++;
|
|
1085
|
-
const c = LOG_PALETTE[_li % LOG_PALETTE.length];
|
|
1086
|
-
output(
|
|
1087
|
-
`${chalk4.hex(THEME.muted)(`[${T()}]`)} ${chalk4.hex(THEME.primary)("[log]")} ${c(trunc(m, 120))}`
|
|
1088
|
-
);
|
|
1089
|
-
};
|
|
1090
|
-
INFO_PALETTE = [
|
|
1091
|
-
(s) => chalk4.hex(THEME.info)(s),
|
|
1092
|
-
(s) => chalk4.hex(THEME.secondary)(s),
|
|
1093
|
-
(s) => chalk4.hex(THEME.primarySoft)(s)
|
|
1094
|
-
];
|
|
1095
|
-
_ii = 0;
|
|
1096
|
-
info = (m) => {
|
|
1097
|
-
_ii++;
|
|
1098
|
-
const c = INFO_PALETTE[_ii % INFO_PALETTE.length];
|
|
1099
|
-
output(
|
|
1100
|
-
`${chalk4.hex(THEME.muted)(`[${T()}]`)} ${chalk4.hex(THEME.info).bold("[info]")} ${c(trunc(m, 120))}`
|
|
1101
|
-
);
|
|
1102
|
-
};
|
|
1103
|
-
warn = (m) => {
|
|
1104
|
-
const colors = [
|
|
1105
|
-
(s) => chalk4.hex(THEME.warning)(s),
|
|
1106
|
-
(s) => chalk4.hex(THEME.primary)(s)
|
|
1107
|
-
];
|
|
1108
|
-
const c = colors[Math.floor(Math.random() * colors.length)];
|
|
1109
|
-
const line = `${chalk4.hex(THEME.muted)(`[${T()}]`)} ${chalk4.hex(THEME.warning).bold("[warn]")} ${c(trunc(m, 120))}`;
|
|
1110
|
-
if (_cooking) _cooking.log(line);
|
|
1111
|
-
else console.warn(line);
|
|
1112
|
-
};
|
|
1113
|
-
success = (m) => {
|
|
1114
|
-
const colors = [
|
|
1115
|
-
(s) => chalk4.hex(THEME.success)(s),
|
|
1116
|
-
(s) => chalk4.hex(THEME.info)(s)
|
|
1117
|
-
];
|
|
1118
|
-
const c = colors[Math.floor(Math.random() * colors.length)];
|
|
1119
|
-
output(
|
|
1120
|
-
`${chalk4.hex(THEME.muted)(`[${T()}]`)} ${chalk4.hex(THEME.success)("[ok]")} ${c(trunc(m, 120))}`
|
|
1121
|
-
);
|
|
1122
|
-
};
|
|
1123
|
-
}
|
|
1124
|
-
});
|
|
1125
|
-
|
|
1126
|
-
// src/platforms/framer.ts
|
|
1127
|
-
var FONT_EXTS, framer;
|
|
1128
|
-
var init_framer = __esm({
|
|
1129
|
-
"src/platforms/framer.ts"() {
|
|
1130
|
-
"use strict";
|
|
1131
|
-
FONT_EXTS = [".woff2", ".woff", ".ttf", ".otf"];
|
|
1132
|
-
framer = {
|
|
1133
|
-
name: "framer",
|
|
1134
|
-
displayName: "Framer",
|
|
1135
|
-
detectByUrl(url) {
|
|
1136
|
-
return /\.framer\.(app|website)|framercanvas\.com/.test(url);
|
|
1137
|
-
},
|
|
1138
|
-
detectByHtml(html) {
|
|
1139
|
-
return html.includes('id="main"') && (html.includes("framerstatic.com") || html.includes("framerusercontent.com"));
|
|
1140
|
-
},
|
|
1141
|
-
stripDomains: ["events.framer.com", "api.framer.com", "collect.frameranalytics.com"],
|
|
1142
|
-
stripSelectors: [
|
|
1143
|
-
'script[src*="events.framer.com"]',
|
|
1144
|
-
"#__framer-badge-container",
|
|
1145
|
-
"#__framer-badge",
|
|
1146
|
-
'link[href*="canvas-sandbox"]',
|
|
1147
|
-
'script[src*="framer.com/bootstrap"]'
|
|
1148
|
-
],
|
|
1149
|
-
stripPatterns: [
|
|
1150
|
-
/<div id="__framer-badge-container"[^>]*><\/div>/g,
|
|
1151
|
-
/<script>try\{if\(localStorage\.get\("__framer_force_showing_editorbar_since"\)\)[^<]*<\/script>/g
|
|
1152
|
-
],
|
|
1153
|
-
hydrationTimeout: 1e4,
|
|
1154
|
-
needsHydrationCheck: true,
|
|
1155
|
-
mapAssetDir(host, pathname, ext) {
|
|
1156
|
-
if (host.includes("framerusercontent.com")) {
|
|
1157
|
-
if (pathname.startsWith("/images/")) return "assets/images";
|
|
1158
|
-
if (pathname.startsWith("/assets/")) {
|
|
1159
|
-
return FONT_EXTS.includes(ext) ? "assets/fonts" : "assets/misc";
|
|
1160
|
-
}
|
|
1161
|
-
if (pathname.startsWith("/sites/")) {
|
|
1162
|
-
if (ext === ".mjs" || ext === ".js") return "scripts/vendor";
|
|
1163
|
-
if (ext === ".json") return "data";
|
|
1164
|
-
if (ext === ".css") return "styles";
|
|
1165
|
-
if (ext === ".framercms") return "data";
|
|
1166
|
-
return "assets/misc";
|
|
1167
|
-
}
|
|
1168
|
-
if (pathname.startsWith("/modules/")) {
|
|
1169
|
-
return ext === ".framercms" ? "data" : "scripts/modules";
|
|
1170
|
-
}
|
|
1171
|
-
return "assets/misc";
|
|
1172
|
-
}
|
|
1173
|
-
if (host.includes("app.framerstatic.com")) {
|
|
1174
|
-
if (ext === ".css") return "styles";
|
|
1175
|
-
if (ext === ".mjs" || ext === ".js") return "scripts/vendor";
|
|
1176
|
-
if (ext === ".woff2" || ext === ".woff") return "assets/fonts";
|
|
1177
|
-
if (ext === ".png" || ext === ".svg") return "assets/images";
|
|
1178
|
-
return "assets/misc";
|
|
1179
|
-
}
|
|
1180
|
-
if (host.includes("framercanvas.com") || host.includes("framer.com")) {
|
|
1181
|
-
if (ext === ".mjs" || ext === ".js") return "scripts/vendor";
|
|
1182
|
-
if (ext === ".css") return "styles";
|
|
1183
|
-
return "assets/misc";
|
|
1184
|
-
}
|
|
1185
|
-
return null;
|
|
1186
|
-
}
|
|
1187
|
-
};
|
|
1188
|
-
}
|
|
1189
|
-
});
|
|
1190
|
-
|
|
1191
|
-
// src/platforms/webflow.ts
|
|
1192
|
-
var IMG_EXTS, FONT_EXTS2, VIDEO_EXTS, webflow;
|
|
1193
|
-
var init_webflow = __esm({
|
|
1194
|
-
"src/platforms/webflow.ts"() {
|
|
1195
|
-
"use strict";
|
|
1196
|
-
IMG_EXTS = [".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp", ".avif"];
|
|
1197
|
-
FONT_EXTS2 = [".woff2", ".woff", ".ttf", ".otf", ".eot"];
|
|
1198
|
-
VIDEO_EXTS = [".mp4", ".webm", ".ogg"];
|
|
1199
|
-
webflow = {
|
|
1200
|
-
name: "webflow",
|
|
1201
|
-
displayName: "Webflow",
|
|
1202
|
-
detectByUrl(url) {
|
|
1203
|
-
return /\.webflow\.(io|com)/.test(url);
|
|
1204
|
-
},
|
|
1205
|
-
detectByHtml(html) {
|
|
1206
|
-
return html.includes("data-wf-site") || html.includes("w-webflow-badge") || html.includes("Webflow") || html.includes("webflow.js");
|
|
1207
|
-
},
|
|
1208
|
-
stripDomains: ["js-agent.newrelic.com", "cdn.heapanalytics.com", "tr.snapchat.com"],
|
|
1209
|
-
stripSelectors: [".w-webflow-badge", 'link[href*="editorbar"]'],
|
|
1210
|
-
stripPatterns: [
|
|
1211
|
-
/<a[^>]*class="[^"]*w-webflow-badge[^"]*"[^>]*>[\s\S]*?<\/a>/g,
|
|
1212
|
-
/<a[^>]*href="[^"]*webflow\.com\?utm_campaign=brandjs[^"]*"[^>]*>[\s\S]*?<\/a>/g,
|
|
1213
|
-
/<style>[^<]*\.w-webflow-badge[^<]*<\/style>/g,
|
|
1214
|
-
/Powered by <a[^>]*href="[^"]*webflow\.com"[^>]*>[^<]*<\/a>/g,
|
|
1215
|
-
/<!-- This site was created in Webflow\.[^>]*-->/g,
|
|
1216
|
-
/<html([^>]*) data-wf-domain="[^"]*"/g,
|
|
1217
|
-
/<html([^>]*) data-wf-page="[^"]*"/g,
|
|
1218
|
-
/<html([^>]*) data-wf-site="[^"]*"/g,
|
|
1219
|
-
/<html([^>]*) data-wf-status="[^"]*"/g,
|
|
1220
|
-
/<meta[^>]*content="Webflow"[^>]*>/g
|
|
1221
|
-
],
|
|
1222
|
-
hydrationTimeout: 2e3,
|
|
1223
|
-
needsHydrationCheck: false,
|
|
1224
|
-
mapAssetDir(host, pathname, ext) {
|
|
1225
|
-
if (host.includes("website-files.com") || host.includes("webflow.com") || host.includes("uploads-ssl.webflow.com")) {
|
|
1226
|
-
if (VIDEO_EXTS.includes(ext)) return "assets/videos";
|
|
1227
|
-
if (pathname.includes("/images/") || IMG_EXTS.includes(ext)) return "assets/images";
|
|
1228
|
-
if (pathname.includes("/css/") || ext === ".css") return "styles";
|
|
1229
|
-
if (pathname.includes("/js/") || ext === ".js") return "scripts/vendor";
|
|
1230
|
-
if (pathname.includes("/gsap/") || pathname.includes("/plugins/")) return "scripts/vendor";
|
|
1231
|
-
if (FONT_EXTS2.includes(ext)) return "assets/fonts";
|
|
1232
|
-
return "assets/misc";
|
|
1233
|
-
}
|
|
1234
|
-
if (host.includes("d3e54v103j8qbb.cloudfront.net")) {
|
|
1235
|
-
if (ext === ".js") return "scripts/vendor";
|
|
1236
|
-
if (ext === ".css") return "styles";
|
|
1237
|
-
if (IMG_EXTS.includes(ext)) return "assets/images";
|
|
1238
|
-
return "assets/misc";
|
|
1239
|
-
}
|
|
1240
|
-
return null;
|
|
1241
|
-
}
|
|
1242
|
-
};
|
|
1243
|
-
}
|
|
1244
|
-
});
|
|
1245
|
-
|
|
1246
|
-
// src/platforms/wix.ts
|
|
1247
|
-
var IMG_EXTS2, FONT_EXTS3, VIDEO_EXTS2, wix;
|
|
1248
|
-
var init_wix = __esm({
|
|
1249
|
-
"src/platforms/wix.ts"() {
|
|
1250
|
-
"use strict";
|
|
1251
|
-
IMG_EXTS2 = [".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp", ".avif", ".ico"];
|
|
1252
|
-
FONT_EXTS3 = [".woff2", ".woff", ".ttf", ".otf", ".eot"];
|
|
1253
|
-
VIDEO_EXTS2 = [".mp4", ".webm", ".ogg"];
|
|
1254
|
-
wix = {
|
|
1255
|
-
name: "wix",
|
|
1256
|
-
displayName: "Wix",
|
|
1257
|
-
detectByUrl(url) {
|
|
1258
|
-
return /\.wixsite\.com|\.wix\.com/.test(url);
|
|
1259
|
-
},
|
|
1260
|
-
detectByHtml(html) {
|
|
1261
|
-
return html.includes("wix-viewer-model") || html.includes('id="WIX_ADS"') || html.includes("X-Wix-") || html.includes("Wix.com") || html.includes("wixcode-sdk") || html.includes("data-wix") || html.includes("wix-code") || html.includes("WixCode") || /\bwix\.com\b/.test(html) || /meta\s+name=["']generator["']\s+content=["'][^"']*Wix[^"']*["']/i.test(html) || /_wix_/i.test(html) || /wix-ecom/i.test(html);
|
|
1262
|
-
},
|
|
1263
|
-
stripDomains: [
|
|
1264
|
-
"frog.wix.com",
|
|
1265
|
-
"bi.wixapi.com",
|
|
1266
|
-
"fed.wixcodescheduler.com",
|
|
1267
|
-
"editor.wix.com",
|
|
1268
|
-
"panorama.wixapps.net"
|
|
1269
|
-
],
|
|
1270
|
-
stripSelectors: ["#WIX_ADS", ".wix-ads", "#wix-badge", "#SCROLL_TO_TOP"],
|
|
1271
|
-
stripPatterns: [
|
|
1272
|
-
/<div[^>]*class="[^"]*wix-ads[^"]*"[^>]*>[\s\S]*?<\/div>/g
|
|
1273
|
-
],
|
|
1274
|
-
hydrationTimeout: 3e3,
|
|
1275
|
-
needsHydrationCheck: false,
|
|
1276
|
-
mapAssetDir(host, pathname, ext) {
|
|
1277
|
-
if (host === "video.wixstatic.com") {
|
|
1278
|
-
return "assets/videos";
|
|
1279
|
-
}
|
|
1280
|
-
if (host.includes("wixstatic.com")) {
|
|
1281
|
-
if (VIDEO_EXTS2.includes(ext)) return "assets/videos";
|
|
1282
|
-
if (pathname.includes("/images/") || IMG_EXTS2.includes(ext)) return "assets/images";
|
|
1283
|
-
if (ext === ".css") return "styles";
|
|
1284
|
-
if (ext === ".js") return "scripts/vendor";
|
|
1285
|
-
if (FONT_EXTS3.includes(ext)) return "assets/fonts";
|
|
1286
|
-
return "assets/misc";
|
|
1287
|
-
}
|
|
1288
|
-
if (host.includes("parastorage.com")) {
|
|
1289
|
-
if (ext === ".js") return "scripts/vendor";
|
|
1290
|
-
if (ext === ".css") return "styles";
|
|
1291
|
-
if (FONT_EXTS3.includes(ext)) return "assets/fonts";
|
|
1292
|
-
if (IMG_EXTS2.includes(ext)) return "assets/images";
|
|
1293
|
-
return "assets/misc";
|
|
1294
|
-
}
|
|
1295
|
-
return null;
|
|
1296
|
-
}
|
|
2828
|
+
"use strict";
|
|
2829
|
+
init_theme();
|
|
2830
|
+
_cooking = null;
|
|
2831
|
+
T = () => (/* @__PURE__ */ new Date()).toISOString().slice(11, 19);
|
|
2832
|
+
LOG_PALETTE = [
|
|
2833
|
+
(s) => chalk4.hex(THEME.primary)(s),
|
|
2834
|
+
(s) => chalk4.hex(THEME.primarySoft)(s),
|
|
2835
|
+
(s) => chalk4.hex(THEME.secondary)(s),
|
|
2836
|
+
(s) => chalk4.hex(THEME.accent)(s),
|
|
2837
|
+
(s) => chalk4.hex(THEME.info)(s),
|
|
2838
|
+
(s) => chalk4.hex(THEME.text)(s)
|
|
2839
|
+
];
|
|
2840
|
+
_li = 0;
|
|
2841
|
+
log = (m) => {
|
|
2842
|
+
_li++;
|
|
2843
|
+
const c = LOG_PALETTE[_li % LOG_PALETTE.length];
|
|
2844
|
+
output(
|
|
2845
|
+
`${chalk4.hex(THEME.muted)(`[${T()}]`)} ${chalk4.hex(THEME.primary)("[log]")} ${c(trunc(m, 120))}`
|
|
2846
|
+
);
|
|
2847
|
+
};
|
|
2848
|
+
INFO_PALETTE = [
|
|
2849
|
+
(s) => chalk4.hex(THEME.info)(s),
|
|
2850
|
+
(s) => chalk4.hex(THEME.secondary)(s),
|
|
2851
|
+
(s) => chalk4.hex(THEME.primarySoft)(s)
|
|
2852
|
+
];
|
|
2853
|
+
_ii = 0;
|
|
2854
|
+
info = (m) => {
|
|
2855
|
+
_ii++;
|
|
2856
|
+
const c = INFO_PALETTE[_ii % INFO_PALETTE.length];
|
|
2857
|
+
output(
|
|
2858
|
+
`${chalk4.hex(THEME.muted)(`[${T()}]`)} ${chalk4.hex(THEME.info).bold("[info]")} ${c(trunc(m, 120))}`
|
|
2859
|
+
);
|
|
2860
|
+
};
|
|
2861
|
+
warn = (m) => {
|
|
2862
|
+
const colors = [
|
|
2863
|
+
(s) => chalk4.hex(THEME.warning)(s),
|
|
2864
|
+
(s) => chalk4.hex(THEME.primary)(s)
|
|
2865
|
+
];
|
|
2866
|
+
const c = colors[Math.floor(Math.random() * colors.length)];
|
|
2867
|
+
const line = `${chalk4.hex(THEME.muted)(`[${T()}]`)} ${chalk4.hex(THEME.warning).bold("[warn]")} ${c(trunc(m, 120))}`;
|
|
2868
|
+
if (_cooking) _cooking.log(line);
|
|
2869
|
+
else console.warn(line);
|
|
2870
|
+
};
|
|
2871
|
+
success = (m) => {
|
|
2872
|
+
const colors = [
|
|
2873
|
+
(s) => chalk4.hex(THEME.success)(s),
|
|
2874
|
+
(s) => chalk4.hex(THEME.info)(s)
|
|
2875
|
+
];
|
|
2876
|
+
const c = colors[Math.floor(Math.random() * colors.length)];
|
|
2877
|
+
output(
|
|
2878
|
+
`${chalk4.hex(THEME.muted)(`[${T()}]`)} ${chalk4.hex(THEME.success)("[ok]")} ${c(trunc(m, 120))}`
|
|
2879
|
+
);
|
|
1297
2880
|
};
|
|
1298
2881
|
}
|
|
1299
2882
|
});
|
|
1300
2883
|
|
|
1301
|
-
// src/
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
2884
|
+
// src/exporter/anti-bot.ts
|
|
2885
|
+
var anti_bot_exports = {};
|
|
2886
|
+
__export(anti_bot_exports, {
|
|
2887
|
+
AntiBotError: () => AntiBotError,
|
|
2888
|
+
detectAntiBotHtml: () => detectAntiBotHtml,
|
|
2889
|
+
detectAntiBotPage: () => detectAntiBotPage,
|
|
2890
|
+
formatAntiBotError: () => formatAntiBotError
|
|
2891
|
+
});
|
|
2892
|
+
function detectAntiBotHtml(html) {
|
|
2893
|
+
if (!html) return null;
|
|
2894
|
+
if (/<title>\s*Just a moment/i.test(html) || /cf-browser-verification|cf-challenge-running|cf_chl_opt|challenge-platform/i.test(html) || /Attention Required!\s*\|\s*Cloudflare/i.test(html) || /Checking if the site connection is secure/i.test(html)) {
|
|
2895
|
+
return "cloudflare";
|
|
1305
2896
|
}
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
function detectByHtml(html) {
|
|
1309
|
-
for (const platform of ALL_PLATFORMS) {
|
|
1310
|
-
if (platform.detectByHtml(html)) return platform;
|
|
2897
|
+
if (/class="g-recaptcha"|h-captcha|hcaptcha\.com|challenges\.cloudflare\.com\/turnstile/i.test(html)) {
|
|
2898
|
+
return "captcha";
|
|
1311
2899
|
}
|
|
1312
2900
|
return null;
|
|
1313
2901
|
}
|
|
1314
|
-
function
|
|
1315
|
-
const byUrl = detectByUrl(url);
|
|
1316
|
-
if (byUrl) return byUrl;
|
|
1317
|
-
if (html) {
|
|
1318
|
-
const byHtml = detectByHtml(html);
|
|
1319
|
-
if (byHtml) return byHtml;
|
|
1320
|
-
}
|
|
1321
|
-
return framer;
|
|
1322
|
-
}
|
|
1323
|
-
async function detectByDom(page) {
|
|
2902
|
+
async function detectAntiBotPage(page) {
|
|
1324
2903
|
try {
|
|
1325
|
-
const
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
if (/
|
|
2904
|
+
const title = await page.title();
|
|
2905
|
+
if (/just a moment/i.test(title) || /attention required/i.test(title)) {
|
|
2906
|
+
return "cloudflare";
|
|
2907
|
+
}
|
|
2908
|
+
const flagged = await page.evaluate(() => {
|
|
2909
|
+
if (document.querySelector('iframe[src*="challenges.cloudflare.com"]')) return "cloudflare";
|
|
2910
|
+
if (document.querySelector(
|
|
2911
|
+
'.g-recaptcha, .h-captcha, iframe[src*="hcaptcha.com"], iframe[src*="recaptcha"]'
|
|
2912
|
+
)) {
|
|
2913
|
+
return "captcha";
|
|
2914
|
+
}
|
|
2915
|
+
const body = document.body ? document.body.innerText || "" : "";
|
|
2916
|
+
const outer = document.documentElement.outerHTML || "";
|
|
2917
|
+
if (body.trim().length < 120 && /cf-chl|cloudflare|cf-browser-verification/i.test(outer)) {
|
|
2918
|
+
return "cloudflare";
|
|
2919
|
+
}
|
|
1339
2920
|
return "";
|
|
1340
2921
|
});
|
|
1341
|
-
|
|
1342
|
-
if (signal === "webflow") return webflow;
|
|
1343
|
-
if (signal === "framer") return framer;
|
|
2922
|
+
return flagged || null;
|
|
1344
2923
|
} catch {
|
|
2924
|
+
return null;
|
|
1345
2925
|
}
|
|
1346
|
-
return null;
|
|
1347
2926
|
}
|
|
1348
|
-
function
|
|
1349
|
-
const
|
|
1350
|
-
|
|
2927
|
+
function formatAntiBotError(err) {
|
|
2928
|
+
const label = err.type === "cloudflare" ? "Cloudflare (browser challenge)" : err.type === "captcha" ? "a captcha challenge" : "a WAF / bot filter";
|
|
2929
|
+
const lines = [
|
|
2930
|
+
"",
|
|
2931
|
+
` ${ui.error("\u2717")} ${ui.error.bold("EXPORT BLOCKED")} \u2014 ${err.platform} is protected by ${label}.`,
|
|
2932
|
+
"",
|
|
2933
|
+
` ${ui.muted("The headless browser could not pass the verification, so nothing was exported.")}`,
|
|
2934
|
+
"",
|
|
2935
|
+
` ${ui.text.bold(" What you can try:")}`,
|
|
2936
|
+
` ${ui.muted(" 1.")} Retry in a few minutes \u2014 the challenge often expires.`,
|
|
2937
|
+
` ${ui.muted(" 2.")} Use an alternative, non-protected canonical URL if one exists.`,
|
|
2938
|
+
` ${ui.muted(" 3.")} Export from a network/IP the site trusts more.`,
|
|
2939
|
+
"",
|
|
2940
|
+
` ${ui.muted("No files were written.")} ${ui.muted("(A stealth-browser mode is on the roadmap.)")}`,
|
|
2941
|
+
""
|
|
2942
|
+
];
|
|
2943
|
+
return lines.join("\n");
|
|
1351
2944
|
}
|
|
1352
|
-
var
|
|
1353
|
-
var
|
|
1354
|
-
"src/
|
|
1355
|
-
"use strict";
|
|
1356
|
-
init_framer();
|
|
1357
|
-
init_webflow();
|
|
1358
|
-
init_wix();
|
|
1359
|
-
ALL_PLATFORMS = [framer, webflow, wix];
|
|
1360
|
-
}
|
|
1361
|
-
});
|
|
1362
|
-
|
|
1363
|
-
// src/platforms/index.ts
|
|
1364
|
-
var platforms_exports = {};
|
|
1365
|
-
__export(platforms_exports, {
|
|
1366
|
-
detectByDom: () => detectByDom,
|
|
1367
|
-
detectByHtml: () => detectByHtml,
|
|
1368
|
-
detectByUrl: () => detectByUrl,
|
|
1369
|
-
detectPlatform: () => detectPlatform,
|
|
1370
|
-
framer: () => framer,
|
|
1371
|
-
getPlatformByName: () => getPlatformByName,
|
|
1372
|
-
webflow: () => webflow,
|
|
1373
|
-
wix: () => wix
|
|
1374
|
-
});
|
|
1375
|
-
var init_platforms = __esm({
|
|
1376
|
-
"src/platforms/index.ts"() {
|
|
2945
|
+
var AntiBotError;
|
|
2946
|
+
var init_anti_bot = __esm({
|
|
2947
|
+
"src/exporter/anti-bot.ts"() {
|
|
1377
2948
|
"use strict";
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
2949
|
+
init_theme();
|
|
2950
|
+
AntiBotError = class extends Error {
|
|
2951
|
+
type;
|
|
2952
|
+
platform;
|
|
2953
|
+
url;
|
|
2954
|
+
constructor(type, platform, url, message) {
|
|
2955
|
+
super(message || `Anti-bot protection detected (${type})`);
|
|
2956
|
+
this.name = "AntiBotError";
|
|
2957
|
+
this.type = type;
|
|
2958
|
+
this.platform = platform;
|
|
2959
|
+
this.url = url;
|
|
2960
|
+
}
|
|
2961
|
+
};
|
|
1382
2962
|
}
|
|
1383
2963
|
});
|
|
1384
2964
|
|
|
@@ -1443,16 +3023,30 @@ async function launchAndCapture(exporter) {
|
|
|
1443
3023
|
log("Platform refined from DOM: " + domDetected.displayName + " (override: " + exporter.platform.name + ")");
|
|
1444
3024
|
exporter.platform = domDetected;
|
|
1445
3025
|
}
|
|
3026
|
+
const antiBot = await detectAntiBotPage(exporter.page);
|
|
3027
|
+
if (antiBot) {
|
|
3028
|
+
throw new AntiBotError(antiBot, exporter.platform.displayName, exporter.siteUrl);
|
|
3029
|
+
}
|
|
3030
|
+
if (exporter.platform.preCapture) {
|
|
3031
|
+
try {
|
|
3032
|
+
exporter.cooking?.update("Preparing " + exporter.platform.displayName + " page...");
|
|
3033
|
+
await exporter.platform.preCapture(exporter.page);
|
|
3034
|
+
log("preCapture hook completed");
|
|
3035
|
+
} catch (e) {
|
|
3036
|
+
log("preCapture hook error (continuing): " + e.message);
|
|
3037
|
+
}
|
|
3038
|
+
}
|
|
1446
3039
|
exporter.cooking?.update("Waiting for " + exporter.platform.displayName + " hydration...");
|
|
1447
3040
|
if (exporter.platform.needsHydrationCheck) {
|
|
1448
|
-
|
|
3041
|
+
const sel = exporter.platform.hydrationSelector || "#main";
|
|
3042
|
+
log("Checking for " + sel + " element hydration...");
|
|
1449
3043
|
log("Hydration timeout: " + exporter.platform.hydrationTimeout + "ms");
|
|
1450
3044
|
const timeout = exporter.platform.hydrationTimeout;
|
|
1451
3045
|
await exporter.page.evaluate(`
|
|
1452
3046
|
new Promise(function(r) {
|
|
1453
3047
|
var start = Date.now();
|
|
1454
3048
|
var tick = function() {
|
|
1455
|
-
var m = document.
|
|
3049
|
+
var m = document.querySelector(${JSON.stringify(sel)}) || document.body;
|
|
1456
3050
|
if (m && m.children.length > 0) setTimeout(r, 2000);
|
|
1457
3051
|
else if (Date.now() - start > ${timeout}) r();
|
|
1458
3052
|
else setTimeout(tick, 200);
|
|
@@ -1466,27 +3060,38 @@ async function launchAndCapture(exporter) {
|
|
|
1466
3060
|
await new Promise((r) => setTimeout(r, exporter.platform.hydrationTimeout));
|
|
1467
3061
|
success(exporter.platform.displayName + " page rendered");
|
|
1468
3062
|
}
|
|
1469
|
-
exporter.
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
3063
|
+
const scrollStrategy = exporter.platform.scrollStrategy || "standard";
|
|
3064
|
+
if (scrollStrategy === "none") {
|
|
3065
|
+
log("Scroll skipped (scrollStrategy: none)");
|
|
3066
|
+
} else {
|
|
3067
|
+
exporter.cooking?.update("Scrolling page...");
|
|
3068
|
+
log("Starting full-page scroll (step: " + CFG.scrollStep + "px, delay: " + CFG.scrollDelay + "ms)");
|
|
3069
|
+
const scrollStep = CFG.scrollStep;
|
|
3070
|
+
const scrollDelay = CFG.scrollDelay;
|
|
3071
|
+
const pageHeight = await exporter.page.evaluate(`
|
|
3072
|
+
Math.max(document.body.scrollHeight, document.documentElement.scrollHeight)
|
|
3073
|
+
`);
|
|
3074
|
+
log("Page height: " + pageHeight + "px (" + Math.ceil(pageHeight / scrollStep) + " scroll steps)");
|
|
3075
|
+
const passes = scrollStrategy === "infinite" ? 3 : 1;
|
|
3076
|
+
for (let pass = 0; pass < passes; pass++) {
|
|
3077
|
+
await exporter.page.evaluate(`
|
|
3078
|
+
new Promise(function(r) {
|
|
3079
|
+
var y = 0;
|
|
3080
|
+
var max = Math.max(document.body.scrollHeight, document.documentElement.scrollHeight);
|
|
3081
|
+
var step = function() {
|
|
3082
|
+
y += ${scrollStep};
|
|
3083
|
+
window.scrollTo({ top: y, behavior: 'instant' });
|
|
3084
|
+
y < max + 500 ? setTimeout(step, ${scrollDelay}) : (window.scrollTo(0, 0), r());
|
|
3085
|
+
};
|
|
3086
|
+
step();
|
|
3087
|
+
})
|
|
3088
|
+
`);
|
|
3089
|
+
if (scrollStrategy === "infinite" && pass < passes - 1) {
|
|
3090
|
+
await new Promise((r) => setTimeout(r, 1e3));
|
|
3091
|
+
}
|
|
3092
|
+
}
|
|
3093
|
+
success("Full-page scroll complete");
|
|
3094
|
+
}
|
|
1490
3095
|
exporter.cooking?.update("Waiting for lazy resources...");
|
|
1491
3096
|
log("Waiting 2s for lazy-loaded resources...");
|
|
1492
3097
|
await new Promise((r) => setTimeout(r, 2e3));
|
|
@@ -1497,6 +3102,19 @@ async function launchAndCapture(exporter) {
|
|
|
1497
3102
|
} catch {
|
|
1498
3103
|
log("Network idle timeout reached (continuing anyway)");
|
|
1499
3104
|
}
|
|
3105
|
+
if (exporter.platform.captureRenderedDom) {
|
|
3106
|
+
try {
|
|
3107
|
+
const rendered = await exporter.page.evaluate(
|
|
3108
|
+
() => "<!DOCTYPE html>\n" + document.documentElement.outerHTML
|
|
3109
|
+
);
|
|
3110
|
+
if (rendered && rendered.length > 200) {
|
|
3111
|
+
exporter.ssrHTML = rendered;
|
|
3112
|
+
log("Captured rendered DOM as HTML source (" + (rendered.length / 1024).toFixed(1) + " KB)");
|
|
3113
|
+
}
|
|
3114
|
+
} catch (e) {
|
|
3115
|
+
log("Rendered DOM capture failed (keeping SSR HTML): " + e.message);
|
|
3116
|
+
}
|
|
3117
|
+
}
|
|
1500
3118
|
const totalCaptured = exporter.assets.buffers.size;
|
|
1501
3119
|
success("Captured " + totalCaptured + " network resources total");
|
|
1502
3120
|
const cssCount = [...exporter.assets.entries.values()].filter((e) => e.localPath.endsWith(".css")).length;
|
|
@@ -1540,6 +3158,7 @@ var init_capture = __esm({
|
|
|
1540
3158
|
init_config();
|
|
1541
3159
|
init_logger();
|
|
1542
3160
|
init_platforms();
|
|
3161
|
+
init_anti_bot();
|
|
1543
3162
|
}
|
|
1544
3163
|
});
|
|
1545
3164
|
|
|
@@ -1953,12 +3572,36 @@ async function buildOutput(exporter) {
|
|
|
1953
3572
|
log(" Pattern removed " + removed + " chars");
|
|
1954
3573
|
}
|
|
1955
3574
|
}
|
|
3575
|
+
if (exporter.platform.stripScripts && exporter.platform.stripScripts.length > 0) {
|
|
3576
|
+
log("Applying " + exporter.platform.stripScripts.length + " script-strip patterns...");
|
|
3577
|
+
for (const pattern of exporter.platform.stripScripts) {
|
|
3578
|
+
const before = html.length;
|
|
3579
|
+
html = html.replace(new RegExp(pattern.source, pattern.flags), "");
|
|
3580
|
+
const removed = before - html.length;
|
|
3581
|
+
if (removed > 0) log(" Script pattern removed " + removed + " chars");
|
|
3582
|
+
}
|
|
3583
|
+
}
|
|
1956
3584
|
success("Platform badges and tracking stripped");
|
|
3585
|
+
if (exporter.platform.postCapture) {
|
|
3586
|
+
try {
|
|
3587
|
+
const before = html.length;
|
|
3588
|
+
html = exporter.platform.postCapture(html, exporter);
|
|
3589
|
+
log("postCapture hook applied (delta " + (html.length - before) + " chars)");
|
|
3590
|
+
} catch (e) {
|
|
3591
|
+
warn("postCapture hook failed: " + e.message);
|
|
3592
|
+
}
|
|
3593
|
+
}
|
|
1957
3594
|
exporter.cooking?.update("Rewriting asset URLs...");
|
|
1958
3595
|
log("Rewriting " + exporter.assets.entries.size + " CDN URLs to local paths...");
|
|
1959
3596
|
const beforeRewrite = html.length;
|
|
1960
3597
|
html = exporter.assets.rewrite(html, "");
|
|
1961
3598
|
log("HTML rewrite delta: " + (html.length - beforeRewrite) + " chars");
|
|
3599
|
+
if (exporter.platform.rewriteUrlPatterns && exporter.platform.rewriteUrlPatterns.length > 0) {
|
|
3600
|
+
for (const { from, to } of exporter.platform.rewriteUrlPatterns) {
|
|
3601
|
+
html = html.replace(new RegExp(from.source, from.flags), to);
|
|
3602
|
+
}
|
|
3603
|
+
log("Applied " + exporter.platform.rewriteUrlPatterns.length + " custom URL rewrites");
|
|
3604
|
+
}
|
|
1962
3605
|
exporter.cooking?.update("Cleaning srcset references...");
|
|
1963
3606
|
html = stripSrcsetCdnUrls(html);
|
|
1964
3607
|
log("Cleaned remaining CDN URLs from srcset attributes");
|
|
@@ -2695,6 +4338,7 @@ var init_exporter = __esm({
|
|
|
2695
4338
|
init_download();
|
|
2696
4339
|
init_logger();
|
|
2697
4340
|
init_capture();
|
|
4341
|
+
init_anti_bot();
|
|
2698
4342
|
init_download2();
|
|
2699
4343
|
init_output();
|
|
2700
4344
|
init_summary();
|
|
@@ -2771,15 +4415,35 @@ var init_exporter = __esm({
|
|
|
2771
4415
|
this.platform = htmlDetected;
|
|
2772
4416
|
log("Platform refined: " + ui.primary(this.platform.displayName) + " (from HTML analysis)");
|
|
2773
4417
|
}
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
4418
|
+
try {
|
|
4419
|
+
await launchAndCapture(this);
|
|
4420
|
+
if (includeSubpages && this.page) {
|
|
4421
|
+
await this.crawlSubpages();
|
|
4422
|
+
}
|
|
4423
|
+
await closeBrowser(this);
|
|
4424
|
+
this.cooking.update("Downloading assets...");
|
|
4425
|
+
await downloadAll(this);
|
|
4426
|
+
this.cooking.update("Building output...");
|
|
4427
|
+
await buildOutput(this);
|
|
4428
|
+
if (this.platform.postProcess) {
|
|
4429
|
+
this.cooking.update("Running " + this.platform.displayName + " post-processing...");
|
|
4430
|
+
try {
|
|
4431
|
+
await this.platform.postProcess(this);
|
|
4432
|
+
log("postProcess hook completed");
|
|
4433
|
+
} catch (e) {
|
|
4434
|
+
log("postProcess hook error (continuing): " + e.message);
|
|
4435
|
+
}
|
|
4436
|
+
}
|
|
4437
|
+
} catch (e) {
|
|
4438
|
+
this.cooking?.stop();
|
|
4439
|
+
setCooking(null);
|
|
4440
|
+
await closeBrowser(this);
|
|
4441
|
+
if (e instanceof AntiBotError) {
|
|
4442
|
+
await fs5.rm(this.outDir, { recursive: true, force: true }).catch(() => {
|
|
4443
|
+
});
|
|
4444
|
+
}
|
|
4445
|
+
throw e;
|
|
2777
4446
|
}
|
|
2778
|
-
await closeBrowser(this);
|
|
2779
|
-
this.cooking.update("Downloading assets...");
|
|
2780
|
-
await downloadAll(this);
|
|
2781
|
-
this.cooking.update("Building output...");
|
|
2782
|
-
await buildOutput(this);
|
|
2783
4447
|
this.cooking.stop();
|
|
2784
4448
|
setCooking(null);
|
|
2785
4449
|
console.log("");
|
|
@@ -2850,6 +4514,70 @@ var init_exporter = __esm({
|
|
|
2850
4514
|
}
|
|
2851
4515
|
});
|
|
2852
4516
|
|
|
4517
|
+
// src/cli/select-grouped.ts
|
|
4518
|
+
import chalk6 from "chalk";
|
|
4519
|
+
async function selectPlatform(siteUrl) {
|
|
4520
|
+
const grouped = platformsByCategory();
|
|
4521
|
+
const detected = detectByUrl(siteUrl);
|
|
4522
|
+
if (detected) {
|
|
4523
|
+
const choice = await select(
|
|
4524
|
+
"Platform detected",
|
|
4525
|
+
[
|
|
4526
|
+
{ label: `Use ${detected.displayName}`, value: USE_DETECTED },
|
|
4527
|
+
{ label: "Browse all platforms", value: BROWSE }
|
|
4528
|
+
],
|
|
4529
|
+
0,
|
|
4530
|
+
{
|
|
4531
|
+
headerLines: [`URL: ${siteUrl}`, `Detected: ${detected.displayName}`],
|
|
4532
|
+
actions: [{ label: "Modify URL", value: MODIFY_URL }],
|
|
4533
|
+
footer: "tab focus button \xB7 enter select \xB7 mouse hover/click"
|
|
4534
|
+
}
|
|
4535
|
+
);
|
|
4536
|
+
if (choice === MODIFY_URL) return { modifyUrl: true };
|
|
4537
|
+
if (choice === USE_DETECTED) return { platform: detected.name };
|
|
4538
|
+
}
|
|
4539
|
+
let categoryDefault = detected ? Math.max(0, CATEGORY_ORDER.indexOf(detected.category)) : 0;
|
|
4540
|
+
while (true) {
|
|
4541
|
+
const categoryOptions = CATEGORY_ORDER.map((cat2) => ({
|
|
4542
|
+
label: `${CATEGORY_LABELS[cat2]} ${chalk6.gray(`(${grouped[cat2].length})`)}`,
|
|
4543
|
+
value: cat2
|
|
4544
|
+
}));
|
|
4545
|
+
const category = await select("Select a category", categoryOptions, categoryDefault, {
|
|
4546
|
+
headerLines: [`URL: ${siteUrl}`],
|
|
4547
|
+
actions: [{ label: "Modify URL", value: MODIFY_URL }],
|
|
4548
|
+
footer: "tab focus button \xB7 enter next \xB7 mouse hover/click"
|
|
4549
|
+
});
|
|
4550
|
+
if (category === MODIFY_URL) return { modifyUrl: true };
|
|
4551
|
+
const cat = category;
|
|
4552
|
+
categoryDefault = CATEGORY_ORDER.indexOf(cat);
|
|
4553
|
+
const handlers = grouped[cat];
|
|
4554
|
+
const platformOptions = handlers.map((handler) => ({
|
|
4555
|
+
label: detected && handler.name === detected.name ? `${handler.displayName}${chalk6.gray(" (detected)")}` : handler.displayName,
|
|
4556
|
+
value: handler.name
|
|
4557
|
+
}));
|
|
4558
|
+
const platformDefault = detected && detected.category === cat ? Math.max(0, handlers.findIndex((handler) => handler.name === detected.name)) : 0;
|
|
4559
|
+
const platform = await select("Select platform", platformOptions, platformDefault, {
|
|
4560
|
+
headerLines: [`Category: ${CATEGORY_LABELS[cat]}`],
|
|
4561
|
+
actions: [{ label: "Back to categories", value: BACK }],
|
|
4562
|
+
footer: "tab focus button \xB7 enter select \xB7 mouse hover/click"
|
|
4563
|
+
});
|
|
4564
|
+
if (platform === BACK) continue;
|
|
4565
|
+
return { platform };
|
|
4566
|
+
}
|
|
4567
|
+
}
|
|
4568
|
+
var MODIFY_URL, BROWSE, USE_DETECTED, BACK;
|
|
4569
|
+
var init_select_grouped = __esm({
|
|
4570
|
+
"src/cli/select-grouped.ts"() {
|
|
4571
|
+
"use strict";
|
|
4572
|
+
init_select();
|
|
4573
|
+
init_platforms();
|
|
4574
|
+
MODIFY_URL = "__modify_url__";
|
|
4575
|
+
BROWSE = "__browse__";
|
|
4576
|
+
USE_DETECTED = "__use_detected__";
|
|
4577
|
+
BACK = "__back__";
|
|
4578
|
+
}
|
|
4579
|
+
});
|
|
4580
|
+
|
|
2853
4581
|
// src/cli/setup.ts
|
|
2854
4582
|
var setup_exports = {};
|
|
2855
4583
|
__export(setup_exports, {
|
|
@@ -2859,7 +4587,7 @@ import readline2 from "readline/promises";
|
|
|
2859
4587
|
import { stdin as stdin3, stdout as stdout3 } from "process";
|
|
2860
4588
|
import path7 from "path";
|
|
2861
4589
|
import { URL as URL4 } from "url";
|
|
2862
|
-
import
|
|
4590
|
+
import chalk7 from "chalk";
|
|
2863
4591
|
function drawHeader(title) {
|
|
2864
4592
|
const w = maxWidth();
|
|
2865
4593
|
if (w < 50) {
|
|
@@ -2876,7 +4604,7 @@ async function runSetup(legacyMode = false) {
|
|
|
2876
4604
|
showBanner();
|
|
2877
4605
|
console.log(` ${ui.text.bold("Framer Export setup")} ${chip("interactive")}`);
|
|
2878
4606
|
console.log(
|
|
2879
|
-
` ${ui.muted("Export
|
|
4607
|
+
` ${ui.muted("Export 25+ website platforms into a clean, self-hosted local mirror.")}
|
|
2880
4608
|
`
|
|
2881
4609
|
);
|
|
2882
4610
|
const rl = legacyMode ? readline2.createInterface({ input: stdin3, output: stdout3 }) : null;
|
|
@@ -2884,7 +4612,7 @@ async function runSetup(legacyMode = false) {
|
|
|
2884
4612
|
if (!legacyMode) {
|
|
2885
4613
|
return promptInput(question, defaultVal || "", { headerLines });
|
|
2886
4614
|
}
|
|
2887
|
-
const suffix = defaultVal ?
|
|
4615
|
+
const suffix = defaultVal ? chalk7.gray(` (${defaultVal})`) : "";
|
|
2888
4616
|
const prompt = ` ${ui.primary("\u25CF")} ${ui.text.bold(question)}${suffix} ${ui.muted(">")} `;
|
|
2889
4617
|
const answer = await rl.question(prompt);
|
|
2890
4618
|
return answer.trim() || defaultVal || "";
|
|
@@ -2911,7 +4639,7 @@ async function runSetup(legacyMode = false) {
|
|
|
2911
4639
|
}
|
|
2912
4640
|
}
|
|
2913
4641
|
if (legacyMode) {
|
|
2914
|
-
console.log(` ${ui.success("\u2713")} ${ui.success("URL:")} ${
|
|
4642
|
+
console.log(` ${ui.success("\u2713")} ${ui.success("URL:")} ${chalk7.underline(siteUrl)}
|
|
2915
4643
|
`);
|
|
2916
4644
|
}
|
|
2917
4645
|
let platformName = null;
|
|
@@ -2919,31 +4647,15 @@ async function runSetup(legacyMode = false) {
|
|
|
2919
4647
|
drawHeader("Step 2 : Platform");
|
|
2920
4648
|
const detected = detectPlatform(siteUrl);
|
|
2921
4649
|
console.log(` ${ui.info("i")} Auto-detected: ${ui.primary(detected.displayName)}`);
|
|
2922
|
-
const platformInput = await ask("Platform
|
|
2923
|
-
|
|
4650
|
+
const platformInput = await ask("Platform name", detected.name);
|
|
4651
|
+
const known = PLATFORM_REGISTRY.some((platform) => platform.name === platformInput);
|
|
4652
|
+
platformName = known ? platformInput : detected.name;
|
|
2924
4653
|
console.log(` ${ui.success("\u2713")} ${ui.success("Platform:")} ${ui.primary(platformName)}
|
|
2925
4654
|
`);
|
|
2926
4655
|
} else {
|
|
2927
4656
|
while (!platformName) {
|
|
2928
|
-
const
|
|
2929
|
-
|
|
2930
|
-
{
|
|
2931
|
-
label: `Framer${detected.name === "framer" ? chalk6.gray(" (detected)") : ""}`,
|
|
2932
|
-
value: "framer"
|
|
2933
|
-
},
|
|
2934
|
-
{
|
|
2935
|
-
label: `Webflow${detected.name === "webflow" ? chalk6.gray(" (detected)") : ""}`,
|
|
2936
|
-
value: "webflow"
|
|
2937
|
-
},
|
|
2938
|
-
{ label: `Wix${detected.name === "wix" ? chalk6.gray(" (detected)") : ""}`, value: "wix" }
|
|
2939
|
-
];
|
|
2940
|
-
const defaultIdx = ["framer", "webflow", "wix"].indexOf(detected.name);
|
|
2941
|
-
const platformChoice = await select("Select platform", platforms, Math.max(0, defaultIdx), {
|
|
2942
|
-
headerLines: [`URL: ${siteUrl}`],
|
|
2943
|
-
actions: [{ label: "Modify URL", value: "modify-url" }],
|
|
2944
|
-
footer: "tab focus button \xB7 mouse hover/click \xB7 enter select"
|
|
2945
|
-
});
|
|
2946
|
-
if (platformChoice === "modify-url") {
|
|
4657
|
+
const selection = await selectPlatform(siteUrl);
|
|
4658
|
+
if ("modifyUrl" in selection) {
|
|
2947
4659
|
siteUrl = "";
|
|
2948
4660
|
urlError = "";
|
|
2949
4661
|
while (!siteUrl) {
|
|
@@ -2962,7 +4674,7 @@ async function runSetup(legacyMode = false) {
|
|
|
2962
4674
|
}
|
|
2963
4675
|
continue;
|
|
2964
4676
|
}
|
|
2965
|
-
platformName =
|
|
4677
|
+
platformName = selection.platform;
|
|
2966
4678
|
}
|
|
2967
4679
|
}
|
|
2968
4680
|
if (!platformName) {
|
|
@@ -2973,26 +4685,62 @@ async function runSetup(legacyMode = false) {
|
|
|
2973
4685
|
if (!legacyMode) {
|
|
2974
4686
|
return promptInput(question, defaultVal || "", { headerLines });
|
|
2975
4687
|
}
|
|
2976
|
-
const suffix = defaultVal ?
|
|
4688
|
+
const suffix = defaultVal ? chalk7.gray(` (${defaultVal})`) : "";
|
|
2977
4689
|
const prompt = ` ${ui.primary("\u25CF")} ${ui.text.bold(question)}${suffix} ${ui.muted(">")} `;
|
|
2978
4690
|
const answer = await rl2.question(prompt);
|
|
2979
4691
|
return answer.trim() || defaultVal || "";
|
|
2980
4692
|
};
|
|
2981
4693
|
const derivedName = deriveOutputName(siteUrl, platformName);
|
|
2982
|
-
|
|
2983
|
-
const outDir = await ask2("Output directory", "./" + derivedName, [
|
|
2984
|
-
"Step 3 : Output Directory",
|
|
2985
|
-
`URL: ${siteUrl}`,
|
|
2986
|
-
`Platform: ${platformName}`
|
|
2987
|
-
]);
|
|
4694
|
+
let outDir = "./" + derivedName;
|
|
2988
4695
|
if (legacyMode) {
|
|
4696
|
+
drawHeader("Step 3 : Output Directory");
|
|
4697
|
+
outDir = await ask2("Output directory", outDir, [
|
|
4698
|
+
"Step 3 : Output Directory",
|
|
4699
|
+
`URL: ${siteUrl}`,
|
|
4700
|
+
`Platform: ${platformName}`
|
|
4701
|
+
]);
|
|
2989
4702
|
console.log(` ${ui.success("\u2713")} ${ui.success("Output:")} ${ui.primary(outDir)}
|
|
2990
4703
|
`);
|
|
4704
|
+
drawHeader("Step 4 : Options");
|
|
4705
|
+
} else {
|
|
4706
|
+
outDir = await ask2("Output directory", outDir, [
|
|
4707
|
+
"Step 3 : Output Directory",
|
|
4708
|
+
`URL: ${siteUrl}`,
|
|
4709
|
+
`Platform: ${platformName}`
|
|
4710
|
+
]);
|
|
2991
4711
|
}
|
|
2992
|
-
|
|
2993
|
-
let
|
|
2994
|
-
let
|
|
2995
|
-
let
|
|
4712
|
+
let prettyPrint = true;
|
|
4713
|
+
let concurrency = 12;
|
|
4714
|
+
let includeSubpages = false;
|
|
4715
|
+
let startExport = false;
|
|
4716
|
+
const summaryRows = () => [
|
|
4717
|
+
["URL", siteUrl],
|
|
4718
|
+
["Platform", platformName],
|
|
4719
|
+
["Output", path7.resolve(outDir)],
|
|
4720
|
+
["Pretty-print", prettyPrint ? "yes" : "no"],
|
|
4721
|
+
["Sub-pages", includeSubpages ? "yes" : "no"],
|
|
4722
|
+
["Concurrency", String(concurrency)]
|
|
4723
|
+
];
|
|
4724
|
+
const summaryLines = () => summaryRows().map(([label, value]) => `${label}: ${value}`);
|
|
4725
|
+
const printSummary2 = () => {
|
|
4726
|
+
const w = maxWidth();
|
|
4727
|
+
const isSmall = w < 50;
|
|
4728
|
+
console.log("");
|
|
4729
|
+
if (!isSmall) {
|
|
4730
|
+
console.log(boxTop(w));
|
|
4731
|
+
console.log(boxLine(w, ui.text.bold(" Summary")));
|
|
4732
|
+
console.log(boxSep(w));
|
|
4733
|
+
} else {
|
|
4734
|
+
console.log(ui.text.bold(" Summary:"));
|
|
4735
|
+
}
|
|
4736
|
+
for (const [label, value] of summaryRows()) {
|
|
4737
|
+
console.log(boxRow(w, label, value));
|
|
4738
|
+
}
|
|
4739
|
+
if (!isSmall) {
|
|
4740
|
+
console.log(boxBot(w));
|
|
4741
|
+
}
|
|
4742
|
+
console.log("");
|
|
4743
|
+
};
|
|
2996
4744
|
if (legacyMode) {
|
|
2997
4745
|
const prettyAnswer = await ask2("Pretty-print JS files? (y/n)", "y");
|
|
2998
4746
|
prettyPrint = prettyAnswer.toLowerCase().startsWith("y");
|
|
@@ -3010,67 +4758,104 @@ async function runSetup(legacyMode = false) {
|
|
|
3010
4758
|
concurrency = parseInt(concurrencyAnswer, 10) || 12;
|
|
3011
4759
|
console.log(` ${ui.success("\u2713")} Concurrency: ${ui.primary(String(concurrency))}
|
|
3012
4760
|
`);
|
|
3013
|
-
|
|
3014
|
-
const prettyVal = await select("Pretty-print JS files?", [
|
|
3015
|
-
{ label: "Yes", value: "yes" },
|
|
3016
|
-
{ label: "No", value: "no" }
|
|
3017
|
-
]);
|
|
3018
|
-
prettyPrint = prettyVal === "yes";
|
|
3019
|
-
const subpagesVal = await select(
|
|
3020
|
-
"Export sub-pages?",
|
|
3021
|
-
[
|
|
3022
|
-
{ label: "No", value: "no" },
|
|
3023
|
-
{ label: "Yes, crawl and export", value: "yes" }
|
|
3024
|
-
],
|
|
3025
|
-
0
|
|
3026
|
-
);
|
|
3027
|
-
includeSubpages = subpagesVal === "yes";
|
|
3028
|
-
const concurrencyVal = await select(
|
|
3029
|
-
"Download concurrency",
|
|
3030
|
-
[
|
|
3031
|
-
{ label: "6 (slow connection)", value: "6" },
|
|
3032
|
-
{ label: "12 (default)", value: "12" },
|
|
3033
|
-
{ label: "20 (fast connection)", value: "20" }
|
|
3034
|
-
],
|
|
3035
|
-
1
|
|
3036
|
-
);
|
|
3037
|
-
concurrency = parseInt(concurrencyVal, 10);
|
|
3038
|
-
}
|
|
3039
|
-
const w = maxWidth();
|
|
3040
|
-
const isSmall = w < 50;
|
|
3041
|
-
console.log("");
|
|
3042
|
-
if (!isSmall) {
|
|
3043
|
-
console.log(boxTop(w));
|
|
3044
|
-
console.log(boxLine(w, ui.text.bold(" Summary")));
|
|
3045
|
-
console.log(boxSep(w));
|
|
3046
|
-
} else {
|
|
3047
|
-
console.log(ui.text.bold(" Summary:"));
|
|
3048
|
-
}
|
|
3049
|
-
for (const [label, value] of [
|
|
3050
|
-
["URL", siteUrl],
|
|
3051
|
-
["Platform", platformName],
|
|
3052
|
-
["Output", path7.resolve(outDir)],
|
|
3053
|
-
["Pretty-print", prettyPrint ? "yes" : "no"],
|
|
3054
|
-
["Sub-pages", includeSubpages ? "yes" : "no"],
|
|
3055
|
-
["Concurrency", String(concurrency)]
|
|
3056
|
-
]) {
|
|
3057
|
-
console.log(boxRow(w, label, value));
|
|
3058
|
-
}
|
|
3059
|
-
if (!isSmall) {
|
|
3060
|
-
console.log(boxBot(w));
|
|
3061
|
-
}
|
|
3062
|
-
console.log("");
|
|
3063
|
-
let startExport;
|
|
3064
|
-
if (legacyMode) {
|
|
4761
|
+
printSummary2();
|
|
3065
4762
|
const confirm = await ask2("Start export? (y/n)", "y");
|
|
3066
4763
|
startExport = confirm.toLowerCase().startsWith("y");
|
|
3067
4764
|
rl2.close();
|
|
3068
4765
|
} else {
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
4766
|
+
let optionStep = 0;
|
|
4767
|
+
while (true) {
|
|
4768
|
+
while (optionStep < 3) {
|
|
4769
|
+
if (optionStep === 0) {
|
|
4770
|
+
const prettyVal = await select(
|
|
4771
|
+
"Pretty-print JS files?",
|
|
4772
|
+
[
|
|
4773
|
+
{ label: "Yes", value: "yes" },
|
|
4774
|
+
{ label: "No", value: "no" }
|
|
4775
|
+
],
|
|
4776
|
+
prettyPrint ? 0 : 1,
|
|
4777
|
+
{
|
|
4778
|
+
headerLines: ["Step 4 : Options"],
|
|
4779
|
+
actions: [{ label: "Previous: output directory", value: "previous" }],
|
|
4780
|
+
footer: "previous go back \xB7 enter next \xB7 mouse hover/click"
|
|
4781
|
+
}
|
|
4782
|
+
);
|
|
4783
|
+
if (prettyVal === "previous") {
|
|
4784
|
+
outDir = await ask2("Output directory", outDir, [
|
|
4785
|
+
"Step 3 : Output Directory",
|
|
4786
|
+
`URL: ${siteUrl}`,
|
|
4787
|
+
`Platform: ${platformName}`
|
|
4788
|
+
]);
|
|
4789
|
+
optionStep = 0;
|
|
4790
|
+
continue;
|
|
4791
|
+
}
|
|
4792
|
+
prettyPrint = prettyVal === "yes";
|
|
4793
|
+
optionStep = 1;
|
|
4794
|
+
continue;
|
|
4795
|
+
}
|
|
4796
|
+
if (optionStep === 1) {
|
|
4797
|
+
const subpagesVal = await select(
|
|
4798
|
+
"Export sub-pages?",
|
|
4799
|
+
[
|
|
4800
|
+
{ label: "No", value: "no" },
|
|
4801
|
+
{ label: "Yes, crawl and export", value: "yes" }
|
|
4802
|
+
],
|
|
4803
|
+
includeSubpages ? 1 : 0,
|
|
4804
|
+
{
|
|
4805
|
+
headerLines: ["Step 4 : Options"],
|
|
4806
|
+
actions: [{ label: "Previous: pretty-print", value: "previous" }],
|
|
4807
|
+
footer: "previous go back \xB7 enter next \xB7 mouse hover/click"
|
|
4808
|
+
}
|
|
4809
|
+
);
|
|
4810
|
+
if (subpagesVal === "previous") {
|
|
4811
|
+
optionStep = 0;
|
|
4812
|
+
continue;
|
|
4813
|
+
}
|
|
4814
|
+
includeSubpages = subpagesVal === "yes";
|
|
4815
|
+
optionStep = 2;
|
|
4816
|
+
continue;
|
|
4817
|
+
}
|
|
4818
|
+
const concurrencyVal = await select(
|
|
4819
|
+
"Download concurrency",
|
|
4820
|
+
[
|
|
4821
|
+
{ label: "6 (slow connection)", value: "6" },
|
|
4822
|
+
{ label: "12 (default)", value: "12" },
|
|
4823
|
+
{ label: "20 (fast connection)", value: "20" }
|
|
4824
|
+
],
|
|
4825
|
+
concurrency === 6 ? 0 : concurrency === 20 ? 2 : 1,
|
|
4826
|
+
{
|
|
4827
|
+
headerLines: ["Step 4 : Options"],
|
|
4828
|
+
actions: [{ label: "Previous: sub-pages", value: "previous" }],
|
|
4829
|
+
footer: "previous go back \xB7 enter next \xB7 mouse hover/click"
|
|
4830
|
+
}
|
|
4831
|
+
);
|
|
4832
|
+
if (concurrencyVal === "previous") {
|
|
4833
|
+
optionStep = 1;
|
|
4834
|
+
continue;
|
|
4835
|
+
}
|
|
4836
|
+
concurrency = parseInt(concurrencyVal, 10);
|
|
4837
|
+
optionStep = 3;
|
|
4838
|
+
}
|
|
4839
|
+
const confirmVal = await select(
|
|
4840
|
+
"Ready to export?",
|
|
4841
|
+
[
|
|
4842
|
+
{ label: "Previous: edit options", value: "previous" },
|
|
4843
|
+
{ label: "Next: start now", value: "next" },
|
|
4844
|
+
{ label: "Cancel", value: "cancel" }
|
|
4845
|
+
],
|
|
4846
|
+
1,
|
|
4847
|
+
{
|
|
4848
|
+
headerLines: ["Summary", ...summaryLines()],
|
|
4849
|
+
footer: "previous edit options \xB7 next start export \xB7 mouse hover/click"
|
|
4850
|
+
}
|
|
4851
|
+
);
|
|
4852
|
+
if (confirmVal === "previous") {
|
|
4853
|
+
optionStep = 2;
|
|
4854
|
+
continue;
|
|
4855
|
+
}
|
|
4856
|
+
startExport = confirmVal === "next";
|
|
4857
|
+
break;
|
|
4858
|
+
}
|
|
3074
4859
|
}
|
|
3075
4860
|
if (!startExport) {
|
|
3076
4861
|
console.log(`
|
|
@@ -3083,7 +4868,16 @@ async function runSetup(legacyMode = false) {
|
|
|
3083
4868
|
CFG2.concurrency = concurrency;
|
|
3084
4869
|
const exporter = new FramerExporter(siteUrl, path7.resolve(outDir), platformName);
|
|
3085
4870
|
exporter.prettyPrint = prettyPrint;
|
|
3086
|
-
|
|
4871
|
+
try {
|
|
4872
|
+
await exporter.run(includeSubpages);
|
|
4873
|
+
} catch (e) {
|
|
4874
|
+
const { AntiBotError: AntiBotError2, formatAntiBotError: formatAntiBotError2 } = await Promise.resolve().then(() => (init_anti_bot(), anti_bot_exports));
|
|
4875
|
+
if (e instanceof AntiBotError2) {
|
|
4876
|
+
console.log(formatAntiBotError2(e));
|
|
4877
|
+
process.exit(1);
|
|
4878
|
+
}
|
|
4879
|
+
throw e;
|
|
4880
|
+
}
|
|
3087
4881
|
}
|
|
3088
4882
|
var init_setup = __esm({
|
|
3089
4883
|
"src/cli/setup.ts"() {
|
|
@@ -3092,6 +4886,7 @@ var init_setup = __esm({
|
|
|
3092
4886
|
init_exporter();
|
|
3093
4887
|
init_platforms();
|
|
3094
4888
|
init_select();
|
|
4889
|
+
init_select_grouped();
|
|
3095
4890
|
init_box();
|
|
3096
4891
|
init_theme();
|
|
3097
4892
|
}
|
|
@@ -3106,6 +4901,7 @@ import { spawnSync } from "child_process";
|
|
|
3106
4901
|
// src/cli/help.ts
|
|
3107
4902
|
init_banner();
|
|
3108
4903
|
init_theme();
|
|
4904
|
+
init_platforms();
|
|
3109
4905
|
function showHelp() {
|
|
3110
4906
|
showBanner();
|
|
3111
4907
|
console.log(`${ui.text.bold(" USAGE")} ${chip("cli")}
|
|
@@ -3120,7 +4916,7 @@ function showHelp() {
|
|
|
3120
4916
|
console.log(ui.text.bold(" OPTIONS\n"));
|
|
3121
4917
|
const opts = [
|
|
3122
4918
|
["--setup", "Launch the interactive setup assistant"],
|
|
3123
|
-
["--platform <p>", "Force platform
|
|
4919
|
+
["--platform <p>", "Force platform by id (e.g. framer, shopify, notion)"],
|
|
3124
4920
|
["--subpages", "Crawl and export sub-pages"],
|
|
3125
4921
|
["--legacy-mode", "Use text input instead of arrow selection"],
|
|
3126
4922
|
["--help, -h", "Show this help message"]
|
|
@@ -3130,13 +4926,10 @@ function showHelp() {
|
|
|
3130
4926
|
}
|
|
3131
4927
|
console.log("");
|
|
3132
4928
|
console.log(ui.text.bold(" SUPPORTED PLATFORMS\n"));
|
|
3133
|
-
const
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
[
|
|
3137
|
-
];
|
|
3138
|
-
for (const [name, desc] of platforms) {
|
|
3139
|
-
console.log(` ${ui.primary(name.padEnd(12))} ${ui.muted(desc)}`);
|
|
4929
|
+
const grouped = platformsByCategory();
|
|
4930
|
+
for (const cat of CATEGORY_ORDER) {
|
|
4931
|
+
const names = grouped[cat].map((handler) => handler.displayName).join(", ");
|
|
4932
|
+
console.log(` ${ui.primary(CATEGORY_LABELS[cat].padEnd(22))} ${ui.muted(names)}`);
|
|
3140
4933
|
}
|
|
3141
4934
|
console.log("");
|
|
3142
4935
|
console.log(ui.text.bold(" EXAMPLES\n"));
|
|
@@ -3285,20 +5078,20 @@ async function main() {
|
|
|
3285
5078
|
process.exit(0);
|
|
3286
5079
|
}
|
|
3287
5080
|
if (args.includes("--about")) {
|
|
3288
|
-
const
|
|
5081
|
+
const chalk8 = (await import("chalk")).default;
|
|
3289
5082
|
showBanner();
|
|
3290
5083
|
console.log(` ${ui.text.bold("Framer Export")} ${ui.muted(`v${package_default.version}`)}`);
|
|
3291
5084
|
console.log(` ${ui.text(package_default.description)}
|
|
3292
5085
|
`);
|
|
3293
5086
|
console.log(` ${ui.text.bold("Author")} ${ui.primary("Dany (danbenba)")}`);
|
|
3294
5087
|
console.log(
|
|
3295
|
-
` ${ui.text.bold("Portfolio")} ${
|
|
5088
|
+
` ${ui.text.bold("Portfolio")} ${chalk8.underline.hex("#FAB283")("https://github.com/danbenba")}`
|
|
3296
5089
|
);
|
|
3297
5090
|
console.log(
|
|
3298
|
-
` ${ui.text.bold("GitHub")} ${
|
|
5091
|
+
` ${ui.text.bold("GitHub")} ${chalk8.underline.hex("#FAB283")(package_default.repository.url.replace("git+", "").replace(".git", ""))}`
|
|
3299
5092
|
);
|
|
3300
5093
|
console.log(
|
|
3301
|
-
` ${ui.text.bold("npm")} ${
|
|
5094
|
+
` ${ui.text.bold("npm")} ${chalk8.underline.hex("#FAB283")(`https://www.npmjs.com/package/${package_default.name}`)}`
|
|
3302
5095
|
);
|
|
3303
5096
|
console.log(` ${ui.text.bold("License")} ${ui.success(package_default.license)}`);
|
|
3304
5097
|
console.log(` ${ui.text.bold("Node")} ${ui.muted(`>=${package_default.engines.node}`)}`);
|
|
@@ -3345,12 +5138,17 @@ async function main() {
|
|
|
3345
5138
|
includeSubpages
|
|
3346
5139
|
);
|
|
3347
5140
|
} catch (e) {
|
|
3348
|
-
const
|
|
5141
|
+
const { AntiBotError: AntiBotError2, formatAntiBotError: formatAntiBotError2 } = await Promise.resolve().then(() => (init_anti_bot(), anti_bot_exports));
|
|
5142
|
+
if (e instanceof AntiBotError2) {
|
|
5143
|
+
console.log(formatAntiBotError2(e));
|
|
5144
|
+
process.exit(1);
|
|
5145
|
+
}
|
|
5146
|
+
const chalk8 = (await import("chalk")).default;
|
|
3349
5147
|
console.log(
|
|
3350
5148
|
`
|
|
3351
5149
|
${ui.error("\u2717")} ${ui.error.bold("FAILED:")} ${ui.text(e.message)}`
|
|
3352
5150
|
);
|
|
3353
|
-
console.log(
|
|
5151
|
+
console.log(chalk8.gray(e.stack || ""));
|
|
3354
5152
|
process.exit(1);
|
|
3355
5153
|
}
|
|
3356
5154
|
}
|