Files
invidious/patches/companion/0004-store-companion-status.patch
T
2026-01-01 11:50:41 +01:00

291 lines
10 KiB
Diff

From 6953eed8bd7dfb68244947dbdd274a23ad501743 Mon Sep 17 00:00:00 2001
From: Fijxu <fijxu@nadeko.net>, weidenwiesel <webmaster@nerdvpn.de>
Date: Mon, 13 Oct 2025 10:26:32 +0200
Subject: [PATCH 4/4] [Patch] 004 - store companion status
---
src/constants.ts | 3 +++
src/lib/helpers/companionStatus.ts | 33 +++++++++++++++++++++++++
src/lib/jobs/potoken.ts | 7 ++++++
src/lib/types/HonoVariables.ts | 2 ++
src/main.ts | 6 +++++
src/routes/index.ts | 2 ++
src/routes/status.ts | 26 +++++++++++++++++++
src/routes/youtube_api_routes/player.ts | 25 ++++++++++++++++++-
8 files changed, 103 insertions(+), 1 deletion(-)
create mode 100644 src/lib/helpers/companionStatus.ts
create mode 100644 src/routes/status.ts
diff --git a/src/constants.ts b/src/constants.ts
index 973d1a1..5c14d06 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -8,4 +8,6 @@ export const PLAYER_ID = undefined;
// Error message shown when tokenMinter is not yet ready
export const TOKEN_MINTER_NOT_READY_MESSAGE =
- "Companion is starting. Please wait until a valid potoken is found. If this process takes too long, please consult: https://docs.invidious.io/youtube-errors-explained/#po-token-initialization-taking-too-much-time-to-complete";
+ "This backend is still starting and not yet available. Please wait for a short time or use another backend.";
+export const COMPANION_BLOCKED_MESSAGE =
+ "This backend is blocked by Youtube. Please wait until it has refreshed itself or switch to another backend."
diff --git a/src/lib/jobs/potoken.ts b/src/lib/jobs/potoken.ts
index a2616da..718eedd 100644
--- a/src/lib/jobs/potoken.ts
+++ b/src/lib/jobs/potoken.ts
@@ -20,6 +20,7 @@ const { getFetchClient } = await import(getFetchClientLocation);
import { InputMessage, OutputMessageSchema } from "./worker.ts";
import { PLAYER_ID } from "../../constants.ts";
+import { CompanionStatus } from "../helpers/companionStatus.ts";
interface TokenGeneratorWorker extends Omit<Worker, "postMessage"> {
postMessage(message: InputMessage): void;
@@ -61,6 +62,7 @@ export type TokenMinter = ReturnType<typeof createMinter>;
export const poTokenGenerate = (
config: Config,
metrics: Metrics | undefined,
+ companionStatus: CompanionStatus,
): Promise<{ innertubeClient: Innertube; tokenMinter: TokenMinter }> => {
const { promise, resolve, reject } = Promise.withResolvers<
Awaited<ReturnType<typeof poTokenGenerate>>
@@ -111,6 +113,7 @@ export const poTokenGenerate = (
config,
integrityTokenBasedMinter: minter,
metrics,
+ companionStatus,
});
console.log("[INFO] Successfully generated PO token");
const numberToKill = workers.length - 1;
@@ -118,11 +121,13 @@ export const poTokenGenerate = (
const workerToKill = workers.shift();
workerToKill?.terminate();
}
+ companionStatus.setStatusOK();
return resolve({
innertubeClient: instantiatedInnertubeClient,
tokenMinter: minter,
});
} catch (err) {
+ companionStatus.setStatusBlocked();
console.log("[WARN] Failed to get valid PO token, will retry", {
err,
});
@@ -140,11 +145,13 @@ async function checkToken({
config,
integrityTokenBasedMinter,
metrics,
+ companionStatus,
}: {
instantiatedInnertubeClient: Innertube;
config: Config;
integrityTokenBasedMinter: TokenMinter;
metrics: Metrics | undefined;
+ companionStatus: CompanionStatus;
}) {
const fetchImpl = getFetchClient(config);
diff --git a/src/lib/types/HonoVariables.ts b/src/lib/types/HonoVariables.ts
index f1e64d5..8b5c6a1 100644
--- a/src/lib/types/HonoVariables.ts
+++ b/src/lib/types/HonoVariables.ts
@@ -2,10 +2,12 @@ import { Innertube } from "youtubei.js";
import type { TokenMinter } from "../jobs/potoken.ts";
import type { Config } from "../helpers/config.ts";
import { Metrics } from "../helpers/metrics.ts";
+import { CompanionStatus } from "../helpers/companionStatus.ts";
export type HonoVariables = {
innertubeClient: Innertube;
config: Config;
tokenMinter: TokenMinter | undefined;
metrics: Metrics | undefined;
+ companionStatus: CompanionStatus;
};
diff --git a/src/main.ts b/src/main.ts
index 7bbe819..5dee6b6 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -13,6 +13,7 @@ const config = await parseConfig();
import { Metrics } from "./lib/helpers/metrics.ts";
import { PLAYER_ID } from "./constants.ts";
import { jsInterpreter } from "./lib/helpers/jsInterpreter.ts";
+import { CompanionStatus } from "./lib/helpers/companionStatus.ts";
const args = parseArgs(Deno.args);
@@ -46,6 +47,7 @@ const companionApp = new Hono({
getPath: (req) => new URL(req.url).pathname,
}).basePath(config.server.base_path);
const metrics = config.server.enable_metrics ? new Metrics() : undefined;
+const companionStatus = new CompanionStatus();
let tokenMinter: TokenMinter | undefined;
let innertubeClient: Innertube;
@@ -91,6 +93,7 @@ if (!innertubeClientOauthEnabled) {
poTokenGenerate,
config,
metrics,
+ companionStatus,
),
{ minTimeout: 1_000, maxTimeout: 60_000, multiplier: 5, jitter: 0 },
).then((result) => {
@@ -116,6 +119,7 @@ if (!innertubeClientOauthEnabled) {
({ innertubeClient, tokenMinter } = await poTokenGenerate(
config,
metrics,
+ companionStatus,
));
} catch (err) {
metrics?.potokenGenerationFailure.inc();
@@ -162,6 +166,7 @@ companionApp.use("*", async (c, next) => {
c.set("tokenMinter", tokenMinter);
c.set("config", config);
c.set("metrics", metrics);
+ c.set("companionStatus", companionStatus);
await next();
});
companionRoutes(companionApp, config);
diff --git a/src/routes/index.ts b/src/routes/index.ts
index 09cd4ff..5113811 100644
--- a/src/routes/index.ts
+++ b/src/routes/index.ts
@@ -11,6 +11,7 @@ import videoPlaybackProxy from "./videoPlaybackProxy.ts";
import type { Config } from "../lib/helpers/config.ts";
import metrics from "./metrics.ts";
import health from "./health.ts";
+import status from "./status.ts";
export const customLogger = () => {
return;
@@ -53,6 +54,7 @@ export const miscRoutes = (
config: Config,
) => {
app.route("/healthz", health);
+ app.route("/status", status);
if (config.server.enable_metrics) {
app.route("/metrics", metrics);
}
diff --git a/src/routes/youtube_api_routes/player.ts b/src/routes/youtube_api_routes/player.ts
index f416fa6..c8d5d0d 100644
--- a/src/routes/youtube_api_routes/player.ts
+++ b/src/routes/youtube_api_routes/player.ts
@@ -2,7 +2,10 @@ import { Hono } from "hono";
import { youtubePlayerParsing } from "../../lib/helpers/youtubePlayerHandling.ts";
import { HTTPException } from "hono/http-exception";
import { validateVideoId } from "../../lib/helpers/validateVideoId.ts";
-import { TOKEN_MINTER_NOT_READY_MESSAGE } from "../../constants.ts";
+import {
+ COMPANION_BLOCKED_MESSAGE,
+ TOKEN_MINTER_NOT_READY_MESSAGE,
+} from "../../constants.ts";
const player = new Hono();
@@ -12,6 +15,7 @@ player.post("/player", async (c) => {
const config = c.get("config");
const metrics = c.get("metrics");
const tokenMinter = c.get("tokenMinter");
+ const companionStatus = c.get("companionStatus");
// Check if tokenMinter is ready (only needed when PO token is enabled)
if (config.jobs.youtube_session.po_token_enabled && !tokenMinter) {
@@ -33,6 +37,25 @@ player.post("/player", async (c) => {
});
}
+ if (companionStatus.isBlocked()) {
+ return c.json({
+ playabilityStatus: {
+ status: "ERROR",
+ reason: COMPANION_BLOCKED_MESSAGE,
+ errorScreen: {
+ playerErrorMessageRenderer: {
+ reason: {
+ simpleText: COMPANION_BLOCKED_MESSAGE,
+ },
+ subreason: {
+ simpleText: COMPANION_BLOCKED_MESSAGE,
+ },
+ },
+ },
+ },
+ });
+ }
+
if (jsonReq.videoId) {
if (!validateVideoId(jsonReq.videoId)) {
throw new HTTPException(400, {
diff --git a/src/lib/helpers/companionStatus.ts b/src/lib/helpers/companionStatus.ts
new file mode 100644
index 0000000..cf01fe5
--- /dev/null
+++ b/src/lib/helpers/companionStatus.ts
@@ -0,0 +1,33 @@
+enum Status {
+ Blocked,
+ OK,
+}
+
+export class CompanionStatus {
+ private currentStatus: Status = Status.OK;
+ private _blockedCount: number = 0;
+
+ public getBlockedCount(): number {
+ return this._blockedCount;
+ }
+
+ public isBlocked(): boolean {
+ if (this.currentStatus == Status.Blocked) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ public setStatusOK(): void {
+ console.log("[INFO] status set to OK");
+ this._blockedCount = 0;
+ this.currentStatus = Status.OK;
+ }
+
+ public setStatusBlocked(): void {
+ console.log("[INFO] status set to BLOCKED");
+ this._blockedCount++;
+ this.currentStatus = Status.Blocked;
+ }
+}
diff --git a/src/routes/status.ts b/src/routes/status.ts
new file mode 100644
index 0000000..61149b9
--- /dev/null
+++ b/src/routes/status.ts
@@ -0,0 +1,26 @@
+import { Hono } from "hono";
+
+const status = new Hono();
+
+interface Data {
+ blocked: boolean;
+ blockedCount: number;
+}
+
+status.get("/", (c) => {
+ const companionStatus = c.get("companionStatus");
+
+ const data: Data = {
+ blocked: companionStatus.isBlocked(),
+ blockedCount: companionStatus.getBlockedCount(),
+ };
+
+ const responseBody = JSON.stringify(data);
+
+ return new Response(responseBody, {
+ status: 200,
+ headers: { "Content-Type": "application/json" },
+ });
+});
+
+export default status;
--
2.52.0