add smashystream

This commit is contained in:
Jorrin 2023-12-17 16:22:42 +01:00
parent 0dc7baeca7
commit 9cdc9b1fad
12 changed files with 234 additions and 32 deletions

4
package-lock.json generated
View File

@ -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",

View File

@ -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,
];
}

View File

@ -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 SourcererEmbed = {
embedId: string;
url: string;
};
export type SourcererOutput = {
embeds: {
embedId: string;
url: string;
}[];
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 {

View 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: [],
},
};
},
});

View 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,
},
};
},
});

View File

@ -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');

View 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,
});

View File

@ -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;

View File

@ -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');

View File

@ -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');

View File

@ -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';

View File

@ -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;
};