Merge pull request #616 from Materialious/update/1.6.0

Update/1.6.0
This commit is contained in:
Ward
2024-10-01 14:13:42 +13:00
committed by GitHub
12 changed files with 92 additions and 177 deletions
+2 -2
View File
@@ -7,8 +7,8 @@ android {
applicationId "us.materialio.app"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 50
versionName "1.5.9"
versionCode 51
versionName "1.6.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions {
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
+2 -3
View File
@@ -1,6 +1,6 @@
{
"name": "Materialious",
"version": "1.5.9",
"version": "1.6.0",
"description": "Modern material design for Invidious.",
"author": {
"name": "Ward Pearce",
@@ -28,8 +28,7 @@
"electron-unhandled": "^4.0.1",
"electron-updater": "^6.3.4",
"electron-window-state": "^5.0.3",
"jsonfile": "^6.1.0",
"youtube-po-token-generator": "^0.2.0"
"jsonfile": "^6.1.0"
},
"devDependencies": {
"electron": "^32.1.0",
-5
View File
@@ -1,6 +1 @@
require('./rt/electron-rt');
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('electron', {
generatePoToken: () => ipcRenderer.invoke('generate-po-token')
});
+1 -11
View File
@@ -6,12 +6,11 @@ import {
} from '@capacitor-community/electron';
import chokidar from 'chokidar';
import type { MenuItemConstructorOptions } from 'electron';
import { app, BrowserWindow, ipcMain, Menu, MenuItem, nativeImage, session, Tray } from 'electron';
import { app, BrowserWindow, Menu, MenuItem, nativeImage, session, Tray } from 'electron';
import electronIsDev from 'electron-is-dev';
import electronServe from 'electron-serve';
import windowStateKeeper from 'electron-window-state';
import { join } from 'path';
import { generate } from 'youtube-po-token-generator';
// Define components for a watcher to detect when the webapp is changed so we can reload in Dev mode.
const reloadWatcher = {
@@ -215,15 +214,6 @@ export class ElectronCapacitorApp {
CapElectronEventEmitter.emit('CAPELECTRON_DeeplinkListenerInitialized', '');
}, 400);
});
ipcMain.handle('generate-po-token', async () => {
try {
return await generate();
} catch (error) {
console.error('Error generating token:', error);
return { error: error.message }; // Return error to renderer
}
});
}
}
+21 -10
View File
@@ -1,12 +1,12 @@
{
"name": "materialious",
"version": "1.5.9",
"version": "1.6.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "materialious",
"version": "1.5.9",
"version": "1.6.0",
"dependencies": {
"@capacitor-community/electron": "^5.0.1",
"@capacitor/android": "6.1.2",
@@ -19,6 +19,7 @@
"@ffmpeg/ffmpeg": "^0.12.10",
"@mediagrid/capacitor-native-audio": "^1.0.0",
"beercss": "^3.7.8",
"bgutils-js": "^2.2.0",
"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",
@@ -35,7 +36,8 @@
"svelte-infinite-loading": "^1.4.0",
"svelte-persisted-store": "^0.11.0",
"terser": "^5.34.1",
"vidstack": "^1.12.9"
"vidstack": "^1.12.9",
"youtubei.js": "^10.5.0"
},
"devDependencies": {
"@capacitor/assets": "^3.0.5",
@@ -65,8 +67,7 @@
"svelte-check": "^3.8.6",
"tslib": "^2.7.0",
"typescript": "^5.5.4",
"vite": "^5.4.6",
"youtubei.js": "^10.5.0"
"vite": "^5.4.6"
}
},
"node_modules/@ampproject/remapping": {
@@ -2003,7 +2004,7 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.1.0.tgz",
"integrity": "sha512-+2Mx67Y3skJ4NCD/qNSdBJNWtu6x6Qr53jeNg+QcwiL6mt0wK+3jwHH2x1p7xaYH6Ve2JKOVn0OxU35WsmqI9A==",
"dev": true
"license": "(Apache-2.0 AND BSD-3-Clause)"
},
"node_modules/@capacitor-community/electron": {
"version": "5.0.1",
@@ -2301,7 +2302,7 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz",
"integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=14"
}
@@ -4306,6 +4307,15 @@
"material-dynamic-colors": "^1.1.2"
}
},
"node_modules/bgutils-js": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/bgutils-js/-/bgutils-js-2.2.0.tgz",
"integrity": "sha512-6IRDTZIdHxoyfttEHlXfCVHdje8yNoxcgp8Os5pu6uaWdeZMrN7iiXFxk6EGVbcaQTDZEUcH5li8qMK7sqWS0g==",
"funding": [
"https://github.com/sponsors/LuanRT"
],
"license": "MIT"
},
"node_modules/big-integer": {
"version": "1.6.52",
"resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz",
@@ -7968,10 +7978,10 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/jintr/-/jintr-2.1.1.tgz",
"integrity": "sha512-89cwX4ouogeDGOBsEVsVYsnWWvWjchmwXBB4kiBhmjOKw19FiOKhNhMhpxhTlK2ctl7DS+d/ethfmuBpzoNNgA==",
"dev": true,
"funding": [
"https://github.com/sponsors/LuanRT"
],
"license": "MIT",
"dependencies": {
"acorn": "^8.8.0"
}
@@ -9189,6 +9199,7 @@
"write-file-atomic"
],
"dev": true,
"license": "Artistic-2.0",
"workspaces": [
"docs",
"smoke-tests",
@@ -15108,7 +15119,7 @@
"version": "5.28.4",
"resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz",
"integrity": "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@fastify/busboy": "^2.0.0"
},
@@ -16720,10 +16731,10 @@
"version": "10.5.0",
"resolved": "https://registry.npmjs.org/youtubei.js/-/youtubei.js-10.5.0.tgz",
"integrity": "sha512-iyA+VF28c15tCCKH9ExM2RKC3zYiHzA/eixGlJ3vERANkuI+xYKzAZ4vtOhmyqwrAddu88R/DkzEsmpph5NWjg==",
"dev": true,
"funding": [
"https://github.com/sponsors/LuanRT"
],
"license": "MIT",
"dependencies": {
"@bufbuild/protobuf": "^2.0.0",
"jintr": "^2.1.1",
+6 -5
View File
@@ -1,6 +1,6 @@
{
"name": "materialious",
"version": "1.5.9",
"version": "1.6.0",
"private": true,
"scripts": {
"dev": "vite dev",
@@ -39,8 +39,7 @@
"svelte-check": "^3.8.6",
"tslib": "^2.7.0",
"typescript": "^5.5.4",
"vite": "^5.4.6",
"youtubei.js": "^10.5.0"
"vite": "^5.4.6"
},
"type": "module",
"dependencies": {
@@ -55,6 +54,7 @@
"@ffmpeg/ffmpeg": "^0.12.10",
"@mediagrid/capacitor-native-audio": "^1.0.0",
"beercss": "^3.7.8",
"bgutils-js": "^2.2.0",
"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",
@@ -71,6 +71,7 @@
"svelte-infinite-loading": "^1.4.0",
"svelte-persisted-store": "^0.11.0",
"terser": "^5.34.1",
"vidstack": "^1.12.9"
"vidstack": "^1.12.9",
"youtubei.js": "^10.5.0"
}
}
}
+1 -4
View File
@@ -425,11 +425,8 @@
}
.video-title {
display: inline-block;
white-space: nowrap;
width: 100%;
word-wrap: break-word;
overflow: hidden;
text-overflow: ellipsis;
}
.thumbnail-details {
@@ -1,123 +0,0 @@
import { InAppBrowser } from '@capgo/inappbrowser';
import { minify } from 'terser';
export interface PoTokens {
visitor_data: string;
po_token: string;
}
const headers = {
'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
'accept-language': 'en-US;q=0.9',
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko)',
};
// Inspired by https://github.com/YunzheZJU/youtube-po-token-generator & https://github.com/YunzheZJU/youtube-po-token-generator
export async function getPoToken(): Promise<PoTokens> {
let visitorData: string;
return new Promise((resolve, reject) => {
const messageListener = async (data: any) => {
if (!data.po_token) {
visitorData = data.visitor_data;
const injectPlayer = await fetch('/po-token-youtube/inject.js');
await InAppBrowser.executeScript({
code: (await minify(`
window.onPoToken = (poToken) => {
window.mobileApp.postMessage({po_token: poToken});
}
ytcfg.set({
'INNERTUBE_CONTEXT': {
...ytcfg.get('INNERTUBE_CONTEXT'),
client: {
...ytcfg.get('INNERTUBE_CONTEXT').client,
visitorData: "${visitorData}",
},
},
'VISITOR_DATA': "${visitorData}",
'IDENTITY_MEMENTO': {
'visitor_data': "${visitorData}",
},
})
${await injectPlayer.text()}`)).code as string
});
} else if (visitorData && data.po_token) {
await InAppBrowser.removeAllListeners();
await InAppBrowser.close();
resolve({ visitor_data: visitorData, po_token: data.po_token });
}
};
const closeListener = async () => {
await InAppBrowser.removeAllListeners();
};
const urlChangeListener = async () => {
// Code must be minified to ensure runs correctly.
await InAppBrowser.executeScript({
code: (await minify(`const headers = {"accept": "${headers.accept}", "accept-language": "${headers['accept-language']}", "user-agent": "${headers['user-agent']}"};
Object.defineProperty(window.navigator, 'userAgent', { value: headers["user-agent"], writable: false });
const originalOpen = XMLHttpRequest.prototype.open;
const originalSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.open = function (method, url) {
this._url = url;
originalOpen.apply(this, arguments);
};
XMLHttpRequest.prototype.send = function (body) {
const xhr = this;
if (this._url.includes("/youtubei/v1/player")) {
for (const [key, value] of Object.entries(headers)) {
this.setRequestHeader(key, value);
}
this.addEventListener("load", function () {
try {
const postJson = JSON.parse(body);
window.mobileApp.postMessage({visitor_data: postJson.context.client.visitorData});
} catch (error) {
console.error("Error parsing request body:", error);
}
});
}
originalSend.apply(this, arguments);
};
function attemptClickPlayButton() {
const interval = setInterval(() => {
const playButtons = document.getElementsByClassName("ytp-play-button");
if (playButtons.length > 0) {
playButtons[0].click();
clearInterval(interval);
}
}, 100);
}
attemptClickPlayButton();
`)).code as string
});
};
InAppBrowser.addListener('messageFromWebview', messageListener);
InAppBrowser.addListener('closeEvent', closeListener);
InAppBrowser.addListener('urlChangeEvent', urlChangeListener);
setTimeout(async () => {
reject(new Error('Timeout trying to pull Po tokens'));
await InAppBrowser.removeAllListeners();
await InAppBrowser.close();
}, 60000);
InAppBrowser.openWebView({
url: 'https://www.youtube.com/embed/jNQXAC9IVRw',
title: 'Pulling po tokens (This may take a moment)',
headers: headers,
});
});
}
+55 -11
View File
@@ -2,31 +2,73 @@ import type { Image, Thumbnail, VideoBase, VideoPlay } from '$lib/Api/model';
import { numberWithCommas } from '$lib/misc';
import { poTokenCacheStore } from '$lib/store';
import { Capacitor } from '@capacitor/core';
import { BG } from 'bgutils-js';
import { get } from 'svelte/store';
import { Innertube, ProtoUtils, UniversalCache, Utils } from 'youtubei.js';
import { capacitorFetch } from './capacitorFetch';
import { type PoTokens } from './poTokenAndroid';
export interface PoTokens {
visitor_data: string;
po_token: string;
}
async function getPo(identifier: string): Promise<string | undefined> {
const requestKey = 'O43z0dpjhgX20SCx4KAo';
const bgConfig = {
fetch: fetch,
globalObj: window,
requestKey,
identifier
};
const challenge = await BG.Challenge.create(bgConfig);
if (!challenge)
throw new Error('Could not get challenge');
if (challenge.script) {
const script = challenge.script.find((sc) => sc !== null);
if (script)
new Function(script)();
}
const poToken = await BG.PoToken.generate({
program: challenge.challenge,
globalName: challenge.globalName,
bgConfig
});
return poToken;
}
export async function patchYoutubeJs(videoId: string): Promise<VideoPlay> {
const innertube = (await import('youtubei.js')).Innertube;
let tokens: PoTokens;
if (!Capacitor.isNativePlatform()) {
throw new Error('Platform not supported');
}
const poTokenCache = get(poTokenCacheStore);
if (!poTokenCache) {
if (Capacitor.getPlatform() === 'electron') {
tokens = await (window as any).electron.generatePoToken();
} else if (Capacitor.getPlatform() === 'android') {
tokens = await (await import('../../lib/patches/poTokenAndroid')).getPoToken();
} else {
throw new Error('This platform cant generate po tokens');
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({
const youtube = await Innertube.create({
visitor_data: tokens.visitor_data,
po_token: tokens.po_token,
fetch: async (input: RequestInfo | URL, init?: RequestInit) => {
@@ -35,7 +77,9 @@ export async function patchYoutubeJs(videoId: string): Promise<VideoPlay> {
} else {
return fetch(input, init);
}
}
},
generate_session_locally: true,
cache: new UniversalCache(false)
});
const video = await youtube.getInfo(videoId);
+1 -1
View File
@@ -5,7 +5,7 @@ import type { DataConnection } from 'peerjs';
import { persisted } from 'svelte-persisted-store';
import { writable, type Writable } from 'svelte/store';
import type { TitleCase } from './misc';
import type { PoTokens } from './patches/poTokenAndroid';
import type { PoTokens } from './patches/youtubejs';
function platformDependentDefault(givenValue: any, defaultValue: any): any {
if (typeof givenValue !== 'undefined' && typeof givenValue !== null) {
@@ -21,6 +21,7 @@
letterCase,
numberWithCommas,
proxyGoogleImage,
truncate,
unsafeRandomItem
} from '$lib/misc';
import type { PlayerEvents } from '$lib/player';
@@ -492,7 +493,7 @@
alt="Channel profile"
/>
<div>
<p style="margin: 0;" class="bold">{data.video.author}</p>
<p style="margin: 0;" class="bold">{truncate(data.video.author, 16)}</p>
<p style="margin: 0;">{data.video.subCountText}</p>
</div>
</nav>
+1 -1
View File
@@ -5,7 +5,7 @@ import json
import os
import re
LATEST_VERSION = "1.5.9"
LATEST_VERSION = "1.6.0"
WORKING_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "materialious")
ROOT_PACKAGE = os.path.join(WORKING_DIR, "package.json")