feat/api-prox: add support for post requests, add 'sponsor' and 'toggles' hooks

This commit is contained in:
Kentai Radiquum 2025-07-02 16:51:16 +05:00
parent 6f45876240
commit bfe932d86c
Signed by: Radiquum
GPG key ID: 858E8EE696525EED
5 changed files with 216 additions and 15 deletions

18
api-prox/hooks/profile.ts Normal file
View file

@ -0,0 +1,18 @@
// хук включает "вечную" спонсорку, отключая рекламу после входа в профиль, в официальном приложении
export function match(path: string): boolean {
const pathRe = /^\/profile\/\d+/;
if (pathRe.test(path) || path == "/profile/info") return true;
return false;
}
export async function get(data: any, url: URL) {
if (data.hasOwnProperty("profile")) {
data["profile"]["is_sponsor"] = true;
data["profile"]["sponsorshipExpires"] = 2147483647;
} else {
data["is_sponsor"] = true;
data["sponsorship_expires"] = 2147483647;
}
return data;
}

66
api-prox/hooks/toggles.ts Normal file
View file

@ -0,0 +1,66 @@
// хук изменяет ответ config/toggles
export interface Toggles {
minVersionCode: number;
lastVersionCode: number;
whatsNew: string;
downloadLink: string;
minGPVersionCode: number;
lastGPVersionCode: number;
gpWhatsNew: string;
gpDownloadLink: string;
overrideGPVersion: boolean;
inAppUpdates: boolean;
inAppUpdatesImmediate: boolean;
inAppUpdatesFlexibleDelay: number;
impMessageEnabled: boolean;
impMessageText: string;
impMessageBackgroundColor: string;
impMessageTextColor: string;
impMessageLink: string;
adBannerBlockId: string;
adBannerSizeType: number;
adInterstitialBlockId: string;
adBannerDelay: number;
adInterstitialDelay: number;
kodikVideoLinksUrl: string;
kodikIframeAd: boolean;
sibnetRandUserAgent: boolean;
sibnetUserAgent: string;
torlookUrl: string;
baseUrl: string;
apiUrl: string;
apiAltUrl: string;
apiAltAvailable: boolean;
iframeEmbedUrl: string;
kodikAdIframeUrl: string;
sponsorshipPromotion: boolean;
sponsorshipText: string;
sponsorshipAvailable: boolean;
pageNoConnectionUrl: string;
snowfall: boolean;
searchBarIconUrl: string;
searchBarIconTint: string;
searchBarIconAction: string;
searchBarIconValue: string;
min_blog_create_rating_score: number;
}
export function match(path: string): boolean {
if (path == "/config/toggles") return true;
return false;
}
export async function get(data: Toggles, url: URL) {
data.lastVersionCode = 25062200;
data.impMessageEnabled = true;
data.impMessageText = "разработчик AniX / Api-Prox-Svc";
data.impMessageLink = "https://bento.me/radiquum";
data.impMessageBackgroundColor = "ffb3d0"
data.impMessageTextColor = "ffffff"
data.apiAltAvailable = false;
data.apiAltUrl = "";
return data;
}

View file

@ -3,18 +3,28 @@ import express from "express";
import fs from "fs/promises"; import fs from "fs/promises";
const app = express(); const app = express();
app.use(express.json());
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 = []; const loadedHooks = [];
app.get("/*path", async (req, res) => { app.get("/*path", async (req, res) => {
const url = new URL(`${ANIXART_API}${req.url}`); if (req.path == "/favicon.ico") return asJSON(res, {}, 404);
logger.debug(`Fetching ${url.protocol}//${url.hostname}${url.pathname}`);
const isApiV2 = url.searchParams.get("API-Version") == "v2" || false; const url = new URL(`${ANIXART_API}${req.url}`);
if (isApiV2) { logger.debug(
logger.debug(` ↳ Force API V2`); `[${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"; ANIXART_HEADERS["API-Version"] = "v2";
url.searchParams.delete("API-Version"); url.searchParams.delete("API-Version");
} }
@ -28,7 +38,9 @@ app.get("/*path", async (req, res) => {
!apiResponse.ok || !apiResponse.ok ||
apiResponse.headers.get("content-type") != "application/json" apiResponse.headers.get("content-type") != "application/json"
) { ) {
logger.error(`Failed to fetch: ${url.protocol}//${url.hostname}${url.pathname}`) logger.error(
`Failed to fetch: '${url.protocol}//${url.hostname}${url.pathname}', Path probably doesn't exist`
);
asJSON( asJSON(
res, res,
{ {
@ -37,6 +49,7 @@ app.get("/*path", async (req, res) => {
request_status: apiResponse.status, request_status: apiResponse.status,
request_content_type: apiResponse.headers.get("content-type"), request_content_type: apiResponse.headers.get("content-type"),
}, },
reason: "Path probably doesn't exist",
}, },
500 500
); );
@ -87,6 +100,92 @@ app.get("/*path", async (req, res) => {
return; 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 = {
"User-Agent": ANIXART_HEADERS["User-Agent"],
"Content-Type": req.headers["content-type"],
};
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.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.status,
request_content_type: apiResponse.headers.get("content-type"),
},
reason: "Path probably doesn't exist",
},
500
);
return;
}
let data = await apiResponse.json();
asJSON(res, data, 200);
return;
});
app.listen(port, host, function () { app.listen(port, host, function () {
logger.info(`Server listen: http://${host}:${port}`); logger.info(`Server listen: http://${host}:${port}`);
}); });

View file

@ -10,13 +10,13 @@ export const resHeaders = {
}; };
export function asJSON(res, object: any, status: number) { export function asJSON(res, object: any, status: number) {
res.status(status).type("application/json"); res.status(status);
res.set(corsHeaders); res.set(resHeaders);
res.send(JSON.stringify(object)); res.send(JSON.stringify(object));
} }
export const ANIXART_UA = export const ANIXART_UA =
"AnixartApp/8.2.1-23121216 (Android 9; SDK 28; arm64-v8a; samsung SM-G975N; en)"; "AnixartApp/9.0 BETA 5-25062213 (Android 9; SDK 28; arm64-v8a; samsung SM-G975N; en)";
export const ANIXART_API = "https://api.anixart.app"; export const ANIXART_API = "https://api.anixart.app";
export const ANIXART_HEADERS = { export const ANIXART_HEADERS = {
"User-Agent": ANIXART_UA, "User-Agent": ANIXART_UA,
@ -47,6 +47,10 @@ export class Log {
return `${datetime.getHours().toString().padStart(2, "0")}:${datetime.getMinutes().toString().padStart(2, "0")}:${datetime.getSeconds().toString().padStart(2, "0")}`; return `${datetime.getHours().toString().padStart(2, "0")}:${datetime.getMinutes().toString().padStart(2, "0")}:${datetime.getSeconds().toString().padStart(2, "0")}`;
} }
console(logLevel: LogLevel = "info", ...msg: any[]) {
if (this.levelInt[this.level] <= this.levelInt[logLevel])
console.log(`[${logLevel.toUpperCase()}](${this.getTime()}) -> `, ...msg);
}
debug(...msg: string[]) { debug(...msg: string[]) {
if (this.levelInt[this.level] <= 0) if (this.levelInt[this.level] <= 0)
console.log(`[DEBUG](${this.getTime()}) -> ${this.getString(...msg)}`); console.log(`[DEBUG](${this.getTime()}) -> ${this.getString(...msg)}`);
@ -64,23 +68,37 @@ export class Log {
console.log(`[ERROR](${this.getTime()}) -> ${this.getString(...msg)}`); console.log(`[ERROR](${this.getTime()}) -> ${this.getString(...msg)}`);
} }
consoleHook(logLevel: LogLevel = "info", ...msg: any[]) {
if (this.levelInt[this.level] <= this.levelInt[logLevel])
console.log(
`[${logLevel.toUpperCase()}|HOOK](${this.getTime()}) -> `,
...msg
);
}
debugHook(...msg: string[]) { debugHook(...msg: string[]) {
if (this.levelInt[this.level] <= 0) if (this.levelInt[this.level] <= 0)
console.log(`[DEBUG|HOOK](${this.getTime()}) -> ${this.getString(...msg)}`); console.log(
`[DEBUG|HOOK](${this.getTime()}) -> ${this.getString(...msg)}`
);
} }
infoHook(...msg: string[]) { infoHook(...msg: string[]) {
if (this.levelInt[this.level] <= 1) if (this.levelInt[this.level] <= 1)
console.log(`[INFO|HOOK](${this.getTime()}) -> ${this.getString(...msg)}`); console.log(
`[INFO|HOOK](${this.getTime()}) -> ${this.getString(...msg)}`
);
} }
warnHook(...msg: string[]) { warnHook(...msg: string[]) {
if (this.levelInt[this.level] <= 2) if (this.levelInt[this.level] <= 2)
console.log(`[WARN|HOOK](${this.getTime()}) -> ${this.getString(...msg)}`); console.log(
`[WARN|HOOK](${this.getTime()}) -> ${this.getString(...msg)}`
);
} }
errorHook(...msg: string[]) { errorHook(...msg: string[]) {
if (this.levelInt[this.level] <= 3) if (this.levelInt[this.level] <= 3)
console.log(`[ERROR|HOOK](${this.getTime()}) -> ${this.getString(...msg)}`); console.log(
`[ERROR|HOOK](${this.getTime()}) -> ${this.getString(...msg)}`
);
} }
} }
export const logger = new Log((process.env.LOG_LEVEL as LogLevel) || "info"); export const logger = new Log((process.env.LOG_LEVEL as LogLevel) || "info");

View file

@ -33,5 +33,5 @@
"**/*.tsx", "**/*.tsx",
"next.config.js" "next.config.js"
], ],
"exclude": ["node_modules", "player-parsers"] "exclude": ["node_modules", "player-parsers", "api-prox"]
} }