add support for prometheus metrics
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 54s
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 54s
This commit is contained in:
parent
f73ed00b6d
commit
9f579c806a
6 changed files with 101 additions and 4 deletions
|
@ -7,6 +7,7 @@
|
||||||
"hono": "jsr:@hono/hono@^4.6.5",
|
"hono": "jsr:@hono/hono@^4.6.5",
|
||||||
"hono/logger": "jsr:@hono/hono@^4.6.5/logger",
|
"hono/logger": "jsr:@hono/hono@^4.6.5/logger",
|
||||||
"hono/bearer-auth": "jsr:@hono/hono@^4.6.5/bearer-auth",
|
"hono/bearer-auth": "jsr:@hono/hono@^4.6.5/bearer-auth",
|
||||||
|
"prom-client": "npm:prom-client@^15.1.3",
|
||||||
"youtubei.js": "https://raw.githubusercontent.com/LuanRT/YouTube.js/refs/tags/v12.2.0-deno/deno.ts",
|
"youtubei.js": "https://raw.githubusercontent.com/LuanRT/YouTube.js/refs/tags/v12.2.0-deno/deno.ts",
|
||||||
"youtubei.js/Utils": "https://raw.githubusercontent.com/LuanRT/YouTube.js/refs/tags/v12.2.0-deno/deno/src/utils/Utils.ts",
|
"youtubei.js/Utils": "https://raw.githubusercontent.com/LuanRT/YouTube.js/refs/tags/v12.2.0-deno/deno/src/utils/Utils.ts",
|
||||||
"youtubei.js/NavigationEndpoint": "https://raw.githubusercontent.com/LuanRT/YouTube.js/refs/tags/v12.2.0-deno/deno/src/parser/classes/NavigationEndpoint.ts",
|
"youtubei.js/NavigationEndpoint": "https://raw.githubusercontent.com/LuanRT/YouTube.js/refs/tags/v12.2.0-deno/deno/src/parser/classes/NavigationEndpoint.ts",
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { ApiResponse, Innertube, YT } from "youtubei.js";
|
||||||
import { generateRandomString } from "youtubei.js/Utils";
|
import { generateRandomString } from "youtubei.js/Utils";
|
||||||
import { compress, decompress } from "https://deno.land/x/brotli@0.1.7/mod.ts";
|
import { compress, decompress } from "https://deno.land/x/brotli@0.1.7/mod.ts";
|
||||||
import { Store } from "@willsoto/node-konfig-core";
|
import { Store } from "@willsoto/node-konfig-core";
|
||||||
|
import { cachedEntries } from "../../routes/index.ts";
|
||||||
let youtubePlayerReqLocation = "youtubePlayerReq";
|
let youtubePlayerReqLocation = "youtubePlayerReq";
|
||||||
if (Deno.env.get("YT_PLAYER_REQ_LOCATION")) {
|
if (Deno.env.get("YT_PLAYER_REQ_LOCATION")) {
|
||||||
if (Deno.env.has("DENO_COMPILED")) {
|
if (Deno.env.has("DENO_COMPILED")) {
|
||||||
|
@ -134,6 +135,7 @@ export const youtubePlayerParsing = async (
|
||||||
expireIn: 1000 * 60 * 60,
|
expireIn: 1000 * 60 * 60,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
cachedEntries.inc()
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ import type { BgConfig } from "bgutils";
|
||||||
import { JSDOM } from "jsdom";
|
import { JSDOM } from "jsdom";
|
||||||
import { Innertube, UniversalCache } from "youtubei.js";
|
import { Innertube, UniversalCache } from "youtubei.js";
|
||||||
import { Store } from "@willsoto/node-konfig-core";
|
import { Store } from "@willsoto/node-konfig-core";
|
||||||
|
import { poTokenFail, externalTokenGeneratorFail } from "../../routes/index.ts";
|
||||||
let getFetchClientLocation = "getFetchClient";
|
let getFetchClientLocation = "getFetchClient";
|
||||||
if (Deno.env.get("GET_FETCH_CLIENT_LOCATION")) {
|
if (Deno.env.get("GET_FETCH_CLIENT_LOCATION")) {
|
||||||
if (Deno.env.has("DENO_COMPILED")) {
|
if (Deno.env.has("DENO_COMPILED")) {
|
||||||
|
@ -70,6 +71,7 @@ export const poTokenGenerate = async (
|
||||||
e,
|
e,
|
||||||
);
|
);
|
||||||
console.log("poTokenGenerate: Using built-in token generator");
|
console.log("poTokenGenerate: Using built-in token generator");
|
||||||
|
externalTokenGeneratorFail.inc()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,6 +102,7 @@ export const poTokenGenerate = async (
|
||||||
const bgChallenge = await BG.Challenge.create(bgConfig);
|
const bgChallenge = await BG.Challenge.create(bgConfig);
|
||||||
|
|
||||||
if (!bgChallenge) {
|
if (!bgChallenge) {
|
||||||
|
poTokenFail.inc()
|
||||||
throw new Error("Could not get challenge");
|
throw new Error("Could not get challenge");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,7 +111,10 @@ export const poTokenGenerate = async (
|
||||||
|
|
||||||
if (interpreterJavascript) {
|
if (interpreterJavascript) {
|
||||||
new Function(interpreterJavascript)();
|
new Function(interpreterJavascript)();
|
||||||
} else throw new Error("Could not load VM");
|
} else {
|
||||||
|
poTokenFail.inc()
|
||||||
|
throw new Error("Could not load VM");
|
||||||
|
}
|
||||||
|
|
||||||
const poTokenResult = await BG.PoToken.generate({
|
const poTokenResult = await BG.PoToken.generate({
|
||||||
program: bgChallenge.program,
|
program: bgChallenge.program,
|
||||||
|
|
|
@ -2,11 +2,58 @@ import { Hono } from "hono";
|
||||||
import { logger } from "hono/logger";
|
import { logger } from "hono/logger";
|
||||||
import { Store } from "@willsoto/node-konfig-core";
|
import { Store } from "@willsoto/node-konfig-core";
|
||||||
import { bearerAuth } from "hono/bearer-auth";
|
import { bearerAuth } from "hono/bearer-auth";
|
||||||
|
import { Registry, Counter } from "prom-client"
|
||||||
|
|
||||||
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";
|
import videoPlaybackProxy from "./videoPlaybackProxy.ts";
|
||||||
|
import metrics from "./metrics.ts";
|
||||||
|
|
||||||
|
const METRICS_PREFIX = "invidious_companion_";
|
||||||
|
export const register = new Registry();
|
||||||
|
|
||||||
|
export const externalTokenGeneratorFail = new Counter({
|
||||||
|
name: `${METRICS_PREFIX}externaltokengenerator_fail`,
|
||||||
|
help: 'TODO',
|
||||||
|
registers: [register]
|
||||||
|
});
|
||||||
|
|
||||||
|
export const cachedEntries = new Counter({
|
||||||
|
name: `${METRICS_PREFIX}cached_entries`,
|
||||||
|
help: 'TODO',
|
||||||
|
registers: [register]
|
||||||
|
});
|
||||||
|
|
||||||
|
export const subreasonProtectCommunity = new Counter({
|
||||||
|
name: `${METRICS_PREFIX}subreason_protect_community`,
|
||||||
|
help: 'TODO',
|
||||||
|
registers: [register]
|
||||||
|
});
|
||||||
|
|
||||||
|
export const poTokenFail = new Counter({
|
||||||
|
name: `${METRICS_PREFIX}potoken_fail`,
|
||||||
|
help: 'TODO',
|
||||||
|
registers: [register]
|
||||||
|
});
|
||||||
|
|
||||||
|
export const reasonBot = new Counter({
|
||||||
|
name: `${METRICS_PREFIX}reason_bot`,
|
||||||
|
help: 'TODO',
|
||||||
|
registers: [register]
|
||||||
|
});
|
||||||
|
|
||||||
|
export const videoUnavailable = new Counter({
|
||||||
|
name: `${METRICS_PREFIX}video_unavailable`,
|
||||||
|
help: 'TODO',
|
||||||
|
registers: [register]
|
||||||
|
});
|
||||||
|
|
||||||
|
export const videoRestricted = new Counter({
|
||||||
|
name: `${METRICS_PREFIX}video_restricted`,
|
||||||
|
help: 'TODO',
|
||||||
|
registers: [register]
|
||||||
|
});
|
||||||
|
|
||||||
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());
|
||||||
|
@ -22,4 +69,5 @@ export const routes = (app: Hono, konfigStore: Store<Record<string, unknown>>) =
|
||||||
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);
|
app.route("/videoplayback", videoPlaybackProxy);
|
||||||
|
app.route("/metrics", metrics)
|
||||||
};
|
};
|
||||||
|
|
12
src/routes/metrics.ts
Normal file
12
src/routes/metrics.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import { Hono } from "hono";
|
||||||
|
import { register } from "./index.ts";
|
||||||
|
|
||||||
|
const metrics = new Hono();
|
||||||
|
|
||||||
|
metrics.get("/", async () => {
|
||||||
|
return new Response(await register.metrics(), {
|
||||||
|
headers: { "Content-Type": "text/plain" },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
export default metrics;
|
|
@ -3,9 +3,33 @@ import { youtubePlayerParsing } from "../../lib/helpers/youtubePlayerHandling.ts
|
||||||
import { HonoVariables } from "../../lib/types/HonoVariables.ts";
|
import { HonoVariables } from "../../lib/types/HonoVariables.ts";
|
||||||
import { Innertube } from "youtubei.js";
|
import { Innertube } from "youtubei.js";
|
||||||
import { Store } from "@willsoto/node-konfig-core";
|
import { Store } from "@willsoto/node-konfig-core";
|
||||||
|
import { reasonBot, subreasonProtectCommunity, videoRestricted, videoUnavailable } from "../index.ts";
|
||||||
|
|
||||||
const player = new Hono<{ Variables: HonoVariables }>();
|
const player = new Hono<{ Variables: HonoVariables }>();
|
||||||
|
|
||||||
|
const errors = [
|
||||||
|
{
|
||||||
|
// @ts-ignore: Property 'playabilityStatus' does not exist on type 'object'
|
||||||
|
check: (yt: object) => yt.playabilityStatus?.reason?.includes("Sign in to confirm you’re not a bot"),
|
||||||
|
action: () => reasonBot.inc(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// @ts-ignore: Property 'playabilityStatus' does not exist on type 'object'
|
||||||
|
check: (yt: object) => yt.playabilityStatus?.errorScreen?.playerErrorMessageRenderer?.subreason?.runs?.[0]?.text?.includes("This helps protect our community"),
|
||||||
|
action: () => subreasonProtectCommunity.inc(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// @ts-ignore: Property 'playabilityStatus' does not exist on type 'object'
|
||||||
|
check: (yt: object) => yt.playabilityStatus?.errorScreen?.playerErrorMessageRenderer?.subreason?.runs?.[0]?.text?.includes("Video unavailable"),
|
||||||
|
action: () => videoUnavailable.inc(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// @ts-ignore: Property 'playabilityStatus' does not exist on type 'object'
|
||||||
|
check: (yt: object) => yt.playabilityStatus?.reason?.includes("This video is restricted"),
|
||||||
|
action: () => videoRestricted.inc(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
player.post("/player", async (c) => {
|
player.post("/player", async (c) => {
|
||||||
const jsonReq = await c.req.json();
|
const jsonReq = await c.req.json();
|
||||||
const innertubeClient = await c.get("innertubeClient") as Innertube;
|
const innertubeClient = await c.get("innertubeClient") as Innertube;
|
||||||
|
@ -14,9 +38,13 @@ player.post("/player", async (c) => {
|
||||||
Record<string, unknown>
|
Record<string, unknown>
|
||||||
>;
|
>;
|
||||||
if (jsonReq.videoId) {
|
if (jsonReq.videoId) {
|
||||||
return c.json(
|
const yt = await youtubePlayerParsing(innertubeClient, jsonReq.videoId, konfigStore)
|
||||||
await youtubePlayerParsing(innertubeClient, jsonReq.videoId, konfigStore)
|
errors.forEach((error) => {
|
||||||
);
|
if (error.check(yt)) {
|
||||||
|
error.action()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return c.json(yt);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue