@serifpersia/dango 3.4.4 → 3.4.6
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 +1 -1
- package/client/dist/assets/{AnimeCard-4CEz8Buu.js → AnimeCard-BVdH0EeW.js} +1 -1
- package/client/dist/assets/{AnimeInfo-DyX9qTwg.js → AnimeInfo-Dw2Nop6Q.js} +1 -1
- package/client/dist/assets/{AnimeInfoPage-Dvp_c8vK.js → AnimeInfoPage-Cw-Tq3YX.js} +2 -2
- package/client/dist/assets/{Asmr-qg3lMAJn.js → Asmr-BrjzzyOi.js} +1 -1
- package/client/dist/assets/{AsmrResetProgressModal-BpPlg_Vv.js → AsmrResetProgressModal-CYjpcYpb.js} +1 -1
- package/client/dist/assets/{AvSyncCalibrator-FJllOgOH.js → AvSyncCalibrator-BuHurVgo.js} +3 -3
- package/client/dist/assets/AvSyncCalibrator-D0FYSwiz.css +1 -0
- package/client/dist/assets/{Home-A7gCeEST.js → Home-KdnP7sNM.js} +1 -1
- package/client/dist/assets/{Insights-G9IXepHJ.js → Insights-DDWry-t1.js} +1 -1
- package/client/dist/assets/{LibraryListPage-Bh2qCJPh.js → LibraryListPage-CV8Z9JlO.js} +1 -1
- package/client/dist/assets/{ListeningList-Czv4cTRl.js → ListeningList-DSMbMZT7.js} +1 -1
- package/client/dist/assets/{Manga-qdAAS1z7.js → Manga-J6XoqEKq.js} +1 -1
- package/client/dist/assets/{MangaInfoPage-1rTgZzf2.js → MangaInfoPage-N7TxsN5u.js} +1 -1
- package/client/dist/assets/{MangaReadPage-CeyxPG6w.js → MangaReadPage-D_VzSNWq.js} +1 -1
- package/client/dist/assets/{MangaResetProgressModal-C_ppotmh.js → MangaResetProgressModal-DyhQYBDy.js} +1 -1
- package/client/dist/assets/{Mature-Bkhxnr3K.js → Mature-Db53R-LR.js} +1 -1
- package/client/dist/assets/{MediaCard-BQZsutEo.js → MediaCard-BcLjlpfR.js} +1 -1
- package/client/dist/assets/{Music-C0rGzSbi.js → Music-DjxQjJOl.js} +1 -1
- package/client/dist/assets/{OptionListMenu-CyfpCBEy.js → OptionListMenu-DngTgHxH.js} +1 -1
- package/client/dist/assets/Player-Be_7Ne5p.js +56 -0
- package/client/dist/assets/{PlayerSettings-icy4SfQa.js → PlayerSettings-D47ItGDR.js} +1 -1
- package/client/dist/assets/{QueueOptionsButton-5eloKF5J.js → QueueOptionsButton-ClVTpH8J.js} +1 -1
- package/client/dist/assets/{QueueRail-DaMjlKL4.js → QueueRail-DeJKVRs5.js} +1 -1
- package/client/dist/assets/{Radio-FxGqCxR4.js → Radio-DU0ma3GA.js} +1 -1
- package/client/dist/assets/{ReadingList-CCm3IAbt.js → ReadingList-qXW7Dk5z.js} +1 -1
- package/client/dist/assets/{ResetProgressModal-CeI4V3Y5.js → ResetProgressModal-C6ukct_c.js} +1 -1
- package/client/dist/assets/{Search-qpILaAiQ.js → Search-DjjKA_Wu.js} +1 -1
- package/client/dist/assets/{Settings-Bw9M3NGz.js → Settings-DQt_S5Qw.js} +1 -1
- package/client/dist/assets/{SkeletonGrid-0dY_xMq3.js → SkeletonGrid-nx2HEi4P.js} +1 -1
- package/client/dist/assets/{Trackers-Zy5GOs0P.js → Trackers-BcGzeqj2.js} +1 -1
- package/client/dist/assets/Tv-COvyHdeJ.js +4 -0
- package/client/dist/assets/{TvInfo-CHIsVzZ0.js → TvInfo-CUd1agFy.js} +1 -1
- package/client/dist/assets/{TvPopup-D_tVTyZ2.js → TvPopup-CJeu3tgc.js} +1 -1
- package/client/dist/assets/{TvSearch-BVxedBW-.js → TvSearch-BIypjbU3.js} +1 -1
- package/client/dist/assets/{TvWatchlist-BEIwq9R9.js → TvWatchlist-DZrBdiqZ.js} +1 -1
- package/client/dist/assets/{Watchlist-CSngjqlz.js → Watchlist-Dw8cPe0n.js} +1 -1
- package/client/dist/assets/{hls-D48GVTkk.js → hls-B3_1VuDP.js} +1 -1
- package/client/dist/assets/{index-CVB5dYhT.js → index-gMxawd-E.js} +2 -2
- package/client/dist/assets/{useAnimeInfoData-DxdOg_B7.js → useAnimeInfoData-_iOLEAa8.js} +1 -1
- package/client/dist/assets/{useAsmr-ClckxnPe.js → useAsmr-Blk4v41t.js} +1 -1
- package/client/dist/assets/{useAsmrPopup-Bbgxefy_.js → useAsmrPopup-D9SQIgZa.js} +1 -1
- package/client/dist/assets/{useMangaLibrary-B3_BUKVk.js → useMangaLibrary-B60at6ni.js} +1 -1
- package/client/dist/assets/{useMangaPopup-Celk4lzf.js → useMangaPopup-DEfGjWSu.js} +1 -1
- package/client/dist/assets/{useProviders-pwxPZPHh.js → useProviders-Ca0bZWt7.js} +1 -1
- package/client/dist/assets/{useTv-D9sQrDop.js → useTv-WJCDL5_5.js} +1 -1
- package/client/dist/assets/{useTvLibrary-UsQpgBTB.js → useTvLibrary-Fgn5i9rJ.js} +1 -1
- package/client/dist/index.html +1 -1
- package/package.json +4 -2
- package/server/dist/app-hono.js +2 -0
- package/server/dist/hono/asmr.js +10 -1
- package/server/dist/hono/music.js +122 -332
- package/server/dist/hono/proxy.js +105 -99
- package/server/dist/hono/tv.js +5 -58
- package/server/dist/hono/watchlist.js +2 -291
- package/server/dist/lib/anilist.js +0 -3
- package/server/dist/lib/mal.js +4 -41
- package/server/dist/lib/offline-db.js +0 -6
- package/server/dist/lib/potoken.js +135 -0
- package/server/dist/lib/tracker/manga-sync.service.js +3 -22
- package/server/dist/lib/tracker/sync-state.js +21 -0
- package/server/dist/lib/tracker/sync.service.js +3 -22
- package/server/dist/lib/ump.js +106 -0
- package/server/dist/providers/remote-loader.js +17 -27
- package/server/dist/providers/title-matching.js +5 -5
- package/server/dist/repositories/asmr.repository.js +4 -53
- package/server/dist/repositories/library.repository.js +46 -0
- package/server/dist/repositories/manga.repository.js +3 -53
- package/server/dist/repositories/settings.repository.js +8 -0
- package/server/dist/repositories/tv.repository.js +3 -46
- package/server/dist/repositories/watched-episodes.repository.js +0 -2
- package/server/dist/server.js +5 -1
- package/server/dist/sync.js +3 -5
- package/server/dist/trpc/routers/asmr-library.js +2 -12
- package/server/dist/trpc/routers/asmr-progress.js +10 -22
- package/server/dist/trpc/routers/data.js +17 -36
- package/server/dist/trpc/routers/discord.js +1 -8
- package/server/dist/trpc/routers/insights.js +15 -16
- package/server/dist/trpc/routers/manga-library.js +5 -22
- package/server/dist/trpc/routers/manga-progress.js +8 -20
- package/server/dist/trpc/routers/music.js +6 -11
- package/server/dist/trpc/routers/queue.js +1 -1
- package/server/dist/trpc/routers/tracker.js +1 -7
- package/server/dist/trpc/routers/tv-library.js +2 -12
- package/server/dist/trpc/routers/tv-progress.js +16 -28
- package/server/dist/trpc/routers/tv.js +5 -21
- package/server/dist/trpc/routers/watchlist.js +1 -8
- package/server/dist/trpc/validation.js +35 -0
- package/server/dist/utils/db-utils.js +8 -0
- package/server/dist/utils/http.utils.js +12 -0
- package/server/dist/utils/machine-id.js +1 -2
- package/server/dist/utils/megaplay.utils.js +1 -2
- package/server/package.json +4 -1
- package/client/dist/assets/AvSyncCalibrator-C2wu1ydI.css +0 -1
- package/client/dist/assets/Player-yhiTlpYU.js +0 -56
- package/client/dist/assets/Tv-DREwc7BX.js +0 -4
|
@@ -6,7 +6,7 @@ import fs from 'fs';
|
|
|
6
6
|
import logger from '../logger.js';
|
|
7
7
|
import { buildCfClearanceCookie, sanitizeCfClearance } from '../utils/cookie.utils.js';
|
|
8
8
|
import { isSafeExternalUrl } from '../utils/security.utils.js';
|
|
9
|
-
import { fetchWithRetry } from '../utils/http.utils.js';
|
|
9
|
+
import { fetchWithRetry, linkAbort, setProxyHeaders } from '../utils/http.utils.js';
|
|
10
10
|
import { MEGAPLAY_ORIGIN, isNexabloomMasterUrl, signNexabloomMasterUrl, } from '../utils/megaplay.utils.js';
|
|
11
11
|
const proxyCache = new AppCache({ ttlSeconds: 30, maxKeys: 500 });
|
|
12
12
|
function assTimeToVtt(value) {
|
|
@@ -82,6 +82,50 @@ function assToVtt(body) {
|
|
|
82
82
|
return null;
|
|
83
83
|
return `WEBVTT\n\n${cues.join('\n\n')}\n`;
|
|
84
84
|
}
|
|
85
|
+
const SUB_WRAP_AT = 50;
|
|
86
|
+
function splitBalancedCueLine(line) {
|
|
87
|
+
const words = line.split(/\s+/).filter((w) => w.length > 0);
|
|
88
|
+
if (words.length < 2)
|
|
89
|
+
return [line];
|
|
90
|
+
const total = words.join(' ').length;
|
|
91
|
+
if (total <= SUB_WRAP_AT)
|
|
92
|
+
return [line];
|
|
93
|
+
const target = total / 2;
|
|
94
|
+
let acc = words[0]?.length ?? 0;
|
|
95
|
+
let best = 1;
|
|
96
|
+
let bestDiff = Math.abs(acc - target);
|
|
97
|
+
for (let i = 1; i < words.length; i++) {
|
|
98
|
+
acc += 1 + (words[i]?.length ?? 0);
|
|
99
|
+
const diff = Math.abs(acc - target);
|
|
100
|
+
if (diff < bestDiff) {
|
|
101
|
+
bestDiff = diff;
|
|
102
|
+
best = i + 1;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (best <= 0 || best >= words.length)
|
|
106
|
+
return [line];
|
|
107
|
+
return [words.slice(0, best).join(' '), words.slice(best).join(' ')];
|
|
108
|
+
}
|
|
109
|
+
function rewrapVttCues(vtt) {
|
|
110
|
+
const blocks = vtt.replace(/\r\n/g, '\n').split(/\n\n+/);
|
|
111
|
+
return blocks
|
|
112
|
+
.map((block, idx) => {
|
|
113
|
+
if (idx === 0 && /^\s*WEBVTT/i.test(block))
|
|
114
|
+
return block;
|
|
115
|
+
const lines = block.split('\n');
|
|
116
|
+
const tIdx = lines.findIndex((l) => l.includes('-->'));
|
|
117
|
+
if (tIdx < 0)
|
|
118
|
+
return block;
|
|
119
|
+
const payload = lines.slice(tIdx + 1).filter((l) => l.trim() !== '');
|
|
120
|
+
if (payload.length !== 1)
|
|
121
|
+
return block;
|
|
122
|
+
const split = splitBalancedCueLine((payload[0] ?? '').trim());
|
|
123
|
+
if (split.length < 2)
|
|
124
|
+
return block;
|
|
125
|
+
return [...lines.slice(0, tIdx + 1), ...split].join('\n');
|
|
126
|
+
})
|
|
127
|
+
.join('\n\n');
|
|
128
|
+
}
|
|
85
129
|
const KWIK_DOMAINS = new Set(['kwik.cx', 'kwik.si', 'kwik.pro']);
|
|
86
130
|
const ANIMEPAHE_URL = 'https://animepahe.pw/';
|
|
87
131
|
const VAULT_CDN_HOSTS = new Set(['uwucdn.top', 'owocdn.top']);
|
|
@@ -159,6 +203,20 @@ function nexabloomFallbackUrls(urlStr) {
|
|
|
159
203
|
}
|
|
160
204
|
return urls;
|
|
161
205
|
}
|
|
206
|
+
async function fetchNexabloomFallback(urlStr, signal, attempt, isOk) {
|
|
207
|
+
let last = null;
|
|
208
|
+
for (const candidate of nexabloomFallbackUrls(urlStr)) {
|
|
209
|
+
if (signal.aborted)
|
|
210
|
+
break;
|
|
211
|
+
const r = await attempt(candidate).catch(() => null);
|
|
212
|
+
if (!r)
|
|
213
|
+
continue;
|
|
214
|
+
if (isOk(r))
|
|
215
|
+
return r;
|
|
216
|
+
last = r;
|
|
217
|
+
}
|
|
218
|
+
return last;
|
|
219
|
+
}
|
|
162
220
|
function isGotScrapingHost(urlStr) {
|
|
163
221
|
try {
|
|
164
222
|
const host = new URL(urlStr).hostname.toLowerCase();
|
|
@@ -299,9 +357,6 @@ function placeholderSvg() {
|
|
|
299
357
|
return null;
|
|
300
358
|
}
|
|
301
359
|
const PROXY_UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:126.0) Gecko/20100101 Firefox/126.0';
|
|
302
|
-
function linkAbort(signal, abort) {
|
|
303
|
-
signal.addEventListener('abort', () => abort.abort(), { once: true });
|
|
304
|
-
}
|
|
305
360
|
export function registerProxy(app) {
|
|
306
361
|
app.get('/api/proxy', async (c) => {
|
|
307
362
|
const url = c.req.query('url');
|
|
@@ -377,24 +432,15 @@ export function registerProxy(app) {
|
|
|
377
432
|
headers.set('Access-Control-Allow-Origin', '*');
|
|
378
433
|
return new Response(cached, { headers });
|
|
379
434
|
}
|
|
380
|
-
const resp = await (
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
followRedirect: true,
|
|
390
|
-
throwHttpErrors: false,
|
|
391
|
-
});
|
|
392
|
-
last = attempt;
|
|
393
|
-
if (attempt.statusCode === 200 || attempt.statusCode === 206)
|
|
394
|
-
return attempt;
|
|
395
|
-
}
|
|
396
|
-
return last;
|
|
397
|
-
})();
|
|
435
|
+
const resp = await fetchNexabloomFallback(urlStr, abort.signal, (candidate) => gotScraping({
|
|
436
|
+
url: isNexabloomMasterUrl(candidate) ? signNexabloomMasterUrl(candidate) : candidate,
|
|
437
|
+
method: 'GET',
|
|
438
|
+
headers,
|
|
439
|
+
responseType: 'text',
|
|
440
|
+
timeout: { request: 30000 },
|
|
441
|
+
followRedirect: true,
|
|
442
|
+
throwHttpErrors: false,
|
|
443
|
+
}), (attempt) => attempt.statusCode === 200 || attempt.statusCode === 206);
|
|
398
444
|
if (!resp || (resp.statusCode !== 200 && resp.statusCode !== 206)) {
|
|
399
445
|
return c.text('Upstream error', (resp?.statusCode || 502));
|
|
400
446
|
}
|
|
@@ -441,21 +487,15 @@ export function registerProxy(app) {
|
|
|
441
487
|
if (isGotScrapingHost(urlStr)) {
|
|
442
488
|
if (range)
|
|
443
489
|
headers['Range'] = range;
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
throwHttpErrors: false,
|
|
454
|
-
});
|
|
455
|
-
resp = attempt;
|
|
456
|
-
if (attempt.statusCode === 200 || attempt.statusCode === 206)
|
|
457
|
-
break;
|
|
458
|
-
}
|
|
490
|
+
const resp = await fetchNexabloomFallback(urlStr, abort.signal, (candidate) => gotScraping({
|
|
491
|
+
url: candidate,
|
|
492
|
+
method: 'GET',
|
|
493
|
+
headers,
|
|
494
|
+
responseType: 'buffer',
|
|
495
|
+
timeout: { request: 30000 },
|
|
496
|
+
followRedirect: true,
|
|
497
|
+
throwHttpErrors: false,
|
|
498
|
+
}), (attempt) => attempt.statusCode === 200 || attempt.statusCode === 206);
|
|
459
499
|
if (!resp || (resp.statusCode !== 200 && resp.statusCode !== 206)) {
|
|
460
500
|
return c.text('Upstream error', (resp?.statusCode || 502));
|
|
461
501
|
}
|
|
@@ -463,31 +503,16 @@ export function registerProxy(app) {
|
|
|
463
503
|
const isVtt = urlStr.endsWith('.vtt') || urlStr.includes('.vtt?');
|
|
464
504
|
const outHeaders = new Headers();
|
|
465
505
|
outHeaders.set('Content-Type', isVtt ? 'text/vtt; charset=utf-8' : ct || 'application/octet-stream');
|
|
466
|
-
|
|
467
|
-
if (cl)
|
|
468
|
-
outHeaders.set('Content-Length', cl);
|
|
469
|
-
const cr = resp.headers['content-range'];
|
|
470
|
-
if (cr)
|
|
471
|
-
outHeaders.set('Content-Range', cr);
|
|
472
|
-
const ar = resp.headers['accept-ranges'];
|
|
473
|
-
if (ar)
|
|
474
|
-
outHeaders.set('Accept-Ranges', ar);
|
|
475
|
-
outHeaders.set('Access-Control-Allow-Origin', '*');
|
|
506
|
+
setProxyHeaders(outHeaders, (name) => resp.headers[name]);
|
|
476
507
|
return new Response(resp.body, {
|
|
477
508
|
status: resp.statusCode,
|
|
478
509
|
headers: outHeaders,
|
|
479
510
|
});
|
|
480
511
|
}
|
|
481
512
|
else {
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
if (
|
|
485
|
-
break;
|
|
486
|
-
try {
|
|
487
|
-
const attempt = await fetchWithRetry(candidate, { method: 'GET', headers }, { retries: 3, timeoutMs: 30000, signal: abort.signal });
|
|
488
|
-
upstream = attempt;
|
|
489
|
-
if (attempt.status === 200 || attempt.status === 206)
|
|
490
|
-
break;
|
|
513
|
+
const upstream = await fetchNexabloomFallback(urlStr, abort.signal, async (candidate) => {
|
|
514
|
+
const attempt = await fetchWithRetry(candidate, { method: 'GET', headers }, { retries: 3, timeoutMs: 30000, signal: abort.signal });
|
|
515
|
+
if (attempt.status !== 200 && attempt.status !== 206) {
|
|
491
516
|
try {
|
|
492
517
|
await attempt.body?.cancel();
|
|
493
518
|
}
|
|
@@ -495,10 +520,8 @@ export function registerProxy(app) {
|
|
|
495
520
|
// ignore
|
|
496
521
|
}
|
|
497
522
|
}
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
}
|
|
501
|
-
}
|
|
523
|
+
return attempt;
|
|
524
|
+
}, (attempt) => attempt.status === 200 || attempt.status === 206);
|
|
502
525
|
if (!upstream) {
|
|
503
526
|
return c.text('Upstream error', 502);
|
|
504
527
|
}
|
|
@@ -516,16 +539,7 @@ export function registerProxy(app) {
|
|
|
516
539
|
const ct = upstream.headers.get('content-type');
|
|
517
540
|
const outHeaders = new Headers();
|
|
518
541
|
outHeaders.set('Content-Type', isVtt ? 'text/vtt; charset=utf-8' : ct || 'application/octet-stream');
|
|
519
|
-
|
|
520
|
-
if (cl)
|
|
521
|
-
outHeaders.set('Content-Length', cl);
|
|
522
|
-
const cr = upstream.headers.get('content-range');
|
|
523
|
-
if (cr)
|
|
524
|
-
outHeaders.set('Content-Range', cr);
|
|
525
|
-
const ar = upstream.headers.get('accept-ranges');
|
|
526
|
-
if (ar)
|
|
527
|
-
outHeaders.set('Accept-Ranges', ar);
|
|
528
|
-
outHeaders.set('Access-Control-Allow-Origin', '*');
|
|
542
|
+
setProxyHeaders(outHeaders, (name) => upstream.headers.get(name));
|
|
529
543
|
if (!upstream.body) {
|
|
530
544
|
return c.text('Upstream error', 502);
|
|
531
545
|
}
|
|
@@ -670,6 +684,12 @@ export function registerProxy(app) {
|
|
|
670
684
|
headers.set('Cache-Control', cacheControl);
|
|
671
685
|
return headers;
|
|
672
686
|
};
|
|
687
|
+
const subtitleError = (text) => {
|
|
688
|
+
const headers = new Headers();
|
|
689
|
+
headers.set('Access-Control-Allow-Origin', '*');
|
|
690
|
+
headers.set('Cache-Control', 'no-store');
|
|
691
|
+
return new Response(text, { status: 502, headers });
|
|
692
|
+
};
|
|
673
693
|
try {
|
|
674
694
|
const headers = buildHeaders();
|
|
675
695
|
let result = await fetchText(subUrl, 1);
|
|
@@ -681,10 +701,7 @@ export function registerProxy(app) {
|
|
|
681
701
|
return new Response(null, { status: 499 });
|
|
682
702
|
}
|
|
683
703
|
if (!result || !result.body) {
|
|
684
|
-
|
|
685
|
-
headers.set('Access-Control-Allow-Origin', '*');
|
|
686
|
-
headers.set('Cache-Control', 'no-store');
|
|
687
|
-
return new Response('Subtitle upstream error', { status: 502, headers });
|
|
704
|
+
return subtitleError('Subtitle upstream error');
|
|
688
705
|
}
|
|
689
706
|
const body = result.body;
|
|
690
707
|
const baseForSegments = innerRawUrl || subUrl;
|
|
@@ -730,32 +747,30 @@ export function registerProxy(app) {
|
|
|
730
747
|
}
|
|
731
748
|
}
|
|
732
749
|
if (parts.length === 0) {
|
|
733
|
-
|
|
734
|
-
headers.set('Access-Control-Allow-Origin', '*');
|
|
735
|
-
headers.set('Cache-Control', 'no-store');
|
|
736
|
-
return new Response('Subtitle playlist empty', { status: 502, headers });
|
|
750
|
+
return subtitleError('Subtitle playlist empty');
|
|
737
751
|
}
|
|
738
|
-
return new Response(`WEBVTT\n\n${parts.join('\n\n')}\n
|
|
752
|
+
return new Response(rewrapVttCues(`WEBVTT\n\n${parts.join('\n\n')}\n`), {
|
|
739
753
|
headers: vttHeaders('public, max-age=86400'),
|
|
740
754
|
});
|
|
741
755
|
}
|
|
742
756
|
if (/^\s*WEBVTT/i.test(body)) {
|
|
743
|
-
return new Response(body, {
|
|
757
|
+
return new Response(rewrapVttCues(body), {
|
|
758
|
+
headers: vttHeaders('public, max-age=86400'),
|
|
759
|
+
});
|
|
744
760
|
}
|
|
745
761
|
const vttFromAss = assToVtt(body);
|
|
746
762
|
if (vttFromAss) {
|
|
747
|
-
return new Response(vttFromAss, {
|
|
763
|
+
return new Response(rewrapVttCues(vttFromAss), {
|
|
764
|
+
headers: vttHeaders('public, max-age=86400'),
|
|
765
|
+
});
|
|
748
766
|
}
|
|
749
|
-
return new Response(`WEBVTT\n\n${body.replace(/\r\n/g, '\n').replace(/(\d{2}:\d{2}:\d{2}),(\d{3})/g, '$1.$2')}
|
|
767
|
+
return new Response(rewrapVttCues(`WEBVTT\n\n${body.replace(/\r\n/g, '\n').replace(/(\d{2}:\d{2}:\d{2}),(\d{3})/g, '$1.$2')}`), { headers: vttHeaders('public, max-age=86400') });
|
|
750
768
|
}
|
|
751
769
|
catch {
|
|
752
770
|
if (abort.signal.aborted)
|
|
753
771
|
return new Response(null, { status: 499 });
|
|
754
772
|
logger.warn({ url: subUrl }, '[subtitle-proxy] failed');
|
|
755
|
-
|
|
756
|
-
headers.set('Access-Control-Allow-Origin', '*');
|
|
757
|
-
headers.set('Cache-Control', 'no-store');
|
|
758
|
-
return new Response('Subtitle upstream error', { status: 502, headers });
|
|
773
|
+
return subtitleError('Subtitle upstream error');
|
|
759
774
|
}
|
|
760
775
|
});
|
|
761
776
|
app.get('/api/image-proxy', async (c) => {
|
|
@@ -779,12 +794,7 @@ export function registerProxy(app) {
|
|
|
779
794
|
try {
|
|
780
795
|
if (targetUrl.includes('animepahe')) {
|
|
781
796
|
refererValue = 'https://animepahe.pw/';
|
|
782
|
-
|
|
783
|
-
let sanitized = rawCookie.trim();
|
|
784
|
-
sanitized = sanitized.replace(/^cf_clearance/i, '');
|
|
785
|
-
sanitized = sanitized.replace(/^[:=]\s*/, '');
|
|
786
|
-
sanitized = sanitized.replace(/["']/g, '').trim();
|
|
787
|
-
headers['Cookie'] = `cf_clearance=${sanitized}`;
|
|
797
|
+
headers['Cookie'] = `cf_clearance=${sanitizeCfClearance(cookie)}`;
|
|
788
798
|
}
|
|
789
799
|
else if (targetUrl.includes('anilist.co')) {
|
|
790
800
|
refererValue = 'https://anilist.co/';
|
|
@@ -800,13 +810,9 @@ export function registerProxy(app) {
|
|
|
800
810
|
}
|
|
801
811
|
else if (targetUrl.includes('weeabo0.xyz')) {
|
|
802
812
|
refererValue = 'https://japaneseasmr.com/';
|
|
803
|
-
const
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
sanitized = sanitized.replace(/^[:=]\s*/, '');
|
|
807
|
-
sanitized = sanitized.replace(/["']/g, '').trim();
|
|
808
|
-
if (sanitized)
|
|
809
|
-
headers['Cookie'] = `cf_clearance=${sanitized}`;
|
|
813
|
+
const jasmrCookieHeader = buildCfClearanceCookie(cookie);
|
|
814
|
+
if (jasmrCookieHeader)
|
|
815
|
+
headers['Cookie'] = jasmrCookieHeader;
|
|
810
816
|
const uaParam = ua || '';
|
|
811
817
|
if (uaParam)
|
|
812
818
|
headers['User-Agent'] = uaParam;
|
package/server/dist/hono/tv.js
CHANGED
|
@@ -1,38 +1,11 @@
|
|
|
1
1
|
import logger from '../logger.js';
|
|
2
|
-
import {
|
|
3
|
-
import { getTmdbKey, TMDB_BASE, TMDB_IMAGE } from '../lib/tmdb.js';
|
|
2
|
+
import { linkAbort, setProxyHeaders } from '../utils/http.utils.js';
|
|
4
3
|
import { URL } from 'url';
|
|
5
4
|
import { isSafeExternalUrl } from '../utils/security.utils.js';
|
|
6
|
-
async function resolveTvMeta(mediaType, numericTmdbId, partial) {
|
|
7
|
-
const meta = { ...partial };
|
|
8
|
-
if (meta.title && meta.imdbId && meta.year)
|
|
9
|
-
return meta;
|
|
10
|
-
try {
|
|
11
|
-
const tmdbKey = await getTmdbKey();
|
|
12
|
-
if (tmdbKey) {
|
|
13
|
-
const tmdbRes = await fetch(`${TMDB_BASE}/${mediaType}/${numericTmdbId}?api_key=${tmdbKey}&append_to_response=external_ids`, { headers: { 'User-Agent': 'Mozilla/5.0' }, signal: AbortSignal.timeout(4000) });
|
|
14
|
-
if (tmdbRes.ok) {
|
|
15
|
-
const d = await parseJsonBody(tmdbRes);
|
|
16
|
-
if (!meta.title)
|
|
17
|
-
meta.title = d.title || d.name || '';
|
|
18
|
-
if (!meta.year)
|
|
19
|
-
meta.year = (d.release_date || d.first_air_date || '').split('-')[0] || '';
|
|
20
|
-
if (!meta.imdbId)
|
|
21
|
-
meta.imdbId = d.external_ids?.imdb_id || d.imdb_id || '';
|
|
22
|
-
if (mediaType === 'tv' && d.number_of_seasons)
|
|
23
|
-
meta.totalSeasons = String(d.number_of_seasons);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
catch {
|
|
28
|
-
// ignore
|
|
29
|
-
}
|
|
30
|
-
return meta;
|
|
31
|
-
}
|
|
32
5
|
function proxiedMediaUrl(targetUrl, refererStr) {
|
|
33
6
|
return `/api/tv/stream-proxy?url=${encodeURIComponent(targetUrl)}&referer=${encodeURIComponent(refererStr)}`;
|
|
34
7
|
}
|
|
35
|
-
|
|
8
|
+
function rewriteTvPlaylist(body, baseUrl, refererStr) {
|
|
36
9
|
return body
|
|
37
10
|
.split('\n')
|
|
38
11
|
.map((line) => {
|
|
@@ -50,7 +23,7 @@ export function rewriteTvPlaylist(body, baseUrl, refererStr) {
|
|
|
50
23
|
})
|
|
51
24
|
.join('\n');
|
|
52
25
|
}
|
|
53
|
-
|
|
26
|
+
function isPlaylistBody(body) {
|
|
54
27
|
return (body.length >= 7 &&
|
|
55
28
|
body
|
|
56
29
|
.subarray(0, 32)
|
|
@@ -59,20 +32,6 @@ export function isPlaylistBody(body) {
|
|
|
59
32
|
.trimStart()
|
|
60
33
|
.startsWith('#EXTM3U'));
|
|
61
34
|
}
|
|
62
|
-
function toSearchResult(item, type) {
|
|
63
|
-
return {
|
|
64
|
-
id: item.id,
|
|
65
|
-
title: item.title || item.name || '',
|
|
66
|
-
year: (item.release_date || item.first_air_date || '').split('-')[0],
|
|
67
|
-
type,
|
|
68
|
-
image: item.poster_path ? `${TMDB_IMAGE}/w500${item.poster_path}` : '',
|
|
69
|
-
backdrop: item.backdrop_path ? `${TMDB_IMAGE}/w780${item.backdrop_path}` : '',
|
|
70
|
-
overview: item.overview || '',
|
|
71
|
-
vote_average: item.vote_average || 0,
|
|
72
|
-
adult: item.adult === true,
|
|
73
|
-
genre_ids: item.genre_ids || [],
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
35
|
export function registerTv(app, getApiCache, getTvProvider) {
|
|
77
36
|
app.get('/api/tv/embed/:provider/:type/:tmdbId', async (c) => {
|
|
78
37
|
const provider = c.req.param('provider');
|
|
@@ -117,10 +76,7 @@ export function registerTv(app, getApiCache, getTvProvider) {
|
|
|
117
76
|
}
|
|
118
77
|
const abort = new AbortController();
|
|
119
78
|
const timeout = setTimeout(() => abort.abort(), 30000);
|
|
120
|
-
c.req.raw.signal
|
|
121
|
-
clearTimeout(timeout);
|
|
122
|
-
abort.abort();
|
|
123
|
-
}, { once: true });
|
|
79
|
+
linkAbort(c.req.raw.signal, abort);
|
|
124
80
|
try {
|
|
125
81
|
const headers = {
|
|
126
82
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
|
@@ -137,18 +93,9 @@ export function registerTv(app, getApiCache, getTvProvider) {
|
|
|
137
93
|
return c.text('Upstream error', (status || 502));
|
|
138
94
|
}
|
|
139
95
|
const contentType = fetchResp.headers.get('content-type') || 'application/octet-stream';
|
|
140
|
-
const contentLength = fetchResp.headers.get('content-length');
|
|
141
|
-
const contentRange = fetchResp.headers.get('content-range');
|
|
142
|
-
const acceptRanges = fetchResp.headers.get('accept-ranges');
|
|
143
96
|
const outHeaders = new Headers();
|
|
144
97
|
outHeaders.set('Content-Type', contentType);
|
|
145
|
-
|
|
146
|
-
outHeaders.set('Content-Length', contentLength);
|
|
147
|
-
if (contentRange)
|
|
148
|
-
outHeaders.set('Content-Range', contentRange);
|
|
149
|
-
if (acceptRanges)
|
|
150
|
-
outHeaders.set('Accept-Ranges', acceptRanges);
|
|
151
|
-
outHeaders.set('Access-Control-Allow-Origin', '*');
|
|
98
|
+
setProxyHeaders(outHeaders, (name) => fetchResp.headers.get(name));
|
|
152
99
|
outHeaders.set('Connection', 'keep-alive');
|
|
153
100
|
if (urlStr.includes('.m3u8') || /mpegurl|m3u8/i.test(contentType)) {
|
|
154
101
|
const body = await fetchResp.text();
|