Rework to typescript, add force proxy, autoplay and subtitles options, now...

Rework to typescript, add force proxy, autoplay and subtitles options, now work with Invidious direct link
parent c881fa83
extension/dist
node_modules
\ No newline at end of file
......@@ -5,9 +5,21 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Added
- Add "Force Proxy" option
- Add "Autoplay" option
- Add "Default Captions" option
- Apply selected options even when using direct Invidious links.
### Changed
- Rework with Typescript and use Webpack
## [0.5.0] - 2019-04-01
## [0.6.0] - 2019-04-05
### Added
- Add 64x64 logos
- Add support for youtu.be
## [0.5.0] - 2019-04-01### Added
- Add README.md
- Add logo
- Add toolbar button to toggle addon activation
......
git_watched_files := $(shell git ls-files)
extension_files := $(shell find extension -type f -not -name '*.map')
.PHONY: build watch clean package
all: build package
extension.zip: $(extension_files)
cd extension && \
find . -type f -not -name '*.map' | xargs zip ../extension.zip
sources.zip: $(git_watched_files)
git ls-files | xargs zip sources.zip
package: extension.zip sources.zip
build:
npm run build
watch:
npm run build:watch
clean:
rm extension/dist/*;
rm extension.zip sources.zip
var isEnabled = true;
const blockedHostnames = [
'www.youtube.com',
'youtube.com',
'www.youtube-nocookie.com',
'youtube-nocookie.com'
];
const invidition = async (r) => {
// If extension is disabled, do not redirect
if (!isEnabled) {
return {};
}
let url = new URL(r.url);
const options = await browser.storage.sync.get();
// Have to workaround Iframe API
if (url.pathname === '/iframe_api') {
url.href = browser.extension.getURL('/iframe_api.js');
} else {
if (url.pathname.indexOf('www-widgetapi.js') != -1) {
url.href = browser.extension.getURL('/www-widgetapi.js');
} else {
// If not using Iframe API, just redirect to invidio.us
let instance = new URL(options.instance || 'https://invidio.us/');
url.hostname = instance.hostname;
url.scheme = 'https'; // Force HTTPS
url.searchParams.append('local', 'true'); // Proxy through Invidious
}
}
return {
redirectUrl: url.href
};
}
const toogleEnabled = async () => {
isEnabled = !isEnabled;
let iconsPaths = {};
let action = "";
if (isEnabled) {
iconsPaths = {
16: 'assets/img/logo-16.png',
32: 'assets/img/logo-32.png',
48: 'assets/img/logo-48.png',
96: 'assets/img/logo-96.png',
125: 'assets/img/logo-128.png'
};
action = "Deactivate";
reloadTab();
} else {
iconsPaths = {
16: 'assets/img/logo-16-disabled.png',
32: 'assets/img/logo-32-disabled.png',
48: 'assets/img/logo-48-disabled.png',
96: 'assets/img/logo-96-disabled.png',
125: 'assets/img/logo-128-disabled.png'
}
action = "Activate"
}
chrome.browserAction.setTitle({title: `${action} Invidious`})
chrome.browserAction.setIcon({
path: iconsPaths
});
}
const reloadTab = async () => {
let activeTab = (await browser.tabs.query({currentWindow: true, active: true}))[0];
console.log(activeTab);
let activeTabHostname = new URL(activeTab.url).hostname;
if (blockedHostnames.indexOf(activeTabHostname) != -1) {
browser.tabs.reload();
}
}
const handleTabActivation = async (tab) => {
if(isEnabled) {
reloadTab();
}
}
browser.webRequest.onBeforeRequest.addListener(
invidition,
{ urls: ['*://*.youtube.com/*', '*://*.youtube-nocookie.com/*', '*://*.s.ytimg.com/*'] },
['blocking']
);
browser.browserAction.onClicked.addListener(toogleEnabled);
browser.tabs.onActivated.addListener(handleTabActivation);
body {
font-size: 14px;
}
option {
padding:0;
}
{
"manifest_version": 2,
"name": "Invidition",
"version": "0.5.0",
"version": "0.7.0",
"description": "Redirects YouTube links and embeds to their Invidious counterpart without any call to YouTube.",
"homepage_url": "https://gitlab.com/Booteille/Invidition",
"permissions": [
......@@ -13,14 +13,14 @@
"storage"
],
"background": {
"scripts": ["background.js"]
"scripts": ["dist/background.js", "dist/vendors.js"]
},
"web_accessible_resources": [
"iframe_api.js",
"www-widgetapi.js"
"assets/js/youtube/iframe_api.js",
"assets/js/youtube/www-widgetapi.js"
],
"options_ui": {
"page": "settings/options.html"
"page": "dist/options.html"
},
"applications": {
"gecko": {
......
This diff is collapsed.
{
"name": "invidition",
"version": "0.6.0",
"description": "Redirects YouTube links and embeds to their Invidious counterpart without any call to YouTube.",
"main": "webpack.config.js",
"dependencies": {},
"devDependencies": {
"@types/lodash": "^4.14.123",
"extract-loader": "^3.1.0",
"file-loader": "^3.0.1",
"html-loader": "^0.5.5",
"iso-639-1": "^2.0.5",
"lodash": "^4.17.11",
"ts-loader": "^5.3.3",
"typescript": "^3.4.2",
"web-ext-types": "^3.1.0",
"webextension-polyfill": "^0.4.0",
"webpack": "^4.29.6",
"webpack-cli": "^3.3.0"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack --display-error-details --progress --colors",
"build:watch": "npm run build -- -w"
},
"repository": {
"type": "git",
"url": "git+ssh://git@gitlab.com/Booteille/invidition.git"
},
"keywords": [
"invidious",
"youtube",
"video",
"redirection"
],
"author": "Booteille",
"license": "UNLICENSED",
"bugs": {
"url": "https://gitlab.com/Booteille/invidition/issues"
},
"homepage": "https://gitlab.com/Booteille/invidition#readme"
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<p>You can find a list of different Invidious instances <a href="https://github.com/omarroth/invidious/wiki/Invidious-Instances" alt="invidious instances">here</a>.</p>
<form>
<label>Instance: <input type="url" id="instance"></label>
<button type="submit">Save</button>
</form>
<script src="options.js"></script>
</body>
</html>
function saveOptions(e) {
e.preventDefault();
browser.storage.sync.set({
instance: document.querySelector("#instance").value
});
}
function restoreOptions() {
function setCurrentChoice(result) {
document.querySelector("#instance").value = result.instance || "https://invidio.us/";
}
function onError(error) {
console.log(`Error: ${error}`);
}
let getting = browser.storage.sync.get("instance");
getting.then(setCurrentChoice, onError);
}
document.addEventListener("DOMContentLoaded", restoreOptions);
document.querySelector("form").addEventListener("submit", saveOptions);
import * as browser from 'webextension-polyfill';
import config from './lib/config';
import * as _ from 'lodash';
const invidition = async (r) => {
// If extension is disabled, do not redirect
if (!config.isEnabled) {
return {};
}
let url = new URL(r.url);
const options: any = await getOptions();
// Have to workaround Iframe API
if (url.pathname === '/iframe_api') {
url.href = browser.extension.getURL('/assets/js/youtube/iframe_api.js');
} else {
if (url.pathname.indexOf('www-widgetapi.js') != -1) {
url.href = browser.extension.getURL('/assets/js/youtube/www-widgetapi.js');
} else {
// If not iFrame API files, redirect to invidio.us
let queryChanged = false;
let instance = new URL(<string> options.instance);
if (url.hostname !== instance.hostname) {
queryChanged = true;
url.hostname = instance.hostname;
url.protocol = 'https'; // Force HTTPS
}
if (_.startsWith(url.pathname, '/embed') || _.startsWith(url.pathname, '/watch')) {
_.forEach(options, (value, key) => {
if (key !== 'instance') {
if (!url.searchParams.get(key)) {
queryChanged = true;
url.searchParams.append(key, value);
} else {
if (url.searchParams.get(key) !== value) {
queryChanged = true;
url.searchParams.delete(key);
url.searchParams.append(key, value);
}
}
}
});
}
if (!queryChanged) {
return {};
}
}
}
return {
redirectUrl: url.href
};
}
const toogleEnabled = async () => {
config.isEnabled = !config.isEnabled;
let iconsPaths = {};
let action = "";
if (config.isEnabled) {
iconsPaths = generateIconsPaths();
action = "Deactivate";
reloadTab();
} else {
iconsPaths = generateIconsPaths(true);
action = "Activate"
}
browser.browserAction.setTitle({title: `${action} Invidious`})
browser.browserAction.setIcon({
path: iconsPaths
});
}
const reloadTab = async () => {
let activeTab = (await browser.tabs.query({currentWindow: true, active: true}))[0];
let activeTabHostname = new URL(activeTab.url).hostname;
if (generateSubdomain().indexOf(activeTabHostname) != -1) {
browser.tabs.reload();
}
}
const handleTabActivation = async (tab) => {
if(config.isEnabled) {
reloadTab();
}
}
const getOptions = async () => {
const options = await browser.storage.sync.get();
let result = {};
_.forEach(config.options, (value, key) => {
result[key] = options[key] || config.options[key];
})
return result;
}
const generateSubdomain = () => {
let domains: string[] = [];
_.forEach(config.domains.toRedirect, (value) => {
domains.push(value);
domains.push(`www.${value}`);
})
const instance = new URL(config.options.instance);
domains.push(instance.hostname);
domains.push(`www.${instance.hostname}`);
return domains;
}
const generateIconsPaths = (disabled = false) => {
let paths = {};
_.forEach(config.icons.resolutions, (value) => {
paths[value] = `/assets/img/logo-${value}${disabled ? '-disabled': ''}.png`;
});
return paths;
};
const generateFilter = (): string[] => {
let urls: string[] = [];
_.forEach(config.domains.toRedirect, (value) => {
urls.push(`*://*.${value}/*`);
})
urls.push(`*://*.${(new URL(config.options.instance)).hostname}/*`);
return urls;
};
browser.webRequest.onBeforeRequest.addListener(
invidition,
{ urls: generateFilter() },
['blocking']
);
browser.browserAction.onClicked.addListener(toogleEnabled);
browser.tabs.onActivated.addListener(handleTabActivation);
export default {
isEnabled: true,
options: {
instance: "https://invidio.us/",
local: 'true',
autoplay: '0',
subtitles: '',
},
domains: {
toRedirect: [
'youtube.com',
'youtube-nocookie.com',
'youtu.be',
's.ytimg.com'
]
},
icons: {
resolutions: [16, 32, 48, 64, 96, 128]
}
};
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link href="/assets/css/options.css" rel="stylesheet" type="text/css">
</head>
<body>
<p>You can find a list of different Invidious instances <a href="https://github.com/omarroth/invidious/wiki/Invidious-Instances" alt="invidious instances">here</a>.</p>
<form>
<label for="instance">Instance:</label>
<input type="url" id="instance"><br><br>
<label for="local" title="Activating this option will force the use of Invidious proxy, even when you already loaded the page." alt="Activating this option will force the use of Invidious proxy." class="tooltip">Force Proxy<sup>?</sup></label>
<input type="checkbox" name="local" id="local"><br><br>
<label for="autoplay">Autoplay:</label>
<input type="checkbox" name="autoplay" id="autoplay"><br><br>
<label for="defaultCaptions1">Default Captions:</label>
<select name="defaultCaptions1" class="defaultCaptions" id="defaultCaptions1"></select>
<select name="defaultCaptions2" class="defaultCaptions" id="defaultCaptions2"></select>
<select name="defaultCaptions3" class="defaultCaptions" id="defaultCaptions3"></select><br><br>
<button type="submit">Save</button>
</form>
<script src="/dist/vendors.js"></script>
<script src="/dist/options.js"></script>
</body>
</html>
import config from './lib/config.ts';
import * as browser from 'webextension-polyfill';
import * as _ from 'lodash';
import ISO6391 from 'iso-639-1';
// When saving options
function saveOptions(e) {
e.preventDefault();
let options = {};
for (var option in config.options) {
if (option !== 'subtitles') {
let element: Element = document.getElementById(option);
let inputElement : HTMLInputElement = element as HTMLInputElement;
if (inputElement.type !== 'undefined' && inputElement.type === 'checkbox') {
if (option === 'autoplay') {
options[option] = inputElement.checked ? '1' : '0';
} else {
options[option] = `${inputElement.checked}`;
}
} else {
options[option] = inputElement.value;
}
} else {
options['subtitles'] = captionsQuery();
}
}
browser.storage.sync.set(options);
}
// When options page is loaded
function restoreOptions() {
function setCurrentChoice(result) {
for (const option in config.options) {
if (option !== 'subtitles') {
let element: Element = document.getElementById(option);
let value = result && result[option] ? result[option] : config.options[option];
let inputElement : HTMLInputElement = element as HTMLInputElement;
if(inputElement.type !== 'undefined' && inputElement.type === 'checkbox') {
if (option !== 'autoplay') {
inputElement.checked = value === '1';
} else {
inputElement.checked = value === 'true';
}
} else {
inputElement.value = value;
}
} else {
let captions = _.split(result[option], ',');
let selectElements = document.getElementsByClassName('defaultCaptions');
let i = 0;
for (i; i < selectElements.length; i++) {
(selectElements[i] as HTMLOptionElement).value = captions[i];
}
}
}
}
function onError(error) {
console.error(`Error: ${error}`);
}
// Generate Locales DropDowns
let defaultCaptions = document.getElementsByClassName('defaultCaptions');
_.forEach(defaultCaptions, (element) => {
let defaultOption: HTMLOptionElement = document.createElement("OPTION") as HTMLOptionElement;
defaultOption.value = "";
defaultOption.selected = true;
element.append(defaultOption);
_.forEach(ISO6391.getAllCodes(), (code) => {
let option: HTMLOptionElement = document.createElement("OPTION") as HTMLOptionElement;
option.value = code;
option.innerHTML = ISO6391.getName(code);
element.append(option);
})
});
let getting = browser.storage.sync.get();
getting.then(setCurrentChoice, onError);
}
const captionsQuery = (): string => {
let defaultCaptions = document.getElementsByClassName('defaultCaptions');
let query = '';