working on new sources
Some checks failed
Testing / Testing (push) Has been cancelled

This commit is contained in:
Pas 2025-11-24 11:31:18 -07:00
parent abefc21dc7
commit 488dbba9f9
4 changed files with 321 additions and 0 deletions

View file

@ -29,6 +29,7 @@ import { cinemaosEmbeds } from './embeds/cinemaos';
import { closeLoadScraper } from './embeds/closeload'; import { closeLoadScraper } from './embeds/closeload';
import { droploadScraper } from './embeds/dropload'; import { droploadScraper } from './embeds/dropload';
import { filelionsScraper } from './embeds/filelions'; import { filelionsScraper } from './embeds/filelions';
import { hubcloudScraper } from './embeds/hubcloud';
import { madplayBaseEmbed, madplayNsapiEmbed, madplayNsapiVidFastEmbed, madplayRoperEmbed } from './embeds/madplay'; import { madplayBaseEmbed, madplayNsapiEmbed, madplayNsapiVidFastEmbed, madplayRoperEmbed } from './embeds/madplay';
import { mp4hydraServer1Scraper, mp4hydraServer2Scraper } from './embeds/mp4hydra'; import { mp4hydraServer1Scraper, mp4hydraServer2Scraper } from './embeds/mp4hydra';
import { myanimedubScraper } from './embeds/myanimedub'; import { myanimedubScraper } from './embeds/myanimedub';
@ -80,7 +81,9 @@ import { cinemaosScraper } from './sources/cinemaos';
import { coitusScraper } from './sources/coitus'; import { coitusScraper } from './sources/coitus';
import { cuevana3Scraper } from './sources/cuevana3'; import { cuevana3Scraper } from './sources/cuevana3';
import { debridScraper } from './sources/debrid'; import { debridScraper } from './sources/debrid';
import { einschaltenScraper } from './sources/einschalten';
import { embedsuScraper } from './sources/embedsu'; import { embedsuScraper } from './sources/embedsu';
import { fourkhdhubScraper } from './sources/fourkhdhub';
import { hdRezkaScraper } from './sources/hdrezka'; import { hdRezkaScraper } from './sources/hdrezka';
import { iosmirrorScraper } from './sources/iosmirror'; import { iosmirrorScraper } from './sources/iosmirror';
import { iosmirrorPVScraper } from './sources/iosmirrorpv'; import { iosmirrorPVScraper } from './sources/iosmirrorpv';
@ -148,7 +151,9 @@ export function gatherAllSources(): Array<Sourcerer> {
primewireScraper, primewireScraper,
movies4fScraper, movies4fScraper,
debridScraper, debridScraper,
fourkhdhubScraper,
cinehdplusScraper, cinehdplusScraper,
einschaltenScraper,
]; ];
} }
@ -217,6 +222,7 @@ export function gatherAllEmbeds(): Array<Embed> {
vidhideSpanishScraper, vidhideSpanishScraper,
vidhideEnglishScraper, vidhideEnglishScraper,
filelionsScraper, filelionsScraper,
hubcloudScraper,
droploadScraper, droploadScraper,
supervideoScraper, supervideoScraper,
]; ];

View file

@ -0,0 +1,83 @@
import { load } from 'cheerio';
import { makeEmbed } from '@/providers/base';
import { NotFoundError } from '@/utils/errors';
export const hubcloudScraper = makeEmbed({
id: 'hubcloud',
name: 'HubCloud',
rank: 140,
async scrape(ctx) {
// Fetch the HubCloud page with referer
const html = await ctx.proxiedFetcher(ctx.url, {
headers: {
Referer: ctx.url, // Use the HubCloud URL as referer
},
});
// Extract redirect URL from var url = '...'
const redirectUrlMatch = html.match(/var url\s*=\s*['"](.*?)['"]/);
if (!redirectUrlMatch) throw new NotFoundError('Redirect URL not found');
// Fetch the links page
const linksHtml = await ctx.proxiedFetcher(redirectUrlMatch[1], {
headers: {
Referer: ctx.url,
},
});
const $ = load(linksHtml);
const qualities: Record<string, { type: 'mp4'; url: string }> = {};
// Extract FSL links
$('a')
.filter((_i, el) => {
const text = $(el).text();
return text.includes('FSL') || text.includes('Download File');
})
.each((i, el) => {
const url = $(el).attr('href');
if (url) {
const fullUrl = url.startsWith('http') ? url : `https://hubcloud.fyi${url}`;
qualities[`fsl_${i}`] = {
type: 'mp4',
url: fullUrl,
};
}
});
// Extract PixelServer links
$('a')
.filter((_i, el) => $(el).text().includes('PixelServer'))
.each((i, el) => {
const url = $(el).attr('href');
if (url) {
// Convert PixelServer URL to API endpoint
const apiUrl = url.replace('/u/', '/api/file/');
const fullUrl = apiUrl.startsWith('http') ? apiUrl : `https://hubcloud.fyi${apiUrl}`;
qualities[`pixelserver_${i}`] = {
type: 'mp4',
url: fullUrl,
};
}
});
if (Object.keys(qualities).length === 0) throw new NotFoundError('No playable links found');
return {
stream: [
{
id: 'primary',
type: 'file',
qualities,
flags: [],
headers: {
referer: 'https://hubcloud.fyi',
},
captions: [],
},
],
};
},
});

View file

@ -0,0 +1,47 @@
import { flags } from '@/entrypoint/utils/targets';
import { SourcererOutput, makeSourcerer } from '@/providers/base';
import { MovieScrapeContext } from '@/utils/context';
import { NotFoundError } from '@/utils/errors';
interface EinschaltenResponse {
releaseName: string;
streamUrl: string;
}
const baseUrl = 'https://einschalten.in';
async function scrapeMovie(ctx: MovieScrapeContext): Promise<SourcererOutput> {
const apiUrl = `${baseUrl}/api/movies/${ctx.media.tmdbId}/watch`;
const response = await ctx.proxiedFetcher<EinschaltenResponse>(apiUrl, {
headers: {
Referer: baseUrl,
'User-Agent':
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
},
});
if (!response?.streamUrl) {
throw new NotFoundError('No stream URL found');
}
ctx.progress(90);
return {
embeds: [
{
embedId: 'dood',
url: response.streamUrl,
},
],
};
}
export const einschaltenScraper = makeSourcerer({
id: 'einschalten',
name: 'Einschalten',
rank: 170,
disabled: false,
flags: [],
scrapeMovie,
});

View file

@ -0,0 +1,185 @@
import { load } from 'cheerio';
import { SourcererOutput, makeSourcerer } from '@/providers/base';
import { MovieScrapeContext, ShowScrapeContext } from '@/utils/context';
import { NotFoundError } from '@/utils/errors';
// ROT13 cipher implementation
function rot13Cipher(input: string): string {
return input.replace(/[a-zA-Z]/g, (char) => {
const code = char.charCodeAt(0);
const base = code >= 65 && code <= 90 ? 65 : 97; // A or a
return String.fromCharCode(((code - base + 13) % 26) + base);
});
}
// Levenshtein distance implementation for string similarity
function levenshteinDistance(a: string, b: string): number {
if (a.length === 0) return b.length;
if (b.length === 0) return a.length;
const matrix = Array.from({ length: b.length + 1 }, (_, i) => [i]);
matrix[0] = Array.from({ length: a.length + 1 }, (_, i) => i);
for (let i = 1; i <= b.length; i++) {
for (let j = 1; j <= a.length; j++) {
const cost = a[j - 1] === b[i - 1] ? 0 : 1;
matrix[i][j] = Math.min(
matrix[i - 1][j] + 1, // deletion
matrix[i][j - 1] + 1, // insertion
matrix[i - 1][j - 1] + cost, // substitution
);
}
}
return matrix[b.length][a.length];
}
const baseUrl = 'https://4khdhub.fans';
// Helper function to resolve redirect URLs
async function resolveRedirectUrl(ctx: ShowScrapeContext | MovieScrapeContext, redirectUrl: URL): Promise<URL> {
const redirectHtml = await ctx.proxiedFetcher(redirectUrl.toString());
const redirectDataMatch = redirectHtml.match(/'o','(.*?)'/) as RegExpMatchArray;
if (!redirectDataMatch) throw new NotFoundError('No redirect data found');
const redirectData = JSON.parse(atob(rot13Cipher(atob(atob(redirectDataMatch[1]))))) as { o: string };
return new URL(atob(redirectData.o));
}
// Helper function to extract embed URL (HubCloud URL)
async function extractEmbedUrl(
ctx: ShowScrapeContext | MovieScrapeContext,
$: ReturnType<typeof load>,
$item: ReturnType<ReturnType<typeof load>>,
): Promise<string> {
// Try HubCloud first
const hubCloudLink = $item.find('a:contains("HubCloud")').first();
if (hubCloudLink.length > 0) {
const redirectUrl = new URL(hubCloudLink.attr('href') || '', baseUrl);
return (await resolveRedirectUrl(ctx, redirectUrl)).toString();
}
// Fallback to HubDrive
const hubDriveLink = $item.find('a:contains("HubDrive")').first();
if (hubDriveLink.length > 0) {
const redirectUrl = new URL(hubDriveLink.attr('href') || '', baseUrl);
const hubDriveHtml = await ctx.proxiedFetcher((await resolveRedirectUrl(ctx, redirectUrl)).toString());
const $hubDrive = load(hubDriveHtml);
const hubCloudUrl = $hubDrive('a:contains("HubCloud")').attr('href');
if (hubCloudUrl) {
return hubCloudUrl.startsWith('http') ? hubCloudUrl : `https://hubcloud.fyi${hubCloudUrl}`;
}
}
throw new NotFoundError('No valid embed URL found');
}
// Main scraper function
async function scrapeFourKHDHub(ctx: ShowScrapeContext | MovieScrapeContext): Promise<SourcererOutput> {
// Build search URL
const searchUrl = new URL(`/?s=${encodeURIComponent(`${ctx.media.title} ${ctx.media.releaseYear}`)}`, baseUrl);
// Fetch search results
const searchPage = await ctx.proxiedFetcher(searchUrl.toString());
const $search = load(searchPage);
// Find matching movie/TV show
const isSeries = ctx.media.type === 'show';
const contentType = isSeries ? 'Series' : 'Movies';
const matchingCard = $search(`.movie-card:has(.movie-card-format:contains("${contentType}"))`)
.filter((_, el) => {
const movieCardYear = parseInt($search('.movie-card-meta', el).text(), 10);
return Math.abs(movieCardYear - ctx.media.releaseYear) <= 1;
})
.filter((_, el) => {
const movieCardTitle = $search('.movie-card-title', el)
.text()
.replace(/\[.*?]/, '')
.trim();
return levenshteinDistance(movieCardTitle.toLowerCase(), ctx.media.title.toLowerCase()) < 5;
})
.first();
if (!matchingCard.length) throw new NotFoundError('No matching content found');
const contentUrl = $search(matchingCard).attr('href');
if (!contentUrl) throw new NotFoundError('No content URL found');
ctx.progress(30);
// Fetch content page
const contentPage = await ctx.proxiedFetcher(contentUrl, { baseUrl });
const $content = load(contentPage);
const embeds: Array<{
embedId: string;
url: string;
}> = [];
if (isSeries) {
// Handle TV shows
const showCtx = ctx as ShowScrapeContext;
const seasonNumber = showCtx.media.season.number;
const episodeNumber = showCtx.media.episode.number;
const episodeItems = $content(`.episode-item`)
.filter((_i, el) =>
$content('.episode-title', el)
.text()
.includes(`S${String(seasonNumber).padStart(2, '0')}`),
)
.map((_i, el) => ({
downloadItem: $content('.episode-download-item', el)
.filter((_j, item) =>
$content(item)
.text()
.includes(`Episode-${String(episodeNumber).padStart(2, '0')}`),
)
.first(),
}))
.get()
.filter(({ downloadItem }) => downloadItem.length > 0);
for (const { downloadItem } of episodeItems) {
try {
const embedUrl = await extractEmbedUrl(ctx, $content, downloadItem);
embeds.push({ embedId: 'hubcloud', url: embedUrl });
} catch (error) {
// Skip failed extractions
continue;
}
}
} else {
// Handle movies
const downloadItems = $content('.download-item').get();
for (const item of downloadItems) {
try {
const $item = $content(item);
const embedUrl = await extractEmbedUrl(ctx, $content, $item);
embeds.push({ embedId: 'hubcloud', url: embedUrl });
} catch (error) {
// Skip failed extractions
continue;
}
}
}
if (embeds.length === 0) throw new NotFoundError('No embeds found');
ctx.progress(90);
return { embeds };
}
export const fourkhdhubScraper = makeSourcerer({
id: '4khdhub',
name: '4KHDHub',
rank: 160,
disabled: false,
flags: [],
scrapeMovie: scrapeFourKHDHub,
scrapeShow: scrapeFourKHDHub,
});