From a6472799a1ec0954575b5e006aa8a98bc0f2c845 Mon Sep 17 00:00:00 2001 From: Shiba <44804845+DeepDoge@users.noreply.github.com> Date: Sun, 24 Jul 2022 20:41:51 +0000 Subject: [PATCH 1/2] =?UTF-8?q?=F0=9F=8D=99=20Bug=20fixes,=20and=20other?= =?UTF-8?q?=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/scripts/ytContent.tsx | 195 +++++++++++++++++++++----------------- src/settings/index.ts | 4 - 2 files changed, 108 insertions(+), 91 deletions(-) diff --git a/src/scripts/ytContent.tsx b/src/scripts/ytContent.tsx index e4294cc..86d1f7b 100644 --- a/src/scripts/ytContent.tsx +++ b/src/scripts/ytContent.tsx @@ -13,10 +13,14 @@ import { getExtensionSettingsAsync, getSourcePlatfromSettingsFromHostname, getTa time: number | null } - const sourcePlatform = getSourcePlatfromSettingsFromHostname(new URL(location.href).hostname) - if (!sourcePlatform) return - const targetPlatforms = getTargetPlatfromSettingsEntiries() + interface Source { + platform: SourcePlatform + id: string + type: ResolveUrlTypes + time: number | null + } + const targetPlatforms = getTargetPlatfromSettingsEntiries() const settings = await getExtensionSettingsAsync() // Listen Settings Change chrome.storage.onChanged.addListener(async (changes, areaName) => { @@ -27,8 +31,8 @@ import { getExtensionSettingsAsync, getSourcePlatfromSettingsFromHostname, getTa const mountPoint = document.createElement('div') mountPoint.style.display = 'flex' - function WatchOnLbryButton({ target }: { target?: Target }) { - if (!target) return null + function WatchOnLbryButton({ source, target }: { source?: Source, target?: Target }) { + if (!target || !source) return null const url = getLbryUrlByTarget(target) return
@@ -45,104 +49,109 @@ import { getExtensionSettingsAsync, getSourcePlatfromSettingsFromHostname, getTa border: '0', color: 'whitesmoke', padding: '10px 16px', - marginRight: target.type === 'channel' ? '10px' : '4px', + marginRight: source?.type === 'channel' ? '10px' : '4px', fontSize: '14px', textDecoration: 'none', ...target.platform.button.style?.button, }} - onClick={() => findVideoElementAwait().then((videoElement) => { + onClick={() => findVideoElementAwait(source).then((videoElement) => { videoElement.pause() })} > - {target.platform.button.text} + {target.type === 'channel' ? 'Channel on' : 'Watch on'} {target.platform.displayName}
} - function updateButton(target: Target | null): void { - if (!target) return render(, mountPoint) + function updateButton(params: { source: Source, target: Target } | null): void { + if (!params) return render(, mountPoint) - const sourcePlatform = getSourcePlatfromSettingsFromHostname(new URL(location.href).hostname) - if (!sourcePlatform) return render(, mountPoint) - - const mountBefore = document.querySelector(sourcePlatform.htmlQueries.mountButtonBefore[target.type]) + const mountBefore = document.querySelector(params.source.platform.htmlQueries.mountButtonBefore[params.source.type]) if (!mountBefore) return render(, mountPoint) mountBefore.parentElement?.insertBefore(mountPoint, mountBefore) - render(, mountPoint) + render(, mountPoint) } - async function findVideoElementAwait() { - const sourcePlatform = getSourcePlatfromSettingsFromHostname(new URL(location.href).hostname) - if (!sourcePlatform) throw new Error(`Unknown source of: ${location.href}`) + async function findVideoElementAwait(source: Source) { let videoElement: HTMLVideoElement | null = null - while (!(videoElement = document.querySelector(sourcePlatform.htmlQueries.videoPlayer))) await sleep(200) + while (!(videoElement = document.querySelector(source.platform.htmlQueries.videoPlayer))) await sleep(200) return videoElement } - async function getTargetsByURL(...urls: URL[]) { - const params: Parameters[0] = [] - const platform = targetPlatformSettings[settings.targetPlatform] + async function getSourceByUrl(url: URL): Promise { + const platform = getSourcePlatfromSettingsFromHostname(new URL(location.href).hostname) + if (!platform) return null - const datas: Record = {} - - for (const url of urls) { - if (url.pathname === '/watch' && url.searchParams.has('v')) { - const id = url.searchParams.get('v')! - const type: ResolveUrlTypes = 'video' - params.push({ id, type }) - datas[id] = { url, type } + if (url.pathname === '/watch' && url.searchParams.has('v')) { + return { + id: url.searchParams.get('v')!, + platform, + time: url.searchParams.has('t') ? parseYouTubeURLTimeString(url.searchParams.get('t')!) : null, + type: 'video' } - else if (url.pathname.startsWith('/channel/')) { - const id = url.pathname.substring("/channel/".length) - const type: ResolveUrlTypes = 'channel' - params.push({ id, type }) - datas[id] = { url, type } + } + else if (url.pathname.startsWith('/channel/')) { + return { + id: url.pathname.substring("/channel/".length), + platform, + time: null, + type: 'channel' } - else if (url.pathname.startsWith('/c/') || url.pathname.startsWith('/user/')) { - // We have to download the page content again because these parts of the page are not responsive - // yt front end sucks anyway - const content = await (await fetch(location.href)).text() - const prefix = `https://www.youtube.com/feeds/videos.xml?channel_id=` - const suffix = `"` - const startsAt = content.indexOf(prefix) + prefix.length - const endsAt = content.indexOf(suffix, startsAt) - const id = content.substring(startsAt, endsAt) - const type: ResolveUrlTypes = 'channel' - params.push({ id, type }) - datas[id] = { url, type } + } + else if (url.pathname.startsWith('/c/') || url.pathname.startsWith('/user/')) { + // We have to download the page content again because these parts of the page are not responsive + // yt front end sucks anyway + const content = await (await fetch(location.href)).text() + const prefix = `https://www.youtube.com/feeds/videos.xml?channel_id=` + const suffix = `"` + const startsAt = content.indexOf(prefix) + prefix.length + const endsAt = content.indexOf(suffix, startsAt) + const id = content.substring(startsAt, endsAt) + return { + id, + platform, + time: null, + type: 'channel' } } - const results = Object.entries(await requestResolveById(params)) - const targets: Record = Object.fromEntries(results.map(([id, result]) => { - const data = datas[id] + return null + } - if (!result) return [ - data.url.href, - null - ] + async function getTargetsBySources(...sources: Source[]) { + const params: Parameters[0] = sources.map((source) => ({ id: source.id, type: source.type })) + const platform = targetPlatformSettings[settings.targetPlatform] - return [ - data.url.href, - { - type: data.type, - lbryPathname: result?.id, - platform, - time: data.type === 'channel' ? null : (data.url.searchParams.has('t') ? parseYouTubeURLTimeString(data.url.searchParams.get('t')!) : null) - } - ] - })) + const results = await requestResolveById(params) + const targets: Record = Object.fromEntries( + sources.map((source) => { + const result = results[source.id] + if (!result) return [ + source.id, + null + ] + + return [ + source.id, + { + type: result.type, + lbryPathname: result.id, + platform, + time: source.time + } + ] + }) + ) return targets } // We should get this from background, so the caching works and we don't get errors in the future if yt decides to impliment CORS async function requestResolveById(...params: Parameters): ReturnType { const json = await new Promise((resolve) => chrome.runtime.sendMessage({ json: JSON.stringify(params) }, resolve)) - if (json === 'error') - { + if (json === 'error') { console.error("Background error on:", params) throw new Error("Background error.") } @@ -160,24 +169,28 @@ import { getExtensionSettingsAsync, getSourcePlatfromSettingsFromHostname, getTa while (true) { await sleep(500) - const url: URL = (urlCache?.href === location.href) ? urlCache : new URL(location.href); + const url: URL = (urlCache?.href === location.href) ? urlCache : new URL(location.href) + const source = await getSourceByUrl(new URL(location.href)) + if (!source) continue + try { if (settings.redirect) { - const target = (await getTargetsByURL(url))[url.href] + const target = (await getTargetsBySources(source))[source.id] if (!target) continue if (url === urlCache) continue - + const lbryURL = getLbryUrlByTarget(target) - - findVideoElementAwait().then((videoElement) => { + + // As soon as video play is ready and start playing, pause it. + findVideoElementAwait(source).then((videoElement) => { videoElement.addEventListener('play', () => videoElement.pause(), { once: true }) videoElement.pause() }) - + if (target.platform === targetPlatformSettings.app) { if (document.hidden) await new Promise((resolve) => document.addEventListener('visibilitychange', resolve, { once: true })) - // Its not gonna be able to replace anyway - // This was empty window doesnt stay open + // Replace is being used so browser doesnt start an empty window + // Its not gonna be able to replace anyway, since its a LBRY Uri location.replace(lbryURL) } else { @@ -188,30 +201,35 @@ import { getExtensionSettingsAsync, getSourcePlatfromSettingsFromHostname, getTa } else { if (urlCache !== url) updateButton(null) - let target = (await getTargetsByURL(url))[url.href] + let target = (await getTargetsBySources(source))[source.id] + + // There is no target found via API try to check Video Description for LBRY links. if (!target) { - const descriptionElement = document.querySelector(sourcePlatform.htmlQueries.videoDescription) + const descriptionElement = document.querySelector(source.platform.htmlQueries.videoDescription) if (descriptionElement) { const anchors = Array.from(descriptionElement.querySelectorAll('a')) - + for (const anchor of anchors) { if (!anchor.href) continue const url = new URL(anchor.href) let lbryURL: URL | null = null - if (sourcePlatform === sourcePlatfromSettings['youtube.com']) { + + // Extract real link from youtube's redirect link + if (source.platform === sourcePlatfromSettings['youtube.com']) { if (!targetPlatforms.some(([key, platform]) => url.searchParams.get('q')?.startsWith(platform.domainPrefix))) continue lbryURL = new URL(url.searchParams.get('q')!) } + // Just directly use the link itself on other platforms else { if (!targetPlatforms.some(([key, platform]) => url.href.startsWith(platform.domainPrefix))) continue lbryURL = new URL(url.href) } - + if (lbryURL) { target = { lbryPathname: lbryURL.pathname.substring(1), time: null, - type: 'video', + type: lbryURL.pathname.substring(1).includes('/') ? 'video' : 'channel', platform: targetPlatformSettings[settings.targetPlatform] } break @@ -219,14 +237,17 @@ import { getExtensionSettingsAsync, getSourcePlatfromSettingsFromHostname, getTa } } } - - if (target?.type === 'video') { - const videoElement = document.querySelector(sourcePlatform.htmlQueries.videoPlayer) - if (videoElement) target.time = videoElement.currentTime > 3 && videoElement.currentTime < videoElement.duration - 1 ? videoElement.currentTime : null + + if (!target) updateButton(null) + else { + // If target is a video target add timestampt to it + if (target.type === 'video') { + const videoElement = document.querySelector(source.platform.htmlQueries.videoPlayer) + if (videoElement) target.time = videoElement.currentTime > 3 && videoElement.currentTime < videoElement.duration - 1 ? videoElement.currentTime : null + } + + updateButton({ target, source }) } - - // We run it anyway with null target to hide the button - updateButton(target) } } catch (error) { console.error(error) diff --git a/src/settings/index.ts b/src/settings/index.ts index 017bcdd..26698f2 100644 --- a/src/settings/index.ts +++ b/src/settings/index.ts @@ -60,7 +60,6 @@ const targetPlatform = (o: { displayName: string theme: string button: { - text: string icon: string style?: { @@ -80,7 +79,6 @@ export const targetPlatformSettings = { displayName: 'Madiator.com', theme: 'linear-gradient(130deg, #499375, #43889d)', button: { - text: 'Watch on', icon: chrome.runtime.getURL('assets/icons/lbry/madiator-logo.svg'), style: { button: { flexDirection: 'row-reverse' }, @@ -93,7 +91,6 @@ export const targetPlatformSettings = { displayName: 'Odysee', theme: 'linear-gradient(130deg, #c63d59, #f77937)', button: { - text: 'Watch on Odysee', icon: chrome.runtime.getURL('assets/icons/lbry/odysee-logo.svg') } }), @@ -102,7 +99,6 @@ export const targetPlatformSettings = { displayName: 'LBRY App', theme: 'linear-gradient(130deg, #499375, #43889d)', button: { - text: 'Watch on LBRY', icon: chrome.runtime.getURL('assets/icons/lbry/lbry-logo.svg') } }), From 29c9b2829a1f9a6850f750e08c82e4af3a3438c9 Mon Sep 17 00:00:00 2001 From: Shiba <44804845+DeepDoge@users.noreply.github.com> Date: Sun, 24 Jul 2022 23:18:34 +0000 Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=8D=A3=20New=20button=20on=20the=20vi?= =?UTF-8?q?deo=20player?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/scripts/ytContent.tsx | 77 +++++++++++++++++++++++++++++++++------ src/settings/index.ts | 36 +++++++++++++----- 2 files changed, 92 insertions(+), 21 deletions(-) diff --git a/src/scripts/ytContent.tsx b/src/scripts/ytContent.tsx index 86d1f7b..bc8e9e6 100644 --- a/src/scripts/ytContent.tsx +++ b/src/scripts/ytContent.tsx @@ -28,8 +28,11 @@ import { getExtensionSettingsAsync, getSourcePlatfromSettingsFromHostname, getTa Object.assign(settings, Object.fromEntries(Object.entries(changes).map(([key, change]) => [key, change.newValue]))) }) - const mountPoint = document.createElement('div') - mountPoint.style.display = 'flex' + const buttonMountPoint = document.createElement('div') + buttonMountPoint.style.display = 'flex' + + const playerButtonMountPoint = document.createElement('div') + playerButtonMountPoint.style.display = 'flex' function WatchOnLbryButton({ source, target }: { source?: Source, target?: Target }) { if (!target || !source) return null @@ -60,19 +63,65 @@ import { getExtensionSettingsAsync, getSourcePlatfromSettingsFromHostname, getTa > - {target.type === 'channel' ? 'Channel on' : 'Watch on'} {target.platform.displayName} + {target.type === 'channel' ? 'Channel on' : 'Watch on'} {target.platform.button.platformNameText} + + + } + + function WatchOnLbryPlayerButton({ source, target }: { source?: Source, target?: Target }) { + if (!target || !source) return null + const url = getLbryUrlByTarget(target) + + return } function updateButton(params: { source: Source, target: Target } | null): void { - if (!params) return render(, mountPoint) + if (!params) { + render(, buttonMountPoint) + render(, playerButtonMountPoint) + return + } - const mountBefore = document.querySelector(params.source.platform.htmlQueries.mountButtonBefore[params.source.type]) - if (!mountBefore) return render(, mountPoint) + const mountPlayerButtonBefore = params.source.platform.htmlQueries.mountPoints.mountPlayerButtonBefore ? + document.querySelector(params.source.platform.htmlQueries.mountPoints.mountPlayerButtonBefore) : + null + if (!mountPlayerButtonBefore) render(, playerButtonMountPoint) + else { + if (mountPlayerButtonBefore.previousSibling !== playerButtonMountPoint) + mountPlayerButtonBefore.parentElement?.insertBefore(playerButtonMountPoint, mountPlayerButtonBefore) + render(, playerButtonMountPoint) + } - mountBefore.parentElement?.insertBefore(mountPoint, mountBefore) - render(, mountPoint) + const mountButtonBefore = document.querySelector(params.source.platform.htmlQueries.mountPoints.mountButtonBefore[params.source.type]) + if (!mountButtonBefore) render(, playerButtonMountPoint) + else { + if (mountButtonBefore.previousSibling !== buttonMountPoint) + mountButtonBefore.parentElement?.insertBefore(buttonMountPoint, mountButtonBefore) + render(, buttonMountPoint) + } } async function findVideoElementAwait(source: Source) { @@ -205,9 +254,15 @@ import { getExtensionSettingsAsync, getSourcePlatfromSettingsFromHostname, getTa // There is no target found via API try to check Video Description for LBRY links. if (!target) { - const descriptionElement = document.querySelector(source.platform.htmlQueries.videoDescription) - if (descriptionElement) { - const anchors = Array.from(descriptionElement.querySelectorAll('a')) + const linksContainer = + source.type === 'video' ? + document.querySelector(source.platform.htmlQueries.videoDescription) : + source.platform.htmlQueries.channelLinks ? document.querySelector(source.platform.htmlQueries.channelLinks) : null + + console.log(linksContainer) + + if (linksContainer) { + const anchors = Array.from(linksContainer.querySelectorAll('a')) for (const anchor of anchors) { if (!anchor.href) continue diff --git a/src/settings/index.ts b/src/settings/index.ts index 26698f2..e71dd40 100644 --- a/src/settings/index.ts +++ b/src/settings/index.ts @@ -60,6 +60,7 @@ const targetPlatform = (o: { displayName: string theme: string button: { + platformNameText: string, icon: string style?: { @@ -79,6 +80,7 @@ export const targetPlatformSettings = { displayName: 'Madiator.com', theme: 'linear-gradient(130deg, #499375, #43889d)', button: { + platformNameText: '', icon: chrome.runtime.getURL('assets/icons/lbry/madiator-logo.svg'), style: { button: { flexDirection: 'row-reverse' }, @@ -91,6 +93,7 @@ export const targetPlatformSettings = { displayName: 'Odysee', theme: 'linear-gradient(130deg, #c63d59, #f77937)', button: { + platformNameText: 'Odysee', icon: chrome.runtime.getURL('assets/icons/lbry/odysee-logo.svg') } }), @@ -99,6 +102,7 @@ export const targetPlatformSettings = { displayName: 'LBRY App', theme: 'linear-gradient(130deg, #499375, #43889d)', button: { + platformNameText: 'LBRY', icon: chrome.runtime.getURL('assets/icons/lbry/lbry-logo.svg') } }), @@ -109,9 +113,13 @@ export const targetPlatformSettings = { const sourcePlatform = (o: { hostnames: string[] htmlQueries: { - mountButtonBefore: Record, + mountPoints: { + mountButtonBefore: Record, + mountPlayerButtonBefore: string | null, + } videoPlayer: string, videoDescription: string + channelLinks: string | null } }) => o export type SourcePlatform = ReturnType @@ -126,24 +134,32 @@ export const sourcePlatfromSettings = { "youtube.com": sourcePlatform({ hostnames: ['www.youtube.com'], htmlQueries: { - mountButtonBefore: { - video: 'ytd-video-owner-renderer~#subscribe-button', - channel: '#channel-header-container #buttons' + mountPoints: { + mountButtonBefore: { + video: 'ytd-video-owner-renderer~#subscribe-button', + channel: '#channel-header-container #buttons' + }, + mountPlayerButtonBefore: 'ytd-player .ytp-right-controls', }, videoPlayer: '#ytd-player video', - videoDescription: 'ytd-video-secondary-info-renderer #description' + videoDescription: 'ytd-video-secondary-info-renderer #description', + channelLinks: '#channel-header #links-holder' } }), "yewtu.be": sourcePlatform({ hostnames: ['yewtu.be', 'vid.puffyan.us', 'invidio.xamh.de', 'invidious.kavin.rocks'], htmlQueries: { - mountButtonBefore: - { - video: '#watch-on-youtube', - channel: '#subscribe' + mountPoints: { + mountButtonBefore: + { + video: '#watch-on-youtube', + channel: '#subscribe' + }, + mountPlayerButtonBefore: null, }, videoPlayer: '#player-container video', - videoDescription: '#descriptionWrapper' + videoDescription: '#descriptionWrapper', + channelLinks: null } }) }