Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

youtube: add support for OAuth2 tokens #558

Merged
merged 8 commits into from
Jun 8, 2024
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@
"scripts": {
"start": "node src/cobalt",
"setup": "node src/modules/setup",
"test": "node src/test/test",
"test": "node src/util/test",
"build": "node src/modules/buildStatic",
"testFilenames": "node src/test/testFilenamePresets"
"token:youtube": "node src/util/generate-youtube-tokens"
},
wukko marked this conversation as resolved.
Show resolved Hide resolved
"repository": {
"type": "git",
Expand Down
47 changes: 44 additions & 3 deletions src/modules/processing/services/youtube.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Innertube, Session } from 'youtubei.js';
import { env } from '../../config.js';
import { cleanString } from '../../sub/utils.js';
import { fetch } from 'undici'
import { getCookie } from '../cookie/manager.js'
import { getCookie, updateCookieValues } from '../cookie/manager.js'

const ytBase = Innertube.create().catch(e => e);

Expand All @@ -24,6 +24,26 @@ const codecMatch = {
}
}

const transformSessionData = (cookie) => {
if (!cookie)
return;

const values = cookie.values();
const REQUIRED_VALUES = [
'access_token', 'refresh_token',
'client_id', 'client_secret',
'expires'
];

if (REQUIRED_VALUES.some(x => typeof values[x] !== 'string')) {
return;
}
return {
...values,
expires: new Date(values.expires),
};
}

const cloneInnertube = async (customFetch) => {
const innertube = await ytBase;
if (innertube instanceof Error) {
Expand All @@ -36,11 +56,32 @@ const cloneInnertube = async (customFetch) => {
innertube.session.api_version,
innertube.session.account_index,
innertube.session.player,
getCookie('youtube')?.toString(),
undefined,
customFetch ?? innertube.session.http.fetch,
innertube.session.cache
);

const cookie = getCookie('youtube_oauth');
const oauthData = transformSessionData(cookie);

if (!session.logged_in && oauthData) {
await session.oauth.init(oauthData);
session.logged_in = true;
}

if (session.logged_in) {
await session.oauth.refreshIfRequired();
const oldExpiry = new Date(cookie.values().expires);
const newExpiry = session.oauth.credentials.expires;

if (oldExpiry.getTime() !== newExpiry.getTime()) {
updateCookieValues(cookie, {
...session.oauth.credentials,
expires: session.oauth.credentials.expires.toISOString()
});
}
}

const yt = new Innertube(session);
return yt;
}
Expand All @@ -62,7 +103,7 @@ export default async function(o) {
}

try {
info = await yt.getBasicInfo(o.id, 'IOS');
info = await yt.getBasicInfo(o.id, yt.session.logged_in ? 'ANDROID' : 'IOS');
} catch(e) {
if (e?.message === 'This video is unavailable') {
return { error: 'ErrorCouldntFetch' };
Expand Down
38 changes: 38 additions & 0 deletions src/util/generate-youtube-tokens.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Innertube } from 'youtubei.js';
import { Red } from '../modules/sub/consoleText.js'

const bail = (...msg) => {
console.error(...msg);
throw new Error(msg);
};

const tube = await Innertube.create();

tube.session.once(
'auth-pending',
({ verification_url, user_code }) => {
console.log(`${Red('[!]')} The token generated by this script is sensitive and you should not share it with anyone!`);
console.log(` By using this token, you are risking your Google account getting terminated.`);
console.log(` You should ${Red('NOT')} use your personal account!`);
console.log();
console.log(`Open ${verification_url} in a browser and enter ${user_code} when asked for the code.`);
}
);

tube.session.once('auth-error', (err) => bail('An error occurred:', err));
tube.session.once('auth', ({ status, credentials, ...rest }) => {
if (status !== 'SUCCESS') {
bail('something went wrong', rest);
}

console.log(
'add this cookie to the youtube_oauth array in your cookies file:',
JSON.stringify(
Object.entries(credentials)
.map(([k, v]) => `${k}=${v instanceof Date ? v.toISOString() : v}`)
.join('; ')
)
);
});

await tube.session.signIn();
2 changes: 1 addition & 1 deletion src/test/test.js → src/util/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { normalizeRequest } from "../modules/processing/request.js";
import { env } from "../modules/config.js";

env.apiURL = 'http://localhost:9000'
let tests = loadJSON('./src/test/tests.json');
let tests = loadJSON('./src/util/tests.json');

let noTest = [];
let failed = [];
Expand Down
File renamed without changes.
File renamed without changes.