mirror of
https://github.com/movie-web/providers.git
synced 2025-09-13 13:33:25 +00:00
add smashystream
This commit is contained in:
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@movie-web/providers",
|
"name": "@movie-web/providers",
|
||||||
"version": "1.1.4",
|
"version": "1.1.5",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@movie-web/providers",
|
"name": "@movie-web/providers",
|
||||||
"version": "1.1.4",
|
"version": "1.1.5",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cheerio": "^1.0.0-rc.12",
|
"cheerio": "^1.0.0-rc.12",
|
||||||
|
@@ -13,7 +13,10 @@ import { remotestreamScraper } from '@/providers/sources/remotestream';
|
|||||||
import { superStreamScraper } from '@/providers/sources/superstream/index';
|
import { superStreamScraper } from '@/providers/sources/superstream/index';
|
||||||
import { zoechipScraper } from '@/providers/sources/zoechip';
|
import { zoechipScraper } from '@/providers/sources/zoechip';
|
||||||
|
|
||||||
|
import { smashyStreamDScraper } from './embeds/smashystream/dued';
|
||||||
|
import { smashyStreamFScraper } from './embeds/smashystream/video1';
|
||||||
import { showBoxScraper } from './sources/showbox';
|
import { showBoxScraper } from './sources/showbox';
|
||||||
|
import { smashyStreamScraper } from './sources/smashystream';
|
||||||
|
|
||||||
export function gatherAllSources(): Array<Sourcerer> {
|
export function gatherAllSources(): Array<Sourcerer> {
|
||||||
// all sources are gathered here
|
// all sources are gathered here
|
||||||
@@ -26,10 +29,20 @@ export function gatherAllSources(): Array<Sourcerer> {
|
|||||||
zoechipScraper,
|
zoechipScraper,
|
||||||
lookmovieScraper,
|
lookmovieScraper,
|
||||||
showBoxScraper,
|
showBoxScraper,
|
||||||
|
smashyStreamScraper,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function gatherAllEmbeds(): Array<Embed> {
|
export function gatherAllEmbeds(): Array<Embed> {
|
||||||
// all embeds are gathered here
|
// 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 { Flags } from '@/main/targets';
|
||||||
import { Stream } from '@/providers/streams';
|
import { Stream } from '@/providers/streams';
|
||||||
import { EmbedScrapeContext, ScrapeContext } from '@/utils/context';
|
import { EmbedScrapeContext, MovieScrapeContext, ShowScrapeContext } from '@/utils/context';
|
||||||
|
|
||||||
export type SourcererOutput = {
|
export type SourcererEmbed = {
|
||||||
embeds: {
|
|
||||||
embedId: string;
|
embedId: string;
|
||||||
url: string;
|
url: string;
|
||||||
}[];
|
};
|
||||||
|
|
||||||
|
export type SourcererOutput = {
|
||||||
|
embeds: SourcererEmbed[];
|
||||||
stream?: Stream;
|
stream?: Stream;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -17,8 +18,8 @@ export type Sourcerer = {
|
|||||||
rank: number; // the higher the number, the earlier it gets put on the queue
|
rank: number; // the higher the number, the earlier it gets put on the queue
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
flags: Flags[];
|
flags: Flags[];
|
||||||
scrapeMovie?: (input: ScrapeContext & { media: MovieMedia }) => Promise<SourcererOutput>;
|
scrapeMovie?: (input: MovieScrapeContext) => Promise<SourcererOutput>;
|
||||||
scrapeShow?: (input: ScrapeContext & { media: ShowMedia }) => Promise<SourcererOutput>;
|
scrapeShow?: (input: ShowScrapeContext) => Promise<SourcererOutput>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function makeSourcerer(state: Sourcerer): Sourcerer {
|
export function makeSourcerer(state: Sourcerer): Sourcerer {
|
||||||
|
68
src/providers/embeds/smashystream/dued.ts
Normal file
68
src/providers/embeds/smashystream/dued.ts
Normal file
@@ -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: 410,
|
||||||
|
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: [],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
51
src/providers/embeds/smashystream/video1.ts
Normal file
51
src/providers/embeds/smashystream/video1.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import { flags } from '@/main/targets';
|
||||||
|
import { makeEmbed } from '@/providers/base';
|
||||||
|
import { Caption } from '@/providers/captions';
|
||||||
|
|
||||||
|
type FPlayerResponse = {
|
||||||
|
sourceUrls: string[];
|
||||||
|
subtitleUrls: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const smashyStreamFScraper = makeEmbed({
|
||||||
|
id: 'smashystream-f',
|
||||||
|
name: 'SmashyStream (F)',
|
||||||
|
rank: 400,
|
||||||
|
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) {
|
||||||
|
return {
|
||||||
|
url: url.replace(',', ''),
|
||||||
|
language,
|
||||||
|
kind: 'subtitles',
|
||||||
|
type: url.includes('.vtt') ? 'vtt' : 'srt',
|
||||||
|
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 { flags } from '@/main/targets';
|
||||||
import { SourcererOutput, makeSourcerer } from '@/providers/base';
|
import { SourcererOutput, makeSourcerer } from '@/providers/base';
|
||||||
|
import { MovieScrapeContext, ShowScrapeContext } from '@/utils/context';
|
||||||
import { NotFoundError } from '@/utils/errors';
|
import { NotFoundError } from '@/utils/errors';
|
||||||
|
|
||||||
import { scrape, searchAndFindMedia } from './util';
|
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);
|
const lookmovieData = await searchAndFindMedia(ctx, ctx.media);
|
||||||
if (!lookmovieData) throw new NotFoundError('Media not found');
|
if (!lookmovieData) throw new NotFoundError('Media not found');
|
||||||
|
|
||||||
|
64
src/providers/sources/smashystream/index.ts
Normal file
64
src/providers/sources/smashystream/index.ts
Normal file
@@ -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: 400,
|
||||||
|
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 { mixdropScraper } from '@/providers/embeds/mixdrop';
|
||||||
import { upcloudScraper } from '@/providers/embeds/upcloud';
|
import { upcloudScraper } from '@/providers/embeds/upcloud';
|
||||||
import { upstreamScraper } from '@/providers/embeds/upstream';
|
import { upstreamScraper } from '@/providers/embeds/upstream';
|
||||||
import { getZoeChipSourceURL, getZoeChipSources } from '@/providers/sources/zoechip/scrape';
|
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 const zoeBase = 'https://zoechip.cc';
|
||||||
|
|
||||||
export type MovieContext = ScrapeContext & {
|
|
||||||
media: MovieMedia;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ShowContext = ScrapeContext & {
|
|
||||||
media: ShowMedia;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ZoeChipSourceDetails = {
|
export type ZoeChipSourceDetails = {
|
||||||
type: string; // Only seen "iframe" so far
|
type: string; // Only seen "iframe" so far
|
||||||
link: string;
|
link: string;
|
||||||
@@ -23,7 +14,10 @@ export type ZoeChipSourceDetails = {
|
|||||||
title: string;
|
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);
|
const link = await getZoeChipSourceURL(ctx, source.episodeId);
|
||||||
if (link) {
|
if (link) {
|
||||||
const embed = {
|
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 sources = await getZoeChipSources(ctx, id);
|
||||||
const embeds: {
|
const embeds: {
|
||||||
embedId: string;
|
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 { getZoeChipMovieID } from '@/providers/sources/zoechip/search';
|
||||||
|
import { MovieScrapeContext } from '@/utils/context';
|
||||||
import { NotFoundError } from '@/utils/errors';
|
import { NotFoundError } from '@/utils/errors';
|
||||||
|
|
||||||
export async function scrapeMovie(ctx: MovieContext) {
|
export async function scrapeMovie(ctx: MovieScrapeContext) {
|
||||||
const movieID = await getZoeChipMovieID(ctx, ctx.media);
|
const movieID = await getZoeChipMovieID(ctx, ctx.media);
|
||||||
if (!movieID) {
|
if (!movieID) {
|
||||||
throw new NotFoundError('no search results match');
|
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 { getZoeChipEpisodeID, getZoeChipSeasonID } from '@/providers/sources/zoechip/scrape';
|
||||||
import { getZoeChipShowID } from '@/providers/sources/zoechip/search';
|
import { getZoeChipShowID } from '@/providers/sources/zoechip/search';
|
||||||
|
import { ShowScrapeContext } from '@/utils/context';
|
||||||
import { NotFoundError } from '@/utils/errors';
|
import { NotFoundError } from '@/utils/errors';
|
||||||
|
|
||||||
export async function scrapeShow(ctx: ShowContext) {
|
export async function scrapeShow(ctx: ShowScrapeContext) {
|
||||||
const showID = await getZoeChipShowID(ctx, ctx.media);
|
const showID = await getZoeChipShowID(ctx, ctx.media);
|
||||||
if (!showID) {
|
if (!showID) {
|
||||||
throw new NotFoundError('no search results match');
|
throw new NotFoundError('no search results match');
|
||||||
|
@@ -1,10 +1,10 @@
|
|||||||
import { load } from 'cheerio';
|
import { load } from 'cheerio';
|
||||||
|
|
||||||
import { ShowMedia } from '@/main/media';
|
import { ShowMedia } from '@/main/media';
|
||||||
import { MovieContext, ShowContext, ZoeChipSourceDetails, zoeBase } from '@/providers/sources/zoechip/common';
|
import { ZoeChipSourceDetails, zoeBase } from '@/providers/sources/zoechip/common';
|
||||||
import { ScrapeContext } from '@/utils/context';
|
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
|
// Movies use /ajax/episode/list/ID
|
||||||
// Shows use /ajax/episode/servers/ID
|
// Shows use /ajax/episode/servers/ID
|
||||||
const endpoint = ctx.media.type === 'movie' ? 'list' : 'servers';
|
const endpoint = ctx.media.type === 'movie' ? 'list' : 'servers';
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
import { UseableFetcher } from '@/fetchers/types';
|
import { UseableFetcher } from '@/fetchers/types';
|
||||||
|
import { MovieMedia, ShowMedia } from '@/main/media';
|
||||||
|
|
||||||
export type ScrapeContext = {
|
export type ScrapeContext = {
|
||||||
proxiedFetcher: <T>(...params: Parameters<UseableFetcher<T>>) => ReturnType<UseableFetcher<T>>;
|
proxiedFetcher: <T>(...params: Parameters<UseableFetcher<T>>) => ReturnType<UseableFetcher<T>>;
|
||||||
@@ -11,3 +12,11 @@ export type EmbedInput = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type EmbedScrapeContext = EmbedInput & ScrapeContext;
|
export type EmbedScrapeContext = EmbedInput & ScrapeContext;
|
||||||
|
|
||||||
|
export type MovieScrapeContext = ScrapeContext & {
|
||||||
|
media: MovieMedia;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ShowScrapeContext = ScrapeContext & {
|
||||||
|
media: ShowMedia;
|
||||||
|
};
|
||||||
|
Reference in New Issue
Block a user