add videoplayback proxy route + fix potoken generation
This commit is contained in:
parent
be1f4bee39
commit
7ba1425bcd
8 changed files with 125 additions and 4 deletions
2
.github/workflows/docker-build-push.yaml
vendored
2
.github/workflows/docker-build-push.yaml
vendored
|
@ -8,7 +8,7 @@ on:
|
||||||
tags:
|
tags:
|
||||||
- '[0-9]+.[0-9]+.[0-9]+' # Trigger on semantic version tags
|
- '[0-9]+.[0-9]+.[0-9]+' # Trigger on semantic version tags
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- 'Cargo.lock'
|
- '.gitignore'
|
||||||
- 'LICENSE'
|
- 'LICENSE'
|
||||||
- 'README.md'
|
- 'README.md'
|
||||||
- 'docker-compose.yml'
|
- 'docker-compose.yml'
|
||||||
|
|
21
LICENSE
Normal file
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2021 LuanRT
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"tasks": {
|
"tasks": {
|
||||||
"dev": "deno run --allow-net --allow-env --allow-sys=hostname --allow-write=/var/tmp/youtubei.js --watch src/main.ts",
|
"dev": "deno run --allow-net --allow-env --allow-sys=hostname --allow-read --allow-write=/var/tmp/youtubei.js --watch src/main.ts",
|
||||||
"compile": "deno compile --output invidious_companion --allow-net --allow-env --allow-read --allow-sys=hostname --allow-write=/var/tmp/youtubei.js src/main.ts"
|
"compile": "deno compile --output invidious_companion --allow-net --allow-env --allow-read --allow-sys=hostname --allow-write=/var/tmp/youtubei.js src/main.ts"
|
||||||
},
|
},
|
||||||
"imports": {
|
"imports": {
|
||||||
|
|
|
@ -5,6 +5,7 @@ export const getFetchClient = (konfigStore: Store): {
|
||||||
(input: Request | URL | string, init?: RequestInit & {
|
(input: Request | URL | string, init?: RequestInit & {
|
||||||
client: Deno.HttpClient;
|
client: Deno.HttpClient;
|
||||||
}): Promise<Response>;
|
}): Promise<Response>;
|
||||||
|
(input: URL | Request | string, init?: RequestInit): Promise<Response>;
|
||||||
} => {
|
} => {
|
||||||
if (Deno.env.get("PROXY") || konfigStore.get("networking.proxy")) {
|
if (Deno.env.get("PROXY") || konfigStore.get("networking.proxy")) {
|
||||||
return async (
|
return async (
|
||||||
|
|
|
@ -47,7 +47,7 @@ innertubeClient = await Innertube.create({
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!innertubeClientOauthEnabled) {
|
if (!innertubeClientOauthEnabled) {
|
||||||
if (innertubeClientOauthEnabled) {
|
if (innertubeClientJobPoTokenEnabled) {
|
||||||
innertubeClient = await poTokenGenerate(
|
innertubeClient = await poTokenGenerate(
|
||||||
innertubeClient,
|
innertubeClient,
|
||||||
konfigStore,
|
konfigStore,
|
||||||
|
@ -58,7 +58,7 @@ if (!innertubeClientOauthEnabled) {
|
||||||
"regenerate youtube session",
|
"regenerate youtube session",
|
||||||
konfigStore.get("jobs.youtube_session.frequency") as string,
|
konfigStore.get("jobs.youtube_session.frequency") as string,
|
||||||
async () => {
|
async () => {
|
||||||
if (innertubeClientOauthEnabled) {
|
if (innertubeClientJobPoTokenEnabled) {
|
||||||
innertubeClient = await poTokenGenerate(
|
innertubeClient = await poTokenGenerate(
|
||||||
innertubeClient,
|
innertubeClient,
|
||||||
konfigStore,
|
konfigStore,
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { bearerAuth } from "hono/bearer-auth";
|
||||||
import youtubeApiPlayer from "./youtube_api_routes/player.ts";
|
import youtubeApiPlayer from "./youtube_api_routes/player.ts";
|
||||||
import invidiousRouteLatestVersion from "./invidious_routes/latestVersion.ts";
|
import invidiousRouteLatestVersion from "./invidious_routes/latestVersion.ts";
|
||||||
import invidiousRouteDashManifest from "./invidious_routes/dashManifest.ts";
|
import invidiousRouteDashManifest from "./invidious_routes/dashManifest.ts";
|
||||||
|
import videoPlaybackProxy from "./videoPlaybackProxy.ts";
|
||||||
|
|
||||||
export const routes = (app: Hono, konfigStore: Store<Record<string, unknown>>) => {
|
export const routes = (app: Hono, konfigStore: Store<Record<string, unknown>>) => {
|
||||||
app.use("*", logger());
|
app.use("*", logger());
|
||||||
|
@ -20,4 +21,5 @@ export const routes = (app: Hono, konfigStore: Store<Record<string, unknown>>) =
|
||||||
app.route("/youtubei/v1", youtubeApiPlayer);
|
app.route("/youtubei/v1", youtubeApiPlayer);
|
||||||
app.route("/latest_version", invidiousRouteLatestVersion);
|
app.route("/latest_version", invidiousRouteLatestVersion);
|
||||||
app.route("/api/manifest/dash/id", invidiousRouteDashManifest);
|
app.route("/api/manifest/dash/id", invidiousRouteDashManifest);
|
||||||
|
app.route("/videoplayback", videoPlaybackProxy);
|
||||||
};
|
};
|
||||||
|
|
97
src/routes/videoPlaybackProxy.ts
Normal file
97
src/routes/videoPlaybackProxy.ts
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
import { Hono } from "hono";
|
||||||
|
import { Store } from "@willsoto/node-konfig-core";
|
||||||
|
import { getFetchClient } from "../lib/helpers/getFetchClient.ts";
|
||||||
|
import { HTTPException } from "hono/http-exception";
|
||||||
|
|
||||||
|
const videoPlaybackProxy = new Hono();
|
||||||
|
|
||||||
|
videoPlaybackProxy.get("/", async (c) => {
|
||||||
|
const { host, c: client } = c.req.query();
|
||||||
|
const urlReq = new URL(c.req.url);
|
||||||
|
|
||||||
|
if (host == undefined || !/[\w-]+.googlevideo.com/.test(host)) {
|
||||||
|
throw new HTTPException(400, {
|
||||||
|
res: new Response("Host do not match or undefined."),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-ignore Do not understand how to fix this error.
|
||||||
|
const konfigStore = await c.get("konfigStore") as Store<
|
||||||
|
Record<string, unknown>
|
||||||
|
>;
|
||||||
|
|
||||||
|
// deno-lint-ignore prefer-const
|
||||||
|
let queryParams = new URLSearchParams(urlReq.search);
|
||||||
|
queryParams.delete("host");
|
||||||
|
queryParams.append("alr", "yes");
|
||||||
|
if (c.req.header("range")) {
|
||||||
|
queryParams.append(
|
||||||
|
"range",
|
||||||
|
(c.req.header("range") as string).split("=")[1],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const headersToSend: HeadersInit = {
|
||||||
|
"accept": "*/*",
|
||||||
|
"accept-encoding": "gzip, deflate, br, zstd",
|
||||||
|
"accept-language": "en-us,en;q=0.5",
|
||||||
|
"origin": "https://www.youtube.com",
|
||||||
|
"referer": "https://www.youtube.com",
|
||||||
|
};
|
||||||
|
|
||||||
|
if (client == "ANDROID") {
|
||||||
|
headersToSend["user-agent"] =
|
||||||
|
"com.google.android.youtube/1537338816 (Linux; U; Android 13; en_US; ; Build/TQ2A.230505.002; Cronet/113.0.5672.24)";
|
||||||
|
} else if (client == "IOS") {
|
||||||
|
headersToSend["user-agent"] =
|
||||||
|
"com.google.ios.youtube/19.32.8 (iPhone14,5; U; CPU iOS 17_6 like Mac OS X;)";
|
||||||
|
} else {
|
||||||
|
headersToSend["user-agent"] =
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36";
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchClient = await getFetchClient(konfigStore);
|
||||||
|
|
||||||
|
let googlevideoResponse = await fetchClient.call(
|
||||||
|
undefined,
|
||||||
|
`https://${host}/videoplayback?${queryParams.toString()}`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
body: new Uint8Array([0x78, 0]), // protobuf: { 15: 0 } (no idea what it means but this is what YouTube uses),
|
||||||
|
headers: headersToSend,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (googlevideoResponse.headers.has("location")) {
|
||||||
|
googlevideoResponse = await fetchClient.call(
|
||||||
|
undefined,
|
||||||
|
googlevideoResponse.headers.get("location") as string,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
body: new Uint8Array([0x78, 0]), // protobuf: { 15: 0 } (no idea what it means but this is what YouTube uses)
|
||||||
|
headers: headersToSend,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Response(googlevideoResponse.body, {
|
||||||
|
status: googlevideoResponse.status,
|
||||||
|
statusText: googlevideoResponse.statusText,
|
||||||
|
headers: {
|
||||||
|
"content-length":
|
||||||
|
googlevideoResponse.headers.get("content-length") || "",
|
||||||
|
"access-control-allow-origin": "*",
|
||||||
|
"accept-ranges": googlevideoResponse.headers.get("accept-ranges") ||
|
||||||
|
"",
|
||||||
|
"cache-control": googlevideoResponse.headers.get("cache-control") ||
|
||||||
|
"",
|
||||||
|
"content-type": googlevideoResponse.headers.get("content-type") ||
|
||||||
|
"",
|
||||||
|
"expires": googlevideoResponse.headers.get("expires") || "",
|
||||||
|
"last-modified": googlevideoResponse.headers.get("last-modified") ||
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
export default videoPlaybackProxy;
|
Loading…
Add table
Reference in a new issue