update patches
Some checks failed
Build and Push Docker Image / build-and-push (push) Has been cancelled

This commit is contained in:
Fijxu 2025-03-25 22:25:40 -03:00
parent 3f98a8862b
commit 6eab5c041a
Signed by: Fijxu
GPG key ID: 32C1DDF333EDA6A4
16 changed files with 2756 additions and 887 deletions

View file

@ -1,7 +1,7 @@
From dcc49f92b16f0a81211a785b8f6226cf0b2d6124 Mon Sep 17 00:00:00 2001
From 7851eff59e43a0d5fbbfd9a334e360e651ad425d Mon Sep 17 00:00:00 2001
From: Fijxu <fijxu@nadeko.net>
Date: Mon, 24 Mar 2025 18:44:10 -0300
Subject: [PATCH 01/14] feat: add support for an external videoplayback proxy
Subject: [PATCH 01/13] feat: add support for an external videoplayback proxy
---
config/config.example.toml | 1 +

View file

@ -1,7 +1,7 @@
From a791d4af82059dcbea92d554502af7d4bfe738dd Mon Sep 17 00:00:00 2001
From 928235f1990e44543f6ee64d98826fb5b6dad54f Mon Sep 17 00:00:00 2001
From: Fijxu <fijxu@nadeko.net>
Date: Mon, 24 Mar 2025 18:52:53 -0300
Subject: [PATCH 02/14] feat: report the external videoplayback proxy via /info
Subject: [PATCH 02/13] feat: report the external videoplayback proxy via /info
endpoint
---

View file

@ -1,7 +1,7 @@
From 22efdf68a74c982732eda55b3ade647c2d7f4572 Mon Sep 17 00:00:00 2001
From 900ea1d2c60cc9e251c6f327e6acc1cf85b5c0dd Mon Sep 17 00:00:00 2001
From: Fijxu <fijxu@nadeko.net>
Date: Mon, 24 Mar 2025 19:02:01 -0300
Subject: [PATCH 03/14] feat: add resolution limit on DASH streams to save
Subject: [PATCH 03/13] feat: add resolution limit on DASH streams to save
bandwidth
---

View file

@ -1,7 +1,7 @@
From f4a5151588c1b943ef11d5110ac1c69a8aee883f Mon Sep 17 00:00:00 2001
From 85fd63770061b5c8410f318fe508371db9c3bfa7 Mon Sep 17 00:00:00 2001
From: Fijxu <fijxu@nadeko.net>
Date: Mon, 24 Mar 2025 19:06:04 -0300
Subject: [PATCH 04/14] feat: add env variable to set verify_requests
Subject: [PATCH 04/13] feat: add env variable to set verify_requests
---
src/lib/helpers/config.ts | 4 +++-

View file

@ -1,7 +1,7 @@
From 8e7d9c444449e8aac26cf6d528ba907051ece482 Mon Sep 17 00:00:00 2001
From 0a7ae6d4acd1aa0ac1d99467ef5dc3a7e7e75bde Mon Sep 17 00:00:00 2001
From: Fijxu <fijxu@nadeko.net>
Date: Mon, 24 Mar 2025 19:20:52 -0300
Subject: [PATCH 05/14] feat: add support for multiple proxies
Subject: [PATCH 05/13] feat: add support for multiple proxies
---
src/lib/helpers/getFetchClient.ts | 17 ++++++++++++++++-

View file

@ -1,7 +1,7 @@
From 38d85d33657e105afce5f007c6aacb9b7d8eb0b1 Mon Sep 17 00:00:00 2001
From fd9e11162bde15d53fecedf3fa8114f02dcda052 Mon Sep 17 00:00:00 2001
From: Fijxu <fijxu@nadeko.net>
Date: Mon, 24 Mar 2025 19:37:34 -0300
Subject: [PATCH 06/14] ci: update deno to 2.2.4
Subject: [PATCH 06/13] ci: update deno to 2.2.4
---
Dockerfile | 2 +-

View file

@ -1,7 +1,7 @@
From 3282cade22404e2fe938f23565b8cc2585b69601 Mon Sep 17 00:00:00 2001
From 47295d3babce911018e1aa92554a1ff1dfb24d29 Mon Sep 17 00:00:00 2001
From: Fijxu <fijxu@nadeko.net>
Date: Mon, 24 Mar 2025 20:22:19 -0300
Subject: [PATCH 08/14] fix: cut off secret_key to 16 characters
Subject: [PATCH 07/13] fix: cut off secret_key to 16 characters
---
src/lib/helpers/verifyRequest.ts | 2 +-

View file

@ -1,157 +0,0 @@
From 2453528d8bc995fa32053e49432a10d22da3bacd Mon Sep 17 00:00:00 2001
From: Fijxu <fijxu@nadeko.net>
Date: Mon, 24 Mar 2025 20:06:47 -0300
Subject: [PATCH 07/14] fix: temporary player_id override until an official fix
cames out
---
deno.lock | 2 +
src/lib/jobs/potoken.ts | 5 +++
src/main.ts | 90 ++++++++++++++++++++++++++++++++++++++++-
3 files changed, 96 insertions(+), 1 deletion(-)
diff --git a/deno.lock b/deno.lock
index 338ad71..27d8ae6 100644
--- a/deno.lock
+++ b/deno.lock
@@ -451,6 +451,8 @@
"https://esm.sh/@bufbuild/protobuf@2.0.0/wire": "eb1be07dad5823fc419cc9e6f62077a70962ce42facb1a5240b7d5c3674e852f",
"https://esm.sh/bgutils-js@3.2.0": "9691c575e7f81d8c2652260f59356b5de97a242fc8b464dbba880a543f6ce075",
"https://esm.sh/bgutils-js@3.2.0/denonext/bgutils-js.mjs": "9acd0267c5bf7273ba122a295f97795cb81d0f6d001db708c92548ac82977f93",
+ "https://raw.githubusercontent.com/LuanRT/YouTube.js/refs/tags/v13.0.0-deno/deno/src/types/Cache.ts": "06cd238bce7c9657055151587e36ee445e8236d54d27272124ced10ea7be0da4",
+ "https://raw.githubusercontent.com/LuanRT/YouTube.js/refs/tags/v13.0.0-deno/deno/src/types/PlatformShim.ts": "06f656f0d2bc20980ef77148455b662af10fe4b0e48d41566bf28e471eea4be1",
"https://raw.githubusercontent.com/LuanRT/YouTube.js/refs/tags/v13.1.0-deno/deno.ts": "f913bfdb3c66b2330fa8b531bd1e291437b836c9fbf002b9ae044bf14e1f397c",
"https://raw.githubusercontent.com/LuanRT/YouTube.js/refs/tags/v13.1.0-deno/deno/package.json": "4e1d16c0f95a2b58dc6dba7e0fe969b800794a6040ae02116e14eae2444e4429",
"https://raw.githubusercontent.com/LuanRT/YouTube.js/refs/tags/v13.1.0-deno/deno/protos/generated/misc/common.ts": "7d6ca8d7cd7eafe1f8d5ddc11c440566c418aeaf2d8a03a72eced7466a691039",
diff --git a/src/lib/jobs/potoken.ts b/src/lib/jobs/potoken.ts
index 6867082..233ac32 100644
--- a/src/lib/jobs/potoken.ts
+++ b/src/lib/jobs/potoken.ts
@@ -1,3 +1,4 @@
+import { player } from "../../main.ts";
import { BG, buildURL, GOOG_API_KEY, USER_AGENT } from "bgutils";
import type { WebPoSignalOutput } from "bgutils";
import { JSDOM } from "jsdom";
@@ -133,8 +134,12 @@ export const poTokenGenerate = async (
fetch: getFetchClient(config),
cache: innertubeClientCache,
generate_session_locally: true,
+ retrieve_player: false,
});
+ instantiatedInnertubeClient.session.player = player;
+ instantiatedInnertubeClient.session.player.po_token = sessionPoToken;
+
try {
const feed = await instantiatedInnertubeClient.getTrending();
// get all videos and shuffle them randomly to avoid using the same trending video over and over
diff --git a/src/main.ts b/src/main.ts
index a792fee..b57cdc5 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -1,6 +1,17 @@
import { Hono } from "hono";
import { routes } from "./routes/index.ts";
-import { Innertube, UniversalCache } from "youtubei.js";
+import {
+ Constants,
+ Innertube,
+ Log,
+ Platform,
+ Player,
+ UniversalCache,
+} from "youtubei.js";
+import { ICache } from "https://raw.githubusercontent.com/LuanRT/YouTube.js/refs/tags/v13.0.0-deno/deno/src/types/Cache.ts";
+import { FetchFunction } from "https://raw.githubusercontent.com/LuanRT/YouTube.js/refs/tags/v13.0.0-deno/deno/src/types/PlatformShim.ts";
+import { getRandomUserAgent, PlayerError } from "youtubei.js/Utils";
+
import { poTokenGenerate } from "./lib/jobs/potoken.ts";
import { USER_AGENT } from "bgutils";
import { retry } from "@std/async";
@@ -64,6 +75,83 @@ innertubeClient = await Innertube.create({
user_agent: USER_AGENT,
});
+export default class CustomPlayer extends Player {
+ constructor(
+ signature_timestamp: number,
+ sig_sc?: string,
+ nsig_sc?: string,
+ ) {
+ super("7795af42", signature_timestamp, sig_sc, nsig_sc);
+ }
+
+ static override async create(
+ cache: ICache | undefined,
+ fetch: FetchFunction = Platform.shim.fetch,
+ po_token?: string,
+ ): Promise<CustomPlayer> {
+ const player_id = "7795af42";
+
+ Log.info(
+ `Got player id (${player_id}). Checking for cached players..`,
+ );
+
+ if (!player_id) {
+ throw new PlayerError("Failed to get player id");
+ }
+
+ // We have the player id, now we can check if we have a cached player.
+ if (cache) {
+ const cached_player = await Player.fromCache(cache, player_id);
+ if (cached_player) {
+ cached_player.po_token = po_token;
+ return cached_player;
+ }
+ }
+
+ const player_url = new URL(
+ `/s/player/${player_id}/player_ias.vflset/en_US/base.js`,
+ Constants.URLS.YT_BASE,
+ );
+
+ const player_res = await fetch(player_url, {
+ headers: {
+ "user-agent": getRandomUserAgent("desktop"),
+ },
+ });
+
+ if (!player_res.ok) {
+ throw new PlayerError(
+ `Failed to get player data: ${player_res.status}`,
+ );
+ }
+
+ const player_js = await player_res.text();
+
+ const sig_timestamp = this.extractSigTimestamp(player_js);
+ const sig_sc = this.extractSigSourceCode(player_js);
+ const nsig_sc = this.extractNSigSourceCode(player_js);
+
+ Log.info(
+ `Got signature timestamp (${sig_timestamp}) and algorithms needed to decipher signatures.`,
+ );
+
+ const player = await Player.fromSource(
+ player_id,
+ sig_timestamp,
+ cache,
+ sig_sc,
+ nsig_sc,
+ );
+ player.po_token = po_token;
+
+ return player as CustomPlayer;
+ }
+}
+
+export const player = await CustomPlayer.create(undefined);
+
+innertubeClient.session.player = player;
+
if (!innertubeClientOauthEnabled) {
if (innertubeClientJobPoTokenEnabled) {
({ innertubeClient, tokenMinter } = await retry(
--
2.49.0

View file

@ -0,0 +1,61 @@
From 76489c09990cfeda96d99b7ad72a0b47d7231364 Mon Sep 17 00:00:00 2001
From: Fijxu <fijxu@nadeko.net>
Date: Mon, 24 Mar 2025 20:34:33 -0300
Subject: [PATCH 08/13] feat: add option to disable potoken generation check
---
config/config.example.toml | 1 +
src/lib/helpers/config.ts | 9 +++++++++
src/lib/jobs/potoken.ts | 4 +++-
3 files changed, 13 insertions(+), 1 deletion(-)
diff --git a/config/config.example.toml b/config/config.example.toml
index a3c73be..4c04ee3 100644
--- a/config/config.example.toml
+++ b/config/config.example.toml
@@ -51,6 +51,7 @@
# [jobs.youtube_session]
# po_token_enabled = true # whether to generate PO tokens
# frequency = "*/5 * * * *" # frequency of PO token refresh in cron format
+# po_token_check = false # whether to check if the PO token is valid or not
# [youtube_session]
# oauth_enabled = false
diff --git a/src/lib/helpers/config.ts b/src/lib/helpers/config.ts
index 05748f0..a2f6be0 100644
--- a/src/lib/helpers/config.ts
+++ b/src/lib/helpers/config.ts
@@ -39,6 +39,15 @@ const ConfigSchema = z.object({
youtube_session: z.object({
po_token_enabled: z.boolean().default(true),
frequency: z.string().default("*/5 * * * *"),
+ po_token_check: z
+ .boolean()
+ .default(
+ Deno.env.get("PO_TOKEN_CHECK") === "true"
+ ? true
+ : Deno.env.get("PO_TOKEN_CHECK") === "false"
+ ? false
+ : true,
+ ),
}).strict().default({}),
}).strict().default({}),
youtube_session: z.object({
diff --git a/src/lib/jobs/potoken.ts b/src/lib/jobs/potoken.ts
index 6867082..3e5dfc0 100644
--- a/src/lib/jobs/potoken.ts
+++ b/src/lib/jobs/potoken.ts
@@ -166,7 +166,9 @@ export const poTokenGenerate = async (
"failed to find valid video with adaptive format to check token against",
);
}
- const result = await fetchImpl(validFormat?.url, { method: "HEAD" });
+ const result = await fetchImpl(validFormat?.url, {
+ method: "HEAD",
+ });
if (result.status !== 200) {
throw new Error(
`did not get a 200 when checking video, got ${result.status} instead`,
--
2.49.0

View file

@ -1,139 +0,0 @@
From 613a99c7a63ae08c1e11b6c286e5af1000867166 Mon Sep 17 00:00:00 2001
From: Fijxu <fijxu@nadeko.net>
Date: Mon, 24 Mar 2025 20:34:33 -0300
Subject: [PATCH 09/14] feat: add option to disable potoken generation check
---
config/config.example.toml | 1 +
src/lib/helpers/config.ts | 9 +++++
src/lib/jobs/potoken.ts | 80 ++++++++++++++++++++------------------
3 files changed, 52 insertions(+), 38 deletions(-)
diff --git a/config/config.example.toml b/config/config.example.toml
index a3c73be..4c04ee3 100644
--- a/config/config.example.toml
+++ b/config/config.example.toml
@@ -51,6 +51,7 @@
# [jobs.youtube_session]
# po_token_enabled = true # whether to generate PO tokens
# frequency = "*/5 * * * *" # frequency of PO token refresh in cron format
+# po_token_check = false # whether to check if the PO token is valid or not
# [youtube_session]
# oauth_enabled = false
diff --git a/src/lib/helpers/config.ts b/src/lib/helpers/config.ts
index 05748f0..a2f6be0 100644
--- a/src/lib/helpers/config.ts
+++ b/src/lib/helpers/config.ts
@@ -39,6 +39,15 @@ const ConfigSchema = z.object({
youtube_session: z.object({
po_token_enabled: z.boolean().default(true),
frequency: z.string().default("*/5 * * * *"),
+ po_token_check: z
+ .boolean()
+ .default(
+ Deno.env.get("PO_TOKEN_CHECK") === "true"
+ ? true
+ : Deno.env.get("PO_TOKEN_CHECK") === "false"
+ ? false
+ : true,
+ ),
}).strict().default({}),
}).strict().default({}),
youtube_session: z.object({
diff --git a/src/lib/jobs/potoken.ts b/src/lib/jobs/potoken.ts
index 233ac32..c7994c5 100644
--- a/src/lib/jobs/potoken.ts
+++ b/src/lib/jobs/potoken.ts
@@ -140,46 +140,50 @@ export const poTokenGenerate = async (
instantiatedInnertubeClient.session.player = player;
instantiatedInnertubeClient.session.player.po_token = sessionPoToken;
- try {
- const feed = await instantiatedInnertubeClient.getTrending();
- // get all videos and shuffle them randomly to avoid using the same trending video over and over
- const videos = feed.videos
- .filter((video) => video.type === "Video")
- .map((value) => ({ value, sort: Math.random() }))
- .sort((a, b) => a.sort - b.sort)
- .map(({ value }) => value);
-
- const video = videos.find((video) => "id" in video);
- if (!video) {
- throw new Error("no videos with id found in trending");
- }
-
- const youtubePlayerResponseJson = await youtubePlayerParsing({
- innertubeClient: instantiatedInnertubeClient,
- videoId: video.id,
- config,
- tokenMinter: integrityTokenBasedMinter,
- overrideCache: true,
- });
- const videoInfo = youtubeVideoInfo(
- instantiatedInnertubeClient,
- youtubePlayerResponseJson,
- );
- const validFormat = videoInfo.streaming_data?.adaptive_formats[0];
- if (!validFormat) {
- throw new Error(
- "failed to find valid video with adaptive format to check token against",
- );
- }
- const result = await fetchImpl(validFormat?.url, { method: "HEAD" });
- if (result.status !== 200) {
- throw new Error(
- `did not get a 200 when checking video, got ${result.status} instead`,
+ if (config.jobs.youtube_session.po_token_check) {
+ try {
+ const feed = await instantiatedInnertubeClient.getTrending();
+ // get all videos and shuffle them randomly to avoid using the same trending video over and over
+ const videos = feed.videos
+ .filter((video) => video.type === "Video")
+ .map((value) => ({ value, sort: Math.random() }))
+ .sort((a, b) => a.sort - b.sort)
+ .map(({ value }) => value);
+
+ const video = videos.find((video) => "id" in video);
+ if (!video) {
+ throw new Error("no videos with id found in trending");
+ }
+
+ const youtubePlayerResponseJson = await youtubePlayerParsing({
+ innertubeClient: instantiatedInnertubeClient,
+ videoId: video.id,
+ config,
+ tokenMinter: integrityTokenBasedMinter,
+ overrideCache: true,
+ });
+ const videoInfo = youtubeVideoInfo(
+ instantiatedInnertubeClient,
+ youtubePlayerResponseJson,
);
+ const validFormat = videoInfo.streaming_data?.adaptive_formats[0];
+ if (!validFormat) {
+ throw new Error(
+ "failed to find valid video with adaptive format to check token against",
+ );
+ }
+ const result = await fetchImpl(validFormat?.url, {
+ method: "HEAD",
+ });
+ if (result.status !== 200) {
+ throw new Error(
+ `did not get a 200 when checking video, got ${result.status} instead`,
+ );
+ }
+ } catch (err) {
+ console.log("Failed to get valid PO token, will retry", { err });
+ throw err;
}
- } catch (err) {
- console.log("Failed to get valid PO token, will retry", { err });
- throw err;
}
return {
--
2.49.0

View file

@ -1,7 +1,7 @@
From 09ca1218e9152cfb3d20347e993b9d60224e9ef1 Mon Sep 17 00:00:00 2001
From a5c04d5daba5f12bd078b76c18d0d2fa073a3893 Mon Sep 17 00:00:00 2001
From: Fijxu <fijxu@nadeko.net>
Date: Mon, 24 Mar 2025 21:38:33 -0300
Subject: [PATCH 10/14] feat: add support for encrypted query parameters
Subject: [PATCH 09/13] feat: add support for encrypted query parameters
---
src/lib/helpers/config.ts | 9 ++++

View file

@ -1,7 +1,7 @@
From 35e04439dbfddc8cf89a561113a968e71f0ce888 Mon Sep 17 00:00:00 2001
From 33f634b5cd1c5e052a712817b1aabdbbe8b54479 Mon Sep 17 00:00:00 2001
From: Fijxu <fijxu@nadeko.net>
Date: Tue, 25 Mar 2025 00:04:47 -0300
Subject: [PATCH 11/14] add proxy retries on innertube error
Subject: [PATCH 10/13] add proxy retries on innertube error
---
src/lib/helpers/config.ts | 1 +

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,7 @@
From eee0f4e30c66f71d88a2564eb87c9da6e052f64f Mon Sep 17 00:00:00 2001
From a3ea592b89c4bc27ac087d4207563d82a9c50592 Mon Sep 17 00:00:00 2001
From: Fijxu <fijxu@nadeko.net>
Date: Tue, 25 Mar 2025 00:07:28 -0300
Subject: [PATCH 13/14] add metrics for proxy retries
Subject: [PATCH 12/13] add metrics for proxy retries
---
src/lib/helpers/metrics.ts | 5 +++++

View file

@ -1,569 +0,0 @@
From 5352eed8c5ba428ac3e17cec5105e0c4e23db53b Mon Sep 17 00:00:00 2001
From: Fijxu <fijxu@nadeko.net>
Date: Tue, 18 Mar 2025 16:38:23 -0300
Subject: [PATCH 12/14] add support for prometheus metrics
fix deno lint and typo
chore: fmt
apply suggestions
better innertube response checking
rename route _metrics to metrics
use Hono context to pass Metrics onto functions
fix: add missing metrics argument
---
config/config.example.toml | 1 +
deno.json | 1 +
deno.lock | 25 ++++-
src/lib/helpers/config.ts | 11 +-
src/lib/helpers/metrics.ts | 112 +++++++++++++++++++
src/lib/helpers/youtubePlayerHandling.ts | 34 +++---
src/lib/jobs/potoken.ts | 3 +
src/lib/types/HonoVariables.ts | 2 +
src/main.ts | 20 +++-
src/routes/index.ts | 4 +
src/routes/invidious_routes/captions.ts | 2 +
src/routes/invidious_routes/dashManifest.ts | 2 +
src/routes/invidious_routes/latestVersion.ts | 2 +
src/routes/metrics.ts | 11 ++
src/routes/youtube_api_routes/player.ts | 2 +
15 files changed, 211 insertions(+), 21 deletions(-)
create mode 100644 src/lib/helpers/metrics.ts
create mode 100644 src/routes/metrics.ts
diff --git a/config/config.example.toml b/config/config.example.toml
index 4c04ee3..01c3f83 100644
--- a/config/config.example.toml
+++ b/config/config.example.toml
@@ -16,6 +16,7 @@
# secret_key = "CHANGE_ME" # env variable: SERVER_SECRET_KEY
# verify_requests = false
# max_dash_resolution = 1080
+# enable_metrics = false # env variable: ENABLE_METRICS
# [cache]
# enabled = true
diff --git a/deno.json b/deno.json
index 02ea3a2..97600ec 100644
--- a/deno.json
+++ b/deno.json
@@ -6,6 +6,7 @@
"imports": {
"hono": "jsr:@hono/hono@4.7.4",
"@std/toml": "jsr:@std/toml@1.0.2",
+ "prom-client": "npm:prom-client@15.1.3",
"youtubei.js": "https://raw.githubusercontent.com/LuanRT/YouTube.js/refs/tags/v13.1.0-deno/deno.ts",
"youtubei.js/Utils": "https://raw.githubusercontent.com/LuanRT/YouTube.js/refs/tags/v13.1.0-deno/deno/src/utils/Utils.ts",
"youtubei.js/NavigationEndpoint": "https://raw.githubusercontent.com/LuanRT/YouTube.js/refs/tags/v13.1.0-deno/deno/src/parser/classes/NavigationEndpoint.ts",
diff --git a/deno.lock b/deno.lock
index 27d8ae6..a9c62e7 100644
--- a/deno.lock
+++ b/deno.lock
@@ -15,7 +15,8 @@
"npm:@bufbuild/protobuf@2": "2.2.4",
"npm:@types/estree@^1.0.6": "1.0.6",
"npm:acorn@^8.8.0": "8.14.1",
- "npm:jsdom@26.0.0": "26.0.0"
+ "npm:jsdom@26.0.0": "26.0.0",
+ "npm:prom-client@15.1.3": "15.1.3"
},
"jsr": {
"@hono/hono@4.7.4": {
@@ -101,6 +102,9 @@
"@csstools/css-tokenizer@3.0.3": {
"integrity": "sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw=="
},
+ "@opentelemetry/api@1.9.0": {
+ "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="
+ },
"@types/estree@1.0.6": {
"integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw=="
},
@@ -113,6 +117,9 @@
"asynckit@0.4.0": {
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
+ "bintrees@1.0.2": {
+ "integrity": "sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw=="
+ },
"call-bind-apply-helpers@1.0.2": {
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"dependencies": [
@@ -318,6 +325,13 @@
"entities"
]
},
+ "prom-client@15.1.3": {
+ "integrity": "sha512-6ZiOBfCywsD4k1BN9IX0uZhF+tJkV8q8llP64G5Hajs4JOeVLPCwpPVcpXy3BwYiUGgyJzsJJQeOIv7+hDSq8g==",
+ "dependencies": [
+ "@opentelemetry/api",
+ "tdigest"
+ ]
+ },
"punycode@2.3.1": {
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="
},
@@ -336,6 +350,12 @@
"symbol-tree@3.2.4": {
"integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw=="
},
+ "tdigest@0.1.2": {
+ "integrity": "sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA==",
+ "dependencies": [
+ "bintrees"
+ ]
+ },
"tldts-core@6.1.84": {
"integrity": "sha512-NaQa1W76W2aCGjXybvnMYzGSM4x8fvG2AN/pla7qxcg0ZHbooOPhA8kctmOZUDfZyhDL27OGNbwAeig8P4p1vg=="
},
@@ -1102,7 +1122,8 @@
"jsr:@std/fs@1.0.14",
"jsr:@std/path@1.0.8",
"jsr:@std/toml@1.0.2",
- "npm:jsdom@26.0.0"
+ "npm:jsdom@26.0.0",
+ "npm:prom-client@15.1.3"
]
}
}
diff --git a/src/lib/helpers/config.ts b/src/lib/helpers/config.ts
index d539738..646132f 100644
--- a/src/lib/helpers/config.ts
+++ b/src/lib/helpers/config.ts
@@ -23,6 +23,13 @@ const ConfigSchema = z.object({
? false
: true,
),
+ enable_metrics: z.boolean().default(
+ Deno.env.get("ENABLE_METRICS") === "true"
+ ? true
+ : Deno.env.get("ENABLE_METRICS") === "false"
+ ? false
+ : true,
+ ),
}).strict().default({}),
cache: z.object({
enabled: z.boolean().default(true),
@@ -43,7 +50,9 @@ const ConfigSchema = z.object({
external_videoplayback_proxy: z.string().default(
Deno.env.get("EXTERNAL_VIDEOPLAYBACK_PROXY") || "",
),
- max_proxy_retries: z.number().default(Number(Deno.env.get("MAX_PROXY_RETIRES") || 5)),
+ max_proxy_retries: z.number().default(
+ Number(Deno.env.get("MAX_PROXY_RETIRES") || 5),
+ ),
}).strict().default({}),
jobs: z.object({
youtube_session: z.object({
diff --git a/src/lib/helpers/metrics.ts b/src/lib/helpers/metrics.ts
new file mode 100644
index 0000000..98ca83d
--- /dev/null
+++ b/src/lib/helpers/metrics.ts
@@ -0,0 +1,112 @@
+import { IRawResponse } from "youtubei.js";
+import { Counter, Registry } from "prom-client";
+
+export let metrics: Metrics | undefined;
+
+export class Metrics {
+ private METRICS_PREFIX = "invidious_companion_";
+ public register = new Registry();
+
+ public createCounter(name: string, help?: string): Counter {
+ return new Counter({
+ name: `${this.METRICS_PREFIX}${name}`,
+ help: help || "No help has been provided for this metric",
+ registers: [this.register],
+ });
+ }
+
+ public potokenGenerationFailure = this.createCounter(
+ "potoken_generation_failure",
+ "Number of times that the PoToken generation job has failed for whatever reason",
+ );
+
+ private innertubeErrorStatusUnknown = this.createCounter(
+ "innertube_error_status_unknown",
+ "Number of times that an unknown status has been returned by Innertube API",
+ );
+
+ private innertubeErrorReasonSignIn = this.createCounter(
+ "innertube_error_reason_SignIn",
+ 'Number of times that the message "Sign in to confirm youre not a bot." has been returned by Innertube API',
+ );
+
+ private innertubeErrorSubreasonProtectCommunity = this.createCounter(
+ "innertube_error_subreason_ProtectCommunity",
+ 'Number of times that the message "This helps protect our community." has been returned by Innertube API',
+ );
+
+ private innertubeErrorReasonUnknown = this.createCounter(
+ "innertube_error_reason_unknown",
+ "Number of times that an unknown reason has been returned by the Innertube API",
+ );
+
+ private innertubeErrorSubreasonUnknown = this.createCounter(
+ "innertube_error_subreason_unknown",
+ "Number of times that an unknown subreason has been returned by the Innertube API",
+ );
+
+ public innertubeSuccessfulRequest = this.createCounter(
+ "innertube_successful_request",
+ "Number successful requests made to the Innertube API",
+ );
+
+ private innertubeFailedRequest = this.createCounter(
+ "innertube_failed_request",
+ "Number failed requests made to the Innertube API for whatever reason",
+ );
+
+ public checkInnertubeResponse(videoData: IRawResponse) {
+ this.innertubeFailedRequest.inc();
+
+ switch (true) {
+ // CONTENT_CHECK_REQUIRED: Sensitive content videos.
+ case (videoData.playabilityStatus?.status ===
+ "CONTENT_CHECK_REQUIRED"): {
+ break;
+ }
+ case (videoData.playabilityStatus?.status === "LOGIN_REQUIRED"): {
+ switch (true) {
+ // Age restricted videos, we don't need to track those.
+ case videoData.playabilityStatus?.reason?.includes(
+ "Sign in to confirm your age",
+ ): {
+ break;
+ }
+
+ case videoData.playabilityStatus?.reason?.includes(
+ "Sign in to confirm youre not a bot",
+ ): {
+ this.innertubeErrorReasonSignIn.inc();
+
+ switch (true) {
+ case videoData.playabilityStatus?.errorScreen
+ ?.playerErrorMessageRenderer
+ ?.subreason?.runs?.[0]?.text?.includes(
+ "This helps protect our community",
+ ): {
+ this.innertubeErrorSubreasonProtectCommunity
+ .inc();
+ break;
+ }
+ default: {
+ this.innertubeErrorSubreasonUnknown.inc();
+ break;
+ }
+ }
+
+ break;
+ }
+
+ default: {
+ this.innertubeErrorReasonUnknown.inc();
+ break;
+ }
+ }
+ break;
+ }
+ default:
+ this.innertubeErrorStatusUnknown.inc();
+ break;
+ }
+ }
+}
diff --git a/src/lib/helpers/youtubePlayerHandling.ts b/src/lib/helpers/youtubePlayerHandling.ts
index 396eabf..2ae878d 100644
--- a/src/lib/helpers/youtubePlayerHandling.ts
+++ b/src/lib/helpers/youtubePlayerHandling.ts
@@ -2,6 +2,7 @@ import { ApiResponse, Innertube, YT } from "youtubei.js";
import { generateRandomString } from "youtubei.js/Utils";
import { compress, decompress } from "brotli";
import type { BG } from "bgutils";
+import { Metrics } from "../helpers/metrics.ts";
let youtubePlayerReqLocation = "youtubePlayerReq";
if (Deno.env.get("YT_PLAYER_REQ_LOCATION")) {
if (Deno.env.has("DENO_COMPILED")) {
@@ -24,12 +25,14 @@ export const youtubePlayerParsing = async ({
videoId,
config,
tokenMinter,
+ metrics,
overrideCache = false,
}: {
innertubeClient: Innertube;
videoId: string;
config: Config;
tokenMinter: BG.WebPoMinter;
+ metrics: Metrics | undefined;
overrideCache?: boolean;
}): Promise<object> => {
const cacheEnabled = overrideCache ? false : config.cache.enabled;
@@ -167,20 +170,25 @@ export const youtubePlayerParsing = async ({
microformat,
}))(videoData);
- if (cacheEnabled && videoData.playabilityStatus?.status == "OK") {
- (async () => {
- await kv.set(
- ["video_cache", videoId],
- compress(
- new TextEncoder().encode(
- JSON.stringify(videoOnlyNecessaryInfo),
+ if (videoData.playabilityStatus?.status == "OK") {
+ metrics?.innertubeSuccessfulRequest.inc();
+ if (cacheEnabled) {
+ (async () => {
+ await kv.set(
+ ["video_cache", videoId],
+ compress(
+ new TextEncoder().encode(
+ JSON.stringify(videoOnlyNecessaryInfo),
+ ),
),
- ),
- {
- expireIn: 1000 * 60 * 60,
- },
- );
- })();
+ {
+ expireIn: 1000 * 60 * 60,
+ },
+ );
+ })();
+ }
+ } else {
+ metrics?.checkInnertubeResponse(videoData);
}
return videoOnlyNecessaryInfo;
diff --git a/src/lib/jobs/potoken.ts b/src/lib/jobs/potoken.ts
index c7994c5..2acc957 100644
--- a/src/lib/jobs/potoken.ts
+++ b/src/lib/jobs/potoken.ts
@@ -8,6 +8,7 @@ import {
youtubeVideoInfo,
} from "../helpers/youtubePlayerHandling.ts";
import type { Config } from "../helpers/config.ts";
+import { Metrics } from "../helpers/metrics.ts";
let getFetchClientLocation = "getFetchClient";
if (Deno.env.get("GET_FETCH_CLIENT_LOCATION")) {
if (Deno.env.has("DENO_COMPILED")) {
@@ -26,6 +27,7 @@ export const poTokenGenerate = async (
innertubeClient: Innertube,
config: Config,
innertubeClientCache: UniversalCache,
+ metrics: Metrics | undefined,
): Promise<{ innertubeClient: Innertube; tokenMinter: BG.WebPoMinter }> => {
if (innertubeClient.session.po_token) {
innertubeClient = await Innertube.create({
@@ -160,6 +162,7 @@ export const poTokenGenerate = async (
videoId: video.id,
config,
tokenMinter: integrityTokenBasedMinter,
+ metrics,
overrideCache: true,
});
const videoInfo = youtubeVideoInfo(
diff --git a/src/lib/types/HonoVariables.ts b/src/lib/types/HonoVariables.ts
index 4f42d28..1e9b7a2 100644
--- a/src/lib/types/HonoVariables.ts
+++ b/src/lib/types/HonoVariables.ts
@@ -1,9 +1,11 @@
import { Innertube } from "youtubei.js";
import { BG } from "bgutils";
import type { Config } from "../helpers/config.ts";
+import { Metrics } from "../helpers/metrics.ts";
export type HonoVariables = {
innertubeClient: Innertube;
config: Config;
tokenMinter: BG.WebPoMinter;
+ metrics: Metrics | undefined;
};
diff --git a/src/main.ts b/src/main.ts
index b57cdc5..952b9f8 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -20,6 +20,7 @@ import type { HonoVariables } from "./lib/types/HonoVariables.ts";
import { parseConfig } from "./lib/helpers/config.ts";
const config = await parseConfig();
+import { Metrics } from "./lib/helpers/metrics.ts";
let getFetchClientLocation = "getFetchClient";
if (Deno.env.get("GET_FETCH_CLIENT_LOCATION")) {
@@ -38,6 +39,7 @@ declare module "hono" {
interface ContextVariableMap extends HonoVariables {}
}
const app = new Hono();
+const metrics = config.server.enable_metrics ? new Metrics() : undefined;
let tokenMinter: BG.WebPoMinter;
let innertubeClient: Innertube;
@@ -160,6 +162,7 @@ if (!innertubeClientOauthEnabled) {
innertubeClient,
config,
innertubeClientCache as UniversalCache,
+ metrics,
),
{ minTimeout: 1_000, maxTimeout: 60_000, multiplier: 5, jitter: 0 },
));
@@ -170,11 +173,17 @@ if (!innertubeClientOauthEnabled) {
{ backoffSchedule: [5_000, 15_000, 60_000, 180_000] },
async () => {
if (innertubeClientJobPoTokenEnabled) {
- ({ innertubeClient, tokenMinter } = await poTokenGenerate(
- innertubeClient,
- config,
- innertubeClientCache,
- ));
+ try {
+ ({ innertubeClient, tokenMinter } = await poTokenGenerate(
+ innertubeClient,
+ config,
+ innertubeClientCache,
+ metrics,
+ ));
+ } catch (err) {
+ metrics?.potokenGenerationFailure.inc();
+ throw err;
+ }
} else {
innertubeClient = await Innertube.create({
enable_session_cache: false,
@@ -212,6 +221,7 @@ app.use("*", async (c, next) => {
c.set("innertubeClient", innertubeClient);
c.set("tokenMinter", tokenMinter);
c.set("config", config);
+ c.set("metrics", metrics);
await next();
});
diff --git a/src/routes/index.ts b/src/routes/index.ts
index 6448e3d..07ff900 100644
--- a/src/routes/index.ts
+++ b/src/routes/index.ts
@@ -11,6 +11,7 @@ import videoPlaybackProxy from "./videoPlaybackProxy.ts";
import health from "./health.ts";
import type { Config } from "../lib/helpers/config.ts";
import info from "./info.ts";
+import metrics from "./metrics.ts";
export const routes = (
app: Hono,
@@ -34,4 +35,7 @@ export const routes = (
app.route("/videoplayback", videoPlaybackProxy);
app.route("/healthz", health);
app.route("/info", info);
+ if (config.server.enable_metrics) {
+ app.route("/metrics", metrics);
+ }
};
diff --git a/src/routes/invidious_routes/captions.ts b/src/routes/invidious_routes/captions.ts
index 1eaeb14..f900298 100644
--- a/src/routes/invidious_routes/captions.ts
+++ b/src/routes/invidious_routes/captions.ts
@@ -19,6 +19,7 @@ const captionsHandler = new Hono<{ Variables: HonoVariables }>();
captionsHandler.get("/:videoId", async (c) => {
const { videoId } = c.req.param();
const config = c.get("config");
+ const metrics = c.get("metrics");
const check = c.req.query("check");
@@ -41,6 +42,7 @@ captionsHandler.get("/:videoId", async (c) => {
videoId,
config,
tokenMinter: c.get("tokenMinter"),
+ metrics,
});
const videoInfo = youtubeVideoInfo(
diff --git a/src/routes/invidious_routes/dashManifest.ts b/src/routes/invidious_routes/dashManifest.ts
index a4c0950..414b4cf 100644
--- a/src/routes/invidious_routes/dashManifest.ts
+++ b/src/routes/invidious_routes/dashManifest.ts
@@ -17,6 +17,7 @@ dashManifest.get("/:videoId", async (c) => {
const innertubeClient = c.get("innertubeClient");
const config = c.get("config");
+ const metrics = c.get("metrics");
if (config.server.verify_requests && check == undefined) {
throw new HTTPException(400, {
@@ -35,6 +36,7 @@ dashManifest.get("/:videoId", async (c) => {
videoId,
config,
tokenMinter: c.get("tokenMinter"),
+ metrics,
});
const videoInfo = youtubeVideoInfo(
innertubeClient,
diff --git a/src/routes/invidious_routes/latestVersion.ts b/src/routes/invidious_routes/latestVersion.ts
index f1a7605..dbf57c2 100644
--- a/src/routes/invidious_routes/latestVersion.ts
+++ b/src/routes/invidious_routes/latestVersion.ts
@@ -21,6 +21,7 @@ latestVersion.get("/", async (c) => {
const innertubeClient = c.get("innertubeClient");
const config = c.get("config");
+ const metrics = c.get("metrics");
if (config.server.verify_requests && check == undefined) {
throw new HTTPException(400, {
@@ -39,6 +40,7 @@ latestVersion.get("/", async (c) => {
videoId: id,
config,
tokenMinter: c.get("tokenMinter"),
+ metrics,
});
const videoInfo = youtubeVideoInfo(
innertubeClient,
diff --git a/src/routes/metrics.ts b/src/routes/metrics.ts
new file mode 100644
index 0000000..8e0eea8
--- /dev/null
+++ b/src/routes/metrics.ts
@@ -0,0 +1,11 @@
+import { Hono } from "hono";
+
+const metrics = new Hono();
+
+metrics.get("/", async (c) => {
+ return new Response(await c.get("metrics")?.register.metrics(), {
+ headers: { "Content-Type": "text/plain" },
+ });
+});
+
+export default metrics;
diff --git a/src/routes/youtube_api_routes/player.ts b/src/routes/youtube_api_routes/player.ts
index 0b4ac0e..a4071cc 100644
--- a/src/routes/youtube_api_routes/player.ts
+++ b/src/routes/youtube_api_routes/player.ts
@@ -7,6 +7,7 @@ player.post("/player", async (c) => {
const jsonReq = await c.req.json();
const innertubeClient = c.get("innertubeClient");
const config = c.get("config");
+ const metrics = c.get("metrics");
if (jsonReq.videoId) {
return c.json(
await youtubePlayerParsing({
@@ -14,6 +15,7 @@ player.post("/player", async (c) => {
videoId: jsonReq.videoId,
config,
tokenMinter: c.get("tokenMinter"),
+ metrics,
}),
);
}
--
2.49.0

View file

@ -1,7 +1,7 @@
From 1d07448dc88171b2159955007b42f5b5238bd8ae Mon Sep 17 00:00:00 2001
From 17122bda7e85d01d0ae799098306424e672ccf7d Mon Sep 17 00:00:00 2001
From: Fijxu <fijxu@nadeko.net>
Date: Tue, 25 Mar 2025 00:24:07 -0300
Subject: [PATCH 14/14] fix: fix tokio overflow on compile
Subject: [PATCH 13/13] fix: fix tokio overflow on compile
---
Dockerfile | 2 ++