mirror of
https://github.com/Radiquum/AniX.git
synced 2025-09-04 13:35:36 +05:00
372 lines
10 KiB
TypeScript
372 lines
10 KiB
TypeScript
import {
|
||
ANIXART_API,
|
||
ANIXART_HEADERS,
|
||
ANIXART_HEADERST,
|
||
asJSON,
|
||
GetHook,
|
||
LoadedHook,
|
||
logger,
|
||
PostHook,
|
||
} from "./shared";
|
||
import express from "express";
|
||
import fs from "fs/promises";
|
||
import { MediaChromeTheme } from "./media-chrome";
|
||
import { Iframe } from "./iframe";
|
||
|
||
const app = express();
|
||
app.use(express.json());
|
||
app.use(express.urlencoded({ extended: true }));
|
||
|
||
const host = "0.0.0.0";
|
||
const port = 7001;
|
||
|
||
const loadedHooks: LoadedHook[] = [];
|
||
|
||
app.get("/player", async (req, res) => {
|
||
let url = req.query.url || null;
|
||
|
||
res.status(200);
|
||
res.set({
|
||
"Access-Control-Allow-Origin": "*",
|
||
"Access-Control-Allow-Methods": "GET,HEAD,POST,OPTIONS",
|
||
"Cache-Control": "no-cache",
|
||
"Content-Type": "text/html; charset=utf-8",
|
||
});
|
||
if (!url) {
|
||
res.send("<h1>No url query found!</h1>");
|
||
return;
|
||
}
|
||
|
||
let player = "";
|
||
let poster = "";
|
||
|
||
const CUSTOM_PLAYER_DOMAINS = [
|
||
"video.sibnet.ru",
|
||
"anixart.libria.fun",
|
||
"kodik.info",
|
||
"aniqit.com",
|
||
"kodik.cc",
|
||
"kodik.biz",
|
||
];
|
||
const urlDomain = new URL(url.toString());
|
||
const PLAYER_PARSER_URL = process.env.PLAYER_PARSER_URL || null;
|
||
|
||
if (CUSTOM_PLAYER_DOMAINS.includes(urlDomain.hostname)) {
|
||
try {
|
||
if (!PLAYER_PARSER_URL) throw new Error();
|
||
|
||
if (
|
||
["kodik.info", "aniqit.com", "kodik.cc", "kodik.biz"].includes(
|
||
urlDomain.hostname
|
||
)
|
||
) {
|
||
player = "kodik";
|
||
}
|
||
if ("anixart.libria.fun" == urlDomain.hostname) {
|
||
player = "libria";
|
||
}
|
||
if ("video.sibnet.ru" == urlDomain.hostname) {
|
||
player = "sibnet";
|
||
}
|
||
|
||
const playerParserRes = await fetch(
|
||
`${PLAYER_PARSER_URL}?url=${encodeURIComponent(url.toString())}&player=${player}`
|
||
);
|
||
|
||
if (!playerParserRes.ok) throw new Error();
|
||
|
||
const playerParserData: { manifest: string; poster: string } =
|
||
await playerParserRes.json();
|
||
|
||
poster = playerParserData.poster;
|
||
if (playerParserData.manifest.startsWith("#EXTM3U")) {
|
||
const playerUrlArray = playerParserData.manifest.split("\n");
|
||
url = playerUrlArray.join("\\n");
|
||
} else {
|
||
url = playerParserData.manifest;
|
||
}
|
||
} catch {
|
||
res.send(Iframe(url.toString()));
|
||
return;
|
||
}
|
||
} else if (url.toString().endsWith("mp4")) {
|
||
player = "mp4";
|
||
} else if (url.toString().endsWith(".m3u8")) {
|
||
player = "hls";
|
||
} else {
|
||
res.send(Iframe(url.toString()));
|
||
return;
|
||
}
|
||
|
||
res.send(`
|
||
<!DOCTYPE html>
|
||
<html>
|
||
<head>
|
||
<title>Веб-плеер</title>
|
||
<meta name='viewport' content='width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=yes' />
|
||
<style>body, html {height: 100%; width: 100%; margin: 0px;padding: 0px;border: 0px;}</style>
|
||
${["kodik", "libria", "hls"].includes(player) ? '<script type="module" src="https://cdn.jsdelivr.net/npm/hls-video-element@1.2/+esm"></script>' : ""}
|
||
<script>
|
||
window.onload = () => {
|
||
const url = "${url}";
|
||
const poster = "${poster}";
|
||
const element = document.querySelector("#video-element");
|
||
|
||
element.poster = poster;
|
||
if (url.startsWith("http")) {
|
||
element.src = url;
|
||
} else {
|
||
let file = new File([url], "manifest.m3u8", {
|
||
type: "application/x-mpegURL",
|
||
});
|
||
element.src = URL.createObjectURL(file);
|
||
};
|
||
};
|
||
</script>
|
||
</head>
|
||
<body>
|
||
|
||
${MediaChromeTheme()}
|
||
|
||
<media-theme
|
||
template="media-theme-sutro"
|
||
style="width:100%;height:100%;">
|
||
${
|
||
["kodik", "libria", "hls"].includes(player) ?
|
||
`<hls-video slot="media" playsinline id="video-element"></hls-video>`
|
||
: `<video slot="media" playsinline id="video-element"></video>`
|
||
}
|
||
</media-theme>
|
||
</body>
|
||
</html>
|
||
`);
|
||
});
|
||
|
||
app.get("/*path", async (req, res) => {
|
||
if (req.path == "/favicon.ico") return asJSON(res, {}, 404);
|
||
|
||
const url = new URL(`${ANIXART_API}${req.url}`);
|
||
logger.debug(
|
||
`[${req.method}] ${url.protocol}//${url.hostname}${url.pathname}`
|
||
);
|
||
// logger.debug(` ↳ [QUERY] ${url.searchParams.toString()}`);
|
||
|
||
if (
|
||
url.searchParams.get("API-Version") == "v2" ||
|
||
req.headers["api-version"] == "v2"
|
||
) {
|
||
// logger.debug(` ↳ Force API V2`);
|
||
ANIXART_HEADERS["Api-Version"] = "v2";
|
||
url.searchParams.delete("API-Version");
|
||
}
|
||
|
||
const apiResponse = await fetch(url.toString(), {
|
||
method: "GET",
|
||
headers: ANIXART_HEADERS,
|
||
});
|
||
|
||
if (
|
||
!apiResponse ||
|
||
!apiResponse.ok ||
|
||
apiResponse.headers.get("content-type") != "application/json"
|
||
) {
|
||
logger.error(
|
||
`Failed to fetch: '${url.protocol}//${url.hostname}${url.pathname}', Path probably doesn't exist`
|
||
);
|
||
asJSON(
|
||
res,
|
||
{
|
||
code: 99,
|
||
returned_value: {
|
||
request_status: apiResponse ? apiResponse.status : null,
|
||
request_content_type:
|
||
apiResponse ? apiResponse.headers.get("content-type") : null,
|
||
},
|
||
reason: "Path probably doesn't exist",
|
||
},
|
||
500
|
||
);
|
||
return;
|
||
}
|
||
|
||
let data = await apiResponse.json();
|
||
let hooks: string[] = [];
|
||
|
||
try {
|
||
hooks = await fs.readdir("./hooks");
|
||
} catch (err) {
|
||
logger.error("'hooks' directory not found");
|
||
}
|
||
|
||
for (let i = 0; i < hooks.length; i++) {
|
||
const name = hooks[i];
|
||
if (
|
||
!name.endsWith(".ts") ||
|
||
name.includes("example") ||
|
||
name.includes("disabled")
|
||
)
|
||
continue;
|
||
|
||
const isHookLoaded = loadedHooks.find(
|
||
(item) => item.path == `./hooks/${name}`
|
||
);
|
||
const stat = await fs.stat(`./hooks/${name}`);
|
||
|
||
if (isHookLoaded && isHookLoaded.mtime != stat.mtime.toISOString()) {
|
||
logger.infoHook(`Updated "./hooks/${name}"`);
|
||
delete require.cache[require.resolve(`./hooks/${name}`)];
|
||
isHookLoaded.mtime = stat.mtime.toISOString();
|
||
}
|
||
|
||
const hook: GetHook = require(`./hooks/${name}`);
|
||
if (!isHookLoaded) {
|
||
logger.infoHook(`Loaded "./hooks/${name}"`);
|
||
loadedHooks.push({
|
||
path: `./hooks/${name}`,
|
||
mtime: stat.mtime.toISOString(),
|
||
});
|
||
}
|
||
|
||
if (!hook.hasOwnProperty("match") || !hook.hasOwnProperty("get")) continue;
|
||
if (!hook.match(req.path)) continue;
|
||
|
||
data = await hook.get(data, url);
|
||
}
|
||
|
||
asJSON(res, data, 200);
|
||
return;
|
||
});
|
||
|
||
app.post("/*path", async (req, res) => {
|
||
const url = new URL(`${ANIXART_API}${req.url}`);
|
||
logger.debug(
|
||
`[${req.method}] ${url.protocol}//${url.hostname}${url.pathname}`
|
||
);
|
||
// logger.debug(` ↳ [QUERY] ${url.searchParams.toString()}`);
|
||
|
||
let apiResponse: null | Response = null;
|
||
const apiHeaders: ANIXART_HEADERST = {
|
||
"User-Agent": ANIXART_HEADERS["User-Agent"],
|
||
"Content-Type": req.headers["content-type"] || "application/json",
|
||
};
|
||
|
||
if (
|
||
url.searchParams.get("API-Version") == "v2" ||
|
||
req.headers["api-version"] == "v2"
|
||
) {
|
||
// logger.debug(` ↳ Force API V2`);
|
||
apiHeaders["Api-Version"] = "v2";
|
||
url.searchParams.delete("API-Version");
|
||
}
|
||
|
||
const reqContentType =
|
||
req.headers["content-type"] ?
|
||
req.headers["content-type"].split(";")[0]
|
||
: "application/json";
|
||
switch (reqContentType) {
|
||
case "multipart/form-data":
|
||
const formData = new FormData();
|
||
for (const name in req.body) {
|
||
formData.append(name, req.body[name]);
|
||
}
|
||
apiResponse = await fetch(url.toString(), {
|
||
method: "POST",
|
||
headers: apiHeaders,
|
||
body: formData,
|
||
});
|
||
break;
|
||
case "application/x-www-form-urlencoded":
|
||
apiResponse = await fetch(url.toString(), {
|
||
method: "POST",
|
||
headers: apiHeaders,
|
||
body: new URLSearchParams(req.body),
|
||
});
|
||
break;
|
||
case "application/json":
|
||
apiResponse = await fetch(url.toString(), {
|
||
method: "POST",
|
||
headers: apiHeaders,
|
||
body: JSON.stringify(req.body),
|
||
});
|
||
break;
|
||
}
|
||
|
||
// logger.console("debug", ` ↳ [REQ BODY]`, req.body);
|
||
// logger.console("debug", ` ↳ [REQ HEADERS]`, req.headers);
|
||
// logger.console("debug", " ↳ [RES TEXT]", await apiResponse.text());
|
||
// logger.console("debug", " ↳ [RES HEADERS]", apiResponse.headers);
|
||
|
||
if (
|
||
!apiResponse ||
|
||
!apiResponse.ok ||
|
||
apiResponse.headers.get("content-type") != "application/json"
|
||
) {
|
||
logger.error(
|
||
`Failed to post: '${url.protocol}//${url.hostname}${url.pathname}', Path probably doesn't exist`
|
||
);
|
||
asJSON(
|
||
res,
|
||
{
|
||
code: 99,
|
||
returned_value: {
|
||
request_status: apiResponse ? apiResponse.status : null,
|
||
request_content_type:
|
||
apiResponse ? apiResponse.headers.get("content-type") : null,
|
||
},
|
||
reason: "Path probably doesn't exist",
|
||
},
|
||
500
|
||
);
|
||
return;
|
||
}
|
||
let data = await apiResponse.json();
|
||
let hooks: string[] = [];
|
||
|
||
try {
|
||
hooks = await fs.readdir("./hooks");
|
||
} catch (err) {
|
||
logger.error("'hooks' directory not found");
|
||
}
|
||
|
||
for (let i = 0; i < hooks.length; i++) {
|
||
const name = hooks[i];
|
||
if (
|
||
!name.endsWith(".ts") ||
|
||
name.includes("example") ||
|
||
name.includes("disabled")
|
||
)
|
||
continue;
|
||
|
||
const isHookLoaded = loadedHooks.find(
|
||
(item) => item.path == `./hooks/${name}`
|
||
);
|
||
const stat = await fs.stat(`./hooks/${name}`);
|
||
|
||
if (isHookLoaded && isHookLoaded.mtime != stat.mtime.toISOString()) {
|
||
logger.infoHook(`Updated "./hooks/${name}"`);
|
||
delete require.cache[require.resolve(`./hooks/${name}`)];
|
||
isHookLoaded.mtime = stat.mtime.toISOString();
|
||
}
|
||
|
||
const hook: PostHook = require(`./hooks/${name}`);
|
||
if (!isHookLoaded) {
|
||
logger.infoHook(`Loaded "./hooks/${name}"`);
|
||
loadedHooks.push({
|
||
path: `./hooks/${name}`,
|
||
mtime: stat.mtime.toISOString(),
|
||
});
|
||
}
|
||
|
||
if (!hook.hasOwnProperty("match") || !hook.hasOwnProperty("post")) continue;
|
||
if (!hook.match(req.path)) continue;
|
||
|
||
data = await hook.post(data, url);
|
||
}
|
||
|
||
asJSON(res, data, 200);
|
||
return;
|
||
});
|
||
|
||
app.listen(port, host, function () {
|
||
logger.info(`Server listen: http://${host}:${port}`);
|
||
});
|