diff --git a/src/providers/all.ts b/src/providers/all.ts index 675aa36..c0bb710 100644 --- a/src/providers/all.ts +++ b/src/providers/all.ts @@ -39,6 +39,7 @@ import { nepuScraper } from './sources/nepu'; import { primewireScraper } from './sources/primewire'; import { ridooMoviesScraper } from './sources/ridomovies'; import { smashyStreamScraper } from './sources/smashystream'; +import { soaperTvScraper } from './sources/soapertv'; import { vidSrcToScraper } from './sources/vidsrcto'; export function gatherAllSources(): Array { @@ -60,6 +61,7 @@ export function gatherAllSources(): Array { hdRezkaScraper, primewireScraper, insertunitScraper, + soaperTvScraper, ]; } diff --git a/src/providers/embeds/vidsrc.ts b/src/providers/embeds/vidsrc.ts index ae945d2..716c82c 100644 --- a/src/providers/embeds/vidsrc.ts +++ b/src/providers/embeds/vidsrc.ts @@ -1,5 +1,5 @@ -import { flags } from '@/entrypoint/utils/targets'; import { makeEmbed } from '@/providers/base'; +import { vidsrcRCPBase } from '@/providers/sources/vidsrc/common'; const hlsURLRegex = /file:"(.*?)"/; const setPassRegex = /var pass_path = "(.*set_pass\.php.*)";/; @@ -56,7 +56,11 @@ export const vidsrcembedScraper = makeEmbed({ id: 'primary', type: 'hls', playlist: finalUrl, - flags: [flags.CORS_ALLOWED], + headers: { + Referer: vidsrcRCPBase, + Origin: vidsrcRCPBase, + }, + flags: [], captions: [], }, ], diff --git a/src/providers/sources/soapertv/index.ts b/src/providers/sources/soapertv/index.ts new file mode 100644 index 0000000..feee555 --- /dev/null +++ b/src/providers/sources/soapertv/index.ts @@ -0,0 +1,120 @@ +import { load } from 'cheerio'; + +import { flags } from '@/entrypoint/utils/targets'; +import { Caption, labelToLanguageCode } from '@/providers/captions'; +import { MovieScrapeContext, ShowScrapeContext } from '@/utils/context'; +import { NotFoundError } from '@/utils/errors'; + +import { InfoResponse } from './types'; +import { SourcererOutput, makeSourcerer } from '../../base'; + +const baseUrl = 'https://soaper.tv'; + +const universalScraper = async (ctx: MovieScrapeContext | ShowScrapeContext): Promise => { + const searchResult = await ctx.proxiedFetcher('/search.html', { + baseUrl, + query: { + keyword: ctx.media.title, + }, + }); + const searchResult$ = load(searchResult); + let showLink = searchResult$('a') + .filter((_, el) => searchResult$(el).text() === ctx.media.title) + .attr('href'); + if (!showLink) throw new NotFoundError('Content not found'); + + if (ctx.media.type === 'show') { + const seasonNumber = ctx.media.season.number; + const episodeNumber = ctx.media.episode.number; + const showPage = await ctx.proxiedFetcher(showLink, { baseUrl }); + const showPage$ = load(showPage); + const seasonBlock = showPage$('h4') + .filter((_, el) => showPage$(el).text().trim().split(':')[0].trim() === `Season${seasonNumber}`) + .parent(); + const episodes = seasonBlock.find('a').toArray(); + showLink = showPage$( + episodes.find((el) => parseInt(showPage$(el).text().split('.')[0], 10) === episodeNumber), + ).attr('href'); + } + if (!showLink) throw new NotFoundError('Content not found'); + const contentPage = await ctx.proxiedFetcher(showLink, { baseUrl }); + const contentPage$ = load(contentPage); + + const pass = contentPage$('#hId').attr('value'); + const param = contentPage$('#divU').text(); + + if (!pass || !param) throw new NotFoundError('Content not found'); + + const formData = new URLSearchParams(); + formData.append('pass', pass); + formData.append('param', param); + formData.append('e2', '0'); + formData.append('server', '0'); + + const infoEndpoint = ctx.media.type === 'show' ? '/home/index/getEInfoAjax' : '/home/index/getMInfoAjax'; + const streamRes = await ctx.proxiedFetcher(infoEndpoint, { + baseUrl, + method: 'POST', + body: formData, + headers: { + referer: `${baseUrl}${showLink}`, + }, + }); + + const streamResJson: InfoResponse = JSON.parse(streamRes); + + const captions: Caption[] = []; + for (const sub of streamResJson.subs) { + // Some subtitles are named .srt, some are named :hi, or just + let language: string | null = ''; + if (sub.name.includes('.srt')) { + language = labelToLanguageCode(sub.name.split('.srt')[0]); + } else if (sub.name.includes(':')) { + language = sub.name.split(':')[0]; + } else { + language = sub.name; + } + if (!language) continue; + + captions.push({ + id: sub.path, + url: sub.path, + type: 'srt', + hasCorsRestrictions: false, + language, + }); + } + + return { + embeds: [], + stream: [ + { + id: 'primary', + playlist: streamResJson.val, + type: 'hls', + flags: [flags.IP_LOCKED], + captions, + }, + ...(streamResJson.val_bak + ? [ + { + id: 'backup', + playlist: streamResJson.val_bak, + type: 'hls' as const, + flags: [flags.IP_LOCKED], + captions, + }, + ] + : []), + ], + }; +}; + +export const soaperTvScraper = makeSourcerer({ + id: 'soapertv', + name: 'SoaperTV', + rank: 115, + flags: [flags.IP_LOCKED], + scrapeMovie: universalScraper, + scrapeShow: universalScraper, +}); diff --git a/src/providers/sources/soapertv/types.ts b/src/providers/sources/soapertv/types.ts new file mode 100644 index 0000000..70fb602 --- /dev/null +++ b/src/providers/sources/soapertv/types.ts @@ -0,0 +1,15 @@ +export interface Subtitle { + path: string; + name: string; +} + +export interface InfoResponse { + key: boolean; + val: string; + vtt: string; + val_bak: string; + pos: number; + type: string; + subs: Subtitle[]; + ip: string; +} diff --git a/src/providers/sources/vidsrc/common.ts b/src/providers/sources/vidsrc/common.ts index 4ccc93c..6f6fd71 100644 --- a/src/providers/sources/vidsrc/common.ts +++ b/src/providers/sources/vidsrc/common.ts @@ -1,2 +1,2 @@ export const vidsrcBase = 'https://vidsrc.me'; -export const vidsrcRCPBase = 'https://rcp.vidsrc.me'; +export const vidsrcRCPBase = 'https://vidsrc.stream'; diff --git a/src/providers/sources/vidsrc/index.ts b/src/providers/sources/vidsrc/index.ts index d6b15a8..3046fb8 100644 --- a/src/providers/sources/vidsrc/index.ts +++ b/src/providers/sources/vidsrc/index.ts @@ -1,4 +1,3 @@ -import { flags } from '@/entrypoint/utils/targets'; import { makeSourcerer } from '@/providers/base'; import { scrapeMovie } from '@/providers/sources/vidsrc/scrape-movie'; import { scrapeShow } from '@/providers/sources/vidsrc/scrape-show'; @@ -7,7 +6,7 @@ export const vidsrcScraper = makeSourcerer({ id: 'vidsrc', name: 'VidSrc', rank: 90, - flags: [flags.CORS_ALLOWED], + flags: [], scrapeMovie, scrapeShow, });