|
|
@ -1,6 +1,10 @@ |
|
|
|
// this is derived from https://github.com/recloudstream/cloudstream-extensions
|
|
|
|
// this is derived from https://github.com/recloudstream/cloudstream-extensions
|
|
|
|
// for more info please check the LICENSE file in the same directory
|
|
|
|
// for more info please check the LICENSE file in the same directory
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import { customAlphabet } from "nanoid"; |
|
|
|
|
|
|
|
import toWebVTT from "srt-webvtt"; |
|
|
|
|
|
|
|
import CryptoJS from "crypto-js"; |
|
|
|
|
|
|
|
import { CORS_PROXY_URL, TMDB_API_KEY } from "@/mw_constants"; |
|
|
|
import { |
|
|
|
import { |
|
|
|
MWMediaProvider, |
|
|
|
MWMediaProvider, |
|
|
|
MWMediaType, |
|
|
|
MWMediaType, |
|
|
@ -9,11 +13,7 @@ import { |
|
|
|
MWQuery, |
|
|
|
MWQuery, |
|
|
|
MWMediaSeasons, |
|
|
|
MWMediaSeasons, |
|
|
|
MWProviderMediaResult, |
|
|
|
MWProviderMediaResult, |
|
|
|
} from "providers/types"; |
|
|
|
} from "@/providers/types"; |
|
|
|
import { CORS_PROXY_URL, TMDB_API_KEY } from "mw_constants"; |
|
|
|
|
|
|
|
import { customAlphabet } from "nanoid"; |
|
|
|
|
|
|
|
import toWebVTT from "srt-webvtt"; |
|
|
|
|
|
|
|
import CryptoJS from "crypto-js"; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const nanoid = customAlphabet("0123456789abcdef", 32); |
|
|
|
const nanoid = customAlphabet("0123456789abcdef", 32); |
|
|
|
|
|
|
|
|
|
|
@ -41,7 +41,7 @@ const crypto = { |
|
|
|
getVerify(str: string, str2: string, str3: string) { |
|
|
|
getVerify(str: string, str2: string, str3: string) { |
|
|
|
if (str) { |
|
|
|
if (str) { |
|
|
|
return CryptoJS.MD5( |
|
|
|
return CryptoJS.MD5( |
|
|
|
CryptoJS.MD5(str2).toString() + str3 + str, |
|
|
|
CryptoJS.MD5(str2).toString() + str3 + str |
|
|
|
).toString(); |
|
|
|
).toString(); |
|
|
|
} |
|
|
|
} |
|
|
|
return null; |
|
|
|
return null; |
|
|
@ -66,7 +66,7 @@ const get = (data: object, altApi = false) => { |
|
|
|
JSON.stringify({ |
|
|
|
JSON.stringify({ |
|
|
|
...defaultData, |
|
|
|
...defaultData, |
|
|
|
...data, |
|
|
|
...data, |
|
|
|
}), |
|
|
|
}) |
|
|
|
); |
|
|
|
); |
|
|
|
const appKeyHash = CryptoJS.MD5(appKey).toString(); |
|
|
|
const appKeyHash = CryptoJS.MD5(appKey).toString(); |
|
|
|
const verify = crypto.getVerify(encryptedData, appKey, key); |
|
|
|
const verify = crypto.getVerify(encryptedData, appKey, key); |
|
|
@ -102,7 +102,7 @@ export const superStreamScraper: MWMediaProvider = { |
|
|
|
displayName: "SuperStream", |
|
|
|
displayName: "SuperStream", |
|
|
|
|
|
|
|
|
|
|
|
async getMediaFromPortable( |
|
|
|
async getMediaFromPortable( |
|
|
|
media: MWPortableMedia, |
|
|
|
media: MWPortableMedia |
|
|
|
): Promise<MWProviderMediaResult> { |
|
|
|
): Promise<MWProviderMediaResult> { |
|
|
|
let apiQuery: any; |
|
|
|
let apiQuery: any; |
|
|
|
if (media.mediaType === MWMediaType.SERIES) { |
|
|
|
if (media.mediaType === MWMediaType.SERIES) { |
|
|
@ -174,10 +174,18 @@ export const superStreamScraper: MWMediaProvider = { |
|
|
|
}; |
|
|
|
}; |
|
|
|
const mediaRes = (await get(apiQuery).then((r) => r.json())).data; |
|
|
|
const mediaRes = (await get(apiQuery).then((r) => r.json())).data; |
|
|
|
const hdQuality = |
|
|
|
const hdQuality = |
|
|
|
mediaRes.list.find((quality: any) => (quality.quality === "1080p" && quality.path)) ?? |
|
|
|
mediaRes.list.find( |
|
|
|
mediaRes.list.find((quality: any) => (quality.quality === "720p" && quality.path)) ?? |
|
|
|
(quality: any) => quality.quality === "1080p" && quality.path |
|
|
|
mediaRes.list.find((quality: any) => (quality.quality === "480p" && quality.path)) ?? |
|
|
|
) ?? |
|
|
|
mediaRes.list.find((quality: any) => (quality.quality === "360p" && quality.path)); |
|
|
|
mediaRes.list.find( |
|
|
|
|
|
|
|
(quality: any) => quality.quality === "720p" && quality.path |
|
|
|
|
|
|
|
) ?? |
|
|
|
|
|
|
|
mediaRes.list.find( |
|
|
|
|
|
|
|
(quality: any) => quality.quality === "480p" && quality.path |
|
|
|
|
|
|
|
) ?? |
|
|
|
|
|
|
|
mediaRes.list.find( |
|
|
|
|
|
|
|
(quality: any) => quality.quality === "360p" && quality.path |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
if (!hdQuality) throw new Error("No quality could be found."); |
|
|
|
if (!hdQuality) throw new Error("No quality could be found."); |
|
|
|
|
|
|
|
|
|
|
@ -192,7 +200,7 @@ export const superStreamScraper: MWMediaProvider = { |
|
|
|
const mappedCaptions = await Promise.all( |
|
|
|
const mappedCaptions = await Promise.all( |
|
|
|
subtitleRes.list.map(async (subtitle: any) => { |
|
|
|
subtitleRes.list.map(async (subtitle: any) => { |
|
|
|
const captionBlob = await fetch( |
|
|
|
const captionBlob = await fetch( |
|
|
|
`${CORS_PROXY_URL}${subtitle.subtitles[0].file_path}`, |
|
|
|
`${CORS_PROXY_URL}${subtitle.subtitles[0].file_path}` |
|
|
|
).then((captionRes) => captionRes.blob()); // cross-origin bypass
|
|
|
|
).then((captionRes) => captionRes.blob()); // cross-origin bypass
|
|
|
|
const captionUrl = await toWebVTT(captionBlob); // convert to vtt so it's playable
|
|
|
|
const captionUrl = await toWebVTT(captionBlob); // convert to vtt so it's playable
|
|
|
|
return { |
|
|
|
return { |
|
|
@ -200,7 +208,7 @@ export const superStreamScraper: MWMediaProvider = { |
|
|
|
url: captionUrl, |
|
|
|
url: captionUrl, |
|
|
|
label: subtitle.language, |
|
|
|
label: subtitle.language, |
|
|
|
}; |
|
|
|
}; |
|
|
|
}), |
|
|
|
}) |
|
|
|
); |
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
return { url: hdQuality.path, type: "mp4", captions: mappedCaptions }; |
|
|
|
return { url: hdQuality.path, type: "mp4", captions: mappedCaptions }; |
|
|
@ -217,10 +225,18 @@ export const superStreamScraper: MWMediaProvider = { |
|
|
|
}; |
|
|
|
}; |
|
|
|
const mediaRes = (await get(apiQuery).then((r) => r.json())).data; |
|
|
|
const mediaRes = (await get(apiQuery).then((r) => r.json())).data; |
|
|
|
const hdQuality = |
|
|
|
const hdQuality = |
|
|
|
mediaRes.list.find((quality: any) => (quality.quality === "1080p" && quality.path)) ?? |
|
|
|
mediaRes.list.find( |
|
|
|
mediaRes.list.find((quality: any) => (quality.quality === "720p" && quality.path)) ?? |
|
|
|
(quality: any) => quality.quality === "1080p" && quality.path |
|
|
|
mediaRes.list.find((quality: any) => (quality.quality === "480p" && quality.path)) ?? |
|
|
|
) ?? |
|
|
|
mediaRes.list.find((quality: any) => (quality.quality === "360p" && quality.path)); |
|
|
|
mediaRes.list.find( |
|
|
|
|
|
|
|
(quality: any) => quality.quality === "720p" && quality.path |
|
|
|
|
|
|
|
) ?? |
|
|
|
|
|
|
|
mediaRes.list.find( |
|
|
|
|
|
|
|
(quality: any) => quality.quality === "480p" && quality.path |
|
|
|
|
|
|
|
) ?? |
|
|
|
|
|
|
|
mediaRes.list.find( |
|
|
|
|
|
|
|
(quality: any) => quality.quality === "360p" && quality.path |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
if (!hdQuality) throw new Error("No quality could be found."); |
|
|
|
if (!hdQuality) throw new Error("No quality could be found."); |
|
|
|
|
|
|
|
|
|
|
@ -237,7 +253,7 @@ export const superStreamScraper: MWMediaProvider = { |
|
|
|
const mappedCaptions = await Promise.all( |
|
|
|
const mappedCaptions = await Promise.all( |
|
|
|
subtitleRes.list.map(async (subtitle: any) => { |
|
|
|
subtitleRes.list.map(async (subtitle: any) => { |
|
|
|
const captionBlob = await fetch( |
|
|
|
const captionBlob = await fetch( |
|
|
|
`${CORS_PROXY_URL}${subtitle.subtitles[0].file_path}`, |
|
|
|
`${CORS_PROXY_URL}${subtitle.subtitles[0].file_path}` |
|
|
|
).then((captionRes) => captionRes.blob()); // cross-origin bypass
|
|
|
|
).then((captionRes) => captionRes.blob()); // cross-origin bypass
|
|
|
|
const captionUrl = await toWebVTT(captionBlob); // convert to vtt so it's playable
|
|
|
|
const captionUrl = await toWebVTT(captionBlob); // convert to vtt so it's playable
|
|
|
|
return { |
|
|
|
return { |
|
|
@ -245,13 +261,13 @@ export const superStreamScraper: MWMediaProvider = { |
|
|
|
url: captionUrl, |
|
|
|
url: captionUrl, |
|
|
|
label: subtitle.language, |
|
|
|
label: subtitle.language, |
|
|
|
}; |
|
|
|
}; |
|
|
|
}), |
|
|
|
}) |
|
|
|
); |
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
return { url: hdQuality.path, type: "mp4", captions: mappedCaptions }; |
|
|
|
return { url: hdQuality.path, type: "mp4", captions: mappedCaptions }; |
|
|
|
}, |
|
|
|
}, |
|
|
|
async getSeasonDataFromMedia( |
|
|
|
async getSeasonDataFromMedia( |
|
|
|
media: MWPortableMedia, |
|
|
|
media: MWPortableMedia |
|
|
|
): Promise<MWMediaSeasons> { |
|
|
|
): Promise<MWMediaSeasons> { |
|
|
|
const apiQuery = { |
|
|
|
const apiQuery = { |
|
|
|
module: "TV_detail_1", |
|
|
|
module: "TV_detail_1", |
|
|
@ -261,11 +277,11 @@ export const superStreamScraper: MWMediaProvider = { |
|
|
|
const detailRes = (await get(apiQuery, true).then((r) => r.json())).data; |
|
|
|
const detailRes = (await get(apiQuery, true).then((r) => r.json())).data; |
|
|
|
const firstSearchResult = ( |
|
|
|
const firstSearchResult = ( |
|
|
|
await fetch( |
|
|
|
await fetch( |
|
|
|
`https://api.themoviedb.org/3/search/tv?api_key=${TMDB_API_KEY}&language=en-US&page=1&query=${detailRes.title}&include_adult=false`, |
|
|
|
`https://api.themoviedb.org/3/search/tv?api_key=${TMDB_API_KEY}&language=en-US&page=1&query=${detailRes.title}&include_adult=false` |
|
|
|
).then((r) => r.json()) |
|
|
|
).then((r) => r.json()) |
|
|
|
).results[0]; |
|
|
|
).results[0]; |
|
|
|
const showDetails = await fetch( |
|
|
|
const showDetails = await fetch( |
|
|
|
`https://api.themoviedb.org/3/tv/${firstSearchResult.id}?api_key=${TMDB_API_KEY}`, |
|
|
|
`https://api.themoviedb.org/3/tv/${firstSearchResult.id}?api_key=${TMDB_API_KEY}` |
|
|
|
).then((r) => r.json()); |
|
|
|
).then((r) => r.json()); |
|
|
|
|
|
|
|
|
|
|
|
return { |
|
|
|
return { |
|
|
@ -279,7 +295,7 @@ export const superStreamScraper: MWMediaProvider = { |
|
|
|
sort: epNum + 1, |
|
|
|
sort: epNum + 1, |
|
|
|
id: (epNum + 1).toString(), |
|
|
|
id: (epNum + 1).toString(), |
|
|
|
episodeNumber: epNum + 1, |
|
|
|
episodeNumber: epNum + 1, |
|
|
|
}), |
|
|
|
}) |
|
|
|
), |
|
|
|
), |
|
|
|
})), |
|
|
|
})), |
|
|
|
}; |
|
|
|
}; |
|
|
|