Improved proxy
This commit is contained in:
@@ -7,8 +7,8 @@ android {
|
||||
applicationId "us.materialio.app"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 212
|
||||
versionName "1.15.4"
|
||||
versionCode 213
|
||||
versionName "1.15.5"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
aaptOptions {
|
||||
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
||||
|
||||
@@ -77,7 +77,11 @@
|
||||
|
||||
|
||||
|
||||
<release version="1.15.4" date="2026-2-16">
|
||||
|
||||
<release version="1.15.5" date="2026-2-16">
|
||||
<url>https://github.com/Materialious/Materialious/releases/tag/1.15.5</url>
|
||||
</release>
|
||||
<release version="1.15.4" date="2026-2-16">
|
||||
<url>https://github.com/Materialious/Materialious/releases/tag/1.15.4</url>
|
||||
</release>
|
||||
<release version="1.15.3" date="2026-2-15">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Materialious",
|
||||
"version": "1.15.4",
|
||||
"version": "1.15.5",
|
||||
"description": "Modern material design for YouTube and Invidious.",
|
||||
"author": {
|
||||
"name": "Ward Pearce",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "materialious",
|
||||
"version": "1.15.4",
|
||||
"version": "1.15.5",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "npm run patch:github && vite dev",
|
||||
@@ -96,4 +96,4 @@
|
||||
"youtubei.js": "^16.0.1",
|
||||
"zod": "^4.3.6"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -71,9 +71,6 @@ export async function getVideoYTjs(videoId: string): Promise<VideoPlay> {
|
||||
contentCheckOk: true,
|
||||
racyCheckOk: true,
|
||||
playbackContext: {
|
||||
adPlaybackContext: {
|
||||
pyv: true
|
||||
},
|
||||
contentPlaybackContext: {
|
||||
signatureTimestamp: innertube.session.player?.signature_timestamp
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
playerDefaultLanguage,
|
||||
playerDefaultPlaybackSpeed,
|
||||
playerDefaultQualityStore,
|
||||
playerPreferredVolume,
|
||||
playerProxyVideosStore,
|
||||
playerSavePlaybackPositionStore,
|
||||
playerState,
|
||||
@@ -184,7 +185,6 @@
|
||||
let clickCounterTimeout: ReturnType<typeof setTimeout>;
|
||||
|
||||
let seekDirection: 'forwards' | 'backwards' | undefined = $state();
|
||||
const STORAGE_KEY_VOLUME = 'shaka-preferred-volume';
|
||||
|
||||
playertheatreModeIsActive.subscribe(async () => {
|
||||
await tick();
|
||||
@@ -246,16 +246,12 @@
|
||||
|
||||
function saveVolumePreference() {
|
||||
if (!playerElement) return;
|
||||
playerVolume = playerElement.volume;
|
||||
localStorage.setItem(STORAGE_KEY_VOLUME, playerElement.volume.toString());
|
||||
playerPreferredVolume.set(playerElement.volume);
|
||||
}
|
||||
|
||||
function restoreVolumePreference() {
|
||||
const savedVolume = localStorage.getItem(STORAGE_KEY_VOLUME);
|
||||
if (savedVolume && playerElement) {
|
||||
playerElement.volume = parseFloat(savedVolume);
|
||||
playerVolume = playerElement.volume;
|
||||
}
|
||||
if (!playerElement) return;
|
||||
playerElement.volume = $playerPreferredVolume;
|
||||
}
|
||||
|
||||
function restoreQualityPreference() {
|
||||
@@ -365,51 +361,52 @@
|
||||
|
||||
async function setupSponsorSkip() {
|
||||
if (!$sponsorBlockUrlStore || !$sponsorBlockCategoriesStore || !$sponsorBlockStore) return;
|
||||
const sponsorBlock = new SponsorBlock('', { baseURL: $sponsorBlockUrlStore });
|
||||
|
||||
if ($sponsorBlockCategoriesStore && $sponsorBlockUrlStore && $sponsorBlockUrlStore !== '') {
|
||||
const sponsorBlock = new SponsorBlock('', { baseURL: $sponsorBlockUrlStore });
|
||||
const sponsorBlockCategoryKeys = Object.keys($sponsorBlockCategoriesStore);
|
||||
|
||||
try {
|
||||
segments = await sponsorBlock.getSegments(
|
||||
data.video.videoId,
|
||||
Object.keys($sponsorBlockCategoriesStore) as Category[]
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Sponsorskip errored with:', error);
|
||||
}
|
||||
if (sponsorBlockCategoryKeys.length === 0) return;
|
||||
|
||||
if (segments.length === 0) return;
|
||||
|
||||
playerElement?.addEventListener('timeupdate', () => {
|
||||
segments.forEach((segment) => {
|
||||
if (!playerElement) return;
|
||||
|
||||
if (
|
||||
playerElement.currentTime >= segment.startTime &&
|
||||
playerElement.currentTime <= segment.endTime
|
||||
) {
|
||||
const segmentTrigger = $sponsorBlockCategoriesStore[segment.category];
|
||||
|
||||
if (segmentTrigger === 'automatic') {
|
||||
skipSegment(segment);
|
||||
} else if (segmentTrigger === 'manual') {
|
||||
if (!segmentManualSkip) {
|
||||
addToast({
|
||||
data: {
|
||||
text: `${$_('upcomingSegment')} ${segment.category}`,
|
||||
action: {
|
||||
action: () => skipSegment(segment),
|
||||
text: $_('skip')
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
segmentManualSkip = segment;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
try {
|
||||
segments = await sponsorBlock.getSegments(
|
||||
data.video.videoId,
|
||||
sponsorBlockCategoryKeys as Category[]
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Sponsorskip errored with:', error);
|
||||
}
|
||||
|
||||
if (segments.length === 0) return;
|
||||
|
||||
playerElement?.addEventListener('timeupdate', () => {
|
||||
segments.forEach((segment) => {
|
||||
if (!playerElement) return;
|
||||
|
||||
if (
|
||||
playerElement.currentTime >= segment.startTime &&
|
||||
playerElement.currentTime <= segment.endTime
|
||||
) {
|
||||
const segmentTrigger = $sponsorBlockCategoriesStore[segment.category];
|
||||
|
||||
if (segmentTrigger === 'automatic') {
|
||||
skipSegment(segment);
|
||||
} else if (segmentTrigger === 'manual') {
|
||||
if (!segmentManualSkip) {
|
||||
addToast({
|
||||
data: {
|
||||
text: `${$_('upcomingSegment')} ${segment.category}`,
|
||||
action: {
|
||||
action: () => skipSegment(segment),
|
||||
text: $_('skip')
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
segmentManualSkip = segment;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function loadTimeFromUrl(page: Page): boolean {
|
||||
|
||||
@@ -52,7 +52,8 @@ import {
|
||||
playerAndroidLockOrientation,
|
||||
playerYouTubeJsFallback,
|
||||
playerYouTubeJsAlways,
|
||||
interfaceSearchHistoryEnabled
|
||||
interfaceSearchHistoryEnabled,
|
||||
playerPreferredVolume
|
||||
} from '$lib/store';
|
||||
import { isOwnBackend } from '$lib/shared';
|
||||
|
||||
@@ -66,6 +67,7 @@ type PersistedStore<T> = {
|
||||
|
||||
const zBoolean = z.coerce.boolean();
|
||||
const zNumber = z.coerce.number();
|
||||
const zFloat = z.coerce.number().min(0).max(1);
|
||||
const zString = z.string();
|
||||
const zArray = z.array(z.string());
|
||||
const zChapterMode = z.enum(['automatic', 'manual', 'timeline']);
|
||||
@@ -256,6 +258,11 @@ export const persistedStores: PersistedStore<any>[] = [
|
||||
store: playerMiniplayerEnabled,
|
||||
schema: zBoolean
|
||||
},
|
||||
{
|
||||
name: 'preferredVolume',
|
||||
store: playerPreferredVolume,
|
||||
schema: zFloat
|
||||
},
|
||||
{
|
||||
name: 'sponsorBlockCategoriesv2',
|
||||
store: sponsorBlockCategoriesStore,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { isUnrestrictedPlatform, timeout } from '$lib/misc';
|
||||
import { Capacitor } from '@capacitor/core';
|
||||
import sodium from 'libsodium-wrappers-sumo';
|
||||
import { isOwnBackend } from './shared';
|
||||
|
||||
export const originalFetch = window.fetch;
|
||||
const corsProxyUrl =
|
||||
@@ -62,6 +64,22 @@ export const fetchProxied = async (
|
||||
}
|
||||
}
|
||||
|
||||
if (isOwnBackend() && Capacitor.getPlatform() === 'web' && requestOptions?.body) {
|
||||
if (requestOptions.body instanceof ArrayBuffer) {
|
||||
requestOptions.body = new Uint8Array(requestOptions?.body);
|
||||
}
|
||||
|
||||
if (requestOptions.body instanceof Uint8Array) {
|
||||
await sodium.ready;
|
||||
requestOptions.body = sodium.to_base64(requestOptions.body);
|
||||
|
||||
requestOptions.headers = {
|
||||
...requestOptions.headers,
|
||||
__is_base64_encoded: 'true'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Use the original fetch with the proxied URL and options
|
||||
return originalFetch(requestInput, requestOptions);
|
||||
};
|
||||
|
||||
@@ -13,7 +13,6 @@ export class ImageCache {
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image();
|
||||
img.crossOrigin = 'anonymous';
|
||||
img.onload = () => {
|
||||
this.cache.set(src, img);
|
||||
resolve(img);
|
||||
|
||||
@@ -43,9 +43,6 @@ export async function injectSabr(
|
||||
contentCheckOk: true,
|
||||
racyCheckOk: true,
|
||||
playbackContext: {
|
||||
adPlaybackContext: {
|
||||
pyv: true
|
||||
},
|
||||
contentPlaybackContext: {
|
||||
signatureTimestamp: video.ytjs.innertube.session.player?.signature_timestamp
|
||||
},
|
||||
|
||||
@@ -179,6 +179,11 @@ export const playerAndroidPauseOnNetworkChange = persist(
|
||||
createStorage(),
|
||||
'pauseOnNetworkChange'
|
||||
);
|
||||
export const playerPreferredVolume: Writable<number> = persist(
|
||||
writable(0.5),
|
||||
createStorage(),
|
||||
'preferredVolume'
|
||||
);
|
||||
export const playerPlaylistHistory: Writable<string[]> = writable([]);
|
||||
|
||||
export interface PlayerState {
|
||||
|
||||
@@ -3,6 +3,8 @@ import { env } from '$env/dynamic/public';
|
||||
|
||||
import { error } from '@sveltejs/kit';
|
||||
import { parse as tldParse } from 'tldts';
|
||||
import { USER_AGENT } from 'bgutils-js';
|
||||
import sodium from 'libsodium-wrappers-sumo';
|
||||
|
||||
const allowedBaseDomains: string[] = [
|
||||
'youtube.com',
|
||||
@@ -30,6 +32,13 @@ for (const dynamicDomain of dynamicAllowDomainsEnvVars) {
|
||||
}
|
||||
}
|
||||
|
||||
function copyHeader(headerName: string, to: Headers, from: Headers) {
|
||||
const hdrVal = from.get(headerName);
|
||||
if (hdrVal) {
|
||||
to.set(headerName, hdrVal);
|
||||
}
|
||||
}
|
||||
|
||||
async function proxyRequest(
|
||||
request: Request,
|
||||
urlToProxy: string,
|
||||
@@ -71,49 +80,38 @@ async function proxyRequest(
|
||||
}
|
||||
}
|
||||
|
||||
if (urlToProxyObj.pathname.includes('v1/player')) {
|
||||
urlToProxyObj.searchParams.set(
|
||||
'$fields',
|
||||
'playerConfig,captions,playabilityStatus,streamingData,responseContext.mainAppWebResponseContext.datasyncId,videoDetails.isLive,videoDetails.isLiveContent,videoDetails.title,videoDetails.author,playbackTracking'
|
||||
);
|
||||
}
|
||||
|
||||
const requestHeaders = request.headers;
|
||||
const requestHeaders = new Headers();
|
||||
requestHeaders.set('host', urlToProxyObj.host);
|
||||
requestHeaders.set('origin', urlToProxyObj.origin);
|
||||
requestHeaders.set('user-agent', USER_AGENT);
|
||||
|
||||
for (const key of [
|
||||
'referer',
|
||||
'x-forwarded-for',
|
||||
'x-requested-with',
|
||||
'sec-ch-ua-mobile',
|
||||
'sec-ch-ua',
|
||||
'sec-ch-ua-platform'
|
||||
]) {
|
||||
requestHeaders.delete(key);
|
||||
}
|
||||
copyHeader('range', requestHeaders, request.headers);
|
||||
|
||||
const cookieHeader = requestHeaders.get('cookie');
|
||||
if (cookieHeader) {
|
||||
const modifiedCookies = cookieHeader
|
||||
.split(';')
|
||||
.filter((cookie) => !cookie.trim().startsWith('userid='))
|
||||
.join('; ');
|
||||
requestHeaders.set('cookie', modifiedCookies);
|
||||
}
|
||||
|
||||
const fetchRes = await fetch(urlToProxy.toString(), {
|
||||
const requestOptions: RequestInit = {
|
||||
method: request.method,
|
||||
headers: requestHeaders,
|
||||
body: request.body,
|
||||
credentials: 'same-origin',
|
||||
...(request.body ? { duplex: 'half' } : {})
|
||||
});
|
||||
};
|
||||
|
||||
return new Response(fetchRes.body, {
|
||||
status: fetchRes.status,
|
||||
statusText: fetchRes.statusText,
|
||||
...(request.body ? { duplex: 'half' } : {})
|
||||
let body;
|
||||
if (request.body && request.headers.has('__is_base64_encoded')) {
|
||||
request.headers.delete('__is_base64_encoded');
|
||||
|
||||
await sodium.ready;
|
||||
body = Uint8Array.from(sodium.from_base64(await request.text()));
|
||||
} else {
|
||||
body = request.body;
|
||||
}
|
||||
|
||||
const response = await fetch(urlToProxy, { ...requestOptions, body });
|
||||
|
||||
const responseHeaders = new Headers();
|
||||
const responseBody = await response.blob();
|
||||
|
||||
return new Response(responseBody, {
|
||||
status: response.status,
|
||||
headers: responseHeaders
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
+1
-1
@@ -3,7 +3,7 @@ import os
|
||||
import re
|
||||
from datetime import datetime
|
||||
|
||||
LATEST_VERSION = "1.15.4"
|
||||
LATEST_VERSION = "1.15.5"
|
||||
RELEASE_DATE = datetime.now().strftime("%Y-%-m-%d") # Format: YYYY-M-D
|
||||
|
||||
WORKING_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "materialious")
|
||||
|
||||
Reference in New Issue
Block a user