Merge pull request #1040 from Materialious/update/1.9.22

Update/1.9.22
This commit is contained in:
Ward
2025-07-26 20:09:56 +12:00
committed by GitHub
23 changed files with 439 additions and 342 deletions
+2 -2
View File
@@ -7,8 +7,8 @@ android {
applicationId "us.materialio.app"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 138
versionName "1.9.21"
versionCode 139
versionName "1.9.22"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions {
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
@@ -55,7 +55,11 @@
<content_attribute id="social-contacts">intense</content_attribute>
</content_rating>
<releases>
<release version="1.9.21" date="2025-7-21">
<release version="1.9.22" date="2025-7-26">
<url>https://github.com/Materialious/Materialious/releases/tag/1.9.22</url>
</release>
<release version="1.9.21" date="2025-7-21">
<url>https://github.com/Materialious/Materialious/releases/tag/1.9.21</url>
</release>
<release version="1.9.20" date="2025-7-09">
+10 -9
View File
@@ -1,12 +1,12 @@
{
"name": "Materialious",
"version": "1.9.21",
"version": "1.9.22",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "Materialious",
"version": "1.9.21",
"version": "1.9.22",
"license": "MIT",
"dependencies": {
"@capacitor-community/electron": "^5.0.0",
@@ -22,7 +22,7 @@
"jsonfile": "^6.1.0"
},
"devDependencies": {
"electron": "^37.2.1",
"electron": "^37.2.3",
"electron-builder": "^26.0.12",
"electron-rebuild": "^3.2.9",
"typescript": "^5.7.3"
@@ -2495,9 +2495,9 @@
}
},
"node_modules/electron": {
"version": "37.2.1",
"resolved": "https://registry.npmjs.org/electron/-/electron-37.2.1.tgz",
"integrity": "sha512-ae2EbzRNqIAHlftfCHtbbt6EgJUW8+zxWLONqNnn2iSrLF0O/pbxbff3xcpZYPpmFBs4uqjoi+s4QS7DQ+zZ/w==",
"version": "37.2.3",
"resolved": "https://registry.npmjs.org/electron/-/electron-37.2.3.tgz",
"integrity": "sha512-JRKKn8cRDXDfkC+oWISbYs+c+L6RA776JM0NiB9bn2yV8H/LnBUlVPzKKfsXgrUIokN4YcbCw694vfAdEJwtGw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
@@ -3093,15 +3093,16 @@
}
},
"node_modules/form-data": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
"integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
"dev": true,
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.2",
"mime-types": "^2.1.12"
},
"engines": {
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "Materialious",
"version": "1.9.21",
"version": "1.9.22",
"description": "Modern material design for Invidious.",
"author": {
"name": "Ward Pearce",
@@ -35,7 +35,7 @@
"jsonfile": "^6.1.0"
},
"devDependencies": {
"electron": "^37.2.1",
"electron": "^37.2.3",
"electron-builder": "^26.0.12",
"electron-rebuild": "^3.2.9",
"typescript": "^5.7.3"
@@ -44,4 +44,4 @@
"capacitor",
"electron"
]
}
}
+2 -16
View File
@@ -3,8 +3,7 @@ import {
getCapacitorElectronConfig,
setupElectronDeepLinking
} from '@capacitor-community/electron';
import type { MenuItemConstructorOptions } from 'electron';
import { app, ipcMain, MenuItem, session } from 'electron';
import { app, ipcMain, session } from 'electron';
import electronIsDev from 'electron-is-dev';
import unhandled from 'electron-unhandled';
import { autoUpdater } from 'electron-updater';
@@ -16,25 +15,12 @@ import { ElectronCapacitorApp, setupContentSecurityPolicy, setupReloadWatcher }
// 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 appMenuBarMenuTemplate: (MenuItemConstructorOptions | MenuItem)[] = [
{ role: process.platform === 'darwin' ? 'appMenu' : 'fileMenu' },
{ role: 'viewMenu' }
];
// Get Config options from capacitor.config
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);
// If deeplinking is enabled then we will set it up here.
if (capacitorFileConfig.electron?.deepLinkingEnabled) {
+217 -193
View File
@@ -1,13 +1,23 @@
import type { CapacitorElectronConfig } from '@capacitor-community/electron';
import {
CapacitorSplashScreen,
CapElectronEventEmitter,
setupCapacitorElectronPlugins,
CapacitorSplashScreen,
CapElectronEventEmitter,
setupCapacitorElectronPlugins
} from '@capacitor-community/electron';
import { USER_AGENT } from 'bgutils-js';
import chokidar from 'chokidar';
import type { MenuItemConstructorOptions } from 'electron';
import { app, BrowserWindow, Menu, MenuItem, nativeImage, session, shell, Tray } from 'electron';
import {
app,
BrowserWindow,
Menu,
MenuItem,
nativeImage,
session,
shell,
Tray,
globalShortcut
} from 'electron';
import electronIsDev from 'electron-is-dev';
import electronServe from 'electron-serve';
import windowStateKeeper from 'electron-window-state';
@@ -15,222 +25,236 @@ import { join } from 'path';
// Define components for a watcher to detect when the webapp is changed so we can reload in Dev mode.
const reloadWatcher = {
debouncer: null,
ready: false,
watcher: null,
debouncer: null,
ready: false,
watcher: null
};
export function setupReloadWatcher(electronCapacitorApp: ElectronCapacitorApp): void {
reloadWatcher.watcher = chokidar
.watch(join(app.getAppPath(), 'app'), {
ignored: /[/\\]\./,
persistent: true,
})
.on('ready', () => {
reloadWatcher.ready = true;
})
.on('all', (_event, _path) => {
if (reloadWatcher.ready) {
clearTimeout(reloadWatcher.debouncer);
reloadWatcher.debouncer = setTimeout(async () => {
electronCapacitorApp.getMainWindow().webContents.reload();
reloadWatcher.ready = false;
clearTimeout(reloadWatcher.debouncer);
reloadWatcher.debouncer = null;
reloadWatcher.watcher = null;
setupReloadWatcher(electronCapacitorApp);
}, 1500);
}
});
reloadWatcher.watcher = chokidar
.watch(join(app.getAppPath(), 'app'), {
ignored: /[/\\]\./,
persistent: true
})
.on('ready', () => {
reloadWatcher.ready = true;
})
.on('all', (_event, _path) => {
if (reloadWatcher.ready) {
clearTimeout(reloadWatcher.debouncer);
reloadWatcher.debouncer = setTimeout(async () => {
electronCapacitorApp.getMainWindow().webContents.reload();
reloadWatcher.ready = false;
clearTimeout(reloadWatcher.debouncer);
reloadWatcher.debouncer = null;
reloadWatcher.watcher = null;
setupReloadWatcher(electronCapacitorApp);
}, 1500);
}
});
}
// Define our class to manage our app.
export class ElectronCapacitorApp {
private MainWindow: BrowserWindow | null = null;
private SplashScreen: CapacitorSplashScreen | null = null;
private TrayIcon: Tray | null = null;
private CapacitorFileConfig: CapacitorElectronConfig;
private TrayMenuTemplate: (MenuItem | MenuItemConstructorOptions)[] = [
new MenuItem({ label: 'Quit App', role: 'quit' }),
];
private AppMenuBarMenuTemplate: (MenuItem | MenuItemConstructorOptions)[] = [
{ role: process.platform === 'darwin' ? 'appMenu' : 'fileMenu' },
{ role: 'viewMenu' },
];
private mainWindowState;
private loadWebApp;
private customScheme: string;
private MainWindow: BrowserWindow | null = null;
private SplashScreen: CapacitorSplashScreen | null = null;
private TrayIcon: Tray | null = null;
private CapacitorFileConfig: CapacitorElectronConfig;
private TrayMenuTemplate: (MenuItem | MenuItemConstructorOptions)[] = [
new MenuItem({ label: 'Quit App', role: 'quit' })
];
private mainWindowState;
private loadWebApp;
private customScheme: string;
constructor(
capacitorFileConfig: CapacitorElectronConfig,
trayMenuTemplate?: (MenuItemConstructorOptions | MenuItem)[],
appMenuBarMenuTemplate?: (MenuItemConstructorOptions | MenuItem)[]
) {
this.CapacitorFileConfig = capacitorFileConfig;
constructor(
capacitorFileConfig: CapacitorElectronConfig,
trayMenuTemplate?: (MenuItemConstructorOptions | MenuItem)[]
) {
this.CapacitorFileConfig = capacitorFileConfig;
this.customScheme = this.CapacitorFileConfig.electron?.customUrlScheme ?? 'capacitor-electron';
this.customScheme = this.CapacitorFileConfig.electron?.customUrlScheme ?? 'capacitor-electron';
if (trayMenuTemplate) {
this.TrayMenuTemplate = trayMenuTemplate;
}
if (trayMenuTemplate) {
this.TrayMenuTemplate = trayMenuTemplate;
}
if (appMenuBarMenuTemplate) {
this.AppMenuBarMenuTemplate = appMenuBarMenuTemplate;
}
// Setup our web app loader, this lets us load apps like react, vue, and angular without changing their build chains.
this.loadWebApp = electronServe({
directory: join(app.getAppPath(), 'app'),
scheme: this.customScheme
});
}
// Setup our web app loader, this lets us load apps like react, vue, and angular without changing their build chains.
this.loadWebApp = electronServe({
directory: join(app.getAppPath(), 'app'),
scheme: this.customScheme,
});
}
// Helper function to load in the app.
private async loadMainWindow(thisRef: any) {
await thisRef.loadWebApp(thisRef.MainWindow);
}
// Helper function to load in the app.
private async loadMainWindow(thisRef: any) {
await thisRef.loadWebApp(thisRef.MainWindow);
}
// Expose the mainWindow ref for use outside of the class.
getMainWindow(): BrowserWindow {
return this.MainWindow;
}
// Expose the mainWindow ref for use outside of the class.
getMainWindow(): BrowserWindow {
return this.MainWindow;
}
getCustomURLScheme(): string {
return this.customScheme;
}
getCustomURLScheme(): string {
return this.customScheme;
}
async init(): Promise<void> {
const icon = nativeImage.createFromPath(
join(app.getAppPath(), 'assets', process.platform === 'win32' ? 'appIcon.ico' : 'appIcon.png')
);
this.mainWindowState = windowStateKeeper({
defaultWidth: 1000,
defaultHeight: 800
});
// Setup preload script path and construct our main window.
const preloadPath = join(app.getAppPath(), 'build', 'src', 'preload.js');
this.MainWindow = new BrowserWindow({
icon,
show: false,
x: this.mainWindowState.x,
y: this.mainWindowState.y,
width: this.mainWindowState.width,
height: this.mainWindowState.height,
webPreferences: {
nodeIntegration: true,
contextIsolation: true,
webSecurity: false,
// Use preload to inject the electron varriant overrides for capacitor plugins.
// preload: join(app.getAppPath(), "node_modules", "@capacitor-community", "electron", "dist", "runtime", "electron-rt.js"),
preload: preloadPath
}
});
this.mainWindowState.manage(this.MainWindow);
async init(): Promise<void> {
const icon = nativeImage.createFromPath(
join(app.getAppPath(), 'assets', process.platform === 'win32' ? 'appIcon.ico' : 'appIcon.png')
);
this.mainWindowState = windowStateKeeper({
defaultWidth: 1000,
defaultHeight: 800,
});
// Setup preload script path and construct our main window.
const preloadPath = join(app.getAppPath(), 'build', 'src', 'preload.js');
this.MainWindow = new BrowserWindow({
icon,
show: false,
x: this.mainWindowState.x,
y: this.mainWindowState.y,
width: this.mainWindowState.width,
height: this.mainWindowState.height,
webPreferences: {
nodeIntegration: true,
contextIsolation: true,
webSecurity: false,
// Use preload to inject the electron varriant overrides for capacitor plugins.
// preload: join(app.getAppPath(), "node_modules", "@capacitor-community", "electron", "dist", "runtime", "electron-rt.js"),
preload: preloadPath,
},
});
this.mainWindowState.manage(this.MainWindow);
if (this.CapacitorFileConfig.backgroundColor) {
this.MainWindow.setBackgroundColor(this.CapacitorFileConfig.electron.backgroundColor);
}
if (this.CapacitorFileConfig.backgroundColor) {
this.MainWindow.setBackgroundColor(this.CapacitorFileConfig.electron.backgroundColor);
}
// If we close the main window with the splashscreen enabled we need to destory the ref.
this.MainWindow.on('closed', () => {
if (
this.SplashScreen?.getSplashWindow() &&
!this.SplashScreen.getSplashWindow().isDestroyed()
) {
this.SplashScreen.getSplashWindow().close();
}
});
// If we close the main window with the splashscreen enabled we need to destory the ref.
this.MainWindow.on('closed', () => {
if (this.SplashScreen?.getSplashWindow() && !this.SplashScreen.getSplashWindow().isDestroyed()) {
this.SplashScreen.getSplashWindow().close();
}
});
// When the tray icon is enabled, setup the options.
if (this.CapacitorFileConfig.electron?.trayIconAndMenuEnabled) {
this.TrayIcon = new Tray(icon);
this.TrayIcon.on('double-click', () => {
if (this.MainWindow) {
if (this.MainWindow.isVisible()) {
this.MainWindow.hide();
} else {
this.MainWindow.show();
this.MainWindow.focus();
}
}
});
this.TrayIcon.on('click', () => {
if (this.MainWindow) {
if (this.MainWindow.isVisible()) {
this.MainWindow.hide();
} else {
this.MainWindow.show();
this.MainWindow.focus();
}
}
});
this.TrayIcon.setToolTip(app.getName());
this.TrayIcon.setContextMenu(Menu.buildFromTemplate(this.TrayMenuTemplate));
}
// When the tray icon is enabled, setup the options.
if (this.CapacitorFileConfig.electron?.trayIconAndMenuEnabled) {
this.TrayIcon = new Tray(icon);
this.TrayIcon.on('double-click', () => {
if (this.MainWindow) {
if (this.MainWindow.isVisible()) {
this.MainWindow.hide();
} else {
this.MainWindow.show();
this.MainWindow.focus();
}
}
});
this.TrayIcon.on('click', () => {
if (this.MainWindow) {
if (this.MainWindow.isVisible()) {
this.MainWindow.hide();
} else {
this.MainWindow.show();
this.MainWindow.focus();
}
}
});
this.TrayIcon.setToolTip(app.getName());
this.TrayIcon.setContextMenu(Menu.buildFromTemplate(this.TrayMenuTemplate));
}
// Disable Menu Bar at the top.
Menu.setApplicationMenu(null);
// Setup the main manu bar at the top of our window.
Menu.setApplicationMenu(Menu.buildFromTemplate(this.AppMenuBarMenuTemplate));
// If the splashscreen is enabled, show it first while the main window loads then switch it out for the main window, or just load the main window from the start.
if (this.CapacitorFileConfig.electron?.splashScreenEnabled) {
this.SplashScreen = new CapacitorSplashScreen({
imageFilePath: join(
app.getAppPath(),
'assets',
this.CapacitorFileConfig.electron?.splashScreenImageName ?? 'splash.png'
),
windowWidth: 400,
windowHeight: 400
});
this.SplashScreen.init(this.loadMainWindow, this);
} else {
this.loadMainWindow(this);
}
// If the splashscreen is enabled, show it first while the main window loads then switch it out for the main window, or just load the main window from the start.
if (this.CapacitorFileConfig.electron?.splashScreenEnabled) {
this.SplashScreen = new CapacitorSplashScreen({
imageFilePath: join(
app.getAppPath(),
'assets',
this.CapacitorFileConfig.electron?.splashScreenImageName ?? 'splash.png'
),
windowWidth: 400,
windowHeight: 400,
});
this.SplashScreen.init(this.loadMainWindow, this);
} else {
this.loadMainWindow(this);
}
globalShortcut.register('Control+Shift+I', () => {
this.MainWindow.webContents.toggleDevTools();
});
// Security
this.MainWindow.webContents.setWindowOpenHandler((details) => {
shell.openExternal(details.url);
return { action: 'deny' };
});
this.MainWindow.webContents.on('will-navigate', (event, _newURL) => {
if (!this.MainWindow.webContents.getURL().includes(this.customScheme)) {
event.preventDefault();
}
});
// Link electron plugins into the system.
setupCapacitorElectronPlugins();
let zoomFactor = 1.0;
globalShortcut.register('Control+Plus', () => {
zoomFactor += 0.1;
this.MainWindow.webContents.setZoomFactor(zoomFactor);
});
// When the web app is loaded we hide the splashscreen if needed and show the mainwindow.
this.MainWindow.webContents.on('dom-ready', () => {
if (this.CapacitorFileConfig.electron?.splashScreenEnabled) {
this.SplashScreen.getSplashWindow().hide();
}
if (!this.CapacitorFileConfig.electron?.hideMainWindowOnLaunch) {
this.MainWindow.show();
}
setTimeout(() => {
if (electronIsDev) {
this.MainWindow.webContents.openDevTools();
}
CapElectronEventEmitter.emit('CAPELECTRON_DeeplinkListenerInitialized', '');
}, 400);
});
}
globalShortcut.register('Control+Minus', () => {
zoomFactor = Math.max(0.2, zoomFactor - 0.1);
this.MainWindow.webContents.setZoomFactor(zoomFactor);
});
globalShortcut.register('Control+0', () => {
zoomFactor = 1.0;
this.MainWindow.webContents.setZoomFactor(zoomFactor);
});
// Security
this.MainWindow.webContents.setWindowOpenHandler((details) => {
shell.openExternal(details.url);
return { action: 'deny' };
});
this.MainWindow.webContents.on('will-navigate', (event, _newURL) => {
if (!this.MainWindow.webContents.getURL().includes(this.customScheme)) {
event.preventDefault();
}
});
// Link electron plugins into the system.
setupCapacitorElectronPlugins();
// When the web app is loaded we hide the splashscreen if needed and show the mainwindow.
this.MainWindow.webContents.on('dom-ready', () => {
if (this.CapacitorFileConfig.electron?.splashScreenEnabled) {
this.SplashScreen.getSplashWindow().hide();
}
if (!this.CapacitorFileConfig.electron?.hideMainWindowOnLaunch) {
this.MainWindow.show();
}
setTimeout(() => {
if (electronIsDev) {
this.MainWindow.webContents.openDevTools();
}
CapElectronEventEmitter.emit('CAPELECTRON_DeeplinkListenerInitialized', '');
}, 400);
});
}
}
// Set a CSP up for our application based on the custom scheme
export function setupContentSecurityPolicy(customScheme: string): void {
session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
callback({
responseHeaders: {
...details.responseHeaders,
},
});
});
session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
callback({
responseHeaders: {
...details.responseHeaders
}
});
});
session.defaultSession.webRequest.onBeforeSendHeaders((details, callback) => {
const uri = new URL(details.url);
session.defaultSession.webRequest.onBeforeSendHeaders((details, callback) => {
const uri = new URL(details.url);
details.requestHeaders['User-Agent'] = USER_AGENT;
details.requestHeaders['origin'] = uri.origin;
details.requestHeaders['host'] = uri.host;
details.requestHeaders['User-Agent'] = USER_AGENT;
details.requestHeaders['origin'] = uri.origin;
details.requestHeaders['host'] = uri.host;
callback({ requestHeaders: details.requestHeaders });
});
callback({ requestHeaders: details.requestHeaders });
});
}
+117 -61
View File
@@ -55,7 +55,7 @@
"@vite-pwa/sveltekit": "^0.6.6",
"eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-svelte": "^2.45.1",
"eslint-plugin-svelte": "^3.11.0",
"patch-package": "^8.0.0",
"prettier": "^3.3.3",
"prettier-plugin-svelte": "^3.2.7",
@@ -3598,9 +3598,9 @@
"license": "BSD-2-Clause"
},
"node_modules/acorn": {
"version": "8.14.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
"integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
"version": "8.15.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"license": "MIT",
"bin": {
"acorn": "bin/acorn"
@@ -5699,22 +5699,6 @@
"url": "https://opencollective.com/eslint"
}
},
"node_modules/eslint-compat-utils": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.5.1.tgz",
"integrity": "sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"semver": "^7.5.4"
},
"engines": {
"node": ">=12"
},
"peerDependencies": {
"eslint": ">=6.0.0"
}
},
"node_modules/eslint-config-prettier": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz",
@@ -5729,32 +5713,31 @@
}
},
"node_modules/eslint-plugin-svelte": {
"version": "2.46.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-2.46.1.tgz",
"integrity": "sha512-7xYr2o4NID/f9OEYMqxsEQsCsj4KaMy4q5sANaKkAb6/QeCjYFxRmDm2S3YC3A3pl1kyPZ/syOx/i7LcWYSbIw==",
"version": "3.11.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-3.11.0.tgz",
"integrity": "sha512-KliWlkieHyEa65aQIkRwUFfHzT5Cn4u3BQQsu3KlkJOs7c1u7ryn84EWaOjEzilbKgttT4OfBURA8Uc4JBSQIw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
"@jridgewell/sourcemap-codec": "^1.4.15",
"eslint-compat-utils": "^0.5.1",
"@eslint-community/eslint-utils": "^4.6.1",
"@jridgewell/sourcemap-codec": "^1.5.0",
"esutils": "^2.0.3",
"known-css-properties": "^0.35.0",
"postcss": "^8.4.38",
"globals": "^16.0.0",
"known-css-properties": "^0.37.0",
"postcss": "^8.4.49",
"postcss-load-config": "^3.1.4",
"postcss-safe-parser": "^6.0.0",
"postcss-selector-parser": "^6.1.0",
"semver": "^7.6.2",
"svelte-eslint-parser": "^0.43.0"
"postcss-safe-parser": "^7.0.0",
"semver": "^7.6.3",
"svelte-eslint-parser": "^1.3.0"
},
"engines": {
"node": "^14.17.0 || >=16.0.0"
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"url": "https://github.com/sponsors/ota-meshi"
},
"peerDependencies": {
"eslint": "^7.0.0 || ^8.0.0-0 || ^9.0.0-0",
"eslint": "^8.57.1 || ^9.0.0",
"svelte": "^3.37.0 || ^4.0.0 || ^5.0.0"
},
"peerDependenciesMeta": {
@@ -5763,6 +5746,19 @@
}
}
},
"node_modules/eslint-plugin-svelte/node_modules/globals": {
"version": "16.3.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-16.3.0.tgz",
"integrity": "sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/eslint-scope": {
"version": "7.2.2",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
@@ -6225,14 +6221,15 @@
}
},
"node_modules/form-data": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
"integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.2",
"mime-types": "^2.1.12"
},
"engines": {
@@ -7966,9 +7963,9 @@
}
},
"node_modules/known-css-properties": {
"version": "0.35.0",
"resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.35.0.tgz",
"integrity": "sha512-a/RAk2BfKk+WFGhhOCAYqSiFLc34k8Mt/6NWRI4joER0EYUzXIcFivjjnoD3+XU1DggLn/tZc3DOAgke7l8a4A==",
"version": "0.37.0",
"resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.37.0.tgz",
"integrity": "sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ==",
"dev": true,
"license": "MIT"
},
@@ -9438,20 +9435,30 @@
}
},
"node_modules/postcss-safe-parser": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-6.0.0.tgz",
"integrity": "sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==",
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-7.0.1.tgz",
"integrity": "sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==",
"dev": true,
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/postcss-safe-parser"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"engines": {
"node": ">=12.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
"node": ">=18.0"
},
"peerDependencies": {
"postcss": "^8.3.3"
"postcss": "^8.4.31"
}
},
"node_modules/postcss-scss": {
@@ -9482,9 +9489,9 @@
}
},
"node_modules/postcss-selector-parser": {
"version": "6.1.2",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
"integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz",
"integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -11469,20 +11476,21 @@
}
},
"node_modules/svelte-eslint-parser": {
"version": "0.43.0",
"resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-0.43.0.tgz",
"integrity": "sha512-GpU52uPKKcVnh8tKN5P4UZpJ/fUDndmq7wfsvoVXsyP+aY0anol7Yqo01fyrlaWGMFfm4av5DyrjlaXdLRJvGA==",
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-1.3.0.tgz",
"integrity": "sha512-VCgMHKV7UtOGcGLGNFSbmdm6kEKjtzo5nnpGU/mnx4OsFY6bZ7QwRF5DUx+Hokw5Lvdyo8dpk8B1m8mliomrNg==",
"dev": true,
"license": "MIT",
"dependencies": {
"eslint-scope": "^7.2.2",
"eslint-visitor-keys": "^3.4.3",
"espree": "^9.6.1",
"postcss": "^8.4.39",
"postcss-scss": "^4.0.9"
"eslint-scope": "^8.2.0",
"eslint-visitor-keys": "^4.0.0",
"espree": "^10.0.0",
"postcss": "^8.4.49",
"postcss-scss": "^4.0.9",
"postcss-selector-parser": "^7.0.0"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"url": "https://github.com/sponsors/ota-meshi"
@@ -11496,6 +11504,54 @@
}
}
},
"node_modules/svelte-eslint-parser/node_modules/eslint-scope": {
"version": "8.4.0",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
"integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"esrecurse": "^4.3.0",
"estraverse": "^5.2.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
"node_modules/svelte-eslint-parser/node_modules/eslint-visitor-keys": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
"integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
"node_modules/svelte-eslint-parser/node_modules/espree": {
"version": "10.4.0",
"resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
"integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"acorn": "^8.15.0",
"acorn-jsx": "^5.3.2",
"eslint-visitor-keys": "^4.2.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
"node_modules/svelte-infinite-loading": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/svelte-infinite-loading/-/svelte-infinite-loading-1.4.0.tgz",
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "materialious",
"version": "1.9.21",
"version": "1.9.22",
"private": true,
"scripts": {
"dev": "vite dev",
@@ -28,7 +28,7 @@
"@vite-pwa/sveltekit": "^0.6.6",
"eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-svelte": "^2.45.1",
"eslint-plugin-svelte": "^3.11.0",
"patch-package": "^8.0.0",
"prettier": "^3.3.3",
"prettier-plugin-svelte": "^3.2.7",
@@ -70,4 +70,4 @@
"svelte-persisted-store": "^0.12.0",
"youtubei.js": "^15.0.0"
}
}
}
+1 -1
View File
@@ -100,7 +100,7 @@ export interface VideoPlay extends Video {
genre: string;
genreUrl: string;
hlsUrl?: string;
dashUrl: string;
dashUrl?: string;
adaptiveFormats: AdaptiveFormats[];
formatStreams: FormatStreams[];
recommendedVideos: VideoBase[];
@@ -0,0 +1,21 @@
<script lang="ts">
import { page } from '$app/state';
</script>
<div class="space"></div>
<div
style="display: flex;align-items: center;flex-direction: column;text-align: center;padding: 90px;"
>
<svg xmlns="http://www.w3.org/2000/svg" height="200" viewBox="0 -960 960 960" width="200"
><path
d="M480-420q-68 0-123.5 38.5T276-280h408q-25-63-80.5-101.5T480-420Zm-168-60 44-42 42 42 42-42-42-42 42-44-42-42-42 42-44-42-42 42 42 44-42 42 42 42Zm250 0 42-42 44 42 42-42-42-42 42-44-42-42-44 42-42-42-42 42 42 44-42 42 42 42ZM480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-400Zm0 320q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Z"
/></svg
>
{#if page.error}
<article>
<code style="white-space: pre-line;word-wrap: break-word;">{page.error.message}</code>
</article>
{/if}
</div>
+23 -15
View File
@@ -8,7 +8,7 @@
import { ScreenOrientation, type ScreenOrientationResult } from '@capacitor/screen-orientation';
import { StatusBar, Style } from '@capacitor/status-bar';
import { NavigationBar } from '@hugotomazi/capacitor-navigation-bar';
import { type Page } from '@sveltejs/kit';
import { error, type Page } from '@sveltejs/kit';
import { HttpFetchPlugin } from '$lib/sabr/shakaHttpPlugin';
import ui from 'beercss';
import ISO6391 from 'iso-639-1';
@@ -385,7 +385,7 @@
watchProgressTimeout = setInterval(() => savePlayerPos(), 60000);
setupSponsorSkip();
let dashUrl: string = '';
let dashUrl: string;
// Due to CORs issues with redirects, hosted instances of Materialious
// dirctly provide the companion instance
@@ -393,6 +393,10 @@
if (import.meta.env.VITE_DEFAULT_COMPANION_INSTANCE && Capacitor.getPlatform() === 'web') {
dashUrl = `${import.meta.env.VITE_DEFAULT_COMPANION_INSTANCE}/api/manifest/dash/id/${data.video.videoId}`;
} else {
if (!data.video.dashUrl) {
error(500, 'No dash manifest found');
return;
}
dashUrl = data.video.dashUrl;
}
@@ -403,6 +407,10 @@
await player.load(dashUrl, await getLastPlayPos());
} else {
if (data.video.fallbackPatch === 'youtubejs') {
if (!data.video.dashUrl) {
error(500, 'No dash manifest found');
return;
}
await player.load(data.video.dashUrl);
} else {
await player.load(data.video.hlsUrl + '?local=true');
@@ -590,22 +598,22 @@
playerElement.playbackRate = playerElement.playbackRate + 0.25;
return false;
});
Mousetrap.bind(',', () => {
if (!playerElement) return
const currentTrack = player.getVariantTracks().find(track => track.active)
const frameTime = 1/(currentTrack?.frameRate || 30)
playerElement.currentTime -= frameTime
})
if (!playerElement) return;
const currentTrack = player.getVariantTracks().find((track) => track.active);
const frameTime = 1 / (currentTrack?.frameRate || 30);
playerElement.currentTime -= frameTime;
});
Mousetrap.bind('.', () => {
if (!playerElement) return
const currentTrack = player.getVariantTracks().find(track => track.active)
const frameTime = 1/(currentTrack?.frameRate || 30)
playerElement.currentTime += frameTime
})
if (!playerElement) return;
const currentTrack = player.getVariantTracks().find((track) => track.active);
const frameTime = 1 / (currentTrack?.frameRate || 30);
playerElement.currentTime += frameTime;
});
playerElement.addEventListener('pause', async () => {
savePlayerPos();
@@ -270,6 +270,7 @@
.video-title {
word-wrap: break-word;
overflow: hidden;
border-radius: 0px;
}
.thumbnail-details {
+2
View File
@@ -7,6 +7,7 @@ export const locale: Writable<string> = writable(defaultLocale);
export const _: Writable<(key: string, options?: any) => string> = writable(() => '');
const resources: Record<string, () => Promise<Record<string, any>>> = {
ar: () => import('./locales/ar.json'),
en: () => import('./locales/en.json'),
ru: () => import('./locales/ru.json'),
'zh-CN': () => import('./locales/zh-CN.json'),
@@ -14,6 +15,7 @@ const resources: Record<string, () => Promise<Record<string, any>>> = {
nl: () => import('./locales/nl.json'),
de: () => import('./locales/de.json'),
es: () => import('./locales/es.json'),
gsw: () => import('./locales/gsw.json'),
sh: () => import('./locales/sh.json'),
'pt-BR': () => import('./locales/pt-BR.json'),
lv: () => import('./locales/lv.json'),
+1 -1
View File
@@ -124,7 +124,7 @@
"previewVideoOnHover": "Video-Vorschau beim Hovern",
"expandDescription": "Beschreibung standardmäßig erweitern",
"expandComments": "Kommentare standardmäßig erweitern",
"disableAutoUpdate": "Automatische Installation von Updates deaktivieren",
"disableAutoUpdate": "Automatische Updates deaktivieren",
"dataPreferences": {
"content": "Du möchtest Abonnements importieren/exportieren, dein Passwort ändern oder dein Konto löschen? Klicke hier und scrolle bis zum Ende der Seite.",
"dataPreferences": "Dateneinstellungen"
@@ -1,6 +1,8 @@
{
"enabled": "Enabled",
"disabled": "Disabled",
"premium": "Premium YouTube content can't be watched on Materialious.",
"isUpcoming": "Video is not in a watchable state yet.",
"copyUrl": "Copy URL",
"loadMore": "Load more",
"views": "views",
+2 -1
View File
@@ -85,7 +85,8 @@
},
"syncPartyWarning": "Bitte denk dra, dass dini IP für die vo dir iigladne Benutzer sichtbar isch.",
"displayThumbnailAvatars": "Vorschaubild-Avatar (Langsam)",
"expandDescription": "Beschriibig standardmässig erwiitere"
"expandDescription": "Beschriibig standardmässig erwiitere",
"disableAutoUpdate": "Automatischi Updates deaktiviere"
},
"videos": "Videos",
"cancel": "Abbreche",
+3 -7
View File
@@ -7,7 +7,6 @@ import type {
VideoBase,
VideoPlay
} from '$lib/api/model';
import { numberWithCommas } from '$lib/numbers';
import { fromFormat } from '$lib/sabr/formatKeyUtils';
import { interfaceRegionStore, poTokenCacheStore } from '$lib/store';
import { Capacitor } from '@capacitor/core';
@@ -77,8 +76,6 @@ export async function patchYoutubeJs(videoId: string): Promise<VideoPlay> {
throw new Error('Unable to pull video info from youtube.js');
}
console.log(video);
let dashUri: string | undefined;
if (video.streaming_data) {
@@ -103,8 +100,6 @@ export async function patchYoutubeJs(videoId: string): Promise<VideoPlay> {
}
}
if (!dashUri) throw Error('Unable to find suitable dash manifest');
const descString = video.secondary_info.description?.toString() || '';
let authorThumbnails: Image[];
@@ -206,9 +201,10 @@ export async function patchYoutubeJs(videoId: string): Promise<VideoPlay> {
premiereTimestamp: 0,
hlsUrl: video.streaming_data?.hls_manifest_url || undefined,
liveNow: video.basic_info.is_live || false,
premium: false,
// @ts-expect-error Type does have offer_id
premium: video?.playability_status?.error_screen?.offer_id === 'sponsors_only_video',
storyboards: storyboard,
isUpcoming: false,
isUpcoming: video?.playability_status?.status !== 'OK',
videoId: videoId,
videoThumbnails: video.basic_info.thumbnail as Thumbnail[],
author: video.basic_info.author || 'Unknown',
+2 -2
View File
@@ -67,7 +67,7 @@ export const returnYTDislikesInstanceStore: Writable<string | null | undefined>
)
);
export const synciousStore = persisted('syncious', true);
export const synciousStore = persisted('syncious', false);
export const synciousInstanceStore: Writable<string | null | undefined> = persisted(
'synciousInstance',
platformDependentDefault(
@@ -92,7 +92,7 @@ export const interfaceDisplayThumbnailAvatars = persisted('disableThumbnailAvata
export const interfaceDefaultPage = persisted('defaultPage', '/');
export const interfaceSearchHistoryEnabled = persisted('searchHistoryEnabled', false);
export const interfaceAllowInsecureRequests = persisted('allowInsecureRequests', false);
export const interfaceDisableAutoUpdate = persisted('disableAutoUpdate', false)
export const interfaceDisableAutoUpdate = persisted('disableAutoUpdate', false);
export const sponsorBlockStore = persisted('sponsorBlock', true);
export const sponsorBlockUrlStore: Writable<string | null | undefined> = persisted(
+9
View File
@@ -16,6 +16,7 @@ import {
import { phaseDescription } from '$lib/timestamps';
import { error } from '@sveltejs/kit';
import { get } from 'svelte/store';
import { _ } from './i18n';
export async function getWatchDetails(videoId: string, url: URL) {
let video;
@@ -25,6 +26,14 @@ export async function getWatchDetails(videoId: string, url: URL) {
error(500, errorMessage);
}
if (video.premium) {
error(400, get(_)('premium'));
}
if (video.isUpcoming) {
error(400, get(_)('isUpcoming'));
}
let personalPlaylists;
if (get(authStore)) {
postHistory(video.videoId);
+2 -26
View File
@@ -1,29 +1,5 @@
<script lang="ts">
import { page } from '$app/stores';
import ErrorMsg from '$lib/components/ErrorMsg.svelte';
</script>
<div class="space"></div>
<div
style="display: flex;align-items: center;flex-direction: column;text-align: center;padding: 90px;"
>
<svg xmlns="http://www.w3.org/2000/svg" height="200" viewBox="0 -960 960 960" width="200"
><path
d="M480-420q-68 0-123.5 38.5T276-280h408q-25-63-80.5-101.5T480-420Zm-168-60 44-42 42 42 42-42-42-42 42-44-42-42-42 42-44-42-42 42 42 44-42 42 42 42Zm250 0 42-42 44 42 42-42-42-42 42-44-42-42-44 42-42-42-42 42 42 44-42 42 42 42ZM480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-400Zm0 320q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Z"
/></svg
>
<h2>Oh no! You've encountered an error.</h2>
<a
href="https://github.com/WardPearce/Materialious/issues/new"
class="link"
target="_blank"
rel="noopener noreferrer"><h3>Report it here</h3></a
>
{#if $page.error}
<article>
<code style="white-space: pre-line;word-wrap: break-word;">{$page.error.message}</code>
</article>
{/if}
</div>
<ErrorMsg />
@@ -0,0 +1,5 @@
<script lang="ts">
import ErrorMsg from '$lib/components/ErrorMsg.svelte';
</script>
<ErrorMsg />
+5
View File
@@ -0,0 +1,5 @@
<script lang="ts">
import ErrorMsg from '$lib/components/ErrorMsg.svelte';
</script>
<ErrorMsg />
+1 -1
View File
@@ -3,7 +3,7 @@ import os
import re
from datetime import datetime
LATEST_VERSION = "1.9.21"
LATEST_VERSION = "1.9.22"
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")