commit
c93cc4babc
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "@movie-web/providers",
|
||||
"version": "1.1.4",
|
||||
"version": "1.1.5",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@movie-web/providers",
|
||||
"version": "1.1.4",
|
||||
"version": "1.1.5",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cheerio": "^1.0.0-rc.12",
|
||||
|
|
|
@ -13,7 +13,10 @@ import { remotestreamScraper } from '@/providers/sources/remotestream';
|
|||
import { superStreamScraper } from '@/providers/sources/superstream/index';
|
||||
import { zoechipScraper } from '@/providers/sources/zoechip';
|
||||
|
||||
import { smashyStreamDScraper } from './embeds/smashystream/dued';
|
||||
import { smashyStreamFScraper } from './embeds/smashystream/video1';
|
||||
import { showBoxScraper } from './sources/showbox';
|
||||
import { smashyStreamScraper } from './sources/smashystream';
|
||||
|
||||
export function gatherAllSources(): Array<Sourcerer> {
|
||||
// all sources are gathered here
|
||||
|
@ -26,10 +29,20 @@ export function gatherAllSources(): Array<Sourcerer> {
|
|||
zoechipScraper,
|
||||
lookmovieScraper,
|
||||
showBoxScraper,
|
||||
smashyStreamScraper,
|
||||
];
|
||||
}
|
||||
|
||||
export function gatherAllEmbeds(): Array<Embed> {
|
||||
// all embeds are gathered here
|
||||
return [upcloudScraper, mp4uploadScraper, streamsbScraper, upstreamScraper, febBoxScraper, mixdropScraper];
|
||||
return [
|
||||
upcloudScraper,
|
||||
mp4uploadScraper,
|
||||
streamsbScraper,
|
||||
upstreamScraper,
|
||||
febBoxScraper,
|
||||
mixdropScraper,
|
||||
smashyStreamFScraper,
|
||||
smashyStreamDScraper,
|
||||
];
|
||||
}
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import { MovieMedia, ShowMedia } from '@/main/media';
|
||||
import { Flags } from '@/main/targets';
|
||||
import { Stream } from '@/providers/streams';
|
||||
import { EmbedScrapeContext, ScrapeContext } from '@/utils/context';
|
||||
import { EmbedScrapeContext, MovieScrapeContext, ShowScrapeContext } from '@/utils/context';
|
||||
|
||||
export type SourcererOutput = {
|
||||
embeds: {
|
||||
export type SourcererEmbed = {
|
||||
embedId: string;
|
||||
url: string;
|
||||
}[];
|
||||
};
|
||||
|
||||
export type SourcererOutput = {
|
||||
embeds: SourcererEmbed[];
|
||||
stream?: Stream;
|
||||
};
|
||||
|
||||
|
@ -17,8 +18,8 @@ export type Sourcerer = {
|
|||
rank: number; // the higher the number, the earlier it gets put on the queue
|
||||
disabled?: boolean;
|
||||
flags: Flags[];
|
||||
scrapeMovie?: (input: ScrapeContext & { media: MovieMedia }) => Promise<SourcererOutput>;
|
||||
scrapeShow?: (input: ScrapeContext & { media: ShowMedia }) => Promise<SourcererOutput>;
|
||||
scrapeMovie?: (input: MovieScrapeContext) => Promise<SourcererOutput>;
|
||||
scrapeShow?: (input: ShowScrapeContext) => Promise<SourcererOutput>;
|
||||
};
|
||||
|
||||
export function makeSourcerer(state: Sourcerer): Sourcerer {
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
import { load } from 'cheerio';
|
||||
|
||||
import { flags } from '@/main/targets';
|
||||
import { makeEmbed } from '@/providers/base';
|
||||
|
||||
type DPlayerSourcesResponse = {
|
||||
title: string;
|
||||
id: string;
|
||||
file: string;
|
||||
}[];
|
||||
|
||||
export const smashyStreamDScraper = makeEmbed({
|
||||
id: 'smashystream-d',
|
||||
name: 'SmashyStream (D)',
|
||||
rank: 71,
|
||||
async scrape(ctx) {
|
||||
const mainPageRes = await ctx.proxiedFetcher<string>(ctx.url, {
|
||||
headers: {
|
||||
Referer: ctx.url,
|
||||
},
|
||||
});
|
||||
const mainPageRes$ = load(mainPageRes);
|
||||
const iframeUrl = mainPageRes$('iframe').attr('src');
|
||||
if (!iframeUrl) throw new Error(`[${this.name}] failed to find iframe url`);
|
||||
const mainUrl = new URL(iframeUrl);
|
||||
const iframeRes = await ctx.proxiedFetcher<string>(iframeUrl, {
|
||||
headers: {
|
||||
Referer: ctx.url,
|
||||
},
|
||||
});
|
||||
const textFilePath = iframeRes.match(/"file":"([^"]+)"/)?.[1];
|
||||
const csrfToken = iframeRes.match(/"key":"([^"]+)"/)?.[1];
|
||||
if (!textFilePath || !csrfToken) throw new Error(`[${this.name}] failed to find text file url or token`);
|
||||
const textFileUrl = `${mainUrl.origin}${textFilePath}`;
|
||||
const textFileRes = await ctx.proxiedFetcher<DPlayerSourcesResponse>(textFileUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'X-CSRF-TOKEN': csrfToken,
|
||||
Referer: iframeUrl,
|
||||
},
|
||||
});
|
||||
// Playlists in Hindi, English, Tamil and Telugu are available. We only get the english one.
|
||||
const textFilePlaylist = textFileRes.find((x) => x.title === 'English')?.file;
|
||||
if (!textFilePlaylist) throw new Error(`[${this.name}] failed to find an english playlist`);
|
||||
|
||||
const playlistRes = await ctx.proxiedFetcher<string>(
|
||||
`${mainUrl.origin}/playlist/${textFilePlaylist.slice(1)}.txt`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'X-CSRF-TOKEN': csrfToken,
|
||||
Referer: iframeUrl,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
stream: {
|
||||
playlist: playlistRes,
|
||||
type: 'hls',
|
||||
flags: [flags.NO_CORS],
|
||||
captions: [],
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
|
@ -0,0 +1,53 @@
|
|||
import { flags } from '@/main/targets';
|
||||
import { makeEmbed } from '@/providers/base';
|
||||
import { Caption, getCaptionTypeFromUrl, labelToLanguageCode } from '@/providers/captions';
|
||||
|
||||
type FPlayerResponse = {
|
||||
sourceUrls: string[];
|
||||
subtitleUrls: string;
|
||||
};
|
||||
|
||||
export const smashyStreamFScraper = makeEmbed({
|
||||
id: 'smashystream-f',
|
||||
name: 'SmashyStream (F)',
|
||||
rank: 70,
|
||||
async scrape(ctx) {
|
||||
const res = await ctx.proxiedFetcher<FPlayerResponse>(ctx.url, {
|
||||
headers: {
|
||||
Referer: ctx.url,
|
||||
},
|
||||
});
|
||||
|
||||
const captions: Caption[] =
|
||||
res.subtitleUrls
|
||||
.match(/\[([^\]]+)\](https?:\/\/\S+?)(?=,\[|$)/g)
|
||||
?.map<Caption | null>((entry: string) => {
|
||||
const match = entry.match(/\[([^\]]+)\](https?:\/\/\S+?)(?=,\[|$)/);
|
||||
if (match) {
|
||||
const [, language, url] = match;
|
||||
if (language && url) {
|
||||
const languageCode = labelToLanguageCode(language);
|
||||
const captionType = getCaptionTypeFromUrl(url);
|
||||
if (!languageCode || !captionType) return null;
|
||||
return {
|
||||
url: url.replace(',', ''),
|
||||
language: languageCode,
|
||||
type: captionType,
|
||||
hasCorsRestrictions: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.filter((x): x is Caption => x !== null) ?? [];
|
||||
|
||||
return {
|
||||
stream: {
|
||||
playlist: res.sourceUrls[0],
|
||||
type: 'hls',
|
||||
flags: [flags.NO_CORS],
|
||||
captions,
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
|
@ -1,11 +1,11 @@
|
|||
import { flags } from '@/main/targets';
|
||||
import { SourcererOutput, makeSourcerer } from '@/providers/base';
|
||||
import { MovieScrapeContext, ShowScrapeContext } from '@/utils/context';
|
||||
import { NotFoundError } from '@/utils/errors';
|
||||
|
||||
import { scrape, searchAndFindMedia } from './util';
|
||||
import { MovieContext, ShowContext } from '../zoechip/common';
|
||||
|
||||
async function universalScraper(ctx: ShowContext | MovieContext): Promise<SourcererOutput> {
|
||||
async function universalScraper(ctx: MovieScrapeContext | ShowScrapeContext): Promise<SourcererOutput> {
|
||||
const lookmovieData = await searchAndFindMedia(ctx, ctx.media);
|
||||
if (!lookmovieData) throw new NotFoundError('Media not found');
|
||||
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
import { load } from 'cheerio';
|
||||
|
||||
import { flags } from '@/main/targets';
|
||||
import { SourcererEmbed, SourcererOutput, makeSourcerer } from '@/providers/base';
|
||||
import { smashyStreamDScraper } from '@/providers/embeds/smashystream/dued';
|
||||
import { smashyStreamFScraper } from '@/providers/embeds/smashystream/video1';
|
||||
import { MovieScrapeContext, ShowScrapeContext } from '@/utils/context';
|
||||
|
||||
const smashyStreamBase = 'https://embed.smashystream.com';
|
||||
const referer = 'https://smashystream.com/';
|
||||
|
||||
const universalScraper = async (ctx: ShowScrapeContext | MovieScrapeContext): Promise<SourcererOutput> => {
|
||||
const mainPage = await ctx.proxiedFetcher<string>('/playere.php', {
|
||||
query: {
|
||||
tmdb: ctx.media.tmdbId,
|
||||
...(ctx.media.type === 'show' && {
|
||||
season: ctx.media.season.number.toString(),
|
||||
episode: ctx.media.episode.number.toString(),
|
||||
}),
|
||||
},
|
||||
headers: {
|
||||
Referer: referer,
|
||||
},
|
||||
baseUrl: smashyStreamBase,
|
||||
});
|
||||
|
||||
ctx.progress(30);
|
||||
|
||||
const mainPage$ = load(mainPage);
|
||||
const sourceUrls = mainPage$('.dropdown-menu a[data-url]')
|
||||
.map((_, el) => mainPage$(el).attr('data-url'))
|
||||
.get();
|
||||
|
||||
const embeds: SourcererEmbed[] = [];
|
||||
for (const sourceUrl of sourceUrls) {
|
||||
if (sourceUrl.includes('video1d.php')) {
|
||||
embeds.push({
|
||||
embedId: smashyStreamFScraper.id,
|
||||
url: sourceUrl,
|
||||
});
|
||||
}
|
||||
if (sourceUrl.includes('dued.php')) {
|
||||
embeds.push({
|
||||
embedId: smashyStreamDScraper.id,
|
||||
url: sourceUrl,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ctx.progress(60);
|
||||
|
||||
return {
|
||||
embeds,
|
||||
};
|
||||
};
|
||||
|
||||
export const smashyStreamScraper = makeSourcerer({
|
||||
id: 'smashystream',
|
||||
name: 'SmashyStream',
|
||||
rank: 70,
|
||||
flags: [flags.NO_CORS],
|
||||
scrapeMovie: universalScraper,
|
||||
scrapeShow: universalScraper,
|
||||
});
|
|
@ -1,20 +1,11 @@
|
|||
import { MovieMedia, ShowMedia } from '@/main/media';
|
||||
import { mixdropScraper } from '@/providers/embeds/mixdrop';
|
||||
import { upcloudScraper } from '@/providers/embeds/upcloud';
|
||||
import { upstreamScraper } from '@/providers/embeds/upstream';
|
||||
import { getZoeChipSourceURL, getZoeChipSources } from '@/providers/sources/zoechip/scrape';
|
||||
import { ScrapeContext } from '@/utils/context';
|
||||
import { MovieScrapeContext, ShowScrapeContext } from '@/utils/context';
|
||||
|
||||
export const zoeBase = 'https://zoechip.cc';
|
||||
|
||||
export type MovieContext = ScrapeContext & {
|
||||
media: MovieMedia;
|
||||
};
|
||||
|
||||
export type ShowContext = ScrapeContext & {
|
||||
media: ShowMedia;
|
||||
};
|
||||
|
||||
export type ZoeChipSourceDetails = {
|
||||
type: string; // Only seen "iframe" so far
|
||||
link: string;
|
||||
|
@ -23,7 +14,10 @@ export type ZoeChipSourceDetails = {
|
|||
title: string;
|
||||
};
|
||||
|
||||
export async function formatSource(ctx: MovieContext | ShowContext, source: { embed: string; episodeId: string }) {
|
||||
export async function formatSource(
|
||||
ctx: MovieScrapeContext | ShowScrapeContext,
|
||||
source: { embed: string; episodeId: string },
|
||||
) {
|
||||
const link = await getZoeChipSourceURL(ctx, source.episodeId);
|
||||
if (link) {
|
||||
const embed = {
|
||||
|
@ -51,7 +45,7 @@ export async function formatSource(ctx: MovieContext | ShowContext, source: { em
|
|||
}
|
||||
}
|
||||
|
||||
export async function createZoeChipStreamData(ctx: MovieContext | ShowContext, id: string) {
|
||||
export async function createZoeChipStreamData(ctx: MovieScrapeContext | ShowScrapeContext, id: string) {
|
||||
const sources = await getZoeChipSources(ctx, id);
|
||||
const embeds: {
|
||||
embedId: string;
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { MovieContext, createZoeChipStreamData } from '@/providers/sources/zoechip/common';
|
||||
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: MovieContext) {
|
||||
export async function scrapeMovie(ctx: MovieScrapeContext) {
|
||||
const movieID = await getZoeChipMovieID(ctx, ctx.media);
|
||||
if (!movieID) {
|
||||
throw new NotFoundError('no search results match');
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import { ShowContext, createZoeChipStreamData } from '@/providers/sources/zoechip/common';
|
||||
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: ShowContext) {
|
||||
export async function scrapeShow(ctx: ShowScrapeContext) {
|
||||
const showID = await getZoeChipShowID(ctx, ctx.media);
|
||||
if (!showID) {
|
||||
throw new NotFoundError('no search results match');
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { load } from 'cheerio';
|
||||
|
||||
import { ShowMedia } from '@/main/media';
|
||||
import { MovieContext, ShowContext, ZoeChipSourceDetails, zoeBase } from '@/providers/sources/zoechip/common';
|
||||
import { ScrapeContext } from '@/utils/context';
|
||||
import { ZoeChipSourceDetails, zoeBase } from '@/providers/sources/zoechip/common';
|
||||
import { MovieScrapeContext, ScrapeContext, ShowScrapeContext } from '@/utils/context';
|
||||
|
||||
export async function getZoeChipSources(ctx: MovieContext | ShowContext, id: string) {
|
||||
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';
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { UseableFetcher } from '@/fetchers/types';
|
||||
import { MovieMedia, ShowMedia } from '@/main/media';
|
||||
|
||||
export type ScrapeContext = {
|
||||
proxiedFetcher: <T>(...params: Parameters<UseableFetcher<T>>) => ReturnType<UseableFetcher<T>>;
|
||||
|
@ -11,3 +12,11 @@ export type EmbedInput = {
|
|||
};
|
||||
|
||||
export type EmbedScrapeContext = EmbedInput & ScrapeContext;
|
||||
|
||||
export type MovieScrapeContext = ScrapeContext & {
|
||||
media: MovieMedia;
|
||||
};
|
||||
|
||||
export type ShowScrapeContext = ScrapeContext & {
|
||||
media: ShowMedia;
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue