Merge pull request #810 from Materialious/update/1.7.9

Update/1.7.9
This commit is contained in:
Ward
2025-02-16 20:04:04 +13:00
committed by GitHub
8 changed files with 113 additions and 87 deletions
+2 -2
View File
@@ -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.
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "Materialious",
"version": "1.7.8",
"version": "1.7.9",
"description": "Modern material design for Invidious.",
"author": {
"name": "Ward Pearce",
+3 -13
View File
@@ -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",
+3 -4
View File
@@ -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"
}
}
}
+86 -46
View File
@@ -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<string | undefined> {
async function getWebPoMinter(): Promise<WebPoMinter> {
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<VideoPlay> {
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<VideoPlay> {
if (!video.basic_info.is_live) {
let manifest = await video.toDash();
// Hack to fix video not displaying.
// Thanks to absidue & Andrews54757
manifest = manifest.replaceAll('<EssentialProperty', '<SupplementalProperty');
let parser = new DOMParser();
let xmlDoc = parser.parseFromString(manifest, 'application/xml');
let baseURLs = xmlDoc.getElementsByTagName('BaseURL');
Array.from(baseURLs).forEach((baseURL) => {
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' }));
}
+1 -2
View File
@@ -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<Record<string, { shuffle: boolean;
export const silenceSkipperStore: Writable<boolean> = persisted('silenceSkipper', false);
export const poTokenCacheStore: Writable<PoTokens> = writable();
export const poTokenCacheStore: Writable<string> = writable();
export const searchHistoryStore: Writable<string[]> = persisted('searchHistory', []);
@@ -664,7 +664,7 @@
</div>
{#if !theatreMode}
<div class="s12 m12 l3">
{#if player && showTranscript}
{#if showTranscript}
<Transcript bind:player video={data.video} />
{/if}
{#if playlist}
@@ -755,23 +755,21 @@
</div>
<dialog class="modal" id="pause-timer">
{#if player}
<div class="field middle-align">
<label class="slider">
<input
type="range"
bind:value={pauseTimerSeconds}
min="0"
step="60"
max={data.video.lengthSeconds - playerCurrentTime - 60}
/>
<span></span>
</label>
{#if pauseTimerSeconds > 0}
<span class="helper">{$_('player.pauseVideoIn')} {humanizeSeconds(pauseTimerSeconds)}</span>
{/if}
</div>
{/if}
<div class="field middle-align">
<label class="slider">
<input
type="range"
bind:value={pauseTimerSeconds}
min="0"
step="60"
max={data.video.lengthSeconds - playerCurrentTime - 60}
/>
<span></span>
</label>
{#if pauseTimerSeconds > 0}
<span class="helper">{$_('player.pauseVideoIn')} {humanizeSeconds(pauseTimerSeconds)}</span>
{/if}
</div>
<nav class="right-align no-space">
<button
class="transparent link"
+1 -1
View File
@@ -5,7 +5,7 @@ import json
import os
import re
LATEST_VERSION = "1.7.8"
LATEST_VERSION = "1.7.9"
WORKING_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "materialious")
ROOT_PACKAGE = os.path.join(WORKING_DIR, "package.json")