diff --git a/materialious/package-lock.json b/materialious/package-lock.json index 9af80fd0..6520b6b0 100644 --- a/materialious/package-lock.json +++ b/materialious/package-lock.json @@ -1,12 +1,12 @@ { "name": "materialious", - "version": "1.16.8", + "version": "1.16.12", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "materialious", - "version": "1.16.8", + "version": "1.16.12", "hasInstallScript": true, "dependencies": { "@capacitor-community/electron": "^5.0.1", @@ -47,6 +47,7 @@ "melt": "^0.44.0", "mousetrap": "^1.6.5", "mysql2": "^3.18.2", + "papaparse": "^5.5.3", "pg": "^8.19.0", "pg-hstore": "^2.3.4", "safe-regex2": "^5.0.0", @@ -76,6 +77,7 @@ "@types/human-number": "^1.0.2", "@types/jsdom": "^28.0.0", "@types/mousetrap": "^1.6.15", + "@types/papaparse": "^5.5.2", "@vite-pwa/sveltekit": "^1.0.0", "eslint": "^10.0.2", "eslint-config-prettier": "^10.1.8", @@ -6125,6 +6127,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/papaparse": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.5.2.tgz", + "integrity": "sha512-gFnFp/JMzLHCwRf7tQHrNnfhN4eYBVYYI897CGX4MY1tzY9l2aLkVyx2IlKZ/SAqDbB3I1AOZW5gTMGGsqWliA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/phoenix": { "version": "1.6.7", "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.7.tgz", @@ -14525,6 +14537,12 @@ "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "license": "BlueOak-1.0.0" }, + "node_modules/papaparse": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.5.3.tgz", + "integrity": "sha512-5QvjGxYVjxO59MGU2lHVYpRWBBtKHnlIAcSe1uNFCkkptUh63NFRj0FJQm7nR67puEruUci/ZkjmEFrjCAyP4A==", + "license": "MIT" + }, "node_modules/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", diff --git a/materialious/package.json b/materialious/package.json index 398049a7..5f7f56e7 100644 --- a/materialious/package.json +++ b/materialious/package.json @@ -30,6 +30,7 @@ "@types/human-number": "^1.0.2", "@types/jsdom": "^28.0.0", "@types/mousetrap": "^1.6.15", + "@types/papaparse": "^5.5.2", "@vite-pwa/sveltekit": "^1.0.0", "eslint": "^10.0.2", "eslint-config-prettier": "^10.1.8", @@ -85,6 +86,7 @@ "melt": "^0.44.0", "mousetrap": "^1.6.5", "mysql2": "^3.18.2", + "papaparse": "^5.5.3", "pg": "^8.19.0", "pg-hstore": "^2.3.4", "safe-regex2": "^5.0.0", @@ -99,4 +101,4 @@ "youtubei.js": "^16.0.1", "zod": "^4.3.6" } -} \ No newline at end of file +} diff --git a/materialious/src/lib/importExport.ts b/materialious/src/lib/importExport.ts index 07a5377a..2f51979d 100644 --- a/materialious/src/lib/importExport.ts +++ b/materialious/src/lib/importExport.ts @@ -1,6 +1,7 @@ import z from 'zod'; import type { Subscription } from './api/model'; import { getChannel, postSubscribe } from './api'; +import Papa from 'papaparse'; const zInvidiousSubs = z.object({ subscriptions: z.array(z.string()) @@ -16,6 +17,32 @@ const zFreetubeSubs = z.object({ ) }); +const zNewPipeSubs = z.object({ + subscriptions: z.array( + z.object({ + service_id: z.number(), + url: z.url(), + name: z.string() + }) + ) +}); + +const zYouTubeSubs = z.array( + z.object({ + snippet: z.object({ + channelId: z.string(), + title: z.string() + }) + }) +); + +const zYouTubeCsvSubs = z.array( + z.object({ + 'Channel ID': z.string(), + 'Channel title': z.string() + }) +); + export async function importSubscriptions(subscriptions: Subscription[]) { const subPromises: Promise[] = []; for (const sub of subscriptions) { @@ -64,7 +91,7 @@ export async function importSubscriptionsFromFile(file: File) { authorId: channelId }); } - } else { + } else if (file.name.endsWith('.json')) { let fileJson: Record | undefined; try { fileJson = JSON.parse(fileContents); @@ -75,6 +102,8 @@ export async function importSubscriptionsFromFile(file: File) { if (fileJson) { const invidiousSubs = zInvidiousSubs.safeParse(fileJson); const freetubeSubs = zFreetubeSubs.safeParse(fileJson); + const newPipeSubs = zNewPipeSubs.safeParse(fileJson); + const youtubeSubs = zYouTubeSubs.safeParse(fileJson); if (invidiousSubs.success) { for (const authorId of invidiousSubs.data.subscriptions) { @@ -95,6 +124,37 @@ export async function importSubscriptionsFromFile(file: File) { authorId: sub.id }); } + } else if (newPipeSubs.success) { + for (const sub of newPipeSubs.data.subscriptions) { + const authorId = sub.url.split('/')[4]; + if (typeof authorId !== 'string') continue; + subsToImport.push({ + author: sub.name, + authorId + }); + } + } else if (youtubeSubs.success) { + for (const sub of youtubeSubs.data) { + subsToImport.push({ + author: sub.snippet.title, + authorId: sub.snippet.channelId + }); + } + } + } + } else if (file.name.endsWith('.csv')) { + const csv = Papa.parse(fileContents, { header: true, skipEmptyLines: true }); + + if (csv.data.length > 0) { + const youtubeSubs = zYouTubeCsvSubs.safeParse(csv.data); + + if (youtubeSubs.success) { + for (const sub of youtubeSubs.data) { + subsToImport.push({ + author: sub['Channel title'], + authorId: sub['Channel ID'] + }); + } } } }