Started conversion to TypeScript.
2
.gitignore
vendored
@@ -4,3 +4,5 @@ ignored
|
|||||||
node_modules
|
node_modules
|
||||||
web-ext-artifacts
|
web-ext-artifacts
|
||||||
.vscode/
|
.vscode/
|
||||||
|
dist/
|
||||||
|
tmp/
|
||||||
8
jest.config.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
module.exports = {
|
||||||
|
"roots": [
|
||||||
|
"src"
|
||||||
|
],
|
||||||
|
"transform": {
|
||||||
|
"^.+\\.ts$": "ts-jest"
|
||||||
|
},
|
||||||
|
};
|
||||||
5237
package-lock.json
generated
23
package.json
@@ -5,12 +5,27 @@
|
|||||||
"main": "background.js",
|
"main": "background.js",
|
||||||
"dependencies": {},
|
"dependencies": {},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"web-ext": "^4.0.0"
|
"web-ext": "^4.0.0",
|
||||||
|
"@types/chrome": "0.0.91",
|
||||||
|
"@types/firefox-webext-browser": "70.0.1",
|
||||||
|
"@types/jest": "^24.0.23",
|
||||||
|
"@types/jquery": "^3.3.31",
|
||||||
|
"copy-webpack-plugin": "^5.0.5",
|
||||||
|
"jest": "^24.9.0",
|
||||||
|
"ts-jest": "^24.2.0",
|
||||||
|
"rimraf": "^3.0.0",
|
||||||
|
"ts-loader": "^6.2.1",
|
||||||
|
"typescript": "~3.7.3",
|
||||||
|
"webpack": "~4.41.2",
|
||||||
|
"webpack-cli": "~3.3.10",
|
||||||
|
"webpack-merge": "~4.2.2"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"dev": "cd dist && web-ext run --start-url https://chrome.google.com/webstore/detail/ublock-origin/cjpalhdlnbpafiamejdnhcphjbkeiagm",
|
||||||
"dev": "web-ext run --start-url https://chrome.google.com/webstore/detail/ublock-origin/cjpalhdlnbpafiamejdnhcphjbkeiagm",
|
"watch": "webpack --config webpack/webpack.dev.js --watch",
|
||||||
"build": "web-ext build --overwrite-dest -i \"*(package-lock.json|README.md|package.json|config.js.example|firefox_manifest-extra.json|manifest.json.original|ignored|crowdin.yml)\""
|
"build": "webpack --config webpack/webpack.prod.js",
|
||||||
|
"clean": "rimraf dist",
|
||||||
|
"test": "npx jest"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 551 B After Width: | Height: | Size: 551 B |
|
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 6.7 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 9.1 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 7.1 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.6 KiB |
@@ -1,82 +1,118 @@
|
|||||||
SB = {
|
|
||||||
|
interface SBObject {
|
||||||
|
configListeners: Array<Function>;
|
||||||
|
defaults: any;
|
||||||
|
localConfig: any;
|
||||||
|
config: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allows a SBMap to be conveted into json form
|
||||||
|
// Currently used for local storage
|
||||||
|
class SBMap<T, U> extends Map {
|
||||||
|
toJSON() {
|
||||||
|
return Array.from(this.entries());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: Rename to something more meaningful
|
||||||
|
var SB: SBObject = {
|
||||||
/**
|
/**
|
||||||
* Callback function when an option is updated
|
* Callback function when an option is updated
|
||||||
*
|
|
||||||
* @type {CallableFunction}
|
|
||||||
*/
|
*/
|
||||||
configListeners: []
|
configListeners: [],
|
||||||
|
defaults: {
|
||||||
|
"sponsorTimes": new SBMap(),
|
||||||
|
"startSponsorKeybind": ";",
|
||||||
|
"submitKeybind": "'",
|
||||||
|
"minutesSaved": 0,
|
||||||
|
"skipCount": 0,
|
||||||
|
"sponsorTimesContributed": 0,
|
||||||
|
"disableSkipping": false,
|
||||||
|
"disableAutoSkip": false,
|
||||||
|
"trackViewCount": true,
|
||||||
|
"dontShowNotice": false,
|
||||||
|
"hideVideoPlayerControls": false,
|
||||||
|
"hideInfoButtonPlayerControls": false,
|
||||||
|
"hideDeleteButtonPlayerControls": false,
|
||||||
|
"hideDiscordLaunches": 0,
|
||||||
|
"hideDiscordLink": false,
|
||||||
|
"invidiousInstances": ["invidio.us", "invidiou.sh", "invidious.snopyta.org"],
|
||||||
|
"invidiousUpdateInfoShowCount": 0,
|
||||||
|
"autoUpvote": true
|
||||||
|
},
|
||||||
|
localConfig: {},
|
||||||
|
config: {}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Function setup
|
// Function setup
|
||||||
|
|
||||||
// Allows a map to be conveted into json form
|
|
||||||
// Currently used for local storage
|
|
||||||
Map.prototype.toJSON = function() {
|
|
||||||
return Array.from(this.entries());
|
|
||||||
};
|
|
||||||
|
|
||||||
// Proxy Map changes to Map in SB.localConfig
|
// Proxy Map changes to Map in SB.localConfig
|
||||||
// Saves the changes to chrome.storage in json form
|
// Saves the changes to chrome.storage in json form
|
||||||
class MapIO {
|
class MapIO {
|
||||||
|
id: string;
|
||||||
|
SBMap: SBMap<String, any>;
|
||||||
|
|
||||||
constructor(id) {
|
constructor(id) {
|
||||||
// The name of the item in the array
|
// The name of the item in the array
|
||||||
this.id = id;
|
this.id = id;
|
||||||
// A local copy of the map (SB.config.mapname.map)
|
// A local copy of the SBMap (SB.config.SBMapname.SBMap)
|
||||||
this.map = SB.localConfig[this.id];
|
this.SBMap = SB.localConfig[this.id];
|
||||||
}
|
}
|
||||||
|
|
||||||
set(key, value) {
|
set(key, value) {
|
||||||
// Proxy to map
|
// Proxy to SBMap
|
||||||
this.map.set(key, value);
|
this.SBMap.set(key, value);
|
||||||
// Store updated map locally
|
// Store updated SBMap locally
|
||||||
chrome.storage.sync.set({
|
chrome.storage.sync.set({
|
||||||
[this.id]: encodeStoredItem(this.map)
|
[this.id]: encodeStoredItem(this.SBMap)
|
||||||
});
|
});
|
||||||
return this.map;
|
return this.SBMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
get(key) {
|
get(key) {
|
||||||
return this.map.get(key);
|
return this.SBMap.get(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
has(key) {
|
has(key) {
|
||||||
return this.map.has(key);
|
return this.SBMap.has(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
size() {
|
size() {
|
||||||
return this.map.size;
|
return this.SBMap.size;
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(key) {
|
delete(key) {
|
||||||
// Proxy to map
|
// Proxy to SBMap
|
||||||
this.map.delete(key);
|
this.SBMap.delete(key);
|
||||||
// Store updated map locally
|
// Store updated SBMap locally
|
||||||
chrome.storage.sync.set({
|
chrome.storage.sync.set({
|
||||||
[this.id]: encodeStoredItem(this.map)
|
[this.id]: encodeStoredItem(this.SBMap)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
clear() {
|
clear() {
|
||||||
this.map.clear();
|
this.SBMap.clear();
|
||||||
chrome.storage.sync.set({
|
chrome.storage.sync.set({
|
||||||
[this.id]: encodeStoredItem(this.map)
|
[this.id]: encodeStoredItem(this.SBMap)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A Map cannot be stored in the chrome storage.
|
* A SBMap cannot be stored in the chrome storage.
|
||||||
* This data will be encoded into an array instead as specified by the toJSON function.
|
* This data will be encoded into an array instead as specified by the toJSON function.
|
||||||
*
|
*
|
||||||
* @param {*} data
|
* @param {*} data
|
||||||
*/
|
*/
|
||||||
function encodeStoredItem(data) {
|
function encodeStoredItem(data) {
|
||||||
// if data is Map convert to json for storing
|
// if data is SBMap convert to json for storing
|
||||||
if(!(data instanceof Map)) return data;
|
if(!(data instanceof SBMap)) return data;
|
||||||
return JSON.stringify(data);
|
return JSON.stringify(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A Map cannot be stored in the chrome storage.
|
* A SBMap cannot be stored in the chrome storage.
|
||||||
* This data will be decoded from the array it is stored in
|
* This data will be decoded from the array it is stored in
|
||||||
*
|
*
|
||||||
* @param {*} data
|
* @param {*} data
|
||||||
@@ -88,7 +124,7 @@ function decodeStoredItem(data) {
|
|||||||
let str = JSON.parse(data);
|
let str = JSON.parse(data);
|
||||||
|
|
||||||
if(!Array.isArray(str)) return data;
|
if(!Array.isArray(str)) return data;
|
||||||
return new Map(str);
|
return new SBMap(str);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
|
|
||||||
// If all else fails, return the data
|
// If all else fails, return the data
|
||||||
@@ -96,7 +132,7 @@ function decodeStoredItem(data) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function configProxy() {
|
function configProxy(): void {
|
||||||
chrome.storage.onChanged.addListener((changes, namespace) => {
|
chrome.storage.onChanged.addListener((changes, namespace) => {
|
||||||
for (const key in changes) {
|
for (const key in changes) {
|
||||||
SB.localConfig[key] = decodeStoredItem(changes[key].newValue);
|
SB.localConfig[key] = decodeStoredItem(changes[key].newValue);
|
||||||
@@ -107,24 +143,28 @@ function configProxy() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var handler = {
|
var handler: ProxyHandler<any> = {
|
||||||
set(obj, prop, value) {
|
set(obj, prop, value) {
|
||||||
SB.localConfig[prop] = value;
|
SB.localConfig[prop] = value;
|
||||||
|
|
||||||
chrome.storage.sync.set({
|
chrome.storage.sync.set({
|
||||||
[prop]: encodeStoredItem(value)
|
[prop]: encodeStoredItem(value)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
get(obj, prop) {
|
get(obj, prop): any {
|
||||||
let data = SB.localConfig[prop];
|
let data = SB.localConfig[prop];
|
||||||
if(data instanceof Map) data = new MapIO(prop);
|
if(data instanceof SBMap) data = new MapIO(prop);
|
||||||
|
|
||||||
return obj[prop] || data;
|
return obj[prop] || data;
|
||||||
},
|
},
|
||||||
|
|
||||||
deleteProperty(obj, prop) {
|
deleteProperty(obj, prop) {
|
||||||
chrome.storage.sync.remove(prop);
|
chrome.storage.sync.remove(<string> prop);
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
@@ -158,27 +198,6 @@ async function setupConfig() {
|
|||||||
migrateOldFormats();
|
migrateOldFormats();
|
||||||
}
|
}
|
||||||
|
|
||||||
SB.defaults = {
|
|
||||||
"sponsorTimes": new Map(),
|
|
||||||
"startSponsorKeybind": ";",
|
|
||||||
"submitKeybind": "'",
|
|
||||||
"minutesSaved": 0,
|
|
||||||
"skipCount": 0,
|
|
||||||
"sponsorTimesContributed": 0,
|
|
||||||
"disableSkipping": false,
|
|
||||||
"disableAutoSkip": false,
|
|
||||||
"trackViewCount": true,
|
|
||||||
"dontShowNotice": false,
|
|
||||||
"hideVideoPlayerControls": false,
|
|
||||||
"hideInfoButtonPlayerControls": false,
|
|
||||||
"hideDeleteButtonPlayerControls": false,
|
|
||||||
"hideDiscordLaunches": 0,
|
|
||||||
"hideDiscordLink": false,
|
|
||||||
"invidiousInstances": ["invidio.us", "invidiou.sh", "invidious.snopyta.org"],
|
|
||||||
"invidiousUpdateInfoShowCount": 0,
|
|
||||||
"autoUpvote": true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset config
|
// Reset config
|
||||||
function resetConfig() {
|
function resetConfig() {
|
||||||
SB.config = SB.defaults;
|
SB.config = SB.defaults;
|
||||||
@@ -201,3 +220,5 @@ function addDefaults() {
|
|||||||
|
|
||||||
// Sync config
|
// Sync config
|
||||||
setupConfig();
|
setupConfig();
|
||||||
|
|
||||||
|
export default SB;
|
||||||
3
src/config.js.example
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
//this file is loaded along iwth content.js
|
||||||
|
//this file sets the server to connect to, and is gitignored
|
||||||
|
var serverAddress = "https://sponsor.ajay.app";
|
||||||
@@ -1,3 +1,9 @@
|
|||||||
|
import Utils from "./utils";
|
||||||
|
import SB from "./SB";
|
||||||
|
|
||||||
|
import PreviewBar from "./js-components/previewBar";
|
||||||
|
import SkipNotice from "./js-components/previewBar";
|
||||||
|
|
||||||
//was sponsor data found when doing SponsorsLookup
|
//was sponsor data found when doing SponsorsLookup
|
||||||
var sponsorDataFound = false;
|
var sponsorDataFound = false;
|
||||||
var previousVideoID = null;
|
var previousVideoID = null;
|
||||||
@@ -40,7 +46,7 @@ var previewBar = null;
|
|||||||
var controls = null;
|
var controls = null;
|
||||||
|
|
||||||
// Direct Links
|
// Direct Links
|
||||||
videoIDChange(getYouTubeVideoID(document.URL));
|
videoIDChange(Utils.getYouTubeVideoID(document.URL));
|
||||||
|
|
||||||
//the last time looked at (used to see if this time is in the interval)
|
//the last time looked at (used to see if this time is in the interval)
|
||||||
var lastTime = -1;
|
var lastTime = -1;
|
||||||
@@ -72,7 +78,7 @@ function messageListener(request, sender, sendResponse) {
|
|||||||
//messages from popup script
|
//messages from popup script
|
||||||
switch(request.message){
|
switch(request.message){
|
||||||
case "update":
|
case "update":
|
||||||
videoIDChange(getYouTubeVideoID(document.URL));
|
videoIDChange(Utils.getYouTubeVideoID(document.URL));
|
||||||
break;
|
break;
|
||||||
case "sponsorStart":
|
case "sponsorStart":
|
||||||
sponsorMessageStarted(sendResponse);
|
sponsorMessageStarted(sendResponse);
|
||||||
@@ -166,7 +172,6 @@ if (!SB.configListeners.includes(contentConfigUpdateListener)) {
|
|||||||
|
|
||||||
//check for hotkey pressed
|
//check for hotkey pressed
|
||||||
document.onkeydown = async function(e){
|
document.onkeydown = async function(e){
|
||||||
e = e || window.event;
|
|
||||||
var key = e.key;
|
var key = e.key;
|
||||||
|
|
||||||
let video = document.getElementById("movie_player");
|
let video = document.getElementById("movie_player");
|
||||||
@@ -217,13 +222,13 @@ function videoIDChange(id) {
|
|||||||
//id is not valid
|
//id is not valid
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
|
|
||||||
let channelIDPromise = wait(getChannelID);
|
let channelIDPromise = Utils.wait(getChannelID);
|
||||||
channelIDPromise.then(() => channelIDPromise.isFulfilled = true).catch(() => channelIDPromise.isRejected = true);
|
channelIDPromise.then(() => channelIDPromise.isFulfilled = true).catch(() => channelIDPromise.isRejected = true);
|
||||||
|
|
||||||
//setup the preview bar
|
//setup the preview bar
|
||||||
if (previewBar == null) {
|
if (previewBar == null) {
|
||||||
//create it
|
//create it
|
||||||
wait(getControls).then(result => {
|
Utils.wait(getControls).then(result => {
|
||||||
const progressElementSelectors = [
|
const progressElementSelectors = [
|
||||||
// For YouTube
|
// For YouTube
|
||||||
"ytp-progress-bar-container",
|
"ytp-progress-bar-container",
|
||||||
@@ -274,7 +279,7 @@ function videoIDChange(id) {
|
|||||||
sponsorTimesSubmitting = [];
|
sponsorTimesSubmitting = [];
|
||||||
|
|
||||||
//see if the onvideo control image needs to be changed
|
//see if the onvideo control image needs to be changed
|
||||||
wait(getControls).then(result => {
|
Utils.wait(getControls).then(result => {
|
||||||
chrome.runtime.sendMessage({
|
chrome.runtime.sendMessage({
|
||||||
message: "getSponsorTimes",
|
message: "getSponsorTimes",
|
||||||
videoID: id
|
videoID: id
|
||||||
@@ -299,12 +304,12 @@ function videoIDChange(id) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
//see if video controls buttons should be added
|
//see if video controls buttons should be added
|
||||||
if (!onInvidious) {
|
if (!Utils.onInvidious) {
|
||||||
updateVisibilityOfPlayerControlsButton();
|
updateVisibilityOfPlayerControlsButton();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function sponsorsLookup(id, channelIDPromise) {
|
function sponsorsLookup(id: string, channelIDPromise = null) {
|
||||||
v = document.querySelector('video') // Youtube video player
|
v = document.querySelector('video') // Youtube video player
|
||||||
//there is no video here
|
//there is no video here
|
||||||
if (v == null) {
|
if (v == null) {
|
||||||
@@ -324,7 +329,7 @@ function sponsorsLookup(id, channelIDPromise) {
|
|||||||
whitelistCheck();
|
whitelistCheck();
|
||||||
} else if (channelIDPromise.isRejected) {
|
} else if (channelIDPromise.isRejected) {
|
||||||
//try again
|
//try again
|
||||||
wait(getChannelID).then(whitelistCheck).catch();
|
Utils.wait(getChannelID).then(whitelistCheck).catch();
|
||||||
} else {
|
} else {
|
||||||
//add it as a then statement
|
//add it as a then statement
|
||||||
channelIDPromise.then(whitelistCheck);
|
channelIDPromise.then(whitelistCheck);
|
||||||
@@ -410,7 +415,7 @@ function updatePreviewBar() {
|
|||||||
types.push("previewSponsor");
|
types.push("previewSponsor");
|
||||||
}
|
}
|
||||||
|
|
||||||
wait(() => previewBar !== null).then((result) => previewBar.set(allSponsorTimes, types, v.duration));
|
Utils.wait(() => previewBar !== null).then((result) => previewBar.set(allSponsorTimes, types, v.duration));
|
||||||
|
|
||||||
//update last video id
|
//update last video id
|
||||||
lastPreviewBarUpdate = sponsorVideoID;
|
lastPreviewBarUpdate = sponsorVideoID;
|
||||||
@@ -423,7 +428,7 @@ function getChannelID() {
|
|||||||
channelURLContainer = document.querySelector("#channel-name > #container > #text-container > #text");
|
channelURLContainer = document.querySelector("#channel-name > #container > #text-container > #text");
|
||||||
if (channelURLContainer !== null) {
|
if (channelURLContainer !== null) {
|
||||||
channelURLContainer = channelURLContainer.firstElementChild;
|
channelURLContainer = channelURLContainer.firstElementChild;
|
||||||
} else if (onInvidious) {
|
} else if (Utils.onInvidious) {
|
||||||
// Unfortunately, the Invidious HTML doesn't have much in the way of element identifiers...
|
// Unfortunately, the Invidious HTML doesn't have much in the way of element identifiers...
|
||||||
channelURLContainer = document.querySelector("body > div > div.pure-u-1.pure-u-md-20-24 div.pure-u-1.pure-u-lg-3-5 > div > a");
|
channelURLContainer = document.querySelector("body > div > div.pure-u-1.pure-u-md-20-24 div.pure-u-1.pure-u-lg-3-5 > div > a");
|
||||||
} else {
|
} else {
|
||||||
@@ -443,8 +448,8 @@ function getChannelID() {
|
|||||||
let titleInfoContainer = document.getElementById("info-contents");
|
let titleInfoContainer = document.getElementById("info-contents");
|
||||||
let currentTitle = "";
|
let currentTitle = "";
|
||||||
if (titleInfoContainer != null) {
|
if (titleInfoContainer != null) {
|
||||||
currentTitle = titleInfoContainer.firstElementChild.firstElementChild.querySelector(".title").firstElementChild.innerText;
|
currentTitle = (<HTMLElement> titleInfoContainer.firstElementChild.firstElementChild.querySelector(".title").firstElementChild).innerText;
|
||||||
} else if (onInvidious) {
|
} else if (Utils.onInvidious) {
|
||||||
// Unfortunately, the Invidious HTML doesn't have much in the way of element identifiers...
|
// Unfortunately, the Invidious HTML doesn't have much in the way of element identifiers...
|
||||||
currentTitle = document.querySelector("body > div > div.pure-u-1.pure-u-md-20-24 div.pure-u-1.pure-u-lg-3-5 > div > a > div > span").textContent;
|
currentTitle = document.querySelector("body > div > div.pure-u-1.pure-u-md-20-24 div.pure-u-1.pure-u-lg-3-5 > div > a > div > span").textContent;
|
||||||
} else {
|
} else {
|
||||||
@@ -637,7 +642,7 @@ function getControls() {
|
|||||||
|
|
||||||
//adds all the player controls buttons
|
//adds all the player controls buttons
|
||||||
async function createButtons() {
|
async function createButtons() {
|
||||||
let result = await wait(getControls).catch();
|
let result = await Utils.wait(getControls).catch();
|
||||||
|
|
||||||
//set global controls variable
|
//set global controls variable
|
||||||
controls = result;
|
controls = result;
|
||||||
@@ -655,7 +660,7 @@ async function updateVisibilityOfPlayerControlsButton() {
|
|||||||
|
|
||||||
await createButtons();
|
await createButtons();
|
||||||
|
|
||||||
if (SB.config.hideVideoPlayerControls || onInvidious) {
|
if (SB.config.hideVideoPlayerControls || Utils.onInvidious) {
|
||||||
document.getElementById("startSponsorButton").style.display = "none";
|
document.getElementById("startSponsorButton").style.display = "none";
|
||||||
document.getElementById("submitButton").style.display = "none";
|
document.getElementById("submitButton").style.display = "none";
|
||||||
} else {
|
} else {
|
||||||
@@ -663,13 +668,13 @@ async function updateVisibilityOfPlayerControlsButton() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//don't show the info button on embeds
|
//don't show the info button on embeds
|
||||||
if (SB.config.hideInfoButtonPlayerControls || document.URL.includes("/embed/") || onInvidious) {
|
if (SB.config.hideInfoButtonPlayerControls || document.URL.includes("/embed/") || Utils.onInvidious) {
|
||||||
document.getElementById("infoButton").style.display = "none";
|
document.getElementById("infoButton").style.display = "none";
|
||||||
} else {
|
} else {
|
||||||
document.getElementById("infoButton").style.removeProperty("display");
|
document.getElementById("infoButton").style.removeProperty("display");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SB.config.hideDeleteButtonPlayerControls || onInvidious) {
|
if (SB.config.hideDeleteButtonPlayerControls || Utils.onInvidious) {
|
||||||
document.getElementById("deleteButton").style.display = "none";
|
document.getElementById("deleteButton").style.display = "none";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -718,15 +723,15 @@ async function changeStartSponsorButton(showStartSponsor, uploadButtonVisible) {
|
|||||||
if(!sponsorVideoID) return false;
|
if(!sponsorVideoID) return false;
|
||||||
|
|
||||||
//make sure submit button is loaded
|
//make sure submit button is loaded
|
||||||
await wait(isSubmitButtonLoaded);
|
await Utils.wait(isSubmitButtonLoaded);
|
||||||
|
|
||||||
//if it isn't visible, there is no data
|
//if it isn't visible, there is no data
|
||||||
let shouldHide = (uploadButtonVisible && !(SB.config.hideDeleteButtonPlayerControls || onInvidious)) ? "unset" : "none"
|
let shouldHide = (uploadButtonVisible && !(SB.config.hideDeleteButtonPlayerControls || Utils.onInvidious)) ? "unset" : "none"
|
||||||
document.getElementById("deleteButton").style.display = shouldHide;
|
document.getElementById("deleteButton").style.display = shouldHide;
|
||||||
|
|
||||||
if (showStartSponsor) {
|
if (showStartSponsor) {
|
||||||
showingStartSponsor = true;
|
showingStartSponsor = true;
|
||||||
document.getElementById("startSponsorImage").src = chrome.extension.getURL("icons/PlayerStartIconSponsorBlocker256px.png");
|
(<HTMLImageElement> document.getElementById("startSponsorImage")).src = chrome.extension.getURL("icons/PlayerStartIconSponsorBlocker256px.png");
|
||||||
document.getElementById("startSponsorButton").setAttribute("title", chrome.i18n.getMessage("sponsorStart"));
|
document.getElementById("startSponsorButton").setAttribute("title", chrome.i18n.getMessage("sponsorStart"));
|
||||||
|
|
||||||
if (document.getElementById("startSponsorImage").style.display != "none" && uploadButtonVisible && !SB.config.hideInfoButtonPlayerControls) {
|
if (document.getElementById("startSponsorImage").style.display != "none" && uploadButtonVisible && !SB.config.hideInfoButtonPlayerControls) {
|
||||||
@@ -737,7 +742,7 @@ async function changeStartSponsorButton(showStartSponsor, uploadButtonVisible) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
showingStartSponsor = false;
|
showingStartSponsor = false;
|
||||||
document.getElementById("startSponsorImage").src = chrome.extension.getURL("icons/PlayerStopIconSponsorBlocker256px.png");
|
(<HTMLImageElement> document.getElementById("startSponsorImage")).src = chrome.extension.getURL("icons/PlayerStopIconSponsorBlocker256px.png");
|
||||||
document.getElementById("startSponsorButton").setAttribute("title", chrome.i18n.getMessage("sponsorEND"));
|
document.getElementById("startSponsorButton").setAttribute("title", chrome.i18n.getMessage("sponsorEND"));
|
||||||
|
|
||||||
//disable submit button
|
//disable submit button
|
||||||
@@ -769,7 +774,7 @@ function openInfoMenu() {
|
|||||||
//close button
|
//close button
|
||||||
let closeButton = document.createElement("div");
|
let closeButton = document.createElement("div");
|
||||||
closeButton.innerText = "Close Popup";
|
closeButton.innerText = "Close Popup";
|
||||||
closeButton.classList = "smallLink";
|
closeButton.classList.add("smallLink");
|
||||||
closeButton.setAttribute("align", "center");
|
closeButton.setAttribute("align", "center");
|
||||||
closeButton.addEventListener("click", closeInfoMenu);
|
closeButton.addEventListener("click", closeInfoMenu);
|
||||||
|
|
||||||
@@ -791,7 +796,7 @@ function openInfoMenu() {
|
|||||||
|
|
||||||
//make the logo source not 404
|
//make the logo source not 404
|
||||||
//query selector must be used since getElementByID doesn't work on a node and this isn't added to the document yet
|
//query selector must be used since getElementByID doesn't work on a node and this isn't added to the document yet
|
||||||
let logo = popup.querySelector("#sponsorBlockPopupLogo");
|
let logo = <HTMLImageElement> popup.querySelector("#sponsorBlockPopupLogo");
|
||||||
logo.src = chrome.extension.getURL("icons/LogoSponsorBlocker256px.png");
|
logo.src = chrome.extension.getURL("icons/LogoSponsorBlocker256px.png");
|
||||||
|
|
||||||
//remove the style sheet and font that are not necessary
|
//remove the style sheet and font that are not necessary
|
||||||
@@ -889,7 +894,7 @@ function vote(type, UUID, skipNotice) {
|
|||||||
skipNotice.addNoticeInfoMessage.bind(skipNotice)(chrome.i18n.getMessage("voteFail"))
|
skipNotice.addNoticeInfoMessage.bind(skipNotice)(chrome.i18n.getMessage("voteFail"))
|
||||||
skipNotice.resetVoteButtonInfo.bind(skipNotice)();
|
skipNotice.resetVoteButtonInfo.bind(skipNotice)();
|
||||||
} else if (response.successType == -1) {
|
} else if (response.successType == -1) {
|
||||||
skipNotice.addNoticeInfoMessage.bind(skipNotice)(getErrorMessage(response.statusCode))
|
skipNotice.addNoticeInfoMessage.bind(skipNotice)(Utils.getErrorMessage(response.statusCode))
|
||||||
skipNotice.resetVoteButtonInfo.bind(skipNotice)();
|
skipNotice.resetVoteButtonInfo.bind(skipNotice)();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -961,7 +966,7 @@ function submitSponsorTimes() {
|
|||||||
//called after all the checks have been made that it's okay to do so
|
//called after all the checks have been made that it's okay to do so
|
||||||
function sendSubmitMessage(){
|
function sendSubmitMessage(){
|
||||||
//add loading animation
|
//add loading animation
|
||||||
document.getElementById("submitImage").src = chrome.extension.getURL("icons/PlayerUploadIconSponsorBlocker256px.png");
|
(<HTMLImageElement> document.getElementById("submitImage")).src = chrome.extension.getURL("icons/PlayerUploadIconSponsorBlocker256px.png");
|
||||||
document.getElementById("submitButton").style.animation = "rotate 1s 0s infinite";
|
document.getElementById("submitButton").style.animation = "rotate 1s 0s infinite";
|
||||||
|
|
||||||
let currentVideoID = sponsorVideoID;
|
let currentVideoID = sponsorVideoID;
|
||||||
@@ -994,7 +999,7 @@ function sendSubmitMessage(){
|
|||||||
sponsorTimes = sponsorTimes.concat(sponsorTimesSubmitting);
|
sponsorTimes = sponsorTimes.concat(sponsorTimesSubmitting);
|
||||||
for (let i = 0; i < sponsorTimesSubmitting.length; i++) {
|
for (let i = 0; i < sponsorTimesSubmitting.length; i++) {
|
||||||
// Add some random IDs
|
// Add some random IDs
|
||||||
UUIDs.push(generateUserID());
|
UUIDs.push(Utils.generateUserID());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Empty the submitting times
|
// Empty the submitting times
|
||||||
@@ -1004,9 +1009,9 @@ function sendSubmitMessage(){
|
|||||||
} else {
|
} else {
|
||||||
//show that the upload failed
|
//show that the upload failed
|
||||||
document.getElementById("submitButton").style.animation = "unset";
|
document.getElementById("submitButton").style.animation = "unset";
|
||||||
document.getElementById("submitImage").src = chrome.extension.getURL("icons/PlayerUploadFailedIconSponsorBlocker256px.png");
|
(<HTMLImageElement> document.getElementById("submitImage")).src = chrome.extension.getURL("icons/PlayerUploadFailedIconSponsorBlocker256px.png");
|
||||||
|
|
||||||
alert(getErrorMessage(response.statusCode));
|
alert(Utils.getErrorMessage(response.statusCode));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -1037,23 +1042,25 @@ function getSponsorTimesMessage(sponsorTimes) {
|
|||||||
//converts time in seconds to minutes:seconds
|
//converts time in seconds to minutes:seconds
|
||||||
function getFormattedTime(seconds) {
|
function getFormattedTime(seconds) {
|
||||||
let minutes = Math.floor(seconds / 60);
|
let minutes = Math.floor(seconds / 60);
|
||||||
let secondsDisplay = Math.round(seconds - minutes * 60);
|
let secondsNum: number = Math.round(seconds - minutes * 60);
|
||||||
if (secondsDisplay < 10) {
|
let secondsDisplay: string = String(secondsNum);
|
||||||
|
|
||||||
|
if (secondsNum < 10) {
|
||||||
//add a zero
|
//add a zero
|
||||||
secondsDisplay = "0" + secondsDisplay;
|
secondsDisplay = "0" + secondsNum;
|
||||||
}
|
}
|
||||||
|
|
||||||
let formatted = minutes+ ":" + secondsDisplay;
|
let formatted = minutes + ":" + secondsDisplay;
|
||||||
|
|
||||||
return formatted;
|
return formatted;
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendRequestToServer(type, address, callback) {
|
function sendRequestToServer(type: string, address: string, callback = null) {
|
||||||
let xmlhttp = new XMLHttpRequest();
|
let xmlhttp = new XMLHttpRequest();
|
||||||
|
|
||||||
xmlhttp.open(type, serverAddress + address, true);
|
xmlhttp.open(type, serverAddress + address, true);
|
||||||
|
|
||||||
if (callback != undefined) {
|
if (callback !== null) {
|
||||||
xmlhttp.onreadystatechange = function () {
|
xmlhttp.onreadystatechange = function () {
|
||||||
callback(xmlhttp, false);
|
callback(xmlhttp, false);
|
||||||
};
|
};
|
||||||
@@ -21,11 +21,13 @@ let barTypes = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
class PreviewBar {
|
class PreviewBar {
|
||||||
|
container: HTMLUListElement;
|
||||||
|
parent: any;
|
||||||
|
|
||||||
constructor(parent) {
|
constructor(parent) {
|
||||||
this.container = document.createElement('ul');
|
this.container = document.createElement('ul');
|
||||||
this.container.id = 'previewbar';
|
this.container.id = 'previewbar';
|
||||||
this.parent = parent;
|
this.parent = parent;
|
||||||
this.bars = []
|
|
||||||
|
|
||||||
this.updatePosition();
|
this.updatePosition();
|
||||||
}
|
}
|
||||||
@@ -39,7 +41,7 @@ class PreviewBar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateColor(segment, color, opacity) {
|
updateColor(segment, color, opacity) {
|
||||||
let bars = document.querySelectorAll('[data-vs-segment-type=' + segment + ']');
|
let bars = <NodeListOf<HTMLElement>> document.querySelectorAll('[data-vs-segment-type=' + segment + ']');
|
||||||
for (let bar of bars) {
|
for (let bar of bars) {
|
||||||
bar.style.backgroundColor = color;
|
bar.style.backgroundColor = color;
|
||||||
bar.style.opacity = opacity;
|
bar.style.opacity = opacity;
|
||||||
@@ -73,8 +75,7 @@ class PreviewBar {
|
|||||||
bar.style.left = (timestamps[i][0] / duration * 100) + "%";
|
bar.style.left = (timestamps[i][0] / duration * 100) + "%";
|
||||||
bar.style.position = "absolute"
|
bar.style.position = "absolute"
|
||||||
|
|
||||||
this.container.insertAdjacentElement('beforeEnd', bar);
|
this.container.insertAdjacentElement("beforeEnd", bar);
|
||||||
this.bars[i] = bar;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,3 +91,5 @@ class PreviewBar {
|
|||||||
this.container = undefined;
|
this.container = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default PreviewBar;
|
||||||
@@ -4,13 +4,16 @@
|
|||||||
* The notice that tells the user that a sponsor was just skipped
|
* The notice that tells the user that a sponsor was just skipped
|
||||||
*/
|
*/
|
||||||
class SkipNotice {
|
class SkipNotice {
|
||||||
/**
|
parent: HTMLElement;
|
||||||
* @param {HTMLElement} parent
|
UUID: string;
|
||||||
* @param {String} UUID
|
manualSkip: boolean;
|
||||||
* @param {String} noticeTitle
|
maxCountdownTime: () => number;
|
||||||
* @param {boolean} manualSkip
|
countdownTime: any;
|
||||||
*/
|
countdownInterval: number;
|
||||||
constructor(parent, UUID, manualSkip = false) {
|
unskipCallback: any;
|
||||||
|
idSuffix: any;
|
||||||
|
|
||||||
|
constructor(parent: HTMLElement, UUID: string, manualSkip: boolean = false) {
|
||||||
this.parent = parent;
|
this.parent = parent;
|
||||||
this.UUID = UUID;
|
this.UUID = UUID;
|
||||||
this.manualSkip = manualSkip;
|
this.manualSkip = manualSkip;
|
||||||
@@ -48,7 +51,7 @@ class SkipNotice {
|
|||||||
noticeElement.id = "sponsorSkipNotice" + this.idSuffix;
|
noticeElement.id = "sponsorSkipNotice" + this.idSuffix;
|
||||||
noticeElement.classList.add("sponsorSkipObject");
|
noticeElement.classList.add("sponsorSkipObject");
|
||||||
noticeElement.classList.add("sponsorSkipNotice");
|
noticeElement.classList.add("sponsorSkipNotice");
|
||||||
noticeElement.style.zIndex = 50 + amountOfPreviousNotices;
|
noticeElement.style.zIndex = String(50 + amountOfPreviousNotices);
|
||||||
|
|
||||||
//add mouse enter and leave listeners
|
//add mouse enter and leave listeners
|
||||||
noticeElement.addEventListener("mouseenter", this.pauseCountdown.bind(this));
|
noticeElement.addEventListener("mouseenter", this.pauseCountdown.bind(this));
|
||||||
@@ -170,12 +173,12 @@ class SkipNotice {
|
|||||||
if (referenceNode == null) {
|
if (referenceNode == null) {
|
||||||
//for embeds
|
//for embeds
|
||||||
let player = document.getElementById("player");
|
let player = document.getElementById("player");
|
||||||
referenceNode = player.firstChild;
|
referenceNode = <HTMLElement> player.firstChild;
|
||||||
let index = 1;
|
let index = 1;
|
||||||
|
|
||||||
//find the child that is the video player (sometimes it is not the first)
|
//find the child that is the video player (sometimes it is not the first)
|
||||||
while (!referenceNode.classList.contains("html5-video-player") || !referenceNode.classList.contains("ytp-embed")) {
|
while (!referenceNode.classList.contains("html5-video-player") || !referenceNode.classList.contains("ytp-embed")) {
|
||||||
referenceNode = player.children[index];
|
referenceNode = <HTMLElement> player.children[index];
|
||||||
|
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
@@ -336,7 +339,7 @@ class SkipNotice {
|
|||||||
noticeElement.innerText = title;
|
noticeElement.innerText = title;
|
||||||
}
|
}
|
||||||
|
|
||||||
addNoticeInfoMessage(message, message2) {
|
addNoticeInfoMessage(message: string, message2: string = "") {
|
||||||
let previousInfoMessage = document.getElementById("sponsorTimesInfoMessage" + this.idSuffix);
|
let previousInfoMessage = document.getElementById("sponsorTimesInfoMessage" + this.idSuffix);
|
||||||
if (previousInfoMessage != null) {
|
if (previousInfoMessage != null) {
|
||||||
//remove it
|
//remove it
|
||||||
@@ -426,3 +429,5 @@ class SkipNotice {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default SkipNotice;
|
||||||
279
src/utils.ts
Normal file
@@ -0,0 +1,279 @@
|
|||||||
|
import SB from "./SB";
|
||||||
|
|
||||||
|
class Utils {
|
||||||
|
|
||||||
|
static isBackgroundScript = false;
|
||||||
|
static onInvidious = false;
|
||||||
|
|
||||||
|
// Function that can be used to wait for a condition before returning
|
||||||
|
static async wait(condition, timeout = 5000, check = 100) {
|
||||||
|
return await new Promise((resolve, reject) => {
|
||||||
|
setTimeout(() => reject("TIMEOUT"), timeout);
|
||||||
|
|
||||||
|
let intervalCheck = () => {
|
||||||
|
let result = condition();
|
||||||
|
if (result !== false) {
|
||||||
|
resolve(result);
|
||||||
|
clearInterval(interval);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
let interval = setInterval(intervalCheck, check);
|
||||||
|
|
||||||
|
//run the check once first, this speeds it up a lot
|
||||||
|
intervalCheck();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static getYouTubeVideoID(url: string) {
|
||||||
|
// For YouTube TV support
|
||||||
|
if(url.startsWith("https://www.youtube.com/tv#/")) url = url.replace("#", "");
|
||||||
|
|
||||||
|
//Attempt to parse url
|
||||||
|
let urlObject = null;
|
||||||
|
try {
|
||||||
|
urlObject = new URL(url);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("[SB] Unable to parse URL: " + url);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Check if valid hostname
|
||||||
|
if (SB.config && SB.config.invidiousInstances.includes(urlObject.host)) {
|
||||||
|
onInvidious = true;
|
||||||
|
} else if (!["www.youtube.com", "www.youtube-nocookie.com"].includes(urlObject.host)) {
|
||||||
|
if (!SB.config) {
|
||||||
|
// Call this later, in case this is an Invidious tab
|
||||||
|
this.wait(() => SB.config !== undefined).then(() => this.videoIDChange(this.getYouTubeVideoID(url)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
//Get ID from searchParam
|
||||||
|
if (urlObject.searchParams.has("v") && ["/watch", "/watch/"].includes(urlObject.pathname) || urlObject.pathname.startsWith("/tv/watch")) {
|
||||||
|
let id = urlObject.searchParams.get("v");
|
||||||
|
return id.length == 11 ? id : false;
|
||||||
|
} else if (urlObject.pathname.startsWith("/embed/")) {
|
||||||
|
try {
|
||||||
|
return urlObject.pathname.substr(7, 11);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("[SB] Video ID not valid for " + url);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asks for the optional permissions required for all extra sites.
|
||||||
|
* It also starts the content script registrations.
|
||||||
|
*
|
||||||
|
* For now, it is just SB.config.invidiousInstances.
|
||||||
|
*
|
||||||
|
* @param {CallableFunction} callback
|
||||||
|
*/
|
||||||
|
static setupExtraSitePermissions(callback) {
|
||||||
|
// Request permission
|
||||||
|
let permissions = ["declarativeContent"];
|
||||||
|
if (this.isFirefox()) permissions = [];
|
||||||
|
|
||||||
|
chrome.permissions.request({
|
||||||
|
origins: this.getInvidiousInstancesRegex(),
|
||||||
|
permissions: permissions
|
||||||
|
}, async function (granted) {
|
||||||
|
if (granted) {
|
||||||
|
this.setupExtraSiteContentScripts();
|
||||||
|
} else {
|
||||||
|
this.removeExtraSiteRegistration();
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(granted);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers the content scripts for the extra sites.
|
||||||
|
* Will use a different method depending on the browser.
|
||||||
|
* This is called by setupExtraSitePermissions().
|
||||||
|
*
|
||||||
|
* For now, it is just SB.config.invidiousInstances.
|
||||||
|
*/
|
||||||
|
static setupExtraSiteContentScripts() {
|
||||||
|
let js = [
|
||||||
|
"config.js",
|
||||||
|
"SB.js",
|
||||||
|
"utils/previewBar.js",
|
||||||
|
"utils/skipNotice.js",
|
||||||
|
"utils.js",
|
||||||
|
"content.js",
|
||||||
|
"popup.js"
|
||||||
|
];
|
||||||
|
let css = [
|
||||||
|
"content.css",
|
||||||
|
"./libs/Source+Sans+Pro.css",
|
||||||
|
"popup.css"
|
||||||
|
];
|
||||||
|
|
||||||
|
if (this.isFirefox()) {
|
||||||
|
let firefoxJS = [];
|
||||||
|
for (const file of js) {
|
||||||
|
firefoxJS.push({file});
|
||||||
|
}
|
||||||
|
let firefoxCSS = [];
|
||||||
|
for (const file of css) {
|
||||||
|
firefoxCSS.push({file});
|
||||||
|
}
|
||||||
|
|
||||||
|
let registration = {
|
||||||
|
message: "registerContentScript",
|
||||||
|
id: "invidious",
|
||||||
|
allFrames: true,
|
||||||
|
js: firefoxJS,
|
||||||
|
css: firefoxCSS,
|
||||||
|
matches: this.getInvidiousInstancesRegex()
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isBackgroundScript) {
|
||||||
|
registerFirefoxContentScript(registration);
|
||||||
|
} else {
|
||||||
|
chrome.runtime.sendMessage(registration);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
chrome.declarativeContent.onPageChanged.removeRules(["invidious"], function() {
|
||||||
|
let conditions = [];
|
||||||
|
for (const regex of this.getInvidiousInstancesRegex()) {
|
||||||
|
conditions.push(new chrome.declarativeContent.PageStateMatcher({
|
||||||
|
pageUrl: { urlMatches: regex }
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add page rule
|
||||||
|
let rule = {
|
||||||
|
id: "invidious",
|
||||||
|
conditions,
|
||||||
|
// This API is experimental and not visible by the TypeScript compiler
|
||||||
|
actions: [new (<any> chrome.declarativeContent).RequestContentScript({
|
||||||
|
allFrames: true,
|
||||||
|
js,
|
||||||
|
css
|
||||||
|
})]
|
||||||
|
};
|
||||||
|
|
||||||
|
chrome.declarativeContent.onPageChanged.addRules([rule]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the permission and content script registration.
|
||||||
|
*/
|
||||||
|
static removeExtraSiteRegistration() {
|
||||||
|
if (this.isFirefox()) {
|
||||||
|
let id = "invidious";
|
||||||
|
|
||||||
|
if (isBackgroundScript) {
|
||||||
|
if (contentScriptRegistrations[id]) {
|
||||||
|
contentScriptRegistrations[id].unregister();
|
||||||
|
delete contentScriptRegistrations[id];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
chrome.runtime.sendMessage({
|
||||||
|
message: "unregisterContentScript",
|
||||||
|
id: id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
chrome.declarativeContent.onPageChanged.removeRules(["invidious"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
chrome.permissions.remove({
|
||||||
|
origins: this.getInvidiousInstancesRegex()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static localizeHtmlPage() {
|
||||||
|
//Localize by replacing __MSG_***__ meta tags
|
||||||
|
var objects = document.getElementsByClassName("sponsorBlockPageBody")[0].children;
|
||||||
|
for (var j = 0; j < objects.length; j++) {
|
||||||
|
var obj = objects[j];
|
||||||
|
|
||||||
|
let localizedMessage = this.getLocalizedMessage(obj.innerHTML.toString());
|
||||||
|
if (localizedMessage) obj.innerHTML = localizedMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static getLocalizedMessage(text) {
|
||||||
|
var valNewH = text.replace(/__MSG_(\w+)__/g, function(match, v1) {
|
||||||
|
return v1 ? chrome.i18n.getMessage(v1) : "";
|
||||||
|
});
|
||||||
|
|
||||||
|
if(valNewH != text) {
|
||||||
|
return valNewH;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {String[]} Invidious Instances in regex form
|
||||||
|
*/
|
||||||
|
static getInvidiousInstancesRegex() {
|
||||||
|
var invidiousInstancesRegex = [];
|
||||||
|
for (const url of SB.config.invidiousInstances) {
|
||||||
|
invidiousInstancesRegex.push("https://*." + url + "/*");
|
||||||
|
invidiousInstancesRegex.push("http://*." + url + "/*");
|
||||||
|
}
|
||||||
|
|
||||||
|
return invidiousInstancesRegex;
|
||||||
|
}
|
||||||
|
|
||||||
|
static generateUserID(length = 36) {
|
||||||
|
let charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||||
|
let result = "";
|
||||||
|
if (window.crypto && window.crypto.getRandomValues) {
|
||||||
|
let values = new Uint32Array(length);
|
||||||
|
window.crypto.getRandomValues(values);
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
result += charset[values[i] % charset.length];
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
result += charset[Math.floor(Math.random() * charset.length)];
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the error message in a nice string
|
||||||
|
*
|
||||||
|
* @param {int} statusCode
|
||||||
|
* @returns {string} errorMessage
|
||||||
|
*/
|
||||||
|
static getErrorMessage(statusCode) {
|
||||||
|
let errorMessage = "";
|
||||||
|
|
||||||
|
if([400, 429, 409, 502, 0].includes(statusCode)) {
|
||||||
|
//treat them the same
|
||||||
|
if (statusCode == 503) statusCode = 502;
|
||||||
|
|
||||||
|
errorMessage = chrome.i18n.getMessage(statusCode + "") + " " + chrome.i18n.getMessage("errorCode") + statusCode
|
||||||
|
+ "\n\n" + chrome.i18n.getMessage("statusReminder");
|
||||||
|
} else {
|
||||||
|
errorMessage = chrome.i18n.getMessage("connectionError") + statusCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
return errorMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is this Firefox (web-extensions)
|
||||||
|
*/
|
||||||
|
static isFirefox() {
|
||||||
|
return typeof(browser) !== "undefined";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Utils;
|
||||||
12
tsconfig.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "commonjs",
|
||||||
|
"target": "es6",
|
||||||
|
"noImplicitAny": false,
|
||||||
|
"sourceMap": false,
|
||||||
|
"rootDir": "src",
|
||||||
|
"outDir": "dist/js",
|
||||||
|
"noEmitOnError": true,
|
||||||
|
"typeRoots": [ "node_modules/@types" ]
|
||||||
|
}
|
||||||
|
}
|
||||||
271
utils.js
@@ -1,271 +0,0 @@
|
|||||||
var isBackgroundScript = false;
|
|
||||||
var onInvidious = false;
|
|
||||||
|
|
||||||
// Function that can be used to wait for a condition before returning
|
|
||||||
async function wait(condition, timeout = 5000, check = 100) {
|
|
||||||
return await new Promise((resolve, reject) => {
|
|
||||||
setTimeout(() => reject("TIMEOUT"), timeout);
|
|
||||||
|
|
||||||
let intervalCheck = () => {
|
|
||||||
let result = condition();
|
|
||||||
if (result !== false) {
|
|
||||||
resolve(result);
|
|
||||||
clearInterval(interval);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
let interval = setInterval(intervalCheck, check);
|
|
||||||
|
|
||||||
//run the check once first, this speeds it up a lot
|
|
||||||
intervalCheck();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getYouTubeVideoID(url) {
|
|
||||||
// For YouTube TV support
|
|
||||||
if(url.startsWith("https://www.youtube.com/tv#/")) url = url.replace("#", "");
|
|
||||||
|
|
||||||
//Attempt to parse url
|
|
||||||
let urlObject = null;
|
|
||||||
try {
|
|
||||||
urlObject = new URL(url);
|
|
||||||
} catch (e) {
|
|
||||||
console.error("[SB] Unable to parse URL: " + url);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Check if valid hostname
|
|
||||||
if (SB.config && SB.config.invidiousInstances.includes(urlObject.host)) {
|
|
||||||
onInvidious = true;
|
|
||||||
} else if (!["www.youtube.com", "www.youtube-nocookie.com"].includes(urlObject.host)) {
|
|
||||||
if (!SB.config) {
|
|
||||||
// Call this later, in case this is an Invidious tab
|
|
||||||
wait(() => SB.config !== undefined).then(() => videoIDChange(getYouTubeVideoID(url)));
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
//Get ID from searchParam
|
|
||||||
if (urlObject.searchParams.has("v") && ["/watch", "/watch/"].includes(urlObject.pathname) || urlObject.pathname.startsWith("/tv/watch")) {
|
|
||||||
id = urlObject.searchParams.get("v");
|
|
||||||
return id.length == 11 ? id : false;
|
|
||||||
} else if (urlObject.pathname.startsWith("/embed/")) {
|
|
||||||
try {
|
|
||||||
return urlObject.pathname.substr(7, 11);
|
|
||||||
} catch (e) {
|
|
||||||
console.error("[SB] Video ID not valid for " + url);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Asks for the optional permissions required for all extra sites.
|
|
||||||
* It also starts the content script registrations.
|
|
||||||
*
|
|
||||||
* For now, it is just SB.config.invidiousInstances.
|
|
||||||
*
|
|
||||||
* @param {CallableFunction} callback
|
|
||||||
*/
|
|
||||||
function setupExtraSitePermissions(callback) {
|
|
||||||
// Request permission
|
|
||||||
let permissions = ["declarativeContent"];
|
|
||||||
if (isFirefox()) permissions = [];
|
|
||||||
|
|
||||||
chrome.permissions.request({
|
|
||||||
origins: getInvidiousInstancesRegex(),
|
|
||||||
permissions: permissions
|
|
||||||
}, async function (granted) {
|
|
||||||
if (granted) {
|
|
||||||
setupExtraSiteContentScripts();
|
|
||||||
} else {
|
|
||||||
removeExtraSiteRegistration();
|
|
||||||
}
|
|
||||||
|
|
||||||
callback(granted);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registers the content scripts for the extra sites.
|
|
||||||
* Will use a different method depending on the browser.
|
|
||||||
* This is called by setupExtraSitePermissions().
|
|
||||||
*
|
|
||||||
* For now, it is just SB.config.invidiousInstances.
|
|
||||||
*/
|
|
||||||
function setupExtraSiteContentScripts() {
|
|
||||||
let js = [
|
|
||||||
"config.js",
|
|
||||||
"SB.js",
|
|
||||||
"utils/previewBar.js",
|
|
||||||
"utils/skipNotice.js",
|
|
||||||
"utils.js",
|
|
||||||
"content.js",
|
|
||||||
"popup.js"
|
|
||||||
];
|
|
||||||
let css = [
|
|
||||||
"content.css",
|
|
||||||
"./libs/Source+Sans+Pro.css",
|
|
||||||
"popup.css"
|
|
||||||
];
|
|
||||||
|
|
||||||
if (isFirefox()) {
|
|
||||||
let firefoxJS = [];
|
|
||||||
for (const file of js) {
|
|
||||||
firefoxJS.push({file});
|
|
||||||
}
|
|
||||||
let firefoxCSS = [];
|
|
||||||
for (const file of css) {
|
|
||||||
firefoxCSS.push({file});
|
|
||||||
}
|
|
||||||
|
|
||||||
let registration = {
|
|
||||||
message: "registerContentScript",
|
|
||||||
id: "invidious",
|
|
||||||
allFrames: true,
|
|
||||||
js: firefoxJS,
|
|
||||||
css: firefoxCSS,
|
|
||||||
matches: getInvidiousInstancesRegex()
|
|
||||||
};
|
|
||||||
|
|
||||||
if (isBackgroundScript) {
|
|
||||||
registerFirefoxContentScript(registration);
|
|
||||||
} else {
|
|
||||||
chrome.runtime.sendMessage(registration);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
chrome.declarativeContent.onPageChanged.removeRules(["invidious"], function() {
|
|
||||||
let conditions = [];
|
|
||||||
for (const regex of getInvidiousInstancesRegex()) {
|
|
||||||
conditions.push(new chrome.declarativeContent.PageStateMatcher({
|
|
||||||
pageUrl: { urlMatches: regex }
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add page rule
|
|
||||||
let rule = {
|
|
||||||
id: "invidious",
|
|
||||||
conditions,
|
|
||||||
actions: [new chrome.declarativeContent.RequestContentScript({
|
|
||||||
allFrames: true,
|
|
||||||
js,
|
|
||||||
css
|
|
||||||
})]
|
|
||||||
};
|
|
||||||
|
|
||||||
chrome.declarativeContent.onPageChanged.addRules([rule]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes the permission and content script registration.
|
|
||||||
*/
|
|
||||||
function removeExtraSiteRegistration() {
|
|
||||||
if (isFirefox()) {
|
|
||||||
let id = "invidious";
|
|
||||||
|
|
||||||
if (isBackgroundScript) {
|
|
||||||
if (contentScriptRegistrations[id]) {
|
|
||||||
contentScriptRegistrations[id].unregister();
|
|
||||||
delete contentScriptRegistrations[id];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
chrome.runtime.sendMessage({
|
|
||||||
message: "unregisterContentScript",
|
|
||||||
id: id
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
chrome.declarativeContent.onPageChanged.removeRules(["invidious"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
chrome.permissions.remove({
|
|
||||||
origins: getInvidiousInstancesRegex()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function localizeHtmlPage() {
|
|
||||||
//Localize by replacing __MSG_***__ meta tags
|
|
||||||
var objects = document.getElementsByClassName("sponsorBlockPageBody")[0].children;
|
|
||||||
for (var j = 0; j < objects.length; j++) {
|
|
||||||
var obj = objects[j];
|
|
||||||
|
|
||||||
let localizedMessage = getLocalizedMessage(obj.innerHTML.toString());
|
|
||||||
if (localizedMessage) obj.innerHTML = localizedMessage;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getLocalizedMessage(text) {
|
|
||||||
var valNewH = text.replace(/__MSG_(\w+)__/g, function(match, v1) {
|
|
||||||
return v1 ? chrome.i18n.getMessage(v1) : "";
|
|
||||||
});
|
|
||||||
|
|
||||||
if(valNewH != text) {
|
|
||||||
return valNewH;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {String[]} Invidious Instances in regex form
|
|
||||||
*/
|
|
||||||
function getInvidiousInstancesRegex() {
|
|
||||||
var invidiousInstancesRegex = [];
|
|
||||||
for (const url of SB.config.invidiousInstances) {
|
|
||||||
invidiousInstancesRegex.push("https://*." + url + "/*");
|
|
||||||
invidiousInstancesRegex.push("http://*." + url + "/*");
|
|
||||||
}
|
|
||||||
|
|
||||||
return invidiousInstancesRegex;
|
|
||||||
}
|
|
||||||
|
|
||||||
function generateUserID(length = 36) {
|
|
||||||
let charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
||||||
let result = "";
|
|
||||||
if (window.crypto && window.crypto.getRandomValues) {
|
|
||||||
values = new Uint32Array(length);
|
|
||||||
window.crypto.getRandomValues(values);
|
|
||||||
for (i = 0; i < length; i++) {
|
|
||||||
result += charset[values[i] % charset.length];
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
} else {
|
|
||||||
for (let i = 0; i < length; i++) {
|
|
||||||
result += charset[Math.floor(Math.random() * charset.length)];
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the error message in a nice string
|
|
||||||
*
|
|
||||||
* @param {int} statusCode
|
|
||||||
* @returns {string} errorMessage
|
|
||||||
*/
|
|
||||||
function getErrorMessage(statusCode) {
|
|
||||||
let errorMessage = "";
|
|
||||||
|
|
||||||
if([400, 429, 409, 502, 0].includes(statusCode)) {
|
|
||||||
//treat them the same
|
|
||||||
if (statusCode == 503) statusCode = 502;
|
|
||||||
|
|
||||||
errorMessage = chrome.i18n.getMessage(statusCode + "") + " " + chrome.i18n.getMessage("errorCode") + statusCode
|
|
||||||
+ "\n\n" + chrome.i18n.getMessage("statusReminder");
|
|
||||||
} else {
|
|
||||||
errorMessage = chrome.i18n.getMessage("connectionError") + statusCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
return errorMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Is this Firefox (web-extensions)
|
|
||||||
*/
|
|
||||||
function isFirefox() {
|
|
||||||
return typeof(browser) !== "undefined";
|
|
||||||
}
|
|
||||||
43
webpack/webpack.common.js
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
const webpack = require("webpack");
|
||||||
|
const path = require('path');
|
||||||
|
const CopyPlugin = require('copy-webpack-plugin');
|
||||||
|
const srcDir = '../src/';
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
entry: {
|
||||||
|
popup: path.join(__dirname, srcDir + 'popup.ts'),
|
||||||
|
background: path.join(__dirname, srcDir + 'background.ts'),
|
||||||
|
content_script: path.join(__dirname, srcDir + 'content_script.ts')
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
path: path.join(__dirname, '../dist/js'),
|
||||||
|
filename: '[name].js'
|
||||||
|
},
|
||||||
|
optimization: {
|
||||||
|
splitChunks: {
|
||||||
|
name: 'vendor',
|
||||||
|
chunks: "initial"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.tsx?$/,
|
||||||
|
use: 'ts-loader',
|
||||||
|
exclude: /node_modules/
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
extensions: ['.ts', '.tsx', '.js']
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
// exclude locale files in moment
|
||||||
|
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
|
||||||
|
new CopyPlugin([
|
||||||
|
{ from: '.', to: '../' }
|
||||||
|
],
|
||||||
|
{context: 'public' }
|
||||||
|
),
|
||||||
|
]
|
||||||
|
};
|
||||||
7
webpack/webpack.dev.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
const merge = require('webpack-merge');
|
||||||
|
const common = require('./webpack.common.js');
|
||||||
|
|
||||||
|
module.exports = merge(common, {
|
||||||
|
devtool: 'inline-source-map',
|
||||||
|
mode: 'development'
|
||||||
|
});
|
||||||
6
webpack/webpack.prod.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
const merge = require('webpack-merge');
|
||||||
|
const common = require('./webpack.common.js');
|
||||||
|
|
||||||
|
module.exports = merge(common, {
|
||||||
|
mode: 'production'
|
||||||
|
});
|
||||||