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:
|
||||
- '[0-9]+.[0-9]+.[0-9]+' # Trigger on semantic version tags
|
||||
paths-ignore:
|
||||
- 'Cargo.lock'
|
||||
- '.gitignore'
|
||||
- 'LICENSE'
|
||||
- 'README.md'
|
||||
- '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": {
|
||||
"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"
|
||||
},
|
||||
"imports": {
|
||||
|
|
|
@ -5,6 +5,7 @@ export const getFetchClient = (konfigStore: Store): {
|
|||
(input: Request | URL | string, init?: RequestInit & {
|
||||
client: Deno.HttpClient;
|
||||
}): Promise<Response>;
|
||||
(input: URL | Request | string, init?: RequestInit): Promise<Response>;
|
||||
} => {
|
||||
if (Deno.env.get("PROXY") || konfigStore.get("networking.proxy")) {
|
||||
return async (
|
||||
|
|
|
@ -47,7 +47,7 @@ innertubeClient = await Innertube.create({
|
|||
});
|
||||
|
||||
if (!innertubeClientOauthEnabled) {
|
||||
if (innertubeClientOauthEnabled) {
|
||||
if (innertubeClientJobPoTokenEnabled) {
|
||||
innertubeClient = await poTokenGenerate(
|
||||
innertubeClient,
|
||||
konfigStore,
|
||||
|
@ -58,7 +58,7 @@ if (!innertubeClientOauthEnabled) {
|
|||
"regenerate youtube session",
|
||||
konfigStore.get("jobs.youtube_session.frequency") as string,
|
||||
async () => {
|
||||
if (innertubeClientOauthEnabled) {
|
||||
if (innertubeClientJobPoTokenEnabled) {
|
||||
innertubeClient = await poTokenGenerate(
|
||||
innertubeClient,
|
||||
konfigStore,
|
||||
|
|
|
@ -6,6 +6,7 @@ import { bearerAuth } from "hono/bearer-auth";
|
|||
import youtubeApiPlayer from "./youtube_api_routes/player.ts";
|
||||
import invidiousRouteLatestVersion from "./invidious_routes/latestVersion.ts";
|
||||
import invidiousRouteDashManifest from "./invidious_routes/dashManifest.ts";
|
||||
import videoPlaybackProxy from "./videoPlaybackProxy.ts";
|
||||
|
||||
export const routes = (app: Hono, konfigStore: Store<Record<string, unknown>>) => {
|
||||
app.use("*", logger());
|
||||
|
@ -20,4 +21,5 @@ export const routes = (app: Hono, konfigStore: Store<Record<string, unknown>>) =
|
|||
app.route("/youtubei/v1", youtubeApiPlayer);
|
||||
app.route("/latest_version", invidiousRouteLatestVersion);
|
||||
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…
Reference in a new issue