From c895d53253b385a73a2126bbc093098ea5216b44 Mon Sep 17 00:00:00 2001 From: Shiba <44804845+DeepDoge@users.noreply.github.com> Date: Sat, 2 Jul 2022 15:15:36 +0000 Subject: [PATCH 1/6] =?UTF-8?q?=F0=9F=8D=99=20new=20stuff=20and=20some=20c?= =?UTF-8?q?hanges=20-=20channel=20buttons=20and=20redirect=20-=20in=20butt?= =?UTF-8?q?on=20mode=20if=20there=20is=20no=20target=20try=20to=20find=20l?= =?UTF-8?q?bry=20url=20in=20the=20description=20-=20used=20a=20loop=20in?= =?UTF-8?q?=20content=20script=20instead=20of=20events=20and=20stuff=20to?= =?UTF-8?q?=20make=20it=20less=20confusing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 97 ---------- package.json | 10 - src/modules/yt/urlResolve.ts | 5 +- src/scripts/background.ts | 4 +- src/scripts/ytContent.tsx | 356 +++++++++++++++++++---------------- src/settings/index.ts | 33 ++-- 6 files changed, 217 insertions(+), 288 deletions(-) diff --git a/package-lock.json b/package-lock.json index a88d951..86aaa75 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4826,91 +4826,6 @@ "timsort": "^0.3.0" } }, - "css-loader": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.1.tgz", - "integrity": "sha512-yB5CNFa14MbPJcomwNh3wLThtkZgcNyI2bNMRt8iE5Z8Vwl7f8vQXFAzn2HDOJvtDq2NTZBUGMSUNNyrv3/+cw==", - "dev": true, - "requires": { - "icss-utils": "^5.1.0", - "postcss": "^8.4.7", - "postcss-modules-extract-imports": "^3.0.0", - "postcss-modules-local-by-default": "^4.0.0", - "postcss-modules-scope": "^3.0.0", - "postcss-modules-values": "^4.0.0", - "postcss-value-parser": "^4.2.0", - "semver": "^7.3.5" - }, - "dependencies": { - "nanoid": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", - "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", - "dev": true - }, - "postcss": { - "version": "8.4.13", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.13.tgz", - "integrity": "sha512-jtL6eTBrza5MPzy8oJLFuUscHDXTV5KcLlqAWHl5q5WYRfnNRGSmOZmOZ1T6Gy7A99mOZfqungmZMpMmCVJ8ZA==", - "dev": true, - "requires": { - "nanoid": "^3.3.3", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - } - }, - "postcss-modules-extract-imports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", - "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", - "dev": true - }, - "postcss-modules-local-by-default": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz", - "integrity": "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==", - "dev": true, - "requires": { - "icss-utils": "^5.0.0", - "postcss-selector-parser": "^6.0.2", - "postcss-value-parser": "^4.1.0" - } - }, - "postcss-modules-scope": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", - "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", - "dev": true, - "requires": { - "postcss-selector-parser": "^6.0.4" - } - }, - "postcss-modules-values": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", - "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", - "dev": true, - "requires": { - "icss-utils": "^5.0.0" - } - }, - "postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true - }, - "semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - } - } - }, "css-modules-loader-core": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/css-modules-loader-core/-/css-modules-loader-core-1.1.0.tgz", @@ -7923,12 +7838,6 @@ "integrity": "sha1-Bupvg2ead0njhs/h/oEq5dsiPe0=", "dev": true }, - "icss-utils": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", - "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", - "dev": true - }, "ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -14965,12 +14874,6 @@ "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", "dev": true }, - "source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", - "dev": true - }, "source-map-resolve": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", diff --git a/package.json b/package.json index 751765d..38d1c18 100644 --- a/package.json +++ b/package.json @@ -5,29 +5,19 @@ "scripts": { "build:assets": "cpx \"src/**/*.{json,png,svg}\" dist/", "watch:assets": "cpx --watch -v \"src/**/*.{json,png,svg}\" dist/", - "build:parcel": "cross-env NODE_ENV=production parcel build --no-source-maps --no-minify \"src/**/*.{js,jsx,ts,tsx}\" \"src/**/*.html\" \"src/**/*.md\"", "watch:parcel": "parcel watch --no-hmr --no-source-maps \"src/**/*.{js,jsx,ts,tsx}\" \"src/**/*.html\" \"src/**/*.md\"", - "build:webext": "web-ext build --source-dir ./dist --overwrite-dest", - "clear:dist": "rm -r ./dist ; mkdir ./dist", - "build:base": "npm-run-all -l -p build:parcel build:assets", - "pick:manifest:v2": "cp -b ./manifest.v2.json ./dist/manifest.json", "pick:manifest:v3": "cp -b ./manifest.v3.json ./dist/manifest.json", - "build:v2": "npm run clear:dist ; npm run build:base && npm run pick:manifest:v2", "build:v3": "npm run clear:dist ; npm run build:base && npm run pick:manifest:v3", - "build": "rm -r ./build ; mkdir ./build && npm run build:v2 && zip -r ./build/manifest-v2.zip ./dist && npm run build:v3 && zip -r ./build/manifest-v3.zip ./dist", - "watch:v2": "npm run clear:dist ; npm run pick:manifest:v2 && npm-run-all -l -p watch:parcel watch:assets", "watch:v3": "npm run clear:dist ; npm run pick:manifest:v3 && npm-run-all -l -p watch:parcel watch:assets", - "watch": "npm run watch:v3", - "start:chrome": "web-ext run -t chromium --source-dir ./dist", "start:firefox": "web-ext run --source-dir ./dist", "test": "jest" diff --git a/src/modules/yt/urlResolve.ts b/src/modules/yt/urlResolve.ts index 212108e..4f0adf8 100644 --- a/src/modules/yt/urlResolve.ts +++ b/src/modules/yt/urlResolve.ts @@ -1,12 +1,13 @@ import { chunk } from "lodash" import path from "path" -import { getExtensionSettingsAsync, ytUrlResolversSettings } from "../../settings" +import { getExtensionSettingsAsync, SourcePlatform, ytUrlResolversSettings } from "../../settings" import { sign } from "../crypto" import { lbryUrlCache } from "./urlCache" const QUERY_CHUNK_SIZE = 100 -export type YtUrlResolveItem = { type: 'video' | 'channel', id: string } +export type ResolveUrlTypes = 'video' | 'channel' +export type YtUrlResolveItem = { type: ResolveUrlTypes, id: string } type Results = Record type Paramaters = YtUrlResolveItem[] diff --git a/src/scripts/background.ts b/src/scripts/background.ts index 93631bb..a6e3f30 100644 --- a/src/scripts/background.ts +++ b/src/scripts/background.ts @@ -23,6 +23,4 @@ chrome.runtime.onMessage.addListener(({ json }, sender, sendResponse) => { })() return true -}) - -chrome.tabs.onUpdated.addListener((tabId, changeInfo) => changeInfo.status === 'complete' && chrome.tabs.sendMessage(tabId, { message: 'url-changed' })) \ No newline at end of file +}) \ No newline at end of file diff --git a/src/scripts/ytContent.tsx b/src/scripts/ytContent.tsx index a986107..91d3ba9 100644 --- a/src/scripts/ytContent.tsx +++ b/src/scripts/ytContent.tsx @@ -1,198 +1,224 @@ import { h, render } from 'preact' import { parseYouTubeURLTimeString } from '../modules/yt' -import type { resolveById } from '../modules/yt/urlResolve' -import { getExtensionSettingsAsync, getSourcePlatfromSettingsFromHostname, TargetPlatform, targetPlatformSettings } from '../settings' +import type { resolveById, ResolveUrlTypes } from '../modules/yt/urlResolve' +import { getExtensionSettingsAsync, getSourcePlatfromSettingsFromHostname, getTargetPlatfromSettingsEntiries, SourcePlatform, sourcePlatfromSettings, TargetPlatform, targetPlatformSettings } from '../settings'; -const sleep = (t: number) => new Promise(resolve => setTimeout(resolve, t)) - -interface WatchOnLbryButtonParameters { - targetPlatform?: TargetPlatform - lbryPathname?: string - time?: number -} - -interface Target { - platfrom: TargetPlatform - lbryPathname: string - time: number | null -} - -function WatchOnLbryButton({ targetPlatform, lbryPathname, time }: WatchOnLbryButtonParameters) { - if (!lbryPathname || !targetPlatform) return null - - const url = new URL(`${targetPlatform.domainPrefix}${lbryPathname}`) - if (time) url.searchParams.set('t', time.toFixed(0)) - - return
- - - {targetPlatform.button.text} - -
-} - -function updateButton(mountPoint: HTMLDivElement, target: Target | null): void { - if (!target) return render(, mountPoint) - render(, mountPoint) -} - -/** Returns a mount point for the button */ -async function findButtonMountPoint(): Promise { - const id = 'watch-on-lbry-button-container' - let mountBefore: HTMLDivElement | null = null - const sourcePlatform = getSourcePlatfromSettingsFromHostname(new URL(location.href).hostname) - if (!sourcePlatform) throw new Error(`Unknown source of: ${location.href}`) - const exits: HTMLDivElement | null = document.querySelector(`#${id}`) - if (exits) return exits - while (!(mountBefore = document.querySelector(sourcePlatform.htmlQueries.mountButtonBefore))) await sleep(200) - - const div = document.createElement('div') - div.id = id - div.style.display = 'flex' - div.style.alignItems = 'center' - mountBefore.parentElement?.insertBefore(div, mountBefore) - - return div -} - -async function findVideoElement() { - const sourcePlatform = getSourcePlatfromSettingsFromHostname(new URL(location.href).hostname) - if (!sourcePlatform) throw new Error(`Unknown source of: ${location.href}`) - let videoElement: HTMLVideoElement | null = null - while (!(videoElement = document.querySelector(sourcePlatform.htmlQueries.videoPlayer))) await sleep(200) - return videoElement -} - -// 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') throw new Error("Background error.") - return json ? JSON.parse(json) : null -} - -// Start (async () => { - const settings = await getExtensionSettingsAsync() - let updater: (() => Promise) + const sleep = (t: number) => new Promise(resolve => setTimeout(resolve, t)) + interface Target { + platform: TargetPlatform + lbryPathname: string + type: ResolveUrlTypes + time: number | null + } + + const sourcePlatform = getSourcePlatfromSettingsFromHostname(new URL(location.href).hostname) + if (!sourcePlatform) return + const targetPlatforms = getTargetPlatfromSettingsEntiries() + + const settings = await getExtensionSettingsAsync() // Listen Settings Change chrome.storage.onChanged.addListener(async (changes, areaName) => { if (areaName !== 'local') return Object.assign(settings, Object.fromEntries(Object.entries(changes).map(([key, change]) => [key, change.newValue]))) - if (changes.redirect) await onModeChange() - await updater() }) - /* - * 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 - */ - // Listen URL Change - chrome.runtime.onMessage.addListener(({ message }, sender) => message === 'url-changed' && updater()) + const mountPoint = document.createElement('div') + mountPoint.style.display = 'flex' - async function getTargetByURL(url: URL) { - if (url.pathname === '/watch' && url.searchParams.has('v')) { - const videoId = url.searchParams.get('v')! - const result = await requestResolveById([{ id: videoId, type: 'video' }]) - const target: Target | null = result?.[videoId] ? { lbryPathname: result[videoId].id, platfrom: targetPlatformSettings[settings.targetPlatform], time: null } : null + function WatchOnLbryButton({ target }: { target?: Target }) { + if (!target) return null + const url = getLbryUrlByTarget(target) - return target - } - else if (url.pathname.startsWith('/channel/')) { - await requestResolveById([{ id: url.pathname.substring("/channel/".length), 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) - await requestResolveById([{ id: content.substring(startsAt, endsAt), type: 'channel' }]) - } - - return null + return } - async function redirectTo({ lbryPathname, platfrom, time }: Target) { - const url = new URL(`${platfrom.domainPrefix}${lbryPathname}`) + function updateButton(target: Target | null): void { + if (!target) return render(, mountPoint) - if (time) url.searchParams.set('t', time.toFixed(0)) + const sourcePlatform = getSourcePlatfromSettingsFromHostname(new URL(location.href).hostname) + if (!sourcePlatform) return render(, mountPoint) - findVideoElement().then((videoElement) => { - videoElement.addEventListener('play', () => videoElement.pause(), { once: true }) - videoElement.pause() - }) + const mountBefore = document.querySelector(sourcePlatform.htmlQueries.mountButtonBefore[target.type]) + if (!mountBefore) return render(, mountPoint) - if (platfrom === targetPlatformSettings.app) { - if (document.hidden) await new Promise((resolve) => document.addEventListener('visibilitychange', resolve, { once: true })) + mountBefore.parentElement?.insertBefore(mountPoint, mountBefore) + render(, mountPoint) + } - // On redirect with app, people might choose to cancel browser's dialog - // So we dont destroy the current window automatically for them - // And also we are keeping the same window for less distiraction - if (settings.redirect) { - location.replace(url.toString()) + async function findVideoElementAwait() { + const sourcePlatform = getSourcePlatfromSettingsFromHostname(new URL(location.href).hostname) + if (!sourcePlatform) throw new Error(`Unknown source of: ${location.href}`) + let videoElement: HTMLVideoElement | null = null + while (!(videoElement = document.querySelector(sourcePlatform.htmlQueries.videoPlayer))) await sleep(200) + return videoElement + } + + async function getTargetsByURL(...urls: URL[]) { + const params: Parameters[0] = [] + const platform = targetPlatformSettings[settings.targetPlatform] + + 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 } + } + 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('/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 } + } + } + + const results = Object.entries(await requestResolveById(params)) + const targets: Record = Object.fromEntries(results.map(([id, result]) => { + const data = datas[id] + + if (!result) return [ + data.url.href, + null + ] + + 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) + } + ] + })) + + 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') throw new Error("Background error.") + return json ? JSON.parse(json) : null + } + + function getLbryUrlByTarget(target: Target) { + const url = new URL(`${target.platform.domainPrefix}${target.lbryPathname}`) + if (target.time) url.searchParams.set('t', target.time.toFixed(0)) + + return url + } + + let urlCache: URL | null = null + while (true) { + await sleep(500) + + const url = new URL(location.href); + let target = (await getTargetsByURL(url))[url.href] + + if (settings.redirect) { + if (!target) continue + if (url === urlCache) continue + + const lbryURL = getLbryUrlByTarget(target) + + findVideoElementAwait().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 + location.replace(lbryURL) } else { - open(url.toString(), '_blank') + open(lbryURL, '_blank') if (window.history.length === 1) window.close() else window.history.back() } } - else - location.replace(url.toString()) - } - - let removeVideoTimeUpdateListener: (() => void) | null = null - async function onModeChange() { - let target: Target | null = null - if (settings.redirect) - updater = async () => { - const url = new URL(location.href) - target = await getTargetByURL(url) - if (!target) return - target.time = url.searchParams.has('t') ? parseYouTubeURLTimeString(url.searchParams.get('t')!) : null - redirectTo(target) - } else { - const mountPoint = await findButtonMountPoint() - const videoElement = await findVideoElement() + if (!target) { + const descriptionElement = document.querySelector(sourcePlatform.htmlQueries.videoDescription) + if (descriptionElement) { + const anchors = Array.from(descriptionElement.querySelectorAll('a')) - const getTime = () => videoElement.currentTime > 3 && videoElement.currentTime < videoElement.duration - 1 ? videoElement.currentTime : null + for (const anchor of anchors) { + const url = new URL(anchor.href) + let lbryURL: URL | null = null + if (sourcePlatform === sourcePlatfromSettings['youtube.com']) { + if (!targetPlatforms.some(([key, platform]) => url.searchParams.get('q')?.startsWith(platform.domainPrefix))) continue + lbryURL = new URL(url.searchParams.get('q')!) + } + else { + if (!targetPlatforms.some(([key, platform]) => url.href.startsWith(platform.domainPrefix))) continue + lbryURL = new URL(url.href) + } - const onTimeUpdate = () => target && updateButton(mountPoint, Object.assign(target, { time: getTime() })) - removeVideoTimeUpdateListener?.call(null) - videoElement.addEventListener('timeupdate', onTimeUpdate) - removeVideoTimeUpdateListener = () => videoElement.removeEventListener('timeupdate', onTimeUpdate) - - updater = async () => { - const url = new URL(location.href) - target = await getTargetByURL(url) - if (target) target.time = getTime() - updateButton(mountPoint, target) + if (lbryURL) { + target = { + lbryPathname: lbryURL.pathname.substring(1), + time: null, + type: 'video', + platform: targetPlatformSettings[settings.targetPlatform] + } + break + } + } + } } + + if (target) { + const videoElement = document.querySelector(sourcePlatform.htmlQueries.videoPlayer) + if (videoElement) target.time = videoElement.currentTime > 3 && videoElement.currentTime < videoElement.duration - 1 ? videoElement.currentTime : null + } + + // We run it anyway with null target to hide the button + updateButton(target) } - await updater() + + urlCache = url } - await onModeChange() })() \ No newline at end of file diff --git a/src/settings/index.ts b/src/settings/index.ts index d2d8bc5..017bcdd 100644 --- a/src/settings/index.ts +++ b/src/settings/index.ts @@ -1,5 +1,6 @@ import type { JSX } from "preact" import { useEffect, useReducer } from "preact/hooks" +import type { ResolveUrlTypes } from "../modules/yt/urlResolve" export interface ExtensionSettings { redirect: boolean @@ -112,8 +113,9 @@ export const targetPlatformSettings = { const sourcePlatform = (o: { hostnames: string[] htmlQueries: { - mountButtonBefore: string, - videoPlayer: string + mountButtonBefore: Record, + videoPlayer: string, + videoDescription: string } }) => o export type SourcePlatform = ReturnType @@ -125,18 +127,27 @@ export function getSourcePlatfromSettingsFromHostname(hostname: string) { return null } export const sourcePlatfromSettings = { - "yewtu.be": sourcePlatform({ - hostnames: ['yewtu.be', 'vid.puffyan.us', 'invidio.xamh.de', 'invidious.kavin.rocks'], - htmlQueries: { - mountButtonBefore: '#watch-on-youtube', - videoPlayer: '#player-container video' - } - }), "youtube.com": sourcePlatform({ hostnames: ['www.youtube.com'], htmlQueries: { - mountButtonBefore: 'ytd-video-owner-renderer~#subscribe-button', - videoPlayer: '#ytd-player video' + mountButtonBefore: { + video: 'ytd-video-owner-renderer~#subscribe-button', + channel: '#channel-header-container #buttons' + }, + videoPlayer: '#ytd-player video', + videoDescription: 'ytd-video-secondary-info-renderer #description' + } + }), + "yewtu.be": sourcePlatform({ + hostnames: ['yewtu.be', 'vid.puffyan.us', 'invidio.xamh.de', 'invidious.kavin.rocks'], + htmlQueries: { + mountButtonBefore: + { + video: '#watch-on-youtube', + channel: '#subscribe' + }, + videoPlayer: '#player-container video', + videoDescription: '#descriptionWrapper' } }) } From 1eb21eb51fe92f2be1895b972bd2a0f8ce62c51e Mon Sep 17 00:00:00 2001 From: Shiba <44804845+DeepDoge@users.noreply.github.com> Date: Wed, 6 Jul 2022 13:10:32 +0000 Subject: [PATCH 2/6] =?UTF-8?q?=F0=9F=8D=99=20made=20yt=20video=20pause,?= =?UTF-8?q?=20since=20we=20use=20new=20tab=20now?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/scripts/ytContent.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/scripts/ytContent.tsx b/src/scripts/ytContent.tsx index 91d3ba9..4d41de4 100644 --- a/src/scripts/ytContent.tsx +++ b/src/scripts/ytContent.tsx @@ -49,7 +49,11 @@ import { getExtensionSettingsAsync, getSourcePlatfromSettingsFromHostname, getTa fontSize: '14px', textDecoration: 'none', ...target.platform.button.style?.button, - }}> + }} + onClick={() => findVideoElementAwait().then((videoElement) => { + videoElement.pause() + })} + > {target.platform.button.text} From ce11d4fdf3f71a41b7d7e161af5fc1d6223523df Mon Sep 17 00:00:00 2001 From: Shiba <44804845+DeepDoge@users.noreply.github.com> Date: Thu, 7 Jul 2022 17:37:57 +0000 Subject: [PATCH 3/6] =?UTF-8?q?=F0=9F=8D=A3=20little=20bug=20fix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/scripts/ytContent.tsx | 114 ++++++++++++++++++++------------------ 1 file changed, 59 insertions(+), 55 deletions(-) diff --git a/src/scripts/ytContent.tsx b/src/scripts/ytContent.tsx index 4d41de4..2e41d0e 100644 --- a/src/scripts/ytContent.tsx +++ b/src/scripts/ytContent.tsx @@ -159,69 +159,73 @@ import { getExtensionSettingsAsync, getSourcePlatfromSettingsFromHostname, getTa const url = new URL(location.href); let target = (await getTargetsByURL(url))[url.href] - if (settings.redirect) { - if (!target) continue - if (url === urlCache) continue - - const lbryURL = getLbryUrlByTarget(target) - - findVideoElementAwait().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 - location.replace(lbryURL) + try { + if (settings.redirect) { + if (!target) continue + if (url === urlCache) continue + + const lbryURL = getLbryUrlByTarget(target) + + findVideoElementAwait().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 + location.replace(lbryURL) + } + else { + open(lbryURL, '_blank') + if (window.history.length === 1) window.close() + else window.history.back() + } } else { - open(lbryURL, '_blank') - if (window.history.length === 1) window.close() - else window.history.back() - } - } - else { - if (!target) { - const descriptionElement = document.querySelector(sourcePlatform.htmlQueries.videoDescription) - if (descriptionElement) { - const anchors = Array.from(descriptionElement.querySelectorAll('a')) - - for (const anchor of anchors) { - const url = new URL(anchor.href) - let lbryURL: URL | null = null - if (sourcePlatform === sourcePlatfromSettings['youtube.com']) { - if (!targetPlatforms.some(([key, platform]) => url.searchParams.get('q')?.startsWith(platform.domainPrefix))) continue - lbryURL = new URL(url.searchParams.get('q')!) - } - 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', - platform: targetPlatformSettings[settings.targetPlatform] + if (!target) { + const descriptionElement = document.querySelector(sourcePlatform.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']) { + if (!targetPlatforms.some(([key, platform]) => url.searchParams.get('q')?.startsWith(platform.domainPrefix))) continue + lbryURL = new URL(url.searchParams.get('q')!) + } + 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', + platform: targetPlatformSettings[settings.targetPlatform] + } + break } - break } } } + + if (target) { + const videoElement = document.querySelector(sourcePlatform.htmlQueries.videoPlayer) + if (videoElement) target.time = videoElement.currentTime > 3 && videoElement.currentTime < videoElement.duration - 1 ? videoElement.currentTime : null + } + + // We run it anyway with null target to hide the button + updateButton(target) } - - if (target) { - const videoElement = document.querySelector(sourcePlatform.htmlQueries.videoPlayer) - if (videoElement) target.time = videoElement.currentTime > 3 && videoElement.currentTime < videoElement.duration - 1 ? videoElement.currentTime : null - } - - // We run it anyway with null target to hide the button - updateButton(target) + } catch (error) { + console.error(error) } - urlCache = url } From 4102177212fce8de23ae38569374197549b5dc40 Mon Sep 17 00:00:00 2001 From: Shiba <44804845+DeepDoge@users.noreply.github.com> Date: Thu, 7 Jul 2022 18:00:32 +0000 Subject: [PATCH 4/6] =?UTF-8?q?=F0=9F=8D=A3=20Some=20more=20bug=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/scripts/ytContent.tsx | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/scripts/ytContent.tsx b/src/scripts/ytContent.tsx index 2e41d0e..e4294cc 100644 --- a/src/scripts/ytContent.tsx +++ b/src/scripts/ytContent.tsx @@ -141,7 +141,11 @@ import { getExtensionSettingsAsync, getSourcePlatfromSettingsFromHostname, getTa // 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') throw new Error("Background error.") + if (json === 'error') + { + console.error("Background error on:", params) + throw new Error("Background error.") + } return json ? JSON.parse(json) : null } @@ -156,11 +160,10 @@ import { getExtensionSettingsAsync, getSourcePlatfromSettingsFromHostname, getTa while (true) { await sleep(500) - const url = new URL(location.href); - let target = (await getTargetsByURL(url))[url.href] - + const url: URL = (urlCache?.href === location.href) ? urlCache : new URL(location.href); try { if (settings.redirect) { + const target = (await getTargetsByURL(url))[url.href] if (!target) continue if (url === urlCache) continue @@ -184,6 +187,8 @@ import { getExtensionSettingsAsync, getSourcePlatfromSettingsFromHostname, getTa } } else { + if (urlCache !== url) updateButton(null) + let target = (await getTargetsByURL(url))[url.href] if (!target) { const descriptionElement = document.querySelector(sourcePlatform.htmlQueries.videoDescription) if (descriptionElement) { @@ -215,7 +220,7 @@ import { getExtensionSettingsAsync, getSourcePlatfromSettingsFromHostname, getTa } } - if (target) { + 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 } 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 5/6] =?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 6/6] =?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 } }) }