Skip to content

Commit 3eb5b69

Browse files
committed
Start Invidious handling [#92]
Start by adding support for specifying instances
1 parent ec8d45c commit 3eb5b69

10 files changed

Lines changed: 178 additions & 7 deletions

File tree

rollup.config.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ const question = q => new Promise(resolve => readline.question(q, answer => reso
4040
* JS BUILD
4141
*/
4242
const jsreplace = (dev = !process.env.BUILD, v3 = false) => ({
43+
'__VERSION__': JSON.stringify(process.env.npm_package_version),
4344
'__YT_API_KEY__': JSON.stringify(process.env.YT_API_KEY),
4445
'__DEV__': JSON.stringify(dev),
4546
'__MV3__': JSON.stringify(v3),

src/_locales/de/messages.json

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@
3131
"message": "Aktivieren",
3232
"description": "Text neben Checkbox zum aktivieren eines Features."
3333
},
34+
"optionsNone": {
35+
"message": "Keine",
36+
"description": "Text zur Information, dass keine Einträge konfiguriert sind."
37+
},
3438
"optionsPlaybackChangeRateTitle": {
3539
"message": "Geschwindigkeits-Änderungsrate",
3640
"description": "Beschriftung für Änderungsrate der Abspielgeschwindigkeit der Optionen Seite."
@@ -219,6 +223,14 @@
219223
"message": "Nebula Videolink laden",
220224
"description": "Beschriftung für LoadNebula der Optionen Seite."
221225
},
226+
"optionsEnableYoutube": {
227+
"message": "Aktiviere bei YouTube",
228+
"description": "Beschriftung für LoadNebula der Optionen Seite."
229+
},
230+
"optionsEnableInvidious": {
231+
"message": "Aktiviere bei Invidious",
232+
"description": "Beschriftung für LoadNebula der Optionen Seite."
233+
},
222234
"optionsYtOpenTab": {
223235
"message": "Link automatisch öffnen",
224236
"description": "Option zum automatischen Öffnen des gefundenen Links."
@@ -231,10 +243,26 @@
231243
"message": "Tab wiederverwenden",
232244
"description": "Option um youtube Tab weiterzuverwenden anstatt eines neuen Tabs"
233245
},
246+
"optionsInvidiousAdd": {
247+
"message": "Instanz hinzufügen",
248+
"description": "Beschriftung für LoadNebula Invidious Instance der Optionen Seite."
249+
},
234250
"optionsLoadNebulaHint": {
235-
"message": "Ob automatisch versucht werden soll, das Nebula Video zu finden.",
251+
"message": "Ob automatisch versucht werden soll, das Nebula Video von YouTube zu finden.",
236252
"description": "Hilfstext für LoadNebula der Optionen Seite."
237253
},
254+
"optionsInvidiousAddTitle": {
255+
"message": "Invidious Instanz hinzufügen",
256+
"description": "Titel für LoadNebula Invidious Instance Modal der Optionen Seite."
257+
},
258+
"optionsInvidiousAddDescription": {
259+
"message": "Um das passende Video von Nebula auf Invidious zu laden, muss die Erweiterung wissen, auf welchem Invidious Instanz(en) sie dies unterstützen soll. Hier kann die URL einer Invidious Instanz hinzugefügt werden, um diese Funktion zu aktivieren, und Links zu Nebula bei bekannten Kanälen finden.",
260+
"description": "Beschriftung für LoadNebula Invidious Instance Modal der Optionen Seite."
261+
},
262+
"optionsInvidiousAddLabel": {
263+
"message": "Instanz URL",
264+
"description": "Beschriftung für LoadNebula Invidious Instance der Optionen Seite."
265+
},
238266
"optionsLoadRSSTitle": {
239267
"message": "RSS/Atom Feed laden",
240268
"description": "Beschriftung für LoadRSS der Optionen Seite."

src/_locales/en/messages.json

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@
3131
"message": "Enable",
3232
"description": "Text next to enable/disable feature textbox."
3333
},
34+
"optionsNone": {
35+
"message": "None",
36+
"description": "Text to indicate no items configured."
37+
},
3438
"optionsPlaybackChangeRateTitle": {
3539
"message": "Playback speed change rate",
3640
"description": "Title for speed change rate of options page."
@@ -219,6 +223,14 @@
219223
"message": "Load Nebula link",
220224
"description": "Label for LoadNebula of options page."
221225
},
226+
"optionsEnableYoutube": {
227+
"message": "Enable on YouTube",
228+
"description": "Label for LoadNebula of options page."
229+
},
230+
"optionsEnableInvidious": {
231+
"message": "Enable on Invidious",
232+
"description": "Label for LoadNebula of options page."
233+
},
222234
"optionsYtOpenTab": {
223235
"message": "Automatically open link",
224236
"description": "Option to automatically open found link."
@@ -231,10 +243,26 @@
231243
"message": "Re-use tab",
232244
"description": "Option to re-use the youtube tab for nebula instead of a new tab"
233245
},
246+
"optionsInvidiousAdd": {
247+
"message": "Add instance",
248+
"description": "Label for LoadNebula Invidious Instance of options page."
249+
},
234250
"optionsLoadNebulaHint": {
235-
"message": "Whether to automatically try and find the matching Nebula video or not.",
251+
"message": "Whether to automatically try and find the matching Nebula video on YouTube or not.",
236252
"description": "Hint for LoadNebula of options page."
237253
},
254+
"optionsInvidiousAddTitle": {
255+
"message": "Add Invidious instance",
256+
"description": "Title for LoadNebula Invidious Instance modal of options page."
257+
},
258+
"optionsInvidiousAddDescription": {
259+
"message": "To load the matching video from Nebula on Indidious, the extension needs to know on which Invidious instance(s) to support this. Here you can add the URL of an Invidious instance to enable this feature, and find links to Nebula on known creators.",
260+
"description": "Description for LoadNebula Invidious Instance modal of options page."
261+
},
262+
"optionsInvidiousAddLabel": {
263+
"message": "Instance URL",
264+
"description": "Label for LoadNebula Invidious Instance of options page."
265+
},
238266
"optionsLoadRSSTitle": {
239267
"message": "Add RSS/Atom Feed",
240268
"description": "Label for LoadRSS of options page."

src/manifest.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ const manifest: browser._manifest.WebExtensionManifest = {
4545
],
4646
optional_permissions: [
4747
'*://*.googleapis.com/*',
48+
'<all_urls>',
4849
],
4950
browser_action: {
5051
default_icon: {

src/manifest_v3.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@ const manifest: browser._manifest.WebExtensionManifest = {
4747
'*://content.api.nebula.app/*',
4848
'*://*.nebula.tv/*',
4949
],
50+
// @ts-expect-error optional_host_permissions is not yet supported in the type definitions
51+
optional_host_permissions: [
52+
'*://*.googleapis.com/*',
53+
'<all_urls>',
54+
],
5055
action: {
5156
default_icon: {
5257
128: 'icons/icon_128.png',

src/options.html

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,12 @@ <h3 class="enhancer-field-title i18n">__MSG_optionsLoadNebulaTitle__</h3>
139139

140140
<label for="watchnebula" class="enhancer-checkbox">
141141
<input type="checkbox" name="watchnebula" id="watchnebula" data-default="" />
142-
<span class="i18n">__MSG_optionsEnable__</span>
142+
<span class="i18n">__MSG_optionsEnableYoutube__</span>
143143
</label>
144+
145+
<p class="hint i18n">__MSG_optionsLoadNebulaHint__</p>
146+
<p></p>
147+
144148
<label for="ytOpenTab" class="enhancer-checkbox">
145149
<input type="checkbox" name="ytOpenTab" id="ytOpenTab" data-default="" />
146150
<span class="i18n">__MSG_optionsYtOpenTab__</span>
@@ -154,7 +158,12 @@ <h3 class="enhancer-field-title i18n">__MSG_optionsLoadNebulaTitle__</h3>
154158
<span class="i18n">__MSG_optionsYtReplaceTab__</span>
155159
</label>
156160

157-
<p class="hint i18n">__MSG_optionsLoadNebulaHint__</p>
161+
<p></p>
162+
163+
<label>
164+
<span class="i18n">__MSG_optionsEnableInvidious__</span>: <i class="i18n" id="watchnebulainv">__MSG_optionsNone__</i><br/>
165+
<button type="button" class="i18n enhancer-button" id="watchnebulainvadd">__MSG_optionsInvidiousAdd__</button>
166+
</label>
158167
</div>
159168

160169
<div class="enhancer-field">

src/scripts/options.ts

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import { marked } from 'marked';
2+
import manifest from '../manifest';
23
import { loadCreators } from './background';
34
import { purgeCache } from './background/ext';
45
import { getChannels } from './helpers/api';
56
import { buildModal } from './helpers/modal';
67
import { BrowserMessage, QualityType, getBrowserInstance, getFromStorage, notification, setToStorage } from './helpers/sharedExt';
78
import { load, saveDirect } from './options/form';
9+
import { showAddInstance } from './options/invidious';
810
import { showLogs } from './options/logs';
911
import { showManageCreators } from './options/managecreators';
1012
import { showConfigurePlayers } from './options/player';
@@ -20,21 +22,57 @@ if (__MV3__)
2022

2123
const els = Settings.get();
2224
const purgeField = document.querySelector('#purgeCacheField');
25+
const youtubePerms = '*://*.googleapis.com/*';
26+
const ownPermissions = [...manifest.permissions, ...manifest.content_scripts.flatMap(c => c.matches)];
27+
let invidiousPerms: string[] = [];
2328

2429
// permissions for youtube comments
2530
const { permissions } = getBrowserInstance();
2631
els.youtube.addEventListener('change', async () => {
2732
const y = els.youtube;
2833
const perms: browser.permissions.Permissions = {
2934
origins: [
30-
'*://*.googleapis.com/*',
35+
youtubePerms,
3136
],
3237
};
3338
const success = await (y.checked ? permissions.request : permissions.remove)(perms);
3439
if (!success) y.checked = !y.checked; // revert
3540
if (y.checked && success) getBrowserInstance().runtime.sendMessage(BrowserMessage.LOAD_CREATORS);
3641
});
37-
permissions.onRemoved.addListener(p => p.origins?.length && (els.youtube.checked = false));
42+
const rebuildInvidiousList = () => {
43+
const el = document.getElementById('watchnebulainv');
44+
if (!el) return;
45+
if (invidiousPerms.length === 0) {
46+
el.textContent = msg('optionsNone');
47+
return;
48+
}
49+
while (el.firstChild) el.removeChild(el.firstChild);
50+
for (const perm of invidiousPerms) {
51+
const node = document.createTextNode(perm.replace(/^\*:\/\/(?:\*\.)?(.*?)\/.*$/, '$1'));
52+
el.appendChild(node);
53+
const btn = document.createElement('button');
54+
btn.type = 'button';
55+
btn.innerHTML = '&times;';
56+
btn.className = 'i18n enhancer-button remove';
57+
btn.addEventListener('click', async () => {
58+
const success = await permissions.remove({ origins: [perm] });
59+
if (success) invidiousPerms = invidiousPerms.filter(p => p !== perm);
60+
rebuildInvidiousList();
61+
});
62+
el.appendChild(btn);
63+
el.append(', ');
64+
}
65+
el.removeChild(el.lastChild); // remove last comma
66+
};
67+
const permissionUpdate = async () => {
68+
const p = await permissions.getAll();
69+
if (!p.origins) return;
70+
els.youtube.checked = p.origins.indexOf(youtubePerms) >= 0;
71+
invidiousPerms = p.origins.filter(o => o != youtubePerms && ownPermissions.indexOf(o) < 0);
72+
rebuildInvidiousList();
73+
};
74+
permissions.onAdded.addListener(permissionUpdate);
75+
permissions.onRemoved.addListener(permissionUpdate);
3876

3977
const aChange = () => {
4078
els.autoplayQueue.disabled = els.autoplay.checked;
@@ -82,6 +120,7 @@ document.querySelector('#purgeCacheNow').addEventListener('click', async () => {
82120
document.querySelector('#showChangelogsNow').addEventListener('click', () => showLogs(getBrowserInstance().runtime.getManifest().version));
83121
document.querySelector('#manageHiddenCreators').addEventListener('click', showManageCreators);
84122
document.querySelector('#configurePlayer').addEventListener('click', showConfigurePlayers);
123+
document.querySelector('#watchnebulainvadd').addEventListener('click', showAddInstance);
85124

86125
// load initial values from storage
87126
load(true)
@@ -91,7 +130,8 @@ load(true)
91130
.then(nChange)
92131
.then(nTabChange)
93132
.then(hChange)
94-
.then(vidChange);
133+
.then(vidChange)
134+
.then(permissionUpdate);
95135

96136
document.querySelector('[href="#save"]').addEventListener('click', async e => {
97137
e.preventDefault();
@@ -186,4 +226,5 @@ if (__DEV__) {
186226
console.warn('Creator', channel.slug, '(', channel.title, ') has no entry in creators list');
187227
}
188228
};
229+
console.log(ownPermissions);
189230
}

src/scripts/options/invidious.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { getBrowserInstance, notification } from '../helpers/sharedExt';
2+
import { buildModal } from './modal';
3+
4+
const msg = getBrowserInstance().i18n.getMessage;
5+
6+
const inputModalBody = () => {
7+
const div = document.createElement('form');
8+
div.className = 'enhancer-field';
9+
div.style.padding = '0';
10+
const control = div.appendChild(document.createElement('div'));
11+
control.className = 'enhancer-control';
12+
const input = control.appendChild(document.createElement('input'));
13+
input.className = 'enhancer-text-input';
14+
input.type = 'text';
15+
input.placeholder = 'https://invidious.example.com';
16+
input.id = 'invidiousInstanceInput';
17+
const label = control.appendChild(document.createElement('label'));
18+
label.textContent = msg('optionsInvidiousAddLabel');
19+
label.htmlFor = 'invidiousInstanceInput';
20+
21+
const buttonControl = div.appendChild(document.createElement('div'));
22+
buttonControl.className = 'enhancer-control';
23+
const addButton = buttonControl.appendChild(document.createElement('button'));
24+
addButton.type = 'submit';
25+
addButton.className = 'i18n enhancer-button';
26+
addButton.textContent = msg('optionsInvidiousAdd');
27+
div.addEventListener('submit', (e) => {
28+
e.preventDefault();
29+
const url = input.value.trim();
30+
if (!url) return;
31+
let perm: string;
32+
try {
33+
const u = new URL(url);
34+
if (!u.protocol.startsWith('http')) throw new Error('Invalid protocol');
35+
perm = `*://${u.host}/*`;
36+
} catch (e) {
37+
notification(e.message || e);
38+
return;
39+
}
40+
getBrowserInstance().permissions.request({ origins: [perm] }).then(success => {
41+
if (success) {
42+
input.value = '';
43+
}
44+
});
45+
});
46+
return div;
47+
};
48+
49+
export const showAddInstance = () => buildModal(msg('optionsInvidiousAddTitle'), msg('optionsInvidiousAddDescription'), undefined, inputModalBody());

src/styles/helpers/shared.scss

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,14 @@
195195
&:focus {
196196
box-shadow: variables.$blueish_transparent 0 0 10px;
197197
}
198+
199+
&.remove {
200+
padding: 2px 4px;
201+
color: variables.$error;
202+
border-color: variables.$error;
203+
margin-left: .3em;
204+
font-size: 80%;
205+
}
198206
}
199207

200208
.notification {

src/styles/helpers/variables.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ $blueish_dark: rgb(65 131 196);
1919
$blueish_light: rgb(135 200 234);
2020
$gray: rgb(159 158 156);
2121
$gray_transparent: rgb(85 85 85 / 0.1);
22+
$error: #ba000d;
2223

2324
// misc
2425
$disabled_alpha: 0.35;

0 commit comments

Comments
 (0)