recycleactor commited on
Commit
c2a7aff
·
verified ·
1 Parent(s): 033f465

Delete cinemar.js

Browse files
Files changed (1) hide show
  1. cinemar.js +0 -412
cinemar.js DELETED
@@ -1,412 +0,0 @@
1
- /**
2
- * Cinemar (cinemar.cc) stream extractor using Puppeteer
3
- * cinemar.cc now uses Cloudflare Rocket Loader — data only available after JS execution
4
- */
5
- const { getBrowser } = require('./alloha');
6
- const https = require('https');
7
- const http = require('http');
8
-
9
- function fetchRaw(reqUrl, headers) {
10
- return new Promise((resolve, reject) => {
11
- let u; try { u = new URL(reqUrl); } catch(e) { return reject(e); }
12
- const mod = u.protocol === 'https:' ? https : http;
13
- const req = mod.request({
14
- hostname: u.hostname, path: u.pathname + u.search,
15
- headers: headers || {}
16
- }, res => {
17
- let d = ''; res.setEncoding('utf8');
18
- res.on('data', c => d += c);
19
- res.on('end', () => resolve(d));
20
- });
21
- req.on('error', reject);
22
- req.setTimeout(12000, () => { req.destroy(); reject(new Error('timeout')); });
23
- req.end();
24
- });
25
- }
26
-
27
- // ── Helpers (same as server.js) ───────────────────────────────────────────────
28
-
29
- function findAllInBuf(buf, pattern) {
30
- const positions = [];
31
- let pos = 0;
32
- while (pos < buf.length) {
33
- const idx = buf.indexOf(pattern, pos);
34
- if (idx < 0) break;
35
- positions.push(idx);
36
- pos = idx + 1;
37
- }
38
- return positions;
39
- }
40
-
41
- function readJsonStr(buf, pos) {
42
- let str = '', i = pos;
43
- while (i < buf.length) {
44
- const b = buf[i];
45
- if (b === 0x22) return { str, end: i };
46
- if (b === 0x5C) {
47
- i++;
48
- if (i >= buf.length) break;
49
- const esc = buf[i];
50
- if (esc === 0x75 && i + 4 < buf.length) {
51
- const hex = buf.slice(i + 1, i + 5).toString('ascii');
52
- if (/^[0-9a-fA-F]{4}$/.test(hex)) { str += String.fromCharCode(parseInt(hex, 16)); i += 5; continue; }
53
- }
54
- str += String.fromCharCode(esc);
55
- } else if (b >= 0x20 && b <= 0x7E) {
56
- str += String.fromCharCode(b);
57
- } else if ((b & 0xE0) === 0xC0 && i + 1 < buf.length) {
58
- str += String.fromCodePoint(((b & 0x1F) << 6) | (buf[i+1] & 0x3F)); i += 2; continue;
59
- } else if ((b & 0xF0) === 0xE0 && i + 2 < buf.length) {
60
- str += String.fromCodePoint(((b & 0x0F) << 12) | ((buf[i+1] & 0x3F) << 6) | (buf[i+2] & 0x3F)); i += 3; continue;
61
- } else break;
62
- i++;
63
- }
64
- return { str, end: i };
65
- }
66
-
67
- function findBufVal(buf, key, fromPos, maxDist) {
68
- const keyBuf = Buffer.from('"' + key + '":"');
69
- const idx = buf.indexOf(keyBuf, fromPos);
70
- if (idx < 0 || idx > fromPos + (maxDist || 400)) return null;
71
- return readJsonStr(buf, idx + keyBuf.length);
72
- }
73
-
74
- const FOLDER_MARKER_BUF = Buffer.from('"folder":[');
75
-
76
- function decodeCinemarFile(fileValue) {
77
- const w3sPos = fileValue.indexOf('W3s');
78
- if (w3sPos < 0) return null;
79
- const raw = fileValue.substring(w3sPos);
80
- const chunks = raw.split('&');
81
- const buffers = [];
82
- for (let i = 0; i < chunks.length; i++) {
83
- let chunk = chunks[i];
84
- if (i < chunks.length - 1) chunk = chunk.slice(0, -4);
85
- const clean = chunk.replace(/[^A-Za-z0-9+/=]/g, '');
86
- if (clean.length > 0) {
87
- const padded = clean + '='.repeat((4 - clean.length % 4) % 4);
88
- try { buffers.push(Buffer.from(padded, 'base64')); } catch(e) {}
89
- }
90
- }
91
- if (!buffers.length) return null;
92
- return Buffer.concat(buffers);
93
- }
94
-
95
- function normUrl(f) { return f ? (f.startsWith('//') ? 'https:' + f : f) : ''; }
96
-
97
- function parseSubs(subtitle) {
98
- const subs = [];
99
- if (!subtitle) return subs;
100
- subtitle.split(',').forEach(part => {
101
- const sm = part.match(/^\[([^\]]+)\](\/\/.+|https?:\/\/.+)/);
102
- if (sm) subs.push({ label: sm[1], url: normUrl(sm[2]) });
103
- });
104
- return subs;
105
- }
106
-
107
- // ── Get HTML via Puppeteer (bypasses Cloudflare Rocket Loader) ────────────────
108
- async function getEmbedHtmlViaPuppeteer(embedUrl) {
109
- const browser = await getBrowser();
110
- const page = await browser.newPage();
111
- try {
112
- await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36');
113
- await page.setExtraHTTPHeaders({
114
- 'Accept-Language': 'ru-RU,ru;q=0.9,en;q=0.8',
115
- 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
116
- 'Referer': 'https://uakinogo.io/',
117
- 'Sec-Fetch-Dest': 'iframe',
118
- 'Sec-Fetch-Mode': 'navigate',
119
- 'Sec-Fetch-Site': 'cross-site',
120
- });
121
-
122
- await page.setViewport({ width: 1920, height: 1080 });
123
-
124
- await page.evaluateOnNewDocument(() => {
125
- Object.defineProperty(navigator, 'webdriver', { get: () => false });
126
- window.chrome = { runtime: {} };
127
- Object.defineProperty(navigator, 'plugins', { get: () => [1, 2, 3, 4, 5] });
128
- Object.defineProperty(navigator, 'languages', { get: () => ['ru-RU', 'ru', 'en-US', 'en'] });
129
- });
130
-
131
- await page.setRequestInterception(true);
132
- page.on('request', req => {
133
- const resourceType = req.resourceType();
134
- if (['image', 'stylesheet', 'font', 'media'].includes(resourceType)) {
135
- req.abort();
136
- } else {
137
- req.continue();
138
- }
139
- });
140
-
141
- console.log('[cinemar] puppeteer navigating to:', embedUrl.substring(0, 80));
142
-
143
- await page.goto(embedUrl, {
144
- waitUntil: 'domcontentloaded',
145
- timeout: 30000,
146
- });
147
-
148
- console.log('[cinemar] waiting for Cloudflare challenge...');
149
- await new Promise(r => setTimeout(r, 5000));
150
-
151
- const currentUrl = page.url();
152
- const title = await page.title().catch(() => '');
153
- console.log('[cinemar] current URL:', currentUrl.substring(0, 80));
154
- console.log('[cinemar] page title:', title);
155
-
156
- if (title.includes('Just a moment') || title.includes('Checking your browser')) {
157
- console.log('[cinemar] still on Cloudflare challenge, waiting more...');
158
- await new Promise(r => setTimeout(r, 5000));
159
- }
160
-
161
- await new Promise(r => setTimeout(r, 2000));
162
-
163
- const fileData = await page.evaluate(() => {
164
- for (const k of Object.keys(window)) {
165
- try {
166
- const v = window[k];
167
- if (v && typeof v === 'object' && v.file && typeof v.file === 'string' && v.file.length > 100) {
168
- return JSON.stringify(v);
169
- }
170
- } catch(e) {}
171
- }
172
- for (const s of document.querySelectorAll('script:not([src])')) {
173
- const t = s.textContent || '';
174
- if (t.includes('"file"') && t.includes('W3s')) return t;
175
- }
176
- return document.documentElement.outerHTML;
177
- });
178
-
179
- console.log('[cinemar] puppeteer got data, length:', fileData ? fileData.length : 0);
180
- console.log('[cinemar] has "file":', fileData ? fileData.includes('"file"') : false);
181
- console.log('[cinemar] has "W3s":', fileData ? fileData.includes('W3s') : false);
182
-
183
- return fileData || '';
184
- } finally {
185
- await page.close();
186
- }
187
- }
188
-
189
- // ── Parse cinemar HTML/data ───────────────────────────────────────────────────
190
- function parseCinemarData(html) {
191
- const fileMatch = html.match(/"file":"([^"]+)"/);
192
- if (!fileMatch) return null;
193
- const buf = decodeCinemarFile(fileMatch[1]);
194
- if (!buf || buf.length < 20) return null;
195
-
196
- const isSerial = buf.indexOf(FOLDER_MARKER_BUF) >= 0;
197
-
198
- if (!isSerial) {
199
- try {
200
- const jsonStr = buf.toString('utf8');
201
- const arr = JSON.parse(jsonStr);
202
- if (Array.isArray(arr) && arr.length && arr[0].title) {
203
- const items = arr.map(item => ({
204
- title: item.title || 'Unknown',
205
- file: (item.file || '').replace(/\\\//g, '/').replace(/^\/\//, 'https://'),
206
- subtitle: item.subtitle || ''
207
- })).filter(i => i.file && i.file.includes('cinemap'));
208
- if (items.length) return { isSerial: false, items };
209
- }
210
- } catch(e) {}
211
-
212
- const items = [];
213
- findAllInBuf(buf, Buffer.from('"src_id"')).forEach(pos => {
214
- const t = findBufVal(buf, 'title', pos, 300);
215
- const f = findBufVal(buf, 'file', pos, 5000);
216
- const s = findBufVal(buf, 'subtitle', pos, 6000);
217
- if (!f) return;
218
- const url = normUrl(f.str.replace(/\\\//g, '/'));
219
- if (!url || !url.includes('cinemap')) return;
220
- const title = t ? t.str : 'Unknown';
221
- if (!items.find(i => i.title === title)) items.push({ title, file: url, subtitle: s ? s.str.replace(/\\\//g, '/') : '' });
222
- });
223
- return { isSerial: false, items };
224
- }
225
-
226
- // Serial
227
- const epIdPrefix = Buffer.from('"id":"s');
228
- const allEpPositions = [];
229
- findAllInBuf(buf, epIdPrefix).forEach(pos => {
230
- let i = pos + epIdPrefix.length;
231
- while (i < buf.length && buf[i] >= 0x30 && buf[i] <= 0x39) i++;
232
- if (buf[i] !== 0x65) return;
233
- i++;
234
- while (i < buf.length && buf[i] >= 0x30 && buf[i] <= 0x39) i++;
235
- if (buf[i] !== 0x22) return;
236
- const fullId = buf.slice(pos + 6, i).toString('ascii');
237
- const seasonMatch = fullId.match(/^(s\d+)e(\d+)$/);
238
- if (!seasonMatch) return;
239
- const t = findBufVal(buf, 'title', pos, 200);
240
- allEpPositions.push({ id: fullId, seasonId: seasonMatch[1], epNum: parseInt(seasonMatch[2], 10), title: t ? t.str : ('Серия ' + seasonMatch[2]), pos });
241
- });
242
-
243
- const seasonMap = {};
244
- allEpPositions.forEach(ep => {
245
- if (!seasonMap[ep.seasonId]) seasonMap[ep.seasonId] = [];
246
- if (!seasonMap[ep.seasonId].find(e => e.id === ep.id)) seasonMap[ep.seasonId].push(ep);
247
- });
248
- const seasonIds = Object.keys(seasonMap).sort((a, b) => parseInt(a.substring(1)) - parseInt(b.substring(1)));
249
- if (!seasonIds.length) return null;
250
-
251
- const episodes = {};
252
- seasonIds.forEach(seasonId => {
253
- const eps = seasonMap[seasonId].sort((a, b) => a.epNum - b.epNum);
254
- episodes[seasonId] = eps.map((ep, ei) => {
255
- const nextEpPos = ei + 1 < eps.length ? eps[ei + 1].pos : buf.length;
256
- const eBuf = buf.slice(ep.pos, nextEpPos);
257
- const voices = [];
258
- findAllInBuf(eBuf, Buffer.from('"src_id"')).forEach(vpos => {
259
- const t = findBufVal(eBuf, 'title', vpos, 300);
260
- const f = findBufVal(eBuf, 'file', vpos, 2000);
261
- const s = findBufVal(eBuf, 'subtitle', vpos, 3000);
262
- if (!f) return;
263
- const url = normUrl(f.str.replace(/\\\//g, '/'));
264
- if (!url || !url.includes('.m3u8')) return;
265
- const title = t ? t.str : 'Unknown';
266
- if (!voices.find(v => v.title === title)) voices.push({ title, file: url, subtitle: s ? s.str.replace(/\\\//g, '/') : '' });
267
- });
268
- return { id: ep.id, title: ep.title, voices };
269
- });
270
- });
271
-
272
- return { isSerial: true, seasonIds, episodes };
273
- }
274
-
275
- // ── Main: get stream for a uakinogo page URL ──────────────────────────────────
276
- async function getCinemarStream(pageUrl) {
277
- console.log('[cinemar] getCinemarStream called with:', pageUrl);
278
-
279
- // Step 1: get embed URL from uakinogo page
280
- const pageHtml = await fetchRaw(pageUrl, {
281
- 'user-agent': 'Mozilla/5.0 Chrome/120', 'referer': 'https://uakinogo.io/'
282
- });
283
-
284
- console.log('[cinemar] page HTML length:', pageHtml.length);
285
- console.log('[cinemar] page has embedUrl:', pageHtml.includes('embedUrl'));
286
- console.log('[cinemar] page has cinemar.cc:', pageHtml.includes('cinemar.cc'));
287
-
288
- const em = pageHtml.match(/"embedUrl"\s*:\s*"(https:\/\/cinemar\.cc\/embed\/[^"]+)"/);
289
- if (!em) {
290
- console.log('[cinemar] ERROR: embedUrl not found in page');
291
- console.log('[cinemar] Page snippet:', pageHtml.substring(0, 500));
292
- throw new Error('embed_url not found');
293
- }
294
- const embedUrl = em[1];
295
- console.log('[cinemar] embed URL:', embedUrl.substring(0, 80));
296
-
297
- // Step 2: get embed HTML via Puppeteer (Cloudflare blocks plain HTTP)
298
- let embedHtml = null;
299
- let attempts = 0;
300
- const maxAttempts = 3;
301
-
302
- while (attempts < maxAttempts) {
303
- attempts++;
304
- console.log(`[cinemar] Puppeteer attempt ${attempts}/${maxAttempts}`);
305
-
306
- try {
307
- embedHtml = await getEmbedHtmlViaPuppeteer(embedUrl);
308
-
309
- if (embedHtml && embedHtml.includes('"file"') && embedHtml.includes('W3s')) {
310
- console.log('[cinemar] Puppeteer success on attempt', attempts);
311
- break;
312
- }
313
-
314
- console.log('[cinemar] Puppeteer attempt', attempts, 'failed, no file data');
315
-
316
- if (attempts < maxAttempts) {
317
- const delay = 2000 * attempts;
318
- console.log(`[cinemar] waiting ${delay}ms before retry...`);
319
- await new Promise(r => setTimeout(r, delay));
320
- }
321
- } catch(e) {
322
- console.log(`[cinemar] Puppeteer attempt ${attempts} error:`, e.message);
323
- if (attempts >= maxAttempts) throw e;
324
- }
325
- }
326
-
327
- if (!embedHtml || !embedHtml.includes('"file"')) {
328
- console.log('[cinemar] ERROR: no file data after all attempts');
329
- console.log('[cinemar] embed HTML length:', embedHtml ? embedHtml.length : 0);
330
- console.log('[cinemar] embed HTML snippet:', embedHtml ? embedHtml.substring(0, 500) : 'null');
331
- throw new Error('no file data in embed (Cloudflare blocked?)');
332
- }
333
-
334
- // Step 3: parse data
335
- const parsed = parseCinemarData(embedHtml);
336
- if (!parsed) throw new Error('failed to parse cinemar data');
337
-
338
- if (!parsed.isSerial) {
339
- const voices = parsed.items.map(item => ({
340
- label: item.title, url: item.file, subtitles: parseSubs(item.subtitle)
341
- }));
342
- return { content_type: 'movie', embed_url: embedUrl, voices, serial: null, stream: voices[0] ? voices[0].url : null };
343
- }
344
-
345
- // Serial: fetch multiple times and merge
346
- const merged = {};
347
- const addEpisodes = (data) => {
348
- if (!data || !data.isSerial) return;
349
- data.seasonIds.forEach(sid => {
350
- (data.episodes[sid] || []).forEach(ep => {
351
- if (!merged[ep.id] || ep.voices.length > (merged[ep.id] ? merged[ep.id].voices.length : 0)) {
352
- merged[ep.id] = { ...ep, seasonId: sid };
353
- }
354
- });
355
- });
356
- };
357
-
358
- addEpisodes(parsed);
359
-
360
- // Fetch more in parallel to get all episodes
361
- const BATCH = 5, MAX_BATCHES = 4;
362
- for (let b = 0; b < MAX_BATCHES; b++) {
363
- const prevCount = Object.keys(merged).length;
364
- const htmls = await Promise.all(
365
- Array.from({ length: BATCH }, () =>
366
- getEmbedHtmlViaPuppeteer(embedUrl).then(html => {
367
- if (html && html.includes('"file"')) return html;
368
- return null;
369
- }).catch(() => null)
370
- )
371
- );
372
- htmls.filter(Boolean).forEach(html => addEpisodes(parseCinemarData(html)));
373
- const newCount = Object.keys(merged).length;
374
- console.log(`[cinemar] batch ${b+1}: ${newCount} eps (+${newCount - prevCount})`);
375
- if (newCount === prevCount && b >= 1) break;
376
- if (b < MAX_BATCHES - 1) await new Promise(r => setTimeout(r, 200));
377
- }
378
-
379
- // Build serial structure
380
- const seasonMap = {};
381
- Object.values(merged).forEach(ep => {
382
- if (!seasonMap[ep.seasonId]) seasonMap[ep.seasonId] = [];
383
- seasonMap[ep.seasonId].push(ep);
384
- });
385
- const seasonIds = Object.keys(seasonMap).sort((a, b) => parseInt(a.substring(1)) - parseInt(b.substring(1)));
386
- const seasons = [], episodes = {}, voicesMap = {};
387
- seasonIds.forEach(sid => {
388
- const eps = seasonMap[sid].sort((a, b) => {
389
- const an = parseInt(a.id.match(/e(\d+)/)[1]);
390
- const bn = parseInt(b.id.match(/e(\d+)/)[1]);
391
- return an - bn;
392
- });
393
- seasons.push({ id: sid, title: 'Сезон ' + parseInt(sid.substring(1)) });
394
- episodes[sid] = eps.map(ep => ({ id: ep.id, title: ep.title }));
395
- voicesMap[sid] = {};
396
- eps.forEach(ep => {
397
- voicesMap[sid][ep.id] = ep.voices.map(v => ({ label: v.title, url: normUrl(v.file), subtitles: parseSubs(v.subtitle) }));
398
- });
399
- });
400
-
401
- const totalEps = Object.values(episodes).reduce((s, e) => s + e.length, 0);
402
- console.log(`[cinemar] done: ${seasons.length} seasons, ${totalEps} eps`);
403
-
404
- const serial = { seasons, episodes, voices: voicesMap };
405
- const firstVoice = seasons[0] && episodes[seasons[0].id] && episodes[seasons[0].id][0]
406
- ? (voicesMap[seasons[0].id][episodes[seasons[0].id][0].id] || [])[0]
407
- : null;
408
-
409
- return { content_type: 'serial', embed_url: embedUrl, serial, voices: [], stream: firstVoice ? firstVoice.url : null };
410
- }
411
-
412
- module.exports = { getCinemarStream };