@serifpersia/dango 3.0.3

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 (141) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +247 -0
  3. package/client/dist/assets/AnimeInfo-CMxs0nRH.css +1 -0
  4. package/client/dist/assets/AnimeInfo-DVdaocCl.js +1 -0
  5. package/client/dist/assets/AnimeInfoPage-Bx5EvGt-.js +2 -0
  6. package/client/dist/assets/Asmr-C2i9GKD4.css +1 -0
  7. package/client/dist/assets/Asmr-CIjmFtRH.js +2 -0
  8. package/client/dist/assets/Asmr.module-DPG2JhDJ.js +1 -0
  9. package/client/dist/assets/AvSyncCalibrator-CIYcyon5.css +1 -0
  10. package/client/dist/assets/AvSyncCalibrator-vqErH7Cm.js +10 -0
  11. package/client/dist/assets/ErrorMessage-BwSjzblw.js +1 -0
  12. package/client/dist/assets/ErrorMessage-CXoRbrLN.css +1 -0
  13. package/client/dist/assets/Home-D7YfGBUk.js +1 -0
  14. package/client/dist/assets/Home-DKCfEiwl.css +1 -0
  15. package/client/dist/assets/Insights-BljI64Ua.css +1 -0
  16. package/client/dist/assets/Insights-CLQGrlpX.js +1 -0
  17. package/client/dist/assets/Manga-DQ9HYnpk.js +1 -0
  18. package/client/dist/assets/Manga-H8Wb04w3.css +1 -0
  19. package/client/dist/assets/Map-CXFqXmsZ.css +1 -0
  20. package/client/dist/assets/Map-L_ryVBTq.js +1 -0
  21. package/client/dist/assets/Mature-BeWzAJ7B.js +1 -0
  22. package/client/dist/assets/Mature-DJmUUtlF.css +1 -0
  23. package/client/dist/assets/Player-CYpd__FP.js +30 -0
  24. package/client/dist/assets/Player-DreAhybc.css +1 -0
  25. package/client/dist/assets/PlayerRedirect-fxUAp9JV.js +1 -0
  26. package/client/dist/assets/PlayerSettings-BcsotxP9.js +1 -0
  27. package/client/dist/assets/PlayerSettings-Cqua1CDD.css +1 -0
  28. package/client/dist/assets/QueueRail-B9Pgbqc3.js +1 -0
  29. package/client/dist/assets/QueueRail-BnaltOP8.css +1 -0
  30. package/client/dist/assets/Radio-BvdY5twD.js +1 -0
  31. package/client/dist/assets/Radio-CUuYy3DY.css +1 -0
  32. package/client/dist/assets/Search-BzO-aRP7.css +1 -0
  33. package/client/dist/assets/Search-R9YLAXS1.js +1 -0
  34. package/client/dist/assets/Search.module-D8ZbQAiK.js +1 -0
  35. package/client/dist/assets/Settings-9kYcXdTR.js +21 -0
  36. package/client/dist/assets/Settings-DF8JThUL.css +1 -0
  37. package/client/dist/assets/SkeletonGrid-D5vRvX5q.js +1 -0
  38. package/client/dist/assets/SkeletonGrid-DEbYtO4H.css +1 -0
  39. package/client/dist/assets/SynopsisText-BlFw0R-0.js +1 -0
  40. package/client/dist/assets/SynopsisText-CUgOjOxt.css +1 -0
  41. package/client/dist/assets/Trackers-DfZlnnVq.js +2 -0
  42. package/client/dist/assets/Trackers-Dt9lR_uP.css +1 -0
  43. package/client/dist/assets/Tv-CVKC2YBs.css +1 -0
  44. package/client/dist/assets/Tv-Db1Wx_b7.js +15 -0
  45. package/client/dist/assets/Watchlist-B2nkNZqM.css +1 -0
  46. package/client/dist/assets/Watchlist-a77CYtzu.js +1 -0
  47. package/client/dist/assets/hls-6Owgw2Aj.js +1 -0
  48. package/client/dist/assets/hls-BOLXTnjL.js +42 -0
  49. package/client/dist/assets/index-B6qdXFtl.css +1 -0
  50. package/client/dist/assets/index-B8TPVaIS.js +30354 -0
  51. package/client/dist/assets/index-CpjgU8ou.js +178 -0
  52. package/client/dist/assets/useAnimeInfoData-BOfyi9UJ.js +1 -0
  53. package/client/dist/assets/useIsMobile-CM15uQn8.css +1 -0
  54. package/client/dist/assets/useIsMobile-Cb8HcKRj.js +1 -0
  55. package/client/dist/assets/useMatureConsent-CZMYBVXd.js +1 -0
  56. package/client/dist/assets/vendor-rLNfZm7Y.js +1 -0
  57. package/client/dist/av-sync-test.mp4 +0 -0
  58. package/client/dist/favicon.ico +0 -0
  59. package/client/dist/index.html +31 -0
  60. package/client/dist/logo-mochi.png +0 -0
  61. package/client/dist/logo-sakura.png +0 -0
  62. package/client/dist/logo.png +0 -0
  63. package/client/dist/placeholder.svg +4 -0
  64. package/client/dist/robots.txt +3 -0
  65. package/orchestrator.js +416 -0
  66. package/package.json +89 -0
  67. package/server/dist/app-auth.js +231 -0
  68. package/server/dist/config.js +133 -0
  69. package/server/dist/controllers/auth.controller.js +247 -0
  70. package/server/dist/controllers/data.controller.js +785 -0
  71. package/server/dist/controllers/insights.controller.js +197 -0
  72. package/server/dist/controllers/proxy.controller.js +665 -0
  73. package/server/dist/controllers/settings.controller.js +267 -0
  74. package/server/dist/controllers/watchlist.controller.js +967 -0
  75. package/server/dist/db.js +152 -0
  76. package/server/dist/discord-gateway.js +381 -0
  77. package/server/dist/discord-rpc.js +424 -0
  78. package/server/dist/github-sync.js +403 -0
  79. package/server/dist/google.js +549 -0
  80. package/server/dist/lib/anilist.js +1519 -0
  81. package/server/dist/lib/anischedule.js +202 -0
  82. package/server/dist/lib/ipc.js +32 -0
  83. package/server/dist/lib/kitsu.js +552 -0
  84. package/server/dist/lib/mal.js +886 -0
  85. package/server/dist/lib/migration.js +335 -0
  86. package/server/dist/lib/temp-ids.js +11 -0
  87. package/server/dist/lib/tmdb.js +71 -0
  88. package/server/dist/lib/tracker/anilist-tracker.js +250 -0
  89. package/server/dist/lib/tracker/sync.service.js +312 -0
  90. package/server/dist/logger.js +19 -0
  91. package/server/dist/providers/123anime.provider.js +240 -0
  92. package/server/dist/providers/anilight.provider.js +366 -0
  93. package/server/dist/providers/animepahe.provider.js +531 -0
  94. package/server/dist/providers/animeya.provider.js +746 -0
  95. package/server/dist/providers/base-provider.js +45 -0
  96. package/server/dist/providers/hn.provider.js +473 -0
  97. package/server/dist/providers/ht.provider.js +439 -0
  98. package/server/dist/providers/jasmr.provider.js +504 -0
  99. package/server/dist/providers/kaa.provider.js +388 -0
  100. package/server/dist/providers/manga/manga.types.js +10 -0
  101. package/server/dist/providers/manga/mangadex.provider.js +227 -0
  102. package/server/dist/providers/manga/mangapill.provider.js +232 -0
  103. package/server/dist/providers/megaplay.provider.js +583 -0
  104. package/server/dist/providers/op.provider.js +440 -0
  105. package/server/dist/providers/provider.interface.js +2 -0
  106. package/server/dist/providers/title-matching.js +234 -0
  107. package/server/dist/providers/wh.provider.js +600 -0
  108. package/server/dist/rclone.js +126 -0
  109. package/server/dist/repositories/insights.repository.js +43 -0
  110. package/server/dist/repositories/mal-cache.repository.js +66 -0
  111. package/server/dist/repositories/notifications.repository.js +40 -0
  112. package/server/dist/repositories/queue.repository.js +64 -0
  113. package/server/dist/repositories/settings.repository.js +15 -0
  114. package/server/dist/repositories/shows-meta.repository.js +43 -0
  115. package/server/dist/repositories/temp-show-ids.repository.js +54 -0
  116. package/server/dist/repositories/watched-episodes.repository.js +54 -0
  117. package/server/dist/repositories/watchlist.repository.js +74 -0
  118. package/server/dist/routes/asmr.routes.js +72 -0
  119. package/server/dist/routes/auth.routes.js +28 -0
  120. package/server/dist/routes/data.routes.js +50 -0
  121. package/server/dist/routes/discord-gateway.routes.js +57 -0
  122. package/server/dist/routes/insights.routes.js +12 -0
  123. package/server/dist/routes/lan-auth.routes.js +49 -0
  124. package/server/dist/routes/manga.routes.js +115 -0
  125. package/server/dist/routes/proxy.routes.js +15 -0
  126. package/server/dist/routes/radio.routes.js +143 -0
  127. package/server/dist/routes/settings.routes.js +27 -0
  128. package/server/dist/routes/tracker.routes.js +143 -0
  129. package/server/dist/routes/translate.routes.js +90 -0
  130. package/server/dist/routes/tv.routes.js +843 -0
  131. package/server/dist/routes/watchlist.routes.js +312 -0
  132. package/server/dist/server.js +310 -0
  133. package/server/dist/shipped-defaults.js +8 -0
  134. package/server/dist/sync-config.js +32 -0
  135. package/server/dist/sync.js +506 -0
  136. package/server/dist/utils/cookie.utils.js +17 -0
  137. package/server/dist/utils/db-utils.js +15 -0
  138. package/server/dist/utils/env.utils.js +83 -0
  139. package/server/dist/utils/machine-id.js +46 -0
  140. package/server/dist/utils/request-context.js +5 -0
  141. package/server/dist/utils/security.utils.js +99 -0
@@ -0,0 +1,583 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.MegaPlayProvider = void 0;
7
+ const node_crypto_1 = require("node:crypto");
8
+ const logger_1 = __importDefault(require("../logger"));
9
+ const anilist_1 = require("../lib/anilist");
10
+ const kitsu_1 = require("../lib/kitsu");
11
+ class MegaPlayProvider {
12
+ name = 'MegaPlay';
13
+ megaPlayBase = 'https://megaplay.buzz/stream/ani';
14
+ cache;
15
+ constructor(cache) {
16
+ this.cache = cache;
17
+ }
18
+ stripHtml(input) {
19
+ if (!input)
20
+ return '';
21
+ return input
22
+ .replace(/<br\s*\/?>/gi, '\n')
23
+ .replace(/<[^>]+>/g, '')
24
+ .replace(/&amp;/g, '&')
25
+ .replace(/&quot;/g, '"')
26
+ .replace(/&#0?39;|&apos;/g, "'")
27
+ .replace(/&lt;/g, '<')
28
+ .replace(/&gt;/g, '>')
29
+ .trim();
30
+ }
31
+ toShow(media) {
32
+ const id = media.id.toString();
33
+ const title = media.title;
34
+ const name = title?.romaji || title?.english || title?.native || 'Unknown';
35
+ return {
36
+ _id: id,
37
+ id,
38
+ name,
39
+ englishName: title?.english,
40
+ nativeName: title?.native,
41
+ names: {
42
+ romaji: title?.romaji,
43
+ english: title?.english,
44
+ native: title?.native,
45
+ },
46
+ thumbnail: media.coverImage?.large,
47
+ type: media.format,
48
+ year: media.seasonYear ?? null,
49
+ episodeCount: media.episodes ?? null,
50
+ description: this.stripHtml(media.description),
51
+ status: media.status,
52
+ genres: media.genres?.map((g) => ({ name: g })),
53
+ score: media.averageScore ?? null,
54
+ };
55
+ }
56
+ normalizeTitle(title) {
57
+ return title
58
+ .toLowerCase()
59
+ .replace(/[^\w\s-]/g, '')
60
+ .replace(/\s+/g, ' ')
61
+ .trim();
62
+ }
63
+ bestMatch(results, query) {
64
+ const q = this.normalizeTitle(query);
65
+ let best = results[0];
66
+ let bestScore = -1;
67
+ for (const anime of results) {
68
+ const title = anime.title ? this.normalizeTitle(anime.title.romaji || '') : '';
69
+ const englishTitle = anime.title?.english ? this.normalizeTitle(anime.title.english) : '';
70
+ const nativeTitle = anime.title?.native ? this.normalizeTitle(anime.title.native) : '';
71
+ let score = -1;
72
+ if (title === q || englishTitle === q || nativeTitle === q) {
73
+ score = 3;
74
+ }
75
+ else if (title.startsWith(q) || englishTitle.startsWith(q) || nativeTitle.startsWith(q)) {
76
+ score = 2;
77
+ }
78
+ else if (title.includes(q) || englishTitle.includes(q) || nativeTitle.includes(q)) {
79
+ score = 1;
80
+ }
81
+ if (score > bestScore) {
82
+ bestScore = score;
83
+ best = anime;
84
+ if (score === 3)
85
+ break;
86
+ }
87
+ }
88
+ return best;
89
+ }
90
+ mediaFields = `
91
+ id
92
+ idMal
93
+ title { romaji english native }
94
+ coverImage { large }
95
+ format
96
+ seasonYear
97
+ episodes
98
+ description
99
+ status
100
+ genres
101
+ averageScore
102
+ `;
103
+ async search(options) {
104
+ try {
105
+ const rawQuery = options.query || '';
106
+ const query = rawQuery.replace(/[""]/g, '').replace(/[']/g, '').replace(/\s+/g, ' ').trim();
107
+ if (!query)
108
+ return [];
109
+ const gql = `query ($q: String, $page: Int, $perPage: Int) {
110
+ Page (page: $page, perPage: $perPage) {
111
+ media (search: $q, type: ANIME) {
112
+ ${this.mediaFields}
113
+ }
114
+ }
115
+ }`;
116
+ const data = await (0, anilist_1.anilistRequest)(gql, {
117
+ q: query,
118
+ page: 1,
119
+ perPage: 20,
120
+ });
121
+ const media = data?.data?.Page?.media;
122
+ if (!media || media.length === 0)
123
+ return [];
124
+ const results = media.map((m) => this.toShow(m));
125
+ if (results.length > 0) {
126
+ const best = this.bestMatch(media, query);
127
+ const bestIndex = media.findIndex((m) => m.id === best.id);
128
+ if (bestIndex > 0) {
129
+ const [bestItem] = results.splice(bestIndex, 1);
130
+ results.unshift(bestItem);
131
+ }
132
+ }
133
+ return results;
134
+ }
135
+ catch (error) {
136
+ logger_1.default.error({ error }, 'MegaPlay (AniList) search failed');
137
+ return [];
138
+ }
139
+ }
140
+ async resolveShowId(title, _romaji) {
141
+ const results = await this.search({ query: title });
142
+ return results[0]?._id || null;
143
+ }
144
+ async getEpisodes(showId) {
145
+ try {
146
+ if (!/^\d+$/.test(showId))
147
+ return null;
148
+ const cacheKey = `megaplay_eps_${showId}`;
149
+ const cached = this.cache.get(cacheKey);
150
+ if (cached)
151
+ return cached;
152
+ const gql = `query ($id: Int) {
153
+ Media (id: $id, type: ANIME) {
154
+ episodes
155
+ status
156
+ idMal
157
+ }
158
+ }`;
159
+ const data = await (0, anilist_1.anilistRequest)(gql, { id: Number(showId) });
160
+ const media = data?.data?.Media;
161
+ if (!media)
162
+ return null;
163
+ const episodeCount = media.episodes || 0;
164
+ let count = episodeCount;
165
+ if (count === 0) {
166
+ if (media.status === 'RELEASING' || media.status === 'FINISHED') {
167
+ count = 12;
168
+ }
169
+ }
170
+ const episodes = Array.from({ length: count }, (_, i) => (i + 1).toString());
171
+ const result = {
172
+ episodes,
173
+ description: '',
174
+ };
175
+ this.cache.set(cacheKey, result, 86400);
176
+ return result;
177
+ }
178
+ catch (error) {
179
+ logger_1.default.error({ error, showId }, 'MegaPlay getEpisodes failed');
180
+ return null;
181
+ }
182
+ }
183
+ megaPlayHeaders = {
184
+ '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',
185
+ Referer: 'https://megaplay.buzz/',
186
+ };
187
+ decodeScriptString(value) {
188
+ return value.replace(/\\u([\dA-Fa-f]{4})|\\x([\dA-Fa-f]{2})|\\([\\'"bnfrtv0])/g, (_, unicode, hex, escaped) => {
189
+ if (unicode)
190
+ return String.fromCharCode(parseInt(unicode, 16));
191
+ if (hex)
192
+ return String.fromCharCode(parseInt(hex, 16));
193
+ return ({ b: '\b', n: '\n', f: '\f', r: '\r', t: '\t', v: '\v', 0: '\0' }[escaped] ?? escaped);
194
+ });
195
+ }
196
+ getScriptStrings(script) {
197
+ const strings = [];
198
+ let index = 0;
199
+ let previous = '';
200
+ while (index < script.length) {
201
+ const char = script[index];
202
+ if (char === '/' && script[index + 1] === '/') {
203
+ index = script.indexOf('\n', index + 2);
204
+ if (index < 0)
205
+ break;
206
+ continue;
207
+ }
208
+ if (char === '/' && script[index + 1] === '*') {
209
+ index = script.indexOf('*/', index + 2);
210
+ if (index < 0)
211
+ break;
212
+ index += 2;
213
+ continue;
214
+ }
215
+ if (char === '/' && /[=(:,[!&|?{};]/.test(previous)) {
216
+ index++;
217
+ let inClass = false;
218
+ while (index < script.length) {
219
+ if (script[index] === '\\') {
220
+ index += 2;
221
+ continue;
222
+ }
223
+ if (script[index] === '[')
224
+ inClass = true;
225
+ if (script[index] === ']')
226
+ inClass = false;
227
+ if (script[index] === '/' && !inClass) {
228
+ index++;
229
+ while (/[a-z]/i.test(script[index] ?? ''))
230
+ index++;
231
+ break;
232
+ }
233
+ index++;
234
+ }
235
+ continue;
236
+ }
237
+ if (char === "'" || char === '"') {
238
+ const quote = char;
239
+ let value = '';
240
+ index++;
241
+ while (index < script.length && script[index] !== quote) {
242
+ if (script[index] === '\\' && index + 1 < script.length)
243
+ value += script[index++];
244
+ value += script[index++];
245
+ }
246
+ strings.push(this.decodeScriptString(value));
247
+ index++;
248
+ continue;
249
+ }
250
+ if (char === '`') {
251
+ index++;
252
+ while (index < script.length && script[index] !== '`')
253
+ index += script[index] === '\\' ? 2 : 1;
254
+ index++;
255
+ continue;
256
+ }
257
+ if (!/\s/.test(char))
258
+ previous = char;
259
+ index++;
260
+ }
261
+ return [...new Set(strings)];
262
+ }
263
+ async getMegaPlayClientScript(pageUrl, html) {
264
+ const cacheKey = 'megaplay_client_script';
265
+ const cached = this.cache.get(cacheKey);
266
+ if (cached)
267
+ return cached;
268
+ const scriptUrls = [...html.matchAll(/<script[^>]+src="([^"]+)"[^>]*>/gi)].map((m) => new URL(m[1], pageUrl).href);
269
+ const scripts = await Promise.all(scriptUrls.map(async (url) => {
270
+ try {
271
+ const res = await fetch(url, {
272
+ headers: { ...this.megaPlayHeaders, Referer: pageUrl },
273
+ signal: AbortSignal.timeout(15000),
274
+ });
275
+ if (!res.ok)
276
+ return null;
277
+ return await res.text();
278
+ }
279
+ catch {
280
+ return null;
281
+ }
282
+ }));
283
+ const script = scripts.find((s) => s && /getSources/i.test(s) && /AES-CBC/i.test(s)) ?? null;
284
+ if (script)
285
+ this.cache.set(cacheKey, script, 86400);
286
+ return script;
287
+ }
288
+ decryptMegaPlaySource(enc, script) {
289
+ let encrypted;
290
+ try {
291
+ encrypted = Buffer.from(enc, 'base64url');
292
+ }
293
+ catch {
294
+ return null;
295
+ }
296
+ if (!encrypted.length || encrypted.length % 16 !== 0)
297
+ return null;
298
+ const tryPair = (keyValue, ivValue) => {
299
+ try {
300
+ const key = Buffer.alloc(32);
301
+ Buffer.from(keyValue).copy(key);
302
+ const decipher = (0, node_crypto_1.createDecipheriv)('aes-256-cbc', key, Buffer.from(ivValue));
303
+ const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]);
304
+ const data = JSON.parse(decrypted.toString('utf8'));
305
+ const source = data?.file ?? data?.url;
306
+ return typeof source === 'string' && source ? source : null;
307
+ }
308
+ catch {
309
+ return null;
310
+ }
311
+ };
312
+ const cachedPair = this.cache.get('megaplay_crypt_pair');
313
+ if (cachedPair) {
314
+ const hit = tryPair(cachedPair.keyValue, cachedPair.ivValue);
315
+ if (hit)
316
+ return hit;
317
+ }
318
+ const values = this.getScriptStrings(script).filter((s) => Buffer.byteLength(s) > 0 && Buffer.byteLength(s) <= 32);
319
+ const ivs = values.filter((s) => Buffer.byteLength(s) === 16);
320
+ for (const keyValue of values) {
321
+ for (const ivValue of ivs) {
322
+ const hit = tryPair(keyValue, ivValue);
323
+ if (hit) {
324
+ this.cache.set('megaplay_crypt_pair', { keyValue, ivValue }, 86400);
325
+ return hit;
326
+ }
327
+ }
328
+ }
329
+ return null;
330
+ }
331
+ async fetchMegaPlayData(fileId, pageUrl, clientScript) {
332
+ const routes = clientScript
333
+ ? this.getScriptStrings(clientScript)
334
+ .filter((v) => /^stream\/getSources[\w/-]*$/i.test(v))
335
+ .sort((a, b) => a.length - b.length)
336
+ : [];
337
+ const legacy = routes[0] ?? 'stream/getSources';
338
+ const modern = routes.find((r) => r !== legacy && r.startsWith(legacy)) ?? null;
339
+ const fetchRoute = async (route) => {
340
+ if (!route)
341
+ return null;
342
+ try {
343
+ const url = new URL(route, 'https://megaplay.buzz/');
344
+ url.searchParams.append('id', fileId);
345
+ url.searchParams.append('id', fileId);
346
+ const res = await fetch(url.href, {
347
+ headers: {
348
+ ...this.megaPlayHeaders,
349
+ Referer: pageUrl,
350
+ 'X-Requested-With': 'XMLHttpRequest',
351
+ },
352
+ signal: AbortSignal.timeout(15000),
353
+ });
354
+ if (!res.ok)
355
+ return null;
356
+ return (await res.json());
357
+ }
358
+ catch {
359
+ return null;
360
+ }
361
+ };
362
+ const [modernData, legacyData] = await Promise.all([fetchRoute(modern), fetchRoute(legacy)]);
363
+ const data = modernData ?? legacyData;
364
+ if (!data)
365
+ return null;
366
+ const directFile = (Array.isArray(data.sources) ? data.sources[0]?.file : data.sources?.file) ??
367
+ (Array.isArray(legacyData?.sources) ? legacyData.sources[0]?.file : legacyData?.sources?.file);
368
+ if (directFile) {
369
+ return {
370
+ sources: { file: directFile },
371
+ tracks: modernData?.tracks ?? legacyData?.tracks,
372
+ };
373
+ }
374
+ const enc = modernData && 'enc' in modernData ? modernData.enc : legacyData?.enc;
375
+ if (enc && clientScript) {
376
+ const url = this.decryptMegaPlaySource(enc, clientScript);
377
+ if (url) {
378
+ return {
379
+ sources: { file: url },
380
+ tracks: modernData?.tracks ?? legacyData?.tracks,
381
+ };
382
+ }
383
+ logger_1.default.warn('[MegaPlay] enc decrypt failed');
384
+ }
385
+ return data;
386
+ }
387
+ async getMalId(anilistId) {
388
+ const cacheKey = `megaplay_malid_${anilistId}`;
389
+ const cached = this.cache.get(cacheKey);
390
+ if (cached !== undefined)
391
+ return cached || null;
392
+ try {
393
+ const gql = `query ($id: Int) {
394
+ Media (id: $id, type: ANIME) {
395
+ idMal
396
+ }
397
+ }`;
398
+ const data = await (0, anilist_1.anilistRequest)(gql, {
399
+ id: Number(anilistId),
400
+ });
401
+ const idMal = data?.data?.Media?.idMal;
402
+ if (idMal) {
403
+ const result = String(idMal);
404
+ this.cache.set(cacheKey, result, 86400);
405
+ return result;
406
+ }
407
+ }
408
+ catch {
409
+ // ignore
410
+ }
411
+ try {
412
+ const fb = await (0, kitsu_1.kitsuMetaByAnilistId)(Number(anilistId));
413
+ if (fb?.idMal) {
414
+ const result = String(fb.idMal);
415
+ this.cache.set(cacheKey, result, 86400);
416
+ return result;
417
+ }
418
+ }
419
+ catch {
420
+ // ignore
421
+ }
422
+ this.cache.set(cacheKey, '', 3600);
423
+ return null;
424
+ }
425
+ async tryFetchStream(showId, targetEpisode, mode, endpoint) {
426
+ const base = this.megaPlayBase.replace('/ani', `/${endpoint}`);
427
+ const streamPageUrl = `${base}/${showId}/${targetEpisode}/${mode}`;
428
+ const pageRes = await fetch(streamPageUrl, {
429
+ headers: this.megaPlayHeaders,
430
+ });
431
+ if (!pageRes.ok)
432
+ return null;
433
+ const html = await pageRes.text();
434
+ const idMatch = html.match(/data-id="([0-9]+)"/);
435
+ const extractedId = idMatch ? idMatch[1] : html.match(/<title>File ([0-9]+)/i)?.[1];
436
+ if (!extractedId)
437
+ return null;
438
+ const clientScript = await this.getMegaPlayClientScript(streamPageUrl, html);
439
+ const data = await this.fetchMegaPlayData(extractedId, streamPageUrl, clientScript);
440
+ if (!data)
441
+ return null;
442
+ let sources = [];
443
+ if (Array.isArray(data.sources)) {
444
+ sources = data.sources;
445
+ }
446
+ else if (data.sources && 'file' in data.sources) {
447
+ sources = [data.sources];
448
+ }
449
+ if (sources.length === 0)
450
+ return null;
451
+ const links = [];
452
+ for (const s of sources) {
453
+ if (s.file.includes('.m3u8')) {
454
+ try {
455
+ const masterRes = await fetch(s.file, {
456
+ headers: {
457
+ Referer: 'https://megaplay.buzz/',
458
+ 'User-Agent': this.megaPlayHeaders['User-Agent'],
459
+ },
460
+ signal: AbortSignal.timeout(10000),
461
+ });
462
+ if (masterRes.ok) {
463
+ const playlist = await masterRes.text();
464
+ const variantRe = /#EXT-X-STREAM-INF:([^\n]*)\n(\S+)/g;
465
+ let m;
466
+ while ((m = variantRe.exec(playlist)) !== null) {
467
+ const attrs = m[1];
468
+ const label = attrs.match(/NAME="([^"]+)"/)?.[1] ||
469
+ (attrs.match(/RESOLUTION=\d+x(\d+)/)?.[1] ?? '') + 'p' ||
470
+ '';
471
+ if (!label || label === 'p')
472
+ continue;
473
+ const variantUrl = new URL(m[2], s.file).href;
474
+ links.push({
475
+ resolutionStr: label,
476
+ link: variantUrl,
477
+ hls: true,
478
+ headers: {
479
+ Referer: 'https://megaplay.buzz/',
480
+ 'User-Agent': this.megaPlayHeaders['User-Agent'],
481
+ },
482
+ });
483
+ }
484
+ }
485
+ }
486
+ catch {
487
+ // ignore
488
+ }
489
+ links.push({
490
+ resolutionStr: 'Auto',
491
+ link: s.file,
492
+ hls: true,
493
+ headers: {
494
+ Referer: 'https://megaplay.buzz/',
495
+ 'User-Agent': this.megaPlayHeaders['User-Agent'],
496
+ },
497
+ });
498
+ }
499
+ else {
500
+ links.push({
501
+ resolutionStr: 'Auto',
502
+ link: s.file,
503
+ hls: false,
504
+ headers: {
505
+ Referer: 'https://megaplay.buzz/',
506
+ 'User-Agent': this.megaPlayHeaders['User-Agent'],
507
+ },
508
+ });
509
+ }
510
+ }
511
+ const subtitles = (data.tracks || [])
512
+ .filter((t) => {
513
+ const kind = (t.kind || '').toLowerCase();
514
+ return t.file && (!kind || kind.includes('caption') || kind.includes('sub'));
515
+ })
516
+ .map((t) => ({
517
+ language: t.label || 'Unknown',
518
+ label: t.label || 'Unknown',
519
+ url: t.file,
520
+ }));
521
+ return [
522
+ {
523
+ sourceName: `MegaPlay (${mode.toUpperCase()})`,
524
+ links,
525
+ subtitles,
526
+ type: 'player',
527
+ actualEpisodeNumber: targetEpisode,
528
+ },
529
+ {
530
+ sourceName: `MegaPlay (${mode.toUpperCase()}) [Fallback]`,
531
+ links: [
532
+ {
533
+ link: streamPageUrl,
534
+ resolutionStr: 'Auto',
535
+ hls: false,
536
+ headers: { Referer: 'https://megaplay.buzz/' },
537
+ },
538
+ ],
539
+ subtitles: [],
540
+ type: 'iframe',
541
+ actualEpisodeNumber: targetEpisode,
542
+ },
543
+ ];
544
+ }
545
+ async getStreamUrls(showId, episodeNumber, mode) {
546
+ const malId = (0, anilist_1.parseMalId)(showId);
547
+ const isAnilistId = /^\d+$/.test(showId);
548
+ if (!isAnilistId && malId === null)
549
+ return null;
550
+ let targetEpisode = episodeNumber;
551
+ if (episodeNumber === '0') {
552
+ targetEpisode = '1';
553
+ }
554
+ try {
555
+ const cacheKey = `megaplay_stream_${showId}_${targetEpisode}_${mode}`;
556
+ const cached = this.cache.get(cacheKey);
557
+ if (cached)
558
+ return cached;
559
+ let result;
560
+ if (!isAnilistId && malId !== null) {
561
+ result = await this.tryFetchStream(String(malId), targetEpisode, mode, 'mal');
562
+ }
563
+ else {
564
+ result = await this.tryFetchStream(showId, targetEpisode, mode, 'ani');
565
+ if (!result) {
566
+ const resolvedMalId = await this.getMalId(showId);
567
+ if (resolvedMalId) {
568
+ result = await this.tryFetchStream(resolvedMalId, targetEpisode, mode, 'mal');
569
+ }
570
+ }
571
+ }
572
+ if (result) {
573
+ this.cache.set(cacheKey, result, 3600);
574
+ }
575
+ return result;
576
+ }
577
+ catch (error) {
578
+ logger_1.default.error({ error, showId, episodeNumber, mode }, '[MegaPlay] getStreamUrls failed');
579
+ return null;
580
+ }
581
+ }
582
+ }
583
+ exports.MegaPlayProvider = MegaPlayProvider;