refactor/api-prox: preload and watch hooks at startup

This commit is contained in:
Kentai Radiquum 2025-07-08 16:20:25 +05:00
parent d23703b212
commit 3762e7e58f
Signed by: Radiquum
GPG key ID: 858E8EE696525EED
2 changed files with 65 additions and 72 deletions

View file

@ -55,7 +55,7 @@ export async function get(data: Toggles, url: URL) {
data.lastVersionCode = 25062200; data.lastVersionCode = 25062200;
data.impMessageEnabled = true; data.impMessageEnabled = true;
data.impMessageText = "разработчик AniX / Api-Prox-Svc"; data.impMessageText = "разработчик AniX / Api-Prox-Service";
data.impMessageLink = "https://wah.su/radiquum"; data.impMessageLink = "https://wah.su/radiquum";
data.impMessageBackgroundColor = "ffb3d0"; data.impMessageBackgroundColor = "ffb3d0";
data.impMessageTextColor = "ffffff"; data.impMessageTextColor = "ffffff";

View file

@ -14,14 +14,54 @@ import { MediaChromeTheme } from "./media-chrome";
import { Iframe } from "./iframe"; import { Iframe } from "./iframe";
const app = express(); const app = express();
app.use(express.raw({ inflate: true, limit: "50mb", type: "multipart/form-data" })); app.use(
express.raw({ inflate: true, limit: "50mb", type: "multipart/form-data" })
);
app.use(express.json()); app.use(express.json());
app.use(express.urlencoded({ extended: true })); app.use(express.urlencoded({ extended: true }));
const host = "0.0.0.0"; const host = "0.0.0.0";
const port = 7001; const port = 7001;
const loadedHooks: LoadedHook[] = []; let hooks: string[] = [];
async function loadHooks() {
let hooksDir: string[] = [];
try {
hooksDir = await fs.readdir("./hooks");
} catch (err) {
logger.error("'hooks' directory not found");
}
for (let i = 0; i < hooksDir.length; i++) {
const name = hooksDir[i];
if (
!name.endsWith(".ts") ||
name.includes("example") ||
name.includes("disabled")
)
continue;
require(`./hooks/${name}`);
logger.infoHook(`Loaded "./hooks/${name}"`);
hooks.push(name);
(async () => {
try {
const watcher = fs.watch(`./hooks/${name}`);
for await (const event of watcher) {
if (event.eventType === "change") {
logger.infoHook(`Updated "./hooks/${event.filename}"`);
delete require.cache[require.resolve(`./hooks/${event.filename}`)];
require(`./hooks/${event.filename}`);
}
}
} catch (err) {
throw err;
}
})();
}
}
app.get("/player", async (req, res) => { app.get("/player", async (req, res) => {
let url = req.query.url || null; let url = req.query.url || null;
@ -90,9 +130,9 @@ app.get("/player", async (req, res) => {
res.send(Iframe(url.toString())); res.send(Iframe(url.toString()));
return; return;
} }
} else if (url.toString().endsWith("mp4")) { } else if (url.toString().toLowerCase().endsWith("mp4")) {
player = "mp4"; player = "mp4";
} else if (url.toString().endsWith(".m3u8")) { } else if (url.toString().toLowerCase().endsWith(".m3u8")) {
player = "hls"; player = "hls";
} else { } else {
res.send(Iframe(url.toString())); res.send(Iframe(url.toString()));
@ -191,46 +231,12 @@ app.get("/*path", async (req, res) => {
} }
let data = await apiResponse.json(); 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++) { for (let i = 0; i < hooks.length; i++) {
const name = hooks[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}`); 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.hasOwnProperty("match") || !hook.hasOwnProperty("get")) continue;
if (!hook.match(req.path)) continue; if (!hook.match(req.path)) continue;
data = await hook.get(data, url); data = await hook.get(data, url);
} }
@ -264,12 +270,30 @@ app.post("/*path", async (req, res) => {
req.headers["content-type"] ? req.headers["content-type"] ?
req.headers["content-type"].split(";")[0] req.headers["content-type"].split(";")[0]
: "application/json"; : "application/json";
const supportedContentTypes = [
"application/json",
"application/x-www-form-urlencoded",
"multipart/form-data",
];
const isSupported = supportedContentTypes.some((type) =>
reqContentType.toLowerCase().startsWith(type)
);
if (!isSupported) {
res.status(500).json({
code: 99,
error: "Unsupported Media Type",
reason: `Content-Type '${reqContentType}' is not supported.`,
});
return;
}
switch (reqContentType) { switch (reqContentType) {
case "multipart/form-data": case "multipart/form-data":
apiResponse = await fetch(url.toString(), { apiResponse = await fetch(url.toString(), {
method: "POST", method: "POST",
headers: apiHeaders, headers: apiHeaders,
body: req.body body: req.body,
}); });
break; break;
case "application/x-www-form-urlencoded": case "application/x-www-form-urlencoded":
@ -288,11 +312,6 @@ app.post("/*path", async (req, res) => {
break; 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 ( if (
!apiResponse || !apiResponse ||
!apiResponse.ok || !apiResponse.ok ||
@ -327,36 +346,9 @@ app.post("/*path", async (req, res) => {
for (let i = 0; i < hooks.length; i++) { for (let i = 0; i < hooks.length; i++) {
const name = hooks[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}`); 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.hasOwnProperty("match") || !hook.hasOwnProperty("post")) continue;
if (!hook.match(req.path)) continue; if (!hook.match(req.path)) continue;
data = await hook.post(data, url); data = await hook.post(data, url);
} }
@ -365,5 +357,6 @@ app.post("/*path", async (req, res) => {
}); });
app.listen(port, host, function () { app.listen(port, host, function () {
loadHooks();
logger.info(`Server listen: http://${host}:${port}`); logger.info(`Server listen: http://${host}:${port}`);
}); });