feat: add support for encrypted query parameters
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 1m18s

This commit is contained in:
Fijxu 2025-03-14 22:20:27 -03:00
parent f266edfedb
commit 90ab018db8
Signed by: Fijxu
GPG key ID: 32C1DDF333EDA6A4
5 changed files with 111 additions and 14 deletions

View file

@ -6,6 +6,7 @@ secret_key = "CHANGE_ME"
base_url = "http://localhost:8282"
verify_requests = false
# max_dash_resolution = 1080
# encrypt_query_params = false
[cache]
enabled = true

View file

@ -0,0 +1,62 @@
import { Store } from "@willsoto/node-konfig-core";
import { decodeBase64, encodeBase64 } from "jsr:@std/encoding/base64";
import { Aes } from "https://deno.land/x/crypto@v0.10.1/aes.ts";
import {
Ecb,
Padding,
} from "https://deno.land/x/crypto@v0.10.1/block-modes.ts";
export const encryptQuery = (
queryParams: string,
konfigStore: Store,
): string => {
try {
const cipher = new Ecb(
Aes,
new TextEncoder().encode((
Deno.env.get("SERVER_SECRET_KEY") ||
konfigStore.get("server.secret_key") as string
).substring(0, 16)),
Padding.PKCS7,
);
const encodedData = new TextEncoder().encode(
queryParams
);
const encryptedData = cipher.encrypt(encodedData)
return encodeBase64(encryptedData).replace(/\+/g, "-").replace(/\//g, "_")
} catch (_) {
return ""
}
};
export const decryptQuery = (
queryParams: string,
konfigStore: Store,
): string => {
try {
const decipher = new Ecb(
Aes,
new TextEncoder().encode((
Deno.env.get("SERVER_SECRET_KEY") ||
konfigStore.get("server.secret_key") as string
).substring(0, 16)),
Padding.PKCS7,
);
const decryptedData = new TextDecoder().decode(
decipher.decrypt(
decodeBase64(
queryParams.replace(/-/g, "+").replace(/_/g, "/"),
),
),
);
console.log(decryptedData)
return decryptedData
} catch (_) {
return ""
}
};

View file

@ -6,6 +6,7 @@ import {
} from "../../lib/helpers/youtubePlayerHandling.ts";
import { verifyRequest } from "../../lib/helpers/verifyRequest.ts";
import { HTTPException } from "hono/http-exception";
import { encryptQuery } from "../../lib/helpers/encrypter.ts";
const dashManifest = new Hono();
@ -93,7 +94,22 @@ dashManifest.get("/:videoId", async (c) => {
videoInfo.page[0].video_details?.is_post_live_dvr,
(url: URL) => {
let dashUrl = url;
let queryParams = dashUrl.search.substring(1) + "&host=" +
dashUrl.host;
if (local) {
if (konfigStore.get("networking.ump") as boolean) {
queryParams = queryParams + "&ump=1";
}
if (
Deno.env.get("ENCRYPT_QUERY_PARAMS") ||
konfigStore.get("server.encrypt_query_params") as boolean
) {
queryParams = "enc=yes&data=" + encryptQuery(
queryParams,
konfigStore,
);
}
// Can't create URL type without host part
dashUrl = (
Deno.env.get("EXTERNAL_VIDEOPLAYBACK_PROXY") ||
@ -101,11 +117,8 @@ dashManifest.get("/:videoId", async (c) => {
"networking.external_videoplayback_proxy",
) as string ?? "")
) +
(dashUrl.pathname + dashUrl.search + "&host=" +
dashUrl.host) as unknown as URL;
if (konfigStore.get("networking.ump") as boolean) {
dashUrl = dashUrl + "&ump=1" as unknown as URL;
}
(dashUrl.pathname + "?" +
queryParams) as unknown as URL;
return dashUrl;
} else {
return dashUrl;

View file

@ -5,6 +5,7 @@ import {
youtubeVideoInfo,
} from "../../lib/helpers/youtubePlayerHandling.ts";
import { verifyRequest } from "../../lib/helpers/verifyRequest.ts";
import { encryptQuery } from "../../lib/helpers/encrypter.ts";
const latestVersion = new Hono();
@ -62,15 +63,24 @@ latestVersion.get("/", async (c) => {
const itagUrl = selectedItagFormat[0].url as string;
const itagUrlParsed = new URL(itagUrl);
let urlToRedirect = itagUrlParsed.toString();
let queryParams = itagUrlParsed.search.substring(1) + "&host=" +
itagUrlParsed.host;
if (local) {
if (
Deno.env.get("ENCRYPT_QUERY_PARAMS") ||
konfigStore.get("server.encrypt_query_params") as boolean
) {
queryParams = "enc=yes&data=" + encryptQuery(
queryParams,
konfigStore,
);
}
urlToRedirect = (
Deno.env.get("EXTERNAL_VIDEOPLAYBACK_PROXY") ||
(konfigStore.get(
"networking.external_videoplayback_proxy",
) as string ?? "")
) +
itagUrlParsed.pathname + itagUrlParsed.search +
"&host=" + itagUrlParsed.host;
) + (itagUrlParsed.pathname + "?" + queryParams) as string;
}
return c.redirect(urlToRedirect);
}

View file

@ -1,5 +1,6 @@
import { Hono } from "hono";
import { HTTPException } from "hono/http-exception";
import { decryptQuery } from "../lib/helpers/encrypter.ts";
let getFetchClientLocation = "getFetchClient";
if (Deno.env.get("GET_FETCH_CLIENT_LOCATION")) {
if (Deno.env.has("DENO_COMPILED")) {
@ -16,9 +17,23 @@ const { getFetchClient } = await import(getFetchClientLocation);
const videoPlaybackProxy = new Hono();
videoPlaybackProxy.get("/", async (c) => {
const { host, c: client, expire } = c.req.query();
const konfigStore = c.get("konfigStore");
let host, client, expire, urlReq, queryParams;
if (c.req.query("enc") === "yes") {
const { data } = c.req.query();
const unencryptedQueryParams = decryptQuery(data, konfigStore);
queryParams = new URLSearchParams(unencryptedQueryParams);
host = queryParams.get("host");
client = queryParams.get("c");
expire = queryParams.get("expire");
} else {
urlReq = new URL(c.req.url);
queryParams = new URLSearchParams(urlReq.search);
({ host, c: client, expire } = c.req.query());
}
const rangeHeader = c.req.header("range") as string | undefined;
const urlReq = new URL(c.req.url);
if (host == undefined || !/[\w-]+.googlevideo.com/.test(host)) {
throw new HTTPException(400, {
@ -43,10 +58,6 @@ videoPlaybackProxy.get("/", async (c) => {
});
}
const konfigStore = c.get("konfigStore");
// deno-lint-ignore prefer-const
let queryParams = new URLSearchParams(urlReq.search);
queryParams.delete("host");
if (rangeHeader) {
queryParams.append(