mirror of
https://github.com/movie-web/movie-web.git
synced 2024-11-14 07:15:06 +01:00
📦 NEW: Superstream scraper for movies
This commit is contained in:
parent
ada19433f0
commit
3b1df3a417
@ -9,6 +9,7 @@
|
|||||||
"fuse.js": "^6.4.6",
|
"fuse.js": "^6.4.6",
|
||||||
"hls.js": "^1.0.7",
|
"hls.js": "^1.0.7",
|
||||||
"json5": "^2.2.0",
|
"json5": "^2.2.0",
|
||||||
|
"nanoid": "^4.0.0",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"react-router-dom": "^5.2.0",
|
"react-router-dom": "^5.2.0",
|
||||||
@ -38,8 +39,8 @@
|
|||||||
"@types/node": "^17.0.15",
|
"@types/node": "^17.0.15",
|
||||||
"@types/react": "^17.0.39",
|
"@types/react": "^17.0.39",
|
||||||
"@types/react-dom": "^17.0.11",
|
"@types/react-dom": "^17.0.11",
|
||||||
"@types/react-router-dom": "^5.3.3",
|
|
||||||
"@types/react-router": "^5.1.18",
|
"@types/react-router": "^5.1.18",
|
||||||
|
"@types/react-router-dom": "^5.3.3",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.13.0",
|
"@typescript-eslint/eslint-plugin": "^5.13.0",
|
||||||
"@typescript-eslint/parser": "^5.13.0",
|
"@typescript-eslint/parser": "^5.13.0",
|
||||||
"autoprefixer": "^10.4.2",
|
"autoprefixer": "^10.4.2",
|
||||||
|
236
src/providers/list/superstream/index.ts
Normal file
236
src/providers/list/superstream/index.ts
Normal file
@ -0,0 +1,236 @@
|
|||||||
|
// this scraper was taken from recloudstream/cloudstream-extensions. much love
|
||||||
|
import {
|
||||||
|
MWMediaProvider,
|
||||||
|
MWMediaType,
|
||||||
|
MWPortableMedia,
|
||||||
|
MWMediaStream,
|
||||||
|
MWQuery,
|
||||||
|
MWMediaSeasons,
|
||||||
|
MWProviderMediaResult,
|
||||||
|
} from "providers/types";
|
||||||
|
import { CORS_PROXY_URL } from "mw_constants";
|
||||||
|
import { customAlphabet } from "nanoid";
|
||||||
|
import CryptoJS from "crypto-js";
|
||||||
|
|
||||||
|
const nanoid = customAlphabet("0123456789abcdef", 32);
|
||||||
|
// CONSTANTS, read below (taken from og)
|
||||||
|
// We do not want content scanners to notice this scraping going on so we've hidden all constants
|
||||||
|
// The source has its origins in China so I added some extra security with banned words
|
||||||
|
// Mayhaps a tiny bit unethical, but this source is just too good :)
|
||||||
|
// If you are copying this code please use precautions so they do not change their api.
|
||||||
|
const iv = atob("d0VpcGhUbiE=");
|
||||||
|
const key = atob("MTIzZDZjZWRmNjI2ZHk1NDIzM2FhMXc2");
|
||||||
|
const apiUrls = [
|
||||||
|
atob("aHR0cHM6Ly9zaG93Ym94LnNoZWd1Lm5ldC9hcGkvYXBpX2NsaWVudC9pbmRleC8="),
|
||||||
|
atob("aHR0cHM6Ly9tYnBhcGkuc2hlZ3UubmV0L2FwaS9hcGlfY2xpZW50L2luZGV4Lw=="),
|
||||||
|
];
|
||||||
|
const appKey = atob("bW92aWVib3g=");
|
||||||
|
const appId = atob("Y29tLnRkby5zaG93Ym94");
|
||||||
|
|
||||||
|
// cryptography stuff
|
||||||
|
const crypto = {
|
||||||
|
encrypt(str: string) {
|
||||||
|
return CryptoJS.TripleDES.encrypt(str, CryptoJS.enc.Utf8.parse(key), {
|
||||||
|
iv: CryptoJS.enc.Utf8.parse(iv),
|
||||||
|
}).toString();
|
||||||
|
},
|
||||||
|
getVerify(str: string, str2: string, str3: string) {
|
||||||
|
if (str) {
|
||||||
|
return CryptoJS.MD5(
|
||||||
|
CryptoJS.MD5(str2).toString() + str3 + str,
|
||||||
|
).toString();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// get expire time
|
||||||
|
const expiry = () => Math.floor(Date.now() / 1000 + 60 * 60 * 12);
|
||||||
|
|
||||||
|
// sending requests
|
||||||
|
const get = (data: object, altApi = false) => {
|
||||||
|
const defaultData = {
|
||||||
|
childmode: "0",
|
||||||
|
app_version: "11.5",
|
||||||
|
appid: appId,
|
||||||
|
lang: "en",
|
||||||
|
expired_date: `${expiry()}`,
|
||||||
|
platform: "android",
|
||||||
|
channel: "Website",
|
||||||
|
};
|
||||||
|
const encryptedData = crypto.encrypt(
|
||||||
|
JSON.stringify({
|
||||||
|
...defaultData,
|
||||||
|
...data,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
const appKeyHash = CryptoJS.MD5(appKey).toString();
|
||||||
|
const verify = crypto.getVerify(encryptedData, appKey, key);
|
||||||
|
const body = JSON.stringify({
|
||||||
|
app_key: appKeyHash,
|
||||||
|
verify,
|
||||||
|
encrypt_data: encryptedData,
|
||||||
|
});
|
||||||
|
const b64Body = btoa(body);
|
||||||
|
|
||||||
|
const formatted = new URLSearchParams();
|
||||||
|
formatted.append("data", b64Body);
|
||||||
|
formatted.append("appid", "27");
|
||||||
|
formatted.append("platform", "android");
|
||||||
|
formatted.append("version", "129");
|
||||||
|
formatted.append("medium", "Website");
|
||||||
|
|
||||||
|
const requestUrl = altApi ? apiUrls[1] : apiUrls[0];
|
||||||
|
return fetch(`${CORS_PROXY_URL}${requestUrl}`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
Platform: "android",
|
||||||
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
|
},
|
||||||
|
body: `${formatted.toString()}&token${nanoid()}`,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const superStreamScraper: MWMediaProvider = {
|
||||||
|
id: "superstream",
|
||||||
|
enabled: true,
|
||||||
|
// type: [MWMediaType.MOVIE, MWMediaType.SERIES],
|
||||||
|
type: [MWMediaType.MOVIE],
|
||||||
|
displayName: "superstream",
|
||||||
|
|
||||||
|
async getMediaFromPortable(
|
||||||
|
media: MWPortableMedia,
|
||||||
|
): Promise<MWProviderMediaResult> {
|
||||||
|
let apiQuery: any;
|
||||||
|
if (media.episodeId) {
|
||||||
|
apiQuery = {
|
||||||
|
module: "TV_detail_1",
|
||||||
|
display_all: "1",
|
||||||
|
tid: media.mediaId,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
apiQuery = {
|
||||||
|
module: "Movie_detail",
|
||||||
|
mid: media.mediaId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const detailRes = (await get(apiQuery, true).then((r) => r.json())).data;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...media,
|
||||||
|
title: detailRes.title,
|
||||||
|
year: detailRes.year,
|
||||||
|
seasonCount: detailRes?.season?.length,
|
||||||
|
} as MWProviderMediaResult;
|
||||||
|
},
|
||||||
|
|
||||||
|
async searchForMedia(query: MWQuery): Promise<MWProviderMediaResult[]> {
|
||||||
|
const apiQuery = {
|
||||||
|
module: "Search3",
|
||||||
|
page: "1",
|
||||||
|
type: "all",
|
||||||
|
keyword: query.searchQuery,
|
||||||
|
pagelimit: "20",
|
||||||
|
};
|
||||||
|
const searchRes = (await get(apiQuery, true).then((r) => r.json())).data;
|
||||||
|
|
||||||
|
const movieResults: MWProviderMediaResult[] = (searchRes || [])
|
||||||
|
.filter((item: any) => item.box_type === 1)
|
||||||
|
.map((item: any) => ({
|
||||||
|
title: item.title,
|
||||||
|
year: item.year,
|
||||||
|
mediaId: item.id,
|
||||||
|
}));
|
||||||
|
const seriesResults: MWProviderMediaResult[] = (searchRes || [])
|
||||||
|
.filter((item: any) => item.box_type === 2)
|
||||||
|
.map((item: any) => ({
|
||||||
|
title: item.title,
|
||||||
|
year: item.year,
|
||||||
|
mediaId: item.id,
|
||||||
|
seasonId: 1,
|
||||||
|
episodeId: 1,
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (query.type === "movie") {
|
||||||
|
return movieResults;
|
||||||
|
}
|
||||||
|
return seriesResults;
|
||||||
|
},
|
||||||
|
|
||||||
|
async getStream(media: MWPortableMedia): Promise<MWMediaStream> {
|
||||||
|
if (media.mediaType === MWMediaType.MOVIE) {
|
||||||
|
const apiQuery = {
|
||||||
|
uid: "",
|
||||||
|
module: "Movie_downloadurl_v3",
|
||||||
|
mid: media.mediaId,
|
||||||
|
oss: "1",
|
||||||
|
group: "",
|
||||||
|
};
|
||||||
|
const mediaRes = (await get(apiQuery).then((r) => r.json())).data;
|
||||||
|
const hdQuality =
|
||||||
|
mediaRes.list.find((quality: any) => quality.quality === "1080p") ??
|
||||||
|
mediaRes.list.find((quality: any) => quality.quality === "720p");
|
||||||
|
|
||||||
|
return { url: hdQuality.path, type: "mp4", captions: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
const apiQuery = {
|
||||||
|
uid: "",
|
||||||
|
module: "TV_downloadurl_v3",
|
||||||
|
episode: media.episodeId,
|
||||||
|
tid: media.mediaId,
|
||||||
|
season: media.seasonId,
|
||||||
|
oss: "1",
|
||||||
|
group: "",
|
||||||
|
};
|
||||||
|
const mediaRes = (await get(apiQuery).then((r) => r.json())).data;
|
||||||
|
const hdQuality =
|
||||||
|
mediaRes.list.find((quality: any) => quality.quality === "1080p") ??
|
||||||
|
mediaRes.list.find((quality: any) => quality.quality === "720p");
|
||||||
|
|
||||||
|
return { url: hdQuality.path, type: "mp4", captions: [] };
|
||||||
|
},
|
||||||
|
// async getSeasonDataFromMedia(
|
||||||
|
// media: MWPortableMedia,
|
||||||
|
// ): Promise<MWMediaSeasons> {
|
||||||
|
// const allSeasonEpisodes = [];
|
||||||
|
// const apiQuery = {
|
||||||
|
// module: "TV_detail_1",
|
||||||
|
// display_all: "1",
|
||||||
|
// tid: media.mediaId,
|
||||||
|
// };
|
||||||
|
// const detailRes = (await get(apiQuery, true).then((r) => r.json())).data;
|
||||||
|
// allSeasonEpisodes.push(...detailRes.episode);
|
||||||
|
|
||||||
|
// if (detailRes.seasons.length > 1) {
|
||||||
|
// for (const season of detailRes.seasons.slice(1)) {
|
||||||
|
// const seasonApiQuery = {
|
||||||
|
// module: "TV_detail_1",
|
||||||
|
// season: season.toString(),
|
||||||
|
// display_all: "1",
|
||||||
|
// tid: media.mediaId,
|
||||||
|
// };
|
||||||
|
// const seasonRes = (
|
||||||
|
// await get(seasonApiQuery, true).then((r) => r.json())
|
||||||
|
// ).data;
|
||||||
|
// allSeasonEpisodes.push(...seasonRes.episode);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return {
|
||||||
|
// seasons: detailRes.season.map((season: number) => ({
|
||||||
|
// sort: season,
|
||||||
|
// id: season.toString(),
|
||||||
|
// type: season === 0 ? "special" : "season",
|
||||||
|
// episodes: detailRes.episode
|
||||||
|
// .filter((episode: any) => episode.season === season)
|
||||||
|
// .map((episode: any) => ({
|
||||||
|
// title: episode.title,
|
||||||
|
// sort: episode.episode,
|
||||||
|
// id: episode.episode.toString(),
|
||||||
|
// episodeNumber: episode.episode,
|
||||||
|
// })),
|
||||||
|
// })),
|
||||||
|
// };
|
||||||
|
// },
|
||||||
|
};
|
@ -4,8 +4,10 @@ import { MWWrappedMediaProvider, WrapProvider } from "providers/wrapper";
|
|||||||
import { gomostreamScraper } from "providers/list/gomostream";
|
import { gomostreamScraper } from "providers/list/gomostream";
|
||||||
import { xemovieScraper } from "providers/list/xemovie";
|
import { xemovieScraper } from "providers/list/xemovie";
|
||||||
import { flixhqProvider } from "providers/list/flixhq";
|
import { flixhqProvider } from "providers/list/flixhq";
|
||||||
|
import { superStreamScraper } from "providers/list/superstream";
|
||||||
|
|
||||||
export const mediaProvidersUnchecked: MWWrappedMediaProvider[] = [
|
export const mediaProvidersUnchecked: MWWrappedMediaProvider[] = [
|
||||||
|
WrapProvider(superStreamScraper),
|
||||||
WrapProvider(theFlixScraper),
|
WrapProvider(theFlixScraper),
|
||||||
WrapProvider(gDrivePlayerScraper),
|
WrapProvider(gDrivePlayerScraper),
|
||||||
WrapProvider(gomostreamScraper),
|
WrapProvider(gomostreamScraper),
|
||||||
|
@ -5979,6 +5979,11 @@ nanoid@^3.3.3:
|
|||||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab"
|
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab"
|
||||||
integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==
|
integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==
|
||||||
|
|
||||||
|
nanoid@^4.0.0:
|
||||||
|
version "4.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-4.0.0.tgz#6e144dee117609232c3f415c34b0e550e64999a5"
|
||||||
|
integrity sha512-IgBP8piMxe/gf73RTQx7hmnhwz0aaEXYakvqZyE302IXW3HyVNhdNGC+O2MwMAVhLEnvXlvKtGbtJf6wvHihCg==
|
||||||
|
|
||||||
natural-compare@^1.4.0:
|
natural-compare@^1.4.0:
|
||||||
version "1.4.0"
|
version "1.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
|
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
|
||||||
|
Loading…
Reference in New Issue
Block a user