From c7a839573a1156fc3884f22a8088f4c60db2596d Mon Sep 17 00:00:00 2001 From: Shiba <44804845+DeepDoge@users.noreply.github.com> Date: Sat, 11 Dec 2021 03:38:45 +0300 Subject: [PATCH 1/5] Update ytContent.tsx --- src/scripts/ytContent.tsx | 49 ++++++++++++++++++++++++++++++++++----- 1 file changed, 43 insertions(+), 6 deletions(-) diff --git a/src/scripts/ytContent.tsx b/src/scripts/ytContent.tsx index 814aa22..fc20257 100644 --- a/src/scripts/ytContent.tsx +++ b/src/scripts/ytContent.tsx @@ -47,9 +47,10 @@ async function handleURLChange(ctx: UpdateContext, { onRedirect, onURL }: Update if (ctx.enabled && onRedirect) onRedirect(ctx); } +const sleep = (t: number) => new Promise(resolve => setTimeout(resolve, t)); + /** Returns a mount point for the button */ async function findMountPoint(): Promise { - const sleep = (t: number) => new Promise(resolve => setTimeout(resolve, t)); let ownerBar = document.querySelector('ytd-video-owner-renderer'); for (let i = 0; !ownerBar && i < 50; i++) { await sleep(200); @@ -63,12 +64,16 @@ async function findMountPoint(): Promise { return div; } -function WatchOnLbryButton({ redirect = 'app', url }: { redirect?: LbrySettings['redirect'], url?: string }) { - if (!url) return null; +function WatchOnLbryButton({ redirect = 'app', url: pathname, time }: { redirect?: LbrySettings['redirect'], url?: string, time?: string }) { + if (!pathname) return null; const domain = redirectDomains[redirect]; const buttonSetting = buttonSettings[redirect]; + + const url = new URL(`${domain.prefix}${pathname}`) + if (time) url.searchParams.append('t', time) + return
- @@ -91,7 +96,40 @@ function WatchOnLbryButton({ redirect = 'app', url }: { redirect?: LbrySettings[ const mountPointPromise = findMountPoint(); -const handle = (ctx: UpdateContext) => handleURLChange(ctx, { + +let ctxCache: UpdateContext | undefined + +{(async () => { + let videoElement: HTMLVideoElement | null = null; + let renderingButton = false + + const handleTimeChange = () => { + if (renderingButton) return + if (!videoElement) return + if (!ctxCache?.url) return + const time = (videoElement.currentTime ?? 0).toFixed(0) + const { url, redirect } = ctxCache + + renderingButton = true + mountPointPromise.then(mountPoint => mountPoint && render(, mountPoint)) + .then(() => renderingButton = false) + } + + while (true) { + await sleep(200) + if (!videoElement) { + videoElement = document.querySelector('video') + if (videoElement) videoElement.addEventListener('timeupdate', handleTimeChange) + } + else if (!videoElement.parentElement) { + videoElement.removeEventListener('timeupdate', handleTimeChange) + videoElement = null + } + } +})()} + + +const handle = (ctx: UpdateContext) => (ctxCache = ctx) && ctx.url && handleURLChange(ctx, { async onURL({ descriptor: { type }, url, redirect }) { const mountPoint = await mountPointPromise; if (type !== 'video' || !mountPoint) return; @@ -113,7 +151,6 @@ chrome.runtime.sendMessage({ url: location.href }, async (ctx: UpdateContext) => */ chrome.runtime.onMessage.addListener(async (ctx: UpdateContext) => { mountPointPromise.then(mountPoint => mountPoint && render(, mountPoint)) - if (!ctx.url) return; handle(ctx); }); From 8c4f3e60e00f5b757aa9f45d7efb083a8cba430f Mon Sep 17 00:00:00 2001 From: Shiba <44804845+DeepDoge@users.noreply.github.com> Date: Sat, 11 Dec 2021 04:23:36 +0300 Subject: [PATCH 2/5] Update ytContent.tsx Turns out YouTube doesn't destroy the `HTMLVideoElement` once its created, so no need to check if its destroyed --- src/scripts/ytContent.tsx | 56 +++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 32 deletions(-) diff --git a/src/scripts/ytContent.tsx b/src/scripts/ytContent.tsx index fc20257..38708a8 100644 --- a/src/scripts/ytContent.tsx +++ b/src/scripts/ytContent.tsx @@ -41,6 +41,8 @@ async function resolveYT(descriptor: YTDescriptor) { return segments.join('/'); } + + /** Compute the URL and determine whether or not a redirect should be performed. Delegates the redirect to callbacks. */ async function handleURLChange(ctx: UpdateContext, { onRedirect, onURL }: UpdaterOptions): Promise { if (onURL) onURL(ctx); @@ -61,6 +63,7 @@ async function findMountPoint(): Promise { const div = document.createElement('div'); div.style.display = 'flex'; ownerBar.insertAdjacentElement('afterend', div); + return div; } @@ -96,39 +99,7 @@ function WatchOnLbryButton({ redirect = 'app', url: pathname, time }: { redirect const mountPointPromise = findMountPoint(); - let ctxCache: UpdateContext | undefined - -{(async () => { - let videoElement: HTMLVideoElement | null = null; - let renderingButton = false - - const handleTimeChange = () => { - if (renderingButton) return - if (!videoElement) return - if (!ctxCache?.url) return - const time = (videoElement.currentTime ?? 0).toFixed(0) - const { url, redirect } = ctxCache - - renderingButton = true - mountPointPromise.then(mountPoint => mountPoint && render(, mountPoint)) - .then(() => renderingButton = false) - } - - while (true) { - await sleep(200) - if (!videoElement) { - videoElement = document.querySelector('video') - if (videoElement) videoElement.addEventListener('timeupdate', handleTimeChange) - } - else if (!videoElement.parentElement) { - videoElement.removeEventListener('timeupdate', handleTimeChange) - videoElement = null - } - } -})()} - - const handle = (ctx: UpdateContext) => (ctxCache = ctx) && ctx.url && handleURLChange(ctx, { async onURL({ descriptor: { type }, url, redirect }) { const mountPoint = await mountPointPromise; @@ -142,6 +113,27 @@ const handle = (ctx: UpdateContext) => (ctxCache = ctx) && ctx.url && handleURLC }, }); +{(async () => { + let videoElement: HTMLVideoElement | null = null; + let renderingButton = false + + while(!(videoElement = document.querySelector('video'))) await sleep(200) + + const handleTimeChange = () => { + if (renderingButton) return + if (!videoElement) return + if (!ctxCache?.url) return + const time = (videoElement.currentTime ?? 0).toFixed(0) + const { url, redirect } = ctxCache + + renderingButton = true + mountPointPromise.then(mountPoint => mountPoint && render(, mountPoint)) + .then(() => renderingButton = false) + } + + videoElement.addEventListener('timeupdate', handleTimeChange) +})()} + // handle the location on load of the page chrome.runtime.sendMessage({ url: location.href }, async (ctx: UpdateContext) => handle(ctx)); From 5c61db3ea02d46895330cbb866b44836fa65aea6 Mon Sep 17 00:00:00 2001 From: Shiba <44804845+DeepDoge@users.noreply.github.com> Date: Sat, 11 Dec 2021 19:28:24 +0000 Subject: [PATCH 3/5] types added, names changed, rewrote most of ytContent.ts - Added more types, so when there is an error it's more visible. - Default setting was using `lbry.tv` which doesn't exists anymore, so i made it odysee. - Changed `redirect` value name in the `LbrySettings` to `platform` which makes more sense to this version. - Changed `url` in UpdateContext to `pathname`. Using `url` for the full URL. - Rewrote most of the `ytContent.tsx` so the timestamp feature doesnt look like a patch. --- .gitignore | 2 + src/common/settings.ts | 30 +++-- src/popup/popup.tsx | 20 ++-- src/scripts/tabOnUpdated.ts | 24 ++-- src/scripts/ytContent.tsx | 218 +++++++++++++++++++----------------- src/tools/YTtoLBRY.tsx | 14 +-- 6 files changed, 166 insertions(+), 142 deletions(-) diff --git a/.gitignore b/.gitignore index a228be6..5aa10a7 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,7 @@ dist node_modules web-ext-artifacts +yarn-error.log +.devcontainer .DS_Store diff --git a/src/common/settings.ts b/src/common/settings.ts index 795a95a..dfb4ea4 100644 --- a/src/common/settings.ts +++ b/src/common/settings.ts @@ -1,16 +1,28 @@ -export interface LbrySettings { - enabled: boolean - redirect: keyof typeof redirectDomains +export type PlatformName = 'madiator.com' | 'odysee' | 'app' + +export interface PlatformSettings +{ + domainPrefix: string + display: string } -export const DEFAULT_SETTINGS: LbrySettings = { enabled: true, redirect: 'lbry.tv' }; - -export const redirectDomains = { - 'madiator.com': { prefix: 'https://madiator.com/', display: 'madiator.com' }, - odysee: { prefix: 'https://odysee.com/', display: 'odysee' }, - app: { prefix: 'lbry://', display: 'App' }, +export const platformSettings: Record = { + 'madiator.com': { domainPrefix: 'https://madiator.com/', display: 'madiator.com' }, + odysee: { domainPrefix: 'https://odysee.com/', display: 'odysee' }, + app: { domainPrefix: 'lbry://', display: 'App' }, }; +export const getPlatfromSettingsEntiries = () => { + return Object.entries(platformSettings) as any as [Extract, PlatformSettings][] +} + +export interface LbrySettings { + enabled: boolean + platform: PlatformName +} + +export const DEFAULT_SETTINGS: LbrySettings = { enabled: true, platform: 'odysee' }; + export function getSettingsAsync>(...keys: K): Promise> { return new Promise(resolve => chrome.storage.local.get(keys, o => resolve(o as any))); } diff --git a/src/popup/popup.tsx b/src/popup/popup.tsx index 9874700..c7ef962 100644 --- a/src/popup/popup.tsx +++ b/src/popup/popup.tsx @@ -1,28 +1,28 @@ -import { h, render } from 'preact'; +import { h, render } from 'preact' +import ButtonRadio, { SelectionOption } from '../common/components/ButtonRadio' +import { getPlatfromSettingsEntiries, LbrySettings, PlatformName } from '../common/settings' +import { useLbrySettings } from '../common/useSettings' +import './popup.sass' -import ButtonRadio, { SelectionOption } from '../common/components/ButtonRadio'; -import { redirectDomains } from '../common/settings'; -import { useLbrySettings } from '../common/useSettings'; -import './popup.sass'; /** Utilty to set a setting in the browser */ -const setSetting = (setting: string, value: any) => chrome.storage.local.set({ [setting]: value }); +const setSetting = (setting: K, value: LbrySettings[K]) => chrome.storage.local.set({ [setting]: value }); /** Gets all the options for redirect destinations as selection options */ -const redirectOptions: SelectionOption[] = Object.entries(redirectDomains) +const platformOptions: SelectionOption[] = getPlatfromSettingsEntiries() .map(([value, { display }]) => ({ value, display })); function WatchOnLbryPopup() { - const { enabled, redirect } = useLbrySettings(); + const { enabled, platform } = useLbrySettings(); return
setSetting('enabled', enabled.toLowerCase() === 'yes')} /> - setSetting('redirect', redirect)} /> + setSetting('platform', platform)} /> diff --git a/src/scripts/tabOnUpdated.ts b/src/scripts/tabOnUpdated.ts index 053d919..aeb8316 100644 --- a/src/scripts/tabOnUpdated.ts +++ b/src/scripts/tabOnUpdated.ts @@ -1,12 +1,12 @@ -import { appRedirectUrl, parseProtocolUrl } from '../common/lbry-url'; -import { getSettingsAsync, LbrySettings } from '../common/settings'; -import { YTDescriptor, ytService } from '../common/yt'; +import { appRedirectUrl, parseProtocolUrl } from '../common/lbry-url' +import { getSettingsAsync, PlatformName } from '../common/settings' +import { YTDescriptor, ytService } from '../common/yt' export interface UpdateContext { descriptor: YTDescriptor /** LBRY URL fragment */ - url: string + pathname: string enabled: boolean - redirect: LbrySettings['redirect'] + platform: PlatformName } async function resolveYT(descriptor: YTDescriptor) { @@ -16,26 +16,26 @@ async function resolveYT(descriptor: YTDescriptor) { return segments.join('/'); } -const urlCache: Record = {}; +const pathnameCache: Record = {}; async function ctxFromURL(url: string): Promise { if (!url || !(url.startsWith('https://www.youtube.com/watch?v=') || url.startsWith('https://www.youtube.com/channel/'))) return; url = new URL(url).href; - const { enabled, redirect } = await getSettingsAsync('enabled', 'redirect'); + const { enabled, platform } = await getSettingsAsync('enabled', 'platform'); const descriptor = ytService.getId(url); if (!descriptor) return; // couldn't get the ID, so we're done - const res = url in urlCache ? urlCache[url] : await resolveYT(descriptor); - urlCache[url] = res; + const res = url in pathnameCache ? pathnameCache[url] : await resolveYT(descriptor); + pathnameCache[url] = res; if (!res) return; // couldn't find it on lbry, so we're done - return { descriptor, url: res, enabled, redirect }; + return { descriptor, pathname: res, enabled, platform }; } // handles lbry.tv -> lbry app redirect chrome.tabs.onUpdated.addListener(async (tabId, changeInfo, { url: tabUrl }) => { - const { enabled, redirect } = await getSettingsAsync('enabled', 'redirect'); - if (!enabled || redirect !== 'app' || !changeInfo.url || !tabUrl?.startsWith('https://odysee.com/')) return; + const { enabled, platform } = await getSettingsAsync('enabled', 'platform'); + if (!enabled || platform !== 'app' || !changeInfo.url || !tabUrl?.startsWith('https://odysee.com/')) return; const url = appRedirectUrl(tabUrl, { encode: true }); if (!url) return; diff --git a/src/scripts/ytContent.tsx b/src/scripts/ytContent.tsx index 38708a8..c2473b9 100644 --- a/src/scripts/ytContent.tsx +++ b/src/scripts/ytContent.tsx @@ -1,16 +1,8 @@ -import { h, JSX, render } from 'preact'; +import { PlatformName, platformSettings } from '../common/settings' +import type { UpdateContext } from '../scripts/tabOnUpdated' +import { h, JSX, render } from 'preact' -import { parseProtocolUrl } from '../common/lbry-url'; -import { LbrySettings, redirectDomains } from '../common/settings'; -import { YTDescriptor, ytService } from '../common/yt'; -import { UpdateContext } from './tabOnUpdated'; - -interface UpdaterOptions { - /** invoked if a redirect should be performed */ - onRedirect?(ctx: UpdateContext): void - /** invoked if a URL is found */ - onURL?(ctx: UpdateContext): void -} +const sleep = (t: number) => new Promise(resolve => setTimeout(resolve, t)); interface ButtonSettings { text: string @@ -18,62 +10,35 @@ interface ButtonSettings { style?: JSX.CSSProperties } -const buttonSettings: Record = { - app: { text: 'Watch on LBRY', icon: chrome.runtime.getURL('icons/lbry/lbry-logo.svg') }, - 'madiator.com': { text: 'Watch on LBRY', icon: chrome.runtime.getURL('icons/lbry/lbry-logo.svg') }, +const buttonSettings: Record = { + app: { + text: 'Watch on LBRY', + icon: chrome.runtime.getURL('icons/lbry/lbry-logo.svg') + }, + 'madiator.com': { + text: 'Watch on LBRY', + icon: chrome.runtime.getURL('icons/lbry/lbry-logo.svg') + }, odysee: { text: 'Watch on Odysee', icon: chrome.runtime.getURL('icons/lbry/odysee-logo.svg'), style: { backgroundColor: '#1e013b' }, }, }; -function pauseVideo() { document.querySelectorAll('video').forEach(v => v.pause()); } - -function openApp(url: string) { - pauseVideo(); - location.assign(url); +interface ButtonParameters +{ + platform?: PlatformName + pathname?: string + time?: number } -async function resolveYT(descriptor: YTDescriptor) { - const lbryProtocolUrl: string | null = await ytService.resolveById(descriptor).then(a => a[0]); - const segments = parseProtocolUrl(lbryProtocolUrl || '', { encode: true }); - if (segments.length === 0) return; - return segments.join('/'); -} +export function WatchOnLbryButton({ platform = 'app', pathname, time }: ButtonParameters) { + if (!pathname || !platform) return null; + const platformSetting = platformSettings[platform]; + const buttonSetting = buttonSettings[platform]; - - -/** Compute the URL and determine whether or not a redirect should be performed. Delegates the redirect to callbacks. */ -async function handleURLChange(ctx: UpdateContext, { onRedirect, onURL }: UpdaterOptions): Promise { - if (onURL) onURL(ctx); - if (ctx.enabled && onRedirect) onRedirect(ctx); -} - -const sleep = (t: number) => new Promise(resolve => setTimeout(resolve, t)); - -/** Returns a mount point for the button */ -async function findMountPoint(): Promise { - let ownerBar = document.querySelector('ytd-video-owner-renderer'); - for (let i = 0; !ownerBar && i < 50; i++) { - await sleep(200); - ownerBar = document.querySelector('ytd-video-owner-renderer'); - } - - if (!ownerBar) return; - const div = document.createElement('div'); - div.style.display = 'flex'; - ownerBar.insertAdjacentElement('afterend', div); - - return div; -} - -function WatchOnLbryButton({ redirect = 'app', url: pathname, time }: { redirect?: LbrySettings['redirect'], url?: string, time?: string }) { - if (!pathname) return null; - const domain = redirectDomains[redirect]; - const buttonSetting = buttonSettings[redirect]; - - const url = new URL(`${domain.prefix}${pathname}`) - if (time) url.searchParams.append('t', time) + const url = new URL(`${platformSetting.domainPrefix}${pathname}`) + if (time) url.searchParams.append('t', time.toFixed(0)) return
; } - -const mountPointPromise = findMountPoint(); - -let ctxCache: UpdateContext | undefined -const handle = (ctx: UpdateContext) => (ctxCache = ctx) && ctx.url && handleURLChange(ctx, { - async onURL({ descriptor: { type }, url, redirect }) { - const mountPoint = await mountPointPromise; - if (type !== 'video' || !mountPoint) return; - render(, mountPoint); - }, - onRedirect({ redirect, url }) { - const domain = redirectDomains[redirect]; - if (redirect === 'app') return openApp(domain.prefix + url); - location.replace(domain.prefix + url); - }, -}); - -{(async () => { - let videoElement: HTMLVideoElement | null = null; - let renderingButton = false - - while(!(videoElement = document.querySelector('video'))) await sleep(200) - - const handleTimeChange = () => { - if (renderingButton) return - if (!videoElement) return - if (!ctxCache?.url) return - const time = (videoElement.currentTime ?? 0).toFixed(0) - const { url, redirect } = ctxCache - - renderingButton = true - mountPointPromise.then(mountPoint => mountPoint && render(, mountPoint)) - .then(() => renderingButton = false) +let mountPoint: HTMLDivElement | null = null +/** Returns a mount point for the button */ +async function findButtonMountPoint(): Promise { + let ownerBar = document.querySelector('ytd-video-owner-renderer'); + for (let i = 0; !ownerBar && i < 50; i++) { + await sleep(200); + ownerBar = document.querySelector('ytd-video-owner-renderer'); } - videoElement.addEventListener('timeupdate', handleTimeChange) -})()} + if (!ownerBar) return; + const div = document.createElement('div'); + div.style.display = 'flex'; + ownerBar.insertAdjacentElement('afterend', div); -// handle the location on load of the page -chrome.runtime.sendMessage({ url: location.href }, async (ctx: UpdateContext) => handle(ctx)); + mountPoint = div +} + +let videoElement: HTMLVideoElement | null = null; +async function findVideoElement() { + while(!(videoElement = document.querySelector('#ytd-player video'))) await sleep(200) + videoElement.addEventListener('timeupdate', () => updateButton(ctxCache)) +} + +function pauseVideo() { document.querySelectorAll('video').forEach(v => v.pause()); } + +function openApp(url: string) { + pauseVideo(); + location.assign(url); +} + +/** Compute the URL and determine whether or not a redirect should be performed. Delegates the redirect to callbacks. */ +let ctxCache: UpdateContext | null = null +function handleURLChange (ctx: UpdateContext | null) { + ctxCache = ctx + updateButton(ctx) + if (ctx?.enabled) redirectTo(ctx) +} + +function updateButton(ctx: UpdateContext | null) { + if (!mountPoint) return + if (!ctx) return render(, mountPoint) + if (ctx.descriptor.type !== 'video') return; + const time = videoElement?.currentTime ?? 0 + const pathname = ctx.pathname + const platform = ctx.platform + + render(, mountPoint) +} + +function redirectTo({ platform, pathname }: UpdateContext) { + + const parseYouTubeTime = (timeString: string) => { + const signs = timeString.replace(/[0-9]/g, '') + if (signs.length === 0) return timeString + const numbers = timeString.replace(/^[0-9]/g, '-').split('-') + let total = 0 + for (let i = 0; i < signs.length; i++) { + let t = parseInt(numbers[i]) + switch (signs[i]) { + case 's': case 'm': t *= 60; case 'h': t *= 60; case 'd': t *= 24; break + default: return '0' + } + total += t + } + return total.toString() + } + + const platformSetting = platformSettings[platform]; + const url = new URL(`${platformSetting.domainPrefix}${pathname}`) + const time = new URL(location.href).searchParams.get('t') + if (time) url.searchParams.append('t', parseYouTubeTime(time)) + + if (platform === 'app') return openApp(url.toString()); + location.replace(url.toString()); +} + + + +findButtonMountPoint().then(() => updateButton(ctxCache)) +findVideoElement().then(() => updateButton(ctxCache)) + + +/** Request UpdateContext from background */ +const requestCtxFromUrl = async (url: string) => await new Promise((resolve) => chrome.runtime.sendMessage({ url }, resolve)) + +/** Handle the location on load of the page */ +requestCtxFromUrl(location.href).then((ctx) => handleURLChange(ctx)) /* * Gets messages from background script which relays tab update events. This is because there's no sensible way to detect * history.pushState changes from a content script */ -chrome.runtime.onMessage.addListener(async (ctx: UpdateContext) => { - mountPointPromise.then(mountPoint => mountPoint && render(, mountPoint)) - handle(ctx); -}); +chrome.runtime.onMessage.addListener(async (ctx: UpdateContext) => handleURLChange(ctx)); -chrome.storage.onChanged.addListener((changes, areaName) => { - if (areaName !== 'local' || !changes.redirect) return; - chrome.runtime.sendMessage({ url: location.href }, async (ctx: UpdateContext) => handle(ctx)); -}); +/** On settings change */ +chrome.storage.onChanged.addListener(async (changes, areaName) => { + if (areaName !== 'local') return; + if (changes.platform) handleURLChange(await requestCtxFromUrl(location.href)) +}); \ No newline at end of file diff --git a/src/tools/YTtoLBRY.tsx b/src/tools/YTtoLBRY.tsx index f998af5..87cf16f 100644 --- a/src/tools/YTtoLBRY.tsx +++ b/src/tools/YTtoLBRY.tsx @@ -1,10 +1,10 @@ -import { Fragment, h, JSX, render } from 'preact'; -import { useState } from 'preact/hooks'; +import { h, render } from 'preact' +import { useState } from 'preact/hooks' +import { getSettingsAsync, platformSettings } from '../common/settings' +import { getFileContent, ytService } from '../common/yt' +import readme from './README.md' -import { getSettingsAsync, redirectDomains } from '../common/settings'; -import { getFileContent, ytService } from '../common/yt'; -import readme from './README.md'; /** * Parses the subscription file and queries the API for lbry channels @@ -18,8 +18,8 @@ async function lbryChannelsFromFile(file: File) { const ids = new Set((ext === 'xml' || ext == 'opml' ? ytService.readOpml(content) : ytService.readJson(content))) const lbryUrls = await ytService.resolveById(...Array.from(ids).map(id => ({ id, type: 'channel' } as const))); - const { redirect } = await getSettingsAsync('redirect'); - const urlPrefix = redirectDomains[redirect].prefix; + const { platform } = await getSettingsAsync('platform'); + const urlPrefix = platformSettings[platform].domainPrefix; return lbryUrls.map(channel => urlPrefix + channel); } From 6cc149a5c625e41978577ff16e326c11f5f2927c Mon Sep 17 00:00:00 2001 From: Shiba <44804845+DeepDoge@users.noreply.github.com> Date: Sat, 11 Dec 2021 20:05:30 +0000 Subject: [PATCH 4/5] parsing yt time from url fixed --- src/scripts/ytContent.tsx | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/src/scripts/ytContent.tsx b/src/scripts/ytContent.tsx index c2473b9..a53d45e 100644 --- a/src/scripts/ytContent.tsx +++ b/src/scripts/ytContent.tsx @@ -111,30 +111,31 @@ function updateButton(ctx: UpdateContext | null) { } function redirectTo({ platform, pathname }: UpdateContext) { - - const parseYouTubeTime = (timeString: string) => { - const signs = timeString.replace(/[0-9]/g, '') - if (signs.length === 0) return timeString - const numbers = timeString.replace(/^[0-9]/g, '-').split('-') - let total = 0 - for (let i = 0; i < signs.length; i++) { - let t = parseInt(numbers[i]) - switch (signs[i]) { - case 's': case 'm': t *= 60; case 'h': t *= 60; case 'd': t *= 24; break - default: return '0' - } - total += t + + const parseYouTubeTime = (timeString: string) => { + const signs = timeString.replace(/[0-9]/g, '') + if (signs.length === 0) return timeString + const numbers = timeString.replace(/[^0-9]/g, '-').split('-') + let total = 0 + for (let i = 0; i < signs.length; i++) { + let t = parseInt(numbers[i]) + switch (signs[i]) { + case 'd': t *= 24; case 'h': t *= 60; case 'm': t *= 60; case 's': break + default: return '0' } + total += t + } return total.toString() } - const platformSetting = platformSettings[platform]; - const url = new URL(`${platformSetting.domainPrefix}${pathname}`) - const time = new URL(location.href).searchParams.get('t') - if (time) url.searchParams.append('t', parseYouTubeTime(time)) + const platformSetting = platformSettings[platform]; + const url = new URL(`${platformSetting.domainPrefix}${pathname}`) + const time = new URL(location.href).searchParams.get('t') + + if (time) url.searchParams.append('t', parseYouTubeTime(time)) - if (platform === 'app') return openApp(url.toString()); - location.replace(url.toString()); + if (platform === 'app') return openApp(url.toString()); + location.replace(url.toString()); } From dfdfe6778a22b4642a6cd9d84c8895adbf12d42f Mon Sep 17 00:00:00 2001 From: Shiba <44804845+DeepDoge@users.noreply.github.com> Date: Sat, 11 Dec 2021 21:38:47 +0000 Subject: [PATCH 5/5] Added CSV support for subscribtion converter --- src/common/yt.ts | 15 ++++++++++++++- src/tools/YTtoLBRY.tsx | 8 +++++--- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/common/yt.ts b/src/common/yt.ts index c72565b..1e2f08d 100644 --- a/src/common/yt.ts +++ b/src/common/yt.ts @@ -57,7 +57,7 @@ export const ytService = { */ readOpml(opmlContents: string): string[] { const opml = new DOMParser().parseFromString(opmlContents, 'application/xml'); - + opmlContents = '' return Array.from(opml.querySelectorAll('outline > outline')) .map(outline => outline.getAttribute('xmlUrl')) .filter((url): url is string => !!url) @@ -73,9 +73,22 @@ export const ytService = { */ readJson(jsonContents: string): string[] { const subscriptions: YtSubscription[] = JSON.parse(jsonContents); + jsonContents = '' return subscriptions.map(sub => sub.snippet.resourceId.channelId); }, + /** + * Reads an array of YT channel IDs from the YT subscriptions CSV file + * + * @param csvContent a CSV file as a string + * @returns the channel IDs + */ + readCsv(csvContent: string): string[] { + const rows = csvContent.split('\n') + csvContent = '' + return rows.map((row) => row.substr(0, row.indexOf(','))) + }, + /** * Extracts the channelID from a YT URL. * diff --git a/src/tools/YTtoLBRY.tsx b/src/tools/YTtoLBRY.tsx index 87cf16f..107c30d 100644 --- a/src/tools/YTtoLBRY.tsx +++ b/src/tools/YTtoLBRY.tsx @@ -14,9 +14,11 @@ import readme from './README.md' */ async function lbryChannelsFromFile(file: File) { const ext = file.name.split('.').pop()?.toLowerCase(); - const content = await getFileContent(file); - - const ids = new Set((ext === 'xml' || ext == 'opml' ? ytService.readOpml(content) : ytService.readJson(content))) + + const ids = new Set(( + ext === 'xml' || ext == 'opml' ? ytService.readOpml : + ext === 'csv' ? ytService.readCsv : + ytService.readJson)(await getFileContent(file))) const lbryUrls = await ytService.resolveById(...Array.from(ids).map(id => ({ id, type: 'channel' } as const))); const { platform } = await getSettingsAsync('platform'); const urlPrefix = platformSettings[platform].domainPrefix;