Compare commits

...

123 Commits
1.2.4 ... 1.2.8

Author SHA1 Message Date
Ajay Ramachandran
79b02c7ae7 Merge pull request #269 from ajayyy/experimental
Fixed Firefox recursion issue
2020-02-09 10:36:20 -05:00
Ajay Ramachandran
cfb902180d Increased version number. 2020-02-09 10:35:32 -05:00
Ajay Ramachandran
1753c05686 Fix Firefox infinite recursion issue. 2020-02-09 10:34:18 -05:00
Ajay Ramachandran
c76d2c9a95 Merge pull request #266 from ajayyy/experimental
Options page improvements + Preview sponsors
2020-02-08 23:30:12 -05:00
Ajay Ramachandran
00117bdfac Increase version number. 2020-02-08 23:29:09 -05:00
Ajay Ramachandran
40cddbf8ee Disabling auto skip now enables the notice. 2020-02-08 23:28:41 -05:00
Ajay Ramachandran
9026044528 Fixed preview sponsor times not turning into actual ones upon submission. 2020-02-08 23:16:44 -05:00
Ajay Ramachandran
50f293d783 Added better server address trimming. 2020-02-08 23:01:28 -05:00
Ajay Ramachandran
2b558c43bd Removed subdomain requirement. 2020-02-08 22:55:59 -05:00
Ajay Ramachandran
22cc51734f Merge pull request #264 from ajayyy/privacy-confirm
Added privacy confirmation for unlisted videos
2020-02-08 22:45:22 -05:00
Ajay Ramachandran
cd5adc1b82 Merge pull request #265 from ajayyy/typescript
Issue fixing
2020-02-08 22:44:47 -05:00
Ajay Ramachandran
b7e83a0efe Added unlisted check option.
https://github.com/ajayyy/SponsorBlock/pull/224

Co-authored-by: OfficialNoob <OfficialNoob@users.noreply.github.com>
2020-02-08 22:43:27 -05:00
Ajay Ramachandran
6159605afd Merge branch 'master' of https://github.com/ajayyy/SponsorBlock into typescript
# Conflicts:
#	public/_locales/en/messages.json
#	src/config.ts
2020-02-08 22:19:07 -05:00
Ajay Ramachandran
ceae02426d Merge pull request #262 from Joe-Dowd/customServerAddress
Add custom server address
2020-02-08 22:18:07 -05:00
Ajay Ramachandran
51d2886f70 Seperated show upload button option from info button. 2020-02-08 22:18:03 -05:00
Ajay Ramachandran
c2eb973a28 Fixed centering and added some more notes to the help page. 2020-02-08 22:13:36 -05:00
Ajay Ramachandran
f8532d39eb Improved invidious warning on the help page. 2020-02-08 22:07:51 -05:00
Ajay Ramachandran
c2be1ee6d4 Added reset button to text change. 2020-02-08 22:03:45 -05:00
Ajay Ramachandran
1735cdb45d Added server address input validation. 2020-02-08 21:58:50 -05:00
Ajay Ramachandran
be6cd62fbe Rename string change option to more fitting name. 2020-02-08 21:42:15 -05:00
Ajay Ramachandran
cfe91f6280 Changed order of options. 2020-02-08 21:34:26 -05:00
Ajay Ramachandran
4e7bfa5ed3 Changed server address config name 2020-02-08 21:32:46 -05:00
Ajay Ramachandran
6f03c68fdc Change the way server address defaults are handled. 2020-02-08 21:30:57 -05:00
Ajay Ramachandran
62653705ff Improved custom instance message 2020-02-08 21:27:55 -05:00
Ajay Ramachandran
3791c5c78b Fixed JSON conversion iterator 2020-02-08 20:05:31 -05:00
Joe Dowd
6bd22896b9 Merged Lartza/min-duration 2020-02-09 01:02:40 +00:00
Ajay Ramachandran
ea4331e4b1 Merge branch 'master' of https://github.com/ajayyy/SponsorBlock into typescript
# Conflicts:
#	src/config.ts
2020-02-08 19:31:18 -05:00
Ajay Ramachandran
84fbc93c8a Merge pull request #250 from Lartza/min-duration
Add minimum duration option
2020-02-08 19:30:36 -05:00
Ajay Ramachandran
553581f67b Removed extra file 2020-02-08 19:28:23 -05:00
Ajay Ramachandran
afe16e6623 Improved confirm message. 2020-02-08 19:27:38 -05:00
Ajay Ramachandran
0942da0cd1 Moved minimum duration open down 2020-02-08 19:23:48 -05:00
Ajay Ramachandran
c2b3835618 Added colon to minimum duration message 2020-02-08 19:23:34 -05:00
Ajay Ramachandran
4a96a52398 Fixed multiple sponsors being hidden. 2020-02-08 19:19:27 -05:00
Ajay Ramachandran
4e1eb82538 Fixed typescript issues. 2020-02-08 19:10:04 -05:00
Ajay Ramachandran
885fae175d Merge branch 'master' of https://github.com/ajayyy/SponsorBlock into min-duration
# Conflicts:
#	SB.js
#	src/options.ts
2020-02-08 19:09:17 -05:00
Ajay Ramachandran
1cd84d8a2c Fixed Chrome Invidious CSS issues.
Apparently the chrome API doesn't work well with CSS.

Resolves https://github.com/ajayyy/SponsorBlock/issues/241
2020-02-08 18:23:08 -05:00
Ajay Ramachandran
0c891e0a09 Changed time saved date to updated time. 2020-02-08 17:51:16 -05:00
Ajay Ramachandran
08c7754f29 Fixed skip count resetting.
Resolves https://github.com/ajayyy/SponsorBlock/issues/256
2020-02-08 17:50:06 -05:00
Ajay Ramachandran
5f21d3f96f Default UUIDs to an empty list instead of null.
Resolves https://github.com/ajayyy/SponsorBlock/issues/252.
2020-02-08 17:45:22 -05:00
Ajay Ramachandran
158b76c17e Fixed incorrect type. 2020-02-06 20:10:35 -05:00
Joe Dowd
3455a79298 Removed console.log calls 2020-02-07 00:57:24 +00:00
Joe Dowd
dd08ff1507 add custom server address 2020-02-07 00:40:01 +00:00
Lartza
b5996a9783 Discard short segments instead of ignoring them later 2020-02-05 01:12:03 +02:00
Lartza
edb108bbcf Confirm shorter than minDuration submissions 2020-02-04 22:36:22 +00:00
Ajay Ramachandran
feda7fd1a0 Merge pull request #260 from ajayyy/typescript
Rename SB.js
2020-02-04 02:16:47 -05:00
Ajay Ramachandran
46ffb330fc Fix invidious enabling error 2020-02-04 02:15:06 -05:00
Ajay Ramachandran
2f7a4bdb36 Fix chrome build scripts 2020-02-04 02:14:30 -05:00
Ajay Ramachandran
96dab6b6b2 Renamed SB.js and setup types for the config. 2020-02-04 02:10:59 -05:00
Ajay Ramachandran
3e9afe1dc8 Merge pull request #259 from ajayyy/typescript
Manifest building
2020-02-04 01:57:48 -05:00
Ajay Ramachandran
2df2b4c53d Mkdir with directory 2020-02-04 01:48:32 -05:00
Ajay Ramachandran
968dd34c3d Update README.md 2020-02-04 01:45:41 -05:00
Ajay Ramachandran
0cbe73c527 Add recursive to mkdir 2020-02-04 01:43:49 -05:00
Ajay Ramachandran
6d312e7ba7 Create dir when building if needed. 2020-02-04 01:41:47 -05:00
Ajay Ramachandran
09a33c4252 Added manifest building. 2020-02-04 01:32:58 -05:00
Ajay Ramachandran
76d966c1f9 Merge pull request #254 from ajayyy/typescript
Moving to TypeScript
2020-02-04 00:22:08 -05:00
Ajay Ramachandran
da364b49f0 Removed old config example 2020-02-04 00:16:46 -05:00
Ajay Ramachandran
f6f416b899 Fixed direct link channel whitelisting 2020-02-04 00:13:38 -05:00
Ajay Ramachandran
3582f45fd8 Fixed skip notice references fixing manual skip notices. 2020-02-04 00:05:03 -05:00
Ajay Ramachandran
13cef5ff14 Remove invidious message 2020-02-03 23:40:44 -05:00
Ajay Ramachandran
4049d2d1ab Fixed keybind not working 2020-02-03 23:39:41 -05:00
Ajay Ramachandran
db7ac02c7d Fix CI filepath 2020-02-03 23:25:50 -05:00
Ajay Ramachandran
26f3560e97 CI uses new manifest location 2020-02-03 23:23:26 -05:00
Ajay Ramachandran
1eaf4f44c2 Fixed incorrect syntax in example config 2020-02-03 23:20:57 -05:00
Ajay Ramachandran
0a0d4d3e0f Fixed config.json.example naming 2020-02-03 23:16:05 -05:00
Ajay Ramachandran
7f22687c24 Updated CI to use config.json 2020-02-03 23:13:40 -05:00
Ajay Ramachandran
7404e0831b Updated CI 2020-02-03 23:11:01 -05:00
Ajay Ramachandran
e3be54cbe7 Updated build commands 2020-02-03 23:10:04 -05:00
Ajay Ramachandran
b0a23a5c4e Converted options page to TypeScript 2020-02-03 22:34:43 -05:00
Ajay Ramachandran
5bb5dae20e Added config types and added back handler to proxy 2020-02-03 14:11:52 -05:00
Ajay Ramachandran
bb9de35722 Fixed naming issue 2020-02-02 18:26:43 -05:00
Ajay Ramachandran
4907be7738 Fixed maps not being handled properly 2020-02-01 19:25:40 -05:00
Ajay Ramachandran
23141aa624 Made the extension successfully build 2020-02-01 19:18:53 -05:00
Ajay Ramachandran
17381e7deb Moved Utils away from a static class.
Moved Firefox content script registration to the background script.

Moved onInvidious to the content script.
2020-02-01 18:47:36 -05:00
Ajay Ramachandran
4bd410f04e Made the skip notice work with TypeScript. 2020-02-01 18:17:48 -05:00
Ajay Ramachandran
20a09d3d27 Fixed type errors. 2020-02-01 17:51:42 -05:00
Ajay Ramachandran
98b36380b5 Update readme to new config name 2020-02-01 17:46:55 -05:00
Ajay Ramachandran
7716827a98 Fixed incorrect import. 2020-02-01 17:40:12 -05:00
Ajay Ramachandran
070a51954a Fixed channel ID promise 2020-02-01 17:38:58 -05:00
Ajay Ramachandran
de66e21d14 Added support for the on page popup. 2020-02-01 17:36:02 -05:00
Ajay Ramachandran
932cf8ecf1 Fixed SBMap not importing entries. 2020-02-01 16:53:33 -05:00
Ajay Ramachandran
4a491f1ebf Moved server code to utils 2020-02-01 16:41:08 -05:00
Ajay Ramachandran
5ebdfd8466 Added config to gitignore. 2020-02-01 16:27:58 -05:00
Ajay Ramachandran
16c720e500 Setup new config.json and fixed typescript errors in the popup 2020-02-01 16:26:57 -05:00
Ajay Ramachandran
d77f8abf42 Converted more code to TypeScript. 2020-01-28 23:52:15 -05:00
Ajay Ramachandran
03836b69f2 Started conversion to TypeScript. 2020-01-28 22:16:48 -05:00
Ajay Ramachandran
5837205a9a Updated API URL 2020-01-28 15:45:18 -05:00
Ajay Ramachandran
0f346c2096 Updated API url 2020-01-28 15:38:32 -05:00
Ajay Ramachandran
30acfdd788 Uses old query 2020-01-25 12:20:40 -05:00
Ajay Ramachandran
478edfee7c Changed to new user count query 2020-01-25 12:20:18 -05:00
Ajay Ramachandran
a685950a66 Use defaults if it the config is broken 2020-01-24 21:44:24 -05:00
Ajay Ramachandran
69d1d709f6 Merge pull request #251 from ajayyy/experimental
Removed December date.
2020-01-24 21:43:03 -05:00
Ajay Ramachandran
1fcfdcd13c Removed December date. 2020-01-24 21:39:06 -05:00
Lartza
34a21c6d8c Handle invalid current minDuration in options.js 2020-01-24 02:29:11 +02:00
Lartza
e6ef27936e Add minimum duration option 2020-01-24 01:02:23 +02:00
Ajay Ramachandran
2b7f940669 Merge pull request #248 from ajayyy/hotfix-invidious
Fixed firefox content script registration
2020-01-21 23:05:33 -05:00
Ajay Ramachandran
bdcd613f02 Update version number 2020-01-21 23:05:23 -05:00
Ajay Ramachandran
7255300903 Merge pull request #244 from OfficialNoob/patch-5
Support options button when not in popup
2020-01-19 21:44:03 -05:00
Ajay Ramachandran
21b2ad71b9 Merge pull request #245 from jplsek/opt-html5
Set options and help page to use html5
2020-01-19 10:11:23 -05:00
Ajay Ramachandran
1ff7e69556 Merge pull request #247 from OfficialNoob/patch-6
Save Map content
2020-01-19 10:09:28 -05:00
Official Noob
f7a3f98ad3 Comments 2020-01-16 18:43:49 +00:00
Official Noob
4cdb874b4f Removed deleteProperty from MapIO 2020-01-16 18:27:29 +00:00
Official Noob
5df0801c64 deleteProperty() if no key provided delete by id 2020-01-16 18:13:07 +00:00
Official Noob
e286797ac5 Update SB.js 2020-01-16 18:03:40 +00:00
Official Noob
e784dc017d Added clear 2020-01-16 18:02:48 +00:00
Official Noob
251339f26b Comments and added delete 2020-01-16 17:57:54 +00:00
Official Noob
3e3b8eab6f Save Map content 2020-01-16 14:29:03 +00:00
Ajay Ramachandran
a7d769dd6d Merge pull request #246 from jplsek/keybind-esc
Allow keybind cancel with ESC
2020-01-15 21:39:14 -05:00
Jeremy Plsek
ad37e9abe7 Allow keybind cancel with ESC 2020-01-15 20:50:51 -05:00
Jeremy Plsek
f99a3f5088 Set options and help page to use html5
- Fix css when using html5
- Add encoding scheme to html
- Remove font-size change on title on hover. It's jarring
seeing the whole page move when trying to move your mouse
to the options.
- Remove center tags
- Remove unnecessary styles
- Fix some minor grammar
2020-01-15 20:30:07 -05:00
Ajay Ramachandran
6f2a09695b Update README.md 2020-01-14 10:32:26 -05:00
Official Noob
9ae8769869 inPopup did not always work 2020-01-13 20:08:06 +00:00
Official Noob
f58a16179a Added "openConfig" message 2020-01-13 19:54:24 +00:00
Official Noob
297c95ac6b Added inPopup check for options button 2020-01-13 19:52:54 +00:00
Ajay Ramachandran
ced3100b4e Merge pull request #243 from Deykun/master
Polish translation created
2020-01-12 19:04:57 -05:00
Szymon T
7358be6faa Shorter version of fullName 2020-01-13 00:27:55 +01:00
Szymon T
03e29dbb81 Polish translation created 2020-01-12 23:46:38 +01:00
Ajay Ramachandran
7cb2915fd7 Fixed firefox content script registration. 2020-01-11 17:48:11 -05:00
Ajay Ramachandran
5348496768 Merge pull request #235 from OfficialNoob/patch-2
Formatting changes & proxy delete support
2020-01-11 15:17:34 -05:00
Ajay Ramachandran
7526abc3e1 Merge pull request #236 from ajayyy/hotfix-invidious
Fixed new line issue
2020-01-11 14:55:34 -05:00
Ajay Ramachandran
1163b6f0ce Fixed wrong variabled being used. 2020-01-11 14:50:11 -05:00
Ajay Ramachandran
7c809419f8 Formatting changes 2020-01-11 14:48:17 -05:00
Ajay Ramachandran
0a42f130ac Fixed new line issue 2020-01-11 14:45:16 -05:00
Official Noob
4de0ae51e8 Formatting changes & proxy delete support 2020-01-11 19:42:35 +00:00
65 changed files with 7788 additions and 1515 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>
@@ -32,13 +32,15 @@
SponsorBlock is an extension that will skip over sponsored segments of YouTube videos. SponsorBlock is a crowdsourced browser extension that lets anyone submit the start and end times of sponsored segments of YouTube videos. Once one person submits this information, everyone else with this extension will skip right over the sponsored segment.
Also support Invidio.us.
# Server
The backend server code is available here: https://github.com/ajayyy/SponsorBlockServer
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.
@@ -48,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

189
SB.js
View File

@@ -1,189 +0,0 @@
SB = {
/**
* Callback function when an option is updated
*
* @type {CallableFunction}
*/
configListeners: []
};
// Function setup
Map.prototype.toJSON = function() {
return Array.from(this.entries());
};
class MapIO {
constructor(id) {
this.id = id;
this.map = SB.localConfig[this.id];
}
set(key, value) {
this.map.set(key, value);
SB.config.handler.set(undefined, this.id, encodeStoredItem(this.map));
return this.map;
}
get(key) {
return this.map.get(key);
}
has(key) {
return this.map.has(key);
}
deleteProperty(key) {
if (this.map.has(key)) {
this.map.delete(key);
return true;
} else {
return false;
}
}
size() {
return this.map.size;
}
delete(key) {
this.map.delete(key);
SB.config.handler.set(undefined, this.id, encodeStoredItem(this.map));
}
}
/**
* A Map 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 instanceof Map)) return data;
return JSON.stringify(data);
}
/**
* A Map cannot be stored in the chrome storage.
* This data will be decoded from the array it is stored in
*
* @param {*} data
*/
function decodeStoredItem(data) {
if(typeof data !== "string") return data;
try {
let str = JSON.parse(data);
if(!Array.isArray(str)) return data;
return new Map(str);
} catch(e) {
// If all else fails, return the data
return data;
}
}
function configProxy() {
chrome.storage.onChanged.addListener((changes, namespace) => {
for (const key in changes) {
SB.localConfig[key] = decodeStoredItem(changes[key].newValue);
}
for (const callback of SB.configListeners) {
callback(changes);
}
});
var handler = {
set: function(obj, prop, value) {
SB.localConfig[prop] = value;
chrome.storage.sync.set({
[prop]: encodeStoredItem(value)
});
},
get: function(obj, prop) {
let data = SB.localConfig[prop];
if(data instanceof Map) data = new MapIO(prop);
return obj[prop] || data;
}
};
return new Proxy({handler}, handler);
}
function fetchConfig() {
return new Promise((resolve, reject) => {
chrome.storage.sync.get(null, function(items) {
SB.localConfig = items; // Data is ready
resolve();
});
});
}
function migrateOldFormats() { // Convert sponsorTimes format
for (key in SB.localConfig) {
if (key.startsWith("sponsorTimes") && key !== "sponsorTimes" && key !== "sponsorTimesContributed") {
SB.config.sponsorTimes.set(key.substr(12), SB.config[key]);
chrome.storage.sync.remove(key);
}
}
}
async function setupConfig() {
await fetchConfig();
addDefaults();
convertJSON();
SB.config = configProxy();
migrateOldFormats();
}
SB.defaults = {
"sponsorTimes": new Map(),
"startSponsorKeybind": ";",
"submitKeybind": "'",
"minutesSaved": 0,
"skipCount": 0,
"sponsorTimesContributed": 0,
"disableSkipping": false,
"disableAutoSkip": false,
"trackViewCount": true,
"dontShowNotice": false,
"hideVideoPlayerControls": false,
"hideInfoButtonPlayerControls": false,
"hideDeleteButtonPlayerControls": false,
"hideDiscordLaunches": 0,
"hideDiscordLink": false,
"invidiousInstances": ["invidio.us", "invidiou.sh", "invidious.snopyta.org"],
"invidiousUpdateInfoShowCount": 0,
"autoUpvote": true
}
// Reset config
function resetConfig() {
SB.config = SB.defaults;
};
function convertJSON() {
Object.keys(SB.defaults).forEach(key => {
SB.localConfig[key] = decodeStoredItem(SB.localConfig[key], key);
});
}
// Add defaults
function addDefaults() {
for (const key in SB.defaults) {
if(!SB.localConfig.hasOwnProperty(key)) {
SB.localConfig[key] = SB.defaults[key];
}
}
};
// Sync config
setupConfig();

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

View File

@@ -3,6 +3,7 @@ files:
translation: /_locales/%two_letters_code%/%original_file_name%
languages_mapping:
two_letters_code:
pl-PL: "pl_PL"
pr-BR: "pt_BR"
pr-PT: "pt_PT"
zh-CN: "zh_CH"

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.4",
"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
}
}

72
manifest/manifest.json Normal file
View File

@@ -0,0 +1,72 @@
{
"name": "__MSG_fullName__",
"short_name": "__MSG_Name__",
"version": "1.2.8",
"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",
"content.css"
],
"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
}

View File

@@ -1,285 +0,0 @@
window.addEventListener('DOMContentLoaded', init);
async function init() {
localizeHtmlPage();
if (!SB.configListeners.includes(optionsConfigUpdateListener)) {
SB.configListeners.push(optionsConfigUpdateListener);
}
await wait(() => SB.config !== undefined);
// Set all of the toggle options to the correct option
let optionsContainer = document.getElementById("options");
let optionsElements = optionsContainer.querySelectorAll("*");
for (let i = 0; i < optionsElements.length; i++) {
switch (optionsElements[i].getAttribute("option-type")) {
case "toggle":
let option = optionsElements[i].getAttribute("sync-option");
let optionResult = SB.config[option];
let checkbox = optionsElements[i].querySelector("input");
let reverse = optionsElements[i].getAttribute("toggle-type") === "reverse";
if (optionResult != undefined) {
checkbox.checked = optionResult;
if (reverse) {
optionsElements[i].querySelector("input").checked = !optionResult;
}
}
// See if anything extra should be run first time
switch (option) {
case "supportInvidious":
invidiousInit(checkbox, option);
break;
}
// Add click listener
checkbox.addEventListener("click", () => {
SB.config[option] = reverse ? !checkbox.checked : checkbox.checked;
// See if anything extra must be run
switch (option) {
case "supportInvidious":
invidiousOnClick(checkbox, option);
break;
}
});
break;
case "text-change":
let button = optionsElements[i].querySelector(".trigger-button");
button.addEventListener("click", () => activateTextChange(optionsElements[i]));
let textChangeOption = optionsElements[i].getAttribute("sync-option");
// See if anything extra must be done
switch (textChangeOption) {
case "invidiousInstances":
invidiousInstanceAddInit(optionsElements[i], textChangeOption);
}
break;
case "keybind-change":
let keybindButton = optionsElements[i].querySelector(".trigger-button");
keybindButton.addEventListener("click", () => activateKeybindChange(optionsElements[i]));
break;
case "display":
updateDisplayElement(optionsElements[i])
}
}
optionsContainer.classList.remove("hidden");
optionsContainer.classList.add("animated");
}
/**
* Called when the config is updated
*
* @param {String} element
*/
function optionsConfigUpdateListener(changes) {
let optionsContainer = document.getElementById("options");
let optionsElements = optionsContainer.querySelectorAll("*");
for (let i = 0; i < optionsElements.length; i++) {
switch (optionsElements[i].getAttribute("option-type")) {
case "display":
updateDisplayElement(optionsElements[i])
}
}
}
/**
* Will set display elements to the proper text
*
* @param {HTMLElement} element
*/
function updateDisplayElement(element) {
let displayOption = element.getAttribute("sync-option")
let displayText = SB.config[displayOption];
element.innerText = displayText;
// See if anything extra must be run
switch (displayOption) {
case "invidiousInstances":
element.innerText = displayText.join(', ');
break;
}
}
/**
* Initializes the option to add Invidious instances
*
* @param {HTMLElement} element
* @param {String} option
*/
function invidiousInstanceAddInit(element, option) {
let textBox = element.querySelector(".option-text-box");
let button = element.querySelector(".trigger-button");
let setButton = element.querySelector(".text-change-set");
setButton.addEventListener("click", async function(e) {
if (textBox.value == "" || textBox.value.includes("/") || textBox.value.includes("http") || textBox.value.includes(":")) {
alert(chrome.i18n.getMessage("addInvidiousInstanceError"));
} else {
// Add this
let instanceList = SB.config[option];
if (!instanceList) instanceList = [];
instanceList.push(textBox.value);
SB.config[option] = instanceList;
let checkbox = document.querySelector("#support-invidious input");
checkbox.checked = true;
invidiousOnClick(checkbox, "supportInvidious");
textBox.value = "";
// Hide this section again
element.querySelector(".option-hidden-section").classList.add("hidden");
button.classList.remove("disabled");
}
});
let resetButton = element.querySelector(".invidious-instance-reset");
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);
}
});
}
/**
* Run when the invidious button is being initialized
*
* @param {HTMLElement} checkbox
* @param {string} option
*/
function invidiousInit(checkbox, option) {
let permissions = ["declarativeContent"];
if (isFirefox()) permissions = [];
chrome.permissions.contains({
origins: getInvidiousInstancesRegex(),
permissions: permissions
}, function (result) {
if (result != checkbox.checked) {
SB.config[option] = result;
checkbox.checked = result;
}
});
}
/**
* Run whenever the invidious checkbox is clicked
*
* @param {HTMLElement} checkbox
* @param {string} option
*/
function invidiousOnClick(checkbox, option) {
if (checkbox.checked) {
setupExtraSitePermissions(function (granted) {
if (!granted) {
SB.config[option] = false;
checkbox.checked = false;
}
});
} else {
removeExtraSiteRegistration();
}
}
/**
* Will trigger the container to ask the user for a keybind.
*
* @param {HTMLElement} element
*/
function activateKeybindChange(element) {
let button = element.querySelector(".trigger-button");
if (button.classList.contains("disabled")) return;
button.classList.add("disabled");
let option = element.getAttribute("sync-option");
let currentlySet = SB.config[option] !== null ? chrome.i18n.getMessage("keybindCurrentlySet") : "";
let status = 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];
}
element.querySelector(".option-hidden-section").classList.remove("hidden");
document.addEventListener("keydown", (e) => keybindKeyPressed(element, e), {once: true});
}
/**
* Called when a key is pressed in an activiated keybind change option.
*
* @param {HTMLElement} element
* @param {KeyboardEvent} e
*/
function keybindKeyPressed(element, e) {
e = e || window.event;
var key = e.key;
let option = element.getAttribute("sync-option");
SB.config[option] = key;
let status = element.querySelector(".option-hidden-section > .keybind-status");
status.innerText = chrome.i18n.getMessage("keybindDescriptionComplete");
let statusKey = element.querySelector(".option-hidden-section > .keybind-status-key");
statusKey.innerText = key;
let button = element.querySelector(".trigger-button");
button.classList.remove("disabled");
}
/**
* Will trigger the textbox to appear to be able to change an option's text.
*
* @param {HTMLElement} element
*/
function activateTextChange(element) {
let button = element.querySelector(".trigger-button");
if (button.classList.contains("disabled")) return;
button.classList.add("disabled");
let textBox = element.querySelector(".option-text-box");
let option = element.getAttribute("sync-option");
// See if anything extra must be done
switch (option) {
case "invidiousInstances":
element.querySelector(".option-hidden-section").classList.remove("hidden");
return;
}
textBox.value = SB.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;
}
});
element.querySelector(".option-hidden-section").classList.remove("hidden");
}

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

@@ -349,7 +349,7 @@
"message": "Support Invidious"
},
"supportInvidiousDescription": {
"message": "Invidious (invidio.us) is a third party YouTube client. To enable support, you must accept the extra permissions. This does NOT work in incongnito on chrome and other chromium variants."
"message": "Invidious (invidio.us) is a third party YouTube client. To enable support, you must accept the extra permissions. This does NOT work in incongnito on Chrome and other Chromium variants."
},
"optionsInfo": {
"message": "Enable Invidious support, disable autoskip, hide buttons and more."
@@ -386,5 +386,47 @@
},
"invidiousInfo2": {
"message": "You MUST enable it in the options for it to work."
},
"minDuration": {
"message": "Minimum duration (seconds):"
},
"minDurationDescription": {
"message": "Sponsor segments shorter than the set value will not be skipeed or show in the player."
},
"shortCheck": {
"message": "The following submission is shorter than your minimum duration option. This could mean that this is already submitted, and just being ignored due to this option. Are you sure you would like to submit?"
},
"showUploadButton": {
"message": "Show Upload Button"
},
"whatUploadButton": {
"message": "This button appears on the YouTube player after you have selected a timestamp and are ready to submit."
},
"customServerAddress": {
"message": "SponsorBlock Server Address"
},
"customServerAddressDescription": {
"message": "The address SponsorBlock uses to make calls to the server.\nUnless you have your own server instance, this should not be changed."
},
"save": {
"message": "Save"
},
"reset": {
"message": "Reset"
},
"customAddressError": {
"message": "This address is not in the right form. Make sure you have http:// or https:// at the begining and no trailing slashes."
},
"areYouSureReset": {
"message": "Are you sure you would like to reset this?"
},
"confirmPrivacy": {
"message": "The video has been detected as unlisted. Click cancel if you do not want to check for sponsors."
},
"unlistedCheck": {
"message": "Ignore Unlisted Videos"
},
"whatUnlistedCheck": {
"message": "This setting will significantly slow down SponsorBlock. Sponsor lookups require sending the video ID to the server. If you are concerned about unlisted video IDs being sent over the internet, enable this option."
}
}

View File

@@ -0,0 +1,390 @@
{
"Name": {
"message": "SponsorBlock",
"description": "Nazwa rozszerzenia."
},
"fullName": {
"message": "SponsorBlock na YouTube - Omiń reklamy sponsorów",
"description": "Nazwa rozszerzenia."
},
"Description": {
"message": "Przewijaj reklamy sponsorów w filmach na YouTube. Zgłaszaj reklamy w nagraniach żeby nie marnować czasu innych.",
"description": "Opis rozszerzenia."
},
"helpPage": {
"message": "index_en.html"
},
"400": {
"message": "Serwer odpowiedział, że to zapytanie jest niepoprawne"
},
"429": {
"message": "Zgłosiłeś bardzo dużo segmentów reklamowych dla tego jednego nagrania, jesteś pewien, że jest ich tak dużo?"
},
"409": {
"message": "Treść została już wcześniej zgłoszona"
},
"channelWhitelisted": {
"message": "Kanał dodany do wyjątków!"
},
"Sponsor": {
"message": "sponsor"
},
"Sponsors": {
"message": "sponsorzy"
},
"Segment": {
"message": "segmet sponsorowany"
},
"Segments": {
"message": "segmenty sponsorowane"
},
"noticeTitle": {
"message": "Segment przewinięty"
},
"reportButtonTitle": {
"message": "Zgłoś"
},
"reportButtonInfo": {
"message": "Zgłoś ten segment reklamowy jako nieprawidłowy."
},
"Dismiss": {
"message": "Odrzuć"
},
"Loading": {
"message": "Ładowanie..."
},
"Mins": {
"message": "Minuty"
},
"Secs": {
"message": "Sekundy"
},
"Hide": {
"message": "Nigdy nie pokazuj"
},
"hitGoBack": {
"message": "Kliknij cofnij aby przenieść się do miejsca przed przewinięciem."
},
"unskip": {
"message": "Cofnij"
},
"reskip": {
"message": "Przewiń"
},
"paused": {
"message": "Zatrzymany"
},
"confirmMSG": {
"message": "Żeby zmienić lub usunąć wartości, kliknij na guzik informacji lub otwórz okienko rozszerzenia klikając w ikonę rozszerzenia znajdującą się w prawym górnym rogu."
},
"clearThis": {
"message": "Jesteś pewien, że chcesz to usunąć?\n\n"
},
"Unknown": {
"message": "Wystąpił błąd podczas przesyłania twojego zgłoszenia, proszę spróbować ponownie później."
},
"sponsorFound": {
"message": "Segmenty reklamowe dla tego nagrania są już w bazie!"
},
"sponsor404": {
"message": "Nie znaleziono segmentów reklamowych"
},
"sponsorStart": {
"message": "Reklama zaczyna się teraz"
},
"sponsorEnd": {
"message": "Reklama kończy się teraz"
},
"noVideoID": {
"message": "Nie znaleziono nagrania wideo w tej karcie. Jeśli wiesz, że to karta YouTube'a, zamknij to okienko i otwórz je ponownie. Jeśli to nie zadziała spróbuj przeładować stronę."
},
"success": {
"message": "Sukces!"
},
"voted": {
"message": "Zagłosowano!"
},
"voteFail": {
"message": "Już na to głosowałeś."
},
"serverDown": {
"message": "Wygląda na to, że serwer nie działa. Skontaktuj się z dewloperem."
},
"connectionError": {
"message": "Błąd z połączeniem. Kod błędu: "
},
"wantToSubmit": {
"message": "Chcesz zgłosić segment sponsorowany dla nagrania z id"
},
"leftTimes": {
"message": "Wygląda na to, że masz nie wysłane segmenty reklamowe. Cofnij się do tej strony i zgłoś je (nie zostały usunięte)."
},
"clearTimes": {
"message": "Wyczyść segmenty reklamowe"
},
"openPopup": {
"message": "Otwórz okienko SponsorBlock"
},
"SubmitTimes": {
"message": "Zgłoś segmenty reklamowe"
},
"submitCheck": {
"message": "Jesteś pewien, że chcesz to zgłosić?"
},
"whitelistChannel": {
"message": "Dodaj kanał do wyjątków"
},
"removeFromWhitelist": {
"message": "Usuń kanał z listy wyjątków"
},
"voteOnTime": {
"message": "Głosuj na segment reklamowy"
},
"recordTimes": {
"message": "Nagraj czasy segmentów reklamowych"
},
"soFarUHSubmited": {
"message": "Jak na razie zgłosiłeś:"
},
"savedPeopleFrom": {
"message": "Ocaliłeś ludzi przed "
},
"viewLeaderboard": {
"message": "Zobacz ranking użytkowników"
},
"here": {
"message": "tutaj"
},
"recordTimesDescription": {
"message": "Kliknij guzik poniżej kiedy segment reklamowy się zaczyna i kończy żeby go oznaczyć i wysłać do bazy danych."
},
"popupHint": {
"message": "Podpowiedź: Klikając średnik kiedy zaznaczone jest zgłaszanie wideo możesz oznaczyć początek reklamy, znakiem cytatu oznaczysz jej koniec. (Klawisze można zmienić w opcjach)"
},
"lastTimes": {
"message": "Ostanie wybrane czasy reklam"
},
"clearTimesButton": {
"message": "Usuń czasy"
},
"submitTimesButton": {
"message": "Zgłoś czasy"
},
"publicStats": {
"message": "Ten dane są używane na naszej stronie żeby pokazać twój wkład. Zobacz to"
},
"setUsername": {
"message": "Ustaw nazwę użytkownika"
},
"discordAdvert": {
"message": "Dołącz do oficjalnego serwera na discordzie i podziel się wrażeniami i sugestiami!"
},
"hideThis": {
"message": "Ukryj to"
},
"Options": {
"message": "Opcje"
},
"showButtons": {
"message": "Pokaż guziki w odtwarzaczu YouTube"
},
"hideButtons": {
"message": "Ukryj guziki w odtwarzaczu YouTube"
},
"hideButtonsDescription": {
"message": "Ta opcja ukrywa guziki zgłaszania reklamy w odtwarzaczu. Wiem, że mogą one irytować niektórych. Zamiast zgłaszania bezpośrednio w odtwarzaczu możesz to zrobić w tym okienku. Zawsze możesz zmienić te opcje później."
},
"showInfoButton": {
"message": "Pokaż guzik informacyjny w odtwarzaczu YouTube"
},
"hideInfoButton": {
"message": "Ukryj guzik informacyjny w odtwarzaczu YouTube"
},
"whatInfoButton": {
"message": "Jest to guzik otwierający popup na stronie YouTube."
},
"hideDeleteButton": {
"message": "Ukryj guzik usuwania w odtwarzaczu YouTube"
},
"showDeleteButton": {
"message": "Pokaż guzik usuwania w odtwarzaczu YouTube"
},
"whatDeleteButton": {
"message": "Ten guzik pozwala ci wyczyścić wszystkie segmenty reklamowe w odtwarzaczu YouTube."
},
"disableViewTracking": {
"message": "Wyłącz licznik przewinięć"
},
"enableViewTracking": {
"message": "Włącz licznik przewinięć"
},
"whatViewTracking": {
"message": "Ta opcja śledzi które segmenty pominąłeś i informuje zgłaszających ile czasu Ci zaoszczędzili, też wraz systemem głosowania pomaga wykrywać spam w zgłoszeniach. Rozszerzenie wysyła zapytanie do serwera za każdym razem kiedy przewinąłeś segment reklamowy. Miejmy nadzieję, że większość ludzi tego nie wyłączy i licznik wyświetleń będzie rzetelny. :)"
},
"showNotice": {
"message": "Pokaż informacje ponownie"
},
"longDescription": {
"message": "SponsorBlock jest rozszerzeniem które przewinie segmenty sponsorów w filmach na YouTube. SponsorBlock jest opartym na crowdsourcing rozszerzeniem które pozwala każdemu zgłaszać początek i koniec segmentu reklamowego w filmach na YouTube. Kiedy ktoś zgłosi taki fragment zostanie on pominięty przez innych użytkowników rozszerzenia.",
"description": "Pełny opis rozszerzenia na stronie w sklepie."
},
"website": {
"message": "Strona",
"description": "Używane w sklepie Firefoxa"
},
"sourceCode": {
"message": "Kod źródłowy",
"description": "Używane w sklepie Firefoxa"
},
"noticeUpdate": {
"message": "Informacje zostały zaktualizowane!",
"description": "Pierwsza linia po aktualizacji informacji."
},
"noticeUpdate2": {
"message": "Jeśli nadal jej nie lubisz wybierz opcje nie pokazuj więcej.",
"description": "Druga linia po aktualizacji informacji."
},
"setStartSponsorShortcut": {
"message": "Ustaw klawisz do oznaczania początku reklamy"
},
"setSubmitKeybind": {
"message": "Ustaw klawisz do wysyłania czasów"
},
"keybindDescription": {
"message": "Wybierz klawisz klikając go na klawiaturze"
},
"keybindDescriptionComplete": {
"message": "Ustawiony klawisz to: "
},
"0": {
"message": "Połączenie przerwane z powodu braku odpowiedzi. Sprawdź swoje połączenie z internetem. Jeśli wszystko z nim w porządku oznacza to, że serwer jest prawdopodobnie przeciążony lub nie działa."
},
"disableSkipping": {
"message": "Wyłącz SponsorBlock"
},
"enableSkipping": {
"message": "Włącz SponsorBlock"
},
"yourWork": {
"message": "Twój wkład",
"description": "Nagłowek sekcji ze statystykami użytkownika."
},
"502": {
"message": "Serwer jest prawdopodobnie przeciążony, spróbuj ponownie za kilka sekund."
},
"errorCode": {
"message": "Kod błędu: "
},
"noticeTitleNotSkipped": {
"message": "Przewinąć reklamę?"
},
"skip": {
"message": "Przewiń"
},
"disableAutoSkip": {
"message": "Wyłącz auto przewijanie"
},
"enableAutoSkip": {
"message": "Włącz auto przewijanie"
},
"autoSkipDescription": {
"message": "Auto przewijanie przewinie segment za ciebie, wyłączone wyświetli komunikat z pytaniem czy chcesz przewinąć reklamę."
},
"youHaveSkipped": {
"message": "Przewinąłeś "
},
"youHaveSaved": {
"message": "Oszczędziłeś sobie "
},
"minLower": {
"message": "minuta"
},
"minsLower": {
"message": "minuty"
},
"hourLower": {
"message": "godzina"
},
"hoursLower": {
"message": "godziny"
},
"youHaveSavedTime": {
"message": "Oszczędziłeś ludziom"
},
"youHaveSavedTimeEnd": {
"message": " czasu."
},
"guildlinesSummary": {
"message": "- Upewnij się, że zgłaszany fragment zawiera tylko reklamę i nic więcej.\n- Upewnij się, że nie zostanie przewinięta wartościowa treść\n- Jeśli całe nagranie to reklama, proszę nie zgłaszaj go. Blokowanie całych nagrań pojawi się wkrótce.\n- Nie ukrywaj treści które są istotne dla użytkownika (nie ukrywaj informacji, że recenzja produktu została opłacona przez producenta)"
},
"statusReminder": {
"message": "Wejdź na status.sponsor.ajay.app żeby sprawdzić czy serwer działa."
},
"changeUserID": {
"message": "Zaimportuj/Wyeksportuj swój UserID"
},
"whatChangeUserID": {
"message": "Ta informacja jest poufna i działa jak hasło, użytkownik który ma do niej dostęp może zgłaszać treści jako ty."
},
"setUserID": {
"message": "Ustaw UserID"
},
"userIDChangeWarning": {
"message": "Ostrzeżenie: Zmiana UserID jest nieodwracalna. Jesteś pewien, że chcesz to zrobić? Skopiuj obecny UserID na wszelki wypadek."
},
"createdBy": {
"message": "Stworzony przez"
},
"autoSkip": {
"message": "Auto przewijanie"
},
"showSkipNotice": {
"message": "Pokaż informację po przewiniętym fragmencie"
},
"keybindCurrentlySet": {
"message": ". Jest obecnie ustawione jako:"
},
"supportInvidious": {
"message": "Wesprzyj Invidious"
},
"supportInvidiousDescription": {
"message": "Invidious (invidio.us) to nieoficjalny klient YouTube. Aby go wesprzeć musisz przyznać dodatkowe uprawnienia rozszerzeniowi. Ta opcja nie działa w incognito i innych wersjach Chromium."
},
"optionsInfo": {
"message": "Wesprzyj Invidious, wyłącz auto przewijanie, ukryj guziki i więcej."
},
"addInvidiousInstance": {
"message": "Dodaj instancje Invidious"
},
"addInvidiousInstanceDescription": {
"message": "Dodaj niestandardową instancje Invidious. W formie domeny. Na przykład: invidious.ajay.app"
},
"add": {
"message": "Dodaj"
},
"addInvidiousInstanceError": {
"message": "Ta domena jest nieprawidłowa. Wartość powinna zawierać TYLKO domenę. Na przykład: invidious.ajay.app"
},
"resetInvidiousInstance": {
"message": "Zresetuj listę instancji Invidious"
},
"resetInvidiousInstanceAlert": {
"message": "Zresetujesz listę instancji Invidious"
},
"currentInstances": {
"message": "Obecne instancje:"
},
"enableAutoUpvote": {
"message": "Auto potwierdzanie"
},
"whatAutoUpvote": {
"message": "To ustawienie sprawia, że wszystkie przewinięte przez ciebie a nie zgłoszone jako błąd segmenty reklamowe zostaną potwierdzone jako prawidłowe. Ta opcja nie działa jeśli okienko z informacją o przewinięciu jest ukryte."
},
"invidiousInfo1": {
"message": "Invidious (nieoficjalny klient YouTube) została dodana do wspieranych!"
},
"invidiousInfo2": {
"message": "Musisz odblokować to w opcjach aby móc to zrobić."
}
}

View File

@@ -1,5 +1,8 @@
<!DOCTYPE html>
<head>
<title> SponsorBlock </title>
<meta charset="utf-8">
<link href="styles.css" rel="stylesheet"/>
</head>
@@ -7,16 +10,16 @@
<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>
<center>
<div class="container">
<p class="createdBy">Created By <a href="https://ajay.app">Ajay Ramachandran</a> <img src="https://ajay.app/newprofilepic.jpg" height="30" class="profilepiccircle"/></p>
<p>
Thanks for installing SponsorBlock. Here are some quick tips for getting started. Please join the Discord if you have any questions or suggestions.
Thanks for installing SponsorBlock. Here are some quick tips for getting started. Feel free to contact me if you have any questions.
</p>
<p class="projectPreview">
@@ -27,10 +30,13 @@
Come contribute, make some suggestions and help out in the Discord: <a href="https://discord.gg/QnmVMpU">https://discord.gg/QnmVMpU</a>
</p>
<a class="bigText" href="/options/options.html">Enable Invidious Support</a>
<div class="center">
<a class="bigText" href="/options/options.html">Enable optional features</a>
</div>
<p>
Invidious is a third-party YouTube viewer. SponsorBlock now supports invidious along with YouTube. Please visit the options page to make sure everything is how you want it to be.
Some features, such as support for non third-party YouTube sites, are disabled by default and can be enabled in the options. These can be enabled or disabled at any time.
You can also hide/show all UI elements added to the YouTube page.
</p>
<h1>How skipping works</h1>
@@ -48,7 +54,7 @@
Whenever you skip a video, you will get a notice report that submission. If the timing seems wrong, report it! You can also vote in the popup. The extension auto upvotes it if you don't report it, so make sure to report when necessary (this can be disabled in the options).
</p>
<center><img height="120px" src="https://user-images.githubusercontent.com/12688112/63067735-5a638700-bede-11e9-8147-f321b57527ec.gif"></center>
<div class="center"><img height="120px" src="https://user-images.githubusercontent.com/12688112/63067735-5a638700-bede-11e9-8147-f321b57527ec.gif"></div>
<h1>Submitting</h1>
@@ -80,6 +86,7 @@
<p>
There are hotkeys if you want to use them. You must be focused on the YouTube player to use them. Press the semicolon key to indicate the start/end of a sponsor segment and click the appostrophe to submit.
These can be changed in the options. If you don't use QWERTY, you should probably change the keybinds.
</p>
<h1>I hate these buttons, they are so ugly</h1>
@@ -106,7 +113,7 @@
Ask on Discord or make an Issue on GitHub. I am happy to hear suggestions or improvements you want. You may also contribute code or graphics if you would like.
</p>
<h1>Where can I get the source code</h1>
<h1>Where can I get the source code?</h1>
<h4 style="display: inline">Client:</h4>
<!-- Github logo -->
@@ -116,7 +123,7 @@
<!-- Github logo -->
<a href="https://github.com/ajayyy/SponsorBlockServer"><svg aria-hidden="true" version="1.1" viewBox="0 0 16 16" height="58px" style="padding-left: 15px"><path fill-rule="evenodd" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z"></path></svg></a>
<h1>Credit</h1>
<h1>Credits</h1>
<p>The awesome <a href="https://github.com/omarroth/invidious/wiki/API">Invidious API</a> is used to grab the time the video was published.</p>
@@ -124,6 +131,6 @@
<p>Some icons made by <a href="https://www.flaticon.com/authors/freepik" title="Freepik">Freepik</a> from <a href="https://www.flaticon.com/" title="Flaticon">www.flaticon.com</a> and are licensed by <a href="http://creativecommons.org/licenses/by/3.0/" title="Creative Commons BY 3.0" target="_blank">CC 3.0 BY</a></p>
</center>
</div>
</body>

View File

@@ -4,6 +4,16 @@
body {
background-color: #333333;
font-family: sans-serif;
}
.center {
text-align: center;
}
.container {
max-width: 60%;
margin: auto;
}
.projectPreview {
@@ -12,24 +22,24 @@ body {
.projectPreviewImage {
position: absolute;
left: -90;
width: 80;
left: -90px;
width: 80px;
top: 50%;
transform: translateY(-50%);
}
.projectPreviewImageLarge {
position: absolute;
left: -210;
width: 200;
left: -210px;
width: 200px;
top: 50%;
transform: translateY(-20%);
}
.projectPreviewImageLargeRight {
position: absolute;
right: -210;
width: 200;
right: -210px;
width: 200px;
top: 50%;
transform: translateY(-50%);
}
@@ -47,37 +57,27 @@ body {
text-align: center;
vertical-align: middle;
font-family: sans-serif;
font-size: 50;
font-size: 50px;
color: #212121;
/* height: 100; */
padding: 20;
padding: 20px;
text-decoration: none;
transition: font-size 1s;
}
#title:hover {
font-size: 60;
transition: font-size 1s;
}
.subtitle {
font-family: sans-serif;
font-size: 40;
font-size: 40px;
color: #dad8d8;
padding-top: 10;
padding-top: 10px;
transition: font-size 0.4s;
}
.subtitle:hover {
font-size: 45;
font-size: 45px;
transition: font-size 0.4s;
}
@@ -99,7 +99,7 @@ a {
}
.link {
padding: 20;
padding: 20px;
height: 80px;
@@ -113,13 +113,12 @@ a {
}
#contact,.smalllink {
font-family: sans-serif;
font-size: 25;
font-size: 25px;
color: #e8e8e8;
text-align: center;
padding: 10;
padding: 10px;
}
#contact {
@@ -127,15 +126,11 @@ a {
}
p,li,a {
font-family: sans-serif;
font-size: 20;
font-size: 20px;
color: #c4c4c4;
padding: 10;
}
p,li,code,a {
max-width: 60%;
text-align: left;
overflow-wrap: break-word;
}
@@ -147,7 +142,7 @@ p,li,code,a {
.projectPreviewImage {
position: unset;
width: 130;
width: 130px;
display: block;
margin: auto;
transform: none;
@@ -165,20 +160,18 @@ img {
}
#recentPostTitle {
font-family: sans-serif;
font-size: 30;
font-size: 30px;
color: #dad8d8;
}
#recentPostDate {
font-family: sans-serif;
font-size: 15;
font-size: 15px;
color: #dad8d8;
}
h1,h2,h3,h4,h5,h6 {
font-family: sans-serif;
color: #dad8d8;
text-align: center;
}
svg {

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

@@ -1,6 +1,6 @@
/* Options page CSS */
body {
font-family: Sans-Serif;
font-family: sans-serif;
}
.center {
@@ -25,12 +25,12 @@ body {
.small-description {
color: white;
font-size: 13;
font-size: 13px;
}
.medium-description {
color: white;
font-size: 15;
font-size: 15px;
}
.option-text-box {
@@ -76,6 +76,11 @@ body {
color: white;
}
.text-label-container {
font-size: 14px;
color: white;
}
.switch {
position: relative;
display: inline-block;
@@ -151,24 +156,24 @@ body {
.projectPreviewImage {
position: absolute;
left: -90;
width: 80;
left: -90px;
width: 80px;
top: 50%;
transform: translateY(-50%);
}
.projectPreviewImageLarge {
position: absolute;
left: -210;
width: 200;
left: -210px;
width: 200px;
top: 50%;
transform: translateY(-20%);
}
.projectPreviewImageLargeRight {
position: absolute;
right: -210;
width: 200;
right: -210px;
width: 200px;
top: 50%;
transform: translateY(-50%);
}
@@ -188,37 +193,27 @@ body {
text-align: center;
vertical-align: middle;
font-family: sans-serif;
font-size: 50;
font-size: 50px;
color: #212121;
/* height: 100; */
padding: 20;
padding: 20px;
text-decoration: none;
transition: font-size 1s;
}
#title:hover {
font-size: 60;
transition: font-size 1s;
}
.subtitle {
font-family: sans-serif;
font-size: 40;
font-size: 40px;
color: #dad8d8;
padding-top: 10;
padding-top: 10px;
transition: font-size 0.4s;
}
.subtitle:hover {
font-size: 45;
font-size: 45px;
transition: font-size 0.4s;
}
@@ -240,7 +235,7 @@ a {
}
.link {
padding: 20;
padding: 20px;
height: 80px;
@@ -254,13 +249,12 @@ a {
}
#contact,.smalllink {
font-family: sans-serif;
font-size: 25;
font-size: 25px;
color: #e8e8e8;
text-align: center;
padding: 10;
padding: 10px;
}
#contact {
@@ -268,11 +262,10 @@ a {
}
p,li {
font-family: sans-serif;
font-size: 20;
font-size: 20px;
color: #c4c4c4;
padding: 10;
padding: 10px;
}
p,li,code,a {
@@ -288,7 +281,7 @@ p,li,code,a {
.projectPreviewImage {
position: unset;
width: 130;
width: 130px;
display: block;
margin: auto;
transform: none;
@@ -306,22 +299,28 @@ img {
}
#recentPostTitle {
font-family: sans-serif;
font-size: 30;
font-size: 30px;
color: #dad8d8;
}
#recentPostDate {
font-family: sans-serif;
font-size: 15;
font-size: 15px;
color: #dad8d8;
}
h1,h2,h3,h4,h5,h6 {
font-family: sans-serif;
color: #dad8d8;
}
svg {
text-decoration: none;
}
.number-container:before {
content: attr(label-name);
padding-right: 4px;
width: max-content;
font-size: 14px;
color: white;
}

View File

@@ -1,11 +1,13 @@
<!DOCTYPE html>
<head>
<title>Options - SponsorBlock</title>
<meta charset="utf-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">
@@ -39,7 +41,7 @@
<br/>
<br/>
<div option-type="text-change" sync-option="invidiousInstances">
<div option-type="private-text-change" sync-option="invidiousInstances">
<div class="option-button trigger-button">
__MSG_addInvidiousInstance__
</div>
@@ -74,7 +76,7 @@
<br/>
<br/>
<div option-type="toggle" toggle-type="reverse" sync-option="disableAutoSkip">
<label class="switch-container" label-name="__MSG_autoSkip__">
<label class="switch">
@@ -92,6 +94,7 @@
<br/>
<br/>
<div option-type="keybind-change" sync-option="startSponsorKeybind">
<div class="option-button trigger-button">
__MSG_setStartSponsorShortcut__
@@ -130,6 +133,32 @@
</span>
</div>
</div>
<br/>
<br/>
<div option-type="number-change" sync-option="minDuration">
<label class="number-container" label-name="__MSG_minDuration__">
<input type="number" step="0.1" min="0">
</label>
<br/>
<br/>
<div class="small-description">__MSG_minDurationDescription__</div>
</div>
<br/>
<br/>
<div option-type="toggle" toggle-type="reverse" sync-option="dontShowNotice">
<label class="switch-container" label-name="__MSG_showSkipNotice__">
<label class="switch">
<input type="checkbox" checked>
<span class="slider round"></span>
</label>
</label>
</div>
<br/>
<br/>
@@ -185,6 +214,23 @@
<br/>
<br/>
<div option-type="toggle" toggle-type="reverse" sync-option="hideUploadButtonPlayerControls">
<label class="switch-container" label-name="__MSG_showUploadButton__">
<label class="switch">
<input type="checkbox" checked>
<span class="slider round"></span>
</label>
</label>
<br/>
<br/>
<div class="small-description">__MSG_whatUploadButton__</div>
</div>
<br/>
<br/>
<div option-type="toggle" sync-option="autoUpvote">
<label class="switch-container" label-name="__MSG_enableAutoUpvote__">
<label class="switch">
@@ -215,11 +261,28 @@
<div class="small-description">__MSG_whatViewTracking__</div>
</div>
<br/>
<br/>
<div option-type="toggle" sync-option="checkForUnlistedVideos">
<label class="switch-container" label-name="__MSG_unlistedCheck__">
<label class="switch">
<input type="checkbox">
<span class="slider round"></span>
</label>
</label>
<br/>
<br/>
<div class="small-description">__MSG_whatUnlistedCheck__</div>
</div>
<br/>
<br/>
<div option-type="text-change" sync-option="userID" confirm-message="userIDChangeWarning">
<div option-type="private-text-change" sync-option="userID" confirm-message="userIDChangeWarning">
<div class="option-button trigger-button">
__MSG_changeUserID__
</div>
@@ -241,19 +304,31 @@
</div>
</div>
</div>
<br/>
<br/>
<div option-type="toggle" toggle-type="reverse" sync-option="dontShowNotice">
<label class="switch-container" label-name="__MSG_showSkipNotice__">
<label class="switch">
<input type="checkbox" checked>
<span class="slider round"></span>
</label>
<div option-type="text-change" sync-option="serverAddress">
<label class="text-label-container">
<div>__MSG_customServerAddress__</div>
<input class="option-text-box" type="text">
</label>
<div class="option-button text-change-set inline">
__MSG_save__
</div>
<div class="option-button text-change-reset inline">
__MSG_reset__
</div>
<br/>
<br/>
<div class="small-description">__MSG_customServerAddressDescription__</div>
</div>
</div>
</div>

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 February).
</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>.
</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();
});
}
@@ -18,6 +25,9 @@ chrome.tabs.onUpdated.addListener(function(tabId) {
chrome.runtime.onMessage.addListener(function (request, sender, callback) {
switch(request.message) {
case "openConfig":
chrome.runtime.openOptionsPage();
return
case "submitTimes":
submitTimes(request.videoID, callback);
@@ -32,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
@@ -54,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;
}
});
@@ -66,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){
@@ -74,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);
});
@@ -100,14 +108,24 @@ function registerFirefoxContentScript(options) {
js: options.js,
css: options.css,
matches: options.matches
}).then(() => void (contentScriptRegistrations[options.id] = registration));
}).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;
}
@@ -130,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
@@ -169,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
@@ -193,51 +211,30 @@ async function submitTimes(videoID, callback) {
//submit these times
for (let i = 0; i < sponsorTimes.length; i++) {
//to prevent it from happeneing twice
let increasedContributionAmount = false;
//to prevent it from happeneing twice
let increasedContributionAmount = false;
//submit the sponsorTime
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({
statusCode: xmlhttp.status
});
//submit the sponsorTime
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({
statusCode: xmlhttp.status
});
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;
}
//save the amount contributed
if (!increasedContributionAmount) {
increasedContributionAmount = true;
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();
}
}

253
src/config.ts Normal file
View File

@@ -0,0 +1,253 @@
import * as CompileConfig from "../config.json";
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,
hideUploadButtonPlayerControls: boolean,
hideDiscordLaunches: number,
hideDiscordLink: boolean,
invidiousInstances: string[],
invidiousUpdateInfoShowCount: number,
autoUpvote: boolean,
supportInvidious: boolean,
serverAddress: string,
minDuration: number,
checkForUnlistedVideos: boolean
}
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) {
super.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,
hideUploadButtonPlayerControls: false,
hideDiscordLaunches: 0,
hideDiscordLink: false,
invidiousInstances: ["invidio.us", "invidiou.sh", "invidious.snopyta.org"],
invidiousUpdateInfoShowCount: 0,
autoUpvote: true,
supportInvidious: false,
serverAddress: CompileConfig.serverAddress,
minDuration: 0,
checkForUnlistedVideos: 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.localConfig).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,9 +1,22 @@
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";
// Hack to get the CSS loaded on permission-based sites (Invidious)
utils.wait(() => Config.config !== null, 5000, 10).then(addCSS);
//was sponsor data found when doing SponsorsLookup
var sponsorDataFound = false;
var previousVideoID = null;
//the actual sponsorTimes if loaded and UUIDs associated with them
var sponsorTimes = null;
var UUIDs = null;
var UUIDs = [];
//what video id are these sponsors for
var sponsorVideoID = null;
@@ -16,7 +29,7 @@ var sponsorSkipped = [];
//the video
var v;
var listenerAdded;
var onInvidious;
//the video id of the last preview bar update
var lastPreviewBarUpdate;
@@ -39,8 +52,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 +78,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":
@@ -106,7 +132,7 @@ function messageListener(request, sender, sendResponse) {
break;
case "getVideoDuration":
sendResponse({
duration: v.duration
duration: v.duration
});
break;
@@ -160,27 +186,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();
}
@@ -193,7 +218,7 @@ function resetValues() {
//reset sponsor times
sponsorTimes = null;
UUIDs = null;
UUIDs = [];
sponsorLookupRetries = 0;
//empty the preview bar
@@ -205,7 +230,7 @@ function resetValues() {
sponsorDataFound = false;
}
function videoIDChange(id) {
async function videoIDChange(id) {
//if the id has not changed return
if (sponsorVideoID === id) return;
@@ -217,13 +242,28 @@ function videoIDChange(id) {
//id is not valid
if (!id) return;
let channelIDPromise = wait(getChannelID);
// Wait for options to be ready
await utils.wait(() => Config.config !== null, 5000, 1);
// If enabled, it will check if this video is private or unlisted and double check with the user if the sponsors should be looked up
if (Config.config.checkForUnlistedVideos) {
await utils.wait(isPrivacyInfoAvailable);
if (isUnlisted()) {
let shouldContinue = confirm(chrome.i18n.getMessage("confirmPrivacy"));
if(!shouldContinue) return;
}
}
// 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 +286,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 +314,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 +344,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 +360,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,12 +375,40 @@ 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;
sponsorTimes = JSON.parse(xmlhttp.responseText).sponsorTimes;
UUIDs = JSON.parse(xmlhttp.responseText).UUIDs;
let recievedSponsorTimes = JSON.parse(xmlhttp.responseText).sponsorTimes;
let recievedUUIDs = JSON.parse(xmlhttp.responseText).UUIDs;
// Check if any old submissions should be kept
for (let i = 0; i < UUIDs.length; i++) {
if (UUIDs[i] === null) {
// This is a user submission, keep it
recievedSponsorTimes.push(sponsorTimes[i]);
recievedUUIDs.push(UUIDs[i]);
}
}
sponsorTimes = recievedSponsorTimes;
UUIDs = recievedUUIDs;
// Remove all submissions smaller than the minimum duration
if (Config.config.minDuration !== 0) {
let smallSponsors = [];
let smallUUIDs = [];
for (let i = 0; i < sponsorTimes.length; i++) {
if (sponsorTimes[i][1] - sponsorTimes[i][0] >= Config.config.minDuration) {
smallSponsors.push(sponsorTimes[i]);
smallUUIDs.push(UUIDs[i]);
}
}
sponsorTimes = smallSponsors;
UUIDs = smallUUIDs;
}
// Reset skip save
sponsorSkipped = [];
@@ -365,7 +434,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);
}
}
});
@@ -376,44 +445,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);
//Attempt to parse url
let urlObject = null;
try {
urlObject = new URL(url);
} catch (e) {
console.error("[SB] Unable to parse URL: " + url);
return false;
}
//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);
// 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)));
}
}
for (let i = 0; i < sponsorTimesSubmitting.length; i++) {
types.push("previewSponsor");
return false
}
wait(() => previewBar !== null).then((result) => previewBar.set(allSponsorTimes, types, v.duration));
//update last video id
lastPreviewBarUpdate = sponsorVideoID;
//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() {
@@ -443,7 +526,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;
@@ -465,10 +548,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;
@@ -477,7 +586,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;
@@ -509,12 +618,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
@@ -543,43 +652,37 @@ 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];
}
lastSponsorTimeSkipped = sponsorTimes[index][0];
let currentUUID = UUIDs[index];
lastSponsorTimeSkippedUUID = currentUUID;
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);
//send telemetry that a this sponsor was skipped
if (Config.config.trackViewCount && !sponsorSkipped[index]) {
utils.sendRequestToServer("POST", "/api/viewedVideoSponsorTime?UUID=" + currentUUID);
if (!SB.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;
sponsorSkipped[index] = true;
if (!Config.config.disableAutoSkip) {
// Count this as a skip
Config.config.minutesSaved = Config.config.minutesSaved + (sponsorTimes[index][1] - sponsorTimes[index][0]) / 60;
Config.config.skipCount = Config.config.skipCount + 1;
sponsorSkipped[index] = true;
}
}
}
}
@@ -637,7 +740,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;
@@ -655,7 +758,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 {
@@ -663,13 +766,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";
}
}
@@ -718,18 +821,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.hideUploadButtonPlayerControls) {
document.getElementById("submitButton").style.display = "unset";
} else if (!uploadButtonVisible) {
//disable submit button
@@ -737,7 +840,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
@@ -769,7 +872,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);
@@ -791,17 +894,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);
}
});
}
@@ -824,15 +927,15 @@ 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);
confirmMessage += chrome.i18n.getMessage("confirmMSG")
let confirmMessage = chrome.i18n.getMessage("clearThis") + getSponsorTimesMessage(sponsorTimes)
+ "\n" + chrome.i18n.getMessage("confirmMSG")
if(!confirm(confirmMessage)) return;
//clear the sponsor times
SB.config.sponsorTimes.delete(currentVideoID);
Config.config.sponsorTimes.delete(currentVideoID);
//clear sponsor times submitting
sponsorTimesSubmitting = [];
@@ -863,12 +966,10 @@ function vote(type, UUID, skipNotice) {
sponsorSkipped[sponsorIndex] = false;
}
// Count this as a skip
SB.config.minutesSaved = SB.config.minutesSaved + factor * (sponsorTimes[sponsorIndex][1] - sponsorTimes[sponsorIndex][0]) / 60;
SB.config.skipCount = 0;
SB.config.skipCount = SB.config.skipCount + factor * 1;
// Count this as a skip
Config.config.minutesSaved = Config.config.minutesSaved + factor * (sponsorTimes[sponsorIndex][1] - sponsorTimes[sponsorIndex][0]) / 60;
Config.config.skipCount = Config.config.skipCount + factor;
}
chrome.runtime.sendMessage({
@@ -889,7 +990,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)();
}
}
@@ -906,7 +1007,7 @@ function closeAllSkipNotices(){
}
function dontShowNoticeAgain() {
SB.config.dontShowNotice = true;
Config.config.dontShowNotice = true;
closeAllSkipNotices();
}
@@ -933,7 +1034,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
@@ -943,11 +1044,22 @@ function submitSponsorTimes() {
}
}
//update sponsorTimes
SB.config.sponsorTimes.set(currentVideoID, sponsorTimes);
Config.config.sponsorTimes.set(currentVideoID, sponsorTimes);
//update sponsorTimesSubmitting
sponsorTimesSubmitting = sponsorTimes;
// Check to see if any of the submissions are below the minimum duration set
if (Config.config.minDuration > 0) {
for (let i = 0; i < sponsorTimes.length; i++) {
if (sponsorTimes[i][1] - sponsorTimes[i][0] < Config.config.minDuration) {
let confirmShort = chrome.i18n.getMessage("shortCheck") + "\n\n" + getSponsorTimesMessage(sponsorTimes);
if(!confirm(confirmShort)) return;
}
}
}
let confirmMessage = chrome.i18n.getMessage("submitCheck") + "\n\n" + getSponsorTimesMessage(sponsorTimes)
+ "\n\n" + chrome.i18n.getMessage("confirmMSG") + "\n\n" + chrome.i18n.getMessage("guildlinesSummary");
if(!confirm(confirmMessage)) return;
@@ -961,7 +1073,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;
@@ -988,13 +1100,15 @@ 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
if (sponsorTimes === null) sponsorTimes = [];
sponsorTimes = sponsorTimes.concat(sponsorTimesSubmitting);
for (let i = 0; i < sponsorTimesSubmitting.length; i++) {
// Add some random IDs
UUIDs.push(generateUserID());
// Add placeholder IDs
UUIDs.push(null);
}
// Empty the submitting times
@@ -1004,9 +1118,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));
}
}
});
@@ -1034,39 +1148,69 @@ function getSponsorTimesMessage(sponsorTimes) {
return sponsorTimesMessage;
}
// Privacy utils
function isPrivacyInfoAvailable(): boolean {
if(document.location.pathname.startsWith("/embed/")) return true;
return document.getElementsByClassName("style-scope ytd-badge-supported-renderer").length >= 2;
}
/**
* What privacy level is this YouTube video?
*/
function getPrivacy(): string {
if(document.location.pathname.startsWith("/embed/")) return "Public";
let privacyElement = <HTMLElement> document.getElementsByClassName("style-scope ytd-badge-supported-renderer")[2];
return privacyElement.innerText;
}
/**
* Is this an unlisted YouTube video.
* Assumes that the the privacy info is available.
*/
function isUnlisted(): boolean {
let privacyElement = <HTMLElement> document.getElementsByClassName("style-scope ytd-badge-supported-renderer")[2];
return privacyElement.innerText.toLocaleLowerCase() === "unlisted";
}
/**
* Adds the CSS to the page if needed. Required on optional sites with Chrome.
*/
function addCSS() {
if (!utils.isFirefox() && Config.config.invidiousInstances.includes(new URL(document.URL).host)) {
window.addEventListener("DOMContentLoaded", () => {
let head = document.getElementsByTagName("head")[0];
for (const file of utils.css) {
let fileref = document.createElement("link");
fileref.rel = "stylesheet";
fileref.type = "text/css";
fileref.href = chrome.extension.getURL(file);
head.appendChild(fileref);
}
});
}
}
//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;
let formatted = minutes + ":" + secondsDisplay;
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);
}
}
@@ -89,4 +90,6 @@ class PreviewBar {
this.container.remove();
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;

384
src/options.ts Normal file
View File

@@ -0,0 +1,384 @@
import Config from "./config";
import Utils from "./utils";
var utils = new Utils();
window.addEventListener('DOMContentLoaded', init);
async function init() {
utils.localizeHtmlPage();
if (!Config.configListeners.includes(optionsConfigUpdateListener)) {
Config.configListeners.push(optionsConfigUpdateListener);
}
await utils.wait(() => Config.config !== null);
// Set all of the toggle options to the correct option
let optionsContainer = document.getElementById("options");
let optionsElements = optionsContainer.querySelectorAll("*");
for (let i = 0; i < optionsElements.length; i++) {
switch (optionsElements[i].getAttribute("option-type")) {
case "toggle":
let option = optionsElements[i].getAttribute("sync-option");
let optionResult = Config.config[option];
let checkbox = optionsElements[i].querySelector("input");
let reverse = optionsElements[i].getAttribute("toggle-type") === "reverse";
if (optionResult != undefined) {
checkbox.checked = optionResult;
if (reverse) {
optionsElements[i].querySelector("input").checked = !optionResult;
}
}
// See if anything extra should be run first time
switch (option) {
case "supportInvidious":
invidiousInit(checkbox, option);
break;
}
// Add click listener
checkbox.addEventListener("click", () => {
Config.config[option] = reverse ? !checkbox.checked : checkbox.checked;
// See if anything extra must be run
switch (option) {
case "supportInvidious":
invidiousOnClick(checkbox, option);
break;
case "disableAutoSkip":
if (!checkbox.checked) {
// Enable the notice
Config.config["dontShowNotice"] = false;
let showNoticeSwitch = <HTMLInputElement> document.querySelector("[sync-option='dontShowNotice'] > label > label > input");
showNoticeSwitch.checked = true;
}
break;
}
});
break;
case "text-change":
let textChangeOption = optionsElements[i].getAttribute("sync-option");
let textChangeInput = <HTMLInputElement> optionsElements[i].querySelector(".option-text-box");
let textChangeSetButton = <HTMLElement> optionsElements[i].querySelector(".text-change-set");
textChangeInput.value = Config.config[textChangeOption];
textChangeSetButton.addEventListener("click", () => {
// See if anything extra must be done
switch (textChangeOption) {
case "serverAddress":
let result = validateServerAddress(textChangeInput.value);
if (result !== null) {
textChangeInput.value = result;
} else {
return;
}
break;
}
Config.config[textChangeOption] = textChangeInput.value;
});
// Reset to the default if needed
let textChangeResetButton = <HTMLElement> optionsElements[i].querySelector(".text-change-reset");
textChangeResetButton.addEventListener("click", () => {
if (!confirm(chrome.i18n.getMessage("areYouSureReset"))) return;
Config.config[textChangeOption] = Config.defaults[textChangeOption];
textChangeInput.value = Config.config[textChangeOption];
});
break;
case "private-text-change":
let button = optionsElements[i].querySelector(".trigger-button");
button.addEventListener("click", () => activatePrivateTextChange(<HTMLElement> optionsElements[i]));
let privateTextChangeOption = optionsElements[i].getAttribute("sync-option");
// See if anything extra must be done
switch (privateTextChangeOption) {
case "invidiousInstances":
invidiousInstanceAddInit(<HTMLElement> optionsElements[i], privateTextChangeOption);
}
break;
case "keybind-change":
let keybindButton = optionsElements[i].querySelector(".trigger-button");
keybindButton.addEventListener("click", () => activateKeybindChange(<HTMLElement> optionsElements[i]));
break;
case "display":
updateDisplayElement(<HTMLElement> optionsElements[i])
break;
case "number-change":
let numberChangeOption = optionsElements[i].getAttribute("sync-option");
let configValue = Config.config[numberChangeOption];
let numberInput = optionsElements[i].querySelector("input");
if (isNaN(configValue) || configValue < 0) {
numberInput.value = Config.defaults[numberChangeOption];
} else {
numberInput.value = configValue;
}
numberInput.addEventListener("input", () => {
Config.config[numberChangeOption] = numberInput.value;
});
break;
}
}
optionsContainer.classList.remove("hidden");
optionsContainer.classList.add("animated");
}
/**
* Called when the config is updated
*
* @param {String} element
*/
function optionsConfigUpdateListener(changes) {
let optionsContainer = document.getElementById("options");
let optionsElements = optionsContainer.querySelectorAll("*");
for (let i = 0; i < optionsElements.length; i++) {
switch (optionsElements[i].getAttribute("option-type")) {
case "display":
updateDisplayElement(<HTMLElement> optionsElements[i])
}
}
}
/**
* Will set display elements to the proper text
*
* @param element
*/
function updateDisplayElement(element: HTMLElement) {
let displayOption = element.getAttribute("sync-option")
let displayText = Config.config[displayOption];
element.innerText = displayText;
// See if anything extra must be run
switch (displayOption) {
case "invidiousInstances":
element.innerText = displayText.join(', ');
break;
}
}
/**
* Initializes the option to add Invidious instances
*
* @param element
* @param option
*/
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");
setButton.addEventListener("click", async function(e) {
if (textBox.value == "" || textBox.value.includes("/") || textBox.value.includes("http") || textBox.value.includes(":")) {
alert(chrome.i18n.getMessage("addInvidiousInstanceError"));
} else {
// Add this
let instanceList = Config.config[option];
if (!instanceList) instanceList = [];
instanceList.push(textBox.value);
Config.config[option] = instanceList;
let checkbox = <HTMLInputElement> document.querySelector("#support-invidious input");
checkbox.checked = true;
invidiousOnClick(checkbox, "supportInvidious");
textBox.value = "";
// Hide this section again
element.querySelector(".option-hidden-section").classList.add("hidden");
button.classList.remove("disabled");
}
});
let resetButton = element.querySelector(".invidious-instance-reset");
resetButton.addEventListener("click", function(e) {
if (confirm(chrome.i18n.getMessage("resetInvidiousInstanceAlert"))) {
// Set to a clone of the default
Config.config[option] = Config.defaults[option].slice(0);
}
});
}
/**
* Run when the invidious button is being initialized
*
* @param checkbox
* @param option
*/
function invidiousInit(checkbox: HTMLInputElement, option: string) {
let permissions = ["declarativeContent"];
if (utils.isFirefox()) permissions = [];
chrome.permissions.contains({
origins: utils.getInvidiousInstancesRegex(),
permissions: permissions
}, function (result) {
if (result != checkbox.checked) {
Config.config[option] = result;
checkbox.checked = result;
}
});
}
/**
* Run whenever the invidious checkbox is clicked
*
* @param checkbox
* @param option
*/
function invidiousOnClick(checkbox: HTMLInputElement, option: string) {
if (checkbox.checked) {
utils.setupExtraSitePermissions(function (granted) {
if (!granted) {
Config.config[option] = false;
checkbox.checked = false;
}
});
} else {
utils.removeExtraSiteRegistration();
}
}
/**
* Will trigger the container to ask the user for a keybind.
*
* @param element
*/
function activateKeybindChange(element: HTMLElement) {
let button = element.querySelector(".trigger-button");
if (button.classList.contains("disabled")) return;
button.classList.add("disabled");
let option = element.getAttribute("sync-option");
let currentlySet = Config.config[option] !== null ? chrome.i18n.getMessage("keybindCurrentlySet") : "";
let status = <HTMLElement> element.querySelector(".option-hidden-section > .keybind-status");
status.innerText = chrome.i18n.getMessage("keybindDescription") + currentlySet;
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");
document.addEventListener("keydown", (e) => keybindKeyPressed(element, e), {once: true});
}
/**
* Called when a key is pressed in an activiated keybind change option.
*
* @param element
* @param e
*/
function keybindKeyPressed(element: HTMLElement, e: KeyboardEvent) {
var key = e.key;
let button = element.querySelector(".trigger-button");
// cancel setting a keybind
if (key === "Escape") {
element.querySelector(".option-hidden-section").classList.add("hidden");
button.classList.remove("disabled");
return;
}
let option = element.getAttribute("sync-option");
Config.config[option] = key;
let status = <HTMLElement> element.querySelector(".option-hidden-section > .keybind-status");
status.innerText = chrome.i18n.getMessage("keybindDescriptionComplete");
let statusKey = <HTMLElement> element.querySelector(".option-hidden-section > .keybind-status-key");
statusKey.innerText = key;
button.classList.remove("disabled");
}
/**
* Will trigger the textbox to appear to be able to change an option's text.
*
* @param element
*/
function activatePrivateTextChange(element: HTMLElement) {
let button = element.querySelector(".trigger-button");
if (button.classList.contains("disabled")) return;
button.classList.add("disabled");
let textBox = <HTMLInputElement> element.querySelector(".option-text-box");
let option = element.getAttribute("sync-option");
// See if anything extra must be done
switch (option) {
case "invidiousInstances":
element.querySelector(".option-hidden-section").classList.remove("hidden");
return;
}
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))) {
Config.config[option] = textBox.value;
}
});
element.querySelector(".option-hidden-section").classList.remove("hidden");
}
/**
* Validates the value used for the database server address.
* Returns null and alerts the user if there is an issue.
*
* @param input Input server address
*/
function validateServerAddress(input: string): string {
input = input.trim();
// Trim the trailing slashes
input = input.replace(/\/+$/, "");
// If it isn't HTTP protocol
if ((!input.startsWith("https://") && !input.startsWith("http://"))) {
alert(chrome.i18n.getMessage("customAddressError"));
return null;
}
return input;
}

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";
//add a dummy query method
chrome.tabs.query = function(config, callback) {
callback([{
url: document.URL,
id: -1
}]);
}
import Utils from "./utils";
var utils = new Utils();
inPopup = false;
interface MessageListener {
(request: any, sender: any, callback: (response: any) => void): void;
}
class MessageHandler {
messageListener: MessageListener;
constructor (messageListener?: MessageListener) {
this.messageListener = messageListener;
}
await wait(() => SB.config !== undefined);
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);
}
}
}
//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,115 +127,115 @@ 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;
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) {
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");
} else {
SB.sponsorTimesViewsDisplayEndWord.innerText = chrome.i18n.getMessage("Segment");
}
SB.sponsorTimesViewsDisplay.innerText = viewCount;
SB.sponsorTimesViewsContainer.style.display = "unset";
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
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) {
PageElements.sponsorTimesViewsDisplayEndWord.innerText = chrome.i18n.getMessage("Segments");
} else {
PageElements.sponsorTimesViewsDisplayEndWord.innerText = chrome.i18n.getMessage("Segment");
}
PageElements.sponsorTimesViewsDisplay.innerText = viewCount;
PageElements.sponsorTimesViewsContainer.style.display = "unset";
}
});
}
});
//get this time in minutes
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");
} else {
SB.sponsorTimesOthersTimeSavedEndWord.innerText = chrome.i18n.getMessage("minLower");
}
SB.sponsorTimesOthersTimeSavedDisplay.innerText = getFormattedHours(minutesSaved);
SB.sponsorTimesOthersTimeSavedContainer.style.display = "unset";
//get this time in minutes
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) {
PageElements.sponsorTimesOthersTimeSavedEndWord.innerText = chrome.i18n.getMessage("minsLower");
} else {
PageElements.sponsorTimesOthersTimeSavedEndWord.innerText = chrome.i18n.getMessage("minLower");
}
PageElements.sponsorTimesOthersTimeSavedDisplay.innerText = getFormattedHours(minutesSaved);
PageElements.sponsorTimesOthersTimeSavedContainer.style.display = "unset";
}
});
}
}
});
}
}
//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";
}
});
}
@@ -304,17 +323,17 @@ async function runThePopup() {
}
function sendSponsorStartMessage() {
//the content script will get the message if a YouTube page is open
chrome.tabs.query({
active: true,
currentWindow: true
}, tabs => {
chrome.tabs.sendMessage(
tabs[0].id,
{from: 'popup', message: 'sponsorStart'},
startSponsorCallback
);
});
//the content script will get the message if a YouTube page is open
messageHandler.query({
active: true,
currentWindow: true
}, tabs => {
messageHandler.sendMessage(
tabs[0].id,
{from: 'popup', message: 'sponsorStart'},
startSponsorCallback
);
});
}
function startSponsorCallback(response) {
@@ -327,19 +346,20 @@ async function runThePopup() {
sponsorTimes[sponsorTimesIndex][startTimeChosen ? 1 : 0] = response.time;
let localStartTimeChosen = startTimeChosen;
SB.config.sponsorTimes.set(currentVideoID, sponsorTimes);
//send a message to the client script
if (localStartTimeChosen) {
chrome.tabs.query({
active: true,
currentWindow: true
}, tabs => {
chrome.tabs.sendMessage(
tabs[0].id,
{message: "sponsorDataChanged"}
);
});
}
Config.config.sponsorTimes.set(currentVideoID, sponsorTimes);
//send a message to the client script
if (localStartTimeChosen) {
messageHandler.query({
active: true,
currentWindow: true
}, tabs => {
messageHandler.sendMessage(
tabs[0].id,
{message: "sponsorDataChanged"}
);
});
}
updateStartTimeChosen();
@@ -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
@@ -831,56 +851,56 @@ async function runThePopup() {
//make the options div visible
function openOptions() {
chrome.runtime.openOptionsPage();
chrome.runtime.sendMessage({"message": "openConfig"});
}
//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) {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
SB.usernameInput.value = JSON.parse(xmlhttp.responseText).userName;
//get username from the server
utils.sendRequestToServer("GET", "/api/getUsername?userID=" + Config.config.userID, function (xmlhttp, error) {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
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";
} else if (xmlhttp.readyState == 4) {
SB.setUsername.style.display = "unset";
SB.submitUsername.style.display = "none";
SB.usernameInput.style.display = "none";
PageElements.setUsernameContainer.style.display = "none";
PageElements.setUsername.style.display = "unset";
PageElements
PageElements.setUsernameStatusContainer.style.display = "none";
} else if (xmlhttp.readyState == 4) {
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);
}
});
}
//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) {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
//submitted
SB.submitUsername.style.display = "none";
SB.usernameInput.style.display = "none";
utils.sendRequestToServer("POST", "/api/setUsername?userID=" + Config.config.userID + "&username=" + PageElements.usernameInput.value, function (xmlhttp, error) {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
//submitted
PageElements.submitUsername.style.display = "none";
PageElements.usernameInput.style.display = "none";
SB.setUsernameStatus.innerText = chrome.i18n.getMessage("success");
} else if (xmlhttp.readyState == 4) {
SB.setUsernameStatus.innerText = getErrorMessageI(xmlhttp.status);
}
});
PageElements.setUsernameStatus.innerText = chrome.i18n.getMessage("success");
} else if (xmlhttp.readyState == 4) {
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 Config from "./config";
class Utils {
// Contains functions needed from the background script
backgroundScriptContainer: any = null;
// Used to add content scripts and CSS required
js = [
"./js/vendor.js",
"./js/content.js"
];
css = [
"content.css",
"./libs/Source+Sans+Pro.css",
"popup.css"
];
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 self = this;
if (this.isFirefox()) {
let firefoxJS = [];
for (const file of this.js) {
firefoxJS.push({file});
}
let firefoxCSS = [];
for (const file of this.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: self.js,
css: self.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, Config.config.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
});
};