@serifpersia/dango 3.6.5 → 3.6.8
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-JkXG40Es.js → AnimeCard-DraA_TF9.js} +1 -1
- package/client/dist/assets/{AnimeInfo-2L0UJkbB.js → AnimeInfo-BJ7pzs-P.js} +1 -1
- package/client/dist/assets/{AnimeInfoPage-BDs03vKk.js → AnimeInfoPage-B_esUr5C.js} +2 -2
- package/client/dist/assets/{Asmr-D2mVKEl9.js → Asmr-Ckuh2xfW.js} +1 -1
- package/client/dist/assets/{AsmrResetProgressModal-MQA_bNQM.js → AsmrResetProgressModal-BS6UFJN4.js} +1 -1
- package/client/dist/assets/AvSyncCalibrator-DwHCd9yB.js +10 -0
- package/client/dist/assets/{Home-DxMBHFmc.js → Home-DuM1EuvN.js} +1 -1
- package/client/dist/assets/{Insights-hmLWsSXY.js → Insights-D3Pd9c9V.js} +1 -1
- package/client/dist/assets/{LibraryListPage-CWI7hgUh.js → LibraryListPage-Dm2yVn0-.js} +1 -1
- package/client/dist/assets/{ListeningList-NOc28KAV.js → ListeningList-D-zt6EXZ.js} +1 -1
- package/client/dist/assets/{Manga-B5qNhI7v.js → Manga-JwprMKBk.js} +1 -1
- package/client/dist/assets/{MangaInfoPage-D4wIyb-V.js → MangaInfoPage-DaeyJl2p.js} +1 -1
- package/client/dist/assets/{MangaReadPage-Ce1VX7-0.js → MangaReadPage-COg0fHI9.js} +1 -1
- package/client/dist/assets/{MangaResetProgressModal-P3oY4_14.js → MangaResetProgressModal-kguXv0_M.js} +1 -1
- package/client/dist/assets/{Mature-D8EwGYQq.js → Mature-IHDaasrs.js} +1 -1
- package/client/dist/assets/{MediaCard-Dp8DjyXB.js → MediaCard-CpwsrXo5.js} +1 -1
- package/client/dist/assets/{Music-DULtVsAM.js → Music-Dks4tPeK.js} +1 -1
- package/client/dist/assets/{OptionListMenu-fIHdE-nq.js → OptionListMenu-11CcAsy8.js} +1 -1
- package/client/dist/assets/{Player-B8XeOmso.js → Player-DQoEQkHL.js} +6 -6
- package/client/dist/assets/{PlayerSettings-BycNCiCs.js → PlayerSettings-D4Ud4kv4.js} +1 -1
- package/client/dist/assets/{QueueOptionsButton-T1J37IZT.js → QueueOptionsButton-B8PlPKjC.js} +1 -1
- package/client/dist/assets/{QueueRail-BAwI-apz.js → QueueRail-B4Dok7U3.js} +1 -1
- package/client/dist/assets/{Radio-Bv9DyKtZ.js → Radio-BHK6LSk4.js} +1 -1
- package/client/dist/assets/{ReadingList-Cl0CcVp3.js → ReadingList-BCsxIz57.js} +1 -1
- package/client/dist/assets/{ResetProgressModal--N60WWCB.js → ResetProgressModal-BXHxSfy-.js} +1 -1
- package/client/dist/assets/{Search-DvgVDlUR.js → Search-PK1ds07O.js} +1 -1
- package/client/dist/assets/{Settings-dCz_-bJn.js → Settings-Bxk_x55l.js} +1 -1
- package/client/dist/assets/{SkeletonGrid-BZHKk1I6.js → SkeletonGrid-CpJCZSRN.js} +1 -1
- package/client/dist/assets/{Trackers-C1T5VcPu.js → Trackers-RieCh_84.js} +1 -1
- package/client/dist/assets/{Tv-Dhv_cg4y.js → Tv-DLytPJUj.js} +4 -4
- package/client/dist/assets/{TvInfo-DgaLK2H6.js → TvInfo-C8djKebR.js} +1 -1
- package/client/dist/assets/{TvPopup-DyX7f8kC.js → TvPopup-40JGGJDk.js} +1 -1
- package/client/dist/assets/{TvSearch-Bp0H6kue.js → TvSearch-A3hbospf.js} +1 -1
- package/client/dist/assets/{TvWatchlist-ohMtMeKa.js → TvWatchlist-DuXjqmZM.js} +1 -1
- package/client/dist/assets/{Watchlist-BbQtzsER.js → Watchlist-B9wGBh0u.js} +1 -1
- package/client/dist/assets/{hls-Bi1cjpLZ.js → hls-CZ5X3Ya-.js} +1 -1
- package/client/dist/assets/{index-BTnhzofk.js → index-dRhc9-ol.js} +2 -2
- package/client/dist/assets/{useAnimeInfoData-Cvmso3IK.js → useAnimeInfoData-ZzDtLfvD.js} +1 -1
- package/client/dist/assets/{useAsmr-CbzGCzmv.js → useAsmr-CKOdHgol.js} +1 -1
- package/client/dist/assets/{useAsmrPopup-DaREZ9Ie.js → useAsmrPopup-SFHPhpcl.js} +1 -1
- package/client/dist/assets/{useMangaLibrary-BVQRhsFh.js → useMangaLibrary-CKATe31P.js} +1 -1
- package/client/dist/assets/{useMangaPopup-CzsCbndG.js → useMangaPopup-C75QuYgY.js} +1 -1
- package/client/dist/assets/{useProviders-UuUPzft-.js → useProviders-CMtvZbEi.js} +1 -1
- package/client/dist/assets/{useTv-CSsbWKH9.js → useTv-DUX4zYMr.js} +1 -1
- package/client/dist/assets/{useTvLibrary-a_Y0c2FS.js → useTvLibrary-By4Gt-R_.js} +1 -1
- package/client/dist/index.html +1 -1
- package/package.json +2 -2
- package/server/dist/app-auth.js +1 -157
- package/server/dist/app-hono.js +7 -557
- package/server/dist/config.js +1 -174
- package/server/dist/db/drizzle.js +1 -16
- package/server/dist/db/schema-anime.js +1 -61
- package/server/dist/db/schema-asmr.js +1 -13
- package/server/dist/db/schema-manga.js +1 -31
- package/server/dist/db/schema-tv.js +1 -18
- package/server/dist/db.js +1 -217
- package/server/dist/discord-gateway.js +1 -385
- package/server/dist/discord-rpc.js +1 -413
- package/server/dist/github-sync.js +1 -508
- package/server/dist/google.js +1 -741
- package/server/dist/hono/asmr.js +1 -10
- package/server/dist/hono/auth.js +3 -26
- package/server/dist/hono/music.js +1 -209
- package/server/dist/hono/proxy.js +36 -905
- package/server/dist/hono/tv.js +5 -169
- package/server/dist/hono/watchlist.js +2 -338
- package/server/dist/lib/anilist.js +26 -1458
- package/server/dist/lib/anischedule.js +1 -191
- package/server/dist/lib/insights.js +1 -185
- package/server/dist/lib/ipc.js +1 -23
- package/server/dist/lib/kitsu.js +1 -517
- package/server/dist/lib/mal-import.js +1 -291
- package/server/dist/lib/mal.js +1 -957
- package/server/dist/lib/migration.js +1 -310
- package/server/dist/lib/offline-db.js +4 -384
- package/server/dist/lib/potoken.js +1 -135
- package/server/dist/lib/recommendations.service.js +1 -216
- package/server/dist/lib/temp-ids.js +1 -3
- package/server/dist/lib/tmdb.js +1 -72
- package/server/dist/lib/tracker/anilist-tracker.js +8 -245
- package/server/dist/lib/tracker/manga-sync.service.js +1 -773
- package/server/dist/lib/tracker/sync-state.js +1 -21
- package/server/dist/lib/tracker/sync.service.js +1 -476
- package/server/dist/lib/translate.js +3 -79
- package/server/dist/lib/ump.js +1 -106
- package/server/dist/lib/ytmusic.js +1 -162
- package/server/dist/logger.js +1 -16
- package/server/dist/providers/manga/manga.types.js +0 -1
- package/server/dist/providers/provider.interface.js +0 -1
- package/server/dist/providers/remote-loader.js +2 -605
- package/server/dist/providers/remote-types.js +1 -124
- package/server/dist/providers/title-matching.js +1 -226
- package/server/dist/providers/tv.types.js +0 -1
- package/server/dist/rclone.js +2 -120
- package/server/dist/repositories/asmr.repository.js +11 -46
- package/server/dist/repositories/insights.repository.js +11 -25
- package/server/dist/repositories/library.repository.js +3 -94
- package/server/dist/repositories/mal-cache.repository.js +1 -52
- package/server/dist/repositories/manga.repository.js +14 -58
- package/server/dist/repositories/notifications.repository.js +6 -52
- package/server/dist/repositories/queue.repository.js +5 -58
- package/server/dist/repositories/settings.repository.js +2 -29
- package/server/dist/repositories/shows-meta.repository.js +3 -56
- package/server/dist/repositories/temp-show-ids.repository.js +3 -63
- package/server/dist/repositories/tv.repository.js +11 -54
- package/server/dist/repositories/watched-episodes.repository.js +6 -55
- package/server/dist/repositories/watchlist.repository.js +3 -54
- package/server/dist/scripts/backfill-episode-duration.js +1 -42
- package/server/dist/server.js +1 -307
- package/server/dist/shipped-defaults.js +1 -6
- package/server/dist/sync-payload.js +1 -91
- package/server/dist/sync.js +3 -647
- package/server/dist/trpc/context.js +1 -7
- package/server/dist/trpc/errors.js +1 -15
- package/server/dist/trpc/index.js +1 -18
- package/server/dist/trpc/router.js +1 -49
- package/server/dist/trpc/routers/asmr-library.js +1 -112
- package/server/dist/trpc/routers/asmr-progress.js +1 -121
- package/server/dist/trpc/routers/asmr.js +1 -87
- package/server/dist/trpc/routers/auth.js +1 -194
- package/server/dist/trpc/routers/continue-watching.js +2 -58
- package/server/dist/trpc/routers/data.js +1 -1026
- package/server/dist/trpc/routers/discord.js +1 -285
- package/server/dist/trpc/routers/discovery.js +1 -11
- package/server/dist/trpc/routers/insights.js +1 -14
- package/server/dist/trpc/routers/manga-library.js +1 -263
- package/server/dist/trpc/routers/manga-progress.js +1 -169
- package/server/dist/trpc/routers/manga.js +1 -201
- package/server/dist/trpc/routers/music.js +1 -655
- package/server/dist/trpc/routers/notifications.js +1 -80
- package/server/dist/trpc/routers/progress.js +1 -109
- package/server/dist/trpc/routers/queue.js +1 -93
- package/server/dist/trpc/routers/radio.js +1 -145
- package/server/dist/trpc/routers/settings.js +1 -96
- package/server/dist/trpc/routers/tracker.js +1 -213
- package/server/dist/trpc/routers/tv-library.js +1 -135
- package/server/dist/trpc/routers/tv-progress.js +1 -159
- package/server/dist/trpc/routers/tv.js +1 -551
- package/server/dist/trpc/routers/watchlist.js +2 -549
- package/server/dist/trpc/validation.js +1 -258
- package/server/dist/utils/cache.utils.js +1 -25
- package/server/dist/utils/cookie.utils.js +1 -13
- package/server/dist/utils/db-utils.js +5 -17
- package/server/dist/utils/env.utils.js +5 -77
- package/server/dist/utils/http.utils.js +1 -82
- package/server/dist/utils/request-context.js +1 -2
- package/server/dist/utils/security.utils.js +1 -76
- package/server/package.json +3 -2
- package/client/dist/assets/AvSyncCalibrator-DmoH5eRA.js +0 -10
- package/server/dist/utils/megaplay.utils.js +0 -40
|
@@ -1,295 +1,30 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
function
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
function assToVtt(body) {
|
|
22
|
-
if (!/^\s*\[Script Info\]/im.test(body))
|
|
23
|
-
return null;
|
|
24
|
-
const lines = body.replace(/\r\n/g, '\n').split('\n');
|
|
25
|
-
let inEvents = false;
|
|
26
|
-
let format = null;
|
|
27
|
-
const cues = [];
|
|
28
|
-
for (const raw of lines) {
|
|
29
|
-
const line = raw.trim();
|
|
30
|
-
if (/^\[Events\]/i.test(line)) {
|
|
31
|
-
inEvents = true;
|
|
32
|
-
continue;
|
|
33
|
-
}
|
|
34
|
-
if (/^\[/.test(line)) {
|
|
35
|
-
inEvents = false;
|
|
36
|
-
continue;
|
|
37
|
-
}
|
|
38
|
-
if (!inEvents)
|
|
39
|
-
continue;
|
|
40
|
-
if (/^Format\s*:/i.test(line)) {
|
|
41
|
-
format = line
|
|
42
|
-
.replace(/^Format\s*:/i, '')
|
|
43
|
-
.split(',')
|
|
44
|
-
.map((s) => s.trim().toLowerCase());
|
|
45
|
-
continue;
|
|
46
|
-
}
|
|
47
|
-
if (!/^Dialogue\s*:/i.test(line) || !format)
|
|
48
|
-
continue;
|
|
49
|
-
const payload = line.replace(/^Dialogue\s*:/i, '');
|
|
50
|
-
const parts = [];
|
|
51
|
-
let rest = payload;
|
|
52
|
-
for (let i = 0; i < format.length - 1; i++) {
|
|
53
|
-
const idx = rest.indexOf(',');
|
|
54
|
-
if (idx < 0)
|
|
55
|
-
break;
|
|
56
|
-
parts.push(rest.slice(0, idx));
|
|
57
|
-
rest = rest.slice(idx + 1);
|
|
58
|
-
}
|
|
59
|
-
parts.push(rest);
|
|
60
|
-
const start = assTimeToVtt(parts[format.indexOf('start')] ?? '');
|
|
61
|
-
const end = assTimeToVtt(parts[format.indexOf('end')] ?? '');
|
|
62
|
-
if (!start || !end || format.indexOf('text') < 0)
|
|
63
|
-
continue;
|
|
64
|
-
const text = (parts[format.indexOf('text')] ?? '')
|
|
65
|
-
.replace(/\\\\/g, '\\u0000')
|
|
66
|
-
.replace(/\{[^}]*\}/g, '')
|
|
67
|
-
.replace(/\\N/gi, '\n')
|
|
68
|
-
.replace(/\\n/g, '\n')
|
|
69
|
-
.replace(/\\h/gi, ' ')
|
|
70
|
-
.replace(/\\[a-zA-Z]+\d*/g, '')
|
|
71
|
-
.replace(/\\u0000/g, '\\')
|
|
72
|
-
.split('\n')
|
|
73
|
-
.map((s) => s.trim())
|
|
74
|
-
.filter(Boolean)
|
|
75
|
-
.join('\n');
|
|
76
|
-
if (!text)
|
|
77
|
-
continue;
|
|
78
|
-
cues.push(`${start} --> ${end}\n${text}`);
|
|
79
|
-
if (cues.length > 5000)
|
|
80
|
-
break;
|
|
81
|
-
}
|
|
82
|
-
if (cues.length === 0)
|
|
83
|
-
return null;
|
|
84
|
-
return `WEBVTT\n\n${cues.join('\n\n')}\n`;
|
|
85
|
-
}
|
|
86
|
-
const SUB_WRAP_AT = 50;
|
|
87
|
-
function splitBalancedCueLine(line) {
|
|
88
|
-
const words = line.split(/\s+/).filter((w) => w.length > 0);
|
|
89
|
-
if (words.length < 2)
|
|
90
|
-
return [line];
|
|
91
|
-
const total = words.join(' ').length;
|
|
92
|
-
if (total <= SUB_WRAP_AT)
|
|
93
|
-
return [line];
|
|
94
|
-
const target = total / 2;
|
|
95
|
-
let acc = words[0]?.length ?? 0;
|
|
96
|
-
let best = 1;
|
|
97
|
-
let bestDiff = Math.abs(acc - target);
|
|
98
|
-
for (let i = 1; i < words.length; i++) {
|
|
99
|
-
acc += 1 + (words[i]?.length ?? 0);
|
|
100
|
-
const diff = Math.abs(acc - target);
|
|
101
|
-
if (diff < bestDiff) {
|
|
102
|
-
bestDiff = diff;
|
|
103
|
-
best = i + 1;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
if (best <= 0 || best >= words.length)
|
|
107
|
-
return [line];
|
|
108
|
-
return [words.slice(0, best).join(' '), words.slice(best).join(' ')];
|
|
109
|
-
}
|
|
110
|
-
function rewrapVttCues(vtt) {
|
|
111
|
-
const blocks = vtt.replace(/\r\n/g, '\n').split(/\n\n+/);
|
|
112
|
-
return blocks
|
|
113
|
-
.map((block, idx) => {
|
|
114
|
-
if (idx === 0 && /^\s*WEBVTT/i.test(block))
|
|
115
|
-
return block;
|
|
116
|
-
const lines = block.split('\n');
|
|
117
|
-
const tIdx = lines.findIndex((l) => l.includes('-->'));
|
|
118
|
-
if (tIdx < 0)
|
|
119
|
-
return block;
|
|
120
|
-
const payload = lines.slice(tIdx + 1).filter((l) => l.trim() !== '');
|
|
121
|
-
if (payload.length !== 1)
|
|
122
|
-
return block;
|
|
123
|
-
const split = splitBalancedCueLine((payload[0] ?? '').trim());
|
|
124
|
-
if (split.length < 2)
|
|
125
|
-
return block;
|
|
126
|
-
return [...lines.slice(0, tIdx + 1), ...split].join('\n');
|
|
127
|
-
})
|
|
128
|
-
.join('\n\n');
|
|
129
|
-
}
|
|
130
|
-
const KWIK_DOMAINS = new Set(['kwik.cx', 'kwik.si', 'kwik.pro']);
|
|
131
|
-
const ANIMEPAHE_URL = 'https://animepahe.pw/';
|
|
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
|
-
'.settlar.io',
|
|
148
|
-
];
|
|
149
|
-
const HANIME_HOSTS = new Set(['r2.1hanime.com', '1.1hanime.com']);
|
|
150
|
-
const OPPAI_HOSTS = new Set(['myspacecat.pictures']);
|
|
151
|
-
const KAA_HOSTS = new Set([
|
|
152
|
-
'hls.krussdomi.com',
|
|
153
|
-
'subst.krussdomi.com',
|
|
154
|
-
'krussdomi.com',
|
|
155
|
-
'st1.advancedairesearchlab.xyz',
|
|
156
|
-
'st1.habibikun.xyz',
|
|
157
|
-
'st1.babybayw.xyz',
|
|
158
|
-
'st1.narutokun.xyz',
|
|
159
|
-
]);
|
|
160
|
-
const KAA_REFERER = 'https://krussdomi.com/';
|
|
161
|
-
const KAA_ORIGIN = 'https://krussdomi.com';
|
|
162
|
-
const ANILIGHT_REFERER = 'https://anilight.live/';
|
|
163
|
-
const ANILIGHT_ORIGIN = 'https://anilight.live';
|
|
164
|
-
const ANILIGHT_HOSTS = [
|
|
165
|
-
'anilight.live',
|
|
166
|
-
'api.anilight.live',
|
|
167
|
-
'lostproject.club',
|
|
168
|
-
'streamzone1.site',
|
|
169
|
-
'nukitashi.top',
|
|
170
|
-
'vivibebe.site',
|
|
171
|
-
'vibeplayer.site',
|
|
172
|
-
'aniwatchtv.site',
|
|
173
|
-
];
|
|
174
|
-
const NEXABLOOM_FAMILY_SUFFIXES = [
|
|
175
|
-
'.quavex.top',
|
|
176
|
-
'.nexabloom.top',
|
|
177
|
-
'.hiddenvertex.top',
|
|
178
|
-
'.ironhorizon.top',
|
|
179
|
-
'.solarhaven.top',
|
|
180
|
-
];
|
|
181
|
-
const NEXABLOOM_FALLBACK_HOSTS = ['fetch.nexabloom.top'];
|
|
182
|
-
function nexabloomFallbackUrls(urlStr) {
|
|
183
|
-
let host = '';
|
|
184
|
-
try {
|
|
185
|
-
host = new URL(urlStr).hostname.toLowerCase();
|
|
186
|
-
}
|
|
187
|
-
catch {
|
|
188
|
-
return [urlStr];
|
|
189
|
-
}
|
|
190
|
-
if (!NEXABLOOM_FAMILY_SUFFIXES.some((s) => host.endsWith(s))) {
|
|
191
|
-
return [urlStr];
|
|
192
|
-
}
|
|
193
|
-
const urls = [urlStr];
|
|
194
|
-
for (const fallback of NEXABLOOM_FALLBACK_HOSTS) {
|
|
195
|
-
if (fallback === host)
|
|
196
|
-
continue;
|
|
197
|
-
try {
|
|
198
|
-
const candidate = new URL(urlStr);
|
|
199
|
-
candidate.hostname = fallback;
|
|
200
|
-
urls.push(candidate.href);
|
|
201
|
-
}
|
|
202
|
-
catch {
|
|
203
|
-
// ignore
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
return urls;
|
|
207
|
-
}
|
|
208
|
-
async function fetchNexabloomFallback(urlStr, signal, attempt, isOk) {
|
|
209
|
-
let last = null;
|
|
210
|
-
for (const candidate of nexabloomFallbackUrls(urlStr)) {
|
|
211
|
-
if (signal.aborted)
|
|
212
|
-
break;
|
|
213
|
-
const r = await attempt(candidate).catch(() => null);
|
|
214
|
-
if (!r)
|
|
215
|
-
continue;
|
|
216
|
-
if (isOk(r))
|
|
217
|
-
return r;
|
|
218
|
-
last = r;
|
|
219
|
-
}
|
|
220
|
-
return last;
|
|
221
|
-
}
|
|
222
|
-
function isGotScrapingHost(urlStr) {
|
|
223
|
-
try {
|
|
224
|
-
const host = new URL(urlStr).hostname.toLowerCase();
|
|
225
|
-
if (GOT_SCRAPING_HOSTS.has(host))
|
|
226
|
-
return true;
|
|
227
|
-
if (HANIME_HOSTS.has(host))
|
|
228
|
-
return true;
|
|
229
|
-
return GOT_SCRAPING_SUFFIXES.some((s) => host.endsWith(s));
|
|
230
|
-
}
|
|
231
|
-
catch {
|
|
232
|
-
return false;
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
function validateKwikUrl(value) {
|
|
236
|
-
if (typeof value !== 'string')
|
|
237
|
-
return null;
|
|
238
|
-
try {
|
|
239
|
-
const url = new URL(value);
|
|
240
|
-
const isSecure = url.protocol === 'https:';
|
|
241
|
-
const isKwik = KWIK_DOMAINS.has(url.hostname.toLowerCase());
|
|
242
|
-
const isEmbedPath = /^\/e\/[A-Za-z0-9_-]+$/.test(url.pathname);
|
|
243
|
-
const noAuthOrQuery = !url.username && !url.password && !url.search && !url.hash;
|
|
244
|
-
return isSecure && isKwik && isEmbedPath && noAuthOrQuery ? url : null;
|
|
245
|
-
}
|
|
246
|
-
catch {
|
|
247
|
-
return null;
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
function pinVariant(body, idx) {
|
|
251
|
-
const lines = body.split('\n');
|
|
252
|
-
let streamIdx = -1;
|
|
253
|
-
let skipUri = false;
|
|
254
|
-
const out = [];
|
|
255
|
-
for (const line of lines) {
|
|
256
|
-
const trimmed = line.trim();
|
|
257
|
-
if (trimmed.startsWith('#EXT-X-STREAM-INF')) {
|
|
258
|
-
streamIdx++;
|
|
259
|
-
skipUri = streamIdx !== idx;
|
|
260
|
-
if (!skipUri)
|
|
261
|
-
out.push(line);
|
|
262
|
-
continue;
|
|
263
|
-
}
|
|
264
|
-
if (skipUri && trimmed && !trimmed.startsWith('#')) {
|
|
265
|
-
skipUri = false;
|
|
266
|
-
continue;
|
|
267
|
-
}
|
|
268
|
-
out.push(line);
|
|
269
|
-
}
|
|
270
|
-
if (streamIdx < 0 || idx > streamIdx)
|
|
271
|
-
return null;
|
|
272
|
-
return out.join('\n');
|
|
273
|
-
}
|
|
274
|
-
function applyKwikPatches(html, kwikUrl, cookieStr = '') {
|
|
275
|
-
const safeReferer = JSON.stringify(kwikUrl.href).replace(/</g, '\\u003c');
|
|
276
|
-
const safeCookie = JSON.stringify(cookieStr).replace(/</g, '\\u003c');
|
|
277
|
-
const patched = html.replace(/\b(src|href|url)\s*[=:]\s*(["']?)(\/\/[^"'>)]+|\/(?!\/)[^"'>)]*)\2/gi, (match, attr, quote, path) => {
|
|
278
|
-
const full = path.startsWith('//') ? `https:${path}` : `${kwikUrl.origin}${path}`;
|
|
279
|
-
const prefix = match.startsWith('url') ? `url(` : `${attr}=`;
|
|
280
|
-
const suffix = match.startsWith('url') ? `)` : '';
|
|
281
|
-
return `${prefix}${quote}${full}${quote}${suffix}`;
|
|
282
|
-
});
|
|
283
|
-
const iconProxy = `/api/proxy?url=${encodeURIComponent(`${kwikUrl.origin}/app/js/vendor/plyr.svg`)}&referer=${encodeURIComponent(kwikUrl.href)}`;
|
|
284
|
-
const plyrPatch = `<script>if(window.Plyr) Plyr.defaults.iconUrl=${JSON.stringify(iconProxy).replace(/</g, '\\u003c')};</script>`;
|
|
285
|
-
const hlsPatch = `<script>
|
|
1
|
+
import{gotScraping as H}from"got-scraping";import j from"path";import{AppCache as _}from"../utils/cache.utils.js";import{CONFIG as N}from"../config.js";import D from"fs";import A from"../logger.js";import{buildCfClearanceCookie as V,sanitizeCfClearance as ee}from"../utils/cookie.utils.js";import{isSafeExternalUrl as W}from"../utils/security.utils.js";import{fetchWithRetry as G,linkAbort as L,setProxyHeaders as X}from"../utils/http.utils.js";const O=new _({ttlSeconds:30,maxKeys:500}),z=new _({ttlSeconds:86400,maxKeys:3e3});function J(c){const o=c.trim().match(/^(-?\d+):(\d{2}):(\d{2})[.:](\d{2,3})$/);if(!o)return null;const e=String(Math.max(0,parseInt(o[1],10))).padStart(2,"0"),r=o[4].length===2?`${o[4]}0`:o[4];return`${e}:${o[2]}:${o[3]}.${r}`}function te(c){if(!/^\s*\[Script Info\]/im.test(c))return null;const o=c.replace(/\r\n/g,`
|
|
2
|
+
`).split(`
|
|
3
|
+
`);let e=!1,r=null;const i=[];for(const t of o){const n=t.trim();if(/^\[Events\]/i.test(n)){e=!0;continue}if(/^\[/.test(n)){e=!1;continue}if(!e)continue;if(/^Format\s*:/i.test(n)){r=n.replace(/^Format\s*:/i,"").split(",").map(h=>h.trim().toLowerCase());continue}if(!/^Dialogue\s*:/i.test(n)||!r)continue;const s=n.replace(/^Dialogue\s*:/i,""),d=[];let m=s;for(let h=0;h<r.length-1;h++){const b=m.indexOf(",");if(b<0)break;d.push(m.slice(0,b)),m=m.slice(b+1)}d.push(m);const R=J(d[r.indexOf("start")]??""),w=J(d[r.indexOf("end")]??"");if(!R||!w||r.indexOf("text")<0)continue;const T=(d[r.indexOf("text")]??"").replace(/\\\\/g,"\\u0000").replace(/\{[^}]*\}/g,"").replace(/\\N/gi,`
|
|
4
|
+
`).replace(/\\n/g,`
|
|
5
|
+
`).replace(/\\h/gi," ").replace(/\\[a-zA-Z]+\d*/g,"").replace(/\\u0000/g,"\\").split(`
|
|
6
|
+
`).map(h=>h.trim()).filter(Boolean).join(`
|
|
7
|
+
`);if(T&&(i.push(`${R} --> ${w}
|
|
8
|
+
${T}`),i.length>5e3))break}return i.length===0?null:`WEBVTT
|
|
9
|
+
|
|
10
|
+
${i.join(`
|
|
11
|
+
|
|
12
|
+
`)}
|
|
13
|
+
`}const re=50;function ne(c){const o=c.split(/\s+/).filter(s=>s.length>0);if(o.length<2)return[c];const e=o.join(" ").length;if(e<=re)return[c];const r=e/2;let i=o[0]?.length??0,t=1,n=Math.abs(i-r);for(let s=1;s<o.length;s++){i+=1+(o[s]?.length??0);const d=Math.abs(i-r);d<n&&(n=d,t=s+1)}return t<=0||t>=o.length?[c]:[o.slice(0,t).join(" "),o.slice(t).join(" ")]}function P(c){return c.replace(/\r\n/g,`
|
|
14
|
+
`).split(/\n\n+/).map((e,r)=>{if(r===0&&/^\s*WEBVTT/i.test(e))return e;const i=e.split(`
|
|
15
|
+
`),t=i.findIndex(d=>d.includes("-->"));if(t<0)return e;const n=i.slice(t+1).filter(d=>d.trim()!=="");if(n.length!==1)return e;const s=ne((n[0]??"").trim());return s.length<2?e:[...i.slice(0,t+1),...s].join(`
|
|
16
|
+
`)}).join(`
|
|
17
|
+
|
|
18
|
+
`)}function I(c,o){let e;try{e=new URL(c).hostname.toLowerCase()}catch{return}return o.find(r=>r.hosts?.some(i=>i===e)||r.suffixes?.some(i=>e.endsWith(i)))}function B(c,o,e){return e||(c?.originFromReferer?o:"")||c?.origin||""}function se(c,o){if(!o?.length)return[c];let e;try{e=new URL(c)}catch{return[c]}const r=new Set([e.hostname.toLowerCase()]),i=[c];for(const t of o){const n=t.toLowerCase();if(r.has(n))continue;r.add(n);const s=new URL(e.href);s.hostname=n,i.push(s.href)}return i}async function K(c,o,e,r,i){let t=null;for(const n of se(c,o)){if(e.aborted)break;const s=await r(n).catch(()=>null);if(s){if(i(s))return s;t=s}}return t}function oe(c,o){const e=c.split(`
|
|
19
|
+
`);let r=-1,i=!1;const t=[];for(const n of e){const s=n.trim();if(s.startsWith("#EXT-X-STREAM-INF")){r++,i=r!==o,i||t.push(n);continue}if(i&&s&&!s.startsWith("#")){i=!1;continue}t.push(n)}return r<0||o>r?null:t.join(`
|
|
20
|
+
`)}function ie(c,o,e=""){const r=JSON.stringify(o.href).replace(/</g,"\\u003c"),i=JSON.stringify(e).replace(/</g,"\\u003c"),t=c.replace(/\b(src|href|url)\s*[=:]\s*(["']?)(\/\/[^"'>)]+|\/(?!\/)[^"'>)]*)\2/gi,(T,h,b,l)=>{const u=l.startsWith("//")?`https:${l}`:`${o.origin}${l}`,y=T.startsWith("url")?"url(":`${h}=`,f=T.startsWith("url")?")":"";return`${y}${b}${u}${b}${f}`}),n=`/api/proxy?url=${encodeURIComponent(`${o.origin}/app/js/vendor/plyr.svg`)}&referer=${encodeURIComponent(o.href)}`,s=`<script>if(window.Plyr) Plyr.defaults.iconUrl=${JSON.stringify(n).replace(/</g,"\\u003c")};</script>`,d=`<script>
|
|
286
21
|
(function() {
|
|
287
22
|
var hook = function() {
|
|
288
23
|
if (!window.Hls) return;
|
|
289
24
|
var original = Hls.prototype.loadSource;
|
|
290
25
|
Hls.prototype.loadSource = function(src) {
|
|
291
26
|
if (typeof src === 'string' && src.includes('.m3u8')) {
|
|
292
|
-
src = window.location.origin + '/api/proxy?url=' + encodeURIComponent(src) + '&referer=' + encodeURIComponent(${
|
|
27
|
+
src = window.location.origin + '/api/proxy?url=' + encodeURIComponent(src) + '&referer=' + encodeURIComponent(${r}) + (${i} ? '&cookie=' + encodeURIComponent(${i}) : '');
|
|
293
28
|
}
|
|
294
29
|
return original.call(this, src);
|
|
295
30
|
};
|
|
@@ -302,8 +37,7 @@ function applyKwikPatches(html, kwikUrl, cookieStr = '') {
|
|
|
302
37
|
observer.observe(document.documentElement, { childList: true, subtree: true });
|
|
303
38
|
}
|
|
304
39
|
})();
|
|
305
|
-
</script
|
|
306
|
-
const endBridgePatch = `<script>
|
|
40
|
+
</script>`,w=t.replace(/(<script[^>]+\/plyr\.min\.js[^>]*><\/script>)/i,`$1${s}`).replace(/(<script[^>]+hls(?:\.min)?\.js[^>]*><\/script>)/i,`$1${d}`).replace(/<\/body>/i,`<script>
|
|
307
41
|
(function() {
|
|
308
42
|
var notified = false;
|
|
309
43
|
var notifyEnded = function() {
|
|
@@ -330,620 +64,17 @@ function applyKwikPatches(html, kwikUrl, cookieStr = '') {
|
|
|
330
64
|
|
|
331
65
|
observer.observe(document.documentElement, { childList: true, subtree: true });
|
|
332
66
|
})();
|
|
333
|
-
</script
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
try {
|
|
348
|
-
if (fs.existsSync(p)) {
|
|
349
|
-
const headers = new Headers();
|
|
350
|
-
headers.set('Cache-Control', 'no-store');
|
|
351
|
-
headers.set('Content-Type', 'image/svg+xml');
|
|
352
|
-
return new Response(fs.readFileSync(p), { headers });
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
catch {
|
|
356
|
-
// ignore
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
return null;
|
|
360
|
-
}
|
|
361
|
-
const PROXY_UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:126.0) Gecko/20100101 Firefox/126.0';
|
|
362
|
-
export function registerProxy(app) {
|
|
363
|
-
app.get('/api/proxy', async (c) => {
|
|
364
|
-
const url = c.req.query('url');
|
|
365
|
-
if (!url)
|
|
366
|
-
return c.text('URL required', 400);
|
|
367
|
-
const safeCheck = isSafeExternalUrl(url);
|
|
368
|
-
if (!safeCheck.safe) {
|
|
369
|
-
return c.text(safeCheck.error || 'Invalid URL', 400);
|
|
370
|
-
}
|
|
371
|
-
const urlStr = url;
|
|
372
|
-
const refererStr = c.req.query('referer') || '';
|
|
373
|
-
const cookieStr = c.req.query('cookie') || '';
|
|
374
|
-
const variantRaw = c.req.query('variant');
|
|
375
|
-
const variantIdx = variantRaw !== undefined ? parseInt(variantRaw, 10) : NaN;
|
|
376
|
-
const pinVar = Number.isInteger(variantIdx) && variantIdx >= 0;
|
|
377
|
-
const cacheKey = `m3u8-${urlStr}-${refererStr}${pinVar ? `-v${variantIdx}` : ''}`;
|
|
378
|
-
const abort = new AbortController();
|
|
379
|
-
linkAbort(c.req.raw.signal, abort);
|
|
380
|
-
try {
|
|
381
|
-
const isVaultCdn = Array.from(VAULT_CDN_HOSTS).some((host) => urlStr.includes(host));
|
|
382
|
-
const isHanime = Array.from(HANIME_HOSTS).some((host) => urlStr.includes(host));
|
|
383
|
-
const isOppai = Array.from(OPPAI_HOSTS).some((host) => urlStr.includes(host));
|
|
384
|
-
const isKaa = refererStr.startsWith(KAA_ORIGIN) ||
|
|
385
|
-
Array.from(KAA_HOSTS).some((host) => urlStr.includes(host));
|
|
386
|
-
const headers = {
|
|
387
|
-
'User-Agent': PROXY_UA,
|
|
388
|
-
};
|
|
389
|
-
if (refererStr)
|
|
390
|
-
headers['Referer'] = refererStr;
|
|
391
|
-
if (isVaultCdn) {
|
|
392
|
-
headers['Origin'] = refererStr || ANIMEPAHE_URL;
|
|
393
|
-
const cookieHeader = buildCfClearanceCookie(cookieStr);
|
|
394
|
-
if (cookieHeader)
|
|
395
|
-
headers['Cookie'] = cookieHeader;
|
|
396
|
-
}
|
|
397
|
-
if (isHanime) {
|
|
398
|
-
if (!headers['Referer'])
|
|
399
|
-
headers['Referer'] = refererStr || 'https://nhplayer.com/';
|
|
400
|
-
headers['Origin'] = 'https://nhplayer.com';
|
|
401
|
-
}
|
|
402
|
-
if (isOppai) {
|
|
403
|
-
if (!headers['Referer'])
|
|
404
|
-
headers['Referer'] = refererStr || 'https://oppai.stream/';
|
|
405
|
-
headers['Origin'] = 'https://oppai.stream';
|
|
406
|
-
}
|
|
407
|
-
if (isKaa) {
|
|
408
|
-
if (!headers['Referer'])
|
|
409
|
-
headers['Referer'] = refererStr || KAA_REFERER;
|
|
410
|
-
headers['Origin'] = KAA_ORIGIN;
|
|
411
|
-
}
|
|
412
|
-
const isMegaplay = refererStr.startsWith(MEGAPLAY_ORIGIN) || urlStr.includes('nexabloom.top');
|
|
413
|
-
if (isMegaplay && !headers['Origin']) {
|
|
414
|
-
headers['Origin'] = MEGAPLAY_ORIGIN;
|
|
415
|
-
}
|
|
416
|
-
if (urlStr.includes('weeabo0.xyz') || urlStr.includes('weeab0o.xyz')) {
|
|
417
|
-
if (!headers['Referer'])
|
|
418
|
-
headers['Referer'] = refererStr || 'https://japaneseasmr.com/';
|
|
419
|
-
const jasmrCookieHeader = buildCfClearanceCookie(cookieStr);
|
|
420
|
-
if (jasmrCookieHeader)
|
|
421
|
-
headers['Cookie'] = jasmrCookieHeader;
|
|
422
|
-
const jasmrUa = c.req.query('ua') || '';
|
|
423
|
-
if (jasmrUa)
|
|
424
|
-
headers['User-Agent'] = jasmrUa;
|
|
425
|
-
}
|
|
426
|
-
const range = c.req.header('range');
|
|
427
|
-
if (range)
|
|
428
|
-
headers['Range'] = range;
|
|
429
|
-
const playlistResponse = (body) => {
|
|
430
|
-
const outHeaders = new Headers();
|
|
431
|
-
outHeaders.set('Content-Type', 'application/vnd.apple.mpegurl');
|
|
432
|
-
outHeaders.set('Access-Control-Allow-Origin', '*');
|
|
433
|
-
return new Response(body, { headers: outHeaders });
|
|
434
|
-
};
|
|
435
|
-
const rewritePlaylist = (playlistBody, finalUrl) => {
|
|
436
|
-
const baseUrl = new URL(finalUrl);
|
|
437
|
-
const proxiedMediaUrl = (targetUrl) => `/api/proxy?url=${encodeURIComponent(targetUrl)}&referer=${encodeURIComponent(refererStr)}` +
|
|
438
|
-
(cookieStr ? `&cookie=${encodeURIComponent(sanitizeCfClearance(cookieStr))}` : '') +
|
|
439
|
-
(c.req.query('ua') ? `&ua=${encodeURIComponent(c.req.query('ua'))}` : '');
|
|
440
|
-
const needsProxy = Boolean(refererStr);
|
|
441
|
-
const isProxied = (value) => value.includes('/api/proxy');
|
|
442
|
-
const pinned = pinVar ? pinVariant(playlistBody, variantIdx) : null;
|
|
443
|
-
return (pinned || playlistBody)
|
|
444
|
-
.split('\n')
|
|
445
|
-
.map((line) => {
|
|
446
|
-
const trimmed = line.trim();
|
|
447
|
-
if (!trimmed)
|
|
448
|
-
return line;
|
|
449
|
-
if (trimmed.startsWith('#')) {
|
|
450
|
-
return trimmed.replace(/URI="([^"]+)"/g, (_, uri) => {
|
|
451
|
-
if (isProxied(uri))
|
|
452
|
-
return `URI="${uri}"`;
|
|
453
|
-
const absolute = new URL(uri, baseUrl).href;
|
|
454
|
-
return `URI="${needsProxy || absolute.includes('.m3u8') ? proxiedMediaUrl(absolute) : absolute}"`;
|
|
455
|
-
});
|
|
456
|
-
}
|
|
457
|
-
if (isProxied(trimmed))
|
|
458
|
-
return line;
|
|
459
|
-
const absolute = new URL(trimmed, baseUrl).href;
|
|
460
|
-
return proxiedMediaUrl(absolute);
|
|
461
|
-
})
|
|
462
|
-
.join('\n');
|
|
463
|
-
};
|
|
464
|
-
if (urlStr.includes('.m3u8')) {
|
|
465
|
-
const cached = proxyCache.get(cacheKey);
|
|
466
|
-
if (cached) {
|
|
467
|
-
const headers = new Headers();
|
|
468
|
-
headers.set('Content-Type', 'application/vnd.apple.mpegurl');
|
|
469
|
-
headers.set('Access-Control-Allow-Origin', '*');
|
|
470
|
-
return new Response(cached, { headers });
|
|
471
|
-
}
|
|
472
|
-
const resp = await fetchNexabloomFallback(urlStr, abort.signal, (candidate) => gotScraping({
|
|
473
|
-
url: isNexabloomMasterUrl(candidate) ? signNexabloomMasterUrl(candidate) : candidate,
|
|
474
|
-
method: 'GET',
|
|
475
|
-
headers,
|
|
476
|
-
responseType: 'text',
|
|
477
|
-
timeout: { request: 30000 },
|
|
478
|
-
followRedirect: true,
|
|
479
|
-
throwHttpErrors: false,
|
|
480
|
-
}), (attempt) => attempt.statusCode === 200 || attempt.statusCode === 206);
|
|
481
|
-
if (!resp || (resp.statusCode !== 200 && resp.statusCode !== 206)) {
|
|
482
|
-
return c.text('Upstream error', (resp?.statusCode || 502));
|
|
483
|
-
}
|
|
484
|
-
const rewritten = rewritePlaylist(resp.body, resp.url || urlStr);
|
|
485
|
-
proxyCache.set(cacheKey, rewritten);
|
|
486
|
-
return playlistResponse(rewritten);
|
|
487
|
-
}
|
|
488
|
-
else {
|
|
489
|
-
if (isGotScrapingHost(urlStr)) {
|
|
490
|
-
if (range)
|
|
491
|
-
headers['Range'] = range;
|
|
492
|
-
const resp = await fetchNexabloomFallback(urlStr, abort.signal, (candidate) => gotScraping({
|
|
493
|
-
url: candidate,
|
|
494
|
-
method: 'GET',
|
|
495
|
-
headers,
|
|
496
|
-
responseType: 'buffer',
|
|
497
|
-
timeout: { request: 30000 },
|
|
498
|
-
followRedirect: true,
|
|
499
|
-
throwHttpErrors: false,
|
|
500
|
-
}), (attempt) => attempt.statusCode === 200 || attempt.statusCode === 206);
|
|
501
|
-
if (!resp || (resp.statusCode !== 200 && resp.statusCode !== 206)) {
|
|
502
|
-
return c.text('Upstream error', (resp?.statusCode || 502));
|
|
503
|
-
}
|
|
504
|
-
const ct = resp.headers['content-type'];
|
|
505
|
-
const isVtt = urlStr.endsWith('.vtt') || urlStr.includes('.vtt?');
|
|
506
|
-
const bodyText = String(resp.body ?? '');
|
|
507
|
-
if (!isVtt &&
|
|
508
|
-
((typeof ct === 'string' && ct.includes('mpegurl')) || bodyText.startsWith('#EXTM3U'))) {
|
|
509
|
-
const cached = proxyCache.get(cacheKey);
|
|
510
|
-
const rewritten = cached ?? rewritePlaylist(bodyText, resp.url || urlStr);
|
|
511
|
-
if (!cached)
|
|
512
|
-
proxyCache.set(cacheKey, rewritten);
|
|
513
|
-
return playlistResponse(rewritten);
|
|
514
|
-
}
|
|
515
|
-
const outHeaders = new Headers();
|
|
516
|
-
outHeaders.set('Content-Type', isVtt ? 'text/vtt; charset=utf-8' : ct || 'application/octet-stream');
|
|
517
|
-
setProxyHeaders(outHeaders, (name) => resp.headers[name]);
|
|
518
|
-
return new Response(resp.body, {
|
|
519
|
-
status: resp.statusCode,
|
|
520
|
-
headers: outHeaders,
|
|
521
|
-
});
|
|
522
|
-
}
|
|
523
|
-
else {
|
|
524
|
-
const upstream = await fetchNexabloomFallback(urlStr, abort.signal, async (candidate) => {
|
|
525
|
-
const attempt = await fetchWithRetry(candidate, { method: 'GET', headers }, { retries: 3, timeoutMs: 30000, signal: abort.signal });
|
|
526
|
-
if (attempt.status !== 200 && attempt.status !== 206) {
|
|
527
|
-
try {
|
|
528
|
-
await attempt.body?.cancel();
|
|
529
|
-
}
|
|
530
|
-
catch {
|
|
531
|
-
// ignore
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
return attempt;
|
|
535
|
-
}, (attempt) => attempt.status === 200 || attempt.status === 206);
|
|
536
|
-
if (!upstream) {
|
|
537
|
-
return c.text('Upstream error', 502);
|
|
538
|
-
}
|
|
539
|
-
const status = upstream.status;
|
|
540
|
-
if (status !== 200 && status !== 206) {
|
|
541
|
-
try {
|
|
542
|
-
await upstream.body?.cancel();
|
|
543
|
-
}
|
|
544
|
-
catch {
|
|
545
|
-
// ignore
|
|
546
|
-
}
|
|
547
|
-
return c.text('Upstream error', status);
|
|
548
|
-
}
|
|
549
|
-
const isVtt = urlStr.endsWith('.vtt') || urlStr.includes('.vtt?');
|
|
550
|
-
const ct = upstream.headers.get('content-type');
|
|
551
|
-
const outHeaders = new Headers();
|
|
552
|
-
outHeaders.set('Content-Type', isVtt ? 'text/vtt; charset=utf-8' : ct || 'application/octet-stream');
|
|
553
|
-
setProxyHeaders(outHeaders, (name) => upstream.headers.get(name));
|
|
554
|
-
if (!upstream.body) {
|
|
555
|
-
return c.text('Upstream error', 502);
|
|
556
|
-
}
|
|
557
|
-
return new Response(upstream.body, { status: status, headers: outHeaders });
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
catch (e) {
|
|
562
|
-
if (abort.signal.aborted)
|
|
563
|
-
return new Response(null, { status: 499 });
|
|
564
|
-
logger.error({ url: urlStr, error: e?.message }, '[proxy] upstream request failed');
|
|
565
|
-
return c.text('Proxy error', 500);
|
|
566
|
-
}
|
|
567
|
-
});
|
|
568
|
-
app.get('/api/embed-proxy', async (c) => {
|
|
569
|
-
const kwikUrl = validateKwikUrl(c.req.query('url'));
|
|
570
|
-
if (!kwikUrl)
|
|
571
|
-
return c.text('Invalid or unsupported gateway URL', 400);
|
|
572
|
-
const cookieStr = c.req.query('cookie') || '';
|
|
573
|
-
const abort = new AbortController();
|
|
574
|
-
linkAbort(c.req.raw.signal, abort);
|
|
575
|
-
try {
|
|
576
|
-
const headers = {
|
|
577
|
-
'User-Agent': PROXY_UA,
|
|
578
|
-
Referer: ANIMEPAHE_URL,
|
|
579
|
-
Origin: 'https://animepahe.pw',
|
|
580
|
-
};
|
|
581
|
-
if (cookieStr) {
|
|
582
|
-
const cookieHeader = buildCfClearanceCookie(cookieStr);
|
|
583
|
-
if (cookieHeader)
|
|
584
|
-
headers['Cookie'] = cookieHeader;
|
|
585
|
-
}
|
|
586
|
-
const response = await gotScraping({
|
|
587
|
-
url: kwikUrl.href,
|
|
588
|
-
method: 'GET',
|
|
589
|
-
headers,
|
|
590
|
-
responseType: 'text',
|
|
591
|
-
timeout: { request: 30000 },
|
|
592
|
-
followRedirect: true,
|
|
593
|
-
throwHttpErrors: false,
|
|
594
|
-
});
|
|
595
|
-
const originalHtml = response.body;
|
|
596
|
-
const patched = applyKwikPatches(originalHtml, kwikUrl, cookieStr);
|
|
597
|
-
if (!patched)
|
|
598
|
-
return c.text('Failed to patch video gateway', 502);
|
|
599
|
-
const outHeaders = new Headers();
|
|
600
|
-
outHeaders.set('Content-Type', 'text/html; charset=utf-8');
|
|
601
|
-
outHeaders.set('Cache-Control', 'private, max-age=120');
|
|
602
|
-
return new Response(patched, { status: 200, headers: outHeaders });
|
|
603
|
-
}
|
|
604
|
-
catch {
|
|
605
|
-
if (abort.signal.aborted)
|
|
606
|
-
return new Response(null, { status: 499 });
|
|
607
|
-
return c.text('Gateway proxy error', 502);
|
|
608
|
-
}
|
|
609
|
-
});
|
|
610
|
-
app.get('/api/subtitle-proxy', async (c) => {
|
|
611
|
-
const url = c.req.query('url');
|
|
612
|
-
if (!url)
|
|
613
|
-
return c.text('URL required', 400);
|
|
614
|
-
const safeCheck = isSafeExternalUrl(url);
|
|
615
|
-
if (!safeCheck.safe) {
|
|
616
|
-
return c.text(safeCheck.error || 'Invalid URL', 400);
|
|
617
|
-
}
|
|
618
|
-
const abort = new AbortController();
|
|
619
|
-
linkAbort(c.req.raw.signal, abort);
|
|
620
|
-
const subUrl = url;
|
|
621
|
-
const refererStr = c.req.query('referer') || '';
|
|
622
|
-
const isAnilight = refererStr.includes('anilight.live') || ANILIGHT_HOSTS.some((host) => subUrl.includes(host));
|
|
623
|
-
const innerRawUrl = (() => {
|
|
624
|
-
try {
|
|
625
|
-
const parsed = new URL(subUrl);
|
|
626
|
-
if (parsed.hostname.toLowerCase().endsWith('anilight.live') &&
|
|
627
|
-
parsed.pathname.includes('/proxy/captions')) {
|
|
628
|
-
const inner = parsed.searchParams.get('url');
|
|
629
|
-
if (inner && (inner.startsWith('http://') || inner.startsWith('https://'))) {
|
|
630
|
-
return inner;
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
catch {
|
|
635
|
-
// ignore
|
|
636
|
-
}
|
|
637
|
-
return null;
|
|
638
|
-
})();
|
|
639
|
-
const buildHeaders = () => {
|
|
640
|
-
const headers = {
|
|
641
|
-
'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',
|
|
642
|
-
Accept: 'text/vtt,text/plain,*/*;q=0.8',
|
|
643
|
-
};
|
|
644
|
-
if (refererStr) {
|
|
645
|
-
headers['Referer'] = refererStr;
|
|
646
|
-
}
|
|
647
|
-
else if (isAnilight) {
|
|
648
|
-
headers['Referer'] = ANILIGHT_REFERER;
|
|
649
|
-
}
|
|
650
|
-
if (refererStr.startsWith(KAA_ORIGIN) ||
|
|
651
|
-
Array.from(KAA_HOSTS).some((host) => subUrl.includes(host))) {
|
|
652
|
-
headers['Origin'] = KAA_ORIGIN;
|
|
653
|
-
}
|
|
654
|
-
else if (isAnilight) {
|
|
655
|
-
headers['Origin'] = ANILIGHT_ORIGIN;
|
|
656
|
-
}
|
|
657
|
-
return headers;
|
|
658
|
-
};
|
|
659
|
-
const fetchText = async (target, retries = 1) => {
|
|
660
|
-
const headers = buildHeaders();
|
|
661
|
-
if (innerRawUrl && target === innerRawUrl) {
|
|
662
|
-
headers['Referer'] = ANILIGHT_REFERER;
|
|
663
|
-
headers['Origin'] = ANILIGHT_ORIGIN;
|
|
664
|
-
}
|
|
665
|
-
if (isGotScrapingHost(target)) {
|
|
666
|
-
try {
|
|
667
|
-
const upstream = await gotScraping({
|
|
668
|
-
url: target,
|
|
669
|
-
method: 'GET',
|
|
670
|
-
headers,
|
|
671
|
-
responseType: 'text',
|
|
672
|
-
timeout: { request: 15000 },
|
|
673
|
-
followRedirect: true,
|
|
674
|
-
throwHttpErrors: false,
|
|
675
|
-
});
|
|
676
|
-
if (upstream.statusCode !== 200) {
|
|
677
|
-
logger.warn({ target, status: upstream.statusCode }, '[subtitle-proxy] upstream returned error status');
|
|
678
|
-
return { status: upstream.statusCode, body: '' };
|
|
679
|
-
}
|
|
680
|
-
const body = String(upstream.body ?? '').replace(/^\uFEFF/, '');
|
|
681
|
-
if (/^\s*\{[\s\S]*"error"[\s\S]*\}\s*$/.test(body.slice(0, 500))) {
|
|
682
|
-
logger.warn({ target, body: body.slice(0, 200) }, '[subtitle-proxy] upstream JSON error');
|
|
683
|
-
return { status: 502, body: '' };
|
|
684
|
-
}
|
|
685
|
-
return { status: 200, body };
|
|
686
|
-
}
|
|
687
|
-
catch (e) {
|
|
688
|
-
if (abort.signal.aborted)
|
|
689
|
-
return null;
|
|
690
|
-
logger.warn({ target, error: e?.message }, '[subtitle-proxy] upstream fetch failed');
|
|
691
|
-
return { status: 502, body: '' };
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
try {
|
|
695
|
-
const upstream = await fetchWithRetry(target, { method: 'GET', headers }, { retries, timeoutMs: 15000, signal: abort.signal });
|
|
696
|
-
if (!upstream.ok) {
|
|
697
|
-
logger.warn({ target, status: upstream.status }, '[subtitle-proxy] upstream returned error status');
|
|
698
|
-
try {
|
|
699
|
-
await upstream.body?.cancel();
|
|
700
|
-
}
|
|
701
|
-
catch {
|
|
702
|
-
// ignore
|
|
703
|
-
}
|
|
704
|
-
return { status: upstream.status, body: '' };
|
|
705
|
-
}
|
|
706
|
-
const body = (await upstream.text()).replace(/^\uFEFF/, '');
|
|
707
|
-
if (/^\s*\{[\s\S]*"error"[\s\S]*\}\s*$/.test(body.slice(0, 500))) {
|
|
708
|
-
logger.warn({ target, body: body.slice(0, 200) }, '[subtitle-proxy] upstream JSON error');
|
|
709
|
-
return { status: 502, body: '' };
|
|
710
|
-
}
|
|
711
|
-
return { status: upstream.status, body };
|
|
712
|
-
}
|
|
713
|
-
catch (e) {
|
|
714
|
-
if (abort.signal.aborted)
|
|
715
|
-
return null;
|
|
716
|
-
logger.warn({ target, error: e?.message }, '[subtitle-proxy] upstream fetch failed');
|
|
717
|
-
return { status: 502, body: '' };
|
|
718
|
-
}
|
|
719
|
-
};
|
|
720
|
-
const vttHeaders = (cacheControl) => {
|
|
721
|
-
const headers = new Headers();
|
|
722
|
-
headers.set('Content-Type', 'text/vtt; charset=utf-8');
|
|
723
|
-
headers.set('Access-Control-Allow-Origin', '*');
|
|
724
|
-
headers.set('Cache-Control', cacheControl);
|
|
725
|
-
return headers;
|
|
726
|
-
};
|
|
727
|
-
const subtitleError = (text) => {
|
|
728
|
-
const headers = new Headers();
|
|
729
|
-
headers.set('Access-Control-Allow-Origin', '*');
|
|
730
|
-
headers.set('Cache-Control', 'no-store');
|
|
731
|
-
return new Response(text, { status: 502, headers });
|
|
732
|
-
};
|
|
733
|
-
try {
|
|
734
|
-
const headers = buildHeaders();
|
|
735
|
-
let result = await fetchText(subUrl, 1);
|
|
736
|
-
if (abort.signal.aborted)
|
|
737
|
-
return new Response(null, { status: 499 });
|
|
738
|
-
if ((!result || !result.body) && innerRawUrl) {
|
|
739
|
-
result = await fetchText(innerRawUrl, 0);
|
|
740
|
-
if (abort.signal.aborted)
|
|
741
|
-
return new Response(null, { status: 499 });
|
|
742
|
-
}
|
|
743
|
-
if (!result || !result.body) {
|
|
744
|
-
return subtitleError('Subtitle upstream error');
|
|
745
|
-
}
|
|
746
|
-
const body = result.body;
|
|
747
|
-
const baseForSegments = innerRawUrl || subUrl;
|
|
748
|
-
if (/#EXTM3U/i.test(body.slice(0, 1000))) {
|
|
749
|
-
const segUrls = body
|
|
750
|
-
.split('\n')
|
|
751
|
-
.map((l) => l.trim())
|
|
752
|
-
.filter((l) => l && !l.startsWith('#'))
|
|
753
|
-
.slice(0, 200)
|
|
754
|
-
.map((u) => {
|
|
755
|
-
try {
|
|
756
|
-
return new URL(u, baseForSegments).href;
|
|
757
|
-
}
|
|
758
|
-
catch {
|
|
759
|
-
return null;
|
|
760
|
-
}
|
|
761
|
-
})
|
|
762
|
-
.filter((u) => Boolean(u));
|
|
763
|
-
const parts = [];
|
|
764
|
-
for (const seg of segUrls) {
|
|
765
|
-
try {
|
|
766
|
-
const segRes = await fetchWithRetry(seg, { method: 'GET', headers }, { retries: 1, timeoutMs: 15000, signal: abort.signal });
|
|
767
|
-
if (!segRes.ok)
|
|
768
|
-
continue;
|
|
769
|
-
let segBody = (await segRes.text()).replace(/^\uFEFF/, '');
|
|
770
|
-
if (/#EXTM3U/i.test(segBody.slice(0, 200)))
|
|
771
|
-
continue;
|
|
772
|
-
segBody = segBody
|
|
773
|
-
.replace(/\r\n/g, '\n')
|
|
774
|
-
.replace(/(\d{2}:\d{2}:\d{2}),(\d{3})/g, '$1.$2');
|
|
775
|
-
if (/^\s*WEBVTT/i.test(segBody)) {
|
|
776
|
-
segBody = segBody.replace(/^\s*WEBVTT[^\n]*\n(\n|.*\n)?/, '');
|
|
777
|
-
}
|
|
778
|
-
segBody = segBody.trim();
|
|
779
|
-
if (segBody)
|
|
780
|
-
parts.push(segBody);
|
|
781
|
-
if (parts.join('\n').length > 2_000_000)
|
|
782
|
-
break;
|
|
783
|
-
}
|
|
784
|
-
catch {
|
|
785
|
-
if (abort.signal.aborted)
|
|
786
|
-
return new Response(null, { status: 499 });
|
|
787
|
-
}
|
|
788
|
-
}
|
|
789
|
-
if (parts.length === 0) {
|
|
790
|
-
return subtitleError('Subtitle playlist empty');
|
|
791
|
-
}
|
|
792
|
-
return new Response(rewrapVttCues(`WEBVTT\n\n${parts.join('\n\n')}\n`), {
|
|
793
|
-
headers: vttHeaders('public, max-age=86400'),
|
|
794
|
-
});
|
|
795
|
-
}
|
|
796
|
-
if (/^\s*WEBVTT/i.test(body)) {
|
|
797
|
-
return new Response(rewrapVttCues(body), {
|
|
798
|
-
headers: vttHeaders('public, max-age=86400'),
|
|
799
|
-
});
|
|
800
|
-
}
|
|
801
|
-
const vttFromAss = assToVtt(body);
|
|
802
|
-
if (vttFromAss) {
|
|
803
|
-
return new Response(rewrapVttCues(vttFromAss), {
|
|
804
|
-
headers: vttHeaders('public, max-age=86400'),
|
|
805
|
-
});
|
|
806
|
-
}
|
|
807
|
-
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') });
|
|
808
|
-
}
|
|
809
|
-
catch {
|
|
810
|
-
if (abort.signal.aborted)
|
|
811
|
-
return new Response(null, { status: 499 });
|
|
812
|
-
logger.warn({ url: subUrl }, '[subtitle-proxy] failed');
|
|
813
|
-
return subtitleError('Subtitle upstream error');
|
|
814
|
-
}
|
|
815
|
-
});
|
|
816
|
-
app.get('/api/image-proxy', async (c) => {
|
|
817
|
-
const url = c.req.query('url');
|
|
818
|
-
if (!url)
|
|
819
|
-
return c.text('URL required', 400);
|
|
820
|
-
const safeCheck = isSafeExternalUrl(url);
|
|
821
|
-
if (!safeCheck.safe) {
|
|
822
|
-
return c.text(safeCheck.error || 'Invalid URL', 400);
|
|
823
|
-
}
|
|
824
|
-
const targetUrl = url;
|
|
825
|
-
const cookie = c.req.query('cookie') || '';
|
|
826
|
-
const ua = c.req.query('ua') || '';
|
|
827
|
-
const strict = c.req.query('strict') === '1';
|
|
828
|
-
const abort = new AbortController();
|
|
829
|
-
linkAbort(c.req.raw.signal, abort);
|
|
830
|
-
let refererValue = 'https://anilist.co/';
|
|
831
|
-
const headers = {
|
|
832
|
-
'User-Agent': ua ||
|
|
833
|
-
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
|
834
|
-
};
|
|
835
|
-
const cacheKey = `${targetUrl}|${cookie}|${ua}`;
|
|
836
|
-
const cached = imageCache.get(cacheKey);
|
|
837
|
-
if (cached) {
|
|
838
|
-
const hit = new Headers();
|
|
839
|
-
hit.set('Cache-Control', 'public, max-age=604800, immutable');
|
|
840
|
-
hit.set('Content-Type', cached.contentType);
|
|
841
|
-
hit.set('Access-Control-Allow-Origin', '*');
|
|
842
|
-
hit.set('X-Dango-Cache', 'hit');
|
|
843
|
-
return new Response(cached.body, { headers: hit });
|
|
844
|
-
}
|
|
845
|
-
try {
|
|
846
|
-
if (targetUrl.includes('animepahe')) {
|
|
847
|
-
refererValue = 'https://animepahe.pw/';
|
|
848
|
-
headers['Cookie'] = `cf_clearance=${sanitizeCfClearance(cookie)}`;
|
|
849
|
-
}
|
|
850
|
-
else if (targetUrl.includes('anilist.co')) {
|
|
851
|
-
refererValue = 'https://anilist.co/';
|
|
852
|
-
}
|
|
853
|
-
else if (targetUrl.includes('gogocdn.net')) {
|
|
854
|
-
refererValue = 'https://gogoanime.lu/';
|
|
855
|
-
}
|
|
856
|
-
else if (targetUrl.includes('animeya.cc')) {
|
|
857
|
-
refererValue = 'https://animeya.cc/';
|
|
858
|
-
}
|
|
859
|
-
else if (targetUrl.includes('myspacecat.pictures')) {
|
|
860
|
-
refererValue = 'https://oppai.stream/';
|
|
861
|
-
}
|
|
862
|
-
else if (targetUrl.includes('weeabo0.xyz')) {
|
|
863
|
-
refererValue = 'https://japaneseasmr.com/';
|
|
864
|
-
const jasmrCookieHeader = buildCfClearanceCookie(cookie);
|
|
865
|
-
if (jasmrCookieHeader)
|
|
866
|
-
headers['Cookie'] = jasmrCookieHeader;
|
|
867
|
-
const uaParam = ua || '';
|
|
868
|
-
if (uaParam)
|
|
869
|
-
headers['User-Agent'] = uaParam;
|
|
870
|
-
}
|
|
871
|
-
headers['Referer'] = refererValue;
|
|
872
|
-
let body = null;
|
|
873
|
-
let contentType = 'image/webp';
|
|
874
|
-
for (let attempt = 0; attempt < 3 && !body; attempt++) {
|
|
875
|
-
const resp = await gotScraping({
|
|
876
|
-
url: targetUrl,
|
|
877
|
-
method: 'GET',
|
|
878
|
-
headers,
|
|
879
|
-
responseType: 'buffer',
|
|
880
|
-
followRedirect: true,
|
|
881
|
-
throwHttpErrors: false,
|
|
882
|
-
timeout: { request: 30000 },
|
|
883
|
-
});
|
|
884
|
-
if (resp.statusCode === 200 && resp.rawBody?.length) {
|
|
885
|
-
body = Buffer.from(resp.rawBody);
|
|
886
|
-
contentType = String(resp.headers['content-type'] || 'image/webp');
|
|
887
|
-
}
|
|
888
|
-
else {
|
|
889
|
-
if (attempt < 2)
|
|
890
|
-
await new Promise((r) => setTimeout(r, 400 * (attempt + 1)));
|
|
891
|
-
}
|
|
892
|
-
}
|
|
893
|
-
if (body) {
|
|
894
|
-
imageCache.set(cacheKey, { body, contentType });
|
|
895
|
-
const outHeaders = new Headers();
|
|
896
|
-
outHeaders.set('Cache-Control', 'public, max-age=604800, immutable');
|
|
897
|
-
outHeaders.set('Content-Type', contentType);
|
|
898
|
-
outHeaders.set('Access-Control-Allow-Origin', '*');
|
|
899
|
-
return new Response(body, { headers: outHeaders });
|
|
900
|
-
}
|
|
901
|
-
// strict=1 callers retry another candidate instead of settling for the
|
|
902
|
-
// placeholder, which would otherwise read as a successful load.
|
|
903
|
-
if (strict)
|
|
904
|
-
return c.text('Not Found', 404);
|
|
905
|
-
const placeholder = placeholderSvg();
|
|
906
|
-
if (placeholder)
|
|
907
|
-
return placeholder;
|
|
908
|
-
return c.text('Not Found', 404);
|
|
909
|
-
}
|
|
910
|
-
catch {
|
|
911
|
-
if (abort.signal.aborted)
|
|
912
|
-
return new Response(null, { status: 499 });
|
|
913
|
-
if (strict)
|
|
914
|
-
return c.text('Not Found', 502);
|
|
915
|
-
const placeholder = placeholderSvg();
|
|
916
|
-
if (placeholder)
|
|
917
|
-
return placeholder;
|
|
918
|
-
return c.text('Not Found', 404);
|
|
919
|
-
}
|
|
920
|
-
});
|
|
921
|
-
app.get('/api/resolve', async (c) => {
|
|
922
|
-
const targetUrl = c.req.query('url');
|
|
923
|
-
if (!targetUrl || typeof targetUrl !== 'string') {
|
|
924
|
-
return c.json({ error: 'URL required' }, 400);
|
|
925
|
-
}
|
|
926
|
-
const safeCheck = isSafeExternalUrl(targetUrl);
|
|
927
|
-
if (!safeCheck.safe) {
|
|
928
|
-
return c.json({ error: safeCheck.error || 'Invalid URL' }, 400);
|
|
929
|
-
}
|
|
930
|
-
try {
|
|
931
|
-
const response = await fetch(targetUrl, {
|
|
932
|
-
method: 'GET',
|
|
933
|
-
headers: {
|
|
934
|
-
'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',
|
|
935
|
-
Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
|
936
|
-
},
|
|
937
|
-
redirect: 'follow',
|
|
938
|
-
});
|
|
939
|
-
return c.json({
|
|
940
|
-
finalUrl: response.url,
|
|
941
|
-
status: response.status,
|
|
942
|
-
});
|
|
943
|
-
}
|
|
944
|
-
catch (e) {
|
|
945
|
-
const err = e;
|
|
946
|
-
return c.json({ error: err.message || 'Resolve failed' }, 500);
|
|
947
|
-
}
|
|
948
|
-
});
|
|
949
|
-
}
|
|
67
|
+
</script></body>`);return w===c?null:w}function Y(){const c=[j.join(N.PACKAGE_ROOT,"client/public/placeholder.svg"),j.join(N.PACKAGE_ROOT,"client/dist/placeholder.svg"),j.join(N.SERVER_ROOT,"..","client/public/placeholder.svg")];for(const o of c)try{if(D.existsSync(o)){const e=new Headers;return e.set("Cache-Control","no-store"),e.set("Content-Type","image/svg+xml"),new Response(D.readFileSync(o),{headers:e})}}catch{}return null}const Z="Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:126.0) Gecko/20100101 Firefox/126.0";function ye(c,o){c.get("/api/proxy",async e=>{const r=e.req.query("url");if(!r)return e.text("URL required",400);const i=W(r);if(!i.safe)return e.text(i.error||"Invalid URL",400);const t=r,n=I(t,o()),s=e.req.query("referer")||n?.referer||"",d=B(n,s,e.req.query("origin")),m=e.req.query("ua")||"",R=e.req.query("cookie")||"",w=e.req.query("scrape")==="1"||n?.scrape===!0,T=e.req.query("cf")==="1"||n?.cfCookie===!0,h=e.req.query("variant"),b=h!==void 0?parseInt(h,10):NaN,l=Number.isInteger(b)&&b>=0,u=`m3u8-${t}-${s}-${d}-${m}${l?`-v${b}`:""}`,y=new AbortController;L(e.req.raw.signal,y);try{const f={"User-Agent":m||Z};if(s&&(f.Referer=s),d&&(f.Origin=d),R){const a=T?V(R):R;a&&(f.Cookie=a)}const p=e.req.header("range");p&&(f.Range=p);const U=a=>{const g=new Headers;return g.set("Content-Type","application/vnd.apple.mpegurl"),g.set("Access-Control-Allow-Origin","*"),new Response(a,{headers:g})},E=(a,g)=>{const x=new URL(g),q=k=>{const v=new URLSearchParams({url:k});return s&&v.set("referer",s),d&&v.set("origin",d),m&&v.set("ua",m),R&&v.set("cookie",ee(R)),w&&v.set("scrape","1"),T&&v.set("cf","1"),`/api/proxy?${v}`},$=!!(s||d||m||w),C=k=>k.includes("/api/proxy");return((l?oe(a,b):null)||a).split(`
|
|
68
|
+
`).map(k=>{const v=k.trim();if(!v)return k;if(v.startsWith("#"))return v.replace(/URI="([^"]+)"/g,(ae,F)=>{if(C(F))return`URI="${F}"`;const M=new URL(F,x).href;return`URI="${$||M.includes(".m3u8")?q(M):M}"`});if(C(v))return k;const Q=new URL(v,x).href;return q(Q)}).join(`
|
|
69
|
+
`)};if(t.includes(".m3u8")){const a=O.get(u);if(a){const q=new Headers;return q.set("Content-Type","application/vnd.apple.mpegurl"),q.set("Access-Control-Allow-Origin","*"),new Response(a,{headers:q})}const g=await K(t,n?.mirrors,y.signal,q=>H({url:q,method:"GET",headers:f,responseType:"text",timeout:{request:3e4},followRedirect:!0,throwHttpErrors:!1}),q=>q.statusCode===200||q.statusCode===206);if(!g||g.statusCode!==200&&g.statusCode!==206)return e.text("Upstream error",g?.statusCode||502);const x=E(g.body,g.url||t);return O.set(u,x),U(x)}else if(w){p&&(f.Range=p);const a=await K(t,n?.mirrors,y.signal,C=>H({url:C,method:"GET",headers:f,responseType:"buffer",timeout:{request:3e4},followRedirect:!0,throwHttpErrors:!1}),C=>C.statusCode===200||C.statusCode===206);if(!a||a.statusCode!==200&&a.statusCode!==206)return e.text("Upstream error",a?.statusCode||502);const g=a.headers["content-type"],x=t.endsWith(".vtt")||t.includes(".vtt?"),q=String(a.body??"");if(!x&&(typeof g=="string"&&g.includes("mpegurl")||q.startsWith("#EXTM3U"))){const C=O.get(u),S=C??E(q,a.url||t);return C||O.set(u,S),U(S)}const $=new Headers;return $.set("Content-Type",x?"text/vtt; charset=utf-8":g||"application/octet-stream"),X($,C=>a.headers[C]),new Response(a.body,{status:a.statusCode,headers:$})}else{const a=await K(t,n?.mirrors,y.signal,async C=>{const S=await G(C,{method:"GET",headers:f},{retries:3,timeoutMs:3e4,signal:y.signal});if(S.status!==200&&S.status!==206)try{await S.body?.cancel()}catch{}return S},C=>C.status===200||C.status===206);if(!a)return e.text("Upstream error",502);const g=a.status;if(g!==200&&g!==206){try{await a.body?.cancel()}catch{}return e.text("Upstream error",g)}const x=t.endsWith(".vtt")||t.includes(".vtt?"),q=a.headers.get("content-type"),$=new Headers;return $.set("Content-Type",x?"text/vtt; charset=utf-8":q||"application/octet-stream"),X($,C=>a.headers.get(C)),a.body?new Response(a.body,{status:g,headers:$}):e.text("Upstream error",502)}}catch(f){return y.signal.aborted?new Response(null,{status:499}):(A.error({url:t,error:f?.message},"[proxy] upstream request failed"),e.text("Proxy error",500))}}),c.get("/api/embed-proxy",async e=>{const r=e.req.query("url");if(!r)return e.text("URL required",400);const i=W(r);if(!i.safe)return e.text(i.error||"Invalid URL",400);const t=I(r,o());if(!t?.embedPatch)return e.text("Invalid or unsupported gateway URL",400);const n=new URL(r),s=e.req.query("cookie")||"",d=e.req.query("referer")||t.referer||"",m=B(t,d,e.req.query("origin")),R=new AbortController;L(e.req.raw.signal,R);try{const w={"User-Agent":e.req.query("ua")||Z};if(d&&(w.Referer=d),m&&(w.Origin=m),s){const u=e.req.query("cf")==="1"||t.cfCookie?V(s):s;u&&(w.Cookie=u)}const h=(await H({url:n.href,method:"GET",headers:w,responseType:"text",timeout:{request:3e4},followRedirect:!0,throwHttpErrors:!1})).body,b=ie(h,n,s);if(!b)return e.text("Failed to patch video gateway",502);const l=new Headers;return l.set("Content-Type","text/html; charset=utf-8"),l.set("Cache-Control","private, max-age=120"),new Response(b,{status:200,headers:l})}catch{return R.signal.aborted?new Response(null,{status:499}):e.text("Gateway proxy error",502)}}),c.get("/api/subtitle-proxy",async e=>{const r=e.req.query("url");if(!r)return e.text("URL required",400);const i=W(r);if(!i.safe)return e.text(i.error||"Invalid URL",400);const t=new AbortController;L(e.req.raw.signal,t);const n=r,s=I(n,o()),d=e.req.query("referer")||s?.referer||"",m=B(s,d,e.req.query("origin")),R=e.req.query("scrape")==="1"||s?.scrape===!0,w=()=>{const l={"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",Accept:"text/vtt,text/plain,*/*;q=0.8"};return d&&(l.Referer=d),m&&(l.Origin=m),l},T=async(l,u=1)=>{const y=w();if(R)try{const f=await H({url:l,method:"GET",headers:y,responseType:"text",timeout:{request:15e3},followRedirect:!0,throwHttpErrors:!1});if(f.statusCode!==200)return A.warn({target:l,status:f.statusCode},"[subtitle-proxy] upstream returned error status"),{status:f.statusCode,body:""};const p=String(f.body??"").replace(/^\uFEFF/,"");return/^\s*\{[\s\S]*"error"[\s\S]*\}\s*$/.test(p.slice(0,500))?(A.warn({target:l,body:p.slice(0,200)},"[subtitle-proxy] upstream JSON error"),{status:502,body:""}):{status:200,body:p}}catch(f){return t.signal.aborted?null:(A.warn({target:l,error:f?.message},"[subtitle-proxy] upstream fetch failed"),{status:502,body:""})}try{const f=await G(l,{method:"GET",headers:y},{retries:u,timeoutMs:15e3,signal:t.signal});if(!f.ok){A.warn({target:l,status:f.status},"[subtitle-proxy] upstream returned error status");try{await f.body?.cancel()}catch{}return{status:f.status,body:""}}const p=(await f.text()).replace(/^\uFEFF/,"");return/^\s*\{[\s\S]*"error"[\s\S]*\}\s*$/.test(p.slice(0,500))?(A.warn({target:l,body:p.slice(0,200)},"[subtitle-proxy] upstream JSON error"),{status:502,body:""}):{status:f.status,body:p}}catch(f){return t.signal.aborted?null:(A.warn({target:l,error:f?.message},"[subtitle-proxy] upstream fetch failed"),{status:502,body:""})}},h=l=>{const u=new Headers;return u.set("Content-Type","text/vtt; charset=utf-8"),u.set("Access-Control-Allow-Origin","*"),u.set("Cache-Control",l),u},b=l=>{const u=new Headers;return u.set("Access-Control-Allow-Origin","*"),u.set("Cache-Control","no-store"),new Response(l,{status:502,headers:u})};try{const l=w(),u=await T(n,1);if(t.signal.aborted)return new Response(null,{status:499});if(!u||!u.body)return b("Subtitle upstream error");const y=u.body,f=n;if(/#EXTM3U/i.test(y.slice(0,1e3))){const U=y.split(`
|
|
70
|
+
`).map(a=>a.trim()).filter(a=>a&&!a.startsWith("#")).slice(0,200).map(a=>{try{return new URL(a,f).href}catch{return null}}).filter(a=>!!a),E=[];for(const a of U)try{const g=await G(a,{method:"GET",headers:l},{retries:1,timeoutMs:15e3,signal:t.signal});if(!g.ok)continue;let x=(await g.text()).replace(/^\uFEFF/,"");if(/#EXTM3U/i.test(x.slice(0,200)))continue;if(x=x.replace(/\r\n/g,`
|
|
71
|
+
`).replace(/(\d{2}:\d{2}:\d{2}),(\d{3})/g,"$1.$2"),/^\s*WEBVTT/i.test(x)&&(x=x.replace(/^\s*WEBVTT[^\n]*\n(\n|.*\n)?/,"")),x=x.trim(),x&&E.push(x),E.join(`
|
|
72
|
+
`).length>2e6)break}catch{if(t.signal.aborted)return new Response(null,{status:499})}return E.length===0?b("Subtitle playlist empty"):new Response(P(`WEBVTT
|
|
73
|
+
|
|
74
|
+
${E.join(`
|
|
75
|
+
|
|
76
|
+
`)}
|
|
77
|
+
`),{headers:h("public, max-age=86400")})}if(/^\s*WEBVTT/i.test(y))return new Response(P(y),{headers:h("public, max-age=86400")});const p=te(y);return p?new Response(P(p),{headers:h("public, max-age=86400")}):new Response(P(`WEBVTT
|
|
78
|
+
|
|
79
|
+
${y.replace(/\r\n/g,`
|
|
80
|
+
`).replace(/(\d{2}:\d{2}:\d{2}),(\d{3})/g,"$1.$2")}`),{headers:h("public, max-age=86400")})}catch{return t.signal.aborted?new Response(null,{status:499}):(A.warn({url:n},"[subtitle-proxy] failed"),b("Subtitle upstream error"))}}),c.get("/api/image-proxy",async e=>{const r=e.req.query("url");if(!r)return e.text("URL required",400);const i=W(r);if(!i.safe)return e.text(i.error||"Invalid URL",400);const t=r,n=e.req.query("cookie")||"",s=e.req.query("ua")||"",d=e.req.query("strict")==="1",m=I(t,o()),R=e.req.query("referer")||m?.referer||"",w=B(m,R,e.req.query("origin")),T=new AbortController;L(e.req.raw.signal,T);const h={"User-Agent":s||"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"},b=`${t}|${n}|${s}|${R}|${w}`,l=z.get(b);if(l){const u=new Headers;return u.set("Cache-Control","public, max-age=604800, immutable"),u.set("Content-Type",l.contentType),u.set("Access-Control-Allow-Origin","*"),u.set("X-Dango-Cache","hit"),new Response(l.body,{headers:u})}try{if(R&&(h.Referer=R),w&&(h.Origin=w),e.req.query("cf")==="1"||m?.cfCookie){const p=V(n);p&&(h.Cookie=p)}let u=null,y="image/webp";for(let p=0;p<3&&!u;p++){const U=await H({url:t,method:"GET",headers:h,responseType:"buffer",followRedirect:!0,throwHttpErrors:!1,timeout:{request:3e4}});U.statusCode===200&&U.rawBody?.length?(u=Buffer.from(U.rawBody),y=String(U.headers["content-type"]||"image/webp")):p<2&&await new Promise(E=>setTimeout(E,400*(p+1)))}if(u){z.set(b,{body:u,contentType:y});const p=new Headers;return p.set("Cache-Control","public, max-age=604800, immutable"),p.set("Content-Type",y),p.set("Access-Control-Allow-Origin","*"),new Response(u,{headers:p})}if(d)return e.text("Not Found",404);const f=Y();return f||e.text("Not Found",404)}catch{if(T.signal.aborted)return new Response(null,{status:499});if(d)return e.text("Not Found",502);const u=Y();return u||e.text("Not Found",404)}}),c.get("/api/resolve",async e=>{const r=e.req.query("url");if(!r||typeof r!="string")return e.json({error:"URL required"},400);const i=W(r);if(!i.safe)return e.json({error:i.error||"Invalid URL"},400);try{const t=await fetch(r,{method:"GET",headers:{"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",Accept:"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"},redirect:"follow"});return e.json({finalUrl:t.url,status:t.status})}catch(t){const n=t;return e.json({error:n.message||"Resolve failed"},500)}})}export{ye as registerProxy};
|