Merge branch 'pr/43' into production

This commit is contained in:
Pas 2025-12-09 22:58:21 -07:00
commit 09872c43d8
4 changed files with 215 additions and 0 deletions

View file

@ -81,6 +81,7 @@ import { coitusScraper } from './sources/coitus';
import { cuevana3Scraper } from './sources/cuevana3';
import { debridScraper } from './sources/debrid';
import { embedsuScraper } from './sources/embedsu';
import { fullhdfilmizleScraper } from './sources/fullhdfilmizle';
import { hdRezkaScraper } from './sources/hdrezka';
import { iosmirrorScraper } from './sources/iosmirror';
import { iosmirrorPVScraper } from './sources/iosmirrorpv';
@ -149,6 +150,7 @@ export function gatherAllSources(): Array<Sourcerer> {
movies4fScraper,
debridScraper,
cinehdplusScraper,
fullhdfilmizleScraper,
];
}

View file

@ -0,0 +1,80 @@
import { PackerParams } from './types';
export function rtt(str: string) {
return str.replace(/[a-z]/gi, (c) => {
return String.fromCharCode(c.charCodeAt(0) + (c.toLowerCase() < 'n' ? 13 : -13));
});
}
export function decodeAtom(e: string) {
const t = atob(e.split('').reverse().join(''));
let o = '';
for (let i = 0; i < t.length; i++) {
const r = 'K9L'[i % 3];
const n = t.charCodeAt(i) - ((r.charCodeAt(0) % 5) + 1);
o += String.fromCharCode(n);
}
return atob(o);
}
export function extractPackerParams(rawInput: string): PackerParams | null {
const regex = /'((?:[^'\\]|\\.)*)',\s*(\d+),\s*(\d+),\s*'((?:[^'\\]|\\.)*)'\.split\('\|'\)/;
const match = regex.exec(rawInput);
if (!match) {
console.error('Could not parse parameters. Format is not as expected.');
return null;
}
return {
payload: match[1],
radix: parseInt(match[2], 10),
count: parseInt(match[3], 10),
keywords: match[4].split('|'),
};
}
export function decodeDeanEdwards(params: PackerParams): string {
const { payload, radix, count, keywords } = params;
const dict: { [key: string]: string } = Object.create(null);
const encodeBase = (num: number): string => {
if (num < radix) {
const char = num % radix;
return char > 35 ? String.fromCharCode(char + 29) : char.toString(36);
}
const prefix = encodeBase(Math.floor(num / radix));
const char = num % radix;
const suffix = char > 35 ? String.fromCharCode(char + 29) : char.toString(36);
return prefix + suffix;
};
let i = count;
while (i--) {
const key = encodeBase(i);
const value = keywords[i] || key;
dict[key] = value;
}
return payload.replace(/\b\w+\b/g, (word) => {
if (word in dict) {
return dict[word];
}
return word;
});
}
export function decodeHex(str: string): string {
return str.replace(/\\x([0-9A-Fa-f]{2})/g, (_match, hexGroup) => {
return String.fromCharCode(parseInt(hexGroup, 16));
});
}
export function unescapeString(str: string) {
return str.replace(/\\(.)/g, (match, char) => char);
}

View file

@ -0,0 +1,127 @@
import { flags } from '@/entrypoint/utils/targets';
import { SourcererOutput, makeSourcerer } from '@/providers/base';
import { MovieScrapeContext } from '@/utils/context';
import { NotFoundError } from '@/utils/errors';
import { createM3U8ProxyUrl } from '@/utils/proxy';
import { decodeAtom, decodeDeanEdwards, decodeHex, extractPackerParams, rtt, unescapeString } from './decrypt';
const baseUrl = 'https://www.fullhdfilmizlesene.tv';
const headers = {
Referer: baseUrl,
Accept: 'application/json, text/plain, */*',
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
};
function extractVidmoxy(body: string) {
const regex = /eval\(function\(p,a,c,k,e,d\){.+}}return p}\((\\?'.+.split\(\\?'\|\\?'\)).+$/m;
let decoded = body;
let i = 0;
while (decoded.includes('eval(')) {
const decodedMatch = decoded.match(regex);
if (!decodedMatch) {
throw new NotFoundError('Decryption unsuccessful');
}
const parameters = extractPackerParams(i > 0 ? unescapeString(decodedMatch[1]) : decodedMatch[1]);
if (!parameters) throw new NotFoundError('Decryption unsuccessful');
decoded = decodeDeanEdwards(parameters);
i++;
}
const fileMatch = decoded.match(/"file":"(.+?)"/);
if (!fileMatch) throw new NotFoundError('No playlist found');
const playlistUrl = unescapeString(decodeHex(fileMatch[1]));
return playlistUrl;
}
function extractAtom(body: string) {
const fileMatch = body.match(/"file": av\('(.+)'\),$/m);
if (!fileMatch) throw new NotFoundError('No playlist found');
const playlistUrl = decodeAtom(fileMatch[1]);
return playlistUrl;
}
async function scrapeMovie(ctx: MovieScrapeContext): Promise<SourcererOutput> {
if (!ctx.media.imdbId) {
throw new NotFoundError('IMDb id not provided');
}
const searchJson = await ctx.proxiedFetcher<{ prefix: string; dizilink: string }[]>(
`/autocomplete/q.php?q=${ctx.media.imdbId}`,
{
baseUrl,
headers,
},
);
ctx.progress(30);
if (!searchJson.length) throw new NotFoundError('Media not found');
const searchResult = searchJson[0];
const mediaUrl = `/${searchResult.prefix}/${searchResult.dizilink}`;
const mediaPage = await ctx.proxiedFetcher<string>(mediaUrl, {
baseUrl,
headers,
});
const playerMatch = mediaPage.match(/var scx = {.+"t":\["(.+)"\]},/);
if (!playerMatch) throw new NotFoundError('No source found');
ctx.progress(60);
const playerUrl = atob(rtt(playerMatch[1]));
const isVidmoxy = playerUrl.startsWith('https://vidmoxy.com');
const playerResponse = await ctx.proxiedFetcher<string>(playerUrl + (isVidmoxy ? '?vst=1' : ''), {
headers: {
Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
Referer: baseUrl,
'Sec-Fetch-Dest': 'iframe',
'Sec-Fetch-Mode': 'navigate',
'Sec-Fetch-Site': 'cross-site',
'Sec-Fetch-User': '?1',
'Sec-GPC': '1',
'Upgrade-Insecure-Requests': '1',
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
},
});
ctx.progress(80);
if (!playerResponse || playerResponse === '404') throw new NotFoundError('Player 404: Source is inaccessible');
const playlistUrl = isVidmoxy ? extractVidmoxy(playerResponse) : extractAtom(playerResponse);
return {
embeds: [],
stream: [
{
id: 'primary',
type: 'hls',
playlist: createM3U8ProxyUrl(playlistUrl, ctx.features, headers),
headers,
flags: [flags.CORS_ALLOWED],
captions: [],
},
],
};
}
export const fullhdfilmizleScraper = makeSourcerer({
id: 'fullhdfilmizle',
name: 'FullHDFilmizle (Turkish)',
rank: 3,
disabled: false,
flags: [flags.CORS_ALLOWED],
scrapeMovie,
});

View file

@ -0,0 +1,6 @@
export interface PackerParams {
payload: string; // p
radix: number; // a
count: number; // c
keywords: string[]; // k
}