recycleactor commited on
Commit
948a9f2
·
verified ·
1 Parent(s): 4a3e08b

Upload cinemar.js

Browse files
Files changed (1) hide show
  1. cinemar.js +63 -117
cinemar.js CHANGED
@@ -1,6 +1,6 @@
1
  /**
2
- * Cinemar (cinemar.cc) stream extractor using Cloudflare Worker proxy
3
- * cinemar.cc uses Cloudflare — use worker to bypass IP blocking
4
  */
5
  const { getBrowser } = require('./alloha');
6
  const https = require('https');
@@ -104,7 +104,7 @@ function parseSubs(subtitle) {
104
  return subs;
105
  }
106
 
107
- // ── Get HTML via Puppeteer (fallback only) ────────────────────────────────────
108
  async function getEmbedHtmlViaPuppeteer(embedUrl) {
109
  const browser = await getBrowser();
110
  const page = await browser.newPage();
@@ -194,7 +194,6 @@ function parseCinemarData(html) {
194
  if (!buf || buf.length < 20) return null;
195
 
196
  const isSerial = buf.indexOf(FOLDER_MARKER_BUF) >= 0;
197
- console.log('[cinemar/parse] buf length:', buf.length, 'isSerial:', isSerial);
198
 
199
  if (!isSerial) {
200
  try {
@@ -241,17 +240,12 @@ function parseCinemarData(html) {
241
  allEpPositions.push({ id: fullId, seasonId: seasonMatch[1], epNum: parseInt(seasonMatch[2], 10), title: t ? t.str : ('Серия ' + seasonMatch[2]), pos });
242
  });
243
 
244
- console.log('[cinemar/parse] found', allEpPositions.length, 'episode positions');
245
-
246
  const seasonMap = {};
247
  allEpPositions.forEach(ep => {
248
  if (!seasonMap[ep.seasonId]) seasonMap[ep.seasonId] = [];
249
  if (!seasonMap[ep.seasonId].find(e => e.id === ep.id)) seasonMap[ep.seasonId].push(ep);
250
  });
251
  const seasonIds = Object.keys(seasonMap).sort((a, b) => parseInt(a.substring(1)) - parseInt(b.substring(1)));
252
-
253
- console.log('[cinemar/parse] seasonIds:', seasonIds.length, seasonIds.slice(0, 3));
254
-
255
  if (!seasonIds.length) return null;
256
 
257
  const episodes = {};
@@ -288,24 +282,68 @@ async function getCinemarStream(pageUrl) {
288
  });
289
 
290
  console.log('[cinemar] page HTML length:', pageHtml.length);
 
 
291
 
292
  const em = pageHtml.match(/"embedUrl"\s*:\s*"(https:\/\/cinemar\.cc\/embed\/[^"]+)"/);
293
  if (!em) {
294
  console.log('[cinemar] ERROR: embedUrl not found in page');
 
295
  throw new Error('embed_url not found');
296
  }
297
  const embedUrl = em[1];
298
  console.log('[cinemar] embed URL:', embedUrl.substring(0, 80));
299
 
300
- // Step 2: get embed HTML via Cloudflare Worker (bypasses IP blocking)
301
- const WORKER_URL = 'https://proxy.recycleactor.workers.dev/cinemar/proxy';
302
-
303
- console.log('[cinemar] fetching via Cloudflare Worker (bypass Cloudflare IP block)');
304
 
305
- // Try worker proxy with parallel requests for better data coverage
306
- const BATCH = 5, MAX_BATCHES = 4;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
307
  const merged = {};
308
-
309
  const addEpisodes = (data) => {
310
  if (!data || !data.isSerial) return;
311
  data.seasonIds.forEach(sid => {
@@ -317,120 +355,28 @@ async function getCinemarStream(pageUrl) {
317
  });
318
  };
319
 
320
- let firstParsed = null;
321
-
 
 
322
  for (let b = 0; b < MAX_BATCHES; b++) {
323
  const prevCount = Object.keys(merged).length;
324
-
325
  const htmls = await Promise.all(
326
  Array.from({ length: BATCH }, () =>
327
- fetchRaw(WORKER_URL + '?url=' + encodeURIComponent(embedUrl), {
328
- 'user-agent': 'Mozilla/5.0 Chrome/120',
329
- 'accept': 'text/html'
330
- }).then(html => {
331
- console.log('[cinemar] worker response len:', html.length, 'hasFile:', html.includes('"file"'));
332
- return html;
333
- }).catch(e => {
334
- console.log('[cinemar] worker request failed:', e.message);
335
  return null;
336
- })
337
  )
338
  );
339
-
340
- htmls.filter(Boolean).forEach(html => {
341
- const parsed = parseCinemarData(html);
342
- if (parsed) {
343
- if (!firstParsed) firstParsed = parsed;
344
- addEpisodes(parsed);
345
- }
346
- });
347
-
348
  const newCount = Object.keys(merged).length;
349
  console.log(`[cinemar] batch ${b+1}: ${newCount} eps (+${newCount - prevCount})`);
350
-
351
- // For movies, one successful fetch is enough
352
- if (firstParsed && !firstParsed.isSerial) break;
353
-
354
- // For serials, stop if no new episodes found
355
  if (newCount === prevCount && b >= 1) break;
356
-
357
  if (b < MAX_BATCHES - 1) await new Promise(r => setTimeout(r, 200));
358
  }
359
 
360
- if (!firstParsed) {
361
- console.log('[cinemar] worker proxy failed, trying Puppeteer fallback...');
362
-
363
- // Fallback: try Puppeteer
364
- let embedHtml = null;
365
- let attempts = 0;
366
- const maxAttempts = 3;
367
-
368
- while (attempts < maxAttempts) {
369
- attempts++;
370
- console.log(`[cinemar] Puppeteer attempt ${attempts}/${maxAttempts}`);
371
-
372
- try {
373
- embedHtml = await getEmbedHtmlViaPuppeteer(embedUrl);
374
-
375
- if (embedHtml && embedHtml.includes('"file"') && embedHtml.includes('W3s')) {
376
- console.log('[cinemar] Puppeteer success on attempt', attempts);
377
- firstParsed = parseCinemarData(embedHtml);
378
- if (firstParsed) {
379
- // Add Puppeteer data to merged episodes for serials
380
- addEpisodes(firstParsed);
381
- break;
382
- }
383
- }
384
-
385
- console.log('[cinemar] Puppeteer attempt', attempts, 'failed, no file data');
386
-
387
- if (attempts < maxAttempts) {
388
- const delay = 2000 * attempts;
389
- console.log(`[cinemar] waiting ${delay}ms before retry...`);
390
- await new Promise(r => setTimeout(r, delay));
391
- }
392
- } catch(e) {
393
- console.log(`[cinemar] Puppeteer attempt ${attempts} error:`, e.message);
394
- if (attempts >= maxAttempts) throw e;
395
- }
396
- }
397
-
398
- if (!firstParsed) {
399
- throw new Error('no file data after worker and Puppeteer attempts (Cloudflare blocked?)');
400
- }
401
- }
402
-
403
- // If serial and we have Puppeteer data, try to get more episodes via Puppeteer
404
- if (firstParsed && firstParsed.isSerial && Object.keys(merged).length > 0) {
405
- console.log('[cinemar] serial detected, fetching more episodes via Puppeteer...');
406
- const PUPBATCH = 3;
407
- for (let pb = 0; pb < PUPBATCH; pb++) {
408
- const prevCount = Object.keys(merged).length;
409
- try {
410
- const moreHtml = await getEmbedHtmlViaPuppeteer(embedUrl);
411
- if (moreHtml && moreHtml.includes('"file"')) {
412
- const moreParsed = parseCinemarData(moreHtml);
413
- if (moreParsed) addEpisodes(moreParsed);
414
- }
415
- } catch(e) {
416
- console.log('[cinemar] Puppeteer batch', pb+1, 'error:', e.message);
417
- }
418
- const newCount = Object.keys(merged).length;
419
- console.log(`[cinemar] Puppeteer batch ${pb+1}: ${newCount} eps (+${newCount - prevCount})`);
420
- if (newCount === prevCount) break;
421
- await new Promise(r => setTimeout(r, 500));
422
- }
423
- }
424
-
425
- // Step 3: build response
426
- if (!firstParsed.isSerial) {
427
- const voices = firstParsed.items.map(item => ({
428
- label: item.title, url: item.file, subtitles: parseSubs(item.subtitle)
429
- }));
430
- return { content_type: 'movie', embed_url: embedUrl, voices, serial: null, stream: voices[0] ? voices[0].url : null };
431
- }
432
-
433
- // Serial: build structure from merged episodes
434
  const seasonMap = {};
435
  Object.values(merged).forEach(ep => {
436
  if (!seasonMap[ep.seasonId]) seasonMap[ep.seasonId] = [];
 
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');
 
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();
 
194
  if (!buf || buf.length < 20) return null;
195
 
196
  const isSerial = buf.indexOf(FOLDER_MARKER_BUF) >= 0;
 
197
 
198
  if (!isSerial) {
199
  try {
 
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 = {};
 
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 => {
 
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] = [];