mirror of
https://github.com/LBRYFoundation/Watch-on-LBRY.git
synced 2025-08-23 17:47:26 +00:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
c0304c1b79
11 changed files with 228 additions and 205 deletions
|
@ -68,6 +68,7 @@ export function Dialogs(params: { manager: ReturnType<typeof createDialogManager
|
|||
{`
|
||||
.alert-dialog
|
||||
{
|
||||
position: fixed;
|
||||
border: none;
|
||||
background: var(--color-dark);
|
||||
color: var(--color-light);
|
||||
|
|
|
@ -1,25 +1,21 @@
|
|||
// This should only work in background
|
||||
|
||||
let db: IDBDatabase | null = null
|
||||
|
||||
if (typeof chrome.extension === 'undefined') throw new Error("YT urlCache can only be accessed from extension windows and service-workers.")
|
||||
|
||||
if (typeof self.indexedDB !== 'undefined') {
|
||||
const openRequest = indexedDB.open("yt-url-resolver-cache")
|
||||
openRequest.addEventListener('upgradeneeded', () => openRequest.result.createObjectStore("store").createIndex("expireAt", "expireAt"))
|
||||
|
||||
// Delete Expired
|
||||
openRequest.addEventListener('success', () => {
|
||||
db = openRequest.result
|
||||
clearExpired()
|
||||
})
|
||||
}
|
||||
else console.warn(`IndexedDB not supported`)
|
||||
let db = new Promise<IDBDatabase>((resolve, reject) => {
|
||||
if (typeof self.indexedDB !== 'undefined') {
|
||||
const openRequest = indexedDB.open("yt-url-resolver-cache")
|
||||
openRequest.addEventListener('upgradeneeded', () => openRequest.result.createObjectStore("store").createIndex("expireAt", "expireAt"))
|
||||
openRequest.addEventListener('success', () => {
|
||||
resolve(openRequest.result)
|
||||
clearExpired()
|
||||
}, { once: true })
|
||||
}
|
||||
else reject(`IndexedDB not supported`)
|
||||
})
|
||||
|
||||
async function clearExpired() {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
if (!db) throw new Error(`IDBDatabase not defined.`)
|
||||
const transaction = db.transaction("store", "readwrite")
|
||||
return new Promise<void>(async (resolve, reject) => {
|
||||
const transaction = (await db).transaction("store", "readwrite")
|
||||
const range = IDBKeyRange.upperBound(new Date())
|
||||
|
||||
const expireAtCursorRequest = transaction.objectStore("store").index("expireAt").openCursor(range)
|
||||
|
@ -40,8 +36,8 @@ async function clearExpired() {
|
|||
}
|
||||
|
||||
async function clearAll() {
|
||||
return await new Promise<void>((resolve, reject) => {
|
||||
const store = db?.transaction("store", "readwrite").objectStore("store")
|
||||
return await new Promise<void>(async (resolve, reject) => {
|
||||
const store = (await db).transaction("store", "readwrite").objectStore("store")
|
||||
if (!store) return resolve()
|
||||
const request = store.clear()
|
||||
request.addEventListener('success', () => resolve())
|
||||
|
@ -50,8 +46,8 @@ async function clearAll() {
|
|||
}
|
||||
|
||||
async function put(url: string | null, id: string): Promise<void> {
|
||||
return await new Promise((resolve, reject) => {
|
||||
const store = db?.transaction("store", "readwrite").objectStore("store")
|
||||
return await new Promise(async (resolve, reject) => {
|
||||
const store = (await db).transaction("store", "readwrite").objectStore("store")
|
||||
if (!store) return resolve()
|
||||
const expireAt = !url ? new Date(Date.now() + 1 * 60 * 60 * 1000) : new Date(Date.now() + 15 * 24 * 60 * 60 * 1000)
|
||||
const request = store.put({ value: url, expireAt }, id)
|
||||
|
@ -65,8 +61,8 @@ async function put(url: string | null, id: string): Promise<void> {
|
|||
// null means there is cache of that id has no lbrypathname
|
||||
// undefined means there is no cache
|
||||
async function get(id: string): Promise<string | null | undefined> {
|
||||
const response = (await new Promise((resolve, reject) => {
|
||||
const store = db?.transaction("store", "readonly").objectStore("store")
|
||||
const response = (await new Promise(async (resolve, reject) => {
|
||||
const store = (await db).transaction("store", "readonly").objectStore("store")
|
||||
if (!store) return reject(`Can't find object store.`)
|
||||
|
||||
const request = store.get(id)
|
||||
|
|
|
@ -49,7 +49,7 @@ export async function resolveById(params: Paramaters, progressCallback?: (progre
|
|||
|
||||
const controller = new AbortController()
|
||||
// 5 second timeout:
|
||||
const timeoutId = setTimeout(() => controller.abort(), 5000)
|
||||
const timeoutId = setTimeout(() => controller.abort(), 15000)
|
||||
const apiResponse = await fetch(url.toString(), { cache: 'no-store', signal: controller.signal })
|
||||
clearTimeout(timeoutId)
|
||||
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
# Getting your subscription data
|
||||
|
||||
1. Go to https://takeout.google.com/settings/takeout
|
||||
2. Deselect everything except `YouTube and YouTube Music` and within that only select `subscriptions`
|
||||
3. Go through the process and create the export
|
||||
4. Once it's exported, open the archive and find `YouTube and YouTube Music/subscriptions/subscriptions.json` and upload it to the extension
|
|
@ -4,7 +4,6 @@ import { getFileContent } from '../../modules/file'
|
|||
import { getSubsFromCsv, getSubsFromJson, getSubsFromOpml } from '../../modules/yt'
|
||||
import { resolveById } from '../../modules/yt/urlResolve'
|
||||
import { targetPlatformSettings, useExtensionSettings } from '../../settings'
|
||||
import readme from './README.md'
|
||||
|
||||
async function getSubscribedChannelIdsFromFile(file: File) {
|
||||
const ext = file.name.split('.').pop()?.toLowerCase()
|
||||
|
@ -71,8 +70,15 @@ function YTtoLBRY() {
|
|||
<Conversion />
|
||||
<aside class="help">
|
||||
<iframe allowFullScreen
|
||||
src='https://lbry.tv/$/embed/howtouseconverter/c9827448d6ac7a74ecdb972c5cdf9ddaf648a28e' />
|
||||
<section dangerouslySetInnerHTML={{ __html: readme }} />
|
||||
src='https://odysee.com/$/embed/convert-subscriptions-from-YouTube-to-LBRY/36f3a010295afe1c55e91b63bcb2eabc028ec86c?r=8bgP4hEdbd9jwBJmhEaqP3dD75LzsUob' />
|
||||
<section><h1 id="getting-your-subscription-data">Getting your subscription data</h1>
|
||||
<ol>
|
||||
<li>Go to <a href="https://takeout.google.com/settings/takeout" target='_blank'>https://takeout.google.com/settings/takeout</a></li>
|
||||
<li>Deselect everything except <code>YouTube and YouTube Music</code> and within that only select <code>subscriptions</code></li>
|
||||
<li>Go through the process and create the export</li>
|
||||
<li>Once it's exported, open the archive and find <code>YouTube and YouTube Music/subscriptions/subscriptions.(json/csv/opml)</code> and upload it to the extension</li>
|
||||
</ol>
|
||||
</section>
|
||||
</aside>
|
||||
</main>
|
||||
}
|
||||
|
|
|
@ -5,12 +5,12 @@ import { lbryUrlCache } from '../../modules/yt/urlCache'
|
|||
import { setExtensionSetting, targetPlatformSettings, useExtensionSettings } from '../../settings'
|
||||
|
||||
function WatchOnLbryPopup(params: {}) {
|
||||
const { redirect, videoSubButton, channelSubButton, videoPlayerButton } = useExtensionSettings()
|
||||
const { redirectChannel, redirectVideo, redirectVideoPlaylist, buttonVideoSub, buttonChannelSub, buttonVideoPlayer } = useExtensionSettings()
|
||||
let [loading, updateLoading] = useState(() => false)
|
||||
let [route, updateRoute] = useState<string>(() => '')
|
||||
|
||||
const dialogManager = createDialogManager()
|
||||
|
||||
|
||||
async function loads<T>(operation: Promise<T>) {
|
||||
try {
|
||||
updateLoading(true)
|
||||
|
@ -27,67 +27,66 @@ function WatchOnLbryPopup(params: {}) {
|
|||
<Dialogs manager={dialogManager} />
|
||||
{
|
||||
<header>
|
||||
<section>
|
||||
<img id="logo" src={targetPlatformSettings.odysee.button.icon}></img>
|
||||
<label>Watch on Odysee</label>
|
||||
</section>
|
||||
<img id="logo" src={targetPlatformSettings.odysee.button.icon}></img>
|
||||
<label>Watch on Odysee</label>
|
||||
</header>
|
||||
}
|
||||
{
|
||||
<main>
|
||||
<section>
|
||||
<label>Pick a mode</label>
|
||||
<div className='options'>
|
||||
<a onClick={() => setExtensionSetting('redirect', true)} className={`button ${redirect ? 'active' : ''}`}>
|
||||
Redirect
|
||||
</a>
|
||||
<a onClick={() => setExtensionSetting('redirect', false)} className={`button ${redirect ? '' : 'active'}`}>
|
||||
Show a button
|
||||
<label>Auto redirect when:</label>
|
||||
<div className='options'>
|
||||
<div class="toggle-option">
|
||||
<span>Playing a video</span>
|
||||
<a onClick={() => setExtensionSetting('redirectVideo', !redirectVideo)} className={`button ${redirectVideo ? 'active' : ''}`}>
|
||||
{redirectVideo ? 'Active' : 'Deactive'}
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
{
|
||||
!redirect &&
|
||||
<section>
|
||||
<label>Show button at:</label>
|
||||
<b className='filled'>Video</b>
|
||||
<div className='options'>
|
||||
<div className="left">
|
||||
<span>Subscribe Button:</span>
|
||||
</div>
|
||||
<a onClick={() => setExtensionSetting('videoSubButton', !videoSubButton)} className={`button ${videoSubButton ? 'active' : ''}`}>
|
||||
{videoSubButton ? 'Active' : 'Deactive'}
|
||||
</a>
|
||||
</div>
|
||||
<div className='options'>
|
||||
<div className="left">
|
||||
<span>Video Player:</span>
|
||||
</div>
|
||||
<a onClick={() => setExtensionSetting('videoPlayerButton', !videoPlayerButton)} className={`button ${videoPlayerButton ? 'active' : ''}`}>
|
||||
{videoPlayerButton ? 'Active' : 'Deactive'}
|
||||
</a>
|
||||
</div>
|
||||
<b className='filled'>Channel</b>
|
||||
<div className='options'>
|
||||
<div className="left">
|
||||
<span>Subscribe Button:</span>
|
||||
</div>
|
||||
<a onClick={() => setExtensionSetting('channelSubButton', !channelSubButton)} className={`button ${channelSubButton ? 'active' : ''}`}>
|
||||
{channelSubButton ? 'Active' : 'Deactive'}
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
}
|
||||
<div class="toggle-option">
|
||||
<span>Playing a playlist</span>
|
||||
<a onClick={() => setExtensionSetting('redirectVideoPlaylist', !redirectVideoPlaylist)} className={`button ${redirectVideoPlaylist ? 'active' : ''}`}>
|
||||
{redirectVideoPlaylist ? 'Active' : 'Deactive'}
|
||||
</a>
|
||||
</div>
|
||||
<div class="toggle-option">
|
||||
<span>Viewing a channel</span>
|
||||
<a onClick={() => setExtensionSetting('redirectChannel', !redirectChannel)} className={`button ${redirectChannel ? 'active' : ''}`}>
|
||||
{redirectChannel ? 'Active' : 'Deactive'}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section>
|
||||
<a onClick={() => loads(lbryUrlCache.clearAll().then(() => dialogManager.alert("Cleared Cache!")))} className={`button active`}>
|
||||
Clear Resolver Cache
|
||||
</a>
|
||||
<label>Show redirect button on:</label>
|
||||
<div className='options'>
|
||||
<div className="toggle-option">
|
||||
<span>Video Page</span>
|
||||
<a onClick={() => setExtensionSetting('buttonVideoSub', !buttonVideoSub)} className={`button ${buttonVideoSub ? 'active' : ''}`}>
|
||||
{buttonVideoSub ? 'Active' : 'Deactive'}
|
||||
</a>
|
||||
</div>
|
||||
<div className="toggle-option">
|
||||
<span>Channel Page</span>
|
||||
<a onClick={() => setExtensionSetting('buttonChannelSub', !buttonChannelSub)} className={`button ${buttonChannelSub ? 'active' : ''}`}>
|
||||
{buttonChannelSub ? 'Active' : 'Deactive'}
|
||||
</a>
|
||||
</div>
|
||||
<div className="toggle-option">
|
||||
<span>Video Player</span>
|
||||
<a onClick={() => setExtensionSetting('buttonVideoPlayer', !buttonVideoPlayer)} className={`button ${buttonVideoPlayer ? 'active' : ''}`}>
|
||||
{buttonVideoPlayer ? 'Active' : 'Deactive'}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section>
|
||||
<label>Tools</label>
|
||||
<a target='_blank' href='/pages/YTtoLBRY/index.html' className={`filled`}>
|
||||
Subscription Converter
|
||||
</a>
|
||||
<a onClick={() => loads(lbryUrlCache.clearAll().then(() => dialogManager.alert("Cleared Cache!")))} className={`button active`}>
|
||||
Clear Resolver Cache
|
||||
</a>
|
||||
</section>
|
||||
</main>
|
||||
}
|
||||
|
|
|
@ -1,3 +1,10 @@
|
|||
#popup {
|
||||
width: 40em;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
header {
|
||||
display: grid;
|
||||
gap: .5em;
|
||||
|
@ -5,11 +12,13 @@ header {
|
|||
position: sticky;
|
||||
top: 0;
|
||||
background: rgba(19, 19, 19, 0.5);
|
||||
justify-items: center;
|
||||
justify-content: center;
|
||||
grid-auto-flow: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#logo {
|
||||
width: 5em;
|
||||
width: 3em;
|
||||
}
|
||||
|
||||
main {
|
||||
|
@ -25,7 +34,7 @@ section {
|
|||
gap: .75em;
|
||||
}
|
||||
|
||||
section>label {
|
||||
label {
|
||||
font-size: 1.75em;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
|
@ -41,13 +50,6 @@ section>* {
|
|||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
#popup {
|
||||
width: 35em;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.center {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
|
@ -58,4 +60,9 @@ section>* {
|
|||
justify-items: start;
|
||||
align-items: center;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.toggle-option {
|
||||
display: grid;
|
||||
gap: .5em;
|
||||
}
|
|
@ -11,8 +11,8 @@ chrome.runtime.onMessage.addListener(({ method, data }, sender, sendResponse) =>
|
|||
switch (method) {
|
||||
case 'openTab':
|
||||
{
|
||||
const { href, active }: { href: string, active: boolean } = JSON.parse(data)
|
||||
chrome.tabs.create({ url: href, active })
|
||||
const { href }: { href: string } = JSON.parse(data)
|
||||
chrome.tabs.create({ url: href, active: sender.tab?.active, index: sender.tab ? sender.tab.index + 1 : undefined })
|
||||
}
|
||||
break
|
||||
case 'resolveUrl':
|
||||
|
@ -23,7 +23,7 @@ chrome.runtime.onMessage.addListener(({ method, data }, sender, sendResponse) =>
|
|||
console.log('lbrypathname request', params, await promise)
|
||||
resolve(await promise)
|
||||
} catch (error) {
|
||||
sendResponse(`error:${(error as any).toString()}`)
|
||||
sendResponse(`error: ${(error as any).toString()}`)
|
||||
console.error(error)
|
||||
}
|
||||
finally {
|
||||
|
|
|
@ -17,6 +17,7 @@ import { getExtensionSettingsAsync, getSourcePlatfromSettingsFromHostname, getTa
|
|||
platform: SourcePlatform
|
||||
id: string
|
||||
type: ResolveUrlTypes
|
||||
url: URL
|
||||
time: number | null
|
||||
}
|
||||
|
||||
|
@ -26,7 +27,6 @@ import { getExtensionSettingsAsync, getSourcePlatfromSettingsFromHostname, getTa
|
|||
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 (settings.redirect) updateButton(null)
|
||||
})
|
||||
|
||||
const buttonMountPoint = document.createElement('div')
|
||||
|
@ -112,7 +112,7 @@ import { getExtensionSettingsAsync, getSourcePlatfromSettingsFromHostname, getTa
|
|||
</div>
|
||||
}
|
||||
|
||||
function updateButton(params: { source: Source, target: Target } | null): void {
|
||||
function updateButtons(params: { source: Source, target: Target } | null): void {
|
||||
if (!params) {
|
||||
render(<WatchOnLbryButton />, buttonMountPoint)
|
||||
render(<WatchOnLbryPlayerButton />, playerButtonMountPoint)
|
||||
|
@ -120,7 +120,7 @@ import { getExtensionSettingsAsync, getSourcePlatfromSettingsFromHostname, getTa
|
|||
}
|
||||
|
||||
{
|
||||
const mountPlayerButtonBefore = settings.videoPlayerButton ?
|
||||
const mountPlayerButtonBefore = settings.buttonVideoPlayer ?
|
||||
document.querySelector(params.source.platform.htmlQueries.mountPoints.mountPlayerButtonBefore) :
|
||||
null
|
||||
if (!mountPlayerButtonBefore) render(<WatchOnLbryPlayerButton />, playerButtonMountPoint)
|
||||
|
@ -134,7 +134,7 @@ import { getExtensionSettingsAsync, getSourcePlatfromSettingsFromHostname, getTa
|
|||
}
|
||||
|
||||
{
|
||||
const mountButtonBefore = settings[(`${params.source.type}SubButton`) as 'videoSubButton' | 'channelSubButton'] ?
|
||||
const mountButtonBefore = settings[(`button${params.source.type[0].toUpperCase() + params.source.type.substring(1)}Sub`) as 'buttonVideoSub' | 'buttonChannelSub'] ?
|
||||
document.querySelector(params.source.platform.htmlQueries.mountPoints.mountButtonBefore[params.source.type]) :
|
||||
null
|
||||
if (!mountButtonBefore) render(<WatchOnLbryButton />, buttonMountPoint)
|
||||
|
@ -163,7 +163,8 @@ import { getExtensionSettingsAsync, getSourcePlatfromSettingsFromHostname, getTa
|
|||
id: url.searchParams.get('v')!,
|
||||
platform,
|
||||
time: url.searchParams.has('t') ? parseYouTubeURLTimeString(url.searchParams.get('t')!) : null,
|
||||
type: 'video'
|
||||
type: 'video',
|
||||
url
|
||||
}
|
||||
}
|
||||
else if (url.pathname.startsWith('/channel/')) {
|
||||
|
@ -171,7 +172,8 @@ import { getExtensionSettingsAsync, getSourcePlatfromSettingsFromHostname, getTa
|
|||
id: url.pathname.substring("/channel/".length),
|
||||
platform,
|
||||
time: null,
|
||||
type: 'channel'
|
||||
type: 'channel',
|
||||
url
|
||||
}
|
||||
}
|
||||
else if (url.pathname.startsWith('/c/') || url.pathname.startsWith('/user/')) {
|
||||
|
@ -187,7 +189,8 @@ import { getExtensionSettingsAsync, getSourcePlatfromSettingsFromHostname, getTa
|
|||
id,
|
||||
platform,
|
||||
time: null,
|
||||
type: 'channel'
|
||||
type: 'channel',
|
||||
url
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -226,14 +229,53 @@ import { getExtensionSettingsAsync, getSourcePlatfromSettingsFromHostname, getTa
|
|||
const response = await new Promise<string | null | 'error'>((resolve) => chrome.runtime.sendMessage({ method: 'resolveUrl', data: JSON.stringify(params) }, resolve))
|
||||
if (response?.startsWith('error:')) {
|
||||
console.error("Background error on:", params)
|
||||
throw new Error(`Background error.${response ?? ''}`)
|
||||
throw new Error(`Background error. ${response ?? ''}`)
|
||||
}
|
||||
console.log(response)
|
||||
return response ? JSON.parse(response) : null
|
||||
}
|
||||
|
||||
// Request new tab
|
||||
async function openNewTab(url: URL, active: boolean) {
|
||||
chrome.runtime.sendMessage({ method: 'openTab', data: JSON.stringify({ href: url.href, active }) })
|
||||
async function openNewTab(url: URL) {
|
||||
chrome.runtime.sendMessage({ method: 'openTab', data: JSON.stringify({ href: url.href }) })
|
||||
}
|
||||
|
||||
function findTargetFromSourcePage(source: Source): Target | null {
|
||||
const linksContainer =
|
||||
source.type === 'video' ?
|
||||
document.querySelector(source.platform.htmlQueries.videoDescription) :
|
||||
source.platform.htmlQueries.channelLinks ? document.querySelector(source.platform.htmlQueries.channelLinks) : null
|
||||
|
||||
if (linksContainer) {
|
||||
const anchors = Array.from(linksContainer.querySelectorAll<HTMLAnchorElement>('a'))
|
||||
|
||||
for (const anchor of anchors) {
|
||||
if (!anchor.href) continue
|
||||
const url = new URL(anchor.href)
|
||||
let lbryURL: URL | null = null
|
||||
|
||||
// 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) {
|
||||
return {
|
||||
lbryPathname: lbryURL.pathname.substring(1),
|
||||
time: null,
|
||||
type: lbryURL.pathname.substring(1).includes('/') ? 'video' : 'channel',
|
||||
platform: targetPlatformSettings[settings.targetPlatform]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
function getLbryUrlByTarget(target: Target) {
|
||||
|
@ -243,98 +285,72 @@ import { getExtensionSettingsAsync, getSourcePlatfromSettingsFromHostname, getTa
|
|||
return url
|
||||
}
|
||||
|
||||
let urlHrefCache: string | null = null
|
||||
while (true) {
|
||||
|
||||
// Master Loop
|
||||
for (
|
||||
let url = new URL(location.href),
|
||||
urlHrefCache: string | null = null;
|
||||
;
|
||||
urlHrefCache = url.href,
|
||||
url = new URL(location.href)
|
||||
) {
|
||||
await sleep(500)
|
||||
const url: URL = new URL(location.href)
|
||||
|
||||
await (async () => {
|
||||
try {
|
||||
const source = await getSourceByUrl(new URL(location.href))
|
||||
if (!source) return
|
||||
|
||||
try {
|
||||
if (settings.redirect) {
|
||||
const target = (await getTargetsBySources(source))[source.id]
|
||||
if (!target) return
|
||||
console.log(url.href, urlHrefCache)
|
||||
if (url.href === urlHrefCache) return
|
||||
|
||||
const lbryURL = getLbryUrlByTarget(target)
|
||||
|
||||
if (source.type === 'video') {
|
||||
// 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()
|
||||
})
|
||||
}
|
||||
|
||||
console.log('open', lbryURL.href)
|
||||
openNewTab(lbryURL, document.hasFocus())
|
||||
|
||||
if (window.history.length === 1) window.close()
|
||||
else window.history.back()
|
||||
}
|
||||
else {
|
||||
if (urlHrefCache !== url.href) updateButton(null)
|
||||
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 linksContainer =
|
||||
source.type === 'video' ?
|
||||
document.querySelector(source.platform.htmlQueries.videoDescription) :
|
||||
source.platform.htmlQueries.channelLinks ? document.querySelector(source.platform.htmlQueries.channelLinks) : null
|
||||
|
||||
if (linksContainer) {
|
||||
const anchors = Array.from(linksContainer.querySelectorAll<HTMLAnchorElement>('a'))
|
||||
|
||||
for (const anchor of anchors) {
|
||||
if (!anchor.href) continue
|
||||
const url = new URL(anchor.href)
|
||||
let lbryURL: URL | null = null
|
||||
|
||||
// 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: lbryURL.pathname.substring(1).includes('/') ? 'video' : 'channel',
|
||||
platform: targetPlatformSettings[settings.targetPlatform]
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!target) updateButton(null)
|
||||
else {
|
||||
// If target is a video target add timestampt to it
|
||||
if (target.type === 'video') {
|
||||
const videoElement = document.querySelector<HTMLVideoElement>(source.platform.htmlQueries.videoPlayer)
|
||||
if (videoElement) target.time = videoElement.currentTime > 3 && videoElement.currentTime < videoElement.duration - 1 ? videoElement.currentTime : null
|
||||
}
|
||||
|
||||
updateButton({ target, source })
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
if (!source) {
|
||||
updateButtons(null)
|
||||
continue
|
||||
}
|
||||
const target = (await getTargetsBySources(source))[source.id] ?? findTargetFromSourcePage(source)
|
||||
if (!target) {
|
||||
updateButtons(null)
|
||||
continue
|
||||
}
|
||||
})()
|
||||
|
||||
urlHrefCache = url.href
|
||||
// Update Buttons
|
||||
if (urlHrefCache !== url.href) updateButtons(null)
|
||||
// If target is a video target add timestampt to it
|
||||
if (target.type === 'video') {
|
||||
const videoElement = document.querySelector<HTMLVideoElement>(source.platform.htmlQueries.videoPlayer)
|
||||
if (videoElement) target.time = videoElement.currentTime > 3 && videoElement.currentTime < videoElement.duration - 1 ? videoElement.currentTime : null
|
||||
}
|
||||
updateButtons({ target, source })
|
||||
|
||||
// Redirect
|
||||
if (
|
||||
source.type === target.type &&
|
||||
(
|
||||
(
|
||||
settings.redirectVideo &&
|
||||
source.type === 'video' && !source.url.searchParams.has('list')
|
||||
) ||
|
||||
(
|
||||
settings.redirectVideoPlaylist &&
|
||||
source.type === 'video' && source.url.searchParams.has('list')
|
||||
) ||
|
||||
(
|
||||
settings.redirectChannel &&
|
||||
source.type === 'channel'
|
||||
)
|
||||
)
|
||||
) {
|
||||
if (url.href === urlHrefCache) continue
|
||||
|
||||
const lbryURL = getLbryUrlByTarget(target)
|
||||
|
||||
if (source.type === 'video') {
|
||||
findVideoElementAwait(source).then((videoElement) => videoElement.pause())
|
||||
}
|
||||
|
||||
openNewTab(lbryURL)
|
||||
if (window.history.length === 1)
|
||||
window.close()
|
||||
else
|
||||
window.history.back()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
})()
|
|
@ -21,13 +21,13 @@ async function initSettings() {
|
|||
if (!Object.keys(targetPlatformSettings).includes(settings.targetPlatform)) setExtensionSetting('targetPlatform', DEFAULT_SETTINGS.targetPlatform)
|
||||
if (!Object.keys(ytUrlResolversSettings).includes(settings.urlResolver)) setExtensionSetting('urlResolver', DEFAULT_SETTINGS.urlResolver)
|
||||
|
||||
chromeAction.setBadgeText({ text: settings.redirect ? 'ON' : 'OFF' })
|
||||
// chromeAction.setBadgeText({ text: settings.redirect ? 'ON' : 'OFF' })
|
||||
}
|
||||
|
||||
chrome.storage.onChanged.addListener((changes, areaName) => {
|
||||
/* chrome.storage.onChanged.addListener((changes, areaName) => {
|
||||
if (areaName !== 'local' || !changes.redirect) return
|
||||
chromeAction.setBadgeText({ text: changes.redirect.newValue ? 'ON' : 'OFF' })
|
||||
})
|
||||
}) */
|
||||
|
||||
chrome.runtime.onStartup.addListener(initSettings)
|
||||
chrome.runtime.onInstalled.addListener(initSettings)
|
|
@ -2,22 +2,26 @@ import type { JSX } from "preact"
|
|||
import { useEffect, useReducer } from "preact/hooks"
|
||||
import type { ResolveUrlTypes } from "../modules/yt/urlResolve"
|
||||
|
||||
export interface ExtensionSettings {
|
||||
redirect: boolean
|
||||
export interface ExtensionSettings extends Record<string, string | number | boolean | null | undefined> {
|
||||
targetPlatform: TargetPlatformName
|
||||
urlResolver: YTUrlResolverName,
|
||||
videoSubButton: boolean
|
||||
videoPlayerButton: boolean
|
||||
channelSubButton: boolean
|
||||
redirectVideo: boolean,
|
||||
redirectChannel: boolean,
|
||||
redirectVideoPlaylist: boolean,
|
||||
buttonVideoSub: boolean
|
||||
buttonVideoPlayer: boolean
|
||||
buttonChannelSub: boolean
|
||||
}
|
||||
|
||||
export const DEFAULT_SETTINGS: ExtensionSettings = {
|
||||
redirect: true,
|
||||
targetPlatform: 'odysee',
|
||||
urlResolver: 'odyseeApi',
|
||||
videoSubButton: true,
|
||||
videoPlayerButton: true,
|
||||
channelSubButton: true,
|
||||
redirectVideo: false,
|
||||
redirectChannel: false,
|
||||
redirectVideoPlaylist: false,
|
||||
buttonVideoSub: true,
|
||||
buttonVideoPlayer: true,
|
||||
buttonChannelSub: true,
|
||||
}
|
||||
|
||||
export function getExtensionSettingsAsync(): Promise<ExtensionSettings> {
|
||||
|
|
Loading…
Add table
Reference in a new issue