🍣 major changes

- modes are removed
- added more options for redirect
- extension badge removed
This commit is contained in:
Shiba 2022-08-09 21:16:05 +00:00
parent 9ed962df96
commit 9c26d553f5
5 changed files with 234 additions and 196 deletions

View file

@ -12,9 +12,9 @@ const targetPlatforms = getTargetPlatfromSettingsEntiries()
const ytUrlResolverOptions = getYtUrlResolversSettingsEntiries() const ytUrlResolverOptions = getYtUrlResolversSettingsEntiries()
function WatchOnLbryPopup(params: { profile: Awaited<ReturnType<typeof getProfile>> | null }) { function WatchOnLbryPopup(params: { profile: Awaited<ReturnType<typeof getProfile>> | null }) {
const { redirect, targetPlatform, urlResolver, videoSubButton, channelSubButton, videoPlayerButton, privateKey, publicKey } = useExtensionSettings() const { targetPlatform, urlResolver, redirectChannel, redirectVideo, redirectVideoPlaylist, buttonVideoSub, buttonChannelSub, buttonVideoPlayer, privateKey, publicKey } = useExtensionSettings()
let [loading, updateLoading] = useState(() => false) let [loading, updateLoading] = useState(() => false)
let [route, updateRoute] = useState<string | null>(() => null) let [route, updateRoute] = useState<string>(() => '')
const dialogManager = createDialogManager() const dialogManager = createDialogManager()
const nickname = params.profile ? params.profile.nickname ?? 'No Nickname' : '...' const nickname = params.profile ? params.profile.nickname ?? 'No Nickname' : '...'
@ -35,29 +35,30 @@ function WatchOnLbryPopup(params: { profile: Awaited<ReturnType<typeof getProfil
return <div id='popup'> return <div id='popup'>
<Dialogs manager={dialogManager} /> <Dialogs manager={dialogManager} />
{ {
publicKey
? <header> <header>
{
publicKey &&
<section> <section>
<label>{nickname}</label> <label>{nickname}</label>
<p>{friendlyPublicKey(publicKey)}</p> <p>{friendlyPublicKey(publicKey)}</p>
<span><b>Score: {params.profile?.score ?? '...'}</b> - <a target='_blank' href="https://finder.madiator.com/leaderboard" class="filled">🔗Leaderboard</a></span> <span><b>Score: {params.profile?.score ?? '...'}</b> - <a target='_blank' href="https://finder.madiator.com/leaderboard" class="filled">🔗Leaderboard</a></span>
{urlResolver !== 'madiatorFinder' && <span class="error">You need to use Madiator Finder API for scoring to work</span>} {urlResolver !== 'madiatorFinder' && <span class="error">You need to use Madiator Finder API for scoring to work</span>}
</section> </section>
<section> }
{
route === 'profile' {
? <a onClick={() => updateRoute('')} className="filled"> Back</a> route !== ''
: <a className='filled' onClick={() => updateRoute('profile')} href="#profile">Profile Settings</a> ?
} <section>
</section> <a onClick={() => updateRoute('')} className="filled"> Back</a>
</header> </section>
: <header> :
{ <section>
route === 'profile' <a className='filled' onClick={() => updateRoute('profile')}>Profile Settings</a>
? <a onClick={() => updateRoute('')} className="filled"> Back</a> </section>
: <a className='filled' onClick={() => updateRoute('profile')} href="#profile">Profile Settings</a> }
} </header>
</header>
} }
{ {
route === 'profile' ? route === 'profile' ?
@ -138,80 +139,88 @@ function WatchOnLbryPopup(params: { profile: Awaited<ReturnType<typeof getProfil
</section> </section>
</main> </main>
: :
<main> route === 'advanced' ?
<section> <main>
<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
</a>
</div>
</section>
{
!redirect &&
<section> <section>
<label>Show button at:</label> <label>Which platform you would like to redirect to?</label>
<b className='filled'>Video</b>
<div className='options'> <div className='options'>
<div className="left"> {targetPlatforms.map(([name, value]) =>
<span>Subscribe Button:</span> <a onClick={() => setExtensionSetting('targetPlatform', name)} className={`button ${targetPlatform === name ? 'active' : ''}`}>
</div> {value.displayName}
<a onClick={() => setExtensionSetting('videoSubButton', !videoSubButton)} className={`button ${videoSubButton ? 'active' : ''}`}> </a>
{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> </div>
</section> </section>
} <section>
<section> <label>Which resolver API you want to use?</label>
<label>Which platform you would like to redirect?</label> <div className='options'>
<div className='options'> {ytUrlResolverOptions.map(([name, value]) =>
{targetPlatforms.map(([name, value]) => <a onClick={() => setExtensionSetting('urlResolver', name)} className={`button ${urlResolver === name ? 'active' : ''}`}>
<a onClick={() => setExtensionSetting('targetPlatform', name)} className={`button ${targetPlatform === name ? 'active' : ''}`}> {value.name}
{value.displayName} </a>
</a> )}
)} </div>
</div> <a onClick={() => loads(lbryUrlCache.clearAll().then(() => dialogManager.alert("Cleared Cache!")))} className={`button active`}>
</section> Clear Resolver Cache
<section> </a>
<label>Which resolver API you want to use?</label> </section>
<div className='options'> </main>
{ytUrlResolverOptions.map(([name, value]) => :
<a onClick={() => setExtensionSetting('urlResolver', name)} className={`button ${urlResolver === name ? 'active' : ''}`}> <main>
{value.name} <section>
</a> <label>Auto redirect when:</label>
)} <div className='options'>
</div> <div class="toggle-option">
<a onClick={() => loads(lbryUrlCache.clearAll().then(() => dialogManager.alert("Cleared Cache!")))} className={`button active`}> <span>Playing a video</span>
Clear Resolver Cache <a onClick={() => setExtensionSetting('redirectVideo', !redirectVideo)} className={`button ${redirectVideo ? 'active' : ''}`}>
</a> {redirectVideo ? 'Active' : 'Deactive'}
</section> </a>
<section> </div>
<label>Tools</label> <div class="toggle-option">
<a target='_blank' href='/pages/YTtoLBRY/index.html' className={`filled`}> <span>Playing a playlist</span>
Subscription Converter <a onClick={() => setExtensionSetting('redirectVideoPlaylist', !redirectVideoPlaylist)} className={`button ${redirectVideoPlaylist ? 'active' : ''}`}>
</a> {redirectVideoPlaylist ? 'Active' : 'Deactive'}
</section> </a>
</main> </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>
<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 className='filled' onClick={() => updateRoute('advanced')}>Advanced Settings</a>
</section>
</main>
} }
{loading && <div class="overlay"> {loading && <div class="overlay">
<span>Loading...</span> <span>Loading...</span>

View file

@ -54,4 +54,9 @@ section>* {
justify-items: start; justify-items: start;
align-items: center; align-items: center;
text-align: left; text-align: left;
}
.toggle-option {
display: grid;
gap: .5em;
} }

View file

@ -17,6 +17,7 @@ import { getExtensionSettingsAsync, getSourcePlatfromSettingsFromHostname, getTa
platform: SourcePlatform platform: SourcePlatform
id: string id: string
type: ResolveUrlTypes type: ResolveUrlTypes
url: URL
time: number | null time: number | null
} }
@ -26,7 +27,6 @@ import { getExtensionSettingsAsync, getSourcePlatfromSettingsFromHostname, getTa
chrome.storage.onChanged.addListener(async (changes, areaName) => { chrome.storage.onChanged.addListener(async (changes, areaName) => {
if (areaName !== 'local') return if (areaName !== 'local') return
Object.assign(settings, Object.fromEntries(Object.entries(changes).map(([key, change]) => [key, change.newValue]))) Object.assign(settings, Object.fromEntries(Object.entries(changes).map(([key, change]) => [key, change.newValue])))
if (settings.redirect) updateButton(null)
}) })
const buttonMountPoint = document.createElement('div') const buttonMountPoint = document.createElement('div')
@ -112,7 +112,7 @@ import { getExtensionSettingsAsync, getSourcePlatfromSettingsFromHostname, getTa
</div> </div>
} }
function updateButton(params: { source: Source, target: Target } | null): void { function updateButtons(params: { source: Source, target: Target } | null): void {
if (!params) { if (!params) {
render(<WatchOnLbryButton />, buttonMountPoint) render(<WatchOnLbryButton />, buttonMountPoint)
render(<WatchOnLbryPlayerButton />, playerButtonMountPoint) 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) : document.querySelector(params.source.platform.htmlQueries.mountPoints.mountPlayerButtonBefore) :
null null
if (!mountPlayerButtonBefore) render(<WatchOnLbryPlayerButton />, playerButtonMountPoint) 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]) : document.querySelector(params.source.platform.htmlQueries.mountPoints.mountButtonBefore[params.source.type]) :
null null
if (!mountButtonBefore) render(<WatchOnLbryButton />, buttonMountPoint) if (!mountButtonBefore) render(<WatchOnLbryButton />, buttonMountPoint)
@ -163,7 +163,8 @@ import { getExtensionSettingsAsync, getSourcePlatfromSettingsFromHostname, getTa
id: url.searchParams.get('v')!, id: url.searchParams.get('v')!,
platform, platform,
time: url.searchParams.has('t') ? parseYouTubeURLTimeString(url.searchParams.get('t')!) : null, time: url.searchParams.has('t') ? parseYouTubeURLTimeString(url.searchParams.get('t')!) : null,
type: 'video' type: 'video',
url
} }
} }
else if (url.pathname.startsWith('/channel/')) { else if (url.pathname.startsWith('/channel/')) {
@ -171,7 +172,8 @@ import { getExtensionSettingsAsync, getSourcePlatfromSettingsFromHostname, getTa
id: url.pathname.substring("/channel/".length), id: url.pathname.substring("/channel/".length),
platform, platform,
time: null, time: null,
type: 'channel' type: 'channel',
url
} }
} }
else if (url.pathname.startsWith('/c/') || url.pathname.startsWith('/user/')) { else if (url.pathname.startsWith('/c/') || url.pathname.startsWith('/user/')) {
@ -187,7 +189,8 @@ import { getExtensionSettingsAsync, getSourcePlatfromSettingsFromHostname, getTa
id, id,
platform, platform,
time: null, time: null,
type: 'channel' type: 'channel',
url
} }
} }
@ -233,9 +236,47 @@ import { getExtensionSettingsAsync, getSourcePlatfromSettingsFromHostname, getTa
} }
// Request new tab // Request new tab
async function openNewTab(url: URL, active: boolean) { async function openNewTab(url: URL) {
if (!open(url.href, '_blank')) if (!open(url.href, '_blank'))
chrome.runtime.sendMessage({ method: 'openTab', data: JSON.stringify({ href: url.href, active }) }) chrome.runtime.sendMessage({ method: 'openTab', data: JSON.stringify({ href: url.href, active: document.hasFocus() }) })
}
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) { function getLbryUrlByTarget(target: Target) {
@ -245,104 +286,83 @@ import { getExtensionSettingsAsync, getSourcePlatfromSettingsFromHostname, getTa
return url 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) await sleep(500)
const url: URL = new URL(location.href) try {
await (async () => {
const source = await getSourceByUrl(new URL(location.href)) const source = await getSourceByUrl(new URL(location.href))
if (!source) return if (!source) {
updateButtons(null)
continue
}
const target = (await getTargetsBySources(source))[source.id] ?? findTargetFromSourcePage(source)
if (!target) {
updateButtons(null)
continue
}
try { // Update Buttons
if (settings.redirect) { if (urlHrefCache !== url.href) updateButtons(null)
const target = (await getTargetsBySources(source))[source.id] // If target is a video target add timestampt to it
if (!target) return if (target.type === 'video') {
if (url.href === urlHrefCache) return 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 })
const lbryURL = getLbryUrlByTarget(target) // 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
if (source.type === 'video') { const lbryURL = getLbryUrlByTarget(target)
// 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 (source.type === 'video') {
if (document.hidden) await new Promise((resolve) => document.addEventListener('visibilitychange', resolve, { once: true })) // As soon as video play is ready and start playing, pause it.
// Replace is being used so browser doesnt start an empty window findVideoElementAwait(source).then((videoElement) => {
// Its not gonna be able to replace anyway, since its a LBRY Uri videoElement.addEventListener('play', () => videoElement.pause(), { once: true })
location.replace(lbryURL) videoElement.pause()
} })
else { }
openNewTab(lbryURL, document.hasFocus())
if (window.history.length === 1) window.close() if (target.platform === targetPlatformSettings.app) {
else window.history.back() if (document.hidden) await new Promise((resolve) => document.addEventListener('visibilitychange', resolve, { once: true }))
} // 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 { else {
if (urlHrefCache !== url.href) updateButton(null) openNewTab(lbryURL)
let target = (await getTargetsBySources(source))[source.id]
// There is no target found via API try to check Video Description for LBRY links. if (window.history.length === 1) window.close()
if (!target) { else window.history.back()
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)
} }
})() } catch (error) {
console.error(error)
urlHrefCache = url.href }
} }
})() })()

View file

@ -21,13 +21,13 @@ async function initSettings() {
if (!Object.keys(targetPlatformSettings).includes(settings.targetPlatform)) setExtensionSetting('targetPlatform', DEFAULT_SETTINGS.targetPlatform) if (!Object.keys(targetPlatformSettings).includes(settings.targetPlatform)) setExtensionSetting('targetPlatform', DEFAULT_SETTINGS.targetPlatform)
if (!Object.keys(ytUrlResolversSettings).includes(settings.urlResolver)) setExtensionSetting('urlResolver', DEFAULT_SETTINGS.urlResolver) 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 if (areaName !== 'local' || !changes.redirect) return
chromeAction.setBadgeText({ text: changes.redirect.newValue ? 'ON' : 'OFF' }) chromeAction.setBadgeText({ text: changes.redirect.newValue ? 'ON' : 'OFF' })
}) }) */
chrome.runtime.onStartup.addListener(initSettings) chrome.runtime.onStartup.addListener(initSettings)
chrome.runtime.onInstalled.addListener(initSettings) chrome.runtime.onInstalled.addListener(initSettings)

View file

@ -2,24 +2,28 @@ import type { JSX } from "preact"
import { useEffect, useReducer } from "preact/hooks" import { useEffect, useReducer } from "preact/hooks"
import type { ResolveUrlTypes } from "../modules/yt/urlResolve" import type { ResolveUrlTypes } from "../modules/yt/urlResolve"
export interface ExtensionSettings { export interface ExtensionSettings extends Record<string, string | number | boolean | null | undefined>{
redirect: boolean
targetPlatform: TargetPlatformName targetPlatform: TargetPlatformName
urlResolver: YTUrlResolverName, urlResolver: YTUrlResolverName,
videoSubButton: boolean redirectVideo: boolean,
videoPlayerButton: boolean redirectChannel: boolean,
channelSubButton: boolean redirectVideoPlaylist: boolean,
buttonVideoSub: boolean
buttonVideoPlayer: boolean
buttonChannelSub: boolean
publicKey: string | null, publicKey: string | null,
privateKey: string | null privateKey: string | null
} }
export const DEFAULT_SETTINGS: ExtensionSettings = { export const DEFAULT_SETTINGS: ExtensionSettings = {
redirect: true,
targetPlatform: 'odysee', targetPlatform: 'odysee',
urlResolver: 'odyseeApi', urlResolver: 'odyseeApi',
videoSubButton: true, redirectVideo: false,
videoPlayerButton: true, redirectChannel: false,
channelSubButton: true, redirectVideoPlaylist: false,
buttonVideoSub: true,
buttonVideoPlayer: true,
buttonChannelSub: true,
privateKey: null, privateKey: null,
publicKey: null publicKey: null
} }