mirror of
https://github.com/wukko/cobalt.git
synced 2025-03-25 18:54:53 +01:00
api: automatically pull youtube session tokens from a session server
if provided, cobalt will pull poToken & visitor_data from an instance of invidious' youtube-trusted-session-generator or its counterpart
This commit is contained in:
parent
a940eb13fd
commit
b6cd0ad727
4 changed files with 97 additions and 5 deletions
|
@ -53,7 +53,10 @@ const env = {
|
|||
keyReloadInterval: 900,
|
||||
|
||||
enabledServices,
|
||||
|
||||
customInnertubeClient: process.env.CUSTOM_INNERTUBE_CLIENT,
|
||||
ytSessionServer: process.env.YOUTUBE_SESSION_SERVER,
|
||||
ytSessionReloadInterval: 300,
|
||||
}
|
||||
|
||||
const genericUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36";
|
||||
|
|
|
@ -18,8 +18,10 @@ import { verifyTurnstileToken } from "../security/turnstile.js";
|
|||
import { friendlyServiceName } from "../processing/service-alias.js";
|
||||
import { verifyStream, getInternalStream } from "../stream/manage.js";
|
||||
import { createResponse, normalizeRequest, getIP } from "../processing/request.js";
|
||||
|
||||
import * as APIKeys from "../security/api-keys.js";
|
||||
import * as Cookies from "../processing/cookie/manager.js";
|
||||
import * as YouTubeSession from "../processing/helpers/youtube-session.js";
|
||||
|
||||
const git = {
|
||||
branch: await getBranch(),
|
||||
|
@ -376,6 +378,10 @@ export const runAPI = async (express, app, __dirname, isPrimary = true) => {
|
|||
if (env.cookiePath) {
|
||||
Cookies.setup(env.cookiePath);
|
||||
}
|
||||
|
||||
if (env.ytSessionServer) {
|
||||
YouTubeSession.setup();
|
||||
}
|
||||
});
|
||||
|
||||
if (isCluster) {
|
||||
|
|
74
api/src/processing/helpers/youtube-session.js
Normal file
74
api/src/processing/helpers/youtube-session.js
Normal file
|
@ -0,0 +1,74 @@
|
|||
import * as cluster from "../../misc/cluster.js";
|
||||
|
||||
import { env } from "../../config.js";
|
||||
import { Green, Yellow } from "../../misc/console-text.js";
|
||||
|
||||
let session;
|
||||
|
||||
const validateSession = (sessionResponse) => {
|
||||
if (!sessionResponse.potoken) {
|
||||
throw "no poToken in session response";
|
||||
}
|
||||
|
||||
if (!sessionResponse.visitor_data) {
|
||||
throw "no visitor_data in session response";
|
||||
}
|
||||
|
||||
if (!sessionResponse.updated) {
|
||||
throw "no last update timestamp in session response";
|
||||
}
|
||||
|
||||
// https://github.com/iv-org/youtube-trusted-session-generator/blob/c2dfe3f/potoken_generator/main.py#L25
|
||||
if (sessionResponse.potoken.length < 160) {
|
||||
console.error(`${Yellow('[!]')} poToken is too short and might not work (${new Date().toISOString()})`);
|
||||
}
|
||||
}
|
||||
|
||||
const updateSession = (newSession) => {
|
||||
session = newSession;
|
||||
}
|
||||
|
||||
const loadSession = async () => {
|
||||
const sessionServerUrl = new URL(env.ytSessionServer);
|
||||
sessionServerUrl.pathname = "/token";
|
||||
|
||||
const newSession = await fetch(sessionServerUrl).then(a => a.json());
|
||||
validateSession(newSession);
|
||||
|
||||
if (!session || session.updated < newSession?.updated) {
|
||||
cluster.broadcast({ youtube_session: newSession });
|
||||
updateSession(newSession);
|
||||
}
|
||||
}
|
||||
|
||||
const wrapLoad = (initial = false) => {
|
||||
loadSession()
|
||||
.then(() => {
|
||||
if (initial) {
|
||||
console.log(`${Green('[✓]')} poToken & visitor_data loaded successfully!`);
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(`${Yellow('[!]')} Failed loading poToken & visitor_data at ${new Date().toISOString()}.`);
|
||||
console.error('Error:', e);
|
||||
})
|
||||
}
|
||||
|
||||
export const getYouTubeSession = () => {
|
||||
return session;
|
||||
}
|
||||
|
||||
export const setup = () => {
|
||||
if (cluster.isPrimary) {
|
||||
wrapLoad(true);
|
||||
if (env.ytSessionReloadInterval > 0) {
|
||||
setInterval(wrapLoad, env.ytSessionReloadInterval * 1000);
|
||||
}
|
||||
} else if (cluster.isWorker) {
|
||||
process.on('message', (message) => {
|
||||
if ('youtube_session' in message) {
|
||||
updateSession(message.youtube_session);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ import { fetch } from "undici";
|
|||
import { Innertube, Session } from "youtubei.js";
|
||||
|
||||
import { env } from "../../config.js";
|
||||
import { getYouTubeSession } from "../helpers/youtube-session.js";
|
||||
import { getCookie, updateCookieValues } from "../cookie/manager.js";
|
||||
|
||||
const PLAYER_REFRESH_PERIOD = 1000 * 60 * 15; // ms
|
||||
|
@ -70,16 +71,22 @@ const cloneInnertube = async (customFetch) => {
|
|||
const shouldRefreshPlayer = lastRefreshedAt + PLAYER_REFRESH_PERIOD < new Date();
|
||||
|
||||
const rawCookie = getCookie('youtube');
|
||||
const rawCookieValues = rawCookie?.values();
|
||||
const cookie = rawCookie?.toString();
|
||||
|
||||
const sessionTokens = getYouTubeSession();
|
||||
const retrieve_player = Boolean(sessionTokens || cookie);
|
||||
|
||||
if (env.ytSessionServer && !sessionTokens?.potoken) {
|
||||
throw "no_session_tokens";
|
||||
}
|
||||
|
||||
if (!innertube || shouldRefreshPlayer) {
|
||||
innertube = await Innertube.create({
|
||||
fetch: customFetch,
|
||||
retrieve_player: !!cookie,
|
||||
retrieve_player,
|
||||
cookie,
|
||||
po_token: rawCookieValues?.po_token,
|
||||
visitor_data: rawCookieValues?.visitor_data,
|
||||
po_token: sessionTokens?.potoken,
|
||||
visitor_data: sessionTokens?.visitor_data,
|
||||
});
|
||||
lastRefreshedAt = +new Date();
|
||||
}
|
||||
|
@ -135,7 +142,9 @@ export default async function (o) {
|
|||
})
|
||||
);
|
||||
} catch (e) {
|
||||
if (e.message?.endsWith("decipher algorithm")) {
|
||||
if (e === "no_session_tokens") {
|
||||
return { error: "youtube.no_session_tokens" };
|
||||
} else if (e.message?.endsWith("decipher algorithm")) {
|
||||
return { error: "youtube.decipher" }
|
||||
} else if (e.message?.includes("refresh access token")) {
|
||||
return { error: "youtube.token_expired" }
|
||||
|
|
Loading…
Add table
Reference in a new issue