diff --git a/api-prox/hooks/profile.ts b/api-prox/hooks/profile.ts new file mode 100644 index 0000000..3a25e2b --- /dev/null +++ b/api-prox/hooks/profile.ts @@ -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; +} diff --git a/api-prox/hooks/toggles.ts b/api-prox/hooks/toggles.ts new file mode 100644 index 0000000..180c63f --- /dev/null +++ b/api-prox/hooks/toggles.ts @@ -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; +} diff --git a/api-prox/index.ts b/api-prox/index.ts index 823f219..60f53be 100644 --- a/api-prox/index.ts +++ b/api-prox/index.ts @@ -3,18 +3,28 @@ import express from "express"; import fs from "fs/promises"; const app = express(); +app.use(express.json()); +app.use(express.urlencoded({ extended: true })); + const host = "0.0.0.0"; const port = 7001; const loadedHooks = []; app.get("/*path", async (req, res) => { - const url = new URL(`${ANIXART_API}${req.url}`); - logger.debug(`Fetching ${url.protocol}//${url.hostname}${url.pathname}`); + if (req.path == "/favicon.ico") return asJSON(res, {}, 404); - const isApiV2 = url.searchParams.get("API-Version") == "v2" || false; - if (isApiV2) { - logger.debug(` ↳ Force API V2`); + 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"); } @@ -28,7 +38,9 @@ app.get("/*path", async (req, res) => { !apiResponse.ok || 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( res, { @@ -37,6 +49,7 @@ app.get("/*path", async (req, res) => { request_status: apiResponse.status, request_content_type: apiResponse.headers.get("content-type"), }, + reason: "Path probably doesn't exist", }, 500 ); @@ -87,6 +100,92 @@ app.get("/*path", async (req, res) => { 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 () { logger.info(`Server listen: http://${host}:${port}`); }); diff --git a/api-prox/shared.ts b/api-prox/shared.ts index 8eb810a..670f2db 100644 --- a/api-prox/shared.ts +++ b/api-prox/shared.ts @@ -10,13 +10,13 @@ export const resHeaders = { }; export function asJSON(res, object: any, status: number) { - res.status(status).type("application/json"); - res.set(corsHeaders); + res.status(status); + res.set(resHeaders); res.send(JSON.stringify(object)); } 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_HEADERS = { "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")}`; } + console(logLevel: LogLevel = "info", ...msg: any[]) { + if (this.levelInt[this.level] <= this.levelInt[logLevel]) + console.log(`[${logLevel.toUpperCase()}](${this.getTime()}) -> `, ...msg); + } debug(...msg: string[]) { if (this.levelInt[this.level] <= 0) console.log(`[DEBUG](${this.getTime()}) -> ${this.getString(...msg)}`); @@ -64,23 +68,37 @@ export class Log { 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[]) { 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[]) { 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[]) { 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[]) { 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"); diff --git a/tsconfig.json b/tsconfig.json index 8fdb696..46666e4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -33,5 +33,5 @@ "**/*.tsx", "next.config.js" ], - "exclude": ["node_modules", "player-parsers"] + "exclude": ["node_modules", "player-parsers", "api-prox"] }