@serifpersia/dango 3.6.7 → 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.
Files changed (149) hide show
  1. package/README.md +1 -1
  2. package/client/dist/assets/{AnimeCard-C3P9zWi9.js → AnimeCard-DraA_TF9.js} +1 -1
  3. package/client/dist/assets/{AnimeInfo-Et6687Tl.js → AnimeInfo-BJ7pzs-P.js} +1 -1
  4. package/client/dist/assets/{AnimeInfoPage-DX4eNnlH.js → AnimeInfoPage-B_esUr5C.js} +2 -2
  5. package/client/dist/assets/{Asmr-CCdEWrFi.js → Asmr-Ckuh2xfW.js} +1 -1
  6. package/client/dist/assets/{AsmrResetProgressModal-DgEWBJOw.js → AsmrResetProgressModal-BS6UFJN4.js} +1 -1
  7. package/client/dist/assets/{AvSyncCalibrator-CQ2fjf2E.js → AvSyncCalibrator-DwHCd9yB.js} +1 -1
  8. package/client/dist/assets/{Home-DUkUTTTy.js → Home-DuM1EuvN.js} +1 -1
  9. package/client/dist/assets/{Insights-QCh4xW12.js → Insights-D3Pd9c9V.js} +1 -1
  10. package/client/dist/assets/{LibraryListPage-BoTxRqHz.js → LibraryListPage-Dm2yVn0-.js} +1 -1
  11. package/client/dist/assets/{ListeningList-DguKjI_t.js → ListeningList-D-zt6EXZ.js} +1 -1
  12. package/client/dist/assets/{Manga-HydR-L7o.js → Manga-JwprMKBk.js} +1 -1
  13. package/client/dist/assets/{MangaInfoPage-CBakEI9a.js → MangaInfoPage-DaeyJl2p.js} +1 -1
  14. package/client/dist/assets/{MangaReadPage-DwAR3iPy.js → MangaReadPage-COg0fHI9.js} +1 -1
  15. package/client/dist/assets/{MangaResetProgressModal-DGzl8vsO.js → MangaResetProgressModal-kguXv0_M.js} +1 -1
  16. package/client/dist/assets/{Mature-Pi0TxKuF.js → Mature-IHDaasrs.js} +1 -1
  17. package/client/dist/assets/{MediaCard--cuFxOCS.js → MediaCard-CpwsrXo5.js} +1 -1
  18. package/client/dist/assets/{Music-CQpyXTLH.js → Music-Dks4tPeK.js} +1 -1
  19. package/client/dist/assets/{OptionListMenu-B3D39dGS.js → OptionListMenu-11CcAsy8.js} +1 -1
  20. package/client/dist/assets/{Player-CsH4x_I_.js → Player-DQoEQkHL.js} +2 -2
  21. package/client/dist/assets/{PlayerSettings-DGxBsi3F.js → PlayerSettings-D4Ud4kv4.js} +1 -1
  22. package/client/dist/assets/{QueueOptionsButton-Djq4Cpex.js → QueueOptionsButton-B8PlPKjC.js} +1 -1
  23. package/client/dist/assets/{QueueRail-DOrYgNVX.js → QueueRail-B4Dok7U3.js} +1 -1
  24. package/client/dist/assets/{Radio-DF6g6odd.js → Radio-BHK6LSk4.js} +1 -1
  25. package/client/dist/assets/{ReadingList-DwYoBrNk.js → ReadingList-BCsxIz57.js} +1 -1
  26. package/client/dist/assets/{ResetProgressModal-CMF86mX6.js → ResetProgressModal-BXHxSfy-.js} +1 -1
  27. package/client/dist/assets/{Search-DFkhuk-x.js → Search-PK1ds07O.js} +1 -1
  28. package/client/dist/assets/{Settings-DBB2r0VQ.js → Settings-Bxk_x55l.js} +1 -1
  29. package/client/dist/assets/{SkeletonGrid-DJelJgAv.js → SkeletonGrid-CpJCZSRN.js} +1 -1
  30. package/client/dist/assets/{Trackers-DGYXFhYZ.js → Trackers-RieCh_84.js} +1 -1
  31. package/client/dist/assets/{Tv-C5MDGD2Z.js → Tv-DLytPJUj.js} +1 -1
  32. package/client/dist/assets/{TvInfo-DrKuK13u.js → TvInfo-C8djKebR.js} +1 -1
  33. package/client/dist/assets/{TvPopup-DtHilRDT.js → TvPopup-40JGGJDk.js} +1 -1
  34. package/client/dist/assets/{TvSearch-CzNxdu_w.js → TvSearch-A3hbospf.js} +1 -1
  35. package/client/dist/assets/{TvWatchlist-D7p4br6S.js → TvWatchlist-DuXjqmZM.js} +1 -1
  36. package/client/dist/assets/{Watchlist-DrOzswl2.js → Watchlist-B9wGBh0u.js} +1 -1
  37. package/client/dist/assets/{hls-BGCX7mJy.js → hls-CZ5X3Ya-.js} +1 -1
  38. package/client/dist/assets/{index-CnfZwsds.js → index-dRhc9-ol.js} +2 -2
  39. package/client/dist/assets/{useAnimeInfoData-DYXWBPF-.js → useAnimeInfoData-ZzDtLfvD.js} +1 -1
  40. package/client/dist/assets/{useAsmr-BbhQ59bk.js → useAsmr-CKOdHgol.js} +1 -1
  41. package/client/dist/assets/{useAsmrPopup-SymV1J-b.js → useAsmrPopup-SFHPhpcl.js} +1 -1
  42. package/client/dist/assets/{useMangaLibrary-DuesWQaE.js → useMangaLibrary-CKATe31P.js} +1 -1
  43. package/client/dist/assets/{useMangaPopup-D57OVGUr.js → useMangaPopup-C75QuYgY.js} +1 -1
  44. package/client/dist/assets/{useProviders-mpQb_dix.js → useProviders-CMtvZbEi.js} +1 -1
  45. package/client/dist/assets/{useTv-DGflCsSI.js → useTv-DUX4zYMr.js} +1 -1
  46. package/client/dist/assets/{useTvLibrary-BqeSmyc4.js → useTvLibrary-By4Gt-R_.js} +1 -1
  47. package/client/dist/index.html +1 -1
  48. package/package.json +2 -2
  49. package/server/dist/app-auth.js +1 -157
  50. package/server/dist/app-hono.js +7 -557
  51. package/server/dist/config.js +1 -174
  52. package/server/dist/db/drizzle.js +1 -16
  53. package/server/dist/db/schema-anime.js +1 -61
  54. package/server/dist/db/schema-asmr.js +1 -13
  55. package/server/dist/db/schema-manga.js +1 -31
  56. package/server/dist/db/schema-tv.js +1 -18
  57. package/server/dist/db.js +1 -217
  58. package/server/dist/discord-gateway.js +1 -385
  59. package/server/dist/discord-rpc.js +1 -413
  60. package/server/dist/github-sync.js +1 -508
  61. package/server/dist/google.js +1 -741
  62. package/server/dist/hono/asmr.js +1 -10
  63. package/server/dist/hono/auth.js +3 -26
  64. package/server/dist/hono/music.js +1 -209
  65. package/server/dist/hono/proxy.js +36 -783
  66. package/server/dist/hono/tv.js +5 -200
  67. package/server/dist/hono/watchlist.js +2 -338
  68. package/server/dist/lib/anilist.js +26 -1458
  69. package/server/dist/lib/anischedule.js +1 -191
  70. package/server/dist/lib/insights.js +1 -185
  71. package/server/dist/lib/ipc.js +1 -23
  72. package/server/dist/lib/kitsu.js +1 -517
  73. package/server/dist/lib/mal-import.js +1 -291
  74. package/server/dist/lib/mal.js +1 -957
  75. package/server/dist/lib/migration.js +1 -310
  76. package/server/dist/lib/offline-db.js +4 -384
  77. package/server/dist/lib/potoken.js +1 -135
  78. package/server/dist/lib/recommendations.service.js +1 -216
  79. package/server/dist/lib/temp-ids.js +1 -3
  80. package/server/dist/lib/tmdb.js +1 -72
  81. package/server/dist/lib/tracker/anilist-tracker.js +8 -245
  82. package/server/dist/lib/tracker/manga-sync.service.js +1 -773
  83. package/server/dist/lib/tracker/sync-state.js +1 -21
  84. package/server/dist/lib/tracker/sync.service.js +1 -476
  85. package/server/dist/lib/translate.js +3 -79
  86. package/server/dist/lib/ump.js +1 -106
  87. package/server/dist/lib/ytmusic.js +1 -162
  88. package/server/dist/logger.js +1 -16
  89. package/server/dist/providers/manga/manga.types.js +0 -1
  90. package/server/dist/providers/provider.interface.js +0 -1
  91. package/server/dist/providers/remote-loader.js +2 -614
  92. package/server/dist/providers/remote-types.js +1 -159
  93. package/server/dist/providers/title-matching.js +1 -226
  94. package/server/dist/providers/tv.types.js +0 -1
  95. package/server/dist/rclone.js +2 -120
  96. package/server/dist/repositories/asmr.repository.js +11 -46
  97. package/server/dist/repositories/insights.repository.js +11 -25
  98. package/server/dist/repositories/library.repository.js +3 -94
  99. package/server/dist/repositories/mal-cache.repository.js +1 -52
  100. package/server/dist/repositories/manga.repository.js +14 -58
  101. package/server/dist/repositories/notifications.repository.js +6 -52
  102. package/server/dist/repositories/queue.repository.js +5 -58
  103. package/server/dist/repositories/settings.repository.js +2 -29
  104. package/server/dist/repositories/shows-meta.repository.js +3 -56
  105. package/server/dist/repositories/temp-show-ids.repository.js +3 -63
  106. package/server/dist/repositories/tv.repository.js +11 -54
  107. package/server/dist/repositories/watched-episodes.repository.js +6 -55
  108. package/server/dist/repositories/watchlist.repository.js +3 -54
  109. package/server/dist/scripts/backfill-episode-duration.js +1 -42
  110. package/server/dist/server.js +1 -310
  111. package/server/dist/shipped-defaults.js +1 -6
  112. package/server/dist/sync-payload.js +1 -91
  113. package/server/dist/sync.js +3 -647
  114. package/server/dist/trpc/context.js +1 -7
  115. package/server/dist/trpc/errors.js +1 -15
  116. package/server/dist/trpc/index.js +1 -18
  117. package/server/dist/trpc/router.js +1 -49
  118. package/server/dist/trpc/routers/asmr-library.js +1 -112
  119. package/server/dist/trpc/routers/asmr-progress.js +1 -121
  120. package/server/dist/trpc/routers/asmr.js +1 -87
  121. package/server/dist/trpc/routers/auth.js +1 -194
  122. package/server/dist/trpc/routers/continue-watching.js +2 -58
  123. package/server/dist/trpc/routers/data.js +1 -1026
  124. package/server/dist/trpc/routers/discord.js +1 -285
  125. package/server/dist/trpc/routers/discovery.js +1 -11
  126. package/server/dist/trpc/routers/insights.js +1 -14
  127. package/server/dist/trpc/routers/manga-library.js +1 -263
  128. package/server/dist/trpc/routers/manga-progress.js +1 -169
  129. package/server/dist/trpc/routers/manga.js +1 -201
  130. package/server/dist/trpc/routers/music.js +1 -655
  131. package/server/dist/trpc/routers/notifications.js +1 -80
  132. package/server/dist/trpc/routers/progress.js +1 -109
  133. package/server/dist/trpc/routers/queue.js +1 -93
  134. package/server/dist/trpc/routers/radio.js +1 -145
  135. package/server/dist/trpc/routers/settings.js +1 -96
  136. package/server/dist/trpc/routers/tracker.js +1 -213
  137. package/server/dist/trpc/routers/tv-library.js +1 -135
  138. package/server/dist/trpc/routers/tv-progress.js +1 -159
  139. package/server/dist/trpc/routers/tv.js +1 -551
  140. package/server/dist/trpc/routers/watchlist.js +2 -549
  141. package/server/dist/trpc/validation.js +1 -258
  142. package/server/dist/utils/cache.utils.js +1 -25
  143. package/server/dist/utils/cookie.utils.js +1 -13
  144. package/server/dist/utils/db-utils.js +5 -17
  145. package/server/dist/utils/env.utils.js +5 -77
  146. package/server/dist/utils/http.utils.js +1 -82
  147. package/server/dist/utils/request-context.js +1 -2
  148. package/server/dist/utils/security.utils.js +1 -76
  149. package/server/package.json +3 -2
@@ -1,224 +1,30 @@
1
- import { gotScraping } from 'got-scraping';
2
- import path from 'path';
3
- import { AppCache } from '../utils/cache.utils.js';
4
- import { CONFIG } from '../config.js';
5
- import fs from 'fs';
6
- import logger from '../logger.js';
7
- import { buildCfClearanceCookie, sanitizeCfClearance } from '../utils/cookie.utils.js';
8
- import { isSafeExternalUrl } from '../utils/security.utils.js';
9
- import { fetchWithRetry, linkAbort, setProxyHeaders } from '../utils/http.utils.js';
10
- const proxyCache = new AppCache({ ttlSeconds: 30, maxKeys: 500 });
11
- const imageCache = new AppCache({ ttlSeconds: 86400, maxKeys: 3000 });
12
- function assTimeToVtt(value) {
13
- const m = value.trim().match(/^(-?\d+):(\d{2}):(\d{2})[.:](\d{2,3})$/);
14
- if (!m)
15
- return null;
16
- const h = String(Math.max(0, parseInt(m[1], 10))).padStart(2, '0');
17
- const ms = m[4].length === 2 ? `${m[4]}0` : m[4];
18
- return `${h}:${m[2]}:${m[3]}.${ms}`;
19
- }
20
- function assToVtt(body) {
21
- if (!/^\s*\[Script Info\]/im.test(body))
22
- return null;
23
- const lines = body.replace(/\r\n/g, '\n').split('\n');
24
- let inEvents = false;
25
- let format = null;
26
- const cues = [];
27
- for (const raw of lines) {
28
- const line = raw.trim();
29
- if (/^\[Events\]/i.test(line)) {
30
- inEvents = true;
31
- continue;
32
- }
33
- if (/^\[/.test(line)) {
34
- inEvents = false;
35
- continue;
36
- }
37
- if (!inEvents)
38
- continue;
39
- if (/^Format\s*:/i.test(line)) {
40
- format = line
41
- .replace(/^Format\s*:/i, '')
42
- .split(',')
43
- .map((s) => s.trim().toLowerCase());
44
- continue;
45
- }
46
- if (!/^Dialogue\s*:/i.test(line) || !format)
47
- continue;
48
- const payload = line.replace(/^Dialogue\s*:/i, '');
49
- const parts = [];
50
- let rest = payload;
51
- for (let i = 0; i < format.length - 1; i++) {
52
- const idx = rest.indexOf(',');
53
- if (idx < 0)
54
- break;
55
- parts.push(rest.slice(0, idx));
56
- rest = rest.slice(idx + 1);
57
- }
58
- parts.push(rest);
59
- const start = assTimeToVtt(parts[format.indexOf('start')] ?? '');
60
- const end = assTimeToVtt(parts[format.indexOf('end')] ?? '');
61
- if (!start || !end || format.indexOf('text') < 0)
62
- continue;
63
- const text = (parts[format.indexOf('text')] ?? '')
64
- .replace(/\\\\/g, '\\u0000')
65
- .replace(/\{[^}]*\}/g, '')
66
- .replace(/\\N/gi, '\n')
67
- .replace(/\\n/g, '\n')
68
- .replace(/\\h/gi, ' ')
69
- .replace(/\\[a-zA-Z]+\d*/g, '')
70
- .replace(/\\u0000/g, '\\')
71
- .split('\n')
72
- .map((s) => s.trim())
73
- .filter(Boolean)
74
- .join('\n');
75
- if (!text)
76
- continue;
77
- cues.push(`${start} --> ${end}\n${text}`);
78
- if (cues.length > 5000)
79
- break;
80
- }
81
- if (cues.length === 0)
82
- return null;
83
- return `WEBVTT\n\n${cues.join('\n\n')}\n`;
84
- }
85
- const SUB_WRAP_AT = 50;
86
- function splitBalancedCueLine(line) {
87
- const words = line.split(/\s+/).filter((w) => w.length > 0);
88
- if (words.length < 2)
89
- return [line];
90
- const total = words.join(' ').length;
91
- if (total <= SUB_WRAP_AT)
92
- return [line];
93
- const target = total / 2;
94
- let acc = words[0]?.length ?? 0;
95
- let best = 1;
96
- let bestDiff = Math.abs(acc - target);
97
- for (let i = 1; i < words.length; i++) {
98
- acc += 1 + (words[i]?.length ?? 0);
99
- const diff = Math.abs(acc - target);
100
- if (diff < bestDiff) {
101
- bestDiff = diff;
102
- best = i + 1;
103
- }
104
- }
105
- if (best <= 0 || best >= words.length)
106
- return [line];
107
- return [words.slice(0, best).join(' '), words.slice(best).join(' ')];
108
- }
109
- function rewrapVttCues(vtt) {
110
- const blocks = vtt.replace(/\r\n/g, '\n').split(/\n\n+/);
111
- return blocks
112
- .map((block, idx) => {
113
- if (idx === 0 && /^\s*WEBVTT/i.test(block))
114
- return block;
115
- const lines = block.split('\n');
116
- const tIdx = lines.findIndex((l) => l.includes('-->'));
117
- if (tIdx < 0)
118
- return block;
119
- const payload = lines.slice(tIdx + 1).filter((l) => l.trim() !== '');
120
- if (payload.length !== 1)
121
- return block;
122
- const split = splitBalancedCueLine((payload[0] ?? '').trim());
123
- if (split.length < 2)
124
- return block;
125
- return [...lines.slice(0, tIdx + 1), ...split].join('\n');
126
- })
127
- .join('\n\n');
128
- }
129
- function matchProxyRule(urlStr, rules) {
130
- let host;
131
- try {
132
- host = new URL(urlStr).hostname.toLowerCase();
133
- }
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)
144
- return [urlStr];
145
- let current;
146
- try {
147
- current = new URL(urlStr);
148
- }
149
- catch {
150
- return [urlStr];
151
- }
152
- const seen = new Set([current.hostname.toLowerCase()]);
153
- const urls = [urlStr];
154
- for (const mirror of mirrors) {
155
- const host = mirror.toLowerCase();
156
- if (seen.has(host))
157
- continue;
158
- seen.add(host);
159
- const candidate = new URL(current.href);
160
- candidate.hostname = host;
161
- urls.push(candidate.href);
162
- }
163
- return urls;
164
- }
165
- async function fetchWithMirrors(urlStr, mirrors, signal, attempt, isOk) {
166
- let last = null;
167
- for (const candidate of mirrorUrls(urlStr, mirrors)) {
168
- if (signal.aborted)
169
- break;
170
- const r = await attempt(candidate).catch(() => null);
171
- if (!r)
172
- continue;
173
- if (isOk(r))
174
- return r;
175
- last = r;
176
- }
177
- return last;
178
- }
179
- function pinVariant(body, idx) {
180
- const lines = body.split('\n');
181
- let streamIdx = -1;
182
- let skipUri = false;
183
- const out = [];
184
- for (const line of lines) {
185
- const trimmed = line.trim();
186
- if (trimmed.startsWith('#EXT-X-STREAM-INF')) {
187
- streamIdx++;
188
- skipUri = streamIdx !== idx;
189
- if (!skipUri)
190
- out.push(line);
191
- continue;
192
- }
193
- if (skipUri && trimmed && !trimmed.startsWith('#')) {
194
- skipUri = false;
195
- continue;
196
- }
197
- out.push(line);
198
- }
199
- if (streamIdx < 0 || idx > streamIdx)
200
- return null;
201
- return out.join('\n');
202
- }
203
- function applyEmbedPatches(html, embedUrl, cookieStr = '') {
204
- const safeReferer = JSON.stringify(embedUrl.href).replace(/</g, '\\u003c');
205
- const safeCookie = JSON.stringify(cookieStr).replace(/</g, '\\u003c');
206
- const patched = html.replace(/\b(src|href|url)\s*[=:]\s*(["']?)(\/\/[^"'>)]+|\/(?!\/)[^"'>)]*)\2/gi, (match, attr, quote, path) => {
207
- const full = path.startsWith('//') ? `https:${path}` : `${embedUrl.origin}${path}`;
208
- const prefix = match.startsWith('url') ? `url(` : `${attr}=`;
209
- const suffix = match.startsWith('url') ? `)` : '';
210
- return `${prefix}${quote}${full}${quote}${suffix}`;
211
- });
212
- const iconProxy = `/api/proxy?url=${encodeURIComponent(`${embedUrl.origin}/app/js/vendor/plyr.svg`)}&referer=${encodeURIComponent(embedUrl.href)}`;
213
- const plyrPatch = `<script>if(window.Plyr) Plyr.defaults.iconUrl=${JSON.stringify(iconProxy).replace(/</g, '\\u003c')};</script>`;
214
- 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>
215
21
  (function() {
216
22
  var hook = function() {
217
23
  if (!window.Hls) return;
218
24
  var original = Hls.prototype.loadSource;
219
25
  Hls.prototype.loadSource = function(src) {
220
26
  if (typeof src === 'string' && src.includes('.m3u8')) {
221
- src = window.location.origin + '/api/proxy?url=' + encodeURIComponent(src) + '&referer=' + encodeURIComponent(${safeReferer}) + (${safeCookie} ? '&cookie=' + encodeURIComponent(${safeCookie}) : '');
27
+ src = window.location.origin + '/api/proxy?url=' + encodeURIComponent(src) + '&referer=' + encodeURIComponent(${r}) + (${i} ? '&cookie=' + encodeURIComponent(${i}) : '');
222
28
  }
223
29
  return original.call(this, src);
224
30
  };
@@ -231,8 +37,7 @@ function applyEmbedPatches(html, embedUrl, cookieStr = '') {
231
37
  observer.observe(document.documentElement, { childList: true, subtree: true });
232
38
  }
233
39
  })();
234
- </script>`;
235
- 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>
236
41
  (function() {
237
42
  var notified = false;
238
43
  var notifyEnded = function() {
@@ -259,569 +64,17 @@ function applyEmbedPatches(html, embedUrl, cookieStr = '') {
259
64
 
260
65
  observer.observe(document.documentElement, { childList: true, subtree: true });
261
66
  })();
262
- </script>`;
263
- const beforePlyr = patched.replace(/(<script[^>]+\/plyr\.min\.js[^>]*><\/script>)/i, `$1${plyrPatch}`);
264
- const final = beforePlyr
265
- .replace(/(<script[^>]+hls(?:\.min)?\.js[^>]*><\/script>)/i, `$1${hlsPatch}`)
266
- .replace(/<\/body>/i, `${endBridgePatch}</body>`);
267
- return final === html ? null : final;
268
- }
269
- function placeholderSvg() {
270
- const possiblePaths = [
271
- path.join(CONFIG.PACKAGE_ROOT, 'client/public/placeholder.svg'),
272
- path.join(CONFIG.PACKAGE_ROOT, 'client/dist/placeholder.svg'),
273
- path.join(CONFIG.SERVER_ROOT, '..', 'client/public/placeholder.svg'),
274
- ];
275
- for (const p of possiblePaths) {
276
- try {
277
- if (fs.existsSync(p)) {
278
- const headers = new Headers();
279
- headers.set('Cache-Control', 'no-store');
280
- headers.set('Content-Type', 'image/svg+xml');
281
- return new Response(fs.readFileSync(p), { headers });
282
- }
283
- }
284
- catch {
285
- // ignore
286
- }
287
- }
288
- return null;
289
- }
290
- const PROXY_UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:126.0) Gecko/20100101 Firefox/126.0';
291
- export function registerProxy(app, getProxyRules) {
292
- app.get('/api/proxy', async (c) => {
293
- const url = c.req.query('url');
294
- if (!url)
295
- return c.text('URL required', 400);
296
- const safeCheck = isSafeExternalUrl(url);
297
- if (!safeCheck.safe) {
298
- return c.text(safeCheck.error || 'Invalid URL', 400);
299
- }
300
- const urlStr = url;
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') || '';
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;
308
- const variantRaw = c.req.query('variant');
309
- const variantIdx = variantRaw !== undefined ? parseInt(variantRaw, 10) : NaN;
310
- const pinVar = Number.isInteger(variantIdx) && variantIdx >= 0;
311
- const cacheKey = `m3u8-${urlStr}-${refererStr}-${originStr}-${uaStr}${pinVar ? `-v${variantIdx}` : ''}`;
312
- const abort = new AbortController();
313
- linkAbort(c.req.raw.signal, abort);
314
- try {
315
- const headers = {
316
- 'User-Agent': uaStr || PROXY_UA,
317
- };
318
- if (refererStr)
319
- headers['Referer'] = refererStr;
320
- if (originStr)
321
- headers['Origin'] = originStr;
322
- if (cookieStr) {
323
- const cookieHeader = useCf ? buildCfClearanceCookie(cookieStr) : cookieStr;
324
- if (cookieHeader)
325
- headers['Cookie'] = cookieHeader;
326
- }
327
- const range = c.req.header('range');
328
- if (range)
329
- headers['Range'] = range;
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) => {
337
- const baseUrl = new URL(finalUrl);
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);
355
- const isProxied = (value) => value.includes('/api/proxy');
356
- const pinned = pinVar ? pinVariant(playlistBody, variantIdx) : null;
357
- return (pinned || playlistBody)
358
- .split('\n')
359
- .map((line) => {
360
- const trimmed = line.trim();
361
- if (!trimmed)
362
- return line;
363
- if (trimmed.startsWith('#')) {
364
- return trimmed.replace(/URI="([^"]+)"/g, (_, uri) => {
365
- if (isProxied(uri))
366
- return `URI="${uri}"`;
367
- const absolute = new URL(uri, baseUrl).href;
368
- return `URI="${needsProxy || absolute.includes('.m3u8') ? proxiedMediaUrl(absolute) : absolute}"`;
369
- });
370
- }
371
- if (isProxied(trimmed))
372
- return line;
373
- const absolute = new URL(trimmed, baseUrl).href;
374
- return proxiedMediaUrl(absolute);
375
- })
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);
399
- proxyCache.set(cacheKey, rewritten);
400
- return playlistResponse(rewritten);
401
- }
402
- else {
403
- if (useScrape) {
404
- if (range)
405
- headers['Range'] = range;
406
- const resp = await fetchWithMirrors(urlStr, rule?.mirrors, abort.signal, (candidate) => gotScraping({
407
- url: candidate,
408
- method: 'GET',
409
- headers,
410
- responseType: 'buffer',
411
- timeout: { request: 30000 },
412
- followRedirect: true,
413
- throwHttpErrors: false,
414
- }), (attempt) => attempt.statusCode === 200 || attempt.statusCode === 206);
415
- if (!resp || (resp.statusCode !== 200 && resp.statusCode !== 206)) {
416
- return c.text('Upstream error', (resp?.statusCode || 502));
417
- }
418
- const ct = resp.headers['content-type'];
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
- }
429
- const outHeaders = new Headers();
430
- outHeaders.set('Content-Type', isVtt ? 'text/vtt; charset=utf-8' : ct || 'application/octet-stream');
431
- setProxyHeaders(outHeaders, (name) => resp.headers[name]);
432
- return new Response(resp.body, {
433
- status: resp.statusCode,
434
- headers: outHeaders,
435
- });
436
- }
437
- else {
438
- const upstream = await fetchWithMirrors(urlStr, rule?.mirrors, abort.signal, async (candidate) => {
439
- const attempt = await fetchWithRetry(candidate, { method: 'GET', headers }, { retries: 3, timeoutMs: 30000, signal: abort.signal });
440
- if (attempt.status !== 200 && attempt.status !== 206) {
441
- try {
442
- await attempt.body?.cancel();
443
- }
444
- catch {
445
- // ignore
446
- }
447
- }
448
- return attempt;
449
- }, (attempt) => attempt.status === 200 || attempt.status === 206);
450
- if (!upstream) {
451
- return c.text('Upstream error', 502);
452
- }
453
- const status = upstream.status;
454
- if (status !== 200 && status !== 206) {
455
- try {
456
- await upstream.body?.cancel();
457
- }
458
- catch {
459
- // ignore
460
- }
461
- return c.text('Upstream error', status);
462
- }
463
- const isVtt = urlStr.endsWith('.vtt') || urlStr.includes('.vtt?');
464
- const ct = upstream.headers.get('content-type');
465
- const outHeaders = new Headers();
466
- outHeaders.set('Content-Type', isVtt ? 'text/vtt; charset=utf-8' : ct || 'application/octet-stream');
467
- setProxyHeaders(outHeaders, (name) => upstream.headers.get(name));
468
- if (!upstream.body) {
469
- return c.text('Upstream error', 502);
470
- }
471
- return new Response(upstream.body, { status: status, headers: outHeaders });
472
- }
473
- }
474
- }
475
- catch (e) {
476
- if (abort.signal.aborted)
477
- return new Response(null, { status: 499 });
478
- logger.error({ url: urlStr, error: e?.message }, '[proxy] upstream request failed');
479
- return c.text('Proxy error', 500);
480
- }
481
- });
482
- app.get('/api/embed-proxy', async (c) => {
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)
491
- return c.text('Invalid or unsupported gateway URL', 400);
492
- const embedUrl = new URL(rawUrl);
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'));
496
- const abort = new AbortController();
497
- linkAbort(c.req.raw.signal, abort);
498
- try {
499
- const headers = {
500
- 'User-Agent': c.req.query('ua') || PROXY_UA,
501
- };
502
- if (refererStr)
503
- headers['Referer'] = refererStr;
504
- if (originStr)
505
- headers['Origin'] = originStr;
506
- if (cookieStr) {
507
- const cookieHeader = c.req.query('cf') === '1' || rule.cfCookie ? buildCfClearanceCookie(cookieStr) : cookieStr;
508
- if (cookieHeader)
509
- headers['Cookie'] = cookieHeader;
510
- }
511
- const response = await gotScraping({
512
- url: embedUrl.href,
513
- method: 'GET',
514
- headers,
515
- responseType: 'text',
516
- timeout: { request: 30000 },
517
- followRedirect: true,
518
- throwHttpErrors: false,
519
- });
520
- const originalHtml = response.body;
521
- const patched = applyEmbedPatches(originalHtml, embedUrl, cookieStr);
522
- if (!patched)
523
- return c.text('Failed to patch video gateway', 502);
524
- const outHeaders = new Headers();
525
- outHeaders.set('Content-Type', 'text/html; charset=utf-8');
526
- outHeaders.set('Cache-Control', 'private, max-age=120');
527
- return new Response(patched, { status: 200, headers: outHeaders });
528
- }
529
- catch {
530
- if (abort.signal.aborted)
531
- return new Response(null, { status: 499 });
532
- return c.text('Gateway proxy error', 502);
533
- }
534
- });
535
- app.get('/api/subtitle-proxy', async (c) => {
536
- const url = c.req.query('url');
537
- if (!url)
538
- return c.text('URL required', 400);
539
- const safeCheck = isSafeExternalUrl(url);
540
- if (!safeCheck.safe) {
541
- return c.text(safeCheck.error || 'Invalid URL', 400);
542
- }
543
- const abort = new AbortController();
544
- linkAbort(c.req.raw.signal, abort);
545
- const subUrl = url;
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;
550
- const buildHeaders = () => {
551
- const headers = {
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',
553
- Accept: 'text/vtt,text/plain,*/*;q=0.8',
554
- };
555
- if (refererStr)
556
- headers['Referer'] = refererStr;
557
- if (originStr)
558
- headers['Origin'] = originStr;
559
- return headers;
560
- };
561
- const fetchText = async (target, retries = 1) => {
562
- const headers = buildHeaders();
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
- }
591
- }
592
- try {
593
- const upstream = await fetchWithRetry(target, { method: 'GET', headers }, { retries, timeoutMs: 15000, signal: abort.signal });
594
- if (!upstream.ok) {
595
- logger.warn({ target, status: upstream.status }, '[subtitle-proxy] upstream returned error status');
596
- try {
597
- await upstream.body?.cancel();
598
- }
599
- catch {
600
- // ignore
601
- }
602
- return { status: upstream.status, body: '' };
603
- }
604
- const body = (await upstream.text()).replace(/^\uFEFF/, '');
605
- if (/^\s*\{[\s\S]*"error"[\s\S]*\}\s*$/.test(body.slice(0, 500))) {
606
- logger.warn({ target, body: body.slice(0, 200) }, '[subtitle-proxy] upstream JSON error');
607
- return { status: 502, body: '' };
608
- }
609
- return { status: upstream.status, body };
610
- }
611
- catch (e) {
612
- if (abort.signal.aborted)
613
- return null;
614
- logger.warn({ target, error: e?.message }, '[subtitle-proxy] upstream fetch failed');
615
- return { status: 502, body: '' };
616
- }
617
- };
618
- const vttHeaders = (cacheControl) => {
619
- const headers = new Headers();
620
- headers.set('Content-Type', 'text/vtt; charset=utf-8');
621
- headers.set('Access-Control-Allow-Origin', '*');
622
- headers.set('Cache-Control', cacheControl);
623
- return headers;
624
- };
625
- const subtitleError = (text) => {
626
- const headers = new Headers();
627
- headers.set('Access-Control-Allow-Origin', '*');
628
- headers.set('Cache-Control', 'no-store');
629
- return new Response(text, { status: 502, headers });
630
- };
631
- try {
632
- const headers = buildHeaders();
633
- const result = await fetchText(subUrl, 1);
634
- if (abort.signal.aborted)
635
- return new Response(null, { status: 499 });
636
- if (!result || !result.body) {
637
- return subtitleError('Subtitle upstream error');
638
- }
639
- const body = result.body;
640
- const baseForSegments = subUrl;
641
- if (/#EXTM3U/i.test(body.slice(0, 1000))) {
642
- const segUrls = body
643
- .split('\n')
644
- .map((l) => l.trim())
645
- .filter((l) => l && !l.startsWith('#'))
646
- .slice(0, 200)
647
- .map((u) => {
648
- try {
649
- return new URL(u, baseForSegments).href;
650
- }
651
- catch {
652
- return null;
653
- }
654
- })
655
- .filter((u) => Boolean(u));
656
- const parts = [];
657
- for (const seg of segUrls) {
658
- try {
659
- const segRes = await fetchWithRetry(seg, { method: 'GET', headers }, { retries: 1, timeoutMs: 15000, signal: abort.signal });
660
- if (!segRes.ok)
661
- continue;
662
- let segBody = (await segRes.text()).replace(/^\uFEFF/, '');
663
- if (/#EXTM3U/i.test(segBody.slice(0, 200)))
664
- continue;
665
- segBody = segBody
666
- .replace(/\r\n/g, '\n')
667
- .replace(/(\d{2}:\d{2}:\d{2}),(\d{3})/g, '$1.$2');
668
- if (/^\s*WEBVTT/i.test(segBody)) {
669
- segBody = segBody.replace(/^\s*WEBVTT[^\n]*\n(\n|.*\n)?/, '');
670
- }
671
- segBody = segBody.trim();
672
- if (segBody)
673
- parts.push(segBody);
674
- if (parts.join('\n').length > 2_000_000)
675
- break;
676
- }
677
- catch {
678
- if (abort.signal.aborted)
679
- return new Response(null, { status: 499 });
680
- }
681
- }
682
- if (parts.length === 0) {
683
- return subtitleError('Subtitle playlist empty');
684
- }
685
- return new Response(rewrapVttCues(`WEBVTT\n\n${parts.join('\n\n')}\n`), {
686
- headers: vttHeaders('public, max-age=86400'),
687
- });
688
- }
689
- if (/^\s*WEBVTT/i.test(body)) {
690
- return new Response(rewrapVttCues(body), {
691
- headers: vttHeaders('public, max-age=86400'),
692
- });
693
- }
694
- const vttFromAss = assToVtt(body);
695
- if (vttFromAss) {
696
- return new Response(rewrapVttCues(vttFromAss), {
697
- headers: vttHeaders('public, max-age=86400'),
698
- });
699
- }
700
- 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') });
701
- }
702
- catch {
703
- if (abort.signal.aborted)
704
- return new Response(null, { status: 499 });
705
- logger.warn({ url: subUrl }, '[subtitle-proxy] failed');
706
- return subtitleError('Subtitle upstream error');
707
- }
708
- });
709
- app.get('/api/image-proxy', async (c) => {
710
- const url = c.req.query('url');
711
- if (!url)
712
- return c.text('URL required', 400);
713
- const safeCheck = isSafeExternalUrl(url);
714
- if (!safeCheck.safe) {
715
- return c.text(safeCheck.error || 'Invalid URL', 400);
716
- }
717
- const targetUrl = url;
718
- const cookie = c.req.query('cookie') || '';
719
- const ua = c.req.query('ua') || '';
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'));
724
- const abort = new AbortController();
725
- linkAbort(c.req.raw.signal, abort);
726
- const headers = {
727
- 'User-Agent': ua ||
728
- 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
729
- };
730
- const cacheKey = `${targetUrl}|${cookie}|${ua}|${refererValue}|${originValue}`;
731
- const cached = imageCache.get(cacheKey);
732
- if (cached) {
733
- const hit = new Headers();
734
- hit.set('Cache-Control', 'public, max-age=604800, immutable');
735
- hit.set('Content-Type', cached.contentType);
736
- hit.set('Access-Control-Allow-Origin', '*');
737
- hit.set('X-Dango-Cache', 'hit');
738
- return new Response(cached.body, { headers: hit });
739
- }
740
- try {
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;
749
- }
750
- let body = null;
751
- let contentType = 'image/webp';
752
- for (let attempt = 0; attempt < 3 && !body; attempt++) {
753
- const resp = await gotScraping({
754
- url: targetUrl,
755
- method: 'GET',
756
- headers,
757
- responseType: 'buffer',
758
- followRedirect: true,
759
- throwHttpErrors: false,
760
- timeout: { request: 30000 },
761
- });
762
- if (resp.statusCode === 200 && resp.rawBody?.length) {
763
- body = Buffer.from(resp.rawBody);
764
- contentType = String(resp.headers['content-type'] || 'image/webp');
765
- }
766
- else {
767
- if (attempt < 2)
768
- await new Promise((r) => setTimeout(r, 400 * (attempt + 1)));
769
- }
770
- }
771
- if (body) {
772
- imageCache.set(cacheKey, { body, contentType });
773
- const outHeaders = new Headers();
774
- outHeaders.set('Cache-Control', 'public, max-age=604800, immutable');
775
- outHeaders.set('Content-Type', contentType);
776
- outHeaders.set('Access-Control-Allow-Origin', '*');
777
- return new Response(body, { headers: outHeaders });
778
- }
779
- // strict=1 callers retry another candidate instead of settling for the
780
- // placeholder, which would otherwise read as a successful load.
781
- if (strict)
782
- return c.text('Not Found', 404);
783
- const placeholder = placeholderSvg();
784
- if (placeholder)
785
- return placeholder;
786
- return c.text('Not Found', 404);
787
- }
788
- catch {
789
- if (abort.signal.aborted)
790
- return new Response(null, { status: 499 });
791
- if (strict)
792
- return c.text('Not Found', 502);
793
- const placeholder = placeholderSvg();
794
- if (placeholder)
795
- return placeholder;
796
- return c.text('Not Found', 404);
797
- }
798
- });
799
- app.get('/api/resolve', async (c) => {
800
- const targetUrl = c.req.query('url');
801
- if (!targetUrl || typeof targetUrl !== 'string') {
802
- return c.json({ error: 'URL required' }, 400);
803
- }
804
- const safeCheck = isSafeExternalUrl(targetUrl);
805
- if (!safeCheck.safe) {
806
- return c.json({ error: safeCheck.error || 'Invalid URL' }, 400);
807
- }
808
- try {
809
- const response = await fetch(targetUrl, {
810
- method: 'GET',
811
- headers: {
812
- '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',
813
- Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
814
- },
815
- redirect: 'follow',
816
- });
817
- return c.json({
818
- finalUrl: response.url,
819
- status: response.status,
820
- });
821
- }
822
- catch (e) {
823
- const err = e;
824
- return c.json({ error: err.message || 'Resolve failed' }, 500);
825
- }
826
- });
827
- }
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};