import {
ANIXART_API,
ANIXART_HEADERS,
ANIXART_HEADERST,
asJSON,
getAnixartApiBaseUrl,
GetHook,
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(function (req, res, next) {
res.header("Access-Control-Allow-Origin", req.headers.origin || "*");
res.header(
"Access-Control-Allow-Headers",
"Origin, X-Requested-With, Content-Type, Accept, Sign, Allow, User-Agent, Api-Version"
);
res.header("Access-Control-Allow-Methods", "GET,HEAD,POST,OPTIONS");
next();
});
app.use(
express.raw({ inflate: true, limit: "50mb", type: "multipart/form-data" })
);
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
const HOST = process.env.HOST || "0.0.0.0";
const PORT = 7001;
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("/", async (req, res) => {
res.status(200);
res.set({
"Content-Type": "text/html; charset=utf-8",
});
res.send("");
});
app.get("/player", async (req, res) => {
let url = req.query.url || null;
res.status(200);
res.set({
"Access-Control-Allow-Origin": req.headers.origin || "*",
"Access-Control-Allow-Methods": "GET,HEAD,POST,OPTIONS",
"Cache-Control": "no-cache",
"Content-Type": "text/html; charset=utf-8",
});
if (!url) {
res.send("
No url query found!
");
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().toLowerCase().endsWith("mp4")) {
player = "mp4";
} else if (url.toString().toLowerCase().endsWith(".m3u8")) {
player = "hls";
} else {
res.send(Iframe(url.toString()));
return;
}
res.send(`
Веб-плеер
${["kodik", "libria", "hls"].includes(player) ? '' : ""}
${MediaChromeTheme()}
${
["kodik", "libria", "hls"].includes(player) ?
``
: ``
}
`);
});
app.get("/*path", async (req, res) => {
if (req.path == "/favicon.ico") return asJSON(req, 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(
req,
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();
for (let i = 0; i < hooks.length; i++) {
const name = hooks[i];
const hook: GetHook = require(`./hooks/${name}`);
if (!hook.hasOwnProperty("match") || !hook.hasOwnProperty("get")) continue;
if (!hook.match(req.path)) continue;
data = await hook.get(data, url);
}
asJSON(req, 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 reqContentType =
req.headers["content-type"] ?
req.headers["content-type"].split(";")[0]
: "x-unknown/unknown";
const supportedContentTypes = [
"application/json",
"application/x-www-form-urlencoded",
"multipart/form-data",
"x-unknown/unknown"
];
const isSupported = supportedContentTypes.includes(
reqContentType.toLowerCase()
);
if (!isSupported) {
res.status(500).json({
code: 99,
error: "Unsupported Media Type",
reason: `Content-Type '${reqContentType}' is not supported.`,
});
return;
}
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");
}
switch (reqContentType) {
case "multipart/form-data":
apiResponse = await fetch(url.toString(), {
method: "POST",
headers: apiHeaders,
body: req.body,
});
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;
}
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(
req,
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();
for (let i = 0; i < hooks.length; i++) {
const name = hooks[i];
const hook: PostHook = require(`./hooks/${name}`);
if (!hook.hasOwnProperty("match") || !hook.hasOwnProperty("post")) continue;
if (!hook.match(req.path)) continue;
data = await hook.post(data, url);
}
asJSON(req, res, data, 200);
return;
});
app.listen(PORT, HOST, async function () {
loadHooks();
await getAnixartApiBaseUrl();
logger.info(`Server listen: http://${HOST}:${PORT}`);
});