diff --git a/api-prox/index.ts b/api-prox/index.ts index 0fdec06..b6e5751 100644 --- a/api-prox/index.ts +++ b/api-prox/index.ts @@ -4,7 +4,6 @@ import { ANIXART_HEADERST, asJSON, GetHook, - LoadedHook, logger, PostHook, } from "./shared"; @@ -14,6 +13,15 @@ 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" + ); + res.header("Access-Control-Allow-Methods", "GET,HEAD,POST,OPTIONS"); + next(); +}); app.use( express.raw({ inflate: true, limit: "50mb", type: "multipart/form-data" }) ); @@ -68,7 +76,7 @@ app.get("/player", async (req, res) => { res.status(200); res.set({ - "Access-Control-Allow-Origin": "*", + "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", @@ -184,7 +192,7 @@ app.get("/player", async (req, res) => { }); app.get("/*path", async (req, res) => { - if (req.path == "/favicon.ico") return asJSON(res, {}, 404); + if (req.path == "/favicon.ico") return asJSON(req, res, {}, 404); const url = new URL(`${ANIXART_API}${req.url}`); logger.debug( @@ -215,6 +223,7 @@ app.get("/*path", async (req, res) => { `Failed to fetch: '${url.protocol}//${url.hostname}${url.pathname}', Path probably doesn't exist` ); asJSON( + req, res, { code: 99, @@ -231,7 +240,6 @@ app.get("/*path", async (req, res) => { } let data = await apiResponse.json(); - for (let i = 0; i < hooks.length; i++) { const name = hooks[i]; const hook: GetHook = require(`./hooks/${name}`); @@ -240,7 +248,7 @@ app.get("/*path", async (req, res) => { data = await hook.get(data, url); } - asJSON(res, data, 200); + asJSON(req, res, data, 200); return; }); @@ -251,10 +259,34 @@ app.post("/*path", async (req, res) => { ); // 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", + ]; + + 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", + "Content-Type": req.headers["content-type"] || "application/json" }; if ( @@ -266,28 +298,6 @@ app.post("/*path", async (req, res) => { url.searchParams.delete("API-Version"); } - const reqContentType = - req.headers["content-type"] ? - req.headers["content-type"].split(";")[0] - : "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) { case "multipart/form-data": apiResponse = await fetch(url.toString(), { @@ -321,6 +331,7 @@ app.post("/*path", async (req, res) => { `Failed to post: '${url.protocol}//${url.hostname}${url.pathname}', Path probably doesn't exist` ); asJSON( + req, res, { code: 99, @@ -335,15 +346,8 @@ app.post("/*path", async (req, res) => { ); 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]; const hook: PostHook = require(`./hooks/${name}`); @@ -352,7 +356,7 @@ app.post("/*path", async (req, res) => { data = await hook.post(data, url); } - asJSON(res, data, 200); + asJSON(req, res, data, 200); return; }); diff --git a/api-prox/shared.ts b/api-prox/shared.ts index 3dfdcc3..eeebd9a 100644 --- a/api-prox/shared.ts +++ b/api-prox/shared.ts @@ -1,18 +1,16 @@ export const corsHeaders = { "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Headers": "Origin, X-Requested-With, Content-Type, Accept, Sign", "Access-Control-Allow-Methods": "GET,HEAD,POST,OPTIONS", "Cache-Control": "no-cache", }; -export const resHeaders = { - ...corsHeaders, - "Content-Type": "application/json", -}; - import { Request, Response } from "express"; -export function asJSON(res: Response, object: any, status: number) { - res.status(status); - res.set(resHeaders); +export function asJSON(req: Request,res: Response, object: any, status: number) { + corsHeaders["Access-Control-Allow-Origin"] = req.headers.origin || "*"; + + res.status(status).type("application/json"); + res.set(corsHeaders); res.send(JSON.stringify(object)); } diff --git a/app/api/config.ts b/app/api/config.ts index f797ee0..b3e8211 100644 --- a/app/api/config.ts +++ b/app/api/config.ts @@ -2,7 +2,7 @@ export const CURRENT_APP_VERSION = "3.7.0"; import { env } from "next-runtime-env"; const NEXT_PUBLIC_API_URL = env("NEXT_PUBLIC_API_URL") || null; -export const API_URL = "https://api.anixart.app"; +export const API_URL = NEXT_PUBLIC_API_URL ||"https://api.anixart.app"; export const API_PREFIX = NEXT_PUBLIC_API_URL || "/api/proxy"; export const USER_AGENT = "AnixartApp/9.0 BETA 5-25062213 (Android 9; SDK 28; arm64-v8a; samsung SM-G975N; en)"; diff --git a/app/api/utils.ts b/app/api/utils.ts index f1f4b8d..fabc2b7 100644 --- a/app/api/utils.ts +++ b/app/api/utils.ts @@ -323,6 +323,7 @@ export async function _FetchHomePageReleases( const data: Object = fetch(url, { method: "POST", + headers: HEADERS, body: JSON.stringify(body), }) .then((response) => { diff --git a/app/components/Comments/Comments.Add.tsx b/app/components/Comments/Comments.Add.tsx index edec270..f4aba68 100644 --- a/app/components/Comments/Comments.Add.tsx +++ b/app/components/Comments/Comments.Add.tsx @@ -42,6 +42,7 @@ export const CommentsAddModal = (props: { const res = await fetch(url, { method: "POST", + headers: { "Content-Type": "application/json" }, body: JSON.stringify(data), }); diff --git a/app/components/Comments/Comments.Edit.tsx b/app/components/Comments/Comments.Edit.tsx index 4cc7fd6..cb997d8 100644 --- a/app/components/Comments/Comments.Edit.tsx +++ b/app/components/Comments/Comments.Edit.tsx @@ -32,6 +32,7 @@ export const CommentsEditModal = (props: { } const res = await fetch(url, { method: "POST", + headers: { "Content-Type": "application/json" }, body: JSON.stringify(data), }); diff --git a/app/components/Profile/Profile.EditModal.tsx b/app/components/Profile/Profile.EditModal.tsx index 7048df5..31ec419 100644 --- a/app/components/Profile/Profile.EditModal.tsx +++ b/app/components/Profile/Profile.EditModal.tsx @@ -1,6 +1,13 @@ "use client"; -import { FileInput, Label, Modal, ModalBody, ModalHeader, useThemeMode } from "flowbite-react"; +import { + FileInput, + Label, + Modal, + ModalBody, + ModalHeader, + useThemeMode, +} from "flowbite-react"; import { Spinner } from "../Spinner/Spinner"; import useSWR from "swr"; import { ENDPOINTS } from "#/api/config"; @@ -143,6 +150,7 @@ export const ProfileEditModal = (props: { const { data, error } = await tryCatchAPI( fetch(`${ENDPOINTS.user.settings.avatar}?token=${props.token}`, { method: "POST", + headers: { "Content-Type": "application/json" }, body: formData, }) ); @@ -188,7 +196,7 @@ export const ProfileEditModal = (props: { if (avatarModalProps.croppedImage) { _uploadAvatar(); } - // eslint-disable-next-line react-hooks/exhaustive-deps + // eslint-disable-next-line react-hooks/exhaustive-deps }, [avatarModalProps.croppedImage]); if (!prefData || !loginData || prefError || loginError) { diff --git a/app/pages/CreateCollection.tsx b/app/pages/CreateCollection.tsx index a7b2982..78e36a8 100644 --- a/app/pages/CreateCollection.tsx +++ b/app/pages/CreateCollection.tsx @@ -188,6 +188,7 @@ export const CreateCollectionPage = () => { const { data, error } = await tryCatchAPI( fetch(url, { method: "POST", + headers: { "Content-Type": "application/json" }, body: JSON.stringify({ ...collectionInfo, is_private: isPrivate, diff --git a/player-parsers/Dockerfile b/player-parsers/Dockerfile index 0963569..8cbd672 100644 --- a/player-parsers/Dockerfile +++ b/player-parsers/Dockerfile @@ -10,6 +10,5 @@ RUN npm ci COPY *.ts ./ EXPOSE 7000 -ENV PORT=7000 -ENV HOSTNAME="0.0.0.0" + CMD ["npm", "run", "serve"] \ No newline at end of file diff --git a/player-parsers/index.ts b/player-parsers/index.ts index e3b891f..c349a49 100644 --- a/player-parsers/index.ts +++ b/player-parsers/index.ts @@ -5,42 +5,58 @@ import { getKodikURL } from "./kodik"; import express from "express"; 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" + ); + res.header("Access-Control-Allow-Methods", "GET,HEAD,POST,OPTIONS"); + next(); +}); -const host = "0.0.0.0"; -const port = 7000; +const HOST = process.env.HOST || "0.0.0.0"; +const PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : 7001; const allowedPlayers = ["kodik", "libria", "sibnet"]; app.get("/", (req, res) => { - const urlParams = new URLSearchParams(req.query) + const urlParams = new URLSearchParams(req.query); const url = urlParams.get("url"); const player = urlParams.get("player"); if (!url) { - asJSON(res, { message: "no 'url' query provided" }, 400) - return + asJSON(req, res, { message: "no 'url' query provided" }, 400); + return; } if (!player) { - asJSON(res, { message: "no 'player' query provided" }, 400) - return + asJSON(req, res, { message: "no 'player' query provided" }, 400); + return; } switch (player) { case "libria": - getAnilibriaURL(res, url) - return + getAnilibriaURL(req, res, url); + return; case "sibnet": - getSibnetURL(res, url) - return + getSibnetURL(req, res, url); + return; case "kodik": - getKodikURL(res, url) - return + getKodikURL(req, res, url); + return; default: - asJSON(res, { message: `player '${player}' is not supported. choose one of: ${allowedPlayers.join(", ")}` }, 400) - return + asJSON( + req, + res, + { + message: `player '${player}' is not supported. choose one of: ${allowedPlayers.join(", ")}`, + }, + 400 + ); + return; } }); -app.listen(port, host, function () { - console.log(`Server listens http://${host}:${port}`); +app.listen(PORT, HOST, function () { + console.log(`Server listens http://${HOST}:${PORT}`); }); diff --git a/player-parsers/kodik.ts b/player-parsers/kodik.ts index 2b033d6..2cca395 100644 --- a/player-parsers/kodik.ts +++ b/player-parsers/kodik.ts @@ -1,12 +1,12 @@ import { asJSON, randomUA } from "./shared"; const altDomains = ["kodik.info", "aniqit.com", "kodik.cc", "kodik.biz"]; -export async function getKodikURL(res, url: string) { +export async function getKodikURL(req, res, url: string) { const origDomain = url.replace("https://", "").split("/")[0]; let domain = url.replace("https://", "").split("/")[0]; if (!altDomains.includes(domain)) { - asJSON(res, { message: "Wrong url provided for player kodik" }, 400); + asJSON(req, res, { message: "Wrong url provided for player kodik" }, 400); return; } @@ -45,7 +45,7 @@ export async function getKodikURL(res, url: string) { } if (!pageRes.ok) { - asJSON(res, { message: "KODIK: failed to load page" }, 500); + asJSON(req, res, { message: "KODIK: failed to load page" }, 500); return; } @@ -54,7 +54,7 @@ export async function getKodikURL(res, url: string) { const urlParamsMatch = urlParamsRe.exec(pageData); if (!urlParamsMatch || urlParamsMatch.length == 0) { - asJSON(res, { message: `KODIK: failed to find data to parse` }, 500); + asJSON(req, res, { message: `KODIK: failed to find data to parse` }, 500); return; } @@ -86,7 +86,7 @@ export async function getKodikURL(res, url: string) { }); if (!linksRes.ok) { - asJSON(res, { message: `KODIK: failed to get links` }, 500); + asJSON(req, res, { message: `KODIK: failed to get links` }, 500); return; } @@ -117,7 +117,7 @@ export async function getKodikURL(res, url: string) { "thumb001.jpg" ); - asJSON(res, data, 200); + asJSON(req, res, data, 200); return; } diff --git a/player-parsers/libria.ts b/player-parsers/libria.ts index 73a23bf..845ba5f 100644 --- a/player-parsers/libria.ts +++ b/player-parsers/libria.ts @@ -1,9 +1,9 @@ import { asJSON } from "./shared"; const API_URL = "https://api.anilibria.tv/v3/title" -export async function getAnilibriaURL(res, url: string) { +export async function getAnilibriaURL(req, res, url: string) { if (!url.includes("libria")) { - asJSON(res, { message: "Wrong url provided for player libria" }, 400); + asJSON(req, res, { message: "Wrong url provided for player libria" }, 400); return } @@ -14,7 +14,7 @@ export async function getAnilibriaURL(res, url: string) { let apiRes = await fetch(`${API_URL}?id=${releaseId}`); if (!apiRes.ok) { - asJSON(res, { message: "LIBRIA: failed to get api response" }, 500); + asJSON(req, res, { message: "LIBRIA: failed to get api response" }, 500); return } @@ -26,7 +26,7 @@ export async function getAnilibriaURL(res, url: string) { } - asJSON(res, data, 200); + asJSON(req, res, data, 200); return } diff --git a/player-parsers/shared.ts b/player-parsers/shared.ts index fd3a955..b03c5eb 100644 --- a/player-parsers/shared.ts +++ b/player-parsers/shared.ts @@ -1,14 +1,10 @@ export const corsHeaders = { "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Headers": "Origin, X-Requested-With, Content-Type, Accept", "Access-Control-Allow-Methods": "GET,HEAD,POST,OPTIONS", "Cache-Control": "no-cache", }; -export const resHeaders = { - ...corsHeaders, - "Content-Type": "application/json", -}; - export const USERAGENTS = [ "Mozilla/5.0 (Linux; Android 12.0; LG G8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.2.7124.71 Mobile Safari/537.36", "Mozilla/5.0 (Windows NT 11.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.5.1269.13 Safari/537.36", @@ -37,7 +33,9 @@ export const USERAGENTS = [ "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:128.0) Gecko/128.0 Firefox/128.0", ]; -export function asJSON(res, object: any, status: number) { +export function asJSON(req, res, object: any, status: number) { + corsHeaders["Access-Control-Allow-Origin"] = req.headers.origin || "*"; + res.status(status).type("application/json"); res.set(corsHeaders); res.send(JSON.stringify(object)); diff --git a/player-parsers/sibnet.ts b/player-parsers/sibnet.ts index ab9b752..756d25f 100644 --- a/player-parsers/sibnet.ts +++ b/player-parsers/sibnet.ts @@ -1,9 +1,9 @@ import { asJSON, randomUA } from "./shared"; -export async function getSibnetURL(res, url: string) { +export async function getSibnetURL(req, res, url: string) { if (!url.includes("sibnet")) { - asJSON(res, { message: "Wrong url provided for player sibnet" }, 400); + asJSON(req, res, { message: "Wrong url provided for player sibnet" }, 400); return } @@ -15,7 +15,7 @@ export async function getSibnetURL(res, url: string) { }, }); if (!pageRes.ok) { - asJSON(res, { message: `SIBNET:${pageRes.status}: failed to load page` }, 500) + asJSON(req, res, { message: `SIBNET:${pageRes.status}: failed to load page` }, 500) return } const pageData = await pageRes.text(); @@ -23,7 +23,7 @@ export async function getSibnetURL(res, url: string) { const videoMatch = videoRe.exec(pageData); if (!videoMatch || videoMatch.length == 0) { - asJSON(res, { message: `SIBNET: failed to find data to parse` }, 500) + asJSON(req, res, { message: `SIBNET: failed to find data to parse` }, 500) return } @@ -42,7 +42,7 @@ export async function getSibnetURL(res, url: string) { ); if (!actualVideoRes.headers.get("location")) { - asJSON(res, { message: `SIBNET: failed to get video link` }, 500) + asJSON(req, res, { message: `SIBNET: failed to get video link` }, 500) return } @@ -54,6 +54,6 @@ export async function getSibnetURL(res, url: string) { : null : null; - asJSON(res, { manifest: video, poster }, 200) + asJSON(req, res, { manifest: video, poster }, 200) return }