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}`); });