recycleactor commited on
Commit
1987e16
·
verified ·
1 Parent(s): 0aacfda

Upload cinemar.js

Browse files
Files changed (1) hide show
  1. cinemar.js +77 -76
cinemar.js CHANGED
@@ -1,6 +1,6 @@
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,12 +104,11 @@ function parseSubs(subtitle) {
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
- // More realistic browser fingerprint
113
  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');
114
  await page.setExtraHTTPHeaders({
115
  'Accept-Language': 'ru-RU,ru;q=0.9,en;q=0.8',
@@ -120,10 +119,8 @@ async function getEmbedHtmlViaPuppeteer(embedUrl) {
120
  'Sec-Fetch-Site': 'cross-site',
121
  });
122
 
123
- // Set viewport to look like real browser
124
  await page.setViewport({ width: 1920, height: 1080 });
125
 
126
- // Disable automation detection
127
  await page.evaluateOnNewDocument(() => {
128
  Object.defineProperty(navigator, 'webdriver', { get: () => false });
129
  window.chrome = { runtime: {} };
@@ -133,7 +130,6 @@ async function getEmbedHtmlViaPuppeteer(embedUrl) {
133
 
134
  await page.setRequestInterception(true);
135
  page.on('request', req => {
136
- // Block unnecessary resources to speed up
137
  const resourceType = req.resourceType();
138
  if (['image', 'stylesheet', 'font', 'media'].includes(resourceType)) {
139
  req.abort();
@@ -149,11 +145,9 @@ async function getEmbedHtmlViaPuppeteer(embedUrl) {
149
  timeout: 30000,
150
  });
151
 
152
- // Wait longer for Cloudflare challenge to complete
153
  console.log('[cinemar] waiting for Cloudflare challenge...');
154
  await new Promise(r => setTimeout(r, 5000));
155
 
156
- // Check if we're still on Cloudflare challenge page
157
  const currentUrl = page.url();
158
  const title = await page.title().catch(() => '');
159
  console.log('[cinemar] current URL:', currentUrl.substring(0, 80));
@@ -164,11 +158,9 @@ async function getEmbedHtmlViaPuppeteer(embedUrl) {
164
  await new Promise(r => setTimeout(r, 5000));
165
  }
166
 
167
- // Wait for player to initialize and extract file data
168
  await new Promise(r => setTimeout(r, 2000));
169
 
170
  const fileData = await page.evaluate(() => {
171
- // Try to find file data in window variables
172
  for (const k of Object.keys(window)) {
173
  try {
174
  const v = window[k];
@@ -177,12 +169,10 @@ async function getEmbedHtmlViaPuppeteer(embedUrl) {
177
  }
178
  } catch(e) {}
179
  }
180
- // Try to get from DOM — look for script with file data
181
  for (const s of document.querySelectorAll('script:not([src])')) {
182
  const t = s.textContent || '';
183
  if (t.includes('"file"') && t.includes('W3s')) return t;
184
  }
185
- // Get full page HTML
186
  return document.documentElement.outerHTML;
187
  });
188
 
@@ -206,7 +196,6 @@ function parseCinemarData(html) {
206
  const isSerial = buf.indexOf(FOLDER_MARKER_BUF) >= 0;
207
 
208
  if (!isSerial) {
209
- // Try JSON parse first (movie format: array of voice objects)
210
  try {
211
  const jsonStr = buf.toString('utf8');
212
  const arr = JSON.parse(jsonStr);
@@ -220,7 +209,6 @@ function parseCinemarData(html) {
220
  }
221
  } catch(e) {}
222
 
223
- // Fallback: binary search for src_id
224
  const items = [];
225
  findAllInBuf(buf, Buffer.from('"src_id"')).forEach(pos => {
226
  const t = findBufVal(buf, 'title', pos, 300);
@@ -294,29 +282,80 @@ async function getCinemarStream(pageUrl) {
294
  });
295
 
296
  console.log('[cinemar] page HTML length:', pageHtml.length);
297
- console.log('[cinemar] page has embedUrl:', pageHtml.includes('embedUrl'));
298
- console.log('[cinemar] page has cinemar.cc:', pageHtml.includes('cinemar.cc'));
299
 
300
  const em = pageHtml.match(/"embedUrl"\s*:\s*"(https:\/\/cinemar\.cc\/embed\/[^"]+)"/);
301
  if (!em) {
302
  console.log('[cinemar] ERROR: embedUrl not found in page');
303
- console.log('[cinemar] Page snippet:', pageHtml.substring(0, 500));
304
  throw new Error('embed_url not found');
305
  }
306
  const embedUrl = em[1];
307
  console.log('[cinemar] embed URL:', embedUrl.substring(0, 80));
308
 
309
- // Step 2: get embed HTML try plain HTTP first, fallback to Puppeteer
310
- let embedHtml = await fetchRaw(embedUrl, {
311
- 'user-agent': 'Mozilla/5.0 Chrome/120',
312
- 'referer': 'https://uakinogo.io/',
313
- 'origin': 'https://uakinogo.io'
314
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
315
 
316
- if (!embedHtml || !embedHtml.includes('"file"')) {
317
- console.log('[cinemar] no file in plain HTML, trying Puppeteer...');
318
 
319
- // Try Puppeteer multiple times with delays
 
320
  let attempts = 0;
321
  const maxAttempts = 3;
322
 
@@ -329,13 +368,14 @@ async function getCinemarStream(pageUrl) {
329
 
330
  if (embedHtml && embedHtml.includes('"file"') && embedHtml.includes('W3s')) {
331
  console.log('[cinemar] Puppeteer success on attempt', attempts);
332
- break;
 
333
  }
334
 
335
  console.log('[cinemar] Puppeteer attempt', attempts, 'failed, no file data');
336
 
337
  if (attempts < maxAttempts) {
338
- const delay = 2000 * attempts; // Increasing delay
339
  console.log(`[cinemar] waiting ${delay}ms before retry...`);
340
  await new Promise(r => setTimeout(r, delay));
341
  }
@@ -344,60 +384,21 @@ async function getCinemarStream(pageUrl) {
344
  if (attempts >= maxAttempts) throw e;
345
  }
346
  }
347
- } else {
348
- console.log('[cinemar] got file data via plain HTTP');
349
- }
350
-
351
- if (!embedHtml || !embedHtml.includes('"file"')) {
352
- console.log('[cinemar] ERROR: no file data after all attempts');
353
- console.log('[cinemar] embed HTML length:', embedHtml ? embedHtml.length : 0);
354
- console.log('[cinemar] embed HTML snippet:', embedHtml ? embedHtml.substring(0, 500) : 'null');
355
- throw new Error('no file data in embed (Cloudflare blocked?)');
356
  }
357
 
358
- // Step 3: parse data
359
- const parsed = parseCinemarData(embedHtml);
360
- if (!parsed) throw new Error('failed to parse cinemar data');
361
-
362
- if (!parsed.isSerial) {
363
- const voices = parsed.items.map(item => ({
364
  label: item.title, url: item.file, subtitles: parseSubs(item.subtitle)
365
  }));
366
  return { content_type: 'movie', embed_url: embedUrl, voices, serial: null, stream: voices[0] ? voices[0].url : null };
367
  }
368
 
369
- // Serial: fetch multiple times and merge
370
- const merged = {};
371
- const addEpisodes = (data) => {
372
- if (!data || !data.isSerial) return;
373
- data.seasonIds.forEach(sid => {
374
- (data.episodes[sid] || []).forEach(ep => {
375
- if (!merged[ep.id] || ep.voices.length > (merged[ep.id] ? merged[ep.id].voices.length : 0)) {
376
- merged[ep.id] = { ...ep, seasonId: sid };
377
- }
378
- });
379
- });
380
- };
381
-
382
- addEpisodes(parsed);
383
-
384
- // Fetch more in parallel to get all episodes
385
- const BATCH = 5, MAX_BATCHES = 4;
386
- for (let b = 0; b < MAX_BATCHES; b++) {
387
- const prevCount = Object.keys(merged).length;
388
- const htmls = await Promise.all(
389
- Array.from({ length: BATCH }, () =>
390
- fetchRaw(embedUrl, { 'user-agent': 'Mozilla/5.0 Chrome/120', 'referer': 'https://uakinogo.io/', 'origin': 'https://uakinogo.io' }).catch(() => null)
391
- )
392
- );
393
- htmls.filter(Boolean).forEach(html => addEpisodes(parseCinemarData(html)));
394
- const newCount = Object.keys(merged).length;
395
- console.log(`[cinemar] batch ${b+1}: ${newCount} eps (+${newCount - prevCount})`);
396
- if (newCount === prevCount && b >= 1) break;
397
- if (b < MAX_BATCHES - 1) await new Promise(r => setTimeout(r, 200));
398
- }
399
-
400
- // Build serial structure
401
  const seasonMap = {};
402
  Object.values(merged).forEach(ep => {
403
  if (!seasonMap[ep.seasonId]) seasonMap[ep.seasonId] = [];
 
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
  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();
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',
 
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: {} };
 
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();
 
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));
 
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];
 
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
 
 
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);
 
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);
 
282
  });
283
 
284
  console.log('[cinemar] page HTML length:', pageHtml.length);
 
 
285
 
286
  const em = pageHtml.match(/"embedUrl"\s*:\s*"(https:\/\/cinemar\.cc\/embed\/[^"]+)"/);
287
  if (!em) {
288
  console.log('[cinemar] ERROR: embedUrl not found in page');
 
289
  throw new Error('embed_url not found');
290
  }
291
  const embedUrl = em[1];
292
  console.log('[cinemar] embed URL:', embedUrl.substring(0, 80));
293
 
294
+ // Step 2: get embed HTML via Cloudflare Worker (bypasses IP blocking)
295
+ const WORKER_URL = 'https://proxy.recycleactor.workers.dev/cinemar/proxy';
296
+
297
+ console.log('[cinemar] fetching via Cloudflare Worker (bypass Cloudflare IP block)');
298
+
299
+ // Try worker proxy with parallel requests for better data coverage
300
+ const BATCH = 5, MAX_BATCHES = 4;
301
+ const merged = {};
302
+
303
+ const addEpisodes = (data) => {
304
+ if (!data || !data.isSerial) return;
305
+ data.seasonIds.forEach(sid => {
306
+ (data.episodes[sid] || []).forEach(ep => {
307
+ if (!merged[ep.id] || ep.voices.length > (merged[ep.id] ? merged[ep.id].voices.length : 0)) {
308
+ merged[ep.id] = { ...ep, seasonId: sid };
309
+ }
310
+ });
311
+ });
312
+ };
313
+
314
+ let firstParsed = null;
315
+
316
+ for (let b = 0; b < MAX_BATCHES; b++) {
317
+ const prevCount = Object.keys(merged).length;
318
+
319
+ const htmls = await Promise.all(
320
+ Array.from({ length: BATCH }, () =>
321
+ fetchRaw(WORKER_URL + '?url=' + encodeURIComponent(embedUrl), {
322
+ 'user-agent': 'Mozilla/5.0 Chrome/120',
323
+ 'accept': 'text/html'
324
+ }).then(html => {
325
+ console.log('[cinemar] worker response len:', html.length, 'hasFile:', html.includes('"file"'));
326
+ return html;
327
+ }).catch(e => {
328
+ console.log('[cinemar] worker request failed:', e.message);
329
+ return null;
330
+ })
331
+ )
332
+ );
333
+
334
+ htmls.filter(Boolean).forEach(html => {
335
+ const parsed = parseCinemarData(html);
336
+ if (parsed) {
337
+ if (!firstParsed) firstParsed = parsed;
338
+ addEpisodes(parsed);
339
+ }
340
+ });
341
+
342
+ const newCount = Object.keys(merged).length;
343
+ console.log(`[cinemar] batch ${b+1}: ${newCount} eps (+${newCount - prevCount})`);
344
+
345
+ // For movies, one successful fetch is enough
346
+ if (firstParsed && !firstParsed.isSerial) break;
347
+
348
+ // For serials, stop if no new episodes found
349
+ if (newCount === prevCount && b >= 1) break;
350
+
351
+ if (b < MAX_BATCHES - 1) await new Promise(r => setTimeout(r, 200));
352
+ }
353
 
354
+ if (!firstParsed) {
355
+ console.log('[cinemar] worker proxy failed, trying Puppeteer fallback...');
356
 
357
+ // Fallback: try Puppeteer
358
+ let embedHtml = null;
359
  let attempts = 0;
360
  const maxAttempts = 3;
361
 
 
368
 
369
  if (embedHtml && embedHtml.includes('"file"') && embedHtml.includes('W3s')) {
370
  console.log('[cinemar] Puppeteer success on attempt', attempts);
371
+ firstParsed = parseCinemarData(embedHtml);
372
+ if (firstParsed) break;
373
  }
374
 
375
  console.log('[cinemar] Puppeteer attempt', attempts, 'failed, no file data');
376
 
377
  if (attempts < maxAttempts) {
378
+ const delay = 2000 * attempts;
379
  console.log(`[cinemar] waiting ${delay}ms before retry...`);
380
  await new Promise(r => setTimeout(r, delay));
381
  }
 
384
  if (attempts >= maxAttempts) throw e;
385
  }
386
  }
387
+
388
+ if (!firstParsed) {
389
+ throw new Error('no file data after worker and Puppeteer attempts (Cloudflare blocked?)');
390
+ }
 
 
 
 
 
391
  }
392
 
393
+ // Step 3: build response
394
+ if (!firstParsed.isSerial) {
395
+ const voices = firstParsed.items.map(item => ({
 
 
 
396
  label: item.title, url: item.file, subtitles: parseSubs(item.subtitle)
397
  }));
398
  return { content_type: 'movie', embed_url: embedUrl, voices, serial: null, stream: voices[0] ? voices[0].url : null };
399
  }
400
 
401
+ // Serial: build structure from merged episodes
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
402
  const seasonMap = {};
403
  Object.values(merged).forEach(ep => {
404
  if (!seasonMap[ep.seasonId]) seasonMap[ep.seasonId] = [];