mirror of
https://github.com/LBRYFoundation/Watch-on-LBRY.git
synced 2025-08-23 17:47:26 +00:00
🥡 Refactor
This commit is contained in:
parent
4f8e807a65
commit
2b91436900
4 changed files with 188 additions and 187 deletions
|
@ -1,10 +1,3 @@
|
|||
import chunk from 'lodash/chunk'
|
||||
import groupBy from 'lodash/groupBy'
|
||||
import { getExtensionSettingsAsync, Keys, SingleValueAtATime, Values, YtUrlResolveFunction, YtUrlResolveResponsePath, ytUrlResolversSettings } from '../settings'
|
||||
import { LbryPathnameCache } from './urlCache'
|
||||
|
||||
// const LBRY_API_HOST = 'https://api.odysee.com'; MOVED TO SETTINGS
|
||||
const QUERY_CHUNK_SIZE = 300
|
||||
|
||||
interface YtExportedJsonSubscription
|
||||
{
|
||||
|
@ -19,11 +12,6 @@ interface YtExportedJsonSubscription
|
|||
}
|
||||
}
|
||||
|
||||
export interface YtIdResolverDescriptor
|
||||
{
|
||||
id: string
|
||||
type: 'channel' | 'video'
|
||||
}
|
||||
|
||||
/**
|
||||
* @param file to load
|
||||
|
@ -44,41 +32,6 @@ export function getFileContent(file: File): Promise<string>
|
|||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the channelID from a YT URL.
|
||||
*
|
||||
* Handles these two types of YT URLs:
|
||||
* * /feeds/videos.xml?channel_id=*
|
||||
* * /channel/*
|
||||
*/
|
||||
export function getChannelId(channelURL: string)
|
||||
{
|
||||
const match = channelURL.match(/channel\/([^\s?]*)/)
|
||||
return match ? match[1] : new URL(channelURL).searchParams.get('channel_id')
|
||||
}
|
||||
|
||||
export function parseYouTubeURLTimeString(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()
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reads the array of YT channels from an OPML file
|
||||
|
@ -124,130 +77,36 @@ export function getSubsFromCsv(csvContent: string): string[]
|
|||
}
|
||||
|
||||
/**
|
||||
* @param descriptorsWithIndex YT resource IDs to check
|
||||
* @returns a promise with the list of channels that were found on lbry
|
||||
* Extracts the channelID from a YT URL.
|
||||
*
|
||||
* Handles these two types of YT URLs:
|
||||
* * /feeds/videos.xml?channel_id=*
|
||||
* * /channel/*
|
||||
*/
|
||||
export async function resolveById(descriptors: YtIdResolverDescriptor[], progressCallback?: (progress: number) => void): Promise<(string | null)[]>
|
||||
export function getChannelId(channelURL: string)
|
||||
{
|
||||
const descriptorsWithIndex: (YtIdResolverDescriptor & { index: number })[] = descriptors.map((descriptor, index) => ({ ...descriptor, index }))
|
||||
descriptors = null as any
|
||||
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
|
||||
if (cache !== undefined)
|
||||
{
|
||||
// Directly setting it to results
|
||||
results[index] = cache
|
||||
|
||||
// We remove it so we dont ask it to API
|
||||
descriptorsWithIndex.splice(index, 1)
|
||||
}
|
||||
}))
|
||||
|
||||
const descriptorsChunks = chunk(descriptorsWithIndex, QUERY_CHUNK_SIZE)
|
||||
let progressCount = 0
|
||||
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 urlResolverSetting = ytUrlResolversSettings[urlResolverSettingName]
|
||||
|
||||
const url = new URL(`https://${urlResolverSetting.hostname}`)
|
||||
|
||||
function followResponsePath<T>(response: any, responsePath: YtUrlResolveResponsePath)
|
||||
{
|
||||
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
|
||||
const match = channelURL.match(/channel\/([^\s?]*)/)
|
||||
return match ? match[1] : new URL(channelURL).searchParams.get('channel_id')
|
||||
}
|
||||
|
||||
async function requestGroup(urlResolverFunction: YtUrlResolveFunction, descriptorsGroup: typeof descriptorsWithIndex)
|
||||
export function parseYouTubeURLTimeString(timeString: string)
|
||||
{
|
||||
url.pathname = urlResolverFunction.pathname
|
||||
|
||||
if (urlResolverFunction.paramArraySeperator === SingleValueAtATime)
|
||||
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++)
|
||||
{
|
||||
await Promise.all(descriptorsGroup.map(async (descriptor) =>
|
||||
let t = parseInt(numbers[i])
|
||||
switch (signs[i])
|
||||
{
|
||||
switch (null)
|
||||
{
|
||||
default:
|
||||
if (!descriptor.id) break
|
||||
url.searchParams.set(urlResolverFunction.paramName, descriptor.id)
|
||||
|
||||
const apiResponse = await fetch(url.toString(), { cache: 'no-store' })
|
||||
if (!apiResponse.ok)
|
||||
{
|
||||
// Some API might not respond with 200 if it can't find the url
|
||||
if (apiResponse.status === 404) await LbryPathnameCache.put(null, descriptor.id)
|
||||
break
|
||||
case 'd': t *= 24
|
||||
case 'h': t *= 60
|
||||
case 'm': t *= 60
|
||||
case 's': break
|
||||
default: return '0'
|
||||
}
|
||||
|
||||
const value = followResponsePath<string>(await apiResponse.json(), urlResolverFunction.responsePath)
|
||||
if (value) results[descriptor.index] = value
|
||||
await LbryPathnameCache.put(value, descriptor.id)
|
||||
total += t
|
||||
}
|
||||
progressCount++
|
||||
if (progressCallback) progressCallback(progressCount / descriptorsWithIndex.length)
|
||||
}))
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
switch (null)
|
||||
{
|
||||
default:
|
||||
url.searchParams
|
||||
.set(urlResolverFunction.paramName, descriptorsGroup
|
||||
.map((descriptor) => descriptor.id)
|
||||
.filter((descriptorId) => descriptorId)
|
||||
.join(urlResolverFunction.paramArraySeperator)
|
||||
)
|
||||
|
||||
const apiResponse = await fetch(url.toString(), { cache: 'no-store' })
|
||||
if (!apiResponse.ok) break
|
||||
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)
|
||||
}))
|
||||
}
|
||||
progressCount += descriptorsGroup.length
|
||||
if (progressCallback) progressCallback(progressCount / descriptorsWithIndex.length)
|
||||
}
|
||||
}
|
||||
|
||||
if (descriptorsGroupedByType['channel']) await requestGroup(urlResolverSetting.functions.getChannelId, descriptorsGroupedByType['channel'])
|
||||
if (descriptorsGroupedByType['video']) await requestGroup(urlResolverSetting.functions.getVideoId, descriptorsGroupedByType['video'])
|
||||
|
||||
}))
|
||||
|
||||
return results
|
||||
return total.toString()
|
||||
}
|
141
src/common/yt/urlResolve.ts
Normal file
141
src/common/yt/urlResolve.ts
Normal file
|
@ -0,0 +1,141 @@
|
|||
import { chunk, groupBy } from "lodash"
|
||||
import { getExtensionSettingsAsync, Keys, SingleValueAtATime, Values, YtUrlResolveFunction, YtUrlResolveResponsePath, ytUrlResolversSettings } from "../settings"
|
||||
import { LbryPathnameCache } from "./urlCache"
|
||||
|
||||
// const LBRY_API_HOST = 'https://api.odysee.com'; MOVED TO SETTINGS
|
||||
const QUERY_CHUNK_SIZE = 300
|
||||
|
||||
export interface YtIdResolverDescriptor
|
||||
{
|
||||
id: string
|
||||
type: 'channel' | 'video'
|
||||
}
|
||||
|
||||
/**
|
||||
* @param descriptorsWithIndex YT resource IDs to check
|
||||
* @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)[]>
|
||||
{
|
||||
const descriptorsWithIndex: (YtIdResolverDescriptor & { index: number })[] = descriptors.map((descriptor, index) => ({ ...descriptor, index }))
|
||||
descriptors = null as any
|
||||
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
|
||||
if (cache !== undefined)
|
||||
{
|
||||
// Directly setting it to results
|
||||
results[index] = cache
|
||||
|
||||
// We remove it so we dont ask it to API
|
||||
descriptorsWithIndex.splice(index, 1)
|
||||
}
|
||||
}))
|
||||
|
||||
const descriptorsChunks = chunk(descriptorsWithIndex, QUERY_CHUNK_SIZE)
|
||||
let progressCount = 0
|
||||
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 urlResolverSetting = ytUrlResolversSettings[urlResolverSettingName]
|
||||
|
||||
const url = new URL(`https://${urlResolverSetting.hostname}`)
|
||||
|
||||
function followResponsePath<T>(response: any, responsePath: YtUrlResolveResponsePath)
|
||||
{
|
||||
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)
|
||||
{
|
||||
url.pathname = urlResolverFunction.pathname
|
||||
|
||||
if (urlResolverFunction.paramArraySeperator === SingleValueAtATime)
|
||||
{
|
||||
await Promise.all(descriptorsGroup.map(async (descriptor) =>
|
||||
{
|
||||
switch (null)
|
||||
{
|
||||
default:
|
||||
if (!descriptor.id) break
|
||||
url.searchParams.set(urlResolverFunction.paramName, descriptor.id)
|
||||
|
||||
const apiResponse = await fetch(url.toString(), { cache: 'no-store' })
|
||||
if (!apiResponse.ok)
|
||||
{
|
||||
// Some API might not respond with 200 if it can't find the url
|
||||
if (apiResponse.status === 404) await LbryPathnameCache.put(null, descriptor.id)
|
||||
break
|
||||
}
|
||||
|
||||
const value = followResponsePath<string>(await apiResponse.json(), urlResolverFunction.responsePath)
|
||||
if (value) results[descriptor.index] = value
|
||||
await LbryPathnameCache.put(value, descriptor.id)
|
||||
}
|
||||
progressCount++
|
||||
if (progressCallback) progressCallback(progressCount / descriptorsWithIndex.length)
|
||||
}))
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
switch (null)
|
||||
{
|
||||
default:
|
||||
url.searchParams
|
||||
.set(urlResolverFunction.paramName, descriptorsGroup
|
||||
.map((descriptor) => descriptor.id)
|
||||
.filter((descriptorId) => descriptorId)
|
||||
.join(urlResolverFunction.paramArraySeperator)
|
||||
)
|
||||
|
||||
const apiResponse = await fetch(url.toString(), { cache: 'no-store' })
|
||||
if (!apiResponse.ok) break
|
||||
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)
|
||||
}))
|
||||
}
|
||||
progressCount += descriptorsGroup.length
|
||||
if (progressCallback) progressCallback(progressCount / descriptorsWithIndex.length)
|
||||
}
|
||||
}
|
||||
|
||||
if (descriptorsGroupedByType['channel']) await requestGroup(urlResolverSetting.functions.getChannelId, descriptorsGroupedByType['channel'])
|
||||
if (descriptorsGroupedByType['video']) await requestGroup(urlResolverSetting.functions.getVideoId, descriptorsGroupedByType['video'])
|
||||
|
||||
}))
|
||||
|
||||
return results
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import { parseProtocolUrl } from '../common/lbry-url'
|
||||
import { resolveById, YtIdResolverDescriptor } from '../common/yt'
|
||||
import { resolveById, YtIdResolverDescriptor } from '../common/yt/urlResolve'
|
||||
async function resolveYT(descriptor: YtIdResolverDescriptor) {
|
||||
const lbryProtocolUrl: string | null = await resolveById([descriptor]).then(a => a[0]);
|
||||
const segments = parseProtocolUrl(lbryProtocolUrl || '', { encode: true });
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { h, render } from 'preact'
|
||||
import { useState } from 'preact/hooks'
|
||||
import { getExtensionSettingsAsync, targetPlatformSettings } from '../common/settings'
|
||||
import { getFileContent, getSubsFromCsv, getSubsFromJson, getSubsFromOpml, resolveById } from '../common/yt'
|
||||
import { getFileContent, getSubsFromCsv, getSubsFromJson, getSubsFromOpml } from '../common/yt'
|
||||
import { resolveById } from '../common/yt/urlResolve'
|
||||
import readme from './README.md'
|
||||
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue