A small web app for watching movies and shows easily
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

250 lines
7.4 KiB

import { registerProvider } from "@/backend/helpers/register";
import { MWMediaType } from "@/backend/metadata/types";
import { conf } from "@/setup/config";
import { customAlphabet } from "nanoid";
// import toWebVTT from "srt-webvtt";
import CryptoJS from "crypto-js";
import { proxiedFetch } from "@/backend/helpers/fetch";
import { MWStreamQuality, MWStreamType } from "@/backend/helpers/streams";
import { MetadataSchema } from "hls.js";
const nanoid = customAlphabet("0123456789abcdef", 32);
const qualityMap = {
"360p": MWStreamQuality.Q360P,
"480p": MWStreamQuality.Q480P,
"720p": MWStreamQuality.Q720P,
"1080p": MWStreamQuality.Q1080P,
};
type QualityInMap = keyof typeof qualityMap;
// 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 proxiedFetch<any>(requestUrl, {
method: "POST",
parseResponse: JSON.parse,
headers: {
Platform: "android",
"Content-Type": "application/x-www-form-urlencoded",
},
body: `${formatted.toString()}&token${nanoid()}`,
});
};
// Find best resolution
const getBestQuality = (list: any[]) => {
return (
list.find((quality: any) => quality.quality === "1080p" && quality.path) ??
list.find((quality: any) => quality.quality === "720p" && quality.path) ??
list.find((quality: any) => quality.quality === "480p" && quality.path) ??
list.find((quality: any) => quality.quality === "360p" && quality.path)
);
};
registerProvider({
id: "superstream",
displayName: "Superstream",
rank: 200,
type: [MWMediaType.MOVIE, MWMediaType.SERIES],
async scrape({ media, episode, progress }) {
// Find Superstream ID for show
const searchQuery = {
module: "Search3",
page: "1",
type: "all",
keyword: media.meta.title,
pagelimit: "20",
};
const searchRes = (await get(searchQuery, true)).data;
progress(33);
// TODO: add fuzzy search and normalise strings before matching
const superstreamEntry = searchRes.find(
(res: any) =>
res.title === media.meta.title && res.year === Number(media.meta.year)
);
if (!superstreamEntry) throw new Error("No entry found on SuperStream");
const superstreamId = superstreamEntry.id;
// Movie logic
if (media.meta.type === MWMediaType.MOVIE) {
const apiQuery = {
uid: "",
module: "Movie_downloadurl_v3",
mid: superstreamId,
oss: "1",
group: "",
};
const mediaRes = (await get(apiQuery)).data;
progress(50);
const hdQuality = getBestQuality(mediaRes.list);
if (!hdQuality) throw new Error("No quality could be found.");
console.log(hdQuality);
// const subtitleApiQuery = {
// fid: hdQuality.fid,
// uid: "",
// module: "Movie_srt_list_v2",
// mid: tmdbId,
// };
// const subtitleRes = (await get(subtitleApiQuery).then((r) => r.json()))
// .data;
// const mappedCaptions = await Promise.all(
// subtitleRes.list.map(async (subtitle: any) => {
// const captionBlob = await fetch(
// `${conf().CORS_PROXY_URL}${subtitle.subtitles[0].file_path}`
// ).then((captionRes) => captionRes.blob()); // cross-origin bypass
// const captionUrl = await toWebVTT(captionBlob); // convert to vtt so it's playable
// return {
// id: subtitle.language,
// url: captionUrl,
// label: subtitle.language,
// };
// })
// );
return {
embeds: [],
stream: {
streamUrl: hdQuality.path,
quality: qualityMap[hdQuality.quality as QualityInMap],
type: MWStreamType.MP4,
},
};
}
if (media.meta.type !== MWMediaType.SERIES)
throw new Error("Unsupported type");
// Fetch requested episode
const apiQuery = {
uid: "",
module: "TV_downloadurl_v3",
tid: superstreamId,
season: media.meta.seasonData.number.toString(),
episode: (
media.meta.seasonData.episodes.find(
(episodeInfo) => episodeInfo.id === episode
)?.number ?? 1
).toString(),
oss: "1",
group: "",
};
const mediaRes = (await get(apiQuery)).data;
progress(66);
const hdQuality = getBestQuality(mediaRes.list);
if (!hdQuality) throw new Error("No quality could be found.");
// const subtitleApiQuery = {
// fid: hdQuality.fid,
// uid: "",
// module: "TV_srt_list_v2",
// episode: media.episodeId,
// tid: media.mediaId,
// season: media.seasonId,
// };
// const subtitleRes = (await get(subtitleApiQuery).then((r) => r.json()))
// .data;
// const mappedCaptions = await Promise.all(
// subtitleRes.list.map(async (subtitle: any) => {
// const captionBlob = await fetch(
// `${conf().CORS_PROXY_URL}${subtitle.subtitles[0].file_path}`
// ).then((captionRes) => captionRes.blob()); // cross-origin bypass
// const captionUrl = await toWebVTT(captionBlob); // convert to vtt so it's playable
// return {
// id: subtitle.language,
// url: captionUrl,
// label: subtitle.language,
// };
// })
// );
return {
embeds: [],
stream: {
quality: qualityMap[
hdQuality.quality as QualityInMap
] as MWStreamQuality,
streamUrl: hdQuality.path,
type: MWStreamType.MP4,
},
};
},
});