multporn-api-sdk 0.1.3 → 0.1.4
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/dist/browser/index.js +1556 -206
- package/dist/browser/index.js.map +1 -1
- package/dist/node/index.cjs +1557 -206
- package/dist/node/index.cjs.map +1 -1
- package/dist/node/index.js +1556 -206
- package/dist/node/index.js.map +1 -1
- package/dist/rn/index.js +1556 -206
- package/dist/rn/index.js.map +1 -1
- package/dist/types/index.d.ts +22 -32
- package/dist/types/index.js +1576 -204
- package/package.json +1 -1
package/dist/node/index.cjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var cheerio = require('cheerio');
|
|
4
|
+
var cheerio3 = require('cheerio/slim');
|
|
4
5
|
|
|
5
6
|
function _interopNamespace(e) {
|
|
6
7
|
if (e && e.__esModule) return e;
|
|
@@ -21,8 +22,13 @@ function _interopNamespace(e) {
|
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
var cheerio__namespace = /*#__PURE__*/_interopNamespace(cheerio);
|
|
25
|
+
var cheerio3__namespace = /*#__PURE__*/_interopNamespace(cheerio3);
|
|
24
26
|
|
|
25
|
-
|
|
27
|
+
var __defProp = Object.defineProperty;
|
|
28
|
+
var __export = (target, all) => {
|
|
29
|
+
for (var name in all)
|
|
30
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
31
|
+
};
|
|
26
32
|
function createCheerioDom() {
|
|
27
33
|
return {
|
|
28
34
|
parse(html) {
|
|
@@ -278,248 +284,1593 @@ var HttpClient = class {
|
|
|
278
284
|
}
|
|
279
285
|
};
|
|
280
286
|
|
|
281
|
-
// src/
|
|
282
|
-
var
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
287
|
+
// src/api/alphabet.ts
|
|
288
|
+
var alphabet_exports = {};
|
|
289
|
+
__export(alphabet_exports, {
|
|
290
|
+
alphabetItems: () => alphabetItems,
|
|
291
|
+
alphabetLetters: () => alphabetLetters
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
// src/utils.ts
|
|
295
|
+
function toAbsolute(baseURL, href) {
|
|
289
296
|
if (!href) return void 0;
|
|
290
297
|
try {
|
|
291
|
-
|
|
292
|
-
return new URL(href, base).href;
|
|
298
|
+
return new URL(href).toString();
|
|
293
299
|
} catch {
|
|
294
|
-
|
|
300
|
+
if (href.startsWith("//")) {
|
|
301
|
+
const base = new URL(baseURL);
|
|
302
|
+
return `${base.protocol}${href}`;
|
|
303
|
+
}
|
|
304
|
+
return new URL(href.replace(/^\//, ""), baseURL.replace(/\/+$/, "") + "/").toString();
|
|
295
305
|
}
|
|
296
306
|
}
|
|
297
|
-
function
|
|
298
|
-
return
|
|
307
|
+
function uniq(arr) {
|
|
308
|
+
return Array.from(new Set(arr));
|
|
299
309
|
}
|
|
300
|
-
function
|
|
301
|
-
if (!
|
|
302
|
-
|
|
310
|
+
function absUrl(base, href) {
|
|
311
|
+
if (!href) return null;
|
|
312
|
+
try {
|
|
313
|
+
return new URL(href, base).toString();
|
|
314
|
+
} catch {
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
303
317
|
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
318
|
+
function normSpace(s) {
|
|
319
|
+
return (s ?? "").replace(/\s+/g, " ").trim();
|
|
320
|
+
}
|
|
321
|
+
function parseNumberLike(s) {
|
|
322
|
+
if (!s) return void 0;
|
|
323
|
+
const m = String(s).replace(/[^\d.,]/g, "").replace(",", ".");
|
|
324
|
+
const n = parseFloat(m);
|
|
325
|
+
return Number.isFinite(n) ? n : void 0;
|
|
326
|
+
}
|
|
327
|
+
function parseIntLike(s) {
|
|
328
|
+
if (!s) return void 0;
|
|
329
|
+
const n = parseInt(String(s).replace(/[^\d]/g, ""), 10);
|
|
330
|
+
return Number.isFinite(n) ? n : void 0;
|
|
331
|
+
}
|
|
332
|
+
function guessKindFromPath(path) {
|
|
333
|
+
const p = path.split("?")[0].split("#")[0];
|
|
334
|
+
if (p.includes("/hentai_manga/") || p.includes("/munga/")) return "manga";
|
|
335
|
+
if (p.includes("/comics/")) return "comics";
|
|
336
|
+
if (p.includes("/pictures/")) return "pictures";
|
|
337
|
+
if (p.includes("/humor/")) return "humor";
|
|
338
|
+
if (p.includes("/video/")) return "video";
|
|
339
|
+
if (p.includes("/games/")) return "game";
|
|
340
|
+
return "other";
|
|
341
|
+
}
|
|
342
|
+
function guessAlphabetSectionFromPath(path) {
|
|
343
|
+
const slug = String(path || "").trim().replace(/^\//, "").split("/")[0]?.toLowerCase();
|
|
344
|
+
const map = {
|
|
345
|
+
comics: "alphabetical_order_comics",
|
|
346
|
+
manga: "alphabetical_order_manga",
|
|
347
|
+
pictures: "alphabetical_order_pipictures",
|
|
348
|
+
porn_gifs: "alphabetical_order_gif",
|
|
349
|
+
characters: "alphabetical_order_characters",
|
|
350
|
+
category_comic: "alphabetical_order_category_comic",
|
|
351
|
+
authors_comics: "alphabetical_order_authors_comics",
|
|
352
|
+
authors_hentai: "alphabetical_order_authors_hentai",
|
|
353
|
+
pipictures: "alphabetical_order_pipictures"
|
|
354
|
+
};
|
|
355
|
+
return map[slug] || (slug ? `alphabetical_order_${slug}` : "alphabetical_order");
|
|
356
|
+
}
|
|
357
|
+
function pageIndexFromParam(raw) {
|
|
358
|
+
if (!raw) return null;
|
|
359
|
+
const decoded = decodeURIComponent(String(raw));
|
|
360
|
+
const parts = decoded.split(",").map((s) => parseInt(s.trim(), 10)).filter((n) => Number.isFinite(n));
|
|
361
|
+
if (!parts.length) return null;
|
|
362
|
+
return Math.max(...parts);
|
|
363
|
+
}
|
|
364
|
+
function pageIndexFromHref(href) {
|
|
365
|
+
if (!href) return null;
|
|
366
|
+
try {
|
|
367
|
+
const u = new URL(href, "https://example.com");
|
|
368
|
+
return pageIndexFromParam(u.searchParams.get("page"));
|
|
369
|
+
} catch {
|
|
370
|
+
const m = href.match(/[?&]page=([^&#]+)/i);
|
|
371
|
+
return m ? pageIndexFromParam(m[1]) : null;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
function extractTotalPages(html) {
|
|
375
|
+
const $ = cheerio3__namespace.load(html);
|
|
376
|
+
const root = $(
|
|
377
|
+
"ul.pager, nav.pagination, .pagination, .item-list .pager, #content .pager"
|
|
378
|
+
).first();
|
|
379
|
+
if (!root.length) return 1;
|
|
380
|
+
const lastHref = root.find('li.pager-last a[href], a[rel="last"], a[title*="Last"], a[title*="\u041F\u043E\u0441\u043B\u0435\u0434\u043D\u044F\u044F"]').attr("href") || void 0;
|
|
381
|
+
if (lastHref) {
|
|
382
|
+
const idx = pageIndexFromHref(lastHref);
|
|
383
|
+
if (idx != null) return Math.max(1, idx + 1);
|
|
384
|
+
}
|
|
385
|
+
let maxPages = 0;
|
|
386
|
+
root.find("a[href]").each((_, a) => {
|
|
387
|
+
const href = String($(a).attr("href") || "");
|
|
388
|
+
const idx = pageIndexFromHref(href);
|
|
389
|
+
if (idx != null) maxPages = Math.max(maxPages, idx + 1);
|
|
390
|
+
});
|
|
391
|
+
root.find("a, li, span").each((_, el) => {
|
|
392
|
+
const n = Number(($(el).text() || "").trim());
|
|
393
|
+
if (Number.isFinite(n)) maxPages = Math.max(maxPages, n);
|
|
394
|
+
});
|
|
395
|
+
return Math.max(1, maxPages || 1);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// src/parsers/alphabet.ts
|
|
399
|
+
function parseAlphabetLetters(html, baseURL) {
|
|
400
|
+
const $ = cheerio3__namespace.load(html);
|
|
401
|
+
const letters = [];
|
|
402
|
+
$(".views-summary a[href]").each((_, a) => {
|
|
403
|
+
const el = $(a);
|
|
404
|
+
const raw = normSpace(el.text());
|
|
405
|
+
if (!raw) return;
|
|
406
|
+
const count = parseIntLike(raw);
|
|
407
|
+
const label = raw.replace(/\(\s*\d+\s*\)\s*$/, "").trim() || raw;
|
|
408
|
+
const hrefRel = el.attr("href") || "";
|
|
409
|
+
const href = toAbsolute(baseURL, hrefRel) || "";
|
|
410
|
+
const lastSeg = decodeURIComponent((hrefRel.split("/").filter(Boolean).pop() || "").trim());
|
|
411
|
+
if (!label || !href) return;
|
|
412
|
+
letters.push({
|
|
413
|
+
label,
|
|
414
|
+
value: lastSeg || label,
|
|
415
|
+
href,
|
|
416
|
+
count: typeof count === "number" ? count : void 0,
|
|
417
|
+
active: el.hasClass("active") || /(^|\s)active(\s|$)/.test(el.attr("class") || "")
|
|
418
|
+
});
|
|
419
|
+
});
|
|
420
|
+
const uniq3 = /* @__PURE__ */ new Map();
|
|
421
|
+
for (const l of letters) if (!uniq3.has(l.value)) uniq3.set(l.value, l);
|
|
422
|
+
return Array.from(uniq3.values());
|
|
423
|
+
}
|
|
424
|
+
function parseAlphabetInline(html, baseURL) {
|
|
425
|
+
const $ = cheerio3__namespace.load(html);
|
|
426
|
+
const containers = [];
|
|
427
|
+
containers.push(...$(".view-glossary-comics").toArray());
|
|
428
|
+
if (containers.length === 0) containers.push(...$(".view-glossary").toArray());
|
|
429
|
+
if (containers.length === 0) {
|
|
430
|
+
const anyNodes = $(".view").has(".views-summary a").toArray();
|
|
431
|
+
const onlyElements = anyNodes.filter((n) => n?.type === "tag");
|
|
432
|
+
containers.push(...onlyElements);
|
|
433
|
+
}
|
|
434
|
+
if (containers.length === 0) return null;
|
|
435
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
436
|
+
for (const cont of containers) {
|
|
437
|
+
const $cont = $(cont);
|
|
438
|
+
$cont.find(".views-summary a[href]").each((__, a) => {
|
|
439
|
+
const el = $(a);
|
|
440
|
+
const raw = normSpace(el.text());
|
|
441
|
+
if (!raw) return;
|
|
442
|
+
const count = parseIntLike(raw);
|
|
443
|
+
const label = raw.replace(/\(\s*\d+\s*\)\s*$/, "").trim() || raw;
|
|
444
|
+
const hrefRel = el.attr("href") || "";
|
|
445
|
+
const hrefAbs = toAbsolute(baseURL, hrefRel);
|
|
446
|
+
if (!hrefAbs) return;
|
|
447
|
+
let section = "";
|
|
448
|
+
try {
|
|
449
|
+
const p = new URL(hrefAbs).pathname;
|
|
450
|
+
const m = p.match(/^\/([^/]+)/);
|
|
451
|
+
section = m ? m[1] : "";
|
|
452
|
+
} catch {
|
|
453
|
+
}
|
|
454
|
+
const value = decodeURIComponent((hrefRel.split("/").filter(Boolean).pop() || "").trim()) || label;
|
|
455
|
+
const entry = {
|
|
456
|
+
label,
|
|
457
|
+
value,
|
|
458
|
+
href: hrefAbs,
|
|
459
|
+
count: typeof count === "number" ? count : void 0,
|
|
460
|
+
active: el.hasClass("active") || /(^|\s)active(\s|$)/.test(el.attr("class") || "")
|
|
461
|
+
};
|
|
462
|
+
const arr = grouped.get(section) ?? [];
|
|
463
|
+
if (!arr.some((x) => x.value === entry.value)) arr.push(entry);
|
|
464
|
+
grouped.set(section, arr);
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
if (!grouped.size) return null;
|
|
468
|
+
let chosen = null;
|
|
469
|
+
for (const [sec, letters] of grouped.entries()) {
|
|
470
|
+
if (letters.some((l) => l.active)) {
|
|
471
|
+
chosen = { section: sec, letters };
|
|
472
|
+
break;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
if (!chosen) {
|
|
476
|
+
let bestSec = "";
|
|
477
|
+
let bestArr = [];
|
|
478
|
+
for (const [sec, letters] of grouped.entries()) {
|
|
479
|
+
if (letters.length > bestArr.length) {
|
|
480
|
+
bestSec = sec;
|
|
481
|
+
bestArr = letters;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
chosen = { section: bestSec, letters: bestArr };
|
|
485
|
+
}
|
|
486
|
+
if (!chosen) {
|
|
487
|
+
const [sec, letters] = grouped.entries().next().value;
|
|
488
|
+
chosen = { section: sec, letters };
|
|
489
|
+
}
|
|
490
|
+
chosen.letters.sort((a, b) => a.label.localeCompare(b.label, "en"));
|
|
491
|
+
if (!looksLikeAlphabet(chosen.letters)) return null;
|
|
492
|
+
return { section: chosen.section, letters: chosen.letters };
|
|
493
|
+
}
|
|
494
|
+
function looksLikeAlphabet(letters) {
|
|
495
|
+
const short = letters.filter((l) => /^[A-Z#]$/i.test(l.label.trim())).length;
|
|
496
|
+
return letters.length >= 10 && short >= 10;
|
|
497
|
+
}
|
|
498
|
+
function pickFromSrcset(srcset) {
|
|
499
|
+
if (!srcset) return;
|
|
500
|
+
const first = srcset.split(",")[0]?.trim();
|
|
501
|
+
if (!first) return;
|
|
502
|
+
const url = first.split(/\s+/)[0];
|
|
503
|
+
return url || void 0;
|
|
504
|
+
}
|
|
505
|
+
function pickFromStyle(style) {
|
|
506
|
+
if (!style) return;
|
|
507
|
+
const m = style.match(/url\((['"]?)(.*?)\1\)/i);
|
|
508
|
+
return m?.[2] || void 0;
|
|
509
|
+
}
|
|
510
|
+
function pickThumbUrl(scope, baseURL) {
|
|
511
|
+
const candidates = [
|
|
512
|
+
".views-field-field-comg-preview img",
|
|
513
|
+
".views-field-field-cat-preview img",
|
|
514
|
+
".views-field-field-category-preview img",
|
|
515
|
+
".views-field-field-man-preview-1 img",
|
|
516
|
+
".views-field-field-man-preview img",
|
|
517
|
+
".views-field-field-author-preview img",
|
|
518
|
+
".views-field-field-authors-pre img",
|
|
519
|
+
".views-field-field-gif-pre-1 img",
|
|
520
|
+
".views-field-field-gif-preview img",
|
|
521
|
+
".views-field-field-gif-pre img",
|
|
522
|
+
".views-field-field-gif img",
|
|
523
|
+
".views-field-field-preview img",
|
|
524
|
+
".views-field-field-image img",
|
|
525
|
+
".views-field-field-avatar img",
|
|
526
|
+
".field-content a > img",
|
|
527
|
+
".field-content img",
|
|
528
|
+
"a > img",
|
|
529
|
+
"img"
|
|
530
|
+
];
|
|
531
|
+
for (const sel of candidates) {
|
|
532
|
+
const img = scope.find(sel).first();
|
|
533
|
+
if (img.length) {
|
|
534
|
+
const ds = img.attr("data-src") || img.attr("data-original");
|
|
535
|
+
const ss = pickFromSrcset(img.attr("srcset") || "");
|
|
536
|
+
const src = ds || ss || img.attr("src");
|
|
537
|
+
const abs = toAbsolute(baseURL, src || "");
|
|
538
|
+
if (abs) return abs;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
const srcset = pickFromSrcset(scope.find("picture source").first().attr("srcset") || "");
|
|
542
|
+
if (srcset) {
|
|
543
|
+
const abs = toAbsolute(baseURL, srcset);
|
|
544
|
+
if (abs) return abs;
|
|
545
|
+
}
|
|
546
|
+
const bg = pickFromStyle(
|
|
547
|
+
scope.attr("style") || scope.find('[style*="background-image"]').first().attr("style")
|
|
548
|
+
);
|
|
549
|
+
if (bg) {
|
|
550
|
+
const abs = toAbsolute(baseURL, bg);
|
|
551
|
+
if (abs) return abs;
|
|
552
|
+
}
|
|
553
|
+
return void 0;
|
|
554
|
+
}
|
|
555
|
+
function parseAlphabetListing(html, baseURL, page) {
|
|
556
|
+
const $ = cheerio3__namespace.load(html);
|
|
557
|
+
const items = [];
|
|
558
|
+
$("table.views-view-grid td").each((_, td) => {
|
|
559
|
+
const scope = $(td);
|
|
560
|
+
const a = scope.find(
|
|
561
|
+
".views-field-title a[href], .views-field-name a[href], strong a[href], h5 a[href], a[href]"
|
|
562
|
+
).first();
|
|
563
|
+
const href = a.attr("href") || "";
|
|
564
|
+
const url = toAbsolute(baseURL, href);
|
|
565
|
+
const title = (a.text() || scope.find(".views-field-title .field-content").text() || scope.find(".views-field-name .field-content").text() || scope.find("strong").first().text() || scope.find("h5").first().text() || "").trim();
|
|
566
|
+
const thumb = pickThumbUrl(scope, baseURL);
|
|
567
|
+
if (url && title) items.push({ title, url, thumb });
|
|
568
|
+
});
|
|
569
|
+
if (items.length === 0) {
|
|
570
|
+
$(".view .view-content .views-row").each((_, row) => {
|
|
571
|
+
const el = $(row);
|
|
572
|
+
const a = el.find(
|
|
573
|
+
".views-field-title a[href], .views-field-name a[href], strong a[href], h5 a[href], a[href]"
|
|
574
|
+
).first();
|
|
575
|
+
const href = a.attr("href") || "";
|
|
576
|
+
const url = toAbsolute(baseURL, href);
|
|
577
|
+
const title = (a.text() || el.find(".views-field-title .field-content").text() || el.find(".views-field-name .field-content").text() || el.find("strong").first().text() || el.find("h5").first().text() || "").trim();
|
|
578
|
+
const thumb = pickThumbUrl(el, baseURL);
|
|
579
|
+
if (url && title) items.push({ title, url, thumb });
|
|
316
580
|
});
|
|
317
|
-
this.dom = opts.dom;
|
|
318
581
|
}
|
|
319
|
-
|
|
320
|
-
|
|
582
|
+
const totalPages = extractTotalPages(html);
|
|
583
|
+
const hasNext = page + 1 < totalPages;
|
|
584
|
+
return { items, page, hasNext, totalPages };
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// src/api/alphabet.ts
|
|
588
|
+
var ALPHA_CFG = {
|
|
589
|
+
comics: { hubPath: "/comic", alphaPrefix: "/alphabetical_order_comics" },
|
|
590
|
+
category_comic: { hubPath: "/category_comic", alphaPrefix: "/category_comic/alphabetical" },
|
|
591
|
+
characters: { hubPath: "/characters", alphaPrefix: "/alphabetical_order_characters" },
|
|
592
|
+
authors_comics: { hubPath: "/authors_comics", alphaPrefix: "/alphabetical_order_authors" },
|
|
593
|
+
pipictures: { hubPath: "/pipictures", alphaPrefix: "/alphabetical_order_pictures" },
|
|
594
|
+
porn_gifs: { hubPath: "/porn_gifs", alphaPrefix: "/alphabetical_order_gif" },
|
|
595
|
+
manga: { hubPath: "/munga", alphaPrefix: "/alphabetical_order_manga" },
|
|
596
|
+
authors_hentai: { hubPath: "/authors_hentai", alphaPrefix: "/authors_hentai/alphabetical" }
|
|
597
|
+
};
|
|
598
|
+
async function alphabetLetters(http, baseURL, section) {
|
|
599
|
+
const cfg = ALPHA_CFG[section];
|
|
600
|
+
const hub = cfg.hubPath;
|
|
601
|
+
const html = await http.getHtml(hub);
|
|
602
|
+
return parseAlphabetLetters(html, baseURL);
|
|
603
|
+
}
|
|
604
|
+
async function alphabetItems(http, baseURL, section, letter, page = 0) {
|
|
605
|
+
const cfg = ALPHA_CFG[section];
|
|
606
|
+
const letterSeg = encodeURIComponent(letter);
|
|
607
|
+
const html = await http.getHtml(`${cfg.alphaPrefix}/${letterSeg}?page=${page}`);
|
|
608
|
+
return parseAlphabetListing(html, baseURL, page);
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
// src/api/listings.ts
|
|
612
|
+
var listings_exports = {};
|
|
613
|
+
__export(listings_exports, {
|
|
614
|
+
latest: () => latest,
|
|
615
|
+
listByPath: () => listByPath
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
// src/parsers/sorting.ts
|
|
619
|
+
function parseExposedSorting(html, baseURL) {
|
|
620
|
+
const vfIdx = html.search(/<div[^>]+class=["'][^"']*view-filters[^"']*["'][^>]*>/i);
|
|
621
|
+
if (vfIdx < 0) return void 0;
|
|
622
|
+
const formStart = html.indexOf("<form", vfIdx);
|
|
623
|
+
if (formStart < 0) return void 0;
|
|
624
|
+
const formEnd = html.indexOf("</form>", formStart);
|
|
625
|
+
if (formEnd < 0) return void 0;
|
|
626
|
+
const formHtml = html.slice(formStart, formEnd + 7);
|
|
627
|
+
const actionMatch = /<form[^>]*\baction=["']([^"']+)["'][^>]*>/i.exec(formHtml);
|
|
628
|
+
if (!actionMatch) return void 0;
|
|
629
|
+
const abs = toAbsolute(baseURL, actionMatch[1] || "");
|
|
630
|
+
if (!abs) return void 0;
|
|
631
|
+
let actionPath = "/";
|
|
632
|
+
try {
|
|
633
|
+
actionPath = new URL(abs).pathname || "/";
|
|
634
|
+
} catch {
|
|
635
|
+
actionPath = "/";
|
|
321
636
|
}
|
|
322
|
-
|
|
323
|
-
|
|
637
|
+
const labels = /* @__PURE__ */ new Map();
|
|
638
|
+
const reLabel = /<label[^>]*\bfor=["']([^"']+)["'][^>]*>([\s\S]*?)<\/label>/gi;
|
|
639
|
+
let lm;
|
|
640
|
+
while (lm = reLabel.exec(formHtml)) {
|
|
641
|
+
const id = lm[1];
|
|
642
|
+
const raw = (lm[2] || "").replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim();
|
|
643
|
+
if (id && raw) labels.set(id, raw);
|
|
324
644
|
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
const
|
|
330
|
-
|
|
645
|
+
const selects = [];
|
|
646
|
+
const reSelect = /<select\b([^>]*)>([\s\S]*?)<\/select>/gi;
|
|
647
|
+
let sm;
|
|
648
|
+
while (sm = reSelect.exec(formHtml)) {
|
|
649
|
+
const attrs = sm[1] || "";
|
|
650
|
+
const inner = sm[2] || "";
|
|
651
|
+
const name = /(?:^|\s)name=["']([^"']+)["']/i.exec(attrs)?.[1];
|
|
652
|
+
if (!name) continue;
|
|
653
|
+
const idAttr = /(?:^|\s)id=["']([^"']+)["']/i.exec(attrs)?.[1];
|
|
654
|
+
const label = idAttr ? labels.get(idAttr) : void 0;
|
|
655
|
+
const options = [];
|
|
656
|
+
const reOpt = /<option\b([^>]*)>([\s\S]*?)<\/option>/gi;
|
|
657
|
+
let om;
|
|
658
|
+
while (om = reOpt.exec(inner)) {
|
|
659
|
+
const oattrs = om[1] || "";
|
|
660
|
+
const text = (om[2] || "").replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim();
|
|
661
|
+
const val = /(?:^|\s)value=["']([^"']*)["']/i.exec(oattrs)?.[1] ?? text;
|
|
662
|
+
const selected = /\bselected\b/i.test(oattrs) || /\bselected=["']selected["']/i.test(oattrs);
|
|
663
|
+
options.push({ value: val, label: text || val, selected });
|
|
664
|
+
}
|
|
665
|
+
selects.push({ name, label, options });
|
|
331
666
|
}
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
const
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
667
|
+
if (!selects.length) return void 0;
|
|
668
|
+
const appliedParams = {};
|
|
669
|
+
for (const s of selects) {
|
|
670
|
+
const selected = s.options.find((o) => o.selected);
|
|
671
|
+
if (selected && selected.value !== void 0) {
|
|
672
|
+
appliedParams[s.name] = String(selected.value);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
return { actionPath, selects, appliedParams };
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// src/parsers/listing.ts
|
|
679
|
+
function pickFromSrcset2(srcset) {
|
|
680
|
+
if (!srcset) return;
|
|
681
|
+
const first = srcset.split(",")[0]?.trim();
|
|
682
|
+
if (!first) return;
|
|
683
|
+
const url = first.split(/\s+/)[0];
|
|
684
|
+
return url || void 0;
|
|
685
|
+
}
|
|
686
|
+
function pickFromStyle2(style) {
|
|
687
|
+
if (!style) return;
|
|
688
|
+
const m = style.match(/url\((['"]?)(.*?)\1\)/i);
|
|
689
|
+
return m?.[2] || void 0;
|
|
690
|
+
}
|
|
691
|
+
function pickThumb($scope, baseURL) {
|
|
692
|
+
const cands = [
|
|
693
|
+
".views-field-field-preview img",
|
|
694
|
+
".views-field-field-image img",
|
|
695
|
+
".views-field-field-avatar img",
|
|
696
|
+
".views-field-field-cat-preview img",
|
|
697
|
+
".views-field-field-category-preview img",
|
|
698
|
+
".views-field-field-comg-preview img",
|
|
699
|
+
".views-field-field-gif-preview img",
|
|
700
|
+
".views-field-field-gif-pre img",
|
|
701
|
+
".views-field-field-gif img",
|
|
702
|
+
".views-field-field-files img",
|
|
703
|
+
"img"
|
|
704
|
+
];
|
|
705
|
+
for (const sel of cands) {
|
|
706
|
+
const img = $scope.find(sel).first();
|
|
707
|
+
if (img.length) {
|
|
708
|
+
const ds = img.attr("data-src") || img.attr("data-original");
|
|
709
|
+
const ss = pickFromSrcset2(img.attr("srcset") || "");
|
|
710
|
+
const src = ds || ss || img.attr("src");
|
|
711
|
+
const abs = toAbsolute(baseURL, src || "");
|
|
712
|
+
if (abs) return abs;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
const srcset = pickFromSrcset2($scope.find("picture source").first().attr("srcset") || "");
|
|
716
|
+
if (srcset) return toAbsolute(baseURL, srcset);
|
|
717
|
+
const bg = pickFromStyle2(
|
|
718
|
+
$scope.attr("style") || $scope.find('[style*="background-image"]').first().attr("style")
|
|
719
|
+
);
|
|
720
|
+
if (bg) return toAbsolute(baseURL, bg);
|
|
721
|
+
return void 0;
|
|
722
|
+
}
|
|
723
|
+
function findTitleLink($scope) {
|
|
724
|
+
const sel = $scope.find(".views-field-title a[href]").first().length ? ".views-field-title a[href]" : $scope.find(".views-field-name a[href]").first().length ? ".views-field-name a[href]" : $scope.find(".views-field-nothing-1 h3 a[href]").first().length ? ".views-field-nothing-1 h3 a[href]" : $scope.find("strong a[href]").first().length ? "strong a[href]" : $scope.find("h5 a[href]").first().length ? "h5 a[href]" : "a[href]";
|
|
725
|
+
return $scope.find(sel).first();
|
|
726
|
+
}
|
|
727
|
+
function textFrom($el) {
|
|
728
|
+
return ($el.text() || "").replace(/\s+/g, " ").trim();
|
|
729
|
+
}
|
|
730
|
+
function isPager($a) {
|
|
731
|
+
return !!$a.closest("ul.pager, .pager, nav.pagination").length;
|
|
732
|
+
}
|
|
733
|
+
function cardRoot($a) {
|
|
734
|
+
const r = $a.closest("li, .views-row, td, .node, .views-col, .view-content > div, tr").first();
|
|
735
|
+
return r.length ? r : $a.parent();
|
|
736
|
+
}
|
|
737
|
+
function parseHubListing(html, baseURL, page) {
|
|
738
|
+
const $ = cheerio3__namespace.load(html);
|
|
739
|
+
const items = [];
|
|
740
|
+
const seen = /* @__PURE__ */ new Set();
|
|
741
|
+
const views = $(".view");
|
|
742
|
+
const scopes = views.length ? views.toArray() : [$("body").get(0)];
|
|
743
|
+
for (const v of scopes) {
|
|
744
|
+
const $view = $(v);
|
|
745
|
+
const $root = $view.find(".view-content").length ? $view.find(".view-content") : $view;
|
|
746
|
+
$root.find("table.views-table tbody tr").each((_, tr) => {
|
|
747
|
+
const $tr = $(tr);
|
|
748
|
+
let a = $tr.find(".views-field-nothing-1 h3 a[href]").first();
|
|
749
|
+
if (!a.length) {
|
|
750
|
+
const cand = $tr.find("a[href]").first();
|
|
751
|
+
if (cand.length && !isPager(cand)) a = cand;
|
|
752
|
+
}
|
|
753
|
+
const href = a.attr("href") || "";
|
|
754
|
+
const url = toAbsolute(baseURL, href);
|
|
755
|
+
if (!url || seen.has(url)) return;
|
|
756
|
+
const title = textFrom($tr.find(".views-field-nothing-1 h3").first()) || textFrom(a) || a.attr("title") || "";
|
|
757
|
+
if (!title.trim()) return;
|
|
758
|
+
const thumbCell = $tr.find(".views-field-field-files").first();
|
|
759
|
+
const thumb = pickThumb(thumbCell, baseURL) || pickThumb($tr, baseURL);
|
|
760
|
+
items.push({ title: title.trim(), url, thumb });
|
|
761
|
+
seen.add(url);
|
|
762
|
+
});
|
|
763
|
+
$root.find("table.views-view-grid td").each((_, td) => {
|
|
764
|
+
const $td = $(td);
|
|
765
|
+
const a = findTitleLink($td);
|
|
766
|
+
const href = a.attr("href") || "";
|
|
767
|
+
const url = toAbsolute(baseURL, href);
|
|
768
|
+
if (!url || seen.has(url)) return;
|
|
769
|
+
const title = textFrom(a) || textFrom($td.find(".views-field-title .field-content")) || textFrom($td.find(".views-field-name .field-content")) || a.attr("title") || "";
|
|
770
|
+
const thumb = pickThumb($td, baseURL);
|
|
771
|
+
if (title) {
|
|
772
|
+
items.push({ title, url, thumb });
|
|
773
|
+
seen.add(url);
|
|
774
|
+
}
|
|
775
|
+
});
|
|
776
|
+
$root.find(".views-row, .node, li").each((_, row) => {
|
|
777
|
+
const $row = $(row);
|
|
778
|
+
const a = findTitleLink($row);
|
|
779
|
+
const href = a.attr("href") || "";
|
|
780
|
+
const url = toAbsolute(baseURL, href);
|
|
781
|
+
if (!url || seen.has(url)) return;
|
|
782
|
+
const title = textFrom(a) || textFrom($row.find(".views-field-title .field-content")) || textFrom($row.find(".views-field-name .field-content")) || a.attr("title") || "";
|
|
783
|
+
const thumb = pickThumb($row, baseURL);
|
|
784
|
+
if (title) {
|
|
785
|
+
items.push({ title, url, thumb });
|
|
786
|
+
seen.add(url);
|
|
787
|
+
}
|
|
788
|
+
});
|
|
789
|
+
$root.find("a[href] img").each((_, imgEl) => {
|
|
790
|
+
const $a = $(imgEl).closest("a[href]");
|
|
791
|
+
if (isPager($a)) return;
|
|
792
|
+
const url = toAbsolute(baseURL, $a.attr("href") || "");
|
|
793
|
+
if (!url || seen.has(url)) return;
|
|
794
|
+
const $card = cardRoot($a);
|
|
795
|
+
const title = textFrom($card.find(".views-field-title .field-content").first()) || textFrom($card.find(".views-field-name .field-content").first()) || textFrom($card.find(".views-field-nothing-1 h3").first()) || $a.attr("title") || $(imgEl).attr("alt") || "";
|
|
796
|
+
if (!title.trim()) return;
|
|
797
|
+
const thumb = pickThumb($card, baseURL) || pickThumb($a, baseURL) || toAbsolute(baseURL, $(imgEl).attr("src") || "");
|
|
798
|
+
items.push({ title: title.trim(), url, thumb });
|
|
348
799
|
seen.add(url);
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
const totalPages = extractTotalPages(html);
|
|
803
|
+
const hasNext = page + 1 < totalPages;
|
|
804
|
+
const alphabet = parseAlphabetInline(html, baseURL) || void 0;
|
|
805
|
+
const sorting = parseExposedSorting(html, baseURL);
|
|
806
|
+
return {
|
|
807
|
+
items,
|
|
808
|
+
page,
|
|
809
|
+
hasNext,
|
|
810
|
+
totalPages,
|
|
811
|
+
pageSize: void 0,
|
|
812
|
+
alphabet,
|
|
813
|
+
sorting
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
// src/api/listings.ts
|
|
818
|
+
function buildUrl(baseURL, path, params) {
|
|
819
|
+
const url = new URL(path.startsWith("/") ? path : `/${path}`, baseURL);
|
|
820
|
+
if (params) {
|
|
821
|
+
for (const [k, v] of Object.entries(params)) {
|
|
822
|
+
if (v === void 0) continue;
|
|
823
|
+
if (typeof v === "boolean") url.searchParams.set(k, v ? "1" : "0");
|
|
824
|
+
else url.searchParams.set(k, String(v));
|
|
349
825
|
}
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
826
|
+
}
|
|
827
|
+
return url.pathname + (url.search ? url.search : "");
|
|
828
|
+
}
|
|
829
|
+
function normalizeCharactersPath(path) {
|
|
830
|
+
const clean = path.startsWith("/") ? path : `/${path}`;
|
|
831
|
+
if (/^\/alphabetical_order_characters\/?$/i.test(clean)) return "/characters";
|
|
832
|
+
return clean;
|
|
833
|
+
}
|
|
834
|
+
function sniffPagePrefixFromHtml(html) {
|
|
835
|
+
const m = html.match(
|
|
836
|
+
/<ul[^>]*class=["'][^"']*pager[^"']*["'][\s\S]*?\bhref=["'][^"']*?\bpage=([^"&]+)["'][\s\S]*?<\/ul>/i
|
|
837
|
+
) || html.match(/\bpage=([^"&]+)/i);
|
|
838
|
+
if (!m) return null;
|
|
839
|
+
try {
|
|
840
|
+
const decoded = decodeURIComponent(m[1]);
|
|
841
|
+
if (decoded.includes(",")) {
|
|
842
|
+
const parts = decoded.split(",").map((s) => s.trim());
|
|
843
|
+
if (parts.length > 1) return parts.slice(0, -1).join(",") + ",";
|
|
363
844
|
}
|
|
364
|
-
|
|
845
|
+
} catch {
|
|
846
|
+
}
|
|
847
|
+
return null;
|
|
848
|
+
}
|
|
849
|
+
function buildPageParam(htmlWithPager, page) {
|
|
850
|
+
const prefix = sniffPagePrefixFromHtml(htmlWithPager);
|
|
851
|
+
return prefix ? `${prefix}${page}` : String(page);
|
|
852
|
+
}
|
|
853
|
+
function getAppliedFromHtml(html, baseURL) {
|
|
854
|
+
const sorting = parseExposedSorting(html, baseURL);
|
|
855
|
+
return sorting?.appliedParams ?? {};
|
|
856
|
+
}
|
|
857
|
+
async function latest(http, baseURL, page = 0, params) {
|
|
858
|
+
const url0 = buildUrl(baseURL, "/new", { ...params ?? {}, page: 0 });
|
|
859
|
+
const html0 = await http.getHtml(url0);
|
|
860
|
+
if (page === 0) {
|
|
861
|
+
return parseHubListing(html0, baseURL, 0);
|
|
365
862
|
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
863
|
+
const applied = params ? {} : getAppliedFromHtml(html0, baseURL);
|
|
864
|
+
const pageParam = buildPageParam(html0, page);
|
|
865
|
+
const html = await http.getHtml(
|
|
866
|
+
buildUrl(baseURL, "/new", { ...applied, ...params ?? {}, page: pageParam })
|
|
867
|
+
);
|
|
868
|
+
return parseHubListing(html, baseURL, page);
|
|
869
|
+
}
|
|
870
|
+
async function listByPath(http, baseURL, path, page = 0, opts) {
|
|
871
|
+
const clean = path.startsWith("/") ? path : `/${path}`;
|
|
872
|
+
const commonParams = { ...opts ?? {} };
|
|
873
|
+
delete commonParams.letter;
|
|
874
|
+
if (opts?.letter) {
|
|
875
|
+
const entryHtml = await http.getHtml(buildUrl(baseURL, clean, commonParams));
|
|
876
|
+
const alphabet = parseAlphabetInline(entryHtml, baseURL);
|
|
877
|
+
const wanted = String(opts.letter).toUpperCase();
|
|
878
|
+
let letterUrl;
|
|
879
|
+
if (alphabet) {
|
|
880
|
+
const match = alphabet.letters.find((l) => (l.value || "").toUpperCase() === wanted) || alphabet.letters.find((l) => (l.label || "").toUpperCase() === wanted);
|
|
881
|
+
letterUrl = match?.href || `/${alphabet.section}/${encodeURIComponent(wanted)}`;
|
|
882
|
+
} else {
|
|
883
|
+
const section = guessAlphabetSectionFromPath(clean);
|
|
884
|
+
letterUrl = `/${section}/${encodeURIComponent(wanted)}`;
|
|
885
|
+
}
|
|
886
|
+
const url0 = buildUrl(baseURL, letterUrl, { ...commonParams, page: 0 });
|
|
887
|
+
const html02 = await http.getHtml(url0);
|
|
888
|
+
const res0 = parseAlphabetListing(html02, baseURL, 0);
|
|
889
|
+
if (alphabet) res0.alphabet = alphabet;
|
|
890
|
+
if (page === 0) {
|
|
891
|
+
return res0;
|
|
371
892
|
}
|
|
372
|
-
const
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
893
|
+
const applied2 = Object.keys(commonParams).length ? {} : getAppliedFromHtml(html02, baseURL);
|
|
894
|
+
const pageParam2 = buildPageParam(html02, page);
|
|
895
|
+
const html2 = await http.getHtml(
|
|
896
|
+
buildUrl(baseURL, letterUrl, { ...applied2, ...commonParams, page: pageParam2 })
|
|
897
|
+
);
|
|
898
|
+
const res = parseAlphabetListing(html2, baseURL, page);
|
|
899
|
+
if (alphabet) res.alphabet = alphabet;
|
|
900
|
+
return res;
|
|
376
901
|
}
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
902
|
+
const effective = normalizeCharactersPath(clean);
|
|
903
|
+
const html0 = await http.getHtml(buildUrl(baseURL, effective, { ...commonParams, page: 0 }));
|
|
904
|
+
const parsed0 = parseHubListing(html0, baseURL, 0);
|
|
905
|
+
if (effective !== clean && !parsed0.alphabet) {
|
|
906
|
+
try {
|
|
907
|
+
const entryHtml = await http.getHtml(buildUrl(baseURL, clean, commonParams));
|
|
908
|
+
const alpha = parseAlphabetInline(entryHtml, baseURL);
|
|
909
|
+
if (alpha) parsed0.alphabet = alpha;
|
|
910
|
+
} catch {
|
|
911
|
+
}
|
|
380
912
|
}
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
const html = await this.http.getHtml(url);
|
|
384
|
-
const items = this.parseListing(html);
|
|
385
|
-
const hasNext = this.parseHasNext(html);
|
|
386
|
-
return { items, page, hasNext, totalPages: hasNext ? page + 2 : page + 1 };
|
|
913
|
+
if (page === 0) {
|
|
914
|
+
return parsed0;
|
|
387
915
|
}
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
916
|
+
const applied = Object.keys(commonParams).length ? {} : parsed0.sorting?.appliedParams ?? getAppliedFromHtml(html0, baseURL);
|
|
917
|
+
const pageParam = buildPageParam(html0, page);
|
|
918
|
+
const html = await http.getHtml(
|
|
919
|
+
buildUrl(baseURL, effective, { ...applied, ...commonParams, page: pageParam })
|
|
920
|
+
);
|
|
921
|
+
const parsed = parseHubListing(html, baseURL, page);
|
|
922
|
+
if (effective !== clean && !parsed.alphabet) {
|
|
923
|
+
try {
|
|
924
|
+
const entryHtml = await http.getHtml(buildUrl(baseURL, clean, commonParams));
|
|
925
|
+
const alpha = parseAlphabetInline(entryHtml, baseURL);
|
|
926
|
+
if (alpha) parsed.alphabet = alpha;
|
|
927
|
+
} catch {
|
|
928
|
+
}
|
|
397
929
|
}
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
930
|
+
return parsed;
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
// src/api/posts.ts
|
|
934
|
+
var posts_exports = {};
|
|
935
|
+
__export(posts_exports, {
|
|
936
|
+
getPost: () => getPost
|
|
937
|
+
});
|
|
938
|
+
function parsePost(html, baseURL, url) {
|
|
939
|
+
const $ = cheerio3__namespace.load(html);
|
|
940
|
+
const title = $("h1").first().text().trim() || $(".page-title, .node-title, .title").first().text().trim() || url;
|
|
941
|
+
const container = $("#content, .content, article, .node, .field-items").first();
|
|
942
|
+
const imgs = [];
|
|
943
|
+
container.find("img").each((_, img) => {
|
|
944
|
+
const el = $(img);
|
|
945
|
+
const src = el.attr("data-src") || el.attr("src");
|
|
946
|
+
const abs = toAbsolute(baseURL, src || "");
|
|
947
|
+
if (abs) imgs.push(abs);
|
|
948
|
+
});
|
|
949
|
+
const images = uniq(imgs);
|
|
950
|
+
const tags = [];
|
|
951
|
+
$('.tags a, a[rel="tag"], .field-name-field-tags a').each((_, a) => {
|
|
952
|
+
const t = $(a).text().trim();
|
|
953
|
+
if (t) tags.push(t);
|
|
954
|
+
});
|
|
955
|
+
const author = $(".author a").first().text().trim() || $(".submitted .username").first().text().trim() || null;
|
|
956
|
+
return { title, url, images, tags: uniq(tags), author };
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
// src/api/posts.ts
|
|
960
|
+
async function getPost(http, baseURL, urlOrSlug) {
|
|
961
|
+
const html = await http.getHtml(urlOrSlug);
|
|
962
|
+
const absolute = (() => {
|
|
963
|
+
try {
|
|
964
|
+
return new URL(urlOrSlug).toString();
|
|
965
|
+
} catch {
|
|
966
|
+
return new URL(urlOrSlug.replace(/^\//, ""), baseURL + "/").toString();
|
|
412
967
|
}
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
968
|
+
})();
|
|
969
|
+
return parsePost(html, baseURL, absolute);
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
// src/api/search.ts
|
|
973
|
+
var search_exports = {};
|
|
974
|
+
__export(search_exports, {
|
|
975
|
+
search: () => search
|
|
976
|
+
});
|
|
977
|
+
function parseSearch(html, baseURL, page) {
|
|
978
|
+
const $ = cheerio3__namespace.load(html);
|
|
979
|
+
const items = [];
|
|
980
|
+
const view = $(".view").filter((_, el) => {
|
|
981
|
+
const cls = $(el).attr("class") || "";
|
|
982
|
+
return /view-id-search/.test(cls) && /view-display-id-page/.test(cls) && $(el).find(".view-content").length > 0;
|
|
983
|
+
}).first();
|
|
984
|
+
const rows = view.find(".view-content .views-row");
|
|
985
|
+
rows.each((_, el) => {
|
|
986
|
+
const row = $(el);
|
|
987
|
+
const a = row.find(".views-field-title a[href]").first().length ? row.find(".views-field-title a[href]").first() : row.find("a[href]").first();
|
|
988
|
+
const href = a.attr("href") || "";
|
|
989
|
+
const url = toAbsolute(baseURL, href);
|
|
990
|
+
const title = (a.text() || row.find(".views-field-title .field-content").text() || "").trim();
|
|
991
|
+
const pickImg = row.find(".views-field-field-preview img").first().length ? row.find(".views-field-field-preview img").first() : row.find(".views-field-field-image img").first().length ? row.find(".views-field-field-image img").first() : row.find(".views-field-field-fl-prev img").first().length ? row.find(".views-field-field-fl-prev img").first() : row.find(".views-field-field-album-preview img").first().length ? row.find(".views-field-field-album-preview img").first() : row.find(".views-field-field-vd-preciew img").first().length ? row.find(".views-field-field-vd-preciew img").first() : row.find(".views-field-field-gif-pre img").first().length ? row.find(".views-field-field-gif-pre img").first() : row.find(".views-field-field-avatar img").first().length ? row.find(".views-field-field-avatar img").first() : row.find("img").first();
|
|
992
|
+
let thumb = pickImg.attr("data-src") || pickImg.attr("src") || void 0;
|
|
993
|
+
thumb = toAbsolute(baseURL, thumb);
|
|
994
|
+
if (url && title) items.push({ title, url, thumb });
|
|
995
|
+
});
|
|
996
|
+
const totalPages = extractTotalPages(html);
|
|
997
|
+
const hasNext = page + 1 < totalPages;
|
|
998
|
+
if (items.length === 0) {
|
|
999
|
+
return { items: [], page, hasNext: false, totalPages: 1 };
|
|
1000
|
+
}
|
|
1001
|
+
return { items, page, hasNext, totalPages };
|
|
1002
|
+
}
|
|
1003
|
+
function extractViewsContextForSearch(html) {
|
|
1004
|
+
const $ = cheerio3__namespace.load(html);
|
|
1005
|
+
const view = $(".view").filter((_, el) => {
|
|
1006
|
+
const cls2 = $(el).attr("class") || "";
|
|
1007
|
+
return /view-id-search/.test(cls2) && /view-display-id-page/.test(cls2);
|
|
1008
|
+
}).first();
|
|
1009
|
+
const cls = view.attr("class") || "";
|
|
1010
|
+
const view_name = cls.match(/view-id-([^\s]+)/)?.[1] || "search";
|
|
1011
|
+
const view_display_id = cls.match(/view-display-id-([^\s]+)/)?.[1] || "page";
|
|
1012
|
+
const domFromClass = cls.match(/view-dom-id-([^\s]+)/)?.[1] || "";
|
|
1013
|
+
const domFromId = (view.attr("id") || "").replace(/^view-dom-id-/, "");
|
|
1014
|
+
const view_dom_id = domFromClass || domFromId || "view-dom-id-1";
|
|
1015
|
+
const ajax_html_id = $('[id^="views-exposed-form"]').attr("id") || view.attr("id") || void 0;
|
|
1016
|
+
return { view_name, view_display_id, view_dom_id, ajax_html_id };
|
|
1017
|
+
}
|
|
1018
|
+
function extractHtmlFromDrupalAjax(payload, wantDomId) {
|
|
1019
|
+
if (Array.isArray(payload)) {
|
|
1020
|
+
for (const cmd of payload) {
|
|
1021
|
+
const c = cmd;
|
|
1022
|
+
const sel = String(c?.selector || "");
|
|
1023
|
+
const data = c?.data;
|
|
1024
|
+
if ((c?.command === "insert" || c?.command === "replaceWith") && typeof data === "string" && data.trim() && (!wantDomId || sel.includes(wantDomId) || sel.includes("view-content")))
|
|
1025
|
+
return data;
|
|
419
1026
|
}
|
|
1027
|
+
const concat = payload.map((x) => typeof x?.data === "string" ? x.data : "").join("");
|
|
1028
|
+
return concat.trim() ? concat : null;
|
|
1029
|
+
}
|
|
1030
|
+
const disp = payload?.display;
|
|
1031
|
+
return typeof disp === "string" ? disp : null;
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
// src/api/search.ts
|
|
1035
|
+
async function search(http, baseURL, query, page = 0) {
|
|
1036
|
+
const q = encodeURIComponent(query);
|
|
1037
|
+
const html1 = await http.getHtml(`/search?search_api_views_fulltext=${q}&page=${page}`);
|
|
1038
|
+
const parsed1 = parseSearch(html1, baseURL, page);
|
|
1039
|
+
if (parsed1.items.length > 0) return parsed1;
|
|
1040
|
+
const firstHtml = await http.getHtml(`/search?search_api_views_fulltext=${q}`);
|
|
1041
|
+
const ctx = extractViewsContextForSearch(firstHtml);
|
|
1042
|
+
const ajaxUrl = `/views/ajax?search_api_views_fulltext=${q}&undefined=Search&_wrapper_format=drupal_ajax`;
|
|
1043
|
+
const payload = {
|
|
1044
|
+
view_name: ctx.view_name || "search",
|
|
1045
|
+
view_display_id: ctx.view_display_id || "page",
|
|
1046
|
+
view_args: "",
|
|
1047
|
+
view_path: "search",
|
|
1048
|
+
view_base_path: "search",
|
|
1049
|
+
view_dom_id: ctx.view_dom_id,
|
|
1050
|
+
pager_element: "0",
|
|
1051
|
+
page: String(page)
|
|
1052
|
+
};
|
|
1053
|
+
if (ctx.ajax_html_id) payload["ajax_html_ids[]"] = [ctx.ajax_html_id];
|
|
1054
|
+
const json = await http.postForm(ajaxUrl, payload);
|
|
1055
|
+
const html2 = extractHtmlFromDrupalAjax(json, ctx.view_dom_id) || "";
|
|
1056
|
+
return parseSearch(html2, baseURL, page);
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
// src/api/updates.ts
|
|
1060
|
+
var updates_exports = {};
|
|
1061
|
+
__export(updates_exports, {
|
|
1062
|
+
updates: () => updates,
|
|
1063
|
+
updatesShortcuts: () => updatesShortcuts
|
|
1064
|
+
});
|
|
1065
|
+
function parseViewDisplay(viewName, html, baseURL) {
|
|
1066
|
+
const $ = cheerio3__namespace.load(html);
|
|
1067
|
+
const items = [];
|
|
1068
|
+
const pickThumb2 = (scope) => {
|
|
1069
|
+
const img = scope.find(".views-field-field-preview img").first().length ? scope.find(".views-field-field-preview img").first() : scope.find(".views-field-field-image img").first().length ? scope.find(".views-field-field-image img").first() : scope.find(".views-field-field-avatar img").first().length ? scope.find(".views-field-field-avatar img").first() : scope.find("img").first();
|
|
1070
|
+
let thumb = img.attr("data-src") || img.attr("src") || void 0;
|
|
1071
|
+
return toAbsolute(baseURL, thumb);
|
|
1072
|
+
};
|
|
1073
|
+
const liList = $("ul.jcarousel li");
|
|
1074
|
+
liList.each((_, li) => {
|
|
1075
|
+
const scope = $(li);
|
|
1076
|
+
const a = scope.find(".views-field-title a[href]").first().length ? scope.find(".views-field-title a[href]").first() : scope.find(".views-field-name a[href]").first();
|
|
1077
|
+
const href = a.attr("href") || "";
|
|
1078
|
+
const url = toAbsolute(baseURL, href);
|
|
1079
|
+
const title = (a.text() || scope.find(".views-field-title .field-content").text() || scope.find(".views-field-name .field-content").text() || "").trim();
|
|
1080
|
+
const thumb = pickThumb2(scope);
|
|
1081
|
+
if (url && title) items.push({ title, url, thumb });
|
|
1082
|
+
});
|
|
1083
|
+
return items;
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
// src/api/updates.ts
|
|
1087
|
+
var VIEW_PRESETS = {
|
|
1088
|
+
random_top_comics: { view_display_id: "block", jcarousel_dom_id: 7 },
|
|
1089
|
+
top_random_characters: { view_display_id: "block", jcarousel_dom_id: 8 }
|
|
1090
|
+
};
|
|
1091
|
+
function uniq2(arr) {
|
|
1092
|
+
return Array.from(new Set(arr));
|
|
1093
|
+
}
|
|
1094
|
+
async function updates(http, baseURL, params = {}) {
|
|
1095
|
+
const viewName = params.view_name ?? "new_mini";
|
|
1096
|
+
const preset = VIEW_PRESETS[viewName] || {};
|
|
1097
|
+
const first = Number(params.first ?? 0);
|
|
1098
|
+
const last = Number(params.last ?? 8);
|
|
1099
|
+
const basePayload = {
|
|
1100
|
+
js: "1",
|
|
1101
|
+
first: String(first),
|
|
1102
|
+
last: String(last),
|
|
1103
|
+
view_args: params.view_args ?? "",
|
|
1104
|
+
view_path: params.view_path ?? "node",
|
|
1105
|
+
view_base_path: params.view_base_path ?? "",
|
|
1106
|
+
view_name: viewName
|
|
1107
|
+
};
|
|
1108
|
+
const displayCandidates = uniq2([
|
|
1109
|
+
params.view_display_id ?? preset.view_display_id ?? "block_1",
|
|
1110
|
+
"block",
|
|
1111
|
+
"block_2",
|
|
1112
|
+
"block_3",
|
|
1113
|
+
"page",
|
|
1114
|
+
"default"
|
|
1115
|
+
]).filter(Boolean);
|
|
1116
|
+
const origDom = Number(params.jcarousel_dom_id ?? preset.jcarousel_dom_id ?? 1);
|
|
1117
|
+
const domCandidates = uniq2([
|
|
1118
|
+
origDom,
|
|
1119
|
+
origDom - 1,
|
|
1120
|
+
origDom + 1,
|
|
1121
|
+
...Array.from({ length: 10 }, (_, i) => i + 1)
|
|
1122
|
+
]).filter((n) => Number.isFinite(n) && n > 0);
|
|
1123
|
+
let displayHtml = "";
|
|
1124
|
+
for (const view_display_id of displayCandidates) {
|
|
1125
|
+
for (const jcarousel_dom_id of domCandidates) {
|
|
1126
|
+
try {
|
|
1127
|
+
const qs = new URLSearchParams({
|
|
1128
|
+
...basePayload,
|
|
1129
|
+
view_display_id: String(view_display_id),
|
|
1130
|
+
jcarousel_dom_id: String(jcarousel_dom_id)
|
|
1131
|
+
}).toString();
|
|
1132
|
+
const data = await http.getJson(`/jcarousel/ajax/views?${qs}`);
|
|
1133
|
+
const html = data?.display?.trim() ?? "";
|
|
1134
|
+
if (html) {
|
|
1135
|
+
displayHtml = html;
|
|
1136
|
+
displayCandidates.length = 0;
|
|
1137
|
+
break;
|
|
1138
|
+
}
|
|
1139
|
+
} catch {
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
if (!displayHtml) {
|
|
1144
|
+
return { items: [], first, last, html: "", viewName };
|
|
1145
|
+
}
|
|
1146
|
+
const items = parseViewDisplay(viewName, displayHtml, baseURL);
|
|
1147
|
+
return { items, first, last, html: displayHtml, viewName };
|
|
1148
|
+
}
|
|
1149
|
+
var updatesShortcuts = {
|
|
1150
|
+
newMini: (http, baseURL, p) => updates(http, baseURL, { ...p ?? {}, view_name: "new_mini" }),
|
|
1151
|
+
userUploadFront: (http, baseURL, p) => updates(http, baseURL, { ...p ?? {}, view_name: "user_upload_front" }),
|
|
1152
|
+
updatedManga: (http, baseURL, p) => updates(http, baseURL, { ...p ?? {}, view_name: "updated_manga" }),
|
|
1153
|
+
updatedMangaPromoted: (http, baseURL, p) => updates(http, baseURL, { ...p ?? {}, view_name: "updated_manga_promoted" }),
|
|
1154
|
+
updatedGames: (http, baseURL, p) => updates(http, baseURL, { ...p ?? {}, view_name: "updated_games" }),
|
|
1155
|
+
randomTopComics: (http, baseURL, p) => updates(http, baseURL, { ...p ?? {}, view_name: "random_top_comics" }),
|
|
1156
|
+
topRandomCharacters: (http, baseURL, p) => updates(http, baseURL, { ...p ?? {}, view_name: "top_random_characters" })
|
|
1157
|
+
};
|
|
1158
|
+
|
|
1159
|
+
// src/parsers/viewer.ts
|
|
1160
|
+
var viewer_exports = {};
|
|
1161
|
+
__export(viewer_exports, {
|
|
1162
|
+
buildJuiceboxXmlUrl: () => buildJuiceboxXmlUrl,
|
|
1163
|
+
default: () => viewer_default,
|
|
1164
|
+
parseInlineJuiceboxFromHtml: () => parseInlineJuiceboxFromHtml,
|
|
1165
|
+
parseJuiceboxXml: () => parseJuiceboxXml,
|
|
1166
|
+
parseViewer: () => parseViewer,
|
|
1167
|
+
parseViewerMeta: () => parseViewerMeta
|
|
1168
|
+
});
|
|
1169
|
+
function decodeHtml(s) {
|
|
1170
|
+
return s.replace(/ /g, " ").replace(/&/g, "&").replace(/"/g, '"').replace(/'/g, "'").replace(/</g, "<").replace(/>/g, ">");
|
|
1171
|
+
}
|
|
1172
|
+
function textBetween(html, openRe, close) {
|
|
1173
|
+
const m = openRe.exec(html);
|
|
1174
|
+
if (!m) return null;
|
|
1175
|
+
const i = m.index + m[0].length;
|
|
1176
|
+
const j = html.indexOf(close, i);
|
|
1177
|
+
if (j < 0) return null;
|
|
1178
|
+
return html.slice(i, j);
|
|
1179
|
+
}
|
|
1180
|
+
function stripTags(s) {
|
|
1181
|
+
return s.replace(/<[^>]*>/g, "");
|
|
1182
|
+
}
|
|
1183
|
+
function collectLinks(blockHtml, baseURL) {
|
|
1184
|
+
const out = [];
|
|
1185
|
+
const re = /<a\s+[^>]*href="([^"]+)"[^>]*>([\s\S]*?)<\/a>/gi;
|
|
1186
|
+
let m;
|
|
1187
|
+
while (m = re.exec(blockHtml)) {
|
|
1188
|
+
const url = absUrl(baseURL, m[1]) ?? "";
|
|
1189
|
+
const title = normSpace(decodeHtml(stripTags(m[2])));
|
|
1190
|
+
if (url && title) out.push({ url, title });
|
|
1191
|
+
}
|
|
1192
|
+
return out;
|
|
1193
|
+
}
|
|
1194
|
+
function findFieldNodeAndSys(html) {
|
|
1195
|
+
const re = /id="field--node--(\d+)--([a-z0-9_-]+)--full"/i;
|
|
1196
|
+
const m = re.exec(html);
|
|
1197
|
+
if (m) {
|
|
1198
|
+
const nodeId = parseInt(m[1], 10);
|
|
1199
|
+
const fieldSys = m[2].replace(/-/g, "_");
|
|
1200
|
+
if (Number.isFinite(nodeId)) return { nodeId, fieldSys };
|
|
1201
|
+
}
|
|
1202
|
+
const re2 = /\/juicebox\/xml\/field\/node\/(\d+)\/([a-z0-9_]+)\/full/gi;
|
|
1203
|
+
const m2 = re2.exec(html);
|
|
1204
|
+
if (m2) {
|
|
1205
|
+
const nodeId = parseInt(m2[1], 10);
|
|
1206
|
+
const fieldSys = m2[2];
|
|
1207
|
+
if (Number.isFinite(nodeId)) return { nodeId, fieldSys };
|
|
1208
|
+
}
|
|
1209
|
+
return null;
|
|
1210
|
+
}
|
|
1211
|
+
function buildJuiceboxXmlUrl(baseURL, nodeId, fieldSys) {
|
|
1212
|
+
const root = baseURL.replace(/\/+$/, "");
|
|
1213
|
+
return `${root}/juicebox/xml/field/node/${nodeId}/${fieldSys}/full`;
|
|
1214
|
+
}
|
|
1215
|
+
function parseJuiceboxXml(xml) {
|
|
1216
|
+
const images = [];
|
|
1217
|
+
const reImg = /<image\b([^>]+)>/gi;
|
|
1218
|
+
let m;
|
|
1219
|
+
while (m = reImg.exec(xml)) {
|
|
1220
|
+
const attrs = m[1];
|
|
1221
|
+
const map = {};
|
|
1222
|
+
let am;
|
|
1223
|
+
const reAttr = /([a-zA-Z0-9_-]+)\s*=\s*"([^"]*)"/g;
|
|
1224
|
+
while (am = reAttr.exec(attrs)) map[am[1]] = am[2];
|
|
1225
|
+
const original = map["linkURL"] || map["imageURL"] || map["largeImageURL"] || map["smallImageURL"] || "";
|
|
1226
|
+
if (!original) continue;
|
|
1227
|
+
images.push({
|
|
1228
|
+
original,
|
|
1229
|
+
large: map["largeImageURL"],
|
|
1230
|
+
medium: map["imageURL"] || map["smallImageURL"],
|
|
1231
|
+
small: map["smallImageURL"],
|
|
1232
|
+
thumb: map["thumbURL"]
|
|
1233
|
+
});
|
|
1234
|
+
}
|
|
1235
|
+
return images;
|
|
1236
|
+
}
|
|
1237
|
+
function parseInlineJuiceboxFromHtml(html) {
|
|
1238
|
+
const out = [];
|
|
1239
|
+
const reMed = /<img[^>]+src="([^"]+\/styles\/juicebox_(?:medium|small|large)\/[^"]+)"[^>]*>/gi;
|
|
1240
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1241
|
+
let m;
|
|
1242
|
+
while (m = reMed.exec(html)) {
|
|
1243
|
+
const url = m[1];
|
|
1244
|
+
if (seen.has(url)) continue;
|
|
1245
|
+
seen.add(url);
|
|
1246
|
+
out.push({ original: url, medium: url });
|
|
1247
|
+
}
|
|
1248
|
+
return out;
|
|
1249
|
+
}
|
|
1250
|
+
function parseBreadcrumbs(html, baseURL) {
|
|
1251
|
+
const block = textBetween(html, /<div[^>]+class="breadcrumb"[^>]*>/i, "</div>");
|
|
1252
|
+
return block ? collectLinks(block, baseURL) : [];
|
|
1253
|
+
}
|
|
1254
|
+
function findLabeledFieldBlock(html, labelStartsWith) {
|
|
1255
|
+
const esc = labelStartsWith.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1256
|
+
const re = new RegExp(
|
|
1257
|
+
`<(?:h3|div)[^>]*class="[^"]*field-label[^"]*"[^>]*>\\s*${esc}\\s*:?\\s*<\\/[^>]+>`,
|
|
1258
|
+
"i"
|
|
1259
|
+
);
|
|
1260
|
+
const idx = html.search(re);
|
|
1261
|
+
if (idx < 0) return null;
|
|
1262
|
+
const open = html.lastIndexOf("<div", idx);
|
|
1263
|
+
let depth = 0;
|
|
1264
|
+
for (let i = open; i < html.length; i++) {
|
|
1265
|
+
if (html.startsWith("<div", i)) depth++;
|
|
1266
|
+
if (html.startsWith("</div>", i)) {
|
|
1267
|
+
depth--;
|
|
1268
|
+
if (depth === 0) {
|
|
1269
|
+
return html.slice(open, i + 6);
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
return null;
|
|
1274
|
+
}
|
|
1275
|
+
function parseLabeledField(html, labelStartsWith) {
|
|
1276
|
+
return findLabeledFieldBlock(html, labelStartsWith);
|
|
1277
|
+
}
|
|
1278
|
+
function parsePlainValuesFromLabeledField(html, label) {
|
|
1279
|
+
const block = findLabeledFieldBlock(html, label);
|
|
1280
|
+
if (!block) return [];
|
|
1281
|
+
const text = normSpace(decodeHtml(stripTags(block))).replace(new RegExp(`^${label}\\s*:?\\s*`, "i"), "").trim();
|
|
1282
|
+
if (!text) return [];
|
|
1283
|
+
return text.split(/\s*[,;]\s*/).filter(Boolean);
|
|
1284
|
+
}
|
|
1285
|
+
function parseTitle(html) {
|
|
1286
|
+
const h1 = textBetween(html, /<h1[^>]*id="page-title"[^>]*>/i, "</h1>");
|
|
1287
|
+
if (h1) return normSpace(decodeHtml(stripTags(h1)));
|
|
1288
|
+
const m = /<meta[^>]+property="og:title"[^>]+content="([^"]+)"/i.exec(html);
|
|
1289
|
+
if (m) return normSpace(decodeHtml(m[1]));
|
|
1290
|
+
return "";
|
|
1291
|
+
}
|
|
1292
|
+
function parseVotesAndRating(html) {
|
|
1293
|
+
const block = /<div[^>]*class="[^"]*\bfivestar-summary\b[^"]*"[^>]*>([\s\S]*?)<\/div>/i.exec(html)?.[1] || /<div[^>]*class="[^"]*\bfivestar-average[^"]*"[^>]*>([\s\S]*?)<\/div>/i.exec(html)?.[1] || "";
|
|
1294
|
+
if (block) {
|
|
1295
|
+
const mAvg = /class="average-rating"[^>]*>[\s\S]*?Average[^:]*:\s*(?:<span[^>]*>)?([\d.,]+)(?:<\/span>)?/i.exec(
|
|
1296
|
+
block
|
|
1297
|
+
);
|
|
1298
|
+
const mVotes = /class="total-votes"[^>]*>\s*\(\s*(?:<span[^>]*>)?(\d+)(?:<\/span>)?\s+votes?\s*\)/i.exec(
|
|
1299
|
+
block
|
|
1300
|
+
);
|
|
1301
|
+
const rating = mAvg ? parseNumberLike(mAvg[1]) : void 0;
|
|
1302
|
+
const votes = mVotes ? parseIntLike(mVotes[1]) : void 0;
|
|
1303
|
+
if (rating != null || votes != null) return { rating, votes };
|
|
1304
|
+
}
|
|
1305
|
+
const m1 = /Average\s*:\s*(?:<span[^>]*>)?([\d.,]+)(?:<\/span>)?[\s\S]*?\(\s*(?:<span[^>]*>)?(\d+)(?:<\/span>)?\s*votes?\s*\)/i.exec(
|
|
1306
|
+
html
|
|
1307
|
+
);
|
|
1308
|
+
if (m1) {
|
|
1309
|
+
return {
|
|
1310
|
+
rating: parseNumberLike(m1[1]),
|
|
1311
|
+
votes: parseIntLike(m1[2])
|
|
1312
|
+
};
|
|
1313
|
+
}
|
|
1314
|
+
const m2 = /Average[^<>\d]*([\d.,]+)/i.exec(html);
|
|
1315
|
+
const m3 = /\((\d+)\s+votes?\)/i.exec(html);
|
|
1316
|
+
if (m2 || m3) {
|
|
1317
|
+
const rating = m2 ? parseNumberLike(m2[1]) : void 0;
|
|
1318
|
+
const votes = m3 ? parseIntLike(m3[1]) : void 0;
|
|
1319
|
+
return { rating, votes };
|
|
1320
|
+
}
|
|
1321
|
+
return {};
|
|
1322
|
+
}
|
|
1323
|
+
function parseViews(html) {
|
|
1324
|
+
const patterns = [
|
|
1325
|
+
/<li[^>]*class="statistics_counter[^"]*"[^>]*>\s*<span>\s*([\d\s.,]+)\s+views\s*<\/span>/i,
|
|
1326
|
+
/>\s*([\d\s.,]+)\s+views\s*<\/(?:span|div|li|h\d)>/i,
|
|
1327
|
+
/>\s*Views\s*[:\-]?\s*<\/?(?:span|strong|b)?[^>]*>\s*([\d\s.,]+)\s*</i,
|
|
1328
|
+
/\bViews\s*[:\-]?\s*([\d\s.,]+)/i
|
|
1329
|
+
];
|
|
1330
|
+
for (const rx of patterns) {
|
|
1331
|
+
const m = rx.exec(html);
|
|
1332
|
+
if (m && m[1]) return parseIntLike(m[1]);
|
|
1333
|
+
}
|
|
1334
|
+
return void 0;
|
|
1335
|
+
}
|
|
1336
|
+
function parseLinksFromLabeledField(html, baseURL, label) {
|
|
1337
|
+
const block = parseLabeledField(html, label);
|
|
1338
|
+
return block ? collectLinks(block, baseURL) : [];
|
|
1339
|
+
}
|
|
1340
|
+
function parseRelated(html, baseURL) {
|
|
1341
|
+
const pieces = [];
|
|
1342
|
+
const re = /<div[^>]+class="view[^"]*down[^"]*random[^"]*hentai[^"]*manga[^"]*"[^>]*>([\s\S]*?)<\/div>\s*<\/div>/gi;
|
|
1343
|
+
let m;
|
|
1344
|
+
while (m = re.exec(html)) pieces.push(m[1]);
|
|
1345
|
+
return uniq(pieces.flatMap((p) => collectLinks(p, baseURL)));
|
|
1346
|
+
}
|
|
1347
|
+
function parseUploader(html, baseURL) {
|
|
1348
|
+
const m = /<footer[^>]*class="[^"]*\bsubmitted\b[^"]*"[^>]*>([\s\S]*?)<\/footer>/i.exec(html);
|
|
1349
|
+
if (!m) return void 0;
|
|
1350
|
+
const block = m[1];
|
|
1351
|
+
const a = /Uploaded\s+by\s*<a[^>]*href="([^"]+)"[^>]*>([\s\S]*?)<\/a>\s*on\s*([^<]+)/i.exec(
|
|
1352
|
+
block
|
|
1353
|
+
);
|
|
1354
|
+
const img = /<div[^>]*class="user-picture"[^>]*>[\s\S]*?<img[^>]*src="([^"]+)"/i.exec(block);
|
|
1355
|
+
const out = {};
|
|
1356
|
+
if (a) {
|
|
1357
|
+
out.url = absUrl(baseURL, a[1]) ?? "";
|
|
1358
|
+
out.name = normSpace(decodeHtml(stripTags(a[2])));
|
|
1359
|
+
out.dateText = normSpace(decodeHtml(a[3]));
|
|
1360
|
+
}
|
|
1361
|
+
if (img) out.avatar = absUrl(baseURL, img[1]) ?? "";
|
|
1362
|
+
return out;
|
|
1363
|
+
}
|
|
1364
|
+
function parseViewerMeta(html, baseURL, absoluteUrl) {
|
|
1365
|
+
const title = parseTitle(html);
|
|
1366
|
+
const breadcrumbs = parseBreadcrumbs(html, baseURL);
|
|
1367
|
+
let authors = parseLinksFromLabeledField(html, baseURL, "Author");
|
|
1368
|
+
if (authors.length === 0) {
|
|
1369
|
+
const altLinks = parseLinksFromLabeledField(html, baseURL, "Artist's name");
|
|
1370
|
+
if (altLinks.length) authors = altLinks;
|
|
1371
|
+
if (authors.length === 0) {
|
|
1372
|
+
const altPlain = parsePlainValuesFromLabeledField(html, "Artist's name");
|
|
1373
|
+
if (altPlain.length) {
|
|
1374
|
+
authors = altPlain.map((t) => ({ title: t, url: "" }));
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
let sections = parseLinksFromLabeledField(html, baseURL, "Section");
|
|
1379
|
+
if (sections.length === 0) {
|
|
1380
|
+
const sPlain = parsePlainValuesFromLabeledField(html, "Section");
|
|
1381
|
+
if (sPlain.length) sections = sPlain.map((t) => ({ title: t, url: "" }));
|
|
1382
|
+
}
|
|
1383
|
+
let tags = parseLinksFromLabeledField(html, baseURL, "Tags");
|
|
1384
|
+
if (tags.length === 0) {
|
|
1385
|
+
const block = /<div[^>]*class="[^"]*field-name-field-category[^"]*"[^>]*>([\s\S]*?)<\/div>/i.exec(html);
|
|
1386
|
+
if (block) tags = collectLinks(block[1], baseURL);
|
|
1387
|
+
}
|
|
1388
|
+
const characters = parseLinksFromLabeledField(html, baseURL, "Characters");
|
|
1389
|
+
const userTags = parseLinksFromLabeledField(html, baseURL, "User tags");
|
|
1390
|
+
const { rating, votes } = parseVotesAndRating(html);
|
|
1391
|
+
const views = parseViews(html);
|
|
1392
|
+
const kind = guessKindFromPath(absoluteUrl);
|
|
1393
|
+
const field = findFieldNodeAndSys(html);
|
|
1394
|
+
const uploader = parseUploader(html, baseURL);
|
|
1395
|
+
const meta = {
|
|
1396
|
+
nodeId: field?.nodeId ?? null,
|
|
1397
|
+
fieldSys: field?.fieldSys ?? null,
|
|
1398
|
+
title,
|
|
1399
|
+
kind,
|
|
1400
|
+
breadcrumbs,
|
|
1401
|
+
authors,
|
|
1402
|
+
sections,
|
|
1403
|
+
tags,
|
|
1404
|
+
characters,
|
|
1405
|
+
userTags,
|
|
1406
|
+
rating,
|
|
1407
|
+
votes,
|
|
1408
|
+
views,
|
|
1409
|
+
related: parseRelated(html, baseURL)
|
|
1410
|
+
};
|
|
1411
|
+
if (uploader) {
|
|
1412
|
+
meta.uploader = uploader;
|
|
1413
|
+
}
|
|
1414
|
+
return { meta, nodeId: meta.nodeId, fieldSys: meta.fieldSys };
|
|
1415
|
+
}
|
|
1416
|
+
function findCanonicalUrl(html, baseURL) {
|
|
1417
|
+
const m1 = /<link[^>]+rel=["']canonical["'][^>]+href=["']([^"']+)["']/i.exec(html);
|
|
1418
|
+
if (m1?.[1]) return absUrl(baseURL, m1[1]) ?? baseURL.replace(/\/+$/, "");
|
|
1419
|
+
const m2 = /<meta[^>]+property=["']og:url["'][^>]+content=["']([^"']+)["']/i.exec(html);
|
|
1420
|
+
if (m2?.[1]) return absUrl(baseURL, m2[1]) ?? baseURL.replace(/\/+$/, "");
|
|
1421
|
+
return baseURL.replace(/\/+$/, "");
|
|
1422
|
+
}
|
|
1423
|
+
var EXCLUDE_URL_PATTERNS = [
|
|
1424
|
+
/\/styles\/avatars\//i,
|
|
1425
|
+
/\/user_avatars\//i,
|
|
1426
|
+
/\/default_images\//i,
|
|
1427
|
+
/\/styles\/taxonomy_(?:comics|manga)\//i,
|
|
1428
|
+
/\/com_preview\//i,
|
|
1429
|
+
/\/promo\//i
|
|
1430
|
+
];
|
|
1431
|
+
function isExcludedUrl(u) {
|
|
1432
|
+
const s = String(u);
|
|
1433
|
+
return EXCLUDE_URL_PATTERNS.some((re) => re.test(s));
|
|
1434
|
+
}
|
|
1435
|
+
function stripExcludedBlocks(html) {
|
|
1436
|
+
const patterns = [
|
|
1437
|
+
/<div[^>]+id="comments"[^>]*>[\s\S]*?<\/div>/gi,
|
|
1438
|
+
/<section[^>]+id="comments"[^>]*>[\s\S]*?<\/section>/gi,
|
|
1439
|
+
/<div[^>]+class="[^"]*\bcomments?\b[^"]*"[^>]*>[\s\S]*?<\/div>/gi,
|
|
1440
|
+
/<div[^>]+class="[^"]*\bcomment\b[^"]*"[^>]*>[\s\S]*?<\/div>/gi,
|
|
1441
|
+
/<ul[^>]+class="[^"]*\bcomment\b[^"]*"[^>]*>[\s\S]*?<\/ul>/gi,
|
|
1442
|
+
/<div[^>]+class="[^"]*\brelated\b[^"]*"[^>]*>[\s\S]*?<\/div>/gi,
|
|
1443
|
+
/<div[^>]+class="[^"]*\bmore\b[^"]*"[^>]*>[\s\S]*?<\/div>/gi,
|
|
1444
|
+
/<div[^>]+class="[^"]*\bmore-comics\b[^"]*"[^>]*>[\s\S]*?<\/div>/gi,
|
|
1445
|
+
/<div[^>]+class="[^"]*\bpane-related\b[^"]*"[^>]*>[\s\S]*?<\/div>/gi,
|
|
1446
|
+
/<aside[\s\S]*?<\/aside>/gi,
|
|
1447
|
+
/<div[^>]+class="[^"]*\bsidebar\b[^"]*"[^>]*>[\s\S]*?<\/div>/gi,
|
|
1448
|
+
/<div[^>]+class="[^"]*\bnode-footer\b[^"]*"[^>]*>[\s\S]*?<\/div>/gi,
|
|
1449
|
+
/<div[^>]+class="[^"]*\bpane-views\b[^"]*"[^>]*>[\s\S]*?<\/div>/gi,
|
|
1450
|
+
/<div[^>]+class="[^"]*\bpane-taxonomy\b[^"]*"[^>]*>[\s\S]*?<\/div>/gi
|
|
1451
|
+
];
|
|
1452
|
+
let cleaned = html;
|
|
1453
|
+
for (const re of patterns) cleaned = cleaned.replace(re, "");
|
|
1454
|
+
return cleaned;
|
|
1455
|
+
}
|
|
1456
|
+
function collectImgSrcs(html) {
|
|
1457
|
+
const out = [];
|
|
1458
|
+
const re = /<img[^>]+src=["']([^"']+)["'][^>]*>/gi;
|
|
1459
|
+
let m;
|
|
1460
|
+
while (m = re.exec(html)) out.push(m[1]);
|
|
1461
|
+
return out;
|
|
1462
|
+
}
|
|
1463
|
+
function collectContentImages(html) {
|
|
1464
|
+
const cleaned = stripExcludedBlocks(html);
|
|
1465
|
+
return collectImgSrcs(cleaned).filter((u) => !!u && !isExcludedUrl(u));
|
|
1466
|
+
}
|
|
1467
|
+
function parseViewer(html, baseURL) {
|
|
1468
|
+
const absoluteUrl = findCanonicalUrl(html, baseURL);
|
|
1469
|
+
const { meta } = parseViewerMeta(html, baseURL, absoluteUrl);
|
|
1470
|
+
const inline = parseInlineJuiceboxFromHtml(html).map((im) => {
|
|
1471
|
+
const original = toAbsolute(baseURL, im.original || "") || "";
|
|
420
1472
|
return {
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
1473
|
+
original,
|
|
1474
|
+
large: im.large ? toAbsolute(baseURL, im.large) || void 0 : void 0,
|
|
1475
|
+
medium: im.medium ? toAbsolute(baseURL, im.medium) || void 0 : void 0,
|
|
1476
|
+
small: im.small ? toAbsolute(baseURL, im.small) || void 0 : void 0,
|
|
1477
|
+
thumb: im.thumb ? toAbsolute(baseURL, im.thumb) || void 0 : void 0
|
|
426
1478
|
};
|
|
1479
|
+
}).filter((im) => !!im.original);
|
|
1480
|
+
let fallback = [];
|
|
1481
|
+
if (inline.length === 0) {
|
|
1482
|
+
const srcs = collectContentImages(html).map((u) => toAbsolute(baseURL, u) || "").filter((u) => !!u);
|
|
1483
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1484
|
+
fallback = srcs.filter((u) => {
|
|
1485
|
+
if (!u || seen.has(u)) return false;
|
|
1486
|
+
seen.add(u);
|
|
1487
|
+
return true;
|
|
1488
|
+
}).map((u) => ({ original: u }));
|
|
427
1489
|
}
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
1490
|
+
const all = [...inline, ...fallback];
|
|
1491
|
+
const byOriginal = /* @__PURE__ */ new Map();
|
|
1492
|
+
for (const im of all) {
|
|
1493
|
+
const key = im.original || "";
|
|
1494
|
+
if (key && !byOriginal.has(key)) byOriginal.set(key, im);
|
|
1495
|
+
}
|
|
1496
|
+
return { meta, images: Array.from(byOriginal.values()) };
|
|
1497
|
+
}
|
|
1498
|
+
var viewer_default = parseViewer;
|
|
1499
|
+
|
|
1500
|
+
// src/parsers/video.ts
|
|
1501
|
+
var ABS_URL = /^https?:\/\//i;
|
|
1502
|
+
function absolutize(u, base) {
|
|
1503
|
+
if (!u) return "";
|
|
1504
|
+
if (ABS_URL.test(u)) return u;
|
|
1505
|
+
if (u.startsWith("//")) return "https:" + u;
|
|
1506
|
+
if (u.startsWith("/")) return base.replace(/\/+$/, "") + u;
|
|
1507
|
+
return u;
|
|
1508
|
+
}
|
|
1509
|
+
function parseVideoFromHtml(html, baseURL) {
|
|
1510
|
+
if (!html || !/node-video|class=["']video-js|<video/i.test(html)) return null;
|
|
1511
|
+
const posterMatch = html.match(/<video[^>]*\sposter=["']([^"']+)["']/i) || html.match(/class=["']video-js[^"']*["'][^>]*\sposter=["']([^"']+)["']/i);
|
|
1512
|
+
const poster = posterMatch ? absolutize(posterMatch[1], baseURL) : void 0;
|
|
1513
|
+
const sources = [];
|
|
1514
|
+
const mainVideo = html.match(/<video[^>]*\ssrc=["']([^"']+)["'][^>]*>/i);
|
|
1515
|
+
if (mainVideo) sources.push({ url: absolutize(mainVideo[1], baseURL) });
|
|
1516
|
+
const re = /<source[^>]*\ssrc=["']([^"']+)["'][^>]*?(?:\stype=["']([^"']+)["'])?[^>]*>/gi;
|
|
1517
|
+
for (let m; m = re.exec(html); ) {
|
|
1518
|
+
sources.push({ url: absolutize(m[1], baseURL), type: m[2] });
|
|
1519
|
+
}
|
|
1520
|
+
if (!sources.length) return null;
|
|
1521
|
+
return { poster, sources };
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
// src/api/viewer.ts
|
|
1525
|
+
var ABS_URL2 = /^https?:\/\//i;
|
|
1526
|
+
function absolutize2(u, base) {
|
|
1527
|
+
if (!u) return "";
|
|
1528
|
+
if (ABS_URL2.test(u)) return u;
|
|
1529
|
+
if (u.startsWith("//")) return "https:" + u;
|
|
1530
|
+
if (u.startsWith("/")) return base.replace(/\/+$/, "") + u;
|
|
1531
|
+
return base.replace(/\/+$/, "") + "/" + u.replace(/^\/+/, "");
|
|
1532
|
+
}
|
|
1533
|
+
function buildViewerUrl(base, urlOrSlug) {
|
|
1534
|
+
if (ABS_URL2.test(urlOrSlug)) return urlOrSlug;
|
|
1535
|
+
if (urlOrSlug.startsWith("/")) return base.replace(/\/+$/, "") + urlOrSlug;
|
|
1536
|
+
return base.replace(/\/+$/, "") + "/" + urlOrSlug.replace(/^\/+/, "");
|
|
1537
|
+
}
|
|
1538
|
+
async function fetchHtml(http, url) {
|
|
1539
|
+
const anyHttp = http;
|
|
1540
|
+
if (typeof anyHttp.getText === "function") return anyHttp.getText(url);
|
|
1541
|
+
if (typeof anyHttp.get === "function") return anyHttp.get(url);
|
|
1542
|
+
if (typeof anyHttp.text === "function") return anyHttp.text(url);
|
|
1543
|
+
if (typeof anyHttp.fetch === "function") {
|
|
1544
|
+
const r = await anyHttp.fetch(url);
|
|
1545
|
+
if (typeof r === "string") return r;
|
|
1546
|
+
if (r && typeof r.text === "function") return await r.text();
|
|
1547
|
+
}
|
|
1548
|
+
if (typeof fetch === "function") {
|
|
1549
|
+
const r = await fetch(url);
|
|
1550
|
+
return await r.text();
|
|
1551
|
+
}
|
|
1552
|
+
throw new Error("HttpClient: no getText/get/text/fetch available");
|
|
1553
|
+
}
|
|
1554
|
+
function proxyImgMaybe(u, opts) {
|
|
1555
|
+
if (!u) return "";
|
|
1556
|
+
return opts?.proxyImage ? opts.proxyImage(u) : u;
|
|
1557
|
+
}
|
|
1558
|
+
function proxyVidMaybe(u, opts) {
|
|
1559
|
+
if (!u) return "";
|
|
1560
|
+
const proxyVideo = opts?.proxyVideo;
|
|
1561
|
+
return proxyVideo ? proxyVideo(u) : proxyImgMaybe(u, opts);
|
|
1562
|
+
}
|
|
1563
|
+
function tryParseRichMeta(html, baseURL, absoluteUrl) {
|
|
1564
|
+
const anyParsers = viewer_exports;
|
|
1565
|
+
if (typeof anyParsers.parseViewerMeta === "function") {
|
|
1566
|
+
try {
|
|
1567
|
+
const { meta } = anyParsers.parseViewerMeta(html, baseURL, absoluteUrl);
|
|
1568
|
+
if (!meta.title) {
|
|
1569
|
+
const t = extractMeta(html, baseURL).title;
|
|
1570
|
+
if (t) meta.title = t;
|
|
1571
|
+
}
|
|
1572
|
+
return meta;
|
|
1573
|
+
} catch {
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
return extractMeta(html, baseURL);
|
|
1577
|
+
}
|
|
1578
|
+
function parseImagesWithBackoff(html, baseURL) {
|
|
1579
|
+
const anyParsers = viewer_exports;
|
|
1580
|
+
const candidate = anyParsers.parseViewer ?? anyParsers.default;
|
|
1581
|
+
if (typeof candidate === "function") {
|
|
1582
|
+
return candidate(html, baseURL);
|
|
1583
|
+
}
|
|
1584
|
+
const images = [];
|
|
1585
|
+
const re = /<img[^>]+src=["']([^"']+)["'][^>]*>/gi;
|
|
1586
|
+
let m;
|
|
1587
|
+
while (m = re.exec(html)) {
|
|
1588
|
+
const original = absolutize2(m[1], baseURL);
|
|
1589
|
+
if (!original) continue;
|
|
1590
|
+
images.push({ original, medium: original });
|
|
1591
|
+
}
|
|
1592
|
+
const meta = extractMeta(html, baseURL);
|
|
1593
|
+
return { meta, images };
|
|
1594
|
+
}
|
|
1595
|
+
function hasViewerClues(html) {
|
|
1596
|
+
const hasJuicebox = /juicebox/i.test(html) || /\/juicebox\/xml\/field\/node\/\d+\/[a-z0-9_]+\/full/i.test(html);
|
|
1597
|
+
const hasFieldNode = /id=["']field--node--\d+--[a-z0-9_-]+--full["']/i.test(html);
|
|
1598
|
+
const hasJbStyles = /styles\/juicebox_(?:large|medium|small)\//i.test(html);
|
|
1599
|
+
const hasNodeBody = /<body[^>]+class=["'][^"']*\bnode\b[^"']*["']/i.test(html);
|
|
1600
|
+
const hasGalleryField = /field-name-(?:field_)?(?:pictures|images|image|post_pictures)\b/i.test(
|
|
1601
|
+
html
|
|
1602
|
+
);
|
|
1603
|
+
return hasJuicebox || hasFieldNode || hasJbStyles || hasNodeBody || hasGalleryField;
|
|
1604
|
+
}
|
|
1605
|
+
var ViewerAPI = {
|
|
1606
|
+
async resolveViewer(http, baseURL, urlOrSlug, opts = {}) {
|
|
1607
|
+
const url = buildViewerUrl(baseURL, urlOrSlug);
|
|
1608
|
+
const html = await fetchHtml(http, url);
|
|
1609
|
+
const parsedVideo = parseVideoFromHtml(html, baseURL);
|
|
1610
|
+
if (parsedVideo && parsedVideo.sources?.length) {
|
|
1611
|
+
const meta = tryParseRichMeta(html, baseURL, url);
|
|
441
1612
|
return {
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
title: post.title,
|
|
451
|
-
kind: "images",
|
|
452
|
-
breadcrumbs: [],
|
|
453
|
-
authors: [],
|
|
454
|
-
sections: [],
|
|
455
|
-
tags: [],
|
|
456
|
-
characters: [],
|
|
457
|
-
userTags: []
|
|
458
|
-
},
|
|
459
|
-
images: post.images.map((u) => ({ original: u }))
|
|
460
|
-
}
|
|
1613
|
+
kind: "video",
|
|
1614
|
+
meta,
|
|
1615
|
+
video: {
|
|
1616
|
+
poster: parsedVideo.poster ? proxyImgMaybe(parsedVideo.poster, opts) : void 0,
|
|
1617
|
+
sources: parsedVideo.sources.map((s) => ({
|
|
1618
|
+
...s,
|
|
1619
|
+
proxied: proxyVidMaybe(s.url, opts)
|
|
1620
|
+
}))
|
|
461
1621
|
}
|
|
462
1622
|
};
|
|
463
1623
|
}
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
1624
|
+
if (hasViewerClues(html)) {
|
|
1625
|
+
const imgRes = parseImagesWithBackoff(html, baseURL);
|
|
1626
|
+
const images = (imgRes.images || []).map((im) => {
|
|
1627
|
+
const preferred = im.original || im.large || im.medium || im.small || im.thumb || "";
|
|
1628
|
+
return { ...im, proxied: proxyImgMaybe(preferred, opts) };
|
|
1629
|
+
});
|
|
1630
|
+
return {
|
|
1631
|
+
kind: "images",
|
|
1632
|
+
meta: imgRes.meta,
|
|
1633
|
+
images
|
|
1634
|
+
};
|
|
1635
|
+
}
|
|
1636
|
+
return { kind: "other", meta: extractMeta(html, baseURL) };
|
|
1637
|
+
},
|
|
1638
|
+
async resolveSmart(http, baseURL, urlOrSlug, opts = {}) {
|
|
1639
|
+
const url = buildViewerUrl(baseURL, urlOrSlug);
|
|
1640
|
+
const u = new URL(url);
|
|
1641
|
+
const path = u.pathname;
|
|
1642
|
+
const html = await fetchHtml(http, url);
|
|
1643
|
+
const parsedVideo = parseVideoFromHtml(html, baseURL);
|
|
1644
|
+
if (parsedVideo && parsedVideo.sources?.length) {
|
|
1645
|
+
const meta = tryParseRichMeta(html, baseURL, url);
|
|
1646
|
+
const viewer = {
|
|
1647
|
+
kind: "video",
|
|
1648
|
+
meta,
|
|
1649
|
+
video: {
|
|
1650
|
+
poster: parsedVideo.poster ? proxyImgMaybe(parsedVideo.poster, opts) : void 0,
|
|
1651
|
+
sources: parsedVideo.sources.map((s) => ({
|
|
1652
|
+
...s,
|
|
1653
|
+
proxied: proxyVidMaybe(s.url, opts)
|
|
1654
|
+
}))
|
|
1655
|
+
}
|
|
1656
|
+
};
|
|
1657
|
+
let recommendations;
|
|
1658
|
+
try {
|
|
1659
|
+
const recPage = parseHubListing(html, baseURL, 0);
|
|
1660
|
+
if (recPage?.items?.length) {
|
|
1661
|
+
recommendations = recPage.items.map((it) => ({
|
|
1662
|
+
...it,
|
|
1663
|
+
proxiedThumb: it.thumb ? proxyImgMaybe(it.thumb, opts) : void 0
|
|
1664
|
+
}));
|
|
1665
|
+
}
|
|
1666
|
+
} catch {
|
|
474
1667
|
}
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
async alphabetLetters(section) {
|
|
479
|
-
const path = section === "manga" ? "/munga" : `/${section}`;
|
|
480
|
-
const html = await this.http.getHtml(path);
|
|
481
|
-
const doc = this.dom.parse(html);
|
|
482
|
-
const root = doc.documentElement ?? doc;
|
|
483
|
-
const letters = [];
|
|
484
|
-
const els = this.dom.qsa(
|
|
485
|
-
root,
|
|
486
|
-
".alphabet a, .alphabet__item a, .letters a, a[href*='letter=']"
|
|
487
|
-
);
|
|
488
|
-
for (const a of els) {
|
|
489
|
-
const label = this.text(a) || this.attr(a, "data-letter");
|
|
490
|
-
if (!label) continue;
|
|
491
|
-
const href = this.attr(a, "href");
|
|
492
|
-
letters.push({ label, value: label, href });
|
|
1668
|
+
const data = { absoluteUrl: url, path, viewer };
|
|
1669
|
+
data.recommendations = recommendations;
|
|
1670
|
+
return { route: "viewer", data };
|
|
493
1671
|
}
|
|
494
|
-
if (
|
|
495
|
-
|
|
1672
|
+
if (hasViewerClues(html)) {
|
|
1673
|
+
const { meta, images } = parseImagesWithBackoff(html, baseURL);
|
|
1674
|
+
const mapped = (images || []).map((im) => {
|
|
1675
|
+
const preferred = im.original || im.large || im.medium || im.small || im.thumb || "";
|
|
1676
|
+
return { ...im, proxied: proxyImgMaybe(preferred, opts) };
|
|
1677
|
+
});
|
|
1678
|
+
const viewer = { kind: "images", meta, images: mapped };
|
|
1679
|
+
let recommendations;
|
|
1680
|
+
try {
|
|
1681
|
+
const recPage = parseHubListing(html, baseURL, 0);
|
|
1682
|
+
if (recPage?.items?.length) {
|
|
1683
|
+
recommendations = recPage.items.map((it) => ({
|
|
1684
|
+
...it,
|
|
1685
|
+
proxiedThumb: it.thumb ? proxyImgMaybe(it.thumb, opts) : void 0
|
|
1686
|
+
}));
|
|
1687
|
+
}
|
|
1688
|
+
} catch {
|
|
1689
|
+
}
|
|
1690
|
+
const data = { absoluteUrl: url, path, viewer };
|
|
1691
|
+
data.recommendations = recommendations;
|
|
1692
|
+
return { route: "viewer", data };
|
|
496
1693
|
}
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
1694
|
+
const page = await listings_exports.listByPath(http, baseURL, path, 0);
|
|
1695
|
+
return { route: "listing", data: { ...page, absoluteUrl: url, path } };
|
|
1696
|
+
}
|
|
1697
|
+
};
|
|
1698
|
+
function extractText(html, re) {
|
|
1699
|
+
const m = html.match(re);
|
|
1700
|
+
return m ? m[1].replace(/<[^>]*>/g, "").trim() : "";
|
|
1701
|
+
}
|
|
1702
|
+
function grabLinksByLabel(html, baseURL, label) {
|
|
1703
|
+
const esc = label.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1704
|
+
const reLbl = new RegExp(
|
|
1705
|
+
`<(?:h3|div)[^>]*class=["'][^"']*field-label[^"']*["'][^>]*>\\s*${esc}\\s*:?\\s*<\\/[^>]+>`,
|
|
1706
|
+
"i"
|
|
1707
|
+
);
|
|
1708
|
+
const idx = html.search(reLbl);
|
|
1709
|
+
if (idx < 0) return [];
|
|
1710
|
+
const open = html.lastIndexOf("<div", idx);
|
|
1711
|
+
const close = html.indexOf("</div>", idx);
|
|
1712
|
+
if (open < 0 || close < 0) return [];
|
|
1713
|
+
const block = html.slice(open, close + 6);
|
|
1714
|
+
const out = [];
|
|
1715
|
+
const reA = /<a[^>]*href=["']([^"']+)["'][^>]*>([\s\S]*?)<\/a>/gi;
|
|
1716
|
+
for (let m; m = reA.exec(block); ) {
|
|
1717
|
+
out.push({
|
|
1718
|
+
title: m[2].replace(/<[^>]*>/g, "").trim(),
|
|
1719
|
+
url: absolutize2(m[1], baseURL)
|
|
1720
|
+
});
|
|
1721
|
+
}
|
|
1722
|
+
return out;
|
|
1723
|
+
}
|
|
1724
|
+
function grabLinks(html, blockRe, baseURL) {
|
|
1725
|
+
const alt = `(?:${blockRe.source})`;
|
|
1726
|
+
const reDiv = new RegExp(
|
|
1727
|
+
`<div[^>]*class=["'][^"']*\\bfield\\b[^"']*\\b${alt}\\b[^"']*["'][^>]*>([\\s\\S]*?)<\\/div>`,
|
|
1728
|
+
"i"
|
|
1729
|
+
);
|
|
1730
|
+
const m = reDiv.exec(html);
|
|
1731
|
+
if (!m) return [];
|
|
1732
|
+
const block = m[1];
|
|
1733
|
+
const arr = [];
|
|
1734
|
+
const reA = /<a[^>]*href=["']([^"']+)["'][^>]*>([\s\S]*?)<\/a>/gi;
|
|
1735
|
+
for (let x; x = reA.exec(block); ) {
|
|
1736
|
+
arr.push({
|
|
1737
|
+
title: x[2].replace(/<[^>]*>/g, "").trim(),
|
|
1738
|
+
url: absolutize2(x[1], baseURL)
|
|
1739
|
+
});
|
|
1740
|
+
}
|
|
1741
|
+
return arr;
|
|
1742
|
+
}
|
|
1743
|
+
function extractMeta(html, baseURL) {
|
|
1744
|
+
const title = extractText(html, /<h1[^>]*id=["']page-title["'][^>]*>([\s\S]*?)<\/h1>/i) || extractText(html, /<title>([\s\S]*?)<\/title>/i);
|
|
1745
|
+
const breadcrumbs = [];
|
|
1746
|
+
const reCrumb = /<span[^>]*typeof=["']v:Breadcrumb["'][^>]*>[\s\S]*?<a[^>]*href=["']([^"']+)["'][^>]*>([\s\S]*?)<\/a>[\s\S]*?<\/span>/gi;
|
|
1747
|
+
for (let m; m = reCrumb.exec(html); ) {
|
|
1748
|
+
breadcrumbs.push({
|
|
1749
|
+
title: m[2].replace(/<[^>]*>/g, "").trim(),
|
|
1750
|
+
url: absolutize2(m[1], baseURL)
|
|
1751
|
+
});
|
|
1752
|
+
}
|
|
1753
|
+
let authors = grabLinks(
|
|
1754
|
+
html,
|
|
1755
|
+
/field-name-field-vd-authors|field-name-field-authors|field-name-field-au/i,
|
|
1756
|
+
baseURL
|
|
1757
|
+
) || [];
|
|
1758
|
+
if (authors.length === 0) {
|
|
1759
|
+
authors = grabLinksByLabel(html, baseURL, "Author");
|
|
1760
|
+
}
|
|
1761
|
+
const sections = grabLinks(
|
|
1762
|
+
html,
|
|
1763
|
+
/field-name-field-vd-group|field-name-field-group|field-name-field-sections/i,
|
|
1764
|
+
baseURL
|
|
1765
|
+
);
|
|
1766
|
+
const tags = grabLinks(html, /field-name-field-(?:vd-)?tags|field-name-field-category/i, baseURL) || [];
|
|
1767
|
+
const meta = {
|
|
1768
|
+
title,
|
|
1769
|
+
breadcrumbs,
|
|
1770
|
+
authors,
|
|
1771
|
+
sections,
|
|
1772
|
+
tags,
|
|
1773
|
+
nodeId: "",
|
|
1774
|
+
fieldSys: {},
|
|
1775
|
+
kind: "viewer"
|
|
1776
|
+
};
|
|
1777
|
+
return meta;
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1780
|
+
// src/client-core.ts
|
|
1781
|
+
var MultpornClient = class {
|
|
1782
|
+
http;
|
|
1783
|
+
baseURL;
|
|
1784
|
+
constructor(opts = {}) {
|
|
1785
|
+
this.baseURL = (opts.baseURL ?? "https://multporn.net").replace(/\/+$/, "");
|
|
1786
|
+
this.http = new HttpClient({ ...opts, baseURL: this.baseURL });
|
|
1787
|
+
}
|
|
1788
|
+
// -------- Listing
|
|
1789
|
+
latest(page = 0, params) {
|
|
1790
|
+
return listings_exports.latest(this.http, this.baseURL, page, params);
|
|
1791
|
+
}
|
|
1792
|
+
listByPath(path, page = 0, params) {
|
|
1793
|
+
return listings_exports.listByPath(this.http, this.baseURL, path, page, params);
|
|
1794
|
+
}
|
|
1795
|
+
// -------- Search
|
|
1796
|
+
search(query, page = 0) {
|
|
1797
|
+
return search_exports.search(this.http, this.baseURL, query, page);
|
|
1798
|
+
}
|
|
1799
|
+
// -------- Post
|
|
1800
|
+
getPost(urlOrSlug) {
|
|
1801
|
+
return posts_exports.getPost(this.http, this.baseURL, urlOrSlug);
|
|
1802
|
+
}
|
|
1803
|
+
// -------- Viewer / Resolver
|
|
1804
|
+
/**
|
|
1805
|
+
* Жёсткое разрешение ссылки в ViewerResult (images/video/other) без маршрутизации.
|
|
1806
|
+
*/
|
|
1807
|
+
resolve(urlOrSlug, opts = {}) {
|
|
1808
|
+
return ViewerAPI.resolveViewer(this.http, this.baseURL, urlOrSlug, opts);
|
|
1809
|
+
}
|
|
1810
|
+
/**
|
|
1811
|
+
* Умный резолвер: возвращает либо listing-страницу (хаб), либо viewer-пейлоад (пост).
|
|
1812
|
+
*/
|
|
1813
|
+
resolveSmart(urlOrSlug, opts = {}) {
|
|
1814
|
+
return ViewerAPI.resolveSmart(this.http, this.baseURL, urlOrSlug, opts);
|
|
1815
|
+
}
|
|
1816
|
+
// -------- Updates (Drupal / jcarousel/ajax/views)
|
|
1817
|
+
updates(params = {}) {
|
|
1818
|
+
return updates_exports.updates(this.http, this.baseURL, params);
|
|
1819
|
+
}
|
|
1820
|
+
viewUpdates(viewName, params) {
|
|
1821
|
+
return this.updates({ ...params ?? {}, view_name: viewName });
|
|
1822
|
+
}
|
|
1823
|
+
// Шорткаты для популярных view_name — выше остаётся общий метод updates()
|
|
1824
|
+
updatesNewMini(p) {
|
|
1825
|
+
return updates_exports.updates(this.http, this.baseURL, { ...p ?? {}, view_name: "new_mini" });
|
|
1826
|
+
}
|
|
1827
|
+
userUploadFront(p) {
|
|
1828
|
+
return updates_exports.updates(this.http, this.baseURL, {
|
|
1829
|
+
...p ?? {},
|
|
1830
|
+
view_name: "user_upload_front"
|
|
1831
|
+
});
|
|
1832
|
+
}
|
|
1833
|
+
updatedManga(p) {
|
|
1834
|
+
return updates_exports.updates(this.http, this.baseURL, {
|
|
1835
|
+
...p ?? {},
|
|
1836
|
+
view_name: "updated_manga"
|
|
1837
|
+
});
|
|
1838
|
+
}
|
|
1839
|
+
updatedMangaPromoted(p) {
|
|
1840
|
+
return updates_exports.updates(this.http, this.baseURL, {
|
|
1841
|
+
...p ?? {},
|
|
1842
|
+
view_name: "updated_manga_promoted"
|
|
1843
|
+
});
|
|
1844
|
+
}
|
|
1845
|
+
updatedGames(p) {
|
|
1846
|
+
return updates_exports.updates(this.http, this.baseURL, {
|
|
1847
|
+
...p ?? {},
|
|
1848
|
+
view_name: "updated_games"
|
|
1849
|
+
});
|
|
1850
|
+
}
|
|
1851
|
+
randomTopComics(p) {
|
|
1852
|
+
return updates_exports.updates(this.http, this.baseURL, {
|
|
1853
|
+
...p ?? {},
|
|
1854
|
+
view_name: "random_top_comics"
|
|
1855
|
+
});
|
|
1856
|
+
}
|
|
1857
|
+
topRandomCharacters(p) {
|
|
1858
|
+
return updates_exports.updates(this.http, this.baseURL, {
|
|
1859
|
+
...p ?? {},
|
|
1860
|
+
view_name: "top_random_characters"
|
|
1861
|
+
});
|
|
1862
|
+
}
|
|
1863
|
+
// -------- Alphabet
|
|
1864
|
+
alphabetLetters(section) {
|
|
1865
|
+
return alphabet_exports.alphabetLetters(this.http, this.baseURL, section);
|
|
515
1866
|
}
|
|
516
|
-
|
|
517
|
-
return this.
|
|
1867
|
+
alphabet(section, letter, page = 0) {
|
|
1868
|
+
return alphabet_exports.alphabetItems(this.http, this.baseURL, section, letter, page);
|
|
518
1869
|
}
|
|
519
1870
|
};
|
|
520
1871
|
|
|
521
1872
|
// src/index.node.ts
|
|
522
|
-
var
|
|
1873
|
+
var MultpornClient2 = class extends MultpornClient {
|
|
523
1874
|
constructor(opts) {
|
|
524
1875
|
super({ ...opts || {}, dom: createCheerioDom() });
|
|
525
1876
|
}
|
|
@@ -527,7 +1878,7 @@ var MultpornClient = class extends MultpornClientCore {
|
|
|
527
1878
|
|
|
528
1879
|
exports.HttpClient = HttpClient;
|
|
529
1880
|
exports.HttpError = HttpError;
|
|
530
|
-
exports.MultpornClient =
|
|
1881
|
+
exports.MultpornClient = MultpornClient2;
|
|
531
1882
|
exports.defaultRetryPolicy = defaultRetryPolicy;
|
|
532
1883
|
//# sourceMappingURL=index.cjs.map
|
|
533
1884
|
//# sourceMappingURL=index.cjs.map
|