Merge pull request #1549 from Materialious/update/1.16.17
Update/1.16.17
This commit is contained in:
@@ -7,8 +7,8 @@ android {
|
||||
applicationId "us.materialio.app"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 233
|
||||
versionName "1.16.16"
|
||||
versionCode 234
|
||||
versionName "1.16.17"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
aaptOptions {
|
||||
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
||||
|
||||
@@ -75,7 +75,11 @@
|
||||
|
||||
|
||||
|
||||
<release version="1.16.16" date="2026-3-08">
|
||||
|
||||
<release version="1.16.17" date="2026-3-09">
|
||||
<url>https://github.com/Materialious/Materialious/releases/tag/1.16.17</url>
|
||||
</release>
|
||||
<release version="1.16.16" date="2026-3-08">
|
||||
<url>https://github.com/Materialious/Materialious/releases/tag/1.16.16</url>
|
||||
</release>
|
||||
<release version="1.16.15" date="2026-3-08">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Materialious",
|
||||
"version": "1.16.16",
|
||||
"version": "1.16.17",
|
||||
"description": "Modern material design for YouTube and Invidious.",
|
||||
"author": {
|
||||
"name": "Ward Pearce",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "materialious",
|
||||
"version": "1.16.16",
|
||||
"version": "1.16.17",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "npm run patch:github && vite dev",
|
||||
|
||||
@@ -118,6 +118,16 @@ export async function deleteWatchHistoryBackend() {
|
||||
await fetch('/api/user/history', { method: 'DELETE' });
|
||||
}
|
||||
|
||||
export async function deleteWatchHistoryItemBackend(videoId: string) {
|
||||
await sodium.ready;
|
||||
const rawKey = await getRawKey();
|
||||
if (!rawKey) return;
|
||||
|
||||
const videoHash = await getSecureHash(videoId, rawKey);
|
||||
|
||||
await fetch(`/api/user/history/${videoHash}`, { method: 'DELETE' });
|
||||
}
|
||||
|
||||
export async function saveWatchHistoryBackend(video: VideoPlay, progress: number = 0) {
|
||||
await sodium.ready;
|
||||
const rawKey = await getRawKey();
|
||||
|
||||
@@ -74,6 +74,7 @@ import {
|
||||
} from './invidious/playlist';
|
||||
import {
|
||||
deleteWatchHistoryBackend,
|
||||
deleteWatchHistoryItemBackend,
|
||||
getVideoWatchHistoryBackend,
|
||||
getWatchHistoryBackend,
|
||||
saveWatchHistoryBackend,
|
||||
@@ -343,6 +344,14 @@ export async function deleteWatchHistory() {
|
||||
await localDb.watchHistory.clear();
|
||||
}
|
||||
|
||||
export async function deleteWatchHistoryItem(videoId: string) {
|
||||
if (isOwnBackend()?.internalAuth && get(rawMasterKeyStore)) {
|
||||
return deleteWatchHistoryItemBackend(videoId);
|
||||
}
|
||||
|
||||
await localDb.watchHistory.delete({ videoId });
|
||||
}
|
||||
|
||||
export async function updateWatchHistory(
|
||||
videoId: string,
|
||||
progress: number,
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
<nav style="gap: 0.5em;">
|
||||
<img class="circle large" {...avatar.image} alt="Channel profile" />
|
||||
<button
|
||||
class="large secondary-container"
|
||||
class="circle large secondary-container"
|
||||
{...mergeAttrs(avatar.fallback, {
|
||||
style: 'text-transform: uppercase;border-radius: 2.5rem !important;'
|
||||
})}>{channel.author[0]}</button
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
<img class="circle small" {...avatar.image} alt="Channel profile" />
|
||||
<button
|
||||
class="secondary-container"
|
||||
class="circle secondary-container"
|
||||
{...mergeAttrs(avatar.fallback, {
|
||||
style: 'text-transform: uppercase;border-radius: 2.5rem !important;'
|
||||
})}>{author[0]}</button
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { _ } from '$lib/i18n';
|
||||
import { removePlaylistVideo } from '$lib/api';
|
||||
import { invidiousAuthStore, feedLastItemId, isAndroidTvStore } from '$lib/store';
|
||||
import { feedLastItemId, isAndroidTvStore } from '$lib/store';
|
||||
import ContentColumn from '$lib/components/layout/ContentColumn.svelte';
|
||||
import { onMount } from 'svelte';
|
||||
import Thumbnail from '$lib/components/thumbnail/VideoThumbnail.svelte';
|
||||
@@ -18,21 +16,10 @@
|
||||
interface Props {
|
||||
items?: FeedItems;
|
||||
playlistId?: string;
|
||||
playlistAuthor?: string;
|
||||
classes?: string;
|
||||
}
|
||||
|
||||
let {
|
||||
items = [],
|
||||
playlistId = '',
|
||||
playlistAuthor = '',
|
||||
classes = 'page right active'
|
||||
}: Props = $props();
|
||||
|
||||
async function removePlaylistItem(indexId: string) {
|
||||
if (!playlistId) return;
|
||||
await removePlaylistVideo(playlistId, indexId);
|
||||
}
|
||||
let { items = [], playlistId = '', classes = 'page right active' }: Props = $props();
|
||||
|
||||
onMount(async () => {
|
||||
if (!$feedLastItemId) return;
|
||||
@@ -99,17 +86,6 @@
|
||||
{#key item.videoId}
|
||||
<Thumbnail video={item} {playlistId} />
|
||||
{/key}
|
||||
{#if $invidiousAuthStore && decodeURIComponent($invidiousAuthStore.username) === playlistAuthor && 'indexId' in item}
|
||||
<div class="right-align" style="margin: 1em .5em;">
|
||||
<button
|
||||
onclick={async () => removePlaylistItem(item.indexId)}
|
||||
class="tertiary circle small"
|
||||
>
|
||||
<i>delete</i>
|
||||
<div class="tooltip">{$_('delete')}</div>
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
{:else if item.type === 'channel'}
|
||||
<ChannelThumbnail channel={item} />
|
||||
{:else if item.type === 'playlist'}
|
||||
|
||||
@@ -93,6 +93,8 @@
|
||||
// Check if sideways should be enabled or disabled.
|
||||
disableSideways();
|
||||
|
||||
addEventListener('resize', disableSideways);
|
||||
|
||||
if (!page.url.pathname.endsWith('/history'))
|
||||
queueGetWatchHistory(video.videoId).then((watchHistory) => {
|
||||
if (watchHistory) {
|
||||
@@ -110,7 +112,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class:sideways-root={sideways} tabindex="0" role="button">
|
||||
<div class:sideways-root={sideways} class:use-flex-column={!sideways} tabindex="0" role="button">
|
||||
<div id="thumbnail-container">
|
||||
<!-- eslint-disable svelte/no-navigation-without-resolve -->
|
||||
<a
|
||||
@@ -168,8 +170,8 @@
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="thumbnail-details video-title">
|
||||
<div class="video-title">
|
||||
<div class="thumbnail-details video-title" style="flex: 1;">
|
||||
<div class="video-title use-flex-column">
|
||||
<a
|
||||
tabindex="-1"
|
||||
style="padding-left: 1px;"
|
||||
@@ -178,10 +180,10 @@
|
||||
href={watchUrl.toString()}
|
||||
onclick={onVideoSelected}
|
||||
>
|
||||
<span class="bold">{letterCase(video.title.trimEnd())}</span>
|
||||
<span class="bold" style="width: 100%;">{letterCase(video.title.trimEnd())}</span>
|
||||
</a>
|
||||
|
||||
<nav>
|
||||
<nav class="align-end">
|
||||
{#if !sideways && 'authorId' in video}
|
||||
<AuthorAvatar author={video.author} authorId={video.authorId} />
|
||||
{/if}
|
||||
@@ -240,6 +242,12 @@
|
||||
margin-bottom: -22%;
|
||||
}
|
||||
|
||||
.use-flex-column {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.thumbnail {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
@@ -307,12 +315,21 @@
|
||||
filter: brightness(40%);
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1499px) {
|
||||
.align-end {
|
||||
flex: 1;
|
||||
align-items: end;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1800px) {
|
||||
.sideways-root .thumbnail {
|
||||
width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.align-end {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.sideways-root .thumbnail-details {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@
|
||||
"defaultLanguage": "既定の言語",
|
||||
"retryText": "動画の読み込みに失敗。どう続けますか?",
|
||||
"enableYoutubejsTemp": "一度だけ動画への直接接続に切り替え",
|
||||
"enableYoutubejsPerm": "常に動画への接続エラーで直接接続を許可",
|
||||
"enableYoutubejsPerm": "ローカル動画へのフォールバックを常に許可",
|
||||
"youtubeJsFallBack": "動画接続エラー 直接接続に切替",
|
||||
"youtubeJsLoading": "動画の読み込みに失敗。動画への直接接続に切り替えます。",
|
||||
"premiere": "動画は利用可能です",
|
||||
@@ -163,12 +163,12 @@
|
||||
"proxyVideos": "動画のプロキシ",
|
||||
"backgroundPlay": "バックグラウンドで音声を再生",
|
||||
"dash": "DASH",
|
||||
"listenByDefault": "最初から音声モード",
|
||||
"silenceSkipper": "無音部分を飛ばす (実験的)",
|
||||
"listenByDefault": "既定で音声再生",
|
||||
"silenceSkipper": "無音部分をスキップ (実験的機能)",
|
||||
"localVideoFallback": "動画の接続エラーで直接接続に切替",
|
||||
"youtubeJsFallback": "失敗時に動画の代替処理を使用",
|
||||
"youtubeJsAlways": "常に使用",
|
||||
"lockOrientation": "アスペクト比と向きを固定",
|
||||
"lockOrientation": "アスペクト比に合わせて向きを固定",
|
||||
"defaultPlaybackSpeed": "既定の再生速度",
|
||||
"playerStatistics": "最初からプレイヤー統計情報を表示",
|
||||
"networkChangePause": "ネットワーク変更時に一時停止"
|
||||
@@ -178,7 +178,7 @@
|
||||
"sponsors": {
|
||||
"sponsor": "協賛",
|
||||
"disableToast": "通知を非表示",
|
||||
"disableTimeline": "タイムライン上のシーン表示を非表示",
|
||||
"disableTimeline": "タイムライン上のセグメントを無効化",
|
||||
"Catagories": "分類",
|
||||
"unpaidSelfPromotion": "無償または自己の宣伝",
|
||||
"interactionReminder": "登録など操作を頼む自己宣伝",
|
||||
@@ -213,7 +213,7 @@
|
||||
"cooldown": "チャンネル時間制限の再取得",
|
||||
"concurrent": "フィードが返される前に解析する最大チャンネル数",
|
||||
"fallbacks": "フォールバック",
|
||||
"importExport": "サブスクリプションのインポート/エクスポート",
|
||||
"importExport": "自動エクスポート/インポート",
|
||||
"exportToInvidious": "MaterialiousからInvidiousにエクスポート",
|
||||
"importToMaterialious": "Invidious から Materialious へのインポート",
|
||||
"importingToMaterialious": "マテリアルへのインポートを開始しました",
|
||||
@@ -222,9 +222,32 @@
|
||||
"exportingToInvidiousFinished": "Invidiousへのエクスポートが終了しました"
|
||||
},
|
||||
"exportToJson": "JSON にエクスポート",
|
||||
"historyEnabled": "視聴履歴を保存"
|
||||
"historyEnabled": "視聴履歴を保存",
|
||||
"filter": {
|
||||
"title": "コンテンツフィルター",
|
||||
"url": "URLからフィルターをインポート",
|
||||
"autoUpdate": "URLからフィルターを自動更新する",
|
||||
"contentType": "コンテンツタイプ",
|
||||
"field": "フィールド",
|
||||
"operator": "オペレーター",
|
||||
"optionPlaceholder": "オプションの選択",
|
||||
"value": "値",
|
||||
"addConditional": "条件を追加",
|
||||
"addFilter": "フィルターを追加"
|
||||
},
|
||||
"export": {
|
||||
"title": "エクスポート/インポート",
|
||||
"importSub": "登録チャンネルをインポート",
|
||||
"importSubSupported": "YouTube CSV/OPML/JSON、Invidious OPML/JSON、Freetube データベース、NewPipe JSON をサポート",
|
||||
"importing": "登録チャンネルをインポートしています (少し時間がかかります)",
|
||||
"importingCompleted": "登録チャンネルを正常にインポートしました",
|
||||
"exportSub": "登録チャンネルをエクスポート",
|
||||
"exportToClipboard": "クリップボードへエクスポート",
|
||||
"exportToFile": "ファイルへエクスポート",
|
||||
"importFromFile": "ファイルからインポート"
|
||||
}
|
||||
},
|
||||
"subscribe": "登録",
|
||||
"subscribe": "チャンネル登録",
|
||||
"invidiousBlockWarning": "Invidious は、現在Googleにブロックされています。このインスタンスで動画が読み込めない場合、{android} 版または {desktop} 版の Materialious でこのインスタンスを使用し、動画取得を直接行う代替接続を使ってください。",
|
||||
"reply": "返信",
|
||||
"comments": "コメント",
|
||||
@@ -248,7 +271,7 @@
|
||||
"popular": "人気",
|
||||
"favouriteChannel": "お気に入りチャンネル",
|
||||
"unfavouriteChannel": "お気に入り解除チャンネル",
|
||||
"copy": "JSON をコピー",
|
||||
"copy": "クリップボードにコピー",
|
||||
"username": "ユーザー名",
|
||||
"password": "パスワード",
|
||||
"captionStyle": "見出しのスタイル",
|
||||
@@ -274,5 +297,16 @@
|
||||
"localFallbackInfo": "Invidious からのビデオの読み込みに失敗した場合、Materialious はそれをローカルに読み込みます。",
|
||||
"configureInstance": "インスタンスの構成",
|
||||
"done": "完了"
|
||||
},
|
||||
"watchParty": {
|
||||
"start": "ウォッチパーティーを開始",
|
||||
"header": "ウォッチパーティー",
|
||||
"createRoom": "ルームを作成",
|
||||
"joinRoom": "ルームに参加",
|
||||
"roomID": "ルーム ID",
|
||||
"shareRoom": "ルームを共有",
|
||||
"leaveRoom": "ルームから退出",
|
||||
"userJoin": "ユーザーがルームに参加しました",
|
||||
"userLeft": "ユーザーがルームから退出しました"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,9 +97,5 @@
|
||||
<div class="space"></div>
|
||||
|
||||
{#if data.playlist.videos}
|
||||
<ItemsList
|
||||
items={data.playlist.videos}
|
||||
playlistAuthor={data.playlist.info.author}
|
||||
playlistId={data.playlist.info.playlistId}
|
||||
/>
|
||||
<ItemsList items={data.playlist.videos} playlistId={data.playlist.info.playlistId} />
|
||||
{/if}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { getSubscriptions } from '$lib/api';
|
||||
import type { Subscription } from '$lib/api/model';
|
||||
import { authProtected } from '$lib/auth';
|
||||
import { error } from '@sveltejs/kit';
|
||||
|
||||
export async function load() {
|
||||
authProtected();
|
||||
|
||||
let subscriptions;
|
||||
let subscriptions: Subscription[];
|
||||
|
||||
try {
|
||||
subscriptions = await getSubscriptions();
|
||||
@@ -13,6 +14,8 @@ export async function load() {
|
||||
error(500, errorMessage);
|
||||
}
|
||||
|
||||
subscriptions.sort((a, b) => a.author.localeCompare(b.author));
|
||||
|
||||
return {
|
||||
subscriptions: subscriptions
|
||||
};
|
||||
|
||||
@@ -100,14 +100,24 @@ async function proxyRequest(
|
||||
}
|
||||
}
|
||||
|
||||
const requestHeaders = new Headers();
|
||||
const requestHeaders = new Headers(request.headers);
|
||||
requestHeaders.set('host', urlToProxyObj.host);
|
||||
requestHeaders.set('origin', urlToProxyObj.origin);
|
||||
requestHeaders.set('user-agent', USER_AGENT);
|
||||
|
||||
const authHeader = request.headers.get('Authorization');
|
||||
if (authHeader) {
|
||||
requestHeaders.set('Authorization', authHeader);
|
||||
// Remove headers that may cause issues or be auto-managed
|
||||
for (const key of [
|
||||
'referer',
|
||||
'x-forwarded-for',
|
||||
'x-requested-with',
|
||||
'sec-ch-ua-mobile',
|
||||
'sec-ch-ua',
|
||||
'sec-ch-ua-platform',
|
||||
'cookie', // Ensure auth cookies don't become included.
|
||||
'content-type',
|
||||
'content-length'
|
||||
]) {
|
||||
requestHeaders.delete(key);
|
||||
}
|
||||
|
||||
const requestOptions: RequestInit = {
|
||||
@@ -119,7 +129,7 @@ async function proxyRequest(
|
||||
|
||||
let body;
|
||||
if (request.body && request.headers.has('__is_base64_encoded')) {
|
||||
request.headers.delete('__is_base64_encoded');
|
||||
requestHeaders.delete('__is_base64_encoded');
|
||||
|
||||
await sodium.ready;
|
||||
body = Uint8Array.from(sodium.from_base64(await request.text()));
|
||||
|
||||
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.16.16"
|
||||
LATEST_VERSION = "1.16.17"
|
||||
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