mirror of
https://github.com/LBRYFoundation/Watch-on-LBRY.git
synced 2025-08-23 17:47:26 +00:00
🍙 Refactor and bugfix
This commit is contained in:
parent
610b47d1e4
commit
30f077ba38
5 changed files with 123 additions and 143 deletions
|
@ -92,7 +92,7 @@ export function getChannelId(channelURL: string)
|
||||||
export function parseYouTubeURLTimeString(timeString: string)
|
export function parseYouTubeURLTimeString(timeString: string)
|
||||||
{
|
{
|
||||||
const signs = timeString.replace(/[0-9]/g, '')
|
const signs = timeString.replace(/[0-9]/g, '')
|
||||||
if (signs.length === 0) return 0
|
if (signs.length === 0) return null
|
||||||
const numbers = timeString.replace(/[^0-9]/g, '-').split('-')
|
const numbers = timeString.replace(/[^0-9]/g, '-').split('-')
|
||||||
let total = 0
|
let total = 0
|
||||||
for (let i = 0; i < signs.length; i++)
|
for (let i = 0; i < signs.length; i++)
|
||||||
|
@ -104,7 +104,7 @@ export function parseYouTubeURLTimeString(timeString: string)
|
||||||
case 'h': t *= 60
|
case 'h': t *= 60
|
||||||
case 'm': t *= 60
|
case 'm': t *= 60
|
||||||
case 's': break
|
case 's': break
|
||||||
default: return 0
|
default: return null
|
||||||
}
|
}
|
||||||
total += t
|
total += t
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,6 @@
|
||||||
|
|
||||||
let db: IDBDatabase | null = null
|
let db: IDBDatabase | null = null
|
||||||
|
|
||||||
// Throw if its not in the background
|
|
||||||
if (chrome.extension.getBackgroundPage() !== self) throw new Error()
|
|
||||||
|
|
||||||
if (typeof self.indexedDB !== 'undefined')
|
if (typeof self.indexedDB !== 'undefined')
|
||||||
{
|
{
|
||||||
const openRequest = indexedDB.open("yt-url-resolver-cache")
|
const openRequest = indexedDB.open("yt-url-resolver-cache")
|
||||||
|
|
|
@ -11,131 +11,110 @@ export interface YtIdResolverDescriptor
|
||||||
type: 'channel' | 'video'
|
type: 'channel' | 'video'
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param descriptorsWithIndex YT resource IDs to check
|
* @param descriptorsWithIndex YT resource IDs to check
|
||||||
* @returns a promise with the list of channels that were found on lbry
|
* @returns a promise with the list of channels that were found on lbry
|
||||||
*/
|
*/
|
||||||
export async function resolveById(descriptors: YtIdResolverDescriptor[], progressCallback?: (progress: number) => void): Promise<(string | null)[]>
|
export async function resolveById(descriptors: YtIdResolverDescriptor[], progressCallback?: (progress: number) => void): Promise<(string | null)[]>
|
||||||
{
|
{
|
||||||
const descriptorsWithIndex: (YtIdResolverDescriptor & { index: number })[] = descriptors.map((descriptor, index) => ({ ...descriptor, index }))
|
let descriptorsPayload: (YtIdResolverDescriptor & { index: number })[] = descriptors.map((descriptor, index) => ({ ...descriptor, index }))
|
||||||
descriptors = null as any
|
descriptors = null as any
|
||||||
const results: (string | null)[] = []
|
const results: (string | null)[] = [];
|
||||||
|
|
||||||
await Promise.all(descriptorsWithIndex.map(async (descriptor, index) =>
|
|
||||||
{
|
|
||||||
if (!descriptor) return
|
|
||||||
const cache = await LbryPathnameCache.get(descriptor.id)
|
|
||||||
|
|
||||||
// Cache can be null, if there is no lbry url yet
|
descriptorsPayload = (await Promise.all(descriptorsPayload.map(async (descriptor, index) =>
|
||||||
if (cache !== undefined)
|
{
|
||||||
{
|
if (!descriptor?.id) return
|
||||||
// Directly setting it to results
|
const cache = await LbryPathnameCache.get(descriptor.id)
|
||||||
results[index] = cache
|
|
||||||
|
|
||||||
// We remove it so we dont ask it to API
|
// Cache can be null, if there is no lbry url yet
|
||||||
descriptorsWithIndex.splice(index, 1)
|
if (cache !== undefined)
|
||||||
}
|
{
|
||||||
}))
|
// Null values shouldn't be in the results
|
||||||
|
if (cache) results[index] = cache
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const descriptorsChunks = chunk(descriptorsWithIndex, QUERY_CHUNK_SIZE)
|
return descriptor
|
||||||
let progressCount = 0
|
}))).filter((descriptor) => descriptor) as any
|
||||||
await Promise.all(descriptorsChunks.map(async (descriptorChunk) =>
|
|
||||||
{
|
|
||||||
const descriptorsGroupedByType: Record<YtIdResolverDescriptor['type'], typeof descriptorsWithIndex | null> = groupBy(descriptorChunk, (descriptor) => descriptor.type) as any
|
|
||||||
|
|
||||||
const { urlResolver: urlResolverSettingName } = await getExtensionSettingsAsync()
|
const descriptorsPayloadChunks = chunk(descriptorsPayload, QUERY_CHUNK_SIZE)
|
||||||
const urlResolverSetting = ytUrlResolversSettings[urlResolverSettingName]
|
let progressCount = 0
|
||||||
|
await Promise.all(descriptorsPayloadChunks.map(async (descriptorChunk) =>
|
||||||
|
{
|
||||||
|
const descriptorsGroupedByType: Record<YtIdResolverDescriptor['type'], typeof descriptorsPayload | null> = groupBy(descriptorChunk, (descriptor) => descriptor.type) as any
|
||||||
|
|
||||||
const url = new URL(`https://${urlResolverSetting.hostname}`)
|
const { urlResolver: urlResolverSettingName } = await getExtensionSettingsAsync()
|
||||||
|
const urlResolverSetting = ytUrlResolversSettings[urlResolverSettingName]
|
||||||
|
|
||||||
function followResponsePath<T>(response: any, responsePath: YtUrlResolveResponsePath)
|
const url = new URL(`https://${urlResolverSetting.hostname}`)
|
||||||
{
|
|
||||||
for (const path of responsePath)
|
|
||||||
{
|
|
||||||
switch (typeof path)
|
|
||||||
{
|
|
||||||
case 'string':
|
|
||||||
case 'number':
|
|
||||||
response = response[path]
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
switch (path)
|
|
||||||
{
|
|
||||||
case Keys:
|
|
||||||
response = Object.keys(response)
|
|
||||||
break
|
|
||||||
case Values:
|
|
||||||
response = Object.values(response)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return response as T
|
|
||||||
}
|
|
||||||
|
|
||||||
async function requestGroup(urlResolverFunction: YtUrlResolveFunction, descriptorsGroup: typeof descriptorsWithIndex)
|
function followResponsePath<T>(response: any, responsePath: YtUrlResolveResponsePath)
|
||||||
{
|
{
|
||||||
url.pathname = urlResolverFunction.pathname
|
for (const path of responsePath)
|
||||||
|
{
|
||||||
|
switch (typeof path)
|
||||||
|
{
|
||||||
|
case 'string': case 'number': response = response[path]; continue
|
||||||
|
}
|
||||||
|
switch (path)
|
||||||
|
{
|
||||||
|
case Keys: response = Object.keys(response); continue
|
||||||
|
case Values: response = Object.values(response); continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return response as T
|
||||||
|
}
|
||||||
|
|
||||||
if (urlResolverFunction.paramArraySeperator === SingleValueAtATime)
|
async function requestGroup(urlResolverFunction: YtUrlResolveFunction, descriptorsGroup: typeof descriptorsPayload)
|
||||||
{
|
{
|
||||||
await Promise.all(descriptorsGroup.map(async (descriptor) =>
|
url.pathname = urlResolverFunction.pathname
|
||||||
{
|
|
||||||
switch (null)
|
|
||||||
{
|
|
||||||
default:
|
|
||||||
if (!descriptor.id) break
|
|
||||||
url.searchParams.set(urlResolverFunction.paramName, descriptor.id)
|
|
||||||
|
|
||||||
const apiResponse = await fetch(url.toString(), { cache: 'no-store' })
|
if (urlResolverFunction.paramArraySeperator === SingleValueAtATime)
|
||||||
if (!apiResponse.ok)
|
{
|
||||||
{
|
await Promise.all(descriptorsGroup.map(async (descriptor) =>
|
||||||
// Some API might not respond with 200 if it can't find the url
|
{
|
||||||
if (apiResponse.status === 404) await LbryPathnameCache.put(null, descriptor.id)
|
url.searchParams.set(urlResolverFunction.paramName, descriptor.id)
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
const value = followResponsePath<string>(await apiResponse.json(), urlResolverFunction.responsePath)
|
const apiResponse = await fetch(url.toString(), { cache: 'no-store' })
|
||||||
if (value) results[descriptor.index] = value
|
if (apiResponse.ok)
|
||||||
await LbryPathnameCache.put(value, descriptor.id)
|
{
|
||||||
}
|
const value = followResponsePath<string>(await apiResponse.json(), urlResolverFunction.responsePath)
|
||||||
progressCount++
|
if (value) results[descriptor.index] = value
|
||||||
if (progressCallback) progressCallback(progressCount / descriptorsWithIndex.length)
|
await LbryPathnameCache.put(value, descriptor.id)
|
||||||
}))
|
}
|
||||||
}
|
else if (apiResponse.status === 404) await LbryPathnameCache.put(null, descriptor.id)
|
||||||
else
|
|
||||||
{
|
|
||||||
|
|
||||||
switch (null)
|
progressCount++
|
||||||
{
|
if (progressCallback) progressCallback(progressCount / descriptorsPayload.length)
|
||||||
default:
|
}))
|
||||||
url.searchParams
|
}
|
||||||
.set(urlResolverFunction.paramName, descriptorsGroup
|
else
|
||||||
.map((descriptor) => descriptor.id)
|
{
|
||||||
.filter((descriptorId) => descriptorId)
|
url.searchParams.set(urlResolverFunction.paramName, descriptorsGroup
|
||||||
.join(urlResolverFunction.paramArraySeperator)
|
.map((descriptor) => descriptor.id)
|
||||||
)
|
.join(urlResolverFunction.paramArraySeperator))
|
||||||
|
|
||||||
const apiResponse = await fetch(url.toString(), { cache: 'no-store' })
|
const apiResponse = await fetch(url.toString(), { cache: 'no-store' })
|
||||||
if (!apiResponse.ok) break
|
if (apiResponse.ok)
|
||||||
const values = followResponsePath<string[]>(await apiResponse.json(), urlResolverFunction.responsePath)
|
{
|
||||||
|
const values = followResponsePath<string[]>(await apiResponse.json(), urlResolverFunction.responsePath)
|
||||||
|
await Promise.all(values.map(async (value, index) =>
|
||||||
|
{
|
||||||
|
const descriptor = descriptorsGroup[index]
|
||||||
|
if (value) results[descriptor.index] = value
|
||||||
|
await LbryPathnameCache.put(value, descriptor.id)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
await Promise.all(values.map(async (value, index) =>
|
progressCount += descriptorsGroup.length
|
||||||
{
|
if (progressCallback) progressCallback(progressCount / descriptorsPayload.length)
|
||||||
const descriptor = descriptorsGroup[index]
|
}
|
||||||
if (value) results[descriptor.index] = value
|
}
|
||||||
await LbryPathnameCache.put(value, descriptor.id)
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
progressCount += descriptorsGroup.length
|
|
||||||
if (progressCallback) progressCallback(progressCount / descriptorsWithIndex.length)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (descriptorsGroupedByType['channel']) await requestGroup(urlResolverSetting.functions.getChannelId, descriptorsGroupedByType['channel'])
|
if (descriptorsGroupedByType['channel']) await requestGroup(urlResolverSetting.functions.getChannelId, descriptorsGroupedByType['channel'])
|
||||||
if (descriptorsGroupedByType['video']) await requestGroup(urlResolverSetting.functions.getVideoId, descriptorsGroupedByType['video'])
|
if (descriptorsGroupedByType['video']) await requestGroup(urlResolverSetting.functions.getVideoId, descriptorsGroupedByType['video'])
|
||||||
|
}))
|
||||||
}))
|
if (progressCallback) progressCallback(1);
|
||||||
|
return results
|
||||||
return results
|
}
|
||||||
}
|
|
|
@ -54,8 +54,7 @@ export function WatchOnLbryButton({ targetPlatform, lbryPathname, time }: WatchO
|
||||||
function updateButton(mountPoint: HTMLDivElement, target: Target | null): void
|
function updateButton(mountPoint: HTMLDivElement, target: Target | null): void
|
||||||
{
|
{
|
||||||
if (!target) return render(<WatchOnLbryButton />, mountPoint)
|
if (!target) return render(<WatchOnLbryButton />, mountPoint)
|
||||||
const time = target.time && target.time > 3 ? target.time : null
|
render(<WatchOnLbryButton targetPlatform={target.platfrom} lbryPathname={target.lbryPathname} time={target.time ?? undefined} />, mountPoint)
|
||||||
render(<WatchOnLbryButton targetPlatform={target.platfrom} lbryPathname={target.lbryPathname} time={time ?? undefined} />, mountPoint)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function redirectTo({ lbryPathname, platfrom, time }: Target)
|
function redirectTo({ lbryPathname, platfrom, time }: Target)
|
||||||
|
@ -112,8 +111,8 @@ window.addEventListener('load', async () =>
|
||||||
chrome.storage.onChanged.addListener(async (changes, areaName) =>
|
chrome.storage.onChanged.addListener(async (changes, areaName) =>
|
||||||
{
|
{
|
||||||
if (areaName !== 'local') return
|
if (areaName !== 'local') return
|
||||||
Object.assign(settings, changes)
|
Object.assign(settings, Object.fromEntries(Object.entries(changes).map(([key, change]) => [key, change.newValue])))
|
||||||
updateByURL(new URL(location.href))
|
await updateByURL(new URL(location.href))
|
||||||
})
|
})
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -127,6 +126,13 @@ window.addEventListener('load', async () =>
|
||||||
// We should get this from background, so the caching works and we don't get erros in the future if yt decides to impliment CORS
|
// We should get this from background, so the caching works and we don't get erros in the future if yt decides to impliment CORS
|
||||||
const requestLbryPathname = async (videoId: string) => await new Promise<string | null>((resolve) => chrome.runtime.sendMessage({ videoId }, resolve))
|
const requestLbryPathname = async (videoId: string) => await new Promise<string | null>((resolve) => chrome.runtime.sendMessage({ videoId }, resolve))
|
||||||
|
|
||||||
|
function getVideoTime(url: URL)
|
||||||
|
{
|
||||||
|
return settings.redirect ?
|
||||||
|
(url.searchParams.has('t') ? parseYouTubeURLTimeString(url.searchParams.get('t')!) : null) :
|
||||||
|
(videoElement.currentTime > 3 && videoElement.currentTime < videoElement.duration - 1 ? videoElement.currentTime : null)
|
||||||
|
}
|
||||||
|
|
||||||
let target: Target | null = null
|
let target: Target | null = null
|
||||||
async function updateByURL(url: URL)
|
async function updateByURL(url: URL)
|
||||||
{
|
{
|
||||||
|
@ -136,15 +142,15 @@ window.addEventListener('load', async () =>
|
||||||
if (!videoId) return
|
if (!videoId) return
|
||||||
const lbryPathname = await requestLbryPathname(videoId)
|
const lbryPathname = await requestLbryPathname(videoId)
|
||||||
if (!lbryPathname) return
|
if (!lbryPathname) return
|
||||||
const time = settings.redirect ? parseYouTubeURLTimeString(url.searchParams.get('t') ?? '0') : videoElement.currentTime
|
const time = getVideoTime(url)
|
||||||
target = { lbryPathname, platfrom: targetPlatformSettings[settings.targetPlatform], time }
|
target = { lbryPathname, platfrom: targetPlatformSettings[settings.targetPlatform], time }
|
||||||
|
|
||||||
if (settings.redirect) redirectTo(target)
|
if (settings.redirect) redirectTo(target)
|
||||||
else updateButton(buttonMountPoint, target)
|
else updateButton(buttonMountPoint, target)
|
||||||
}
|
}
|
||||||
|
|
||||||
videoElement.addEventListener('timeupdate', () => target && updateButton(buttonMountPoint, Object.assign(target, { time: videoElement.currentTime })))
|
videoElement.addEventListener('timeupdate',
|
||||||
videoElement.addEventListener('ended', () => target && updateButton(buttonMountPoint, Object.assign(target, { time: null })))
|
() => target && updateButton(buttonMountPoint, Object.assign(target, { time: getVideoTime(new URL(location.href)) })))
|
||||||
|
|
||||||
async function onUrlChange()
|
async function onUrlChange()
|
||||||
{
|
{
|
||||||
|
|
|
@ -5,8 +5,6 @@ import { getFileContent, getSubsFromCsv, getSubsFromJson, getSubsFromOpml } from
|
||||||
import { resolveById } from '../common/yt/urlResolve'
|
import { resolveById } from '../common/yt/urlResolve'
|
||||||
import readme from './README.md'
|
import readme from './README.md'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses the subscription file and queries the API for lbry channels
|
* Parses the subscription file and queries the API for lbry channels
|
||||||
*
|
*
|
||||||
|
|
Loading…
Add table
Reference in a new issue