update zoechip

This commit is contained in:
Pas 2025-07-10 10:44:41 -06:00
parent a0e45a46bb
commit 2181ab3eed
11 changed files with 243 additions and 359 deletions

BIN
src/providers/.DS_Store vendored Normal file

Binary file not shown.

View file

@ -12,6 +12,7 @@ import { mp4hydraScraper } from '@/providers/sources/mp4hydra';
import { tugaflixScraper } from '@/providers/sources/tugaflix';
import { vidsrcScraper } from '@/providers/sources/vidsrc';
import { vidsrcsuScraper } from '@/providers/sources/vidsrcsu';
import { zoechipScraper } from '@/providers/sources/zoechip';
import {
autoembedBengaliScraper,
@ -86,6 +87,7 @@ export function gatherAllSources(): Array<Sourcerer> {
fsharetvScraper,
vidsrcsuScraper,
vidsrcScraper,
zoechipScraper,
mp4hydraScraper,
embedsuScraper,
slidemoviesScraper,

Binary file not shown.

View file

@ -1,71 +0,0 @@
// import { mixdropScraper } from '@/providers/embeds/mixdrop';
// import { upcloudScraper } from '@/providers/embeds/upcloud';
// import { upstreamScraper } from '@/providers/embeds/upstream';
// import { vidCloudScraper } from '@/providers/embeds/vidcloud';
// import { getZoeChipSourceURL, getZoeChipSources } from '@/providers/sources/zoechip/scrape';
// import { MovieScrapeContext, ShowScrapeContext } from '@/utils/context';
// export const zoeBase = 'https://zoechip.cc';
// export type ZoeChipSourceDetails = {
// type: string; // Only seen "iframe" so far
// link: string;
// sources: string[]; // Never seen this populated, assuming it's a string array
// tracks: string[]; // Never seen this populated, assuming it's a string array
// title: string;
// };
// export async function formatSource(
// ctx: MovieScrapeContext | ShowScrapeContext,
// source: { embed: string; episodeId: string },
// ) {
// const link = await getZoeChipSourceURL(ctx, source.episodeId);
// if (link) {
// const embed = {
// embedId: '',
// url: link,
// };
// const parsedUrl = new URL(link);
// switch (parsedUrl.host) {
// case 'rabbitstream.net':
// embed.embedId = upcloudScraper.id;
// break;
// case 'upstream.to':
// embed.embedId = upstreamScraper.id;
// break;
// case 'mixdrop.co':
// embed.embedId = mixdropScraper.id;
// break;
// default:
// return null;
// }
// return embed;
// }
// }
// export async function createZoeChipStreamData(ctx: MovieScrapeContext | ShowScrapeContext, id: string) {
// const sources = await getZoeChipSources(ctx, id);
// const embeds: {
// embedId: string;
// url: string;
// }[] = [];
// for (const source of sources) {
// const formatted = await formatSource(ctx, source);
// if (formatted) {
// // Zoechip does not return titles for their sources, so we can not check if a source is upcloud or vidcloud because the domain is the same.
// const upCloudAlreadyExists = embeds.find((e) => e.embedId === upcloudScraper.id);
// if (formatted.embedId === upcloudScraper.id && upCloudAlreadyExists) {
// formatted.embedId = vidCloudScraper.id;
// }
// embeds.push(formatted);
// }
// }
// return {
// embeds,
// };
// }

View file

@ -1,14 +0,0 @@
// import { flags } from '@/entrypoint/utils/targets';
// import { makeSourcerer } from '@/providers/base';
// import { scrapeMovie } from '@/providers/sources/zoechip/scrape-movie';
// import { scrapeShow } from '@/providers/sources/zoechip/scrape-show';
// export const zoechipScraper = makeSourcerer({
// id: 'zoechip',
// name: 'ZoeChip',
// rank: 240,
// flags: [flags.CORS_ALLOWED],
// disabled: true,
// scrapeMovie,
// scrapeShow,
// });

View file

@ -1,13 +0,0 @@
// import { createZoeChipStreamData } from '@/providers/sources/zoechip/common';
// import { getZoeChipMovieID } from '@/providers/sources/zoechip/search';
// import { MovieScrapeContext } from '@/utils/context';
// import { NotFoundError } from '@/utils/errors';
// export async function scrapeMovie(ctx: MovieScrapeContext) {
// const movieID = await getZoeChipMovieID(ctx, ctx.media);
// if (!movieID) {
// throw new NotFoundError('no search results match');
// }
// return createZoeChipStreamData(ctx, movieID);
// }

View file

@ -1,24 +0,0 @@
// import { createZoeChipStreamData } from '@/providers/sources/zoechip/common';
// import { getZoeChipEpisodeID, getZoeChipSeasonID } from '@/providers/sources/zoechip/scrape';
// import { getZoeChipShowID } from '@/providers/sources/zoechip/search';
// import { ShowScrapeContext } from '@/utils/context';
// import { NotFoundError } from '@/utils/errors';
// export async function scrapeShow(ctx: ShowScrapeContext) {
// const showID = await getZoeChipShowID(ctx, ctx.media);
// if (!showID) {
// throw new NotFoundError('no search results match');
// }
// const seasonID = await getZoeChipSeasonID(ctx, ctx.media, showID);
// if (!seasonID) {
// throw new NotFoundError('no season found');
// }
// const episodeID = await getZoeChipEpisodeID(ctx, ctx.media, seasonID);
// if (!episodeID) {
// throw new NotFoundError('no episode found');
// }
// return createZoeChipStreamData(ctx, episodeID);
// }

View file

@ -1,126 +0,0 @@
// import { load } from 'cheerio';
// import { ShowMedia } from '@/entrypoint/utils/media';
// import { ZoeChipSourceDetails, zoeBase } from '@/providers/sources/zoechip/common';
// import { MovieScrapeContext, ScrapeContext, ShowScrapeContext } from '@/utils/context';
// export async function getZoeChipSources(ctx: MovieScrapeContext | ShowScrapeContext, id: string) {
// // Movies use /ajax/episode/list/ID
// // Shows use /ajax/episode/servers/ID
// const endpoint = ctx.media.type === 'movie' ? 'list' : 'servers';
// const html = await ctx.proxiedFetcher<string>(`/ajax/episode/${endpoint}/${id}`, {
// baseUrl: zoeBase,
// });
// const $ = load(html);
// return $('.nav-item a')
// .toArray()
// .map((el) => {
// // Movies use data-linkid
// // Shows use data-id
// const idAttribute = ctx.media.type === 'movie' ? 'data-linkid' : 'data-id';
// const element = $(el);
// const embedTitle = element.attr('title');
// const linkId = element.attr(idAttribute);
// if (!embedTitle || !linkId) {
// throw new Error('invalid sources');
// }
// return {
// embed: embedTitle,
// episodeId: linkId,
// };
// });
// }
// export async function getZoeChipSourceURL(ctx: ScrapeContext, sourceID: string): Promise<string | null> {
// const details = await ctx.proxiedFetcher<ZoeChipSourceDetails>(`/ajax/sources/${sourceID}`, {
// baseUrl: zoeBase,
// });
// // TODO - Support non-iframe sources
// if (details.type !== 'iframe') {
// return null;
// }
// // TODO - Extract the other data from the source
// return details.link;
// }
// export async function getZoeChipSeasonID(ctx: ScrapeContext, media: ShowMedia, showID: string): Promise<string | null> {
// const html = await ctx.proxiedFetcher<string>(`/ajax/season/list/${showID}`, {
// baseUrl: zoeBase,
// });
// const $ = load(html);
// const seasons = $('.dropdown-menu a')
// .toArray()
// .map((el) => {
// const element = $(el);
// const seasonID = element.attr('data-id');
// const seasonNumber = element.html()?.split(' ')[1];
// if (!seasonID || !seasonNumber || Number.isNaN(Number(seasonNumber))) {
// throw new Error('invalid season');
// }
// return {
// id: seasonID,
// season: Number(seasonNumber),
// };
// });
// const foundSeason = seasons.find((season) => season.season === media.season.number);
// if (!foundSeason) {
// return null;
// }
// return foundSeason.id;
// }
// export async function getZoeChipEpisodeID(
// ctx: ScrapeContext,
// media: ShowMedia,
// seasonID: string,
// ): Promise<string | null> {
// const episodeNumberRegex = /Eps (\d*):/;
// const html = await ctx.proxiedFetcher<string>(`/ajax/season/episodes/${seasonID}`, {
// baseUrl: zoeBase,
// });
// const $ = load(html);
// const episodes = $('.eps-item')
// .toArray()
// .map((el) => {
// const element = $(el);
// const episodeID = element.attr('data-id');
// const title = element.attr('title');
// if (!episodeID || !title) {
// throw new Error('invalid episode');
// }
// const regexResult = title.match(episodeNumberRegex);
// if (!regexResult || Number.isNaN(Number(regexResult[1]))) {
// throw new Error('invalid episode');
// }
// return {
// id: episodeID,
// episode: Number(regexResult[1]),
// };
// });
// const foundEpisode = episodes.find((episode) => episode.episode === media.episode.number);
// if (!foundEpisode) {
// return null;
// }
// return foundEpisode.id;
// }

View file

@ -1,111 +0,0 @@
// import { load } from 'cheerio';
// import { MovieMedia, ShowMedia } from '@/entrypoint/utils/media';
// import { zoeBase } from '@/providers/sources/zoechip/common';
// import { compareMedia } from '@/utils/compare';
// import { ScrapeContext } from '@/utils/context';
// export async function getZoeChipSearchResults(ctx: ScrapeContext, media: MovieMedia | ShowMedia) {
// const titleCleaned = media.title.toLocaleLowerCase().replace(/ /g, '-');
// const html = await ctx.proxiedFetcher<string>(`/search/${titleCleaned}`, {
// baseUrl: zoeBase,
// });
// const $ = load(html);
// return $('.film_list-wrap .flw-item .film-detail')
// .toArray()
// .map((element) => {
// const movie = $(element);
// const anchor = movie.find('.film-name a');
// const info = movie.find('.fd-infor');
// const title = anchor.attr('title');
// const href = anchor.attr('href');
// const type = info.find('.fdi-type').html();
// let year = info.find('.fdi-item').html();
// const id = href?.split('-').pop();
// if (!title) {
// return null;
// }
// if (!href) {
// return null;
// }
// if (!type) {
// return null;
// }
// // TV shows on ZoeChip do not have a year in their search results
// // Allow TV shows to pass this failure
// if (!year || Number.isNaN(Number(year))) {
// if (type === 'TV') {
// year = '0';
// } else {
// return null;
// }
// }
// if (!id) {
// return null;
// }
// return {
// title,
// year: Number(year),
// id,
// type,
// href,
// };
// });
// }
// export async function getZoeChipMovieID(ctx: ScrapeContext, media: MovieMedia): Promise<string | null> {
// const searchResults = await getZoeChipSearchResults(ctx, media);
// const matchingItem = searchResults.find((v) => v && v.type === 'Movie' && compareMedia(media, v.title, v.year));
// if (!matchingItem) {
// return null;
// }
// return matchingItem.id;
// }
// export async function getZoeChipShowID(ctx: ScrapeContext, media: ShowMedia): Promise<string | null> {
// // ZoeChip TV shows don't have a year on their search results
// // This makes it hard to filter between shows with the same name
// // To find the year, we must query each shows details page
// // This is slower, but more reliable
// const releasedRegex = /<\/strong><\/span> (\d.*)-\d.*-\d.*/;
// const searchResults = await getZoeChipSearchResults(ctx, media);
// const filtered = searchResults.filter((v) => v && v.type === 'TV' && compareMedia(media, v.title));
// for (const result of filtered) {
// // This gets filtered above but the linter Gods don't think so
// if (!result) {
// continue;
// }
// const html = await ctx.proxiedFetcher<string>(result.href, {
// baseUrl: zoeBase,
// });
// // The HTML is not structured in a way that makes using Cheerio clean
// // There are no unique IDs or classes to query, resulting in long ugly queries
// // Regex is faster and cleaner in this case
// const regexResult = html.match(releasedRegex);
// if (regexResult) {
// const year = Number(regexResult[1]);
// if (!Number.isNaN(year) && compareMedia(media, result.title, year)) {
// return result.id;
// }
// }
// }
// return null;
// }

Binary file not shown.

View file

@ -0,0 +1,241 @@
import { load } from 'cheerio';
import { unpack } from 'unpacker';
import { flags } from '@/entrypoint/utils/targets';
import { SourcererOutput, makeSourcerer } from '@/providers/base';
import { MovieScrapeContext, ShowScrapeContext } from '@/utils/context';
import { NotFoundError } from '@/utils/errors';
const zoeBase = 'https://zoechip.org';
function createSlug(title: string): string {
return title
.toLowerCase()
.replace(/[^a-z0-9\s-]/g, '')
.replace(/\s+/g, '-')
.replace(/-+/g, '-')
.trim();
}
async function extractFileFromFilemoon(
ctx: MovieScrapeContext | ShowScrapeContext,
filemoonUrl: string,
): Promise<string | null> {
const headers = {
Referer: zoeBase,
'User-Agent':
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36',
};
try {
// console.log(`Extracting from Filemoon URL: ${filemoonUrl}`);
// Follow redirects to get the actual iframe URL
const redirectResponse = await ctx.proxiedFetcher.full(filemoonUrl, {
method: 'HEAD',
headers,
});
const redirectUrl = redirectResponse.finalUrl;
// console.log(`Redirect URL: ${redirectUrl}`);
if (!redirectUrl) {
// console.log('No redirect URL found');
return null;
}
// Get the redirect page content
const redirectHtml = await ctx.proxiedFetcher<string>(redirectUrl, {
headers,
});
const redirectCheerio = load(redirectHtml);
const iframeUrl = redirectCheerio('iframe').attr('src');
// console.log(`Iframe URL: ${iframeUrl}`);
if (!iframeUrl) {
// console.log('No iframe URL found');
throw new NotFoundError('No iframe URL found');
}
// Fetch the iframe content
const iframeHtml = await ctx.proxiedFetcher<string>(iframeUrl, {
headers,
});
// Extract the packed JavaScript code
const evalMatch = iframeHtml.match(/eval\(function\(p,a,c,k,e,.*\)\)/i);
if (!evalMatch) {
// console.log('No packed JavaScript found');
throw new NotFoundError('No packed JavaScript found');
}
// console.log('Found packed JavaScript, unpacking...');
// Unpack the JavaScript
const unpacked = unpack(evalMatch[0]);
// console.log(`Unpacked JavaScript (first 200 chars): ${unpacked.substring(0, 200)}`);
// Extract the file URL from the unpacked code
const fileMatch = unpacked.match(/file\s*:\s*"([^"]+)"/i);
if (!fileMatch) {
// console.log('No file URL found in unpacked JavaScript');
throw new NotFoundError('No file URL found in unpacked JavaScript');
}
const fileUrl = fileMatch[1];
// console.log(`Extracted file URL: ${fileUrl}`);
return fileUrl;
} catch (error) {
// console.error('Error in extractFileFromFilemoon:', error);
throw new NotFoundError('Failed to extract file URL from streaming server');
}
}
async function comboScraper(ctx: MovieScrapeContext | ShowScrapeContext): Promise<SourcererOutput> {
const headers = {
Referer: zoeBase,
'User-Agent':
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36',
};
// console.log(`Starting scrape for ${ctx.media.type}: ${ctx.media.title}`);
let url: string;
let movieId: string | undefined;
try {
// Construct URLs based on media type
if (ctx.media.type === 'movie') {
const slug = createSlug(ctx.media.title);
url = `${zoeBase}/film/${slug}-${ctx.media.releaseYear}`;
// console.log(`Movie URL: ${url}`);
} else {
const slug = createSlug(ctx.media.title);
url = `${zoeBase}/episode/${slug}-season-${ctx.media.season.number}-episode-${ctx.media.episode.number}`;
// console.log(`Show URL: ${url}`);
}
ctx.progress(20);
// Get the page and extract movie ID
const html = await ctx.proxiedFetcher<string>(url, { headers });
const $ = load(html);
movieId = $('div#show_player_ajax').attr('movie-id');
// console.log(`Movie ID: ${movieId}`);
if (!movieId) {
// Try alternative methods to find content
// console.log('No movie ID found, trying alternative search...');
// Look for other possible IDs
const altId =
$('[data-movie-id]').attr('data-movie-id') ||
$('[movie-id]').attr('movie-id') ||
$('.player-wrapper').attr('data-id');
if (altId) {
movieId = altId;
// console.log(`Found alternative ID: ${movieId}`);
} else {
throw new NotFoundError(`No content found for ${ctx.media.type === 'movie' ? 'movie' : 'episode'}`);
}
}
ctx.progress(40);
// Make AJAX request to get sources
const ajaxUrl = `${zoeBase}/wp-admin/admin-ajax.php`;
const ajaxHeaders = {
...headers,
'X-Requested-With': 'XMLHttpRequest',
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
Referer: url,
};
const body = new URLSearchParams({
action: 'lazy_player',
movieID: movieId,
});
// console.log('Making AJAX request for sources...');
const ajaxHtml = await ctx.proxiedFetcher<string>(ajaxUrl, {
method: 'POST',
headers: ajaxHeaders,
body: body.toString(),
});
const $ajax = load(ajaxHtml);
const filemoonUrl = $ajax('ul.nav a:contains(Filemoon)').attr('data-server');
// console.log(`Filemoon URL: ${filemoonUrl}`);
if (!filemoonUrl) {
// Try to find other available servers
const allServers = $ajax('ul.nav a')
.map((_, el) => ({
name: $ajax(el).text().trim(),
url: $ajax(el).attr('data-server'),
}))
.get();
// console.log('Available servers:', allServers);
if (allServers.length === 0) {
throw new NotFoundError('No streaming servers found');
}
throw new NotFoundError('Filemoon server not available');
}
ctx.progress(60);
// Extract file URL from Filemoon
const fileUrl = await extractFileFromFilemoon(ctx, filemoonUrl);
if (!fileUrl) {
throw new NotFoundError('Failed to extract file URL from streaming server');
}
ctx.progress(90);
return {
stream: [
{
id: 'primary',
type: 'hls' as const,
playlist: fileUrl,
flags: [flags.CORS_ALLOWED],
captions: [],
},
],
embeds: [],
};
} catch (error) {
// console.error('Error during scraping:', error);
if (error instanceof NotFoundError) {
throw error;
}
// Provide more specific error messages
if (error instanceof Error) {
if (error.message.includes('fetch')) {
throw new NotFoundError('Failed to connect to ZoeChip');
}
if (error.message.includes('timeout')) {
throw new NotFoundError('Request timed out');
}
}
throw new NotFoundError(`ZoeChip scraping failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
export const zoechipScraper = makeSourcerer({
id: 'zoechip',
name: 'ZoeChip',
rank: 170,
flags: [flags.CORS_ALLOWED],
scrapeMovie: comboScraper,
scrapeShow: comboScraper,
});