Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] Yewtube support added #75

Merged
merged 8 commits into from
Dec 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/common/components/ButtonRadio.sass
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
.ButtonRadio
display: flex
justify-content: center
flex-wrap: wrap
gap: .25em

.radio-button
@extend .button
margin: 6px

.radio-button.checked
@extend .button.active
Expand Down
81 changes: 64 additions & 17 deletions src/common/settings.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,76 @@
export type PlatformName = 'madiator.com' | 'odysee' | 'app'
export interface ExtensionSettings {
redirect: boolean
targetPlatform: TargetPlatformName
}

export const DEFAULT_SETTINGS: ExtensionSettings = { redirect: true, targetPlatform: 'odysee' };

export function getExtensionSettingsAsync<K extends Array<keyof ExtensionSettings>>(...keys: K): Promise<Pick<ExtensionSettings, K[number]>> {
return new Promise(resolve => chrome.storage.local.get(keys, o => resolve(o as any)));
}


export interface PlatformSettings
{

export type TargetPlatformName = 'madiator.com' | 'odysee' | 'app'
export interface TargetPlatformSettings {
domainPrefix: string
display: string
displayName: string
theme: string
}

export const platformSettings: Record<PlatformName, PlatformSettings> = {
'madiator.com': { domainPrefix: 'https://madiator.com/', display: 'Madiator.com', theme: '#075656' },
odysee: { domainPrefix: 'https://odysee.com/', display: 'Odysee', theme: '#1e013b' },
app: { domainPrefix: 'lbry://', display: 'App', theme: '#075656' },
export const TargetPlatformSettings: Record<TargetPlatformName, TargetPlatformSettings> = {
'madiator.com': {
domainPrefix: 'https://madiator.com/',
displayName: 'Madiator.com',
theme: '#075656'
},
odysee: {
domainPrefix: 'https://odysee.com/',
displayName: 'Odysee',
theme: '#1e013b'
},
app: {
domainPrefix: 'lbry://',
displayName: 'LBRY App',
theme: '#075656'
},
};

export const getPlatfromSettingsEntiries = () => {
return Object.entries(platformSettings) as any as [Extract<keyof typeof platformSettings, string>, PlatformSettings][]
export const getTargetPlatfromSettingsEntiries = () => {
return Object.entries(TargetPlatformSettings) as any as [Extract<keyof typeof TargetPlatformSettings, string>, TargetPlatformSettings][]
}

export interface LbrySettings {
enabled: boolean
platform: PlatformName
}

export const DEFAULT_SETTINGS: LbrySettings = { enabled: true, platform: 'odysee' };

export function getSettingsAsync<K extends Array<keyof LbrySettings>>(...keys: K): Promise<Pick<LbrySettings, K[number]>> {
return new Promise(resolve => chrome.storage.local.get(keys, o => resolve(o as any)));
export type SourcePlatfromName = 'youtube.com' | 'yewtu.be'
export interface SourcePlatfromSettings {
hostnames: string[]
htmlQueries: {
mountButtonBefore: string,
videoPlayer: string
}
}

export const SourcePlatfromSettings: Record<SourcePlatfromName, SourcePlatfromSettings> = {
"yewtu.be": {
hostnames: ['yewtu.be'],
htmlQueries: {
mountButtonBefore: '#watch-on-youtube',
videoPlayer: '#player-container video'
}
},
"youtube.com": {
hostnames: ['www.youtube.com'],
htmlQueries: {
mountButtonBefore: 'ytd-video-owner-renderer~#subscribe-button',
videoPlayer: '#ytd-player video'
}
}
}

export function getSourcePlatfromSettingsFromHostname(hostname: string) {
const values = Object.values(SourcePlatfromSettings)
for (const settings of values)
if (settings.hostnames.includes(hostname)) return settings
return null
}
13 changes: 6 additions & 7 deletions src/common/style.sass
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,29 @@ $btn-color: #075656 !default
$btn-select: teal !default

body
width: 400px
width: 30em
text-align: center
background-color: $background-color
color: $text-color
font-family: sans-serif
padding: 1em

.container
display: block
text-align: center
margin: 0 32px
margin-bottom: 15px

.button
border-radius: 5px
border-radius: .5em
background-color: $btn-color
border: 4px solid $btn-color
border: .2em solid $btn-color
color: $text-color
font-size: 0.8rem
font-weight: 400
padding: 4px 15px
padding: .5em
text-align: center

&.active
border: 4px solid $btn-select
border-color: $btn-select

&:focus
outline: none
6 changes: 3 additions & 3 deletions src/common/yt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ interface YtResolverResponse {
};
}

interface YtSubscription {
interface YtExportedJsonSubscription {
id: string;
etag: string;
title: string;
Expand Down Expand Up @@ -72,7 +72,7 @@ export const ytService = {
* @returns the channel IDs
*/
readJson(jsonContents: string): string[] {
const subscriptions: YtSubscription[] = JSON.parse(jsonContents);
const subscriptions: YtExportedJsonSubscription[] = JSON.parse(jsonContents);
jsonContents = ''
return subscriptions.map(sub => sub.snippet.resourceId.channelId);
},
Expand All @@ -86,7 +86,7 @@ export const ytService = {
readCsv(csvContent: string): string[] {
const rows = csvContent.split('\n')
csvContent = ''
return rows.map((row) => row.substr(0, row.indexOf(',')))
return rows.map((row) => row.substring(0, row.indexOf(',')))
},

/**
Expand Down
6 changes: 3 additions & 3 deletions src/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
"version": "1.7.5",
"permissions": [
"https://www.youtube.com/",
"https://invidio.us/channel/*",
"https://invidio.us/watch?v=*",
"https://yewtu.be/",
"https://api.odysee.com/*",
"https://lbry.tv/*",
"https://odysee.com/*",
Expand All @@ -15,7 +14,8 @@
"content_scripts": [
{
"matches": [
"https://www.youtube.com/*"
"https://www.youtube.com/*",
"https://yewtu.be/*"
],
"js": [
"scripts/ytContent.js"
Expand Down
11 changes: 10 additions & 1 deletion src/popup/popup.sass
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
.radio-label
font-size: 1.1rem
margin: 15px auto
display: block

.container
display: grid
grid-auto-flow: row
gap: 1.5em

.container > section
display: grid
grid-auto-flow: row
gap: 1em
38 changes: 21 additions & 17 deletions src/popup/popup.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,36 @@
import { h, render } from 'preact'
import ButtonRadio, { SelectionOption } from '../common/components/ButtonRadio'
import { getPlatfromSettingsEntiries, LbrySettings, PlatformName } from '../common/settings'
import { getTargetPlatfromSettingsEntiries, ExtensionSettings, TargetPlatformName } from '../common/settings'
import { useLbrySettings } from '../common/useSettings'
import './popup.sass'



/** Utilty to set a setting in the browser */
const setSetting = <K extends keyof LbrySettings>(setting: K, value: LbrySettings[K]) => chrome.storage.local.set({ [setting]: value });
const setSetting = <K extends keyof ExtensionSettings>(setting: K, value: ExtensionSettings[K]) => chrome.storage.local.set({ [setting]: value });

/** Gets all the options for redirect destinations as selection options */
const platformOptions: SelectionOption[] = getPlatfromSettingsEntiries()
.map(([value, { display }]) => ({ value, display }));
const platformOptions: SelectionOption[] = getTargetPlatfromSettingsEntiries()
.map(([value, { displayName: display }]) => ({ value, display }));

function WatchOnLbryPopup() {
const { enabled, platform } = useLbrySettings();
const { redirect, targetPlatform } = useLbrySettings();

return <div className='container'>
<label className='radio-label'>Enable Redirection:</label>
<ButtonRadio value={enabled ? 'YES' : 'NO'} options={['YES', 'NO']}
onChange={enabled => setSetting('enabled', enabled.toLowerCase() === 'yes')} />
<label className='radio-label'>Where would you like to redirect?</label>
<ButtonRadio value={platform} options={platformOptions}
onChange={(platform: PlatformName) => setSetting('platform', platform)} />
<label className='radio-label'>Other useful tools:</label>
<a href='/tools/YTtoLBRY.html' target='_blank'>
<button type='button' className='btn1 button is-primary'>Subscriptions Converter</button>
</a>
<section>
<label className='radio-label'>Enable Redirection:</label>
<ButtonRadio value={redirect ? 'YES' : 'NO'} options={['YES', 'NO']}
onChange={redirect => setSetting('redirect', redirect.toLowerCase() === 'yes')} />
</section>
<section>
<label className='radio-label'>Where would you like to redirect?</label>
<ButtonRadio value={targetPlatform} options={platformOptions}
onChange={(platform: TargetPlatformName) => setSetting('targetPlatform', platform)} />
</section>
<section>
<label className='radio-label'>Other useful tools:</label>
<a href='/tools/YTtoLBRY.html' target='_blank'>
<button type='button' className='btn1 button is-primary'>Subscriptions Converter</button>
</a>
</section>
</div>;
}

Expand Down
12 changes: 6 additions & 6 deletions src/scripts/storageSetup.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { DEFAULT_SETTINGS, LbrySettings, getSettingsAsync } from '../common/settings';
import { DEFAULT_SETTINGS, ExtensionSettings, getExtensionSettingsAsync } from '../common/settings';

/** Reset settings to default value and update the browser badge text */
async function initSettings() {
const settings = await getSettingsAsync(...Object.keys(DEFAULT_SETTINGS) as Array<keyof LbrySettings>);
const settings = await getExtensionSettingsAsync(...Object.keys(DEFAULT_SETTINGS) as Array<keyof ExtensionSettings>);

// get all the values that aren't set and use them as a change set
const invalidEntries = (Object.entries(DEFAULT_SETTINGS) as Array<[keyof LbrySettings, LbrySettings[keyof LbrySettings]]>)
const invalidEntries = (Object.entries(DEFAULT_SETTINGS) as Array<[keyof ExtensionSettings, ExtensionSettings[keyof ExtensionSettings]]>)
.filter(([k]) => settings[k] === null || settings[k] === undefined);

// fix our local var and set it in storage for later
Expand All @@ -15,12 +15,12 @@ async function initSettings() {
chrome.storage.local.set(changeSet);
}

chrome.browserAction.setBadgeText({ text: settings.enabled ? 'ON' : 'OFF' });
chrome.browserAction.setBadgeText({ text: settings.redirect ? 'ON' : 'OFF' });
}

chrome.storage.onChanged.addListener((changes, areaName) => {
if (areaName !== 'local' || !changes.enabled) return;
chrome.browserAction.setBadgeText({ text: changes.enabled.newValue ? 'ON' : 'OFF' });
if (areaName !== 'local' || !changes.redirect) return;
chrome.browserAction.setBadgeText({ text: changes.redirect.newValue ? 'ON' : 'OFF' });
});


Expand Down
39 changes: 21 additions & 18 deletions src/scripts/tabOnUpdated.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { appRedirectUrl, parseProtocolUrl } from '../common/lbry-url'
import { getSettingsAsync, PlatformName } from '../common/settings'
import { getExtensionSettingsAsync, getSourcePlatfromSettingsFromHostname, TargetPlatformName } from '../common/settings'
import { YTDescriptor, ytService } from '../common/yt'
export interface UpdateContext {
descriptor: YTDescriptor
/** LBRY URL fragment */
pathname: string
enabled: boolean
platform: PlatformName
lbryPathname: string
redirect: boolean
targetPlatform: TargetPlatformName
}

async function resolveYT(descriptor: YTDescriptor) {
Expand All @@ -16,26 +16,31 @@ async function resolveYT(descriptor: YTDescriptor) {
return segments.join('/');
}

const pathnameCache: Record<string, string | undefined> = {};
const lbryPathnameCache: Record<string, string | undefined> = {};

async function ctxFromURL(url: string): Promise<UpdateContext | void> {
if (!url || !(url.startsWith('https://www.youtube.com/watch?v=') || url.startsWith('https://www.youtube.com/channel/'))) return;
url = new URL(url).href;
const { enabled, platform } = await getSettingsAsync('enabled', 'platform');
const descriptor = ytService.getId(url);
async function ctxFromURL(href: string): Promise<UpdateContext | void> {
if (!href) return;

const url = new URL(href);
if (!getSourcePlatfromSettingsFromHostname(url.hostname)) return
if (url.pathname.startsWith('/watch?')) return
if (url.pathname.startsWith('/channel?')) return

const { redirect, targetPlatform } = await getExtensionSettingsAsync('redirect', 'targetPlatform');
const descriptor = ytService.getId(href);
if (!descriptor) return; // couldn't get the ID, so we're done

const res = url in pathnameCache ? pathnameCache[url] : await resolveYT(descriptor);
pathnameCache[url] = res;
const res = href in lbryPathnameCache ? lbryPathnameCache[href] : await resolveYT(descriptor);
lbryPathnameCache[href] = res;
if (!res) return; // couldn't find it on lbry, so we're done

return { descriptor, pathname: res, enabled, platform };
return { descriptor, lbryPathname: res, redirect, targetPlatform };
}

// handles lbry.tv -> lbry app redirect
chrome.tabs.onUpdated.addListener(async (tabId, changeInfo, { url: tabUrl }) => {
const { enabled, platform } = await getSettingsAsync('enabled', 'platform');
if (!enabled || platform !== 'app' || !changeInfo.url || !tabUrl?.startsWith('https://odysee.com/')) return;
const { redirect, targetPlatform } = await getExtensionSettingsAsync('redirect', 'targetPlatform');
if (!redirect || targetPlatform !== 'app' || !changeInfo.url || !tabUrl?.startsWith('https://odysee.com/')) return;

const url = appRedirectUrl(tabUrl, { encode: true });
if (!url) return;
Expand All @@ -61,7 +66,5 @@ chrome.runtime.onMessage.addListener(({ url }: { url: string }, sender, sendResp

// relay youtube link changes to the content script
chrome.tabs.onUpdated.addListener((tabId, changeInfo, { url }) => {
if (!changeInfo.url || !url || !(url.startsWith('https://www.youtube.com/watch?v=') || url.startsWith('https://www.youtube.com/channel/'))) return;

ctxFromURL(url).then(ctx => chrome.tabs.sendMessage(tabId, ctx));
if (url) ctxFromURL(url).then(ctx => chrome.tabs.sendMessage(tabId, ctx));
});
Loading