diff options
| author | Ritesh Ghosh <[email protected]> | 2023-08-17 23:46:13 +0530 |
|---|---|---|
| committer | Ritesh Ghosh <[email protected]> | 2023-08-17 23:46:13 +0530 |
| commit | e335910e7a06bb48eaab2817e22d528c353242a9 (patch) | |
| tree | 7458207483a4b01e3777cd6ac6ac2b011c020eca /src | |
| parent | eaa384a83e33ad820c82f3860ccd88e40d5a0e33 (diff) | |
| download | aniwatch-api-e335910e7a06bb48eaab2817e22d528c353242a9.tar.xz aniwatch-api-e335910e7a06bb48eaab2817e22d528c353242a9.zip | |
feat(extractors): added anime extractors
Diffstat (limited to 'src')
| -rw-r--r-- | src/extractors/index.ts | 5 | ||||
| -rw-r--r-- | src/extractors/rapidcloud.ts | 156 | ||||
| -rw-r--r-- | src/extractors/streamsb.ts | 83 | ||||
| -rw-r--r-- | src/extractors/streamtape.ts | 37 |
4 files changed, 281 insertions, 0 deletions
diff --git a/src/extractors/index.ts b/src/extractors/index.ts new file mode 100644 index 0000000..e161a17 --- /dev/null +++ b/src/extractors/index.ts @@ -0,0 +1,5 @@ +import StreamSB from "./streamsb"; +import StreamTape from "./streamtape"; +import RapidCloud from "./rapidcloud"; + +export { StreamSB, StreamTape, RapidCloud }; diff --git a/src/extractors/rapidcloud.ts b/src/extractors/rapidcloud.ts new file mode 100644 index 0000000..2faba6f --- /dev/null +++ b/src/extractors/rapidcloud.ts @@ -0,0 +1,156 @@ +import axios from "axios"; +import { AES, enc as CryptojsEnc } from "crypto-js"; +import { substringAfter, substringBefore } from "../utils"; +import { Video, Subtitle, Intro } from "../models/extractor"; + +type extractReturn = { sources: Video[]; subtitles: Subtitle[] }; + +// https://megacloud.tv/embed-2/e-1/IxJ7GjGVCyml?k=1 +class RapidCloud { + private serverName = "RapidCloud"; + private sources: Video[] = []; + + // https://rapid-cloud.co/embed-6/eVZPDXwVfrY3?vast=1 + private readonly fallbackKey = "c1d17096f2ca11b7"; + private readonly host = "https://rapid-cloud.co"; + + async extract(videoUrl: URL): Promise<extractReturn> { + const result: extractReturn & { intro?: Intro } = { + sources: [], + subtitles: [], + }; + + try { + const id = videoUrl.href.split("/").pop()?.split("?")[0]; + const options = { + headers: { + "X-Requested-With": "XMLHttpRequest", + }, + }; + + let res = null; + + res = await axios.get( + `${this.host}/ajax/embed-6/getSources?id=${id}`, + options + ); + + let { + data: { sources, tracks, intro, encrypted }, + } = res; + + let decryptKey = await ( + await axios.get("https://github.com/enimax-anime/key/blob/e6/key.txt") + ).data; + + decryptKey = substringBefore( + substringAfter(decryptKey, '"blob-code blob-code-inner js-file-line">'), + "</td>" + ); + + if (!decryptKey) { + decryptKey = await ( + await axios.get( + "https://raw.githubusercontent.com/enimax-anime/key/e6/key.txt" + ) + ).data; + } + + if (!decryptKey) decryptKey = this.fallbackKey; + + try { + if (encrypted) { + const sourcesArray = sources.split(""); + let extractedKey = ""; + + for (const index of decryptKey) { + for (let i = index[0]; i < index[1]; i++) { + extractedKey += sources[i]; + sourcesArray[i] = ""; + } + } + + decryptKey = extractedKey; + sources = sourcesArray.join(""); + + const decrypt = AES.decrypt(sources, decryptKey); + sources = JSON.parse(decrypt.toString(CryptojsEnc.Utf8)); + } + } catch (err: any) { + console.log(err.message); + throw new Error("Cannot decrypt sources. Perhaps the key is invalid."); + } + + this.sources = sources?.map((s: any) => ({ + url: s.file, + isM3U8: s.file.includes(".m3u8"), + })); + + result.sources.push(...this.sources); + + if (videoUrl.href.includes(new URL(this.host).host)) { + result.sources = []; + this.sources = []; + + for (const source of sources) { + const { data } = await axios.get(source.file, options); + const m3u8data = data + .split("\n") + .filter( + (line: string) => + line.includes(".m3u8") && line.includes("RESOLUTION=") + ); + + const secondHalf = m3u8data.map((line: string) => + line.match(/RESOLUTION=.*,(C)|URI=.*/g)?.map((s) => s.split("=")[1]) + ); + + const TdArray = secondHalf.map((s: string[]) => { + const f1 = s[0].split(",C")[0]; + const f2 = s[1].replace(/"/g, ""); + + return [f1, f2]; + }); + for (const [f1, f2] of TdArray) { + this.sources.push({ + url: `${source.file?.split("master.m3u8")[0]}${f2.replace( + "iframes", + "index" + )}`, + quality: f1.split("x")[1] + "p", + isM3U8: f2.includes(".m3u8"), + }); + } + result.sources.push(...this.sources); + } + if (intro.end > 1) { + result.intro = { + start: intro.start, + end: intro.end, + }; + } + } + + result.sources.push({ + url: sources[0].file, + isM3U8: sources[0].file.includes(".m3u8"), + quality: "auto", + }); + + result.subtitles = tracks + .map((s: any) => + s.file + ? { url: s.file, lang: s.label ? s.label : "Thumbnails" } + : null + ) + .filter((s: any) => s); + + return result; + } catch (err: any) { + console.log(err.message); + throw err; + } + } +} + +export default RapidCloud; diff --git a/src/extractors/streamsb.ts b/src/extractors/streamsb.ts new file mode 100644 index 0000000..3e70b3e --- /dev/null +++ b/src/extractors/streamsb.ts @@ -0,0 +1,83 @@ +import axios from "axios"; +import { Video } from "../models/extractor"; +import { USER_AGENT_HEADER } from "../utils"; + +class StreamSB { + private serverName = "streamSB"; + private sources: Video[] = []; + + private readonly host = "https://watchsb.com/sources50"; + private readonly host2 = "https://streamsss.net/sources16"; + + private PAYLOAD(hex: string): string { + // `5363587530696d33443675687c7c${hex}7c7c433569475830474c497a65767c7c73747265616d7362`; + return `566d337678566f743674494a7c7c${hex}7c7c346b6767586d6934774855537c7c73747265616d7362/6565417268755339773461447c7c346133383438333436313335376136323337373433383634376337633465366534393338373136643732373736343735373237613763376334363733353737303533366236333463353333363534366137633763373337343732363536313664373336327c7c6b586c3163614468645a47617c7c73747265616d7362`; + } + + async extract(videoUrl: URL, isAlt: boolean = false): Promise<Video[]> { + let headers: Record<string, string> = { + watchsb: "sbstream", + Referer: videoUrl.href, + "User-Agent": USER_AGENT_HEADER, + }; + let id = videoUrl.href.split("/e/").pop(); + if (id?.includes("html")) { + id = id.split(".html")[0]; + } + const bytes = new TextEncoder().encode(id); + + const res = await axios + .get( + `${isAlt ? this.host2 : this.host}/${this.PAYLOAD( + Buffer.from(bytes).toString("hex") + )}`, + { headers } + ) + .catch(() => null); + + if (!res?.data.stream_data) { + throw new Error("No source found. Try a different server"); + } + + headers = { + "User-Agent": USER_AGENT_HEADER, + Referer: videoUrl.href.split("e/")[0], + }; + + const m3u8_urls = await axios.get(res.data.stream_data.file, { + headers, + }); + + const videoList = m3u8_urls?.data?.split("#EXT-X-STREAM-INF:") ?? []; + + for (const video of videoList) { + if (!video.includes("m3u8")) continue; + + const url = video.split("\n")[1]; + const quality = video.split("RESOLUTION=")[1].split(",")[0].split("x")[1]; + + this.sources.push({ + url: url, + quality: `${quality}p`, + isM3U8: true, + }); + } + + this.sources.push({ + url: res.data.stream_data.file, + quality: "auto", + isM3U8: res.data.stream_data.file.includes(".m3u8"), + }); + + return this.sources; + } + + private addSources(source: any): void { + this.sources.push({ + url: source.file, + isM3U8: source.file.includes(".m3u8"), + }); + } +} + +export default StreamSB; diff --git a/src/extractors/streamtape.ts b/src/extractors/streamtape.ts new file mode 100644 index 0000000..4b770d0 --- /dev/null +++ b/src/extractors/streamtape.ts @@ -0,0 +1,37 @@ +import axios from "axios"; +import { load, CheerioAPI } from "cheerio"; +import { Video } from "../models/extractor"; + +class StreamTape { + private serverName = "StreamTape"; + private sources: Video[] = []; + + async extract(videoUrl: URL): Promise<Video[]> { + try { + const { data } = await axios.get(videoUrl.href).catch(() => { + throw new Error("Video not found"); + }); + + const $: CheerioAPI = load(data); + + let [fh, sh] = $.html() + ?.match(/robotlink'\).innerHTML = (.*)'/)![1] + .split("+ ('"); + + sh = sh.substring(3); + fh = fh.replace(/\'/g, ""); + + const url = `https:${fh}${sh}`; + + this.sources.push({ + url: url, + isM3U8: url.includes(".m3u8"), + }); + + return this.sources; + } catch (err) { + throw new Error((err as Error).message); + } + } +} +export default StreamTape; |
