From bd5adf66528571459be4461735af515f13752e99 Mon Sep 17 00:00:00 2001 From: Kevin Raoofi Date: Wed, 11 Nov 2020 14:21:34 -0500 Subject: [PATCH 1/5] Support YouTube takeout JSON --- src/common/yt.ts | 40 +++++++++++++++++++++++++++++++++------- src/tools/YTtoLBRY.tsx | 16 ++++++++++------ 2 files changed, 43 insertions(+), 13 deletions(-) diff --git a/src/common/yt.ts b/src/common/yt.ts index 9ee3e53..7cd5df3 100644 --- a/src/common/yt.ts +++ b/src/common/yt.ts @@ -6,12 +6,24 @@ const LBRY_API_HOST = 'https://api.lbry.com'; const QUERY_CHUNK_SIZE = 300; interface YtResolverResponse { - success: boolean - error: object | null + success: boolean; + error: object | null; data: { - videos?: Record - channels?: Record - } + videos?: Record; + channels?: Record; + }; +} + +interface YtSubscription { + id: string; + etag: string; + title: string; + snippet: { + description: string; + resourceId: { + channelId: string; + }; + }; } /** @@ -41,15 +53,29 @@ export const ytService = { * Reads the array of YT channels from an OPML file * * @param opmlContents an opml file as as tring - * @returns the channel URLs + * @returns the channel IDs */ readOpml(opmlContents: string): string[] { const opml = new DOMParser().parseFromString(opmlContents, 'application/xml'); + return Array.from(opml.querySelectorAll('outline > outline')) .map(outline => outline.getAttribute('xmlUrl')) + .filter((url): url is string => !!url) + .map(url => ytService.getChannelId(url)) .filter((url): url is string => !!url); // we don't want it if it's empty }, + /** + * Reads an array of YT channel IDs from the YT subscriptions JSON file + * + * @param jsonContents a JSON file as a string + * @returns the channel IDs + */ + readJson(jsonContents: string): string[] { + const subscriptions: YtSubscription[] = JSON.parse(jsonContents); + return subscriptions.map(sub => sub.snippet.resourceId.channelId); + }, + /** * Extracts the channelID from a YT URL. * @@ -89,7 +115,7 @@ export const ytService = { video_ids: groups['video']?.map(s => s.id).join(','), channel_ids: groups['channel']?.map(s => s.id).join(','), })); - return fetch(`${LBRY_API_HOST}/yt/resolve?${params}`, {cache: 'force-cache'}) + return fetch(`${LBRY_API_HOST}/yt/resolve?${params}`, { cache: 'force-cache' }) .then(rsp => rsp.ok ? rsp.json() : null); })); diff --git a/src/tools/YTtoLBRY.tsx b/src/tools/YTtoLBRY.tsx index 7680422..e553455 100644 --- a/src/tools/YTtoLBRY.tsx +++ b/src/tools/YTtoLBRY.tsx @@ -5,16 +5,19 @@ import { getSettingsAsync, redirectDomains } from '../common/settings'; import { YTDescriptor, getFileContent, ytService } from '../common/yt'; /** - * Parses OPML file and queries the API for lbry channels + * Parses the subscription file and queries the API for lbry channels * * @param file to read * @returns a promise with the list of channels that were found on lbry */ -async function lbryChannelsFromOpml(file: File): Promise { - const lbryUrls = await ytService.resolveById(...ytService.readOpml(await getFileContent(file)) - .map(url => ytService.getId(url)) - .filter((id): id is YTDescriptor => !!id)); +async function lbryChannelsFromFile(file: File) { + const ext = file.name.split('.').pop()?.toLowerCase(); + const content = await getFileContent(file); + const ids: YTDescriptor[] = (ext === 'xml' || ext == 'opml' ? ytService.readOpml(content) : ytService.readJson(content)) + .map(id => ({ id, type: 'channel' })); + + const lbryUrls = await ytService.resolveById(...ids); const { redirect } = await getSettingsAsync('redirect'); const urlPrefix = redirectDomains[redirect].prefix; return lbryUrls.map(channel => urlPrefix + channel); @@ -24,6 +27,7 @@ function YTtoLBRY() { const [file, setFile] = useState(null as File | null); const [lbryChannels, setLbryChannels] = useState([] as string[]); const [isLoading, setLoading] = useState(false); + return <>