diff --git a/package.json b/package.json index 72104aba..7d271fea 100644 --- a/package.json +++ b/package.json @@ -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" }, "repository": { "type": "git", diff --git a/src/modules/processing/services/youtube.js b/src/modules/processing/services/youtube.js index 39a94f3a..49de029b 100644 --- a/src/modules/processing/services/youtube.js +++ b/src/modules/processing/services/youtube.js @@ -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); @@ -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) { @@ -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; } @@ -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' }; diff --git a/src/util/generate-youtube-tokens.js b/src/util/generate-youtube-tokens.js new file mode 100644 index 00000000..924afb13 --- /dev/null +++ b/src/util/generate-youtube-tokens.js @@ -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(); \ No newline at end of file diff --git a/src/test/test.js b/src/util/test.js similarity index 98% rename from src/test/test.js rename to src/util/test.js index f26f3b95..294ff7e7 100644 --- a/src/test/test.js +++ b/src/util/test.js @@ -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 = []; diff --git a/src/test/testFilenamePresets.js b/src/util/testFilenamePresets.js similarity index 100% rename from src/test/testFilenamePresets.js rename to src/util/testFilenamePresets.js diff --git a/src/test/tests.json b/src/util/tests.json similarity index 100% rename from src/test/tests.json rename to src/util/tests.json