aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorRitesh Ghosh <[email protected]>2023-08-17 23:46:13 +0530
committerRitesh Ghosh <[email protected]>2023-08-17 23:46:13 +0530
commite335910e7a06bb48eaab2817e22d528c353242a9 (patch)
tree7458207483a4b01e3777cd6ac6ac2b011c020eca /src
parenteaa384a83e33ad820c82f3860ccd88e40d5a0e33 (diff)
downloadaniwatch-api-e335910e7a06bb48eaab2817e22d528c353242a9.tar.xz
aniwatch-api-e335910e7a06bb48eaab2817e22d528c353242a9.zip
feat(extractors): added anime extractors
Diffstat (limited to 'src')
-rw-r--r--src/extractors/index.ts5
-rw-r--r--src/extractors/rapidcloud.ts156
-rw-r--r--src/extractors/streamsb.ts83
-rw-r--r--src/extractors/streamtape.ts37
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;