diff --git a/materialious/Dockerfile b/materialious/Dockerfile index 806ea6d9..d61d1192 100644 --- a/materialious/Dockerfile +++ b/materialious/Dockerfile @@ -11,6 +11,7 @@ COPY . . # Create placeholder env vars RUN echo "VITE_DEFAULT_INVIDIOUS_INSTANCE=VITE_DEFAULT_INVIDIOUS_INSTANCE_PLACEHOLDER" > .env && \ + echo "VITE_DEFAULT_COMPANION_INSTANCE=VITE_DEFAULT_COMPANION_INSTANCE_PLACEHOLDER" >> .env && \ echo "VITE_DEFAULT_SPONSERBLOCK_INSTANCE=VITE_DEFAULT_SPONSERBLOCK_INSTANCE_PLACEHOLDER" >> .env && \ echo "VITE_DEFAULT_DEARROW_INSTANCE=VITE_DEFAULT_DEARROW_INSTANCE_PLACEHOLDER" >> .env && \ echo "VITE_DEFAULT_DEARROW_THUMBNAIL_INSTANCE=VITE_DEFAULT_DEARROW_THUMBNAIL_INSTANCE_PLACEHOLDER" >> .env && \ diff --git a/materialious/electron/src/index.ts b/materialious/electron/src/index.ts index 92b385cb..476b8cd3 100644 --- a/materialious/electron/src/index.ts +++ b/materialious/electron/src/index.ts @@ -4,19 +4,13 @@ import { setupElectronDeepLinking } from '@capacitor-community/electron'; import type { MenuItemConstructorOptions } from 'electron'; -import { app, ipcMain, MenuItem } from 'electron'; +import { app, ipcMain, MenuItem, session, webContents } from 'electron'; import electronIsDev from 'electron-is-dev'; import unhandled from 'electron-unhandled'; import { autoUpdater } from 'electron-updater'; import { JSDOM } from 'jsdom'; -import BG, { - buildURL, - DescrambledChallenge, - GOOG_API_KEY, - USER_AGENT, - WebPoSignalOutput -} from 'bgutils-js'; +import BG, { buildURL, GOOG_API_KEY, USER_AGENT, WebPoSignalOutput } from 'bgutils-js'; import { ElectronCapacitorApp, setupContentSecurityPolicy, setupReloadWatcher } from './setup'; // Graceful handling of unhandled errors. @@ -54,6 +48,8 @@ if (electronIsDev) { setupReloadWatcher(myCapacitorApp); } +let allowInsecureSSL = false; + // Run Application (async () => { // Wait for electron app to be ready. @@ -66,6 +62,15 @@ if (electronIsDev) { autoUpdater.checkForUpdatesAndNotify(); })(); +app.on('certificate-error', (event, webContents, url, error, certificate, callback) => { + if (allowInsecureSSL) { + event.preventDefault(); // prevent default behavior (blocking the cert) + callback(true); // trust the certificate + } else { + callback(false); // reject others + } +}); + // 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 @@ -154,3 +159,11 @@ ipcMain.handle( return await integrityTokenBasedMinter.mintAsWebsafeString(decodeURIComponent(visitorData)); } ); + +ipcMain.handle('setAllowInsecureSSL', async (_, allow) => { + allowInsecureSSL = allow; + await session.defaultSession.clearAuthCache(); + await session.defaultSession.clearCache(); + + return allowInsecureSSL; +}); diff --git a/materialious/electron/src/preload.ts b/materialious/electron/src/preload.ts index e54e2e00..278f9285 100644 --- a/materialious/electron/src/preload.ts +++ b/materialious/electron/src/preload.ts @@ -5,5 +5,8 @@ require('./rt/electron-rt'); contextBridge.exposeInMainWorld('electronAPI', { generatePoToken: (bgChallenge: DescrambledChallenge, requestKey: string, visitorData: string) => - ipcRenderer.invoke('generatePoToken', bgChallenge, requestKey, visitorData) + ipcRenderer.invoke('generatePoToken', bgChallenge, requestKey, visitorData), + setAllowInsecureSSL: async (allow: boolean) => { + return await ipcRenderer.invoke('setAllowInsecureSSL', allow); + } }); diff --git a/materialious/replace_env_vars.sh b/materialious/replace_env_vars.sh index 329fb6d3..18f71f88 100644 --- a/materialious/replace_env_vars.sh +++ b/materialious/replace_env_vars.sh @@ -3,6 +3,7 @@ ROOT_DIR=/usr/share/nginx/html # Replace env vars in JavaScript and HTML files served by NGINX find "$ROOT_DIR" -type f \( -name "*.js" -o -name "*.html" \) -exec sed -i 's|VITE_DEFAULT_INVIDIOUS_INSTANCE_PLACEHOLDER|'"$VITE_DEFAULT_INVIDIOUS_INSTANCE"'|g' {} + +find "$ROOT_DIR" -type f \( -name "*.js" -o -name "*.html" \) -exec sed -i 's|VITE_DEFAULT_COMPANION_INSTANCE_PLACEHOLDER|'"$VITE_DEFAULT_COMPANION_INSTANCE"'|g' {} + find "$ROOT_DIR" -type f \( -name "*.js" -o -name "*.html" \) -exec sed -i 's|VITE_DEFAULT_RETURNYTDISLIKES_INSTANCE_PLACEHOLDER|'"$VITE_DEFAULT_RETURNYTDISLIKES_INSTANCE"'|g' {} + find "$ROOT_DIR" -type f \( -name "*.js" -o -name "*.html" \) -exec sed -i 's|VITE_DEFAULT_SPONSERBLOCK_INSTANCE_PLACEHOLDER|'"$VITE_DEFAULT_SPONSERBLOCK_INSTANCE"'|g' {} + find "$ROOT_DIR" -type f \( -name "*.js" -o -name "*.html" \) -exec sed -i 's|VITE_DEFAULT_DEARROW_THUMBNAIL_INSTANCE_PLACEHOLDER|'"$VITE_DEFAULT_DEARROW_THUMBNAIL_INSTANCE"'|g' {} + diff --git a/materialious/src/app.d.ts b/materialious/src/app.d.ts index 0ce56bce..753ee6b1 100644 --- a/materialious/src/app.d.ts +++ b/materialious/src/app.d.ts @@ -18,6 +18,7 @@ declare global { requestKey: string, visitorData: string ) => Promise; + setAllowInsecureSSL: (allowInsecureSSL: boolean) => Promoise; }; } } diff --git a/materialious/src/lib/android/http/androidRequests.ts b/materialious/src/lib/android/http/androidRequests.ts index 50676c9c..a3fe52ed 100644 --- a/materialious/src/lib/android/http/androidRequests.ts +++ b/materialious/src/lib/android/http/androidRequests.ts @@ -1,5 +1,4 @@ import { goto } from '$app/navigation'; -import { interfaceAllowInsecureRequests } from '$lib/store'; import { Capacitor } from '@capacitor/core'; const originalFetch = window.fetch; @@ -53,14 +52,5 @@ if (Capacitor.getPlatform() === 'android') { return originalXhrOpen.apply(this, args); }; - // Used to toggle rejectUnauthorized in the backend - interfaceAllowInsecureRequests.subscribe(async (isAllowed) => { - if (isAllowed) { - await fetch('http://materialious__allow-insecure-requests'); - } else { - await fetch('http://materialious__deny-insecure-requests'); - } - }); - setTimeout(() => goto('/', { replaceState: true }), 100); } diff --git a/materialious/src/lib/components/Player.svelte b/materialious/src/lib/components/Player.svelte index 6b08ed41..b5c299be 100644 --- a/materialious/src/lib/components/Player.svelte +++ b/materialious/src/lib/components/Player.svelte @@ -23,6 +23,7 @@ import type { VideoPlay } from '../api/model'; import { authStore, + companionStore, instanceStore, playerAndroidLockOrientation, playerAutoPlayStore, @@ -76,7 +77,6 @@ const sessionId = Array.from(Array(16), () => Math.floor(Math.random() * 36).toString(36)).join( '' ); - let isLive = false; let videoPlaybackUstreamerConfig: string | undefined; let serverAbrStreamingUrl: URL | undefined = undefined; let drmParams: string | undefined; @@ -251,7 +251,7 @@ 'language', 'statistics' ], - playbackRates: [0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 2.25, 2.5, 2.75, 3], + playbackRates: [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 2.25, 2.5, 2.75, 3], enableTooltips: false, seekBarColors: { played: (await getDynamicTheme())['--primary'] @@ -259,8 +259,6 @@ }); if (data.video.fallbackPatch === 'youtubejs' && data.video.ytjs) { - isLive = !!data.video.ytjs.video.basic_info.is_live; - if ( data.video.ytjs.rawApiResponse.data.streamingData && (data.video.ytjs.rawApiResponse.data.streamingData as any).drmParams @@ -343,7 +341,7 @@ type === shaka.net.NetworkingEngine.RequestType.SEGMENT && url.pathname.includes('videoplayback') ) { - if (!isLive) { + if (!data.video.liveNow) { const currentFormat = formatList.find( (format) => fromFormat(format) === (new URL(request.uris[0]).searchParams.get('___key') || '') @@ -632,7 +630,12 @@ await player.attach(playerElement); if (!data.video.hlsUrl) { - let dashUrl = data.video.dashUrl; + let dashUrl: string = ''; + if ($companionStore) { + dashUrl = `${$companionStore}/api/manifest/dash/id/${data.video.videoId}`; + } else { + dashUrl = data.video.dashUrl; + } if (!data.video.fallbackPatch && (!Capacitor.isNativePlatform() || proxyVideos)) { dashUrl += '?local=true'; diff --git a/materialious/src/lib/components/Settings/Interface.svelte b/materialious/src/lib/components/Settings/Interface.svelte index 58509765..642f6c6d 100644 --- a/materialious/src/lib/components/Settings/Interface.svelte +++ b/materialious/src/lib/components/Settings/Interface.svelte @@ -13,6 +13,7 @@ import { getPages } from '../../navPages'; import { authStore, + companionStore, darkModeStore, instanceStore, interfaceAllowInsecureRequests, @@ -30,6 +31,7 @@ } from '../../store'; let invidiousInstance = $state(get(instanceStore)); + let companionInstance = $state(get(companionStore)); let region = $state(get(interfaceRegionStore)); let forceCase = $state(get(interfaceForceCase)); let defaultPage = $state(get(interfaceDefaultPage)); @@ -91,10 +93,28 @@ ui('#dialog-settings'); } + function setCompanion(event: Event) { + event.preventDefault(); + companionStore.set(ensureNoTrailingSlash(companionInstance)); + } + function allowInsecureRequests() { - if (Capacitor.getPlatform() !== 'android') return; + if (!Capacitor.isNativePlatform()) return; interfaceAllowInsecureRequests.set(!$interfaceAllowInsecureRequests); } + + // Used to toggle rejectUnauthorized in the backend + interfaceAllowInsecureRequests.subscribe(async (isAllowed) => { + if (Capacitor.getPlatform() === 'android') { + if (isAllowed) { + await fetch('http://materialious__allow-insecure-requests'); + } else { + await fetch('http://materialious__deny-insecure-requests'); + } + } else if (Capacitor.getPlatform() === 'electron') { + await window.electronAPI.setAllowInsecureSSL(isAllowed); + } + }); {#if Capacitor.isNativePlatform()} @@ -115,24 +135,39 @@ {#if invalidInstance}
{/if} -{/if} -{#if Capacitor.getPlatform() === 'android' && (invalidInstance || $interfaceAllowInsecureRequests)} -
-