mirror of
https://github.com/p-stream/providers.git
synced 2026-01-11 20:10:33 +00:00
This commit is contained in:
parent
abefc21dc7
commit
488dbba9f9
4 changed files with 321 additions and 0 deletions
|
|
@ -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,
|
||||||
];
|
];
|
||||||
|
|
|
||||||
83
src/providers/embeds/hubcloud.ts
Normal file
83
src/providers/embeds/hubcloud.ts
Normal 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: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
47
src/providers/sources/einschalten.ts
Normal file
47
src/providers/sources/einschalten.ts
Normal 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,
|
||||||
|
});
|
||||||
185
src/providers/sources/fourkhdhub.ts
Normal file
185
src/providers/sources/fourkhdhub.ts
Normal 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,
|
||||||
|
});
|
||||||
Loading…
Reference in a new issue