Compare commits

...

150 Commits
5.6 ... 5.11.4

Author SHA1 Message Date
Ajay
6965279d36 bump version 2025-01-22 14:17:15 -05:00
Ajay
eb61ad76c1 Add extension name to storage error message 2025-01-22 14:17:04 -05:00
Ajay
7a1fa601da bump version 2025-01-21 23:43:11 -05:00
Ajay
945ac5aad3 Fix local storage error appearing unnecessarily
Also adds more details to the log message
2025-01-21 23:42:58 -05:00
Ajay
617f0e25e8 bump version 2025-01-20 02:28:35 -05:00
Ajay
ac52a1cb1d update translations 2025-01-20 02:28:23 -05:00
Ajay
2cefb9822b Make copy button in popup use full segment ID 2025-01-18 03:13:40 -05:00
Ajay
b81de96277 Add support for partial segment IDs 2025-01-18 03:10:14 -05:00
Ajay
236c95d2f6 Only do pre-fetch on hover if there are segments near the start of the video 2025-01-18 00:25:33 -05:00
Ajay
0f75953ad1 bump version 2025-01-17 04:35:52 -05:00
Ajay
0f4c1c50cb Add delay to fetch on hover to make it less easy to trigger 2025-01-17 04:33:56 -05:00
Ajay
9e4939e2e3 Fix options page layout issue 2025-01-14 15:08:01 -05:00
Ajay
4a217300a2 bump version 2025-01-12 20:03:33 -05:00
Ajay
f4822fa587 update translations 2025-01-12 20:03:21 -05:00
Ajay
e4d6fdbd97 New filler wiki page 2025-01-11 15:33:04 -05:00
Ajay
1bd72e4db1 filler is gone🦀, long live tangents 2025-01-11 15:24:23 -05:00
Ajay
307eadc2da Fix compilation error 2025-01-10 13:45:30 -05:00
Ajay
99014dd113 Handle when local storage gets reset
Might fix the age old issue where the entire extension randomly becomes corrupted
2025-01-10 03:42:36 -05:00
Ajay
89b7cb0230 Fix audio glitch when chapter boundary passes
Fixes #1596
2025-01-10 03:29:04 -05:00
Ajay
e3da6c8643 bump version 2024-12-25 23:36:48 -05:00
Ajay
136f5e7d61 Fix preview bar on mobile for videos without chapters 2024-12-25 23:36:33 -05:00
Ajay
16f24978ff Fix offset with chapters on hover preview 2024-12-24 15:20:16 -05:00
Ajay
091d67e6b3 bump version 2024-12-24 14:34:49 -05:00
Ajay
fcf9bdc68e Fix linting issues 2024-12-24 14:34:19 -05:00
Ajay
af73aabbae Fix errors 2024-12-24 14:33:28 -05:00
Ajay
778b433ad4 Remove unused auto refetch segments option 2024-12-24 14:31:17 -05:00
Ajay
dc9c08f1fa Fix chapter titles still showing on hover after they've been removed from the seek bar 2024-12-24 14:29:07 -05:00
Ajay
4efa8b464e Fix capitalization 2024-12-14 18:30:13 -05:00
Ajay
e7d0180e15 Change options page to sentence case 2024-12-14 16:36:02 -05:00
Ajay
0fa24dbae4 bump version 2024-12-13 03:10:54 -05:00
Ajay
975b876a74 Fix preview bar on mobile
Fixes #2172
2024-12-13 03:10:17 -05:00
Ajay
557e2ea2df Fix full size manual skip notices showing "unskip" button instead of "skip"
Fixes #2160
2024-12-11 00:50:55 -05:00
Ajay
11f576eb69 bump version 2024-12-10 00:00:39 -05:00
Ajay
2a9e036986 update translations 2024-12-10 00:00:30 -05:00
Ajay
15976777ed Add option to hide autogenerated chapters by YouTube 2024-12-08 20:02:32 -05:00
Ajay
9ad636fdad Make chapter similarity check more strict 2024-12-08 14:50:40 -05:00
Ajay
6bfc66740e Hide video uploader chapters if there is a sponsorblock chapter to replace it at almost the same time 2024-12-08 14:48:32 -05:00
Ajay
7e824df73d Add support for vorapis v3
Based on patch by @ten4dinosaur
2024-12-08 02:56:10 -05:00
Ajay
62c3bc1c7a Don't show server-side ad error if someone seeks the video before check occurred 2024-12-07 02:58:56 -05:00
Ajay Ramachandran
266508ca2d Merge pull request #2162 from strafe/master
Respect iOS/macOS accent color by using a monochrome toolbar icon for…
2024-11-29 00:02:50 -05:00
strafe
19ccb5e9e9 Respect iOS/macOS accent color by using a monochrome toolbar icon for Safari 2024-11-29 00:40:40 +00:00
Ajay
46b8c41db7 Wait for skip button control bar to be setup before using it 2024-11-28 10:20:42 -05:00
Ajay
4e5ddd07c9 update translations 2024-11-27 23:55:17 -05:00
Ajay
2291658371 bump version 2024-11-27 23:54:36 -05:00
Ajay
cfa8eee193 Fix full video label sometimes not appearing when you open a video in a background tab 2024-11-27 23:54:09 -05:00
Ajay
14f8ee565a Handle video not existing when playback rate being determined 2024-11-27 14:00:51 -05:00
Ajay
b28c72bc52 Show only first segment category in upcoming notice 2024-11-27 13:55:13 -05:00
Ajay
b98300596c Fix inaccurate timer on upcoming notice 2024-11-27 13:31:39 -05:00
Ajay
38e6d5469f Fix chapters and unsubmitted segments triggering upcoming notice 2024-11-27 12:56:24 -05:00
Ajay
4d868055e7 Fix missing upcoming notice string 2024-11-27 12:25:28 -05:00
Ajay
086525c178 Fix release workflow 2024-11-27 00:51:16 -05:00
Ajay
32a35aa19c bump version 2024-11-27 00:43:46 -05:00
Ajay
80148804fb update translations 2024-11-27 00:42:00 -05:00
Ajay
8ce15e69fb Fix cases where skipping sometimes doesn't work
Should fix compatibility with new update to Enhancer for YouTube

Should fix #2138
2024-11-27 00:41:12 -05:00
Ajay
d3cd0962d5 Fix fade in animation for skip notice 2024-11-26 13:31:18 -05:00
Ajay Ramachandran
c1168582a9 Merge pull request #2086 from Acors24/add-upcoming-notices
add upcoming notices
2024-11-26 02:32:03 -05:00
Ajay
dbd51d026b Merge branch 'master' of https://github.com/ajayyy/SponsorBlock into pr/Acors24/2086 2024-11-26 02:29:22 -05:00
Ajay
61f1a8918c Fix upcoming notice toggles on options page 2024-11-26 02:25:52 -05:00
Ajay
3a488a9110 Fix upcoming notice behavior 2024-11-26 02:25:42 -05:00
Ajay
842f7b33b8 Add better logging for server side ads error 2024-11-10 16:04:22 -05:00
Ajay
fcc7ce8dfc Change release build order 2024-11-10 15:57:09 -05:00
Ajay
9c4946a3fe update translations 2024-11-10 15:51:30 -05:00
Ajay
7d2e4c8515 bump version 2024-11-10 15:50:52 -05:00
Ajay
3b2fe51973 Make options page more compact 2024-10-30 13:53:55 -04:00
Ajay Ramachandran
b2ac881b9b Merge pull request #2135 from calledude/master
Fix skip audio notification not working on firefox
2024-10-29 16:59:27 -04:00
Ajay
4a134d7604 Add a document body check to garbage collector 2024-10-28 13:54:12 -04:00
Ajay
ad7d3a320b Update for new related videos layout 2024-10-23 20:14:37 -04:00
calledude
0c3aa922ca Fix skip audio notification not working on firefox 2024-10-18 22:47:45 +02:00
Ajay
875ec357d9 Make gradiant display for chapter seek bar follow the same UI as YouTube 2024-10-18 05:05:03 -04:00
Ajay
510c3723ad bump version 2024-10-18 04:35:47 -04:00
Ajay
f5910407ba Simple implementation to fix seekbar rendering on gradiant layout
Fixes #2099
2024-10-18 04:27:23 -04:00
Ajay
2691dc5df5 Merge branch 'master' of https://github.com/ajayyy/SponsorBlock 2024-10-02 03:05:38 -04:00
Ajay
31ef4f39bf bump version 2024-10-02 03:05:37 -04:00
Ajay Ramachandran
4044a22e04 Merge pull request #2124 from Choromanski/feature/node-20-deprecated
Node deprecated versions
2024-10-01 23:15:22 -04:00
Ajay
3c2c140eb5 Fix preview bar on mobile
Fixes #2116
2024-10-01 23:14:12 -04:00
Ajay
5cdbc233e4 Fix time without skips on mobile 2024-10-01 23:12:56 -04:00
Ajay
86f8809de7 Fix time without skips on new YouTube design 2024-10-01 23:07:16 -04:00
Brian Choromanski
5163b3362f Missed one checkout 2024-10-01 22:46:24 -04:00
Brian Choromanski
443fb1f448 Updated peter-evans/create-pull-request to v7 2024-10-01 22:43:30 -04:00
Brian Choromanski
7cd7089980 Updated upload-artifact to v4 2024-10-01 22:34:11 -04:00
Brian Choromanski
61f5d7a902 Updated setup-node to v4 2024-10-01 22:32:28 -04:00
Brian Choromanski
6ab88e9fa0 Updated checkout to v4 2024-10-01 22:31:43 -04:00
Ajay
3bddd0405c Improve content editable check 2024-09-27 16:12:35 -04:00
Ajay
e4b05a316a bump version 2024-09-27 15:50:18 -04:00
Ajay
451ceb370b Fix keybinds not working at all 2024-09-27 15:50:10 -04:00
Ajay
ab5291702a bump version 2024-09-27 02:06:20 -04:00
Ajay
bec183d595 Fix not allowing you to type ' in live chats 2024-09-27 02:06:08 -04:00
Ajay
a2f4f34dfe bump version 2024-09-26 19:27:30 -04:00
Ajay
7ba6fee9e5 Add more room for server-side rendering check 2024-09-26 19:26:01 -04:00
Ajay
9d9713ee82 Attempt to fix issue with autoplay disabled on some browser breaking skipping
Fix #2074 and Fix #2078
2024-09-26 19:18:30 -04:00
Ajay
91556f981a Remove unnecessary log
Fix #2111
2024-09-23 16:00:17 -04:00
Ajay
9f86aa6740 bump version 2024-09-23 04:07:50 -04:00
Ajay
466573e2a4 update translations 2024-09-23 04:07:36 -04:00
Ajay
4c417b10fe Make sure old segment fetch request doesn't get used
May fix #2101

See dbf80b4929 (r147058589)
2024-09-23 04:05:20 -04:00
Ajay
36549e7898 Fix chapter box title selector 2024-09-23 03:58:11 -04:00
Ajay
0d2d5f2eb0 try to improve chapters importing 2024-09-23 03:46:19 -04:00
Ajay
e089b83797 Improve timeout waiting error 2024-09-23 03:32:33 -04:00
Ajay
dbd0d157a8 Fix preview bar not appearing on mobile due to new layout
Fixes #2109
2024-09-23 01:37:11 -04:00
Ajay
aef2b113f8 Remove legacy keybdind check 2024-09-12 19:45:35 -04:00
Ajay
e610e03e46 Make submission menu keyboard of ff not open find menu 2024-09-12 19:44:35 -04:00
Ajay
17cbd068b9 Add size limit to description for edge 2024-09-08 01:32:58 -04:00
Ajay
7064d3fe82 bump version 2024-09-08 01:30:09 -04:00
Ajay
abfe0b3f38 Fix minimum firefox version in manifest
Failing web-ext linter
2024-09-08 01:29:55 -04:00
Ajay
045607b146 Merge branch 'master' of https://github.com/ajayyy/SponsorBlock 2024-09-08 01:24:48 -04:00
Ajay
be90af7440 bump version 2024-09-08 01:24:47 -04:00
Ajay Ramachandran
0a7497be27 Merge pull request #1886 from Choromanski/master
Overriding default webpack performance config to reduce warnings
2024-09-08 01:23:19 -04:00
Ajay
fcf096c66d Fix lint issue 2024-09-08 01:21:59 -04:00
Ajay
12e8c7aad8 Add way to copy debug logs from popup 2024-09-08 01:20:25 -04:00
Ajay Ramachandran
2a2896786c Merge pull request #2095 from mchangrh/inv-ci
fully typed updown response
2024-09-07 01:00:23 -04:00
Michael C
20d538b6e9 fully typed updown response 2024-09-07 00:17:03 -04:00
Ajay Ramachandran
36a5bc105b Merge pull request #2040 from ajayyy/dependabot/npm_and_yarn/braces-3.0.3
Bump braces from 3.0.2 to 3.0.3
2024-09-06 16:32:37 -04:00
Ajay Ramachandran
e6c39c099c Merge pull request #2091 from ajayyy/ci/update_invidious_list
Update Invidious List
2024-09-06 02:48:56 -04:00
github-actions[bot]
d74ece2785 Update Invidious List 2024-09-06 06:46:40 +00:00
Ajay
e7eeee1b9d Fix invidious CI 2024-09-06 02:45:32 -04:00
Ajay Ramachandran
c0a579e84b Merge pull request #2083 from ajayyy/dependabot/npm_and_yarn/webpack-5.94.0
Bump webpack from 5.75.0 to 5.94.0
2024-09-06 02:23:51 -04:00
Ajay
e0a602eaf4 Fix compatibility with Vinegar extension on Safari
Also fixes miniplayer detection
2024-09-06 02:16:35 -04:00
dependabot[bot]
536ac01476 Bump webpack from 5.75.0 to 5.94.0
Bumps [webpack](https://github.com/webpack/webpack) from 5.75.0 to 5.94.0.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v5.75.0...v5.94.0)

---
updated-dependencies:
- dependency-name: webpack
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-06 02:53:06 +00:00
Ajay Ramachandran
18a0cf2953 Merge pull request #2089 from ajayyy/dependabot/npm_and_yarn/multi-4c1fdd2521
Bump ws and web-ext
2024-09-05 22:51:48 -04:00
Ajay Ramachandran
7ae29f967b Merge pull request #2088 from ajayyy/dependabot/npm_and_yarn/axios-1.7.7
Bump axios from 1.6.1 to 1.7.7
2024-09-05 22:51:42 -04:00
Ajay Ramachandran
2509cf9919 Merge pull request #2090 from ajayyy/dependabot/npm_and_yarn/multi-bc3fc687d2
Bump tough-cookie and web-ext
2024-09-05 22:51:34 -04:00
dependabot[bot]
e8c65fb21b Bump tough-cookie and web-ext
Bumps [tough-cookie](https://github.com/salesforce/tough-cookie) to 4.1.4 and updates ancestor dependency [web-ext](https://github.com/mozilla/web-ext). These dependencies need to be updated together.


Updates `tough-cookie` from 4.1.2 to 4.1.4
- [Release notes](https://github.com/salesforce/tough-cookie/releases)
- [Changelog](https://github.com/salesforce/tough-cookie/blob/master/CHANGELOG.md)
- [Commits](https://github.com/salesforce/tough-cookie/compare/v4.1.2...v4.1.4)

Updates `web-ext` from 7.10.0 to 8.2.0
- [Release notes](https://github.com/mozilla/web-ext/releases)
- [Commits](https://github.com/mozilla/web-ext/compare/7.10.0...8.2.0)

---
updated-dependencies:
- dependency-name: tough-cookie
  dependency-type: indirect
- dependency-name: web-ext
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-06 02:38:48 +00:00
dependabot[bot]
5d155f6c74 Bump ws and web-ext
Bumps [ws](https://github.com/websockets/ws) to 8.17.1 and updates ancestor dependency [web-ext](https://github.com/mozilla/web-ext). These dependencies need to be updated together.


Updates `ws` from 8.13.0 to 8.17.1
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/8.13.0...8.17.1)

Updates `web-ext` from 7.10.0 to 8.2.0
- [Release notes](https://github.com/mozilla/web-ext/releases)
- [Commits](https://github.com/mozilla/web-ext/compare/7.10.0...8.2.0)

---
updated-dependencies:
- dependency-name: ws
  dependency-type: indirect
- dependency-name: web-ext
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-06 02:38:40 +00:00
dependabot[bot]
dca6780979 Bump axios from 1.6.1 to 1.7.7
Bumps [axios](https://github.com/axios/axios) from 1.6.1 to 1.7.7.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.6.1...v1.7.7)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-06 02:38:38 +00:00
Ajay Ramachandran
65b7d6589a Fix web ext signing 2024-09-05 22:37:33 -04:00
Ajay
dfdc052f4b Change upcoming notice default 2024-09-03 20:04:33 -04:00
Ajay
7ca3b69965 update locales repo 2024-09-03 18:45:55 -04:00
Acors24
5577b38e2b fix upcoming notice closing 2024-09-04 00:15:17 +02:00
Acors24
ae4f850ff4 add upcoming notices 2024-09-03 23:37:42 +02:00
Ajay
dbf80b4929 Add pre-fetching on hover with a small segment data cache 2024-09-02 03:22:33 -04:00
Ajay
e181c64775 Use consistent request url for better caching 2024-09-02 02:04:32 -04:00
Ajay
ac9b2d12fe Decrease sponsor frame flashing when skipping segments at the start 2024-09-01 03:51:37 -04:00
Ajay
3738b180dd Don't play notification if video is muted
Fixes #1888
2024-07-17 13:17:50 -04:00
Ajay
3ec1e85712 Don't play audio notification when previewing 2024-07-17 13:14:52 -04:00
Ajay
e9a77355bb bump version 2024-06-23 00:33:39 +05:30
Ajay
116190f2b3 Warn if server-side ad wasn't detected and submitting a segment 2024-06-23 00:31:11 +05:30
Ajay
4bbd59b988 Support server side rendered ads
Fixes #2035
2024-06-23 00:03:03 +05:30
Ajay
5c279d80df Show submission count from server 2024-06-22 17:42:48 +05:30
Ajay
f177b95a5a Fix submission menu after video ends and unskip button after video ends 2024-06-22 17:06:29 +05:30
Ajay Ramachandran
d86dd37ec0 Merge pull request #2046 from ajayyy/mv3
Support mv3
2024-06-22 16:29:43 +05:30
Ajay
fae7d921a7 Merge branch 'master' of https://github.com/ajayyy/SponsorBlock into mv3 2024-06-22 16:27:18 +05:30
Ajay
60a2eff40a Fix linting issue 2024-06-19 23:16:29 +05:30
Ajay
98a4a076bc Support invidious in mv3 2024-06-19 23:14:27 +05:30
Ajay
a95020dda3 Manifest v3 without invidious working 2024-06-19 22:37:29 +05:30
dependabot[bot]
3c2bf20321 Bump braces from 3.0.2 to 3.0.3
Bumps [braces](https://github.com/micromatch/braces) from 3.0.2 to 3.0.3.
- [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3)

---
updated-dependencies:
- dependency-name: braces
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-18 01:45:58 +00:00
Ajay Ramachandran
5e179cf9ff Merge pull request #2036 from mini-bomba/refresh-fix
Fix refreshing
2024-06-13 09:07:57 +05:30
mini-bomba
0b946d5ef7 Don't fetch segments when videoID is null
Also log an error to the console if we ever try to do this in the future
2024-06-13 01:18:02 +02:00
mini-bomba
f48c57b1c5 Don't inject into the cookie rotate iframe
There's nothing there, it only breaks the refreshing.
2024-06-13 01:09:27 +02:00
Ajay
98ac2fc618 bump version 2024-06-06 19:53:08 +05:30
Ajay
36e3333e39 Fix submission menu disappearing when video ends 2024-06-06 19:52:57 +05:30
Brian Choromanski
2a5b76ddfb Overriding default webpack performance config to reduce warnings 2023-10-19 15:09:19 -04:00
57 changed files with 2718 additions and 2449 deletions

View File

@@ -10,10 +10,10 @@ jobs:
steps: steps:
# Initialization # Initialization
- uses: actions/checkout@v3 - uses: actions/checkout@v4
with: with:
submodules: recursive submodules: recursive
- uses: actions/setup-node@v3 - uses: actions/setup-node@v4
with: with:
node-version: '18' node-version: '18'
- run: npm ci - run: npm ci
@@ -27,7 +27,7 @@ jobs:
# Create Chrome artifacts # Create Chrome artifacts
- name: Create Chrome artifacts - name: Create Chrome artifacts
run: npm run build:chrome run: npm run build:chrome
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v4
with: with:
name: ChromeExtension name: ChromeExtension
path: dist path: dist
@@ -39,7 +39,7 @@ jobs:
# Create Firefox artifacts # Create Firefox artifacts
- name: Create Firefox artifacts - name: Create Firefox artifacts
run: npm run build:firefox run: npm run build:firefox
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v4
with: with:
name: FirefoxExtension name: FirefoxExtension
path: dist path: dist
@@ -50,7 +50,7 @@ jobs:
# Create Beta artifacts (Builds with the name changed to beta) # Create Beta artifacts (Builds with the name changed to beta)
- name: Create Chrome Beta artifacts - name: Create Chrome Beta artifacts
run: npm run build:chrome -- --env stream=beta run: npm run build:chrome -- --env stream=beta
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v4
with: with:
name: ChromeExtensionBeta name: ChromeExtensionBeta
path: dist path: dist
@@ -60,7 +60,7 @@ jobs:
- name: Create Firefox Beta artifacts - name: Create Firefox Beta artifacts
run: npm run build:firefox -- --env stream=beta run: npm run build:firefox -- --env stream=beta
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v4
with: with:
name: FirefoxExtensionBeta name: FirefoxExtensionBeta
path: dist path: dist

View File

@@ -12,10 +12,10 @@ jobs:
steps: steps:
# Initialization # Initialization
- uses: actions/checkout@v3 - uses: actions/checkout@v4
with: with:
submodules: recursive submodules: recursive
- uses: actions/setup-node@v3 - uses: actions/setup-node@v4
with: with:
node-version: '18' node-version: '18'
- name: Copy configuration - name: Copy configuration
@@ -36,23 +36,10 @@ jobs:
- run: npm ci - run: npm ci
# Create Chrome artifacts
- name: Create Chrome artifacts
run: npm run build:chrome
- run: mkdir ./builds
- name: Zip Artifacts
run: cd ./dist ; zip -r ../builds/ChromeExtension.zip *
- name: Upload ChromeExtension to release
uses: Shopify/upload-to-release@07611424e04f1475ddf550e1c0dd650b867d5467
with:
args: builds/ChromeExtension.zip
name: ChromeExtension.zip
path: ./builds/ChromeExtension.zip
repo-token: ${{ secrets.GITHUB_TOKEN }}
# Create Firefox artifacts # Create Firefox artifacts
- name: Create Firefox artifacts - name: Create Firefox artifacts
run: npm run build:firefox run: npm run build:firefox
- run: mkdir ./builds
- name: Zip Artifacts - name: Zip Artifacts
run: cd ./dist ; zip -r ../builds/FirefoxExtension.zip * run: cd ./dist ; zip -r ../builds/FirefoxExtension.zip *
- name: Upload FirefoxExtension to release - name: Upload FirefoxExtension to release
@@ -63,31 +50,17 @@ jobs:
path: ./builds/FirefoxExtension.zip path: ./builds/FirefoxExtension.zip
repo-token: ${{ secrets.GITHUB_TOKEN }} repo-token: ${{ secrets.GITHUB_TOKEN }}
# Create Beta artifacts (Builds with the name changed to beta) # Create Chrome artifacts
- name: Create Chrome Beta artifacts - name: Create Chrome artifacts
run: npm run build:chrome -- --env stream=beta run: npm run build:chrome
- name: Zip Artifacts - name: Zip Artifacts
run: cd ./dist ; zip -r ../builds/ChromeExtensionBeta.zip * run: cd ./dist ; zip -r ../builds/ChromeExtension.zip *
- name: Upload ChromeExtensionBeta to release - name: Upload ChromeExtension to release
uses: Shopify/upload-to-release@07611424e04f1475ddf550e1c0dd650b867d5467 uses: Shopify/upload-to-release@07611424e04f1475ddf550e1c0dd650b867d5467
with: with:
args: builds/ChromeExtensionBeta.zip args: builds/ChromeExtension.zip
name: ChromeExtensionBeta.zip name: ChromeExtension.zip
path: ./builds/ChromeExtensionBeta.zip path: ./builds/ChromeExtension.zip
repo-token: ${{ secrets.GITHUB_TOKEN }}
# Create Safari artifacts
- name: Create Safari artifacts
run: npm run build:safari
- name: Zip Artifacts
run: cd ./dist ; zip -r ../builds/SafariExtension.zip *
- name: Upload SafariExtension to release
uses: Shopify/upload-to-release@07611424e04f1475ddf550e1c0dd650b867d5467
with:
args: builds/SafariExtension.zip
name: SafariExtension.zip
path: ./builds/SafariExtension.zip
repo-token: ${{ secrets.GITHUB_TOKEN }} repo-token: ${{ secrets.GITHUB_TOKEN }}
# Create Edge artifacts # Create Edge artifacts
@@ -105,10 +78,36 @@ jobs:
path: ./builds/EdgeExtension.zip path: ./builds/EdgeExtension.zip
repo-token: ${{ secrets.GITHUB_TOKEN }} repo-token: ${{ secrets.GITHUB_TOKEN }}
# Create Safari artifacts
- name: Create Safari artifacts
run: npm run build:safari
- name: Zip Artifacts
run: cd ./dist ; zip -r ../builds/SafariExtension.zip *
- name: Upload SafariExtension to release
uses: Shopify/upload-to-release@07611424e04f1475ddf550e1c0dd650b867d5467
with:
args: builds/SafariExtension.zip
name: SafariExtension.zip
path: ./builds/SafariExtension.zip
repo-token: ${{ secrets.GITHUB_TOKEN }}
# Create Beta artifacts (Builds with the name changed to beta)
- name: Create Chrome Beta artifacts
run: npm run build:chrome -- --env stream=beta
- name: Zip Artifacts
run: cd ./dist ; zip -r ../builds/ChromeExtensionBeta.zip *
- name: Upload ChromeExtensionBeta to release
uses: Shopify/upload-to-release@07611424e04f1475ddf550e1c0dd650b867d5467
with:
args: builds/ChromeExtensionBeta.zip
name: ChromeExtensionBeta.zip
path: ./builds/ChromeExtensionBeta.zip
repo-token: ${{ secrets.GITHUB_TOKEN }}
# Firefox Beta # Firefox Beta
- name: Create Firefox Beta artifacts - name: Create Firefox Beta artifacts
run: npm run build:firefox -- --env stream=beta run: npm run build:firefox -- --env stream=beta
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v4
with: with:
name: FirefoxExtensionBeta name: FirefoxExtensionBeta
path: dist path: dist
@@ -125,7 +124,7 @@ jobs:
run: sudo apt-get install rename run: sudo apt-get install rename
- name: Rename signed file - name: Rename signed file
run: cd ./web-ext-artifacts ; rename 's/.*/FirefoxSignedInstaller.xpi/' * run: cd ./web-ext-artifacts ; rename 's/.*/FirefoxSignedInstaller.xpi/' *
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v4
with: with:
name: FirefoxExtensionSigned.xpi name: FirefoxExtensionSigned.xpi
path: ./web-ext-artifacts/FirefoxSignedInstaller.xpi path: ./web-ext-artifacts/FirefoxSignedInstaller.xpi

View File

@@ -9,10 +9,10 @@ jobs:
steps: steps:
# Initialization # Initialization
- uses: actions/checkout@v3 - uses: actions/checkout@v4
with: with:
submodules: recursive submodules: recursive
- uses: actions/setup-node@v3 - uses: actions/setup-node@v4
with: with:
node-version: '18' node-version: '18'
- run: npm ci - run: npm ci
@@ -25,7 +25,7 @@ jobs:
- name: Upload results on fail - name: Upload results on fail
if: ${{ failure() }} if: ${{ failure() }}
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: Test Results name: Test Results
path: ./test-results path: ./test-results

View File

@@ -12,10 +12,10 @@ jobs:
update-oss: update-oss:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
with: with:
submodules: recursive submodules: recursive
- uses: actions/setup-node@v3 - uses: actions/setup-node@v4
with: with:
node-version: '18' node-version: '18'
- name: Install and generate attribution - name: Install and generate attribution
@@ -29,7 +29,7 @@ jobs:
cd ci && npx ts-node prettify.ts cd ci && npx ts-node prettify.ts
- name: Create pull request to update list - name: Create pull request to update list
uses: peter-evans/create-pull-request@2b011faafdcbc9ceb11414d64d0573f37c774b04 uses: peter-evans/create-pull-request@v7
# v4.2.3 # v4.2.3
with: with:
commit-message: Update OSS Attribution commit-message: Update OSS Attribution

View File

@@ -8,7 +8,7 @@ jobs:
check-list: check-list:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
with: with:
submodules: recursive submodules: recursive
- name: Download instance lists - name: Download instance lists
@@ -21,7 +21,7 @@ jobs:
run: npm run ci:invidious run: npm run ci:invidious
- name: Create pull request to update list - name: Create pull request to update list
uses: peter-evans/create-pull-request@2b011faafdcbc9ceb11414d64d0573f37c774b04 uses: peter-evans/create-pull-request@v7
# v4.2.3 # v4.2.3
with: with:
commit-message: Update Invidious List commit-message: Update Invidious List

View File

@@ -7,8 +7,7 @@ This file should not be shipped with the extension
/* /*
Criteria for inclusion: Criteria for inclusion:
Invidious Invidious
- 30d uptime >= 90% - uptime >= 80%
- available for at least 80/90 days
- must have been up for at least 90 days - must have been up for at least 90 days
- HTTPS only - HTTPS only
- url includes name (this is to avoid redirects) - url includes name (this is to avoid redirects)

View File

@@ -1,31 +1,34 @@
import { InvidiousInstance, instanceMap } from "./invidiousType" import { InvidiousInstance, monitor } from "./invidiousType"
import * as data from "../ci/invidious_instances.json"; import * as data from "../ci/invidious_instances.json";
// only https servers // only https servers
const mapped: instanceMap = data const mapped = (data as InvidiousInstance[])
.filter((i: InvidiousInstance) => i[1]?.type === "https") .filter((i) =>
.map((instance: InvidiousInstance) => { i[1]?.type === "https"
&& i[1]?.monitor?.enabled
)
.map((instance) => {
const monitor = instance[1].monitor as monitor;
return { return {
name: instance[0], name: instance[0],
url: instance[1].uri, url: instance[1].uri,
dailyRatios: instance[1].monitor.dailyRatios, uptime: monitor.uptime || 0,
thirtyDayUptime: instance[1]?.monitor["30dRatio"].ratio, down: monitor.down ?? false,
created_at: monitor.created_at,
} }
}); });
// reliability and sanity checks // reliability and sanity checks
const reliableCheck = mapped const reliableCheck = mapped
.filter(instance => { .filter(instance => {
// 30d uptime >= 90% const uptime = instance.uptime > 80 && !instance.down;
const thirtyDayUptime = Number(instance.thirtyDayUptime) >= 90; const nameIncluded = instance.url.includes(instance.name);
// available for at least 80/90 days const ninetyDays = 90 * 24 * 60 * 60 * 1000;
const dailyRatioCheck = instance.dailyRatios.filter(status => status.label !== "black"); const ninetyDaysAgo = new Date(Date.now() - ninetyDays);
return thirtyDayUptime && dailyRatioCheck.length >= 80; const createdAt = new Date(instance.created_at).getTime() < ninetyDaysAgo.getTime();
return uptime && nameIncluded && createdAt;
}) })
// url includes name
.filter(instance => instance.url.includes(instance.name));
export function getInvidiousList(): string[] { export const getInvidiousList = (): string[] =>
return reliableCheck.map(instance => instance.name).sort() reliableCheck.map(instance => instance.name).sort()
}

View File

@@ -1,26 +1,56 @@
type ratio = {
ratio: string;
label: string;
}
export type instanceMap = {
name: string;
url: string;
dailyRatios: {ratio: string; label: string }[];
thirtyDayUptime: string;
}[]
export type InvidiousInstance = [ export type InvidiousInstance = [
string, string,
{ {
flag: string; flag: string;
region: string; region: string;
stats: null | { stats: null | ivStats;
cors: null | boolean;
api: null | boolean;
type: "https" | "http" | "onion" | "i2p";
uri: string;
monitor: null | monitor;
}
]
export type monitor = {
token: string;
url: string;
alias: string;
last_status: number;
uptime: number;
down: boolean;
down_since: null | string;
up_since: null | string;
error: null | string;
period: number;
apdex_t: number;
string_match: string;
enabled: boolean;
published: boolean;
disabled_locations: string[];
recipients: string[];
last_check_at: string;
next_check_at: string;
created_at: string;
mute_until: null | string;
favicon_url: string;
custom_headers: Record<string, string>;
http_verb: string;
http_body: string;
ssl: {
tested_at: string;
expires_at: string;
valid: boolean;
error: null | string;
};
}
export type ivStats = {
version: string; version: string;
software: { software: {
name: string; name: "invidious" | string;
version: string; version: string;
branch: string; branch: "master" | string;
}; };
openRegistrations: boolean; openRegistrations: boolean;
usage: { usage: {
@@ -34,21 +64,9 @@ export type InvidiousInstance = [
updatedAt: number; updatedAt: number;
lastChannelRefreshedAt: number; lastChannelRefreshedAt: number;
}; };
playback: {
totalRequests: number;
successfulRequests: number;
ratio: number;
}; };
cors: boolean | null; }
api: boolean | null;
type: "https" | "http" | "onion" | "i2p";
uri: string;
monitor: null | {
monitorId: number;
createdAt: number;
statusClass: string;
name: string;
url: string | null;
type: "HTTP(s)";
dailyRatios: ratio[];
"90dRatio": ratio;
"30dRatio": ratio;
};
}
]

View File

@@ -1 +1 @@
["www.youtubekids.com","anontube.lvkaszus.pl","inv.citw.lgbt","inv.in.projectsegfau.lt","inv.tux.pizza","inv.zzls.xyz","invidious.asir.dev","invidious.drgns.space","invidious.fdn.fr","invidious.flokinet.to","invidious.io.lol","invidious.lunar.icu","invidious.nerdvpn.de","invidious.no-logs.com","invidious.perennialte.ch","invidious.privacydev.net","invidious.private.coffee","invidious.projectsegfau.lt","invidious.protokolla.fi","invidious.slipfox.xyz","iv.datura.network","iv.ggtyler.dev","iv.melmac.space","iv.nboeck.de","onion.tube","vid.priv.au","vid.puffyan.us","yewtu.be","yt.artemislena.eu","yt.cdaut.de","yt.drgnz.club","yt.oelrichsgarcia.de"] ["www.youtubekids.com","inv.nadeko.net","inv.tux.pizza","invidious.adminforge.de","invidious.jing.rocks","invidious.nerdvpn.de","invidious.perennialte.ch","invidious.privacyredirect.com","invidious.reallyaweso.me","invidious.yourdevice.ch","iv.ggtyler.dev","iv.nboeck.de","yewtu.be"]

View File

@@ -24,7 +24,7 @@
"intro": "https://wiki.sponsor.ajay.app/w/Intermission/Intro_Animation", "intro": "https://wiki.sponsor.ajay.app/w/Intermission/Intro_Animation",
"outro": "https://wiki.sponsor.ajay.app/w/Endcards/Credits", "outro": "https://wiki.sponsor.ajay.app/w/Endcards/Credits",
"preview": "https://wiki.sponsor.ajay.app/w/Preview/Recap", "preview": "https://wiki.sponsor.ajay.app/w/Preview/Recap",
"filler": "https://wiki.sponsor.ajay.app/w/Filler_Tangent", "filler": "https://wiki.sponsor.ajay.app/w/Tangents/Jokes",
"music_offtopic": "https://wiki.sponsor.ajay.app/w/Music:_Non-Music_Section", "music_offtopic": "https://wiki.sponsor.ajay.app/w/Music:_Non-Music_Section",
"poi_highlight": "https://wiki.sponsor.ajay.app/w/Highlight", "poi_highlight": "https://wiki.sponsor.ajay.app/w/Highlight",
"guidelines": "https://wiki.sponsor.ajay.app/w/Guidelines", "guidelines": "https://wiki.sponsor.ajay.app/w/Guidelines",

View File

@@ -1,12 +1,156 @@
{ {
"optional_permissions": [ "host_permissions": [
"declarativeContent", "https://*.youtube.com/*",
"webNavigation" "https://sponsor.ajay.app/*"
], ],
"background": { "optional_host_permissions": [
"persistent": false "*://*/*"
],
"web_accessible_resources": [{
"resources": [
"icons/LogoSponsorBlocker256px.png",
"icons/IconSponsorBlocker256px.png",
"icons/PlayerStartIconSponsorBlocker.svg",
"icons/PlayerStopIconSponsorBlocker.svg",
"icons/PlayerUploadIconSponsorBlocker.svg",
"icons/PlayerUploadFailedIconSponsorBlocker.svg",
"icons/PlayerCancelSegmentIconSponsorBlocker.svg",
"icons/clipboard.svg",
"icons/settings.svg",
"icons/pencil.svg",
"icons/check.svg",
"icons/check-smaller.svg",
"icons/upvote.png",
"icons/downvote.png",
"icons/thumbs_down.svg",
"icons/thumbs_down_locked.svg",
"icons/thumbs_up.svg",
"icons/help.svg",
"icons/report.png",
"icons/close.png",
"icons/skipIcon.svg",
"icons/refresh.svg",
"icons/beep.oga",
"icons/pause.svg",
"icons/stop.svg",
"icons/skip.svg",
"icons/heart.svg",
"icons/visible.svg",
"icons/not_visible.svg",
"icons/sort.svg",
"icons/money.svg",
"icons/segway.png",
"icons/close-smaller.svg",
"icons/right-arrow.svg",
"icons/campaign.svg",
"icons/star.svg",
"icons/lightbulb.svg",
"icons/bolt.svg",
"icons/stopwatch.svg",
"icons/music-note.svg",
"icons/import.svg",
"icons/export.svg",
"icons/PlayerInfoIconSponsorBlocker.svg",
"icons/PlayerDeleteIconSponsorBlocker.svg",
"icons/dearrow.svg",
"popup.html",
"popup.css",
"content.css",
"shared.css",
"js/document.js",
"libs/Source+Sans+Pro.css",
"libs/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlxdu.woff2",
"libs/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwmRduz8A.woff2",
"libs/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwmBduz8A.woff2",
"libs/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlBduz8A.woff2"
],
"matches": ["<all_urls>"]
}],
"content_scripts": [
{
"world": "MAIN",
"js": [
"./js/document.js"
],
"matches": [
"https://*.youtube.com/*",
"https://www.youtube-nocookie.com/embed/*"
],
"exclude_matches": [
"https://accounts.youtube.com/RotateCookiesPage*"
],
"all_frames": true,
"run_at": "document_start"
}, },
"permissions": [ {
"https://*.youtube.com/*" "world": "ISOLATED",
"js": [
"./js/content.js"
],
"css": [
"content.css",
"shared.css"
],
"matches": [
"https://*.youtube.com/*",
"https://www.youtube-nocookie.com/embed/*"
],
"exclude_matches": [
"https://accounts.youtube.com/RotateCookiesPage*"
],
"all_frames": true,
"run_at": "document_start"
}
],
"action": {
"default_title": "SponsorBlock",
"default_popup": "popup.html",
"default_icon": {
"16": "icons/IconSponsorBlocker16px.png",
"32": "icons/IconSponsorBlocker32px.png",
"64": "icons/IconSponsorBlocker64px.png",
"128": "icons/IconSponsorBlocker128px.png"
},
"theme_icons": [
{
"light": "icons/IconSponsorBlocker16px.png",
"dark": "icons/IconSponsorBlocker16px.png",
"size": 16
},
{
"light": "icons/IconSponsorBlocker32px.png",
"dark": "icons/IconSponsorBlocker32px.png",
"size": 32
},
{
"light": "icons/IconSponsorBlocker64px.png",
"dark": "icons/IconSponsorBlocker64px.png",
"size": 64
},
{
"light": "icons/IconSponsorBlocker128px.png",
"dark": "icons/IconSponsorBlocker128px.png",
"size": 128
},
{
"light": "icons/IconSponsorBlocker256px.png",
"dark": "icons/IconSponsorBlocker256px.png",
"size": 256
},
{
"light": "icons/IconSponsorBlocker512px.png",
"dark": "icons/IconSponsorBlocker512px.png",
"size": 512
},
{
"light": "icons/IconSponsorBlocker1024px.png",
"dark": "icons/IconSponsorBlocker1024px.png",
"size": 1024
}
] ]
},
"background": {
"service_worker": "./js/background.js"
},
"manifest_version": 3
} }

View File

@@ -2,7 +2,7 @@
"browser_specific_settings": { "browser_specific_settings": {
"gecko": { "gecko": {
"id": "sponsorBlocker@ajay.app", "id": "sponsorBlocker@ajay.app",
"strict_min_version": "56.0" "strict_min_version": "102.0"
}, },
"gecko_android": { "gecko_android": {
"strict_min_version": "113.0" "strict_min_version": "113.0"
@@ -11,9 +11,6 @@
"background": { "background": {
"persistent": false "persistent": false
}, },
"permissions": [
"scripting"
],
"browser_action": { "browser_action": {
"default_area": "navbar" "default_area": "navbar"
} }

View File

@@ -0,0 +1,136 @@
{
"web_accessible_resources": [
"icons/LogoSponsorBlocker256px.png",
"icons/IconSponsorBlocker256px.png",
"icons/PlayerStartIconSponsorBlocker.svg",
"icons/PlayerStopIconSponsorBlocker.svg",
"icons/PlayerUploadIconSponsorBlocker.svg",
"icons/PlayerUploadFailedIconSponsorBlocker.svg",
"icons/PlayerCancelSegmentIconSponsorBlocker.svg",
"icons/clipboard.svg",
"icons/settings.svg",
"icons/pencil.svg",
"icons/check.svg",
"icons/check-smaller.svg",
"icons/upvote.png",
"icons/downvote.png",
"icons/thumbs_down.svg",
"icons/thumbs_down_locked.svg",
"icons/thumbs_up.svg",
"icons/help.svg",
"icons/report.png",
"icons/close.png",
"icons/skipIcon.svg",
"icons/refresh.svg",
"icons/beep.oga",
"icons/pause.svg",
"icons/stop.svg",
"icons/skip.svg",
"icons/heart.svg",
"icons/visible.svg",
"icons/not_visible.svg",
"icons/sort.svg",
"icons/money.svg",
"icons/segway.png",
"icons/close-smaller.svg",
"icons/right-arrow.svg",
"icons/campaign.svg",
"icons/star.svg",
"icons/lightbulb.svg",
"icons/bolt.svg",
"icons/stopwatch.svg",
"icons/music-note.svg",
"icons/import.svg",
"icons/export.svg",
"icons/PlayerInfoIconSponsorBlocker.svg",
"icons/PlayerDeleteIconSponsorBlocker.svg",
"icons/dearrow.svg",
"popup.html",
"popup.css",
"content.css",
"shared.css",
"js/document.js",
"libs/Source+Sans+Pro.css",
"libs/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlxdu.woff2",
"libs/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwmRduz8A.woff2",
"libs/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwmBduz8A.woff2",
"libs/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlBduz8A.woff2"
],
"permissions": [
"https://sponsor.ajay.app/*"
],
"optional_permissions": [
"*://*/*"
],
"browser_action": {
"default_title": "SponsorBlock",
"default_popup": "popup.html",
"default_icon": {
"16": "icons/IconSponsorBlocker16px.png",
"32": "icons/IconSponsorBlocker32px.png",
"64": "icons/IconSponsorBlocker64px.png",
"128": "icons/IconSponsorBlocker128px.png"
},
"theme_icons": [
{
"light": "icons/IconSponsorBlocker16px.png",
"dark": "icons/IconSponsorBlocker16px.png",
"size": 16
},
{
"light": "icons/IconSponsorBlocker32px.png",
"dark": "icons/IconSponsorBlocker32px.png",
"size": 32
},
{
"light": "icons/IconSponsorBlocker64px.png",
"dark": "icons/IconSponsorBlocker64px.png",
"size": 64
},
{
"light": "icons/IconSponsorBlocker128px.png",
"dark": "icons/IconSponsorBlocker128px.png",
"size": 128
},
{
"light": "icons/IconSponsorBlocker256px.png",
"dark": "icons/IconSponsorBlocker256px.png",
"size": 256
},
{
"light": "icons/IconSponsorBlocker512px.png",
"dark": "icons/IconSponsorBlocker512px.png",
"size": 512
},
{
"light": "icons/IconSponsorBlocker1024px.png",
"dark": "icons/IconSponsorBlocker1024px.png",
"size": 1024
}
]
},
"background": {
"scripts":[
"./js/background.js"
]
},
"content_scripts": [{
"run_at": "document_start",
"matches": [
"https://*.youtube.com/*",
"https://www.youtube-nocookie.com/embed/*"
],
"exclude_matches": [
"https://accounts.youtube.com/RotateCookiesPage*"
],
"all_frames": true,
"js": [
"./js/content.js"
],
"css": [
"content.css",
"shared.css"
]
}],
"manifest_version": 2
}

View File

@@ -1,141 +1,10 @@
{ {
"name": "__MSG_fullName__", "name": "__MSG_fullName__",
"short_name": "SponsorBlock", "short_name": "SponsorBlock",
"version": "5.6", "version": "5.11.4",
"default_locale": "en", "default_locale": "en",
"description": "__MSG_Description__", "description": "__MSG_Description__",
"homepage_url": "https://sponsor.ajay.app", "homepage_url": "https://sponsor.ajay.app",
"content_scripts": [{
"run_at": "document_start",
"matches": [
"https://*.youtube.com/*",
"https://www.youtube-nocookie.com/embed/*"
],
"all_frames": true,
"js": [
"./js/content.js"
],
"css": [
"content.css",
"shared.css"
]
}],
"web_accessible_resources": [
"icons/LogoSponsorBlocker256px.png",
"icons/IconSponsorBlocker256px.png",
"icons/PlayerStartIconSponsorBlocker.svg",
"icons/PlayerStopIconSponsorBlocker.svg",
"icons/PlayerUploadIconSponsorBlocker.svg",
"icons/PlayerUploadFailedIconSponsorBlocker.svg",
"icons/PlayerCancelSegmentIconSponsorBlocker.svg",
"icons/clipboard.svg",
"icons/settings.svg",
"icons/pencil.svg",
"icons/check.svg",
"icons/check-smaller.svg",
"icons/upvote.png",
"icons/downvote.png",
"icons/thumbs_down.svg",
"icons/thumbs_down_locked.svg",
"icons/thumbs_up.svg",
"icons/help.svg",
"icons/report.png",
"icons/close.png",
"icons/skipIcon.svg",
"icons/refresh.svg",
"icons/beep.ogg",
"icons/pause.svg",
"icons/stop.svg",
"icons/skip.svg",
"icons/heart.svg",
"icons/visible.svg",
"icons/not_visible.svg",
"icons/sort.svg",
"icons/money.svg",
"icons/segway.png",
"icons/close-smaller.svg",
"icons/right-arrow.svg",
"icons/campaign.svg",
"icons/star.svg",
"icons/lightbulb.svg",
"icons/bolt.svg",
"icons/stopwatch.svg",
"icons/music-note.svg",
"icons/import.svg",
"icons/export.svg",
"icons/PlayerInfoIconSponsorBlocker.svg",
"icons/PlayerDeleteIconSponsorBlocker.svg",
"icons/dearrow.svg",
"popup.html",
"popup.css",
"content.css",
"shared.css",
"js/document.js",
"libs/Source+Sans+Pro.css",
"libs/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlxdu.woff2",
"libs/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwmRduz8A.woff2",
"libs/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwmBduz8A.woff2",
"libs/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlBduz8A.woff2"
],
"permissions": [
"storage",
"https://sponsor.ajay.app/*"
],
"optional_permissions": [
"*://*/*"
],
"browser_action": {
"default_title": "SponsorBlock",
"default_popup": "popup.html",
"default_icon": {
"16": "icons/IconSponsorBlocker16px.png",
"32": "icons/IconSponsorBlocker32px.png",
"64": "icons/IconSponsorBlocker64px.png",
"128": "icons/IconSponsorBlocker128px.png"
},
"theme_icons": [
{
"light": "icons/IconSponsorBlocker16px.png",
"dark": "icons/IconSponsorBlocker16px.png",
"size": 16
},
{
"light": "icons/IconSponsorBlocker32px.png",
"dark": "icons/IconSponsorBlocker32px.png",
"size": 32
},
{
"light": "icons/IconSponsorBlocker64px.png",
"dark": "icons/IconSponsorBlocker64px.png",
"size": 64
},
{
"light": "icons/IconSponsorBlocker128px.png",
"dark": "icons/IconSponsorBlocker128px.png",
"size": 128
},
{
"light": "icons/IconSponsorBlocker256px.png",
"dark": "icons/IconSponsorBlocker256px.png",
"size": 256
},
{
"light": "icons/IconSponsorBlocker512px.png",
"dark": "icons/IconSponsorBlocker512px.png",
"size": 512
},
{
"light": "icons/IconSponsorBlocker1024px.png",
"dark": "icons/IconSponsorBlocker1024px.png",
"size": 1024
}
]
},
"background": {
"scripts":[
"./js/background.js"
]
},
"icons": { "icons": {
"16": "icons/IconSponsorBlocker16px.png", "16": "icons/IconSponsorBlocker16px.png",
"32": "icons/IconSponsorBlocker32px.png", "32": "icons/IconSponsorBlocker32px.png",
@@ -145,9 +14,12 @@
"512": "icons/IconSponsorBlocker512px.png", "512": "icons/IconSponsorBlocker512px.png",
"1024": "icons/IconSponsorBlocker1024px.png" "1024": "icons/IconSponsorBlocker1024px.png"
}, },
"permissions": [
"storage",
"scripting"
],
"options_ui": { "options_ui": {
"page": "options/options.html", "page": "options/options.html",
"open_in_tab": true "open_in_tab": true
}, }
"manifest_version": 2
} }

View File

@@ -2,10 +2,15 @@
"background": { "background": {
"persistent": false "persistent": false
}, },
"permissions": [
"scripting"
],
"optional_permissions": [ "optional_permissions": [
"webNavigation" "webNavigation"
] ],
"browser_action": {
"default_icon": {
"16": "icons/SafariIconSponsorBlocker16px.png",
"32": "icons/SafariIconSponsorBlocker32px.png",
"64": "icons/SafariIconSponsorBlocker64px.png",
"128": "icons/SafariIconSponsorBlocker128px.png"
}
}
} }

3150
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -38,14 +38,14 @@
"ts-loader": "^9.4.2", "ts-loader": "^9.4.2",
"ts-node": "^10.9.1", "ts-node": "^10.9.1",
"typescript": "4.9", "typescript": "4.9",
"web-ext": "^7.10.0", "web-ext": "^8.2.0",
"webpack": "^5.75.0", "webpack": "^5.94.0",
"webpack-cli": "^4.10.0", "webpack-cli": "^4.10.0",
"webpack-merge": "^5.8.0" "webpack-merge": "^5.8.0"
}, },
"scripts": { "scripts": {
"web-run": "npm run web-run:chrome", "web-run": "npm run web-run:chrome",
"web-sign": "web-ext sign -s dist", "web-sign": "web-ext sign --channel unlisted -s dist",
"web-run:firefox": "cd dist && web-ext run --start-url https://addons.mozilla.org/firefox/addon/ublock-origin/", "web-run:firefox": "cd dist && web-ext run --start-url https://addons.mozilla.org/firefox/addon/ublock-origin/",
"web-run:firefox-android": "cd dist && web-ext run -t firefox-android --firefox-apk org.mozilla.fenix", "web-run:firefox-android": "cd dist && web-ext run -t firefox-android --firefox-apk org.mozilla.fenix",
"web-run:chrome": "cd dist && web-ext run --start-url https://chrome.google.com/webstore/detail/ublock-origin/cjpalhdlnbpafiamejdnhcphjbkeiagm -t chromium", "web-run:chrome": "cd dist && web-ext run --start-url https://chrome.google.com/webstore/detail/ublock-origin/cjpalhdlnbpafiamejdnhcphjbkeiagm -t chromium",

View File

@@ -11,6 +11,11 @@
display: none; display: none;
} }
/* Vorapi compatibility */
#player-api_VORAPI_ELEMENT_ID #previewbar {
z-index: 999;
}
#previewbar { #previewbar {
overflow: visible; overflow: visible;
padding: 0; padding: 0;
@@ -44,7 +49,16 @@
} }
div:hover > #previewbar.sbNotInvidious { div:hover > #previewbar.sbNotInvidious {
transform: scaleY(1) transform: scaleY(1);
}
/* Vorapis */
.v3 #previewbar.sbNotInvidious {
transform: scaleY(1);
}
.sponsorCategoryTooltipVisible.ytp-progress-tooltip {
width: 216px !important;
/* left: 264.308px !important; */
} }
.previewbar { .previewbar {
@@ -74,7 +88,7 @@ div:hover > #previewbar.sbNotInvidious {
display: none !important; display: none !important;
} }
.ytp-tooltip.sponsorCategoryTooltipVisible { .ytp-tooltip.sponsorCategoryTooltipVisible:not(.sponsorTooltipHasYTChapters) {
transform: translateY(-1em) !important; transform: translateY(-1em) !important;
} }
@@ -218,6 +232,11 @@ div:hover > .sponsorBlockChapterBar {
from { opacity: 0; } from { opacity: 0; }
} }
@keyframes fadeInToFaded {
from { opacity: 0; }
to { opacity: 0.5; }
}
@keyframes fadeOut { @keyframes fadeOut {
to { opacity: 0; } to { opacity: 0; }
} }
@@ -290,6 +309,10 @@ div:hover > .sponsorBlockChapterBar {
animation: fadeIn 0.5s ease-out; animation: fadeIn 0.5s ease-out;
} }
.sponsorSkipNoticeFadeIn.sponsorSkipNoticeFaded {
animation: fadeInToFaded 0.5s ease-out;
}
.exportCopiedNotice .sponsorSkipNoticeFadeIn { .exportCopiedNotice .sponsorSkipNoticeFadeIn {
animation: none; animation: none;
} }
@@ -826,6 +849,15 @@ input::-webkit-inner-spin-button {
white-space: nowrap; white-space: nowrap;
} }
/* Vorapis V3 support */
#watch7-content .sponsorBlockCategoryPill {
padding-top: 5px;
padding-bottom: 5px;
}
#watch7-content .sponsorBlockCategoryPillTitle {
font-size: 15px;
}
.categoryPillClose { .categoryPillClose {
display: none; display: none;
height: 10px; height: 10px;

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

@@ -118,13 +118,13 @@ html, body {
.option-group > div { .option-group > div {
min-height: 50px; min-height: 50px;
padding: 20px 0; padding: 15px 0;
border-bottom: 1px solid var(--border-color); border-bottom: 1px solid var(--border-color);
border-image: linear-gradient(to right, var(--border-color), #00000000 80%) 1; border-image: linear-gradient(to right, var(--border-color), #00000000 80%) 1;
} }
.categoryExtraOptions { .categoryExtraOptions {
padding-bottom: 20px; padding-bottom: 15px;
} }
#music_offtopic_autoSkipOnMusicVideos { #music_offtopic_autoSkipOnMusicVideos {
@@ -271,11 +271,11 @@ input[type='number'] {
.small-description { .small-description {
font-size: 13px; font-size: 13px;
padding: 15px 0 0 20px; padding: 5px 0 0 20px;
} }
.small-description td { .small-description td {
padding: 10px 0 20px 20px; padding: 2.5px 0 10px 20px;
} }
.indent { .indent {
@@ -283,7 +283,7 @@ input[type='number'] {
} }
.categoryTableElement td { .categoryTableElement td {
padding-top: 10px; padding-top: 5px;
border-top: 1px solid var(--border-color); border-top: 1px solid var(--border-color);
} }
@@ -353,7 +353,8 @@ input[type='number'] {
font-size: 14px; font-size: 14px;
display: table; display: flex;
align-items: center;
} }
.switch-container .switch-label { .switch-container .switch-label {
@@ -372,6 +373,7 @@ input[type='number'] {
display: inline-block; display: inline-block;
width: 40px; width: 40px;
height: 24px; height: 24px;
min-width: 40px;
} }
.switch input { .switch input {

View File

@@ -154,20 +154,6 @@
<div class="small-description">__MSG_whatForceChannelCheck__</div> <div class="small-description">__MSG_whatForceChannelCheck__</div>
</div> </div>
<div data-type="toggle" data-sync="refetchWhenNotFound">
<div class="switch-container">
<label class="switch">
<input id="refetchWhenNotFound" type="checkbox" checked>
<span class="slider round"></span>
</label>
<label class="switch-label" for="refetchWhenNotFound">
__MSG_enableRefetchWhenNotFound__
</label>
</div>
<div class="small-description">__MSG_whatRefetchWhenNotFound__</div>
</div>
<div data-type="toggle" data-sync="showCategoryWithoutPermission"> <div data-type="toggle" data-sync="showCategoryWithoutPermission">
<div class="switch-container"> <div class="switch-container">
<label class="switch"> <label class="switch">
@@ -209,6 +195,21 @@
<div class="small-description">__MSG_skipNoticeDurationDescription__</div> <div class="small-description">__MSG_skipNoticeDurationDescription__</div>
</div> </div>
<div>
<div data-type="toggle" data-sync="showUpcomingNotice">
<div class="switch-container">
<label class="switch">
<input id="showUpcomingNotice" type="checkbox" checked>
<span class="slider round"></span>
</label>
<label class="switch-label" for="showUpcomingNotice">
__MSG_showUpcomingNotice__
</label>
</div>
</div>
<br/>
<div data-type="toggle" data-toggle-type="reverse" data-sync="dontShowNotice"> <div data-type="toggle" data-toggle-type="reverse" data-sync="dontShowNotice">
<div class="switch-container"> <div class="switch-container">
<label class="switch"> <label class="switch">
@@ -219,6 +220,7 @@
__MSG_showSkipNotice__ __MSG_showSkipNotice__
</label> </label>
</div> </div>
</div>
<div data-type="selector" data-sync="noticeVisibilityMode" data-dependent-on="dontShowNotice"> <div data-type="selector" data-sync="noticeVisibilityMode" data-dependent-on="dontShowNotice">
<br/> <br/>

View File

@@ -196,6 +196,8 @@
<a href="https://discord.gg/SponsorBlock" target="_blank" rel="noopener">Discord</a> <a href="https://discord.gg/SponsorBlock" target="_blank" rel="noopener">Discord</a>
<a href="https://matrix.to/#/#sponsor:ajay.app?via=ajay.app&via=matrix.org&via=mozilla.org" target="_blank" rel="noopener">Matrix</a> <a href="https://matrix.to/#/#sponsor:ajay.app?via=ajay.app&via=matrix.org&via=mozilla.org" target="_blank" rel="noopener">Matrix</a>
<a href="https://sponsor.ajay.app/donate" target="_blank" rel="noopener" id="sbDonate">__MSG_Donate__</a> <a href="https://sponsor.ajay.app/donate" target="_blank" rel="noopener" id="sbDonate">__MSG_Donate__</a>
<br />
<a id="debugLogs">__MSG_copyDebugLogs__</a>
</footer> </footer>
<button id="showNoticeAgain" style="display: none">__MSG_showNotice__</button> <button id="showNoticeAgain" style="display: none">__MSG_showNotice__</button>

View File

@@ -7,13 +7,9 @@ import { sendRealRequestToCustomServer, setupBackgroundRequestProxy } from "../m
import { setupTabUpdates } from "../maze-utils/src/tab-updates"; import { setupTabUpdates } from "../maze-utils/src/tab-updates";
import { generateUserID } from "../maze-utils/src/setup"; import { generateUserID } from "../maze-utils/src/setup";
// Make the config public for debugging purposes
window.SB = Config;
import Utils from "./utils"; import Utils from "./utils";
import { getExtensionIdsToImportFrom } from "./utils/crossExtension"; import { getExtensionIdsToImportFrom } from "./utils/crossExtension";
import { isFirefoxOrSafari } from "../maze-utils/src"; import { isFirefoxOrSafari, waitFor } from "../maze-utils/src";
import { injectUpdatedScripts } from "../maze-utils/src/cleanup"; import { injectUpdatedScripts } from "../maze-utils/src/cleanup";
import { logWarn } from "./utils/logger"; import { logWarn } from "./utils/logger";
import { chromeP } from "../maze-utils/src/browserApi"; import { chromeP } from "../maze-utils/src/browserApi";
@@ -47,7 +43,7 @@ chrome.runtime.onMessage.addListener(function (request, sender, callback) {
chrome.tabs.create({url: chrome.runtime.getURL(request.url)}); chrome.tabs.create({url: chrome.runtime.getURL(request.url)});
return false; return false;
case "submitVote": case "submitVote":
submitVote(request.type, request.UUID, request.category).then(callback); submitVote(request.type, request.UUID, request.category, request.videoID).then(callback);
//this allows the callback to be called later //this allows the callback to be called later
return true; return true;
@@ -142,9 +138,16 @@ chrome.runtime.onInstalled.addListener(function () {
} }
}, 1500); }, 1500);
// Only do this once the old version understands how to clean itself up if (!isFirefoxOrSafari()) {
if (!isFirefoxOrSafari() && chrome.runtime.getManifest().version !== "5.4.13") {
injectUpdatedScripts().catch(logWarn); injectUpdatedScripts().catch(logWarn);
waitFor(() => Config.isReady()).then(() => {
if (Config.config.supportInvidious) {
injectUpdatedScripts([
utils.getExtraSiteRegistration()
])
}
}).catch(logWarn);
} }
}); });
@@ -211,7 +214,7 @@ async function unregisterFirefoxContentScript(id: string) {
} }
} }
async function submitVote(type: number, UUID: string, category: string) { async function submitVote(type: number, UUID: string, category: string, videoID: string) {
let userID = Config.config.userID; let userID = Config.config.userID;
if (userID == undefined || userID === "undefined") { if (userID == undefined || userID === "undefined") {
@@ -223,7 +226,7 @@ async function submitVote(type: number, UUID: string, category: string) {
const typeSection = (type !== undefined) ? "&type=" + type : "&category=" + category; const typeSection = (type !== undefined) ? "&type=" + type : "&category=" + category;
try { try {
const response = await asyncRequestToServer("POST", "/api/voteOnSponsorTime?UUID=" + UUID + "&userID=" + userID + typeSection); const response = await asyncRequestToServer("POST", "/api/voteOnSponsorTime?UUID=" + UUID + "&videoID=" + videoID + "&userID=" + userID + typeSection);
if (response.ok) { if (response.ok) {
return { return {

View File

@@ -19,6 +19,7 @@ export interface NoticeProps {
idSuffix?: string; idSuffix?: string;
fadeIn?: boolean; fadeIn?: boolean;
fadeOut?: boolean;
startFaded?: boolean; startFaded?: boolean;
firstColumn?: React.ReactElement[] | React.ReactElement; firstColumn?: React.ReactElement[] | React.ReactElement;
firstRow?: React.ReactElement; firstRow?: React.ReactElement;
@@ -244,7 +245,7 @@ class NoticeComponent extends React.Component<NoticeProps, NoticeState> {
id={"skipNoticeTimerText" + this.idSuffix} id={"skipNoticeTimerText" + this.idSuffix}
key="skipNoticeTimerText" key="skipNoticeTimerText"
className={this.state.countdownMode !== CountdownMode.Timer ? "sbhidden" : ""} > className={this.state.countdownMode !== CountdownMode.Timer ? "sbhidden" : ""} >
{chrome.i18n.getMessage("NoticeTimeAfterSkip").replace("{seconds}", this.state.countdownTime.toString())} {chrome.i18n.getMessage("NoticeTimeAfterSkip").replace("{seconds}", Math.ceil(this.state.countdownTime).toString())}
</span> </span>
),( ),(
<img <img
@@ -326,7 +327,7 @@ class NoticeComponent extends React.Component<NoticeProps, NoticeState> {
return; return;
} }
if (countdownTime == 3) { if (countdownTime == 3 && this.props.fadeOut) {
//start fade out animation //start fade out animation
const notice = document.getElementById("sponsorSkipNotice" + this.idSuffix); const notice = document.getElementById("sponsorSkipNotice" + this.idSuffix);
notice?.style.removeProperty("animation"); notice?.style.removeProperty("animation");

View File

@@ -6,7 +6,7 @@ import NoticeComponent from "./NoticeComponent";
import NoticeTextSelectionComponent from "./NoticeTextSectionComponent"; import NoticeTextSelectionComponent from "./NoticeTextSectionComponent";
import Utils from "../utils"; import Utils from "../utils";
const utils = new Utils(); const utils = new Utils();
import { getSkippingText } from "../utils/categoryUtils"; import { getSkippingText, getUpcomingText } from "../utils/categoryUtils";
import ThumbsUpSvg from "../svg-icons/thumbs_up_svg"; import ThumbsUpSvg from "../svg-icons/thumbs_up_svg";
import ThumbsDownSvg from "../svg-icons/thumbs_down_svg"; import ThumbsDownSvg from "../svg-icons/thumbs_down_svg";
@@ -15,6 +15,7 @@ import { downvoteButtonColor, SkipNoticeAction } from "../utils/noticeUtils";
import { generateUserID } from "../../maze-utils/src/setup"; import { generateUserID } from "../../maze-utils/src/setup";
import { keybindToString } from "../../maze-utils/src/config"; import { keybindToString } from "../../maze-utils/src/config";
import { getFormattedTime } from "../../maze-utils/src/formating"; import { getFormattedTime } from "../../maze-utils/src/formating";
import { getCurrentTime, getVideo } from "../../maze-utils/src/video";
enum SkipButtonState { enum SkipButtonState {
Undo, // Unskip Undo, // Unskip
@@ -27,12 +28,17 @@ export interface SkipNoticeProps {
autoSkip: boolean; autoSkip: boolean;
startReskip?: boolean; startReskip?: boolean;
upcomingNotice?: boolean;
// Contains functions and variables from the content script needed by the skip notice // Contains functions and variables from the content script needed by the skip notice
contentContainer: ContentContainer; contentContainer: ContentContainer;
closeListener: () => void; closeListener: () => void;
showKeybindHint?: boolean; showKeybindHint?: boolean;
smaller: boolean; smaller: boolean;
fadeIn: boolean;
maxCountdownTime?: number;
componentDidMount?: () => void;
unskipTime?: number; unskipTime?: number;
} }
@@ -96,9 +102,9 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
this.autoSkip = props.autoSkip; this.autoSkip = props.autoSkip;
this.contentContainer = props.contentContainer; this.contentContainer = props.contentContainer;
const noticeTitle = getSkippingText(this.segments, this.props.autoSkip); const noticeTitle = !this.props.upcomingNotice ? getSkippingText(this.segments, this.props.autoSkip) : getUpcomingText(this.segments);
const previousSkipNotices = document.getElementsByClassName("sponsorSkipNoticeParent"); const previousSkipNotices = document.querySelectorAll(".sponsorSkipNoticeParent:not(.sponsorSkipUpcomingNotice)");
this.amountOfPreviousNotices = previousSkipNotices.length; this.amountOfPreviousNotices = previousSkipNotices.length;
// If there is at least one already in the first slot // If there is at least one already in the first slot
this.showInSecondSlot = previousSkipNotices.length > 0 && [...previousSkipNotices].some(notice => !notice.classList.contains("secondSkipNotice")); this.showInSecondSlot = previousSkipNotices.length > 0 && [...previousSkipNotices].some(notice => !notice.classList.contains("secondSkipNotice"));
@@ -119,7 +125,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
this.lockedColor = Config.config.colorPalette.locked; this.lockedColor = Config.config.colorPalette.locked;
const isMuteSegment = this.segments[0].actionType === ActionType.Mute; const isMuteSegment = this.segments[0].actionType === ActionType.Mute;
const maxCountdownTime = isMuteSegment ? this.getFullDurationCountdown(0) : () => Config.config.skipNoticeDuration; const maxCountdownTime = props.maxCountdownTime ? () => props.maxCountdownTime : (isMuteSegment ? this.getFullDurationCountdown(0) : () => Config.config.skipNoticeDuration);
const defaultSkipButtonState = this.props.startReskip ? SkipButtonState.Redo : SkipButtonState.Undo; const defaultSkipButtonState = this.props.startReskip ? SkipButtonState.Redo : SkipButtonState.Undo;
const skipButtonStates = [defaultSkipButtonState, isMuteSegment ? SkipButtonState.Start : defaultSkipButtonState]; const skipButtonStates = [defaultSkipButtonState, isMuteSegment ? SkipButtonState.Start : defaultSkipButtonState];
@@ -170,12 +176,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
noticeStyle.transform = "scale(0.8) translate(10%, 10%)"; noticeStyle.transform = "scale(0.8) translate(10%, 10%)";
} }
// If it started out as smaller, always keep the const firstColumn = this.getSkipButton(0);
// skip button there
const showFirstSkipButton = this.props.smaller || this.segments[0].actionType === ActionType.Mute;
const firstColumn = showFirstSkipButton ? (
this.getSkipButton(0)
) : null;
return ( return (
<NoticeComponent <NoticeComponent
@@ -183,7 +184,8 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
amountOfPreviousNotices={this.amountOfPreviousNotices} amountOfPreviousNotices={this.amountOfPreviousNotices}
showInSecondSlot={this.showInSecondSlot} showInSecondSlot={this.showInSecondSlot}
idSuffix={this.idSuffix} idSuffix={this.idSuffix}
fadeIn={true} fadeIn={this.props.fadeIn}
fadeOut={!this.props.upcomingNotice}
startFaded={Config.config.noticeVisibilityMode >= NoticeVisbilityMode.FadedForAll startFaded={Config.config.noticeVisibilityMode >= NoticeVisbilityMode.FadedForAll
|| (Config.config.noticeVisibilityMode >= NoticeVisbilityMode.FadedForAutoSkip && this.autoSkip)} || (Config.config.noticeVisibilityMode >= NoticeVisbilityMode.FadedForAutoSkip && this.autoSkip)}
timed={true} timed={true}
@@ -196,12 +198,20 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
logoFill={Config.config.barTypes[this.segments[0].category].color} logoFill={Config.config.barTypes[this.segments[0].category].color}
limitWidth={true} limitWidth={true}
firstColumn={firstColumn} firstColumn={firstColumn}
dontPauseCountdown={!!this.props.upcomingNotice}
bottomRow={[...this.getMessageBoxes(), ...this.getBottomRow() ]} bottomRow={[...this.getMessageBoxes(), ...this.getBottomRow() ]}
extraClass={this.props.upcomingNotice ? "sponsorSkipUpcomingNotice" : ""}
onMouseEnter={() => this.onMouseEnter() } > onMouseEnter={() => this.onMouseEnter() } >
</NoticeComponent> </NoticeComponent>
); );
} }
componentDidMount(): void {
if (this.props.componentDidMount) {
this.props.componentDidMount();
}
}
getBottomRow(): JSX.Element[] { getBottomRow(): JSX.Element[] {
return [ return [
/* Bottom Row */ /* Bottom Row */
@@ -364,8 +374,10 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
style.minWidth = "100px"; style.minWidth = "100px";
} }
const showSkipButton = (buttonIndex !== 0 || this.props.smaller || this.segments[0].actionType === ActionType.Mute) && !this.props.upcomingNotice;
return ( return (
<span className="sponsorSkipNoticeUnskipSection"> <span className="sponsorSkipNoticeUnskipSection" style={{ visibility: !showSkipButton ? "hidden" : null }}>
<button id={"sponsorSkipUnskipButton" + this.idSuffix} <button id={"sponsorSkipUnskipButton" + this.idSuffix}
className="sponsorSkipObject sponsorSkipNoticeButton" className="sponsorSkipObject sponsorSkipNoticeButton"
style={style} style={style}
@@ -419,7 +431,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
} }
onMouseEnter(): void { onMouseEnter(): void {
if (this.state.smaller) { if (this.state.smaller && !this.props.upcomingNotice) {
this.setState({ this.setState({
smaller: false smaller: false
}); });
@@ -657,7 +669,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
const skipButtonStates = this.state.skipButtonStates; const skipButtonStates = this.state.skipButtonStates;
const skipButtonCallbacks = this.state.skipButtonCallbacks; const skipButtonCallbacks = this.state.skipButtonCallbacks;
if (buttonIndex === null) { if (buttonIndex === null) {
for (let i = 0; i < this.segments.length; i++) { for (let i = 0; i < skipButtonStates.length; i++) {
skipButtonStates[i] = skipButtonState; skipButtonStates[i] = skipButtonState;
skipButtonCallbacks[i] = this.reskip.bind(this); skipButtonCallbacks[i] = this.reskip.bind(this);
} }
@@ -685,7 +697,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
getFullDurationCountdown(index: number): () => number { getFullDurationCountdown(index: number): () => number {
return () => { return () => {
const sponsorTime = this.segments[index]; const sponsorTime = this.segments[index];
const duration = Math.round((sponsorTime.segment[1] - this.contentContainer().v.currentTime) * (1 / this.contentContainer().v.playbackRate)); const duration = Math.round((sponsorTime.segment[1] - getCurrentTime()) * (1 / getVideo().playbackRate));
return Math.max(duration, Config.config.skipNoticeDuration); return Math.max(duration, Config.config.skipNoticeDuration);
}; };

View File

@@ -9,6 +9,7 @@ import { DEFAULT_CATEGORY } from "../utils/categoryUtils";
import { getFormattedTime, getFormattedTimeToSeconds } from "../../maze-utils/src/formating"; import { getFormattedTime, getFormattedTimeToSeconds } from "../../maze-utils/src/formating";
import { asyncRequestToServer } from "../utils/requests"; import { asyncRequestToServer } from "../utils/requests";
import { defaultPreviewTime } from "../utils/constants"; import { defaultPreviewTime } from "../utils/constants";
import { getVideo, getVideoDuration } from "../../maze-utils/src/video";
export interface SponsorTimeEditProps { export interface SponsorTimeEditProps {
index: number; index: number;
@@ -401,7 +402,7 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
checkToShowFullVideoWarning(): void { checkToShowFullVideoWarning(): void {
const sponsorTime = this.props.contentContainer().sponsorTimesSubmitting[this.props.index]; const sponsorTime = this.props.contentContainer().sponsorTimesSubmitting[this.props.index];
const segmentDuration = sponsorTime.segment[1] - sponsorTime.segment[0]; const segmentDuration = sponsorTime.segment[1] - sponsorTime.segment[0];
const videoPercentage = segmentDuration / this.props.contentContainer().v.duration; const videoPercentage = segmentDuration / getVideoDuration();
if (videoPercentage > 0.6 && !this.fullVideoWarningShown if (videoPercentage > 0.6 && !this.fullVideoWarningShown
&& (sponsorTime.category === "sponsor" || sponsorTime.category === "selfpromo" || sponsorTime.category === "chooseACategory")) { && (sponsorTime.category === "sponsor" || sponsorTime.category === "selfpromo" || sponsorTime.category === "chooseACategory")) {
@@ -553,7 +554,7 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
} }
setTimeToEnd(): void { setTimeToEnd(): void {
this.setTimeTo(1, this.props.contentContainer().v.duration); this.setTimeTo(1, getVideoDuration());
} }
/** /**
@@ -640,7 +641,7 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
sponsorTimesSubmitting[this.props.index].segment[0] = startTime; sponsorTimesSubmitting[this.props.index].segment[0] = startTime;
} }
} else if (this.state.sponsorTimeEdits[1] === null && category === "outro" && !sponsorTimesSubmitting[this.props.index].segment[1]) { } else if (this.state.sponsorTimeEdits[1] === null && category === "outro" && !sponsorTimesSubmitting[this.props.index].segment[1]) {
sponsorTimesSubmitting[this.props.index].segment[1] = this.props.contentContainer().v.duration; sponsorTimesSubmitting[this.props.index].segment[1] = getVideoDuration();
this.props.contentContainer().updateEditButtonsOnPlayer(); this.props.contentContainer().updateEditButtonsOnPlayer();
} }
@@ -683,7 +684,7 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
const endTime = sponsorTimes[index].segment[1]; const endTime = sponsorTimes[index].segment[1];
// If segment starts at 0:00, start playback at the end of the segment // If segment starts at 0:00, start playback at the end of the segment
const skipTime = (startTime === 0 || skipToEndTime) ? endTime : (startTime - (seekTime * this.props.contentContainer().v.playbackRate)); const skipTime = (startTime === 0 || skipToEndTime) ? endTime : (startTime - (seekTime * getVideo().playbackRate));
this.props.contentContainer().previewTime(skipTime, !skipToEndTime); this.props.contentContainer().previewTime(skipTime, !skipToEndTime);
} }

View File

@@ -9,6 +9,7 @@ import NoticeTextSelectionComponent from "./NoticeTextSectionComponent";
import SponsorTimeEditComponent from "./SponsorTimeEditComponent"; import SponsorTimeEditComponent from "./SponsorTimeEditComponent";
import { getGuidelineInfo } from "../utils/constants"; import { getGuidelineInfo } from "../utils/constants";
import { exportTimes } from "../utils/exporter"; import { exportTimes } from "../utils/exporter";
import { getVideo, isCurrentTimeWrong } from "../../maze-utils/src/video";
export interface SubmissionNoticeProps { export interface SubmissionNoticeProps {
// Contains functions and variables from the content script needed by the skip notice // Contains functions and variables from the content script needed by the skip notice
@@ -66,7 +67,7 @@ class SubmissionNoticeComponent extends React.Component<SubmissionNoticeProps, S
this.forceUpdate(); this.forceUpdate();
}); });
this.videoObserver.observe(this.contentContainer().v, { this.videoObserver.observe(getVideo(), {
attributes: true attributes: true
}); });
@@ -131,7 +132,7 @@ class SubmissionNoticeComponent extends React.Component<SubmissionNoticeProps, S
{/* Sponsor Time List */} {/* Sponsor Time List */}
<tr id={"sponsorSkipNoticeMiddleRow" + this.state.idSuffix} <tr id={"sponsorSkipNoticeMiddleRow" + this.state.idSuffix}
className="sponsorTimeMessagesRow" className="sponsorTimeMessagesRow"
style={{maxHeight: (this.contentContainer().v.offsetHeight - 200) + "px"}} style={{maxHeight: (getVideo()?.offsetHeight - 200) + "px"}}
onMouseDown={(e) => e.stopPropagation()}> onMouseDown={(e) => e.stopPropagation()}>
<td style={{width: "100%"}}> <td style={{width: "100%"}}>
{this.getSponsorTimeMessages()} {this.getSponsorTimeMessages()}
@@ -215,6 +216,11 @@ class SubmissionNoticeComponent extends React.Component<SubmissionNoticeProps, S
} }
submit(): void { submit(): void {
if (isCurrentTimeWrong()) {
alert(chrome.i18n.getMessage("submissionFailedServerSideAds"));
return;
}
// save all items // save all items
for (const ref of this.timeEditRefs) { for (const ref of this.timeEditRefs) {
ref.current.saveEditTimes(); ref.current.saveEditTimes();
@@ -283,7 +289,7 @@ class SubmissionNoticeComponent extends React.Component<SubmissionNoticeProps, S
categoryChangeListener(index: number, category: Category): void { categoryChangeListener(index: number, category: Category): void {
const dialogWidth = this.noticeRef?.current?.getElement()?.current?.offsetWidth; const dialogWidth = this.noticeRef?.current?.getElement()?.current?.offsetWidth;
if (category !== "chooseACategory" && Config.config.showCategoryGuidelines if (category !== "chooseACategory" && Config.config.showCategoryGuidelines
&& this.contentContainer().v.offsetWidth > dialogWidth * 2) { && getVideo().offsetWidth > dialogWidth * 2) {
const options = { const options = {
title: chrome.i18n.getMessage(`category_${category}`), title: chrome.i18n.getMessage(`category_${category}`),
textBoxes: getGuidelineInfo(category), textBoxes: getGuidelineInfo(category),

View File

@@ -234,6 +234,10 @@ class CategorySkipOptionsComponent extends React.Component<CategorySkipOptionsPr
configKey: "showSegmentNameInChapterBar", configKey: "showSegmentNameInChapterBar",
label: chrome.i18n.getMessage("showSegmentNameInChapterBar"), label: chrome.i18n.getMessage("showSegmentNameInChapterBar"),
dontDisable: true dontDisable: true
}, {
configKey: "showAutogeneratedChapters",
label: chrome.i18n.getMessage("showAutogeneratedChapters"),
dontDisable: true
}]; }];
case "music_offtopic": case "music_offtopic":
return [{ return [{

View File

@@ -31,6 +31,7 @@ interface SBConfig {
trackDownvotes: boolean; trackDownvotes: boolean;
trackDownvotesInPrivate: boolean; trackDownvotesInPrivate: boolean;
dontShowNotice: boolean; dontShowNotice: boolean;
showUpcomingNotice: boolean;
noticeVisibilityMode: NoticeVisbilityMode; noticeVisibilityMode: NoticeVisbilityMode;
hideVideoPlayerControls: boolean; hideVideoPlayerControls: boolean;
hideInfoButtonPlayerControls: boolean; hideInfoButtonPlayerControls: boolean;
@@ -47,7 +48,6 @@ interface SBConfig {
audioNotificationOnSkip: boolean; audioNotificationOnSkip: boolean;
checkForUnlistedVideos: boolean; checkForUnlistedVideos: boolean;
testingServer: boolean; testingServer: boolean;
refetchWhenNotFound: boolean;
ytInfoPermissionGranted: boolean; ytInfoPermissionGranted: boolean;
allowExpirements: boolean; allowExpirements: boolean;
showDonationLink: boolean; showDonationLink: boolean;
@@ -69,6 +69,7 @@ interface SBConfig {
showCategoryGuidelines: boolean; showCategoryGuidelines: boolean;
showCategoryWithoutPermission: boolean; showCategoryWithoutPermission: boolean;
showSegmentNameInChapterBar: boolean; showSegmentNameInChapterBar: boolean;
showAutogeneratedChapters: boolean;
useVirtualTime: boolean; useVirtualTime: boolean;
showSegmentFailedToFetchWarning: boolean; showSegmentFailedToFetchWarning: boolean;
allowScrollingToEdit: boolean; allowScrollingToEdit: boolean;
@@ -293,6 +294,7 @@ const syncDefaults = {
trackDownvotes: true, trackDownvotes: true,
trackDownvotesInPrivate: false, trackDownvotesInPrivate: false,
dontShowNotice: false, dontShowNotice: false,
showUpcomingNotice: false,
noticeVisibilityMode: NoticeVisbilityMode.FadedForAutoSkip, noticeVisibilityMode: NoticeVisbilityMode.FadedForAutoSkip,
hideVideoPlayerControls: false, hideVideoPlayerControls: false,
hideInfoButtonPlayerControls: false, hideInfoButtonPlayerControls: false,
@@ -309,7 +311,6 @@ const syncDefaults = {
audioNotificationOnSkip: false, audioNotificationOnSkip: false,
checkForUnlistedVideos: false, checkForUnlistedVideos: false,
testingServer: false, testingServer: false,
refetchWhenNotFound: true,
ytInfoPermissionGranted: false, ytInfoPermissionGranted: false,
allowExpirements: true, allowExpirements: true,
showDonationLink: true, showDonationLink: true,
@@ -326,6 +327,7 @@ const syncDefaults = {
showCategoryGuidelines: true, showCategoryGuidelines: true,
showCategoryWithoutPermission: false, showCategoryWithoutPermission: false,
showSegmentNameInChapterBar: true, showSegmentNameInChapterBar: true,
showAutogeneratedChapters: true,
useVirtualTime: true, useVirtualTime: true,
showSegmentFailedToFetchWarning: true, showSegmentFailedToFetchWarning: true,
allowScrollingToEdit: true, allowScrollingToEdit: true,
@@ -478,3 +480,25 @@ const localDefaults = {
const Config = new ConfigClass(syncDefaults, localDefaults, migrateOldSyncFormats); const Config = new ConfigClass(syncDefaults, localDefaults, migrateOldSyncFormats);
export default Config; export default Config;
export function generateDebugDetails(): string {
// Build output debug information object
const output = {
debug: {
userAgent: navigator.userAgent,
platform: navigator.platform,
language: navigator.language,
extensionVersion: chrome.runtime.getManifest().version
},
config: JSON.parse(JSON.stringify(Config.cachedSyncConfig)) // Deep clone config object
};
// Sanitise sensitive user config values
delete output.config.userID;
output.config.serverAddress = (output.config.serverAddress === CompileConfig.serverAddress)
? "Default server address" : "Custom server address";
output.config.invidiousInstances = output.config.invidiousInstances.length;
output.config.whitelistedChannels = output.config.whitelistedChannels.length;
return JSON.stringify(output, null, 4);
}

View File

@@ -20,11 +20,12 @@ import Utils from "./utils";
import PreviewBar, { PreviewBarSegment } from "./js-components/previewBar"; import PreviewBar, { PreviewBarSegment } from "./js-components/previewBar";
import SkipNotice from "./render/SkipNotice"; import SkipNotice from "./render/SkipNotice";
import SkipNoticeComponent from "./components/SkipNoticeComponent"; import SkipNoticeComponent from "./components/SkipNoticeComponent";
import UpcomingNotice from "./render/UpcomingNotice";
import SubmissionNotice from "./render/SubmissionNotice"; import SubmissionNotice from "./render/SubmissionNotice";
import { Message, MessageResponse, VoteResponse } from "./messageTypes"; import { Message, MessageResponse, VoteResponse } from "./messageTypes";
import { SkipButtonControlBar } from "./js-components/skipButtonControlBar"; import { SkipButtonControlBar } from "./js-components/skipButtonControlBar";
import { getStartTimeFromUrl } from "./utils/urlParser"; import { getStartTimeFromUrl } from "./utils/urlParser";
import { getControls, getExistingChapters, getHashParams, isPlayingPlaylist, isVisible } from "./utils/pageUtils"; import { getControls, getExistingChapters, getHashParams, hasAutogeneratedChapters, isPlayingPlaylist, isVisible } from "./utils/pageUtils";
import { CategoryPill } from "./render/CategoryPill"; import { CategoryPill } from "./render/CategoryPill";
import { AnimationUtils } from "../maze-utils/src/animationUtils"; import { AnimationUtils } from "../maze-utils/src/animationUtils";
import { GenericUtils } from "./utils/genericUtils"; import { GenericUtils } from "./utils/genericUtils";
@@ -34,7 +35,7 @@ import { ChapterVote } from "./render/ChapterVote";
import { openWarningDialog } from "./utils/warnings"; import { openWarningDialog } from "./utils/warnings";
import { isFirefoxOrSafari, waitFor } from "../maze-utils/src"; import { isFirefoxOrSafari, waitFor } from "../maze-utils/src";
import { getErrorMessage, getFormattedTime } from "../maze-utils/src/formating"; import { getErrorMessage, getFormattedTime } from "../maze-utils/src/formating";
import { getChannelIDInfo, getVideo, getIsAdPlaying, getIsLivePremiere, setIsAdPlaying, checkVideoIDChange, getVideoID, getYouTubeVideoID, setupVideoModule, checkIfNewVideoID, isOnInvidious, isOnMobileYouTube, getLastNonInlineVideoID, triggerVideoIDChange, triggerVideoElementChange, getIsInline } from "../maze-utils/src/video"; import { getChannelIDInfo, getVideo, getIsAdPlaying, getIsLivePremiere, setIsAdPlaying, checkVideoIDChange, getVideoID, getYouTubeVideoID, setupVideoModule, checkIfNewVideoID, isOnInvidious, isOnMobileYouTube, getLastNonInlineVideoID, triggerVideoIDChange, triggerVideoElementChange, getIsInline, getCurrentTime, setCurrentTime, getVideoDuration, verifyCurrentTime, waitForVideo } from "../maze-utils/src/video";
import { Keybind, StorageChangesObject, isSafari, keybindEquals, keybindToString } from "../maze-utils/src/config"; import { Keybind, StorageChangesObject, isSafari, keybindEquals, keybindToString } from "../maze-utils/src/config";
import { findValidElement } from "../maze-utils/src/dom" import { findValidElement } from "../maze-utils/src/dom"
import { getHash, HashedValue } from "../maze-utils/src/hash"; import { getHash, HashedValue } from "../maze-utils/src/hash";
@@ -42,7 +43,7 @@ import { generateUserID } from "../maze-utils/src/setup";
import { updateAll } from "../maze-utils/src/thumbnailManagement"; import { updateAll } from "../maze-utils/src/thumbnailManagement";
import { setupThumbnailListener } from "./utils/thumbnails"; import { setupThumbnailListener } from "./utils/thumbnails";
import * as documentScript from "../dist/js/document.js"; import * as documentScript from "../dist/js/document.js";
import { runCompatibilityChecks } from "./utils/compatibility"; import { isVorapisInstalled, runCompatibilityChecks } from "./utils/compatibility";
import { cleanPage } from "./utils/pageCleaner"; import { cleanPage } from "./utils/pageCleaner";
import { addCleanupListener } from "../maze-utils/src/cleanup"; import { addCleanupListener } from "../maze-utils/src/cleanup";
import { hideDeArrowPromotion, tryShowingDeArrowPromotion } from "./dearrowPromotion"; import { hideDeArrowPromotion, tryShowingDeArrowPromotion } from "./dearrowPromotion";
@@ -50,6 +51,7 @@ import { asyncRequestToServer } from "./utils/requests";
import { isMobileControlsOpen } from "./utils/mobileUtils"; import { isMobileControlsOpen } from "./utils/mobileUtils";
import { defaultPreviewTime } from "./utils/constants"; import { defaultPreviewTime } from "./utils/constants";
import { onVideoPage } from "../maze-utils/src/pageInfo"; import { onVideoPage } from "../maze-utils/src/pageInfo";
import { getSegmentsForVideo } from "./utils/segmentData";
cleanPage(); cleanPage();
@@ -74,11 +76,10 @@ let sponsorTimes: SponsorTime[] = [];
let existingChaptersImported = false; let existingChaptersImported = false;
let importingChaptersWaitingForFocus = false; let importingChaptersWaitingForFocus = false;
let importingChaptersWaiting = false; let importingChaptersWaiting = false;
let triedImportingChapters = false;
// List of open skip notices // List of open skip notices
const skipNotices: SkipNotice[] = []; const skipNotices: SkipNotice[] = [];
let upcomingNotice: UpcomingNotice | null = null;
let activeSkipKeybindElement: ToggleSkippable = null; let activeSkipKeybindElement: ToggleSkippable = null;
let retryFetchTimeout: NodeJS.Timeout = null;
let shownSegmentFailedToFetchWarning = false; let shownSegmentFailedToFetchWarning = false;
let selectedSegment: SegmentUUID | null = null; let selectedSegment: SegmentUUID | null = null;
let previewedSegment = false; let previewedSegment = false;
@@ -107,6 +108,7 @@ const lastNextChapterKeybind = {
let currentSkipSchedule: NodeJS.Timeout = null; let currentSkipSchedule: NodeJS.Timeout = null;
let currentSkipInterval: NodeJS.Timeout = null; let currentSkipInterval: NodeJS.Timeout = null;
let currentVirtualTimeInterval: NodeJS.Timeout = null; let currentVirtualTimeInterval: NodeJS.Timeout = null;
let currentUpcomingSchedule: NodeJS.Timeout = null;
/** Has the sponsor been skipped */ /** Has the sponsor been skipped */
let sponsorSkipped: boolean[] = []; let sponsorSkipped: boolean[] = [];
@@ -127,7 +129,7 @@ setupVideoModule({
updateVisibilityOfPlayerControlsButton(); updateVisibilityOfPlayerControlsButton();
}, },
resetValues, resetValues,
documentScript documentScript: chrome.runtime.getManifest().manifest_version === 2 ? documentScript : undefined
}, () => Config); }, () => Config);
setupThumbnailListener(); setupThumbnailListener();
@@ -139,6 +141,9 @@ let switchingVideos = null;
let lastCheckTime = 0; let lastCheckTime = 0;
let lastCheckVideoTime = -1; let lastCheckVideoTime = -1;
// To determine if a video resolution change is happening
let firstPlay = true;
//is this channel whitelised from getting sponsors skipped //is this channel whitelised from getting sponsors skipped
let channelWhitelisted = false; let channelWhitelisted = false;
@@ -167,7 +172,6 @@ let popupInitialised = false;
let submissionNotice: SubmissionNotice = null; let submissionNotice: SubmissionNotice = null;
let lastResponseStatus: number; let lastResponseStatus: number;
let retryCount = 0;
// Contains all of the functions and variables needed by the skip notice // Contains all of the functions and variables needed by the skip notice
const skipNoticeContentContainer: ContentContainer = () => ({ const skipNoticeContentContainer: ContentContainer = () => ({
@@ -177,7 +181,6 @@ const skipNoticeContentContainer: ContentContainer = () => ({
sponsorTimes, sponsorTimes,
sponsorTimesSubmitting, sponsorTimesSubmitting,
skipNotices, skipNotices,
v: getVideo(),
sponsorVideoID: getVideoID(), sponsorVideoID: getVideoID(),
reskipSponsorTime, reskipSponsorTime,
updatePreviewBar, updatePreviewBar,
@@ -218,7 +221,7 @@ function messageListener(request: Message, sender: unknown, sendResponse: (respo
found: sponsorDataFound, found: sponsorDataFound,
status: lastResponseStatus, status: lastResponseStatus,
sponsorTimes: sponsorTimes, sponsorTimes: sponsorTimes,
time: getVideo()?.currentTime ?? 0, time: getCurrentTime() ?? 0,
onMobileYouTube: isOnMobileYouTube() onMobileYouTube: isOnMobileYouTube()
}); });
@@ -264,7 +267,9 @@ function messageListener(request: Message, sender: unknown, sendResponse: (respo
// it will assume the page is not a video page and stop the refresh animation // it will assume the page is not a video page and stop the refresh animation
sendResponse({ hasVideo: getVideoID() != null }); sendResponse({ hasVideo: getVideoID() != null });
// fetch segments // fetch segments
sponsorsLookup(false); if (getVideoID()) {
sponsorsLookup(false, true);
}
break; break;
case "unskip": case "unskip":
@@ -296,7 +301,7 @@ function messageListener(request: Message, sender: unknown, sendResponse: (respo
navigator.clipboard.writeText(request.text); navigator.clipboard.writeText(request.text);
break; break;
case "importSegments": { case "importSegments": {
const importedSegments = importTimes(request.data, getVideo().duration); const importedSegments = importTimes(request.data, getVideoDuration());
let addedSegments = false; let addedSegments = false;
for (const segment of importedSegments) { for (const segment of importedSegments) {
if (!sponsorTimesSubmitting.some( if (!sponsorTimesSubmitting.some(
@@ -341,6 +346,12 @@ function messageListener(request: Message, sender: unknown, sendResponse: (respo
metaKey: request.metaKey metaKey: request.metaKey
})); }));
break; break;
case "getLogs":
sendResponse({
debug: window["SBLogs"].debug,
warn: window["SBLogs"].warn
});
break;
} }
sendResponse({}); sendResponse({});
@@ -358,7 +369,7 @@ function contentConfigUpdateListener(changes: StorageChangesObject) {
updateVisibilityOfPlayerControlsButton() updateVisibilityOfPlayerControlsButton()
break; break;
case "categorySelections": case "categorySelections":
sponsorsLookup(); sponsorsLookup(true, true);
break; break;
case "barTypes": case "barTypes":
setCategoryColorCSSVariables(); setCategoryColorCSSVariables();
@@ -378,12 +389,11 @@ if (!Config.configSyncListeners.includes(contentConfigUpdateListener)) {
function resetValues() { function resetValues() {
lastCheckTime = 0; lastCheckTime = 0;
lastCheckVideoTime = -1; lastCheckVideoTime = -1;
retryCount = 0;
previewedSegment = false; previewedSegment = false;
firstPlay = true;
sponsorTimes = []; sponsorTimes = [];
existingChaptersImported = false; existingChaptersImported = false;
triedImportingChapters = false;
sponsorSkipped = []; sponsorSkipped = [];
lastResponseStatus = 0; lastResponseStatus = 0;
shownSegmentFailedToFetchWarning = false; shownSegmentFailedToFetchWarning = false;
@@ -416,6 +426,11 @@ function resetValues() {
skipNotices.pop()?.close(); skipNotices.pop()?.close();
} }
if (upcomingNotice) {
upcomingNotice.close();
upcomingNotice = null;
}
hideDeArrowPromotion(); hideDeArrowPromotion();
} }
@@ -500,12 +515,8 @@ function handleMobileControlsMutations(): void {
function getPreviewBarAttachElement(): HTMLElement | null { function getPreviewBarAttachElement(): HTMLElement | null {
const progressElementOptions = [{ const progressElementOptions = [{
// For new mobile YouTube (#1287) // For newer mobile YouTube (Sept 2024)
selector: ".progress-bar-line", selector: ".ytChapteredProgressBarHost, .ytProgressBarLineHost, .YtProgressBarLineHost, .YtChapteredProgressBarHost",
isVisibleCheck: true
}, {
// For newer mobile YouTube (Jan 2024)
selector: ".YtProgressBarProgressBarLine",
isVisibleCheck: true isVisibleCheck: true
}, { }, {
// For newer mobile YouTube (May 2024) // For newer mobile YouTube (May 2024)
@@ -538,6 +549,9 @@ function getPreviewBarAttachElement(): HTMLElement | null {
// For piped // For piped
selector: ".shaka-ad-markers", selector: ".shaka-ad-markers",
isVisibleCheck: false isVisibleCheck: false
}, {
// For Vorapis v3
selector: ".ytp-progress-bar-container > .html5-progress-bar > .ytp-progress-list"
} }
]; ];
@@ -600,6 +614,11 @@ function cancelSponsorSchedule(): void {
clearInterval(currentSkipInterval); clearInterval(currentSkipInterval);
currentSkipInterval = null; currentSkipInterval = null;
} }
if (currentUpcomingSchedule !== null) {
clearTimeout(currentUpcomingSchedule);
currentUpcomingSchedule = null;
}
} }
/** /**
@@ -632,8 +651,8 @@ async function startSponsorSchedule(includeIntersectingSegments = false, current
updateActiveSegment(currentTime); updateActiveSegment(currentTime);
if (getVideo().paused if ((getVideo().paused && getCurrentTime() !== 0) // Allow autoplay disabled videos to skip before playing
|| (getVideo().currentTime >= getVideo().duration - 0.01 && getVideo().duration > 1)) return; || (getCurrentTime() >= getVideoDuration() - 0.01 && getVideoDuration() > 1)) return;
const skipInfo = getNextSkipIndex(currentTime, includeIntersectingSegments, includeNonIntersectingSegments); const skipInfo = getNextSkipIndex(currentTime, includeIntersectingSegments, includeNonIntersectingSegments);
const currentSkip = skipInfo.array[skipInfo.index]; const currentSkip = skipInfo.array[skipInfo.index];
@@ -683,7 +702,7 @@ async function startSponsorSchedule(includeIntersectingSegments = false, current
let forcedIncludeNonIntersectingSegments = true; let forcedIncludeNonIntersectingSegments = true;
if (incorrectVideoCheck(videoID, currentSkip)) return; if (incorrectVideoCheck(videoID, currentSkip)) return;
forceVideoTime ||= Math.max(getVideo().currentTime, getVirtualTime()); forceVideoTime ||= Math.max(getCurrentTime(), getVirtualTime());
if ((shouldSkip(currentSkip) || sponsorTimesSubmitting?.some((segment) => segment.segment === currentSkip.segment))) { if ((shouldSkip(currentSkip) || sponsorTimesSubmitting?.some((segment) => segment.segment === currentSkip.segment))) {
if (forceVideoTime >= skipTime[0] - skipBuffer && forceVideoTime < skipTime[1]) { if (forceVideoTime >= skipTime[0] - skipBuffer && forceVideoTime < skipTime[1]) {
@@ -715,7 +734,7 @@ async function startSponsorSchedule(includeIntersectingSegments = false, current
forcedIncludeNonIntersectingSegments = false; forcedIncludeNonIntersectingSegments = false;
// Only if not at the end of the video // Only if not at the end of the video
if (Math.abs(skipTime[1] - getVideo().duration) > endTimeSkipBuffer) { if (Math.abs(skipTime[1] - getVideoDuration()) > endTimeSkipBuffer) {
forcedIncludeIntersectingSegments = true; forcedIncludeIntersectingSegments = true;
} }
} }
@@ -738,7 +757,8 @@ async function startSponsorSchedule(includeIntersectingSegments = false, current
skippingFunction(currentTime); skippingFunction(currentTime);
} else { } else {
let delayTime = timeUntilSponsor * 1000 * (1 / getVideo().playbackRate); let delayTime = timeUntilSponsor * 1000 * (1 / getVideo().playbackRate);
if (delayTime < (isFirefoxOrSafari() && !isSafari() ? 750 : 300)) { if (delayTime < (isFirefoxOrSafari() && !isSafari() ? 750 : 300)
&& shouldAutoSkip(skippingSegments[0])) {
let forceStartIntervalTime: number | null = null; let forceStartIntervalTime: number | null = null;
if (isFirefoxOrSafari() && !isSafari() && delayTime > 300) { if (isFirefoxOrSafari() && !isSafari() && delayTime > 300) {
forceStartIntervalTime = await waitForNextTimeChange(); forceStartIntervalTime = await waitForNextTimeChange();
@@ -746,42 +766,57 @@ async function startSponsorSchedule(includeIntersectingSegments = false, current
// Use interval instead of timeout near the end to combat imprecise video time // Use interval instead of timeout near the end to combat imprecise video time
const startIntervalTime = forceStartIntervalTime || performance.now(); const startIntervalTime = forceStartIntervalTime || performance.now();
const startVideoTime = Math.max(currentTime, getVideo().currentTime); const startVideoTime = Math.max(currentTime, getCurrentTime());
delayTime = (skipTime?.[0] - startVideoTime) * 1000 * (1 / getVideo().playbackRate); delayTime = (skipTime?.[0] - startVideoTime) * 1000 * (1 / getVideo().playbackRate);
let startWaitingForReportedTimeToChange = true; let startWaitingForReportedTimeToChange = true;
const reportedVideoTimeAtStart = getVideo().currentTime; const reportedVideoTimeAtStart = getCurrentTime();
logDebug(`Starting setInterval skipping ${getVideo().currentTime} to skip at ${skipTime[0]}`); logDebug(`Starting setInterval skipping ${getCurrentTime()} to skip at ${skipTime[0]}`);
if (currentSkipInterval !== null) clearInterval(currentSkipInterval); if (currentSkipInterval !== null) clearInterval(currentSkipInterval);
currentSkipInterval = setInterval(() => { currentSkipInterval = setInterval(() => {
// Estimate delay, but only take the current time right after a change // Estimate delay, but only take the current time right after a change
// Current time remains the same for many "frames" on Firefox // Current time remains the same for many "frames" on Firefox
if (isFirefoxOrSafari() && !lastKnownVideoTime.fromPause && startWaitingForReportedTimeToChange if (isFirefoxOrSafari() && !lastKnownVideoTime.fromPause && startWaitingForReportedTimeToChange
&& reportedVideoTimeAtStart !== getVideo().currentTime) { && reportedVideoTimeAtStart !== getCurrentTime()) {
startWaitingForReportedTimeToChange = false; startWaitingForReportedTimeToChange = false;
const delay = getVirtualTime() - getVideo().currentTime; const delay = getVirtualTime() - getCurrentTime();
if (delay > 0) lastKnownVideoTime.approximateDelay = delay; if (delay > 0) lastKnownVideoTime.approximateDelay = delay;
} }
const intervalDuration = performance.now() - startIntervalTime; const intervalDuration = performance.now() - startIntervalTime;
if (intervalDuration + skipBuffer * 1000 >= delayTime || getVideo().currentTime >= skipTime[0]) { if (intervalDuration + skipBuffer * 1000 >= delayTime || getCurrentTime() >= skipTime[0]) {
clearInterval(currentSkipInterval); clearInterval(currentSkipInterval);
if (!isFirefoxOrSafari() && !getVideo().muted && !inMuteSegment(getVideo().currentTime, true)) { if (!isFirefoxOrSafari() && !getVideo().muted && !inMuteSegment(getCurrentTime(), true)) {
// Workaround for more accurate skipping on Chromium // Workaround for more accurate skipping on Chromium
getVideo().muted = true; getVideo().muted = true;
getVideo().muted = false; getVideo().muted = false;
} }
skippingFunction(Math.max(getVideo().currentTime, startVideoTime + getVideo().playbackRate * Math.max(delayTime, intervalDuration) / 1000)); skippingFunction(Math.max(getCurrentTime(), startVideoTime + getVideo().playbackRate * Math.max(delayTime, intervalDuration) / 1000));
} }
}, 0); }, 0);
} else { } else {
logDebug(`Starting timeout to skip ${getVideo().currentTime} to skip at ${skipTime[0]}`); logDebug(`Starting timeout to skip ${getCurrentTime()} to skip at ${skipTime[0]}`);
const offset = (isFirefoxOrSafari() && !isSafari() ? 600 : 150); const offset = (isFirefoxOrSafari() && !isSafari() ? 600 : 150);
// Schedule for right before to be more precise than normal timeout // Schedule for right before to be more precise than normal timeout
currentSkipSchedule = setTimeout(skippingFunction, Math.max(0, delayTime - offset)); const offsetDelayTime = Math.max(0, delayTime - offset);
currentSkipSchedule = setTimeout(skippingFunction, offsetDelayTime);
// Show the notice only if the segment hasn't already started
if (Config.config.showUpcomingNotice && getCurrentTime() < skippingSegments[0].segment[0]
&& !sponsorTimesSubmitting?.some((segment) => segment.segment === currentSkip.segment)
&& [ActionType.Skip, ActionType.Mute].includes(skippingSegments[0].actionType)
&& !getVideo()?.paused) {
const maxPopupTime = 3000;
const timeUntilPopup = Math.max(0, offsetDelayTime - maxPopupTime);
const popupTime = offsetDelayTime - timeUntilPopup;
const autoSkip = shouldAutoSkip(skippingSegments[0]);
if (currentUpcomingSchedule) clearTimeout(currentUpcomingSchedule);
currentUpcomingSchedule = setTimeout(createUpcomingNotice, timeUntilPopup, [skippingSegments[0]], popupTime, autoSkip);
}
} }
} }
} }
@@ -798,13 +833,13 @@ function waitForNextTimeChange(): Promise<DOMHighResTimeStamp | null> {
function getVirtualTime(): number { function getVirtualTime(): number {
const virtualTime = lastTimeFromWaitingEvent ?? (lastKnownVideoTime.videoTime !== null ? const virtualTime = lastTimeFromWaitingEvent ?? (lastKnownVideoTime.videoTime !== null ?
(performance.now() - lastKnownVideoTime.preciseTime) * getVideo().playbackRate / 1000 + lastKnownVideoTime.videoTime : null); (performance.now() - lastKnownVideoTime.preciseTime) * (getVideo()?.playbackRate || 1) / 1000 + lastKnownVideoTime.videoTime : null);
if (Config.config.useVirtualTime && !isSafari() && virtualTime if (Config.config.useVirtualTime && !isSafari() && virtualTime
&& Math.abs(virtualTime - getVideo().currentTime) < 0.2 && getVideo().currentTime !== 0) { && Math.abs(virtualTime - getCurrentTime()) < 0.2 && getCurrentTime() !== 0) {
return Math.max(virtualTime, getVideo().currentTime); return Math.max(virtualTime, getCurrentTime());
} else { } else {
return getVideo().currentTime; return getCurrentTime();
} }
} }
@@ -844,8 +879,8 @@ function incorrectVideoCheck(videoID?: string, sponsorTime?: SponsorTime): boole
let playbackRateCheckInterval: NodeJS.Timeout | null = null; let playbackRateCheckInterval: NodeJS.Timeout | null = null;
let lastPlaybackSpeed = 1; let lastPlaybackSpeed = 1;
let setupVideoListenersFirstTime = true; let setupVideoListenersFirstTime = true;
function setupVideoListeners() { function setupVideoListeners(video: HTMLVideoElement) {
const video = getVideo(); if (!video) return; // Maybe video became invisible
//wait until it is loaded //wait until it is loaded
video.addEventListener('loadstart', videoOnReadyListener) video.addEventListener('loadstart', videoOnReadyListener)
@@ -863,6 +898,7 @@ function setupVideoListeners() {
let startedWaiting = false; let startedWaiting = false;
let lastPausedAtZero = true; let lastPausedAtZero = true;
let lastVideoDataChange = 0;
const rateChangeListener = () => { const rateChangeListener = () => {
updateVirtualTime(); updateVirtualTime();
@@ -875,13 +911,10 @@ function setupVideoListeners() {
video.addEventListener('videoSpeed_ratechange', rateChangeListener); video.addEventListener('videoSpeed_ratechange', rateChangeListener);
const playListener = () => { const playListener = () => {
// If it is not the first event, then the only way to get to 0 is if there is a seek event // Prevent video resolution changes from causing skips
// This check makes sure that changing the video resolution doesn't cause the extension to think it if (!firstPlay && Date.now() - lastVideoDataChange < 200 && video.currentTime === 0) return;
// gone back to the begining
if (video.readyState <= HTMLMediaElement.HAVE_CURRENT_DATA
&& video.currentTime === 0) return;
firstPlay = false;
updateVirtualTime(); updateVirtualTime();
checkForMiniplayerPlaying(); checkForMiniplayerPlaying();
@@ -971,15 +1004,15 @@ function setupVideoListeners() {
clearWaitingTime(); clearWaitingTime();
// Sometimes looped videos loop back to almost zero, but not quite // Sometimes looped videos loop back to almost zero, but not quite
if (video.loop && video.currentTime < 0.2) { if (video.loop && video.currentTime < 0.2 && getCurrentTime() < 0.2) {
startSponsorSchedule(false, 0); startSponsorSchedule(false, 0);
} else { } else {
startSponsorSchedule(); startSponsorSchedule();
} }
} else { } else {
updateActiveSegment(video.currentTime); updateActiveSegment(getCurrentTime());
if (video.currentTime === 0) { if (getCurrentTime() === 0) {
lastPausedAtZero = true; lastPausedAtZero = true;
} }
} }
@@ -1013,6 +1046,24 @@ function setupVideoListeners() {
}; };
video.addEventListener('waiting', waitingListener); video.addEventListener('waiting', waitingListener);
// When video data is changed
const emptyListener = () => {
lastVideoDataChange = Date.now();
if (firstPlay && video.currentTime === 0) {
playListener();
}
}
video.addEventListener('emptied', emptyListener);
// For when autoplay is off to skip before starting playback
const metadataLoadedListener = () => {
if (firstPlay && getCurrentTime() === 0) {
playListener();
}
}
video.addEventListener('loadedmetadata', metadataLoadedListener)
startSponsorSchedule(); startSponsorSchedule();
if (setupVideoListenersFirstTime) { if (setupVideoListenersFirstTime) {
@@ -1024,6 +1075,8 @@ function setupVideoListeners() {
video.removeEventListener('videoSpeed_ratechange', rateChangeListener); video.removeEventListener('videoSpeed_ratechange', rateChangeListener);
video.removeEventListener('pause', pauseListener); video.removeEventListener('pause', pauseListener);
video.removeEventListener('waiting', waitingListener); video.removeEventListener('waiting', waitingListener);
video.removeEventListener('empty', emptyListener);
video.removeEventListener('loadedmetadata', metadataLoadedListener);
if (playbackRateCheckInterval) clearInterval(playbackRateCheckInterval); if (playbackRateCheckInterval) clearInterval(playbackRateCheckInterval);
}); });
@@ -1036,7 +1089,7 @@ function setupVideoListeners() {
function updateVirtualTime() { function updateVirtualTime() {
if (currentVirtualTimeInterval) clearInterval(currentVirtualTimeInterval); if (currentVirtualTimeInterval) clearInterval(currentVirtualTimeInterval);
lastKnownVideoTime.videoTime = getVideo().currentTime; lastKnownVideoTime.videoTime = getCurrentTime();
lastKnownVideoTime.preciseTime = performance.now(); lastKnownVideoTime.preciseTime = performance.now();
// If on Firefox, wait for the second time change (time remains fixed for many "frames" for privacy reasons) // If on Firefox, wait for the second time change (time remains fixed for many "frames" for privacy reasons)
@@ -1048,21 +1101,21 @@ function updateVirtualTime() {
currentVirtualTimeInterval = setInterval(() => { currentVirtualTimeInterval = setInterval(() => {
const frameTime = performance.now() - lastPerformanceTime; const frameTime = performance.now() - lastPerformanceTime;
if (lastTime !== getVideo().currentTime) { if (lastTime !== getCurrentTime()) {
rawCount++; rawCount++;
// If there is lag, give it another shot at finding a good change time // If there is lag, give it another shot at finding a good change time
if (frameTime < 20 || rawCount > 30) { if (frameTime < 20 || rawCount > 30) {
count++; count++;
} }
lastTime = getVideo().currentTime; lastTime = getCurrentTime();
} }
if (count > 1) { if (count > 1) {
const delay = lastKnownVideoTime.fromPause && lastKnownVideoTime.approximateDelay ? const delay = lastKnownVideoTime.fromPause && lastKnownVideoTime.approximateDelay ?
lastKnownVideoTime.approximateDelay : 0; lastKnownVideoTime.approximateDelay : 0;
lastKnownVideoTime.videoTime = getVideo().currentTime + delay; lastKnownVideoTime.videoTime = getCurrentTime() + delay;
lastKnownVideoTime.preciseTime = performance.now(); lastKnownVideoTime.preciseTime = performance.now();
clearInterval(currentVirtualTimeInterval); clearInterval(currentVirtualTimeInterval);
@@ -1108,33 +1161,23 @@ function setupCategoryPill() {
categoryPill.attachToPage(isOnMobileYouTube(), isOnInvidious(), voteAsync); categoryPill.attachToPage(isOnMobileYouTube(), isOnInvidious(), voteAsync);
} }
async function sponsorsLookup(keepOldSubmissions = true) { async function sponsorsLookup(keepOldSubmissions = true, ignoreCache = false) {
const categories: string[] = Config.config.categorySelections.map((category) => category.name); const videoID = getVideoID();
if (!videoID) {
console.error("[SponsorBlock] Attempted to fetch segments with a null/undefined videoID.");
return;
}
const extraRequestData: Record<string, unknown> = {}; const segmentData = await getSegmentsForVideo(videoID, ignoreCache);
const hashParams = getHashParams();
if (hashParams.requiredSegment) extraRequestData.requiredSegment = hashParams.requiredSegment;
const hashPrefix = (await getHash(getVideoID(), 1)).slice(0, 4) as VideoID & HashedValue; // Make sure an old pending request doesn't get used.
const response = await asyncRequestToServer('GET', "/api/skipSegments/" + hashPrefix, { if (videoID !== getVideoID()) return;
categories,
actionTypes: getEnabledActionTypes(),
userAgent: `${chrome.runtime.id}`,
...extraRequestData
});
// store last response status // store last response status
lastResponseStatus = response?.status; lastResponseStatus = segmentData.status;
if (segmentData.status === 200) {
const receivedSegments = segmentData.segments;
if (response?.ok) {
const receivedSegments: SponsorTime[] = JSON.parse(response.responseText)
?.filter((video) => video.videoID === getVideoID())
?.map((video) => video.segments)?.[0]
?.map((segment) => ({
...segment,
source: SponsorSourceType.Server
}))
?.sort((a, b) => a.segment[0] - b.segment[0]);
if (receivedSegments && receivedSegments.length) { if (receivedSegments && receivedSegments.length) {
sponsorDataFound = true; sponsorDataFound = true;
@@ -1174,6 +1217,7 @@ async function sponsorsLookup(keepOldSubmissions = true) {
} }
// See if some segments should be hidden // See if some segments should be hidden
const hashPrefix = (await getHash(videoID, 1)).slice(0, 4) as VideoID & HashedValue;
const downvotedData = Config.local.downvotedSegments[hashPrefix]; const downvotedData = Config.local.downvotedSegments[hashPrefix];
if (downvotedData) { if (downvotedData) {
for (const segment of sponsorTimes) { for (const segment of sponsorTimes) {
@@ -1187,19 +1231,15 @@ async function sponsorsLookup(keepOldSubmissions = true) {
if (!getVideo()) { if (!getVideo()) {
//there is still no video here //there is still no video here
await waitFor(() => getVideo(), 5000, 10); await waitForVideo();
} }
startSkipScheduleCheckingForStartSponsors(); startSkipScheduleCheckingForStartSponsors();
if (!isNaN(getVideo().duration)) { if (!isNaN(getVideoDuration())) {
updatePreviewBar(); updatePreviewBar();
} }
} else {
retryFetch(404);
} }
} else {
retryFetch(lastResponseStatus);
} }
importExistingChapters(true); importExistingChapters(true);
@@ -1210,7 +1250,7 @@ async function sponsorsLookup(keepOldSubmissions = true) {
found: sponsorDataFound, found: sponsorDataFound,
status: lastResponseStatus, status: lastResponseStatus,
sponsorTimes: sponsorTimes, sponsorTimes: sponsorTimes,
time: getVideo()?.currentTime ?? 0, time: getCurrentTime() ?? 0,
onMobileYouTube: isOnMobileYouTube() onMobileYouTube: isOnMobileYouTube()
}); });
@@ -1220,8 +1260,8 @@ async function sponsorsLookup(keepOldSubmissions = true) {
} }
function importExistingChapters(wait: boolean) { function importExistingChapters(wait: boolean) {
if (!existingChaptersImported && !importingChaptersWaiting && !triedImportingChapters && onVideoPage() && !isOnMobileYouTube()) { if (!existingChaptersImported && !importingChaptersWaiting && onVideoPage() && !isOnMobileYouTube()) {
const waitCondition = () => getVideo()?.duration && getExistingChapters(getVideoID(), getVideo().duration); const waitCondition = () => getVideoDuration() && getExistingChapters(getVideoID(), getVideoDuration());
if (wait && !document.hasFocus() && !importingChaptersWaitingForFocus && !waitCondition()) { if (wait && !document.hasFocus() && !importingChaptersWaitingForFocus && !waitCondition()) {
importingChaptersWaitingForFocus = true; importingChaptersWaitingForFocus = true;
@@ -1241,21 +1281,15 @@ function importExistingChapters(wait: boolean) {
existingChaptersImported = true; existingChaptersImported = true;
updatePreviewBar(); updatePreviewBar();
} }
}).catch(() => { importingChaptersWaiting = false; triedImportingChapters = true; }); // eslint-disable-line @typescript-eslint/no-empty-function }).catch(() => { importingChaptersWaiting = false; });
}
}
}
function getEnabledActionTypes(forceFullVideo = false): ActionType[] { if (!Config.config.showAutogeneratedChapters) {
const actionTypes = [ActionType.Skip, ActionType.Poi, ActionType.Chapter]; waitFor(() => hasAutogeneratedChapters(), wait ? 15000 : 0, 400).then(() => {
if (Config.config.muteSegments) { updateActiveSegment(getCurrentTime());
actionTypes.push(ActionType.Mute); }).catch(() => { }); // eslint-disable-line @typescript-eslint/no-empty-function
}
} }
if (Config.config.fullVideoSegments || forceFullVideo) {
actionTypes.push(ActionType.Full);
} }
return actionTypes;
} }
async function lockedCategoriesLookup(): Promise<void> { async function lockedCategoriesLookup(): Promise<void> {
@@ -1272,27 +1306,6 @@ async function lockedCategoriesLookup(): Promise<void> {
} }
} }
function retryFetch(errorCode: number): void {
sponsorDataFound = false;
if (!Config.config.refetchWhenNotFound) return;
if (retryFetchTimeout) clearTimeout(retryFetchTimeout);
if ((errorCode !== 404 && retryCount > 1) || (errorCode !== 404 && retryCount > 10)) {
// Too many errors (50x), give up
return;
}
retryCount++;
const delay = errorCode === 404 ? (30000 + Math.random() * 30000) : (2000 + Math.random() * 10000);
retryFetchTimeout = setTimeout(() => {
if (getVideoID() && sponsorTimes?.length === 0
|| sponsorTimes.every((segment) => segment.source !== SponsorSourceType.Server)) {
// sponsorsLookup();
}
}, delay);
}
/** /**
* Only should be used when it is okay to skip a sponsor when in the middle of it * Only should be used when it is okay to skip a sponsor when in the middle of it
* *
@@ -1305,7 +1318,7 @@ function startSkipScheduleCheckingForStartSponsors() {
let startingSegmentTime = getStartTimeFromUrl(document.URL) || -1; let startingSegmentTime = getStartTimeFromUrl(document.URL) || -1;
let found = false; let found = false;
for (const time of sponsorTimes) { for (const time of sponsorTimes) {
if (time.segment[0] <= getVideo().currentTime && time.segment[0] > startingSegmentTime && time.segment[1] > getVideo().currentTime if (time.segment[0] <= getCurrentTime() && time.segment[0] > startingSegmentTime && time.segment[1] > getCurrentTime()
&& time.actionType !== ActionType.Poi) { && time.actionType !== ActionType.Poi) {
startingSegmentTime = time.segment[0]; startingSegmentTime = time.segment[0];
found = true; found = true;
@@ -1314,7 +1327,7 @@ function startSkipScheduleCheckingForStartSponsors() {
} }
if (!found) { if (!found) {
for (const time of sponsorTimesSubmitting) { for (const time of sponsorTimesSubmitting) {
if (time.segment[0] <= getVideo().currentTime && time.segment[0] > startingSegmentTime && time.segment[1] > getVideo().currentTime if (time.segment[0] <= getCurrentTime() && time.segment[0] > startingSegmentTime && time.segment[1] > getCurrentTime()
&& time.actionType !== ActionType.Poi) { && time.actionType !== ActionType.Poi) {
startingSegmentTime = time.segment[0]; startingSegmentTime = time.segment[0];
found = true; found = true;
@@ -1325,7 +1338,7 @@ function startSkipScheduleCheckingForStartSponsors() {
// For highlight category // For highlight category
const poiSegments = sponsorTimes const poiSegments = sponsorTimes
.filter((time) => time.segment[1] > getVideo().currentTime .filter((time) => time.segment[1] > getCurrentTime()
&& time.actionType === ActionType.Poi && time.hidden === SponsorHideType.Visible) && time.actionType === ActionType.Poi && time.hidden === SponsorHideType.Visible)
.sort((a, b) => b.segment[0] - a.segment[0]); .sort((a, b) => b.segment[0] - a.segment[0]);
for (const time of poiSegments) { for (const time of poiSegments) {
@@ -1336,7 +1349,7 @@ function startSkipScheduleCheckingForStartSponsors() {
skipTime: time.segment, skipTime: time.segment,
skippingSegments: [time], skippingSegments: [time],
openNotice: true, openNotice: true,
unskipTime: getVideo().currentTime unskipTime: getCurrentTime()
}); });
if (skipOption === CategorySkipOption.AutoSkip) break; if (skipOption === CategorySkipOption.AutoSkip) break;
} }
@@ -1344,7 +1357,9 @@ function startSkipScheduleCheckingForStartSponsors() {
const fullVideoSegment = sponsorTimes.filter((time) => time.actionType === ActionType.Full)[0]; const fullVideoSegment = sponsorTimes.filter((time) => time.actionType === ActionType.Full)[0];
if (fullVideoSegment) { if (fullVideoSegment) {
waitFor(() => categoryPill).then(() => {
categoryPill?.setSegment(fullVideoSegment); categoryPill?.setSegment(fullVideoSegment);
});
} }
if (startingSegmentTime !== -1) { if (startingSegmentTime !== -1) {
@@ -1385,7 +1400,7 @@ function updatePreviewBar(): void {
showLarger: segment.actionType === ActionType.Poi, showLarger: segment.actionType === ActionType.Poi,
description: segment.description, description: segment.description,
source: segment.source, source: segment.source,
requiredSegment: requiredSegment && (segment.UUID === requiredSegment || segment.UUID?.startsWith(requiredSegment)), requiredSegment: requiredSegment && (segment.UUID === requiredSegment || segment.UUID?.startsWith(requiredSegment) || requiredSegment.startsWith(segment.UUID)),
selectedSegment: selectedSegment && segment.UUID === selectedSegment selectedSegment: selectedSegment && segment.UUID === selectedSegment
}); });
}); });
@@ -1403,8 +1418,8 @@ function updatePreviewBar(): void {
}); });
}); });
previewBar.set(previewBarSegments.filter((segment) => segment.actionType !== ActionType.Full), getVideo()?.duration) previewBar.set(previewBarSegments.filter((segment) => segment.actionType !== ActionType.Full), getVideoDuration())
if (getVideo()) updateActiveSegment(getVideo().currentTime); if (getVideo()) updateActiveSegment(getCurrentTime());
if (Config.config.showTimeWithSkips) { if (Config.config.showTimeWithSkips) {
const skippedDuration = utils.getTimestampsDuration(previewBarSegments const skippedDuration = utils.getTimestampsDuration(previewBarSegments
@@ -1429,10 +1444,10 @@ async function channelIDChange(channelIDInfo: ChannelIDInfo) {
if (Config.config.forceChannelCheck && sponsorTimes?.length > 0) startSkipScheduleCheckingForStartSponsors(); if (Config.config.forceChannelCheck && sponsorTimes?.length > 0) startSkipScheduleCheckingForStartSponsors();
} }
function videoElementChange(newVideo: boolean): void { function videoElementChange(newVideo: boolean, video: HTMLVideoElement): void {
waitFor(() => Config.isReady()).then(() => { waitFor(() => Config.isReady()).then(() => {
if (newVideo) { if (newVideo) {
setupVideoListeners(); setupVideoListeners(video);
setupSkipButtonControlBar(); setupSkipButtonControlBar();
setupCategoryPill(); setupCategoryPill();
} }
@@ -1631,7 +1646,7 @@ function getStartTimes(sponsorTimes: SponsorTime[], includeIntersectingSegments:
*/ */
function previewTime(time: number, unpause = true) { function previewTime(time: number, unpause = true) {
previewedSegment = true; previewedSegment = true;
getVideo().currentTime = time; setCurrentTime(time);
// Unpause the video if needed // Unpause the video if needed
if (unpause && getVideo().paused){ if (unpause && getVideo().paused){
@@ -1663,7 +1678,7 @@ function sendTelemetryAndCount(skippingSegments: SponsorTime[], secondsSkipped:
counted = true; counted = true;
} }
if (fullSkip) asyncRequestToServer("POST", "/api/viewedVideoSponsorTime?UUID=" + segment.UUID); if (fullSkip) asyncRequestToServer("POST", "/api/viewedVideoSponsorTime?UUID=" + segment.UUID + "&videoID=" + getVideoID());
} }
} }
} }
@@ -1677,22 +1692,22 @@ function skipToTime({v, skipTime, skippingSegments, openNotice, forceAutoSkip, u
const isSubmittingSegment = sponsorTimesSubmitting.some((time) => time.segment === skippingSegments[0].segment); const isSubmittingSegment = sponsorTimesSubmitting.some((time) => time.segment === skippingSegments[0].segment);
if ((autoSkip || isSubmittingSegment) if ((autoSkip || isSubmittingSegment)
&& v.currentTime !== skipTime[1]) { && getCurrentTime() !== skipTime[1]) {
switch(skippingSegments[0].actionType) { switch(skippingSegments[0].actionType) {
case ActionType.Poi: case ActionType.Poi:
case ActionType.Skip: { case ActionType.Skip: {
// Fix for looped videos not working when skipping to the end #426 // Fix for looped videos not working when skipping to the end #426
// for some reason you also can't skip to 1 second before the end // for some reason you also can't skip to 1 second before the end
if (v.loop && v.duration > 1 && skipTime[1] >= v.duration - 1) { if (v.loop && getVideoDuration() > 1 && skipTime[1] >= getVideoDuration() - 1) {
v.currentTime = 0; setCurrentTime(0);
} else if (v.duration > 1 && skipTime[1] >= v.duration } else if (getVideoDuration() > 1 && skipTime[1] >= getVideoDuration()
&& (navigator.vendor === "Apple Computer, Inc." || isPlayingPlaylist())) { && (navigator.vendor === "Apple Computer, Inc." || isPlayingPlaylist())) {
// MacOS will loop otherwise #1027 // MacOS will loop otherwise #1027
// Sometimes playlists loop too #1804 // Sometimes playlists loop too #1804
v.currentTime = v.duration - 0.001; setCurrentTime(getVideoDuration() - 0.001);
} else if (v.duration > 1 && Math.abs(skipTime[1] - v.duration) < endTimeSkipBuffer } else if (getVideoDuration() > 1 && Math.abs(skipTime[1] - getVideoDuration()) < endTimeSkipBuffer
&& isFirefoxOrSafari() && !isSafari()) { && isFirefoxOrSafari() && !isSafari()) {
v.currentTime = v.duration; setCurrentTime(getVideoDuration());
} else { } else {
if (inMuteSegment(skipTime[1], true)) { if (inMuteSegment(skipTime[1], true)) {
// Make sure not to mute if skipping into a mute segment // Make sure not to mute if skipping into a mute segment
@@ -1700,7 +1715,7 @@ function skipToTime({v, skipTime, skippingSegments, openNotice, forceAutoSkip, u
videoMuted = true; videoMuted = true;
} }
v.currentTime = skipTime[1]; setCurrentTime(skipTime[1]);
} }
break; break;
@@ -1715,8 +1730,9 @@ function skipToTime({v, skipTime, skippingSegments, openNotice, forceAutoSkip, u
} }
} }
if (autoSkip && Config.config.audioNotificationOnSkip) { if (autoSkip && Config.config.audioNotificationOnSkip
const beep = new Audio(chrome.runtime.getURL("icons/beep.ogg")); && !isSubmittingSegment && !getVideo()?.muted) {
const beep = new Audio(chrome.runtime.getURL("icons/beep.oga"));
beep.volume = getVideo().volume * 0.1; beep.volume = getVideo().volume * 0.1;
const oldMetadata = navigator.mediaSession.metadata const oldMetadata = navigator.mediaSession.metadata
beep.play(); beep.play();
@@ -1732,11 +1748,13 @@ function skipToTime({v, skipTime, skippingSegments, openNotice, forceAutoSkip, u
if (!autoSkip if (!autoSkip
&& skippingSegments.length === 1 && skippingSegments.length === 1
&& skippingSegments[0].actionType === ActionType.Poi) { && skippingSegments[0].actionType === ActionType.Poi) {
waitFor(() => skipButtonControlBar).then(() => {
skipButtonControlBar.enable(skippingSegments[0]); skipButtonControlBar.enable(skippingSegments[0]);
if (isOnMobileYouTube() || Config.config.skipKeybind == null) skipButtonControlBar.setShowKeybindHint(false); if (isOnMobileYouTube() || Config.config.skipKeybind == null) skipButtonControlBar.setShowKeybindHint(false);
activeSkipKeybindElement?.setShowKeybindHint(false); activeSkipKeybindElement?.setShowKeybindHint(false);
activeSkipKeybindElement = skipButtonControlBar; activeSkipKeybindElement = skipButtonControlBar;
})
} else { } else {
if (openNotice) { if (openNotice) {
//send out the message saying that a sponsor message was skipped //send out the message saying that a sponsor message was skipped
@@ -1769,7 +1787,12 @@ function createSkipNotice(skippingSegments: SponsorTime[], autoSkip: boolean, un
} }
} }
const newSkipNotice = new SkipNotice(skippingSegments, autoSkip, skipNoticeContentContainer, unskipTime, startReskip); const upcomingNoticeShown = !!upcomingNotice && !upcomingNotice.closed;
const newSkipNotice = new SkipNotice(skippingSegments, autoSkip, skipNoticeContentContainer, () => {
upcomingNotice?.close();
upcomingNotice = null;
}, unskipTime, startReskip, upcomingNoticeShown);
if (isOnMobileYouTube() || Config.config.skipKeybind == null) newSkipNotice.setShowKeybindHint(false); if (isOnMobileYouTube() || Config.config.skipKeybind == null) newSkipNotice.setShowKeybindHint(false);
skipNotices.push(newSkipNotice); skipNotices.push(newSkipNotice);
@@ -1777,6 +1800,17 @@ function createSkipNotice(skippingSegments: SponsorTime[], autoSkip: boolean, un
activeSkipKeybindElement = newSkipNotice; activeSkipKeybindElement = newSkipNotice;
} }
function createUpcomingNotice(skippingSegments: SponsorTime[], timeLeft: number, autoSkip: boolean): void {
if (upcomingNotice
&& !upcomingNotice.closed
&& upcomingNotice.sameNotice(skippingSegments)) {
return;
}
upcomingNotice?.close();
upcomingNotice = new UpcomingNotice(skippingSegments, skipNoticeContentContainer, timeLeft / 1000, autoSkip);
}
function unskipSponsorTime(segment: SponsorTime, unskipTime: number = null, forceSeek = false) { function unskipSponsorTime(segment: SponsorTime, unskipTime: number = null, forceSeek = false) {
if (segment.actionType === ActionType.Mute) { if (segment.actionType === ActionType.Mute) {
getVideo().muted = false; getVideo().muted = false;
@@ -1785,7 +1819,7 @@ function unskipSponsorTime(segment: SponsorTime, unskipTime: number = null, forc
if (forceSeek || segment.actionType === ActionType.Skip) { if (forceSeek || segment.actionType === ActionType.Skip) {
//add a tiny bit of time to make sure it is not skipped again //add a tiny bit of time to make sure it is not skipped again
getVideo().currentTime = unskipTime ?? segment.segment[0] + 0.001; setCurrentTime(unskipTime ?? segment.segment[0] + 0.001);
} }
} }
@@ -1795,11 +1829,11 @@ function reskipSponsorTime(segment: SponsorTime, forceSeek = false) {
getVideo().muted = true; getVideo().muted = true;
videoMuted = true; videoMuted = true;
} else { } else {
const skippedTime = Math.max(segment.segment[1] - getVideo().currentTime, 0); const skippedTime = Math.max(segment.segment[1] - getCurrentTime(), 0);
const segmentDuration = segment.segment[1] - segment.segment[0]; const segmentDuration = segment.segment[1] - segment.segment[0];
const fullSkip = skippedTime / segmentDuration > manualSkipPercentCount; const fullSkip = skippedTime / segmentDuration > manualSkipPercentCount;
getVideo().currentTime = segment.segment[1]; setCurrentTime(segment.segment[1]);
sendTelemetryAndCount([segment], segment.actionType !== ActionType.Chapter ? skippedTime : 0, fullSkip); sendTelemetryAndCount([segment], segment.actionType !== ActionType.Chapter ? skippedTime : 0, fullSkip);
startSponsorSchedule(true, segment.segment[1], false); startSponsorSchedule(true, segment.segment[1], false);
} }
@@ -1949,13 +1983,14 @@ function getRealCurrentTime(): number {
if (playButtonSVGData === replaceSVGData) { if (playButtonSVGData === replaceSVGData) {
// At the end of the video // At the end of the video
return getVideo()?.duration; return getVideoDuration();
} else { } else {
return getVideo().currentTime; return getCurrentTime();
} }
} }
function startOrEndTimingNewSegment() { function startOrEndTimingNewSegment() {
verifyCurrentTime();
const roundedTime = Math.round((getRealCurrentTime() + Number.EPSILON) * 1000) / 1000; const roundedTime = Math.round((getRealCurrentTime() + Number.EPSILON) * 1000) / 1000;
if (!isSegmentCreationInProgress()) { if (!isSegmentCreationInProgress()) {
sponsorTimesSubmitting.push({ sponsorTimesSubmitting.push({
@@ -1980,7 +2015,7 @@ function startOrEndTimingNewSegment() {
Config.forceLocalUpdate("unsubmittedSegments"); Config.forceLocalUpdate("unsubmittedSegments");
// Make sure they know if someone has already submitted something it while they were watching // Make sure they know if someone has already submitted something it while they were watching
sponsorsLookup(); sponsorsLookup(true, true);
updateEditButtonsOnPlayer(); updateEditButtonsOnPlayer();
updateSponsorTimesSubmitting(false); updateSponsorTimesSubmitting(false);
@@ -2233,7 +2268,8 @@ async function voteAsync(type: number, UUID: SegmentUUID, category?: Category):
message: "submitVote", message: "submitVote",
type: type, type: type,
UUID: UUID, UUID: UUID,
category: category category: category,
videoID: getVideoID()
}, (response) => { }, (response) => {
if (response.successType === 1) { if (response.successType === 1) {
// Change the sponsor locally // Change the sponsor locally
@@ -2330,7 +2366,7 @@ async function sendSubmitMessage(): Promise<boolean> {
if (!previewedSegment if (!previewedSegment
&& !sponsorTimesSubmitting.every((segment) => && !sponsorTimesSubmitting.every((segment) =>
[ActionType.Full, ActionType.Chapter, ActionType.Poi].includes(segment.actionType) [ActionType.Full, ActionType.Chapter, ActionType.Poi].includes(segment.actionType)
|| segment.segment[1] >= getVideo()?.duration || segment.segment[1] >= getVideoDuration()
|| segment.segment[0] === 0)) { || segment.segment[0] === 0)) {
alert(`${chrome.i18n.getMessage("previewSegmentRequired")} ${keybindToString(Config.config.previewKeybind)}`); alert(`${chrome.i18n.getMessage("previewSegmentRequired")} ${keybindToString(Config.config.previewKeybind)}`);
return false; return false;
@@ -2342,8 +2378,8 @@ async function sendSubmitMessage(): Promise<boolean> {
//check if a sponsor exceeds the duration of the video //check if a sponsor exceeds the duration of the video
for (let i = 0; i < sponsorTimesSubmitting.length; i++) { for (let i = 0; i < sponsorTimesSubmitting.length; i++) {
if (sponsorTimesSubmitting[i].segment[1] > getVideo().duration) { if (sponsorTimesSubmitting[i].segment[1] > getVideoDuration()) {
sponsorTimesSubmitting[i].segment[1] = getVideo().duration; sponsorTimesSubmitting[i].segment[1] = getVideoDuration();
} }
} }
@@ -2368,7 +2404,7 @@ async function sendSubmitMessage(): Promise<boolean> {
videoID: getVideoID(), videoID: getVideoID(),
userID: Config.config.userID, userID: Config.config.userID,
segments: sponsorTimesSubmitting, segments: sponsorTimesSubmitting,
videoDuration: getVideo()?.duration, videoDuration: getVideoDuration(),
userAgent: `${chrome.runtime.id}/v${chrome.runtime.getManifest().version}` userAgent: `${chrome.runtime.id}/v${chrome.runtime.getManifest().version}`
}); });
@@ -2461,38 +2497,38 @@ function nextChapter(): void {
const chapters = previewBar.unfilteredChapterGroups?.filter((time) => [ActionType.Chapter, null].includes(time.actionType)); const chapters = previewBar.unfilteredChapterGroups?.filter((time) => [ActionType.Chapter, null].includes(time.actionType));
if (!chapters || chapters.length <= 0) return; if (!chapters || chapters.length <= 0) return;
lastNextChapterKeybind.time = getVideo().currentTime; lastNextChapterKeybind.time = getCurrentTime();
lastNextChapterKeybind.date = Date.now(); lastNextChapterKeybind.date = Date.now();
const nextChapter = chapters.findIndex((time) => time.segment[0] > getVideo().currentTime); const nextChapter = chapters.findIndex((time) => time.segment[0] > getCurrentTime());
if (nextChapter !== -1) { if (nextChapter !== -1) {
getVideo().currentTime = chapters[nextChapter].segment[0]; setCurrentTime(chapters[nextChapter].segment[0]);
} else { } else {
getVideo().currentTime = getVideo().duration; setCurrentTime(getVideoDuration());
} }
} }
function previousChapter(): void { function previousChapter(): void {
if (Date.now() - lastNextChapterKeybind.date < 3000) { if (Date.now() - lastNextChapterKeybind.date < 3000) {
getVideo().currentTime = lastNextChapterKeybind.time; setCurrentTime(lastNextChapterKeybind.time);
lastNextChapterKeybind.date = 0; lastNextChapterKeybind.date = 0;
return; return;
} }
const chapters = previewBar.unfilteredChapterGroups?.filter((time) => [ActionType.Chapter, null].includes(time.actionType)); const chapters = previewBar.unfilteredChapterGroups?.filter((time) => [ActionType.Chapter, null].includes(time.actionType));
if (!chapters || chapters.length <= 0) { if (!chapters || chapters.length <= 0) {
getVideo().currentTime = 0; setCurrentTime(0);
return; return;
} }
// subtract 5 seconds to allow skipping back to the previous chapter if close to start of // subtract 5 seconds to allow skipping back to the previous chapter if close to start of
// the current one // the current one
const nextChapter = chapters.findIndex((time) => time.segment[0] > getVideo().currentTime - Math.min(5, time.segment[1] - time.segment[0])); const nextChapter = chapters.findIndex((time) => time.segment[0] > getCurrentTime() - Math.min(5, time.segment[1] - time.segment[0]));
const previousChapter = nextChapter !== -1 ? (nextChapter - 1) : (chapters.length - 1); const previousChapter = nextChapter !== -1 ? (nextChapter - 1) : (chapters.length - 1);
if (previousChapter !== -1) { if (previousChapter !== -1) {
getVideo().currentTime = chapters[previousChapter].segment[0]; setCurrentTime(chapters[previousChapter].segment[0]);
} else { } else {
getVideo().currentTime = 0; setCurrentTime(0);
} }
} }
@@ -2517,8 +2553,10 @@ function addHotkeyListener(): void {
} }
function hotkeyListener(e: KeyboardEvent): void { function hotkeyListener(e: KeyboardEvent): void {
if (["textarea", "input"].includes(document.activeElement?.tagName?.toLowerCase()) if ((["textarea", "input"].includes(document.activeElement?.tagName?.toLowerCase())
|| document.activeElement?.id?.toLowerCase()?.includes("editable")) return; || (document.activeElement as HTMLElement)?.isContentEditable
|| document.activeElement?.id?.toLowerCase()?.match(/editable|input/))
&& document.hasFocus()) return;
const key: Keybind = { const key: Keybind = {
key: e.key, key: e.key,
@@ -2555,6 +2593,8 @@ function hotkeyListener(e: KeyboardEvent): void {
skipNotices.pop().close(); skipNotices.pop().close();
} }
upcomingNotice?.close();
upcomingNotice = null;
return; return;
} else if (keybindEquals(key, startSponsorKey)) { } else if (keybindEquals(key, startSponsorKey)) {
startOrEndTimingNewSegment(); startOrEndTimingNewSegment();
@@ -2563,6 +2603,8 @@ function hotkeyListener(e: KeyboardEvent): void {
submitSegments(); submitSegments();
return; return;
} else if (keybindEquals(key, openSubmissionMenuKey)) { } else if (keybindEquals(key, openSubmissionMenuKey)) {
e.preventDefault();
openSubmissionMenu(); openSubmissionMenu();
return; return;
} else if (keybindEquals(key, previewKey)) { } else if (keybindEquals(key, previewKey)) {
@@ -2577,16 +2619,6 @@ function hotkeyListener(e: KeyboardEvent): void {
previousChapter(); previousChapter();
return; return;
} }
//legacy - to preserve keybinds for skipKey, startSponsorKey and submitKey for people who set it before the update. (shouldn't be changed for future keybind options)
if (key.key == skipKey?.key && skipKey.code == null && !keybindEquals(Config.syncDefaults.skipKeybind, skipKey)) {
if (activeSkipKeybindElement)
activeSkipKeybindElement.toggleSkip.call(activeSkipKeybindElement);
} else if (key.key == startSponsorKey?.key && startSponsorKey.code == null && !keybindEquals(Config.syncDefaults.startSponsorKeybind, startSponsorKey)) {
startOrEndTimingNewSegment();
} else if (key.key == submitKey?.key && submitKey.code == null && !keybindEquals(Config.syncDefaults.submitKeybind, submitKey)) {
openSubmissionMenu();
}
} }
/** /**
@@ -2634,11 +2666,11 @@ function showTimeWithoutSkips(skippedDuration: number): void {
} }
// YouTube player time display // YouTube player time display
const displayClass = const selector =
isOnInvidious() ? "vjs-duration" : isOnInvidious() ? ".vjs-duration" :
isOnMobileYouTube() ? "ytm-time-display" : isOnMobileYouTube() ? ".ytwPlayerTimeDisplayContent" :
"ytp-time-display.notranslate"; ".ytp-time-display.notranslate .ytp-time-wrapper";
const display = document.querySelector(`.${displayClass}`); const display = document.querySelector(selector);
if (!display) return; if (!display) return;
const durationID = "sponsorBlockDurationAfterSkips"; const durationID = "sponsorBlockDurationAfterSkips";
@@ -2648,12 +2680,16 @@ function showTimeWithoutSkips(skippedDuration: number): void {
if (duration === null) { if (duration === null) {
duration = document.createElement('span'); duration = document.createElement('span');
duration.id = durationID; duration.id = durationID;
if (!isOnInvidious()) duration.classList.add(displayClass);
if (isOnMobileYouTube()) {
duration.style.paddingLeft = "4px";
display.insertBefore(duration, display.lastChild);
} else {
display.appendChild(duration); display.appendChild(duration);
} }
}
const durationAfterSkips = getFormattedTime(getVideo()?.duration - skippedDuration); const durationAfterSkips = getFormattedTime(getVideoDuration() - skippedDuration);
duration.innerText = (durationAfterSkips == null || skippedDuration <= 0) ? "" : " (" + durationAfterSkips + ")"; duration.innerText = (durationAfterSkips == null || skippedDuration <= 0) ? "" : " (" + durationAfterSkips + ")";
} }
@@ -2697,6 +2733,10 @@ function setCategoryColorCSSVariables() {
if (!styleContainer) { if (!styleContainer) {
styleContainer = document.createElement("style"); styleContainer = document.createElement("style");
styleContainer.id = "sbCategoryColorStyle"; styleContainer.id = "sbCategoryColorStyle";
if (isVorapisInstalled()) {
// Vorapi deletes styles
styleContainer.className = 'stylus';
}
const head = (document.head || document.documentElement); const head = (document.head || document.documentElement);
head.appendChild(styleContainer) head.appendChild(styleContainer)

15
src/globals.d.ts vendored
View File

@@ -1,19 +1,4 @@
import { SBObject } from "./config"; import { SBObject } from "./config";
declare global { declare global {
interface Window { SB: SBObject } interface Window { SB: SBObject }
// Remove this once the API becomes stable and types are shipped in @types/chrome
namespace chrome {
namespace declarativeContent {
export interface RequestContentScriptOptions {
allFrames?: boolean;
css?: string[];
instanceType?: "declarativeContent.RequestContentScript";
js?: string[];
matchAboutBlanck?: boolean;
}
export class RequestContentScript {
constructor(options: RequestContentScriptOptions);
}
}
}
} }

View File

@@ -13,7 +13,8 @@ import { DEFAULT_CATEGORY, shortCategoryName } from "../utils/categoryUtils";
import { normalizeChapterName } from "../utils/exporter"; import { normalizeChapterName } from "../utils/exporter";
import { findValidElement } from "../../maze-utils/src/dom"; import { findValidElement } from "../../maze-utils/src/dom";
import { addCleanupListener } from "../../maze-utils/src/cleanup"; import { addCleanupListener } from "../../maze-utils/src/cleanup";
import { isVisible } from "../utils/pageUtils"; import { hasAutogeneratedChapters, isVisible } from "../utils/pageUtils";
import { isVorapisInstalled } from "../utils/compatibility";
const TOOLTIP_VISIBLE_CLASS = 'sponsorCategoryTooltipVisible'; const TOOLTIP_VISIBLE_CLASS = 'sponsorCategoryTooltipVisible';
const MIN_CHAPTER_SIZE = 0.003; const MIN_CHAPTER_SIZE = 0.003;
@@ -51,6 +52,7 @@ class PreviewBar {
progressBar: HTMLElement; progressBar: HTMLElement;
segments: PreviewBarSegment[] = []; segments: PreviewBarSegment[] = [];
hasYouTubeChapters = false;
existingChapters: PreviewBarSegment[] = []; existingChapters: PreviewBarSegment[] = [];
videoDuration = 0; videoDuration = 0;
updateExistingChapters: () => void; updateExistingChapters: () => void;
@@ -100,13 +102,15 @@ class PreviewBar {
this.chapterTooltip.className = "ytp-tooltip-title sponsorCategoryTooltip"; this.chapterTooltip.className = "ytp-tooltip-title sponsorCategoryTooltip";
// global chaper tooltip or duration tooltip // global chaper tooltip or duration tooltip
const tooltipTextWrapper = document.querySelector(".ytp-tooltip-text-wrapper") ?? document.querySelector("#progress-bar-container.ytk-player > #hover-time-info"); // YT, Vorapis, unknown
const originalTooltip = tooltipTextWrapper.querySelector(".ytp-tooltip-title:not(.sponsorCategoryTooltip)") as HTMLElement; const tooltipTextWrapper = document.querySelector(".ytp-tooltip-text-wrapper, .ytp-progress-tooltip-text-container") ?? document.querySelector("#progress-bar-container.ytk-player > #hover-time-info");
const originalTooltip = tooltipTextWrapper.querySelector(".ytp-tooltip-title:not(.sponsorCategoryTooltip), .ytp-progress-tooltip-text:not(.sponsorCategoryTooltip)") as HTMLElement;
if (!tooltipTextWrapper || !tooltipTextWrapper.parentElement) return; if (!tooltipTextWrapper || !tooltipTextWrapper.parentElement) return;
// Grab the tooltip from the text wrapper as the tooltip doesn't have its classes on init // Grab the tooltip from the text wrapper as the tooltip doesn't have its classes on init
this.categoryTooltipContainer = tooltipTextWrapper.parentElement; this.categoryTooltipContainer = tooltipTextWrapper.parentElement;
const titleTooltip = tooltipTextWrapper.querySelector(".ytp-tooltip-title") as HTMLElement; // YT, Vorapis
const titleTooltip = tooltipTextWrapper.querySelector(".ytp-tooltip-title, .ytp-progress-tooltip-text") as HTMLElement;
if (!this.categoryTooltipContainer || !titleTooltip) return; if (!this.categoryTooltipContainer || !titleTooltip) return;
tooltipTextWrapper.insertBefore(this.categoryTooltip, titleTooltip.nextSibling); tooltipTextWrapper.insertBefore(this.categoryTooltip, titleTooltip.nextSibling);
@@ -128,12 +132,12 @@ class PreviewBar {
seekBar.addEventListener("mousemove", (e: MouseEvent) => { seekBar.addEventListener("mousemove", (e: MouseEvent) => {
if (!mouseOnSeekBar || !this.categoryTooltip || !this.categoryTooltipContainer || !chrome.runtime?.id) return; if (!mouseOnSeekBar || !this.categoryTooltip || !this.categoryTooltipContainer || !chrome.runtime?.id) return;
let noYoutubeChapters = !!tooltipTextWrapper.querySelector(".ytp-tooltip-text.ytp-tooltip-text-no-title"); let noYoutubeChapters = !!tooltipTextWrapper.querySelector(".ytp-tooltip-text.ytp-tooltip-text-no-title, .ytp-progress-tooltip-timestamp");
const timeInSeconds = this.decimalToTime((e.clientX - seekBar.getBoundingClientRect().x) / seekBar.clientWidth); const timeInSeconds = this.decimalToTime((e.clientX - seekBar.getBoundingClientRect().x) / seekBar.clientWidth);
// Find the segment at that location, using the shortest if multiple found // Find the segment at that location, using the shortest if multiple found
const [normalSegments, chapterSegments] = const [normalSegments, chapterSegments] =
partition(this.segments.filter((s) => s.source !== SponsorSourceType.YouTube), partition(this.segments,
(segment) => segment.actionType !== ActionType.Chapter); (segment) => segment.actionType !== ActionType.Chapter);
let mainSegment = this.getSmallestSegment(timeInSeconds, normalSegments, "normal"); let mainSegment = this.getSmallestSegment(timeInSeconds, normalSegments, "normal");
let secondarySegment = this.getSmallestSegment(timeInSeconds, chapterSegments, "chapter"); let secondarySegment = this.getSmallestSegment(timeInSeconds, chapterSegments, "chapter");
@@ -142,13 +146,28 @@ class PreviewBar {
secondarySegment = this.getSmallestSegment(timeInSeconds, chapterSegments.filter((s) => s !== secondarySegment)); secondarySegment = this.getSmallestSegment(timeInSeconds, chapterSegments.filter((s) => s !== secondarySegment));
} }
const hasAYouTubeChapterRemoved = this.hasYouTubeChapters
|| (!Config.config.showAutogeneratedChapters && hasAutogeneratedChapters());
if (hasAYouTubeChapterRemoved) {
// Hide original tooltip if some chapter has been filtered out
originalTooltip.style.display = "none";
noYoutubeChapters = true;
originalTooltip.classList.add("sponsorTooltipHasYTChapters");
} else {
originalTooltip.classList.remove("sponsorTooltipHasYTChapters");
}
if (mainSegment === null && secondarySegment === null) { if (mainSegment === null && secondarySegment === null) {
if (!hasAYouTubeChapterRemoved) {
this.categoryTooltipContainer.classList.remove(TOOLTIP_VISIBLE_CLASS); this.categoryTooltipContainer.classList.remove(TOOLTIP_VISIBLE_CLASS);
originalTooltip.style.removeProperty("display"); originalTooltip.style.removeProperty("display");
}
} else { } else {
this.categoryTooltipContainer.classList.add(TOOLTIP_VISIBLE_CLASS); this.categoryTooltipContainer.classList.add(TOOLTIP_VISIBLE_CLASS);
if (mainSegment !== null && secondarySegment !== null) { if (mainSegment !== null && secondarySegment !== null) {
this.categoryTooltipContainer.classList.add("sponsorTwoTooltips"); this.categoryTooltipContainer.classList.add("sponsorTwoTooltips");
originalTooltip.classList.remove("sponsorTooltipHasYTChapters");
} else { } else {
this.categoryTooltipContainer.classList.remove("sponsorTwoTooltips"); this.categoryTooltipContainer.classList.remove("sponsorTwoTooltips");
} }
@@ -156,6 +175,11 @@ class PreviewBar {
this.setTooltipTitle(mainSegment, this.categoryTooltip); this.setTooltipTitle(mainSegment, this.categoryTooltip);
this.setTooltipTitle(secondarySegment, this.chapterTooltip); this.setTooltipTitle(secondarySegment, this.chapterTooltip);
if (isVorapisInstalled()) {
const tooltipParent = tooltipTextWrapper.parentElement!;
tooltipParent.classList.add("with-text");
}
if (normalizeChapterName(originalTooltip.textContent) === normalizeChapterName(this.categoryTooltip.textContent) if (normalizeChapterName(originalTooltip.textContent) === normalizeChapterName(this.categoryTooltip.textContent)
|| normalizeChapterName(originalTooltip.textContent) === normalizeChapterName(this.chapterTooltip.textContent)) { || normalizeChapterName(originalTooltip.textContent) === normalizeChapterName(this.chapterTooltip.textContent)) {
if (originalTooltip.style.display !== "none") originalTooltip.style.display = "none"; if (originalTooltip.style.display !== "none") originalTooltip.style.display = "none";
@@ -164,16 +188,16 @@ class PreviewBar {
originalTooltip.style.removeProperty("display"); originalTooltip.style.removeProperty("display");
} }
// Used to prevent overlapping
this.categoryTooltip.classList.toggle("ytp-tooltip-text-no-title", noYoutubeChapters);
this.chapterTooltip.classList.toggle("ytp-tooltip-text-no-title", noYoutubeChapters);
// To prevent offset issue // To prevent offset issue
this.categoryTooltip.style.right = titleTooltip.style.right; this.categoryTooltip.style.right = titleTooltip.style.right;
this.chapterTooltip.style.right = titleTooltip.style.right; this.chapterTooltip.style.right = titleTooltip.style.right;
this.categoryTooltip.style.textAlign = titleTooltip.style.textAlign; this.categoryTooltip.style.textAlign = titleTooltip.style.textAlign;
this.chapterTooltip.style.textAlign = titleTooltip.style.textAlign; this.chapterTooltip.style.textAlign = titleTooltip.style.textAlign;
} }
// Used to prevent overlapping
this.categoryTooltip.classList.toggle("ytp-tooltip-text-no-title", noYoutubeChapters);
this.chapterTooltip.classList.toggle("ytp-tooltip-text-no-title", noYoutubeChapters);
}); });
} }
@@ -197,7 +221,6 @@ class PreviewBar {
if (this.onMobileYouTube) { if (this.onMobileYouTube) {
this.container.style.transform = "none"; this.container.style.transform = "none";
this.container.style.height = "var(--yt-progress-bar-height)";
} else if (!this.onInvidious) { } else if (!this.onInvidious) {
this.container.classList.add("sbNotInvidious"); this.container.classList.add("sbNotInvidious");
} }
@@ -225,7 +248,22 @@ class PreviewBar {
set(segments: PreviewBarSegment[], videoDuration: number): void { set(segments: PreviewBarSegment[], videoDuration: number): void {
this.segments = segments ?? []; this.segments = segments ?? [];
this.videoDuration = videoDuration ?? 0; this.videoDuration = videoDuration ?? 0;
this.hasYouTubeChapters = segments.some((segment) => segment.source === SponsorSourceType.YouTube);
// Remove unnecessary original chapters if submitted replacements exist
for (const chapter of this.segments.filter((s) => s.actionType === ActionType.Chapter && s.source === SponsorSourceType.Server)) {
const segmentDuration = chapter.segment[1] - chapter.segment[0];
const duplicate = this.segments.find((s) => s.actionType === ActionType.Chapter
&& s.source === SponsorSourceType.YouTube
&& Math.abs(s.segment[0] - chapter.segment[0]) < Math.min(3, segmentDuration / 3)
&& Math.abs(s.segment[1] - chapter.segment[1]) < Math.min(3, segmentDuration / 3));
if (duplicate) {
const index = this.segments.indexOf(duplicate);
this.segments.splice(index, 1);
}
}
this.updatePageElements(); this.updatePageElements();
// Sometimes video duration is inaccurate, pull from accessibility info // Sometimes video duration is inaccurate, pull from accessibility info
@@ -239,7 +277,8 @@ class PreviewBar {
} }
private updatePageElements(): void { private updatePageElements(): void {
const allProgressBars = document.querySelectorAll(".ytp-progress-bar") as NodeListOf<HTMLElement>; // YT, Vorapis v3
const allProgressBars = document.querySelectorAll(".ytp-progress-bar, .ytp-progress-bar-container > .html5-progress-bar > .ytp-progress-list") as NodeListOf<HTMLElement>;
this.progressBar = findValidElement(allProgressBars) ?? allProgressBars?.[0]; this.progressBar = findValidElement(allProgressBars) ?? allProgressBars?.[0];
if (this.progressBar) { if (this.progressBar) {
@@ -344,10 +383,12 @@ class PreviewBar {
this.unfilteredChapterGroups = this.createChapterRenderGroups(segments); this.unfilteredChapterGroups = this.createChapterRenderGroups(segments);
} }
if (segments.every((segments) => segments.source === SponsorSourceType.YouTube) if ((segments.every((segments) => segments.source === SponsorSourceType.YouTube)
|| (!Config.config.renderSegmentsAsChapters || (!Config.config.renderSegmentsAsChapters
&& segments.every((segment) => segment.actionType !== ActionType.Chapter && segments.every((segment) => segment.actionType !== ActionType.Chapter
|| segment.source === SponsorSourceType.YouTube))) { || segment.source === SponsorSourceType.YouTube)))
&& !(hasAutogeneratedChapters() && !Config.config.showAutogeneratedChapters)) {
if (this.customChaptersBar) this.customChaptersBar.style.display = "none"; if (this.customChaptersBar) this.customChaptersBar.style.display = "none";
this.originalChapterBar.style.removeProperty("display"); this.originalChapterBar.style.removeProperty("display");
return; return;
@@ -373,6 +414,15 @@ class PreviewBar {
this.chapterGroups = this.unfilteredChapterGroups; this.chapterGroups = this.unfilteredChapterGroups;
} }
if (this.chapterGroups.length === 0 && !Config.config.showAutogeneratedChapters && hasAutogeneratedChapters()) {
// Add placeholder chapter group for whole video
this.chapterGroups = [{
segment: [0, this.videoDuration],
originalDuration: 0,
actionType: null
}];
}
if (!this.chapterGroups || this.chapterGroups.length <= 0) { if (!this.chapterGroups || this.chapterGroups.length <= 0) {
if (this.customChaptersBar) this.customChaptersBar.style.display = "none"; if (this.customChaptersBar) this.customChaptersBar.style.display = "none";
this.originalChapterBar.style.removeProperty("display"); this.originalChapterBar.style.removeProperty("display");
@@ -648,8 +698,21 @@ class PreviewBar {
if (changedData.scale !== null) { if (changedData.scale !== null) {
const transformScale = (changedData.scale) / progressBar.clientWidth; const transformScale = (changedData.scale) / progressBar.clientWidth;
const scale = Math.max(0, Math.min(1 - calculatedLeft, (transformScale - cursor) / fullSectionWidth - calculatedLeft));
customChangedElement.style.transform = customChangedElement.style.transform =
`scaleX(${Math.max(0, Math.min(1 - calculatedLeft, (transformScale - cursor) / fullSectionWidth - calculatedLeft))}`; `scaleX(${scale})`;
if (customChangedElement.style.backgroundSize) {
const backgroundSize = Math.max(changedData.scale / scale, fullSectionWidth * progressBar.clientWidth);
customChangedElement.style.backgroundSize = `${backgroundSize}px`;
if (changedData.scale < (cursor + fullSectionWidth) * progressBar.clientWidth) {
customChangedElement.style.backgroundPosition = `-${backgroundSize - fullSectionWidth * progressBar.clientWidth}px`;
} else {
// Passed this section
customChangedElement.style.backgroundPosition = `-${cursor * progressBar.clientWidth}px`;
}
}
if (firstUpdate) { if (firstUpdate) {
customChangedElement.style.transition = "none"; customChangedElement.style.transition = "none";
setTimeout(() => customChangedElement.style.removeProperty("transition"), 50); setTimeout(() => customChangedElement.style.removeProperty("transition"), 50);
@@ -752,7 +815,8 @@ class PreviewBar {
updateChapterText(segments: SponsorTime[], submittingSegments: SponsorTime[], currentTime: number): SponsorTime[] { updateChapterText(segments: SponsorTime[], submittingSegments: SponsorTime[], currentTime: number): SponsorTime[] {
if (!Config.config.showSegmentNameInChapterBar if (!Config.config.showSegmentNameInChapterBar
|| Config.config.disableSkipping || Config.config.disableSkipping
|| ((!segments || segments.length <= 0) && submittingSegments?.length <= 0)) { || ((!segments || segments.length <= 0) && submittingSegments?.length <= 0
&& (Config.config.showAutogeneratedChapters || !hasAutogeneratedChapters()))) {
const chaptersContainer = this.getChaptersContainer(); const chaptersContainer = this.getChaptersContainer();
if (chaptersContainer) { if (chaptersContainer) {
chaptersContainer.querySelector(".sponsorChapterText")?.remove(); chaptersContainer.querySelector(".sponsorChapterText")?.remove();
@@ -792,8 +856,14 @@ class PreviewBar {
return -1; return -1;
} else if (a.actionType !== ActionType.Chapter && b.actionType === ActionType.Chapter) { } else if (a.actionType !== ActionType.Chapter && b.actionType === ActionType.Chapter) {
return 1; return 1;
} else if (a.actionType === ActionType.Chapter && b.actionType === ActionType.Chapter
&& a.source === SponsorSourceType.Server && b.source !== SponsorSourceType.Server) {
return -0.5;
} else if (a.actionType === ActionType.Chapter && b.actionType === ActionType.Chapter
&& a.source !== SponsorSourceType.Server && b.source === SponsorSourceType.Server) {
return 0.5;
} else { } else {
return (b.segment[0] - a.segment[0]); return (b.segment[0] - a.segment[0]) * 4;
} }
})[0]; })[0];
@@ -834,6 +904,18 @@ class PreviewBar {
} else { } else {
this.chapterVote.setVisibility(false); this.chapterVote.setVisibility(false);
} }
} else if (!Config.config.showAutogeneratedChapters && hasAutogeneratedChapters()) {
// Keep original hidden
chaptersContainer.querySelector(".sponsorChapterText")?.remove();
const chapterTitle = chaptersContainer.querySelector(".ytp-chapter-title-content") as HTMLDivElement;
chapterTitle.style.display = "none";
chaptersContainer.classList.remove("sponsorblock-chapter-visible");
const titlePrefixDot = chaptersContainer.querySelector(".ytp-chapter-title-prefix") as HTMLElement;
if (titlePrefixDot) titlePrefixDot.style.display = "none";
this.chapterVote.setVisibility(false);
} else { } else {
chaptersContainer.querySelector(".sponsorChapterText")?.remove(); chaptersContainer.querySelector(".sponsorChapterText")?.remove();
const chapterTitle = chaptersContainer.querySelector(".ytp-chapter-title-content") as HTMLDivElement; const chapterTitle = chaptersContainer.querySelector(".ytp-chapter-title-content") as HTMLDivElement;

View File

@@ -17,7 +17,8 @@ interface DefaultMessage {
| "isChannelWhitelisted" | "isChannelWhitelisted"
| "submitTimes" | "submitTimes"
| "refreshSegments" | "refreshSegments"
| "closePopup"; | "closePopup"
| "getLogs";
} }
interface BoolValueMessage { interface BoolValueMessage {
@@ -104,7 +105,8 @@ export type MessageResponse =
| Record<string, never> // empty object response {} | Record<string, never> // empty object response {}
| VoteResponse | VoteResponse
| ImportSegmentsResponse | ImportSegmentsResponse
| RefreshSegmentsResponse; | RefreshSegmentsResponse
| LogResponse;
export interface VoteResponse { export interface VoteResponse {
successType: number; successType: number;
@@ -120,6 +122,11 @@ export interface RefreshSegmentsResponse {
hasVideo: boolean; hasVideo: boolean;
} }
export interface LogResponse {
debug: string[];
warn: string[];
}
export interface TimeUpdateMessage { export interface TimeUpdateMessage {
message: "time"; message: "time";
time: number; time: number;

View File

@@ -1,8 +1,7 @@
import * as React from "react"; import * as React from "react";
import { createRoot } from 'react-dom/client'; import { createRoot } from 'react-dom/client';
import Config from "./config"; import Config, { generateDebugDetails } from "./config";
import * as CompileConfig from "../config.json";
import * as invidiousList from "../ci/invidiouslist.json"; import * as invidiousList from "../ci/invidiouslist.json";
// Make the config public for debugging purposes // Make the config public for debugging purposes
@@ -698,26 +697,8 @@ function validateServerAddress(input: string): string {
} }
function copyDebugOutputToClipboard() { function copyDebugOutputToClipboard() {
// Build output debug information object
const output = {
debug: {
userAgent: navigator.userAgent,
platform: navigator.platform,
language: navigator.language,
extensionVersion: chrome.runtime.getManifest().version
},
config: JSON.parse(JSON.stringify(Config.cachedSyncConfig)) // Deep clone config object
};
// Sanitise sensitive user config values
delete output.config.userID;
output.config.serverAddress = (output.config.serverAddress === CompileConfig.serverAddress)
? "Default server address" : "Custom server address";
output.config.invidiousInstances = output.config.invidiousInstances.length;
output.config.whitelistedChannels = output.config.whitelistedChannels.length;
// Copy object to clipboard // Copy object to clipboard
navigator.clipboard.writeText(JSON.stringify(output, null, 4)) navigator.clipboard.writeText(generateDebugDetails())
.then(() => { .then(() => {
alert(chrome.i18n.getMessage("copyDebugInformationComplete")); alert(chrome.i18n.getMessage("copyDebugInformationComplete"));
}) })

View File

@@ -1,4 +1,4 @@
import Config from "./config"; import Config, { generateDebugDetails } from "./config";
import Utils from "./utils"; import Utils from "./utils";
import { import {
@@ -12,6 +12,7 @@ import {
GetChannelIDResponse, GetChannelIDResponse,
IsChannelWhitelistedResponse, IsChannelWhitelistedResponse,
IsInfoFoundMessageResponse, IsInfoFoundMessageResponse,
LogResponse,
Message, Message,
MessageResponse, MessageResponse,
PopupMessage, PopupMessage,
@@ -184,7 +185,8 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
"exportSegmentsButton", "exportSegmentsButton",
"importSegmentsMenu", "importSegmentsMenu",
"importSegmentsText", "importSegmentsText",
"importSegmentsSubmit" "importSegmentsSubmit",
"debugLogs"
].forEach(id => PageElements[id] = document.getElementById(id)); ].forEach(id => PageElements[id] = document.getElementById(id));
@@ -255,6 +257,7 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
PageElements.helpButton.addEventListener("click", openHelp); PageElements.helpButton.addEventListener("click", openHelp);
PageElements.refreshSegmentsButton.addEventListener("click", refreshSegments); PageElements.refreshSegmentsButton.addEventListener("click", refreshSegments);
PageElements.sbPopupIconCopyUserID.addEventListener("click", async () => copyToClipboard(await getHash(Config.config.userID))); PageElements.sbPopupIconCopyUserID.addEventListener("click", async () => copyToClipboard(await getHash(Config.config.userID)));
PageElements.debugLogs.addEventListener("click", copyDebgLogs);
// Forward click events // Forward click events
if (window !== window.top) { if (window !== window.top) {
@@ -303,7 +306,7 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
PageElements.showNoticeAgain.style.display = "unset"; PageElements.showNoticeAgain.style.display = "unset";
} }
const values = ["userName", "viewCount", "minutesSaved", "vip", "permissions"]; const values = ["userName", "viewCount", "minutesSaved", "vip", "permissions", "segmentCount"];
asyncRequestToServer("GET", "/api/userInfo", { asyncRequestToServer("GET", "/api/userInfo", {
publicUserID: await getHash(Config.config.userID), publicUserID: await getHash(Config.config.userID),
@@ -336,16 +339,18 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
PageElements.sponsorTimesOthersTimeSavedDisplay.innerText = getFormattedHours(minutesSaved); PageElements.sponsorTimesOthersTimeSavedDisplay.innerText = getFormattedHours(minutesSaved);
} }
//get the amount of times this user has contributed and display it to thank them
PageElements.sponsorTimesContributionsDisplay.innerText = Math.max(Config.config.sponsorTimesContributed ?? 0, userInfo.segmentCount).toLocaleString();
PageElements.sponsorTimesContributionsContainer.classList.remove("hidden");
PageElements.sponsorTimesOthersTimeSavedEndWord.innerText = chrome.i18n.getMessage("minsLower");
Config.config.isVip = userInfo.vip; Config.config.isVip = userInfo.vip;
Config.config.permissions = userInfo.permissions; Config.config.permissions = userInfo.permissions;
} }
}); });
//get the amount of times this user has contributed and display it to thank them
if (Config.config.sponsorTimesContributed != undefined) {
PageElements.sponsorTimesContributionsDisplay.innerText = Config.config.sponsorTimesContributed.toLocaleString();
PageElements.sponsorTimesContributionsContainer.classList.remove("hidden");
}
//get the amount of times this user has skipped a sponsor //get the amount of times this user has skipped a sponsor
if (Config.config.skipCount != undefined) { if (Config.config.skipCount != undefined) {
@@ -676,9 +681,22 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
uuidButton.className = "voteButton"; uuidButton.className = "voteButton";
uuidButton.src = chrome.runtime.getURL("icons/clipboard.svg"); uuidButton.src = chrome.runtime.getURL("icons/clipboard.svg");
uuidButton.title = chrome.i18n.getMessage("copySegmentID"); uuidButton.title = chrome.i18n.getMessage("copySegmentID");
uuidButton.addEventListener("click", () => { uuidButton.addEventListener("click", async () => {
copyToClipboard(UUID);
const stopAnimation = AnimationUtils.applyLoadingAnimation(uuidButton, 0.3); const stopAnimation = AnimationUtils.applyLoadingAnimation(uuidButton, 0.3);
if (UUID.length > 60) {
copyToClipboard(UUID);
} else {
const segmentIDData = await asyncRequestToServer("GET", "/api/segmentID", {
UUID: UUID,
videoID: currentVideoID
});
if (segmentIDData.ok && segmentIDData.responseText) {
copyToClipboard(segmentIDData.responseText);
}
}
stopAnimation(); stopAnimation();
}); });
@@ -1141,6 +1159,12 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
} }
} }
function copyDebgLogs() {
sendTabMessage({ message: "getLogs" }, (logs: LogResponse) => {
copyToClipboard(`${generateDebugDetails()}\n\nWarn:\n${logs.warn.join("\n")}\n\nDebug:\n${logs.debug.join("\n")}`);
});
}
function onMessage(msg: PopupMessage) { function onMessage(msg: PopupMessage) {
switch (msg.message) { switch (msg.message) {
case "time": case "time":

View File

@@ -119,6 +119,8 @@ export class CategoryPill {
} }
async setSegment(segment: SponsorTime): Promise<void> { async setSegment(segment: SponsorTime): Promise<void> {
await waitFor(() => this.ref.current);
if (this.ref.current?.state?.segment !== segment) { if (this.ref.current?.state?.segment !== segment) {
const newState = { const newState = {
segment, segment,

View File

@@ -8,6 +8,7 @@ const utils = new Utils();
import { ContentContainer } from "../types"; import { ContentContainer } from "../types";
import NoticeTextSelectionComponent from "../components/NoticeTextSectionComponent"; import NoticeTextSelectionComponent from "../components/NoticeTextSectionComponent";
import { ButtonListener } from "../../maze-utils/src/components/component-types"; import { ButtonListener } from "../../maze-utils/src/components/component-types";
import { getVideo } from "../../maze-utils/src/video";
export interface TextBox { export interface TextBox {
icon: string; icon: string;
@@ -75,7 +76,7 @@ export default class GenericNotice {
{options.textBoxes?.length > 0 ? {options.textBoxes?.length > 0 ?
<tr id={"sponsorSkipNoticeMiddleRow" + this.idSuffix} <tr id={"sponsorSkipNoticeMiddleRow" + this.idSuffix}
className="sponsorTimeMessagesRow" className="sponsorTimeMessagesRow"
style={{maxHeight: this.contentContainer ? (this.contentContainer().v.offsetHeight - 200) + "px" : null}}> style={{maxHeight: getVideo() ? (getVideo().offsetHeight - 200) + "px" : null}}>
<td style={{width: "100%"}}> <td style={{width: "100%"}}>
{this.getMessageBoxes(this.idSuffix, options.textBoxes)} {this.getMessageBoxes(this.idSuffix, options.textBoxes)}
</td> </td>

View File

@@ -20,7 +20,7 @@ class SkipNotice {
skipNoticeRef: React.MutableRefObject<SkipNoticeComponent>; skipNoticeRef: React.MutableRefObject<SkipNoticeComponent>;
root: Root; root: Root;
constructor(segments: SponsorTime[], autoSkip = false, contentContainer: ContentContainer, unskipTime: number = null, startReskip = false) { constructor(segments: SponsorTime[], autoSkip = false, contentContainer: ContentContainer, componentDidMount: () => void, unskipTime: number = null, startReskip = false, upcomingNoticeShown: boolean) {
this.skipNoticeRef = React.createRef(); this.skipNoticeRef = React.createRef();
this.segments = segments; this.segments = segments;
@@ -53,7 +53,9 @@ class SkipNotice {
closeListener={() => this.close()} closeListener={() => this.close()}
smaller={Config.config.noticeVisibilityMode >= NoticeVisbilityMode.MiniForAll smaller={Config.config.noticeVisibilityMode >= NoticeVisbilityMode.MiniForAll
|| (Config.config.noticeVisibilityMode >= NoticeVisbilityMode.MiniForAutoSkip && autoSkip)} || (Config.config.noticeVisibilityMode >= NoticeVisbilityMode.MiniForAutoSkip && autoSkip)}
unskipTime={unskipTime} /> fadeIn={!upcomingNoticeShown}
unskipTime={unskipTime}
componentDidMount={componentDidMount} />
); );
} }

View File

@@ -0,0 +1,66 @@
import * as React from "react";
import { createRoot, Root } from "react-dom/client";
import { ContentContainer, SponsorTime } from "../types";
import Utils from "../utils";
import SkipNoticeComponent from "../components/SkipNoticeComponent";
const utils = new Utils();
class UpcomingNotice {
segments: SponsorTime[];
// Contains functions and variables from the content script needed by the skip notice
contentContainer: ContentContainer;
noticeElement: HTMLDivElement;
upcomingNoticeRef: React.MutableRefObject<SkipNoticeComponent>;
root: Root;
closed = false;
constructor(segments: SponsorTime[], contentContainer: ContentContainer, timeLeft: number, autoSkip: boolean) {
this.upcomingNoticeRef = React.createRef();
this.segments = segments;
this.contentContainer = contentContainer;
const referenceNode = utils.findReferenceNode();
this.noticeElement = document.createElement("div");
this.noticeElement.className = "sponsorSkipNoticeContainer";
referenceNode.prepend(this.noticeElement);
this.root = createRoot(this.noticeElement);
this.root.render(
<SkipNoticeComponent segments={segments}
autoSkip={autoSkip}
upcomingNotice={true}
contentContainer={contentContainer}
ref={this.upcomingNoticeRef}
closeListener={() => this.close()}
smaller={true}
fadeIn={true}
maxCountdownTime={timeLeft} />
);
}
close(): void {
this.root.unmount();
this.noticeElement.remove();
this.closed = true;
}
sameNotice(segments: SponsorTime[]): boolean {
if (segments.length !== this.segments.length) return false;
for (let i = 0; i < segments.length; i++) {
if (segments[i].UUID !== this.segments[i].UUID) return false;
}
return true;
}
}
export default UpcomingNotice;

View File

@@ -10,7 +10,6 @@ export interface ContentContainer {
sponsorTimes: SponsorTime[]; sponsorTimes: SponsorTime[];
sponsorTimesSubmitting: SponsorTime[]; sponsorTimesSubmitting: SponsorTime[];
skipNotices: SkipNotice[]; skipNotices: SkipNotice[];
v: HTMLVideoElement;
sponsorVideoID; sponsorVideoID;
reskipSponsorTime: (segment: SponsorTime, forceSeek?: boolean) => void; reskipSponsorTime: (segment: SponsorTime, forceSeek?: boolean) => void;
updatePreviewBar: () => void; updatePreviewBar: () => void;
@@ -57,7 +56,13 @@ export enum ActionType {
Poi = "poi" Poi = "poi"
} }
export const ActionTypes = [ActionType.Skip, ActionType.Mute]; export const ActionTypes = [
ActionType.Skip,
ActionType.Mute,
ActionType.Chapter,
ActionType.Full,
ActionType.Poi
];
export type SegmentUUID = string & { __segmentUUIDBrand: unknown }; export type SegmentUUID = string & { __segmentUUIDBrand: unknown };
export type Category = string & { __categoryBrand: unknown }; export type Category = string & { __categoryBrand: unknown };

View File

@@ -2,9 +2,10 @@ import Config, { VideoDownvotes } from "./config";
import { CategorySelection, SponsorTime, BackgroundScriptContainer, Registration, VideoID, SponsorHideType, CategorySkipOption } from "./types"; import { CategorySelection, SponsorTime, BackgroundScriptContainer, Registration, VideoID, SponsorHideType, CategorySkipOption } from "./types";
import { getHash, HashedValue } from "../maze-utils/src/hash"; import { getHash, HashedValue } from "../maze-utils/src/hash";
import { isFirefoxOrSafari, waitFor } from "../maze-utils/src"; import { waitFor } from "../maze-utils/src";
import { findValidElementFromSelector } from "../maze-utils/src/dom"; import { findValidElementFromSelector } from "../maze-utils/src/dom";
import { isSafari } from "../maze-utils/src/config"; import { isSafari } from "../maze-utils/src/config";
import { asyncRequestToServer } from "./utils/requests";
export default class Utils { export default class Utils {
@@ -46,10 +47,7 @@ export default class Utils {
*/ */
setupExtraSitePermissions(callback: (granted: boolean) => void): void { setupExtraSitePermissions(callback: (granted: boolean) => void): void {
const permissions = []; const permissions = [];
if (!isFirefoxOrSafari()) { if (isSafari()) {
permissions.push("declarativeContent");
}
if (!isFirefoxOrSafari() || isSafari()) {
permissions.push("webNavigation"); permissions.push("webNavigation");
} }
@@ -67,6 +65,17 @@ export default class Utils {
}); });
} }
getExtraSiteRegistration(): Registration {
return {
message: "registerContentScript",
id: "invidious",
allFrames: true,
js: this.js,
css: this.css,
matches: this.getPermissionRegex()
};
}
/** /**
* Registers the content scripts for the extra sites. * Registers the content scripts for the extra sites.
* Will use a different method depending on the browser. * Will use a different method depending on the browser.
@@ -75,14 +84,7 @@ export default class Utils {
* For now, it is just SB.config.invidiousInstances. * For now, it is just SB.config.invidiousInstances.
*/ */
setupExtraSiteContentScripts(): void { setupExtraSiteContentScripts(): void {
const registration: Registration = { const registration = this.getExtraSiteRegistration();
message: "registerContentScript",
id: "invidious",
allFrames: true,
js: this.js,
css: this.css,
matches: this.getPermissionRegex()
};
if (this.backgroundScriptContainer) { if (this.backgroundScriptContainer) {
this.backgroundScriptContainer.registerFirefoxContentScript(registration); this.backgroundScriptContainer.registerFirefoxContentScript(registration);
@@ -106,11 +108,6 @@ export default class Utils {
}); });
} }
if (!isFirefoxOrSafari() && chrome.declarativeContent) {
// Only if we have permission
chrome.declarativeContent.onPageChanged.removeRules(["invidious"]);
}
chrome.permissions.remove({ chrome.permissions.remove({
origins: this.getPermissionRegex() origins: this.getPermissionRegex()
}); });
@@ -135,8 +132,10 @@ export default class Utils {
containsInvidiousPermission(): Promise<boolean> { containsInvidiousPermission(): Promise<boolean> {
return new Promise((resolve) => { return new Promise((resolve) => {
let permissions = ["declarativeContent"]; const permissions = [];
if (isFirefoxOrSafari()) permissions = []; if (isSafari()) {
permissions.push("webNavigation");
}
chrome.permissions.contains({ chrome.permissions.contains({
origins: this.getPermissionRegex(), origins: this.getPermissionRegex(),
@@ -200,7 +199,7 @@ export default class Utils {
getSponsorIndexFromUUID(sponsorTimes: SponsorTime[], UUID: string): number { getSponsorIndexFromUUID(sponsorTimes: SponsorTime[], UUID: string): number {
for (let i = 0; i < sponsorTimes.length; i++) { for (let i = 0; i < sponsorTimes.length; i++) {
if (sponsorTimes[i].UUID === UUID) { if (sponsorTimes[i].UUID.startsWith(UUID) || UUID.startsWith(sponsorTimes[i].UUID)) {
return i; return i;
} }
} }
@@ -284,6 +283,17 @@ export default class Utils {
if ((chrome.extension.inIncognitoContext && !Config.config.trackDownvotesInPrivate) if ((chrome.extension.inIncognitoContext && !Config.config.trackDownvotesInPrivate)
|| !Config.config.trackDownvotes) return; || !Config.config.trackDownvotes) return;
if (segmentUUID.length < 60) {
const segmentIDData = await asyncRequestToServer("GET", "/api/segmentID", {
UUID: segmentUUID,
videoID
});
if (segmentIDData.ok && segmentIDData.responseText) {
segmentUUID = segmentIDData.responseText;
}
}
const hashedVideoID = (await getHash(videoID, 1)).slice(0, 4) as VideoID & HashedValue; const hashedVideoID = (await getHash(videoID, 1)).slice(0, 4) as VideoID & HashedValue;
const UUIDHash = await getHash(segmentUUID, 1); const UUIDHash = await getHash(segmentUUID, 1);

View File

@@ -36,6 +36,15 @@ export function getSkippingText(segments: SponsorTime[], autoSkip: boolean): str
} }
} }
export function getUpcomingText(segments: SponsorTime[]): string {
const categoryName = chrome.i18n.getMessage(segments.length > 1 ? "multipleSegments"
: "category_" + segments[0].category + "_short") || chrome.i18n.getMessage("category_" + segments[0].category);
const messageId = "upcoming";
return chrome.i18n.getMessage(messageId).replace("{0}", categoryName);
}
export function getCategorySuffix(category: Category): string { export function getCategorySuffix(category: Category): string {
if (category.startsWith("poi_")) { if (category.startsWith("poi_")) {
return "_POI"; return "_POI";
@@ -51,5 +60,4 @@ export function getCategorySuffix(category: Category): string {
export function shortCategoryName(categoryName: string): string { export function shortCategoryName(categoryName: string): string {
return chrome.i18n.getMessage("category_" + categoryName + "_short") || chrome.i18n.getMessage("category_" + categoryName); return chrome.i18n.getMessage("category_" + categoryName + "_short") || chrome.i18n.getMessage("category_" + categoryName);
} }
export const DEFAULT_CATEGORY = "chooseACategory"; export const DEFAULT_CATEGORY = "chooseACategory";

View File

@@ -13,3 +13,7 @@ export function runCompatibilityChecks() {
}, 10000); }, 10000);
} }
} }
export function isVorapisInstalled() {
return document.querySelector(`.v3`);
}

View File

@@ -1,12 +1,22 @@
window["SBLogs"] = { if (typeof (window) !== "undefined") {
window["SBLogs"] = {
debug: [], debug: [],
warn: [] warn: []
}; };
}
export function logDebug(message: string) { export function logDebug(message: string) {
if (typeof (window) !== "undefined") {
window["SBLogs"].debug.push(`[${new Date().toISOString()}] ${message}`); window["SBLogs"].debug.push(`[${new Date().toISOString()}] ${message}`);
} else {
console.log(`[${new Date().toISOString()}] ${message}`)
}
} }
export function logWarn(message: string) { export function logWarn(message: string) {
if (typeof (window) !== "undefined") {
window["SBLogs"].warn.push(`[${new Date().toISOString()}] ${message}`); window["SBLogs"].warn.push(`[${new Date().toISOString()}] ${message}`);
} else {
console.warn(`[${new Date().toISOString()}] ${message}`)
}
} }

View File

@@ -1,5 +1,6 @@
import { ActionType, Category, SponsorSourceType, SponsorTime, VideoID } from "../types"; import { ActionType, Category, SponsorSourceType, SponsorTime, VideoID } from "../types";
import { getFormattedTimeToSeconds } from "../../maze-utils/src/formating"; import { getFormattedTimeToSeconds } from "../../maze-utils/src/formating";
import Config from "../config";
export function getControls(): HTMLElement { export function getControls(): HTMLElement {
const controlsSelectors = [ const controlsSelectors = [
@@ -10,7 +11,9 @@ export function getControls(): HTMLElement {
// Invidious/videojs video element's controls element // Invidious/videojs video element's controls element
".vjs-control-bar", ".vjs-control-bar",
// Piped shaka player // Piped shaka player
".shaka-bottom-controls" ".shaka-bottom-controls",
// Vorapis v3
".html5-player-chrome"
]; ];
for (const controlsSelector of controlsSelectors) { for (const controlsSelector of controlsSelectors) {
@@ -53,10 +56,15 @@ export function getHashParams(): Record<string, unknown> {
return {}; return {};
} }
export function hasAutogeneratedChapters(): boolean {
return !!document.querySelector("ytd-engagement-panel-section-list-renderer ytd-macro-markers-list-renderer #menu");
}
export function getExistingChapters(currentVideoID: VideoID, duration: number): SponsorTime[] { export function getExistingChapters(currentVideoID: VideoID, duration: number): SponsorTime[] {
const chaptersBox = document.querySelector("ytd-macro-markers-list-renderer"); const chaptersBox = document.querySelector("ytd-macro-markers-list-renderer");
const title = document.querySelector("[target-id=engagement-panel-macro-markers-auto-chapters] #title-text"); const title = chaptersBox?.closest("ytd-engagement-panel-section-list-renderer")?.querySelector("#title-text.ytd-engagement-panel-title-header-renderer");
if (title?.textContent?.includes("Key moment")) return []; if (title?.textContent?.includes("Key moment")) return [];
if (!Config.config.showAutogeneratedChapters && hasAutogeneratedChapters()) return [];
const chapters: SponsorTime[] = []; const chapters: SponsorTime[] = [];
// .ytp-timed-markers-container indicates that key-moments are present, which should not be divided // .ytp-timed-markers-container indicates that key-moments are present, which should not be divided

View File

@@ -9,8 +9,8 @@ import { FetchResponse, sendRequestToCustomServer } from "../../maze-utils/src/b
* @param address The address to add to the SponsorBlock server address * @param address The address to add to the SponsorBlock server address
* @param callback * @param callback
*/ */
export function asyncRequestToCustomServer(type: string, url: string, data = {}): Promise<FetchResponse> { export function asyncRequestToCustomServer(type: string, url: string, data = {}, headers = {}): Promise<FetchResponse> {
return sendRequestToCustomServer(type, url, data); return sendRequestToCustomServer(type, url, data, headers);
} }
/** /**
@@ -20,10 +20,10 @@ export function asyncRequestToCustomServer(type: string, url: string, data = {})
* @param address The address to add to the SponsorBlock server address * @param address The address to add to the SponsorBlock server address
* @param callback * @param callback
*/ */
export async function asyncRequestToServer(type: string, address: string, data = {}): Promise<FetchResponse> { export async function asyncRequestToServer(type: string, address: string, data = {}, headers = {}): Promise<FetchResponse> {
const serverAddress = Config.config.testingServer ? CompileConfig.testingServerAddress : Config.config.serverAddress; const serverAddress = Config.config.testingServer ? CompileConfig.testingServerAddress : Config.config.serverAddress;
return await (asyncRequestToCustomServer(type, serverAddress + address, data)); return await (asyncRequestToCustomServer(type, serverAddress + address, data, headers));
} }
/** /**

106
src/utils/segmentData.ts Normal file
View File

@@ -0,0 +1,106 @@
import { DataCache } from "../../maze-utils/src/cache";
import { getHash, HashedValue } from "../../maze-utils/src/hash";
import Config from "../config";
import * as CompileConfig from "../../config.json";
import { ActionType, ActionTypes, SponsorSourceType, SponsorTime, VideoID } from "../types";
import { getHashParams } from "./pageUtils";
import { asyncRequestToServer } from "./requests";
const segmentDataCache = new DataCache<VideoID, SegmentResponse>(() => {
return {
segments: null,
status: 200
};
}, 5);
const pendingList: Record<VideoID, Promise<SegmentResponse>> = {};
export interface SegmentResponse {
segments: SponsorTime[] | null;
status: number;
}
export async function getSegmentsForVideo(videoID: VideoID, ignoreCache: boolean): Promise<SegmentResponse> {
if (!ignoreCache) {
const cachedData = segmentDataCache.getFromCache(videoID);
if (cachedData) {
segmentDataCache.cacheUsed(videoID);
return cachedData;
}
}
if (pendingList[videoID]) {
return await pendingList[videoID];
}
const pendingData = fetchSegmentsForVideo(videoID);
pendingList[videoID] = pendingData;
const result = await pendingData;
delete pendingList[videoID];
return result;
}
async function fetchSegmentsForVideo(videoID: VideoID): Promise<SegmentResponse> {
const categories: string[] = Config.config.categorySelections.map((category) => category.name);
const extraRequestData: Record<string, unknown> = {};
const hashParams = getHashParams();
if (hashParams.requiredSegment) extraRequestData.requiredSegment = hashParams.requiredSegment;
const hashPrefix = (await getHash(videoID, 1)).slice(0, 4) as VideoID & HashedValue;
const hasDownvotedSegments = !!Config.local.downvotedSegments[hashPrefix];
const response = await asyncRequestToServer('GET', "/api/skipSegments/" + hashPrefix, {
categories: CompileConfig.categoryList,
actionTypes: ActionTypes,
trimUUIDs: hasDownvotedSegments ? null : 5,
...extraRequestData
}, {
"X-CLIENT-NAME": `${chrome.runtime.id}/v${chrome.runtime.getManifest().version}`
});
if (response.ok) {
const enabledActionTypes = getEnabledActionTypes();
const receivedSegments: SponsorTime[] = JSON.parse(response.responseText)
?.filter((video) => video.videoID === videoID)
?.map((video) => video.segments)?.[0]
?.filter((segment) => enabledActionTypes.includes(segment.actionType) && categories.includes(segment.category))
?.map((segment) => ({
...segment,
source: SponsorSourceType.Server
}))
?.sort((a, b) => a.segment[0] - b.segment[0]);
if (receivedSegments && receivedSegments.length) {
const result = {
segments: receivedSegments,
status: response.status
};
segmentDataCache.setupCache(videoID).segments = result.segments;
return result;
} else {
// Setup with null data
segmentDataCache.setupCache(videoID);
}
}
return {
segments: null,
status: response.status
};
}
function getEnabledActionTypes(forceFullVideo = false): ActionType[] {
const actionTypes = [ActionType.Skip, ActionType.Poi, ActionType.Chapter];
if (Config.config.muteSegments) {
actionTypes.push(ActionType.Mute);
}
if (Config.config.fullVideoSegments || forceFullVideo) {
actionTypes.push(ActionType.Full);
}
return actionTypes;
}

View File

@@ -1,10 +1,15 @@
import { isOnInvidious, parseYouTubeVideoIDFromURL } from "../../maze-utils/src/video"; import { isOnInvidious, parseYouTubeVideoIDFromURL } from "../../maze-utils/src/video";
import Config from "../config"; import Config from "../config";
import { getVideoLabel } from "./videoLabels"; import { getHasStartSegment, getVideoLabel } from "./videoLabels";
import { setThumbnailListener } from "../../maze-utils/src/thumbnailManagement"; import { getThumbnailSelector, setThumbnailListener } from "../../maze-utils/src/thumbnailManagement";
import { VideoID } from "../types";
import { getSegmentsForVideo } from "./segmentData";
export async function labelThumbnails(thumbnails: HTMLImageElement[]): Promise<void> { export async function handleThumbnails(thumbnails: HTMLImageElement[]): Promise<void> {
await Promise.all(thumbnails.map((t) => labelThumbnail(t))); await Promise.all(thumbnails.map((t) => {
labelThumbnail(t);
setupThumbnailHover(t);
}));
} }
export async function labelThumbnail(thumbnail: HTMLImageElement): Promise<HTMLElement | null> { export async function labelThumbnail(thumbnail: HTMLImageElement): Promise<HTMLElement | null> {
@@ -13,9 +18,7 @@ export async function labelThumbnail(thumbnail: HTMLImageElement): Promise<HTMLE
return null; return null;
} }
const link = (isOnInvidious() ? thumbnail.parentElement : thumbnail.querySelector("#thumbnail")) as HTMLAnchorElement const videoID = extractVideoID(thumbnail);
if (!link || link.nodeName !== "A" || !link.href) return null; // no link found
const videoID = parseYouTubeVideoIDFromURL(link.href)?.videoID;
if (!videoID) { if (!videoID) {
hideThumbnailLabel(thumbnail); hideThumbnailLabel(thumbnail);
return null; return null;
@@ -37,6 +40,64 @@ export async function labelThumbnail(thumbnail: HTMLImageElement): Promise<HTMLE
return overlay; return overlay;
} }
export async function setupThumbnailHover(thumbnail: HTMLImageElement): Promise<void> {
// Cache would be reset every load due to no SPA
if (isOnInvidious()) return;
const mainElement = thumbnail.closest("#dismissible") as HTMLElement;
if (mainElement) {
mainElement.removeEventListener("mouseenter", thumbnailHoverListener);
mainElement.addEventListener("mouseenter", thumbnailHoverListener);
}
}
function thumbnailHoverListener(e: MouseEvent) {
if (!chrome.runtime?.id) return;
const thumbnail = (e.target as HTMLElement).querySelector(getThumbnailSelector()) as HTMLImageElement;
if (!thumbnail) return;
// Pre-fetch data for this video
let fetched = false;
const preFetch = async () => {
fetched = true;
const videoID = extractVideoID(thumbnail);
if (videoID && await getHasStartSegment(videoID)) {
void getSegmentsForVideo(videoID, false);
}
};
const timeout = setTimeout(preFetch, 100);
const onMouseDown = () => {
clearTimeout(timeout);
if (!fetched) {
preFetch();
}
};
e.target.addEventListener("mousedown", onMouseDown, { once: true });
e.target.addEventListener("mouseleave", () => {
clearTimeout(timeout);
e.target.removeEventListener("mousedown", onMouseDown);
}, { once: true });
}
function getLink(thumbnail: HTMLImageElement): HTMLAnchorElement | null {
if (isOnInvidious()) {
return thumbnail.parentElement as HTMLAnchorElement | null;
} else if (thumbnail.nodeName.toLowerCase() === "yt-thumbnail-view-model") {
return thumbnail.closest("yt-lockup-view-model")?.querySelector("a.yt-lockup-metadata-view-model-wiz__title");
} else {
return thumbnail.querySelector("#thumbnail");
}
}
function extractVideoID(thumbnail: HTMLImageElement): VideoID | null {
const link = getLink(thumbnail);
if (!link || link.nodeName !== "A" || !link.href) return null; // no link found
return parseYouTubeVideoIDFromURL(link.href)?.videoID;
}
function getOldThumbnailLabel(thumbnail: HTMLImageElement): HTMLElement | null { function getOldThumbnailLabel(thumbnail: HTMLImageElement): HTMLElement | null {
return thumbnail.querySelector(".sponsorThumbnailLabel") as HTMLElement | null; return thumbnail.querySelector(".sponsorThumbnailLabel") as HTMLElement | null;
} }
@@ -109,7 +170,7 @@ function insertSBIconDefinition() {
} }
export function setupThumbnailListener(): void { export function setupThumbnailListener(): void {
setThumbnailListener(labelThumbnails, () => { setThumbnailListener(handleThumbnails, () => {
insertSBIconDefinition(); insertSBIconDefinition();
}, () => Config.isReady()); }, () => Config.isReady());
} }

View File

@@ -6,9 +6,14 @@ import { asyncRequestToServer } from "./requests";
const utils = new Utils(); const utils = new Utils();
interface VideoLabelsCacheData {
category: Category;
hasStartSegment: boolean;
}
export interface LabelCacheEntry { export interface LabelCacheEntry {
timestamp: number; timestamp: number;
videos: Record<VideoID, Category>; videos: Record<VideoID, VideoLabelsCacheData>;
} }
const labelCache: Record<string, LabelCacheEntry> = {}; const labelCache: Record<string, LabelCacheEntry> = {};
@@ -21,7 +26,7 @@ async function getLabelHashBlock(hashPrefix: string): Promise<LabelCacheEntry |
return cachedEntry; return cachedEntry;
} }
const response = await asyncRequestToServer("GET", `/api/videoLabels/${hashPrefix}`); const response = await asyncRequestToServer("GET", `/api/videoLabels/${hashPrefix}?hasStartSegment=true`);
if (response.status !== 200) { if (response.status !== 200) {
// No video labels or server down // No video labels or server down
labelCache[hashPrefix] = { labelCache[hashPrefix] = {
@@ -36,7 +41,10 @@ async function getLabelHashBlock(hashPrefix: string): Promise<LabelCacheEntry |
const newEntry: LabelCacheEntry = { const newEntry: LabelCacheEntry = {
timestamp: Date.now(), timestamp: Date.now(),
videos: Object.fromEntries(data.map(video => [video.videoID, video.segments[0].category])), videos: Object.fromEntries(data.map(video => [video.videoID, {
category: video.segments[0]?.category,
hasStartSegment: video.hasStartSegment
}])),
}; };
labelCache[hashPrefix] = newEntry; labelCache[hashPrefix] = newEntry;
@@ -55,11 +63,11 @@ async function getLabelHashBlock(hashPrefix: string): Promise<LabelCacheEntry |
} }
export async function getVideoLabel(videoID: VideoID): Promise<Category | null> { export async function getVideoLabel(videoID: VideoID): Promise<Category | null> {
const prefix = (await getHash(videoID, 1)).slice(0, 3); const prefix = (await getHash(videoID, 1)).slice(0, 4);
const result = await getLabelHashBlock(prefix); const result = await getLabelHashBlock(prefix);
if (result) { if (result) {
const category = result.videos[videoID]; const category = result.videos[videoID]?.category;
if (category && utils.getCategorySelection(category).option !== CategorySkipOption.Disabled) { if (category && utils.getCategorySelection(category).option !== CategorySkipOption.Disabled) {
return category; return category;
} else { } else {
@@ -69,3 +77,14 @@ export async function getVideoLabel(videoID: VideoID): Promise<Category | null>
return null; return null;
} }
export async function getHasStartSegment(videoID: VideoID): Promise<boolean | null> {
const prefix = (await getHash(videoID, 1)).slice(0, 4);
const result = await getLabelHashBlock(prefix);
if (result) {
return result?.videos[videoID]?.hasStartSegment ?? false;
}
return null;
}

View File

@@ -171,6 +171,13 @@ module.exports = env => {
} }
} }
if (env.browser.toLowerCase() === "edge") {
parsed.Description.message = parsed.Description.message.match(/^.+(?=\. )/)?.[0] || parsed.Description.message;
if (parsed.Description.message.length > 132) {
parsed.Description.message = parsed.Description.message.slice(0, 129) + "...";
}
}
return Buffer.from(JSON.stringify(parsed)); return Buffer.from(JSON.stringify(parsed));
} }
@@ -185,6 +192,12 @@ module.exports = env => {
stream: env.stream stream: env.stream
}), }),
new configDiffPlugin() new configDiffPlugin()
] ],
performance: {
hints: false,
maxEntrypointSize: 512000,
maxAssetSize: 512000
}
}; };
}; };

View File

@@ -11,6 +11,7 @@ const chromeManifestExtra = require("../manifest/chrome-manifest-extra.json");
const safariManifestExtra = require("../manifest/safari-manifest-extra.json"); const safariManifestExtra = require("../manifest/safari-manifest-extra.json");
const betaManifestExtra = require("../manifest/beta-manifest-extra.json"); const betaManifestExtra = require("../manifest/beta-manifest-extra.json");
const firefoxBetaManifestExtra = require("../manifest/firefox-beta-manifest-extra.json"); const firefoxBetaManifestExtra = require("../manifest/firefox-beta-manifest-extra.json");
const manifestV2ManifestExtra = require("../manifest/manifest-v2-extra.json");
// schema for options object // schema for options object
const schema = { const schema = {
@@ -41,12 +42,14 @@ class BuildManifest {
// Add missing manifest elements // Add missing manifest elements
if (this.options.browser.toLowerCase() === "firefox") { if (this.options.browser.toLowerCase() === "firefox") {
mergeObjects(manifest, manifestV2ManifestExtra);
mergeObjects(manifest, firefoxManifestExtra); mergeObjects(manifest, firefoxManifestExtra);
} else if (this.options.browser.toLowerCase() === "chrome" } else if (this.options.browser.toLowerCase() === "chrome"
|| this.options.browser.toLowerCase() === "chromium" || this.options.browser.toLowerCase() === "chromium"
|| this.options.browser.toLowerCase() === "edge") { || this.options.browser.toLowerCase() === "edge") {
mergeObjects(manifest, chromeManifestExtra); mergeObjects(manifest, chromeManifestExtra);
} else if (this.options.browser.toLowerCase() === "safari") { } else if (this.options.browser.toLowerCase() === "safari") {
mergeObjects(manifest, manifestV2ManifestExtra);
mergeObjects(manifest, safariManifestExtra); mergeObjects(manifest, safariManifestExtra);
} }