diff --git a/src/providers/all.ts b/src/providers/all.ts index aaed49b..fb2a875 100644 --- a/src/providers/all.ts +++ b/src/providers/all.ts @@ -3,6 +3,7 @@ import { doodScraper } from '@/providers/embeds/dood'; import { mixdropScraper } from '@/providers/embeds/mixdrop'; import { turbovidScraper } from '@/providers/embeds/turbovid'; import { upcloudScraper } from '@/providers/embeds/upcloud'; +import { vidsrcCometEmbed, vidsrcNovaEmbed, vidsrcPulsarEmbed } from '@/providers/embeds/vidsrcvip'; import { autoembedScraper } from '@/providers/sources/autoembed'; import { catflixScraper } from '@/providers/sources/catflix'; import { ee3Scraper } from '@/providers/sources/ee3'; @@ -14,6 +15,7 @@ import { pirxcyScraper } from '@/providers/sources/pirxcy'; import { tugaflixScraper } from '@/providers/sources/tugaflix'; import { vidsrcScraper } from '@/providers/sources/vidsrc'; import { vidsrcsuScraper } from '@/providers/sources/vidsrcsu'; +import { vidsrcvipScraper } from '@/providers/sources/vidsrcvip'; import { zoechipScraper } from '@/providers/sources/zoechip'; import { @@ -105,6 +107,7 @@ export function gatherAllSources(): Array { cinemaosScraper, nepuScraper, pirxcyScraper, + vidsrcvipScraper, ]; } @@ -151,5 +154,8 @@ export function gatherAllEmbeds(): Array { streamtapeLatinoScraper, ...cinemaosEmbeds, // ...cinemaosHexaEmbeds, + vidsrcNovaEmbed, + vidsrcCometEmbed, + vidsrcPulsarEmbed, ]; } diff --git a/src/providers/embeds/vidsrcvip.ts b/src/providers/embeds/vidsrcvip.ts new file mode 100644 index 0000000..5bc04ce --- /dev/null +++ b/src/providers/embeds/vidsrcvip.ts @@ -0,0 +1,43 @@ +import { flags } from '@/entrypoint/utils/targets'; +import { makeEmbed } from '@/providers/base'; + +const embedProviders = [ + { + id: 'vidsrc-nova', + name: 'Nova', + rank: 558, + }, + { + id: 'vidsrc-comet', + name: 'Comet', + rank: 560, + }, + { + id: 'vidsrc-pulsar', + name: 'Pulsar', + rank: 559, + }, +]; + +function makeVidSrcEmbed(provider: { id: string; name: string; rank: number }) { + return makeEmbed({ + id: provider.id, + name: provider.name, + rank: provider.rank, + async scrape(ctx) { + return { + stream: [ + { + id: 'primary', + type: 'hls', + playlist: ctx.url, + flags: [flags.CORS_ALLOWED], + captions: [], + }, + ], + }; + }, + }); +} + +export const [vidsrcNovaEmbed, vidsrcCometEmbed, vidsrcPulsarEmbed] = embedProviders.map(makeVidSrcEmbed); diff --git a/src/providers/sources/vidsrcvip.ts b/src/providers/sources/vidsrcvip.ts new file mode 100644 index 0000000..be627bc --- /dev/null +++ b/src/providers/sources/vidsrcvip.ts @@ -0,0 +1,66 @@ +import { flags } from '@/entrypoint/utils/targets'; +import { SourcererEmbed, SourcererOutput, makeSourcerer } from '@/providers/base'; +import { MovieScrapeContext, ShowScrapeContext } from '@/utils/context'; +import { NotFoundError } from '@/utils/errors'; + +const baseUrl = 'https://api2.vidsrc.vip'; + +function digitToLetterMap(digit: string): string { + const map = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']; + return map[parseInt(digit, 10)]; +} + +function encodeTmdbId(tmdb: string, type: 'movie' | 'show', season?: number, episode?: number): string { + let raw: string; + if (type === 'show' && season && episode) { + raw = `${tmdb}-${season}-${episode}`; + } else { + raw = tmdb.split('').map(digitToLetterMap).join(''); + } + const reversed = raw.split('').reverse().join(''); + return btoa(btoa(reversed)); +} + +async function comboScraper(ctx: ShowScrapeContext | MovieScrapeContext): Promise { + const apiType = ctx.media.type === 'show' ? 'tv' : 'movie'; + const encodedId = encodeTmdbId( + ctx.media.tmdbId, + ctx.media.type, + ctx.media.type === 'show' ? ctx.media.season.number : undefined, + ctx.media.type === 'show' ? ctx.media.episode.number : undefined, + ); + + const url = `${baseUrl}/${apiType}/${encodedId}`; + const data = await ctx.proxiedFetcher(url); + + if (!data || !data.source1) throw new NotFoundError('No sources found'); + + const embeds: SourcererEmbed[] = []; + const embedIds = ['vidsrc-nova', 'vidsrc-comet', 'vidsrc-pulsar']; + let sourceIndex = 0; + for (let i = 1; data[`source${i}`]; i++) { + const source = data[`source${i}`]; + if (source?.url) { + embeds.push({ + embedId: embedIds[sourceIndex % embedIds.length], + url: source.url, + }); + sourceIndex++; + } + } + + if (embeds.length === 0) throw new NotFoundError('No embeds found'); + + return { + embeds, + }; +} + +export const vidsrcvipScraper = makeSourcerer({ + id: 'vidsrcvip', + name: 'VidSrc.vip', + rank: 560, + flags: [flags.CORS_ALLOWED], + scrapeMovie: comboScraper, + scrapeShow: comboScraper, +});