moving youtubeplayer handling + add latest_version + dash manifest + rewrite config
This commit is contained in:
parent
de5d75d51d
commit
a62729e003
14 changed files with 367 additions and 153 deletions
|
@ -2,9 +2,13 @@
|
|||
port = 8282
|
||||
host = "127.0.0.1"
|
||||
hmac_key = "CHANGE_ME"
|
||||
base_url = "http://localhost:8282"
|
||||
|
||||
[cache]
|
||||
enabled = true
|
||||
|
||||
[networking]
|
||||
#proxy = ""
|
||||
#proxy = ""
|
||||
|
||||
[jobs]
|
||||
po_token_cron = "0 * * * *"
|
|
@ -4,15 +4,15 @@
|
|||
"compile": "deno compile --output invidious_companion --allow-net --allow-env --allow-sys=hostname --allow-read src/main.ts"
|
||||
},
|
||||
"imports": {
|
||||
"@std/assert": "jsr:@std/assert@1",
|
||||
"hono": "jsr:@hono/hono@^4.6.5",
|
||||
"hono/logger": "jsr:@hono/hono@^4.6.5/logger",
|
||||
"hono/bearer-auth": "jsr:@hono/hono@^4.6.5/bearer-auth",
|
||||
"youtubei.js": "npm:youtubei.js@10.5.0/web",
|
||||
"jsdom": "https://esm.sh/jsdom@25.0.1",
|
||||
"bgutils": "https://esm.sh/bgutils-js@3.0.0",
|
||||
"node-config": "npm:config@3.3.12",
|
||||
"toml": "npm:toml@3.0.0",
|
||||
"@willsoto/node-konfig-core": "https://esm.sh/@willsoto/node-konfig-core@5.0.0",
|
||||
"@willsoto/node-konfig-file": "https://esm.sh/@willsoto/node-konfig-file@3.0.0",
|
||||
"@willsoto/node-konfig-toml-parser": "https://esm.sh/@willsoto/node-konfig-toml-parser@3.0.0",
|
||||
"youtubePlayerReq": "./src/lib/helpers/youtubePlayerReq.ts"
|
||||
},
|
||||
"unstable": ["cron", "kv", "http"]
|
||||
|
|
43
deno.lock
43
deno.lock
|
@ -3,13 +3,23 @@
|
|||
"packages": {
|
||||
"specifiers": {
|
||||
"jsr:@hono/hono@^4.6.5": "jsr:@hono/hono@4.6.5",
|
||||
"npm:config@3.3.12": "npm:config@3.3.12",
|
||||
"npm:toml@3.0.0": "npm:toml@3.0.0",
|
||||
"jsr:@std/fs": "jsr:@std/fs@1.0.4",
|
||||
"jsr:@std/path": "jsr:@std/path@1.0.6",
|
||||
"jsr:@std/path@^1.0.6": "jsr:@std/path@1.0.6",
|
||||
"npm:youtubei.js@10.5.0": "npm:youtubei.js@10.5.0"
|
||||
},
|
||||
"jsr": {
|
||||
"@hono/hono@4.6.5": {
|
||||
"integrity": "68efe4a0ab7c4fb082cb71aa894a25e1c6cfad6d124dc943471e6758a7a1bdee"
|
||||
},
|
||||
"@std/fs@1.0.4": {
|
||||
"integrity": "2907d32d8d1d9e540588fd5fe0ec21ee638134bd51df327ad4e443aaef07123c",
|
||||
"dependencies": [
|
||||
"jsr:@std/path@^1.0.6"
|
||||
]
|
||||
},
|
||||
"@std/path@1.0.6": {
|
||||
"integrity": "ab2c55f902b380cf28e0eec501b4906e4c1960d13f00e11cfbcd21de15f18fed"
|
||||
}
|
||||
},
|
||||
"npm": {
|
||||
|
@ -25,26 +35,12 @@
|
|||
"integrity": "sha512-8zSiw54Oxrdym50NlZ9sUusyO1Z1ZchgRLWRaK6c86XJFClyCgFKetdowBg5bKxyp/u+CDBJG4Mpp0m3HLZl9w==",
|
||||
"dependencies": {}
|
||||
},
|
||||
"config@3.3.12": {
|
||||
"integrity": "sha512-Vmx389R/QVM3foxqBzXO8t2tUikYZP64Q6vQxGrsMpREeJc/aWRnPRERXWsYzOHAumx/AOoILWe6nU3ZJL+6Sw==",
|
||||
"dependencies": {
|
||||
"json5": "json5@2.2.3"
|
||||
}
|
||||
},
|
||||
"jintr@2.1.1": {
|
||||
"integrity": "sha512-89cwX4ouogeDGOBsEVsVYsnWWvWjchmwXBB4kiBhmjOKw19FiOKhNhMhpxhTlK2ctl7DS+d/ethfmuBpzoNNgA==",
|
||||
"dependencies": {
|
||||
"acorn": "acorn@8.13.0"
|
||||
}
|
||||
},
|
||||
"json5@2.2.3": {
|
||||
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
|
||||
"dependencies": {}
|
||||
},
|
||||
"toml@3.0.0": {
|
||||
"integrity": "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==",
|
||||
"dependencies": {}
|
||||
},
|
||||
"tslib@2.8.0": {
|
||||
"integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==",
|
||||
"dependencies": {}
|
||||
|
@ -66,22 +62,26 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"redirects": {
|
||||
"https://deno.land/x/brotli/mod.ts": "https://deno.land/x/brotli@0.1.7/mod.ts"
|
||||
},
|
||||
"remote": {
|
||||
"https://deno.land/std@0.159.0/encoding/ascii85.ts": "f2b9cb8da1a55b3f120d3de2e78ac993183a4fd00dfa9cb03b51cf3a75bc0baa",
|
||||
"https://deno.land/x/brotli@0.1.7/mod.ts": "08b913e51488b6e7fa181f2814b9ad087fdb5520041db0368f8156bfa45fd73e",
|
||||
"https://deno.land/x/brotli@0.1.7/wasm.js": "77771b89e89ec7ff6e3e0939a7fb4f9b166abec3504cec0532ad5c127d6f35d2",
|
||||
"https://deno.land/x/lz4@v0.1.2/mod.ts": "4decfc1a3569d03fd1813bd39128b71c8f082850fe98ecfdde20025772916582",
|
||||
"https://deno.land/x/lz4@v0.1.2/wasm.js": "b9c65605327ba273f0c76a6dc596ec534d4cda0f0225d7a94ebc606782319e46",
|
||||
"https://esm.sh/@willsoto/node-konfig-core@5.0.0": "c9674093947a209fa06eaaa84fc039b5c64e60090b24af3f9c04e651825b39df",
|
||||
"https://esm.sh/@willsoto/node-konfig-file@3.0.0": "701049153614b9cce5658772cc5bf319cbc3f764b3b6d6de8b0a0e568c60fef7",
|
||||
"https://esm.sh/@willsoto/node-konfig-toml-parser@3.0.0": "1eea4c4405787d23c3e8ed4ed1b955496e8367b4a05962b25aec9bc0154be371",
|
||||
"https://esm.sh/bgutils-js@3.0.0": "cc8243ff36620e02845bb12565036f93e464066cb0ec21b94b44562373e236bd",
|
||||
"https://esm.sh/jsdom@25.0.1": "74c4782b56ddcf7cd1079c1d5e31832f0adbbb4099a116e360b5e0523444832f",
|
||||
"https://esm.sh/v135/@willsoto/node-konfig-core@5.0.0/denonext/node-konfig-core.mjs": "94b17731548b6f604e942023f7cb065c614002e1c4577ba269ddfaa2abcc96e0",
|
||||
"https://esm.sh/v135/@willsoto/node-konfig-file@3.0.0/denonext/node-konfig-file.mjs": "273590da81e45fb62d2264f2fe9e71576d761a98224a9e378a3853196ad82b95",
|
||||
"https://esm.sh/v135/@willsoto/node-konfig-toml-parser@3.0.0/denonext/node-konfig-toml-parser.mjs": "7ed9898984d6f723a5899f27d2afdcb883eff4da3d3d12ba215d966041ff338f",
|
||||
"https://esm.sh/v135/agent-base@7.1.0/denonext/agent-base.mjs": "0cfda332cb4694510eaeaa42dc88eed2223a9e6bed7352727a15ff163ee6b285",
|
||||
"https://esm.sh/v135/agent-base@7.1.1/denonext/agent-base.mjs": "e7f92e882f955036b3054644e3b01623bfe61065102ddfffb98099966dad628c",
|
||||
"https://esm.sh/v135/bgutils-js@3.0.0/denonext/bgutils-js.mjs": "de89db45dde68ef5ada5a3041611cc2083db2bccbdfdafbd773e937d3536de4d",
|
||||
"https://esm.sh/v135/bufferutil@4.0.8/denonext/bufferutil.mjs": "60a4618cbd1a5cb24935c55590b793d4ecb33862357d32e1d4614a0bbb90947f",
|
||||
"https://esm.sh/v135/canvas@2.11.2/denonext/canvas.mjs": "4245b1d01d91b5e807b85e40e98efe28c93634260bd8cb5ac0da71c42098a1a4",
|
||||
"https://esm.sh/v135/cockatiel@2.0.2/denonext/cockatiel.mjs": "333aa0d3f0d06e4cee30fb66ce882a3385485763f10caf7ce20276f41a9a2680",
|
||||
"https://esm.sh/v135/cssstyle@4.1.0/denonext/cssstyle.mjs": "efe3d039b97cde71be202193e728d3f23636eca5301df3a83d21527202c4bf38",
|
||||
"https://esm.sh/v135/data-urls@5.0.0/denonext/data-urls.mjs": "0a38da21608a5cf482ce7f18e78b24277fc6a63e58df0f3ec210b2810026ada5",
|
||||
"https://esm.sh/v135/debug@4.3.4/denonext/debug.mjs": "d2ebf776ea77aa7df1b4460eb2a4aab245a9d5b19f11fa1db25f756b350bae9d",
|
||||
|
@ -96,6 +96,7 @@
|
|||
"https://esm.sh/v135/iconv-lite@0.6.3/denonext/iconv-lite.mjs": "768e37377191ab3c7414bbb15fce202a328c411da0429764984c79b8bc65abd4",
|
||||
"https://esm.sh/v135/is-potential-custom-element-name@1.0.1/denonext/is-potential-custom-element-name.mjs": "de2781ef99795b662f43c0840c3dcfdc303f9e60a75e66924370f902133469ed",
|
||||
"https://esm.sh/v135/jsdom@25.0.1/denonext/jsdom.mjs": "166355ac54ff09711e7c6e8182ea68a008045d4921a8ef67fca627bc4a2998cd",
|
||||
"https://esm.sh/v135/lodash@4.17.21/denonext/lodash.mjs": "f04a5db09228738fd8cd06b6d1eaf3463b1b639d1529cf11673c3ac7bda1b1a8",
|
||||
"https://esm.sh/v135/ms@2.1.2/denonext/ms.mjs": "aa4dc45ba72554c5011168f8910cc646c37af53cfff1a15a4decced838b8eb14",
|
||||
"https://esm.sh/v135/node-gyp-build@4.6.1/denonext/node-gyp-build.mjs": "5d28b312f145a6cb2ec0dbdd80a7d34c0e0e6b5dcada65411d8bcff6c8991cc6",
|
||||
"https://esm.sh/v135/node-gyp-build@4.8.1/denonext/node-gyp-build.mjs": "cddfc39c5f2d6e228fb1cd8cc36a594d870470b01348f866a7fb4e6f3ed8c66d",
|
||||
|
@ -107,6 +108,7 @@
|
|||
"https://esm.sh/v135/symbol-tree@3.2.4/denonext/symbol-tree.mjs": "67199d1e47bd6e5b7d2715dd04d25658061c95fc4464f7d200b6aab9e439b5f4",
|
||||
"https://esm.sh/v135/tldts-core@6.1.47/denonext/tldts-core.mjs": "1ec163f0c44c05ab278859568719445a467f99d71ff63fe873a696e45560cbf1",
|
||||
"https://esm.sh/v135/tldts@6.1.47/denonext/tldts.mjs": "9d166ad2aa7f9753aac76569b46d48ccf1e846401c268d12b8f1b7bc92c5522b",
|
||||
"https://esm.sh/v135/toml@3.0.0/denonext/toml.mjs": "067506d3e46560e450b498b3306c9f0338f05a6b2b8d1e262044ece4aa622a55",
|
||||
"https://esm.sh/v135/tough-cookie@5.0.0/denonext/tough-cookie.mjs": "13a12fd7e56bd78bc54df00af86674dbffecd4bf9995a46b51ef45da09431cb9",
|
||||
"https://esm.sh/v135/tr46@5.0.0/denonext/tr46.mjs": "66ea6f0789e30702596b0c5d0c2c2ae3e511aab829bb5b696938f61cd309e0dd",
|
||||
"https://esm.sh/v135/utf-8-validate@6.0.4/denonext/utf-8-validate.mjs": "ab4990b545a45f10f7711c69046ee3e9c5b732b9781937f922cefd3fc99d0e88",
|
||||
|
@ -125,9 +127,6 @@
|
|||
"workspace": {
|
||||
"dependencies": [
|
||||
"jsr:@hono/hono@^4.6.5",
|
||||
"jsr:@std/assert@1",
|
||||
"npm:config@3.3.12",
|
||||
"npm:toml@3.0.0",
|
||||
"npm:youtubei.js@10.5.0"
|
||||
]
|
||||
}
|
||||
|
|
33
src/lib/helpers/konfigLoader.ts
Normal file
33
src/lib/helpers/konfigLoader.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
import { Store } from "@willsoto/node-konfig-core";
|
||||
import { FileLoader as KonfigFileLoader } from "@willsoto/node-konfig-file";
|
||||
import { TOMLParser } from "@willsoto/node-konfig-toml-parser";
|
||||
import { join as pathJoin } from "jsr:@std/path";
|
||||
import { existsSync } from "jsr:@std/fs";
|
||||
|
||||
export const konfigLoader = async (): Promise<
|
||||
Store<Record<string, unknown>>
|
||||
> => {
|
||||
const konfigStore = new Store();
|
||||
const konfigFilesToLoad = {
|
||||
files: [
|
||||
{
|
||||
path: pathJoin(Deno.cwd(), "config/default.toml"),
|
||||
parser: new TOMLParser(),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
if (existsSync(pathJoin(Deno.cwd(), "config/local.toml"))) {
|
||||
konfigFilesToLoad.files.push({
|
||||
path: pathJoin(Deno.cwd(), "config/local.toml"),
|
||||
parser: new TOMLParser(),
|
||||
});
|
||||
}
|
||||
|
||||
const konfigLoader = new KonfigFileLoader(konfigFilesToLoad);
|
||||
// @ts-ignore Safe to ignore
|
||||
konfigStore.registerLoader(konfigLoader);
|
||||
await konfigStore.init();
|
||||
|
||||
return konfigStore;
|
||||
};
|
123
src/lib/helpers/youtubePlayerHandling.ts
Normal file
123
src/lib/helpers/youtubePlayerHandling.ts
Normal file
|
@ -0,0 +1,123 @@
|
|||
import { Innertube, YT, ApiResponse } from "youtubei.js";
|
||||
import { compress, decompress } from "https://deno.land/x/brotli@0.1.7/mod.ts";
|
||||
import { youtubePlayerReq } from "youtubePlayerReq";
|
||||
import { Store } from "@willsoto/node-konfig-core";
|
||||
|
||||
const kv = await Deno.openKv();
|
||||
|
||||
export const youtubePlayerParsing = async (
|
||||
innertubeClient: Innertube,
|
||||
videoId: string,
|
||||
konfigStore: Store,
|
||||
): Promise<object> => {
|
||||
const videoCached = (await kv.get(["video_cache", videoId]))
|
||||
.value as Uint8Array;
|
||||
|
||||
if (videoCached != null) {
|
||||
return JSON.parse(new TextDecoder().decode(decompress(videoCached)));
|
||||
} else {
|
||||
const youtubePlayerResponse = await youtubePlayerReq(
|
||||
innertubeClient,
|
||||
videoId,
|
||||
);
|
||||
const videoData = youtubePlayerResponse.data;
|
||||
|
||||
const video = new YT.VideoInfo(
|
||||
[youtubePlayerResponse],
|
||||
innertubeClient.actions,
|
||||
"",
|
||||
);
|
||||
|
||||
const streamingData = video.streaming_data;
|
||||
|
||||
// Modify the original YouTube response to include deciphered URLs
|
||||
if (streamingData && videoData && videoData.streamingData) {
|
||||
streamingData.adaptive_formats;
|
||||
for (const [index, format] of streamingData.formats.entries()) {
|
||||
videoData.streamingData.formats[index].url = format.decipher(
|
||||
innertubeClient.session.player,
|
||||
);
|
||||
if (
|
||||
videoData.streamingData.formats[index].signatureCipher !==
|
||||
undefined
|
||||
) {
|
||||
delete videoData.streamingData.formats[index]
|
||||
.signatureCipher;
|
||||
}
|
||||
}
|
||||
for (
|
||||
const [index, adaptive_format] of streamingData.adaptive_formats
|
||||
.entries()
|
||||
) {
|
||||
videoData.streamingData.adaptiveFormats[index].url =
|
||||
adaptive_format
|
||||
.decipher(
|
||||
innertubeClient.session.player,
|
||||
);
|
||||
if (
|
||||
videoData.streamingData.adaptiveFormats[index]
|
||||
.signatureCipher !==
|
||||
undefined
|
||||
) {
|
||||
delete videoData.streamingData.adaptiveFormats[index]
|
||||
.signatureCipher;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const videoOnlyNecessaryInfo = ((
|
||||
{
|
||||
captions,
|
||||
playabilityStatus,
|
||||
storyboards,
|
||||
streamingData,
|
||||
videoDetails,
|
||||
microformat,
|
||||
},
|
||||
) => ({
|
||||
captions,
|
||||
playabilityStatus,
|
||||
storyboards,
|
||||
streamingData,
|
||||
videoDetails,
|
||||
microformat,
|
||||
invidiousCompanion: {
|
||||
"baseUrl": konfigStore.get("server.base_url") as string,
|
||||
},
|
||||
}))(videoData);
|
||||
|
||||
if (konfigStore.get("cache.enabled") == true) {
|
||||
(async () => {
|
||||
await kv.set(
|
||||
["video_cache", videoId],
|
||||
compress(
|
||||
new TextEncoder().encode(
|
||||
JSON.stringify(videoOnlyNecessaryInfo),
|
||||
),
|
||||
),
|
||||
{
|
||||
expireIn: 1000 * 60 * 60,
|
||||
},
|
||||
);
|
||||
})();
|
||||
}
|
||||
|
||||
return videoOnlyNecessaryInfo;
|
||||
}
|
||||
};
|
||||
|
||||
export const youtubeVideoInfo = (
|
||||
innertubeClient: Innertube,
|
||||
youtubePlayerResponseJson: object
|
||||
): YT.VideoInfo => {
|
||||
const playerResponse = {
|
||||
success: true,
|
||||
status_code: 200,
|
||||
data: youtubePlayerResponseJson,
|
||||
} as ApiResponse;
|
||||
return new YT.VideoInfo(
|
||||
[playerResponse],
|
||||
innertubeClient.actions,
|
||||
"",
|
||||
);
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import { Innertube } from "youtubei.js";
|
||||
import { Innertube, ApiResponse } from "youtubei.js";
|
||||
|
||||
export const youtubePlayerReq = async (innertubeClient: Innertube, videoId: string) => {
|
||||
export const youtubePlayerReq = async (innertubeClient: Innertube, videoId: string): Promise<ApiResponse> => {
|
||||
return await innertubeClient.actions.execute("/player", {
|
||||
videoId: videoId,
|
||||
});
|
||||
|
|
|
@ -2,12 +2,13 @@ import { BG } from "bgutils";
|
|||
import type { BgConfig } from "bgutils";
|
||||
import { JSDOM } from "jsdom";
|
||||
import { Innertube, UniversalCache } from "youtubei.js";
|
||||
import Config from "node-config";
|
||||
import { Store } from "@willsoto/node-konfig-core";
|
||||
|
||||
// Adapted from https://github.com/LuanRT/BgUtils/blob/main/examples/node/index.ts
|
||||
export const poTokenGenerate = async (
|
||||
innertubeClient: Innertube,
|
||||
config: Config,
|
||||
) => {
|
||||
konfigStore: Store<Record<string, unknown>>,
|
||||
): Promise<Innertube> => {
|
||||
const requestKey = "O43z0dpjhgX20SCx4KAo";
|
||||
|
||||
if (innertubeClient.session.po_token) {
|
||||
|
@ -58,14 +59,14 @@ export const poTokenGenerate = async (
|
|||
|
||||
let fetchMethod = fetch;
|
||||
|
||||
if (config.has("networking.proxy")) {
|
||||
if (konfigStore.get("networking.proxy")) {
|
||||
fetchMethod = async (
|
||||
input: RequestInfo | URL,
|
||||
init?: RequestInit,
|
||||
) => {
|
||||
const client = Deno.createHttpClient({
|
||||
proxy: {
|
||||
url: config.get("networking.proxy"),
|
||||
url: konfigStore.get("networking.proxy") as string,
|
||||
},
|
||||
});
|
||||
const fetchRes = await fetch(input, {
|
||||
|
|
21
src/main.ts
21
src/main.ts
|
@ -2,28 +2,31 @@ import { Hono } from "hono";
|
|||
import { routes } from "./routes/index.ts";
|
||||
import { Innertube } from "youtubei.js";
|
||||
import { poTokenGenerate } from "./lib/jobs/potoken.ts";
|
||||
import config from "node-config";
|
||||
// deno-lint-ignore no-unused-vars
|
||||
import toml from "toml";
|
||||
import { konfigLoader } from "./lib/helpers/konfigLoader.ts";
|
||||
|
||||
const app = new Hono();
|
||||
|
||||
const konfigStore = await konfigLoader();
|
||||
|
||||
let innertubeClient = await Innertube.create({ retrieve_player: false });
|
||||
|
||||
innertubeClient = await poTokenGenerate(innertubeClient, config);
|
||||
innertubeClient = await poTokenGenerate(innertubeClient, konfigStore);
|
||||
|
||||
Deno.cron("regenerate poToken", "*/10 * * * *", async () => {
|
||||
innertubeClient = await poTokenGenerate(innertubeClient, config);
|
||||
Deno.cron("regenerate poToken", konfigStore.get("jobs.po_token_cron"), async () => {
|
||||
innertubeClient = await poTokenGenerate(innertubeClient, konfigStore);
|
||||
});
|
||||
|
||||
app.use("*", async (c, next) => {
|
||||
// @ts-ignore Do not understand how to fix this error.
|
||||
c.set("innertubeClient", innertubeClient);
|
||||
// @ts-ignore Do not understand how to fix this error.
|
||||
c.set("config", config)
|
||||
c.set("konfigStore", konfigStore);
|
||||
await next();
|
||||
});
|
||||
|
||||
routes(app);
|
||||
routes(app, konfigStore);
|
||||
|
||||
Deno.serve({ port: config.get("server.port"), hostname: config.get("server.host") }, app.fetch);
|
||||
Deno.serve({
|
||||
port: konfigStore.get("server.port") as number,
|
||||
hostname: konfigStore.get("server.host") as string,
|
||||
}, app.fetch);
|
||||
|
|
|
@ -1,19 +1,23 @@
|
|||
import { Hono } from "hono";
|
||||
import { bearerAuth } from "hono/bearer-auth";
|
||||
import { logger } from "hono/logger";
|
||||
import config from 'node-config';
|
||||
import { Store } from "@willsoto/node-konfig-core";
|
||||
import { bearerAuth } from "hono/bearer-auth";
|
||||
|
||||
import youtube_route_player from "./youtube_routes/player.ts";
|
||||
import youtubeApiPlayer from "./youtube_api_routes/player.ts";
|
||||
import invidiousRouteLatestVersion from "./invidious_routes/latestVersion.ts";
|
||||
import invidiousRouteDashManifest from "./invidious_routes/dashManifest.ts";
|
||||
|
||||
export const routes = (app: Hono) => {
|
||||
export const routes = (app: Hono, konfigStore: Store<Record<string, unknown>>) => {
|
||||
app.use("*", logger());
|
||||
|
||||
app.use(
|
||||
"/youtubei/v1/*",
|
||||
bearerAuth({
|
||||
token: config.get("server.hmac_key"),
|
||||
token: konfigStore.get("server.hmac_key") as string,
|
||||
}),
|
||||
);
|
||||
|
||||
app.route("/youtubei/v1", youtube_route_player);
|
||||
app.route("/youtubei/v1", youtubeApiPlayer);
|
||||
app.route("/latest_version", invidiousRouteLatestVersion);
|
||||
app.route("/api/manifest/dash/id", invidiousRouteDashManifest);
|
||||
};
|
||||
|
|
62
src/routes/invidious_routes/dashManifest.ts
Normal file
62
src/routes/invidious_routes/dashManifest.ts
Normal file
|
@ -0,0 +1,62 @@
|
|||
import { Hono } from "hono";
|
||||
import { HTTPException } from "hono/http-exception";
|
||||
import { Innertube } from "youtubei.js";
|
||||
import { HonoVariables } from "../../lib/types/HonoVariables.ts";
|
||||
import { Store } from "@willsoto/node-konfig-core";
|
||||
import {
|
||||
youtubePlayerParsing,
|
||||
youtubeVideoInfo,
|
||||
} from "../../lib/helpers/youtubePlayerHandling.ts";
|
||||
|
||||
const dashManifest = new Hono<{ Variables: HonoVariables }>();
|
||||
|
||||
dashManifest.get("/:videoId", async (c) => {
|
||||
const { videoId } = c.req.param();
|
||||
const { local } = c.req.query();
|
||||
c.header("access-control-allow-origin", "*");
|
||||
|
||||
const innertubeClient = await c.get("innertubeClient") as Innertube;
|
||||
// @ts-ignore Do not understand how to fix this error.
|
||||
const konfigStore = await c.get("konfigStore") as Store<
|
||||
Record<string, unknown>
|
||||
>;
|
||||
|
||||
const youtubePlayerResponseJson = await youtubePlayerParsing(
|
||||
innertubeClient,
|
||||
videoId,
|
||||
konfigStore,
|
||||
);
|
||||
const videoInfo = youtubeVideoInfo(
|
||||
innertubeClient,
|
||||
youtubePlayerResponseJson,
|
||||
);
|
||||
|
||||
if (videoInfo.playability_status?.status !== "OK") {
|
||||
throw ("The video can't be played: " + videoId + " due to reason: " +
|
||||
videoInfo.playability_status?.reason);
|
||||
}
|
||||
|
||||
c.header("content-type", "application/dash+xml");
|
||||
|
||||
if (videoInfo.streaming_data) {
|
||||
videoInfo.streaming_data.adaptive_formats = videoInfo
|
||||
.streaming_data.adaptive_formats
|
||||
.filter((i) => i.mime_type.includes("mp4"));
|
||||
|
||||
const dashFile = await videoInfo.toDash(
|
||||
(url) => {
|
||||
if (local) {
|
||||
const dashUrl = url.pathname + url.search + "&host=" +
|
||||
url.host;
|
||||
// Can't create URL type without host part
|
||||
return dashUrl as unknown as URL;
|
||||
} else {
|
||||
return url;
|
||||
}
|
||||
},
|
||||
);
|
||||
return c.text(dashFile);
|
||||
}
|
||||
});
|
||||
|
||||
export default dashManifest;
|
67
src/routes/invidious_routes/latestVersion.ts
Normal file
67
src/routes/invidious_routes/latestVersion.ts
Normal file
|
@ -0,0 +1,67 @@
|
|||
import { Hono } from "hono";
|
||||
import { HTTPException } from "hono/http-exception";
|
||||
import { Innertube } from "youtubei.js";
|
||||
import { HonoVariables } from "../../lib/types/HonoVariables.ts";
|
||||
import { Store } from "@willsoto/node-konfig-core";
|
||||
import {
|
||||
youtubePlayerParsing,
|
||||
youtubeVideoInfo,
|
||||
} from "../../lib/helpers/youtubePlayerHandling.ts";
|
||||
|
||||
const latestVersion = new Hono<{ Variables: HonoVariables }>();
|
||||
|
||||
latestVersion.get("/", async (c) => {
|
||||
const { itag, id, local } = c.req.query();
|
||||
c.header("access-control-allow-origin", "*");
|
||||
|
||||
if (!id || !itag) {
|
||||
throw new HTTPException(400, {
|
||||
res: new Response("Please specify the itag and video ID."),
|
||||
});
|
||||
}
|
||||
|
||||
const innertubeClient = await c.get("innertubeClient") as Innertube;
|
||||
// @ts-ignore Do not understand how to fix this error.
|
||||
const konfigStore = await c.get("konfigStore") as Store<
|
||||
Record<string, unknown>
|
||||
>;
|
||||
|
||||
const youtubePlayerResponseJson = await youtubePlayerParsing(
|
||||
innertubeClient,
|
||||
id,
|
||||
konfigStore,
|
||||
);
|
||||
const videoInfo = youtubeVideoInfo(
|
||||
innertubeClient,
|
||||
youtubePlayerResponseJson,
|
||||
);
|
||||
|
||||
if (videoInfo.playability_status?.status !== "OK") {
|
||||
throw ("The video can't be played: " + id + " due to reason: " +
|
||||
videoInfo.playability_status?.reason);
|
||||
}
|
||||
const streamingData = videoInfo.streaming_data;
|
||||
const availableFormats = streamingData?.formats.concat(
|
||||
streamingData.adaptive_formats,
|
||||
);
|
||||
const selectedItagFormat = availableFormats?.filter((i) =>
|
||||
i.itag == Number(itag)
|
||||
);
|
||||
if (selectedItagFormat?.length === 0) {
|
||||
throw new HTTPException(400, {
|
||||
res: new Response("No itag found."),
|
||||
});
|
||||
} else if (selectedItagFormat) {
|
||||
const itagUrl = selectedItagFormat[0].url as string;
|
||||
const urlToRedirect = new URL(itagUrl);
|
||||
if (local) {
|
||||
return c.redirect(
|
||||
urlToRedirect.pathname + urlToRedirect.search + "&host=" +
|
||||
urlToRedirect.host,
|
||||
);
|
||||
}
|
||||
return c.redirect(urlToRedirect.toString());
|
||||
}
|
||||
});
|
||||
|
||||
export default latestVersion;
|
0
src/routes/videoPlayback.ts
Normal file
0
src/routes/videoPlayback.ts
Normal file
23
src/routes/youtube_api_routes/player.ts
Normal file
23
src/routes/youtube_api_routes/player.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
import { Hono } from "hono";
|
||||
import { youtubePlayerParsing } from "../../lib/helpers/youtubePlayerHandling.ts";
|
||||
import { HonoVariables } from "../../lib/types/HonoVariables.ts";
|
||||
import { Innertube } from "youtubei.js";
|
||||
import { Store } from "@willsoto/node-konfig-core";
|
||||
|
||||
const player = new Hono<{ Variables: HonoVariables }>();
|
||||
|
||||
player.post("/player", async (c) => {
|
||||
const jsonReq = await c.req.json();
|
||||
const innertubeClient = await c.get("innertubeClient") as Innertube;
|
||||
// @ts-ignore Do not understand how to fix this error.
|
||||
const konfigStore = await c.get("konfigStore") as Store<
|
||||
Record<string, unknown>
|
||||
>;
|
||||
if (jsonReq.videoId) {
|
||||
return c.json(
|
||||
await youtubePlayerParsing(innertubeClient, jsonReq.videoId, konfigStore)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default player;
|
|
@ -1,105 +0,0 @@
|
|||
import { Hono } from "hono";
|
||||
import { Innertube, YT } from "youtubei.js";
|
||||
import { compress, decompress } from "https://deno.land/x/brotli/mod.ts";
|
||||
import { HonoVariables } from "../../lib/types/HonoVariables.ts";
|
||||
import { youtubePlayerReq } from "../../lib/helpers/youtubePlayerReq.ts";
|
||||
import Config from "node-config";
|
||||
|
||||
const player = new Hono<{ Variables: HonoVariables }>();
|
||||
|
||||
const kv = await Deno.openKv();
|
||||
|
||||
player.post("/player", async (c) => {
|
||||
const jsonReq = await c.req.json();
|
||||
if (jsonReq.videoId) {
|
||||
const reqVideoId = jsonReq.videoId;
|
||||
const innertubeClient: Innertube = await c.get("innertubeClient");
|
||||
// @ts-ignore Do not understand how to fix this error.
|
||||
const config: Config = await c.get("config") as Config;
|
||||
const videoCached = (await kv.get(["video_cache", reqVideoId]))
|
||||
.value as Uint8Array;
|
||||
|
||||
if (videoCached != null) {
|
||||
return c.json(
|
||||
JSON.parse(new TextDecoder().decode(decompress(videoCached))),
|
||||
);
|
||||
} else {
|
||||
const youtubePlayerResponse = await youtubePlayerReq(
|
||||
innertubeClient,
|
||||
reqVideoId,
|
||||
);
|
||||
const videoData = youtubePlayerResponse.data;
|
||||
|
||||
const video = new YT.VideoInfo(
|
||||
[youtubePlayerResponse],
|
||||
innertubeClient.actions,
|
||||
"",
|
||||
);
|
||||
|
||||
const streamingData = video.streaming_data;
|
||||
|
||||
if (streamingData && videoData && videoData.streamingData) {
|
||||
streamingData.adaptive_formats;
|
||||
for (const [index, format] of streamingData.formats.entries()) {
|
||||
videoData.streamingData.formats[index].url = format.decipher(
|
||||
innertubeClient.session.player,
|
||||
);
|
||||
if (
|
||||
videoData.streamingData.formats[index].signatureCipher !== undefined
|
||||
) {
|
||||
delete videoData.streamingData.formats[index].signatureCipher;
|
||||
}
|
||||
}
|
||||
for (
|
||||
const [index, adaptive_format] of streamingData.adaptive_formats
|
||||
.entries()
|
||||
) {
|
||||
videoData.streamingData.adaptiveFormats[index].url = adaptive_format
|
||||
.decipher(
|
||||
innertubeClient.session.player,
|
||||
);
|
||||
if (
|
||||
videoData.streamingData.adaptiveFormats[index].signatureCipher !==
|
||||
undefined
|
||||
) {
|
||||
delete videoData.streamingData.adaptiveFormats[index]
|
||||
.signatureCipher;
|
||||
}
|
||||
}
|
||||
}
|
||||
const videoOnlyNecessaryInfo = ((
|
||||
{
|
||||
captions,
|
||||
playabilityStatus,
|
||||
storyboards,
|
||||
streamingData,
|
||||
videoDetails,
|
||||
microformat,
|
||||
},
|
||||
) => ({
|
||||
captions,
|
||||
playabilityStatus,
|
||||
storyboards,
|
||||
streamingData,
|
||||
videoDetails,
|
||||
microformat,
|
||||
}))(videoData);
|
||||
if (config.get("cache.enabled") == true) {
|
||||
(async () => {
|
||||
await kv.set(
|
||||
["video_cache", reqVideoId],
|
||||
compress(
|
||||
new TextEncoder().encode(JSON.stringify(videoOnlyNecessaryInfo)),
|
||||
),
|
||||
{
|
||||
expireIn: 1000 * 60 * 60,
|
||||
},
|
||||
);
|
||||
})();
|
||||
}
|
||||
return c.json(videoOnlyNecessaryInfo);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export default player;
|
Loading…
Reference in a new issue