add cloudnestra

This commit is contained in:
Pas 2025-06-29 10:25:32 -06:00
parent af8ac930f2
commit dcd9f5a749
4 changed files with 353 additions and 0 deletions

View file

@ -10,6 +10,7 @@ import { fsharetvScraper } from '@/providers/sources/fsharetv';
import { insertunitScraper } from '@/providers/sources/insertunit';
import { mp4hydraScraper } from '@/providers/sources/mp4hydra';
import { tugaflixScraper } from '@/providers/sources/tugaflix';
import { vidsrcScraper } from '@/providers/sources/vidsrc';
import { vidsrcsuScraper } from '@/providers/sources/vidsrcsu';
import {
@ -84,6 +85,7 @@ export function gatherAllSources(): Array<Sourcerer> {
ee3Scraper,
fsharetvScraper,
vidsrcsuScraper,
vidsrcScraper,
mp4hydraScraper,
embedsuScraper,
slidemoviesScraper,

View file

@ -0,0 +1,228 @@
const abc = String.fromCharCode(
65,
66,
67,
68,
69,
70,
71,
72,
73,
74,
75,
76,
77,
97,
98,
99,
100,
101,
102,
103,
104,
105,
106,
107,
108,
109,
78,
79,
80,
81,
82,
83,
84,
85,
86,
87,
88,
89,
90,
110,
111,
112,
113,
114,
115,
116,
117,
118,
119,
120,
121,
122,
);
const dechar = (x: number): string => String.fromCharCode(x);
const salt = {
_keyStr: `${abc}0123456789+/=`,
e(input: string): string {
let t = '';
let n: number;
let r: number;
let i: number;
let s: number;
let o: number;
let u: number;
let a: number;
let f = 0;
input = salt._ue(input); // eslint-disable-line no-param-reassign
while (f < input.length) {
n = input.charCodeAt(f++);
r = input.charCodeAt(f++);
i = input.charCodeAt(f++);
s = n >> 2;
o = ((n & 3) << 4) | (r >> 4);
u = ((r & 15) << 2) | (i >> 6);
a = i & 63;
if (Number.isNaN(r)) {
u = 64;
a = 64;
} else if (Number.isNaN(i)) {
a = 64;
}
t += this._keyStr.charAt(s) + this._keyStr.charAt(o) + this._keyStr.charAt(u) + this._keyStr.charAt(a);
}
return t;
},
d(encoded: string): string {
let t = '';
let n: number;
let r: number;
let i: number;
let s: number;
let o: number;
let u: number;
let a: number;
let f = 0;
encoded = encoded.replace(/[^A-Za-z0-9+/=]/g, ''); // eslint-disable-line no-param-reassign
while (f < encoded.length) {
s = this._keyStr.indexOf(encoded.charAt(f++));
o = this._keyStr.indexOf(encoded.charAt(f++));
u = this._keyStr.indexOf(encoded.charAt(f++));
a = this._keyStr.indexOf(encoded.charAt(f++));
n = (s << 2) | (o >> 4);
r = ((o & 15) << 4) | (u >> 2);
i = ((u & 3) << 6) | a;
t += dechar(n);
if (u !== 64) t += dechar(r);
if (a !== 64) t += dechar(i);
}
t = salt._ud(t);
return t;
},
_ue(input: string): string {
input = input.replace(/\r\n/g, '\n'); // eslint-disable-line no-param-reassign
let t = '';
for (let n = 0; n < input.length; n++) {
const r = input.charCodeAt(n);
if (r < 128) {
t += dechar(r);
} else if (r > 127 && r < 2048) {
t += dechar((r >> 6) | 192);
t += dechar((r & 63) | 128);
} else {
t += dechar((r >> 12) | 224);
t += dechar(((r >> 6) & 63) | 128);
t += dechar((r & 63) | 128);
}
}
return t;
},
_ud(input: string): string {
let t = '';
let n = 0;
let r: number;
let c2: number;
let c3: number;
while (n < input.length) {
r = input.charCodeAt(n);
if (r < 128) {
t += dechar(r);
n++;
} else if (r > 191 && r < 224) {
c2 = input.charCodeAt(n + 1);
t += dechar(((r & 31) << 6) | (c2 & 63));
n += 2;
} else {
c2 = input.charCodeAt(n + 1);
c3 = input.charCodeAt(n + 2);
t += dechar(((r & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
n += 3;
}
}
return t;
},
};
const sugar = (input: string): string => {
const parts = input.split(dechar(61));
let result = '';
const c1 = dechar(120);
for (const part of parts) {
let encoded = '';
for (let i = 0; i < part.length; i++) {
encoded += part[i] === c1 ? dechar(49) : dechar(48);
}
const chr = parseInt(encoded, 2);
result += dechar(chr);
}
return result.substring(0, result.length - 1);
};
const pepper = (s: string, n: number): string => {
s = s.replace(/\+/g, '#'); // eslint-disable-line no-param-reassign
s = s.replace(/#/g, '+'); // eslint-disable-line no-param-reassign
// Default value for vidsrc player
const yValue = 'xx??x?=xx?xx?=';
let a = Number(sugar(yValue)) * n;
if (n < 0) a += abc.length / 2;
const r = abc.substr(a * 2) + abc.substr(0, a * 2);
return s.replace(/[A-Za-z]/g, (c) => r.charAt(abc.indexOf(c)));
};
export const decode = (x: string): string => {
if (x.substr(0, 2) === '#1') {
return salt.d(pepper(x.substr(2), -1));
}
if (x.substr(0, 2) === '#0') {
return salt.d(x.substr(2));
}
return x;
};
export const mirza = (encodedUrl: string, v: any): string => {
let a = encodedUrl.substring(2);
for (let i = 4; i >= 0; i--) {
if (v[`bk${i}`]) {
const b1 = (str: string) =>
btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, (_, p1) => String.fromCharCode(parseInt(p1, 16))));
a = a.replace(v.file3_separator + b1(v[`bk${i}`]), '');
}
}
const b2 = (str: string) =>
decodeURIComponent(
atob(str)
.split('')
.map((c) => `%${`00${c.charCodeAt(0).toString(16)}`.slice(-2)}`)
.join(''),
);
return b2(a);
};

View file

@ -0,0 +1,121 @@
import type { ShowMedia } from '@/entrypoint/utils/media';
import { flags } from '@/entrypoint/utils/targets';
import { SourcererOutput, makeSourcerer } from '@/providers/base';
import { MovieScrapeContext, ShowScrapeContext } from '@/utils/context';
import { NotFoundError } from '@/utils/errors';
import { createM3U8ProxyUrl } from '@/utils/proxy';
import { decode, mirza } from './decrypt';
// Default player configuration
const o = {
y: 'xx??x?=xx?xx?=',
u: '#1RyJzl3JYmljm0mkJWOGYWNyI6MfwVNGYXmj9uQj5tQkeYIWoxLCJXNkawOGF5QZ9sQj1YIWowLCJXO20VbVJ1OZ11QGiSlni0QG9uIn19',
};
async function vidsrcScrape(ctx: MovieScrapeContext | ShowScrapeContext): Promise<SourcererOutput> {
const imdbId = ctx.media.imdbId;
if (!imdbId) throw new NotFoundError('IMDb ID not found');
const isShow = ctx.media.type === 'show';
let season: number | undefined;
let episode: number | undefined;
if (isShow) {
const show = ctx.media as ShowMedia;
season = show.season?.number;
episode = show.episode?.number;
}
const embedUrl = isShow
? `https://vidsrc.net/embed/tv?imdb=${imdbId}&season=${season}&episode=${episode}`
: `https://vidsrc.net/embed/${imdbId}`;
ctx.progress(10);
const embedHtml = await ctx.proxiedFetcher<string>(embedUrl, {
headers: {
Referer: 'https://vidsrc.net/',
'User-Agent': 'Mozilla/5.0',
},
});
ctx.progress(30);
// Extract the iframe source using regex
const iframeMatch = embedHtml.match(/<iframe[^>]*id="player_iframe"[^>]*src="([^"]*)"[^>]*>/);
if (!iframeMatch) throw new NotFoundError('Initial iframe not found');
const rcpUrl = iframeMatch[1].startsWith('//') ? `https:${iframeMatch[1]}` : iframeMatch[1];
ctx.progress(50);
const rcpHtml = await ctx.proxiedFetcher<string>(rcpUrl, {
headers: { Referer: embedUrl, 'User-Agent': 'Mozilla/5.0' },
});
// Find the script with prorcp
const scriptMatch = rcpHtml.match(/src\s*:\s*['"]([^'"]+)['"]/);
if (!scriptMatch) throw new NotFoundError('prorcp iframe not found');
const prorcpUrl = scriptMatch[1].startsWith('/') ? `https://cloudnestra.com${scriptMatch[1]}` : scriptMatch[1];
ctx.progress(70);
const finalHtml = await ctx.proxiedFetcher<string>(prorcpUrl, {
headers: { Referer: rcpUrl, 'User-Agent': 'Mozilla/5.0' },
});
// Find script containing Playerjs
const scripts = finalHtml.split('<script');
let scriptWithPlayer = '';
for (const script of scripts) {
if (script.includes('Playerjs')) {
scriptWithPlayer = script;
break;
}
}
if (!scriptWithPlayer) throw new NotFoundError('No Playerjs config found');
const m3u8Match = scriptWithPlayer.match(/file\s*:\s*['"]([^'"]+)['"]/);
if (!m3u8Match) throw new NotFoundError('No file field in Playerjs');
let streamUrl = m3u8Match[1];
if (!streamUrl.includes('.m3u8')) {
// Check if we need to decode the URL
const v = JSON.parse(decode(o.u));
streamUrl = mirza(streamUrl, v);
}
ctx.progress(90);
const headers = {
referer: 'https://cloudnestra.com/',
origin: 'https://cloudnestra.com',
};
return {
stream: [
{
id: 'vidsrc-cloudnestra',
type: 'hls',
playlist: createM3U8ProxyUrl(streamUrl, headers),
flags: [flags.CORS_ALLOWED],
captions: [],
},
],
embeds: [],
};
}
export const vidsrcScraper = makeSourcerer({
id: 'cloudnestra',
name: 'Cloudnestra',
rank: 180,
flags: [flags.CORS_ALLOWED],
scrapeMovie: vidsrcScrape,
scrapeShow: vidsrcScrape,
});

View file

@ -10,6 +10,7 @@ import { viperScraper } from '@/providers/embeds/viper';
import { warezcdnembedMp4Scraper } from '@/providers/embeds/warezcdn/mp4';
import { embedsuScraper } from '@/providers/sources/embedsu';
import { soaperTvScraper } from '@/providers/sources/soapertv';
import { vidsrcScraper } from '@/providers/sources/vidsrc';
import { wecimaScraper } from '@/providers/sources/wecima';
import { Stream } from '@/providers/streams';
import { IndividualEmbedRunnerOptions } from '@/runners/individualRunner';
@ -30,6 +31,7 @@ const SKIP_VALIDATION_CHECK_IDS = [
wecimaScraper.id,
...cinemaosHexaEmbeds.map((e) => e.id),
soaperTvScraper.id,
vidsrcScraper.id,
];
export function isValidStream(stream: Stream | undefined): boolean {