SIAP / src /sub-router /proxima.ts
GUI-STUDIO
changes
699186a
Raw
History Blame Contribute Delete
12.1 kB
import { Hono, type Context } from "hono";
import { proxy } from "hono/proxy";
import { NS_promise } from "../../../../../packages/lib/helpers/client/promise";
import { NS_routes_c } from "../../../../../packages/lib/helpers/client/route";
import { stream } from "hono/streaming";
import prettify from "html-prettify";
import { NS_Misc } from "../../../../../packages/lib/helpers/client/misc";
import { NS_playback_helper } from "../../../../../packages/lib/helpers/server/playback";
import axios from "axios";
import { sValidator } from "@hono/standard-validator";
import { NS_unpack_proxima } from "../../../../../packages/lib/helpers/server/proxima/unpack";
import { NS_error } from "../../../../../packages/lib/helpers/client/error";
import { NS_proxy } from "../../../../../packages/lib/helpers/client/proxy";
import { NS_html_proxima } from "../../../../../packages/lib/helpers/server/proxima/html";
import ky from "ky";
const APP_ENV = process.env.APP_ENV;
const X_OFFLINE = process.env.X_OFFLINE;
const APP_ORIGIN = process.env.APP_ORIGIN;
const REPO_ORIGIN = process.env.REPO_ORIGIN;
const sub_router = new Hono().basePath("/proxima");
sub_router.get(
"/:proxima_encoded",
sValidator("param", NS_unpack_proxima.schema_2, (result, c) => {
if (!result.success) {
const message = result.error[0].message;
return c.text(message, 400);
}
}),
async (ctx) => {
const params = ctx.req.valid("param");
const query = ctx.req.query();
const REQ0 = new URL(ctx.req.url);
const REQ_ORIGIN = APP_ORIGIN || REQ0.origin;
const REQ_headers = ctx.req.raw.headers;
const { proxima_encoded } = params;
const _is_proxima_h = REQ0.pathname.includes("/proxima-h/");
const [E_, R_] = await NS_promise.F_catch_promise({
promise: NS_unpack_proxima.F_unpack_proxima_endpoint_n_commands({
proxima_encoded
})
});
if (E_ || !R_) {
const { status, message } = NS_error.F_collect_error_status_n_message({
ERR: E_
});
return ctx.text(message, status);
}
const { decoded_proxy_endpoint, decoded_proxy_commands } = R_;
const iFilm_id =
decoded_proxy_commands?.iMovie_id || decoded_proxy_commands?.iFilm_id;
const season = decoded_proxy_commands?.season;
const episode = decoded_proxy_commands?.episode;
const picture_quality = decoded_proxy_commands?.picture_quality;
const og_endpoint = decoded_proxy_endpoint.href;
if (query?.ops === "decode") {
return ctx.json({
decoded_proxy_endpoint: og_endpoint,
decoded_proxy_commands
});
}
const replace_response_content_with =
decoded_proxy_commands?.replace_response_content_with;
if (decoded_proxy_commands)
delete decoded_proxy_commands.replace_response_content_with;
const req0_headers_object = Object.fromEntries(
(REQ_headers as any).entries()
);
const upstream_url = decoded_proxy_endpoint.href;
const send_headers = NS_proxy.modify_request_headers({
req_headers: req0_headers_object,
new_headers:
decoded_proxy_commands?.headers || decoded_proxy_commands?.header || {},
avoid_cert_val: false
});
const fallback_env = "cloudflare"; // edgeone
const fallback_proxy = await (async () => {
if (!fallback_env || REPO_ORIGIN === REQ_ORIGIN) return;
const _ = new URL("/proxima/multiverse/next", REPO_ORIGIN);
_.searchParams.set("service_env", fallback_env);
const [E_, R_] = await NS_promise.F_catch_promise({
promise: ky.get(_.toString()).json()
});
const proxy = (R_ as any)?.data?.url;
if (!proxy) {
return;
}
const _u = new URL(REQ0.pathname, proxy);
if (query?.ops) {
_u.searchParams.set("ops", query.ops);
}
const new_url = _u.toString().replace("/proxima-h", "/proxima");
return new_url;
})();
// 1. fetch upstream
const upstream_v1 = async () => {
try {
return await proxy(upstream_url, {
headers: send_headers,
method: decoded_proxy_commands?.method || "GET",
body: decoded_proxy_commands?.upstream_payload
? JSON.stringify(decoded_proxy_commands.upstream_payload)
: undefined
});
} catch (err: any) {
if (err?.code === "ConnectionRefused" || err?.code === "ECONNREFUSED") {
if (
!decoded_proxy_commands ||
!decoded_proxy_commands.ECONNREFUSED_fallback
)
return;
const ECONNREFUSED_fallback = new URL(
decoded_proxy_commands.ECONNREFUSED_fallback.toString()
);
if (
send_headers &&
send_headers.host &&
send_headers.host !== ECONNREFUSED_fallback.hostname
) {
send_headers.host = ECONNREFUSED_fallback.hostname;
}
console.log({ ECONNREFUSED_fallback }, { send_headers });
return await proxy(ECONNREFUSED_fallback.toString(), {
headers: send_headers,
method: decoded_proxy_commands?.method || "GET",
body: decoded_proxy_commands?.upstream_payload
? JSON.stringify(decoded_proxy_commands.upstream_payload)
: undefined
});
}
}
};
const upstream_v2 = async () => {
const _1 = await proxy(upstream_url, {
headers: send_headers,
method: decoded_proxy_commands?.method || "GET",
body: decoded_proxy_commands?.upstream_payload
? JSON.stringify(decoded_proxy_commands.upstream_payload)
: undefined
});
if (!_1 || !fallback_proxy) {
return _1;
}
const content_length = _1.headers.get("content-length");
if (_1.ok && content_length && parseInt(content_length) > 0) {
return _1;
}
if (query?.ch !== "1") {
return _1;
}
return _1;
// if (!fallback_proxy) {
// return _1;
// }
// const _u = new URL(fallback_proxy);
// _u.searchParams.set("ops", "raw-ch");
// const _2 = await proxy(_u.toString());
// return _2;
};
// const upstream = await upstream_v1();
const upstream = await upstream_v2();
// 2. short-circuit
if (!upstream) {
return new Response("upstream undefined", {
status: 500
});
}
if (query?.ops === "raw") {
return upstream;
}
if (query?.ops === "raw2") {
const data = {
app: "hono",
REPO_ORIGIN,
APP_ORIGIN,
fallback_env,
fallback_proxy,
status: upstream.status,
headers: Object.fromEntries(upstream.headers.entries()),
ok: upstream.ok
};
return ctx.json(data);
}
const content_length = upstream.headers.get("content-length");
if (
decoded_proxy_commands?.redirect_origin &&
(["403", "500", "503", "429", "401", "404"].includes(
`${upstream.status}`
) ||
(content_length && !parseInt(content_length))) &&
fallback_proxy
) {
// if (query?.ch !== "1") {
return ctx.redirect(fallback_proxy, 308);
// }
}
// 3. strip security headers
upstream.headers.delete("content-security-policy");
upstream.headers.delete("x-frame-options");
upstream.headers.delete("link");
upstream.headers.delete("x-proxied-path");
upstream.headers.delete("x-proxied-host");
const resHeaders = new Headers(upstream.headers);
const res_headers_obj = Object.fromEntries((resHeaders as any).entries());
const content_type = resHeaders.get("content-type") ?? "";
if (!upstream.ok) {
// simply forward the response unchanged
return upstream;
}
if (replace_response_content_with) {
upstream.headers.delete("content-length");
}
const is_part_m3u8 =
decoded_proxy_commands?.m3u8_playlist_child ||
(await NS_playback_helper.F_is_M3U8_response({
response: upstream.clone()
})) ||
content_type?.includes("application/vnd.apple.mpegurl") ||
content_type?.includes("application/x-mpegurl") ||
decoded_proxy_endpoint.toString().includes(".m3u8");
const is_m3u8 =
content_type?.includes("application/vnd.apple.mpegurl") ||
content_type?.includes("application/x-mpegurl") ||
decoded_proxy_endpoint.toString().includes(".m3u8");
// 4. HTML rewrite
if (
!decoded_proxy_commands?.m3u8_playlist_child &&
content_type?.includes("text/html") &&
!is_part_m3u8
) {
return stream(ctx, async (stream) => {
ctx.header("Content-Encoding", "Identity");
ctx.status(upstream.status as any);
for (const key in res_headers_obj) {
if (Object.prototype.hasOwnProperty.call(res_headers_obj, key)) {
const element = res_headers_obj[key];
ctx.header(key, element);
}
}
set_manual_response_headers();
stream.onAbort(() => {
console.log("Aborted!");
});
await stream.write(
await (async () => {
let html = replace_response_content_with || (await upstream.text());
const [E_, rewritten] = await NS_promise.F_catch_promise({
promise: NS_html_proxima.F_transform_html_content({
html,
target_endpoint: decoded_proxy_endpoint,
proxima_commands: decoded_proxy_commands,
REQ_ORIGIN
})
});
return rewritten || "";
})()
);
});
}
if (is_m3u8) {
let m3u8_content =
replace_response_content_with || (await upstream.text());
const [E_, R_] = await NS_promise.F_catch_promise({
promise: NS_playback_helper.F_transform_m3u8_v2({
content: m3u8_content,
true_origin: decoded_proxy_endpoint.origin,
true_base: (() => {
const _ = NS_Misc.F_remove_last_array_element({
arr: decoded_proxy_endpoint.pathname.split("/")
}).join("/");
if (!_ && decoded_proxy_endpoint.pathname) {
return "/";
}
return _;
})(),
proxy_base: NS_routes_c.F_flat_url_slash(
REQ_ORIGIN + (_is_proxima_h ? "/proxima-h" : "/proxima")
),
proxima_commands: decoded_proxy_commands
? { ...decoded_proxy_commands }
: {},
search_params: query.ch ? { ch: query.ch } : {}
})
});
m3u8_content = R_ || m3u8_content || "";
if (query.ops === "get-master-resolutions") {
const _ = NS_playback_helper.F_extract_resolutions_from_M3U8({
m3u8Content: m3u8_content
});
const play_resolutions = _?.rxd || [];
// const adaptive_res_status = NS_playback_helper.F_get_adaptive_res_status(m3u8_content);
const is_fhd = _?.fhd;
const is_hd = _?.hd;
return ctx.json({
is_fhd,
is_hd,
resolutions: play_resolutions,
content: m3u8_content
});
}
ctx.status(upstream.status as any);
for (const key in res_headers_obj) {
if (key in res_headers_obj) {
const element = res_headers_obj[key];
if (`${key}`.toLowerCase() == "content-length") {
continue;
}
ctx.header(key, element);
}
}
set_manual_response_headers();
return ctx.body(m3u8_content);
}
set_manual_response_headers();
return upstream;
function set_manual_response_headers() {
if (
decoded_proxy_commands.response_headers &&
NS_Misc.F_is_record(decoded_proxy_commands.response_headers)
) {
for (const k in decoded_proxy_commands.response_headers) {
const v = decoded_proxy_commands.response_headers[k];
ctx.header(k, v);
}
}
}
}
);
export const ProximaRouter = sub_router;