@@ -7,8 +7,8 @@ android {
|
||||
applicationId "us.materialio.app"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 215
|
||||
versionName "1.15.7"
|
||||
versionCode 216
|
||||
versionName "1.15.8"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
aaptOptions {
|
||||
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
||||
|
||||
@@ -80,7 +80,11 @@
|
||||
|
||||
|
||||
|
||||
<release version="1.15.7" date="2026-2-16">
|
||||
|
||||
<release version="1.15.8" date="2026-2-17">
|
||||
<url>https://github.com/Materialious/Materialious/releases/tag/1.15.8</url>
|
||||
</release>
|
||||
<release version="1.15.7" date="2026-2-16">
|
||||
<url>https://github.com/Materialious/Materialious/releases/tag/1.15.7</url>
|
||||
</release>
|
||||
<release version="1.15.6" date="2026-2-16">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Materialious",
|
||||
"version": "1.15.7",
|
||||
"version": "1.15.8",
|
||||
"description": "Modern material design for YouTube and Invidious.",
|
||||
"author": {
|
||||
"name": "Ward Pearce",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "materialious",
|
||||
"version": "1.15.7",
|
||||
"version": "1.15.8",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "npm run patch:github && vite dev",
|
||||
|
||||
@@ -71,13 +71,18 @@ export function setRegion(url: URL): URL {
|
||||
export async function fetchErrorHandle(response: Response): Promise<Response> {
|
||||
if (!response.ok) {
|
||||
let message = 'Internal error';
|
||||
|
||||
// Attempt to parse error.
|
||||
try {
|
||||
const json = await response.json();
|
||||
message = 'errorBacktrace' in json ? json.errorBacktrace : json.error;
|
||||
message = json.errorBacktrace || json.error || json.message;
|
||||
} catch {
|
||||
// Continue regardless of error
|
||||
}
|
||||
throw Error(message);
|
||||
|
||||
throw Error(
|
||||
`${response.status} - ${response.statusText}\n${decodeURIComponent(response.url)}\n${message}`
|
||||
);
|
||||
}
|
||||
|
||||
return response;
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
engineMaxConcurrentChannelsStore,
|
||||
rawMasterKeyStore
|
||||
} from '$lib/store';
|
||||
import { getSubscriptionsBackend, updateRSSLastUpdated } from '../backend';
|
||||
import { getSubscriptionsBackend } from '../backend';
|
||||
|
||||
export async function getSubscriptionsYTjs(): Promise<Subscription[]> {
|
||||
const subscriptions: Subscription[] = [];
|
||||
@@ -69,6 +69,8 @@ export async function parseChannelRSS(channelId: string): Promise<void> {
|
||||
const xmlDoc = parser.parseFromString(text, 'text/xml');
|
||||
const entries = xmlDoc.getElementsByTagName('entry');
|
||||
|
||||
let channelName: string | undefined;
|
||||
|
||||
for (const entry of entries) {
|
||||
const videoId = entry.getElementsByTagName('yt:videoId')[0]?.textContent || '';
|
||||
const title = entry.getElementsByTagName('title')[0]?.textContent || 'Untitled';
|
||||
@@ -80,7 +82,7 @@ export async function parseChannelRSS(channelId: string): Promise<void> {
|
||||
);
|
||||
const published = publishedAt.getTime();
|
||||
const publishedText = relativeTimestamp(published, false);
|
||||
const author =
|
||||
channelName =
|
||||
entry.getElementsByTagName('author')[0]?.getElementsByTagName('name')[0]?.textContent ||
|
||||
'Unknown Author';
|
||||
const authorId =
|
||||
@@ -115,7 +117,7 @@ export async function parseChannelRSS(channelId: string): Promise<void> {
|
||||
videoId,
|
||||
title,
|
||||
videoThumbnails,
|
||||
author,
|
||||
author: channelName,
|
||||
authorId,
|
||||
description,
|
||||
descriptionHtml,
|
||||
@@ -133,15 +135,29 @@ export async function parseChannelRSS(channelId: string): Promise<void> {
|
||||
} catch {
|
||||
// Continue regardless of error
|
||||
}
|
||||
|
||||
if (!get(rawMasterKeyStore)) {
|
||||
localDb.channelSubscriptions.where('channelId').equals(channelId).modify({
|
||||
lastRSSFetch: new Date()
|
||||
});
|
||||
} else {
|
||||
updateRSSLastUpdated(authorId);
|
||||
}
|
||||
}
|
||||
|
||||
updateLastRssFetch(channelId, channelName ?? 'Unknown');
|
||||
}
|
||||
|
||||
async function updateLastRssFetch(channelId: string, channelName: string) {
|
||||
localDb.channelSubscriptions
|
||||
.where('channelId')
|
||||
.equals(channelId)
|
||||
.first()
|
||||
.then((subscription) => {
|
||||
if (subscription) {
|
||||
localDb.channelSubscriptions.where('channelId').equals(channelId).modify({
|
||||
lastRSSFetch: new Date()
|
||||
});
|
||||
} else {
|
||||
localDb.channelSubscriptions.add({
|
||||
channelId: channelId,
|
||||
channelName: channelName,
|
||||
lastRSSFetch: new Date()
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function clearFeedYTjs() {
|
||||
@@ -163,7 +179,23 @@ export async function getFeedYTjs(maxResults: number, page: number): Promise<Fee
|
||||
|
||||
let totalChannelsToParse = 0;
|
||||
for (const channel of channelSubscriptions) {
|
||||
const lastRSSFetch = new Date(channel.lastRSSFetch);
|
||||
let lastRSSFetch = new Date(channel.lastRSSFetch);
|
||||
|
||||
// If using our own backend we still need to keep
|
||||
// RSS feed updates per device.
|
||||
if (get(rawMasterKeyStore)) {
|
||||
const localSub = await localDb.channelSubscriptions
|
||||
.where('channelId')
|
||||
.equals(channel.channelId)
|
||||
.first();
|
||||
|
||||
if (!localSub) {
|
||||
lastRSSFetch = new Date(0);
|
||||
} else {
|
||||
lastRSSFetch = new Date(localSub.lastRSSFetch);
|
||||
}
|
||||
}
|
||||
|
||||
const timeDifference = now.getTime() - lastRSSFetch.getTime();
|
||||
const cooldownTime = get(engineCooldownYTStore) * 60 * 60 * 1000;
|
||||
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
>
|
||||
|
||||
{#if page.error}
|
||||
<article>
|
||||
<code style="white-space: pre-line;word-wrap: break-word;">{page.error.message}</code>
|
||||
<article style="white-space: pre-line;word-wrap: break-word;">
|
||||
<article class="surface-container-highest"><p>{page.error.message}</p></article>
|
||||
</article>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -230,6 +230,7 @@
|
||||
tabindex="-1"
|
||||
class:author={!sideways}
|
||||
href={resolve(`/channel/[authorId]`, { authorId: video.authorId })}
|
||||
data-sveltekit-preload-data="off"
|
||||
>{video.author}
|
||||
</a>
|
||||
{:else}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { resolve } from '$app/paths';
|
||||
import { bookmarkletSaveToUrl } from '$lib/externalSettings/index';
|
||||
import { letterCase, titleCases } from '$lib/letterCasing';
|
||||
import { setAmoledTheme } from '$lib/theme';
|
||||
@@ -84,12 +82,13 @@
|
||||
async function setInstance(event: Event) {
|
||||
event.preventDefault();
|
||||
invalidInstance = !(await setInvidiousInstance(invidiousInstance));
|
||||
goto(resolve('/', {}), { replaceState: true });
|
||||
location.reload();
|
||||
}
|
||||
|
||||
async function setBackend(event: Event) {
|
||||
const select = event.target as HTMLSelectElement;
|
||||
backendInUseStore.set(select.value as 'ivg' | 'yt');
|
||||
location.reload();
|
||||
}
|
||||
|
||||
function allowInsecureRequests() {
|
||||
@@ -136,7 +135,7 @@
|
||||
<form onsubmit={setInstance}>
|
||||
<nav>
|
||||
<div
|
||||
class="field prefix label surface-container-highest max"
|
||||
class="field prefix suffix label surface-container-highest max"
|
||||
class:invalid={invalidInstance}
|
||||
>
|
||||
<i>link</i>
|
||||
@@ -150,6 +149,17 @@
|
||||
{#if invalidInstance}
|
||||
<span class="error">{$_('invalidInstance')}</span>
|
||||
{/if}
|
||||
{#if $invidiousInstanceStore}
|
||||
<i
|
||||
role="presentation"
|
||||
class="front"
|
||||
onclick={() => {
|
||||
invidiousInstanceStore.set(undefined);
|
||||
invidiousInstance = undefined;
|
||||
invidiousLogout();
|
||||
}}>close</i
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
<button class="square">
|
||||
<i>done</i>
|
||||
|
||||
@@ -22,13 +22,18 @@ export async function load() {
|
||||
let popularDisabled: boolean = false;
|
||||
|
||||
if (!popular) {
|
||||
let errorMsg: Error | undefined;
|
||||
try {
|
||||
popular = await getPopular();
|
||||
} catch (errorMessage: any) {
|
||||
if (errorMessage.toString() === 'Error: Administrator has disabled this endpoint.') {
|
||||
} catch (popularError) {
|
||||
errorMsg = popularError as Error;
|
||||
}
|
||||
|
||||
if (errorMsg) {
|
||||
if (errorMsg.toString() === 'Error: Administrator has disabled this endpoint.') {
|
||||
popularDisabled = true;
|
||||
} else {
|
||||
error(500, errorMessage);
|
||||
throw error(500, errorMsg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -107,11 +107,21 @@ async function proxyRequest(
|
||||
body = request.body;
|
||||
}
|
||||
|
||||
const response = await fetch(urlToProxyObj.toString(), {
|
||||
...requestOptions,
|
||||
body,
|
||||
signal: AbortSignal.timeout(10000)
|
||||
});
|
||||
let response: Response | undefined;
|
||||
let errorMsg = '';
|
||||
try {
|
||||
response = await fetch(urlToProxyObj.toString(), {
|
||||
...requestOptions,
|
||||
body,
|
||||
signal: AbortSignal.timeout(10000)
|
||||
});
|
||||
} catch (err) {
|
||||
errorMsg = (err as any).toString();
|
||||
}
|
||||
|
||||
if (!response || errorMsg) {
|
||||
throw error(500, errorMsg);
|
||||
}
|
||||
|
||||
const responseHeaders = new Headers(response.headers);
|
||||
responseHeaders.set('transfer-encoding', 'chunked');
|
||||
|
||||
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.7"
|
||||
LATEST_VERSION = "1.15.8"
|
||||
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