diff --git a/package.json b/package.json index 7083652c..916ecf89 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "react-dom": "^17.0.2", "react-router-dom": "^5.2.0", "react-scripts": "5.0.1", + "srt-webvtt": "^2.0.0", "unpacker": "^1.0.1" }, "scripts": { diff --git a/src/mw_constants.ts b/src/mw_constants.ts index 7f6200d7..a4d18741 100644 --- a/src/mw_constants.ts +++ b/src/mw_constants.ts @@ -1,4 +1,6 @@ -export const CORS_PROXY_URL = "https://proxy-1.movie-web.workers.dev/?destination="; +export const CORS_PROXY_URL = + "https://proxy-1.movie-web.workers.dev/?destination="; +export const TMDB_API_KEY = "9a31326bc179b029cd4513c489628e79"; export const OMDB_API_KEY = "aa0937c0"; export const DISCORD_LINK = "https://discord.gg/Jhqt4Xzpfb"; export const GITHUB_LINK = "https://github.com/JamesHawkinss/movie-web"; diff --git a/src/providers/list/superstream/index.ts b/src/providers/list/superstream/index.ts index 47ddf9c4..2325ea58 100644 --- a/src/providers/list/superstream/index.ts +++ b/src/providers/list/superstream/index.ts @@ -8,8 +8,9 @@ import { MWMediaSeasons, MWProviderMediaResult, } from "providers/types"; -import { CORS_PROXY_URL } from "mw_constants"; +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); @@ -94,9 +95,8 @@ const get = (data: object, altApi = false) => { export const superStreamScraper: MWMediaProvider = { id: "superstream", enabled: true, - // type: [MWMediaType.MOVIE, MWMediaType.SERIES], - type: [MWMediaType.MOVIE], - displayName: "superstream", + type: [MWMediaType.MOVIE, MWMediaType.SERIES], + displayName: "SuperStream", async getMediaFromPortable( media: MWPortableMedia, @@ -147,8 +147,8 @@ export const superStreamScraper: MWMediaProvider = { title: item.title, year: item.year, mediaId: item.id, - seasonId: 1, - episodeId: 1, + seasonId: "1", + episodeId: "1", })); if (query.type === "movie") { @@ -171,7 +171,29 @@ export const superStreamScraper: MWMediaProvider = { mediaRes.list.find((quality: any) => quality.quality === "1080p") ?? mediaRes.list.find((quality: any) => quality.quality === "720p"); - return { url: hdQuality.path, type: "mp4", captions: [] }; + const subtitleApiQuery = { + fid: hdQuality.fid, + uid: "", + module: "Movie_srt_list_v2", + mid: media.mediaId, + }; + 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( + `${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 { url: hdQuality.path, type: "mp4", captions: mappedCaptions }; } const apiQuery = { @@ -188,49 +210,64 @@ export const superStreamScraper: MWMediaProvider = { mediaRes.list.find((quality: any) => quality.quality === "1080p") ?? mediaRes.list.find((quality: any) => quality.quality === "720p"); - return { url: hdQuality.path, type: "mp4", captions: [] }; + 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( + `${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 { url: hdQuality.path, type: "mp4", captions: mappedCaptions }; }, - // async getSeasonDataFromMedia( - // media: MWPortableMedia, - // ): Promise { - // 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); + async getSeasonDataFromMedia( + media: MWPortableMedia, + ): Promise { + const apiQuery = { + module: "TV_detail_1", + display_all: "1", + tid: media.mediaId, + }; + const detailRes = (await get(apiQuery, true).then((r) => r.json())).data; + const firstSearchResult = ( + 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&first_air_date_year=${detailRes.year}`, + ).then((r) => r.json()) + ).results[0]; + const showDetails = await fetch( + `https://api.themoviedb.org/3/tv/${firstSearchResult.id}?api_key=${TMDB_API_KEY}`, + ).then((r) => r.json()); - // 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, - // })), - // })), - // }; - // }, + return { + seasons: showDetails.seasons.map((season: any) => ({ + sort: season.season_number, + id: season.season_number.toString(), + type: season.season_number === 0 ? "special" : "season", + episodes: Array.from({ length: season.episode_count }).map( + (_, epNum) => ({ + title: `Episode ${epNum + 1}`, + sort: epNum + 1, + id: (epNum + 1).toString(), + episodeNumber: epNum + 1, + }), + ), + })), + }; + }, }; diff --git a/yarn.lock b/yarn.lock index a1460c6b..10ceb4ee 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7794,6 +7794,11 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= +srt-webvtt@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/srt-webvtt/-/srt-webvtt-2.0.0.tgz#debd2f56dd2b6600894caa11bb78893e5fc6509b" + integrity sha512-G2Z7/Jf2NRKrmLYNSIhSYZZYE6OFlKXFp9Au2/zJBKgrioUzmrAys1x7GT01dwl6d2sEnqr5uahEIOd0JW/Rbw== + stable@^0.1.8: version "0.1.8" resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf"