@serifpersia/dango 3.6.4 → 3.6.7
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-DVk4BNex.js → AnimeCard-C3P9zWi9.js} +1 -1
- package/client/dist/assets/{AnimeInfo-DevqUuNX.js → AnimeInfo-Et6687Tl.js} +1 -1
- package/client/dist/assets/{AnimeInfoPage-ZoJMem3T.js → AnimeInfoPage-DX4eNnlH.js} +2 -2
- package/client/dist/assets/{Asmr-B7Hc4hT4.js → Asmr-CCdEWrFi.js} +1 -1
- package/client/dist/assets/{AsmrResetProgressModal-AdrZB33a.js → AsmrResetProgressModal-DgEWBJOw.js} +1 -1
- package/client/dist/assets/AvSyncCalibrator-CQ2fjf2E.js +10 -0
- package/client/dist/assets/{Home-Dy6jlbe-.js → Home-DUkUTTTy.js} +1 -1
- package/client/dist/assets/{Insights-L72FYEhY.js → Insights-QCh4xW12.js} +1 -1
- package/client/dist/assets/{LibraryListPage-CJiChvn7.js → LibraryListPage-BoTxRqHz.js} +1 -1
- package/client/dist/assets/{ListeningList-C6DmhQYE.js → ListeningList-DguKjI_t.js} +1 -1
- package/client/dist/assets/{Manga-pBfz3N91.js → Manga-HydR-L7o.js} +1 -1
- package/client/dist/assets/{MangaInfoPage-DanNLum0.js → MangaInfoPage-CBakEI9a.js} +1 -1
- package/client/dist/assets/{MangaReadPage-CmSrkaL3.js → MangaReadPage-DwAR3iPy.js} +1 -1
- package/client/dist/assets/{MangaResetProgressModal-DYBD4XfF.js → MangaResetProgressModal-DGzl8vsO.js} +1 -1
- package/client/dist/assets/{Mature-APIFXLUd.js → Mature-Pi0TxKuF.js} +1 -1
- package/client/dist/assets/{MediaCard-lk6ZO2T2.js → MediaCard--cuFxOCS.js} +1 -1
- package/client/dist/assets/{Music-SWFqsret.js → Music-CQpyXTLH.js} +1 -1
- package/client/dist/assets/{OptionListMenu-DCG-BF9D.js → OptionListMenu-B3D39dGS.js} +1 -1
- package/client/dist/assets/Player-CsH4x_I_.js +56 -0
- package/client/dist/assets/{PlayerSettings-aHrCaLly.js → PlayerSettings-DGxBsi3F.js} +1 -1
- package/client/dist/assets/{QueueOptionsButton-9ptjLfgY.js → QueueOptionsButton-Djq4Cpex.js} +1 -1
- package/client/dist/assets/{QueueRail-DSA94IWc.js → QueueRail-DOrYgNVX.js} +1 -1
- package/client/dist/assets/{Radio-fo7eMtmX.js → Radio-DF6g6odd.js} +1 -1
- package/client/dist/assets/{ReadingList-DOZYEIaT.js → ReadingList-DwYoBrNk.js} +1 -1
- package/client/dist/assets/{ResetProgressModal-PmJXKPXV.js → ResetProgressModal-CMF86mX6.js} +1 -1
- package/client/dist/assets/{Search-q_45kuOY.js → Search-DFkhuk-x.js} +1 -1
- package/client/dist/assets/{Settings-DCG_27q2.js → Settings-DBB2r0VQ.js} +1 -1
- package/client/dist/assets/{SkeletonGrid-D6e8JTc2.js → SkeletonGrid-DJelJgAv.js} +1 -1
- package/client/dist/assets/{Trackers-Br8u_-n7.js → Trackers-DGYXFhYZ.js} +1 -1
- package/client/dist/assets/{Tv-9jGD-POg.js → Tv-C5MDGD2Z.js} +4 -4
- package/client/dist/assets/{TvInfo-DaoG5qd8.js → TvInfo-DrKuK13u.js} +1 -1
- package/client/dist/assets/{TvPopup-B11MGIN0.js → TvPopup-DtHilRDT.js} +1 -1
- package/client/dist/assets/{TvSearch-BJ_lOIHZ.js → TvSearch-CzNxdu_w.js} +1 -1
- package/client/dist/assets/{TvWatchlist-DGvG3C9M.js → TvWatchlist-D7p4br6S.js} +1 -1
- package/client/dist/assets/{Watchlist-BHxo147b.js → Watchlist-DrOzswl2.js} +1 -1
- package/client/dist/assets/{hls-CnVLfyq6.js → hls-BGCX7mJy.js} +1 -1
- package/client/dist/assets/{index-CQlPx8UA.js → index-CnfZwsds.js} +3 -3
- package/client/dist/assets/{useAnimeInfoData-BRP9E_M5.js → useAnimeInfoData-DYXWBPF-.js} +1 -1
- package/client/dist/assets/{useAsmr-BnqAqfIG.js → useAsmr-BbhQ59bk.js} +1 -1
- package/client/dist/assets/{useAsmrPopup-BJ79NC61.js → useAsmrPopup-SymV1J-b.js} +1 -1
- package/client/dist/assets/{useMangaLibrary-nC3VQT2C.js → useMangaLibrary-DuesWQaE.js} +1 -1
- package/client/dist/assets/{useMangaPopup-CTSFWlC4.js → useMangaPopup-D57OVGUr.js} +1 -1
- package/client/dist/assets/{useProviders-DQLAbAta.js → useProviders-mpQb_dix.js} +1 -1
- package/client/dist/assets/{useTv-BeQG1pC1.js → useTv-DGflCsSI.js} +1 -1
- package/client/dist/assets/{useTvLibrary-F0MkBioA.js → useTvLibrary-BqeSmyc4.js} +1 -1
- package/client/dist/index.html +1 -1
- package/package.json +2 -2
- package/server/dist/app-hono.js +1 -1
- package/server/dist/google.js +1 -1
- package/server/dist/hono/proxy.js +171 -254
- package/server/dist/hono/tv.js +43 -8
- package/server/dist/providers/remote-loader.js +15 -4
- package/server/dist/providers/remote-types.js +35 -0
- package/server/dist/server.js +3 -0
- package/server/dist/trpc/routers/data.js +15 -8
- package/server/package.json +1 -1
- package/client/dist/assets/AvSyncCalibrator-IXHL6uH0.js +0 -10
- package/client/dist/assets/Player-Bww3XdSp.js +0 -56
- package/server/dist/utils/megaplay.utils.js +0 -40
|
@@ -7,7 +7,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
9
|
import { fetchWithRetry, linkAbort, setProxyHeaders } from '../utils/http.utils.js';
|
|
10
|
-
import { MEGAPLAY_ORIGIN, isNexabloomMasterUrl, signNexabloomMasterUrl, } from '../utils/megaplay.utils.js';
|
|
11
10
|
const proxyCache = new AppCache({ ttlSeconds: 30, maxKeys: 500 });
|
|
12
11
|
const imageCache = new AppCache({ ttlSeconds: 86400, maxKeys: 3000 });
|
|
13
12
|
function assTimeToVtt(value) {
|
|
@@ -127,86 +126,45 @@ function rewrapVttCues(vtt) {
|
|
|
127
126
|
})
|
|
128
127
|
.join('\n\n');
|
|
129
128
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
const VAULT_CDN_HOSTS = new Set(['uwucdn.top', 'owocdn.top']);
|
|
133
|
-
const GOT_SCRAPING_HOSTS = new Set([
|
|
134
|
-
'kwik.cx',
|
|
135
|
-
'kwik.si',
|
|
136
|
-
'kwik.pro',
|
|
137
|
-
'animepahe.pw',
|
|
138
|
-
'animepahe.ru',
|
|
139
|
-
]);
|
|
140
|
-
const GOT_SCRAPING_SUFFIXES = [
|
|
141
|
-
'.uwucdn.top',
|
|
142
|
-
'.owocdn.top',
|
|
143
|
-
'.streamzone1.site',
|
|
144
|
-
'.nekostream.site',
|
|
145
|
-
'.nukitashith.top',
|
|
146
|
-
'.aniwatchtv.site',
|
|
147
|
-
];
|
|
148
|
-
const HANIME_HOSTS = new Set(['r2.1hanime.com', '1.1hanime.com']);
|
|
149
|
-
const OPPAI_HOSTS = new Set(['myspacecat.pictures']);
|
|
150
|
-
const KAA_HOSTS = new Set([
|
|
151
|
-
'hls.krussdomi.com',
|
|
152
|
-
'subst.krussdomi.com',
|
|
153
|
-
'krussdomi.com',
|
|
154
|
-
'st1.advancedairesearchlab.xyz',
|
|
155
|
-
'st1.habibikun.xyz',
|
|
156
|
-
'st1.babybayw.xyz',
|
|
157
|
-
'st1.narutokun.xyz',
|
|
158
|
-
]);
|
|
159
|
-
const KAA_REFERER = 'https://krussdomi.com/';
|
|
160
|
-
const KAA_ORIGIN = 'https://krussdomi.com';
|
|
161
|
-
const ANILIGHT_REFERER = 'https://anilight.live/';
|
|
162
|
-
const ANILIGHT_ORIGIN = 'https://anilight.live';
|
|
163
|
-
const ANILIGHT_HOSTS = [
|
|
164
|
-
'anilight.live',
|
|
165
|
-
'api.anilight.live',
|
|
166
|
-
'lostproject.club',
|
|
167
|
-
'streamzone1.site',
|
|
168
|
-
'nukitashi.top',
|
|
169
|
-
'vivibebe.site',
|
|
170
|
-
'vibeplayer.site',
|
|
171
|
-
'aniwatchtv.site',
|
|
172
|
-
];
|
|
173
|
-
const NEXABLOOM_FAMILY_SUFFIXES = [
|
|
174
|
-
'.quavex.top',
|
|
175
|
-
'.nexabloom.top',
|
|
176
|
-
'.hiddenvertex.top',
|
|
177
|
-
'.ironhorizon.top',
|
|
178
|
-
'.solarhaven.top',
|
|
179
|
-
];
|
|
180
|
-
const NEXABLOOM_FALLBACK_HOSTS = ['fetch.nexabloom.top'];
|
|
181
|
-
function nexabloomFallbackUrls(urlStr) {
|
|
182
|
-
let host = '';
|
|
129
|
+
function matchProxyRule(urlStr, rules) {
|
|
130
|
+
let host;
|
|
183
131
|
try {
|
|
184
132
|
host = new URL(urlStr).hostname.toLowerCase();
|
|
185
133
|
}
|
|
186
134
|
catch {
|
|
135
|
+
return undefined;
|
|
136
|
+
}
|
|
137
|
+
return rules.find((r) => r.hosts?.some((h) => h === host) || r.suffixes?.some((s) => host.endsWith(s)));
|
|
138
|
+
}
|
|
139
|
+
function resolveOrigin(rule, referer, queryOrigin) {
|
|
140
|
+
return queryOrigin || (rule?.originFromReferer ? referer : '') || rule?.origin || '';
|
|
141
|
+
}
|
|
142
|
+
function mirrorUrls(urlStr, mirrors) {
|
|
143
|
+
if (!mirrors?.length)
|
|
187
144
|
return [urlStr];
|
|
145
|
+
let current;
|
|
146
|
+
try {
|
|
147
|
+
current = new URL(urlStr);
|
|
188
148
|
}
|
|
189
|
-
|
|
149
|
+
catch {
|
|
190
150
|
return [urlStr];
|
|
191
151
|
}
|
|
152
|
+
const seen = new Set([current.hostname.toLowerCase()]);
|
|
192
153
|
const urls = [urlStr];
|
|
193
|
-
for (const
|
|
194
|
-
|
|
154
|
+
for (const mirror of mirrors) {
|
|
155
|
+
const host = mirror.toLowerCase();
|
|
156
|
+
if (seen.has(host))
|
|
195
157
|
continue;
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
}
|
|
201
|
-
catch {
|
|
202
|
-
// ignore
|
|
203
|
-
}
|
|
158
|
+
seen.add(host);
|
|
159
|
+
const candidate = new URL(current.href);
|
|
160
|
+
candidate.hostname = host;
|
|
161
|
+
urls.push(candidate.href);
|
|
204
162
|
}
|
|
205
163
|
return urls;
|
|
206
164
|
}
|
|
207
|
-
async function
|
|
165
|
+
async function fetchWithMirrors(urlStr, mirrors, signal, attempt, isOk) {
|
|
208
166
|
let last = null;
|
|
209
|
-
for (const candidate of
|
|
167
|
+
for (const candidate of mirrorUrls(urlStr, mirrors)) {
|
|
210
168
|
if (signal.aborted)
|
|
211
169
|
break;
|
|
212
170
|
const r = await attempt(candidate).catch(() => null);
|
|
@@ -218,34 +176,6 @@ async function fetchNexabloomFallback(urlStr, signal, attempt, isOk) {
|
|
|
218
176
|
}
|
|
219
177
|
return last;
|
|
220
178
|
}
|
|
221
|
-
function isGotScrapingHost(urlStr) {
|
|
222
|
-
try {
|
|
223
|
-
const host = new URL(urlStr).hostname.toLowerCase();
|
|
224
|
-
if (GOT_SCRAPING_HOSTS.has(host))
|
|
225
|
-
return true;
|
|
226
|
-
if (HANIME_HOSTS.has(host))
|
|
227
|
-
return true;
|
|
228
|
-
return GOT_SCRAPING_SUFFIXES.some((s) => host.endsWith(s));
|
|
229
|
-
}
|
|
230
|
-
catch {
|
|
231
|
-
return false;
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
function validateKwikUrl(value) {
|
|
235
|
-
if (typeof value !== 'string')
|
|
236
|
-
return null;
|
|
237
|
-
try {
|
|
238
|
-
const url = new URL(value);
|
|
239
|
-
const isSecure = url.protocol === 'https:';
|
|
240
|
-
const isKwik = KWIK_DOMAINS.has(url.hostname.toLowerCase());
|
|
241
|
-
const isEmbedPath = /^\/e\/[A-Za-z0-9_-]+$/.test(url.pathname);
|
|
242
|
-
const noAuthOrQuery = !url.username && !url.password && !url.search && !url.hash;
|
|
243
|
-
return isSecure && isKwik && isEmbedPath && noAuthOrQuery ? url : null;
|
|
244
|
-
}
|
|
245
|
-
catch {
|
|
246
|
-
return null;
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
179
|
function pinVariant(body, idx) {
|
|
250
180
|
const lines = body.split('\n');
|
|
251
181
|
let streamIdx = -1;
|
|
@@ -270,16 +200,16 @@ function pinVariant(body, idx) {
|
|
|
270
200
|
return null;
|
|
271
201
|
return out.join('\n');
|
|
272
202
|
}
|
|
273
|
-
function
|
|
274
|
-
const safeReferer = JSON.stringify(
|
|
203
|
+
function applyEmbedPatches(html, embedUrl, cookieStr = '') {
|
|
204
|
+
const safeReferer = JSON.stringify(embedUrl.href).replace(/</g, '\\u003c');
|
|
275
205
|
const safeCookie = JSON.stringify(cookieStr).replace(/</g, '\\u003c');
|
|
276
206
|
const patched = html.replace(/\b(src|href|url)\s*[=:]\s*(["']?)(\/\/[^"'>)]+|\/(?!\/)[^"'>)]*)\2/gi, (match, attr, quote, path) => {
|
|
277
|
-
const full = path.startsWith('//') ? `https:${path}` : `${
|
|
207
|
+
const full = path.startsWith('//') ? `https:${path}` : `${embedUrl.origin}${path}`;
|
|
278
208
|
const prefix = match.startsWith('url') ? `url(` : `${attr}=`;
|
|
279
209
|
const suffix = match.startsWith('url') ? `)` : '';
|
|
280
210
|
return `${prefix}${quote}${full}${quote}${suffix}`;
|
|
281
211
|
});
|
|
282
|
-
const iconProxy = `/api/proxy?url=${encodeURIComponent(`${
|
|
212
|
+
const iconProxy = `/api/proxy?url=${encodeURIComponent(`${embedUrl.origin}/app/js/vendor/plyr.svg`)}&referer=${encodeURIComponent(embedUrl.href)}`;
|
|
283
213
|
const plyrPatch = `<script>if(window.Plyr) Plyr.defaults.iconUrl=${JSON.stringify(iconProxy).replace(/</g, '\\u003c')};</script>`;
|
|
284
214
|
const hlsPatch = `<script>
|
|
285
215
|
(function() {
|
|
@@ -358,7 +288,7 @@ function placeholderSvg() {
|
|
|
358
288
|
return null;
|
|
359
289
|
}
|
|
360
290
|
const PROXY_UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:126.0) Gecko/20100101 Firefox/126.0';
|
|
361
|
-
export function registerProxy(app) {
|
|
291
|
+
export function registerProxy(app, getProxyRules) {
|
|
362
292
|
app.get('/api/proxy', async (c) => {
|
|
363
293
|
const url = c.req.query('url');
|
|
364
294
|
if (!url)
|
|
@@ -368,97 +298,63 @@ export function registerProxy(app) {
|
|
|
368
298
|
return c.text(safeCheck.error || 'Invalid URL', 400);
|
|
369
299
|
}
|
|
370
300
|
const urlStr = url;
|
|
371
|
-
const
|
|
301
|
+
const rule = matchProxyRule(urlStr, getProxyRules());
|
|
302
|
+
const refererStr = c.req.query('referer') || rule?.referer || '';
|
|
303
|
+
const originStr = resolveOrigin(rule, refererStr, c.req.query('origin'));
|
|
304
|
+
const uaStr = c.req.query('ua') || '';
|
|
372
305
|
const cookieStr = c.req.query('cookie') || '';
|
|
306
|
+
const useScrape = c.req.query('scrape') === '1' || rule?.scrape === true;
|
|
307
|
+
const useCf = c.req.query('cf') === '1' || rule?.cfCookie === true;
|
|
373
308
|
const variantRaw = c.req.query('variant');
|
|
374
309
|
const variantIdx = variantRaw !== undefined ? parseInt(variantRaw, 10) : NaN;
|
|
375
310
|
const pinVar = Number.isInteger(variantIdx) && variantIdx >= 0;
|
|
376
|
-
const cacheKey = `m3u8-${urlStr}-${refererStr}${pinVar ? `-v${variantIdx}` : ''}`;
|
|
311
|
+
const cacheKey = `m3u8-${urlStr}-${refererStr}-${originStr}-${uaStr}${pinVar ? `-v${variantIdx}` : ''}`;
|
|
377
312
|
const abort = new AbortController();
|
|
378
313
|
linkAbort(c.req.raw.signal, abort);
|
|
379
314
|
try {
|
|
380
|
-
const isVaultCdn = Array.from(VAULT_CDN_HOSTS).some((host) => urlStr.includes(host));
|
|
381
|
-
const isHanime = Array.from(HANIME_HOSTS).some((host) => urlStr.includes(host));
|
|
382
|
-
const isOppai = Array.from(OPPAI_HOSTS).some((host) => urlStr.includes(host));
|
|
383
|
-
const isKaa = refererStr.startsWith(KAA_ORIGIN) ||
|
|
384
|
-
Array.from(KAA_HOSTS).some((host) => urlStr.includes(host));
|
|
385
315
|
const headers = {
|
|
386
|
-
'User-Agent': PROXY_UA,
|
|
316
|
+
'User-Agent': uaStr || PROXY_UA,
|
|
387
317
|
};
|
|
388
318
|
if (refererStr)
|
|
389
319
|
headers['Referer'] = refererStr;
|
|
390
|
-
if (
|
|
391
|
-
headers['Origin'] =
|
|
392
|
-
|
|
320
|
+
if (originStr)
|
|
321
|
+
headers['Origin'] = originStr;
|
|
322
|
+
if (cookieStr) {
|
|
323
|
+
const cookieHeader = useCf ? buildCfClearanceCookie(cookieStr) : cookieStr;
|
|
393
324
|
if (cookieHeader)
|
|
394
325
|
headers['Cookie'] = cookieHeader;
|
|
395
326
|
}
|
|
396
|
-
if (isHanime) {
|
|
397
|
-
if (!headers['Referer'])
|
|
398
|
-
headers['Referer'] = refererStr || 'https://nhplayer.com/';
|
|
399
|
-
headers['Origin'] = 'https://nhplayer.com';
|
|
400
|
-
}
|
|
401
|
-
if (isOppai) {
|
|
402
|
-
if (!headers['Referer'])
|
|
403
|
-
headers['Referer'] = refererStr || 'https://oppai.stream/';
|
|
404
|
-
headers['Origin'] = 'https://oppai.stream';
|
|
405
|
-
}
|
|
406
|
-
if (isKaa) {
|
|
407
|
-
if (!headers['Referer'])
|
|
408
|
-
headers['Referer'] = refererStr || KAA_REFERER;
|
|
409
|
-
headers['Origin'] = KAA_ORIGIN;
|
|
410
|
-
}
|
|
411
|
-
const isMegaplay = refererStr.startsWith(MEGAPLAY_ORIGIN) || urlStr.includes('nexabloom.top');
|
|
412
|
-
if (isMegaplay && !headers['Origin']) {
|
|
413
|
-
headers['Origin'] = MEGAPLAY_ORIGIN;
|
|
414
|
-
}
|
|
415
|
-
if (urlStr.includes('weeabo0.xyz') || urlStr.includes('weeab0o.xyz')) {
|
|
416
|
-
if (!headers['Referer'])
|
|
417
|
-
headers['Referer'] = refererStr || 'https://japaneseasmr.com/';
|
|
418
|
-
const jasmrCookieHeader = buildCfClearanceCookie(cookieStr);
|
|
419
|
-
if (jasmrCookieHeader)
|
|
420
|
-
headers['Cookie'] = jasmrCookieHeader;
|
|
421
|
-
const jasmrUa = c.req.query('ua') || '';
|
|
422
|
-
if (jasmrUa)
|
|
423
|
-
headers['User-Agent'] = jasmrUa;
|
|
424
|
-
}
|
|
425
327
|
const range = c.req.header('range');
|
|
426
328
|
if (range)
|
|
427
329
|
headers['Range'] = range;
|
|
428
|
-
|
|
429
|
-
const
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
}
|
|
436
|
-
const resp = await fetchNexabloomFallback(urlStr, abort.signal, (candidate) => gotScraping({
|
|
437
|
-
url: isNexabloomMasterUrl(candidate) ? signNexabloomMasterUrl(candidate) : candidate,
|
|
438
|
-
method: 'GET',
|
|
439
|
-
headers,
|
|
440
|
-
responseType: 'text',
|
|
441
|
-
timeout: { request: 30000 },
|
|
442
|
-
followRedirect: true,
|
|
443
|
-
throwHttpErrors: false,
|
|
444
|
-
}), (attempt) => attempt.statusCode === 200 || attempt.statusCode === 206);
|
|
445
|
-
if (!resp || (resp.statusCode !== 200 && resp.statusCode !== 206)) {
|
|
446
|
-
return c.text('Upstream error', (resp?.statusCode || 502));
|
|
447
|
-
}
|
|
448
|
-
const finalUrl = resp.url || urlStr;
|
|
330
|
+
const playlistResponse = (body) => {
|
|
331
|
+
const outHeaders = new Headers();
|
|
332
|
+
outHeaders.set('Content-Type', 'application/vnd.apple.mpegurl');
|
|
333
|
+
outHeaders.set('Access-Control-Allow-Origin', '*');
|
|
334
|
+
return new Response(body, { headers: outHeaders });
|
|
335
|
+
};
|
|
336
|
+
const rewritePlaylist = (playlistBody, finalUrl) => {
|
|
449
337
|
const baseUrl = new URL(finalUrl);
|
|
450
|
-
const proxiedMediaUrl = (targetUrl) =>
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
338
|
+
const proxiedMediaUrl = (targetUrl) => {
|
|
339
|
+
const params = new URLSearchParams({ url: targetUrl });
|
|
340
|
+
if (refererStr)
|
|
341
|
+
params.set('referer', refererStr);
|
|
342
|
+
if (originStr)
|
|
343
|
+
params.set('origin', originStr);
|
|
344
|
+
if (uaStr)
|
|
345
|
+
params.set('ua', uaStr);
|
|
346
|
+
if (cookieStr)
|
|
347
|
+
params.set('cookie', sanitizeCfClearance(cookieStr));
|
|
348
|
+
if (useScrape)
|
|
349
|
+
params.set('scrape', '1');
|
|
350
|
+
if (useCf)
|
|
351
|
+
params.set('cf', '1');
|
|
352
|
+
return `/api/proxy?${params}`;
|
|
353
|
+
};
|
|
354
|
+
const needsProxy = Boolean(refererStr || originStr || uaStr || useScrape);
|
|
454
355
|
const isProxied = (value) => value.includes('/api/proxy');
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
const pinned = pinVariant(playlistBody, variantIdx);
|
|
458
|
-
if (pinned)
|
|
459
|
-
playlistBody = pinned;
|
|
460
|
-
}
|
|
461
|
-
const rewritten = playlistBody
|
|
356
|
+
const pinned = pinVar ? pinVariant(playlistBody, variantIdx) : null;
|
|
357
|
+
return (pinned || playlistBody)
|
|
462
358
|
.split('\n')
|
|
463
359
|
.map((line) => {
|
|
464
360
|
const trimmed = line.trim();
|
|
@@ -478,17 +374,36 @@ export function registerProxy(app) {
|
|
|
478
374
|
return proxiedMediaUrl(absolute);
|
|
479
375
|
})
|
|
480
376
|
.join('\n');
|
|
377
|
+
};
|
|
378
|
+
if (urlStr.includes('.m3u8')) {
|
|
379
|
+
const cached = proxyCache.get(cacheKey);
|
|
380
|
+
if (cached) {
|
|
381
|
+
const headers = new Headers();
|
|
382
|
+
headers.set('Content-Type', 'application/vnd.apple.mpegurl');
|
|
383
|
+
headers.set('Access-Control-Allow-Origin', '*');
|
|
384
|
+
return new Response(cached, { headers });
|
|
385
|
+
}
|
|
386
|
+
const resp = await fetchWithMirrors(urlStr, rule?.mirrors, abort.signal, (candidate) => gotScraping({
|
|
387
|
+
url: candidate,
|
|
388
|
+
method: 'GET',
|
|
389
|
+
headers,
|
|
390
|
+
responseType: 'text',
|
|
391
|
+
timeout: { request: 30000 },
|
|
392
|
+
followRedirect: true,
|
|
393
|
+
throwHttpErrors: false,
|
|
394
|
+
}), (attempt) => attempt.statusCode === 200 || attempt.statusCode === 206);
|
|
395
|
+
if (!resp || (resp.statusCode !== 200 && resp.statusCode !== 206)) {
|
|
396
|
+
return c.text('Upstream error', (resp?.statusCode || 502));
|
|
397
|
+
}
|
|
398
|
+
const rewritten = rewritePlaylist(resp.body, resp.url || urlStr);
|
|
481
399
|
proxyCache.set(cacheKey, rewritten);
|
|
482
|
-
|
|
483
|
-
outHeaders.set('Content-Type', 'application/vnd.apple.mpegurl');
|
|
484
|
-
outHeaders.set('Access-Control-Allow-Origin', '*');
|
|
485
|
-
return new Response(rewritten, { headers: outHeaders });
|
|
400
|
+
return playlistResponse(rewritten);
|
|
486
401
|
}
|
|
487
402
|
else {
|
|
488
|
-
if (
|
|
403
|
+
if (useScrape) {
|
|
489
404
|
if (range)
|
|
490
405
|
headers['Range'] = range;
|
|
491
|
-
const resp = await
|
|
406
|
+
const resp = await fetchWithMirrors(urlStr, rule?.mirrors, abort.signal, (candidate) => gotScraping({
|
|
492
407
|
url: candidate,
|
|
493
408
|
method: 'GET',
|
|
494
409
|
headers,
|
|
@@ -502,6 +417,15 @@ export function registerProxy(app) {
|
|
|
502
417
|
}
|
|
503
418
|
const ct = resp.headers['content-type'];
|
|
504
419
|
const isVtt = urlStr.endsWith('.vtt') || urlStr.includes('.vtt?');
|
|
420
|
+
const bodyText = String(resp.body ?? '');
|
|
421
|
+
if (!isVtt &&
|
|
422
|
+
((typeof ct === 'string' && ct.includes('mpegurl')) || bodyText.startsWith('#EXTM3U'))) {
|
|
423
|
+
const cached = proxyCache.get(cacheKey);
|
|
424
|
+
const rewritten = cached ?? rewritePlaylist(bodyText, resp.url || urlStr);
|
|
425
|
+
if (!cached)
|
|
426
|
+
proxyCache.set(cacheKey, rewritten);
|
|
427
|
+
return playlistResponse(rewritten);
|
|
428
|
+
}
|
|
505
429
|
const outHeaders = new Headers();
|
|
506
430
|
outHeaders.set('Content-Type', isVtt ? 'text/vtt; charset=utf-8' : ct || 'application/octet-stream');
|
|
507
431
|
setProxyHeaders(outHeaders, (name) => resp.headers[name]);
|
|
@@ -511,7 +435,7 @@ export function registerProxy(app) {
|
|
|
511
435
|
});
|
|
512
436
|
}
|
|
513
437
|
else {
|
|
514
|
-
const upstream = await
|
|
438
|
+
const upstream = await fetchWithMirrors(urlStr, rule?.mirrors, abort.signal, async (candidate) => {
|
|
515
439
|
const attempt = await fetchWithRetry(candidate, { method: 'GET', headers }, { retries: 3, timeoutMs: 30000, signal: abort.signal });
|
|
516
440
|
if (attempt.status !== 200 && attempt.status !== 206) {
|
|
517
441
|
try {
|
|
@@ -556,25 +480,36 @@ export function registerProxy(app) {
|
|
|
556
480
|
}
|
|
557
481
|
});
|
|
558
482
|
app.get('/api/embed-proxy', async (c) => {
|
|
559
|
-
const
|
|
560
|
-
if (!
|
|
483
|
+
const rawUrl = c.req.query('url');
|
|
484
|
+
if (!rawUrl)
|
|
485
|
+
return c.text('URL required', 400);
|
|
486
|
+
const safeCheck = isSafeExternalUrl(rawUrl);
|
|
487
|
+
if (!safeCheck.safe)
|
|
488
|
+
return c.text(safeCheck.error || 'Invalid URL', 400);
|
|
489
|
+
const rule = matchProxyRule(rawUrl, getProxyRules());
|
|
490
|
+
if (!rule?.embedPatch)
|
|
561
491
|
return c.text('Invalid or unsupported gateway URL', 400);
|
|
492
|
+
const embedUrl = new URL(rawUrl);
|
|
562
493
|
const cookieStr = c.req.query('cookie') || '';
|
|
494
|
+
const refererStr = c.req.query('referer') || rule.referer || '';
|
|
495
|
+
const originStr = resolveOrigin(rule, refererStr, c.req.query('origin'));
|
|
563
496
|
const abort = new AbortController();
|
|
564
497
|
linkAbort(c.req.raw.signal, abort);
|
|
565
498
|
try {
|
|
566
499
|
const headers = {
|
|
567
|
-
'User-Agent': PROXY_UA,
|
|
568
|
-
Referer: ANIMEPAHE_URL,
|
|
569
|
-
Origin: 'https://animepahe.pw',
|
|
500
|
+
'User-Agent': c.req.query('ua') || PROXY_UA,
|
|
570
501
|
};
|
|
502
|
+
if (refererStr)
|
|
503
|
+
headers['Referer'] = refererStr;
|
|
504
|
+
if (originStr)
|
|
505
|
+
headers['Origin'] = originStr;
|
|
571
506
|
if (cookieStr) {
|
|
572
|
-
const cookieHeader = buildCfClearanceCookie(cookieStr);
|
|
507
|
+
const cookieHeader = c.req.query('cf') === '1' || rule.cfCookie ? buildCfClearanceCookie(cookieStr) : cookieStr;
|
|
573
508
|
if (cookieHeader)
|
|
574
509
|
headers['Cookie'] = cookieHeader;
|
|
575
510
|
}
|
|
576
511
|
const response = await gotScraping({
|
|
577
|
-
url:
|
|
512
|
+
url: embedUrl.href,
|
|
578
513
|
method: 'GET',
|
|
579
514
|
headers,
|
|
580
515
|
responseType: 'text',
|
|
@@ -583,7 +518,7 @@ export function registerProxy(app) {
|
|
|
583
518
|
throwHttpErrors: false,
|
|
584
519
|
});
|
|
585
520
|
const originalHtml = response.body;
|
|
586
|
-
const patched =
|
|
521
|
+
const patched = applyEmbedPatches(originalHtml, embedUrl, cookieStr);
|
|
587
522
|
if (!patched)
|
|
588
523
|
return c.text('Failed to patch video gateway', 502);
|
|
589
524
|
const outHeaders = new Headers();
|
|
@@ -608,49 +543,51 @@ export function registerProxy(app) {
|
|
|
608
543
|
const abort = new AbortController();
|
|
609
544
|
linkAbort(c.req.raw.signal, abort);
|
|
610
545
|
const subUrl = url;
|
|
611
|
-
const
|
|
612
|
-
const
|
|
613
|
-
const
|
|
614
|
-
|
|
615
|
-
const parsed = new URL(subUrl);
|
|
616
|
-
if (parsed.hostname.toLowerCase().endsWith('anilight.live') &&
|
|
617
|
-
parsed.pathname.includes('/proxy/captions')) {
|
|
618
|
-
const inner = parsed.searchParams.get('url');
|
|
619
|
-
if (inner && (inner.startsWith('http://') || inner.startsWith('https://'))) {
|
|
620
|
-
return inner;
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
catch {
|
|
625
|
-
// ignore
|
|
626
|
-
}
|
|
627
|
-
return null;
|
|
628
|
-
})();
|
|
546
|
+
const rule = matchProxyRule(subUrl, getProxyRules());
|
|
547
|
+
const refererStr = c.req.query('referer') || rule?.referer || '';
|
|
548
|
+
const originStr = resolveOrigin(rule, refererStr, c.req.query('origin'));
|
|
549
|
+
const useScrape = c.req.query('scrape') === '1' || rule?.scrape === true;
|
|
629
550
|
const buildHeaders = () => {
|
|
630
551
|
const headers = {
|
|
631
552
|
'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',
|
|
632
553
|
Accept: 'text/vtt,text/plain,*/*;q=0.8',
|
|
633
554
|
};
|
|
634
|
-
if (refererStr)
|
|
555
|
+
if (refererStr)
|
|
635
556
|
headers['Referer'] = refererStr;
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
headers['Referer'] = ANILIGHT_REFERER;
|
|
639
|
-
}
|
|
640
|
-
if (refererStr.startsWith(KAA_ORIGIN) ||
|
|
641
|
-
Array.from(KAA_HOSTS).some((host) => subUrl.includes(host))) {
|
|
642
|
-
headers['Origin'] = KAA_ORIGIN;
|
|
643
|
-
}
|
|
644
|
-
else if (isAnilight) {
|
|
645
|
-
headers['Origin'] = ANILIGHT_ORIGIN;
|
|
646
|
-
}
|
|
557
|
+
if (originStr)
|
|
558
|
+
headers['Origin'] = originStr;
|
|
647
559
|
return headers;
|
|
648
560
|
};
|
|
649
561
|
const fetchText = async (target, retries = 1) => {
|
|
650
562
|
const headers = buildHeaders();
|
|
651
|
-
if (
|
|
652
|
-
|
|
653
|
-
|
|
563
|
+
if (useScrape) {
|
|
564
|
+
try {
|
|
565
|
+
const upstream = await gotScraping({
|
|
566
|
+
url: target,
|
|
567
|
+
method: 'GET',
|
|
568
|
+
headers,
|
|
569
|
+
responseType: 'text',
|
|
570
|
+
timeout: { request: 15000 },
|
|
571
|
+
followRedirect: true,
|
|
572
|
+
throwHttpErrors: false,
|
|
573
|
+
});
|
|
574
|
+
if (upstream.statusCode !== 200) {
|
|
575
|
+
logger.warn({ target, status: upstream.statusCode }, '[subtitle-proxy] upstream returned error status');
|
|
576
|
+
return { status: upstream.statusCode, body: '' };
|
|
577
|
+
}
|
|
578
|
+
const body = String(upstream.body ?? '').replace(/^\uFEFF/, '');
|
|
579
|
+
if (/^\s*\{[\s\S]*"error"[\s\S]*\}\s*$/.test(body.slice(0, 500))) {
|
|
580
|
+
logger.warn({ target, body: body.slice(0, 200) }, '[subtitle-proxy] upstream JSON error');
|
|
581
|
+
return { status: 502, body: '' };
|
|
582
|
+
}
|
|
583
|
+
return { status: 200, body };
|
|
584
|
+
}
|
|
585
|
+
catch (e) {
|
|
586
|
+
if (abort.signal.aborted)
|
|
587
|
+
return null;
|
|
588
|
+
logger.warn({ target, error: e?.message }, '[subtitle-proxy] upstream fetch failed');
|
|
589
|
+
return { status: 502, body: '' };
|
|
590
|
+
}
|
|
654
591
|
}
|
|
655
592
|
try {
|
|
656
593
|
const upstream = await fetchWithRetry(target, { method: 'GET', headers }, { retries, timeoutMs: 15000, signal: abort.signal });
|
|
@@ -693,19 +630,14 @@ export function registerProxy(app) {
|
|
|
693
630
|
};
|
|
694
631
|
try {
|
|
695
632
|
const headers = buildHeaders();
|
|
696
|
-
|
|
633
|
+
const result = await fetchText(subUrl, 1);
|
|
697
634
|
if (abort.signal.aborted)
|
|
698
635
|
return new Response(null, { status: 499 });
|
|
699
|
-
if ((!result || !result.body) && innerRawUrl) {
|
|
700
|
-
result = await fetchText(innerRawUrl, 0);
|
|
701
|
-
if (abort.signal.aborted)
|
|
702
|
-
return new Response(null, { status: 499 });
|
|
703
|
-
}
|
|
704
636
|
if (!result || !result.body) {
|
|
705
637
|
return subtitleError('Subtitle upstream error');
|
|
706
638
|
}
|
|
707
639
|
const body = result.body;
|
|
708
|
-
const baseForSegments =
|
|
640
|
+
const baseForSegments = subUrl;
|
|
709
641
|
if (/#EXTM3U/i.test(body.slice(0, 1000))) {
|
|
710
642
|
const segUrls = body
|
|
711
643
|
.split('\n')
|
|
@@ -786,14 +718,16 @@ export function registerProxy(app) {
|
|
|
786
718
|
const cookie = c.req.query('cookie') || '';
|
|
787
719
|
const ua = c.req.query('ua') || '';
|
|
788
720
|
const strict = c.req.query('strict') === '1';
|
|
721
|
+
const rule = matchProxyRule(targetUrl, getProxyRules());
|
|
722
|
+
const refererValue = c.req.query('referer') || rule?.referer || '';
|
|
723
|
+
const originValue = resolveOrigin(rule, refererValue, c.req.query('origin'));
|
|
789
724
|
const abort = new AbortController();
|
|
790
725
|
linkAbort(c.req.raw.signal, abort);
|
|
791
|
-
let refererValue = 'https://anilist.co/';
|
|
792
726
|
const headers = {
|
|
793
727
|
'User-Agent': ua ||
|
|
794
728
|
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
|
795
729
|
};
|
|
796
|
-
const cacheKey = `${targetUrl}|${cookie}|${ua}`;
|
|
730
|
+
const cacheKey = `${targetUrl}|${cookie}|${ua}|${refererValue}|${originValue}`;
|
|
797
731
|
const cached = imageCache.get(cacheKey);
|
|
798
732
|
if (cached) {
|
|
799
733
|
const hit = new Headers();
|
|
@@ -804,32 +738,15 @@ export function registerProxy(app) {
|
|
|
804
738
|
return new Response(cached.body, { headers: hit });
|
|
805
739
|
}
|
|
806
740
|
try {
|
|
807
|
-
if (
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
refererValue = 'https://gogoanime.lu/';
|
|
816
|
-
}
|
|
817
|
-
else if (targetUrl.includes('animeya.cc')) {
|
|
818
|
-
refererValue = 'https://animeya.cc/';
|
|
819
|
-
}
|
|
820
|
-
else if (targetUrl.includes('myspacecat.pictures')) {
|
|
821
|
-
refererValue = 'https://oppai.stream/';
|
|
822
|
-
}
|
|
823
|
-
else if (targetUrl.includes('weeabo0.xyz')) {
|
|
824
|
-
refererValue = 'https://japaneseasmr.com/';
|
|
825
|
-
const jasmrCookieHeader = buildCfClearanceCookie(cookie);
|
|
826
|
-
if (jasmrCookieHeader)
|
|
827
|
-
headers['Cookie'] = jasmrCookieHeader;
|
|
828
|
-
const uaParam = ua || '';
|
|
829
|
-
if (uaParam)
|
|
830
|
-
headers['User-Agent'] = uaParam;
|
|
741
|
+
if (refererValue)
|
|
742
|
+
headers['Referer'] = refererValue;
|
|
743
|
+
if (originValue)
|
|
744
|
+
headers['Origin'] = originValue;
|
|
745
|
+
if (c.req.query('cf') === '1' || rule?.cfCookie) {
|
|
746
|
+
const cookieHeader = buildCfClearanceCookie(cookie);
|
|
747
|
+
if (cookieHeader)
|
|
748
|
+
headers['Cookie'] = cookieHeader;
|
|
831
749
|
}
|
|
832
|
-
headers['Referer'] = refererValue;
|
|
833
750
|
let body = null;
|
|
834
751
|
let contentType = 'image/webp';
|
|
835
752
|
for (let attempt = 0; attempt < 3 && !body; attempt++) {
|