diff --git a/materialious/android/app/build.gradle b/materialious/android/app/build.gradle index dd730e40..0c107a5c 100644 --- a/materialious/android/app/build.gradle +++ b/materialious/android/app/build.gradle @@ -7,8 +7,8 @@ android { applicationId "us.materialio.app" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 90 - versionName "1.7.8" + versionCode 91 + versionName "1.7.9" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" aaptOptions { // Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps. diff --git a/materialious/electron/package.json b/materialious/electron/package.json index 9a537042..da815481 100644 --- a/materialious/electron/package.json +++ b/materialious/electron/package.json @@ -1,6 +1,6 @@ { "name": "Materialious", - "version": "1.7.8", + "version": "1.7.9", "description": "Modern material design for Invidious.", "author": { "name": "Ward Pearce", diff --git a/materialious/package-lock.json b/materialious/package-lock.json index 7870e299..82011ffc 100644 --- a/materialious/package-lock.json +++ b/materialious/package-lock.json @@ -1,12 +1,12 @@ { "name": "materialious", - "version": "1.7.6", + "version": "1.7.9", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "materialious", - "version": "1.7.6", + "version": "1.7.9", "dependencies": { "@capacitor-community/electron": "^5.0.1", "@capacitor/android": "^6.1.2", @@ -21,7 +21,7 @@ "@hugotomazi/capacitor-navigation-bar": "^4.0.1", "@mediagrid/capacitor-native-audio": "^1.0.0", "beercss": "^3.8.0", - "bgutils-js": "^3.1.1", + "bgutils-js": "^3.1.3", "capacitor-nodejs": "https://github.com/EdenwareApps/Capacitor-NodeJS/releases/download/v1.0.0-beta.7/capacitor6-nodejs.tgz", "dashjs": "^4.7.4", "fuse.js": "^7.0.0", @@ -60,7 +60,6 @@ "eslint": "^8.56.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-svelte": "^2.45.1", - "i": "^0.3.7", "jsdom": "^25.0.1", "prettier": "^3.3.3", "prettier-plugin-svelte": "^3.2.7", @@ -7866,15 +7865,6 @@ "node": ">= 8" } }, - "node_modules/i": { - "version": "0.3.7", - "resolved": "https://registry.npmjs.org/i/-/i-0.3.7.tgz", - "integrity": "sha512-FYz4wlXgkQwIPqhzC5TdNMLSE5+GS1IIDJZY/1ZiEPCT2S3COUVZeT5OW4BmW4r5LHLQuOosSwsvnroG9GR59Q==", - "dev": true, - "engines": { - "node": ">=0.4" - } - }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", diff --git a/materialious/package.json b/materialious/package.json index 49c8ad2f..6099212b 100644 --- a/materialious/package.json +++ b/materialious/package.json @@ -1,6 +1,6 @@ { "name": "materialious", - "version": "1.7.8", + "version": "1.7.9", "private": true, "scripts": { "dev": "vite dev", @@ -29,7 +29,6 @@ "eslint": "^8.56.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-svelte": "^2.45.1", - "i": "^0.3.7", "jsdom": "^25.0.1", "prettier": "^3.3.3", "prettier-plugin-svelte": "^3.2.7", @@ -54,7 +53,7 @@ "@hugotomazi/capacitor-navigation-bar": "^4.0.1", "@mediagrid/capacitor-native-audio": "^1.0.0", "beercss": "^3.8.0", - "bgutils-js": "^3.1.1", + "bgutils-js": "^3.1.3", "capacitor-nodejs": "https://github.com/EdenwareApps/Capacitor-NodeJS/releases/download/v1.0.0-beta.7/capacitor6-nodejs.tgz", "dashjs": "^4.7.4", "fuse.js": "^7.0.0", @@ -75,4 +74,4 @@ "vidstack": "^1.12.12", "youtubei.js": "^12.2.0" } -} \ No newline at end of file +} diff --git a/materialious/src/lib/patches/youtubejs.ts b/materialious/src/lib/patches/youtubejs.ts index 8bbd08cc..88e2d693 100644 --- a/materialious/src/lib/patches/youtubejs.ts +++ b/materialious/src/lib/patches/youtubejs.ts @@ -2,80 +2,109 @@ import type { AdaptiveFormats, Captions, Image, StoryBoard, Thumbnail, VideoBase import { interfaceRegionStore, poTokenCacheStore } from '$lib/store'; import { numberWithCommas } from '$lib/time'; import { Capacitor } from '@capacitor/core'; -import { BG } from 'bgutils-js'; +import type { WebPoSignalOutput } from 'bgutils-js'; +import { BG, buildURL, GOOG_API_KEY } from 'bgutils-js'; import { get } from 'svelte/store'; -import { Innertube, ProtoUtils, UniversalCache, Utils } from 'youtubei.js'; +import { Innertube, UniversalCache } from 'youtubei.js'; import { capacitorFetch } from '../android/http/capacitorFetch'; -export interface PoTokens { - visitor_data: string; - po_token: string; -} +type WebPoMinter = { + integrityTokenBasedMinter?: BG.WebPoMinter; + botguardClient?: BG.BotGuardClient; +}; -async function getPo(identifier: string): Promise { +async function getWebPoMinter(): Promise { const requestKey = 'O43z0dpjhgX20SCx4KAo'; - const bgConfig = { - fetch: Capacitor.getPlatform() === 'android' ? capacitorFetch : fetch, - globalObj: window, - requestKey, - identifier - }; + const challengeResponse = await fetch(buildURL('Create', 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]) + }); - const bgChallenge = await BG.Challenge.create(bgConfig); + const challengeResponseData = await challengeResponse.json(); + + const bgChallenge = BG.Challenge.parseChallengeData(challengeResponseData); if (!bgChallenge) throw new Error('Could not get challenge'); const interpreterJavascript = bgChallenge.interpreterJavascript.privateDoNotAccessOrElseSafeScriptWrappedValue; - if (interpreterJavascript) { - new Function(interpreterJavascript)(); - } else throw new Error('Could not load VM'); + if (!document.getElementById(bgChallenge.interpreterHash)) { + const script = document.createElement('script'); + script.type = 'text/javascript'; + script.id = bgChallenge.interpreterHash; + script.textContent = interpreterJavascript; + document.head.appendChild(script); + } - const poTokenResult = await BG.PoToken.generate({ - program: bgChallenge.program, + const botguardClient = await BG.BotGuardClient.create({ + globalObj: globalThis, globalName: bgChallenge.globalName, - bgConfig + program: bgChallenge.program }); - return poTokenResult.poToken; + if (bgChallenge) { + 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-javacript/0.1' + }, + body: JSON.stringify([requestKey, botguardResponse]) + }); + + const integrityTokenResponseData = await integrityTokenResponse.json(); + const integrityToken = integrityTokenResponseData[0] as string | undefined; + + if (!integrityToken) { + console.error('Could not get integrity token. Interpreter Hash:', bgChallenge.interpreterHash); + return {}; + } + + const integrityTokenBasedMinter = await BG.WebPoMinter.create({ integrityToken }, webPoSignalOutput); + + return { + integrityTokenBasedMinter, + botguardClient + }; + } + + return {}; } export async function patchYoutubeJs(videoId: string): Promise { - let tokens: PoTokens; - if (!Capacitor.isNativePlatform()) { throw new Error('Platform not supported'); } - const poTokenCache = get(poTokenCacheStore); - if (!poTokenCache) { - const visitorData = ProtoUtils.encodeVisitorData(Utils.generateRandomString(11), Math.floor(Date.now() / 1000)); - const poToken = await getPo(visitorData); - - if (!poToken) { - throw new Error('Unable to generate PO token'); - } - - tokens = { - visitor_data: visitorData, - po_token: poToken - }; - poTokenCacheStore.set(tokens); - } else { - tokens = poTokenCache; - } - const youtube = await Innertube.create({ - visitor_data: tokens.visitor_data, - po_token: tokens.po_token, fetch: Capacitor.getPlatform() === 'android' ? capacitorFetch : fetch, generate_session_locally: true, cache: new UniversalCache(false), location: get(interfaceRegionStore) }); + const { integrityTokenBasedMinter } = await getWebPoMinter(); + + let sessionWebPo: string | undefined; + const poTokensCached = get(poTokenCacheStore); + if (!poTokensCached && integrityTokenBasedMinter) { + sessionWebPo = await integrityTokenBasedMinter.mintAsWebsafeString(youtube.session.context.client.visitorData ?? ''); + poTokenCacheStore.set(sessionWebPo); + } else { + sessionWebPo = poTokensCached; + } + const video = await youtube.getInfo(videoId); if (!video.primary_info || !video.secondary_info) { @@ -87,9 +116,20 @@ export async function patchYoutubeJs(videoId: string): Promise { if (!video.basic_info.is_live) { let manifest = await video.toDash(); - // Hack to fix video not displaying. - // Thanks to absidue & Andrews54757 - manifest = manifest.replaceAll(' { + let url = new URL(baseURL.textContent as string); + url.searchParams.set('pot', (sessionWebPo ?? BG.PoToken.generateColdStartToken(youtube.session.context.client.visitorData ?? ''))); + baseURL.textContent = url.toString(); + }); + + const serializer = new XMLSerializer(); + manifest = serializer.serializeToString(xmlDoc); + dashUri = URL.createObjectURL(new Blob([manifest], { type: 'application/dash+xml;charset=utf8' })); } diff --git a/materialious/src/lib/store.ts b/materialious/src/lib/store.ts index 4e83db47..37b9ead4 100644 --- a/materialious/src/lib/store.ts +++ b/materialious/src/lib/store.ts @@ -4,7 +4,6 @@ import type { DataConnection } from 'peerjs'; import { persisted } from 'svelte-persisted-store'; import { writable, type Writable } from 'svelte/store'; import type { TitleCase } from './letterCasing'; -import type { PoTokens } from './patches/youtubejs'; function platformDependentDefault(givenValue: any, defaultValue: any): any { if (typeof givenValue !== 'undefined' && typeof givenValue !== null) { @@ -106,6 +105,6 @@ export const playlistSettingsStore: Writable = persisted('silenceSkipper', false); -export const poTokenCacheStore: Writable = writable(); +export const poTokenCacheStore: Writable = writable(); export const searchHistoryStore: Writable = persisted('searchHistory', []); diff --git a/materialious/src/routes/(app)/watch/[slug]/+page.svelte b/materialious/src/routes/(app)/watch/[slug]/+page.svelte index fb70bfd7..d3dd6e23 100644 --- a/materialious/src/routes/(app)/watch/[slug]/+page.svelte +++ b/materialious/src/routes/(app)/watch/[slug]/+page.svelte @@ -664,7 +664,7 @@ {#if !theatreMode}
- {#if player && showTranscript} + {#if showTranscript} {/if} {#if playlist} @@ -755,23 +755,21 @@
- {#if player} -
- - {#if pauseTimerSeconds > 0} - {$_('player.pauseVideoIn')} {humanizeSeconds(pauseTimerSeconds)} - {/if} -
- {/if} +
+ + {#if pauseTimerSeconds > 0} + {$_('player.pauseVideoIn')} {humanizeSeconds(pauseTimerSeconds)} + {/if} +