mirror of
https://github.com/p-stream/providers.git
synced 2026-01-11 20:10:33 +00:00
Merge branch 'pr/9' into production
This commit is contained in:
commit
390d83e2df
3 changed files with 167 additions and 1 deletions
|
|
@ -1,5 +1,7 @@
|
|||
export function getConfig() {
|
||||
let tmdbApiKey = process.env.MOVIE_WEB_TMDB_API_KEY ?? '';
|
||||
let tmdbApiKey =
|
||||
process.env.MOVIE_WEB_TMDB_API_KEY ??
|
||||
'eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJmMjUwMjc3YmZjNjNkNzNiYjY5NmI3MWU2NThjMjUyMSIsIm5iZiI6MTcyNTcyNTQ1OS4wOTksInN1YiI6IjY2ZGM3YjEzNDQyYjExNDQzMmRkMTU5MSIsInNjb3BlcyI6WyJhcGlfcmVhZCJdLCJ2ZXJzaW9uIjoxfQ.W7jVmiYfA3XQg0eXTdnjer5EkNlJ1F4k4bJyw5zdgVY';
|
||||
tmdbApiKey = tmdbApiKey.trim();
|
||||
|
||||
if (!tmdbApiKey) {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import { fsharetvScraper } from '@/providers/sources/fsharetv';
|
|||
import { insertunitScraper } from '@/providers/sources/insertunit';
|
||||
import { mp4hydraScraper } from '@/providers/sources/mp4hydra';
|
||||
import { nepuScraper } from '@/providers/sources/nepu';
|
||||
import { pirxcyScraper } from '@/providers/sources/pirxcy';
|
||||
import { tugaflixScraper } from '@/providers/sources/tugaflix';
|
||||
import { vidsrcScraper } from '@/providers/sources/vidsrc';
|
||||
import { vidsrcsuScraper } from '@/providers/sources/vidsrcsu';
|
||||
|
|
@ -103,6 +104,7 @@ export function gatherAllSources(): Array<Sourcerer> {
|
|||
animeflvScraper,
|
||||
cinemaosScraper,
|
||||
nepuScraper,
|
||||
pirxcyScraper,
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
|||
162
src/providers/sources/pirxcy.ts
Normal file
162
src/providers/sources/pirxcy.ts
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
import { flags } from '@/entrypoint/utils/targets';
|
||||
import { SourcererOutput, makeSourcerer } from '@/providers/base';
|
||||
import { MovieScrapeContext, ShowScrapeContext } from '@/utils/context';
|
||||
import { NotFoundError } from '@/utils/errors';
|
||||
|
||||
const API_SERVER = 'https://mbp.pirxcy.dev';
|
||||
|
||||
// Helper function to convert quality string to standard format
|
||||
function normalizeQuality(quality: string): string {
|
||||
const qualityMap: { [key: string]: string } = {
|
||||
'4K': '2160',
|
||||
'1080p': '1080',
|
||||
'720p': '720',
|
||||
HDTV: '720',
|
||||
'480p': '480',
|
||||
'360p': '360',
|
||||
};
|
||||
return qualityMap[quality] || '720';
|
||||
}
|
||||
|
||||
// Helper function to build qualities object from all available streams
|
||||
function buildQualitiesFromStreams(streams: Array<{ path: string; real_quality: string }>) {
|
||||
const mp4Streams = streams.filter((s) => s.path && new URL(s.path).pathname.endsWith('.mp4'));
|
||||
if (mp4Streams.length === 0) {
|
||||
throw new NotFoundError('No playable MP4 streams found');
|
||||
}
|
||||
|
||||
const qualities: { [key: string]: { url: string } } = {};
|
||||
|
||||
// Add all available qualities
|
||||
for (const stream of mp4Streams) {
|
||||
const normalizedQuality = normalizeQuality(stream.real_quality);
|
||||
qualities[normalizedQuality] = { url: stream.path };
|
||||
}
|
||||
|
||||
return qualities;
|
||||
}
|
||||
|
||||
// Helper function to find media by TMDB ID
|
||||
async function findMediaByTMDBId(
|
||||
ctx: MovieScrapeContext | ShowScrapeContext,
|
||||
tmdbId: string,
|
||||
title: string,
|
||||
type: 'movie' | 'tv',
|
||||
year?: string,
|
||||
): Promise<string> {
|
||||
const searchUrl = `${API_SERVER}/search?q=${encodeURIComponent(title)}&type=${type}${year ? `&year=${year}` : ''}`;
|
||||
const searchRes = await ctx.proxiedFetcher(searchUrl);
|
||||
|
||||
if (!searchRes.data || searchRes.data.length === 0) {
|
||||
throw new NotFoundError('No results found in search');
|
||||
}
|
||||
|
||||
// Find the correct internal ID by matching TMDB ID
|
||||
for (const result of searchRes.data) {
|
||||
const detailUrl = `${API_SERVER}/details/${type}/${result.id}`;
|
||||
const detailRes = await ctx.proxiedFetcher(detailUrl);
|
||||
|
||||
if (detailRes.data && detailRes.data.tmdb_id.toString() === tmdbId) {
|
||||
return result.id;
|
||||
}
|
||||
}
|
||||
|
||||
throw new NotFoundError('Could not find matching media item for TMDB ID');
|
||||
}
|
||||
|
||||
async function scrapeMovie(ctx: MovieScrapeContext): Promise<SourcererOutput> {
|
||||
const tmdbId = ctx.media.tmdbId;
|
||||
const title = ctx.media.title;
|
||||
const year = ctx.media.releaseYear?.toString();
|
||||
|
||||
if (!tmdbId || !title) {
|
||||
throw new NotFoundError('Missing required media information');
|
||||
}
|
||||
|
||||
try {
|
||||
// Find internal media ID
|
||||
const mediaId = await findMediaByTMDBId(ctx, tmdbId, title, 'movie', year);
|
||||
|
||||
// Get stream links
|
||||
const streamUrl = `${API_SERVER}/movie/${mediaId}`;
|
||||
const streamData = await ctx.proxiedFetcher(streamUrl);
|
||||
|
||||
if (!streamData.data || !streamData.data.list) {
|
||||
throw new NotFoundError('No streams found for this movie');
|
||||
}
|
||||
|
||||
const qualities = buildQualitiesFromStreams(streamData.data.list);
|
||||
|
||||
return {
|
||||
stream: [
|
||||
{
|
||||
id: 'pirxcy',
|
||||
type: 'file',
|
||||
qualities,
|
||||
flags: [flags.CORS_ALLOWED],
|
||||
captions: [],
|
||||
},
|
||||
],
|
||||
embeds: [],
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof NotFoundError) {
|
||||
throw error;
|
||||
}
|
||||
throw new NotFoundError(`Failed to scrape movie: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function scrapeShow(ctx: ShowScrapeContext): Promise<SourcererOutput> {
|
||||
const tmdbId = ctx.media.tmdbId;
|
||||
const title = ctx.media.title;
|
||||
const year = ctx.media.releaseYear?.toString();
|
||||
const season = ctx.media.season.number;
|
||||
const episode = ctx.media.episode.number;
|
||||
|
||||
if (!tmdbId || !title || !season || !episode) {
|
||||
throw new NotFoundError('Missing required media information');
|
||||
}
|
||||
|
||||
try {
|
||||
// Find internal media ID
|
||||
const mediaId = await findMediaByTMDBId(ctx, tmdbId, title, 'tv', year);
|
||||
|
||||
// Get stream links
|
||||
const streamUrl = `${API_SERVER}/tv/${mediaId}/${season}/${episode}`;
|
||||
const streamData = await ctx.proxiedFetcher(streamUrl);
|
||||
|
||||
if (!streamData.data || !streamData.data.list) {
|
||||
throw new NotFoundError('No streams found for this episode');
|
||||
}
|
||||
|
||||
const qualities = buildQualitiesFromStreams(streamData.data.list);
|
||||
|
||||
return {
|
||||
stream: [
|
||||
{
|
||||
id: 'pirxcy',
|
||||
type: 'file',
|
||||
qualities,
|
||||
flags: [flags.CORS_ALLOWED],
|
||||
captions: [],
|
||||
},
|
||||
],
|
||||
embeds: [],
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof NotFoundError) {
|
||||
throw error;
|
||||
}
|
||||
throw new NotFoundError(`Failed to scrape show: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
||||
}
|
||||
}
|
||||
|
||||
export const pirxcyScraper = makeSourcerer({
|
||||
id: 'pirxcy',
|
||||
name: 'Pirxcy',
|
||||
rank: 150,
|
||||
flags: [flags.CORS_ALLOWED],
|
||||
scrapeMovie,
|
||||
scrapeShow,
|
||||
});
|
||||
Loading…
Reference in a new issue