Implemented Invidious import

This commit is contained in:
WardPearce
2026-02-13 02:57:32 +13:00
parent 050bfb6a77
commit 5e230aca7e
6 changed files with 85 additions and 17 deletions
+11 -4
View File
@@ -274,8 +274,11 @@ export async function notificationsMarkAsRead(fetchOptions: RequestInit = {}) {
await fetchErrorHandle(await fetch(path, { ...buildAuthHeaders(), ...fetchOptions }));
}
export async function getSubscriptions(fetchOptions: RequestInit = {}): Promise<Subscription[]> {
if (isYTBackend()) {
export async function getSubscriptions(
fetchOptions: RequestInit = {},
bypassYTBackend: boolean = false
): Promise<Subscription[]> {
if (isYTBackend() && !bypassYTBackend) {
return getSubscriptionsYTjs();
}
const resp = await fetchErrorHandle(
@@ -304,8 +307,12 @@ export async function amSubscribed(
}
}
export async function postSubscribe(authorId: string, fetchOptions: RequestInit = {}) {
if (isYTBackend()) {
export async function postSubscribe(
authorId: string,
fetchOptions: RequestInit = {},
bypassYTBackend: boolean = false
) {
if (isYTBackend() && !bypassYTBackend) {
return postSubscribeYTjs(authorId);
}
@@ -24,13 +24,19 @@ export async function amSubscribedYTjs(authorId: string): Promise<boolean> {
return (await localDb.channelSubscriptions.where('channelId').equals(authorId).count()) > 0;
}
export async function postSubscribeYTjs(authorId: string) {
const channel = await getChannelYTjs(authorId);
export async function postSubscribeYTjs(
authorId: string,
authorName: string | undefined = undefined
) {
if (!authorName) {
const channel = await getChannelYTjs(authorId);
authorName = channel.author;
}
try {
await localDb.channelSubscriptions.add({
channelId: authorId,
channelName: channel.author,
channelName: authorName,
lastRSSFetch: new Date(0)
});
} catch {
@@ -147,16 +153,17 @@ export async function getFeedYTjs(): Promise<Feed> {
await Promise.all(toUpdatePromises);
}
const videos = await localDb.subscriptionFeed.toArray();
let videos = await localDb.subscriptionFeed.toArray();
videos.sort((a, b) => b.published - a.published);
const cullAfter = get(engineCullYTStore);
if (videos.length > cullAfter) {
const videosToDelete = videos.slice(cullAfter);
const videoIdsToDelete = videosToDelete.map((video) => video.videoId);
await localDb.subscriptionFeed.where('videoId').anyOf(videoIdsToDelete).delete();
await localDb.subscriptionFeed.where('id').anyOf(videoIdsToDelete).delete();
// Don't display culled videos.
videos = videos.slice(0, cullAfter);
}
return {
@@ -1,9 +1,18 @@
<script lang="ts">
import { isYTBackend } from '$lib/misc';
import { _ } from '$lib/i18n';
import { engineCooldownYTStore, engineCullYTStore, engineFallbacksStore } from '$lib/store';
import {
authStore,
engineCooldownYTStore,
engineCullYTStore,
engineFallbacksStore,
instanceStore
} from '$lib/store';
import { useEngineFallback, type EngineFallback } from '$lib/api/misc';
import { get } from 'svelte/store';
import { getSubscriptions } from '$lib/api';
import { postSubscribeYTjs } from '$lib/api/youtubejs/subscriptions';
import { addToast } from '../Toast.svelte';
const engineFallbacks: EngineFallback[] = [
'Channel',
@@ -33,6 +42,29 @@
engineFallbacksStore.set(enabledFallbacks);
}
async function importInvidiousSubs() {
const importedSubs = await getSubscriptions({}, true);
addToast({
data: {
text: $_('layout.backendEngine.importingToMaterialious')
}
});
const subPromises: Promise<void>[] = [];
importedSubs.forEach((sub) => {
subPromises.push(postSubscribeYTjs(sub.authorId, sub.author));
});
await Promise.all(subPromises);
addToast({
data: {
text: $_('layout.backendEngine.importingToMaterialiousFinished')
}
});
}
</script>
<article class="error-container">
@@ -40,7 +72,7 @@
</article>
{#if isYTBackend()}
<h4>Feed</h4>
<h6>Feed</h6>
<div class="field label prefix border">
<i>view_stream</i>
<input
@@ -65,8 +97,25 @@
/>
<label for="cull">{$_('layout.backendEngine.cooldown')}</label>
</div>
{#if $authStore && $instanceStore}
<h6>{$_('layout.backendEngine.importExport')}</h6>
<div class="space"></div>
<button class="surface-container-highest">
<i>upload</i>
<div>{$_('layout.backendEngine.exportToInvidious')}</div>
</button>
<div class="space"></div>
<button onclick={importInvidiousSubs} class="surface-container-highest">
<i>download</i>
<div>{$_('layout.backendEngine.importToMaterialious')}</div>
</button>
{/if}
{:else}
<h4>{$_('layout.backendEngine.fallbacks')}</h4>
<h6>{$_('layout.backendEngine.fallbacks')}</h6>
{#each engineFallbacks as fallback (fallback)}
<nav class="no-padding">
<div class="max">
+6 -1
View File
@@ -179,7 +179,12 @@
"warning": "Advanced settings! For experienced users only. Do not change unless you know what youre doing.",
"cull": "Max feed items",
"cooldown": "Re-fetch channel hour cooldown",
"fallbacks": "Fallbacks"
"fallbacks": "Fallbacks",
"importExport": "Import/Export subscriptions",
"exportToInvidious": "Export to Invidious from Materialious",
"importToMaterialious": "Import from Invidious to Materialious",
"importingToMaterialious": "Importing to Materialious began",
"importingToMaterialiousFinished": "Importing to Materialious finished"
},
"player": {
"title": "Player",
+1 -1
View File
@@ -315,7 +315,7 @@ export const deArrowThumbnailInstanceStore = persist(
);
export const engineCullYTStore: Writable<number> = persist(
writable(1000),
writable(500),
createStorage(),
'engineCullYT'
);
+2 -2
View File
@@ -237,7 +237,7 @@
setTheme();
setAmoledTheme();
if (isLoggedIn) {
if (isLoggedIn && !isYTBackend()) {
loadNotifications().catch(() => authStore.set(null));
}
@@ -344,7 +344,7 @@
<i class:primary-text={$syncPartyPeerStore}>group</i>
<div class="tooltip bottom">{$_('layout.syncParty')}</div>
</button>
{#if isLoggedIn}
{#if isLoggedIn && !isYTBackend()}
<button
class="circle large transparent"
onclick={() => {