mirror of
https://github.com/p-stream/providers.git
synced 2026-05-13 12:40:58 +00:00
feat: Add Wecima embed scraper and update related sources
- Implemented wecimaEmbedScraper to extract HLS stream URLs from Wecima embeds. - Updated all.ts to include wecimaEmbedScraper in gatherAllEmbeds. - Refactored animetsuScraper to use animetsuId instead of anilistId. - Enhanced zunimeScraper to support different content types and improved error handling. - Updated fsharetv, ridomovies, and tugaflix scrapers to reflect current status and disabled state. - Added detailed provider status report with testing results and current issues.
This commit is contained in:
parent
8b667bd776
commit
5469e6f40b
11 changed files with 658 additions and 231 deletions
99
PROVIDER_STATUS.md
Normal file
99
PROVIDER_STATUS.md
Normal file
|
|
@ -0,0 +1,99 @@
|
||||||
|
# Provider Status Report
|
||||||
|
|
||||||
|
Last Updated: September 2, 2025
|
||||||
|
|
||||||
|
## Testing Details
|
||||||
|
- **Test Movie**: Fight Club (TMDB ID: 550)
|
||||||
|
- **Fetcher**: Native
|
||||||
|
- **Environment**: Windows PowerShell
|
||||||
|
|
||||||
|
## Current Testing Progress
|
||||||
|
- **Total Tested**: 11/~50 providers
|
||||||
|
- **Success Rate**: 45% (5/11 working)
|
||||||
|
- **Working**: 5 providers
|
||||||
|
- **Failed**: 6 providers
|
||||||
|
|
||||||
|
## Source Providers Status
|
||||||
|
|
||||||
|
### ✅ Working Providers
|
||||||
|
| Provider | Status | Notes | Last Tested |
|
||||||
|
|----------|--------|-------|-------------|
|
||||||
|
| cloudnestra | ✅ Working | Returns HLS stream with proxy depth 2 | 2025-09-02 |
|
||||||
|
| rgshows | ✅ Working | Returns HLS stream with custom headers | 2025-09-02 |
|
||||||
|
| lookmovie | ✅ Working | Returns HLS index.m3u8 + 44 subtitle languages, ip-locked flag | 2025-09-02 |
|
||||||
|
| vidnest | ✅ Working | Returns 2 embed URLs (hollymoviehd, allmovies) | 2025-09-02 |
|
||||||
|
| hdrezka | ✅ Working | File-based MP4 streams, 4 qualities + 3 subtitle languages | 2025-09-02 |
|
||||||
|
|
||||||
|
### ❌ Failing Providers
|
||||||
|
| Provider | Status | Error | Last Tested |
|
||||||
|
|----------|--------|-------|-------------|
|
||||||
|
| ridomovies | ❌ Disabled | Depends on closeload embed, which has broken decoding logic | 2025-09-02 |
|
||||||
|
| zunime | ❌ Disabled | API authentication issues with backend services | 2025-09-02 |
|
||||||
|
| tugaflix | ❌ Disabled | Mixed embeds (mixdrop/streamtape), streamtape blocked in India | 2025-09-02 |
|
||||||
|
| animetsu | ❌ Disabled | Returns embed URLs but no actual streams, anime-only | 2025-09-02 |
|
||||||
|
| wecima | ❌ Disabled | Arabic site, complex embed structure not extracting streams | 2025-09-02 |
|
||||||
|
| fsharetv | ❌ Disabled | Geo-blocked, works only with VPN (provides MP4 streams) | 2025-09-02 |
|
||||||
|
|
||||||
|
### ⏳ Pending Tests
|
||||||
|
| Provider | Rank | Media Types | Notes |
|
||||||
|
|----------|------|-------------|-------|
|
||||||
|
| vidsrcvip | 200 | Movies/Shows | - |
|
||||||
|
| vidify | 180 | Movies/Shows | - |
|
||||||
|
| animeflv | 170 | Movies/Shows | - |
|
||||||
|
| animeflv | 170 | Movies/Shows | - |
|
||||||
|
| catflix | 160 | Movies/Shows | - |
|
||||||
|
| coitus | 150 | Movies/Shows | - |
|
||||||
|
| cuevana3 | 140 | Movies/Shows | - |
|
||||||
|
| embedsu | 130 | Movies/Shows | - |
|
||||||
|
| fsharetv | 120 | Movies/Shows | - |
|
||||||
|
| iosmirror | 110 | Movies/Shows | - |
|
||||||
|
| iosmirrorpv | 105 | Movies/Shows | - |
|
||||||
|
| madplay | 100 | Movies/Shows | - |
|
||||||
|
| mp4hydra | 95 | Movies/Shows | - |
|
||||||
|
| nepu | 90 | Movies/Shows | - |
|
||||||
|
| nunflix | 85 | Movies/Shows | - |
|
||||||
|
| pirxcy | 80 | Movies/Shows | - |
|
||||||
|
| slidemovies | 70 | Movies/Shows | - |
|
||||||
|
| streambox | 65 | Movies/Shows | - |
|
||||||
|
| streambox | 65 | Movies/Shows | - |
|
||||||
|
| vidapiclick | 60 | Movies/Shows | - |
|
||||||
|
| wecima | 55 | Movies/Shows | - |
|
||||||
|
| zoechip | 50 | Movies/Shows | - |
|
||||||
|
| autoembed | 35 | Movies/Shows | - |
|
||||||
|
| cinemaos | 30 | Movies/Shows | - |
|
||||||
|
|
||||||
|
## Embed Providers Status
|
||||||
|
|
||||||
|
### ⏳ Pending Tests
|
||||||
|
| Provider | Rank | Notes |
|
||||||
|
|----------|------|-------|
|
||||||
|
| upcloud | 201 | - |
|
||||||
|
| vidcloud | 201 | Disabled, uses upcloud |
|
||||||
|
| streamwish | 125 | - |
|
||||||
|
| mp4hydra | 120 | - |
|
||||||
|
| mixdrop | 115 | - |
|
||||||
|
| streamtape | 110 | - |
|
||||||
|
| dood | 105 | - |
|
||||||
|
| streamvid | 100 | - |
|
||||||
|
| vidsrcsu | 95 | - |
|
||||||
|
| ridoo | 90 | - |
|
||||||
|
| turbovid | 85 | - |
|
||||||
|
| closeload | 80 | - |
|
||||||
|
| viper | 75 | - |
|
||||||
|
| madplay | 70 | - |
|
||||||
|
| animetsu | 65 | - |
|
||||||
|
| autoembed | 60 | - |
|
||||||
|
| cinemaos | 55 | - |
|
||||||
|
| zunime | 50 | - |
|
||||||
|
|
||||||
|
## Common Issues Encountered
|
||||||
|
1. **Connection Timeouts**: Sites may be down or blocking requests
|
||||||
|
2. **Domain Changes**: Streaming sites frequently change domains
|
||||||
|
3. **Anti-Bot Protection**: Sites may have enhanced protection
|
||||||
|
4. **Geo-blocking**: Some sites may be region-restricted
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
- Continue testing providers in rank order
|
||||||
|
- Update status as testing progresses
|
||||||
|
- Identify patterns in failures
|
||||||
|
- Document working provider configurations
|
||||||
|
|
@ -63,6 +63,7 @@ import { viperScraper } from './embeds/viper';
|
||||||
import { warezcdnembedHlsScraper } from './embeds/warezcdn/hls';
|
import { warezcdnembedHlsScraper } from './embeds/warezcdn/hls';
|
||||||
import { warezcdnembedMp4Scraper } from './embeds/warezcdn/mp4';
|
import { warezcdnembedMp4Scraper } from './embeds/warezcdn/mp4';
|
||||||
import { warezPlayerScraper } from './embeds/warezcdn/warezplayer';
|
import { warezPlayerScraper } from './embeds/warezcdn/warezplayer';
|
||||||
|
import { wecimaEmbedScraper } from './embeds/wecima-embed';
|
||||||
import { zunimeEmbeds } from './embeds/zunime';
|
import { zunimeEmbeds } from './embeds/zunime';
|
||||||
import { EightStreamScraper } from './sources/8stream';
|
import { EightStreamScraper } from './sources/8stream';
|
||||||
import { animeflvScraper } from './sources/animeflv';
|
import { animeflvScraper } from './sources/animeflv';
|
||||||
|
|
@ -187,5 +188,6 @@ export function gatherAllEmbeds(): Array<Embed> {
|
||||||
vidnestAllmoviesEmbed,
|
vidnestAllmoviesEmbed,
|
||||||
vidnestFlixhqEmbed,
|
vidnestFlixhqEmbed,
|
||||||
vidnestOfficialEmbed,
|
vidnestOfficialEmbed,
|
||||||
|
wecimaEmbedScraper,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ export function makeAnimetsuEmbed(id: string, rank: number = 100) {
|
||||||
const serverName = id as (typeof ANIMETSU_SERVERS)[number];
|
const serverName = id as (typeof ANIMETSU_SERVERS)[number];
|
||||||
|
|
||||||
const query = JSON.parse(ctx.url);
|
const query = JSON.parse(ctx.url);
|
||||||
const { type, anilistId, episode } = query;
|
const { type, animetsuId, episode } = query; // Use animetsuId instead of anilistId
|
||||||
|
|
||||||
if (type !== 'movie' && type !== 'show') {
|
if (type !== 'movie' && type !== 'show') {
|
||||||
throw new NotFoundError('Unsupported media type');
|
throw new NotFoundError('Unsupported media type');
|
||||||
|
|
@ -34,7 +34,7 @@ export function makeAnimetsuEmbed(id: string, rank: number = 100) {
|
||||||
headers,
|
headers,
|
||||||
query: {
|
query: {
|
||||||
server: serverName,
|
server: serverName,
|
||||||
id: String(anilistId),
|
id: String(animetsuId), // Use animetsu internal ID
|
||||||
num: String(episode ?? 1),
|
num: String(episode ?? 1),
|
||||||
subType: 'dub',
|
subType: 'dub',
|
||||||
},
|
},
|
||||||
|
|
|
||||||
73
src/providers/embeds/wecima-embed.ts
Normal file
73
src/providers/embeds/wecima-embed.ts
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
import { load } from 'cheerio';
|
||||||
|
|
||||||
|
import { flags } from '@/entrypoint/utils/targets';
|
||||||
|
import { makeEmbed } from '@/providers/base';
|
||||||
|
|
||||||
|
export const wecimaEmbedScraper = makeEmbed({
|
||||||
|
id: 'wecima-embed',
|
||||||
|
name: 'Wecima Embed',
|
||||||
|
rank: 90,
|
||||||
|
async scrape(ctx) {
|
||||||
|
// Get the embed page
|
||||||
|
const embedPage = await ctx.proxiedFetcher<string>(ctx.url);
|
||||||
|
const $ = load(embedPage);
|
||||||
|
|
||||||
|
// Look for the HLS master playlist URL in the JavaScript
|
||||||
|
// Based on your analysis, it should be in the format:
|
||||||
|
// https://fdewsdc.sbs/stream/TOKEN/ID/TIMESTAMP/FILEID/master.m3u8
|
||||||
|
|
||||||
|
let hlsUrl: string | undefined;
|
||||||
|
|
||||||
|
// Try to extract from the JavaScript setup
|
||||||
|
const scriptTags = $('script');
|
||||||
|
for (const script of scriptTags) {
|
||||||
|
const scriptContent = $(script).html() || '';
|
||||||
|
|
||||||
|
// Look for master.m3u8 URLs
|
||||||
|
const hlsMatch = scriptContent.match(/https?:\/\/[^"']+\/master\.m3u8/);
|
||||||
|
if (hlsMatch) {
|
||||||
|
hlsUrl = hlsMatch[0];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also look for the stream URL pattern you found
|
||||||
|
const streamMatch = scriptContent.match(/https?:\/\/fdewsdc\.sbs\/stream\/[^"']+\/master\.m3u8/);
|
||||||
|
if (streamMatch) {
|
||||||
|
hlsUrl = streamMatch[0];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not found in scripts, try to find it in data attributes or other places
|
||||||
|
if (!hlsUrl) {
|
||||||
|
const videoElements = $('video, source, div[data-src], div[data-url]');
|
||||||
|
for (const element of videoElements) {
|
||||||
|
const src = $(element).attr('src') || $(element).attr('data-src') || $(element).attr('data-url');
|
||||||
|
if (src && src.includes('master.m3u8')) {
|
||||||
|
hlsUrl = src;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hlsUrl) {
|
||||||
|
throw new Error('No HLS stream URL found in wecima embed');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
stream: [
|
||||||
|
{
|
||||||
|
id: 'primary',
|
||||||
|
type: 'hls',
|
||||||
|
flags: [flags.IP_LOCKED],
|
||||||
|
captions: [],
|
||||||
|
playlist: hlsUrl,
|
||||||
|
headers: {
|
||||||
|
Referer: ctx.url,
|
||||||
|
Origin: new URL(ctx.url).origin,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
@ -4,7 +4,7 @@ import { EmbedOutput, makeEmbed } from '../base';
|
||||||
|
|
||||||
const ZUNIME_SERVERS = ['hd-2', 'miko', 'shiro', 'zaza'];
|
const ZUNIME_SERVERS = ['hd-2', 'miko', 'shiro', 'zaza'];
|
||||||
|
|
||||||
const baseUrl = 'https://backend.xaiby.sbs';
|
const baseUrl = 'https://vidnest.fun'; // Try direct vidnest API
|
||||||
const headers = {
|
const headers = {
|
||||||
referer: 'https://vidnest.fun/',
|
referer: 'https://vidnest.fun/',
|
||||||
origin: 'https://vidnest.fun',
|
origin: 'https://vidnest.fun',
|
||||||
|
|
@ -19,33 +19,94 @@ export function makeZunimeEmbed(id: string, rank: number = 100) {
|
||||||
rank,
|
rank,
|
||||||
async scrape(ctx): Promise<EmbedOutput> {
|
async scrape(ctx): Promise<EmbedOutput> {
|
||||||
const serverName = id as (typeof ZUNIME_SERVERS)[number];
|
const serverName = id as (typeof ZUNIME_SERVERS)[number];
|
||||||
|
const embedUrl = ctx.url;
|
||||||
|
|
||||||
const query = JSON.parse(ctx.url);
|
// Parse the embed URL to determine content type and parameters
|
||||||
const { anilistId, episode } = query;
|
let apiPath = '';
|
||||||
|
let apiQuery: any = {};
|
||||||
|
|
||||||
const res = await ctx.proxiedFetcher(`${'/sources'}`, {
|
if (embedUrl.includes('/movie/')) {
|
||||||
|
// Movie format: https://vidnest.fun/movie/550
|
||||||
|
const tmdbId = embedUrl.split('/movie/')[1];
|
||||||
|
apiPath = '/api/movie'; // Try API prefix
|
||||||
|
apiQuery = {
|
||||||
|
tmdb: tmdbId,
|
||||||
|
server: serverName,
|
||||||
|
};
|
||||||
|
} else if (embedUrl.includes('/tv/')) {
|
||||||
|
// TV format: https://vidnest.fun/tv/1396/1/1
|
||||||
|
const pathParts = embedUrl.split('/tv/')[1].split('/');
|
||||||
|
const tmdbId = pathParts[0];
|
||||||
|
const season = pathParts[1] || '1';
|
||||||
|
const episode = pathParts[2] || '1';
|
||||||
|
|
||||||
|
apiPath = '/api/tv'; // Try API prefix
|
||||||
|
apiQuery = {
|
||||||
|
tmdb: tmdbId,
|
||||||
|
season,
|
||||||
|
episode,
|
||||||
|
server: serverName,
|
||||||
|
};
|
||||||
|
} else if (embedUrl.includes('/anime/')) {
|
||||||
|
// Anime format: https://vidnest.fun/anime/16498/1/dub
|
||||||
|
const pathParts = embedUrl.split('/anime/')[1].split('/');
|
||||||
|
const anilistId = pathParts[0];
|
||||||
|
const episode = pathParts[1] || '1';
|
||||||
|
const type = pathParts[2] || 'dub';
|
||||||
|
|
||||||
|
apiPath = '/api/anime'; // Try API prefix
|
||||||
|
apiQuery = {
|
||||||
|
anilist: anilistId,
|
||||||
|
episode,
|
||||||
|
server: serverName,
|
||||||
|
type,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
throw new NotFoundError('Invalid embed URL format');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the backend API
|
||||||
|
const res = await ctx.proxiedFetcher(apiPath, {
|
||||||
baseUrl,
|
baseUrl,
|
||||||
headers,
|
headers,
|
||||||
query: {
|
query: apiQuery,
|
||||||
id: String(anilistId),
|
|
||||||
ep: String(episode ?? 1),
|
|
||||||
host: serverName,
|
|
||||||
type: 'dub',
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log(res);
|
console.log('API Response:', res);
|
||||||
|
|
||||||
const resAny: any = res as any;
|
const resAny: any = res as any;
|
||||||
|
|
||||||
if (!resAny?.success || !resAny?.sources?.url) {
|
// Check for different possible response structures
|
||||||
|
let streamUrl: string | null = null;
|
||||||
|
let streamHeaders: Record<string, string> = headers;
|
||||||
|
|
||||||
|
if (resAny?.success && resAny?.sources?.url) {
|
||||||
|
// Standard response structure
|
||||||
|
streamUrl = resAny.sources.url;
|
||||||
|
streamHeaders = resAny?.sources?.headers || headers;
|
||||||
|
} else if (resAny?.url) {
|
||||||
|
// Direct URL response
|
||||||
|
streamUrl = resAny.url;
|
||||||
|
} else if (resAny?.stream?.url) {
|
||||||
|
// Alternative response structure
|
||||||
|
streamUrl = resAny.stream.url;
|
||||||
|
streamHeaders = resAny?.stream?.headers || headers;
|
||||||
|
} else if (typeof resAny === 'string' && resAny.startsWith('http')) {
|
||||||
|
// Direct string URL response
|
||||||
|
streamUrl = resAny;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!streamUrl) {
|
||||||
throw new NotFoundError('No stream URL found in response');
|
throw new NotFoundError('No stream URL found in response');
|
||||||
}
|
}
|
||||||
|
|
||||||
const streamUrl = resAny.sources.url;
|
// If the URL is already proxied through vidnest.fun, use it directly
|
||||||
const upstreamHeaders: Record<string, string> =
|
// Otherwise, wrap it with the old proxy
|
||||||
resAny?.sources?.headers && Object.keys(resAny.sources.headers).length > 0 ? resAny.sources.headers : headers;
|
let finalStreamUrl = streamUrl;
|
||||||
|
if (!streamUrl.includes('proxy.vidnest.fun') && !streamUrl.includes('proxy-2.madaraverse.online')) {
|
||||||
|
finalStreamUrl = `https://proxy-2.madaraverse.online/proxy?url=${encodeURIComponent(streamUrl)}`;
|
||||||
|
}
|
||||||
|
|
||||||
ctx.progress(100);
|
ctx.progress(100);
|
||||||
|
|
||||||
|
|
@ -54,8 +115,8 @@ export function makeZunimeEmbed(id: string, rank: number = 100) {
|
||||||
{
|
{
|
||||||
id: 'primary',
|
id: 'primary',
|
||||||
type: 'hls',
|
type: 'hls',
|
||||||
playlist: `https://proxy-2.madaraverse.online/proxy?url=${encodeURIComponent(streamUrl)}`,
|
playlist: finalStreamUrl,
|
||||||
headers: upstreamHeaders,
|
headers: streamHeaders,
|
||||||
flags: [],
|
flags: [],
|
||||||
captions: [],
|
captions: [],
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,54 +1,88 @@
|
||||||
import { SourcererOutput, makeSourcerer } from '@/providers/base';
|
import { SourcererOutput, makeSourcerer } from '@/providers/base';
|
||||||
import { getAnilistIdFromMedia } from '@/utils/anilist';
|
|
||||||
import { MovieScrapeContext, ShowScrapeContext } from '@/utils/context';
|
import { MovieScrapeContext, ShowScrapeContext } from '@/utils/context';
|
||||||
|
import { NotFoundError } from '@/utils/errors';
|
||||||
|
|
||||||
async function comboScraper(ctx: ShowScrapeContext | MovieScrapeContext): Promise<SourcererOutput> {
|
async function comboScraper(ctx: ShowScrapeContext | MovieScrapeContext): Promise<SourcererOutput> {
|
||||||
const anilistId = await getAnilistIdFromMedia(ctx, ctx.media);
|
// First, search animetsu for the content using title
|
||||||
|
const searchQuery = ctx.media.title;
|
||||||
|
try {
|
||||||
|
// Search animetsu's own database
|
||||||
|
const searchRes = await ctx.proxiedFetcher('/api/search', {
|
||||||
|
baseUrl: 'https://backend.animetsu.to',
|
||||||
|
headers: {
|
||||||
|
referer: 'https://animetsu.cc/',
|
||||||
|
origin: 'https://animetsu.cc',
|
||||||
|
'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',
|
||||||
|
},
|
||||||
|
query: {
|
||||||
|
q: searchQuery,
|
||||||
|
limit: '10',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const query: any = {
|
// eslint-disable-next-line no-console
|
||||||
type: ctx.media.type,
|
console.log('Animetsu Search Response:', JSON.stringify(searchRes, null, 2));
|
||||||
title: ctx.media.title,
|
|
||||||
tmdbId: ctx.media.tmdbId,
|
|
||||||
imdbId: ctx.media.imdbId,
|
|
||||||
anilistId,
|
|
||||||
...(ctx.media.type === 'show' && {
|
|
||||||
season: ctx.media.season.number,
|
|
||||||
episode: ctx.media.episode.number,
|
|
||||||
}),
|
|
||||||
...(ctx.media.type === 'movie' && { episode: 1 }),
|
|
||||||
releaseYear: ctx.media.releaseYear,
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
// Find the best match from search results
|
||||||
embeds: [
|
const results = searchRes?.results || searchRes?.data || [];
|
||||||
{
|
if (!results || results.length === 0) {
|
||||||
embedId: 'animetsu-pahe',
|
throw new NotFoundError('No results found in animetsu search');
|
||||||
url: JSON.stringify(query),
|
}
|
||||||
},
|
|
||||||
{
|
// Get the first result (you could improve matching logic here)
|
||||||
embedId: 'animetsu-zoro',
|
const firstResult = results[0];
|
||||||
url: JSON.stringify(query),
|
const animetsuId = firstResult?.id || firstResult?.animetsu_id;
|
||||||
},
|
if (!animetsuId) {
|
||||||
{
|
throw new NotFoundError('No animetsu ID found in search results');
|
||||||
embedId: 'animetsu-zaza',
|
}
|
||||||
url: JSON.stringify(query),
|
|
||||||
},
|
const query: any = {
|
||||||
{
|
type: ctx.media.type,
|
||||||
embedId: 'animetsu-meg',
|
title: ctx.media.title,
|
||||||
url: JSON.stringify(query),
|
animetsuId, // Use animetsu's internal ID instead of anilist
|
||||||
},
|
...(ctx.media.type === 'show' && {
|
||||||
{
|
season: ctx.media.season.number,
|
||||||
embedId: 'animetsu-bato',
|
episode: ctx.media.episode.number,
|
||||||
url: JSON.stringify(query),
|
}),
|
||||||
},
|
...(ctx.media.type === 'movie' && { episode: 1 }),
|
||||||
],
|
releaseYear: ctx.media.releaseYear,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
embeds: [
|
||||||
|
{
|
||||||
|
embedId: 'animetsu-pahe',
|
||||||
|
url: JSON.stringify(query),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
embedId: 'animetsu-zoro',
|
||||||
|
url: JSON.stringify(query),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
embedId: 'animetsu-zaza',
|
||||||
|
url: JSON.stringify(query),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
embedId: 'animetsu-meg',
|
||||||
|
url: JSON.stringify(query),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
embedId: 'animetsu-bato',
|
||||||
|
url: JSON.stringify(query),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
throw new NotFoundError(`Animetsu search failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const animetsuScraper = makeSourcerer({
|
export const animetsuScraper = makeSourcerer({
|
||||||
id: 'animetsu',
|
id: 'animetsu',
|
||||||
name: 'Animetsu',
|
name: 'Animetsu',
|
||||||
rank: 112,
|
rank: 112,
|
||||||
|
disabled: true,
|
||||||
flags: [],
|
flags: [],
|
||||||
scrapeShow: comboScraper,
|
scrapeShow: comboScraper,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -91,6 +91,7 @@ export const fsharetvScraper = makeSourcerer({
|
||||||
id: 'fsharetv',
|
id: 'fsharetv',
|
||||||
name: 'FshareTV',
|
name: 'FshareTV',
|
||||||
rank: 190,
|
rank: 190,
|
||||||
|
disabled: true,
|
||||||
flags: [],
|
flags: [],
|
||||||
scrapeMovie: comboScraper,
|
scrapeMovie: comboScraper,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { load } from 'cheerio';
|
import { load } from 'cheerio';
|
||||||
|
|
||||||
|
import { flags } from '@/entrypoint/utils/targets';
|
||||||
import { SourcererEmbed, makeSourcerer } from '@/providers/base';
|
import { SourcererEmbed, makeSourcerer } from '@/providers/base';
|
||||||
import { closeLoadScraper } from '@/providers/embeds/closeload';
|
import { closeLoadScraper } from '@/providers/embeds/closeload';
|
||||||
import { ridooScraper } from '@/providers/embeds/ridoo';
|
import { ridooScraper } from '@/providers/embeds/ridoo';
|
||||||
|
|
@ -78,8 +79,8 @@ export const ridooMoviesScraper = makeSourcerer({
|
||||||
id: 'ridomovies',
|
id: 'ridomovies',
|
||||||
name: 'RidoMovies',
|
name: 'RidoMovies',
|
||||||
rank: 210,
|
rank: 210,
|
||||||
flags: [],
|
flags: [flags.CF_BLOCKED],
|
||||||
disabled: false,
|
disabled: true, // Disabled: closeload embed scraper is broken
|
||||||
scrapeMovie: universalScraper,
|
scrapeMovie: universalScraper,
|
||||||
scrapeShow: universalScraper,
|
scrapeShow: universalScraper,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ export const tugaflixScraper = makeSourcerer({
|
||||||
id: 'tugaflix',
|
id: 'tugaflix',
|
||||||
name: 'Tugaflix',
|
name: 'Tugaflix',
|
||||||
rank: 70,
|
rank: 70,
|
||||||
|
disabled: true,
|
||||||
flags: [flags.IP_LOCKED],
|
flags: [flags.IP_LOCKED],
|
||||||
scrapeMovie: async (ctx) => {
|
scrapeMovie: async (ctx) => {
|
||||||
const searchResults = parseSearch(
|
const searchResults = parseSearch(
|
||||||
|
|
@ -27,39 +28,62 @@ export const tugaflixScraper = makeSourcerer({
|
||||||
if (!url) throw new NotFoundError('No watchable item found');
|
if (!url) throw new NotFoundError('No watchable item found');
|
||||||
ctx.progress(50);
|
ctx.progress(50);
|
||||||
|
|
||||||
const videoPage = await ctx.proxiedFetcher<string>(url, {
|
// Get the movie page
|
||||||
method: 'POST',
|
const moviePage = await ctx.proxiedFetcher<string>(url, {
|
||||||
body: new URLSearchParams({ play: '' }),
|
baseUrl,
|
||||||
});
|
});
|
||||||
const $ = load(videoPage);
|
const $ = load(moviePage);
|
||||||
|
|
||||||
const embeds: SourcererEmbed[] = [];
|
const embeds: SourcererEmbed[] = [];
|
||||||
|
|
||||||
for (const element of $('.play a')) {
|
// Look for mixdrop embed links
|
||||||
const embedUrl = $(element).attr('href');
|
// Check for player buttons or iframe sources
|
||||||
|
const playerElements = $('iframe[src*="mixdrop"], a[href*="mixdrop"], button[data-url*="mixdrop"]');
|
||||||
|
|
||||||
|
for (const element of playerElements) {
|
||||||
|
const embedUrl = $(element).attr('src') || $(element).attr('href') || $(element).attr('data-url');
|
||||||
if (!embedUrl) continue;
|
if (!embedUrl) continue;
|
||||||
|
|
||||||
const embedPage = await ctx.proxiedFetcher.full(
|
if (embedUrl.includes('mixdrop')) {
|
||||||
embedUrl.startsWith('https://') ? embedUrl : `https://${embedUrl}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
const finalUrl = load(embedPage.body)('a:contains("Download Filme")').attr('href');
|
|
||||||
if (!finalUrl) continue;
|
|
||||||
|
|
||||||
if (finalUrl.includes('streamtape')) {
|
|
||||||
embeds.push({
|
embeds.push({
|
||||||
embedId: 'streamtape',
|
embedId: 'mixdrop',
|
||||||
url: finalUrl,
|
url: embedUrl.startsWith('http') ? embedUrl : `https:${embedUrl}`,
|
||||||
});
|
|
||||||
// found doodstream on a few shows, maybe movies use it too?
|
|
||||||
// the player 2 is just streamtape in a custom player
|
|
||||||
} else if (finalUrl.includes('dood')) {
|
|
||||||
embeds.push({
|
|
||||||
embedId: 'dood',
|
|
||||||
url: finalUrl,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If no direct mixdrop links found, look for watch buttons that might lead to mixdrop
|
||||||
|
if (embeds.length === 0) {
|
||||||
|
const watchButtons = $('a:contains("Watch"), a:contains("Assistir"), .watch-btn, .play-btn');
|
||||||
|
|
||||||
|
for (const button of watchButtons) {
|
||||||
|
const buttonUrl = $(button).attr('href');
|
||||||
|
if (!buttonUrl) continue;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const buttonPage = await ctx.proxiedFetcher<string>(buttonUrl, {
|
||||||
|
baseUrl,
|
||||||
|
});
|
||||||
|
const $button = load(buttonPage);
|
||||||
|
|
||||||
|
// Look for mixdrop links in the button page
|
||||||
|
const mixdropLinks = $button('iframe[src*="mixdrop"], a[href*="mixdrop"]');
|
||||||
|
for (const link of mixdropLinks) {
|
||||||
|
const embedUrl = $button(link).attr('src') || $button(link).attr('href');
|
||||||
|
if (embedUrl && embedUrl.includes('mixdrop')) {
|
||||||
|
embeds.push({
|
||||||
|
embedId: 'mixdrop',
|
||||||
|
url: embedUrl.startsWith('http') ? embedUrl : `https:${embedUrl}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// Continue to next button if this one fails
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ctx.progress(90);
|
ctx.progress(90);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
@ -81,36 +105,91 @@ export const tugaflixScraper = makeSourcerer({
|
||||||
if (!url) throw new NotFoundError('No watchable item found');
|
if (!url) throw new NotFoundError('No watchable item found');
|
||||||
ctx.progress(50);
|
ctx.progress(50);
|
||||||
|
|
||||||
const s = ctx.media.season.number < 10 ? `0${ctx.media.season.number}` : ctx.media.season.number.toString();
|
// Get the show page
|
||||||
const e = ctx.media.episode.number < 10 ? `0${ctx.media.episode.number}` : ctx.media.episode.number.toString();
|
const showPage = await ctx.proxiedFetcher<string>(url, {
|
||||||
const videoPage = await ctx.proxiedFetcher(url, {
|
baseUrl,
|
||||||
method: 'POST',
|
|
||||||
body: new URLSearchParams({ [`S${s}E${e}`]: '' }),
|
|
||||||
});
|
|
||||||
|
|
||||||
const embedUrl = load(videoPage)('iframe[name="player"]').attr('src');
|
|
||||||
if (!embedUrl) throw new Error('Failed to find iframe');
|
|
||||||
|
|
||||||
const playerPage = await ctx.proxiedFetcher(embedUrl.startsWith('https:') ? embedUrl : `https:${embedUrl}`, {
|
|
||||||
method: 'POST',
|
|
||||||
body: new URLSearchParams({ submit: '' }),
|
|
||||||
});
|
});
|
||||||
|
const $ = load(showPage);
|
||||||
|
|
||||||
const embeds: SourcererEmbed[] = [];
|
const embeds: SourcererEmbed[] = [];
|
||||||
|
|
||||||
const finalUrl = load(playerPage)('a:contains("Download Episodio")').attr('href');
|
// Look for episode selection or season/episode links
|
||||||
|
const s = ctx.media.season.number < 10 ? `0${ctx.media.season.number}` : ctx.media.season.number.toString();
|
||||||
|
const e = ctx.media.episode.number < 10 ? `0${ctx.media.episode.number}` : ctx.media.episode.number.toString();
|
||||||
|
|
||||||
if (finalUrl?.includes('streamtape')) {
|
// Try to find episode link or submit episode form
|
||||||
embeds.push({
|
const episodeLink = $(
|
||||||
embedId: 'streamtape',
|
`a:contains("S${s}E${e}"), a:contains("${ctx.media.season.number}x${ctx.media.episode.number}")`,
|
||||||
url: finalUrl,
|
).attr('href');
|
||||||
});
|
|
||||||
} else if (finalUrl?.includes('dood')) {
|
let episodePage = showPage;
|
||||||
embeds.push({
|
if (episodeLink) {
|
||||||
embedId: 'dood',
|
episodePage = await ctx.proxiedFetcher<string>(episodeLink, {
|
||||||
url: finalUrl,
|
baseUrl,
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
// Try POST method with episode data
|
||||||
|
try {
|
||||||
|
episodePage = await ctx.proxiedFetcher<string>(url, {
|
||||||
|
method: 'POST',
|
||||||
|
body: new URLSearchParams({ [`S${s}E${e}`]: '' }),
|
||||||
|
baseUrl,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
// If POST fails, continue with the original page
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const $episode = load(episodePage);
|
||||||
|
|
||||||
|
// Look for mixdrop embed links
|
||||||
|
const playerElements = $episode('iframe[src*="mixdrop"], a[href*="mixdrop"], button[data-url*="mixdrop"]');
|
||||||
|
|
||||||
|
for (const element of playerElements) {
|
||||||
|
const embedUrl =
|
||||||
|
$episode(element).attr('src') || $episode(element).attr('href') || $episode(element).attr('data-url');
|
||||||
|
if (!embedUrl) continue;
|
||||||
|
|
||||||
|
if (embedUrl.includes('mixdrop')) {
|
||||||
|
embeds.push({
|
||||||
|
embedId: 'mixdrop',
|
||||||
|
url: embedUrl.startsWith('http') ? embedUrl : `https:${embedUrl}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no direct mixdrop links found, look for player iframes or watch buttons
|
||||||
|
if (embeds.length === 0) {
|
||||||
|
const iframes = $episode('iframe[name="player"], iframe[src]');
|
||||||
|
|
||||||
|
for (const iframe of iframes) {
|
||||||
|
const iframeUrl = $episode(iframe).attr('src');
|
||||||
|
if (!iframeUrl) continue;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const iframePage = await ctx.proxiedFetcher<string>(
|
||||||
|
iframeUrl.startsWith('http') ? iframeUrl : `https:${iframeUrl}`,
|
||||||
|
);
|
||||||
|
const $iframe = load(iframePage);
|
||||||
|
|
||||||
|
// Look for mixdrop links in the iframe page
|
||||||
|
const mixdropLinks = $iframe('iframe[src*="mixdrop"], a[href*="mixdrop"]');
|
||||||
|
for (const link of mixdropLinks) {
|
||||||
|
const embedUrl = $iframe(link).attr('src') || $iframe(link).attr('href');
|
||||||
|
if (embedUrl && embedUrl.includes('mixdrop')) {
|
||||||
|
embeds.push({
|
||||||
|
embedId: 'mixdrop',
|
||||||
|
url: embedUrl.startsWith('http') ? embedUrl : `https:${embedUrl}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// Continue to next iframe if this one fails
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ctx.progress(90);
|
ctx.progress(90);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -1,102 +1,175 @@
|
||||||
import { load } from 'cheerio';
|
import { load } from 'cheerio';
|
||||||
|
|
||||||
import { SourcererOutput, makeSourcerer } from '@/providers/base';
|
import { flags } from '@/entrypoint/utils/targets';
|
||||||
import { MovieScrapeContext, ShowScrapeContext } from '@/utils/context';
|
import { SourcererEmbed, makeSourcerer } from '@/providers/base';
|
||||||
|
import { compareMedia } from '@/utils/compare';
|
||||||
import { NotFoundError } from '@/utils/errors';
|
import { NotFoundError } from '@/utils/errors';
|
||||||
|
|
||||||
const baseUrl = 'https://wecima.tube';
|
const baseUrl = 'https://mycima.pics/';
|
||||||
|
|
||||||
async function comboScraper(ctx: ShowScrapeContext | MovieScrapeContext): Promise<SourcererOutput> {
|
|
||||||
const searchPage = await ctx.proxiedFetcher(`/search/${encodeURIComponent(ctx.media.title)}/`, {
|
|
||||||
baseUrl,
|
|
||||||
});
|
|
||||||
|
|
||||||
const search$ = load(searchPage);
|
|
||||||
const firstResult = search$('.Grid--WecimaPosts .GridItem a').first();
|
|
||||||
if (!firstResult.length) throw new NotFoundError('No results found');
|
|
||||||
|
|
||||||
const contentUrl = firstResult.attr('href');
|
|
||||||
if (!contentUrl) throw new NotFoundError('No content URL found');
|
|
||||||
ctx.progress(30);
|
|
||||||
|
|
||||||
const contentPage = await ctx.proxiedFetcher(contentUrl, { baseUrl });
|
|
||||||
const content$ = load(contentPage);
|
|
||||||
|
|
||||||
let embedUrl: string | undefined;
|
|
||||||
|
|
||||||
if (ctx.media.type === 'movie') {
|
|
||||||
embedUrl = content$('meta[itemprop="embedURL"]').attr('content');
|
|
||||||
} else {
|
|
||||||
const seasonLinks = content$('.List--Seasons--Episodes a');
|
|
||||||
let seasonUrl: string | undefined;
|
|
||||||
|
|
||||||
for (const element of seasonLinks) {
|
|
||||||
const text = content$(element).text().trim();
|
|
||||||
if (text.includes(`موسم ${ctx.media.season}`)) {
|
|
||||||
seasonUrl = content$(element).attr('href');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!seasonUrl) throw new NotFoundError(`Season ${ctx.media.season} not found`);
|
|
||||||
|
|
||||||
const seasonPage = await ctx.proxiedFetcher(seasonUrl, { baseUrl });
|
|
||||||
const season$ = load(seasonPage);
|
|
||||||
|
|
||||||
const episodeLinks = season$('.Episodes--Seasons--Episodes a');
|
|
||||||
for (const element of episodeLinks) {
|
|
||||||
const epTitle = season$(element).find('episodetitle').text().trim();
|
|
||||||
if (epTitle === `الحلقة ${ctx.media.episode}`) {
|
|
||||||
const episodeUrl = season$(element).attr('href');
|
|
||||||
if (episodeUrl) {
|
|
||||||
const episodePage = await ctx.proxiedFetcher(episodeUrl, { baseUrl });
|
|
||||||
const episode$ = load(episodePage);
|
|
||||||
embedUrl = episode$('meta[itemprop="embedURL"]').attr('content');
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!embedUrl) throw new NotFoundError('No embed URL found');
|
|
||||||
ctx.progress(60);
|
|
||||||
|
|
||||||
// Get the final video URL
|
|
||||||
const embedPage = await ctx.proxiedFetcher(embedUrl);
|
|
||||||
const embed$ = load(embedPage);
|
|
||||||
const videoSource = embed$('source[type="video/mp4"]').attr('src');
|
|
||||||
|
|
||||||
if (!videoSource) throw new NotFoundError('No video source found');
|
|
||||||
ctx.progress(90);
|
|
||||||
|
|
||||||
return {
|
|
||||||
embeds: [],
|
|
||||||
stream: [
|
|
||||||
{
|
|
||||||
id: 'primary',
|
|
||||||
type: 'file',
|
|
||||||
flags: [],
|
|
||||||
headers: {
|
|
||||||
referer: baseUrl,
|
|
||||||
},
|
|
||||||
qualities: {
|
|
||||||
unknown: {
|
|
||||||
type: 'mp4',
|
|
||||||
url: videoSource,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
captions: [],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export const wecimaScraper = makeSourcerer({
|
export const wecimaScraper = makeSourcerer({
|
||||||
id: 'wecima',
|
id: 'wecima',
|
||||||
name: 'Wecima (Arabic)',
|
name: 'Wecima (Arabic)',
|
||||||
rank: 3,
|
rank: 55,
|
||||||
disabled: false,
|
disabled: true,
|
||||||
flags: [],
|
flags: [flags.IP_LOCKED],
|
||||||
scrapeMovie: comboScraper,
|
scrapeMovie: async (ctx) => {
|
||||||
scrapeShow: comboScraper,
|
// Search for the movie
|
||||||
|
const searchResults = await ctx.proxiedFetcher<string>(`/search/${encodeURIComponent(ctx.media.title)}/`, {
|
||||||
|
baseUrl,
|
||||||
|
});
|
||||||
|
|
||||||
|
const search$ = load(searchResults);
|
||||||
|
const movieLinks = search$('.Grid--WecimaPosts .GridItem a');
|
||||||
|
|
||||||
|
let movieUrl: string | undefined;
|
||||||
|
for (const element of movieLinks) {
|
||||||
|
const title = search$(element).find('.title').text().trim();
|
||||||
|
const year = search$(element).find('.year').text().trim();
|
||||||
|
|
||||||
|
if (compareMedia(ctx.media, title, year ? parseInt(year, 10) : undefined)) {
|
||||||
|
movieUrl = search$(element).attr('href');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!movieUrl) throw new NotFoundError('Movie not found');
|
||||||
|
ctx.progress(40);
|
||||||
|
|
||||||
|
// Get movie page
|
||||||
|
const moviePage = await ctx.proxiedFetcher<string>(movieUrl, { baseUrl });
|
||||||
|
const movie$ = load(moviePage);
|
||||||
|
|
||||||
|
const embeds: SourcererEmbed[] = [];
|
||||||
|
|
||||||
|
// Look for server buttons or embed links
|
||||||
|
const serverButtons = movie$('.servers-list a, .server-item a, button[data-server]');
|
||||||
|
|
||||||
|
for (const button of serverButtons) {
|
||||||
|
const embedUrl =
|
||||||
|
movie$(button).attr('href') || movie$(button).attr('data-url') || movie$(button).attr('data-server');
|
||||||
|
if (!embedUrl) continue;
|
||||||
|
|
||||||
|
// Check if it's an fdewsdc.sbs embed (the host you found)
|
||||||
|
if (embedUrl.includes('fdewsdc.sbs') || embedUrl.includes('/embed/')) {
|
||||||
|
embeds.push({
|
||||||
|
embedId: 'wecima-embed',
|
||||||
|
url: embedUrl.startsWith('http') ? embedUrl : `https:${embedUrl}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also check for any embed iframes
|
||||||
|
const iframes = movie$('iframe[src*="/embed/"], iframe[src*="fdewsdc"], iframe[src*="player"]');
|
||||||
|
for (const iframe of iframes) {
|
||||||
|
const iframeSrc = movie$(iframe).attr('src');
|
||||||
|
if (iframeSrc) {
|
||||||
|
embeds.push({
|
||||||
|
embedId: 'wecima-embed',
|
||||||
|
url: iframeSrc.startsWith('http') ? iframeSrc : `https:${iframeSrc}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.progress(90);
|
||||||
|
return { embeds };
|
||||||
|
},
|
||||||
|
|
||||||
|
scrapeShow: async (ctx) => {
|
||||||
|
// Search for the show
|
||||||
|
const searchResults = await ctx.proxiedFetcher<string>(`/search/${encodeURIComponent(ctx.media.title)}/`, {
|
||||||
|
baseUrl,
|
||||||
|
});
|
||||||
|
|
||||||
|
const search$ = load(searchResults);
|
||||||
|
const showLinks = search$('.Grid--WecimaPosts .GridItem a');
|
||||||
|
|
||||||
|
let showUrl: string | undefined;
|
||||||
|
for (const element of showLinks) {
|
||||||
|
const title = search$(element).find('.title').text().trim();
|
||||||
|
const year = search$(element).find('.year').text().trim();
|
||||||
|
|
||||||
|
if (compareMedia(ctx.media, title, year ? parseInt(year, 10) : undefined)) {
|
||||||
|
showUrl = search$(element).attr('href');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!showUrl) throw new NotFoundError('Show not found');
|
||||||
|
ctx.progress(30);
|
||||||
|
|
||||||
|
// Get show page and find season
|
||||||
|
const showPage = await ctx.proxiedFetcher<string>(showUrl, { baseUrl });
|
||||||
|
const show$ = load(showPage);
|
||||||
|
|
||||||
|
// Look for season links
|
||||||
|
const seasonLinks = show$('.List--Seasons--Episodes a, .season-link');
|
||||||
|
let seasonUrl: string | undefined;
|
||||||
|
|
||||||
|
for (const element of seasonLinks) {
|
||||||
|
const text = show$(element).text().trim();
|
||||||
|
if (text.includes(`موسم ${ctx.media.season.number}`) || text.includes(`Season ${ctx.media.season.number}`)) {
|
||||||
|
seasonUrl = show$(element).attr('href');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!seasonUrl) throw new NotFoundError(`Season ${ctx.media.season.number} not found`);
|
||||||
|
ctx.progress(50);
|
||||||
|
|
||||||
|
// Get season page and find episode
|
||||||
|
const seasonPage = await ctx.proxiedFetcher<string>(seasonUrl, { baseUrl });
|
||||||
|
const season$ = load(seasonPage);
|
||||||
|
|
||||||
|
const episodeLinks = season$('.Episodes--Seasons--Episodes a, .episode-link');
|
||||||
|
let episodeUrl: string | undefined;
|
||||||
|
|
||||||
|
for (const element of episodeLinks) {
|
||||||
|
const text = season$(element).text().trim();
|
||||||
|
if (text.includes(`الحلقة ${ctx.media.episode.number}`) || text.includes(`Episode ${ctx.media.episode.number}`)) {
|
||||||
|
episodeUrl = season$(element).attr('href');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!episodeUrl) throw new NotFoundError(`Episode ${ctx.media.episode.number} not found`);
|
||||||
|
ctx.progress(70);
|
||||||
|
|
||||||
|
// Get episode page
|
||||||
|
const episodePage = await ctx.proxiedFetcher<string>(episodeUrl, { baseUrl });
|
||||||
|
const episode$ = load(episodePage);
|
||||||
|
|
||||||
|
const embeds: SourcererEmbed[] = [];
|
||||||
|
|
||||||
|
// Look for server buttons or embed links
|
||||||
|
const serverButtons = episode$('.servers-list a, .server-item a, button[data-server]');
|
||||||
|
|
||||||
|
for (const button of serverButtons) {
|
||||||
|
const embedUrl =
|
||||||
|
episode$(button).attr('href') || episode$(button).attr('data-url') || episode$(button).attr('data-server');
|
||||||
|
if (!embedUrl) continue;
|
||||||
|
|
||||||
|
if (embedUrl.includes('fdewsdc.sbs') || embedUrl.includes('/embed/')) {
|
||||||
|
embeds.push({
|
||||||
|
embedId: 'wecima-embed',
|
||||||
|
url: embedUrl.startsWith('http') ? embedUrl : `https:${embedUrl}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also check for any embed iframes
|
||||||
|
const iframes = episode$('iframe[src*="/embed/"], iframe[src*="fdewsdc"], iframe[src*="player"]');
|
||||||
|
for (const iframe of iframes) {
|
||||||
|
const iframeSrc = episode$(iframe).attr('src');
|
||||||
|
if (iframeSrc) {
|
||||||
|
embeds.push({
|
||||||
|
embedId: 'wecima-embed',
|
||||||
|
url: iframeSrc.startsWith('http') ? iframeSrc : `https:${iframeSrc}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.progress(90);
|
||||||
|
return { embeds };
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -3,41 +3,43 @@ import { getAnilistIdFromMedia } from '@/utils/anilist';
|
||||||
import { MovieScrapeContext, ShowScrapeContext } from '@/utils/context';
|
import { MovieScrapeContext, ShowScrapeContext } from '@/utils/context';
|
||||||
|
|
||||||
async function comboScraper(ctx: ShowScrapeContext | MovieScrapeContext): Promise<SourcererOutput> {
|
async function comboScraper(ctx: ShowScrapeContext | MovieScrapeContext): Promise<SourcererOutput> {
|
||||||
const anilistId = await getAnilistIdFromMedia(ctx, ctx.media);
|
let embedUrls: { embedId: string; url: string }[] = [];
|
||||||
|
|
||||||
const query: any = {
|
// Generate proper embed URLs based on content type
|
||||||
type: ctx.media.type,
|
if (ctx.media.type === 'movie') {
|
||||||
title: ctx.media.title,
|
// For movies, use TMDB-based embed URLs
|
||||||
tmdbId: ctx.media.tmdbId,
|
const baseEmbedUrl = `https://vidnest.fun/movie/${ctx.media.tmdbId}`;
|
||||||
imdbId: ctx.media.imdbId,
|
embedUrls = [
|
||||||
anilistId,
|
{ embedId: 'zunime-hd-2', url: baseEmbedUrl },
|
||||||
...(ctx.media.type === 'show' && {
|
{ embedId: 'zunime-miko', url: baseEmbedUrl },
|
||||||
season: ctx.media.season.number,
|
{ embedId: 'zunime-shiro', url: baseEmbedUrl },
|
||||||
episode: ctx.media.episode.number,
|
{ embedId: 'zunime-zaza', url: baseEmbedUrl },
|
||||||
}),
|
];
|
||||||
...(ctx.media.type === 'movie' && { episode: 1 }),
|
} else if (ctx.media.type === 'show') {
|
||||||
releaseYear: ctx.media.releaseYear,
|
try {
|
||||||
};
|
// Try to get Anilist ID for anime shows
|
||||||
|
const anilistId = await getAnilistIdFromMedia(ctx, ctx.media);
|
||||||
|
const baseEmbedUrl = `https://vidnest.fun/anime/${anilistId}/${ctx.media.episode.number}/dub`;
|
||||||
|
embedUrls = [
|
||||||
|
{ embedId: 'zunime-hd-2', url: baseEmbedUrl },
|
||||||
|
{ embedId: 'zunime-miko', url: baseEmbedUrl },
|
||||||
|
{ embedId: 'zunime-shiro', url: baseEmbedUrl },
|
||||||
|
{ embedId: 'zunime-zaza', url: baseEmbedUrl },
|
||||||
|
];
|
||||||
|
} catch {
|
||||||
|
// Fallback to TMDB for regular TV shows
|
||||||
|
const baseEmbedUrl = `https://vidnest.fun/tv/${ctx.media.tmdbId}/${ctx.media.season.number}/${ctx.media.episode.number}`;
|
||||||
|
embedUrls = [
|
||||||
|
{ embedId: 'zunime-hd-2', url: baseEmbedUrl },
|
||||||
|
{ embedId: 'zunime-miko', url: baseEmbedUrl },
|
||||||
|
{ embedId: 'zunime-shiro', url: baseEmbedUrl },
|
||||||
|
{ embedId: 'zunime-zaza', url: baseEmbedUrl },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
embeds: [
|
embeds: embedUrls,
|
||||||
{
|
|
||||||
embedId: 'zunime-hd-2',
|
|
||||||
url: JSON.stringify(query),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
embedId: 'zunime-miko',
|
|
||||||
url: JSON.stringify(query),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
embedId: 'zunime-shiro',
|
|
||||||
url: JSON.stringify(query),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
embedId: 'zunime-zaza',
|
|
||||||
url: JSON.stringify(query),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -45,6 +47,8 @@ export const zunimeScraper = makeSourcerer({
|
||||||
id: 'zunime',
|
id: 'zunime',
|
||||||
name: 'Zunime',
|
name: 'Zunime',
|
||||||
rank: 125,
|
rank: 125,
|
||||||
|
disabled: true, // Disabled due to API authentication issues
|
||||||
flags: [],
|
flags: [],
|
||||||
|
scrapeMovie: comboScraper,
|
||||||
scrapeShow: comboScraper,
|
scrapeShow: comboScraper,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue