Removed API-Extended

This commit is contained in:
WardPearce
2026-02-23 19:30:16 +13:00
parent cc0e2e0bf3
commit f97d7d6453
14 changed files with 63 additions and 311 deletions
-34
View File
@@ -136,37 +136,3 @@ ryd-proxy:
```
Modify/add `VITE_DEFAULT_RETURNYTDISLIKES_INSTANCE` for Materialious to be the reverse proxied URL of RYD-Proxy.
## Step 3 (Optional, but recommended): Self-host Invidious API extended
```yaml
services:
api_extended:
image: wardpearce/invidious_api_extended:latest
restart: unless-stopped
ports:
- 3004:80
environment:
api_extended_postgre: '{"host": "invidious-db", "port": 5432, "database": "invidious", "user": "kemal", "password": "kemal"}'
api_extended_allowed_origins: '["https://materialious.example.com"]'
api_extended_debug: false
# No trailing backslashes!
api_extended_invidious_instance: "https://invidious.example.com"
api_extended_production_instance: "https://syncious.example.com"
```
Add these additional environment variables to Materialious.
```yaml
VITE_DEFAULT_API_EXTENDED_INSTANCE: "https://syncious.example.com"
```
## Step 4 (Optional): Self-host PeerJS
[Read the official guide.](https://github.com/peers/peerjs-server?tab=readme-ov-file#docker)
Add these additional environment variables to Materialious.
```yaml
# Will differ depending on how you self-host peerjs.
VITE_DEFAULT_PEERJS_HOST: "peerjs.example.com"
VITE_DEFAULT_PEERJS_PATH: "/"
VITE_DEFAULT_PEERJS_PORT: 443
```
+1 -28
View File
@@ -278,34 +278,7 @@ http:
### Step 3:
Modify/add `VITE_DEFAULT_RETURNYTDISLIKES_INSTANCE` for Materialious to be the reverse proxied URL of RYD-Proxy.
## Step 5 (Optional, but recommended): Self-host Invidious API extended
### Step 1: Docker compose
Add the following to your docker compose
```yaml
services:
api_extended:
image: wardpearce/invidious_api_extended:latest
restart: unless-stopped
ports:
- 3004:80
environment:
api_extended_postgre: '{"host": "invidious-db", "port": 5432, "database": "invidious", "user": "kemal", "password": "kemal"}'
api_extended_allowed_origins: '["https://materialious.example.com"]'
api_extended_debug: false
# No trailing backslashes!
api_extended_invidious_instance: "https://invidious.example.com"
api_extended_production_instance: "https://syncious.example.com"
```
Add these additional environment variables to Materialious.
```yaml
VITE_DEFAULT_API_EXTENDED_INSTANCE: "https://syncious.example.com"
```
## Step 6 (Optional): Self-host PeerJS
## Step 5 (Optional): Self-host PeerJS
[Read the official guide.](https://github.com/peers/peerjs-server?tab=readme-ov-file#docker)
Add these additional environment variables to Materialious.
-4
View File
@@ -20,10 +20,6 @@ RUN echo "VITE_DEFAULT_INVIDIOUS_INSTANCE=VITE_DEFAULT_INVIDIOUS_INSTANCE_PLACEH
echo "VITE_DEFAULT_PEERJS_HOST=VITE_DEFAULT_PEERJS_HOST_PLACEHOLDER" >> .env && \
echo "VITE_DEFAULT_PEERJS_PATH=VITE_DEFAULT_PEERJS_PATH_PLACEHOLDER" >> .env && \
echo "VITE_DEFAULT_PEERJS_PORT=VITE_DEFAULT_PEERJS_PORT_PLACEHOLDER" >> .env && \
echo "VITE_DEFAULT_DOWNLOAD_ENABLED=VITE_DEFAULT_DOWNLOAD_ENABLED_PLACEHOLDER" >> .env && \
echo "VITE_DEFAULT_SYNCIOUS_INSTANCE=VITE_DEFAULT_SYNCIOUS_INSTANCE_PLACEHOLDER" >> .env && \
echo "VITE_DEFAULT_API_EXTENDED_INSTANCE=VITE_DEFAULT_API_EXTENDED_INSTANCE_PLACEHOLDER" >> .env && \
echo "VITE_DEFAULT_DASH_BITRATE=VITE_DEFAULT_DASH_BITRATE_PLACEHOLDER" >> .env && \
echo "VITE_DEFAULT_SETTINGS='\"VITE_DEFAULT_SETTINGS_PLACEHOLDER\"'" >> .env && \
echo "VITE_DEFAULT_RETURNYTDISLIKES_INSTANCE=VITE_DEFAULT_RETURNYTDISLIKES_INSTANCE_PLACEHOLDER" >> .env
-4
View File
@@ -11,8 +11,4 @@ find "$ROOT_DIR" -type f \( -name "*.js" -o -name "*.html" \) -exec sed -i 's|VI
find "$ROOT_DIR" -type f \( -name "*.js" -o -name "*.html" \) -exec sed -i 's|VITE_DEFAULT_PEERJS_HOST_PLACEHOLDER|'"$VITE_DEFAULT_PEERJS_HOST"'|g' {} +
find "$ROOT_DIR" -type f \( -name "*.js" -o -name "*.html" \) -exec sed -i 's|VITE_DEFAULT_PEERJS_PATH_PLACEHOLDER|'"$VITE_DEFAULT_PEERJS_PATH"'|g' {} +
find "$ROOT_DIR" -type f \( -name "*.js" -o -name "*.html" \) -exec sed -i 's|VITE_DEFAULT_PEERJS_PORT_PLACEHOLDER|'"$VITE_DEFAULT_PEERJS_PORT"'|g' {} +
find "$ROOT_DIR" -type f \( -name "*.js" -o -name "*.html" \) -exec sed -i 's|VITE_DEFAULT_DOWNLOAD_ENABLED_PLACEHOLDER|'"$VITE_DEFAULT_DOWNLOAD_ENABLED"'|g' {} +
find "$ROOT_DIR" -type f \( -name "*.js" -o -name "*.html" \) -exec sed -i 's|VITE_DEFAULT_SYNCIOUS_INSTANCE_PLACEHOLDER|'"$VITE_DEFAULT_SYNCIOUS_INSTANCE"'|g' {} +
find "$ROOT_DIR" -type f \( -name "*.js" -o -name "*.html" \) -exec sed -i 's|VITE_DEFAULT_DASH_BITRATE_PLACEHOLDER|'"$VITE_DEFAULT_DASH_BITRATE"'|g' {} +
find "$ROOT_DIR" -type f \( -name "*.js" -o -name "*.html" \) -exec sed -i 's|VITE_DEFAULT_API_EXTENDED_INSTANCE_PLACEHOLDER|'"$VITE_DEFAULT_API_EXTENDED_INSTANCE"'|g' {} +
find "$ROOT_DIR" -type f \( -name "*.js" -o -name "*.html" \) -exec sed -i 's|VITE_DEFAULT_SETTINGS_PLACEHOLDER|'"$VITE_DEFAULT_SETTINGS"'|g' {} +
+40 -43
View File
@@ -1,53 +1,50 @@
import type { ApiExntendedProgressModel } from './model';
import { getVideoProgress } from '.';
// const videoIds: string[] = [];
// const pendingResolves = new Map<string, (result: ApiExntendedProgressModel | undefined) => void>();
const videoIds: string[] = [];
const pendingResolves = new Map<string, (result: ApiExntendedProgressModel | undefined) => void>();
// let timeout: ReturnType<typeof setTimeout> | null = null;
// const DEBOUNCE_MS = 1000;
// const BATCH_SIZE = 100;
let timeout: ReturnType<typeof setTimeout> | null = null;
const DEBOUNCE_MS = 1000;
const BATCH_SIZE = 100;
// async function processBatches(): Promise<void> {
// const batches: string[][] = [];
async function processBatches(): Promise<void> {
const batches: string[][] = [];
// while (videoIds.length > 0) {
// batches.push(videoIds.splice(0, BATCH_SIZE));
// }
while (videoIds.length > 0) {
batches.push(videoIds.splice(0, BATCH_SIZE));
}
// const results: ApiExntendedProgressModel[] = [];
const results: ApiExntendedProgressModel[] = [];
// for (const batch of batches) {
// const res: ApiExntendedProgressModel[] = await getVideoProgress(batch.join(','));
// results.push(...res);
for (const batch of batches) {
const res: ApiExntendedProgressModel[] = await getVideoProgress(batch.join(','));
results.push(...res);
// // Resolve pending promises for this batch
// for (const videoId of batch) {
// const match = res.find((item) => item.video_id === videoId);
// const resolve = pendingResolves.get(videoId);
// if (resolve) {
// resolve(match);
// pendingResolves.delete(videoId);
// }
// }
// }
// }
// Resolve pending promises for this batch
for (const videoId of batch) {
const match = res.find((item) => item.video_id === videoId);
const resolve = pendingResolves.get(videoId);
if (resolve) {
resolve(match);
pendingResolves.delete(videoId);
}
}
}
}
// export function queueGetWatchProgress(
// videoId: string
// ): Promise<ApiExntendedProgressModel | undefined> {
// videoIds.push(videoId);
export function queueGetWatchProgress(
videoId: string
): Promise<ApiExntendedProgressModel | undefined> {
videoIds.push(videoId);
// const promise = new Promise<ApiExntendedProgressModel | undefined>((resolve) => {
// pendingResolves.set(videoId, resolve);
// });
const promise = new Promise<ApiExntendedProgressModel | undefined>((resolve) => {
pendingResolves.set(videoId, resolve);
});
// if (timeout) clearTimeout(timeout);
// timeout = setTimeout(() => {
// processBatches().catch((e) => {
// console.error('Failed to process batches:', e);
// });
// }, DEBOUNCE_MS);
if (timeout) clearTimeout(timeout);
timeout = setTimeout(() => {
processBatches().catch((e) => {
console.error('Failed to process batches:', e);
});
}, DEBOUNCE_MS);
return promise;
}
// return promise;
// }
+1 -61
View File
@@ -9,8 +9,7 @@ import {
playerYouTubeJsAlways,
playerYouTubeJsFallback,
rawMasterKeyStore,
returnYTDislikesInstanceStore,
synciousInstanceStore
returnYTDislikesInstanceStore
} from '../store';
import type {
ChannelPage,
@@ -597,62 +596,3 @@ export async function getThumbnail(
);
return URL.createObjectURL(await resp.blob());
}
function buildApiExtendedAuthHeaders(): Record<string, Record<string, string>> {
const authToken = get(invidiousAuthStore)?.token ?? '';
return { headers: { Authorization: `Bearer ${authToken.replace('SID=', '')}` } };
}
export async function getVideoProgress(
videoId: string,
fetchOptions: RequestInit = {}
): Promise<ApiExntendedProgressModel[]> {
const resp = await fetchErrorHandle(
await fetch(`${get(synciousInstanceStore)}/video/${encodeURIComponent(videoId)}`, {
...buildApiExtendedAuthHeaders(),
...fetchOptions
})
);
return resp.json();
}
export async function saveVideoProgress(
videoId: string,
time: number,
fetchOptions: RequestInit = {}
) {
const headers: Record<string, Record<string, string>> = buildApiExtendedAuthHeaders();
headers['headers']['Content-type'] = 'application/json';
await fetchErrorHandle(
await fetch(`${get(synciousInstanceStore)}/video/${encodeURIComponent(videoId)}`, {
...headers,
method: 'POST',
body: JSON.stringify({
time: time
}),
...fetchOptions
})
);
}
export async function deleteVideoProgress(videoId: string, fetchOptions: RequestInit = {}) {
await fetchErrorHandle(
await fetch(`${get(synciousInstanceStore)}/video/${videoId}`, {
method: 'DELETE',
...buildApiExtendedAuthHeaders(),
...fetchOptions
})
);
}
export async function deleteAllVideoProgress(fetchOptions: RequestInit = {}) {
await fetchErrorHandle(
await fetch(`${get(synciousInstanceStore)}/videos`, {
method: 'DELETE',
...buildApiExtendedAuthHeaders(),
...fetchOptions
})
);
}
-9
View File
@@ -302,15 +302,6 @@ export interface DeArrow {
videoDuration: number;
}
export interface ApiExntendedProgressModel {
time: number;
video_id: string;
}
export interface SynciousSaveProgressModel {
time: number;
}
export type SearchResults = (Channel | Video | Playlist | HashTag)[] & {
getContinuation?: () => Promise<SearchResults>;
};
@@ -13,7 +13,6 @@
import { _ } from '$lib/i18n';
import { get } from 'svelte/store';
import { Slider } from 'melt/builders';
import { deleteVideoProgress, getVideoProgress, saveVideoProgress } from '$lib/api';
import type { VideoPlay } from '$lib/api/model';
import {
invidiousAuthStore,
@@ -34,9 +33,7 @@
sponsorBlockCategoriesStore,
sponsorBlockDisplayToastStore,
sponsorBlockStore,
sponsorBlockUrlStore,
synciousInstanceStore,
synciousStore
sponsorBlockUrlStore
} from '$lib/store';
import { setStatusBarColor } from '$lib/theme';
import { getVideoYTjs } from '$lib/api/youtubejs/video';
@@ -257,7 +254,7 @@
clearInterval(watchProgressInterval);
}
// Auto save watch progress every minute.
watchProgressInterval = setInterval(() => savePlayerPos(), 60000);
watchProgressInterval = setInterval(() => savePlayerbackHistory(), 60000);
let dashUrl: string;
@@ -284,9 +281,9 @@
!data.video.fallbackPatch
) {
const manifest = await manifestDomainInclusion(dashUrl);
await player.load(manifest, await getLastPlayPos());
await player.load(manifest, await getPlaybackHistory());
} else {
await player.load(dashUrl, await getLastPlayPos());
await player.load(dashUrl, await getPlaybackHistory());
}
if (data.content.timestamps) {
@@ -675,7 +672,7 @@
playerElement?.addEventListener('pause', async () => {
playerCurrentPlaybackState = false;
playerIsBuffering = false;
savePlayerPos();
savePlayerbackHistory();
showPlayerUI();
});
@@ -739,7 +736,7 @@
updateVideoPlayerHeight();
});
async function getLastPlayPos(): Promise<number> {
async function getPlaybackHistory(): Promise<number> {
if (loadTimeFromUrl($page) || !$playerSavePlaybackPositionStore) return 0;
let toSetTime = 0;
@@ -753,46 +750,23 @@
// Continue regardless of error
}
if ($synciousStore && $synciousInstanceStore && $invidiousAuthStore) {
try {
toSetTime = (await getVideoProgress(data.video.videoId))[0].time;
} catch {
// Continue regardless of error
}
}
return toSetTime;
}
function savePlayerPos() {
if (data.video.liveNow) return;
function savePlayerbackHistory() {
if (data.video.liveNow || !$playerSavePlaybackPositionStore || !playerElement) return;
const synciousEnabled = $synciousStore && $synciousInstanceStore && $invidiousAuthStore;
if ($playerSavePlaybackPositionStore && playerElement) {
if (
playerElement.currentTime < playerElement.duration - 10 &&
playerElement.currentTime > 10
) {
try {
localStorage.setItem(`v_${data.video.videoId}`, playerElement.currentTime.toString());
} catch {
// Continue regardless of error
}
if (synciousEnabled) {
saveVideoProgress(data.video.videoId, playerElement.currentTime);
}
} else {
try {
localStorage.removeItem(`v_${data.video.videoId}`);
} catch {
// Continue regardless of error
}
if (synciousEnabled) {
deleteVideoProgress(data.video.videoId);
}
if (playerElement.currentTime < playerElement.duration - 10 && playerElement.currentTime > 10) {
try {
localStorage.setItem(`v_${data.video.videoId}`, playerElement.currentTime.toString());
} catch {
// Continue regardless of error
}
} else {
try {
localStorage.removeItem(`v_${data.video.videoId}`);
} catch {
// Continue regardless of error
}
}
}
@@ -814,7 +788,7 @@
}
try {
savePlayerPos();
savePlayerbackHistory();
} catch {
// Continue regardless of error
}
@@ -1,42 +0,0 @@
<script lang="ts">
import { preventDefault } from 'svelte/legacy';
import { _ } from '$lib/i18n';
import { get } from 'svelte/store';
import { ensureNoTrailingSlash } from '../../misc';
import { synciousInstanceStore, synciousStore } from '../../store';
let synciousInstance = $state(get(synciousInstanceStore));
</script>
<form
onsubmit={preventDefault(() =>
synciousInstanceStore.set(ensureNoTrailingSlash(synciousInstance))
)}
>
<nav>
<div class="field prefix label surface-container-highest max">
<i>link</i>
<input tabindex="0" bind:value={synciousInstance} name="syncious-instance" type="text" />
<label tabindex="-1" for="syncious-instance">{$_('layout.instanceUrl')}</label>
</div>
<button class="square">
<i>done</i>
</button>
</nav>
</form>
<nav class="no-padding">
<div class="max">
<p>{$_('enabled')}</p>
</div>
<label class="switch" tabindex="0">
<input
bind:checked={$synciousStore}
onclick={() => synciousStore.set(!$synciousStore)}
type="checkbox"
role="switch"
/>
<span></span>
</label>
</nav>
@@ -1,7 +1,6 @@
<script lang="ts">
import { onMount, type Component } from 'svelte';
import { _ } from '$lib/i18n';
import ApiExtended from './ApiExtended.svelte';
import DeArrow from './DeArrow.svelte';
import Interface from './Interface.svelte';
import Player from './Player.svelte';
@@ -23,7 +22,6 @@
{ id: 'interface', label: $_('layout.interface'), icon: 'grid_view', component: Interface },
{ id: 'player', label: $_('layout.player.title'), icon: 'smart_display', component: Player },
{ id: 'ryd', label: 'Return YT Dislike', icon: 'thumb_down', component: Ryd },
{ id: 'api extended', label: 'API Extended', icon: 'sync', component: ApiExtended },
{ id: 'sponsorblock', label: 'Sponsorblock', icon: 'block', component: SponsorBlock },
{
id: 'dearrow',
@@ -11,18 +11,14 @@
import { createVideoUrl, insecureRequestImageHandler } from '$lib/misc';
import type { PlayerEvents } from '$lib/player';
import {
invidiousAuthStore,
deArrowEnabledStore,
interfaceLowBandwidthMode,
isAndroidTvStore,
playerSavePlaybackPositionStore,
playerState,
syncPartyConnectionsStore,
syncPartyPeerStore,
synciousInstanceStore,
synciousStore
syncPartyPeerStore
} from '$lib/store';
import { queueGetWatchProgress } from '$lib/api/apiExtended';
import { relativeTimestamp } from '$lib/time';
interface Props {
@@ -107,14 +103,6 @@
img.onload = () => {
thumbnail = img;
};
if (get(synciousStore) && get(synciousInstanceStore) && get(invidiousAuthStore)) {
try {
progress = (await queueGetWatchProgress(video.videoId))?.time?.toString() ?? undefined;
} catch {
// Continue regardless of error
}
}
});
onDestroy(() => {
@@ -30,8 +30,6 @@ import {
sponsorBlockDisplayToastStore,
sponsorBlockStore,
sponsorBlockUrlStore,
synciousInstanceStore,
synciousStore,
themeColorStore,
interfaceAutoExpandChapters,
playerDefaultPlaybackSpeed,
@@ -163,16 +161,6 @@ export const persistedStores: PersistedStore<any>[] = [
store: deArrowThumbnailInstanceStore,
schema: zString
},
{
name: 'syncious',
store: synciousStore,
schema: zBoolean
},
{
name: 'synciousInstance',
store: synciousInstanceStore,
schema: zString
},
{
name: 'region',
store: interfaceRegionStore,
-12
View File
@@ -203,18 +203,6 @@ export const returnYTDislikesInstanceStore: Writable<string | null | undefined>
'returnYTDislikesInstance'
);
export const synciousStore = persist(writable(false), createStorage(), 'syncious');
export const synciousInstanceStore: Writable<string | null | undefined> = persist(
writable(
ifNotWebDefault(
getPublicEnv('DEFAULT_SYNCIOUS_INSTANCE') || getPublicEnv('DEFAULT_API_EXTENDED_INSTANCE'),
'https://extended-api.materialio.us'
)
),
createStorage(),
'synciousInstance'
);
export const interfaceRegionStore: Writable<string> = persist(
writable('US'),
createStorage(),
@@ -48,7 +48,6 @@ const dynamicAllowDomainsEnvVars = [
env.PUBLIC_DEFAULT_INVIDIOUS_INSTANCE,
env.PUBLIC_DEFAULT_RETURNYTDISLIKES_INSTANCE,
env.PUBLIC_DEFAULT_API_EXTENDED_INSTANCE,
env.PUBLIC_DEFAULT_SYNCIOUS_INSTANCE,
env.PUBLIC_DEFAULT_COMPANION_INSTANCE
];