Compare commits
393 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
adfba72f19 | ||
|
|
f00337c376 | ||
|
|
737a023b65 | ||
|
|
5551344355 | ||
|
|
07f64382fb | ||
|
|
1c7cde2a19 | ||
|
|
8510a7f3d8 | ||
|
|
db60b11a17 | ||
|
|
6a212b762a | ||
|
|
c8ec2922cf | ||
|
|
b629b7d333 | ||
|
|
514a8b62d6 | ||
|
|
cd11618a5d | ||
|
|
8be3cb157a | ||
|
|
4ca57cc025 | ||
|
|
397bcc94c5 | ||
|
|
8b28bccfd7 | ||
|
|
c6107057d9 | ||
|
|
ab2a9530e9 | ||
|
|
bfc771bd99 | ||
|
|
e75e588755 | ||
|
|
0266bb49ca | ||
|
|
9e693fd555 | ||
|
|
1f30b9ec84 | ||
|
|
50862e3c03 | ||
|
|
20e90bbc34 | ||
|
|
2e212e6d10 | ||
|
|
2039bfa081 | ||
|
|
7dc8a99247 | ||
|
|
1b25ea7f95 | ||
|
|
1869382166 | ||
|
|
d58cd639c7 | ||
|
|
6cd2d4cf83 | ||
|
|
b681f5abd9 | ||
|
|
5b2a0feccf | ||
|
|
cd58f6bc73 | ||
|
|
aeabf806ac | ||
|
|
af7ba31c2f | ||
|
|
5b962b1b9d | ||
|
|
219a7ba8c3 | ||
|
|
933babb4e6 | ||
|
|
023ba2e051 | ||
|
|
1c833a8b1d | ||
|
|
7143d7532d | ||
|
|
1f10bdf593 | ||
|
|
3b543916ff | ||
|
|
c4da85340a | ||
|
|
ec59c7e0f9 | ||
|
|
2454cd9a39 | ||
|
|
c19e2bea29 | ||
|
|
2f2c1ad49b | ||
|
|
657aff2167 | ||
|
|
2450457fe5 | ||
|
|
ba9e42e6f0 | ||
|
|
f05d80523b | ||
|
|
d2779aba86 | ||
|
|
852912a42b | ||
|
|
45e0f87f9f | ||
|
|
79b02c7ae7 | ||
|
|
cfb902180d | ||
|
|
1753c05686 | ||
|
|
c76d2c9a95 | ||
|
|
00117bdfac | ||
|
|
40cddbf8ee | ||
|
|
9026044528 | ||
|
|
50f293d783 | ||
|
|
2b558c43bd | ||
|
|
22cc51734f | ||
|
|
cd5adc1b82 | ||
|
|
b7e83a0efe | ||
|
|
6159605afd | ||
|
|
ceae02426d | ||
|
|
51d2886f70 | ||
|
|
c2eb973a28 | ||
|
|
f8532d39eb | ||
|
|
c2be1ee6d4 | ||
|
|
1735cdb45d | ||
|
|
be6cd62fbe | ||
|
|
cfe91f6280 | ||
|
|
4e7bfa5ed3 | ||
|
|
6f03c68fdc | ||
|
|
62653705ff | ||
|
|
3791c5c78b | ||
|
|
6bd22896b9 | ||
|
|
ea4331e4b1 | ||
|
|
84fbc93c8a | ||
|
|
553581f67b | ||
|
|
afe16e6623 | ||
|
|
0942da0cd1 | ||
|
|
c2b3835618 | ||
|
|
4a96a52398 | ||
|
|
4e1eb82538 | ||
|
|
885fae175d | ||
|
|
1cd84d8a2c | ||
|
|
0c891e0a09 | ||
|
|
08c7754f29 | ||
|
|
5f21d3f96f | ||
|
|
158b76c17e | ||
|
|
3455a79298 | ||
|
|
dd08ff1507 | ||
|
|
b5996a9783 | ||
|
|
edb108bbcf | ||
|
|
feda7fd1a0 | ||
|
|
46ffb330fc | ||
|
|
2f7a4bdb36 | ||
|
|
96dab6b6b2 | ||
|
|
3e9afe1dc8 | ||
|
|
2df2b4c53d | ||
|
|
968dd34c3d | ||
|
|
0cbe73c527 | ||
|
|
6d312e7ba7 | ||
|
|
09a33c4252 | ||
|
|
76d966c1f9 | ||
|
|
da364b49f0 | ||
|
|
f6f416b899 | ||
|
|
3582f45fd8 | ||
|
|
13cef5ff14 | ||
|
|
4049d2d1ab | ||
|
|
db7ac02c7d | ||
|
|
26f3560e97 | ||
|
|
1eaf4f44c2 | ||
|
|
0a0d4d3e0f | ||
|
|
7f22687c24 | ||
|
|
7404e0831b | ||
|
|
e3be54cbe7 | ||
|
|
b0a23a5c4e | ||
|
|
5bb5dae20e | ||
|
|
bb9de35722 | ||
|
|
4907be7738 | ||
|
|
23141aa624 | ||
|
|
17381e7deb | ||
|
|
4bd410f04e | ||
|
|
20a09d3d27 | ||
|
|
98b36380b5 | ||
|
|
7716827a98 | ||
|
|
070a51954a | ||
|
|
de66e21d14 | ||
|
|
932cf8ecf1 | ||
|
|
4a491f1ebf | ||
|
|
5ebdfd8466 | ||
|
|
16c720e500 | ||
|
|
d77f8abf42 | ||
|
|
03836b69f2 | ||
|
|
5837205a9a | ||
|
|
0f346c2096 | ||
|
|
30acfdd788 | ||
|
|
478edfee7c | ||
|
|
a685950a66 | ||
|
|
69d1d709f6 | ||
|
|
1fcfdcd13c | ||
|
|
34a21c6d8c | ||
|
|
e6ef27936e | ||
|
|
2b7f940669 | ||
|
|
bdcd613f02 | ||
|
|
7255300903 | ||
|
|
21b2ad71b9 | ||
|
|
1ff7e69556 | ||
|
|
f7a3f98ad3 | ||
|
|
4cdb874b4f | ||
|
|
5df0801c64 | ||
|
|
e286797ac5 | ||
|
|
e784dc017d | ||
|
|
251339f26b | ||
|
|
3e3b8eab6f | ||
|
|
a7d769dd6d | ||
|
|
ad37e9abe7 | ||
|
|
f99a3f5088 | ||
|
|
6f2a09695b | ||
|
|
9ae8769869 | ||
|
|
f58a16179a | ||
|
|
297c95ac6b | ||
|
|
ced3100b4e | ||
|
|
7358be6faa | ||
|
|
03e29dbb81 | ||
|
|
7cb2915fd7 | ||
|
|
5348496768 | ||
|
|
7526abc3e1 | ||
|
|
1163b6f0ce | ||
|
|
7c809419f8 | ||
|
|
0a42f130ac | ||
|
|
4de0ae51e8 | ||
|
|
f5a4ffabde | ||
|
|
115b1edf11 | ||
|
|
04942a3a5d | ||
|
|
aee0712ef1 | ||
|
|
63568aae9c | ||
|
|
bf90d2a8e6 | ||
|
|
d87cb77895 | ||
|
|
36c4ebda65 | ||
|
|
968df5a2e4 | ||
|
|
259f7a9439 | ||
|
|
4f8e1bc827 | ||
|
|
9e8a520d1e | ||
|
|
7335429541 | ||
|
|
5c227f445a | ||
|
|
dce90e86bf | ||
|
|
36d7dcce9b | ||
|
|
502bec55c4 | ||
|
|
21a61bb238 | ||
|
|
fb974de53b | ||
|
|
a5adf73b88 | ||
|
|
1f4c4c0901 | ||
|
|
338fbcea9c | ||
|
|
a20fa9871e | ||
|
|
25436d9620 | ||
|
|
8d3a633d85 | ||
|
|
f67568e3b6 | ||
|
|
d902cc5efe | ||
|
|
9e89eb521a | ||
|
|
f4c5f50f8c | ||
|
|
4301a6bfe3 | ||
|
|
2c5a78947a | ||
|
|
8f7eb82b6e | ||
|
|
d2e1373e2a | ||
|
|
d705610855 | ||
|
|
c40b6576d6 | ||
|
|
1c3e2fcdad | ||
|
|
7acc6c3329 | ||
|
|
1715ee327f | ||
|
|
0481943737 | ||
|
|
c53bc20294 | ||
|
|
a8fc22eae8 | ||
|
|
4ba82f6e00 | ||
|
|
8fd671d4d3 | ||
|
|
309b1b007e | ||
|
|
d0e7213cc4 | ||
|
|
a2e9688418 | ||
|
|
f42c23cd9a | ||
|
|
52f60d70e2 | ||
|
|
339d05e157 | ||
|
|
c0c3640638 | ||
|
|
a314139302 | ||
|
|
bfa0472f84 | ||
|
|
fa19e435cc | ||
|
|
eb4bf89194 | ||
|
|
da8a1376a7 | ||
|
|
267132cdde | ||
|
|
b8ebe5076b | ||
|
|
cb9877b3b4 | ||
|
|
c73b9ed5a6 | ||
|
|
1bd0b4349f | ||
|
|
d15785146f | ||
|
|
47d9d93818 | ||
|
|
601ff44145 | ||
|
|
cbd451949b | ||
|
|
a7dc207c5f | ||
|
|
c20b67d11f | ||
|
|
0b9def800b | ||
|
|
d8ae73e96a | ||
|
|
c99f7925eb | ||
|
|
3627661e1f | ||
|
|
f1c68a98cf | ||
|
|
92f10d51aa | ||
|
|
bdae68be35 | ||
|
|
2a4abf958d | ||
|
|
1362331a93 | ||
|
|
a70aa7723e | ||
|
|
a3f5200c9a | ||
|
|
1005783034 | ||
|
|
278e40207c | ||
|
|
509b627e88 | ||
|
|
ecd9f6eaff | ||
|
|
1d2b6b2010 | ||
|
|
6c4d5c0705 | ||
|
|
0533919bde | ||
|
|
e02ca1c822 | ||
|
|
039c1a178f | ||
|
|
6f682baa45 | ||
|
|
0241c0a036 | ||
|
|
4ebdcf1cc5 | ||
|
|
46c3187c89 | ||
|
|
c65e3c21e3 | ||
|
|
e35774138f | ||
|
|
a9b678f0ff | ||
|
|
32356d711d | ||
|
|
477ae3eb2c | ||
|
|
0b41118232 | ||
|
|
ea3f9f246e | ||
|
|
9dd954e7d8 | ||
|
|
41352a5116 | ||
|
|
1ac9989288 | ||
|
|
b441cc2123 | ||
|
|
5e2bc43722 | ||
|
|
da17dd8bae | ||
|
|
87098d1c3e | ||
|
|
2a1b60596b | ||
|
|
7bbbe0dcf3 | ||
|
|
25672a6496 | ||
|
|
796bf6ef45 | ||
|
|
6ceeaebd9d | ||
|
|
61b8427270 | ||
|
|
4450aaa3b3 | ||
|
|
b0e0c380dd | ||
|
|
8baf11a053 | ||
|
|
67f9697f3f | ||
|
|
a94d941125 | ||
|
|
2917de6776 | ||
|
|
272698f97b | ||
|
|
3fbb689e0a | ||
|
|
4817af5e4f | ||
|
|
aba08a515e | ||
|
|
83801db82e | ||
|
|
84e4adbc12 | ||
|
|
6a37753753 | ||
|
|
0898c7e28c | ||
|
|
412be37ae5 | ||
|
|
beec376e10 | ||
|
|
df30f00347 | ||
|
|
dcfdade927 | ||
|
|
741ddcd8aa | ||
|
|
433db26078 | ||
|
|
fc033cb69d | ||
|
|
bd8c67bc24 | ||
|
|
a80fbc1a04 | ||
|
|
e347504616 | ||
|
|
94cbf089db | ||
|
|
cb2fb6925b | ||
|
|
b3361a473c | ||
|
|
fcb6ed7676 | ||
|
|
91f4d7f669 | ||
|
|
2e2ff60c23 | ||
|
|
13727270d4 | ||
|
|
b27f56bc00 | ||
|
|
7ad7d793d0 | ||
|
|
491bef680e | ||
|
|
e581dc818f | ||
|
|
8d0b032eb6 | ||
|
|
9cb4fd6f79 | ||
|
|
87abcae249 | ||
|
|
fe608b579e | ||
|
|
f74e25a482 | ||
|
|
1db9d25f3b | ||
|
|
f25aeefe83 | ||
|
|
1abc1b9b28 | ||
|
|
d10c8041bf | ||
|
|
c14c795474 | ||
|
|
e5937431c3 | ||
|
|
b24a688e63 | ||
|
|
10fd22f49c | ||
|
|
f1dda95847 | ||
|
|
f3f6dd171b | ||
|
|
c802212a62 | ||
|
|
afb666797d | ||
|
|
85a591c984 | ||
|
|
3edaea8102 | ||
|
|
93c39c3ecc | ||
|
|
ab2f0ef395 | ||
|
|
005ca73cce | ||
|
|
8dd8897f1f | ||
|
|
95555ed3fd | ||
|
|
11b71f7366 | ||
|
|
4537c70105 | ||
|
|
42a0cff162 | ||
|
|
527e6d4c24 | ||
|
|
2c19da2117 | ||
|
|
7ee1cc911f | ||
|
|
9fc846bd7a | ||
|
|
5b628ccbf9 | ||
|
|
5471273673 | ||
|
|
58c0c6e152 | ||
|
|
36078acaee | ||
|
|
7d62c9f575 | ||
|
|
45a64fcb03 | ||
|
|
630b099fd6 | ||
|
|
c17e47ddd3 | ||
|
|
81bf54c42d | ||
|
|
5b693e81fd | ||
|
|
00115cd707 | ||
|
|
e992844fa2 | ||
|
|
b30acaac78 | ||
|
|
46ac654224 | ||
|
|
8578e96c3a | ||
|
|
b42bdc6e6f | ||
|
|
369364f574 | ||
|
|
a5baebab3f | ||
|
|
b34e6076e4 | ||
|
|
226c5d601c | ||
|
|
cc1a12e1a2 | ||
|
|
1d342b21fd | ||
|
|
5dc67a386c | ||
|
|
6d442b9e80 | ||
|
|
2f6ddeb5f1 | ||
|
|
3d01ffee1b | ||
|
|
9c51df9159 | ||
|
|
3a29fcc3d8 | ||
|
|
b18f2ae60d | ||
|
|
ab07478e25 | ||
|
|
d6aeec933a | ||
|
|
cd5207f816 | ||
|
|
381c11ed99 | ||
|
|
62d4b365c2 | ||
|
|
524df6389a | ||
|
|
24292e771b |
62
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
name: CI
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
|
||||
build:
|
||||
name: Create artifacts
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
# Initialization
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/setup-node@v1
|
||||
- run: npm install
|
||||
- name: Copy configuration
|
||||
run: cp config.json.example config.json
|
||||
|
||||
# Create Chrome artifacts
|
||||
- name: Create Chrome artifacts
|
||||
run: npm run build:chrome
|
||||
- uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: ChromeExtension
|
||||
path: dist
|
||||
- run: mkdir ./builds
|
||||
- uses: montudor/action-zip@v0.1.0
|
||||
with:
|
||||
args: zip -qq -r ./builds/ChromeExtension.zip ./dist
|
||||
|
||||
# Create Firefox artifacts
|
||||
- name: Create Firefox artifacts
|
||||
run: npm run build:firefox
|
||||
- uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: FirefoxExtension
|
||||
path: dist
|
||||
- uses: montudor/action-zip@v0.1.0
|
||||
with:
|
||||
args: zip -qq -r ./builds/FirefoxExtension.zip ./dist
|
||||
|
||||
# Create Beta artifacts (Builds with the name changed to beta)
|
||||
- name: Create Chrome Beta artifacts
|
||||
run: npm run build:chrome -- --env.stream=beta
|
||||
- uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: ChromeExtensionBeta
|
||||
path: dist
|
||||
- uses: montudor/action-zip@v0.1.0
|
||||
with:
|
||||
args: zip -qq -r ./builds/ChromeExtensionBeta.zip ./dist
|
||||
|
||||
- name: Create Firefox Beta artifacts
|
||||
run: npm run build:firefox -- --env.stream=beta
|
||||
- uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: FirefoxExtensionBeta
|
||||
path: dist
|
||||
- uses: montudor/action-zip@v0.1.0
|
||||
with:
|
||||
args: zip -qq -r ./builds/FirefoxExtensionBeta.zip ./dist
|
||||
|
||||
29
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
name: Upload Release Build
|
||||
|
||||
on: release
|
||||
|
||||
jobs:
|
||||
|
||||
build:
|
||||
name: Upload Release
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
# Build Artifacts
|
||||
- name: Build Artifacts
|
||||
uses: ./.github/workflows/ci
|
||||
|
||||
# Upload each release asset
|
||||
- name: Upload to release
|
||||
uses: JasonEtco/upload-to-release@master
|
||||
with:
|
||||
args: ./builds/ChromeExtension.zip
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Upload to release
|
||||
uses: JasonEtco/upload-to-release@master
|
||||
with:
|
||||
args: ./builds/FirefoxExtension.zip
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
7
.gitignore
vendored
@@ -1,3 +1,8 @@
|
||||
config.js
|
||||
config.json
|
||||
ignored
|
||||
.idea/
|
||||
node_modules
|
||||
web-ext-artifacts
|
||||
.vscode/
|
||||
dist/
|
||||
tmp/
|
||||
26
README.md
@@ -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,13 +50,27 @@ 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. 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 Chrome extension.
|
||||
|
||||
Use `npm run build:firefox` to generate a Firefox extension.
|
||||
|
||||
The result is in `dist`.
|
||||
|
||||
# Credit
|
||||
|
||||
The awesome [Invidious API](https://github.com/omarroth/invidious/wiki/API) is used to grab the time the video was published.
|
||||
The awesome [Invidious API](https://github.com/omarroth/invidious/wiki/API) previously was used.
|
||||
|
||||
Original code from [YTSponsorSkip](https://github.com/OfficialNoob/YTSponsorSkip), but not much of the code is left.
|
||||
Originally forked from [YTSponsorSkip](https://github.com/OfficialNoob/YTSponsorSkip), but zero code remains.
|
||||
|
||||
Some icons made by <a href="https://www.flaticon.com/authors/gregor-cresnar" title="Gregor Cresnar">Gregor Cresnar</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>
|
||||
|
||||
|
||||
236
background.js
@@ -1,236 +0,0 @@
|
||||
chrome.tabs.onUpdated.addListener(function(tabId) {
|
||||
chrome.tabs.sendMessage(tabId, {
|
||||
message: 'update',
|
||||
}, () => void chrome.runtime.lastError ); // Suppress error on Firefox
|
||||
});
|
||||
|
||||
chrome.runtime.onMessage.addListener(function (request, sender, callback) {
|
||||
switch(request.message) {
|
||||
case "submitTimes":
|
||||
submitTimes(request.videoID, callback);
|
||||
|
||||
//this allows the callback to be called later by the submitTimes function
|
||||
return true;
|
||||
case "addSponsorTime":
|
||||
addSponsorTime(request.time, request.videoID, callback);
|
||||
|
||||
//this allows the callback to be called later
|
||||
return true;
|
||||
case "getSponsorTimes":
|
||||
getSponsorTimes(request.videoID, function(sponsorTimes) {
|
||||
callback({
|
||||
sponsorTimes: sponsorTimes
|
||||
})
|
||||
});
|
||||
|
||||
//this allows the callback to be called later
|
||||
return true;
|
||||
case "submitVote":
|
||||
submitVote(request.type, request.UUID, callback);
|
||||
|
||||
//this allows the callback to be called later
|
||||
return true;
|
||||
case "alertPrevious":
|
||||
chrome.notifications.create("stillThere" + Math.random(), {
|
||||
type: "basic",
|
||||
title: chrome.i18n.getMessage("wantToSubmit") + request.previousVideoID + "?",
|
||||
message: chrome.i18n.getMessage("leftTimes"),
|
||||
iconUrl: "./icons/LogoSponsorBlocker256px.png"
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
//add help page on install
|
||||
chrome.runtime.onInstalled.addListener(function (object) {
|
||||
setTimeout(function() {
|
||||
chrome.storage.sync.get(["userID"], function(result) {
|
||||
const userID = result.userID;
|
||||
|
||||
// If there is no userID, then it is the first install.
|
||||
if (!userID){
|
||||
//open up the install page
|
||||
chrome.tabs.create({url: chrome.extension.getURL("/help/index_en.html")});
|
||||
|
||||
//generate a userID
|
||||
const newUserID = generateUserID();
|
||||
//save this UUID
|
||||
chrome.storage.sync.set({
|
||||
"userID": newUserID
|
||||
});
|
||||
}
|
||||
});
|
||||
}, 1500);
|
||||
});
|
||||
|
||||
//gets the sponsor times from memory
|
||||
function getSponsorTimes(videoID, callback) {
|
||||
let sponsorTimes = [];
|
||||
let sponsorTimeKey = "sponsorTimes" + videoID;
|
||||
chrome.storage.sync.get([sponsorTimeKey], function(result) {
|
||||
let sponsorTimesStorage = result[sponsorTimeKey];
|
||||
if (sponsorTimesStorage != undefined && sponsorTimesStorage.length > 0) {
|
||||
sponsorTimes = sponsorTimesStorage;
|
||||
}
|
||||
|
||||
callback(sponsorTimes)
|
||||
});
|
||||
}
|
||||
|
||||
function addSponsorTime(time, videoID, callback) {
|
||||
getSponsorTimes(videoID, function(sponsorTimes) {
|
||||
//add to sponsorTimes
|
||||
if (sponsorTimes.length > 0 && sponsorTimes[sponsorTimes.length - 1].length < 2) {
|
||||
//it is an end time
|
||||
sponsorTimes[sponsorTimes.length - 1][1] = time;
|
||||
} else {
|
||||
//it is a start time
|
||||
let sponsorTimesIndex = sponsorTimes.length;
|
||||
sponsorTimes[sponsorTimesIndex] = [];
|
||||
|
||||
sponsorTimes[sponsorTimesIndex][0] = time;
|
||||
}
|
||||
|
||||
//save this info
|
||||
let sponsorTimeKey = "sponsorTimes" + videoID;
|
||||
chrome.storage.sync.set({[sponsorTimeKey]: sponsorTimes}, callback);
|
||||
});
|
||||
}
|
||||
|
||||
function submitVote(type, UUID, callback) {
|
||||
chrome.storage.sync.get(["userID"], function(result) {
|
||||
let userID = result.userID;
|
||||
|
||||
if (userID == undefined || userID === "undefined") {
|
||||
//generate one
|
||||
userID = generateUserID();
|
||||
chrome.storage.sync.set({
|
||||
"userID": userID
|
||||
});
|
||||
}
|
||||
|
||||
//publish this vote
|
||||
sendRequestToServer("GET", "/api/voteOnSponsorTime?UUID=" + UUID + "&userID=" + userID + "&type=" + type, function(xmlhttp, error) {
|
||||
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
|
||||
callback({
|
||||
successType: 1
|
||||
});
|
||||
} else if (xmlhttp.readyState == 4 && xmlhttp.status == 405) {
|
||||
//duplicate vote
|
||||
callback({
|
||||
successType: 0,
|
||||
statusCode: xmlhttp.status
|
||||
});
|
||||
} else if (error) {
|
||||
//error while connect
|
||||
callback({
|
||||
successType: -1,
|
||||
statusCode: xmlhttp.status
|
||||
});
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function submitTimes(videoID, callback) {
|
||||
//get the video times from storage
|
||||
let sponsorTimeKey = 'sponsorTimes' + videoID;
|
||||
chrome.storage.sync.get([sponsorTimeKey, "userID"], async function(result) {
|
||||
let sponsorTimes = result[sponsorTimeKey];
|
||||
let userID = result.userID;
|
||||
|
||||
if (sponsorTimes != undefined && sponsorTimes.length > 0) {
|
||||
let durationResult = await new Promise((resolve, reject) => {
|
||||
chrome.tabs.query({
|
||||
active: true,
|
||||
currentWindow: true
|
||||
}, function(tabs) {
|
||||
chrome.tabs.sendMessage(tabs[0].id, {
|
||||
message: "getVideoDuration"
|
||||
}, (response) => resolve(response));
|
||||
});
|
||||
});
|
||||
|
||||
//check if a sponsor exceeds the duration of the video
|
||||
for (let i = 0; i < sponsorTimes.length; i++) {
|
||||
if (sponsorTimes[i][1] > durationResult.duration) {
|
||||
sponsorTimes[i][1] = durationResult.duration;
|
||||
}
|
||||
}
|
||||
|
||||
//submit these times
|
||||
for (let i = 0; i < sponsorTimes.length; i++) {
|
||||
//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
|
||||
});
|
||||
|
||||
if (xmlhttp.status == 200) {
|
||||
//add these to the storage log
|
||||
chrome.storage.sync.get(["sponsorTimesContributed"], function(result) {
|
||||
let currentContributionAmount = 0;
|
||||
if (result.sponsorTimesContributed != undefined) {
|
||||
//current contribution amount is known
|
||||
currentContributionAmount = result.sponsorTimesContributed;
|
||||
}
|
||||
|
||||
//save the amount contributed
|
||||
if (!increasedContributionAmount) {
|
||||
increasedContributionAmount = true;
|
||||
|
||||
chrome.storage.sync.set({"sponsorTimesContributed": currentContributionAmount + 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();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"serverAddress": "https://sponsor.ajay.app",
|
||||
"serverAddressComment": "This specifies the default SponsorBlock server to conect to"
|
||||
}
|
||||
1132
content.js
10
crowdin.yml
Normal file
@@ -0,0 +1,10 @@
|
||||
files:
|
||||
- source: /_locales/en/*
|
||||
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"
|
||||
zh-TW: "zh_TW"
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"browser_specific_settings": {
|
||||
"gecko": {
|
||||
"id": "sponsorBlocker@ajay.app",
|
||||
"strict_min_version": "57.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
8
jest.config.js
Normal file
@@ -0,0 +1,8 @@
|
||||
module.exports = {
|
||||
"roots": [
|
||||
"src"
|
||||
],
|
||||
"transform": {
|
||||
"^.+\\.ts$": "ts-jest"
|
||||
},
|
||||
};
|
||||
@@ -1,69 +0,0 @@
|
||||
{
|
||||
"name": "__MSG_fullName__",
|
||||
"short_name": "__MSG_Name__",
|
||||
"version": "1.1.9.5",
|
||||
"default_locale": "en",
|
||||
"description": "__MSG_Description__",
|
||||
"content_scripts": [
|
||||
{
|
||||
"matches": [
|
||||
"https://*.youtube.com/*",
|
||||
"https://www.youtube-nocookie.com/embed/*"
|
||||
],
|
||||
"all_frames": true,
|
||||
"js": [
|
||||
"config.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/*"
|
||||
],
|
||||
"browser_action": {
|
||||
"default_title": "__MSG_Name__",
|
||||
"default_popup": "popup.html"
|
||||
},
|
||||
"background": {
|
||||
"scripts":[
|
||||
"utils.js",
|
||||
"config.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"
|
||||
},
|
||||
"manifest_version": 2
|
||||
}
|
||||
4
manifest/beta-manifest-extra.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "__MSG_betaName__"
|
||||
}
|
||||
|
||||
8
manifest/chrome-manifest-extra.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"optional_permissions": [
|
||||
"declarativeContent"
|
||||
],
|
||||
"background": {
|
||||
"persistent": false
|
||||
}
|
||||
}
|
||||
7
manifest/firefox-manifest-extra.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"browser_specific_settings": {
|
||||
"gecko": {
|
||||
"id": "sponsorBlocker@ajay.app"
|
||||
}
|
||||
}
|
||||
}
|
||||
70
manifest/manifest.json
Normal file
@@ -0,0 +1,70 @@
|
||||
{
|
||||
"name": "__MSG_fullName__",
|
||||
"short_name": "__MSG_Name__",
|
||||
"version": "1.2.16",
|
||||
"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
|
||||
}
|
||||
12847
package-lock.json
generated
Normal file
50
package.json
Normal file
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"name": "sponsorblock",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "background.js",
|
||||
"dependencies": {
|
||||
"concurrently": "^5.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"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": {
|
||||
"web-run": "npm run web-run:chrome",
|
||||
"web-run:firefox": "cd dist && web-ext run --start-url https://addons.mozilla.org/firefox/addon/ublock-origin/",
|
||||
"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:dev": "npm run build:dev:chrome",
|
||||
"build:dev:chrome": "webpack --env.browser=chrome --config webpack/webpack.dev.js",
|
||||
"build:dev:firefox": "webpack --env.browser=firefox --config webpack/webpack.dev.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:dev && concurrently \"npm run web-run\" \"npm run build:watch\"",
|
||||
"dev:firefox": "npm run build:dev:firefox && concurrently \"npm run web-run:firefox\" \"npm run build:watch:firefox\"",
|
||||
"clean": "rimraf dist",
|
||||
"test": "npx jest"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/ajayyy/SponsorBlock.git"
|
||||
},
|
||||
"author": "Ajay Ramachandran",
|
||||
"license": "GPL-3.0-only",
|
||||
"private": true
|
||||
}
|
||||
240
popup.html
@@ -1,240 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>__MSG_openPopup__</title>
|
||||
<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"/>
|
||||
</head>
|
||||
|
||||
<body class="popupBody">
|
||||
<center>
|
||||
<div id="app" class="popupBody">
|
||||
<h1 class="popupElement">
|
||||
<img src="icons/IconSponsorBlocker256px.png" height="32px" id="sponsorBlockPopupLogo"/>
|
||||
__MSG_Name__
|
||||
</h1>
|
||||
|
||||
<!-- Loading text -->
|
||||
<p id="loadingIndicator" class="popupElement">__MSG_Loading__</p>
|
||||
|
||||
<!-- Hidden until loading complete -->
|
||||
<div id="mainControls" class="main popupElement" style="display: none">
|
||||
<!-- If the video was found in the database -->
|
||||
<div id="videoFound">
|
||||
|
||||
</div>
|
||||
|
||||
<div id="downloadedSponsorMessageTimes" class="popupElement">
|
||||
|
||||
</div>
|
||||
|
||||
<p class="popupElement">
|
||||
__MSG_recordTimesDescription__
|
||||
</p>
|
||||
|
||||
<div>
|
||||
<button id="sponsorStart" class="greenButton popupElement">__MSG_sponsorStart__</button>
|
||||
</div>
|
||||
|
||||
<sub class="popupElement">__MSG_popupHint__</sub>
|
||||
|
||||
<div id="submissionSection" class="popupElement" style="display: none">
|
||||
<h3 class="popupElement">__MSG_lastTimes__</h3>
|
||||
<b>
|
||||
<div id="sponsorMessageTimes" class="popupElement">
|
||||
|
||||
</div>
|
||||
</b>
|
||||
|
||||
<br/>
|
||||
|
||||
<button id="clearTimes" class="smallButton popupElement">__MSG_clearTimesButton__</button>
|
||||
|
||||
<div id="submitTimesContainer" class="popupElement" style="display: none">
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<button id="submitTimes" class="smallButton popupElement">__MSG_submitTimesButton__</button>
|
||||
|
||||
<div id="submitTimesInfoMessageContainer" class="popupElement" style="display: none">
|
||||
<h3 id="submitTimesInfoMessage" class="popupElement">
|
||||
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="discordButtonContainer" class="popupElement" style="display: none">
|
||||
<br/>
|
||||
|
||||
<a href="https://discord.gg/QnmVMpU" class="popupElement" target="_blank"><img src="https://www.logolynx.com/images/logolynx/1b/1bcc0f0aefe71b2c8ce66ffe8645d365.png" height="32px"/></a>
|
||||
|
||||
<br/>
|
||||
|
||||
__MSG_discordAdvert__
|
||||
|
||||
<br/>
|
||||
|
||||
<span id="hideDiscordButton" class="smallLink popupElement">__MSG_hideThis__</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<br/>
|
||||
|
||||
<button id="whitelistChannel" class="whitelistButton popupElement">__MSG_whitelistChannel__</button>
|
||||
<button id="unwhitelistChannel" class="whitelistButton popupElement" style="display: none">__MSG_removeFromWhitelist__</button>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
|
||||
<div>
|
||||
<button id="disableSkipping" class="greenButton popupElement">__MSG_disableSkipping__</button>
|
||||
<button id="enableSkipping" class="whitelistButton popupElement" style="display: none">__MSG_enableSkipping__</button>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
|
||||
<button id="reportAnIssue" class="dangerButton popupElement">__MSG_voteOnTime__</button>
|
||||
|
||||
<div id="issueReporterContainer" class="popupElement" style="display: none">
|
||||
|
||||
<h3 style="margin-top: 0px" class="popupElement">__MSG_voteOnTime__</h3>
|
||||
|
||||
<div id="issueReporterTimeButtons" class="popupElement">
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<h2 class="recordingSubtitle popupElement">__MSG_yourWork__</h2>
|
||||
|
||||
<p class="popupElement">
|
||||
<span id=sponsorTimesContributionsContainer class="popupElement" style="display: none">
|
||||
__MSG_soFarUHSubmited__
|
||||
<span id="sponsorTimesContributionsDisplay" class="popupElement">
|
||||
0
|
||||
</span>
|
||||
<span id="sponsorTimesContributionsDisplayEndWord" class="popupElement">
|
||||
__MSG_Sponsors__.
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<span id=sponsorTimesViewsContainer class="popupElement" style="display: none">
|
||||
__MSG_savedPeopleFrom__
|
||||
<span id="sponsorTimesViewsDisplay" class="popupElement">
|
||||
0
|
||||
</span>
|
||||
<span id="sponsorTimesViewsDisplayEndWord" class="popupElement">
|
||||
__MSG_Segments__.
|
||||
__MSG_savedPeopleFrom__
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<div class="popupElement">
|
||||
__MSG_viewLeaderboard__ <a class="popupElement discreteLink" href="https://sponsor.ajay.app/stats" target="_blank">__MSG_here__</a>.
|
||||
</div>
|
||||
</p>
|
||||
|
||||
<div id="setUsernameContainer" class="popupElement">
|
||||
|
||||
<button id="setUsernameButton" class="warningButton popupElement">__MSG_setUsername__</button>
|
||||
<br/>
|
||||
<sub class="popupElement">
|
||||
__MSG_publicStats__ <a class="popupElement discreteLink" href="https://sponsor.ajay.app/stats" target="_blank">__MSG_here__</a>.
|
||||
</sub>
|
||||
</div>
|
||||
|
||||
<div id="setUsername" class="popupElement" style="display: none">
|
||||
<h3>__MSG_setUsername__</h3>
|
||||
|
||||
<div id="setUsernameStatusContainer" style="display: none">
|
||||
<h2 id="setUsernameStatus"></h2>
|
||||
</div>
|
||||
|
||||
<input id="usernameInput" hint="Username"></input>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<button id="submitUsername" class="warningButton popupElement">__MSG_setUsername__</button>
|
||||
</div>
|
||||
|
||||
<div id="optionsButtonContainer" class="popupElement">
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<button id="optionsButton" class="dangerButton popupElement">__MSG_Options__</button>
|
||||
</div>
|
||||
|
||||
<div id="options" class="popupElement" style="display: none">
|
||||
<br/>
|
||||
|
||||
<h3>__MSG_Options__</h3>
|
||||
|
||||
<span id="keybindButtons">
|
||||
<button id="setStartSponsorKeybind" class="warningButton popupElement">__MSG_setStartSponsorShortcut__</button>
|
||||
<br/>
|
||||
<br/>
|
||||
<button id="setSubmitKeybind" class="warningButton popupElement">__MSG_setSubmitKeybind__</button>
|
||||
<br/>
|
||||
</span>
|
||||
|
||||
<h2 id="keybindDescription" style="display: none" class="popupElement">__MSG_keybindDescription__</h2>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<button id="hideVideoPlayerControls" class="warningButton popupElement">__MSG_hideButtons__</button>
|
||||
<button id="showVideoPlayerControls" style="display: none" class="warningButton popupElement">__MSG_showButtons__</button>
|
||||
<br/>
|
||||
<sub class="popupElement">
|
||||
__MSG_hideButtonsDescription__
|
||||
</sub>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<button id="hideInfoButtonPlayerControls" class="warningButton popupElement">__MSG_hideInfoButton__</button>
|
||||
<button id="showInfoButtonPlayerControls" style="display: none" class="warningButton popupElement">__MSG_showInfoButton__</button>
|
||||
<br/>
|
||||
<sub class="popupElement">
|
||||
__MSG_whatInfoButton__
|
||||
</sub>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<button id="hideDeleteButtonPlayerControls" class="warningButton popupElement">__MSG_hideDeleteButton__</button>
|
||||
<button id="showDeleteButtonPlayerControls" style="display: none" class="warningButton popupElement">__MSG_showDeleteButton__</button>
|
||||
<br/>
|
||||
<sub class="popupElement">
|
||||
__MSG_whatDeleteButton__
|
||||
</sub>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<button id="disableSponsorViewTracking" class="warningButton popupElement">__MSG_disableViewTracking__</button>
|
||||
<button id="enableSponsorViewTracking" style="display: none" class="warningButton popupElement">__MSG_enableViewTracking__</button>
|
||||
<br/>
|
||||
<sub class="popupElement">
|
||||
__MSG_whatViewTracking__
|
||||
</sub>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<button id="showNoticeAgain" style="display: none" class="dangerButton popupElement">__MSG_showNotice__</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</center>
|
||||
</body>
|
||||
|
||||
<!-- Scripts that need to load after the html -->
|
||||
<script src="config.js"></script>
|
||||
<script src="utils.js"></script>
|
||||
<script src="popup.js"></script>
|
||||
</html>
|
||||
318
public/_locales/de/messages.json
Normal file
@@ -0,0 +1,318 @@
|
||||
{
|
||||
"Name": {
|
||||
"message": "SponsorBlock",
|
||||
"description": "Name of the extension."
|
||||
},
|
||||
"fullName": {
|
||||
"message": "SponsorBlock for YouTube - Skip Sponsorships",
|
||||
"description": "Name of the extension."
|
||||
},
|
||||
|
||||
"Description": {
|
||||
"message": "Überspringe die gesponserten Inhalte in YouTube-Videos. Melde gesponsorte Inhalte in den von dir angesehenen Videos und erspare anderen die Zeit.",
|
||||
"description": "Description of the extension."
|
||||
},
|
||||
"helpPage": {
|
||||
"message": "index_en.html"
|
||||
},
|
||||
"400": {
|
||||
"message": "Ungültige Anforderung"
|
||||
},
|
||||
"429": {
|
||||
"message": "Du hast zu viele Segmente in diesem Video eingereicht. Bist du dir sicher?"
|
||||
},
|
||||
"409": {
|
||||
"message": "Dieser Inhalt wurde bereits eingereicht."
|
||||
},
|
||||
"channelWhitelisted": {
|
||||
"message": "Kanal auf Whitelist gesetzt!"
|
||||
},
|
||||
"Sponsor": {
|
||||
"message": "Sponsor"
|
||||
},
|
||||
"Sponsors": {
|
||||
"message": "Sponsoren"
|
||||
},
|
||||
"Segment": {
|
||||
"message": "gesponsorter Inhalt"
|
||||
},
|
||||
"Segments": {
|
||||
"message": "gesponsorte Inhalte"
|
||||
},
|
||||
"noticeTitle": {
|
||||
"message": "Sponsor übersprungen"
|
||||
},
|
||||
"reportButtonTitle": {
|
||||
"message": "Melden"
|
||||
},
|
||||
"reportButtonInfo": {
|
||||
"message": "Melde dieses Segment als unzulässig."
|
||||
},
|
||||
"Dismiss": {
|
||||
"message": "Abbrechen"
|
||||
},
|
||||
"Loading": {
|
||||
"message": "Laden..."
|
||||
},
|
||||
"Mins": {
|
||||
"message": "Minuten"
|
||||
},
|
||||
"Secs": {
|
||||
"message": "Sekunden"
|
||||
},
|
||||
"Hide": {
|
||||
"message": "Verstecken"
|
||||
},
|
||||
"hitGoBack": {
|
||||
"message": "Klicke Zurück um die Aktion rückgängig zu machen."
|
||||
},
|
||||
"unskip": {
|
||||
"message": "Zurück"
|
||||
},
|
||||
"reskip": {
|
||||
"message": "Vorwärts"
|
||||
},
|
||||
"paused": {
|
||||
"message": "Pausiert"
|
||||
},
|
||||
"confirmMSG": {
|
||||
"message": "\n\nUm einzelne Werte zu löschen oder zu ändern, klicke auf den Info-Button, oder öffne die Erweiterungs-Übersicht, indem du das Erweiterungssymbol in der rechten oberen Ecke anklickst."
|
||||
},
|
||||
"clearThis": {
|
||||
"message": "Bist du sicher, dass du Folgendes löschen möchtest?\n\n"
|
||||
},
|
||||
"Unknown": {
|
||||
"message": "Deine Segmente konnten nicht gesendet werden, bitte versuche es später erneut."
|
||||
},
|
||||
"sponsorFound": {
|
||||
"message": "Die gesponsorten Inhalte dieses Videos befinden sich bereits in der Datenbank!"
|
||||
},
|
||||
"sponsor404": {
|
||||
"message": "Keine Sponsoren gefunden"
|
||||
},
|
||||
"sponsorStart": {
|
||||
"message": "Gesponserter Inhalt beginnt"
|
||||
},
|
||||
"sponsorEnd": {
|
||||
"message": "Gesponserter Inhalt endet"
|
||||
},
|
||||
"noVideoID": {
|
||||
"message": "Keine YouTube-Videos in diesem Tab gefunden. Wenn dies ein Youtube-Tab ist, schließe dieses Pop-up und öffne es erneut. Wenn das nicht hilft, versuche den Tab neu zu laden."
|
||||
},
|
||||
"success": {
|
||||
"message": "Erfolg!"
|
||||
},
|
||||
"voted": {
|
||||
"message": "Abgestimmt!"
|
||||
},
|
||||
"voteFail": {
|
||||
"message": "Du hast bereits so abgestimmt."
|
||||
},
|
||||
"serverDown": {
|
||||
"message": "Der Server ist scheinbar offline. Bitte unverzüglich dem Entwickler melden."
|
||||
},
|
||||
"connectionError": {
|
||||
"message": "Ein Verbindungsfehler ist aufgetreten. Fehlermeldung: "
|
||||
},
|
||||
"wantToSubmit": {
|
||||
"message": "Möchtest du die Segmente für die Video ID senden?"
|
||||
},
|
||||
"leftTimes": {
|
||||
"message": "Scheinbar hast du einige Segmente noch nicht gesendet. Kehre zur Seite zurück um sie zu senden (sie sind noch gespeichert)."
|
||||
},
|
||||
"clearTimes": {
|
||||
"message": "Lösche Auswahl"
|
||||
},
|
||||
"openPopup": {
|
||||
"message": "Öffne SponsorBlock-Popup"
|
||||
},
|
||||
"SubmitTimes": {
|
||||
"message": "Sende Auswahl"
|
||||
},
|
||||
"submitCheck": {
|
||||
"message": "Bist du sicher, dass die Auswahl abgeschickt werden soll?"
|
||||
},
|
||||
"whitelistChannel": {
|
||||
"message": "Kanal auf Whitelist setzen "
|
||||
},
|
||||
"removeFromWhitelist": {
|
||||
"message": "Kanal von Whitelist entfernen"
|
||||
},
|
||||
"voteOnTime": {
|
||||
"message": "Stimme für Zeiten ab"
|
||||
},
|
||||
"recordTimes": {
|
||||
"message": "Lege das Zeitfenster eines gesponsorten Inhalts fest"
|
||||
},
|
||||
"soFarUHSubmited": {
|
||||
"message": "Gemeldet wurden von dir bisher"
|
||||
},
|
||||
"savedPeopleFrom": {
|
||||
"message": "Du hast andere Benutzer bewahrt vor"
|
||||
},
|
||||
"viewLeaderboard": {
|
||||
"message": "Siehe Rangliste"
|
||||
},
|
||||
"here": {
|
||||
"message": "hier"
|
||||
},
|
||||
"recordTimesDescription": {
|
||||
"message": "Klicke den Knopf unten, wenn der gesponsorte Inhalt beginnt und endet, um aufzunehmen und\n einzusenden"
|
||||
},
|
||||
"popupHint": {
|
||||
"message": "Hinweis: In den Optionen lässt sich eine Taste für das Festlegen von Anfang/Ende des gesponsorten Inhalts, sowie für das Einsenden festlegen"
|
||||
},
|
||||
"lastTimes": {
|
||||
"message": "Letzte ausgewählte Zeitabschnitte"
|
||||
},
|
||||
"clearTimesButton": {
|
||||
"message": "Zeiten löschen"
|
||||
},
|
||||
"submitTimesButton": {
|
||||
"message": "Zeiten einsenden"
|
||||
},
|
||||
"publicStats": {
|
||||
"message": "Dies ist wichtig für die öffentliche Nutzerstatistik. Siehe"
|
||||
},
|
||||
"setUsername": {
|
||||
"message": "Alias festlegen"
|
||||
},
|
||||
"discordAdvert": {
|
||||
"message": "Tritt dem offiziellen Discord-Kanal bei und teile Anregungen und Feedback!"
|
||||
},
|
||||
"hideThis": {
|
||||
"message": "Verstecken"
|
||||
},
|
||||
"Options": {
|
||||
"message": "Optionen"
|
||||
},
|
||||
"showButtons": {
|
||||
"message": "Knöpfe in YouTube-Leiste zeigen"
|
||||
},
|
||||
"hideButtons": {
|
||||
"message": "Knöpfe in YouTube-Leiste verstecken"
|
||||
},
|
||||
"hideButtonsDescription": {
|
||||
"message": "Die Einstellung versteckt den Einsende-Knopf in der Youtube-Leiste. Ich kann verstehen, weshalb manchen diese Funktion\n an dieser Stelle stört. Stattdessen kann dafür dieses Pop-up genutzt werden. Um die transparente Benachrichtigung zu verstecken, klicke auf den \"Verstecken\"-Knopf \n der Benachrichtigung. Diese Einstellungen können wieder rückgängig gemacht werden."
|
||||
},
|
||||
"showInfoButton": {
|
||||
"message": "Zeige Info-Knopf in Youtube-Leiste"
|
||||
},
|
||||
"hideInfoButton": {
|
||||
"message": "Verstecke Info-Knopf in Youtube-Leiste"
|
||||
},
|
||||
"whatInfoButton": {
|
||||
"message": "Dieser Knopf öffnet ein Pop-up auf der Youtube-Seite."
|
||||
},
|
||||
"hideDeleteButton": {
|
||||
"message": "Verstecke Löschen-Knopf in Youtube Leiste"
|
||||
},
|
||||
"showDeleteButton": {
|
||||
"message": "Zeige Löschen-Knopf in Youtube Leiste"
|
||||
},
|
||||
"whatDeleteButton": {
|
||||
"message": "Dieser Knopf entfernt sämtlich Segmente in der Youtube-Zeitleiste."
|
||||
},
|
||||
"disableViewTracking": {
|
||||
"message": "Deaktiviere das Mitzählen übersprungener Sponsoren"
|
||||
},
|
||||
"enableViewTracking": {
|
||||
"message": "Aktiviere das Mitzählen übersprungener Sponsoren"
|
||||
},
|
||||
"whatViewTracking": {
|
||||
"message": "Diese Funktion hält fest, welche Sponsoren von dir übersprungen wurden und hilft anderen zu erfahren, was ihre Einsendungen bewirkt haben.\n Außerdem dienen die Werte zusammen mit positiven Rückmeldungen als Anti-Spam-Schutz. \n Wenn ein gesponsorter Inhalt übersprungen wird, sendet die Erweiterung eine Nachricht an den Server. \n Hoffentlich wird diese Funktion auch weiterhin genutzt, damit der Algorithmus funktioniert. :)"
|
||||
},
|
||||
"showNotice": {
|
||||
"message": "Benachrichtigung wieder zeigen"
|
||||
},
|
||||
"longDescription": {
|
||||
"message": "SponsorBlock ist eine Erweiterung, die gesponsorte Segmente in YouTube-Videos überspringt. SponsorBlock ist ein Benutzernetzwerk, bei dem jeder Anfang und Ende eines Werbeblocks einreichen kann. Sobald die Information von einem Nutzer eingereicht wurde, überspringen die Erweiterungen der anderen dieses Segment automatisch.",
|
||||
"description": "Full description of the extension on the store pages."
|
||||
},
|
||||
"website": {
|
||||
"message": "Webseite",
|
||||
"description": "Used on Firefox Store Page"
|
||||
},
|
||||
"sourceCode": {
|
||||
"message": "Quellcode",
|
||||
"description": "Used on Firefox Store Page"
|
||||
},
|
||||
"noticeUpdate": {
|
||||
"message": "Die Benachrichtigung wurde verbessert!",
|
||||
"description": "The first line of the message displayed after the notice was upgraded."
|
||||
},
|
||||
"noticeUpdate2": {
|
||||
"message": "Gefällt dir immer noch nicht? Dann klicke den Verstecken-Knopf.",
|
||||
"description": "The second line of the message displayed after the notice was upgraded."
|
||||
},
|
||||
"setStartSponsorShortcut": {
|
||||
"message": "Segment aufnehmen Taste festlegen"
|
||||
},
|
||||
"setSubmitKeybind": {
|
||||
"message": "Segment einsenden Taste festlegen"
|
||||
},
|
||||
"keybindDescription": {
|
||||
"message": "Taste drücken, um festzulegen"
|
||||
},
|
||||
"keybindDescriptionComplete": {
|
||||
"message": "Die Taste wurde festgelegt auf: "
|
||||
},
|
||||
"0": {
|
||||
"message": "Verbindungsüberschreibung. Überprüfe deine Internetverbindung. Bist du mit dem Internet verbunden, ist der Server wahrscheinlich offline."
|
||||
},
|
||||
"disableSkipping": {
|
||||
"message": "SponsorBlock ausschalten"
|
||||
},
|
||||
"enableSkipping": {
|
||||
"message": "SponsorBlock einschalten"
|
||||
},
|
||||
"yourWork": {
|
||||
"message": "Deine Statistik",
|
||||
"description": "Used to describe the section that will show you the statistics from your submissions."
|
||||
},
|
||||
"502": {
|
||||
"message": "Der Server scheint überlastet zu sein. Probiere es in ein paar Sekunden erneut."
|
||||
},
|
||||
"errorCode": {
|
||||
"message": "Fehlermeldung: "
|
||||
},
|
||||
"noticeTitleNotSkipped": {
|
||||
"message": "Sponsor überspringen?"
|
||||
},
|
||||
"skip": {
|
||||
"message": "Überspringen"
|
||||
},
|
||||
"disableAutoSkip": {
|
||||
"message": "Auto-Überspringen deaktivieren"
|
||||
},
|
||||
"enableAutoSkip": {
|
||||
"message": "Auto-Überspringen aktivieren"
|
||||
},
|
||||
"autoSkipDescription": {
|
||||
"message": "Auto-Überspringen überspringt gesponsorte Inhalte für dich. Wenn deaktiviert, fragt die Benachrichtigung, ob übersprungen werden soll."
|
||||
},
|
||||
"youHaveSkipped": {
|
||||
"message": "Du übersprangst "
|
||||
},
|
||||
"youHaveSaved": {
|
||||
"message": "Du erspartest dir "
|
||||
},
|
||||
"minLower": {
|
||||
"message": "Minute"
|
||||
},
|
||||
"minsLower": {
|
||||
"message": "Minuten"
|
||||
},
|
||||
"hourLower": {
|
||||
"message": "Stunde"
|
||||
},
|
||||
"hoursLower": {
|
||||
"message": "Stunden"
|
||||
},
|
||||
"youHaveSavedTime": {
|
||||
"message": "Du erspartest anderen"
|
||||
},
|
||||
"youHaveSavedTimeEnd": {
|
||||
"message": " ihrer Zeit."
|
||||
}
|
||||
}
|
||||
@@ -24,9 +24,6 @@
|
||||
"409": {
|
||||
"message": "This has already been submitted before"
|
||||
},
|
||||
"502": {
|
||||
"message": "It seems the server is down. Contact the dev to inform them."
|
||||
},
|
||||
"channelWhitelisted": {
|
||||
"message": "Channel Whitelisted!"
|
||||
},
|
||||
@@ -79,7 +76,7 @@
|
||||
"message": "Paused"
|
||||
},
|
||||
"confirmMSG": {
|
||||
"message": "\n\nTo edit or delete individual values, click the info button or open the extension popup by clicking the extension icon in the top right corner."
|
||||
"message": "To edit or delete individual values, click the info button or open the extension popup by clicking the extension icon in the top right corner."
|
||||
},
|
||||
"clearThis": {
|
||||
"message": "Are you sure you want to clear this?\n\n"
|
||||
@@ -100,7 +97,7 @@
|
||||
"message": "Sponsorship Ends Now"
|
||||
},
|
||||
"noVideoID": {
|
||||
"message": "This probably isn't a YouTube tab, or you clicked too early. \n If you know this is a YouTube tab,\n close this popup and open it again.\n\nYou must refresh your YouTube tab after installing or updating (even auto-update) the extension."
|
||||
"message": "No YouTube video found at this tab. If you know this is a YouTube tab, close this popup and open it again. If that does not work, try reloading the tab."
|
||||
},
|
||||
"success": {
|
||||
"message": "Success!"
|
||||
@@ -217,10 +214,10 @@
|
||||
"message": "This is the button that allows you to clear all sponsors on the YouTube player."
|
||||
},
|
||||
"disableViewTracking": {
|
||||
"message": "Disable Sponsor View Tracking"
|
||||
"message": "Disable Sponsor Skip Count Tracking"
|
||||
},
|
||||
"enableViewTracking": {
|
||||
"message": "Enable Sponsor View Tracking"
|
||||
"message": "Enable Sponsor Skip Count Tracking"
|
||||
},
|
||||
"whatViewTracking": {
|
||||
"message": "This feature tracks which sponsors you have skipped to let users know how much their submission has helped others and\nused as a metric along with upvotes to ensure that spam doesn't get into the database. The extension sends a message\nto the server each time you skip a sponsor. Hopefully most people don't change this setting so that the view numbers are accurate. :)"
|
||||
@@ -272,5 +269,164 @@
|
||||
"yourWork": {
|
||||
"message": "Your Work",
|
||||
"description": "Used to describe the section that will show you the statistics from your submissions."
|
||||
},
|
||||
"502": {
|
||||
"message": "The server seems to be overloaded. Try again in a few seconds."
|
||||
},
|
||||
"errorCode": {
|
||||
"message": "Error Code: "
|
||||
},
|
||||
"noticeTitleNotSkipped": {
|
||||
"message": "Skip Sponsor?"
|
||||
},
|
||||
"skip": {
|
||||
"message": "Skip"
|
||||
},
|
||||
"disableAutoSkip": {
|
||||
"message": "Disable Auto Skip"
|
||||
},
|
||||
"enableAutoSkip": {
|
||||
"message": "Enable Auto Skip"
|
||||
},
|
||||
"autoSkipDescription": {
|
||||
"message": "Auto skip will skip sponsors for you. If disabled, a notice will appear asking if you'd like to skip."
|
||||
},
|
||||
"youHaveSkipped": {
|
||||
"message": "You have skipped "
|
||||
},
|
||||
"youHaveSaved": {
|
||||
"message": "You have saved yourself "
|
||||
},
|
||||
"minLower": {
|
||||
"message": "minute"
|
||||
},
|
||||
"minsLower": {
|
||||
"message": "minutes"
|
||||
},
|
||||
"hourLower": {
|
||||
"message": "hour"
|
||||
},
|
||||
"hoursLower": {
|
||||
"message": "hours"
|
||||
},
|
||||
"youHaveSavedTime": {
|
||||
"message": "You have saved people"
|
||||
},
|
||||
"youHaveSavedTimeEnd": {
|
||||
"message": " of their lives."
|
||||
},
|
||||
"guildlinesSummary": {
|
||||
"message": "- Make sure your segment only contains paid promotion segment, nothing else.\n- Make sure skipping this segment will not skip valuable content\n- If the whole video is a sponsor, please do not report it. A full video reporting system will come out soon.\n- Please do not report disclaimers that could show bias (if a review video is sponsored, don't skip when they mention that)."
|
||||
},
|
||||
"statusReminder": {
|
||||
"message": "Check status.sponsor.ajay.app for server status."
|
||||
},
|
||||
"changeUserID": {
|
||||
"message": "Import/Export Your UserID"
|
||||
},
|
||||
"whatChangeUserID": {
|
||||
"message": "This should be kept private. This is like a password and should not be shared with anyone. If someone has this, they can impersonate you."
|
||||
},
|
||||
"setUserID": {
|
||||
"message": "Set UserID"
|
||||
},
|
||||
"userIDChangeWarning": {
|
||||
"message": "Warning: Changing the UserID is permanent. Are you sure you would like to do this? Make sure to backup your old one just in case."
|
||||
},
|
||||
"createdBy": {
|
||||
"message": "Created By"
|
||||
},
|
||||
"autoSkip": {
|
||||
"message": "Auto Skip"
|
||||
},
|
||||
"showSkipNotice": {
|
||||
"message": "Show Notice After A Sponsor Is Skipped"
|
||||
},
|
||||
"keybindCurrentlySet": {
|
||||
"message": ". It is currently set to:"
|
||||
},
|
||||
"supportInvidious": {
|
||||
"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."
|
||||
},
|
||||
"optionsInfo": {
|
||||
"message": "Enable Invidious support, disable autoskip, hide buttons and more."
|
||||
},
|
||||
"addInvidiousInstance": {
|
||||
"message": "Add Invidious Instance"
|
||||
},
|
||||
"addInvidiousInstanceDescription": {
|
||||
"message": "Add a custom instance of Invidious. This must be formatted with JUST the domain. Example: invidious.ajay.app"
|
||||
},
|
||||
"add": {
|
||||
"message": "Add"
|
||||
},
|
||||
"addInvidiousInstanceError": {
|
||||
"message": "This is an invalid domain. This should JUST include the domain part. Example: invidious.ajay.app"
|
||||
},
|
||||
"resetInvidiousInstance": {
|
||||
"message": "Reset Invidious Instance List"
|
||||
},
|
||||
"resetInvidiousInstanceAlert": {
|
||||
"message": "You are about to reset the Invidious instance list"
|
||||
},
|
||||
"currentInstances": {
|
||||
"message": "Current Instances:"
|
||||
},
|
||||
"enableAutoUpvote": {
|
||||
"message": "Auto Upvote"
|
||||
},
|
||||
"whatAutoUpvote": {
|
||||
"message": "With this enabled, the extension will upvote all submissions you view if you do not report them. If the notice is disabled, this will not occur."
|
||||
},
|
||||
"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."
|
||||
},
|
||||
"betaName": {
|
||||
"message": "BETA - SponsorBlock"
|
||||
},
|
||||
"mobileUpdateInfo": {
|
||||
"message": "m.youtube.com is now supported"
|
||||
}
|
||||
}
|
||||
@@ -24,9 +24,6 @@
|
||||
"409": {
|
||||
"message": "Déja soumis"
|
||||
},
|
||||
"502": {
|
||||
"message": "Le serveur ne fonctionne pas. Contactez le développeur."
|
||||
},
|
||||
"channelWhitelisted": {
|
||||
"message": "Cette chaîne est sur la liste blanche !"
|
||||
},
|
||||
@@ -24,9 +24,6 @@
|
||||
"409": {
|
||||
"message": "Questo spezzone è già stato inviato"
|
||||
},
|
||||
"502": {
|
||||
"message": "Sembra che il server non funzioni. Contatta lo sviluppatore."
|
||||
},
|
||||
"channelWhitelisted": {
|
||||
"message": "Canale aggiunto alla whitelist!"
|
||||
},
|
||||
390
public/_locales/pl_PL/messages.json
Normal 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ć."
|
||||
}
|
||||
}
|
||||
@@ -24,9 +24,6 @@
|
||||
"409": {
|
||||
"message": "Isso já foi enviado antes"
|
||||
},
|
||||
"502": {
|
||||
"message": "Parece que o servidor caiu. Contate o desenvolvedor para informá-los."
|
||||
},
|
||||
"channelWhitelisted": {
|
||||
"message": "Canal adicionado à lista branca!"
|
||||
},
|
||||
@@ -24,9 +24,6 @@
|
||||
"409": {
|
||||
"message": "Isso já foi enviado antes"
|
||||
},
|
||||
"502": {
|
||||
"message": "Parece que o servidor caiu. Contacte o desenvolvedor para o informar."
|
||||
},
|
||||
"channelWhitelisted": {
|
||||
"message": "Canal adicionado à whitelist!"
|
||||
},
|
||||
@@ -24,9 +24,6 @@
|
||||
"409": {
|
||||
"message": "Этот запрос был отправлен ранее."
|
||||
},
|
||||
"502": {
|
||||
"message": "Сервер недоступен. Свяжитесь с разработчиком и сообщите ему об этом."
|
||||
},
|
||||
"channelWhitelisted": {
|
||||
"message": "Канал добавлен в белый список!"
|
||||
},
|
||||
@@ -71,7 +71,7 @@
|
||||
}
|
||||
|
||||
.sponsorSkipNotice {
|
||||
min-width: 300px;
|
||||
min-width: 350px;
|
||||
background-color: rgba(28, 28, 28, 0.9);
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
@@ -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></p>
|
||||
<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,6 +30,15 @@
|
||||
Come contribute, make some suggestions and help out in the Discord: <a href="https://discord.gg/QnmVMpU">https://discord.gg/QnmVMpU</a>
|
||||
</p>
|
||||
|
||||
<div class="center">
|
||||
<a class="bigText" href="/options/options.html">Enable optional features</a>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
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>
|
||||
|
||||
<p class="projectPreview">
|
||||
@@ -39,10 +51,10 @@
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
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.
|
||||
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>
|
||||
|
||||
@@ -74,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>
|
||||
@@ -100,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 -->
|
||||
@@ -110,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>
|
||||
|
||||
@@ -118,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>
|
||||
@@ -1,5 +1,19 @@
|
||||
:not(.hljs-keyword):not(.hljs-comment):not(.hljs-number):not(.hljs-string):not(pre):not(code) {
|
||||
.bigText {
|
||||
font-size: 50px;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #333333;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
.center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 60%;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.projectPreview {
|
||||
@@ -8,24 +22,24 @@
|
||||
|
||||
.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%);
|
||||
}
|
||||
@@ -43,37 +57,27 @@
|
||||
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;
|
||||
}
|
||||
@@ -83,13 +87,19 @@
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.profilepiccircle {
|
||||
vertical-align: middle;
|
||||
overflow: hidden;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: underline;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.link {
|
||||
padding: 20;
|
||||
padding: 20px;
|
||||
|
||||
height: 80px;
|
||||
|
||||
@@ -103,29 +113,24 @@ a {
|
||||
}
|
||||
|
||||
#contact,.smalllink {
|
||||
font-family: sans-serif;
|
||||
font-size: 25;
|
||||
font-size: 25px;
|
||||
color: #e8e8e8;
|
||||
|
||||
text-align: center;
|
||||
|
||||
padding: 10;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
#contact {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
p,li {
|
||||
font-family: sans-serif;
|
||||
font-size: 20;
|
||||
p,li,a {
|
||||
font-size: 20px;
|
||||
color: #c4c4c4;
|
||||
|
||||
padding: 10;
|
||||
}
|
||||
|
||||
p,li,code,a {
|
||||
max-width: 60%;
|
||||
text-align: left;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
@@ -137,7 +142,7 @@ p,li,code,a {
|
||||
|
||||
.projectPreviewImage {
|
||||
position: unset;
|
||||
width: 130;
|
||||
width: 130px;
|
||||
display: block;
|
||||
margin: auto;
|
||||
transform: none;
|
||||
@@ -155,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 {
|
||||
|
Before Width: | Height: | Size: 551 B After Width: | Height: | Size: 551 B |
|
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 6.7 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 9.1 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 7.1 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 6.4 KiB |
BIN
public/icons/newprofilepic.jpg
Normal file
|
After Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.6 KiB |
326
public/options/options.css
Normal file
@@ -0,0 +1,326 @@
|
||||
/* Options page CSS */
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
.center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.inline {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.keybind-status {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.small-description {
|
||||
color: white;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.medium-description {
|
||||
color: white;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.option-text-box {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.option-button {
|
||||
cursor: pointer;
|
||||
|
||||
background-color: #c00000;
|
||||
padding: 10px;
|
||||
color: white;
|
||||
border-radius: 5px;
|
||||
font-size: 14px;
|
||||
|
||||
width: max-content;
|
||||
}
|
||||
|
||||
.option-button:hover {
|
||||
background-color: #fc0303;
|
||||
}
|
||||
|
||||
.option-button.disabled {
|
||||
cursor: default;
|
||||
|
||||
background-color: #520000;
|
||||
color: grey;
|
||||
}
|
||||
|
||||
#options {
|
||||
max-width: 60%;
|
||||
text-align: left;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.switch-container:after {
|
||||
content: attr(label-name);
|
||||
position: absolute;
|
||||
padding: 4px;
|
||||
width: max-content;
|
||||
|
||||
font-size: 14px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.text-label-container {
|
||||
font-size: 14px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.switch {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 40px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.switch input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: #707070;
|
||||
}
|
||||
|
||||
.animated * {
|
||||
-webkit-transition: .4s;
|
||||
transition: .4s;
|
||||
}
|
||||
|
||||
.slider:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
left: 4px;
|
||||
bottom: 4px;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.animated .slider:before {
|
||||
-webkit-transition: .4s;
|
||||
transition: .4s;
|
||||
}
|
||||
|
||||
input:checked + .slider {
|
||||
background-color: #fc0303;
|
||||
}
|
||||
|
||||
input:checked + .slider:before {
|
||||
-webkit-transform: translateX(16px);
|
||||
-ms-transform: translateX(16px);
|
||||
transform: translateX(16px);
|
||||
}
|
||||
|
||||
/* Rounded sliders */
|
||||
.slider.round {
|
||||
border-radius: 34px;
|
||||
}
|
||||
|
||||
.slider.round:before {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
|
||||
/* Boilerplate CSS from https://ajay.app */
|
||||
|
||||
body {
|
||||
background-color: #333333;
|
||||
}
|
||||
|
||||
.projectPreview {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.projectPreviewImage {
|
||||
position: absolute;
|
||||
left: -90px;
|
||||
width: 80px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.projectPreviewImageLarge {
|
||||
position: absolute;
|
||||
left: -210px;
|
||||
width: 200px;
|
||||
top: 50%;
|
||||
transform: translateY(-20%);
|
||||
}
|
||||
|
||||
.projectPreviewImageLargeRight {
|
||||
position: absolute;
|
||||
right: -210px;
|
||||
width: 200px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.createdBy {
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
padding-top: 0px;
|
||||
padding-bottom: 0px;
|
||||
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
#title {
|
||||
background-color: #636363;
|
||||
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
|
||||
font-size: 50px;
|
||||
color: #212121;
|
||||
|
||||
padding: 20px;
|
||||
|
||||
text-decoration: none;
|
||||
|
||||
transition: font-size 1s;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 40px;
|
||||
color: #dad8d8;
|
||||
|
||||
padding-top: 10px;
|
||||
|
||||
transition: font-size 0.4s;
|
||||
}
|
||||
|
||||
.subtitle:hover {
|
||||
font-size: 45px;
|
||||
|
||||
transition: font-size 0.4s;
|
||||
}
|
||||
|
||||
.profilepic {
|
||||
background-color: #636363 !important;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.profilepiccircle {
|
||||
vertical-align: middle;
|
||||
overflow: hidden;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: underline;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.link {
|
||||
padding: 20px;
|
||||
|
||||
height: 80px;
|
||||
|
||||
transition: height 0.2s;
|
||||
}
|
||||
|
||||
.link:hover {
|
||||
height: 95px;
|
||||
|
||||
transition: height 0.2s;
|
||||
}
|
||||
|
||||
#contact,.smalllink {
|
||||
font-size: 25px;
|
||||
color: #e8e8e8;
|
||||
|
||||
text-align: center;
|
||||
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
#contact {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
p,li {
|
||||
font-size: 20px;
|
||||
color: #c4c4c4;
|
||||
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
p,li,code,a {
|
||||
max-width: 60%;
|
||||
text-align: left;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
@media screen and (orientation:portrait) {
|
||||
p,li,code,a {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.projectPreviewImage {
|
||||
position: unset;
|
||||
width: 130px;
|
||||
display: block;
|
||||
margin: auto;
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
.previewImage {
|
||||
max-height: 200px;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#recentPostTitle {
|
||||
font-size: 30px;
|
||||
color: #dad8d8;
|
||||
}
|
||||
|
||||
#recentPostDate {
|
||||
font-size: 15px;
|
||||
color: #dad8d8;
|
||||
}
|
||||
|
||||
h1,h2,h3,h4,h5,h6 {
|
||||
color: #dad8d8;
|
||||
}
|
||||
|
||||
svg {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.number-container:before {
|
||||
content: attr(label-name);
|
||||
padding-right: 4px;
|
||||
width: max-content;
|
||||
|
||||
font-size: 14px;
|
||||
color: white;
|
||||
}
|
||||
335
public/options/options.html
Normal file
@@ -0,0 +1,335 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<head>
|
||||
<title>Options - SponsorBlock</title>
|
||||
<meta charset="utf-8">
|
||||
|
||||
<link href="options.css" rel="stylesheet"/>
|
||||
|
||||
<script src="../js/vendor.js"></script>
|
||||
<script src="../js/options.js"></script>
|
||||
</head>
|
||||
|
||||
<body class="sponsorBlockPageBody">
|
||||
|
||||
<div id="title">
|
||||
<img src="../icons/LogoSponsorBlocker256px.png" height="80" class="profilepic"/>
|
||||
SponsorBlock
|
||||
</div>
|
||||
|
||||
<div class="center">
|
||||
<p class="createdBy">__MSG_createdBy__ <a href="https://ajay.app">Ajay Ramachandran</a> <img src="../icons/newprofilepic.jpg" height="30" class="profilepiccircle"/></p>
|
||||
|
||||
<h1>__MSG_Options__</h1>
|
||||
|
||||
<div id="options" class="hidden">
|
||||
|
||||
<div id="support-invidious" option-type="toggle" sync-option="supportInvidious">
|
||||
<label class="switch-container" label-name="__MSG_supportInvidious__">
|
||||
<label class="switch">
|
||||
<input type="checkbox">
|
||||
<span class="slider round"></span>
|
||||
</label>
|
||||
</label>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<div class="small-description">__MSG_supportInvidiousDescription__</div>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<div option-type="private-text-change" sync-option="invidiousInstances">
|
||||
<div class="option-button trigger-button">
|
||||
__MSG_addInvidiousInstance__
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
|
||||
<div class="small-description">__MSG_addInvidiousInstanceDescription__</div>
|
||||
|
||||
<div class="option-hidden-section hidden">
|
||||
<br/>
|
||||
|
||||
<input class="option-text-box" type="text">
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<div class="option-button text-change-set inline">
|
||||
__MSG_add__
|
||||
</div>
|
||||
|
||||
<div class="option-button invidious-instance-reset inline">
|
||||
__MSG_resetInvidiousInstance__
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<span class="small-description">__MSG_currentInstances__</span>
|
||||
<span class="small-description" option-type="display" sync-option="invidiousInstances"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<div option-type="toggle" toggle-type="reverse" sync-option="disableAutoSkip">
|
||||
<label class="switch-container" label-name="__MSG_autoSkip__">
|
||||
<label class="switch">
|
||||
<input type="checkbox" checked>
|
||||
<span class="slider round"></span>
|
||||
</label>
|
||||
</label>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<div class="small-description">__MSG_autoSkipDescription__</div>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
|
||||
<div option-type="keybind-change" sync-option="startSponsorKeybind">
|
||||
<div class="option-button trigger-button">
|
||||
__MSG_setStartSponsorShortcut__
|
||||
</div>
|
||||
|
||||
<div class="option-hidden-section hidden">
|
||||
<br/>
|
||||
|
||||
<div class="medium-description keybind-status">
|
||||
__MSG_keybindDescription__
|
||||
</div>
|
||||
|
||||
<span class="medium-description bold keybind-status-key">
|
||||
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<div option-type="keybind-change" sync-option="submitKeybind">
|
||||
<div class="option-button trigger-button">
|
||||
__MSG_setSubmitKeybind__
|
||||
</div>
|
||||
|
||||
<div class="option-hidden-section hidden">
|
||||
<br/>
|
||||
|
||||
<div class="medium-description keybind-status">
|
||||
__MSG_keybindDescription__
|
||||
</div>
|
||||
|
||||
<span class="medium-description bold keybind-status-key">
|
||||
|
||||
</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/>
|
||||
|
||||
<div option-type="toggle" toggle-type="reverse" sync-option="hideVideoPlayerControls">
|
||||
<label class="switch-container" label-name="__MSG_showButtons__">
|
||||
<label class="switch">
|
||||
<input type="checkbox" checked>
|
||||
<span class="slider round"></span>
|
||||
</label>
|
||||
</label>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<div class="small-description">__MSG_hideButtonsDescription__</div>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<div option-type="toggle" toggle-type="reverse" sync-option="hideInfoButtonPlayerControls">
|
||||
<label class="switch-container" label-name="__MSG_showInfoButton__">
|
||||
<label class="switch">
|
||||
<input type="checkbox" checked>
|
||||
<span class="slider round"></span>
|
||||
</label>
|
||||
</label>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<div class="small-description">__MSG_whatInfoButton__</div>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<div option-type="toggle" toggle-type="reverse" sync-option="hideDeleteButtonPlayerControls">
|
||||
<label class="switch-container" label-name="__MSG_showDeleteButton__">
|
||||
<label class="switch">
|
||||
<input type="checkbox" checked>
|
||||
<span class="slider round"></span>
|
||||
</label>
|
||||
</label>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<div class="small-description">__MSG_whatDeleteButton__</div>
|
||||
</div>
|
||||
|
||||
<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">
|
||||
<input type="checkbox" checked>
|
||||
<span class="slider round"></span>
|
||||
</label>
|
||||
</label>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<div class="small-description">__MSG_whatAutoUpvote__</div>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<div option-type="toggle" sync-option="trackViewCount">
|
||||
<label class="switch-container" label-name="__MSG_enableViewTracking__">
|
||||
<label class="switch">
|
||||
<input type="checkbox" checked>
|
||||
<span class="slider round"></span>
|
||||
</label>
|
||||
</label>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<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="private-text-change" sync-option="userID" confirm-message="userIDChangeWarning">
|
||||
<div class="option-button trigger-button">
|
||||
__MSG_changeUserID__
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
|
||||
<div class="small-description">__MSG_whatChangeUserID__</div>
|
||||
|
||||
<div class="option-hidden-section hidden">
|
||||
<br/>
|
||||
|
||||
<input class="option-text-box" type="text">
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<div class="option-button text-change-set">
|
||||
__MSG_setUserID__
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<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>
|
||||
|
||||
</body>
|
||||
214
public/popup.html
Normal file
@@ -0,0 +1,214 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>__MSG_openPopup__</title>
|
||||
<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">
|
||||
<center>
|
||||
<div id="app" class="popupBody sponsorBlockPageBody">
|
||||
<h1 class="popupElement">
|
||||
<img src="icons/IconSponsorBlocker256px.png" height="32px" id="sponsorBlockPopupLogo"/>
|
||||
__MSG_Name__
|
||||
</h1>
|
||||
|
||||
<!-- Loading text -->
|
||||
<p id="loadingIndicator" class="popupElement">__MSG_noVideoID__</p>
|
||||
|
||||
<!-- Hidden until loading complete -->
|
||||
<div id="mainControls" class="main popupElement" style="display: none">
|
||||
<!-- If the video was found in the database -->
|
||||
<div id="videoFound">
|
||||
|
||||
</div>
|
||||
|
||||
<div id="downloadedSponsorMessageTimes" class="popupElement">
|
||||
|
||||
</div>
|
||||
|
||||
<p class="popupElement">
|
||||
__MSG_recordTimesDescription__
|
||||
</p>
|
||||
|
||||
<div>
|
||||
<button id="sponsorStart" class="greenButton popupElement">__MSG_sponsorStart__</button>
|
||||
</div>
|
||||
|
||||
<sub class="popupElement">__MSG_popupHint__</sub>
|
||||
|
||||
<div id="submissionSection" class="popupElement" style="display: none">
|
||||
<h3 class="popupElement">__MSG_lastTimes__</h3>
|
||||
<b>
|
||||
<div id="sponsorMessageTimes" class="popupElement">
|
||||
|
||||
</div>
|
||||
</b>
|
||||
|
||||
<br/>
|
||||
|
||||
<button id="clearTimes" class="smallButton popupElement">__MSG_clearTimesButton__</button>
|
||||
|
||||
<div id="submitTimesContainer" class="popupElement" style="display: none">
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<button id="submitTimes" class="smallButton popupElement">__MSG_submitTimesButton__</button>
|
||||
|
||||
<div id="submitTimesInfoMessageContainer" class="popupElement" style="display: none">
|
||||
<h3 id="submitTimesInfoMessage" class="popupElement">
|
||||
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="discordButtonContainer" class="popupElement" style="display: none">
|
||||
<br/>
|
||||
|
||||
<a href="https://discord.gg/QnmVMpU" class="popupElement" target="_blank"><img src="https://www.logolynx.com/images/logolynx/1b/1bcc0f0aefe71b2c8ce66ffe8645d365.png" height="32px"/></a>
|
||||
|
||||
<br/>
|
||||
|
||||
__MSG_discordAdvert__
|
||||
|
||||
<br/>
|
||||
|
||||
<span id="hideDiscordButton" class="smallLink popupElement">__MSG_hideThis__</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<br/>
|
||||
|
||||
<button id="whitelistChannel" class="whitelistButton popupElement">__MSG_whitelistChannel__</button>
|
||||
<button id="unwhitelistChannel" class="whitelistButton popupElement" style="display: none">__MSG_removeFromWhitelist__</button>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
|
||||
<button id="reportAnIssue" class="dangerButton popupElement">__MSG_voteOnTime__</button>
|
||||
|
||||
<div id="issueReporterContainer" class="popupElement" style="display: none">
|
||||
|
||||
<h3 style="margin-top: 0px" class="popupElement">__MSG_voteOnTime__</h3>
|
||||
|
||||
<div id="issueReporterTimeButtons" class="popupElement">
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button id="disableSkipping" class="greenButton popupElement">__MSG_disableSkipping__</button>
|
||||
<button id="enableSkipping" class="whitelistButton popupElement" style="display: none">__MSG_enableSkipping__</button>
|
||||
</div>
|
||||
|
||||
<h2 class="recordingSubtitle popupElement">__MSG_yourWork__</h2>
|
||||
|
||||
<p class="popupElement">
|
||||
<span id="sponsorTimesContributionsContainer" class="popupElement" style="display: none">
|
||||
__MSG_soFarUHSubmited__
|
||||
<span id="sponsorTimesContributionsDisplay" class="popupElement">
|
||||
0
|
||||
</span>
|
||||
<span id="sponsorTimesContributionsDisplayEndWord" class="popupElement">__MSG_Sponsors__</span>.
|
||||
</span>
|
||||
|
||||
<span id="sponsorTimesViewsContainer" class="popupElement" style="display: none">
|
||||
__MSG_savedPeopleFrom__
|
||||
<span id="sponsorTimesViewsDisplay" class="popupElement">
|
||||
0
|
||||
</span>
|
||||
<span id="sponsorTimesViewsDisplayEndWord" class="popupElement">__MSG_Segments__</span>.
|
||||
</span>
|
||||
|
||||
<span id="sponsorTimesOthersTimeSavedContainer" class="popupElement" style="display: none">
|
||||
__MSG_youHaveSavedTime__
|
||||
<span id="sponsorTimesOthersTimeSavedDisplay" class="popupElement">
|
||||
0
|
||||
</span>
|
||||
<span id="sponsorTimesOthersTimeSavedEndWord" class="popupElement">__MSG_minsLower__</span>
|
||||
|
||||
<span class="popupElement">__MSG_youHaveSavedTimeEnd__</span>
|
||||
</span>
|
||||
|
||||
<div id="sponsorTimesSkipsDoneContainer" class="popupElement" style="display: none">
|
||||
__MSG_youHaveSkipped__
|
||||
<span id="sponsorTimesSkipsDoneDisplay" class="popupElement">
|
||||
0
|
||||
</span>
|
||||
<span id="sponsorTimesSkipsDoneEndWord" class="popupElement">__MSG_Segments__</span> (since February).
|
||||
</div>
|
||||
|
||||
<div id="sponsorTimeSavedContainer" class="popupElement" style="display: none">
|
||||
__MSG_youHaveSaved__
|
||||
<span id="sponsorTimeSavedDisplay" class="popupElement">
|
||||
0
|
||||
</span>
|
||||
<span id="sponsorTimeSavedEndWord" class="popupElement">__MSG_minsLower__</span>.
|
||||
|
||||
</br/>
|
||||
</br/>
|
||||
</div>
|
||||
|
||||
<div class="popupElement">
|
||||
__MSG_viewLeaderboard__ <a class="popupElement discreteLink" href="https://sponsor.ajay.app/stats" target="_blank">__MSG_here__</a>.
|
||||
</div>
|
||||
</p>
|
||||
|
||||
<div id="setUsernameContainer" class="popupElement">
|
||||
|
||||
<button id="setUsernameButton" class="warningButton popupElement">__MSG_setUsername__</button>
|
||||
<br/>
|
||||
<sub class="popupElement">
|
||||
__MSG_publicStats__ <a class="popupElement discreteLink" href="https://sponsor.ajay.app/stats" target="_blank">__MSG_here__</a>.
|
||||
</sub>
|
||||
</div>
|
||||
|
||||
<div id="setUsername" class="popupElement" style="display: none">
|
||||
<h3>__MSG_setUsername__</h3>
|
||||
|
||||
<div id="setUsernameStatusContainer" style="display: none">
|
||||
<h2 id="setUsernameStatus"></h2>
|
||||
</div>
|
||||
|
||||
<input id="usernameInput" hint="Username"></input>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<button id="submitUsername" class="warningButton popupElement">__MSG_setUsername__</button>
|
||||
</div>
|
||||
|
||||
<div id="optionsButtonContainer" class="popupElement">
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<button id="optionsButton" class="dangerButton popupElement">__MSG_Options__</button>
|
||||
|
||||
<br/>
|
||||
|
||||
<sub class="popupElement">
|
||||
__MSG_optionsInfo__
|
||||
</sub>
|
||||
|
||||
<br/>
|
||||
</div>
|
||||
|
||||
<button id="showNoticeAgain" style="display: none" class="dangerButton popupElement">__MSG_showNotice__</button>
|
||||
</div>
|
||||
</center>
|
||||
</body>
|
||||
|
||||
<!-- Scripts that need to load after the html -->
|
||||
<script src="./js/vendor.js"></script>
|
||||
<script src="./js/popup.js"></script>
|
||||
</html>
|
||||
240
src/background.ts
Normal file
@@ -0,0 +1,240 @@
|
||||
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 (utils.isFirefox()) {
|
||||
utils.wait(() => Config.config !== null).then(function() {
|
||||
if (Config.config.supportInvidious) utils.setupExtraSiteContentScripts();
|
||||
});
|
||||
}
|
||||
|
||||
chrome.tabs.onUpdated.addListener(function(tabId) {
|
||||
chrome.tabs.sendMessage(tabId, {
|
||||
message: 'update',
|
||||
}, () => void chrome.runtime.lastError ); // Suppress error on Firefox
|
||||
});
|
||||
|
||||
chrome.runtime.onMessage.addListener(function (request, sender, callback) {
|
||||
switch(request.message) {
|
||||
case "openConfig":
|
||||
chrome.runtime.openOptionsPage();
|
||||
return
|
||||
case "submitTimes":
|
||||
submitTimes(request.videoID, callback);
|
||||
|
||||
//this allows the callback to be called later by the submitTimes function
|
||||
return true;
|
||||
case "addSponsorTime":
|
||||
addSponsorTime(request.time, request.videoID, callback);
|
||||
|
||||
//this allows the callback to be called later
|
||||
return true;
|
||||
|
||||
case "getSponsorTimes":
|
||||
getSponsorTimes(request.videoID, function(sponsorTimes) {
|
||||
callback({
|
||||
sponsorTimes
|
||||
});
|
||||
});
|
||||
|
||||
//this allows the callback to be called later
|
||||
return true;
|
||||
case "submitVote":
|
||||
submitVote(request.type, request.UUID, callback);
|
||||
|
||||
//this allows the callback to be called later
|
||||
return true;
|
||||
case "alertPrevious":
|
||||
chrome.notifications.create("stillThere" + Math.random(), {
|
||||
type: "basic",
|
||||
title: chrome.i18n.getMessage("wantToSubmit") + " " + request.previousVideoID + "?",
|
||||
message: chrome.i18n.getMessage("leftTimes"),
|
||||
iconUrl: "./icons/LogoSponsorBlocker256px.png"
|
||||
});
|
||||
case "registerContentScript":
|
||||
registerFirefoxContentScript(request);
|
||||
return false;
|
||||
case "unregisterContentScript":
|
||||
unregisterFirefoxContentScript(request.id)
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
//add help page on install
|
||||
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 = Config.config.userID;
|
||||
|
||||
// If there is no userID, then it is the first install.
|
||||
if (!userID){
|
||||
//open up the install page
|
||||
chrome.tabs.create({url: chrome.extension.getURL("/help/index_en.html")});
|
||||
|
||||
//generate a userID
|
||||
const newUserID = utils.generateUserID();
|
||||
//save this UUID
|
||||
Config.config.userID = newUserID;
|
||||
|
||||
//TODO: Remove when mobile support is old
|
||||
// Don't show this to new users
|
||||
// Config.config.mobileUpdateShowCount = 1;
|
||||
}
|
||||
}, 1500);
|
||||
});
|
||||
|
||||
/**
|
||||
* Only works on Firefox.
|
||||
* Firefox requires that it be applied after every extension restart.
|
||||
*
|
||||
* @param {JSON} options
|
||||
*/
|
||||
function registerFirefoxContentScript(options) {
|
||||
let oldRegistration = contentScriptRegistrations[options.id];
|
||||
if (oldRegistration) oldRegistration.unregister();
|
||||
|
||||
browser.contentScripts.register({
|
||||
allFrames: options.allFrames,
|
||||
js: options.js,
|
||||
css: options.css,
|
||||
matches: options.matches
|
||||
}).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 = Config.config.sponsorTimes.get(videoID);
|
||||
|
||||
if (sponsorTimesStorage != undefined && sponsorTimesStorage.length > 0) {
|
||||
sponsorTimes = sponsorTimesStorage;
|
||||
}
|
||||
|
||||
callback(sponsorTimes);
|
||||
}
|
||||
|
||||
function addSponsorTime(time, videoID, callback) {
|
||||
getSponsorTimes(videoID, function(sponsorTimes) {
|
||||
//add to sponsorTimes
|
||||
if (sponsorTimes.length > 0 && sponsorTimes[sponsorTimes.length - 1].length < 2) {
|
||||
//it is an end time
|
||||
sponsorTimes[sponsorTimes.length - 1][1] = time;
|
||||
} else {
|
||||
//it is a start time
|
||||
let sponsorTimesIndex = sponsorTimes.length;
|
||||
sponsorTimes[sponsorTimesIndex] = [];
|
||||
|
||||
sponsorTimes[sponsorTimesIndex][0] = time;
|
||||
}
|
||||
|
||||
//save this info
|
||||
Config.config.sponsorTimes.set(videoID, sponsorTimes);
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
function submitVote(type, UUID, callback) {
|
||||
let userID = Config.config.userID;
|
||||
|
||||
if (userID == undefined || userID === "undefined") {
|
||||
//generate one
|
||||
userID = utils.generateUserID();
|
||||
Config.config.userID = userID;
|
||||
}
|
||||
|
||||
//publish this vote
|
||||
utils.sendRequestToServer("POST", "/api/voteOnSponsorTime?UUID=" + UUID + "&userID=" + userID + "&type=" + type, function(xmlhttp, error) {
|
||||
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
|
||||
callback({
|
||||
successType: 1
|
||||
});
|
||||
} else if (xmlhttp.readyState == 4 && xmlhttp.status == 405) {
|
||||
//duplicate vote
|
||||
callback({
|
||||
successType: 0,
|
||||
statusCode: xmlhttp.status
|
||||
});
|
||||
} else if (error) {
|
||||
//error while connect
|
||||
callback({
|
||||
successType: -1,
|
||||
statusCode: xmlhttp.status
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
async function submitTimes(videoID, callback) {
|
||||
//get the video times from storage
|
||||
let sponsorTimes = Config.config.sponsorTimes.get(videoID);
|
||||
let userID = Config.config.userID;
|
||||
|
||||
if (sponsorTimes != undefined && sponsorTimes.length > 0) {
|
||||
let durationResult = <Types.videoDurationResponse> await new Promise((resolve, reject) => {
|
||||
chrome.tabs.query({
|
||||
active: true,
|
||||
currentWindow: true
|
||||
}, function(tabs) {
|
||||
chrome.tabs.sendMessage(tabs[0].id, {
|
||||
message: "getVideoDuration"
|
||||
}, (response) => resolve(response));
|
||||
});
|
||||
});
|
||||
|
||||
//check if a sponsor exceeds the duration of the video
|
||||
for (let i = 0; i < sponsorTimes.length; i++) {
|
||||
if (sponsorTimes[i][1] > durationResult.duration) {
|
||||
sponsorTimes[i][1] = durationResult.duration;
|
||||
}
|
||||
}
|
||||
|
||||
//submit these times
|
||||
for (let i = 0; i < sponsorTimes.length; i++) {
|
||||
//to prevent it from happeneing twice
|
||||
let increasedContributionAmount = false;
|
||||
|
||||
//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) {
|
||||
//save the amount contributed
|
||||
if (!increasedContributionAmount) {
|
||||
increasedContributionAmount = true;
|
||||
Config.config.sponsorTimesContributed = Config.config.sponsorTimesContributed + sponsorTimes.length;
|
||||
}
|
||||
} else if (error) {
|
||||
callback({
|
||||
statusCode: -1
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
253
src/config.ts
Normal 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[],
|
||||
autoUpvote: boolean,
|
||||
supportInvidious: boolean,
|
||||
serverAddress: string,
|
||||
minDuration: number,
|
||||
checkForUnlistedVideos: boolean,
|
||||
mobileUpdateShowCount: number
|
||||
}
|
||||
|
||||
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"],
|
||||
autoUpvote: true,
|
||||
supportInvidious: false,
|
||||
serverAddress: CompileConfig.serverAddress,
|
||||
minDuration: 0,
|
||||
checkForUnlistedVideos: false,
|
||||
mobileUpdateShowCount: 0
|
||||
},
|
||||
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;
|
||||
1420
src/content.ts
Normal file
@@ -21,25 +21,39 @@ let barTypes = {
|
||||
};
|
||||
|
||||
class PreviewBar {
|
||||
constructor(parent) {
|
||||
container: HTMLUListElement;
|
||||
parent: any;
|
||||
onMobileYouTube: boolean;
|
||||
|
||||
constructor(parent, onMobileYouTube) {
|
||||
this.container = document.createElement('ul');
|
||||
this.container.id = 'previewbar';
|
||||
this.parent = parent;
|
||||
this.bars = []
|
||||
|
||||
this.updatePosition();
|
||||
this.onMobileYouTube = onMobileYouTube;
|
||||
|
||||
this.updatePosition(parent);
|
||||
}
|
||||
|
||||
updatePosition() {
|
||||
updatePosition(parent) {
|
||||
//below the seek bar
|
||||
// this.parent.insertAdjacentElement("afterEnd", this.container);
|
||||
|
||||
this.parent = parent;
|
||||
|
||||
if (this.onMobileYouTube) {
|
||||
parent.style.backgroundColor = "rgba(255, 255, 255, 0.3)";
|
||||
parent.style.opacity = "1";
|
||||
|
||||
this.container.style.transform = "none";
|
||||
}
|
||||
|
||||
//on the seek bar
|
||||
this.parent.insertAdjacentElement("afterBegin", this.container);
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -59,6 +73,8 @@ class PreviewBar {
|
||||
duration = Math.floor(duration * 100) / 100;
|
||||
let width;
|
||||
for (let i = 0; i < timestamps.length; i++) {
|
||||
if (types[i] == null) continue;
|
||||
|
||||
width = (timestamps[i][1] - timestamps[i][0]) / duration * 100;
|
||||
width = Math.floor(width * 100) / 100;
|
||||
|
||||
@@ -66,13 +82,12 @@ class PreviewBar {
|
||||
bar.setAttribute('data-vs-segment-type', types[i]);
|
||||
|
||||
bar.style.backgroundColor = barTypes[types[i]].color;
|
||||
bar.style.opacity = barTypes[types[i]].opacity;
|
||||
if (!this.onMobileYouTube) bar.style.opacity = barTypes[types[i]].opacity;
|
||||
bar.style.width = width + '%';
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,3 +103,5 @@ class PreviewBar {
|
||||
this.container = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export default PreviewBar;
|
||||
@@ -1,16 +1,38 @@
|
||||
'use strict';
|
||||
|
||||
//The notice that tells the user that a sponsor was just skipped
|
||||
/**
|
||||
* The notice that tells the user that a sponsor was just skipped
|
||||
*/
|
||||
class SkipNotice {
|
||||
constructor(parent, UUID) {
|
||||
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");
|
||||
|
||||
if (manualSkip) {
|
||||
noticeTitle = chrome.i18n.getMessage("noticeTitleNotSkipped");
|
||||
}
|
||||
|
||||
this.maxCountdownTime = () => 4;
|
||||
//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);
|
||||
@@ -33,7 +55,11 @@ 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);
|
||||
if (contentContainer().onMobileYouTube) {
|
||||
noticeElement.style.bottom = "4em";
|
||||
noticeElement.style.transform = "scale(0.8) translate(10%, 10%)";
|
||||
}
|
||||
|
||||
//add mouse enter and leave listeners
|
||||
noticeElement.addEventListener("mouseenter", this.pauseCountdown.bind(this));
|
||||
@@ -54,7 +80,7 @@ class SkipNotice {
|
||||
noticeMessage.id = "sponsorSkipMessage" + this.idSuffix;
|
||||
noticeMessage.classList.add("sponsorSkipMessage");
|
||||
noticeMessage.classList.add("sponsorSkipObject");
|
||||
noticeMessage.innerText = chrome.i18n.getMessage("noticeTitle");
|
||||
noticeMessage.innerText = noticeTitle;
|
||||
|
||||
//create the first column
|
||||
logoColumn.appendChild(logoElement);
|
||||
@@ -106,7 +132,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
|
||||
@@ -134,9 +160,12 @@ 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) {
|
||||
dontshowContainer.appendChild(dontShowAgainButton);
|
||||
}
|
||||
|
||||
//add to row
|
||||
secondRow.appendChild(voteButtonsContainer);
|
||||
@@ -148,16 +177,17 @@ class SkipNotice {
|
||||
noticeElement.appendChild(secondRow);
|
||||
|
||||
//get reference node
|
||||
let referenceNode = document.getElementById("movie_player");
|
||||
let referenceNode = document.getElementById("player-container-id")
|
||||
|| document.getElementById("movie_player") || document.querySelector("#player-container .video-js");
|
||||
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++;
|
||||
}
|
||||
@@ -165,6 +195,10 @@ class SkipNotice {
|
||||
|
||||
referenceNode.prepend(noticeElement);
|
||||
|
||||
if (manualSkip) {
|
||||
this.unskippedMode(chrome.i18n.getMessage("skip"));
|
||||
}
|
||||
|
||||
this.startCountdown();
|
||||
}
|
||||
|
||||
@@ -195,7 +229,7 @@ class SkipNotice {
|
||||
pauseCountdown() {
|
||||
//remove setInterval
|
||||
clearInterval(this.countdownInterval);
|
||||
this.countdownInterval = -1;
|
||||
this.countdownInterval = null;
|
||||
|
||||
//reset countdown
|
||||
this.countdownTime = this.maxCountdownTime();
|
||||
@@ -212,7 +246,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);
|
||||
|
||||
@@ -226,12 +260,15 @@ class SkipNotice {
|
||||
}
|
||||
|
||||
unskip() {
|
||||
unskipSponsorTime(this.UUID);
|
||||
this.contentContainer().unskipSponsorTime(this.UUID);
|
||||
|
||||
this.unskippedMode(chrome.i18n.getMessage("reskip"));
|
||||
}
|
||||
|
||||
/** Sets up notice to be not skipped yet */
|
||||
unskippedMode(buttonText) {
|
||||
//change unskip button to a reskip button
|
||||
let unskipButton = document.getElementById("sponsorSkipUnskipButton" + this.idSuffix);
|
||||
unskipButton.innerText = chrome.i18n.getMessage("reskip");
|
||||
unskipButton.removeEventListener("click", this.unskipCallback);
|
||||
let unskipButton = this.changeUnskipButton(buttonText);
|
||||
|
||||
//setup new callback
|
||||
this.unskipCallback = this.reskip.bind(this);
|
||||
@@ -239,8 +276,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);
|
||||
};
|
||||
@@ -250,12 +287,10 @@ class SkipNotice {
|
||||
}
|
||||
|
||||
reskip() {
|
||||
reskipSponsorTime(this.UUID);
|
||||
this.contentContainer().reskipSponsorTime(this.UUID);
|
||||
|
||||
//change unskip button to a reskip button
|
||||
let unskipButton = document.getElementById("sponsorSkipUnskipButton" + this.idSuffix);
|
||||
unskipButton.innerText = chrome.i18n.getMessage("unskip");
|
||||
unskipButton.removeEventListener("click", this.unskipCallback);
|
||||
//change reskip button to a unskip button
|
||||
let unskipButton = this.changeUnskipButton(chrome.i18n.getMessage("unskip"));
|
||||
|
||||
//setup new callback
|
||||
this.unskipCallback = this.unskip.bind(this);
|
||||
@@ -265,6 +300,27 @@ class SkipNotice {
|
||||
this.maxCountdownTime = () => 4;
|
||||
this.countdownTime = this.maxCountdownTime();
|
||||
this.updateTimerDisplay();
|
||||
|
||||
// See if the title should be changed
|
||||
if (this.manualSkip) {
|
||||
this.changeNoticeTitle(chrome.i18n.getMessage("noticeTitle"));
|
||||
|
||||
this.contentContainer().vote(1, this.UUID, this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the text on the reskip button
|
||||
*
|
||||
* @param {string} text
|
||||
* @returns {HTMLElement} unskipButton
|
||||
*/
|
||||
changeUnskipButton(text) {
|
||||
let unskipButton = document.getElementById("sponsorSkipUnskipButton" + this.idSuffix);
|
||||
unskipButton.innerText = text;
|
||||
unskipButton.removeEventListener("click", this.unskipCallback);
|
||||
|
||||
return unskipButton;
|
||||
}
|
||||
|
||||
afterDownvote() {
|
||||
@@ -273,28 +329,26 @@ 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);
|
||||
|
||||
let sponsorTimesLeft = sponsorTimes.slice();
|
||||
for (let j = 0; j < hiddenSponsorTimes.length; j++) {
|
||||
//remove this sponsor time
|
||||
sponsorTimesLeft.splice(hiddenSponsorTimes[j], 1);
|
||||
}
|
||||
|
||||
//update the preview
|
||||
previewBar.set(sponsorTimesLeft, [], v.duration);
|
||||
this.contentContainer().hiddenSponsorTimes.push(i);
|
||||
|
||||
this.contentContainer().updatePreviewBar();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addNoticeInfoMessage(message, message2) {
|
||||
changeNoticeTitle(title) {
|
||||
let noticeElement = document.getElementById("sponsorSkipMessage" + this.idSuffix);
|
||||
|
||||
noticeElement.innerText = title;
|
||||
}
|
||||
|
||||
addNoticeInfoMessage(message: string, message2: string = "") {
|
||||
let previousInfoMessage = document.getElementById("sponsorTimesInfoMessage" + this.idSuffix);
|
||||
if (previousInfoMessage != null) {
|
||||
//remove it
|
||||
@@ -380,7 +434,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
@@ -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;
|
||||
}
|
||||
7
src/types.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
interface videoDurationResponse {
|
||||
duration: number;
|
||||
}
|
||||
|
||||
export {
|
||||
videoDurationResponse
|
||||
};
|
||||
267
src/utils.ts
Normal 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
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es6",
|
||||
"noImplicitAny": false,
|
||||
"sourceMap": false,
|
||||
"outDir": "dist/js",
|
||||
"noEmitOnError": true,
|
||||
"typeRoots": [ "node_modules/@types" ],
|
||||
"resolveJsonModule": true
|
||||
}
|
||||
}
|
||||
68
utils.js
@@ -1,68 +0,0 @@
|
||||
// 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) {
|
||||
//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(!["www.youtube.com","www.youtube-nocookie.com"].includes(urlObject.host)) return false;
|
||||
|
||||
//Get ID from searchParam
|
||||
if ((urlObject.pathname == "/watch" || urlObject.pathname == "/watch/") && urlObject.searchParams.has("v")) {
|
||||
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 localizeHtmlPage() {
|
||||
//Localize by replacing __MSG_***__ meta tags
|
||||
var objects = document.getElementsByClassName("popupBody")[0].children;
|
||||
for (var j = 0; j < objects.length; j++) {
|
||||
var obj = objects[j];
|
||||
|
||||
var valStrH = obj.innerHTML.toString();
|
||||
var valNewH = valStrH.replace(/__MSG_(\w+)__/g, function(match, v1)
|
||||
{
|
||||
return v1 ? chrome.i18n.getMessage(v1) : "";
|
||||
});
|
||||
|
||||
if(valNewH != valStrH)
|
||||
{
|
||||
obj.innerHTML = valNewH;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
49
webpack/webpack.common.js
Normal file
@@ -0,0 +1,49 @@
|
||||
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",
|
||||
stream: env.stream
|
||||
})
|
||||
]
|
||||
});
|
||||
7
webpack/webpack.dev.js
Normal 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'
|
||||
});
|
||||
75
webpack/webpack.manifest.js
Normal file
@@ -0,0 +1,75 @@
|
||||
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");
|
||||
const betaManifestExtra = require("../manifest/beta-manifest-extra.json");
|
||||
|
||||
// schema for options object
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
browser: {
|
||||
type: 'string'
|
||||
},
|
||||
pretty: {
|
||||
type: 'boolean'
|
||||
},
|
||||
steam: {
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
if (this.options.stream === "beta") {
|
||||
mergeObjects(manifest, betaManifestExtra);
|
||||
}
|
||||
|
||||
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
@@ -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
|
||||
});
|
||||
};
|
||||