Watch-on-LBRY/src/scripts/ytContent.tsx
2020-10-28 05:21:49 -04:00

118 lines
4 KiB
TypeScript

import { h, render } from 'preact';
import { parseProtocolUrl } from '../common/lbry-url';
import { getSettingsAsync, LbrySettings, redirectDomains } from '../common/settings';
import { YTDescriptor, ytService } from '../common/yt';
interface UpdaterOptions {
/** invoked if a redirect should be performed */
onRedirect?(ctx: UpdateContext): void
/** invoked if a URL is found */
onURL?(ctx: UpdateContext): void
}
interface UpdateContext {
descriptor: YTDescriptor
url: string
enabled: boolean
redirect: LbrySettings['redirect']
}
function pauseVideo() { document.querySelectorAll<HTMLVideoElement>('video').forEach(v => v.pause()); }
function openApp(url: string) {
pauseVideo();
location.assign(url);
}
async function resolveYT(descriptor: YTDescriptor) {
const lbryProtocolUrl: string | null = await ytService.resolveById(descriptor).then(a => a[0]);
const segments = parseProtocolUrl(lbryProtocolUrl || '', { encode: true });
if (segments.length === 0) return;
return segments.join('/');
}
/** Compute the URL and determine whether or not a redirect should be performed. Delegates the redirect to callbacks. */
async function handleURLChange(url: URL | Location, { onRedirect, onURL }: UpdaterOptions): Promise<void> {
const { enabled, redirect } = await getSettingsAsync('enabled', 'redirect');
const urlPrefix = redirectDomains[redirect].prefix;
const descriptor = ytService.getId(url.href);
if (!descriptor) return; // couldn't get the ID, so we're done
const res = await resolveYT(descriptor);
if (!res) return; // couldn't find it on lbry, so we're done
const ctx = { descriptor, url: urlPrefix + res, enabled, redirect };
if (onURL) onURL(ctx);
if (enabled && onRedirect) onRedirect(ctx);
}
/** Returns a mount point for the button */
async function findMountPoint(): Promise<HTMLDivElement | void> {
const sleep = (t: number) => new Promise(resolve => setTimeout(resolve, t));
let ownerBar = document.querySelector('ytd-video-owner-renderer');
for (let i = 0; !ownerBar && i < 50; i++) {
await sleep(200);
ownerBar = document.querySelector('ytd-video-owner-renderer');
}
if (!ownerBar) return;
const div = document.createElement('div');
div.style.display = 'flex';
ownerBar.insertAdjacentElement('afterend', div);
return div;
}
function WatchOnLbryButton({ url }: { url?: string }) {
if (!url) return null;
return <div style={{ display: 'flex', justifyContent: 'center', flexDirection: 'column' }}>
<a href={url} onClick={pauseVideo} role='button'
children={<div>
<img src={chrome.runtime.getURL('icons/lbry-logo.svg')} height={10} width={14}
style={{ marginRight: 12, transform: 'scale(1.75)' }} />
Watch on LBRY
</div>}
style={{
borderRadius: '2px',
backgroundColor: '#075656',
border: '0',
color: 'whitesmoke',
padding: '10px 16px',
marginRight: '5px',
fontSize: '14px',
textDecoration: 'none',
}} />
</div>
}
const mountPointPromise = findMountPoint();
const handle = (url: URL | Location) => handleURLChange(url, {
async onURL({ descriptor: { type }, url }) {
const mountPoint = await mountPointPromise;
if (type !== 'video' || !mountPoint) return;
render(<WatchOnLbryButton url={url} />, mountPoint)
},
onRedirect({ redirect, url }) {
if (redirect === 'app') return openApp(url);
location.replace(url);
},
});
// handle the location on load of the page
handle(location);
/*
* 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
*/
chrome.runtime.onMessage.addListener(async (req: { url: string }) => {
mountPointPromise.then(mountPoint => mountPoint && render(<WatchOnLbryButton />, mountPoint))
if (!req.url) return;
handle(new URL(req.url));
});
chrome.storage.onChanged.addListener((changes, areaName) => {
if (areaName !== 'local' || !changes.redirect) return;
handle(new URL(location.href))
});