From 9cdc9b1fadafc7b3196fb7f2338cdab1d212c9a9 Mon Sep 17 00:00:00 2001 From: Jorrin Date: Sun, 17 Dec 2023 16:22:42 +0100 Subject: [PATCH] add smashystream --- package-lock.json | 4 +- src/providers/all.ts | 15 +++- src/providers/base.ts | 17 ++--- src/providers/embeds/smashystream/dued.ts | 68 +++++++++++++++++++ src/providers/embeds/smashystream/video1.ts | 51 ++++++++++++++ src/providers/sources/lookmovie/index.ts | 4 +- src/providers/sources/smashystream/index.ts | 64 +++++++++++++++++ src/providers/sources/zoechip/common.ts | 18 ++--- src/providers/sources/zoechip/scrape-movie.ts | 5 +- src/providers/sources/zoechip/scrape-show.ts | 5 +- src/providers/sources/zoechip/scrape.ts | 6 +- src/utils/context.ts | 9 +++ 12 files changed, 234 insertions(+), 32 deletions(-) create mode 100644 src/providers/embeds/smashystream/dued.ts create mode 100644 src/providers/embeds/smashystream/video1.ts create mode 100644 src/providers/sources/smashystream/index.ts diff --git a/package-lock.json b/package-lock.json index 9231ec0..92f5028 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/src/providers/all.ts b/src/providers/all.ts index 8181e94..847ebf1 100644 --- a/src/providers/all.ts +++ b/src/providers/all.ts @@ -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 { // all sources are gathered here @@ -26,10 +29,20 @@ export function gatherAllSources(): Array { zoechipScraper, lookmovieScraper, showBoxScraper, + smashyStreamScraper, ]; } export function gatherAllEmbeds(): Array { // all embeds are gathered here - return [upcloudScraper, mp4uploadScraper, streamsbScraper, upstreamScraper, febBoxScraper, mixdropScraper]; + return [ + upcloudScraper, + mp4uploadScraper, + streamsbScraper, + upstreamScraper, + febBoxScraper, + mixdropScraper, + smashyStreamFScraper, + smashyStreamDScraper, + ]; } diff --git a/src/providers/base.ts b/src/providers/base.ts index 902a5a6..022cd87 100644 --- a/src/providers/base.ts +++ b/src/providers/base.ts @@ -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; - scrapeShow?: (input: ScrapeContext & { media: ShowMedia }) => Promise; + scrapeMovie?: (input: MovieScrapeContext) => Promise; + scrapeShow?: (input: ShowScrapeContext) => Promise; }; export function makeSourcerer(state: Sourcerer): Sourcerer { diff --git a/src/providers/embeds/smashystream/dued.ts b/src/providers/embeds/smashystream/dued.ts new file mode 100644 index 0000000..1dd223e --- /dev/null +++ b/src/providers/embeds/smashystream/dued.ts @@ -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(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(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(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( + `${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: [], + }, + }; + }, +}); diff --git a/src/providers/embeds/smashystream/video1.ts b/src/providers/embeds/smashystream/video1.ts new file mode 100644 index 0000000..6aaad4f --- /dev/null +++ b/src/providers/embeds/smashystream/video1.ts @@ -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(ctx.url, { + headers: { + Referer: ctx.url, + }, + }); + + const captions: Caption[] = + res.subtitleUrls + .match(/\[([^\]]+)\](https?:\/\/\S+?)(?=,\[|$)/g) + ?.map((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, + }, + }; + }, +}); diff --git a/src/providers/sources/lookmovie/index.ts b/src/providers/sources/lookmovie/index.ts index 101dae9..08e8e64 100644 --- a/src/providers/sources/lookmovie/index.ts +++ b/src/providers/sources/lookmovie/index.ts @@ -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 { +async function universalScraper(ctx: MovieScrapeContext | ShowScrapeContext): Promise { const lookmovieData = await searchAndFindMedia(ctx, ctx.media); if (!lookmovieData) throw new NotFoundError('Media not found'); diff --git a/src/providers/sources/smashystream/index.ts b/src/providers/sources/smashystream/index.ts new file mode 100644 index 0000000..663debb --- /dev/null +++ b/src/providers/sources/smashystream/index.ts @@ -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 => { + const mainPage = await ctx.proxiedFetcher('/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, +}); diff --git a/src/providers/sources/zoechip/common.ts b/src/providers/sources/zoechip/common.ts index 7ca1b2e..2454780 100644 --- a/src/providers/sources/zoechip/common.ts +++ b/src/providers/sources/zoechip/common.ts @@ -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; diff --git a/src/providers/sources/zoechip/scrape-movie.ts b/src/providers/sources/zoechip/scrape-movie.ts index 448af0b..86161fc 100644 --- a/src/providers/sources/zoechip/scrape-movie.ts +++ b/src/providers/sources/zoechip/scrape-movie.ts @@ -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'); diff --git a/src/providers/sources/zoechip/scrape-show.ts b/src/providers/sources/zoechip/scrape-show.ts index eb21d13..fe9f4eb 100644 --- a/src/providers/sources/zoechip/scrape-show.ts +++ b/src/providers/sources/zoechip/scrape-show.ts @@ -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'); diff --git a/src/providers/sources/zoechip/scrape.ts b/src/providers/sources/zoechip/scrape.ts index 5be0edd..126a3e0 100644 --- a/src/providers/sources/zoechip/scrape.ts +++ b/src/providers/sources/zoechip/scrape.ts @@ -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'; diff --git a/src/utils/context.ts b/src/utils/context.ts index 5a9664a..1a84253 100644 --- a/src/utils/context.ts +++ b/src/utils/context.ts @@ -1,4 +1,5 @@ import { UseableFetcher } from '@/fetchers/types'; +import { MovieMedia, ShowMedia } from '@/main/media'; export type ScrapeContext = { proxiedFetcher: (...params: Parameters>) => ReturnType>; @@ -11,3 +12,11 @@ export type EmbedInput = { }; export type EmbedScrapeContext = EmbedInput & ScrapeContext; + +export type MovieScrapeContext = ScrapeContext & { + media: MovieMedia; +}; + +export type ShowScrapeContext = ScrapeContext & { + media: ShowMedia; +};