Merge branch 'master' of https://github.com/ajayyy/SponsorBlock into min-duration

# Conflicts:
#	SB.js
#	src/options.ts
This commit is contained in:
Ajay Ramachandran
2020-02-08 19:09:17 -05:00
62 changed files with 6723 additions and 982 deletions

View File

@@ -14,25 +14,21 @@ jobs:
- uses: actions/setup-node@v1
- run: npm install
- name: Copy configuration
run: cp config.js.example config.js
run: cp config.json.example config.json
# Create Chrome artifacts
- name: Create Chrome artifacts
run: npm run build
run: npm run build:chrome
- uses: actions/upload-artifact@v1
with:
name: Chrome Extension
path: web-ext-artifacts
path: dist
# Create Firefox artifacts
- name: Move manifest
run: mv manifest.json manifest.json.original
- name: Combine manifest for Firefox
run: jq -s '.[0] * .[1]' manifest.json.original firefox_manifest-extra.json > manifest.json
- name: Create Firefox artifacts
run: npm run build
run: npm run build:firefox
- uses: actions/upload-artifact@v1
with:
name: Firefox Extension
path: web-ext-artifacts
path: dist

4
.gitignore vendored
View File

@@ -1,6 +1,8 @@
config.js
config.json
ignored
.idea/
node_modules
web-ext-artifacts
.vscode/
dist/
tmp/

View File

@@ -1,5 +1,5 @@
<p align="center">
<a href="https://sponsor.ajay.app"><img src="icons/LogoSponsorBlocker256px.png" alt="Logo"></img></a>
<a href="https://sponsor.ajay.app"><img src="public/icons/LogoSponsorBlocker256px.png" alt="Logo"></img></a>
<br/>
<sub>Logo by <a href="https://github.com/munadikieh">@munadikieh</a></sub>
@@ -40,7 +40,7 @@ The backend server code is available here: https://github.com/ajayyy/SponsorBloc
It is a simple Sqlite database that will hold all the timing data.
To make sure that this project doesn't die, I have made the database publicly downloadable at https://sponsor.ajay.app/database.db. So, you can download a backup or get archive.org to take a backup for you if you want.
To make sure that this project doesn't die, I have made the database publicly downloadable at https://api.sponsor.ajay.app/database.db. So, you can download a backup or get archive.org to take a backup for you if you want.
Hopefully this project can be combined with projects like [this](https://github.com/Sponsoff/sponsorship_remover) and use this data to create a neural network to predict when sponsored segments happen. That project is sadly abandoned now, so I have decided to attempt to revive this idea.
@@ -50,17 +50,21 @@ You can read the API docs [here](https://github.com/ajayyy/SponsorBlockServer#ap
# Build Yourself
You can load this project as an unpacked extension. Make sure to rename the `config.js.example` file to `config.js` before installing.
You can load this project as an unpacked extension. Make sure to rename the `config.json.example` file to `config.json` before installing.
There are also other build scripts available. Install `npm`, then run `npm install` in the repository.
## Developing with a clean profile
Run `npm run dev` to run the extension using a clean browser profile with hot reloading [(by default Firefox)](https://hacks.mozilla.org/2019/10/developing-cross-browser-extensions-with-web-ext-3-2-0/). This uses [`web-ext run`](https://extensionworkshop.com/documentation/develop/web-ext-command-reference/#commands).
Run `npm run dev` to run the extension using a clean browser profile with hot reloading. Use `npm run dev:firefox` for Firefox. This uses [`web-ext run`](https://extensionworkshop.com/documentation/develop/web-ext-command-reference/#commands).
## Packing
Run `npm run build` to generate a packed extension.
Run `npm run build` to generate a packed Chrome extension.
Use `npm run build:firefox` to generate a Firefox extension.
The result is in `dist`.
# Credit

View File

@@ -1,3 +0,0 @@
//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";

4
config.json.example Normal file
View File

@@ -0,0 +1,4 @@
{
"serverAddress": "https://sponsor.ajay.app",
"serverAddressComment": "This specifies the default SponsorBlock server to conect to"
}

BIN
icon.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

8
jest.config.js Normal file
View File

@@ -0,0 +1,8 @@
module.exports = {
"roots": [
"src"
],
"transform": {
"^.+\\.ts$": "ts-jest"
},
};

View File

@@ -1,80 +0,0 @@
{
"name": "__MSG_fullName__",
"short_name": "__MSG_Name__",
"version": "1.2.5",
"default_locale": "en",
"description": "__MSG_Description__",
"content_scripts": [
{
"run_at": "document_start",
"matches": [
"https://*.youtube.com/*",
"https://www.youtube-nocookie.com/embed/*"
],
"all_frames": true,
"js": [
"config.js",
"SB.js",
"utils/previewBar.js",
"utils/skipNotice.js",
"utils.js",
"content.js",
"popup.js"
],
"css": [
"content.css",
"./libs/Source+Sans+Pro.css",
"popup.css"
]
}
],
"web_accessible_resources": [
"icons/LogoSponsorBlocker256px.png",
"icons/IconSponsorBlocker256px.png",
"icons/PlayerStartIconSponsorBlocker256px.png",
"icons/PlayerStopIconSponsorBlocker256px.png",
"icons/PlayerUploadIconSponsorBlocker256px.png",
"icons/PlayerUploadFailedIconSponsorBlocker256px.png",
"icons/upvote.png",
"icons/downvote.png",
"icons/report.png",
"icons/close.png",
"icons/PlayerInfoIconSponsorBlocker256px.png",
"icons/PlayerDeleteIconSponsorBlocker256px.png",
"popup.html"
],
"permissions": [
"storage",
"notifications",
"https://sponsor.ajay.app/*"
],
"optional_permissions": [
"*://*/*",
"declarativeContent"
],
"browser_action": {
"default_title": "__MSG_Name__",
"default_popup": "popup.html"
},
"background": {
"scripts":[
"config.js",
"SB.js",
"utils.js",
"background.js"
],
"persistent": false
},
"icons": {
"16": "icons/IconSponsorBlocker16px.png",
"32": "icons/IconSponsorBlocker32px.png",
"64": "icons/LogoSponsorBlocker64px.png",
"128": "icons/LogoSponsorBlocker128px.png",
"256": "icons/LogoSponsorBlocker256px.png"
},
"options_ui": {
"page": "options/options.html",
"open_in_tab": true
},
"manifest_version": 2
}

View File

@@ -0,0 +1,8 @@
{
"optional_permissions": [
"declarativeContent"
],
"background": {
"persistent": false
}
}

71
manifest/manifest.json Normal file
View File

@@ -0,0 +1,71 @@
{
"name": "__MSG_fullName__",
"short_name": "__MSG_Name__",
"version": "1.2.5",
"default_locale": "en",
"description": "__MSG_Description__",
"content_scripts": [
{
"run_at": "document_start",
"matches": [
"https://*.youtube.com/*",
"https://www.youtube-nocookie.com/embed/*"
],
"all_frames": true,
"js": [
"./js/vendor.js",
"./js/content.js"
],
"css": [
"content.css",
"./libs/Source+Sans+Pro.css",
"popup.css"
]
}
],
"web_accessible_resources": [
"icons/LogoSponsorBlocker256px.png",
"icons/IconSponsorBlocker256px.png",
"icons/PlayerStartIconSponsorBlocker256px.png",
"icons/PlayerStopIconSponsorBlocker256px.png",
"icons/PlayerUploadIconSponsorBlocker256px.png",
"icons/PlayerUploadFailedIconSponsorBlocker256px.png",
"icons/upvote.png",
"icons/downvote.png",
"icons/report.png",
"icons/close.png",
"icons/PlayerInfoIconSponsorBlocker256px.png",
"icons/PlayerDeleteIconSponsorBlocker256px.png",
"popup.html"
],
"permissions": [
"storage",
"notifications",
"https://sponsor.ajay.app/*"
],
"optional_permissions": [
"*://*/*"
],
"browser_action": {
"default_title": "__MSG_Name__",
"default_popup": "popup.html"
},
"background": {
"scripts":[
"./js/vendor.js",
"./js/background.js"
]
},
"icons": {
"16": "icons/IconSponsorBlocker16px.png",
"32": "icons/IconSponsorBlocker32px.png",
"64": "icons/LogoSponsorBlocker64px.png",
"128": "icons/LogoSponsorBlocker128px.png",
"256": "icons/LogoSponsorBlocker256px.png"
},
"options_ui": {
"page": "options/options.html",
"open_in_tab": true
},
"manifest_version": 2
}

5405
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,14 +3,39 @@
"version": "1.0.0",
"description": "",
"main": "background.js",
"dependencies": {},
"dependencies": {
"concurrently": "^5.1.0"
},
"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": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "web-ext run --start-url https://chrome.google.com/webstore/detail/ublock-origin/cjpalhdlnbpafiamejdnhcphjbkeiagm",
"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)\""
"web-run": "npm run web-run:chrome",
"web-run:firefox": "cd dist && web-ext run --start-url https://chrome.google.com/webstore/detail/ublock-origin/cjpalhdlnbpafiamejdnhcphjbkeiagm",
"web-run:chrome": "cd dist && web-ext run --start-url https://chrome.google.com/webstore/detail/ublock-origin/cjpalhdlnbpafiamejdnhcphjbkeiagm -t chromium",
"build": "npm run build:chrome",
"build:chrome": "webpack --env.browser=chrome --config webpack/webpack.prod.js",
"build:firefox": "webpack --env.browser=firefox --config webpack/webpack.prod.js",
"build:watch": "npm run build:watch:chrome",
"build:watch:chrome": "webpack --env.browser=chrome --config webpack/webpack.dev.js --watch",
"build:watch:firefox": "webpack --env.browser=firefox --config webpack/webpack.dev.js --watch",
"dev": "npm run build && concurrently \"npm run web-run\" \"npm run build:watch\"",
"dev:firefox": "npm run build:firefox && concurrently \"npm run web-run:firefox\" \"npm run build:watch:firefox\"",
"clean": "rimraf dist",
"test": "npx jest"
},
"repository": {
"type": "git",

View File

@@ -10,7 +10,7 @@
<body>
<div id="title">
<img src="https://github.com/ajayyy/SponsorBlock/raw/master/icons/LogoSponsorBlocker256px.png" height="80" class="profilepic"/>
<img src="../icons/LogoSponsorBlocker256px.png" height="80" class="profilepic"/>
SponsorBlock
</div>

View File

Before

Width:  |  Height:  |  Size: 551 B

After

Width:  |  Height:  |  Size: 551 B

View File

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 7.1 KiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

View File

Before

Width:  |  Height:  |  Size: 83 KiB

After

Width:  |  Height:  |  Size: 83 KiB

View File

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

View File

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@@ -6,9 +6,8 @@
<link href="options.css" rel="stylesheet"/>
<script src="../utils.js"></script>
<script src="../SB.js"></script>
<script src="options.js"></script>
<script src="../js/vendor.js"></script>
<script src="../js/options.js"></script>
</head>
<body class="sponsorBlockPageBody">

View File

@@ -1,9 +1,8 @@
<html>
<head>
<title>__MSG_openPopup__</title>
<script src="SB.js"></script>
<link id="sponorBlockPopupFont" rel="stylesheet" type="text/css" href="/libs/Source+Sans+Pro.css"/>
<link id="sponorBlockStyleSheet" rel="stylesheet" type="text/css" href="popup.css"/>
<link id="sponsorBlockPopupFont" rel="stylesheet" type="text/css" href="/libs/Source+Sans+Pro.css"/>
<link id="sponsorBlockStyleSheet" rel="stylesheet" type="text/css" href="popup.css"/>
</head>
<body class="popupBody">
@@ -146,7 +145,7 @@
<span id="sponsorTimesSkipsDoneDisplay" class="popupElement">
0
</span>
<span id="sponsorTimesSkipsDoneEndWord" class="popupElement">__MSG_Segments__</span> since December 5th.
<span id="sponsorTimesSkipsDoneEndWord" class="popupElement">__MSG_Segments__</span> (since December).
</div>
<div id="sponsorTimeSavedContainer" class="popupElement" style="display: none">
@@ -154,7 +153,7 @@
<span id="sponsorTimeSavedDisplay" class="popupElement">
0
</span>
<span id="sponsorTimeSavedEndWord" class="popupElement">__MSG_minsLower__</span> since December 5th.
<span id="sponsorTimeSavedEndWord" class="popupElement">__MSG_minsLower__</span> (since December).
</br/>
</br/>
@@ -210,7 +209,6 @@
</body>
<!-- Scripts that need to load after the html -->
<script src="config.js"></script>
<script src="utils.js"></script>
<script src="popup.js"></script>
<script src="./js/vendor.js"></script>
<script src="./js/popup.js"></script>
</html>

View File

@@ -1,12 +1,19 @@
isBackgroundScript = true;
import * as Types from "./types";
import Config from "./config";
import Utils from "./utils";
var utils = new Utils({
registerFirefoxContentScript,
unregisterFirefoxContentScript
});
// Used only on Firefox, which does not support non persistent background pages.
var contentScriptRegistrations = {};
// Register content script if needed
if (isFirefox()) {
wait(() => SB.config !== undefined).then(function() {
if (SB.config.supportInvidious) setupExtraSiteContentScripts();
if (utils.isFirefox()) {
utils.wait(() => Config.config !== null).then(function() {
if (Config.config.supportInvidious) utils.setupExtraSiteContentScripts();
});
}
@@ -35,8 +42,8 @@ chrome.runtime.onMessage.addListener(function (request, sender, callback) {
case "getSponsorTimes":
getSponsorTimes(request.videoID, function(sponsorTimes) {
callback({
sponsorTimes: sponsorTimes
})
sponsorTimes
});
});
//this allows the callback to be called later
@@ -57,9 +64,7 @@ chrome.runtime.onMessage.addListener(function (request, sender, callback) {
registerFirefoxContentScript(request);
return false;
case "unregisterContentScript":
contentScriptRegistrations[request.id].unregister();
delete contentScriptRegistrations[request.id];
unregisterFirefoxContentScript(request.id)
return false;
}
});
@@ -69,7 +74,7 @@ chrome.runtime.onInstalled.addListener(function (object) {
// This let's the config sync to run fully before checking.
// This is required on Firefox
setTimeout(function() {
const userID = SB.config.userID;
const userID = Config.config.userID;
// If there is no userID, then it is the first install.
if (!userID){
@@ -77,13 +82,13 @@ chrome.runtime.onInstalled.addListener(function (object) {
chrome.tabs.create({url: chrome.extension.getURL("/help/index_en.html")});
//generate a userID
const newUserID = generateUserID();
const newUserID = utils.generateUserID();
//save this UUID
SB.config.userID = newUserID;
Config.config.userID = newUserID;
//TODO: Remove when invidious support is old
// Don't show this to new users
SB.config.invidiousUpdateInfoShowCount = 6;
Config.config.invidiousUpdateInfoShowCount = 6;
}
}, 1500);
});
@@ -106,10 +111,20 @@ function registerFirefoxContentScript(options) {
}).then((registration) => void (contentScriptRegistrations[options.id] = registration));
}
/**
* Only works on Firefox.
* Firefox requires that this is handled by the background script
*
*/
function unregisterFirefoxContentScript(id: string) {
contentScriptRegistrations[id].unregister();
delete contentScriptRegistrations[id];
}
//gets the sponsor times from memory
function getSponsorTimes(videoID, callback) {
let sponsorTimes = [];
let sponsorTimesStorage = SB.config.sponsorTimes.get(videoID);
let sponsorTimesStorage = Config.config.sponsorTimes.get(videoID);
if (sponsorTimesStorage != undefined && sponsorTimesStorage.length > 0) {
sponsorTimes = sponsorTimesStorage;
@@ -133,22 +148,22 @@ function addSponsorTime(time, videoID, callback) {
}
//save this info
SB.config.sponsorTimes.set(videoID, sponsorTimes);
Config.config.sponsorTimes.set(videoID, sponsorTimes);
callback();
});
}
function submitVote(type, UUID, callback) {
let userID = SB.config.userID;
let userID = Config.config.userID;
if (userID == undefined || userID === "undefined") {
//generate one
userID = generateUserID();
SB.config.userID = userID;
userID = utils.generateUserID();
Config.config.userID = userID;
}
//publish this vote
sendRequestToServer("POST", "/api/voteOnSponsorTime?UUID=" + UUID + "&userID=" + userID + "&type=" + type, function(xmlhttp, error) {
utils.sendRequestToServer("POST", "/api/voteOnSponsorTime?UUID=" + UUID + "&userID=" + userID + "&type=" + type, function(xmlhttp, error) {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
callback({
successType: 1
@@ -172,11 +187,11 @@ function submitVote(type, UUID, callback) {
async function submitTimes(videoID, callback) {
//get the video times from storage
let sponsorTimes = SB.config.sponsorTimes.get(videoID);
let userID = SB.config.userID;
let sponsorTimes = Config.config.sponsorTimes.get(videoID);
let userID = Config.config.userID;
if (sponsorTimes != undefined && sponsorTimes.length > 0) {
let durationResult = await new Promise((resolve, reject) => {
let durationResult = <Types.videoDurationResponse> await new Promise((resolve, reject) => {
chrome.tabs.query({
active: true,
currentWindow: true
@@ -200,7 +215,7 @@ async function submitTimes(videoID, callback) {
let increasedContributionAmount = false;
//submit the sponsorTime
sendRequestToServer("GET", "/api/postVideoSponsorTimes?videoID=" + videoID + "&startTime=" + sponsorTimes[i][0] + "&endTime=" + sponsorTimes[i][1]
utils.sendRequestToServer("GET", "/api/postVideoSponsorTimes?videoID=" + videoID + "&startTime=" + sponsorTimes[i][0] + "&endTime=" + sponsorTimes[i][1]
+ "&userID=" + userID, function(xmlhttp, error) {
if (xmlhttp.readyState == 4 && !error) {
callback({
@@ -208,39 +223,18 @@ async function submitTimes(videoID, callback) {
});
if (xmlhttp.status == 200) {
//add these to the storage log
currentContributionAmount = SB.config.sponsorTimesContributed;
//save the amount contributed
if (!increasedContributionAmount) {
increasedContributionAmount = true;
SB.config.sponsorTimesContributed = currentContributionAmount + sponsorTimes.length;
}
Config.config.sponsorTimesContributed = Config.config.sponsorTimesContributed + sponsorTimes.length;
}
} else if (error) {
callback({
statusCode: -1
});
}
}
});
}
}
}
function sendRequestToServer(type, address, callback) {
let xmlhttp = new XMLHttpRequest();
xmlhttp.open(type, serverAddress + address, true);
if (callback != undefined) {
xmlhttp.onreadystatechange = function () {
callback(xmlhttp, false);
};
xmlhttp.onerror = function(ev) {
callback(xmlhttp, true);
};
}
//submit this request
xmlhttp.send();
}

243
src/config.ts Normal file
View File

@@ -0,0 +1,243 @@
interface SBConfig {
userID: string,
sponsorTimes: SBMap<string, any>,
whitelistedChannels: Array<any>,
startSponsorKeybind: string,
submitKeybind: string,
minutesSaved: number,
skipCount: number,
sponsorTimesContributed: number,
disableSkipping: boolean,
disableAutoSkip: boolean,
trackViewCount: boolean,
dontShowNotice: boolean,
hideVideoPlayerControls: boolean,
hideInfoButtonPlayerControls: boolean,
hideDeleteButtonPlayerControls: boolean,
hideDiscordLaunches: number,
hideDiscordLink: boolean,
invidiousInstances: string[],
invidiousUpdateInfoShowCount: number,
autoUpvote: boolean,
supportInvidious: false
}
interface SBObject {
configListeners: Array<Function>;
defaults: SBConfig;
localConfig: SBConfig;
config: SBConfig;
}
// Allows a SBMap to be conveted into json form
// Currently used for local storage
class SBMap<T, U> extends Map {
id: string;
constructor(id: string, entries?: [T, U][]) {
super();
this.id = id;
// Import all entries if they were given
if (entries !== undefined) {
for (const item of entries) {
this.set(item[0], item[1])
}
}
}
set(key, value) {
const result = super.set(key, value);
// Store updated SBMap locally
chrome.storage.sync.set({
[this.id]: encodeStoredItem(this)
});
return result;
}
delete(key) {
const result = super.delete(key);
// Store updated SBMap locally
chrome.storage.sync.set({
[this.id]: encodeStoredItem(this)
});
return result;
}
clear() {
const result = super.clear();
chrome.storage.sync.set({
[this.id]: encodeStoredItem(this)
});
return result;
}
toJSON() {
return Array.from(this.entries());
}
}
var Config: SBObject = {
/**
* Callback function when an option is updated
*/
configListeners: [],
defaults: {
userID: null,
sponsorTimes: new SBMap("sponsorTimes"),
whitelistedChannels: [],
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,
supportInvidious: false
},
localConfig: null,
config: null
};
// Function setup
/**
* A SBMap cannot be stored in the chrome storage.
* This data will be encoded into an array instead as specified by the toJSON function.
*
* @param data
*/
function encodeStoredItem(data) {
// if data is SBMap convert to json for storing
if(!(data instanceof SBMap)) return data;
return JSON.stringify(data);
}
/**
* An SBMap cannot be stored in the chrome storage.
* This data will be decoded from the array it is stored in
*
* @param {*} data
*/
function decodeStoredItem(id: string, data) {
if(typeof data !== "string") return data;
try {
let str = JSON.parse(data);
if(!Array.isArray(str)) return data;
return new SBMap(id, str);
} catch(e) {
// If all else fails, return the data
return data;
}
}
function configProxy(): any {
chrome.storage.onChanged.addListener((changes, namespace) => {
for (const key in changes) {
Config.localConfig[key] = decodeStoredItem(key, changes[key].newValue);
}
for (const callback of Config.configListeners) {
callback(changes);
}
});
var handler: ProxyHandler<any> = {
set(obj, prop, value) {
Config.localConfig[prop] = value;
chrome.storage.sync.set({
[prop]: encodeStoredItem(value)
});
return true;
},
get(obj, prop): any {
let data = Config.localConfig[prop];
return obj[prop] || data;
},
deleteProperty(obj, prop) {
chrome.storage.sync.remove(<string> prop);
return true;
}
};
return new Proxy({handler}, handler);
}
function fetchConfig() {
return new Promise((resolve, reject) => {
chrome.storage.sync.get(null, function(items) {
Config.localConfig = <SBConfig> <unknown> items; // Data is ready
resolve();
});
});
}
function migrateOldFormats() { // Convert sponsorTimes format
for (const key in Config.localConfig) {
if (key.startsWith("sponsorTimes") && key !== "sponsorTimes" && key !== "sponsorTimesContributed") {
Config.config.sponsorTimes.set(key.substr(12), Config.config[key]);
delete Config.config[key];
}
}
}
async function setupConfig() {
await fetchConfig();
addDefaults();
convertJSON();
Config.config = configProxy();
migrateOldFormats();
}
// Reset config
function resetConfig() {
Config.config = Config.defaults;
};
function convertJSON() {
Object.keys(Config.defaults).forEach(key => {
Config.localConfig[key] = decodeStoredItem(key, Config.localConfig[key]);
});
}
// Add defaults
function addDefaults() {
for (const key in Config.defaults) {
if(!Config.localConfig.hasOwnProperty(key)) {
Config.localConfig[key] = Config.defaults[key];
}
}
};
// Sync config
setupConfig();
export default Config;

View File

@@ -1,3 +1,13 @@
import Config from "./config";
import Utils from "./utils";
var utils = new Utils();
import runThePopup from "./popup";
import PreviewBar from "./js-components/previewBar";
import SkipNotice from "./js-components/skipNotice";
//was sponsor data found when doing SponsorsLookup
var sponsorDataFound = false;
var previousVideoID = null;
@@ -16,7 +26,7 @@ var sponsorSkipped = [];
//the video
var v;
var listenerAdded;
var onInvidious;
//the video id of the last preview bar update
var lastPreviewBarUpdate;
@@ -39,8 +49,8 @@ var previewBar = null;
//the player controls on the YouTube player
var controls = null;
// Direct Links
videoIDChange(getYouTubeVideoID(document.URL));
// Direct Links after the config is loaded
utils.wait(() => Config.config !== null).then(() => videoIDChange(getYouTubeVideoID(document.URL)));
//the last time looked at (used to see if this time is in the interval)
var lastTime = -1;
@@ -65,10 +75,23 @@ var sponsorTimesSubmitting = [];
//this is used to close the popup on YouTube when the other popup opens
var popupInitialised = false;
// Contains all of the functions and variables needed by the skip notice
var skipNoticeContentContainer = () => ({
vote,
dontShowNoticeAgain,
unskipSponsorTime,
sponsorTimes,
UUIDs,
v,
reskipSponsorTime,
hiddenSponsorTimes,
updatePreviewBar
});
//get messages from the background script and the popup
chrome.runtime.onMessage.addListener(messageListener);
function messageListener(request, sender, sendResponse) {
function messageListener(request: any, sender: any, sendResponse: (response: any) => void): void {
//messages from popup script
switch(request.message){
case "update":
@@ -160,27 +183,26 @@ function contentConfigUpdateListener(changes) {
}
}
if (!SB.configListeners.includes(contentConfigUpdateListener)) {
SB.configListeners.push(contentConfigUpdateListener);
if (!Config.configListeners.includes(contentConfigUpdateListener)) {
Config.configListeners.push(contentConfigUpdateListener);
}
//check for hotkey pressed
document.onkeydown = async function(e){
e = e || window.event;
document.onkeydown = function(e: KeyboardEvent){
var key = e.key;
let video = document.getElementById("movie_player");
let startSponsorKey = SB.config.startSponsorKeybind;
let startSponsorKey = Config.config.startSponsorKeybind;
let submitKey = SB.config.submitKeybind;
let submitKey = Config.config.submitKeybind;
//is the video in focus, otherwise they could be typing a comment
if (document.activeElement === video) {
if(key == startSponsorKey.startSponsorKeybind){
if(key == startSponsorKey){
//semicolon
startSponsorClicked();
} else if (key == submitKey.submitKeybind) {
} else if (key == submitKey) {
//single quote
submitSponsorTimes();
}
@@ -217,13 +239,15 @@ function videoIDChange(id) {
//id is not valid
if (!id) return;
let channelIDPromise = wait(getChannelID);
// TODO: Use a better method here than using type any
// This is done to be able to do channelIDPromise.isFulfilled and channelIDPromise.isRejected
let channelIDPromise: any = utils.wait(getChannelID);
channelIDPromise.then(() => channelIDPromise.isFulfilled = true).catch(() => channelIDPromise.isRejected = true);
//setup the preview bar
if (previewBar == null) {
//create it
wait(getControls).then(result => {
utils.wait(getControls).then(result => {
const progressElementSelectors = [
// For YouTube
"ytp-progress-bar-container",
@@ -246,7 +270,7 @@ function videoIDChange(id) {
//warn them if they had unsubmitted times
if (previousVideoID != null) {
//get the sponsor times from storage
let sponsorTimes = SB.config.sponsorTimes.get(previousVideoID);
let sponsorTimes = Config.config.sponsorTimes.get(previousVideoID);
if (sponsorTimes != undefined && sponsorTimes.length > 0) {
//warn them that they have unsubmitted sponsor times
chrome.runtime.sendMessage({
@@ -274,7 +298,7 @@ function videoIDChange(id) {
sponsorTimesSubmitting = [];
//see if the onvideo control image needs to be changed
wait(getControls).then(result => {
utils.wait(getControls).then(result => {
chrome.runtime.sendMessage({
message: "getSponsorTimes",
videoID: id
@@ -304,11 +328,12 @@ function videoIDChange(id) {
}
}
function sponsorsLookup(id, channelIDPromise) {
function sponsorsLookup(id: string, channelIDPromise?) {
v = document.querySelector('video') // Youtube video player
//there is no video here
if (v == null) {
setTimeout(() => sponsorsLookup(id), 100);
setTimeout(() => sponsorsLookup(id, channelIDPromise), 100);
return;
}
@@ -319,12 +344,12 @@ function sponsorsLookup(id, channelIDPromise) {
v.addEventListener('durationchange', updatePreviewBar);
}
if (channelIDPromise != null) {
if (channelIDPromise !== undefined) {
if (channelIDPromise.isFulfilled) {
whitelistCheck();
} else if (channelIDPromise.isRejected) {
//try again
wait(getChannelID).then(whitelistCheck).catch();
utils.wait(getChannelID).then(whitelistCheck).catch();
} else {
//add it as a then statement
channelIDPromise.then(whitelistCheck);
@@ -334,7 +359,7 @@ function sponsorsLookup(id, channelIDPromise) {
//check database for sponsor times
//made true once a setTimeout has been created to try again after a server error
let recheckStarted = false;
sendRequestToServer('GET', "/api/getVideoSponsorTimes?videoID=" + id, function(xmlhttp) {
utils.sendRequestToServer('GET', "/api/getVideoSponsorTimes?videoID=" + id, function(xmlhttp) {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
sponsorDataFound = true;
@@ -372,7 +397,7 @@ function sponsorsLookup(id, channelIDPromise) {
//if less than 3 days old
if ((Date.now() / 1000) - unixTimePublished < 259200) {
//TODO lower when server becomes better
setTimeout(() => sponsorsLookup(id), 180000);
setTimeout(() => sponsorsLookup(id, channelIDPromise), 180000);
}
}
});
@@ -383,44 +408,58 @@ function sponsorsLookup(id, channelIDPromise) {
//TODO lower when server becomes better (back to 1 second)
//some error occurred, try again in a second
setTimeout(() => sponsorsLookup(id), 10000);
setTimeout(() => sponsorsLookup(id, channelIDPromise), 10000);
sponsorLookupRetries++;
}
});
//add the event to run on the videos "ontimeupdate"
if (!SB.config.disableSkipping) {
if (!Config.config.disableSkipping) {
v.ontimeupdate = function () {
sponsorCheck();
};
}
}
function updatePreviewBar() {
let localSponsorTimes = sponsorTimes;
if (localSponsorTimes == null) localSponsorTimes = [];
function getYouTubeVideoID(url: string) {
// For YouTube TV support
if(url.startsWith("https://www.youtube.com/tv#/")) url = url.replace("#", "");
let allSponsorTimes = localSponsorTimes.concat(sponsorTimesSubmitting);
//create an array of the sponsor types
let types = [];
for (let i = 0; i < localSponsorTimes.length; i++) {
if (!hiddenSponsorTimes.includes(i)) {
types.push("sponsor");
} else {
// Don't show this sponsor
types.push(null);
}
}
for (let i = 0; i < sponsorTimesSubmitting.length; i++) {
types.push("previewSponsor");
//Attempt to parse url
let urlObject = null;
try {
urlObject = new URL(url);
} catch (e) {
console.error("[SB] Unable to parse URL: " + url);
return false;
}
wait(() => previewBar !== null).then((result) => previewBar.set(allSponsorTimes, types, v.duration));
// Check if valid hostname
if (Config.config && Config.config.invidiousInstances.includes(urlObject.host)) {
onInvidious = true;
} else if (!["www.youtube.com", "www.youtube-nocookie.com"].includes(urlObject.host)) {
if (!Config.config) {
// Call this later, in case this is an Invidious tab
utils.wait(() => Config.config !== null).then(() => videoIDChange(getYouTubeVideoID(url)));
}
//update last video id
lastPreviewBarUpdate = sponsorVideoID;
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;
}
function getChannelID() {
@@ -450,7 +489,7 @@ function getChannelID() {
let titleInfoContainer = document.getElementById("info-contents");
let currentTitle = "";
if (titleInfoContainer != null) {
currentTitle = titleInfoContainer.firstElementChild.firstElementChild.querySelector(".title").firstElementChild.innerText;
currentTitle = (<HTMLElement> titleInfoContainer.firstElementChild.firstElementChild.querySelector(".title").firstElementChild).innerText;
} else if (onInvidious) {
// 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;
@@ -472,10 +511,36 @@ function getChannelID() {
channelWhitelisted = false;
}
function updatePreviewBar() {
let localSponsorTimes = sponsorTimes;
if (localSponsorTimes == null) localSponsorTimes = [];
let allSponsorTimes = localSponsorTimes.concat(sponsorTimesSubmitting);
//create an array of the sponsor types
let types = [];
for (let i = 0; i < localSponsorTimes.length; i++) {
if (!hiddenSponsorTimes.includes(i)) {
types.push("sponsor");
} else {
// Don't show this sponsor
types.push(null);
}
}
for (let i = 0; i < sponsorTimesSubmitting.length; i++) {
types.push("previewSponsor");
}
utils.wait(() => previewBar !== null).then((result) => previewBar.set(allSponsorTimes, types, v.duration));
//update last video id
lastPreviewBarUpdate = sponsorVideoID;
}
//checks if this channel is whitelisted, should be done only after the channelID has been loaded
function whitelistCheck() {
//see if this is a whitelisted channel
let whitelistedChannels = SB.config.whitelistedChannels;
let whitelistedChannels = Config.config.whitelistedChannels;
if (whitelistedChannels != undefined && whitelistedChannels.includes(channelURL)) {
channelWhitelisted = true;
@@ -484,7 +549,7 @@ function whitelistCheck() {
//video skipping
function sponsorCheck() {
if (SB.config.disableSkipping) {
if (Config.config.disableSkipping) {
// Make sure this isn't called again
v.ontimeupdate = null;
return;
@@ -516,12 +581,12 @@ function sponsorCheck() {
}
//don't keep track until they are loaded in
if (sponsorTimes != null || sponsorTimesSubmitting.length > 0) {
if (sponsorTimes !== null || sponsorTimesSubmitting.length > 0) {
lastTime = v.currentTime;
}
}
function checkSponsorTime(sponsorTimes, index, openNotice) {
function checkSponsorTime(sponsorTimes, index, openNotice): boolean {
//this means part of the video was just skipped
if (Math.abs(v.currentTime - lastTime) > 1 && lastTime != -1) {
//make lastTime as if the video was playing normally
@@ -550,7 +615,7 @@ function checkIfTimeToSkip(currentVideoTime, startTime, endTime) {
//skip fromt he start time to the end time for a certain index sponsor time
function skipToTime(v, index, sponsorTimes, openNotice) {
if (!SB.config.disableAutoSkip) {
if (!Config.config.disableAutoSkip) {
v.currentTime = sponsorTimes[index][1];
}
@@ -561,31 +626,24 @@ function skipToTime(v, index, sponsorTimes, openNotice) {
if (openNotice) {
//send out the message saying that a sponsor message was skipped
if (!SB.config.dontShowNotice) {
let skipNotice = new SkipNotice(this, currentUUID, SB.config.disableAutoSkip);
//TODO: Remove this when Invidious support is old
if (SB.config.invidiousUpdateInfoShowCount < 5) {
skipNotice.addNoticeInfoMessage(chrome.i18n.getMessage("invidiousInfo1"), chrome.i18n.getMessage("invidiousInfo2"));
SB.config.invidiousUpdateInfoShowCount += 1;
}
if (!Config.config.dontShowNotice) {
let skipNotice = new SkipNotice(this, currentUUID, Config.config.disableAutoSkip, skipNoticeContentContainer);
//auto-upvote this sponsor
if (SB.config.trackViewCount && !SB.config.disableAutoSkip && SB.config.autoUpvote) {
if (Config.config.trackViewCount && !Config.config.disableAutoSkip && Config.config.autoUpvote) {
vote(1, currentUUID, null);
}
}
}
//send telemetry that a this sponsor was skipped
if (SB.config.trackViewCount && !sponsorSkipped[index]) {
sendRequestToServer("POST", "/api/viewedVideoSponsorTime?UUID=" + currentUUID);
if (Config.config.trackViewCount && !sponsorSkipped[index]) {
utils.sendRequestToServer("POST", "/api/viewedVideoSponsorTime?UUID=" + currentUUID);
if (!SB.config.disableAutoSkip) {
if (!Config.config.disableAutoSkip) {
// Count this as a skip
SB.config.minutesSaved = SB.config.minutesSaved + (sponsorTimes[index][1] - sponsorTimes[index][0]) / 60;
SB.config.skipCount = SB.config.skipCount + 1;
Config.config.minutesSaved = Config.config.minutesSaved + (sponsorTimes[index][1] - sponsorTimes[index][0]) / 60;
Config.config.skipCount = Config.config.skipCount + 1;
sponsorSkipped[index] = true;
}
}
@@ -644,7 +702,7 @@ function getControls() {
//adds all the player controls buttons
async function createButtons() {
let result = await wait(getControls).catch();
let result = await utils.wait(getControls).catch();
//set global controls variable
controls = result;
@@ -662,7 +720,7 @@ async function updateVisibilityOfPlayerControlsButton() {
await createButtons();
if (SB.config.hideVideoPlayerControls || onInvidious) {
if (Config.config.hideVideoPlayerControls || onInvidious) {
document.getElementById("startSponsorButton").style.display = "none";
document.getElementById("submitButton").style.display = "none";
} else {
@@ -670,13 +728,13 @@ async function updateVisibilityOfPlayerControlsButton() {
}
//don't show the info button on embeds
if (SB.config.hideInfoButtonPlayerControls || document.URL.includes("/embed/") || onInvidious) {
if (Config.config.hideInfoButtonPlayerControls || document.URL.includes("/embed/") || onInvidious) {
document.getElementById("infoButton").style.display = "none";
} else {
document.getElementById("infoButton").style.removeProperty("display");
}
if (SB.config.hideDeleteButtonPlayerControls || onInvidious) {
if (Config.config.hideDeleteButtonPlayerControls || onInvidious) {
document.getElementById("deleteButton").style.display = "none";
}
}
@@ -725,18 +783,18 @@ async function changeStartSponsorButton(showStartSponsor, uploadButtonVisible) {
if(!sponsorVideoID) return false;
//make sure submit button is loaded
await wait(isSubmitButtonLoaded);
await utils.wait(isSubmitButtonLoaded);
//if it isn't visible, there is no data
let shouldHide = (uploadButtonVisible && !(SB.config.hideDeleteButtonPlayerControls || onInvidious)) ? "unset" : "none"
let shouldHide = (uploadButtonVisible && !(Config.config.hideDeleteButtonPlayerControls || onInvidious)) ? "unset" : "none"
document.getElementById("deleteButton").style.display = shouldHide;
if (showStartSponsor) {
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"));
if (document.getElementById("startSponsorImage").style.display != "none" && uploadButtonVisible && !SB.config.hideInfoButtonPlayerControls) {
if (document.getElementById("startSponsorImage").style.display != "none" && uploadButtonVisible && !Config.config.hideInfoButtonPlayerControls) {
document.getElementById("submitButton").style.display = "unset";
} else if (!uploadButtonVisible) {
//disable submit button
@@ -744,7 +802,7 @@ async function changeStartSponsorButton(showStartSponsor, uploadButtonVisible) {
}
} else {
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"));
//disable submit button
@@ -776,7 +834,7 @@ function openInfoMenu() {
//close button
let closeButton = document.createElement("div");
closeButton.innerText = "Close Popup";
closeButton.classList = "smallLink";
closeButton.classList.add("smallLink");
closeButton.setAttribute("align", "center");
closeButton.addEventListener("click", closeInfoMenu);
@@ -798,17 +856,17 @@ function openInfoMenu() {
//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
let logo = popup.querySelector("#sponsorBlockPopupLogo");
let logo = <HTMLImageElement> popup.querySelector("#sponsorBlockPopupLogo");
logo.src = chrome.extension.getURL("icons/LogoSponsorBlocker256px.png");
//remove the style sheet and font that are not necessary
popup.querySelector("#sponorBlockPopupFont").remove();
popup.querySelector("#sponorBlockStyleSheet").remove();
popup.querySelector("#sponsorBlockPopupFont").remove();
popup.querySelector("#sponsorBlockStyleSheet").remove();
parentNode.insertBefore(popup, parentNode.firstChild);
//run the popup init script
runThePopup();
runThePopup(messageListener);
}
});
}
@@ -831,7 +889,7 @@ function clearSponsorTimes() {
let currentVideoID = sponsorVideoID;
let sponsorTimes = SB.config.sponsorTimes.get(currentVideoID);
let sponsorTimes = Config.config.sponsorTimes.get(currentVideoID);
if (sponsorTimes != undefined && sponsorTimes.length > 0) {
let confirmMessage = chrome.i18n.getMessage("clearThis") + getSponsorTimesMessage(sponsorTimes)
@@ -839,7 +897,7 @@ function clearSponsorTimes() {
if(!confirm(confirmMessage)) return;
//clear the sponsor times
SB.config.sponsorTimes.delete(currentVideoID);
Config.config.sponsorTimes.delete(currentVideoID);
//clear sponsor times submitting
sponsorTimesSubmitting = [];
@@ -871,11 +929,11 @@ function vote(type, UUID, skipNotice) {
}
// Count this as a skip
SB.config.minutesSaved = SB.config.minutesSaved + factor * (sponsorTimes[sponsorIndex][1] - sponsorTimes[sponsorIndex][0]) / 60;
Config.config.minutesSaved = Config.config.minutesSaved + factor * (sponsorTimes[sponsorIndex][1] - sponsorTimes[sponsorIndex][0]) / 60;
SB.config.skipCount = 0;
Config.config.skipCount = 0;
SB.config.skipCount = SB.config.skipCount + factor * 1;
Config.config.skipCount = Config.config.skipCount + factor * 1;
}
chrome.runtime.sendMessage({
@@ -896,7 +954,7 @@ function vote(type, UUID, skipNotice) {
skipNotice.addNoticeInfoMessage.bind(skipNotice)(chrome.i18n.getMessage("voteFail"))
skipNotice.resetVoteButtonInfo.bind(skipNotice)();
} else if (response.successType == -1) {
skipNotice.addNoticeInfoMessage.bind(skipNotice)(getErrorMessage(response.statusCode))
skipNotice.addNoticeInfoMessage.bind(skipNotice)(utils.getErrorMessage(response.statusCode))
skipNotice.resetVoteButtonInfo.bind(skipNotice)();
}
}
@@ -913,7 +971,7 @@ function closeAllSkipNotices(){
}
function dontShowNoticeAgain() {
SB.config.dontShowNotice = true;
Config.config.dontShowNotice = true;
closeAllSkipNotices();
}
@@ -940,7 +998,7 @@ function submitSponsorTimes() {
let currentVideoID = sponsorVideoID;
let sponsorTimes = SB.config.sponsorTimes.get(currentVideoID);
let sponsorTimes = Config.config.sponsorTimes.get(currentVideoID);
if (sponsorTimes != undefined && sponsorTimes.length > 0) {
//check if a sponsor exceeds the duration of the video
@@ -950,7 +1008,7 @@ function submitSponsorTimes() {
}
}
//update sponsorTimes
SB.config.sponsorTimes.set(currentVideoID, sponsorTimes);
Config.config.sponsorTimes.set(currentVideoID, sponsorTimes);
//update sponsorTimesSubmitting
sponsorTimesSubmitting = sponsorTimes;
@@ -976,7 +1034,7 @@ function submitSponsorTimes() {
//called after all the checks have been made that it's okay to do so
function sendSubmitMessage(){
//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";
let currentVideoID = sponsorVideoID;
@@ -1003,13 +1061,13 @@ function sendSubmitMessage(){
submitButton.addEventListener("animationend", animationEndListener);
//clear the sponsor times
SB.config.sponsorTimes.delete(currentVideoID);
Config.config.sponsorTimes.delete(currentVideoID);
//add submissions to current sponsors list
sponsorTimes = sponsorTimes.concat(sponsorTimesSubmitting);
for (let i = 0; i < sponsorTimesSubmitting.length; i++) {
// Add some random IDs
UUIDs.push(generateUserID());
UUIDs.push(utils.generateUserID());
}
// Empty the submitting times
@@ -1019,9 +1077,9 @@ function sendSubmitMessage(){
} else {
//show that the upload failed
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));
}
}
});
@@ -1052,10 +1110,12 @@ function getSponsorTimesMessage(sponsorTimes) {
//converts time in seconds to minutes:seconds
function getFormattedTime(seconds) {
let minutes = Math.floor(seconds / 60);
let secondsDisplay = Math.round(seconds - minutes * 60);
if (secondsDisplay < 10) {
let secondsNum: number = Math.round(seconds - minutes * 60);
let secondsDisplay: string = String(secondsNum);
if (secondsNum < 10) {
//add a zero
secondsDisplay = "0" + secondsDisplay;
secondsDisplay = "0" + secondsNum;
}
let formatted = minutes + ":" + secondsDisplay;
@@ -1063,25 +1123,6 @@ function getFormattedTime(seconds) {
return formatted;
}
function sendRequestToServer(type, address, callback) {
let xmlhttp = new XMLHttpRequest();
xmlhttp.open(type, serverAddress + address, true);
if (callback != undefined) {
xmlhttp.onreadystatechange = function () {
callback(xmlhttp, false);
};
xmlhttp.onerror = function(ev) {
callback(xmlhttp, true);
};
}
//submit this request
xmlhttp.send();
}
function sendRequestToCustomServer(type, fullAddress, callback) {
let xmlhttp = new XMLHttpRequest();

View File

@@ -21,11 +21,13 @@ let barTypes = {
};
class PreviewBar {
container: HTMLUListElement;
parent: any;
constructor(parent) {
this.container = document.createElement('ul');
this.container.id = 'previewbar';
this.parent = parent;
this.bars = []
this.updatePosition();
}
@@ -39,7 +41,7 @@ class PreviewBar {
}
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) {
bar.style.backgroundColor = color;
bar.style.opacity = opacity;
@@ -73,8 +75,7 @@ class PreviewBar {
bar.style.left = (timestamps[i][0] / duration * 100) + "%";
bar.style.position = "absolute"
this.container.insertAdjacentElement('beforeEnd', bar);
this.bars[i] = bar;
this.container.insertAdjacentElement("beforeend", bar);
}
}
@@ -90,3 +91,5 @@ class PreviewBar {
this.container = undefined;
}
}
export default PreviewBar;

View File

@@ -4,16 +4,23 @@
* The notice that tells the user that a sponsor was just skipped
*/
class SkipNotice {
/**
* @param {HTMLElement} parent
* @param {String} UUID
* @param {String} noticeTitle
* @param {boolean} manualSkip
*/
constructor(parent, UUID, manualSkip = false) {
parent: HTMLElement;
UUID: string;
manualSkip: boolean;
// Contains functions and variables from the content script needed by the skip notice
contentContainer: () => any;
maxCountdownTime: () => number;
countdownTime: any;
countdownInterval: NodeJS.Timeout;
unskipCallback: any;
idSuffix: any;
constructor(parent: HTMLElement, UUID: string, manualSkip: boolean = false, contentContainer) {
this.parent = parent;
this.UUID = UUID;
this.manualSkip = manualSkip;
this.contentContainer = contentContainer;
let noticeTitle = chrome.i18n.getMessage("noticeTitle");
@@ -25,7 +32,7 @@ class SkipNotice {
//the countdown until this notice closes
this.countdownTime = this.maxCountdownTime();
//the id for the setInterval running the countdown
this.countdownInterval = -1;
this.countdownInterval = null;
//the unskip button's callback
this.unskipCallback = this.unskip.bind(this);
@@ -48,7 +55,7 @@ class SkipNotice {
noticeElement.id = "sponsorSkipNotice" + this.idSuffix;
noticeElement.classList.add("sponsorSkipObject");
noticeElement.classList.add("sponsorSkipNotice");
noticeElement.style.zIndex = 50 + amountOfPreviousNotices;
noticeElement.style.zIndex = String(50 + amountOfPreviousNotices);
//add mouse enter and leave listeners
noticeElement.addEventListener("mouseenter", this.pauseCountdown.bind(this));
@@ -121,7 +128,7 @@ class SkipNotice {
downvoteButton.id = "sponsorTimesDownvoteButtonsContainer" + this.idSuffix;
downvoteButton.className = "sponsorSkipObject voteButton";
downvoteButton.src = chrome.extension.getURL("icons/report.png");
downvoteButton.addEventListener("click", () => vote(0, this.UUID, this));
downvoteButton.addEventListener("click", () => this.contentContainer().vote(0, this.UUID, this));
downvoteButton.setAttribute("title", chrome.i18n.getMessage("reportButtonInfo"));
//add downvote and report text to container
@@ -149,7 +156,7 @@ class SkipNotice {
let dontShowAgainButton = document.createElement("button");
dontShowAgainButton.innerText = chrome.i18n.getMessage("Hide");
dontShowAgainButton.className = "sponsorSkipObject sponsorSkipNoticeButton sponsorSkipNoticeRightButton";
dontShowAgainButton.addEventListener("click", dontShowNoticeAgain);
dontShowAgainButton.addEventListener("click", this.contentContainer().dontShowNoticeAgain);
// Don't let them hide it if manually skipping
if (!this.manualSkip) {
@@ -170,12 +177,12 @@ class SkipNotice {
if (referenceNode == null) {
//for embeds
let player = document.getElementById("player");
referenceNode = player.firstChild;
referenceNode = <HTMLElement> player.firstChild;
let index = 1;
//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")) {
referenceNode = player.children[index];
referenceNode = <HTMLElement> player.children[index];
index++;
}
@@ -217,7 +224,7 @@ class SkipNotice {
pauseCountdown() {
//remove setInterval
clearInterval(this.countdownInterval);
this.countdownInterval = -1;
this.countdownInterval = null;
//reset countdown
this.countdownTime = this.maxCountdownTime();
@@ -234,7 +241,7 @@ class SkipNotice {
startCountdown() {
//if it has already started, don't start it again
if (this.countdownInterval != -1) return;
if (this.countdownInterval !== null) return;
this.countdownInterval = setInterval(this.countdown.bind(this), 1000);
@@ -248,7 +255,7 @@ class SkipNotice {
}
unskip() {
unskipSponsorTime(this.UUID);
this.contentContainer().unskipSponsorTime(this.UUID);
this.unskippedMode(chrome.i18n.getMessage("reskip"));
}
@@ -264,8 +271,8 @@ class SkipNotice {
//change max duration to however much of the sponsor is left
this.maxCountdownTime = function() {
let sponsorTime = sponsorTimes[UUIDs.indexOf(this.UUID)];
let duration = Math.round(sponsorTime[1] - v.currentTime);
let sponsorTime = this.contentContainer().sponsorTimes[this.contentContainer().UUIDs.indexOf(this.UUID)];
let duration = Math.round(sponsorTime[1] - this.contentContainer().v.currentTime);
return Math.max(duration, 4);
};
@@ -275,7 +282,7 @@ class SkipNotice {
}
reskip() {
reskipSponsorTime(this.UUID);
this.contentContainer().reskipSponsorTime(this.UUID);
//change reskip button to a unskip button
let unskipButton = this.changeUnskipButton(chrome.i18n.getMessage("unskip"));
@@ -293,7 +300,7 @@ class SkipNotice {
if (this.manualSkip) {
this.changeNoticeTitle(chrome.i18n.getMessage("noticeTitle"));
vote(1, this.UUID, this);
this.contentContainer().vote(1, this.UUID, this);
}
}
@@ -317,14 +324,14 @@ class SkipNotice {
//remove this sponsor from the sponsors looked up
//find which one it is
for (let i = 0; i < sponsorTimes.length; i++) {
if (UUIDs[i] == this.UUID) {
for (let i = 0; i < this.contentContainer().sponsorTimes.length; i++) {
if (this.contentContainer().UUIDs[i] == this.UUID) {
//this one is the one to hide
//add this as a hidden sponsorTime
hiddenSponsorTimes.push(i);
this.contentContainer().hiddenSponsorTimes.push(i);
updatePreviewBar();
this.contentContainer().updatePreviewBar();
break;
}
}
@@ -336,7 +343,7 @@ class SkipNotice {
noticeElement.innerText = title;
}
addNoticeInfoMessage(message, message2) {
addNoticeInfoMessage(message: string, message2: string = "") {
let previousInfoMessage = document.getElementById("sponsorTimesInfoMessage" + this.idSuffix);
if (previousInfoMessage != null) {
//remove it
@@ -422,7 +429,9 @@ class SkipNotice {
}
//remove setInterval
if (this.countdownInterval != -1) clearInterval(this.countdownInterval);
if (this.countdownInterval !== null) clearInterval(this.countdownInterval);
}
}
export default SkipNotice;

View File

@@ -1,13 +1,18 @@
import Config from "./config";
import Utils from "./utils";
var utils = new Utils();
window.addEventListener('DOMContentLoaded', init);
async function init() {
localizeHtmlPage();
utils.localizeHtmlPage();
if (!SB.configListeners.includes(optionsConfigUpdateListener)) {
SB.configListeners.push(optionsConfigUpdateListener);
if (!Config.configListeners.includes(optionsConfigUpdateListener)) {
Config.configListeners.push(optionsConfigUpdateListener);
}
await wait(() => SB.config !== undefined);
await utils.wait(() => Config.config !== null);
// Set all of the toggle options to the correct option
let optionsContainer = document.getElementById("options");
@@ -17,7 +22,7 @@ async function init() {
switch (optionsElements[i].getAttribute("option-type")) {
case "toggle":
let option = optionsElements[i].getAttribute("sync-option");
let optionResult = SB.config[option];
let optionResult = Config.config[option];
let checkbox = optionsElements[i].querySelector("input");
let reverse = optionsElements[i].getAttribute("toggle-type") === "reverse";
@@ -39,7 +44,7 @@ async function init() {
// Add click listener
checkbox.addEventListener("click", () => {
SB.config[option] = reverse ? !checkbox.checked : checkbox.checked;
Config.config[option] = reverse ? !checkbox.checked : checkbox.checked;
// See if anything extra must be run
switch (option) {
@@ -51,23 +56,23 @@ async function init() {
break;
case "text-change":
let button = optionsElements[i].querySelector(".trigger-button");
button.addEventListener("click", () => activateTextChange(optionsElements[i]));
button.addEventListener("click", () => activateTextChange(<HTMLElement> optionsElements[i]));
let textChangeOption = optionsElements[i].getAttribute("sync-option");
// See if anything extra must be done
switch (textChangeOption) {
case "invidiousInstances":
invidiousInstanceAddInit(optionsElements[i], textChangeOption);
invidiousInstanceAddInit(<HTMLElement> optionsElements[i], textChangeOption);
}
break;
case "keybind-change":
let keybindButton = optionsElements[i].querySelector(".trigger-button");
keybindButton.addEventListener("click", () => activateKeybindChange(optionsElements[i]));
keybindButton.addEventListener("click", () => activateKeybindChange(<HTMLElement> optionsElements[i]));
break;
case "display":
updateDisplayElement(optionsElements[i])
updateDisplayElement(<HTMLElement> optionsElements[i])
break;
case "number-change":
@@ -84,6 +89,7 @@ async function init() {
numberInput.addEventListener("input", () => {
SB.config[numberChangeOption] = numberInput.value;
});
break;
}
}
@@ -104,7 +110,7 @@ function optionsConfigUpdateListener(changes) {
for (let i = 0; i < optionsElements.length; i++) {
switch (optionsElements[i].getAttribute("option-type")) {
case "display":
updateDisplayElement(optionsElements[i])
updateDisplayElement(<HTMLElement> optionsElements[i])
}
}
}
@@ -112,11 +118,11 @@ function optionsConfigUpdateListener(changes) {
/**
* Will set display elements to the proper text
*
* @param {HTMLElement} element
* @param element
*/
function updateDisplayElement(element) {
function updateDisplayElement(element: HTMLElement) {
let displayOption = element.getAttribute("sync-option")
let displayText = SB.config[displayOption];
let displayText = Config.config[displayOption];
element.innerText = displayText;
// See if anything extra must be run
@@ -130,11 +136,11 @@ function updateDisplayElement(element) {
/**
* Initializes the option to add Invidious instances
*
* @param {HTMLElement} element
* @param {String} option
* @param element
* @param option
*/
function invidiousInstanceAddInit(element, option) {
let textBox = element.querySelector(".option-text-box");
function invidiousInstanceAddInit(element: HTMLElement, option: string) {
let textBox = <HTMLInputElement> element.querySelector(".option-text-box");
let button = element.querySelector(".trigger-button");
let setButton = element.querySelector(".text-change-set");
@@ -143,14 +149,14 @@ function invidiousInstanceAddInit(element, option) {
alert(chrome.i18n.getMessage("addInvidiousInstanceError"));
} else {
// Add this
let instanceList = SB.config[option];
let instanceList = Config.config[option];
if (!instanceList) instanceList = [];
instanceList.push(textBox.value);
SB.config[option] = instanceList;
Config.config[option] = instanceList;
let checkbox = document.querySelector("#support-invidious input");
let checkbox = <HTMLInputElement> document.querySelector("#support-invidious input");
checkbox.checked = true;
invidiousOnClick(checkbox, "supportInvidious");
@@ -167,7 +173,7 @@ function invidiousInstanceAddInit(element, option) {
resetButton.addEventListener("click", function(e) {
if (confirm(chrome.i18n.getMessage("resetInvidiousInstanceAlert"))) {
// Set to a clone of the default
SB.config[option] = SB.defaults[option].slice(0);
Config.config[option] = Config.defaults[option].slice(0);
}
});
}
@@ -175,19 +181,19 @@ function invidiousInstanceAddInit(element, option) {
/**
* Run when the invidious button is being initialized
*
* @param {HTMLElement} checkbox
* @param {string} option
* @param checkbox
* @param option
*/
function invidiousInit(checkbox, option) {
function invidiousInit(checkbox: HTMLInputElement, option: string) {
let permissions = ["declarativeContent"];
if (isFirefox()) permissions = [];
if (utils.isFirefox()) permissions = [];
chrome.permissions.contains({
origins: getInvidiousInstancesRegex(),
origins: utils.getInvidiousInstancesRegex(),
permissions: permissions
}, function (result) {
if (result != checkbox.checked) {
SB.config[option] = result;
Config.config[option] = result;
checkbox.checked = result;
}
@@ -197,28 +203,28 @@ function invidiousInit(checkbox, option) {
/**
* Run whenever the invidious checkbox is clicked
*
* @param {HTMLElement} checkbox
* @param {string} option
* @param checkbox
* @param option
*/
function invidiousOnClick(checkbox, option) {
function invidiousOnClick(checkbox: HTMLInputElement, option: string) {
if (checkbox.checked) {
setupExtraSitePermissions(function (granted) {
utils.setupExtraSitePermissions(function (granted) {
if (!granted) {
SB.config[option] = false;
Config.config[option] = false;
checkbox.checked = false;
}
});
} else {
removeExtraSiteRegistration();
utils.removeExtraSiteRegistration();
}
}
/**
* Will trigger the container to ask the user for a keybind.
*
* @param {HTMLElement} element
* @param element
*/
function activateKeybindChange(element) {
function activateKeybindChange(element: HTMLElement) {
let button = element.querySelector(".trigger-button");
if (button.classList.contains("disabled")) return;
@@ -226,14 +232,14 @@ function activateKeybindChange(element) {
let option = element.getAttribute("sync-option");
let currentlySet = SB.config[option] !== null ? chrome.i18n.getMessage("keybindCurrentlySet") : "";
let currentlySet = Config.config[option] !== null ? chrome.i18n.getMessage("keybindCurrentlySet") : "";
let status = element.querySelector(".option-hidden-section > .keybind-status");
let status = <HTMLElement> element.querySelector(".option-hidden-section > .keybind-status");
status.innerText = chrome.i18n.getMessage("keybindDescription") + currentlySet;
if (SB.config[option] !== null) {
let statusKey = element.querySelector(".option-hidden-section > .keybind-status-key");
statusKey.innerText = SB.config[option];
if (Config.config[option] !== null) {
let statusKey = <HTMLElement> element.querySelector(".option-hidden-section > .keybind-status-key");
statusKey.innerText = Config.config[option];
}
element.querySelector(".option-hidden-section").classList.remove("hidden");
@@ -244,11 +250,10 @@ function activateKeybindChange(element) {
/**
* Called when a key is pressed in an activiated keybind change option.
*
* @param {HTMLElement} element
* @param {KeyboardEvent} e
* @param element
* @param e
*/
function keybindKeyPressed(element, e) {
e = e || window.event;
function keybindKeyPressed(element: HTMLElement, e: KeyboardEvent) {
var key = e.key;
let button = element.querySelector(".trigger-button");
@@ -262,12 +267,12 @@ function keybindKeyPressed(element, e) {
let option = element.getAttribute("sync-option");
SB.config[option] = key;
Config.config[option] = key;
let status = element.querySelector(".option-hidden-section > .keybind-status");
let status = <HTMLElement> element.querySelector(".option-hidden-section > .keybind-status");
status.innerText = chrome.i18n.getMessage("keybindDescriptionComplete");
let statusKey = element.querySelector(".option-hidden-section > .keybind-status-key");
let statusKey = <HTMLElement> element.querySelector(".option-hidden-section > .keybind-status-key");
statusKey.innerText = key;
button.classList.remove("disabled");
@@ -276,15 +281,15 @@ function keybindKeyPressed(element, e) {
/**
* Will trigger the textbox to appear to be able to change an option's text.
*
* @param {HTMLElement} element
* @param element
*/
function activateTextChange(element) {
function activateTextChange(element: HTMLElement) {
let button = element.querySelector(".trigger-button");
if (button.classList.contains("disabled")) return;
button.classList.add("disabled");
let textBox = element.querySelector(".option-text-box");
let textBox = <HTMLInputElement> element.querySelector(".option-text-box");
let option = element.getAttribute("sync-option");
// See if anything extra must be done
@@ -294,14 +299,14 @@ function activateTextChange(element) {
return;
}
textBox.value = SB.config[option];
textBox.value = Config.config[option];
let setButton = element.querySelector(".text-change-set");
setButton.addEventListener("click", () => {
let confirmMessage = element.getAttribute("confirm-message");
if (confirmMessage === null || confirm(chrome.i18n.getMessage(confirmMessage))) {
SB.config[option] = textBox.value;
Config.config[option] = textBox.value;
}
});

View File

@@ -1,27 +1,50 @@
//make this a function to allow this to run on the content page
async function runThePopup() {
localizeHtmlPage();
//is it in the popup or content script
var inPopup = true;
if (chrome.tabs == undefined) {
//this is on the content script, use direct communication
chrome.tabs = {};
chrome.tabs.sendMessage = function(id, request, callback) {
messageListener(request, null, callback);
import Config from "./config";
import Utils from "./utils";
var utils = new Utils();
interface MessageListener {
(request: any, sender: any, callback: (response: any) => void): void;
}
//add a dummy query method
chrome.tabs.query = function(config, callback) {
class MessageHandler {
messageListener: MessageListener;
constructor (messageListener?: MessageListener) {
this.messageListener = messageListener;
}
sendMessage(id: number, request, callback?) {
if (this.messageListener) {
this.messageListener(request, null, callback);
} else {
chrome.tabs.sendMessage(id, request, callback);
}
}
query(config, callback) {
if (this.messageListener) {
// Send back dummy info
callback([{
url: document.URL,
id: -1
}]);
} else {
chrome.tabs.query(config, callback);
}
inPopup = false;
}
}
await wait(() => SB.config !== undefined);
//make this a function to allow this to run on the content page
async function runThePopup(messageListener?: MessageListener) {
var messageHandler = new MessageHandler();
utils.localizeHtmlPage();
await utils.wait(() => Config.config !== null);
var PageElements: any = {};
["sponsorStart",
// Top toggles
@@ -77,22 +100,22 @@ async function runThePopup() {
"videoFound",
"sponsorMessageTimes",
"downloadedSponsorMessageTimes",
].forEach(id => SB[id] = document.getElementById(id));
].forEach(id => PageElements[id] = document.getElementById(id));
//setup click listeners
SB.sponsorStart.addEventListener("click", sendSponsorStartMessage);
SB.whitelistChannel.addEventListener("click", whitelistChannel);
SB.unwhitelistChannel.addEventListener("click", unwhitelistChannel);
SB.disableSkipping.addEventListener("click", () => toggleSkipping(true));
SB.enableSkipping.addEventListener("click", () => toggleSkipping(false));
SB.clearTimes.addEventListener("click", clearTimes);
SB.submitTimes.addEventListener("click", submitTimes);
SB.showNoticeAgain.addEventListener("click", showNoticeAgain);
SB.setUsernameButton.addEventListener("click", setUsernameButton);
SB.submitUsername.addEventListener("click", submitUsername);
SB.optionsButton.addEventListener("click", openOptions);
SB.reportAnIssue.addEventListener("click", reportAnIssue);
SB.hideDiscordButton.addEventListener("click", hideDiscordButton);
PageElements.sponsorStart.addEventListener("click", sendSponsorStartMessage);
PageElements.whitelistChannel.addEventListener("click", whitelistChannel);
PageElements.unwhitelistChannel.addEventListener("click", unwhitelistChannel);
PageElements.disableSkipping.addEventListener("click", () => toggleSkipping(true));
PageElements.enableSkipping.addEventListener("click", () => toggleSkipping(false));
PageElements.clearTimes.addEventListener("click", clearTimes);
PageElements.submitTimes.addEventListener("click", submitTimes);
PageElements.showNoticeAgain.addEventListener("click", showNoticeAgain);
PageElements.setUsernameButton.addEventListener("click", setUsernameButton);
PageElements.submitUsername.addEventListener("click", submitUsername);
PageElements.optionsButton.addEventListener("click", openOptions);
PageElements.reportAnIssue.addEventListener("click", reportAnIssue);
PageElements.hideDiscordButton.addEventListener("click", hideDiscordButton);
//if true, the button now selects the end time
let startTimeChosen = false;
@@ -104,78 +127,78 @@ async function runThePopup() {
let currentVideoID = null;
//see if discord link can be shown
let hideDiscordLink = SB.config.hideDiscordLink;
let hideDiscordLink = Config.config.hideDiscordLink;
if (hideDiscordLink == undefined || !hideDiscordLink) {
let hideDiscordLaunches = SB.config.hideDiscordLaunches;
let hideDiscordLaunches = Config.config.hideDiscordLaunches;
//only if less than 10 launches
if (hideDiscordLaunches == undefined || hideDiscordLaunches < 10) {
SB.discordButtonContainer.style.display = null;
PageElements.discordButtonContainer.style.display = null;
if (hideDiscordLaunches == undefined) {
hideDiscordLaunches = 1;
}
SB.config.hideDiscordLaunches = hideDiscordLaunches + 1;
Config.config.hideDiscordLaunches = hideDiscordLaunches + 1;
}
}
//show proper disable skipping button
let disableSkipping = SB.config.disableSkipping;
let disableSkipping = Config.config.disableSkipping;
if (disableSkipping != undefined && disableSkipping) {
SB.disableSkipping.style.display = "none";
SB.enableSkipping.style.display = "unset";
PageElements.disableSkipping.style.display = "none";
PageElements.enableSkipping.style.display = "unset";
}
//if the don't show notice again variable is true, an option to
// disable should be available
let dontShowNotice = SB.config.dontShowNotice;
let dontShowNotice = Config.config.dontShowNotice;
if (dontShowNotice != undefined && dontShowNotice) {
SB.showNoticeAgain.style.display = "unset";
PageElements.showNoticeAgain.style.display = "unset";
}
//get the amount of times this user has contributed and display it to thank them
if (SB.config.sponsorTimesContributed != undefined) {
if (SB.config.sponsorTimesContributed > 1) {
SB.sponsorTimesContributionsDisplayEndWord.innerText = chrome.i18n.getMessage("Sponsors");
if (Config.config.sponsorTimesContributed != undefined) {
if (Config.config.sponsorTimesContributed > 1) {
PageElements.sponsorTimesContributionsDisplayEndWord.innerText = chrome.i18n.getMessage("Sponsors");
} else {
SB.sponsorTimesContributionsDisplayEndWord.innerText = chrome.i18n.getMessage("Sponsor");
PageElements.sponsorTimesContributionsDisplayEndWord.innerText = chrome.i18n.getMessage("Sponsor");
}
SB.sponsorTimesContributionsDisplay.innerText = SB.config.sponsorTimesContributed;
SB.sponsorTimesContributionsContainer.style.display = "unset";
PageElements.sponsorTimesContributionsDisplay.innerText = Config.config.sponsorTimesContributed;
PageElements.sponsorTimesContributionsContainer.style.display = "unset";
//get the userID
let userID = SB.config.userID;
let userID = Config.config.userID;
if (userID != undefined) {
//there are probably some views on these submissions then
//get the amount of views from the sponsors submitted
sendRequestToServer("GET", "/api/getViewsForUser?userID=" + userID, function(xmlhttp) {
utils.sendRequestToServer("GET", "/api/getViewsForUser?userID=" + userID, function(xmlhttp) {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
let viewCount = JSON.parse(xmlhttp.responseText).viewCount;
if (viewCount != 0) {
if (viewCount > 1) {
SB.sponsorTimesViewsDisplayEndWord.innerText = chrome.i18n.getMessage("Segments");
PageElements.sponsorTimesViewsDisplayEndWord.innerText = chrome.i18n.getMessage("Segments");
} else {
SB.sponsorTimesViewsDisplayEndWord.innerText = chrome.i18n.getMessage("Segment");
PageElements.sponsorTimesViewsDisplayEndWord.innerText = chrome.i18n.getMessage("Segment");
}
SB.sponsorTimesViewsDisplay.innerText = viewCount;
SB.sponsorTimesViewsContainer.style.display = "unset";
PageElements.sponsorTimesViewsDisplay.innerText = viewCount;
PageElements.sponsorTimesViewsContainer.style.display = "unset";
}
}
});
//get this time in minutes
sendRequestToServer("GET", "/api/getSavedTimeForUser?userID=" + userID, function(xmlhttp) {
utils.sendRequestToServer("GET", "/api/getSavedTimeForUser?userID=" + userID, function(xmlhttp) {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
let minutesSaved = JSON.parse(xmlhttp.responseText).timeSaved;
if (minutesSaved != 0) {
if (minutesSaved != 1) {
SB.sponsorTimesOthersTimeSavedEndWord.innerText = chrome.i18n.getMessage("minsLower");
PageElements.sponsorTimesOthersTimeSavedEndWord.innerText = chrome.i18n.getMessage("minsLower");
} else {
SB.sponsorTimesOthersTimeSavedEndWord.innerText = chrome.i18n.getMessage("minLower");
PageElements.sponsorTimesOthersTimeSavedEndWord.innerText = chrome.i18n.getMessage("minLower");
}
SB.sponsorTimesOthersTimeSavedDisplay.innerText = getFormattedHours(minutesSaved);
SB.sponsorTimesOthersTimeSavedContainer.style.display = "unset";
PageElements.sponsorTimesOthersTimeSavedDisplay.innerText = getFormattedHours(minutesSaved);
PageElements.sponsorTimesOthersTimeSavedContainer.style.display = "unset";
}
}
});
@@ -183,36 +206,36 @@ async function runThePopup() {
}
//get the amount of times this user has skipped a sponsor
if (SB.config.skipCount != undefined) {
if (SB.config.skipCount != 1) {
SB.sponsorTimesSkipsDoneEndWord.innerText = chrome.i18n.getMessage("Sponsors");
if (Config.config.skipCount != undefined) {
if (Config.config.skipCount != 1) {
PageElements.sponsorTimesSkipsDoneEndWord.innerText = chrome.i18n.getMessage("Sponsors");
} else {
SB.sponsorTimesSkipsDoneEndWord.innerText = chrome.i18n.getMessage("Sponsor");
PageElements.sponsorTimesSkipsDoneEndWord.innerText = chrome.i18n.getMessage("Sponsor");
}
SB.sponsorTimesSkipsDoneDisplay.innerText = SB.config.skipCount;
SB.sponsorTimesSkipsDoneContainer.style.display = "unset";
PageElements.sponsorTimesSkipsDoneDisplay.innerText = Config.config.skipCount;
PageElements.sponsorTimesSkipsDoneContainer.style.display = "unset";
}
//get the amount of time this user has saved.
if (SB.config.minutesSaved != undefined) {
if (SB.config.minutesSaved != 1) {
SB.sponsorTimeSavedEndWord.innerText = chrome.i18n.getMessage("minsLower");
if (Config.config.minutesSaved != undefined) {
if (Config.config.minutesSaved != 1) {
PageElements.sponsorTimeSavedEndWord.innerText = chrome.i18n.getMessage("minsLower");
} else {
SB.sponsorTimeSavedEndWord.innerText = chrome.i18n.getMessage("minLower");
PageElements.sponsorTimeSavedEndWord.innerText = chrome.i18n.getMessage("minLower");
}
SB.sponsorTimeSavedDisplay.innerText = getFormattedHours(SB.config.minutesSaved);
SB.sponsorTimeSavedContainer.style.display = "unset";
PageElements.sponsorTimeSavedDisplay.innerText = getFormattedHours(Config.config.minutesSaved);
PageElements.sponsorTimeSavedContainer.style.display = "unset";
}
chrome.tabs.query({
messageHandler.query({
active: true,
currentWindow: true
}, onTabs);
function onTabs(tabs) {
chrome.tabs.sendMessage(tabs[0].id, {message: 'getVideoID'}, function(result) {
messageHandler.sendMessage(tabs[0].id, {message: 'getVideoID'}, function(result) {
if (result != undefined && result.videoID) {
currentVideoID = result.videoID;
loadTabData(tabs);
@@ -231,11 +254,11 @@ async function runThePopup() {
}
//load video times for this video
let sponsorTimesStorage = SB.config.sponsorTimes.get(currentVideoID);
let sponsorTimesStorage = Config.config.sponsorTimes.get(currentVideoID);
if (sponsorTimesStorage != undefined && sponsorTimesStorage.length > 0) {
if (sponsorTimesStorage[sponsorTimesStorage.length - 1] != undefined && sponsorTimesStorage[sponsorTimesStorage.length - 1].length < 2) {
startTimeChosen = true;
SB.sponsorStart.innerHTML = chrome.i18n.getMessage("sponsorEnd");
PageElements.sponsorStart.innerHTML = chrome.i18n.getMessage("sponsorEnd");
}
sponsorTimes = sponsorTimesStorage;
@@ -243,13 +266,13 @@ async function runThePopup() {
displaySponsorTimes();
//show submission section
SB.submissionSection.style.display = "unset";
PageElements.submissionSection.style.display = "unset";
showSubmitTimesIfNecessary();
}
//check if this video's sponsors are known
chrome.tabs.sendMessage(
messageHandler.sendMessage(
tabs[0].id,
{message: 'isInfoFound'},
infoFound
@@ -265,38 +288,34 @@ async function runThePopup() {
//if request is undefined, then the page currently being browsed is not YouTube
if (request != undefined) {
//this must be a YouTube video
//set letiable
isYouTubeTab = true;
//remove loading text
SB.mainControls.style.display = "unset"
SB.loadingIndicator.style.display = "none";
PageElements.mainControls.style.display = "unset"
PageElements.loadingIndicator.style.display = "none";
if (request.found) {
SB.videoFound.innerHTML = chrome.i18n.getMessage("sponsorFound");
PageElements.videoFound.innerHTML = chrome.i18n.getMessage("sponsorFound");
displayDownloadedSponsorTimes(request);
} else {
SB.videoFound.innerHTML = chrome.i18n.getMessage("sponsor404");
PageElements.videoFound.innerHTML = chrome.i18n.getMessage("sponsor404");
}
}
//see if whitelist button should be swapped
chrome.tabs.query({
messageHandler.query({
active: true,
currentWindow: true
}, tabs => {
chrome.tabs.sendMessage(
messageHandler.sendMessage(
tabs[0].id,
{message: 'isChannelWhitelisted'},
function(response) {
if (response.value) {
SB.whitelistChannel.style.display = "none";
SB.unwhitelistChannel.style.display = "unset";
PageElements.whitelistChannel.style.display = "none";
PageElements.unwhitelistChannel.style.display = "unset";
SB.downloadedSponsorMessageTimes.innerText = chrome.i18n.getMessage("channelWhitelisted");
SB.downloadedSponsorMessageTimes.style.fontWeight = "bold";
PageElements.downloadedSponsorMessageTimes.innerText = chrome.i18n.getMessage("channelWhitelisted");
PageElements.downloadedSponsorMessageTimes.style.fontWeight = "bold";
}
});
}
@@ -305,11 +324,11 @@ async function runThePopup() {
function sendSponsorStartMessage() {
//the content script will get the message if a YouTube page is open
chrome.tabs.query({
messageHandler.query({
active: true,
currentWindow: true
}, tabs => {
chrome.tabs.sendMessage(
messageHandler.sendMessage(
tabs[0].id,
{from: 'popup', message: 'sponsorStart'},
startSponsorCallback
@@ -327,14 +346,15 @@ async function runThePopup() {
sponsorTimes[sponsorTimesIndex][startTimeChosen ? 1 : 0] = response.time;
let localStartTimeChosen = startTimeChosen;
SB.config.sponsorTimes.set(currentVideoID, sponsorTimes);
Config.config.sponsorTimes.set(currentVideoID, sponsorTimes);
//send a message to the client script
if (localStartTimeChosen) {
chrome.tabs.query({
messageHandler.query({
active: true,
currentWindow: true
}, tabs => {
chrome.tabs.sendMessage(
messageHandler.sendMessage(
tabs[0].id,
{message: "sponsorDataChanged"}
);
@@ -347,7 +367,7 @@ async function runThePopup() {
displaySponsorTimes();
//show submission section
SB.submissionSection.style.display = "unset";
PageElements.submissionSection.style.display = "unset";
showSubmitTimesIfNecessary();
}
@@ -355,20 +375,20 @@ async function runThePopup() {
//display the video times from the array
function displaySponsorTimes() {
//remove all children
while (SB.sponsorMessageTimes.firstChild) {
SB.sponsorMessageTimes.removeChild(SB.sponsorMessageTimes.firstChild);
while (PageElements.sponsorMessageTimes.firstChild) {
PageElements.sponsorMessageTimes.removeChild(PageElements.sponsorMessageTimes.firstChild);
}
//add sponsor times
SB.sponsorMessageTimes.appendChild(getSponsorTimesMessageDiv(sponsorTimes));
PageElements.sponsorMessageTimes.appendChild(getSponsorTimesMessageDiv(sponsorTimes));
}
//display the video times from the array at the top, in a different section
function displayDownloadedSponsorTimes(request) {
if (request.sponsorTimes != undefined) {
//set it to the message
if (SB.downloadedSponsorMessageTimes.innerText != chrome.i18n.getMessage("channelWhitelisted")) {
SB.downloadedSponsorMessageTimes.innerText = getSponsorTimesMessage(request.sponsorTimes);
if (PageElements.downloadedSponsorMessageTimes.innerText != chrome.i18n.getMessage("channelWhitelisted")) {
PageElements.downloadedSponsorMessageTimes.innerText = getSponsorTimesMessage(request.sponsorTimes);
}
//add them as buttons to the issue reporting container
@@ -526,11 +546,11 @@ async function runThePopup() {
saveSponsorTimeEdit(index, false);
}
chrome.tabs.query({
messageHandler.query({
active: true,
currentWindow: true
}, tabs => {
chrome.tabs.sendMessage(
messageHandler.sendMessage(
tabs[0].id, {
message: "skipToTime",
time: skipTime - 2
@@ -562,7 +582,7 @@ async function runThePopup() {
startTimeMinutes.id = "startTimeMinutes" + index;
startTimeMinutes.className = "sponsorTime popupElement";
startTimeMinutes.type = "text";
startTimeMinutes.value = getTimeInMinutes(sponsorTimes[index][0]);
startTimeMinutes.value = String(getTimeInMinutes(sponsorTimes[index][0]));
startTimeMinutes.style.width = "45px";
let startTimeSeconds = document.createElement("input");
@@ -576,7 +596,7 @@ async function runThePopup() {
endTimeMinutes.id = "endTimeMinutes" + index;
endTimeMinutes.className = "sponsorTime popupElement";
endTimeMinutes.type = "text";
endTimeMinutes.value = getTimeInMinutes(sponsorTimes[index][1]);
endTimeMinutes.value = String(getTimeInMinutes(sponsorTimes[index][1]));
endTimeMinutes.style.width = "45px";
let endTimeSeconds = document.createElement("input");
@@ -628,18 +648,18 @@ async function runThePopup() {
}
function setEditTimeToCurrentTime(idStartName, index) {
chrome.tabs.query({
messageHandler.query({
active: true,
currentWindow: true
}, tabs => {
chrome.tabs.sendMessage(
messageHandler.sendMessage(
tabs[0].id,
{message: "getCurrentTime"},
function (response) {
let minutes = document.getElementById(idStartName + "Minutes" + index);
let seconds = document.getElementById(idStartName + "Seconds" + index);
let minutes = <HTMLInputElement> <unknown> document.getElementById(idStartName + "Minutes" + index);
let seconds = <HTMLInputElement> <unknown> document.getElementById(idStartName + "Seconds" + index);
minutes.value = getTimeInMinutes(response.currentTime);
minutes.value = String(getTimeInMinutes(response.currentTime));
seconds.value = getTimeInFormattedSeconds(response.currentTime);
});
});
@@ -648,10 +668,10 @@ async function runThePopup() {
//id start name is whether it is the startTime or endTime
//gives back the time in seconds
function getSponsorTimeEditTimes(idStartName, index) {
let minutes = document.getElementById(idStartName + "Minutes" + index);
let seconds = document.getElementById(idStartName + "Seconds" + index);
let minutes = <HTMLInputElement> <unknown> document.getElementById(idStartName + "Minutes" + index);
let seconds = <HTMLInputElement> <unknown> document.getElementById(idStartName + "Seconds" + index);
return parseInt(minutes.value) * 60 + parseFloat(seconds.value);
return parseInt(minutes.value) * 60 + seconds.value;
}
function saveSponsorTimeEdit(index, closeEditMode = true) {
@@ -659,12 +679,12 @@ async function runThePopup() {
sponsorTimes[index][1] = getSponsorTimeEditTimes("endTime", index);
//save this
SB.config.sponsorTimes.set(currentVideoID, sponsorTimes);
chrome.tabs.query({
Config.config.sponsorTimes.set(currentVideoID, sponsorTimes);
messageHandler.query({
active: true,
currentWindow: true
}, tabs => {
chrome.tabs.sendMessage(
messageHandler.sendMessage(
tabs[0].id,
{message: "sponsorDataChanged"}
);
@@ -681,11 +701,11 @@ async function runThePopup() {
function deleteSponsorTime(index) {
//if it is not a complete sponsor time
if (sponsorTimes[index].length < 2) {
chrome.tabs.query({
messageHandler.query({
active: true,
currentWindow: true
}, function(tabs) {
chrome.tabs.sendMessage(tabs[0].id, {
messageHandler.sendMessage(tabs[0].id, {
message: "changeStartSponsorButton",
showStartSponsor: true,
uploadButtonVisible: false
@@ -698,12 +718,12 @@ async function runThePopup() {
sponsorTimes.splice(index, 1);
//save this
SB.config.sponsorTimes.set(currentVideoID, sponsorTimes);
chrome.tabs.query({
Config.config.sponsorTimes.set(currentVideoID, sponsorTimes);
messageHandler.query({
active: true,
currentWindow: true
}, tabs => {
chrome.tabs.sendMessage(
messageHandler.sendMessage(
tabs[0].id,
{message: "sponsorDataChanged"}
);
@@ -715,11 +735,11 @@ async function runThePopup() {
//if they are all removed
if (sponsorTimes.length == 0) {
//update chrome tab
chrome.tabs.query({
messageHandler.query({
active: true,
currentWindow: true
}, function(tabs) {
chrome.tabs.sendMessage(tabs[0].id, {
messageHandler.sendMessage(tabs[0].id, {
message: "changeStartSponsorButton",
showStartSponsor: true,
uploadButtonVisible: false
@@ -734,11 +754,11 @@ async function runThePopup() {
function clearTimes() {
//send new sponsor time state to tab
if (sponsorTimes.length > 0) {
chrome.tabs.query({
messageHandler.query({
active: true,
currentWindow: true
}, function(tabs) {
chrome.tabs.sendMessage(tabs[0].id, {
messageHandler.sendMessage(tabs[0].id, {
message: "changeStartSponsorButton",
showStartSponsor: true,
uploadButtonVisible: false
@@ -749,12 +769,12 @@ async function runThePopup() {
//reset sponsorTimes
sponsorTimes = [];
SB.config.sponsorTimes.set(currentVideoID, sponsorTimes);
chrome.tabs.query({
Config.config.sponsorTimes.set(currentVideoID, sponsorTimes);
messageHandler.query({
active: true,
currentWindow: true
}, tabs => {
chrome.tabs.sendMessage(
messageHandler.sendMessage(
tabs[0].id,
{message: "sponsorDataChanged"}
);
@@ -770,8 +790,8 @@ async function runThePopup() {
function submitTimes() {
//make info message say loading
SB.submitTimesInfoMessage.innerText = chrome.i18n.getMessage("Loading");
SB.submitTimesInfoMessageContainer.style.display = "unset";
PageElements.submitTimesInfoMessage.innerText = chrome.i18n.getMessage("Loading");
PageElements.submitTimesInfoMessageContainer.style.display = "unset";
if (sponsorTimes.length > 0) {
chrome.runtime.sendMessage({
@@ -781,14 +801,14 @@ async function runThePopup() {
if (response != undefined) {
if (response.statusCode == 200) {
//hide loading message
SB.submitTimesInfoMessageContainer.style.display = "none";
PageElements.submitTimesInfoMessageContainer.style.display = "none";
clearTimes();
} else {
document.getElementById("submitTimesInfoMessage").innerText = getErrorMessage(response.statusCode);
document.getElementById("submitTimesInfoMessage").innerText = utils.getErrorMessage(response.statusCode);
document.getElementById("submitTimesInfoMessageContainer").style.display = "unset";
SB.submitTimesInfoMessageContainer.style.display = "unset";
PageElements.submitTimesInfoMessageContainer.style.display = "unset";
}
}
});
@@ -796,16 +816,16 @@ async function runThePopup() {
}
function showNoticeAgain() {
SB.config.dontShowNotice = false;
Config.config.dontShowNotice = false;
SB.showNoticeAgain.style.display = "none";
PageElements.showNoticeAgain.style.display = "none";
}
function updateStartTimeChosen() {
//update startTimeChosen letiable
if (!startTimeChosen) {
startTimeChosen = true;
SB.sponsorStart.innerHTML = chrome.i18n.getMessage("sponsorEnd");
PageElements.sponsorStart.innerHTML = chrome.i18n.getMessage("sponsorEnd");
} else {
resetStartTimeChosen();
}
@@ -814,7 +834,7 @@ async function runThePopup() {
//set it to false
function resetStartTimeChosen() {
startTimeChosen = false;
SB.sponsorStart.innerHTML = chrome.i18n.getMessage("sponsorStart");
PageElements.sponsorStart.innerHTML = chrome.i18n.getMessage("sponsorStart");
}
//hides and shows the submit times button when needed
@@ -837,24 +857,24 @@ async function runThePopup() {
//make the options username setting option visible
function setUsernameButton() {
//get username from the server
sendRequestToServer("GET", "/api/getUsername?userID=" + SB.config.userID, function (xmlhttp, error) {
utils.sendRequestToServer("GET", "/api/getUsername?userID=" + Config.config.userID, function (xmlhttp, error) {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
SB.usernameInput.value = JSON.parse(xmlhttp.responseText).userName;
PageElements.usernameInput.value = JSON.parse(xmlhttp.responseText).userName;
SB.submitUsername.style.display = "unset";
SB.usernameInput.style.display = "unset";
PageElements.submitUsername.style.display = "unset";
PageElements.usernameInput.style.display = "unset";
SB.setUsernameContainer.style.display = "none";
SB.setUsername.style.display = "unset";
SB.setUsernameStatusContainer.style.display = "none";
PageElements.setUsernameContainer.style.display = "none";
PageElements.setUsername.style.display = "unset";
PageElements
PageElements.setUsernameStatusContainer.style.display = "none";
} else if (xmlhttp.readyState == 4) {
SB.setUsername.style.display = "unset";
SB.submitUsername.style.display = "none";
SB.usernameInput.style.display = "none";
PageElements.setUsername.style.display = "unset";
PageElements.submitUsername.style.display = "none";
PageElements.usernameInput.style.display = "none";
SB.setUsernameStatusContainer.style.display = "unset";
SB.setUsernameStatus.innerText = getErrorMessage(xmlhttp.status);
PageElements.setUsernameStatusContainer.style.display = "unset";
PageElements.setUsernameStatus.innerText = utils.getErrorMessage(xmlhttp.status);
}
});
}
@@ -862,25 +882,25 @@ async function runThePopup() {
//submit the new username
function submitUsername() {
//add loading indicator
SB.setUsernameStatusContainer.style.display = "unset";
SB.setUsernameStatus.innerText = "Loading...";
PageElements.setUsernameStatusContainer.style.display = "unset";
PageElements.setUsernameStatus.innerText = "Loading...";
//get the userID
sendRequestToServer("POST", "/api/setUsername?userID=" + SB.config.userID + "&username=" + SB.usernameInput.value, function (xmlhttp, error) {
utils.sendRequestToServer("POST", "/api/setUsername?userID=" + Config.config.userID + "&username=" + PageElements.usernameInput.value, function (xmlhttp, error) {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
//submitted
SB.submitUsername.style.display = "none";
SB.usernameInput.style.display = "none";
PageElements.submitUsername.style.display = "none";
PageElements.usernameInput.style.display = "none";
SB.setUsernameStatus.innerText = chrome.i18n.getMessage("success");
PageElements.setUsernameStatus.innerText = chrome.i18n.getMessage("success");
} else if (xmlhttp.readyState == 4) {
SB.setUsernameStatus.innerText = getErrorMessageI(xmlhttp.status);
PageElements.setUsernameStatus.innerText = utils.getErrorMessage(xmlhttp.status);
}
});
SB.setUsernameContainer.style.display = "none";
SB.setUsername.style.display = "unset";
PageElements.setUsernameContainer.style.display = "none";
PageElements.setUsername.style.display = "unset";
}
//this is not a YouTube video page
@@ -890,7 +910,7 @@ async function runThePopup() {
function reportAnIssue() {
document.getElementById("issueReporterContainer").style.display = "unset";
SB.reportAnIssue.style.display = "none";
PageElements.reportAnIssue.style.display = "none";
}
function addVoteMessage(message, UUID) {
@@ -927,22 +947,23 @@ async function runThePopup() {
//failure: duplicate vote
addVoteMessage(chrome.i18n.getMessage("voteFail"), UUID)
} else if (response.successType == -1) {
addVoteMessage(getErrorMessage(response.statusCode), UUID)
addVoteMessage(utils.getErrorMessage(response.statusCode), UUID)
}
}
});
}
function hideDiscordButton() {
SB.config.hideDiscordLink = true;
SB.discordButtonContainer.style.display = "none";
Config.config.hideDiscordLink = true;
PageElements.discordButtonContainer.style.display = "none";
}
//converts time in seconds to minutes:seconds
function getFormattedTime(seconds) {
let minutes = Math.floor(seconds / 60);
let secondsDisplay = Math.round(seconds - minutes * 60);
if (secondsDisplay < 10) {
let secondsDisplayNumber = Math.round(seconds - minutes * 60);
let secondsDisplay = String(secondsDisplayNumber);
if (secondsDisplayNumber < 10) {
//add a zero
secondsDisplay = "0" + secondsDisplay;
}
@@ -954,16 +975,16 @@ async function runThePopup() {
function whitelistChannel() {
//get the channel url
chrome.tabs.query({
messageHandler.query({
active: true,
currentWindow: true
}, tabs => {
chrome.tabs.sendMessage(
messageHandler.sendMessage(
tabs[0].id,
{message: 'getChannelURL'},
function(response) {
//get whitelisted channels
let whitelistedChannels = SB.config.whitelistedChannels;
let whitelistedChannels = Config.config.whitelistedChannels;
if (whitelistedChannels == undefined) {
whitelistedChannels = [];
}
@@ -972,21 +993,21 @@ async function runThePopup() {
whitelistedChannels.push(response.channelURL);
//change button
SB.whitelistChannel.style.display = "none";
SB.unwhitelistChannel.style.display = "unset";
PageElements.whitelistChannel.style.display = "none";
PageElements.unwhitelistChannel.style.display = "unset";
SB.downloadedSponsorMessageTimes.innerText = chrome.i18n.getMessage("channelWhitelisted");
SB.downloadedSponsorMessageTimes.style.fontWeight = "bold";
PageElements.downloadedSponsorMessageTimes.innerText = chrome.i18n.getMessage("channelWhitelisted");
PageElements.downloadedSponsorMessageTimes.style.fontWeight = "bold";
//save this
SB.config.whitelistedChannels = whitelistedChannels;
Config.config.whitelistedChannels = whitelistedChannels;
//send a message to the client
chrome.tabs.query({
messageHandler.query({
active: true,
currentWindow: true
}, tabs => {
chrome.tabs.sendMessage(
messageHandler.sendMessage(
tabs[0].id, {
message: 'whitelistChange',
value: true
@@ -1000,16 +1021,16 @@ async function runThePopup() {
function unwhitelistChannel() {
//get the channel url
chrome.tabs.query({
messageHandler.query({
active: true,
currentWindow: true
}, tabs => {
chrome.tabs.sendMessage(
messageHandler.sendMessage(
tabs[0].id,
{message: 'getChannelURL'},
function(response) {
//get whitelisted channels
let whitelistedChannels = SB.config.whitelistedChannels;
let whitelistedChannels = Config.config.whitelistedChannels;
if (whitelistedChannels == undefined) {
whitelistedChannels = [];
}
@@ -1019,21 +1040,21 @@ async function runThePopup() {
whitelistedChannels.splice(index, 1);
//change button
SB.whitelistChannel.style.display = "unset";
SB.unwhitelistChannel.style.display = "none";
PageElements.whitelistChannel.style.display = "unset";
PageElements.unwhitelistChannel.style.display = "none";
SB.downloadedSponsorMessageTimes.innerText = "";
SB.downloadedSponsorMessageTimes.style.fontWeight = "unset";
PageElements.downloadedSponsorMessageTimes.innerText = "";
PageElements.downloadedSponsorMessageTimes.style.fontWeight = "unset";
//save this
SB.config.whitelistedChannels = whitelistedChannels;
Config.config.whitelistedChannels = whitelistedChannels;
//send a message to the client
chrome.tabs.query({
messageHandler.query({
active: true,
currentWindow: true
}, tabs => {
chrome.tabs.sendMessage(
messageHandler.sendMessage(
tabs[0].id, {
message: 'whitelistChange',
value: false
@@ -1049,14 +1070,14 @@ async function runThePopup() {
* Should skipping be disabled (visuals stay)
*/
function toggleSkipping(disabled) {
SB.config.disableSkipping = disabled;
Config.config.disableSkipping = disabled;
let hiddenButton = SB.disableSkipping;
let shownButton = SB.enableSkipping;
let hiddenButton = PageElements.disableSkipping;
let shownButton = PageElements.enableSkipping;
if (!disabled) {
hiddenButton = SB.enableSkipping;
shownButton = SB.disableSkipping;
hiddenButton = PageElements.enableSkipping;
shownButton = PageElements.disableSkipping;
}
shownButton.style.display = "unset";
@@ -1072,34 +1093,16 @@ async function runThePopup() {
//converts time in seconds to seconds past the last minute
function getTimeInFormattedSeconds(seconds) {
let secondsFormatted = (seconds % 60).toFixed(3);
let minutes = seconds % 60;
let secondsFormatted = minutes.toFixed(3);
if (secondsFormatted < 10) {
if (minutes < 10) {
secondsFormatted = "0" + secondsFormatted;
}
return secondsFormatted;
}
function sendRequestToServer(type, address, callback) {
let xmlhttp = new XMLHttpRequest();
xmlhttp.open(type, serverAddress + address, true);
if (callback != undefined) {
xmlhttp.onreadystatechange = function () {
callback(xmlhttp, false);
};
xmlhttp.onerror = function(ev) {
callback(xmlhttp, true);
};
}
//submit this request
xmlhttp.send();
}
/**
* Converts time in hours to 5h 25.1
* If less than 1 hour, just returns minutes
@@ -1117,7 +1120,11 @@ async function runThePopup() {
if (chrome.tabs != undefined) {
//add the width restriction (because Firefox)
document.getElementById("sponorBlockStyleSheet").sheet.insertRule('.popupBody { width: 325 }', 0);
let link = <HTMLLinkElement> document.getElementById("sponsorBlockStyleSheet");
(<CSSStyleSheet> link.sheet).insertRule('.popupBody { width: 325 }', 0);
//this means it is actually opened in the popup
runThePopup();
}
export default runThePopup;

7
src/types.ts Normal file
View File

@@ -0,0 +1,7 @@
interface videoDurationResponse {
duration: number;
}
export {
videoDurationResponse
};

267
src/utils.ts Normal file
View File

@@ -0,0 +1,267 @@
import * as CompileConfig from "../config.json";
import Config from "./config";
class Utils {
// Contains functions needed from the background script
backgroundScriptContainer: any = null;
constructor(backgroundScriptContainer?: any) {
this.backgroundScriptContainer = backgroundScriptContainer;
}
// Function that can be used to wait for a condition before returning
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();
});
}
/**
* 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
*/
setupExtraSitePermissions(callback) {
// Request permission
let permissions = ["declarativeContent"];
if (this.isFirefox()) permissions = [];
let self = this;
chrome.permissions.request({
origins: this.getInvidiousInstancesRegex(),
permissions: permissions
}, async function (granted) {
if (granted) {
self.setupExtraSiteContentScripts();
} else {
self.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.
*/
setupExtraSiteContentScripts() {
let js = [
"./js/vendor.js",
"./js/content.js"
];
let css = [
"content.css",
"./libs/Source+Sans+Pro.css",
"popup.css"
];
let self = this;
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 (this.backgroundScriptContainer) {
this.backgroundScriptContainer.registerFirefoxContentScript(registration);
} else {
chrome.runtime.sendMessage(registration);
}
} else {
chrome.declarativeContent.onPageChanged.removeRules(["invidious"], function() {
let conditions = [];
for (const regex of self.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.
*/
removeExtraSiteRegistration() {
if (this.isFirefox()) {
let id = "invidious";
if (this.backgroundScriptContainer) {
this.backgroundScriptContainer.unregisterFirefoxContentScript(id);
} else {
chrome.runtime.sendMessage({
message: "unregisterContentScript",
id: id
});
}
} else if (chrome.declarativeContent) {
// Only if we have permission
chrome.declarativeContent.onPageChanged.removeRules(["invidious"]);
}
chrome.permissions.remove({
origins: this.getInvidiousInstancesRegex()
});
}
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;
}
}
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
*/
getInvidiousInstancesRegex() {
var invidiousInstancesRegex = [];
for (const url of Config.config.invidiousInstances) {
invidiousInstancesRegex.push("https://*." + url + "/*");
invidiousInstancesRegex.push("http://*." + url + "/*");
}
return invidiousInstancesRegex;
}
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
*/
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;
}
/**
* Sends a request to the SponsorBlock server with address added as a query
*
* @param type The request type. "GET", "POST", etc.
* @param address The address to add to the SponsorBlock server address
* @param callback
*/
sendRequestToServer(type: string, address: string, callback?: (xmlhttp: XMLHttpRequest, err: boolean) => any) {
let xmlhttp = new XMLHttpRequest();
xmlhttp.open(type, CompileConfig.serverAddress + address, true);
if (callback != undefined) {
xmlhttp.onreadystatechange = function () {
callback(xmlhttp, false);
};
xmlhttp.onerror = function(ev) {
callback(xmlhttp, true);
};
}
//submit this request
xmlhttp.send();
}
/**
* Is this Firefox (web-extensions)
*/
isFirefox() {
return typeof(browser) !== "undefined";
}
}
export default Utils;

12
tsconfig.json Normal file
View File

@@ -0,0 +1,12 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es6",
"noImplicitAny": false,
"sourceMap": false,
"outDir": "dist/js",
"noEmitOnError": true,
"typeRoots": [ "node_modules/@types" ],
"resolveJsonModule": true
}
}

271
utils.js
View File

@@ -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";
}

48
webpack/webpack.common.js Normal file
View File

@@ -0,0 +1,48 @@
const webpack = require("webpack");
const path = require('path');
const CopyPlugin = require('copy-webpack-plugin');
const BuildManifest = require('./webpack.manifest');
const srcDir = '../src/';
module.exports = env => ({
entry: {
popup: path.join(__dirname, srcDir + 'popup.ts'),
background: path.join(__dirname, srcDir + 'background.ts'),
content: path.join(__dirname, srcDir + 'content.ts'),
options: path.join(__dirname, srcDir + 'options.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 CopyPlugin([
{ from: '.', to: '../', ignore: ['manifest.json'] }
],
{context: 'public' }
),
new BuildManifest({
browser: env.browser,
pretty: env.mode === "production"
})
]
});

7
webpack/webpack.dev.js Normal file
View File

@@ -0,0 +1,7 @@
const merge = require('webpack-merge');
const common = require('./webpack.common.js');
module.exports = env => merge(common(env), {
devtool: 'inline-source-map',
mode: 'development'
});

View File

@@ -0,0 +1,67 @@
const webpack = require("webpack");
const path = require('path');
const CopyPlugin = require('copy-webpack-plugin');
const validateOptions = require('schema-utils');
const fs = require('fs');
const manifest = require("../manifest/manifest.json");
const firefoxManifestExtra = require("../manifest/firefox-manifest-extra.json");
const chromeManifestExtra = require("../manifest/chrome-manifest-extra.json");
// schema for options object
const schema = {
type: 'object',
properties: {
browser: {
type: 'string'
},
pretty: {
type: 'boolean'
}
}
};
class BuildManifest {
constructor (options = {}) {
validateOptions(schema, options, "Build Manifest Plugin");
this.options = options;
}
apply(compiler) {
const distFolder = path.resolve(__dirname, "../dist/");
const distManifestFile = path.resolve(distFolder, "manifest.json");
// Add missing manifest elements
if (this.options.browser.toLowerCase() === "firefox") {
mergeObjects(manifest, firefoxManifestExtra);
} else if (this.options.browser.toLowerCase() === "chrome" || this.options.browser.toLowerCase() === "chromium") {
mergeObjects(manifest, chromeManifestExtra);
}
let result = JSON.stringify(manifest);
if (this.options.pretty) result = JSON.stringify(manifest, null, 2);
fs.mkdirSync(distFolder, {recursive: true});
fs.writeFileSync(distManifestFile, result);
}
}
function mergeObjects(object1, object2) {
for (const key in object2) {
if (key in object1) {
if (Array.isArray(object1[key])) {
object1[key] = object1[key].concat(object2[key]);
} else if (typeof object1[key] == 'object') {
mergeObjects(object1[key], object2[key]);
} else {
object1[key] = object2[key];
}
} else {
object1[key] = object2[key];
}
}
}
module.exports = BuildManifest;

11
webpack/webpack.prod.js Normal file
View File

@@ -0,0 +1,11 @@
const merge = require('webpack-merge');
const common = require('./webpack.common.js');
module.exports = env => {
let mode = "production";
env.mode = mode;
return merge(common(env), {
mode
});
};