feat: add support for encrypted query parameters
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 1m18s
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 1m18s
This commit is contained in:
parent
f266edfedb
commit
90ab018db8
5 changed files with 111 additions and 14 deletions
|
@ -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
|
||||
|
|
62
src/lib/helpers/encrypter.ts
Normal file
62
src/lib/helpers/encrypter.ts
Normal 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 ""
|
||||
}
|
||||
};
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
Reference in a new issue