mirror of
https://github.com/p-stream/providers.git
synced 2026-04-21 09:22:19 +00:00
update zoechip
This commit is contained in:
parent
a0e45a46bb
commit
2181ab3eed
11 changed files with 243 additions and 359 deletions
BIN
src/providers/.DS_Store
vendored
Normal file
BIN
src/providers/.DS_Store
vendored
Normal file
Binary file not shown.
|
|
@ -12,6 +12,7 @@ import { mp4hydraScraper } from '@/providers/sources/mp4hydra';
|
||||||
import { tugaflixScraper } from '@/providers/sources/tugaflix';
|
import { tugaflixScraper } from '@/providers/sources/tugaflix';
|
||||||
import { vidsrcScraper } from '@/providers/sources/vidsrc';
|
import { vidsrcScraper } from '@/providers/sources/vidsrc';
|
||||||
import { vidsrcsuScraper } from '@/providers/sources/vidsrcsu';
|
import { vidsrcsuScraper } from '@/providers/sources/vidsrcsu';
|
||||||
|
import { zoechipScraper } from '@/providers/sources/zoechip';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
autoembedBengaliScraper,
|
autoembedBengaliScraper,
|
||||||
|
|
@ -86,6 +87,7 @@ export function gatherAllSources(): Array<Sourcerer> {
|
||||||
fsharetvScraper,
|
fsharetvScraper,
|
||||||
vidsrcsuScraper,
|
vidsrcsuScraper,
|
||||||
vidsrcScraper,
|
vidsrcScraper,
|
||||||
|
zoechipScraper,
|
||||||
mp4hydraScraper,
|
mp4hydraScraper,
|
||||||
embedsuScraper,
|
embedsuScraper,
|
||||||
slidemoviesScraper,
|
slidemoviesScraper,
|
||||||
|
|
|
||||||
BIN
src/providers/archive/sources/.DS_Store
vendored
BIN
src/providers/archive/sources/.DS_Store
vendored
Binary file not shown.
|
|
@ -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,
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
|
|
@ -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,
|
|
||||||
// });
|
|
||||||
|
|
@ -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);
|
|
||||||
// }
|
|
||||||
|
|
@ -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);
|
|
||||||
// }
|
|
||||||
|
|
@ -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;
|
|
||||||
// }
|
|
||||||
|
|
@ -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;
|
|
||||||
// }
|
|
||||||
BIN
src/providers/sources/.DS_Store
vendored
BIN
src/providers/sources/.DS_Store
vendored
Binary file not shown.
241
src/providers/sources/zoechip.ts
Normal file
241
src/providers/sources/zoechip.ts
Normal 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,
|
||||||
|
});
|
||||||
Loading…
Reference in a new issue