/// /// class ProviderN { api = "https://anitaku.to" ajaxURL = "https://ajax.gogocdn.net" getSettings(): Settings { return { episodeServers: ["gogocdn", "vidstreaming", "streamsb"], supportsDub: true, } } async search(opts: SearchOptions): Promise { const request = await fetch(`${this.api}/search.html?keyword=${encodeURIComponent(opts.query)}`) if (!request.ok) { return [] } const data = await request.text() const results: SearchResult[] = [] const $ = LoadDoc(data) $("ul.items > li").each((_, el) => { const title = el.find("p.name a").text().trim() const id = el.find("div.img a").attr("href") if (!id) { return } results.push({ id: id, title: title, url: id, subOrDub: "sub", }) }) return results } async findEpisodes(id: string): Promise { const episodes: EpisodeDetails[] = [] const data = await (await fetch(`${this.api}${id}`)).text() const $ = LoadDoc(data) const epStart = $("#episode_page > li").first().find("a").attr("ep_start") const epEnd = $("#episode_page > li").last().find("a").attr("ep_end") const movieId = $("#movie_id").attr("value") const alias = $("#alias_anime").attr("value") const req = await (await fetch(`${this.ajaxURL}/ajax/load-list-episode?ep_start=${epStart}&ep_end=${epEnd}&id=${movieId}&default_ep=${0}&alias=${alias}`)).text() const $$ = LoadDoc(req) $$("#episode_related > li").each((i, el) => { episodes?.push({ id: el.find("a").attr("href")?.trim() ?? "", url: el.find("a").attr("href")?.trim() ?? "", number: parseFloat(el.find(`div.name`).text().replace("EP ", "")), title: el.find(`div.name`).text(), }) }) return episodes.reverse() } async findEpisodeServer(episode: EpisodeDetails, _server: string): Promise { let server = "gogocdn" if (_server !== "default") { server = _server } const episodeServer: EpisodeServer = { server: server, headers: {}, videoSources: [], } if (episode.id.startsWith("http")) { const serverURL = episode.id try { const es = await new Extractor(serverURL, episodeServer).extract(server) if (es) { return es } } catch (e) { console.error(e) return episodeServer } return episodeServer } const data = await (await fetch(`${this.api}${episode.id}`)).text() const $ = LoadDoc(data) let serverURL: string switch (server) { case "gogocdn": serverURL = `${$("#load_anime > div > div > iframe").attr("src")}` break case "vidstreaming": serverURL = `${$("div.anime_video_body > div.anime_muti_link > ul > li.vidcdn > a").attr("data-video")}` break case "streamsb": serverURL = $("div.anime_video_body > div.anime_muti_link > ul > li.streamsb > a").attr("data-video")! break default: serverURL = `${$("#load_anime > div > div > iframe").attr("src")}` break } episode.id = serverURL return await this.findEpisodeServer(episode, server) } } class Extractor { private url: string private result: EpisodeServer constructor(url: string, result: EpisodeServer) { this.url = url this.result = result } async extract(server: string): Promise { try { switch (server) { case "gogocdn": console.log("GogoCDN extraction") return await this.extractGogoCDN(this.url, this.result) case "vidstreaming": return await this.extractGogoCDN(this.url, this.result) default: return undefined } } catch (e) { console.error(e) return undefined } } public async extractGogoCDN(url: string, result: EpisodeServer): Promise { const keys = { key: CryptoJS.enc.Utf8.parse("37911490979715163134003223491201"), secondKey: CryptoJS.enc.Utf8.parse("54674138327930866480207815084989"), iv: CryptoJS.enc.Utf8.parse("3134003223491201"), } function generateEncryptedAjaxParams(id: string) { const encryptedKey = CryptoJS.AES.encrypt(id, keys.key, { iv: keys.iv, }) const scriptValue = $("script[data-name='episode']").data("value")! const decryptedToken = CryptoJS.AES.decrypt(scriptValue, keys.key, { iv: keys.iv, }).toString(CryptoJS.enc.Utf8) return `id=${encryptedKey.toString(CryptoJS.enc.Base64)}&alias=${id}&${decryptedToken}` } function decryptAjaxData(encryptedData: string) { const decryptedData = CryptoJS.AES.decrypt(encryptedData, keys.secondKey, { iv: keys.iv, }).toString(CryptoJS.enc.Utf8) return JSON.parse(decryptedData) } const req = await fetch(url) const $ = LoadDoc(await req.text()) const encryptedParams = generateEncryptedAjaxParams(new URL(url).searchParams.get("id") ?? "") const xmlHttpUrl = `${new URL(url).protocol}//${new URL(url).hostname}/encrypt-ajax.php?${encryptedParams}` const encryptedData = await fetch(xmlHttpUrl, { headers: { "X-Requested-With": "XMLHttpRequest", }, }) const decryptedData = await decryptAjaxData(((await encryptedData.json()) as { data: any })?.data) if (!decryptedData.source) throw new Error("No source found. Try a different server.") if (decryptedData.source[0].file.includes(".m3u8")) { const resResult = await fetch(decryptedData.source[0].file.toString()) const resolutions = (await resResult.text()).match(/(RESOLUTION=)(.*)(\s*?)(\s*.*)/g) resolutions?.forEach((res: string) => { const index = decryptedData.source[0].file.lastIndexOf("/") const quality = res.split("\n")[0].split("x")[1].split(",")[0] const url = decryptedData.source[0].file.slice(0, index) result.videoSources.push({ url: url + "/" + res.split("\n")[1], quality: quality + "p", subtitles: [], type: "m3u8", }) }) decryptedData.source.forEach((source: any) => { result.videoSources.push({ url: source.file, quality: "default", subtitles: [], type: "m3u8", }) }) } else { decryptedData.source.forEach((source: any) => { result.videoSources.push({ url: source.file, quality: source.label.split(" ")[0] + "p", subtitles: [], type: "m3u8", }) }) decryptedData.source_bk.forEach((source: any) => { result.videoSources.push({ url: source.file, quality: "backup", subtitles: [], type: "m3u8", }) }) } return result } }