4 changed files with 251 additions and 7 deletions
@ -0,0 +1,236 @@
@@ -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,
|
||||
// })),
|
||||
// })),
|
||||
// };
|
||||
// },
|
||||
}; |
Loading…
Reference in new issue