AniX/api-prox/index.ts

271 lines
7.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import {
ANIXART_API,
ANIXART_HEADERS,
ANIXART_HEADERST,
asJSON,
GetHook,
LoadedHook,
logger,
PostHook,
} from "./shared";
import express from "express";
import fs from "fs/promises";
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("/iframe", async (req, res) => {
const 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;
}
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>
</head>
<body>
<iframe src="${url}" width='100%' height='100%' frameborder='0' AllowFullScreen allow="autoplay"></iframe>
</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")) continue;
if (name.includes("example")) 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")) continue;
if (name.includes("example")) 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}`);
});