|
|
@ -1,12 +1,12 @@ |
|
|
|
// this scraper was taken from recloudstream/cloudstream-extensions. much love
|
|
|
|
// this scraper was taken from recloudstream/cloudstream-extensions. much love
|
|
|
|
import { |
|
|
|
import { |
|
|
|
MWMediaProvider, |
|
|
|
MWMediaProvider, |
|
|
|
MWMediaType, |
|
|
|
MWMediaType, |
|
|
|
MWPortableMedia, |
|
|
|
MWPortableMedia, |
|
|
|
MWMediaStream, |
|
|
|
MWMediaStream, |
|
|
|
MWQuery, |
|
|
|
MWQuery, |
|
|
|
MWMediaSeasons, |
|
|
|
MWMediaSeasons, |
|
|
|
MWProviderMediaResult, |
|
|
|
MWProviderMediaResult, |
|
|
|
} from "providers/types"; |
|
|
|
} from "providers/types"; |
|
|
|
import { CORS_PROXY_URL, TMDB_API_KEY } from "mw_constants"; |
|
|
|
import { CORS_PROXY_URL, TMDB_API_KEY } from "mw_constants"; |
|
|
|
import { customAlphabet } from "nanoid"; |
|
|
|
import { customAlphabet } from "nanoid"; |
|
|
@ -22,27 +22,27 @@ const nanoid = customAlphabet("0123456789abcdef", 32); |
|
|
|
const iv = atob("d0VpcGhUbiE="); |
|
|
|
const iv = atob("d0VpcGhUbiE="); |
|
|
|
const key = atob("MTIzZDZjZWRmNjI2ZHk1NDIzM2FhMXc2"); |
|
|
|
const key = atob("MTIzZDZjZWRmNjI2ZHk1NDIzM2FhMXc2"); |
|
|
|
const apiUrls = [ |
|
|
|
const apiUrls = [ |
|
|
|
atob("aHR0cHM6Ly9zaG93Ym94LnNoZWd1Lm5ldC9hcGkvYXBpX2NsaWVudC9pbmRleC8="), |
|
|
|
atob("aHR0cHM6Ly9zaG93Ym94LnNoZWd1Lm5ldC9hcGkvYXBpX2NsaWVudC9pbmRleC8="), |
|
|
|
atob("aHR0cHM6Ly9tYnBhcGkuc2hlZ3UubmV0L2FwaS9hcGlfY2xpZW50L2luZGV4Lw=="), |
|
|
|
atob("aHR0cHM6Ly9tYnBhcGkuc2hlZ3UubmV0L2FwaS9hcGlfY2xpZW50L2luZGV4Lw=="), |
|
|
|
]; |
|
|
|
]; |
|
|
|
const appKey = atob("bW92aWVib3g="); |
|
|
|
const appKey = atob("bW92aWVib3g="); |
|
|
|
const appId = atob("Y29tLnRkby5zaG93Ym94"); |
|
|
|
const appId = atob("Y29tLnRkby5zaG93Ym94"); |
|
|
|
|
|
|
|
|
|
|
|
// cryptography stuff
|
|
|
|
// cryptography stuff
|
|
|
|
const crypto = { |
|
|
|
const crypto = { |
|
|
|
encrypt(str: string) { |
|
|
|
encrypt(str: string) { |
|
|
|
return CryptoJS.TripleDES.encrypt(str, CryptoJS.enc.Utf8.parse(key), { |
|
|
|
return CryptoJS.TripleDES.encrypt(str, CryptoJS.enc.Utf8.parse(key), { |
|
|
|
iv: CryptoJS.enc.Utf8.parse(iv), |
|
|
|
iv: CryptoJS.enc.Utf8.parse(iv), |
|
|
|
}).toString(); |
|
|
|
}).toString(); |
|
|
|
}, |
|
|
|
}, |
|
|
|
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; |
|
|
|
}, |
|
|
|
}, |
|
|
|
}; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
// get expire time
|
|
|
|
// get expire time
|
|
|
@ -50,224 +50,226 @@ const expiry = () => Math.floor(Date.now() / 1000 + 60 * 60 * 12); |
|
|
|
|
|
|
|
|
|
|
|
// sending requests
|
|
|
|
// sending requests
|
|
|
|
const get = (data: object, altApi = false) => { |
|
|
|
const get = (data: object, altApi = false) => { |
|
|
|
const defaultData = { |
|
|
|
const defaultData = { |
|
|
|
childmode: "0", |
|
|
|
childmode: "0", |
|
|
|
app_version: "11.5", |
|
|
|
app_version: "11.5", |
|
|
|
appid: appId, |
|
|
|
appid: appId, |
|
|
|
lang: "en", |
|
|
|
lang: "en", |
|
|
|
expired_date: `${expiry()}`, |
|
|
|
expired_date: `${expiry()}`, |
|
|
|
platform: "android", |
|
|
|
platform: "android", |
|
|
|
channel: "Website", |
|
|
|
channel: "Website", |
|
|
|
}; |
|
|
|
}; |
|
|
|
const encryptedData = crypto.encrypt( |
|
|
|
const encryptedData = crypto.encrypt( |
|
|
|
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); |
|
|
|
const body = JSON.stringify({ |
|
|
|
const body = JSON.stringify({ |
|
|
|
app_key: appKeyHash, |
|
|
|
app_key: appKeyHash, |
|
|
|
verify, |
|
|
|
verify, |
|
|
|
encrypt_data: encryptedData, |
|
|
|
encrypt_data: encryptedData, |
|
|
|
}); |
|
|
|
}); |
|
|
|
const b64Body = btoa(body); |
|
|
|
const b64Body = btoa(body); |
|
|
|
|
|
|
|
|
|
|
|
const formatted = new URLSearchParams(); |
|
|
|
const formatted = new URLSearchParams(); |
|
|
|
formatted.append("data", b64Body); |
|
|
|
formatted.append("data", b64Body); |
|
|
|
formatted.append("appid", "27"); |
|
|
|
formatted.append("appid", "27"); |
|
|
|
formatted.append("platform", "android"); |
|
|
|
formatted.append("platform", "android"); |
|
|
|
formatted.append("version", "129"); |
|
|
|
formatted.append("version", "129"); |
|
|
|
formatted.append("medium", "Website"); |
|
|
|
formatted.append("medium", "Website"); |
|
|
|
|
|
|
|
|
|
|
|
const requestUrl = altApi ? apiUrls[1] : apiUrls[0]; |
|
|
|
const requestUrl = altApi ? apiUrls[1] : apiUrls[0]; |
|
|
|
return fetch(`${CORS_PROXY_URL}${requestUrl}`, { |
|
|
|
return fetch(`${CORS_PROXY_URL}${requestUrl}`, { |
|
|
|
method: "POST", |
|
|
|
method: "POST", |
|
|
|
headers: { |
|
|
|
headers: { |
|
|
|
Platform: "android", |
|
|
|
Platform: "android", |
|
|
|
"Content-Type": "application/x-www-form-urlencoded", |
|
|
|
"Content-Type": "application/x-www-form-urlencoded", |
|
|
|
}, |
|
|
|
}, |
|
|
|
body: `${formatted.toString()}&token${nanoid()}`, |
|
|
|
body: `${formatted.toString()}&token${nanoid()}`, |
|
|
|
}); |
|
|
|
}); |
|
|
|
}; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
export const superStreamScraper: MWMediaProvider = { |
|
|
|
export const superStreamScraper: MWMediaProvider = { |
|
|
|
id: "superstream", |
|
|
|
id: "superstream", |
|
|
|
enabled: true, |
|
|
|
enabled: true, |
|
|
|
type: [MWMediaType.MOVIE, MWMediaType.SERIES], |
|
|
|
type: [MWMediaType.MOVIE, MWMediaType.SERIES], |
|
|
|
displayName: "SuperStream", |
|
|
|
displayName: "SuperStream", |
|
|
|
|
|
|
|
|
|
|
|
async getMediaFromPortable( |
|
|
|
async getMediaFromPortable( |
|
|
|
media: MWPortableMedia, |
|
|
|
media: MWPortableMedia, |
|
|
|
): Promise<MWProviderMediaResult> { |
|
|
|
): Promise<MWProviderMediaResult> { |
|
|
|
let apiQuery: any; |
|
|
|
let apiQuery: any; |
|
|
|
if (media.episodeId) { |
|
|
|
if (media.mediaType == MWMediaType.MOVIE) { |
|
|
|
apiQuery = { |
|
|
|
apiQuery = { |
|
|
|
module: "TV_detail_1", |
|
|
|
module: "TV_detail_1", |
|
|
|
display_all: "1", |
|
|
|
display_all: "1", |
|
|
|
tid: media.mediaId, |
|
|
|
tid: media.mediaId, |
|
|
|
}; |
|
|
|
}; |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
apiQuery = { |
|
|
|
apiQuery = { |
|
|
|
module: "Movie_detail", |
|
|
|
module: "Movie_detail", |
|
|
|
mid: media.mediaId, |
|
|
|
mid: media.mediaId, |
|
|
|
}; |
|
|
|
}; |
|
|
|
} |
|
|
|
} |
|
|
|
const detailRes = (await get(apiQuery, true).then((r) => r.json())).data; |
|
|
|
const detailRes = (await get(apiQuery, true).then((r) => r.json())).data; |
|
|
|
|
|
|
|
|
|
|
|
return { |
|
|
|
return { |
|
|
|
...media, |
|
|
|
...media, |
|
|
|
title: detailRes.title, |
|
|
|
title: detailRes.title, |
|
|
|
year: detailRes.year, |
|
|
|
year: detailRes.year, |
|
|
|
seasonCount: detailRes?.season?.length, |
|
|
|
seasonCount: detailRes?.season?.length, |
|
|
|
} as MWProviderMediaResult; |
|
|
|
} as MWProviderMediaResult; |
|
|
|
}, |
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
async searchForMedia(query: MWQuery): Promise<MWProviderMediaResult[]> { |
|
|
|
async searchForMedia(query: MWQuery): Promise<MWProviderMediaResult[]> { |
|
|
|
const apiQuery = { |
|
|
|
const apiQuery = { |
|
|
|
module: "Search3", |
|
|
|
module: "Search3", |
|
|
|
page: "1", |
|
|
|
page: "1", |
|
|
|
type: "all", |
|
|
|
type: "all", |
|
|
|
keyword: query.searchQuery, |
|
|
|
keyword: query.searchQuery, |
|
|
|
pagelimit: "20", |
|
|
|
pagelimit: "20", |
|
|
|
}; |
|
|
|
}; |
|
|
|
const searchRes = (await get(apiQuery, true).then((r) => r.json())).data; |
|
|
|
const searchRes = (await get(apiQuery, true).then((r) => r.json())).data; |
|
|
|
|
|
|
|
|
|
|
|
const movieResults: MWProviderMediaResult[] = (searchRes || []) |
|
|
|
const movieResults: MWProviderMediaResult[] = (searchRes || []) |
|
|
|
.filter((item: any) => item.box_type === 1) |
|
|
|
.filter((item: any) => item.box_type === 1) |
|
|
|
.map((item: any) => ({ |
|
|
|
.map((item: any) => ({ |
|
|
|
title: item.title, |
|
|
|
title: item.title, |
|
|
|
year: item.year, |
|
|
|
year: item.year, |
|
|
|
mediaId: item.id, |
|
|
|
mediaId: item.id, |
|
|
|
})); |
|
|
|
})); |
|
|
|
const seriesResults: MWProviderMediaResult[] = (searchRes || []) |
|
|
|
const seriesResults: MWProviderMediaResult[] = (searchRes || []) |
|
|
|
.filter((item: any) => item.box_type === 2) |
|
|
|
.filter((item: any) => item.box_type === 2) |
|
|
|
.map((item: any) => ({ |
|
|
|
.map((item: any) => ({ |
|
|
|
title: item.title, |
|
|
|
title: item.title, |
|
|
|
year: item.year, |
|
|
|
year: item.year, |
|
|
|
mediaId: item.id, |
|
|
|
mediaId: item.id, |
|
|
|
seasonId: "1", |
|
|
|
seasonId: "1", |
|
|
|
episodeId: "1", |
|
|
|
episodeId: "1", |
|
|
|
})); |
|
|
|
})); |
|
|
|
|
|
|
|
|
|
|
|
if (query.type === "movie") { |
|
|
|
if (query.type === MWMediaType.MOVIE) { |
|
|
|
return movieResults; |
|
|
|
return movieResults; |
|
|
|
} |
|
|
|
} else if (query.type === MWMediaType.SERIES) { |
|
|
|
return seriesResults; |
|
|
|
return seriesResults; |
|
|
|
}, |
|
|
|
} |
|
|
|
|
|
|
|
return []; |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
async getStream(media: MWPortableMedia): Promise<MWMediaStream> { |
|
|
|
async getStream(media: MWPortableMedia): Promise<MWMediaStream> { |
|
|
|
if (media.mediaType === MWMediaType.MOVIE) { |
|
|
|
if (media.mediaType === MWMediaType.MOVIE) { |
|
|
|
const apiQuery = { |
|
|
|
const apiQuery = { |
|
|
|
uid: "", |
|
|
|
uid: "", |
|
|
|
module: "Movie_downloadurl_v3", |
|
|
|
module: "Movie_downloadurl_v3", |
|
|
|
mid: media.mediaId, |
|
|
|
mid: media.mediaId, |
|
|
|
oss: "1", |
|
|
|
oss: "1", |
|
|
|
group: "", |
|
|
|
group: "", |
|
|
|
}; |
|
|
|
}; |
|
|
|
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") ?? |
|
|
|
mediaRes.list.find((quality: any) => quality.quality === "1080p") ?? |
|
|
|
mediaRes.list.find((quality: any) => quality.quality === "720p"); |
|
|
|
mediaRes.list.find((quality: any) => quality.quality === "720p"); |
|
|
|
|
|
|
|
|
|
|
|
const subtitleApiQuery = { |
|
|
|
const subtitleApiQuery = { |
|
|
|
fid: hdQuality.fid, |
|
|
|
fid: hdQuality.fid, |
|
|
|
uid: "", |
|
|
|
uid: "", |
|
|
|
module: "Movie_srt_list_v2", |
|
|
|
module: "Movie_srt_list_v2", |
|
|
|
mid: media.mediaId, |
|
|
|
mid: media.mediaId, |
|
|
|
}; |
|
|
|
}; |
|
|
|
const subtitleRes = (await get(subtitleApiQuery).then((r) => r.json())) |
|
|
|
const subtitleRes = (await get(subtitleApiQuery).then((r) => r.json())) |
|
|
|
.data; |
|
|
|
.data; |
|
|
|
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 { |
|
|
|
id: subtitle.language, |
|
|
|
id: subtitle.language, |
|
|
|
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 }; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const apiQuery = { |
|
|
|
const apiQuery = { |
|
|
|
uid: "", |
|
|
|
uid: "", |
|
|
|
module: "TV_downloadurl_v3", |
|
|
|
module: "TV_downloadurl_v3", |
|
|
|
episode: media.episodeId, |
|
|
|
episode: media.episodeId, |
|
|
|
tid: media.mediaId, |
|
|
|
tid: media.mediaId, |
|
|
|
season: media.seasonId, |
|
|
|
season: media.seasonId, |
|
|
|
oss: "1", |
|
|
|
oss: "1", |
|
|
|
group: "", |
|
|
|
group: "", |
|
|
|
}; |
|
|
|
}; |
|
|
|
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") ?? |
|
|
|
mediaRes.list.find((quality: any) => quality.quality === "1080p") ?? |
|
|
|
mediaRes.list.find((quality: any) => quality.quality === "720p"); |
|
|
|
mediaRes.list.find((quality: any) => quality.quality === "720p"); |
|
|
|
|
|
|
|
|
|
|
|
const subtitleApiQuery = { |
|
|
|
const subtitleApiQuery = { |
|
|
|
fid: hdQuality.fid, |
|
|
|
fid: hdQuality.fid, |
|
|
|
uid: "", |
|
|
|
uid: "", |
|
|
|
module: "TV_srt_list_v2", |
|
|
|
module: "TV_srt_list_v2", |
|
|
|
episode: media.episodeId, |
|
|
|
episode: media.episodeId, |
|
|
|
tid: media.mediaId, |
|
|
|
tid: media.mediaId, |
|
|
|
season: media.seasonId, |
|
|
|
season: media.seasonId, |
|
|
|
}; |
|
|
|
}; |
|
|
|
const subtitleRes = (await get(subtitleApiQuery).then((r) => r.json())) |
|
|
|
const subtitleRes = (await get(subtitleApiQuery).then((r) => r.json())) |
|
|
|
.data; |
|
|
|
.data; |
|
|
|
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 { |
|
|
|
id: subtitle.language, |
|
|
|
id: subtitle.language, |
|
|
|
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", |
|
|
|
display_all: "1", |
|
|
|
display_all: "1", |
|
|
|
tid: media.mediaId, |
|
|
|
tid: media.mediaId, |
|
|
|
}; |
|
|
|
}; |
|
|
|
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&first_air_date_year=${detailRes.year}`, |
|
|
|
`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()) |
|
|
|
).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 { |
|
|
|
seasons: showDetails.seasons.map((season: any) => ({ |
|
|
|
seasons: showDetails.seasons.map((season: any) => ({ |
|
|
|
sort: season.season_number, |
|
|
|
sort: season.season_number, |
|
|
|
id: season.season_number.toString(), |
|
|
|
id: season.season_number.toString(), |
|
|
|
type: season.season_number === 0 ? "special" : "season", |
|
|
|
type: season.season_number === 0 ? "special" : "season", |
|
|
|
episodes: Array.from({ length: season.episode_count }).map( |
|
|
|
episodes: Array.from({ length: season.episode_count }).map( |
|
|
|
(_, epNum) => ({ |
|
|
|
(_, epNum) => ({ |
|
|
|
title: `Episode ${epNum + 1}`, |
|
|
|
title: `Episode ${epNum + 1}`, |
|
|
|
sort: epNum + 1, |
|
|
|
sort: epNum + 1, |
|
|
|
id: (epNum + 1).toString(), |
|
|
|
id: (epNum + 1).toString(), |
|
|
|
episodeNumber: epNum + 1, |
|
|
|
episodeNumber: epNum + 1, |
|
|
|
}), |
|
|
|
}), |
|
|
|
), |
|
|
|
), |
|
|
|
})), |
|
|
|
})), |
|
|
|
}; |
|
|
|
}; |
|
|
|
}, |
|
|
|
}, |
|
|
|
}; |
|
|
|
}; |
|
|
|