| 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"; |
|
|
| 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; |
| })(); |
|
|
| |
| 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; |
|
|
| |
| |
| |
|
|
| |
| |
|
|
| |
|
|
| |
| }; |
|
|
| |
| const upstream = await upstream_v2(); |
|
|
| |
| 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 |
| ) { |
| |
| return ctx.redirect(fallback_proxy, 308); |
| |
| } |
|
|
| |
| 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) { |
| |
| 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"); |
|
|
| |
| 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 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; |
|
|