mirror of
https://github.com/LBRYFoundation/Watch-on-LBRY.git
synced 2025-08-23 17:47:26 +00:00
🍙 new stuff and some changes
- channel buttons and redirect - in button mode if there is no target try to find lbry url in the description - used a loop in content script instead of events and stuff to make it less confusing
This commit is contained in:
parent
66cb8ccccf
commit
c895d53253
6 changed files with 217 additions and 288 deletions
97
package-lock.json
generated
97
package-lock.json
generated
|
@ -4826,91 +4826,6 @@
|
||||||
"timsort": "^0.3.0"
|
"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": {
|
"css-modules-loader-core": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/css-modules-loader-core/-/css-modules-loader-core-1.1.0.tgz",
|
"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=",
|
"integrity": "sha1-Bupvg2ead0njhs/h/oEq5dsiPe0=",
|
||||||
"dev": true
|
"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": {
|
"ieee754": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||||
|
@ -14965,12 +14874,6 @@
|
||||||
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
|
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
|
||||||
"dev": true
|
"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": {
|
"source-map-resolve": {
|
||||||
"version": "0.5.3",
|
"version": "0.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz",
|
"resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz",
|
||||||
|
|
10
package.json
10
package.json
|
@ -5,29 +5,19 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build:assets": "cpx \"src/**/*.{json,png,svg}\" dist/",
|
"build:assets": "cpx \"src/**/*.{json,png,svg}\" dist/",
|
||||||
"watch:assets": "cpx --watch -v \"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\"",
|
"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\"",
|
"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",
|
"build:webext": "web-ext build --source-dir ./dist --overwrite-dest",
|
||||||
|
|
||||||
"clear:dist": "rm -r ./dist ; mkdir ./dist",
|
"clear:dist": "rm -r ./dist ; mkdir ./dist",
|
||||||
|
|
||||||
"build:base": "npm-run-all -l -p build:parcel build:assets",
|
"build:base": "npm-run-all -l -p build:parcel build:assets",
|
||||||
|
|
||||||
"pick:manifest:v2": "cp -b ./manifest.v2.json ./dist/manifest.json",
|
"pick:manifest:v2": "cp -b ./manifest.v2.json ./dist/manifest.json",
|
||||||
"pick:manifest:v3": "cp -b ./manifest.v3.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: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: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",
|
"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: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:v3": "npm run clear:dist ; npm run pick:manifest:v3 && npm-run-all -l -p watch:parcel watch:assets",
|
||||||
|
|
||||||
"watch": "npm run watch:v3",
|
"watch": "npm run watch:v3",
|
||||||
|
|
||||||
"start:chrome": "web-ext run -t chromium --source-dir ./dist",
|
"start:chrome": "web-ext run -t chromium --source-dir ./dist",
|
||||||
"start:firefox": "web-ext run --source-dir ./dist",
|
"start:firefox": "web-ext run --source-dir ./dist",
|
||||||
"test": "jest"
|
"test": "jest"
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
import { chunk } from "lodash"
|
import { chunk } from "lodash"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import { getExtensionSettingsAsync, ytUrlResolversSettings } from "../../settings"
|
import { getExtensionSettingsAsync, SourcePlatform, ytUrlResolversSettings } from "../../settings"
|
||||||
import { sign } from "../crypto"
|
import { sign } from "../crypto"
|
||||||
import { lbryUrlCache } from "./urlCache"
|
import { lbryUrlCache } from "./urlCache"
|
||||||
|
|
||||||
const QUERY_CHUNK_SIZE = 100
|
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<string, YtUrlResolveItem>
|
type Results = Record<string, YtUrlResolveItem>
|
||||||
type Paramaters = YtUrlResolveItem[]
|
type Paramaters = YtUrlResolveItem[]
|
||||||
|
|
||||||
|
|
|
@ -24,5 +24,3 @@ chrome.runtime.onMessage.addListener(({ json }, sender, sendResponse) => {
|
||||||
|
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|
||||||
chrome.tabs.onUpdated.addListener((tabId, changeInfo) => changeInfo.status === 'complete' && chrome.tabs.sendMessage(tabId, { message: 'url-changed' }))
|
|
|
@ -1,123 +1,101 @@
|
||||||
import { h, render } from 'preact'
|
import { h, render } from 'preact'
|
||||||
import { parseYouTubeURLTimeString } from '../modules/yt'
|
import { parseYouTubeURLTimeString } from '../modules/yt'
|
||||||
import type { resolveById } from '../modules/yt/urlResolve'
|
import type { resolveById, ResolveUrlTypes } from '../modules/yt/urlResolve'
|
||||||
import { getExtensionSettingsAsync, getSourcePlatfromSettingsFromHostname, TargetPlatform, targetPlatformSettings } from '../settings'
|
import { getExtensionSettingsAsync, getSourcePlatfromSettingsFromHostname, getTargetPlatfromSettingsEntiries, SourcePlatform, sourcePlatfromSettings, TargetPlatform, targetPlatformSettings } from '../settings';
|
||||||
|
|
||||||
const sleep = (t: number) => new Promise(resolve => setTimeout(resolve, t))
|
(async () => {
|
||||||
|
const sleep = (t: number) => new Promise(resolve => setTimeout(resolve, t))
|
||||||
|
|
||||||
interface WatchOnLbryButtonParameters {
|
interface Target {
|
||||||
targetPlatform?: TargetPlatform
|
platform: TargetPlatform
|
||||||
lbryPathname?: string
|
|
||||||
time?: number
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Target {
|
|
||||||
platfrom: TargetPlatform
|
|
||||||
lbryPathname: string
|
lbryPathname: string
|
||||||
|
type: ResolveUrlTypes
|
||||||
time: number | null
|
time: number | null
|
||||||
}
|
}
|
||||||
|
|
||||||
function WatchOnLbryButton({ targetPlatform, lbryPathname, time }: WatchOnLbryButtonParameters) {
|
const sourcePlatform = getSourcePlatfromSettingsFromHostname(new URL(location.href).hostname)
|
||||||
if (!lbryPathname || !targetPlatform) return null
|
if (!sourcePlatform) return
|
||||||
|
const targetPlatforms = getTargetPlatfromSettingsEntiries()
|
||||||
|
|
||||||
const url = new URL(`${targetPlatform.domainPrefix}${lbryPathname}`)
|
const settings = await getExtensionSettingsAsync()
|
||||||
if (time) url.searchParams.set('t', time.toFixed(0))
|
// 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])))
|
||||||
|
})
|
||||||
|
|
||||||
|
const mountPoint = document.createElement('div')
|
||||||
|
mountPoint.style.display = 'flex'
|
||||||
|
|
||||||
|
function WatchOnLbryButton({ target }: { target?: Target }) {
|
||||||
|
if (!target) return null
|
||||||
|
const url = getLbryUrlByTarget(target)
|
||||||
|
|
||||||
return <div style={{ display: 'flex', justifyContent: 'center', flexDirection: 'column' }}>
|
return <div style={{ display: 'flex', justifyContent: 'center', flexDirection: 'column' }}>
|
||||||
<a href={`${url.toString()}`} role='button'
|
<a href={`${url.href}`} target={target.platform === targetPlatformSettings.app ? '' : '_blank'} role='button'
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
gap: '12px',
|
gap: '12px',
|
||||||
borderRadius: '2px',
|
borderRadius: '2px',
|
||||||
backgroundColor: targetPlatform.theme,
|
backgroundColor: target.platform.theme,
|
||||||
backgroundImage: targetPlatform.theme,
|
backgroundImage: target.platform.theme,
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
border: '0',
|
border: '0',
|
||||||
color: 'whitesmoke',
|
color: 'whitesmoke',
|
||||||
padding: '10px 16px',
|
padding: '10px 16px',
|
||||||
marginRight: '4px',
|
marginRight: target.type === 'channel' ? '10px' : '4px',
|
||||||
fontSize: '14px',
|
fontSize: '14px',
|
||||||
textDecoration: 'none',
|
textDecoration: 'none',
|
||||||
...targetPlatform.button.style?.button,
|
...target.platform.button.style?.button,
|
||||||
}}>
|
}}>
|
||||||
<img src={targetPlatform.button.icon} height={16}
|
<img src={target.platform.button.icon} height={16}
|
||||||
style={{ transform: 'scale(1.5)', ...targetPlatform.button.style?.icon }} />
|
style={{ transform: 'scale(1.5)', ...target.platform.button.style?.icon }} />
|
||||||
<span>{targetPlatform.button.text}</span>
|
<span>{target.platform.button.text}</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateButton(mountPoint: HTMLDivElement, target: Target | null): void {
|
function updateButton(target: Target | null): void {
|
||||||
if (!target) return render(<WatchOnLbryButton />, mountPoint)
|
if (!target) return render(<WatchOnLbryButton />, mountPoint)
|
||||||
render(<WatchOnLbryButton targetPlatform={target.platfrom} lbryPathname={target.lbryPathname} time={target.time ?? undefined} />, mountPoint)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns a mount point for the button */
|
|
||||||
async function findButtonMountPoint(): Promise<HTMLDivElement> {
|
|
||||||
const id = 'watch-on-lbry-button-container'
|
|
||||||
let mountBefore: HTMLDivElement | null = null
|
|
||||||
const sourcePlatform = getSourcePlatfromSettingsFromHostname(new URL(location.href).hostname)
|
const sourcePlatform = getSourcePlatfromSettingsFromHostname(new URL(location.href).hostname)
|
||||||
if (!sourcePlatform) throw new Error(`Unknown source of: ${location.href}`)
|
if (!sourcePlatform) return render(<WatchOnLbryButton />, mountPoint)
|
||||||
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')
|
const mountBefore = document.querySelector(sourcePlatform.htmlQueries.mountButtonBefore[target.type])
|
||||||
div.id = id
|
if (!mountBefore) return render(<WatchOnLbryButton />, mountPoint)
|
||||||
div.style.display = 'flex'
|
|
||||||
div.style.alignItems = 'center'
|
|
||||||
mountBefore.parentElement?.insertBefore(div, mountBefore)
|
|
||||||
|
|
||||||
return div
|
mountBefore.parentElement?.insertBefore(mountPoint, mountBefore)
|
||||||
}
|
render(<WatchOnLbryButton target={target} />, mountPoint)
|
||||||
|
}
|
||||||
|
|
||||||
async function findVideoElement() {
|
async function findVideoElementAwait() {
|
||||||
const sourcePlatform = getSourcePlatfromSettingsFromHostname(new URL(location.href).hostname)
|
const sourcePlatform = getSourcePlatfromSettingsFromHostname(new URL(location.href).hostname)
|
||||||
if (!sourcePlatform) throw new Error(`Unknown source of: ${location.href}`)
|
if (!sourcePlatform) throw new Error(`Unknown source of: ${location.href}`)
|
||||||
let videoElement: HTMLVideoElement | null = null
|
let videoElement: HTMLVideoElement | null = null
|
||||||
while (!(videoElement = document.querySelector(sourcePlatform.htmlQueries.videoPlayer))) await sleep(200)
|
while (!(videoElement = document.querySelector(sourcePlatform.htmlQueries.videoPlayer))) await sleep(200)
|
||||||
return videoElement
|
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 getTargetsByURL(...urls: URL[]) {
|
||||||
async function requestResolveById(...params: Parameters<typeof resolveById>): ReturnType<typeof resolveById> {
|
const params: Parameters<typeof requestResolveById>[0] = []
|
||||||
const json = await new Promise<string | null | 'error'>((resolve) => chrome.runtime.sendMessage({ json: JSON.stringify(params) }, resolve))
|
const platform = targetPlatformSettings[settings.targetPlatform]
|
||||||
if (json === 'error') throw new Error("Background error.")
|
|
||||||
return json ? JSON.parse(json) : null
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start
|
const datas: Record<string, { url: URL, type: ResolveUrlTypes }> = {}
|
||||||
(async () => {
|
|
||||||
const settings = await getExtensionSettingsAsync()
|
|
||||||
let updater: (() => Promise<void>)
|
|
||||||
|
|
||||||
// Listen Settings Change
|
for (const url of urls) {
|
||||||
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())
|
|
||||||
|
|
||||||
async function getTargetByURL(url: URL) {
|
|
||||||
if (url.pathname === '/watch' && url.searchParams.has('v')) {
|
if (url.pathname === '/watch' && url.searchParams.has('v')) {
|
||||||
const videoId = url.searchParams.get('v')!
|
const id = url.searchParams.get('v')!
|
||||||
const result = await requestResolveById([{ id: videoId, type: 'video' }])
|
const type: ResolveUrlTypes = 'video'
|
||||||
const target: Target | null = result?.[videoId] ? { lbryPathname: result[videoId].id, platfrom: targetPlatformSettings[settings.targetPlatform], time: null } : null
|
params.push({ id, type })
|
||||||
|
datas[id] = { url, type }
|
||||||
return target
|
|
||||||
}
|
}
|
||||||
else if (url.pathname.startsWith('/channel/')) {
|
else if (url.pathname.startsWith('/channel/')) {
|
||||||
await requestResolveById([{ id: url.pathname.substring("/channel/".length), type: '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/')) {
|
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
|
// We have to download the page content again because these parts of the page are not responsive
|
||||||
|
@ -127,72 +105,120 @@ async function requestResolveById(...params: Parameters<typeof resolveById>): Re
|
||||||
const suffix = `"`
|
const suffix = `"`
|
||||||
const startsAt = content.indexOf(prefix) + prefix.length
|
const startsAt = content.indexOf(prefix) + prefix.length
|
||||||
const endsAt = content.indexOf(suffix, startsAt)
|
const endsAt = content.indexOf(suffix, startsAt)
|
||||||
await requestResolveById([{ id: content.substring(startsAt, endsAt), type: 'channel' }])
|
const id = content.substring(startsAt, endsAt)
|
||||||
|
const type: ResolveUrlTypes = 'channel'
|
||||||
|
params.push({ id, type })
|
||||||
|
datas[id] = { url, type }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null
|
const results = Object.entries(await requestResolveById(params))
|
||||||
|
const targets: Record<string, Target | null> = 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<typeof resolveById>): ReturnType<typeof resolveById> {
|
||||||
|
const json = await new Promise<string | null | 'error'>((resolve) => chrome.runtime.sendMessage({ json: JSON.stringify(params) }, resolve))
|
||||||
|
if (json === 'error') throw new Error("Background error.")
|
||||||
|
return json ? JSON.parse(json) : null
|
||||||
}
|
}
|
||||||
|
|
||||||
async function redirectTo({ lbryPathname, platfrom, time }: Target) {
|
function getLbryUrlByTarget(target: Target) {
|
||||||
const url = new URL(`${platfrom.domainPrefix}${lbryPathname}`)
|
const url = new URL(`${target.platform.domainPrefix}${target.lbryPathname}`)
|
||||||
|
if (target.time) url.searchParams.set('t', target.time.toFixed(0))
|
||||||
|
|
||||||
if (time) url.searchParams.set('t', time.toFixed(0))
|
return url
|
||||||
|
}
|
||||||
|
|
||||||
findVideoElement().then((videoElement) => {
|
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.addEventListener('play', () => videoElement.pause(), { once: true })
|
||||||
videoElement.pause()
|
videoElement.pause()
|
||||||
})
|
})
|
||||||
|
|
||||||
if (platfrom === targetPlatformSettings.app) {
|
if (target.platform === targetPlatformSettings.app) {
|
||||||
if (document.hidden) await new Promise((resolve) => document.addEventListener('visibilitychange', resolve, { once: true }))
|
if (document.hidden) await new Promise((resolve) => document.addEventListener('visibilitychange', resolve, { once: true }))
|
||||||
|
// Its not gonna be able to replace anyway
|
||||||
// On redirect with app, people might choose to cancel browser's dialog
|
// This was empty window doesnt stay open
|
||||||
// So we dont destroy the current window automatically for them
|
location.replace(lbryURL)
|
||||||
// And also we are keeping the same window for less distiraction
|
|
||||||
if (settings.redirect) {
|
|
||||||
location.replace(url.toString())
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
open(url.toString(), '_blank')
|
open(lbryURL, '_blank')
|
||||||
if (window.history.length === 1) window.close()
|
if (window.history.length === 1) window.close()
|
||||||
else window.history.back()
|
else window.history.back()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else {
|
||||||
location.replace(url.toString())
|
if (!target) {
|
||||||
}
|
const descriptionElement = document.querySelector(sourcePlatform.htmlQueries.videoDescription)
|
||||||
|
if (descriptionElement) {
|
||||||
|
const anchors = Array.from(descriptionElement.querySelectorAll<HTMLAnchorElement>('a'))
|
||||||
|
|
||||||
let removeVideoTimeUpdateListener: (() => void) | null = null
|
for (const anchor of anchors) {
|
||||||
async function onModeChange() {
|
const url = new URL(anchor.href)
|
||||||
let target: Target | null = null
|
let lbryURL: URL | null = null
|
||||||
if (settings.redirect)
|
if (sourcePlatform === sourcePlatfromSettings['youtube.com']) {
|
||||||
updater = async () => {
|
if (!targetPlatforms.some(([key, platform]) => url.searchParams.get('q')?.startsWith(platform.domainPrefix))) continue
|
||||||
const url = new URL(location.href)
|
lbryURL = new URL(url.searchParams.get('q')!)
|
||||||
target = await getTargetByURL(url)
|
|
||||||
if (!target) return
|
|
||||||
target.time = url.searchParams.has('t') ? parseYouTubeURLTimeString(url.searchParams.get('t')!) : null
|
|
||||||
redirectTo(target)
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
const mountPoint = await findButtonMountPoint()
|
if (!targetPlatforms.some(([key, platform]) => url.href.startsWith(platform.domainPrefix))) continue
|
||||||
const videoElement = await findVideoElement()
|
lbryURL = new URL(url.href)
|
||||||
|
}
|
||||||
const getTime = () => videoElement.currentTime > 3 && videoElement.currentTime < videoElement.duration - 1 ? videoElement.currentTime : null
|
|
||||||
|
if (lbryURL) {
|
||||||
const onTimeUpdate = () => target && updateButton(mountPoint, Object.assign(target, { time: getTime() }))
|
target = {
|
||||||
removeVideoTimeUpdateListener?.call(null)
|
lbryPathname: lbryURL.pathname.substring(1),
|
||||||
videoElement.addEventListener('timeupdate', onTimeUpdate)
|
time: null,
|
||||||
removeVideoTimeUpdateListener = () => videoElement.removeEventListener('timeupdate', onTimeUpdate)
|
type: 'video',
|
||||||
|
platform: targetPlatformSettings[settings.targetPlatform]
|
||||||
updater = async () => {
|
}
|
||||||
const url = new URL(location.href)
|
break
|
||||||
target = await getTargetByURL(url)
|
}
|
||||||
if (target) target.time = getTime()
|
}
|
||||||
updateButton(mountPoint, target)
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
await updater()
|
if (target) {
|
||||||
|
const videoElement = document.querySelector<HTMLVideoElement>(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)
|
||||||
|
}
|
||||||
|
|
||||||
|
urlCache = url
|
||||||
}
|
}
|
||||||
|
|
||||||
await onModeChange()
|
|
||||||
})()
|
})()
|
|
@ -1,5 +1,6 @@
|
||||||
import type { JSX } from "preact"
|
import type { JSX } from "preact"
|
||||||
import { useEffect, useReducer } from "preact/hooks"
|
import { useEffect, useReducer } from "preact/hooks"
|
||||||
|
import type { ResolveUrlTypes } from "../modules/yt/urlResolve"
|
||||||
|
|
||||||
export interface ExtensionSettings {
|
export interface ExtensionSettings {
|
||||||
redirect: boolean
|
redirect: boolean
|
||||||
|
@ -112,8 +113,9 @@ export const targetPlatformSettings = {
|
||||||
const sourcePlatform = (o: {
|
const sourcePlatform = (o: {
|
||||||
hostnames: string[]
|
hostnames: string[]
|
||||||
htmlQueries: {
|
htmlQueries: {
|
||||||
mountButtonBefore: string,
|
mountButtonBefore: Record<ResolveUrlTypes, string>,
|
||||||
videoPlayer: string
|
videoPlayer: string,
|
||||||
|
videoDescription: string
|
||||||
}
|
}
|
||||||
}) => o
|
}) => o
|
||||||
export type SourcePlatform = ReturnType<typeof sourcePlatform>
|
export type SourcePlatform = ReturnType<typeof sourcePlatform>
|
||||||
|
@ -125,18 +127,27 @@ export function getSourcePlatfromSettingsFromHostname(hostname: string) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
export const sourcePlatfromSettings = {
|
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({
|
"youtube.com": sourcePlatform({
|
||||||
hostnames: ['www.youtube.com'],
|
hostnames: ['www.youtube.com'],
|
||||||
htmlQueries: {
|
htmlQueries: {
|
||||||
mountButtonBefore: 'ytd-video-owner-renderer~#subscribe-button',
|
mountButtonBefore: {
|
||||||
videoPlayer: '#ytd-player video'
|
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'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue