Support YouTube takeout JSON

This commit is contained in:
Kevin Raoofi 2020-11-11 14:21:34 -05:00
parent 08842c4ba8
commit bd5adf6652
2 changed files with 43 additions and 13 deletions

View file

@ -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<string, string>
channels?: Record<string, string>
}
videos?: Record<string, string>;
channels?: Record<string, string>;
};
}
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);
}));

View file

@ -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<string[]> {
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 <>
<iframe width="100%" height="400px" allowFullScreen
src="https://lbry.tv/$/embed/howtouseconverter/c9827448d6ac7a74ecdb972c5cdf9ddaf648a28e" />
@ -35,7 +39,7 @@ function YTtoLBRY() {
<input type="button" value="Start Conversion!" class="goButton" disabled={!file || isLoading} onClick={async () => {
if (!file) return;
setLoading(true);
setLbryChannels(await lbryChannelsFromOpml(file));
setLbryChannels(await lbryChannelsFromFile(file));
setLoading(false);
}} />
<ul>