recycleactor commited on
Commit
27387a4
·
verified ·
1 Parent(s): f991346

Delete cinemar.js

Browse files
Files changed (1) hide show
  1. cinemar.js +0 -348
cinemar.js DELETED
@@ -1,348 +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 Chrome/120 Safari/537.36');
113
- await page.setExtraHTTPHeaders({ 'Accept-Language': 'ru-RU,ru;q=0.9' });
114
-
115
- // Intercept the file data from window object after JS executes
116
- let fileData = null;
117
-
118
- await page.setRequestInterception(true);
119
- page.on('request', req => req.continue());
120
-
121
- await page.goto(embedUrl, {
122
- waitUntil: 'networkidle2',
123
- timeout: 25000,
124
- referer: 'https://uakinogo.io/'
125
- });
126
-
127
- // Wait for player to initialize and extract file data
128
- await new Promise(r => setTimeout(r, 2000));
129
-
130
- fileData = await page.evaluate(() => {
131
- // Try to find file data in window variables
132
- for (const k of Object.keys(window)) {
133
- try {
134
- const v = window[k];
135
- if (v && typeof v === 'object' && v.file && typeof v.file === 'string' && v.file.length > 100) {
136
- return JSON.stringify(v);
137
- }
138
- } catch(e) {}
139
- }
140
- // Try to get from DOM — look for script with file data
141
- for (const s of document.querySelectorAll('script:not([src])')) {
142
- const t = s.textContent || '';
143
- if (t.includes('"file"') && t.includes('W3s')) return t;
144
- }
145
- // Get full page HTML
146
- return document.documentElement.outerHTML;
147
- });
148
-
149
- console.log('[cinemar] puppeteer got data, length:', fileData ? fileData.length : 0);
150
- return fileData || '';
151
- } finally {
152
- await page.close();
153
- }
154
- }
155
-
156
- // ── Parse cinemar HTML/data ───────────────────────────────────────────────────
157
- function parseCinemarData(html) {
158
- const fileMatch = html.match(/"file":"([^"]+)"/);
159
- if (!fileMatch) return null;
160
- const buf = decodeCinemarFile(fileMatch[1]);
161
- if (!buf || buf.length < 20) return null;
162
-
163
- const isSerial = buf.indexOf(FOLDER_MARKER_BUF) >= 0;
164
-
165
- if (!isSerial) {
166
- // Try JSON parse first (movie format: array of voice objects)
167
- try {
168
- const jsonStr = buf.toString('utf8');
169
- const arr = JSON.parse(jsonStr);
170
- if (Array.isArray(arr) && arr.length && arr[0].title) {
171
- const items = arr.map(item => ({
172
- title: item.title || 'Unknown',
173
- file: (item.file || '').replace(/\\\//g, '/').replace(/^\/\//, 'https://'),
174
- subtitle: item.subtitle || ''
175
- })).filter(i => i.file && i.file.includes('cinemap'));
176
- if (items.length) return { isSerial: false, items };
177
- }
178
- } catch(e) {}
179
-
180
- // Fallback: binary search for src_id
181
- const items = [];
182
- findAllInBuf(buf, Buffer.from('"src_id"')).forEach(pos => {
183
- const t = findBufVal(buf, 'title', pos, 300);
184
- const f = findBufVal(buf, 'file', pos, 5000);
185
- const s = findBufVal(buf, 'subtitle', pos, 6000);
186
- if (!f) return;
187
- const url = normUrl(f.str.replace(/\\\//g, '/'));
188
- if (!url || !url.includes('cinemap')) return;
189
- const title = t ? t.str : 'Unknown';
190
- if (!items.find(i => i.title === title)) items.push({ title, file: url, subtitle: s ? s.str.replace(/\\\//g, '/') : '' });
191
- });
192
- return { isSerial: false, items };
193
- }
194
-
195
- // Serial
196
- const epIdPrefix = Buffer.from('"id":"s');
197
- const allEpPositions = [];
198
- findAllInBuf(buf, epIdPrefix).forEach(pos => {
199
- let i = pos + epIdPrefix.length;
200
- while (i < buf.length && buf[i] >= 0x30 && buf[i] <= 0x39) i++;
201
- if (buf[i] !== 0x65) return;
202
- i++;
203
- while (i < buf.length && buf[i] >= 0x30 && buf[i] <= 0x39) i++;
204
- if (buf[i] !== 0x22) return;
205
- const fullId = buf.slice(pos + 6, i).toString('ascii');
206
- const seasonMatch = fullId.match(/^(s\d+)e(\d+)$/);
207
- if (!seasonMatch) return;
208
- const t = findBufVal(buf, 'title', pos, 200);
209
- allEpPositions.push({ id: fullId, seasonId: seasonMatch[1], epNum: parseInt(seasonMatch[2], 10), title: t ? t.str : ('Серия ' + seasonMatch[2]), pos });
210
- });
211
-
212
- const seasonMap = {};
213
- allEpPositions.forEach(ep => {
214
- if (!seasonMap[ep.seasonId]) seasonMap[ep.seasonId] = [];
215
- if (!seasonMap[ep.seasonId].find(e => e.id === ep.id)) seasonMap[ep.seasonId].push(ep);
216
- });
217
- const seasonIds = Object.keys(seasonMap).sort((a, b) => parseInt(a.substring(1)) - parseInt(b.substring(1)));
218
- if (!seasonIds.length) return null;
219
-
220
- const episodes = {};
221
- seasonIds.forEach(seasonId => {
222
- const eps = seasonMap[seasonId].sort((a, b) => a.epNum - b.epNum);
223
- episodes[seasonId] = eps.map((ep, ei) => {
224
- const nextEpPos = ei + 1 < eps.length ? eps[ei + 1].pos : buf.length;
225
- const eBuf = buf.slice(ep.pos, nextEpPos);
226
- const voices = [];
227
- findAllInBuf(eBuf, Buffer.from('"src_id"')).forEach(vpos => {
228
- const t = findBufVal(eBuf, 'title', vpos, 300);
229
- const f = findBufVal(eBuf, 'file', vpos, 2000);
230
- const s = findBufVal(eBuf, 'subtitle', vpos, 3000);
231
- if (!f) return;
232
- const url = normUrl(f.str.replace(/\\\//g, '/'));
233
- if (!url || !url.includes('.m3u8')) return;
234
- const title = t ? t.str : 'Unknown';
235
- if (!voices.find(v => v.title === title)) voices.push({ title, file: url, subtitle: s ? s.str.replace(/\\\//g, '/') : '' });
236
- });
237
- return { id: ep.id, title: ep.title, voices };
238
- });
239
- });
240
-
241
- return { isSerial: true, seasonIds, episodes };
242
- }
243
-
244
- // ── Main: get stream for a uakinogo page URL ──────────────────────────────────
245
- async function getCinemarStream(pageUrl) {
246
- // Step 1: get embed URL from uakinogo page
247
- const pageHtml = await fetchRaw(pageUrl, {
248
- 'user-agent': 'Mozilla/5.0 Chrome/120', 'referer': 'https://uakinogo.io/'
249
- });
250
- const em = pageHtml.match(/"embedUrl"\s*:\s*"(https:\/\/cinemar\.cc\/embed\/[^"]+)"/);
251
- if (!em) throw new Error('embed_url not found');
252
- const embedUrl = em[1];
253
- console.log('[cinemar] embed URL:', embedUrl.substring(0, 80));
254
-
255
- // Step 2: get embed HTML — try plain HTTP first, fallback to Puppeteer
256
- let embedHtml = await fetchRaw(embedUrl, {
257
- 'user-agent': 'Mozilla/5.0 Chrome/120',
258
- 'referer': 'https://uakinogo.io/',
259
- 'origin': 'https://uakinogo.io'
260
- });
261
-
262
- if (!embedHtml || !embedHtml.includes('"file"')) {
263
- console.log('[cinemar] no file in plain HTML, trying Puppeteer...');
264
- embedHtml = await getEmbedHtmlViaPuppeteer(embedUrl);
265
- } else {
266
- console.log('[cinemar] got file data via plain HTTP');
267
- }
268
-
269
- if (!embedHtml || !embedHtml.includes('"file"')) {
270
- throw new Error('no file data in embed (Cloudflare blocked?)');
271
- }
272
-
273
- // Step 3: parse data
274
- const parsed = parseCinemarData(embedHtml);
275
- if (!parsed) throw new Error('failed to parse cinemar data');
276
-
277
- if (!parsed.isSerial) {
278
- const voices = parsed.items.map(item => ({
279
- label: item.title, url: item.file, subtitles: parseSubs(item.subtitle)
280
- }));
281
- return { content_type: 'movie', embed_url: embedUrl, voices, serial: null, stream: voices[0] ? voices[0].url : null };
282
- }
283
-
284
- // Serial: fetch multiple times and merge
285
- const merged = {};
286
- const addEpisodes = (data) => {
287
- if (!data || !data.isSerial) return;
288
- data.seasonIds.forEach(sid => {
289
- (data.episodes[sid] || []).forEach(ep => {
290
- if (!merged[ep.id] || ep.voices.length > (merged[ep.id] ? merged[ep.id].voices.length : 0)) {
291
- merged[ep.id] = { ...ep, seasonId: sid };
292
- }
293
- });
294
- });
295
- };
296
-
297
- addEpisodes(parsed);
298
-
299
- // Fetch more in parallel to get all episodes
300
- const BATCH = 5, MAX_BATCHES = 4;
301
- for (let b = 0; b < MAX_BATCHES; b++) {
302
- const prevCount = Object.keys(merged).length;
303
- const htmls = await Promise.all(
304
- Array.from({ length: BATCH }, () =>
305
- fetchRaw(embedUrl, { 'user-agent': 'Mozilla/5.0 Chrome/120', 'referer': 'https://uakinogo.io/', 'origin': 'https://uakinogo.io' }).catch(() => null)
306
- )
307
- );
308
- htmls.filter(Boolean).forEach(html => addEpisodes(parseCinemarData(html)));
309
- const newCount = Object.keys(merged).length;
310
- console.log(`[cinemar] batch ${b+1}: ${newCount} eps (+${newCount - prevCount})`);
311
- if (newCount === prevCount && b >= 1) break;
312
- if (b < MAX_BATCHES - 1) await new Promise(r => setTimeout(r, 200));
313
- }
314
-
315
- // Build serial structure
316
- const seasonMap = {};
317
- Object.values(merged).forEach(ep => {
318
- if (!seasonMap[ep.seasonId]) seasonMap[ep.seasonId] = [];
319
- seasonMap[ep.seasonId].push(ep);
320
- });
321
- const seasonIds = Object.keys(seasonMap).sort((a, b) => parseInt(a.substring(1)) - parseInt(b.substring(1)));
322
- const seasons = [], episodes = {}, voicesMap = {};
323
- seasonIds.forEach(sid => {
324
- const eps = seasonMap[sid].sort((a, b) => {
325
- const an = parseInt(a.id.match(/e(\d+)/)[1]);
326
- const bn = parseInt(b.id.match(/e(\d+)/)[1]);
327
- return an - bn;
328
- });
329
- seasons.push({ id: sid, title: 'Сезон ' + parseInt(sid.substring(1)) });
330
- episodes[sid] = eps.map(ep => ({ id: ep.id, title: ep.title }));
331
- voicesMap[sid] = {};
332
- eps.forEach(ep => {
333
- voicesMap[sid][ep.id] = ep.voices.map(v => ({ label: v.title, url: normUrl(v.file), subtitles: parseSubs(v.subtitle) }));
334
- });
335
- });
336
-
337
- const totalEps = Object.values(episodes).reduce((s, e) => s + e.length, 0);
338
- console.log(`[cinemar] done: ${seasons.length} seasons, ${totalEps} eps`);
339
-
340
- const serial = { seasons, episodes, voices: voicesMap };
341
- const firstVoice = seasons[0] && episodes[seasons[0].id] && episodes[seasons[0].id][0]
342
- ? (voicesMap[seasons[0].id][episodes[seasons[0].id][0].id] || [])[0]
343
- : null;
344
-
345
- return { content_type: 'serial', embed_url: embedUrl, serial, voices: [], stream: firstVoice ? firstVoice.url : null };
346
- }
347
-
348
- module.exports = { getCinemarStream };