Added export support

This commit is contained in:
WardPearce
2026-03-07 00:08:55 +13:00
parent 18bb744969
commit 62ba5905ac
4 changed files with 94 additions and 3 deletions
@@ -1,6 +1,6 @@
<script lang="ts">
import { _ } from '$lib/i18n';
import { importSubscriptionsFromFile } from '$lib/importExport';
import { exportSubscriptionsAsFile, importSubscriptionsFromFile } from '$lib/importExport';
import { addToast, toaster } from '../Toast.svelte';
</script>
@@ -36,3 +36,20 @@
/>
</div>
<p><i>info</i> {$_('layout.export.importSubSupported')}</p>
<div class="space"></div>
<div>
<button>
<i>file_export</i>
<span>{$_('layout.export.exportSub')}</span>
</button>
<menu>
<li role="presentation" onclick={() => exportSubscriptionsAsFile('InvidiousJSON')}>
Invidious JSON
</li>
<li role="presentation" onclick={() => exportSubscriptionsAsFile('OPML')}>
FreeTube/NewPipe/YouTube OPML
</li>
</menu>
</div>
+2 -1
View File
@@ -276,7 +276,8 @@
"importSub": "Import subscriptions",
"importSubSupported": "YouTube CSV/OPML, Invidious OPML/JSON, Freetube database & NewPipe JSON supported",
"importing": "Importing subscriptions (this will take a moment)",
"importingCompleted": "Subscriptions successfully imported"
"importingCompleted": "Subscriptions successfully imported",
"exportSub": "Export subscriptions"
},
"deArrow": {
"title": "DeArrow",
+60 -1
View File
@@ -1,7 +1,8 @@
import z from 'zod';
import type { Subscription } from './api/model';
import { getChannel, postSubscribe } from './api';
import { getChannel, getSubscriptions, postSubscribe } from './api';
import Papa from 'papaparse';
import { downloadStringAsFile } from './misc';
const zInvidiousSubs = z.object({
subscriptions: z.array(z.string())
@@ -166,3 +167,61 @@ export async function importSubscriptionsFromFile(file: File) {
throw new Error('Unable to determine imported file type');
}
export async function exportSubscriptionsAsFile(exportType: 'InvidiousJSON' | 'OPML') {
const subscriptions = await getSubscriptions();
let data: string | undefined;
if (exportType === 'InvidiousJSON') {
const subIds = [];
for (const sub of subscriptions) {
subIds.push(sub.authorId);
}
data = JSON.stringify({
subscriptions: subIds
});
} else {
const xmlDoc = document.implementation.createDocument('', '', null);
const opml = xmlDoc.createElement('opml');
opml.setAttribute('version', '1.1');
const body = xmlDoc.createElement('body');
const mainOutline = xmlDoc.createElement('outline');
mainOutline.setAttribute('text', 'YouTube Subscriptions');
mainOutline.setAttribute('title', 'YouTube Subscriptions');
body.appendChild(mainOutline);
for (const sub of subscriptions) {
const outline = xmlDoc.createElement('outline');
outline.setAttribute('text', sub.author);
outline.setAttribute('title', sub.author);
outline.setAttribute('type', 'rss');
outline.setAttribute(
'xmlUrl',
`https://www.youtube.com/feeds/videos.xml?channel_id=${sub.authorId}`
);
body.appendChild(outline);
}
opml.appendChild(body);
xmlDoc.appendChild(opml);
const serializer = new XMLSerializer();
data = serializer.serializeToString(xmlDoc);
}
if (data) {
downloadStringAsFile(
data,
`materialious-export-${new Date().toDateString().replaceAll(' ', '')}.${exportType === 'InvidiousJSON' ? 'json' : 'opml'}`
);
}
}
+14
View File
@@ -118,3 +118,17 @@ export function isUnrestrictedPlatform(): boolean {
export function isYTBackend(): boolean {
return get(backendInUseStore) === 'yt' && isUnrestrictedPlatform();
}
export function downloadStringAsFile(content: string, filename: string) {
const blob = new Blob([content]);
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}