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">
|
||||
|
||||
@@ -7,8 +7,8 @@ android {
|
||||
applicationId "us.materialio.app"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 105
|
||||
versionName "1.8.1"
|
||||
versionCode 106
|
||||
versionName "1.8.2"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
aaptOptions {
|
||||
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
||||
|
||||
@@ -66,7 +66,11 @@
|
||||
|
||||
|
||||
|
||||
<release version="1.8.1" date="2025-5-03">
|
||||
|
||||
<release version="1.8.2" date="2025-5-04">
|
||||
<url>https://github.com/Materialious/Materialious/releases/tag/1.8.2</url>
|
||||
</release>
|
||||
<release version="1.8.1" date="2025-5-03">
|
||||
<url>https://github.com/Materialious/Materialious/releases/tag/1.8.1</url>
|
||||
</release>
|
||||
<release version="1.8.0" date="2025-4-30">
|
||||
|
||||
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,6 +1,6 @@
|
||||
{
|
||||
"name": "Materialious",
|
||||
"version": "1.8.1",
|
||||
"version": "1.8.2",
|
||||
"description": "Modern material design for Invidious.",
|
||||
"author": {
|
||||
"name": "Ward Pearce",
|
||||
|
||||
@@ -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)
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "materialious",
|
||||
"version": "1.8.1",
|
||||
"version": "1.8.2",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
@@ -71,4 +71,4 @@
|
||||
"terser": "^5.34.1",
|
||||
"youtubei.js": "^13.4.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
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
|
||||
|
||||
@@ -36,6 +36,8 @@
|
||||
let forceCase = $state(get(interfaceForceCase));
|
||||
let defaultPage = $state(get(interfaceDefaultPage));
|
||||
|
||||
let invalidInstance = $state(false);
|
||||
|
||||
async function setColor(color: any) {
|
||||
const target = color.target;
|
||||
const hex = (target as { value: string }).value;
|
||||
@@ -55,21 +57,44 @@
|
||||
darkModeStore.set(true);
|
||||
}
|
||||
}
|
||||
|
||||
async function setInstance(event: Event) {
|
||||
event.preventDefault();
|
||||
|
||||
invalidInstance = false;
|
||||
|
||||
const instance = ensureNoTrailingSlash(invidiousInstance);
|
||||
|
||||
try {
|
||||
new URL(instance);
|
||||
} catch {
|
||||
invalidInstance = true;
|
||||
}
|
||||
|
||||
if (invalidInstance) return;
|
||||
|
||||
const resp = await fetch(`${instance}/api/v1/trending`);
|
||||
if (!resp.ok) {
|
||||
invalidInstance = true;
|
||||
return;
|
||||
}
|
||||
|
||||
instanceStore.set(instance);
|
||||
authStore.set(null);
|
||||
goto('/', { replaceState: true });
|
||||
ui('#dialog-settings');
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if Capacitor.isNativePlatform()}
|
||||
<form
|
||||
onsubmit={preventDefault(() => {
|
||||
instanceStore.set(ensureNoTrailingSlash(invidiousInstance));
|
||||
authStore.set(null);
|
||||
goto('/', { replaceState: true });
|
||||
ui('#dialog-settings');
|
||||
})}
|
||||
>
|
||||
<form style="margin-bottom: 2em;" onsubmit={setInstance}>
|
||||
<nav>
|
||||
<div class="field label border max">
|
||||
<div class="field label border max" class:invalid={invalidInstance}>
|
||||
<input bind:value={invidiousInstance} name="invidious-instance" type="text" />
|
||||
<label for="invidious-instance">{$_('layout.instanceUrl')}</label>
|
||||
{#if invalidInstance}
|
||||
<span class="error">{$_('invalidInstance')}</span>
|
||||
{/if}
|
||||
</div>
|
||||
<button class="square round">
|
||||
<i>done</i>
|
||||
@@ -78,8 +103,6 @@
|
||||
</form>
|
||||
{/if}
|
||||
|
||||
<div class="margin"></div>
|
||||
|
||||
<button onclick={toggleDarkMode} class="no-margin">
|
||||
{#if !$darkModeStore}
|
||||
<i>dark_mode</i>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"copyUrl": "Copy URL",
|
||||
"loadMore": "Load more",
|
||||
"views": "views",
|
||||
"invalidInstance": "Please verify the URL. If it's correct, the instance may be down or may not support third-party clients.",
|
||||
"invidiousBlockWarning": "Invidious is currently being blocked by Google. If videos aren't loading for this instance, please use this instance on Materialious on {android} or {desktop} to get around this with local video fallback.",
|
||||
"videos": "videos",
|
||||
"cancel": "Cancel",
|
||||
@@ -161,4 +162,4 @@
|
||||
}
|
||||
},
|
||||
"subscribe": "Subscribe"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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: {
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ import os
|
||||
import re
|
||||
from datetime import datetime
|
||||
|
||||
LATEST_VERSION = "1.8.1"
|
||||
LATEST_VERSION = "1.8.2"
|
||||
RELEASE_DATE = datetime.now().strftime("%Y-%-m-%d") # Format: YYYY-M-D
|
||||
|
||||
WORKING_DIR = os.path.join(
|
||||
|
||||
Reference in New Issue
Block a user