mirror of
https://github.com/p-stream/providers.git
synced 2026-04-20 09:22:07 +00:00
Add cuevana3
This commit is contained in:
parent
3de86d4dfa
commit
5b8749e18f
4 changed files with 312 additions and 0 deletions
|
|
@ -49,6 +49,7 @@ import { mp4hydraServer1Scraper, mp4hydraServer2Scraper } from './embeds/mp4hydr
|
|||
import { ridooScraper } from './embeds/ridoo';
|
||||
import { streamtapeScraper } from './embeds/streamtape';
|
||||
import { streamvidScraper } from './embeds/streamvid';
|
||||
import { streamwishEnglishScraper, streamwishLatinoScraper, streamwishSpanishScraper } from './embeds/streamwish';
|
||||
import { vidCloudScraper } from './embeds/vidcloud';
|
||||
import {
|
||||
VidsrcsuServer10Scraper,
|
||||
|
|
@ -82,6 +83,7 @@ import { oneServerScraper } from './sources/1server';
|
|||
import { EightStreamScraper } from './sources/8stream';
|
||||
import { coitusScraper } from './sources/coitus';
|
||||
import { ConsumetScraper } from './sources/consumet';
|
||||
import { cuevana3Scraper } from './sources/cuevana3';
|
||||
import { embedsuScraper } from './sources/embedsu';
|
||||
import { FedAPIScraper } from './sources/fedapi';
|
||||
import { hdRezkaScraper } from './sources/hdrezka';
|
||||
|
|
@ -102,6 +104,7 @@ import { xprimeScraper } from './sources/xprime';
|
|||
export function gatherAllSources(): Array<Sourcerer> {
|
||||
// all sources are gathered here
|
||||
return [
|
||||
cuevana3Scraper,
|
||||
catflixScraper,
|
||||
ridooMoviesScraper,
|
||||
hdRezkaScraper,
|
||||
|
|
@ -199,5 +202,8 @@ export function gatherAllEmbeds(): Array<Embed> {
|
|||
oneServerHianimeEmbed,
|
||||
oneServerAnimepaheEmbed,
|
||||
oneServerAnizoneEmbed,
|
||||
streamwishLatinoScraper,
|
||||
streamwishSpanishScraper,
|
||||
streamwishEnglishScraper,
|
||||
];
|
||||
}
|
||||
|
|
|
|||
64
src/providers/embeds/streamwish.ts
Normal file
64
src/providers/embeds/streamwish.ts
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
/* eslint-disable no-console */
|
||||
import { flags } from '@/entrypoint/utils/targets';
|
||||
import { makeEmbed } from '@/providers/base';
|
||||
import { NotFoundError } from '@/utils/errors';
|
||||
|
||||
const providers = [
|
||||
{
|
||||
id: 'streamwish-latino',
|
||||
name: 'StreamWish (Latino)',
|
||||
rank: 170,
|
||||
},
|
||||
{
|
||||
id: 'streamwish-spanish',
|
||||
name: 'StreamWish (Castellano)',
|
||||
rank: 169,
|
||||
},
|
||||
{
|
||||
id: 'streamwish-english',
|
||||
name: 'StreamWish (English)',
|
||||
rank: 168,
|
||||
},
|
||||
];
|
||||
|
||||
function embed(provider: { id: string; name: string; rank: number }) {
|
||||
return makeEmbed({
|
||||
id: provider.id,
|
||||
name: provider.name,
|
||||
rank: provider.rank,
|
||||
async scrape(ctx) {
|
||||
console.log('Starting scrape for StreamWish with URL:', ctx.url);
|
||||
|
||||
const encodedUrl = encodeURIComponent(ctx.url);
|
||||
const apiUrl = `https://ws-m3u8.moonpic.qzz.io/m3u8/${encodedUrl}`;
|
||||
|
||||
const response = await ctx.proxiedFetcher<{ m3u8: string }>(apiUrl, {
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
// 'ngrok-skip-browser-warning': 'true', // this header bypass ngrok warning
|
||||
},
|
||||
});
|
||||
|
||||
const videoUrl = response.m3u8;
|
||||
if (!videoUrl) throw new NotFoundError('No video URL found');
|
||||
|
||||
return {
|
||||
stream: [
|
||||
{
|
||||
id: 'primary',
|
||||
type: 'hls',
|
||||
playlist: videoUrl,
|
||||
flags: [],
|
||||
captions: [],
|
||||
headers: {
|
||||
Referer: 'https://streamwish.to/',
|
||||
Origin: 'https://streamwish.to',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export const [streamwishLatinoScraper, streamwishSpanishScraper, streamwishEnglishScraper] = providers.map(embed);
|
||||
234
src/providers/sources/cuevana3.ts
Normal file
234
src/providers/sources/cuevana3.ts
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
import { load } from 'cheerio';
|
||||
|
||||
import { SourcererOutput, makeSourcerer } from '@/providers/base';
|
||||
import { MovieScrapeContext, ShowScrapeContext } from '@/utils/context';
|
||||
import { NotFoundError } from '@/utils/errors';
|
||||
|
||||
const baseUrl = 'https://www.cuevana3.eu';
|
||||
|
||||
interface Video {
|
||||
result: string;
|
||||
}
|
||||
|
||||
interface VideosByLanguage {
|
||||
latino?: Video[];
|
||||
spanish?: Video[];
|
||||
english?: Video[];
|
||||
[key: string]: Video[] | undefined;
|
||||
}
|
||||
|
||||
interface MovieData {
|
||||
videos: VideosByLanguage;
|
||||
}
|
||||
|
||||
interface EpisodeData {
|
||||
videos: VideosByLanguage;
|
||||
}
|
||||
|
||||
function normalizeTitle(title: string): string {
|
||||
return title
|
||||
.normalize('NFD') // Remove accents
|
||||
.replace(/[\u0300-\u036f]/g, '')
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9\s-]/gi, '') // Remove non-alphanumeric characters
|
||||
.replace(/\s+/g, '-') // Replace spaces with hyphens
|
||||
.replace(/-+/g, '-'); // Remove multiple hyphens
|
||||
}
|
||||
|
||||
async function getStreamUrl(ctx: MovieScrapeContext | ShowScrapeContext, embedUrl: string): Promise<string | null> {
|
||||
try {
|
||||
const html = await ctx.proxiedFetcher(embedUrl);
|
||||
const match = html.match(/var url = '([^']+)'/);
|
||||
if (match) {
|
||||
return match[1];
|
||||
}
|
||||
} catch {
|
||||
// Ignore errors from dead embeds
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function validateStream(url: string): boolean {
|
||||
return (
|
||||
url.startsWith('https://') && (url.includes('streamwish') || url.includes('filemoon') || url.includes('vidhide'))
|
||||
);
|
||||
}
|
||||
|
||||
async function extractVideos(ctx: MovieScrapeContext | ShowScrapeContext, videos: VideosByLanguage) {
|
||||
const videoList: { embedId: string; url: string }[] = [];
|
||||
|
||||
for (const [lang, videoArray] of Object.entries(videos)) {
|
||||
if (!videoArray) continue;
|
||||
|
||||
for (const video of videoArray) {
|
||||
if (!video.result) continue;
|
||||
|
||||
const realUrl = await getStreamUrl(ctx, video.result);
|
||||
if (!realUrl || !validateStream(realUrl)) continue;
|
||||
|
||||
let embedId = '';
|
||||
if (realUrl.includes('filemoon')) embedId = 'filemoon';
|
||||
else if (realUrl.includes('streamwish')) {
|
||||
if (lang === 'latino') embedId = 'streamwish-latino';
|
||||
else if (lang === 'spanish') embedId = 'streamwish-spanish';
|
||||
else if (lang === 'english') embedId = 'streamwish-english';
|
||||
else embedId = 'streamwish-latino';
|
||||
} else if (realUrl.includes('vidhide')) embedId = 'vidhide';
|
||||
else if (realUrl.includes('voe')) embedId = 'voe';
|
||||
else continue;
|
||||
|
||||
videoList.push({
|
||||
embedId,
|
||||
url: realUrl,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return videoList;
|
||||
}
|
||||
|
||||
async function fetchTmdbTitleInSpanish(tmdbId: number, apiKey: string, mediaType: 'movie' | 'show'): Promise<string> {
|
||||
const endpoint =
|
||||
mediaType === 'movie'
|
||||
? `https://api.themoviedb.org/3/movie/${tmdbId}?api_key=${apiKey}&language=es-ES`
|
||||
: `https://api.themoviedb.org/3/tv/${tmdbId}?api_key=${apiKey}&language=es-ES`;
|
||||
|
||||
const response = await fetch(endpoint);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Error fetching TMDB data: ${response.statusText}`);
|
||||
}
|
||||
const tmdbData = await response.json();
|
||||
return mediaType === 'movie' ? tmdbData.title : tmdbData.name;
|
||||
}
|
||||
|
||||
async function fetchTitleSubstitutes(): Promise<Record<string, string>> {
|
||||
try {
|
||||
const response = await fetch('https://raw.githubusercontent.com/moonpic/fixed-titles/refs/heads/main/main.json');
|
||||
if (!response.ok) throw new Error('Failed to fetch fallback titles');
|
||||
return await response.json();
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
async function comboScraper(ctx: ShowScrapeContext | MovieScrapeContext): Promise<SourcererOutput> {
|
||||
const mediaType = ctx.media.type;
|
||||
const tmdbId = ctx.media.tmdbId;
|
||||
const apiKey = '7604525319adb2db8e7e841cb98e9217';
|
||||
|
||||
if (!tmdbId) {
|
||||
throw new NotFoundError('TMDB ID is required to fetch the title in Spanish');
|
||||
}
|
||||
|
||||
const translatedTitle = await fetchTmdbTitleInSpanish(Number(tmdbId), apiKey, mediaType);
|
||||
let normalizedTitle = normalizeTitle(translatedTitle);
|
||||
|
||||
let pageUrl =
|
||||
mediaType === 'movie'
|
||||
? `${baseUrl}/ver-pelicula/${normalizedTitle}`
|
||||
: `${baseUrl}/episodio/${normalizedTitle}-temporada-${ctx.media.season?.number}-episodio-${ctx.media.episode?.number}`;
|
||||
|
||||
ctx.progress(60);
|
||||
|
||||
let pageContent = await ctx.proxiedFetcher(pageUrl);
|
||||
let $ = load(pageContent);
|
||||
|
||||
let script = $('script')
|
||||
.toArray()
|
||||
.find((scriptEl) => {
|
||||
const content = (scriptEl.children[0] as any)?.data || '';
|
||||
return content.includes('{"props":{"pageProps":');
|
||||
});
|
||||
|
||||
let embeds: { embedId: string; url: string }[] = [];
|
||||
|
||||
if (script) {
|
||||
let jsonData: any;
|
||||
try {
|
||||
const jsonString = (script.children[0] as any).data;
|
||||
const start = jsonString.indexOf('{"props":{"pageProps":');
|
||||
if (start === -1) throw new Error('No valid JSON start found');
|
||||
const partialJson = jsonString.slice(start);
|
||||
jsonData = JSON.parse(partialJson);
|
||||
} catch (error: any) {
|
||||
throw new NotFoundError(`Failed to parse JSON: ${error.message}`);
|
||||
}
|
||||
|
||||
if (mediaType === 'movie') {
|
||||
const movieData = jsonData.props.pageProps.thisMovie as MovieData;
|
||||
if (movieData?.videos) {
|
||||
embeds = (await extractVideos(ctx, movieData.videos)) ?? [];
|
||||
}
|
||||
} else {
|
||||
const episodeData = jsonData.props.pageProps.episode as EpisodeData;
|
||||
if (episodeData?.videos) {
|
||||
embeds = (await extractVideos(ctx, episodeData.videos)) ?? [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (embeds.length === 0) {
|
||||
const fallbacks = await fetchTitleSubstitutes();
|
||||
const fallbackTitle = fallbacks[tmdbId.toString()];
|
||||
|
||||
if (!fallbackTitle) {
|
||||
throw new NotFoundError('No embed data found and no fallback title available');
|
||||
}
|
||||
|
||||
normalizedTitle = normalizeTitle(fallbackTitle);
|
||||
pageUrl =
|
||||
mediaType === 'movie'
|
||||
? `${baseUrl}/ver-pelicula/${normalizedTitle}`
|
||||
: `${baseUrl}/episodio/${normalizedTitle}-temporada-${ctx.media.season?.number}-episodio-${ctx.media.episode?.number}`;
|
||||
|
||||
pageContent = await ctx.proxiedFetcher(pageUrl);
|
||||
$ = load(pageContent);
|
||||
script = $('script')
|
||||
.toArray()
|
||||
.find((scriptEl) => {
|
||||
const content = (scriptEl.children[0] as any)?.data || '';
|
||||
return content.includes('{"props":{"pageProps":');
|
||||
});
|
||||
|
||||
if (script) {
|
||||
let jsonData: any;
|
||||
try {
|
||||
const jsonString = (script.children[0] as any).data;
|
||||
const start = jsonString.indexOf('{"props":{"pageProps":');
|
||||
if (start === -1) throw new Error('No valid JSON start found');
|
||||
const partialJson = jsonString.slice(start);
|
||||
jsonData = JSON.parse(partialJson);
|
||||
} catch (error: any) {
|
||||
throw new NotFoundError(`Failed to parse JSON: ${error.message}`);
|
||||
}
|
||||
|
||||
if (mediaType === 'movie') {
|
||||
const movieData = jsonData.props.pageProps.thisMovie as MovieData;
|
||||
if (movieData?.videos) {
|
||||
embeds = (await extractVideos(ctx, movieData.videos)) ?? [];
|
||||
}
|
||||
} else {
|
||||
const episodeData = jsonData.props.pageProps.episode as EpisodeData;
|
||||
if (episodeData?.videos) {
|
||||
embeds = (await extractVideos(ctx, episodeData.videos)) ?? [];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (embeds.length === 0) {
|
||||
throw new NotFoundError('No valid streams found');
|
||||
}
|
||||
|
||||
return { embeds };
|
||||
}
|
||||
|
||||
export const cuevana3Scraper = makeSourcerer({
|
||||
id: 'cuevana3',
|
||||
name: 'Cuevana3',
|
||||
rank: 80,
|
||||
disabled: false,
|
||||
flags: [],
|
||||
scrapeMovie: comboScraper,
|
||||
scrapeShow: comboScraper,
|
||||
});
|
||||
|
|
@ -24,6 +24,11 @@ import {
|
|||
hianimeHd2DubEmbed,
|
||||
hianimeHd2SubEmbed,
|
||||
} from '@/providers/embeds/hianime';
|
||||
import {
|
||||
streamwishEnglishScraper,
|
||||
streamwishLatinoScraper,
|
||||
streamwishSpanishScraper,
|
||||
} from '@/providers/embeds/streamwish';
|
||||
import { viperScraper } from '@/providers/embeds/viper';
|
||||
import { warezcdnembedMp4Scraper } from '@/providers/embeds/warezcdn/mp4';
|
||||
import {
|
||||
|
|
@ -49,6 +54,9 @@ const SKIP_VALIDATION_CHECK_IDS = [
|
|||
// astraScraper.id,
|
||||
// orionScraper.id,
|
||||
viperScraper.id,
|
||||
streamwishLatinoScraper.id,
|
||||
streamwishSpanishScraper.id,
|
||||
streamwishEnglishScraper.id,
|
||||
uiraliveScraper.id,
|
||||
embedsuScraper.id,
|
||||
FedAPIPrivateScraper.id,
|
||||
|
|
|
|||
Loading…
Reference in a new issue