Improvements to PO token generation and caching
This commit is contained in:
Generated
-1
@@ -1,4 +1,3 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "Materialious",
|
||||
"version": "1.8.1",
|
||||
"version": "1.8.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "Materialious",
|
||||
"version": "1.8.1",
|
||||
"version": "1.8.2",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@capacitor-community/electron": "^5.0.0",
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import type { CapacitorElectronConfig } from '@capacitor-community/electron';
|
||||
import { getCapacitorElectronConfig, setupElectronDeepLinking } from '@capacitor-community/electron';
|
||||
import {
|
||||
getCapacitorElectronConfig,
|
||||
setupElectronDeepLinking
|
||||
} from '@capacitor-community/electron';
|
||||
import type { MenuItemConstructorOptions } from 'electron';
|
||||
import { app, ipcMain, MenuItem } from 'electron';
|
||||
import electronIsDev from 'electron-is-dev';
|
||||
@@ -7,17 +10,25 @@ import unhandled from 'electron-unhandled';
|
||||
import { autoUpdater } from 'electron-updater';
|
||||
import { JSDOM } from 'jsdom';
|
||||
|
||||
import BG, { buildURL, GOOG_API_KEY, USER_AGENT, WebPoSignalOutput } from 'bgutils-js';
|
||||
import BG, {
|
||||
buildURL,
|
||||
DescrambledChallenge,
|
||||
GOOG_API_KEY,
|
||||
USER_AGENT,
|
||||
WebPoSignalOutput
|
||||
} from 'bgutils-js';
|
||||
import { ElectronCapacitorApp, setupContentSecurityPolicy, setupReloadWatcher } from './setup';
|
||||
|
||||
// Graceful handling of unhandled errors.
|
||||
unhandled();
|
||||
|
||||
// Define our menu templates (these are optional)
|
||||
const trayMenuTemplate: (MenuItemConstructorOptions | MenuItem)[] = [new MenuItem({ label: 'Quit App', role: 'quit' })];
|
||||
const trayMenuTemplate: (MenuItemConstructorOptions | MenuItem)[] = [
|
||||
new MenuItem({ label: 'Quit App', role: 'quit' })
|
||||
];
|
||||
const appMenuBarMenuTemplate: (MenuItemConstructorOptions | MenuItem)[] = [
|
||||
{ role: process.platform === 'darwin' ? 'appMenu' : 'fileMenu' },
|
||||
{ role: 'viewMenu' },
|
||||
{ role: process.platform === 'darwin' ? 'appMenu' : 'fileMenu' },
|
||||
{ role: 'viewMenu' }
|
||||
];
|
||||
|
||||
// Get Config options from capacitor.config
|
||||
@@ -25,116 +36,121 @@ const capacitorFileConfig: CapacitorElectronConfig = getCapacitorElectronConfig(
|
||||
|
||||
// Initialize our app. You can pass menu templates into the app here.
|
||||
// const myCapacitorApp = new ElectronCapacitorApp(capacitorFileConfig);
|
||||
const myCapacitorApp = new ElectronCapacitorApp(capacitorFileConfig, trayMenuTemplate, appMenuBarMenuTemplate);
|
||||
const myCapacitorApp = new ElectronCapacitorApp(
|
||||
capacitorFileConfig,
|
||||
trayMenuTemplate,
|
||||
appMenuBarMenuTemplate
|
||||
);
|
||||
|
||||
// If deeplinking is enabled then we will set it up here.
|
||||
if (capacitorFileConfig.electron?.deepLinkingEnabled) {
|
||||
setupElectronDeepLinking(myCapacitorApp, {
|
||||
customProtocol: capacitorFileConfig.electron.deepLinkingCustomProtocol ?? 'mycapacitorapp',
|
||||
});
|
||||
setupElectronDeepLinking(myCapacitorApp, {
|
||||
customProtocol: capacitorFileConfig.electron.deepLinkingCustomProtocol ?? 'mycapacitorapp'
|
||||
});
|
||||
}
|
||||
|
||||
// If we are in Dev mode, use the file watcher components.
|
||||
if (electronIsDev) {
|
||||
setupReloadWatcher(myCapacitorApp);
|
||||
setupReloadWatcher(myCapacitorApp);
|
||||
}
|
||||
|
||||
// Run Application
|
||||
(async () => {
|
||||
// Wait for electron app to be ready.
|
||||
await app.whenReady();
|
||||
// Security - Set Content-Security-Policy based on whether or not we are in dev mode.
|
||||
setupContentSecurityPolicy(myCapacitorApp.getCustomURLScheme());
|
||||
// Initialize our app, build windows, and load content.
|
||||
await myCapacitorApp.init();
|
||||
// Check for updates if we are in a packaged app.
|
||||
autoUpdater.checkForUpdatesAndNotify();
|
||||
// Wait for electron app to be ready.
|
||||
await app.whenReady();
|
||||
// Security - Set Content-Security-Policy based on whether or not we are in dev mode.
|
||||
setupContentSecurityPolicy(myCapacitorApp.getCustomURLScheme());
|
||||
// Initialize our app, build windows, and load content.
|
||||
await myCapacitorApp.init();
|
||||
// Check for updates if we are in a packaged app.
|
||||
autoUpdater.checkForUpdatesAndNotify();
|
||||
})();
|
||||
|
||||
// Handle when all of our windows are close (platforms have their own expectations).
|
||||
app.on('window-all-closed', function () {
|
||||
// On OS X it is common for applications and their menu bar
|
||||
// to stay active until the user quits explicitly with Cmd + Q
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit();
|
||||
}
|
||||
// On OS X it is common for applications and their menu bar
|
||||
// to stay active until the user quits explicitly with Cmd + Q
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit();
|
||||
}
|
||||
});
|
||||
|
||||
// When the dock icon is clicked.
|
||||
app.on('activate', async function () {
|
||||
// On OS X it's common to re-create a window in the app when the
|
||||
// dock icon is clicked and there are no other windows open.
|
||||
if (myCapacitorApp.getMainWindow().isDestroyed()) {
|
||||
await myCapacitorApp.init();
|
||||
}
|
||||
// On OS X it's common to re-create a window in the app when the
|
||||
// dock icon is clicked and there are no other windows open.
|
||||
if (myCapacitorApp.getMainWindow().isDestroyed()) {
|
||||
await myCapacitorApp.init();
|
||||
}
|
||||
});
|
||||
|
||||
// Place all ipc or other electron api calls and custom functionality under this line
|
||||
ipcMain.handle('generatePoToken', async (
|
||||
_,
|
||||
challengeResponse: any,
|
||||
requestKey: string,
|
||||
visitorData: string,
|
||||
videoId: string
|
||||
) => {
|
||||
if (!challengeResponse.bg_challenge)
|
||||
throw new Error('Yt.js: Could not get challenge');
|
||||
ipcMain.handle(
|
||||
'generatePoToken',
|
||||
async (_, bgChallenge: any, requestKey: string, visitorData: string) => {
|
||||
const youtubeUrl = 'https://www.youtube.com/';
|
||||
|
||||
const dom = new JSDOM('<!DOCTYPE html><html lang="en"><head><title></title></head><body></body></html>', {
|
||||
url: 'https://www.youtube.com/',
|
||||
referrer: 'https://www.youtube.com/',
|
||||
USER_AGENT
|
||||
});
|
||||
const dom = new JSDOM(
|
||||
'<!DOCTYPE html><html lang="en"><head><title></title></head><body></body></html>',
|
||||
{
|
||||
url: youtubeUrl,
|
||||
referrer: youtubeUrl,
|
||||
origin: youtubeUrl,
|
||||
USER_AGENT
|
||||
}
|
||||
);
|
||||
|
||||
Object.assign(globalThis, {
|
||||
window: dom.window,
|
||||
document: dom.window.document,
|
||||
location: dom.window.location,
|
||||
origin: dom.window.origin
|
||||
});
|
||||
Object.assign(globalThis, {
|
||||
window: dom.window,
|
||||
document: dom.window.document,
|
||||
location: dom.window.location,
|
||||
origin: dom.window.origin
|
||||
});
|
||||
|
||||
if (!Reflect.has(globalThis, 'navigator')) {
|
||||
Object.defineProperty(globalThis, 'navigator', { value: dom.window.navigator });
|
||||
}
|
||||
if (!Reflect.has(globalThis, 'navigator')) {
|
||||
Object.defineProperty(globalThis, 'navigator', { value: dom.window.navigator });
|
||||
}
|
||||
|
||||
const interpreterUrl = challengeResponse.bg_challenge.interpreter_url.private_do_not_access_or_else_trusted_resource_url_wrapped_value;
|
||||
const bgScriptResponse = await fetch(`https:${interpreterUrl}`);
|
||||
const interpreterJavascript = await bgScriptResponse.text();
|
||||
const interpreterUrl =
|
||||
bgChallenge.interpreter_url.private_do_not_access_or_else_trusted_resource_url_wrapped_value;
|
||||
const bgScriptResponse = await fetch(`https:${interpreterUrl}`);
|
||||
const interpreterJavascript = await bgScriptResponse.text();
|
||||
|
||||
if (interpreterJavascript) {
|
||||
new Function(interpreterJavascript)();
|
||||
} else throw new Error('Could not load VM');
|
||||
if (interpreterJavascript) {
|
||||
new Function(interpreterJavascript)();
|
||||
} else throw new Error('Could not load VM');
|
||||
|
||||
const botguardClient = await BG.BotGuardClient.create({
|
||||
program: challengeResponse.bg_challenge.program,
|
||||
globalName: challengeResponse.bg_challenge.global_name,
|
||||
globalObj: globalThis
|
||||
});
|
||||
const botguardClient = await BG.BotGuardClient.create({
|
||||
program: bgChallenge.program,
|
||||
globalName: bgChallenge.global_name,
|
||||
globalObj: globalThis
|
||||
});
|
||||
|
||||
const webPoSignalOutput: WebPoSignalOutput = [];
|
||||
const botguardResponse = await botguardClient.snapshot({ webPoSignalOutput });
|
||||
const webPoSignalOutput: WebPoSignalOutput = [];
|
||||
const botguardResponse = await botguardClient.snapshot({ webPoSignalOutput });
|
||||
|
||||
const integrityTokenResponse = await fetch(buildURL('GenerateIT', true), {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'content-type': 'application/json+protobuf',
|
||||
'x-goog-api-key': GOOG_API_KEY,
|
||||
'x-user-agent': 'grpc-web-javascript/0.1',
|
||||
},
|
||||
body: JSON.stringify([requestKey, botguardResponse])
|
||||
});
|
||||
const integrityTokenResponse = await fetch(buildURL('GenerateIT', true), {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'content-type': 'application/json+protobuf',
|
||||
'x-goog-api-key': GOOG_API_KEY,
|
||||
'x-user-agent': 'grpc-web-javascript/0.1'
|
||||
},
|
||||
body: JSON.stringify([requestKey, botguardResponse])
|
||||
});
|
||||
|
||||
const integrityTokenResponseData = await integrityTokenResponse.json();
|
||||
const integrityTokenResponseData = await integrityTokenResponse.json();
|
||||
|
||||
if (typeof integrityTokenResponseData[0] !== 'string')
|
||||
throw new Error('Yt.js: Could not get integrity token');
|
||||
if (typeof integrityTokenResponseData[0] !== 'string')
|
||||
throw new Error('Could not get integrity token');
|
||||
|
||||
const integrityToken = integrityTokenResponseData[0];
|
||||
const integrityToken = integrityTokenResponseData[0];
|
||||
|
||||
const integrityTokenBasedMinter = await BG.WebPoMinter.create({ integrityToken }, webPoSignalOutput);
|
||||
const integrityTokenBasedMinter = await BG.WebPoMinter.create(
|
||||
{ integrityToken },
|
||||
webPoSignalOutput
|
||||
);
|
||||
|
||||
return [
|
||||
await integrityTokenBasedMinter.mintAsWebsafeString(visitorData),
|
||||
await integrityTokenBasedMinter.mintAsWebsafeString(videoId)
|
||||
];
|
||||
});
|
||||
return await integrityTokenBasedMinter.mintAsWebsafeString(decodeURIComponent(visitorData));
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
import { DescrambledChallenge } from 'bgutils-js';
|
||||
|
||||
const { contextBridge, ipcRenderer } = require('electron');
|
||||
require('./rt/electron-rt');
|
||||
|
||||
contextBridge.exposeInMainWorld('electronAPI', {
|
||||
generatePoToken: (challengeResponse: any,
|
||||
requestKey: string,
|
||||
visitorData: string,
|
||||
videoId: string) => ipcRenderer.invoke(
|
||||
'generatePoToken',
|
||||
challengeResponse,
|
||||
requestKey,
|
||||
visitorData,
|
||||
videoId
|
||||
)
|
||||
});
|
||||
generatePoToken: (bgChallenge: DescrambledChallenge, requestKey: string, visitorData: string) =>
|
||||
ipcRenderer.invoke('generatePoToken', bgChallenge, requestKey, visitorData)
|
||||
});
|
||||
|
||||
Vendored
+5
-7
@@ -1,6 +1,6 @@
|
||||
// See https://kit.svelte.dev/docs/types#app
|
||||
|
||||
import type { IGetChallengeResponse } from "youtubei.js";
|
||||
import type { IGetChallengeResponse } from 'youtubei.js';
|
||||
|
||||
// for information about these interfaces
|
||||
declare global {
|
||||
@@ -14,14 +14,12 @@ declare global {
|
||||
interface Window {
|
||||
electronAPI: {
|
||||
generatePoToken: (
|
||||
challengeResponse: IGetChallengeResponse,
|
||||
bgChallenge: IGetChallengeResponse,
|
||||
requestKey: string,
|
||||
visitorData: string,
|
||||
videoId: string
|
||||
) => Promise<[string, string]>;
|
||||
visitorData: string
|
||||
) => Promise<string>;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export { };
|
||||
|
||||
export {};
|
||||
|
||||
@@ -1,53 +1,50 @@
|
||||
import { BG, buildURL, GOOG_API_KEY, type WebPoSignalOutput } from 'bgutils-js';
|
||||
import { type IGetChallengeResponse } from 'youtubei.js';
|
||||
import type { IBotguardChallenge } from 'youtubei.js';
|
||||
|
||||
export async function androidPoTokenMinter(
|
||||
challengeResponse: IGetChallengeResponse,
|
||||
requestKey: string,
|
||||
visitorData: string,
|
||||
videoId: string
|
||||
): Promise<[string, string]> {
|
||||
if (!challengeResponse.bg_challenge)
|
||||
throw new Error('Yt.js: Could not get challenge');
|
||||
bgChallenge: IBotguardChallenge,
|
||||
requestKey: string,
|
||||
visitorData: string
|
||||
): Promise<string> {
|
||||
const interpreterUrl =
|
||||
bgChallenge.interpreter_url.private_do_not_access_or_else_trusted_resource_url_wrapped_value;
|
||||
const bgScriptResponse = await fetch(`https:${interpreterUrl}`);
|
||||
const interpreterJavascript = await bgScriptResponse.text();
|
||||
|
||||
const interpreterUrl = challengeResponse.bg_challenge.interpreter_url.private_do_not_access_or_else_trusted_resource_url_wrapped_value;
|
||||
const bgScriptResponse = await fetch(`https:${interpreterUrl}`);
|
||||
const interpreterJavascript = await bgScriptResponse.text();
|
||||
if (interpreterJavascript) {
|
||||
new Function(interpreterJavascript)();
|
||||
} else throw new Error('Could not load VM');
|
||||
|
||||
if (interpreterJavascript) {
|
||||
new Function(interpreterJavascript)();
|
||||
} else throw new Error('Could not load VM');
|
||||
const botguardClient = await BG.BotGuardClient.create({
|
||||
program: bgChallenge.program,
|
||||
globalName: bgChallenge.global_name,
|
||||
globalObj: globalThis
|
||||
});
|
||||
|
||||
const botguardClient = await BG.BotGuardClient.create({
|
||||
program: challengeResponse.bg_challenge.program,
|
||||
globalName: challengeResponse.bg_challenge.global_name,
|
||||
globalObj: globalThis
|
||||
});
|
||||
const webPoSignalOutput: WebPoSignalOutput = [];
|
||||
const botguardResponse = await botguardClient.snapshot({ webPoSignalOutput });
|
||||
|
||||
const webPoSignalOutput: WebPoSignalOutput = [];
|
||||
const botguardResponse = await botguardClient.snapshot({ webPoSignalOutput });
|
||||
const integrityTokenResponse = await fetch(buildURL('GenerateIT', true), {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'content-type': 'application/json+protobuf',
|
||||
'x-goog-api-key': GOOG_API_KEY,
|
||||
'x-user-agent': 'grpc-web-javascript/0.1'
|
||||
},
|
||||
body: JSON.stringify([requestKey, botguardResponse])
|
||||
});
|
||||
|
||||
const integrityTokenResponse = await fetch(buildURL('GenerateIT', true), {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'content-type': 'application/json+protobuf',
|
||||
'x-goog-api-key': GOOG_API_KEY,
|
||||
'x-user-agent': 'grpc-web-javascript/0.1',
|
||||
},
|
||||
body: JSON.stringify([requestKey, botguardResponse])
|
||||
});
|
||||
const integrityTokenResponseData = await integrityTokenResponse.json();
|
||||
|
||||
const integrityTokenResponseData = await integrityTokenResponse.json();
|
||||
if (typeof integrityTokenResponseData[0] !== 'string')
|
||||
throw new Error('Could not get integrity token');
|
||||
|
||||
if (typeof integrityTokenResponseData[0] !== 'string')
|
||||
throw new Error('Yt.js: Could not get integrity token');
|
||||
const integrityToken = integrityTokenResponseData[0];
|
||||
|
||||
const integrityToken = integrityTokenResponseData[0];
|
||||
const integrityTokenBasedMinter = await BG.WebPoMinter.create(
|
||||
{ integrityToken },
|
||||
webPoSignalOutput
|
||||
);
|
||||
|
||||
const integrityTokenBasedMinter = await BG.WebPoMinter.create({ integrityToken }, webPoSignalOutput);
|
||||
|
||||
return [
|
||||
await integrityTokenBasedMinter.mintAsWebsafeString(visitorData),
|
||||
await integrityTokenBasedMinter.mintAsWebsafeString(videoId)
|
||||
];
|
||||
}
|
||||
return await integrityTokenBasedMinter.mintAsWebsafeString(decodeURIComponent(visitorData));
|
||||
}
|
||||
|
||||
@@ -451,8 +451,6 @@
|
||||
if (videoFormatId) videoPlaybackAbrRequest.selectedFormatIds.push(videoFormatId);
|
||||
}
|
||||
|
||||
console.log(videoPlaybackAbrRequest);
|
||||
|
||||
request.body = Protos.VideoPlaybackAbrRequest.encode(videoPlaybackAbrRequest).finish();
|
||||
|
||||
const byteRange = headers.Range
|
||||
|
||||
@@ -1,39 +1,40 @@
|
||||
import { _ } from 'svelte-i18n';
|
||||
import { get } from 'svelte/store';
|
||||
|
||||
|
||||
// Must be a func do to how i18n is loaded
|
||||
export function getPages(): { icon: string, href: string, name: string, requiresAuth: boolean; }[] {
|
||||
return [
|
||||
{
|
||||
icon: 'home',
|
||||
href: '/',
|
||||
name: get(_)('pages.home'),
|
||||
requiresAuth: false
|
||||
},
|
||||
{
|
||||
icon: 'whatshot',
|
||||
href: '/trending',
|
||||
name: get(_)('pages.trending'),
|
||||
requiresAuth: false
|
||||
},
|
||||
{
|
||||
icon: 'subscriptions',
|
||||
href: '/subscriptions',
|
||||
name: get(_)('pages.subscriptions'),
|
||||
requiresAuth: true
|
||||
},
|
||||
{
|
||||
icon: 'video_library',
|
||||
href: '/playlists',
|
||||
name: get(_)('pages.playlists'),
|
||||
requiresAuth: true
|
||||
},
|
||||
{
|
||||
icon: 'history',
|
||||
href: '/history',
|
||||
name: get(_)('pages.history'),
|
||||
requiresAuth: true
|
||||
}
|
||||
];
|
||||
}
|
||||
export function getPages(): { icon: string; href: string; name: string; requiresAuth: boolean }[] {
|
||||
return [
|
||||
{
|
||||
icon: 'home',
|
||||
href: '/',
|
||||
name: get(_)('pages.home'),
|
||||
requiresAuth: false
|
||||
},
|
||||
{
|
||||
icon: 'whatshot',
|
||||
href: '/trending',
|
||||
name: get(_)('pages.trending'),
|
||||
requiresAuth: false
|
||||
},
|
||||
{
|
||||
icon: 'subscriptions',
|
||||
href: '/subscriptions',
|
||||
name: get(_)('pages.subscriptions'),
|
||||
requiresAuth: true
|
||||
},
|
||||
{
|
||||
icon: 'video_library',
|
||||
href: '/playlists',
|
||||
name: get(_)('pages.playlists'),
|
||||
requiresAuth: true
|
||||
}
|
||||
// Temporarily disable history page due to limitations with
|
||||
// Invidious's API
|
||||
// {
|
||||
// icon: 'history',
|
||||
// href: '/history',
|
||||
// name: get(_)('pages.history'),
|
||||
// requiresAuth: true
|
||||
// }
|
||||
];
|
||||
}
|
||||
|
||||
@@ -29,30 +29,24 @@ export async function patchYoutubeJs(videoId: string): Promise<VideoPlay> {
|
||||
enable_session_cache: false
|
||||
});
|
||||
|
||||
let sessionPoToken: string;
|
||||
let contentPoToken: string;
|
||||
if (!get(poTokenCacheStore)) {
|
||||
const visitorData = youtube.session.context.client.visitorData ?? '';
|
||||
|
||||
const requestKey = 'O43z0dpjhgX20SCx4KAo';
|
||||
const challengeResponse = await youtube.getAttestationChallenge('ENGAGEMENT_TYPE_UNBOUND');
|
||||
const requestKey = 'O43z0dpjhgX20SCx4KAo';
|
||||
const challengeResponse = await youtube.getAttestationChallenge('ENGAGEMENT_TYPE_UNBOUND');
|
||||
|
||||
if (Capacitor.getPlatform() === 'android') {
|
||||
[sessionPoToken, contentPoToken] = await androidPoTokenMinter(
|
||||
challengeResponse,
|
||||
requestKey,
|
||||
youtube.session.context.client.visitorData ?? '',
|
||||
videoId
|
||||
);
|
||||
} else {
|
||||
[sessionPoToken, contentPoToken] = await window.electronAPI.generatePoToken(
|
||||
challengeResponse,
|
||||
requestKey,
|
||||
youtube.session.context.client.visitorData ?? '',
|
||||
videoId
|
||||
if (!challengeResponse.bg_challenge) throw new Error('Could not get challenge');
|
||||
|
||||
const platformMinter =
|
||||
Capacitor.getPlatform() === 'android'
|
||||
? androidPoTokenMinter
|
||||
: window.electronAPI.generatePoToken;
|
||||
|
||||
poTokenCacheStore.set(
|
||||
await platformMinter(challengeResponse.bg_challenge, requestKey, visitorData)
|
||||
);
|
||||
}
|
||||
|
||||
poTokenCacheStore.set(sessionPoToken);
|
||||
|
||||
const extraArgs: Record<string, any> = {
|
||||
playbackContext: {
|
||||
contentPlaybackContext: {
|
||||
|
||||
Reference in New Issue
Block a user