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;