Compare commits

...

270 Commits
5.5.2 ... 5.12

Author SHA1 Message Date
Ajay
8b305ba5ec bump version 2025-04-23 21:33:55 -04:00
Ajay
0fdcade27e update translations 2025-04-23 21:33:33 -04:00
Ajay
897ebda42d update chromedriver to make tests run 2025-04-23 21:33:24 -04:00
Ajay Ramachandran
ddd0b4ea05 Merge pull request #2235 from FlorianZahn/auto-skip-only-on-youtube-music
feat: Option to auto-skip non-music on youtube music only
2025-04-23 21:30:41 -04:00
Ajay
4826bcf7df Remove unnecessary change 2025-04-23 21:27:56 -04:00
Ajay
09d8bc4425 Merge branch 'master' of https://github.com/ajayyy/SponsorBlock into pr/FlorianZahn/2235 2025-04-23 21:25:41 -04:00
Ajay
aeb3299c50 update submodules 2025-04-23 21:23:56 -04:00
Ajay
be11012096 Remove unnecessary change 2025-04-23 21:23:00 -04:00
Ajay
22da289151 Improve youtube music check code readability 2025-04-23 21:21:07 -04:00
Ajay Ramachandran
67a16a7b34 Merge pull request #2243 from gosha305/loop-chapters
Added the possibility to loop a chapter
2025-04-23 21:06:49 -04:00
Ajay
312ac788b3 update translations 2025-04-23 21:06:31 -04:00
Ajay
8117f3c215 Formatting changes 2025-04-23 21:05:49 -04:00
Ajay
3c1d3f5d8e Fix cases where hidden segments are not skipped when a chapter is at the same time
Fixes #2228
2025-04-23 20:55:06 -04:00
Ajay
7cbd1fa59d Fix downvoted segments not getting saved 2025-04-23 20:16:33 -04:00
Ajay
f9e9c5c868 Fix volume controls on new YouTube video layout
Fixes #2248
2025-04-23 19:52:00 -04:00
gosha305
3b89ef0487 Added the possibility to loop a chapter 2025-04-13 22:19:05 +02:00
Ajay
d49bfa0a4c bump version 2025-04-02 15:40:16 -04:00
Ajay
28cfcf7ddc Remove optional permissions in safari for now due to safari bug
https://bugs.webkit.org/show_bug.cgi?id=290508

Forgot to commit it in the last build
2025-04-02 15:40:08 -04:00
Florian Zahn
e656dd8d8b feat: Option to auto-skip non-music on youtube music only 2025-04-01 22:53:28 +02:00
Ajay
7904913fdb update translations 2025-03-31 18:37:36 -04:00
Ajay
464561992e bump version 2025-03-31 18:36:51 -04:00
Ajay
4db73d5e54 Disable Invidious support in safari due to permission bug
Will be re-enabled later

#2227
2025-03-31 18:36:38 -04:00
Ajay
19020d2131 Try to fix NaN showing in skip popup 2025-03-20 01:37:08 -04:00
Ajay
244cce56c0 Fix server-side add detector when setting time at the end of the video 2025-03-12 23:28:52 -04:00
Ajay
b05d696057 Use longer hash prefix when fetching segments to save bandwidth 2025-03-12 02:51:07 -04:00
Ajay
d8ddcdc117 bump version 2025-03-10 22:55:12 -04:00
Ajay
48e8abb247 bump version 2025-03-09 14:28:08 -04:00
Ajay
a10f95f900 update translations 2025-03-09 14:28:03 -04:00
Ajay
70b415b67a Fix skipping on /live/ links
Fixes #2219
2025-03-09 03:15:18 -04:00
Ajay
c22a763da6 Fix issue with unfinished chapter submissions causing the seek bar to look wrong 2025-03-08 16:10:33 -05:00
Ajay
abbab887c6 More fixes for skiping accuracy with bluetooth headphones
Raise threshold virtual time is allowed to adjust by
2025-03-08 04:59:16 -05:00
Ajay
169350a5bf Fix extension context lost errors 2025-03-08 04:05:29 -05:00
Ajay
a4bb17f935 Fix skipping accuracy with bluetooth headphones on firefox 2025-03-08 03:56:19 -05:00
Ajay
1fa886690a Add back clarified comment 2025-03-06 21:22:30 -05:00
Ajay Ramachandran
5540728fc6 Merge pull request #2187 from tech234a/master
Implement support for YouTube TV (tv.youtube.com)
2025-03-06 03:16:36 -05:00
Ajay
9ab235c017 Merge branch 'master' of https://github.com/ajayyy/SponsorBlock into pr/tech234a/2187 2025-03-06 03:14:22 -05:00
Ajay
e5cc0587e2 update submodules 2025-03-06 03:12:59 -05:00
Ajay
e8b3753a17 Remove confusing comment 2025-03-06 03:10:21 -05:00
Ajay Ramachandran
b0984e9bbc Merge pull request #2203 from gosha305/master
Added keybinds for voting on segments
2025-03-05 01:49:53 -05:00
Ajay
1a3964e238 Merge branch 'master' of https://github.com/ajayyy/SponsorBlock into pr/gosha305/2203 2025-03-05 01:45:52 -05:00
Ajay
5903d1a023 Fixes for vote notice 2025-03-05 01:45:08 -05:00
Ajay
8b0b670ea8 update translation 2025-03-05 01:44:01 -05:00
Ajay
9ef948670c bump version 2025-03-04 21:08:39 -05:00
gosha305
5a7e16b51c Fixed chapter skip notices not working + fixed typo
Fixed chapter skip notices not working + fixed typo

Fixed chapter skip notices not working + fixed typo

Fixed chapter skip notices not working + fixed typo

A skip notice is now shown upon voting if one isn't already on screen

minor fixes

fixed commit error
2025-03-04 03:12:05 +01:00
Ajay
be98dc7b73 update translations 2025-03-02 23:41:58 -05:00
Ajay
3174eda29a bump version 2025-03-02 23:40:30 -05:00
Ajay
6a4bb50006 Fix document script player client fetching 2025-03-02 23:34:50 -05:00
gosha305
66876283b4 A skip notice is now shown upon voting if one isn't already on screen
A skip notice is now shown upon voting if one isn't already on screen

minor fixes
2025-02-20 21:00:14 +01:00
gosha305
75dc6a1980 Added limit to how far back the segment voted on can be 2025-02-12 20:15:32 +01:00
Ajay
2823e51b1d Allow importing placeholder chapters without start times 2025-02-11 20:28:10 -05:00
gosha305
c9c02e91ff Added keybinds to vote on the last segment
Added keybinds to vote on the last segment

fixed Upvote/Downvote keybind message variable names to match others in that options section
2025-02-11 23:16:01 +01:00
Ajay
19efcb5a98 Handle video being null in skip notice
Resolves #2201
2025-02-02 22:17:51 -05:00
Ajay
5c7e3b5cb3 bump version 2025-01-23 05:19:10 -05:00
Ajay
c3cbc44d28 Fix hiding segments not working on videos with YouTube chapters
Fixes #2194
2025-01-23 05:18:49 -05:00
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
tech234a
f4dc98112a Fix tests 2025-01-13 00:57:12 -05:00
tech234a
138e57b720 Use a specific error message channel whitelisting on YTTV 2025-01-13 00:47:23 -05:00
tech234a
de1d6bd76a Implement support for YouTube TV (tv.youtube.com) 2025-01-12 23:01:33 -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
Ajay
15fcc971f6 bump version 2024-05-24 03:26:02 -04:00
Ajay
32adbb9581 update translations 2024-05-24 03:22:36 -04:00
Ajay
4ecd02510b Fix warnings 2024-05-24 03:22:07 -04:00
Ajay
0dbb6474b8 Fix preview bar on mobile
Fixes #2021
2024-05-24 03:20:21 -04:00
Ajay
c974c2a6d6 Merge branch 'master' of https://github.com/ajayyy/SponsorBlock 2024-05-24 03:15:59 -04:00
Ajay
a6695254b6 Fix skipping and preview bar on "add to queue", miniplayer and hover preview
Fixes #1486
2024-05-24 03:15:58 -04:00
Ajay
e8a82eddca Change chapters sorting 2024-05-09 19:09:37 -04:00
Ajay Ramachandran
d3684dbedd Merge pull request #2017 from stdedos/fix/disabled-comment-typo
Fix comment typo
2024-05-08 17:54:10 -04:00
Stavros Ntentos
6c9a403731 Fix comment typo
Signed-off-by: Stavros Ntentos <133706+stdedos@users.noreply.github.com>
2024-05-08 23:44:01 +03:00
Ajay
3be51c89a9 Fix popup sometimes saying "No video found"
Fixes #2006
Fixes #2013
Fixes #1999
2024-04-30 20:54:54 -04:00
Ajay
5b845a56e1 Fix unsubmitted segments sometimes manual skipping if manual skip enabled for category
Fixes #2002
2024-04-28 22:59:02 -04:00
Ajay
7b572c3c0e Fix previewed segment check
Fixes #1996
2024-04-28 21:51:35 -04:00
Ajay
56bb22d03d Allow importing segments with no category
Also fixes case when importing chapters when having no permission to submit chapters
2024-04-26 16:44:20 -04:00
Ajay
e1d656f43f Make notice visibility option not appear when notice disabled 2024-04-04 13:54:47 -04:00
Ajay
e14b3c76f9 Improve submission box padding 2024-03-27 16:14:10 -04:00
Ajay
de830cda48 bump version 2024-03-18 21:03:23 -04:00
Ajay
d8057fb547 bump translations 2024-03-18 21:03:09 -04:00
Ajay Ramachandran
2dd69d443b Merge pull request #1983 from HanYaodong/dev
[Fix #1979] Stop Refresh Animation on Popup When Content Script is Not Injected
2024-03-18 20:59:19 -04:00
Ajay
6732850b41 Fix chapters not changing when skipping is disabled 2024-03-18 20:58:07 -04:00
Ajay
287233785e Merge branch 'master' of https://github.com/ajayyy/SponsorBlock 2024-03-18 19:45:36 -04:00
Ajay
de094cb11f Add option to store downvotes when done in private tabs 2024-03-18 19:45:35 -04:00
Ajay Ramachandran
2307691044 Merge pull request #1986 from ajayyy/dependabot/npm_and_yarn/follow-redirects-1.15.6
Bump follow-redirects from 1.15.4 to 1.15.6
2024-03-16 21:07:43 -04:00
dependabot[bot]
2dca0d7526 Bump follow-redirects from 1.15.4 to 1.15.6
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.4 to 1.15.6.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.4...v1.15.6)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-16 23:28:04 +00:00
HanYaodong
a1505bcf20 Refractor popup infoFound() empty message detection 2024-03-09 23:47:48 +08:00
HanYaodong
25b53754ba Stop refresh animation on popup when content script is not injected 2024-03-09 23:42:17 +08:00
Ajay
6c71036356 update translations 2024-03-07 17:22:22 -05:00
Ajay
f55f2606a7 bump version 2024-03-07 17:21:50 -05:00
Ajay
e9b7abd219 Fix rate-limiting when setting options from options page 2024-03-07 17:20:23 -05:00
Ajay Ramachandran
63d3309605 Merge pull request #1948 from ajayyy/dependabot/npm_and_yarn/follow-redirects-1.15.4
Bump follow-redirects from 1.15.3 to 1.15.4
2024-02-29 03:35:22 -05:00
Ajay Ramachandran
b4615d7d79 Merge pull request #1950 from ajayyy/dependabot/npm_and_yarn/postcss-and-web-ext-8.4.33
Bump postcss and web-ext
2024-02-29 03:35:13 -05:00
Ajay Ramachandran
ec7eee650b Merge pull request #1979 from HanYaodong/dev_refresh
[Fix] Stop Refresh Animation on Popup When Triggered on Non-YouTube Pages
2024-02-29 03:34:45 -05:00
HanYaodong
154bb1749d Stop refresh animation on popup when not on youtube page 2024-02-29 12:36:06 +08:00
Ajay
742eb7ef57 bump version 2024-02-28 01:13:57 -05:00
Ajay
88526aa46e Fix missing key in tooltip and update maze utils 2024-02-27 22:16:53 -05:00
Ajay
1909e66c87 Move category pill to top if on description on right layout 2024-02-27 22:13:58 -05:00
Ajay
5f8447ec6b bump version 2024-02-24 18:54:19 -05:00
Ajay
e6db5b43ff update translations 2024-02-24 18:54:11 -05:00
Ajay
6566c129c7 Fix issue with BlockTube version 0.4.1 2024-02-24 18:53:26 -05:00
Ajay
e7f4be2d57 Don't require preview for starting segments 2024-02-21 14:32:44 -05:00
Ajay
9d04482d19 bump version 2024-02-13 22:35:44 -05:00
Ajay
7c661f8e67 update translations 2024-02-13 22:33:11 -05:00
Ajay
0b7a2fd197 Fix adding custom Invidious instances on Firefox
Fixes #815
2024-02-13 22:32:37 -05:00
Ajay
3382d8a500 Fix chapter names not appearing 2024-02-13 21:52:10 -05:00
Ajay
5d871d5fe7 Fix hidden mute segments sometimes still muting 2024-02-13 21:32:23 -05:00
Ajay Ramachandran
b7c5737a95 Merge pull request #1967 from HanYaodong/dev
Fix license info in README
2024-02-02 19:17:14 -05:00
HanYaodong
0cca1c3566 Fix license info in README 2024-02-02 19:32:31 +08:00
Ajay
e0fe0fad67 Don't close submission menu if submission didn't go through
Fxies submission menu closing for warning about previewing a segment
2024-02-01 13:31:19 -05:00
Ajay
c0bc068a18 Handle exceptions for voting
Maybe fixes #1961
2024-01-31 19:06:16 -05:00
Ajay
7cb413db15 Don't display preview bar while scrubbing on mobile
Should help with #1962
2024-01-31 18:58:06 -05:00
Ajay
fdf1a6acf9 bump version 2024-01-30 23:12:36 -05:00
Ajay
b533c6c1c8 update translations 2024-01-30 23:12:28 -05:00
Ajay
926423db5c Fix compatibility with vinegar on Safari 2024-01-30 23:12:03 -05:00
Ajay
e7d55d2bac Fix preview bar end time sometimes being inaccurate 2024-01-25 19:38:10 -05:00
Ajay
16f27e5c5c Move animation utils to maze utils 2024-01-24 16:21:31 -05:00
Ajay
88dc8db6e7 bump version 2024-01-23 14:27:05 -05:00
Ajay
c69a574379 update translations 2024-01-23 14:27:01 -05:00
Ajay
516d624f16 Don't require preview for segments ending at end of the video
Fixes #1959
2024-01-23 14:25:07 -05:00
Ajay
a662c3e04f Reloading after creating segments shouldn't require previewing 2024-01-23 14:22:54 -05:00
Ajay
985910cbf6 Count previewed unsubmitted segments previewed manually 2024-01-23 14:20:24 -05:00
Ajay
feae86f6ea Don't throw errors on extension live-update 2024-01-21 20:42:40 -05:00
Ajay
1f96e3b117 Improve precision of hover preview
Also fixes issue with YouTube "most-replayed" messing it up
2024-01-21 20:35:00 -05:00
dependabot[bot]
ba92e6e386 Bump postcss and web-ext
Bumps [postcss](https://github.com/postcss/postcss) to 8.4.33 and updates ancestor dependency [web-ext](https://github.com/mozilla/web-ext). These dependencies need to be updated together.


Updates `postcss` from 8.4.21 to 8.4.33
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.4.21...8.4.33)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-12 18:16:09 +00:00
dependabot[bot]
bdbe8b60cc Bump follow-redirects from 1.15.3 to 1.15.4
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.3 to 1.15.4.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.3...v1.15.4)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-11 03:55:58 +00:00
Brian Choromanski
2a5b76ddfb Overriding default webpack performance config to reduce warnings 2023-10-19 15:09:19 -04:00
69 changed files with 4907 additions and 3132 deletions

View File

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

View File

@@ -12,10 +12,10 @@ jobs:
steps:
# Initialization
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
with:
node-version: '18'
- name: Copy configuration
@@ -35,24 +35,11 @@ jobs:
repo-token: ${{ secrets.GITHUB_TOKEN }}
- 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
- name: Create Firefox artifacts
run: npm run build:firefox
- run: mkdir ./builds
- name: Zip Artifacts
run: cd ./dist ; zip -r ../builds/FirefoxExtension.zip *
- name: Upload FirefoxExtension to release
@@ -62,32 +49,18 @@ jobs:
name: FirefoxExtension.zip
path: ./builds/FirefoxExtension.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
# Create Chrome artifacts
- name: Create Chrome artifacts
run: npm run build:chrome
- name: Zip Artifacts
run: cd ./dist ; zip -r ../builds/ChromeExtensionBeta.zip *
- name: Upload ChromeExtensionBeta to release
run: cd ./dist ; zip -r ../builds/ChromeExtension.zip *
- name: Upload ChromeExtension 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 }}
# 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
args: builds/ChromeExtension.zip
name: ChromeExtension.zip
path: ./builds/ChromeExtension.zip
repo-token: ${{ secrets.GITHUB_TOKEN }}
# Create Edge artifacts
@@ -105,10 +78,36 @@ jobs:
path: ./builds/EdgeExtension.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 }}
# 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
- name: Create Firefox Beta artifacts
run: npm run build:firefox -- --env stream=beta
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
with:
name: FirefoxExtensionBeta
path: dist
@@ -125,7 +124,7 @@ jobs:
run: sudo apt-get install rename
- name: Rename signed file
run: cd ./web-ext-artifacts ; rename 's/.*/FirefoxSignedInstaller.xpi/' *
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
with:
name: FirefoxExtensionSigned.xpi
path: ./web-ext-artifacts/FirefoxSignedInstaller.xpi

View File

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

View File

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

View File

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

View File

@@ -75,4 +75,4 @@ Icons made by:
### License
This project is licensed under GNU LGPL v3 or any later version
This project is licensed under GNU GPL v3 or any later version

View File

@@ -7,8 +7,7 @@ This file should not be shipped with the extension
/*
Criteria for inclusion:
Invidious
- 30d uptime >= 90%
- available for at least 80/90 days
- uptime >= 80%
- must have been up for at least 90 days
- HTTPS only
- 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";
// only https servers
const mapped: instanceMap = data
.filter((i: InvidiousInstance) => i[1]?.type === "https")
.map((instance: InvidiousInstance) => {
const mapped = (data as InvidiousInstance[])
.filter((i) =>
i[1]?.type === "https"
&& i[1]?.monitor?.enabled
)
.map((instance) => {
const monitor = instance[1].monitor as monitor;
return {
name: instance[0],
url: instance[1].uri,
dailyRatios: instance[1].monitor.dailyRatios,
thirtyDayUptime: instance[1]?.monitor["30dRatio"].ratio,
uptime: monitor.uptime || 0,
down: monitor.down ?? false,
created_at: monitor.created_at,
}
});
// reliability and sanity checks
const reliableCheck = mapped
.filter(instance => {
// 30d uptime >= 90%
const thirtyDayUptime = Number(instance.thirtyDayUptime) >= 90;
// available for at least 80/90 days
const dailyRatioCheck = instance.dailyRatios.filter(status => status.label !== "black");
return thirtyDayUptime && dailyRatioCheck.length >= 80;
const uptime = instance.uptime > 80 && !instance.down;
const nameIncluded = instance.url.includes(instance.name);
const ninetyDays = 90 * 24 * 60 * 60 * 1000;
const ninetyDaysAgo = new Date(Date.now() - ninetyDays);
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[] {
return reliableCheck.map(instance => instance.name).sort()
}
export const getInvidiousList = (): string[] =>
reliableCheck.map(instance => instance.name).sort()

View File

@@ -1,54 +1,72 @@
type ratio = {
ratio: string;
label: string;
}
export type instanceMap = {
name: string;
url: string;
dailyRatios: {ratio: string; label: string }[];
thirtyDayUptime: string;
}[]
export type InvidiousInstance = [
string,
{
flag: string;
region: string;
stats: null | {
version: string;
software: {
name: string;
version: string;
branch: string;
};
openRegistrations: boolean;
usage: {
users: {
total: number;
activeHalfyear: number;
activeMonth: number;
};
};
metadata: {
updatedAt: number;
lastChannelRefreshedAt: number;
};
};
cors: boolean | null;
api: boolean | null;
stats: null | ivStats;
cors: null | boolean;
api: null | boolean;
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;
};
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;
software: {
name: "invidious" | string;
version: string;
branch: "master" | string;
};
openRegistrations: boolean;
usage: {
users: {
total: number;
activeHalfyear: number;
activeMonth: number;
};
};
metadata: {
updatedAt: number;
lastChannelRefreshedAt: number;
};
playback: {
totalRequests: number;
successfulRequests: number;
ratio: number;
};
}

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",
"outro": "https://wiki.sponsor.ajay.app/w/Endcards/Credits",
"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",
"poi_highlight": "https://wiki.sponsor.ajay.app/w/Highlight",
"guidelines": "https://wiki.sponsor.ajay.app/w/Guidelines",

View File

@@ -1,12 +1,156 @@
{
"optional_permissions": [
"declarativeContent",
"webNavigation"
"host_permissions": [
"https://*.youtube.com/*",
"https://sponsor.ajay.app/*"
],
"background": {
"persistent": false
"optional_host_permissions": [
"*://*/*"
],
"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"
},
{
"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
}
]
},
"permissions": [
"https://*.youtube.com/*"
]
"background": {
"service_worker": "./js/background.js"
},
"manifest_version": 3
}

View File

@@ -2,7 +2,7 @@
"browser_specific_settings": {
"gecko": {
"id": "sponsorBlocker@ajay.app",
"strict_min_version": "56.0"
"strict_min_version": "102.0"
},
"gecko_android": {
"strict_min_version": "113.0"
@@ -11,9 +11,6 @@
"background": {
"persistent": false
},
"permissions": [
"scripting"
],
"browser_action": {
"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__",
"short_name": "SponsorBlock",
"version": "5.5.2",
"version": "5.12",
"default_locale": "en",
"description": "__MSG_Description__",
"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": {
"16": "icons/IconSponsorBlocker16px.png",
"32": "icons/IconSponsorBlocker32px.png",
@@ -145,9 +14,12 @@
"512": "icons/IconSponsorBlocker512px.png",
"1024": "icons/IconSponsorBlocker1024px.png"
},
"permissions": [
"storage",
"scripting"
],
"options_ui": {
"page": "options/options.html",
"open_in_tab": true
},
"manifest_version": 2
}
}

View File

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

4768
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -23,7 +23,7 @@
"@types/wicg-mediasession": "^1.1.4",
"@typescript-eslint/eslint-plugin": "^5.54.1",
"@typescript-eslint/parser": "^5.54.1",
"chromedriver": "^110.0.0",
"chromedriver": "^135.0.0",
"concurrently": "^7.6.0",
"copy-webpack-plugin": "^11.0.0",
"eslint": "^8.35.0",
@@ -38,14 +38,14 @@
"ts-loader": "^9.4.2",
"ts-node": "^10.9.1",
"typescript": "4.9",
"web-ext": "^7.6.2",
"webpack": "^5.75.0",
"web-ext": "^8.2.0",
"webpack": "^5.94.0",
"webpack-cli": "^4.10.0",
"webpack-merge": "^5.8.0"
},
"scripts": {
"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-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",

View File

@@ -11,6 +11,11 @@
display: none;
}
/* Vorapi compatibility */
#player-api_VORAPI_ELEMENT_ID #previewbar {
z-index: 999;
}
#previewbar {
overflow: visible;
padding: 0;
@@ -26,6 +31,20 @@
transition: transform .1s cubic-bezier(0,0,0.2,1);
}
/* Prevent bar from covering highlights on YTTV */
#previewbar.sponsorblock-yttv-container {
z-index: unset;
}
ytu-time-bar.ytu-storyboard {
text-align: center;
}
/* May 2024 hover preview */
.YtPlayerProgressBarProgressBar #previewbar {
transform: none;
}
.ytp-big-mode #previewbar {
transform: scaleY(0.625) translateY(-30%) translateY(1.5px);
}
@@ -39,7 +58,16 @@
}
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 {
@@ -48,6 +76,11 @@ div:hover > #previewbar.sbNotInvidious {
min-width: 1px;
}
.previewbar-yttv {
height: 10px;
top: 14px;
}
.previewbar.requiredSegment {
transform: scaleY(3);
}
@@ -69,7 +102,7 @@ div:hover > #previewbar.sbNotInvidious {
display: none !important;
}
.ytp-tooltip.sponsorCategoryTooltipVisible {
.ytp-tooltip.sponsorCategoryTooltipVisible:not(.sponsorTooltipHasYTChapters) {
transform: translateY(-1em) !important;
}
@@ -165,6 +198,16 @@ div:hover > .sponsorBlockChapterBar {
padding-right: 3.6px;
}
.sbButtonYTTV {
padding-left: 5px !important;
}
/* YTTV only */
.ytu-player-controls > .skipButtonControlBarContainer > div {
padding-left: 5px;
align-content: center;
}
.autoHiding {
overflow: visible !important;
}
@@ -213,6 +256,11 @@ div:hover > .sponsorBlockChapterBar {
from { opacity: 0; }
}
@keyframes fadeInToFaded {
from { opacity: 0; }
to { opacity: 0.5; }
}
@keyframes fadeOut {
to { opacity: 0; }
}
@@ -285,6 +333,10 @@ div:hover > .sponsorBlockChapterBar {
animation: fadeIn 0.5s ease-out;
}
.sponsorSkipNoticeFadeIn.sponsorSkipNoticeFaded {
animation: fadeInToFaded 0.5s ease-out;
}
.exportCopiedNotice .sponsorSkipNoticeFadeIn {
animation: none;
}
@@ -780,6 +832,18 @@ input::-webkit-inner-spin-button {
line-height: 1.5em;
}
/* Description on right layout */
#title > #categoryPillParent {
font-size: 2rem;
font-weight: bold;
display: flex;
justify-content: center;
line-height: 2.8rem;
}
#title > #categoryPillParent > #categoryPill.cbPillOpen {
margin-bottom: 5px;
}
#categoryPillParent {
height: fit-content;
margin-top: auto;
@@ -809,6 +873,15 @@ input::-webkit-inner-spin-button {
white-space: nowrap;
}
/* Vorapis V3 support */
#watch7-content .sponsorBlockCategoryPill {
padding-top: 5px;
padding-bottom: 5px;
}
#watch7-content .sponsorBlockCategoryPillTitle {
font-size: 15px;
}
.categoryPillClose {
display: none;
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

4
public/icons/loop.svg Normal file
View File

@@ -0,0 +1,4 @@
<svg width="24px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="#FFFFFF">
<path d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-8 4-8 7h2s1-5 6-5c1.66 0 3.14.69 4.22 1.78L14 10h6V4l-2.35 2.35z"/>
<path d="M5.85 17.65C7.3 19.1 9.29 20 11.5 20c4.42 0 8-4 8-7h-2s-1 5-6 5c-1.66 0-3.14-.69-4.22-1.78L9.5 14h-6v6z"/>
</svg>

After

Width:  |  Height:  |  Size: 335 B

4
public/icons/looped.svg Normal file
View File

@@ -0,0 +1,4 @@
<svg width="24px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="#80fff6">
<path d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-8 4-8 7h2s1-5 6-5c1.66 0 3.14.69 4.22 1.78L14 10h6V4l-2.35 2.35z"/>
<path d="M5.85 17.65C7.3 19.1 9.29 20 11.5 20c4.42 0 8-4 8-7h-2s-1 5-6 5c-1.66 0-3.14-.69-4.22-1.78L9.5 14h-6v6z"/>
</svg>

After

Width:  |  Height:  |  Size: 334 B

View File

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

View File

@@ -154,20 +154,6 @@
<div class="small-description">__MSG_whatForceChannelCheck__</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 class="switch-container">
<label class="switch">
@@ -209,30 +195,48 @@
<div class="small-description">__MSG_skipNoticeDurationDescription__</div>
</div>
<div data-type="toggle" data-toggle-type="reverse" data-sync="dontShowNotice">
<div class="switch-container">
<label class="switch">
<input id="dontShowNotice" type="checkbox" checked>
<span class="slider round"></span>
</label>
<label class="switch-label" for="dontShowNotice">
__MSG_showSkipNotice__
</label>
<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 class="switch-container">
<label class="switch">
<input id="dontShowNotice" type="checkbox" checked>
<span class="slider round"></span>
</label>
<label class="switch-label" for="dontShowNotice">
__MSG_showSkipNotice__
</label>
</div>
</div>
<div data-type="selector" data-sync="noticeVisibilityMode" data-dependent-on="dontShowNotice">
<br/>
<label class="optionLabel" for="noticeVisibilityMode">__MSG_noticeVisibilityLabel__:</label>
<select id="noticeVisibilityMode" class="selector-element optionsSelector" >
<option value="0">__MSG_noticeVisibilityMode0__</option>
<option value="1">__MSG_noticeVisibilityMode1__</option>
<option value="2">__MSG_noticeVisibilityMode2__</option>
<option value="3">__MSG_noticeVisibilityMode3__</option>
<option value="4">__MSG_noticeVisibilityMode4__</option>
</select>
</div>
</div>
<div data-type="selector" data-sync="noticeVisibilityMode">
<label class="optionLabel" for="noticeVisibilityMode">__MSG_noticeVisibilityLabel__:</label>
<select id="noticeVisibilityMode" class="selector-element optionsSelector" >
<option value="0">__MSG_noticeVisibilityMode0__</option>
<option value="1">__MSG_noticeVisibilityMode1__</option>
<option value="2">__MSG_noticeVisibilityMode2__</option>
<option value="3">__MSG_noticeVisibilityMode3__</option>
<option value="4">__MSG_noticeVisibilityMode4__</option>
</select>
</div>
<div data-type="toggle" data-sync="showCategoryGuidelines">
<div class="switch-container">
<label class="switch">
@@ -468,6 +472,16 @@
<div class="inline"></div>
</div>
<div data-type="keybind-change" data-sync="upvoteKeybind">
<label class="optionLabel">__MSG_setUpvoteKeybind__:</label>
<div class="inline"></div>
</div>
<div data-type="keybind-change" data-sync="downvoteKeybind">
<label class="optionLabel">__MSG_setDownvoteKeybind__:</label>
<div class="inline"></div>
</div>
</div>
<div id="import" class="option-group hidden">
@@ -552,7 +566,7 @@
<div id="advanced" class="option-group hidden">
<div id="support-invidious" data-type="toggle" data-sync="supportInvidious">
<div id="support-invidious" data-type="toggle" data-sync="supportInvidious" data-no-safari="true">
<div class="switch-container">
<label class="switch">
<input id="supportInvidious" type="checkbox">
@@ -567,7 +581,7 @@
<div class="small-description">__MSG_supportOtherSitesDescription__ </div>
</div>
<div data-type="private-text-change" data-sync="invidiousInstances" data-dependent-on="supportInvidious">
<div data-type="private-text-change" data-sync="invidiousInstances" data-dependent-on="supportInvidious" data-no-safari="true">
<div class="option-button trigger-button">
__MSG_addInvidiousInstance__
</div>
@@ -634,7 +648,19 @@
<div class="small-description">__MSG_whatTrackDownvotes__</div>
</div>
<div data-type="toggle" data-sync="trackDownvotesInPrivate" data-confirm-on="false">
<div class="switch-container">
<label class="switch">
<input id="trackDownvotesInPrivate" type="checkbox" checked>
<span class="slider round"></span>
</label>
<label class="switch-label" for="trackDownvotesInPrivate">
__MSG_enableTrackDownvotesInPrivate__
</label>
</div>
</div>
<div data-type="button-press" data-sync="copyDebugInformation" data-confirm-message="copyDebugInformation">
<div class="option-button trigger-button">
__MSG_copyDebugInformation__

View File

@@ -196,6 +196,8 @@
<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://sponsor.ajay.app/donate" target="_blank" rel="noopener" id="sbDonate">__MSG_Donate__</a>
<br />
<a id="debugLogs">__MSG_copyDebugLogs__</a>
</footer>
<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 { generateUserID } from "../maze-utils/src/setup";
// Make the config public for debugging purposes
window.SB = Config;
import Utils from "./utils";
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 { logWarn } from "./utils/logger";
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)});
return false;
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
return true;
@@ -142,9 +138,16 @@ chrome.runtime.onInstalled.addListener(function () {
}
}, 1500);
// Only do this once the old version understands how to clean itself up
if (!isFirefoxOrSafari() && chrome.runtime.getManifest().version !== "5.4.13") {
if (!isFirefoxOrSafari()) {
injectUpdatedScripts().catch(logWarn);
waitFor(() => Config.isReady()).then(() => {
if (Config.config.supportInvidious) {
injectUpdatedScripts([
utils.getExtraSiteRegistration()
])
}
}).catch(logWarn);
}
});
@@ -160,8 +163,8 @@ async function registerFirefoxContentScript(options: Registration) {
ids: [options.id]
}).catch(() => []);
if (existingRegistrations.length > 0
&& existingRegistrations[0].matches.every((match) => options.matches.includes(match))) {
if (existingRegistrations && existingRegistrations.length > 0
&& options.matches.every((match) => existingRegistrations[0].matches.includes(match))) {
// No need to register another script, already registered
return;
}
@@ -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;
if (userID == undefined || userID === "undefined") {
@@ -222,27 +225,35 @@ async function submitVote(type: number, UUID: string, category: string) {
const typeSection = (type !== undefined) ? "&type=" + type : "&category=" + category;
//publish this vote
const response = await asyncRequestToServer("POST", "/api/voteOnSponsorTime?UUID=" + UUID + "&userID=" + userID + typeSection);
if (response.ok) {
return {
successType: 1,
responseText: await response.text()
};
} else if (response.status == 405) {
//duplicate vote
return {
successType: 0,
statusCode: response.status,
responseText: await response.text()
};
} else {
//error while connect
try {
const response = await asyncRequestToServer("POST", "/api/voteOnSponsorTime?UUID=" + UUID + "&videoID=" + videoID + "&userID=" + userID + typeSection);
if (response.ok) {
return {
successType: 1,
responseText: await response.text()
};
} else if (response.status == 405) {
//duplicate vote
return {
successType: 0,
statusCode: response.status,
responseText: await response.text()
};
} else {
//error while connect
return {
successType: -1,
statusCode: response.status,
responseText: await response.text()
};
}
} catch (e) {
console.error(e);
return {
successType: -1,
statusCode: response.status,
responseText: await response.text()
statusCode: -1,
responseText: ""
};
}
}

View File

@@ -6,7 +6,7 @@ import ThumbsUpSvg from "../svg-icons/thumbs_up_svg";
import ThumbsDownSvg from "../svg-icons/thumbs_down_svg";
import { downvoteButtonColor, SkipNoticeAction } from "../utils/noticeUtils";
import { VoteResponse } from "../messageTypes";
import { AnimationUtils } from "../utils/animationUtils";
import { AnimationUtils } from "../../maze-utils/src/animationUtils";
import { Tooltip } from "../render/Tooltip";
import { getErrorMessage } from "../../maze-utils/src/formating";
@@ -23,12 +23,14 @@ export interface CategoryPillState {
}
class CategoryPillComponent extends React.Component<CategoryPillProps, CategoryPillState> {
mainRef: React.MutableRefObject<HTMLSpanElement>;
tooltip?: Tooltip;
constructor(props: CategoryPillProps) {
super(props);
this.mainRef = React.createRef();
this.state = {
segment: null,
show: false,
@@ -43,13 +45,17 @@ class CategoryPillComponent extends React.Component<CategoryPillProps, CategoryP
color: this.getTextColor(),
}
// To be able to remove the margin from the parent
this.mainRef?.current?.parentElement?.classList?.toggle("cbPillOpen", this.state.show);
return (
<span style={style}
className={"sponsorBlockCategoryPill" + (!this.props.showTextByDefault ? " sbPillNoText" : "")}
aria-label={this.getTitleText()}
onClick={(e) => this.toggleOpen(e)}
onMouseEnter={() => this.openTooltip()}
onMouseLeave={() => this.closeTooltip()}>
onMouseLeave={() => this.closeTooltip()}
ref={this.mainRef}>
<span className="sponsorBlockCategoryPillTitleSection">
<img className="sponsorSkipLogo sponsorSkipObject"

View File

@@ -6,7 +6,7 @@ import ThumbsUpSvg from "../svg-icons/thumbs_up_svg";
import ThumbsDownSvg from "../svg-icons/thumbs_down_svg";
import { downvoteButtonColor, SkipNoticeAction } from "../utils/noticeUtils";
import { VoteResponse } from "../messageTypes";
import { AnimationUtils } from "../utils/animationUtils";
import { AnimationUtils } from "../../maze-utils/src/animationUtils";
import { Tooltip } from "../render/Tooltip";
import { getErrorMessage } from "../../maze-utils/src/formating";
@@ -44,7 +44,7 @@ class ChapterVoteComponent extends React.Component<ChapterVoteProps, ChapterVote
<>
{/* Upvote Button */}
<button id={"sponsorTimesDownvoteButtonsContainerUpvoteChapter"}
className={"playerButton sbPlayerUpvote ytp-button " + (!this.state.show ? "sbhidden" : "")}
className={"playerButton sbPlayerUpvote ytp-button " + (!this.state.show ? "sbhidden " : " ") + (document.location.host === "tv.youtube.com" ? "sbButtonYTTV" : "")}
draggable="false"
title={chrome.i18n.getMessage("upvoteButtonInfo")}
onClick={(e) => this.vote(e, 1)}>
@@ -55,7 +55,7 @@ class ChapterVoteComponent extends React.Component<ChapterVoteProps, ChapterVote
{/* Downvote Button */}
<button id={"sponsorTimesDownvoteButtonsContainerDownvoteChapter"}
className={"playerButton sbPlayerDownvote ytp-button " + (!this.state.show ? "sbhidden" : "")}
className={"playerButton sbPlayerDownvote ytp-button " + (!this.state.show ? "sbhidden " : " ") + (document.location.host === "tv.youtube.com" ? "sbButtonYTTV" : "")}
draggable="false"
title={chrome.i18n.getMessage("reportButtonInfo")}
onClick={(e) => {

View File

@@ -19,6 +19,7 @@ export interface NoticeProps {
idSuffix?: string;
fadeIn?: boolean;
fadeOut?: boolean;
startFaded?: boolean;
firstColumn?: React.ReactElement[] | React.ReactElement;
firstRow?: React.ReactElement;
@@ -244,7 +245,7 @@ class NoticeComponent extends React.Component<NoticeProps, NoticeState> {
id={"skipNoticeTimerText" + this.idSuffix}
key="skipNoticeTimerText"
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>
),(
<img
@@ -326,7 +327,7 @@ class NoticeComponent extends React.Component<NoticeProps, NoticeState> {
return;
}
if (countdownTime == 3) {
if (countdownTime == 3 && this.props.fadeOut) {
//start fade out animation
const notice = document.getElementById("sponsorSkipNotice" + this.idSuffix);
notice?.style.removeProperty("animation");

View File

@@ -1,12 +1,12 @@
import * as React from "react";
import * as CompileConfig from "../../config.json";
import Config from "../config"
import { Category, ContentContainer, SponsorTime, NoticeVisbilityMode, ActionType, SponsorSourceType, SegmentUUID } from "../types";
import { Category, ContentContainer, SponsorTime, NoticeVisibilityMode, ActionType, SponsorSourceType, SegmentUUID } from "../types";
import NoticeComponent from "./NoticeComponent";
import NoticeTextSelectionComponent from "./NoticeTextSectionComponent";
import Utils from "../utils";
const utils = new Utils();
import { getSkippingText } from "../utils/categoryUtils";
import { getSkippingText, getUpcomingText, getVoteText } from "../utils/categoryUtils";
import ThumbsUpSvg from "../svg-icons/thumbs_up_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 { keybindToString } from "../../maze-utils/src/config";
import { getFormattedTime } from "../../maze-utils/src/formating";
import { getCurrentTime, getVideo } from "../../maze-utils/src/video";
enum SkipButtonState {
Undo, // Unskip
@@ -27,12 +28,18 @@ export interface SkipNoticeProps {
autoSkip: boolean;
startReskip?: boolean;
upcomingNotice?: boolean;
voteNotice?: boolean;
// Contains functions and variables from the content script needed by the skip notice
contentContainer: ContentContainer;
closeListener: () => void;
showKeybindHint?: boolean;
smaller: boolean;
fadeIn: boolean;
maxCountdownTime?: number;
componentDidMount?: () => void;
unskipTime?: number;
}
@@ -96,9 +103,9 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
this.autoSkip = props.autoSkip;
this.contentContainer = props.contentContainer;
const noticeTitle = getSkippingText(this.segments, this.props.autoSkip);
const noticeTitle = this.props.voteNotice ? getVoteText(this.segments) : !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;
// If there is at least one already in the first slot
this.showInSecondSlot = previousSkipNotices.length > 0 && [...previousSkipNotices].some(notice => !notice.classList.contains("secondSkipNotice"));
@@ -119,7 +126,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
this.lockedColor = Config.config.colorPalette.locked;
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 skipButtonStates = [defaultSkipButtonState, isMuteSegment ? SkipButtonState.Start : defaultSkipButtonState];
@@ -170,12 +177,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
noticeStyle.transform = "scale(0.8) translate(10%, 10%)";
}
// If it started out as smaller, always keep the
// skip button there
const showFirstSkipButton = this.props.smaller || this.segments[0].actionType === ActionType.Mute;
const firstColumn = showFirstSkipButton ? (
this.getSkipButton(0)
) : null;
const firstColumn = this.getSkipButton(0);
return (
<NoticeComponent
@@ -183,9 +185,10 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
amountOfPreviousNotices={this.amountOfPreviousNotices}
showInSecondSlot={this.showInSecondSlot}
idSuffix={this.idSuffix}
fadeIn={true}
startFaded={Config.config.noticeVisibilityMode >= NoticeVisbilityMode.FadedForAll
|| (Config.config.noticeVisibilityMode >= NoticeVisbilityMode.FadedForAutoSkip && this.autoSkip)}
fadeIn={this.props.fadeIn}
fadeOut={!this.props.upcomingNotice}
startFaded={Config.config.noticeVisibilityMode >= NoticeVisibilityMode.FadedForAll
|| (Config.config.noticeVisibilityMode >= NoticeVisibilityMode.FadedForAutoSkip && this.autoSkip)}
timed={true}
maxCountdownTime={this.state.maxCountdownTime}
style={noticeStyle}
@@ -196,12 +199,20 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
logoFill={Config.config.barTypes[this.segments[0].category].color}
limitWidth={true}
firstColumn={firstColumn}
dontPauseCountdown={!!this.props.upcomingNotice}
bottomRow={[...this.getMessageBoxes(), ...this.getBottomRow() ]}
extraClass={this.props.upcomingNotice ? "sponsorSkipUpcomingNotice" : ""}
onMouseEnter={() => this.onMouseEnter() } >
</NoticeComponent>
);
}
componentDidMount(): void {
if (this.props.componentDidMount) {
this.props.componentDidMount();
}
}
getBottomRow(): JSX.Element[] {
return [
/* Bottom Row */
@@ -232,15 +243,18 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
</div>
{/* Copy and Downvote Button */}
<div id={"sponsorTimesDownvoteButtonsContainerCopyDownvote" + this.idSuffix}
className="voteButton"
style={{marginLeft: "5px"}}
onClick={() => this.openEditingOptions()}>
<PencilSvg fill={this.state.editing === true
|| this.state.actionState === SkipNoticeAction.CopyDownvote
|| this.state.choosingCategory === true
? this.selectedColor : this.unselectedColor} />
</div>
{
!this.props.voteNotice &&
<div id={"sponsorTimesDownvoteButtonsContainerCopyDownvote" + this.idSuffix}
className="voteButton"
style={{marginLeft: "5px"}}
onClick={() => this.openEditingOptions()}>
<PencilSvg fill={this.state.editing === true
|| this.state.actionState === SkipNoticeAction.CopyDownvote
|| this.state.choosingCategory === true
? this.selectedColor : this.unselectedColor} />
</div>
}
</td>
:
@@ -268,11 +282,11 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
}
{/* Unskip/Skip Button */}
{!this.props.smaller || this.segments[0].actionType === ActionType.Mute
{!this.props.voteNotice && (!this.props.smaller || this.segments[0].actionType === ActionType.Mute)
? this.getSkipButton(1) : null}
{/* Never show button */}
{!this.autoSkip || this.props.startReskip ? "" :
{!this.autoSkip || this.props.startReskip || this.props.voteNotice ? "" :
<td className="sponsorSkipNoticeRightSection"
key={1}>
<button className="sponsorSkipObject sponsorSkipNoticeButton sponsorSkipNoticeRightButton"
@@ -364,8 +378,10 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
style.minWidth = "100px";
}
const showSkipButton = (buttonIndex !== 0 || this.props.smaller || !this.props.voteNotice || this.segments[0].actionType === ActionType.Mute) && !this.props.upcomingNotice;
return (
<span className="sponsorSkipNoticeUnskipSection">
<span className="sponsorSkipNoticeUnskipSection" style={{ visibility: !showSkipButton ? "hidden" : null }}>
<button id={"sponsorSkipUnskipButton" + this.idSuffix}
className="sponsorSkipObject sponsorSkipNoticeButton"
style={style}
@@ -419,7 +435,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
}
onMouseEnter(): void {
if (this.state.smaller) {
if (this.state.smaller && !this.props.upcomingNotice) {
this.setState({
smaller: false
});
@@ -612,14 +628,17 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
}
unskip(buttonIndex: number, index: number, forceSeek: boolean): void {
this.contentContainer().unskipSponsorTime(this.segments[index], this.props.unskipTime, forceSeek);
this.contentContainer().unskipSponsorTime(this.segments[index], this.props.unskipTime, forceSeek, this.props.voteNotice);
this.unskippedMode(buttonIndex, index, SkipButtonState.Redo);
this.unskippedMode(buttonIndex, index, this.segments[0].actionType === ActionType.Poi ? SkipButtonState.Undo : SkipButtonState.Redo);
}
reskip(buttonIndex: number, index: number, forceSeek: boolean): void {
this.contentContainer().reskipSponsorTime(this.segments[index], forceSeek);
this.reskippedMode(buttonIndex);
}
reskippedMode(buttonIndex: number): void {
const skipButtonStates = this.state.skipButtonStates;
skipButtonStates[buttonIndex] = SkipButtonState.Undo;
@@ -649,7 +668,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
}
getUnskippedModeInfo(buttonIndex: number, index: number, skipButtonState: SkipButtonState): SkipNoticeState {
const changeCountdown = this.segments[index].actionType !== ActionType.Poi;
const changeCountdown = !this.props.voteNotice && this.segments[index].actionType !== ActionType.Poi;
const maxCountdownTime = changeCountdown ?
this.getFullDurationCountdown(index) : this.state.maxCountdownTime;
@@ -657,7 +676,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
const skipButtonStates = this.state.skipButtonStates;
const skipButtonCallbacks = this.state.skipButtonCallbacks;
if (buttonIndex === null) {
for (let i = 0; i < this.segments.length; i++) {
for (let i = 0; i < skipButtonStates.length; i++) {
skipButtonStates[i] = skipButtonState;
skipButtonCallbacks[i] = this.reskip.bind(this);
}
@@ -685,7 +704,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
getFullDurationCountdown(index: number): () => number {
return () => {
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() ?? 0)) * (1 / (getVideo()?.playbackRate ?? 1)));
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 { asyncRequestToServer } from "../utils/requests";
import { defaultPreviewTime } from "../utils/constants";
import { getVideo, getVideoDuration } from "../../maze-utils/src/video";
export interface SponsorTimeEditProps {
index: number;
@@ -272,48 +273,49 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
</div>
): ""}
<br/>
{/* Editing Tools */}
<span id={"sponsorTimeDeleteButton" + this.idSuffix}
className="sponsorTimeEditButton"
onClick={this.deleteTime.bind(this)}>
{chrome.i18n.getMessage("delete")}
</span>
{(!isNaN(segment[1]) && ![ActionType.Poi, ActionType.Full].includes(this.state.selectedActionType))
&& this.state.selectedActionType !== ActionType.Chapter ? (
<span id={"sponsorTimePreviewButton" + this.idSuffix}
<div style={{ marginTop: "3px" }}>
<span id={"sponsorTimeDeleteButton" + this.idSuffix}
className="sponsorTimeEditButton"
onClick={(e) => this.previewTime(e.ctrlKey, e.shiftKey)}>
{chrome.i18n.getMessage("preview")}
onClick={this.deleteTime.bind(this)}>
{chrome.i18n.getMessage("delete")}
</span>
): ""}
{(!isNaN(segment[1]) && this.state.selectedActionType != ActionType.Full) ? (
<span id={"sponsorTimeInspectButton" + this.idSuffix}
className="sponsorTimeEditButton"
onClick={this.inspectTime.bind(this)}>
{chrome.i18n.getMessage("inspect")}
</span>
): ""}
{(!isNaN(segment[1]) && ![ActionType.Poi, ActionType.Full].includes(this.state.selectedActionType))
&& this.state.selectedActionType !== ActionType.Chapter ? (
<span id={"sponsorTimePreviewButton" + this.idSuffix}
className="sponsorTimeEditButton"
onClick={(e) => this.previewTime(e.ctrlKey, e.shiftKey)}>
{chrome.i18n.getMessage("preview")}
</span>
): ""}
{(!isNaN(segment[1]) && ![ActionType.Poi, ActionType.Full].includes(this.state.selectedActionType)) ? (
<span id={"sponsorTimePreviewEndButton" + this.idSuffix}
className="sponsorTimeEditButton"
onClick={(e) => this.previewTime(e.ctrlKey, e.shiftKey, true)}>
{chrome.i18n.getMessage("End")}
</span>
): ""}
{(!isNaN(segment[1]) && this.state.selectedActionType != ActionType.Full) ? (
<span id={"sponsorTimeInspectButton" + this.idSuffix}
className="sponsorTimeEditButton"
onClick={this.inspectTime.bind(this)}>
{chrome.i18n.getMessage("inspect")}
</span>
): ""}
{(!isNaN(segment[1]) && ![ActionType.Poi, ActionType.Full].includes(this.state.selectedActionType)) ? (
<span id={"sponsorTimePreviewEndButton" + this.idSuffix}
className="sponsorTimeEditButton"
onClick={(e) => this.previewTime(e.ctrlKey, e.shiftKey, true)}>
{chrome.i18n.getMessage("End")}
</span>
): ""}
{(!isNaN(segment[1]) && this.state.selectedActionType != ActionType.Full) ? (
<span id={"sponsorTimeEditButton" + this.idSuffix}
className="sponsorTimeEditButton"
onClick={this.toggleEditTime.bind(this)}>
{this.state.editing ? chrome.i18n.getMessage("save") : chrome.i18n.getMessage("edit")}
</span>
): ""}
</div>
{(!isNaN(segment[1]) && this.state.selectedActionType != ActionType.Full) ? (
<span id={"sponsorTimeEditButton" + this.idSuffix}
className="sponsorTimeEditButton"
onClick={this.toggleEditTime.bind(this)}>
{this.state.editing ? chrome.i18n.getMessage("save") : chrome.i18n.getMessage("edit")}
</span>
): ""}
</div>
);
}
@@ -400,7 +402,7 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
checkToShowFullVideoWarning(): void {
const sponsorTime = this.props.contentContainer().sponsorTimesSubmitting[this.props.index];
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
&& (sponsorTime.category === "sponsor" || sponsorTime.category === "selfpromo" || sponsorTime.category === "chooseACategory")) {
@@ -552,7 +554,7 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
}
setTimeToEnd(): void {
this.setTimeTo(1, this.props.contentContainer().v.duration);
this.setTimeTo(1, getVideoDuration());
}
/**
@@ -639,7 +641,7 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
sponsorTimesSubmitting[this.props.index].segment[0] = startTime;
}
} 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();
}
@@ -682,7 +684,7 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
const endTime = sponsorTimes[index].segment[1];
// 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);
}

View File

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

View File

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

View File

@@ -146,7 +146,9 @@ class KeybindDialogComponent extends React.Component<KeybindDialogProps, Keybind
this.props.option !== "actuallySubmitKeybind" && this.equals(Config.config['actuallySubmitKeybind']) ||
this.props.option !== "previewKeybind" && this.equals(Config.config['previewKeybind']) ||
this.props.option !== "closeSkipNoticeKeybind" && this.equals(Config.config['closeSkipNoticeKeybind']) ||
this.props.option !== "startSponsorKeybind" && this.equals(Config.config['startSponsorKeybind']))
this.props.option !== "startSponsorKeybind" && this.equals(Config.config['startSponsorKeybind']) ||
this.props.option !== "downvoteKeybind" && this.equals(Config.config['downvoteKeybind']) ||
this.props.option !== "upvoteKeybind" && this.equals(Config.config['upvoteKeybind']))
return {message: chrome.i18n.getMessage("keyAlreadyUsed"), blocking: true};
return null;

View File

@@ -1,6 +1,6 @@
import * as CompileConfig from "../config.json";
import * as invidiousList from "../ci/invidiouslist.json";
import { Category, CategorySelection, CategorySkipOption, NoticeVisbilityMode, PreviewBarOption, SponsorTime, VideoID, SponsorHideType } from "./types";
import { Category, CategorySelection, CategorySkipOption, NoticeVisibilityMode, PreviewBarOption, SponsorTime, VideoID, SponsorHideType } from "./types";
import { Keybind, ProtoConfig, keybindEquals } from "../maze-utils/src/config";
import { HashedValue } from "../maze-utils/src/hash";
@@ -29,8 +29,10 @@ interface SBConfig {
trackViewCount: boolean;
trackViewCountInPrivate: boolean;
trackDownvotes: boolean;
trackDownvotesInPrivate: boolean;
dontShowNotice: boolean;
noticeVisibilityMode: NoticeVisbilityMode;
showUpcomingNotice: boolean;
noticeVisibilityMode: NoticeVisibilityMode;
hideVideoPlayerControls: boolean;
hideInfoButtonPlayerControls: boolean;
hideDeleteButtonPlayerControls: boolean;
@@ -46,7 +48,6 @@ interface SBConfig {
audioNotificationOnSkip: boolean;
checkForUnlistedVideos: boolean;
testingServer: boolean;
refetchWhenNotFound: boolean;
ytInfoPermissionGranted: boolean;
allowExpirements: boolean;
showDonationLink: boolean;
@@ -56,6 +57,7 @@ interface SBConfig {
donateClicked: number;
autoHideInfoButton: boolean;
autoSkipOnMusicVideos: boolean;
skipNonMusicOnlyOnYoutubeMusic: boolean;
colorPalette: {
red: string;
white: string;
@@ -68,6 +70,7 @@ interface SBConfig {
showCategoryGuidelines: boolean;
showCategoryWithoutPermission: boolean;
showSegmentNameInChapterBar: boolean;
showAutogeneratedChapters: boolean;
useVirtualTime: boolean;
showSegmentFailedToFetchWarning: boolean;
allowScrollingToEdit: boolean;
@@ -95,6 +98,8 @@ interface SBConfig {
nextChapterKeybind: Keybind;
previousChapterKeybind: Keybind;
closeSkipNoticeKeybind: Keybind;
upvoteKeybind: Keybind;
downvoteKeybind: Keybind;
// What categories should be skipped
categorySelections: CategorySelection[];
@@ -139,7 +144,7 @@ interface SBStorage {
downvotedSegments: Record<VideoID & HashedValue, VideoDownvotes>;
navigationApiAvailable: boolean;
// Used when sync storage disbaled
// Used when sync storage disabled
alreadyInstalled: boolean;
/* Contains unsubmitted segments that the user has created. */
@@ -290,8 +295,10 @@ const syncDefaults = {
trackViewCount: true,
trackViewCountInPrivate: true,
trackDownvotes: true,
trackDownvotesInPrivate: false,
dontShowNotice: false,
noticeVisibilityMode: NoticeVisbilityMode.FadedForAutoSkip,
showUpcomingNotice: false,
noticeVisibilityMode: NoticeVisibilityMode.FadedForAutoSkip,
hideVideoPlayerControls: false,
hideInfoButtonPlayerControls: false,
hideDeleteButtonPlayerControls: false,
@@ -307,7 +314,6 @@ const syncDefaults = {
audioNotificationOnSkip: false,
checkForUnlistedVideos: false,
testingServer: false,
refetchWhenNotFound: true,
ytInfoPermissionGranted: false,
allowExpirements: true,
showDonationLink: true,
@@ -317,6 +323,7 @@ const syncDefaults = {
donateClicked: 0,
autoHideInfoButton: true,
autoSkipOnMusicVideos: false,
skipNonMusicOnlyOnYoutubeMusic: false,
scrollToEditTimeUpdate: false, // false means the tooltip will be shown
categoryPillUpdate: false,
showChapterInfoMessage: true,
@@ -324,6 +331,7 @@ const syncDefaults = {
showCategoryGuidelines: true,
showCategoryWithoutPermission: false,
showSegmentNameInChapterBar: true,
showAutogeneratedChapters: true,
useVirtualTime: true,
showSegmentFailedToFetchWarning: true,
allowScrollingToEdit: true,
@@ -352,6 +360,8 @@ const syncDefaults = {
nextChapterKeybind: { key: "ArrowRight", ctrl: true },
previousChapterKeybind: { key: "ArrowLeft", ctrl: true },
closeSkipNoticeKeybind: { key: "Backspace" },
downvoteKeybind: { key: "h", shift: true },
upvoteKeybind: { key: "g", shift: true },
categorySelections: [{
name: "sponsor" as Category,
@@ -462,7 +472,11 @@ const syncDefaults = {
"preview-filler": {
color: "#2E0066",
opacity: "0.7"
}
},
"chapter": {
color: "#fff",
opacity: "0"
},
}
};
@@ -475,4 +489,26 @@ const localDefaults = {
};
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);
}

File diff suppressed because it is too large Load Diff

15
src/globals.d.ts vendored
View File

@@ -1,19 +1,4 @@
import { SBObject } from "./config";
declare global {
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

@@ -11,9 +11,11 @@ import { ActionType, Category, SegmentContainer, SponsorHideType, SponsorSourceT
import { partition } from "../utils/arrayUtils";
import { DEFAULT_CATEGORY, shortCategoryName } from "../utils/categoryUtils";
import { normalizeChapterName } from "../utils/exporter";
import { getFormattedTimeToSeconds } from "../../maze-utils/src/formating";
import { findValidElement } from "../../maze-utils/src/dom";
import { addCleanupListener } from "../../maze-utils/src/cleanup";
import { hasAutogeneratedChapters, isVisible } from "../utils/pageUtils";
import { isVorapisInstalled } from "../utils/compatibility";
import { isOnYTTV } from "../../maze-utils/src/video";
const TOOLTIP_VISIBLE_CLASS = 'sponsorCategoryTooltipVisible';
const MIN_CHAPTER_SIZE = 0.003;
@@ -40,6 +42,12 @@ class PreviewBar {
categoryTooltip?: HTMLDivElement;
categoryTooltipContainer?: HTMLElement;
chapterTooltip?: HTMLDivElement;
// ScrubTooltips for YTTV only
categoryScrubTooltip?: HTMLDivElement;
categoryScrubTooltipContainer?: HTMLElement;
chapterScrubTooltip?: HTMLDivElement;
lastSmallestSegment: Record<string, {
index: number;
segment: PreviewBarSegment;
@@ -48,9 +56,11 @@ class PreviewBar {
parent: HTMLElement;
onMobileYouTube: boolean;
onInvidious: boolean;
onYTTV: boolean;
progressBar: HTMLElement;
segments: PreviewBarSegment[] = [];
hasYouTubeChapters = false;
existingChapters: PreviewBarSegment[] = [];
videoDuration = 0;
updateExistingChapters: () => void;
@@ -68,14 +78,19 @@ class PreviewBar {
unfilteredChapterGroups: ChapterGroup[];
chapterGroups: ChapterGroup[];
constructor(parent: HTMLElement, onMobileYouTube: boolean, onInvidious: boolean, chapterVote: ChapterVote, updateExistingChapters: () => void, test=false) {
constructor(parent: HTMLElement, onMobileYouTube: boolean, onInvidious: boolean, onYTTV: boolean, chapterVote: ChapterVote, updateExistingChapters: () => void, test=false) {
if (test) return;
this.container = document.createElement('ul');
this.container.id = 'previewbar';
if (onYTTV) {
this.container.classList.add("sponsorblock-yttv-container");
}
this.parent = parent;
this.onMobileYouTube = onMobileYouTube;
this.onInvidious = onInvidious;
this.onYTTV = onYTTV;
this.chapterVote = chapterVote;
this.updateExistingChapters = updateExistingChapters;
@@ -95,24 +110,49 @@ class PreviewBar {
// Create label placeholder
this.categoryTooltip = document.createElement("div");
this.categoryTooltip.className = "ytp-tooltip-title sponsorCategoryTooltip";
if (isOnYTTV()) {
this.categoryTooltip.className = "sponsorCategoryTooltip";
} else {
this.categoryTooltip.className = "ytp-tooltip-title sponsorCategoryTooltip";
}
this.chapterTooltip = document.createElement("div");
this.chapterTooltip.className = "ytp-tooltip-title sponsorCategoryTooltip";
if (isOnYTTV()) {
this.chapterTooltip.className = "sponsorCategoryTooltip";
} else {
this.chapterTooltip.className = "ytp-tooltip-title sponsorCategoryTooltip";
}
// global chaper tooltip or duration tooltip
const tooltipTextWrapper = document.querySelector(".ytp-tooltip-text-wrapper") ?? document.querySelector("#progress-bar-container.ytk-player > #hover-time-info");
const originalTooltip = tooltipTextWrapper.querySelector(".ytp-tooltip-title:not(.sponsorCategoryTooltip)") as HTMLElement;
if (isOnYTTV()) {
this.categoryScrubTooltip = document.createElement("div");
this.categoryScrubTooltip.className = "sponsorCategoryTooltip";
this.chapterScrubTooltip = document.createElement("div");
this.chapterScrubTooltip.className = "sponsorCategoryTooltip";
}
// global chapter tooltip or duration tooltip
// YT, Vorapis, unknown, YTTV
const tooltipTextWrapper = document.querySelector(".ytp-tooltip-text-wrapper, .ytp-progress-tooltip-text-container, .yssi-slider .ys-seek-details .time-info-bar") ?? 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), .current-time:not(.sponsorCategoryTooltip)") as HTMLElement;
if (!tooltipTextWrapper || !tooltipTextWrapper.parentElement) return;
// Grab the tooltip from the text wrapper as the tooltip doesn't have its classes on init
this.categoryTooltipContainer = tooltipTextWrapper.parentElement;
const titleTooltip = tooltipTextWrapper.querySelector(".ytp-tooltip-title") as HTMLElement;
// YT, Vorapis, YTTV
const titleTooltip = tooltipTextWrapper.querySelector(".ytp-tooltip-title, .ytp-progress-tooltip-text, .current-time") as HTMLElement;
if (!this.categoryTooltipContainer || !titleTooltip) return;
tooltipTextWrapper.insertBefore(this.categoryTooltip, titleTooltip.nextSibling);
tooltipTextWrapper.insertBefore(this.chapterTooltip, titleTooltip.nextSibling);
const seekBar = document.querySelector(".ytp-progress-bar-container");
if (isOnYTTV()) {
const scrubTooltipTextWrapper = document.querySelector(".yssi-slider .ysl-filmstrip-lens .time-info-bar")
if (!this.categoryTooltipContainer) return;
scrubTooltipTextWrapper.appendChild(this.categoryScrubTooltip);
scrubTooltipTextWrapper.appendChild(this.chapterScrubTooltip);
}
const seekBar = (document.querySelector(".ytp-progress-bar-container, .ypcs-scrub-slider-slot.ytu-player-controls"));
if (!seekBar) return;
let mouseOnSeekBar = false;
@@ -125,38 +165,15 @@ class PreviewBar {
mouseOnSeekBar = false;
});
const observer = new MutationObserver((mutations) => {
if (!mouseOnSeekBar || !this.categoryTooltip || !this.categoryTooltipContainer) return;
seekBar.addEventListener("mousemove", (e: MouseEvent) => {
if (!mouseOnSeekBar || !this.categoryTooltip || !this.categoryTooltipContainer || !chrome.runtime?.id) return;
// Only care about mutations to time tooltip
if (!mutations.some((mutation) => (mutation.target as HTMLElement).classList.contains("ytp-tooltip-text"))) {
return;
}
const tooltipTextElements = tooltipTextWrapper.querySelectorAll(".ytp-tooltip-text");
let timeInSeconds: number | null = null;
let noYoutubeChapters = false;
for (const tooltipTextElement of tooltipTextElements) {
if (tooltipTextElement.classList.contains('ytp-tooltip-text-no-title')) noYoutubeChapters = true;
const tooltipText = tooltipTextElement.textContent;
if (tooltipText === null || tooltipText.length === 0) continue;
timeInSeconds = getFormattedTimeToSeconds(tooltipText);
if (timeInSeconds !== null) break;
}
if (timeInSeconds === null) {
originalTooltip.style.removeProperty("display");
return;
}
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);
// Find the segment at that location, using the shortest if multiple found
const [normalSegments, chapterSegments] =
partition(this.segments.filter((s) => s.source !== SponsorSourceType.YouTube),
partition(this.segments,
(segment) => segment.actionType !== ActionType.Chapter);
let mainSegment = this.getSmallestSegment(timeInSeconds, normalSegments, "normal");
let secondarySegment = this.getSmallestSegment(timeInSeconds, chapterSegments, "chapter");
@@ -165,19 +182,49 @@ class PreviewBar {
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) {
this.categoryTooltipContainer.classList.remove(TOOLTIP_VISIBLE_CLASS);
originalTooltip.style.removeProperty("display");
if (!hasAYouTubeChapterRemoved) {
this.categoryTooltipContainer.classList.remove(TOOLTIP_VISIBLE_CLASS);
originalTooltip.style.removeProperty("display");
}
if (this.onYTTV) {
this.setTooltipTitle(mainSegment, this.categoryTooltip);
this.setTooltipTitle(secondarySegment, this.chapterTooltip);
this.setTooltipTitle(mainSegment, this.categoryScrubTooltip);
this.setTooltipTitle(secondarySegment, this.chapterScrubTooltip);
}
} else {
this.categoryTooltipContainer.classList.add(TOOLTIP_VISIBLE_CLASS);
if (mainSegment !== null && secondarySegment !== null) {
this.categoryTooltipContainer.classList.add("sponsorTwoTooltips");
originalTooltip.classList.remove("sponsorTooltipHasYTChapters");
} else {
this.categoryTooltipContainer.classList.remove("sponsorTwoTooltips");
}
this.setTooltipTitle(mainSegment, this.categoryTooltip);
this.setTooltipTitle(secondarySegment, this.chapterTooltip);
if (this.onYTTV) {
this.setTooltipTitle(mainSegment, this.categoryScrubTooltip);
this.setTooltipTitle(secondarySegment, this.chapterScrubTooltip);
}
if (isVorapisInstalled()) {
const tooltipParent = tooltipTextWrapper.parentElement!;
tooltipParent.classList.add("with-text");
}
if (normalizeChapterName(originalTooltip.textContent) === normalizeChapterName(this.categoryTooltip.textContent)
|| normalizeChapterName(originalTooltip.textContent) === normalizeChapterName(this.chapterTooltip.textContent)) {
@@ -187,25 +234,16 @@ class PreviewBar {
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
this.categoryTooltip.style.right = titleTooltip.style.right;
this.chapterTooltip.style.right = titleTooltip.style.right;
this.categoryTooltip.style.textAlign = titleTooltip.style.textAlign;
this.chapterTooltip.style.textAlign = titleTooltip.style.textAlign;
}
});
observer.observe(tooltipTextWrapper, {
childList: true,
subtree: true,
});
addCleanupListener(() => {
observer.disconnect();
// Used to prevent overlapping
this.categoryTooltip.classList.toggle("ytp-tooltip-text-no-title", noYoutubeChapters);
this.chapterTooltip.classList.toggle("ytp-tooltip-text-no-title", noYoutubeChapters);
});
}
@@ -229,13 +267,17 @@ class PreviewBar {
if (this.onMobileYouTube) {
this.container.style.transform = "none";
this.container.style.height = "var(--yt-progress-bar-height)";
} else if (!this.onInvidious) {
this.container.classList.add("sbNotInvidious");
}
// On the seek bar
this.parent.prepend(this.container);
if (this.onYTTV) {
// order of sibling elements matters on YTTV
this.parent.insertBefore(this.container, this.parent.firstChild.nextSibling.nextSibling);
} else {
this.parent.prepend(this.container);
}
}
clear(): void {
@@ -257,11 +299,28 @@ class PreviewBar {
set(segments: PreviewBarSegment[], videoDuration: number): void {
this.segments = segments ?? [];
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();
// Sometimes video duration is inaccurate, pull from accessibility info
const ariaDuration = parseInt(this.progressBar?.getAttribute('aria-valuemax')) ?? 0;
if (ariaDuration && Math.abs(ariaDuration - this.videoDuration) > 3) {
const multipleActiveVideos = [...document.querySelectorAll("video")].filter((v) => isVisible(v)).length > 1;
if (!multipleActiveVideos && ariaDuration && Math.abs(ariaDuration - this.videoDuration) > 3) {
this.videoDuration = ariaDuration;
}
@@ -269,7 +328,8 @@ class PreviewBar {
}
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];
if (this.progressBar) {
@@ -307,6 +367,7 @@ class PreviewBar {
return (b[1] - b[0]) - (a[1] - a[0]);
});
for (const segment of sortedSegments) {
if (segment.actionType === ActionType.Chapter) continue;
const bar = this.createBar(segment);
this.container.appendChild(bar);
@@ -346,12 +407,16 @@ class PreviewBar {
bar.style.left = this.timeToPercentage(startTime);
if (duration > 0) {
bar.style.right = this.timeToPercentage(this.videoDuration - endTime);
bar.style.right = this.timeToRightPercentage(endTime);
}
if (this.chapterFilter(barSegment) && segment[1] < this.videoDuration) {
bar.style.marginRight = `${this.chapterMargin}px`;
}
if (this.onYTTV) {
bar.classList.add("previewbar-yttv");
}
return bar;
}
@@ -372,11 +437,13 @@ class PreviewBar {
// Merge overlapping chapters
this.unfilteredChapterGroups = this.createChapterRenderGroups(segments);
}
if (segments.every((segments) => segments.source === SponsorSourceType.YouTube)
if ((segments.every((segments) => segments.source === SponsorSourceType.YouTube)
|| (!Config.config.renderSegmentsAsChapters
&& 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";
this.originalChapterBar.style.removeProperty("display");
return;
@@ -402,6 +469,15 @@ class PreviewBar {
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.customChaptersBar) this.customChaptersBar.style.display = "none";
this.originalChapterBar.style.removeProperty("display");
@@ -677,8 +753,21 @@ class PreviewBar {
if (changedData.scale !== null) {
const transformScale = (changedData.scale) / progressBar.clientWidth;
const scale = Math.max(0, Math.min(1 - calculatedLeft, (transformScale - cursor) / fullSectionWidth - calculatedLeft));
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) {
customChangedElement.style.transition = "none";
setTimeout(() => customChangedElement.style.removeProperty("transition"), 50);
@@ -780,7 +869,9 @@ class PreviewBar {
updateChapterText(segments: SponsorTime[], submittingSegments: SponsorTime[], currentTime: number): SponsorTime[] {
if (!Config.config.showSegmentNameInChapterBar
|| ((!segments || segments.length <= 0) && submittingSegments?.length <= 0)) {
|| Config.config.disableSkipping
|| ((!segments || segments.length <= 0) && submittingSegments?.length <= 0
&& (Config.config.showAutogeneratedChapters || !hasAutogeneratedChapters()))) {
const chaptersContainer = this.getChaptersContainer();
if (chaptersContainer) {
chaptersContainer.querySelector(".sponsorChapterText")?.remove();
@@ -820,14 +911,22 @@ class PreviewBar {
return -1;
} else if (a.actionType !== ActionType.Chapter && b.actionType === ActionType.Chapter) {
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 {
return (b.segment[0] - a.segment[0]);
return (b.segment[0] - a.segment[0]) * 4;
}
})[0];
const chapterButton = this.getChapterButton(chaptersContainer);
chapterButton.classList.remove("ytp-chapter-container-disabled");
chapterButton.disabled = false;
if (chapterButton) {
chapterButton.classList.remove("ytp-chapter-container-disabled");
chapterButton.disabled = false;
}
const chapterTitle = chaptersContainer.querySelector(".ytp-chapter-title-content") as HTMLDivElement;
chapterTitle.style.display = "none";
@@ -836,6 +935,9 @@ class PreviewBar {
const elem = document.createElement("div");
chapterTitle.parentElement.insertBefore(elem, chapterTitle);
elem.classList.add("sponsorChapterText");
if (document.location.host === "tv.youtube.com") {
elem.style.lineHeight = "initial";
}
return elem;
})()) as HTMLDivElement;
chapterCustomText.innerText = chosenSegment.description || shortCategoryName(chosenSegment.category);
@@ -848,7 +950,15 @@ class PreviewBar {
if (chosenSegment.source === SponsorSourceType.Server) {
const chapterVoteContainer = this.chapterVote.getContainer();
if (!chapterButton.contains(chapterVoteContainer)) {
if (document.location.host === "tv.youtube.com") {
if (!chaptersContainer.contains(chapterVoteContainer)) {
const oldVoteContainers = document.querySelectorAll("#chapterVote");
if (oldVoteContainers.length > 0) {
oldVoteContainers.forEach((oldVoteContainer) => oldVoteContainer.remove());
}
chaptersContainer.appendChild(chapterVoteContainer);
}
} else if (!chapterButton.contains(chapterVoteContainer)) {
const oldVoteContainers = document.querySelectorAll("#chapterVote");
if (oldVoteContainers.length > 0) {
oldVoteContainers.forEach((oldVoteContainer) => oldVoteContainer.remove());
@@ -862,6 +972,18 @@ class PreviewBar {
} else {
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 {
chaptersContainer.querySelector(".sponsorChapterText")?.remove();
const chapterTitle = chaptersContainer.querySelector(".ytp-chapter-title-content") as HTMLDivElement;
@@ -875,6 +997,18 @@ class PreviewBar {
}
private getChaptersContainer(): HTMLElement {
if (document.location.host === "tv.youtube.com") {
if (!document.querySelector(".ytp-chapter-container")) {
const dest = document.querySelector(".ypcs-control-buttons-left");
if (!dest) return null;
const sbChapterContainer = document.createElement("div");
sbChapterContainer.className = "ytp-chapter-container";
const sbChapterTitleContent = document.createElement("div");
sbChapterTitleContent.className = "ytp-chapter-title-content";
sbChapterContainer.appendChild(sbChapterTitleContent);
dest.appendChild(sbChapterContainer);
}
}
return document.querySelector(".ytp-chapter-container") as HTMLElement;
}
@@ -919,7 +1053,22 @@ class PreviewBar {
return `${this.timeToDecimal(time) * 100}%`
}
timeToRightPercentage(time: number): string {
return `${(1 - this.timeToDecimal(time)) * 100}%`
}
timeToDecimal(time: number): number {
return this.decimalTimeConverter(time, true);
}
decimalToTime(decimal: number): number {
return this.decimalTimeConverter(decimal, false);
}
/**
* Decimal to time or time to decimal
*/
decimalTimeConverter(value: number, isTime: boolean): number {
if (this.originalChapterBarBlocks?.length > 1 && this.existingChapters.length === this.originalChapterBarBlocks?.length) {
// Parent element to still work when display: none
const totalPixels = this.originalChapterBar.parentElement.clientWidth;
@@ -929,8 +1078,9 @@ class PreviewBar {
const chapterElement = this.originalChapterBarBlocks[i];
const widthPixels = parseFloat(chapterElement.style.width.replace("px", ""));
if (time >= this.existingChapters[i].segment[1]) {
const marginPixels = chapterElement.style.marginRight ? parseFloat(chapterElement.style.marginRight.replace("px", "")) : 0;
const marginPixels = chapterElement.style.marginRight ? parseFloat(chapterElement.style.marginRight.replace("px", "")) : 0;
if ((isTime && value >= this.existingChapters[i].segment[1])
|| (!isTime && value >= (pixelOffset + widthPixels + marginPixels) / totalPixels)) {
pixelOffset += widthPixels + marginPixels;
lastCheckedChapter = i;
} else {
@@ -944,13 +1094,22 @@ class PreviewBar {
const latestWidth = parseFloat(this.originalChapterBarBlocks[lastCheckedChapter + 1].style.width.replace("px", ""));
const latestChapterDuration = latestChapter.segment[1] - latestChapter.segment[0];
const percentageInCurrentChapter = (time - latestChapter.segment[0]) / latestChapterDuration;
const sizeOfCurrentChapter = latestWidth / totalPixels;
return Math.min(1, ((pixelOffset / totalPixels) + (percentageInCurrentChapter * sizeOfCurrentChapter)));
if (isTime) {
const percentageInCurrentChapter = (value - latestChapter.segment[0]) / latestChapterDuration;
const sizeOfCurrentChapter = latestWidth / totalPixels;
return Math.min(1, ((pixelOffset / totalPixels) + (percentageInCurrentChapter * sizeOfCurrentChapter)));
} else {
const percentageInCurrentChapter = (value * totalPixels - pixelOffset) / latestWidth;
return Math.max(0, latestChapter.segment[0] + (percentageInCurrentChapter * latestChapterDuration));
}
}
}
return Math.min(1, time / this.videoDuration);
if (isTime) {
return Math.min(1, value / this.videoDuration);
} else {
return Math.max(0, value * this.videoDuration);
}
}
/*

View File

@@ -1,7 +1,7 @@
import Config from "../config";
import { SegmentUUID, SponsorTime } from "../types";
import { getSkippingText } from "../utils/categoryUtils";
import { AnimationUtils } from "../utils/animationUtils";
import { AnimationUtils } from "../../maze-utils/src/animationUtils";
import { keybindToString } from "../../maze-utils/src/config";
import { isMobileControlsOpen } from "../utils/mobileUtils";
@@ -9,6 +9,7 @@ export interface SkipButtonControlBarProps {
skip: (segment: SponsorTime) => void;
selectSegment: (UUID: SegmentUUID) => void;
onMobileYouTube: boolean;
onYTTV: boolean;
}
export class SkipButtonControlBar {
@@ -21,6 +22,7 @@ export class SkipButtonControlBar {
showKeybindHint = true;
onMobileYouTube: boolean;
onYTTV: boolean;
enabled = false;
@@ -40,6 +42,7 @@ export class SkipButtonControlBar {
constructor(props: SkipButtonControlBarProps) {
this.skip = props.skip;
this.onMobileYouTube = props.onMobileYouTube;
this.onYTTV = props.onYTTV;
this.container = document.createElement("div");
this.container.classList.add("skipButtonControlBarContainer");
@@ -50,6 +53,10 @@ export class SkipButtonControlBar {
this.skipIcon.src = chrome.runtime.getURL("icons/skipIcon.svg");
this.skipIcon.classList.add("ytp-button");
this.skipIcon.id = "sbSkipIconControlBarImage";
if (this.onYTTV) {
this.skipIcon.style.width = "24px";
this.skipIcon.style.height = "24px";
}
this.textContainer = document.createElement("div");
@@ -84,7 +91,7 @@ export class SkipButtonControlBar {
this.chapterText = document.querySelector(".ytp-chapter-container");
if (mountingContainer && !mountingContainer.contains(this.container)) {
if (this.onMobileYouTube) {
if (this.onMobileYouTube || this.onYTTV) {
mountingContainer.appendChild(this.container);
} else {
mountingContainer.insertBefore(this.container, this.chapterText);
@@ -101,8 +108,10 @@ export class SkipButtonControlBar {
}
private getMountingContainer(): HTMLElement {
if (!this.onMobileYouTube) {
if (!this.onMobileYouTube && !this.onYTTV) {
return document.querySelector(".ytp-left-controls");
} else if (this.onYTTV) {
return document.querySelector(".ypcs-control-buttons-left");
} else {
return document.getElementById("player-container-id");
}

View File

@@ -17,7 +17,9 @@ interface DefaultMessage {
| "isChannelWhitelisted"
| "submitTimes"
| "refreshSegments"
| "closePopup";
| "closePopup"
| "getLogs"
| "getLoopedChapter";
}
interface BoolValueMessage {
@@ -57,6 +59,11 @@ interface ImportSegmentsMessage {
data: string;
}
interface LoopChapterMessage {
message: "loopChapter";
UUID: SegmentUUID;
}
interface KeyDownMessage {
message: "keydown";
key: string;
@@ -69,7 +76,7 @@ interface KeyDownMessage {
metaKey: boolean;
}
export type Message = BaseMessage & (DefaultMessage | BoolValueMessage | IsInfoFoundMessage | SkipMessage | SubmitVoteMessage | HideSegmentMessage | CopyToClipboardMessage | ImportSegmentsMessage | KeyDownMessage);
export type Message = BaseMessage & (DefaultMessage | BoolValueMessage | IsInfoFoundMessage | SkipMessage | SubmitVoteMessage | HideSegmentMessage | CopyToClipboardMessage | ImportSegmentsMessage | KeyDownMessage | LoopChapterMessage);
export interface IsInfoFoundMessageResponse {
found: boolean;
@@ -85,6 +92,7 @@ interface GetVideoIdResponse {
export interface GetChannelIDResponse {
channelID: string;
isYTTV: boolean;
}
export interface SponsorStartResponse {
@@ -95,6 +103,10 @@ export interface IsChannelWhitelistedResponse {
value: boolean;
}
export interface LoopedChapterResponse {
UUID: SegmentUUID;
}
export type MessageResponse =
IsInfoFoundMessageResponse
| GetVideoIdResponse
@@ -103,7 +115,10 @@ export type MessageResponse =
| IsChannelWhitelistedResponse
| Record<string, never> // empty object response {}
| VoteResponse
| ImportSegmentsResponse;
| ImportSegmentsResponse
| RefreshSegmentsResponse
| LogResponse
| LoopedChapterResponse;
export interface VoteResponse {
successType: number;
@@ -115,6 +130,15 @@ interface ImportSegmentsResponse {
importedSegments: SponsorTime[];
}
export interface RefreshSegmentsResponse {
hasVideo: boolean;
}
export interface LogResponse {
debug: string[];
warn: string[];
}
export interface TimeUpdateMessage {
message: "time";
time: number;

View File

@@ -1,8 +1,7 @@
import * as React from "react";
import { createRoot } from 'react-dom/client';
import Config from "./config";
import * as CompileConfig from "../config.json";
import Config, { generateDebugDetails } from "./config";
import * as invidiousList from "../ci/invidiouslist.json";
// Make the config public for debugging purposes
@@ -115,8 +114,25 @@ async function init() {
if (await shouldHideOption(optionsElements[i]) || (dependentOn && (isDependentOnReversed ? Config.config[dependentOnName] : !Config.config[dependentOnName]))) {
optionsElements[i].classList.add("hidden", "hiding");
if (!dependentOn)
if (!dependentOn) {
if (optionsElements[i].getAttribute("data-no-safari") === "true" && optionsElements[i].id === "support-invidious") {
// Put message about being disabled on safari
const infoBox = document.createElement("div");
infoBox.innerText = chrome.i18n.getMessage("invidiousDisabledSafari");
const link = document.createElement("a");
link.style.display = "block";
const url = "https://bugs.webkit.org/show_bug.cgi?id=290508";
link.href = url;
link.innerText = url;
infoBox.appendChild(link);
optionsElements[i].parentElement.insertBefore(infoBox, optionsElements[i].nextSibling);
}
continue;
}
}
const option = optionsElements[i].getAttribute("data-sync");
@@ -286,7 +302,7 @@ async function init() {
break;
case "resetToDefault":
Config.resetToDefault();
window.location.reload();
setTimeout(() => window.location.reload(), 200);
break;
}
});
@@ -632,8 +648,7 @@ async function setTextOption(option: string, element: HTMLElement, value: string
await invidiousOnClick(checkbox, "supportInvidious");
}
window.location.reload();
setTimeout(() => window.location.reload(), 200);
} catch (e) {
alert(chrome.i18n.getMessage("incorrectlyFormattedOptions"));
}
@@ -699,32 +714,14 @@ function validateServerAddress(input: string): string {
}
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
navigator.clipboard.writeText(JSON.stringify(output, null, 4))
.then(() => {
navigator.clipboard.writeText(generateDebugDetails())
.then(() => {
alert(chrome.i18n.getMessage("copyDebugInformationComplete"));
})
.catch(() => {
})
.catch(() => {
alert(chrome.i18n.getMessage("copyDebugInformationFailed"));
});
});
}
function isIncognitoAllowed(): Promise<boolean> {

View File

@@ -1,4 +1,4 @@
import Config from "./config";
import Config, { generateDebugDetails } from "./config";
import Utils from "./utils";
import {
@@ -12,14 +12,17 @@ import {
GetChannelIDResponse,
IsChannelWhitelistedResponse,
IsInfoFoundMessageResponse,
LogResponse,
LoopedChapterResponse,
Message,
MessageResponse,
PopupMessage,
RefreshSegmentsResponse,
SponsorStartResponse,
VoteResponse,
} from "./messageTypes";
import { showDonationLink } from "./utils/configUtils";
import { AnimationUtils } from "./utils/animationUtils";
import { AnimationUtils } from "../maze-utils/src/animationUtils";
import { shortCategoryName } from "./utils/categoryUtils";
import { localizeHtmlPage } from "../maze-utils/src/setup";
import { exportTimes } from "./utils/exporter";
@@ -183,7 +186,8 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
"exportSegmentsButton",
"importSegmentsMenu",
"importSegmentsText",
"importSegmentsSubmit"
"importSegmentsSubmit",
"debugLogs"
].forEach(id => PageElements[id] = document.getElementById(id));
@@ -254,6 +258,7 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
PageElements.helpButton.addEventListener("click", openHelp);
PageElements.refreshSegmentsButton.addEventListener("click", refreshSegments);
PageElements.sbPopupIconCopyUserID.addEventListener("click", async () => copyToClipboard(await getHash(Config.config.userID)));
PageElements.debugLogs.addEventListener("click", copyDebgLogs);
// Forward click events
if (window !== window.top) {
@@ -302,7 +307,7 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
PageElements.showNoticeAgain.style.display = "unset";
}
const values = ["userName", "viewCount", "minutesSaved", "vip", "permissions"];
const values = ["userName", "viewCount", "minutesSaved", "vip", "permissions", "segmentCount"];
asyncRequestToServer("GET", "/api/userInfo", {
publicUserID: await getHash(Config.config.userID),
@@ -335,16 +340,18 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
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.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
if (Config.config.skipCount != undefined) {
@@ -459,37 +466,35 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
stopLoadingAnimation = null;
}
if (chrome.runtime.lastError) {
if (chrome.runtime.lastError || request == undefined || request.found == undefined) {
//This page doesn't have the injected content script, or at least not yet
// Or if the request is empty, meaning the current page is not YouTube or a video page
displayNoVideo();
return;
}
//if request is undefined, then the page currently being browsed is not YouTube
if (request != undefined) {
//remove loading text
PageElements.mainControls.style.display = "block";
if (request.onMobileYouTube) PageElements.mainControls.classList.add("hidden");
PageElements.whitelistButton.classList.remove("hidden");
PageElements.loadingIndicator.style.display = "none";
//remove loading text
PageElements.mainControls.style.display = "block";
if (request.onMobileYouTube) PageElements.mainControls.classList.add("hidden");
PageElements.whitelistButton.classList.remove("hidden");
PageElements.loadingIndicator.style.display = "none";
downloadedTimes = request.sponsorTimes ?? [];
displayDownloadedSponsorTimes(downloadedTimes, request.time);
if (request.found) {
PageElements.videoFound.innerHTML = chrome.i18n.getMessage("sponsorFound");
PageElements.issueReporterImportExport.classList.remove("hidden");
} else if (request.status == 404 || request.status == 200) {
PageElements.videoFound.innerHTML = chrome.i18n.getMessage("sponsor404");
PageElements.issueReporterImportExport.classList.remove("hidden");
downloadedTimes = request.sponsorTimes ?? [];
displayDownloadedSponsorTimes(downloadedTimes, request.time);
if (request.found) {
PageElements.videoFound.innerHTML = chrome.i18n.getMessage("sponsorFound");
PageElements.issueReporterImportExport.classList.remove("hidden");
} else if (request.status == 404 || request.status == 200) {
PageElements.videoFound.innerHTML = chrome.i18n.getMessage("sponsor404");
PageElements.issueReporterImportExport.classList.remove("hidden");
} else {
if (request.status) {
PageElements.videoFound.innerHTML = chrome.i18n.getMessage("connectionError") + request.status;
} else {
if (request.status) {
PageElements.videoFound.innerHTML = chrome.i18n.getMessage("connectionError") + request.status;
} else {
PageElements.videoFound.innerHTML = chrome.i18n.getMessage("segmentsStillLoading");
}
PageElements.issueReporterImportExport.classList.remove("hidden");
PageElements.videoFound.innerHTML = chrome.i18n.getMessage("segmentsStillLoading");
}
PageElements.issueReporterImportExport.classList.remove("hidden");
}
//see if whitelist button should be swapped
@@ -535,7 +540,7 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
}
//display the video times from the array at the top, in a different section
function displayDownloadedSponsorTimes(sponsorTimes: SponsorTime[], time: number) {
async function displayDownloadedSponsorTimes(sponsorTimes: SponsorTime[], time: number) {
let currentSegmentTab = segmentTab;
if (!sponsorTimes.some((segment) => segment.actionType === ActionType.Chapter && segment.source !== SponsorSourceType.YouTube)) {
PageElements.issueReporterTabs.classList.add("hidden");
@@ -550,6 +555,9 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
}
}
const response = await sendTabMessageAsync({message : "getLoopedChapter"}) as LoopedChapterResponse
const loopedChapter = response.UUID;
// Sort list by start time
const downloadedTimes = sponsorTimes
.filter((segment) => {
@@ -562,7 +570,7 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
return true;
}
})
.sort((a, b) => a.segment[1] - b.segment[1])
.sort((a, b) => b.segment[1] - a.segment[1])
.sort((a, b) => a.segment[0] - b.segment[0]);
//add them as buttons to the issue reporting container
@@ -677,9 +685,22 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
uuidButton.className = "voteButton";
uuidButton.src = chrome.runtime.getURL("icons/clipboard.svg");
uuidButton.title = chrome.i18n.getMessage("copySegmentID");
uuidButton.addEventListener("click", () => {
copyToClipboard(UUID);
uuidButton.addEventListener("click", async () => {
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();
});
@@ -711,6 +732,37 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
})
});
const loopButton = document.createElement("input");
const loopButtonIcon = document.createElement("img");
loopButtonIcon.src = loopedChapter === UUID ? chrome.runtime.getURL("icons/looped.svg") : chrome.runtime.getURL("icons/loop.svg");
loopButtonIcon.className = "loopButtonIcon";
loopButton.type = "checkbox";
loopButton.checked = loopedChapter === UUID;
loopButton.id = "loopChapterButtonContainer" + UUID;
loopButton.className = "loopButton hidden";
loopButton.addEventListener("click", () => {
const stopAnimation = AnimationUtils.applyLoadingAnimation(loopButtonIcon, 0.4);
stopAnimation();
if (loopButton.checked) {
document.querySelectorAll(".loopButton").forEach((e :HTMLInputElement) => e.checked = false);
document.querySelectorAll(".loopButtonIcon").forEach((e :HTMLImageElement) => e.src = chrome.runtime.getURL("icons/loop.svg"));
sendTabMessage({message: "loopChapter", UUID : UUID})
loopButton.checked = true;
} else {
sendTabMessage({message: "loopChapter", UUID : null})
loopButton.checked = false;
}
loopButtonIcon.src = loopButton.checked ? chrome.runtime.getURL("icons/looped.svg") : chrome.runtime.getURL("icons/loop.svg");
loopButtonLabel.title = loopButton.checked ? chrome.i18n.getMessage("unloopChapter") : chrome.i18n.getMessage("loopChapter");
});
const loopButtonLabel = document.createElement("label");
loopButtonLabel.setAttribute("for", loopButton.id);
loopButtonLabel.className = "voteButton";
loopButtonLabel.title = loopedChapter === UUID ? chrome.i18n.getMessage("unloopChapter") : chrome.i18n.getMessage("loopChapter");
loopButtonLabel.appendChild(loopButtonIcon);
loopButtonLabel.appendChild(loopButton);
const skipButton = document.createElement("img");
skipButton.id = "sponsorTimesSkipButtonContainer" + UUID;
skipButton.className = "voteButton";
@@ -726,6 +778,9 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
voteButtonsContainer.appendChild(upvoteButton);
voteButtonsContainer.appendChild(downvoteButton);
voteButtonsContainer.appendChild(uuidButton);
if (downloadedTimes[i].actionType === ActionType.Chapter) {
voteButtonsContainer.appendChild(loopButtonLabel);
}
if (downloadedTimes[i].actionType === ActionType.Skip || downloadedTimes[i].actionType === ActionType.Mute
|| downloadedTimes[i].actionType === ActionType.Poi
&& [SponsorHideType.Visible, SponsorHideType.Hidden].includes(downloadedTimes[i].hidden)) {
@@ -913,7 +968,11 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
//get the channel url
const response = await sendTabMessageAsync({ message: 'getChannelID' }) as GetChannelIDResponse;
if (!response.channelID) {
alert(chrome.i18n.getMessage("channelDataNotFound") + " https://github.com/ajayyy/SponsorBlock/issues/753");
if (response.isYTTV) {
alert(chrome.i18n.getMessage("yttvNoChannelWhitelist"));
} else {
alert(chrome.i18n.getMessage("channelDataNotFound") + " https://github.com/ajayyy/SponsorBlock/issues/753");
}
return;
}
@@ -980,9 +1039,17 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
stopLoadingAnimation = AnimationUtils.applyLoadingAnimation(PageElements.refreshSegmentsButton, 0.3);
}
function refreshSegments() {
async function refreshSegments() {
startLoadingAnimation();
sendTabMessage({ message: 'refreshSegments' });
const response = await sendTabMessageAsync({ message: 'refreshSegments' }) as RefreshSegmentsResponse;
if (response == null || !response.hasVideo) {
if (stopLoadingAnimation != null) {
stopLoadingAnimation();
stopLoadingAnimation = null;
}
displayNoVideo();
}
}
function skipSegment(actionType: ActionType, UUID: SegmentUUID, element?: HTMLElement): void {
@@ -1134,6 +1201,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) {
switch (msg.message) {
case "time":

View File

@@ -43,9 +43,15 @@ export class CategoryPill {
}
private async attachToPageInternal(): Promise<void> {
const referenceNode =
let referenceNode =
await waitFor(() => getYouTubeTitleNode());
// Experimental YouTube layout with description on right
const isOnDescriptionOnRightLayout = document.querySelector("#title #description");
if (isOnDescriptionOnRightLayout) {
referenceNode = referenceNode.parentElement;
}
if (referenceNode && !referenceNode.contains(this.container)) {
if (!this.container) {
this.container = document.createElement('span');
@@ -91,7 +97,9 @@ export class CategoryPill {
parent.appendChild(this.container);
referenceNode.prepend(parent);
referenceNode.style.display = "flex";
if (!isOnDescriptionOnRightLayout) {
referenceNode.style.display = "flex";
}
}
}
@@ -111,6 +119,8 @@ export class CategoryPill {
}
async setSegment(segment: SponsorTime): Promise<void> {
await waitFor(() => this.ref.current);
if (this.ref.current?.state?.segment !== segment) {
const newState = {
segment,

View File

@@ -20,6 +20,10 @@ export class ChapterVote {
this.container.id = "chapterVote";
this.container.style.height = "100%";
if (document.location.host === "tv.youtube.com") {
this.container.style.lineHeight = "initial";
}
this.root = createRoot(this.container);
this.root.render(<ChapterVoteComponent ref={this.ref} vote={vote} />);
}

View File

@@ -8,6 +8,7 @@ const utils = new Utils();
import { ContentContainer } from "../types";
import NoticeTextSelectionComponent from "../components/NoticeTextSectionComponent";
import { ButtonListener } from "../../maze-utils/src/components/component-types";
import { getVideo } from "../../maze-utils/src/video";
export interface TextBox {
icon: string;
@@ -75,7 +76,7 @@ export default class GenericNotice {
{options.textBoxes?.length > 0 ?
<tr id={"sponsorSkipNoticeMiddleRow" + this.idSuffix}
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%"}}>
{this.getMessageBoxes(this.idSuffix, options.textBoxes)}
</td>

View File

@@ -5,7 +5,7 @@ import Utils from "../utils";
const utils = new Utils();
import SkipNoticeComponent from "../components/SkipNoticeComponent";
import { SponsorTime, ContentContainer, NoticeVisbilityMode } from "../types";
import { SponsorTime, ContentContainer, NoticeVisibilityMode } from "../types";
import Config from "../config";
import { SkipNoticeAction } from "../utils/noticeUtils";
@@ -20,7 +20,7 @@ class SkipNotice {
skipNoticeRef: React.MutableRefObject<SkipNoticeComponent>;
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, voteNotice = false) {
this.skipNoticeRef = React.createRef();
this.segments = segments;
@@ -42,18 +42,20 @@ class SkipNotice {
this.noticeElement.id = "sponsorSkipNoticeContainer" + idSuffix;
referenceNode.prepend(this.noticeElement);
this.root = createRoot(this.noticeElement);
this.root.render(
<SkipNoticeComponent segments={segments}
autoSkip={autoSkip}
startReskip={startReskip}
voteNotice={voteNotice}
contentContainer={contentContainer}
ref={this.skipNoticeRef}
closeListener={() => this.close()}
smaller={Config.config.noticeVisibilityMode >= NoticeVisbilityMode.MiniForAll
|| (Config.config.noticeVisibilityMode >= NoticeVisbilityMode.MiniForAutoSkip && autoSkip)}
unskipTime={unskipTime} />
smaller={!voteNotice && (Config.config.noticeVisibilityMode >= NoticeVisibilityMode.MiniForAll
|| (Config.config.noticeVisibilityMode >= NoticeVisibilityMode.MiniForAutoSkip && autoSkip))}
fadeIn={!upcomingNoticeShown && !voteNotice}
unskipTime={unskipTime}
componentDidMount={componentDidMount} />
);
}
@@ -79,6 +81,26 @@ class SkipNotice {
unmutedListener(time: number): void {
this.skipNoticeRef?.current?.unmutedListener(time);
}
async waitForSkipNoticeRef(): Promise<SkipNoticeComponent> {
const waitForRef = () => new Promise<SkipNoticeComponent>((resolve) => {
const observer = new MutationObserver(() => {
if (this.skipNoticeRef.current) {
observer.disconnect();
resolve(this.skipNoticeRef.current);
}
});
observer.observe(document.getElementsByClassName("sponsorSkipNoticeContainer")[0], { childList: true, subtree: true});
if (this.skipNoticeRef.current) {
observer.disconnect();
resolve(this.skipNoticeRef.current);
}
});
return this.skipNoticeRef?.current || await waitForRef();
}
}
export default SkipNotice;

View File

@@ -11,7 +11,7 @@ class SubmissionNotice {
// Contains functions and variables from the content script needed by the skip notice
contentContainer: () => unknown;
callback: () => unknown;
callback: () => Promise<boolean>;
noticeRef: React.MutableRefObject<SubmissionNoticeComponent>;
@@ -19,7 +19,7 @@ class SubmissionNotice {
root: Root;
constructor(contentContainer: ContentContainer, callback: () => unknown) {
constructor(contentContainer: ContentContainer, callback: () => Promise<boolean>) {
this.noticeRef = React.createRef();
this.contentContainer = contentContainer;

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

@@ -6,11 +6,10 @@ export interface ContentContainer {
(): {
vote: (type: number, UUID: SegmentUUID, category?: Category, skipNotice?: SkipNoticeComponent) => void;
dontShowNoticeAgain: () => void;
unskipSponsorTime: (segment: SponsorTime, unskipTime: number, forceSeek?: boolean) => void;
unskipSponsorTime: (segment: SponsorTime, unskipTime: number, forceSeek?: boolean, voteNotice?: boolean) => void;
sponsorTimes: SponsorTime[];
sponsorTimesSubmitting: SponsorTime[];
skipNotices: SkipNotice[];
v: HTMLVideoElement;
sponsorVideoID;
reskipSponsorTime: (segment: SponsorTime, forceSeek?: boolean) => void;
updatePreviewBar: () => void;
@@ -57,7 +56,13 @@ export enum ActionType {
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 Category = string & { __categoryBrand: unknown };
@@ -214,7 +219,7 @@ export interface ToggleSkippable {
setShowKeybindHint: (show: boolean) => void;
}
export enum NoticeVisbilityMode {
export enum NoticeVisibilityMode {
FullSize = 0,
MiniForAutoSkip = 1,
MiniForAll = 2,

View File

@@ -2,9 +2,10 @@ import Config, { VideoDownvotes } from "./config";
import { CategorySelection, SponsorTime, BackgroundScriptContainer, Registration, VideoID, SponsorHideType, CategorySkipOption } from "./types";
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 { isSafari } from "../maze-utils/src/config";
import { asyncRequestToServer } from "./utils/requests";
export default class Utils {
@@ -46,10 +47,7 @@ export default class Utils {
*/
setupExtraSitePermissions(callback: (granted: boolean) => void): void {
const permissions = [];
if (!isFirefoxOrSafari()) {
permissions.push("declarativeContent");
}
if (!isFirefoxOrSafari() || isSafari()) {
if (isSafari()) {
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.
* 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.
*/
setupExtraSiteContentScripts(): void {
const registration: Registration = {
message: "registerContentScript",
id: "invidious",
allFrames: true,
js: this.js,
css: this.css,
matches: this.getPermissionRegex()
};
const registration = this.getExtraSiteRegistration();
if (this.backgroundScriptContainer) {
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({
origins: this.getPermissionRegex()
});
@@ -135,8 +132,10 @@ export default class Utils {
containsInvidiousPermission(): Promise<boolean> {
return new Promise((resolve) => {
let permissions = ["declarativeContent"];
if (isFirefoxOrSafari()) permissions = [];
const permissions = [];
if (isSafari()) {
permissions.push("webNavigation");
}
chrome.permissions.contains({
origins: this.getPermissionRegex(),
@@ -200,7 +199,7 @@ export default class Utils {
getSponsorIndexFromUUID(sponsorTimes: SponsorTime[], UUID: string): number {
for (let i = 0; i < sponsorTimes.length; i++) {
if (sponsorTimes[i].UUID === UUID) {
if (sponsorTimes[i].UUID && (sponsorTimes[i].UUID.startsWith(UUID) || UUID.startsWith(sponsorTimes[i].UUID))) {
return i;
}
}
@@ -250,6 +249,7 @@ export default class Utils {
".main-video-section > .video-container", // Cloudtube
".shaka-video-container", // Piped
"#player-container.ytk-player", // YT Kids
"#id-tv-container" // YTTV
];
let referenceNode = findValidElementFromSelector(selectors)
@@ -281,7 +281,19 @@ export default class Utils {
}
async addHiddenSegment(videoID: VideoID, segmentUUID: string, hidden: SponsorHideType) {
if (chrome.extension.inIncognitoContext || !Config.config.trackDownvotes) return;
if ((chrome.extension.inIncognitoContext && !Config.config.trackDownvotesInPrivate)
|| !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 UUIDHash = await getHash(segmentUUID, 1);
@@ -309,6 +321,7 @@ export default class Utils {
allDownvotes[hashedVideoID] = currentVideoData;
}
console.log(allDownvotes)
const entries = Object.entries(allDownvotes);
if (entries.length > 10000) {

View File

@@ -1,78 +0,0 @@
/**
* Starts a spinning animation and returns a function to be called when it should be stopped
* The callback will be called when the animation is finished
* It waits until a full rotation is complete
*/
function applyLoadingAnimation(element: HTMLElement, time: number, callback?: () => void): () => Promise<void> {
element.style.animation = `rotate ${time}s 0s infinite`;
return async () => new Promise((resolve) => {
// Make the animation finite
element.style.animation = `rotate ${time}s`;
// When the animation is over, hide the button
const animationEndListener = () => {
if (callback) callback();
element.style.animation = "none";
element.removeEventListener("animationend", animationEndListener);
resolve();
};
element.addEventListener("animationend", animationEndListener);
});
}
function setupCustomHideAnimation(element: Element, container: Element, enabled = true, rightSlide = true): { hide: () => void; show: () => void } {
if (enabled) element.classList.add("autoHiding");
element.classList.add("sbhidden");
element.classList.add("animationDone");
if (!rightSlide) element.classList.add("autoHideLeft");
let mouseEntered = false;
return {
hide: () => {
mouseEntered = false;
if (element.classList.contains("autoHiding")) {
element.classList.add("sbhidden");
}
},
show: () => {
mouseEntered = true;
element.classList.remove("animationDone");
// Wait for next event loop
setTimeout(() => {
if (mouseEntered) element.classList.remove("sbhidden")
}, 10);
}
};
}
function setupAutoHideAnimation(element: Element, container: Element, enabled = true, rightSlide = true): void {
const { hide, show } = this.setupCustomHideAnimation(element, container, enabled, rightSlide);
container.addEventListener("mouseleave", () => hide());
container.addEventListener("mouseenter", () => show());
}
function enableAutoHideAnimation(element: Element): void {
element.classList.add("autoHiding");
element.classList.add("sbhidden");
}
function disableAutoHideAnimation(element: Element): void {
element.classList.remove("autoHiding");
element.classList.remove("sbhidden");
}
export const AnimationUtils = {
applyLoadingAnimation,
setupAutoHideAnimation,
setupCustomHideAnimation,
enableAutoHideAnimation,
disableAutoHideAnimation
};

View File

@@ -36,6 +36,23 @@ 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 getVoteText(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 = "voted_on";
return chrome.i18n.getMessage(messageId).replace("{0}", categoryName);
}
export function getCategorySuffix(category: Category): string {
if (category.startsWith("poi_")) {
return "_POI";
@@ -51,5 +68,4 @@ export function getCategorySuffix(category: Category): string {
export function shortCategoryName(categoryName: string): string {
return chrome.i18n.getMessage("category_" + categoryName + "_short") || chrome.i18n.getMessage("category_" + categoryName);
}
export const DEFAULT_CATEGORY = "chooseACategory";

View File

@@ -12,4 +12,8 @@ export function runCompatibilityChecks() {
Config.config.showZoomToFillError2 = false;
}, 10000);
}
}
export function isVorapisInstalled() {
return document.querySelector(`.v3`);
}

View File

@@ -34,9 +34,12 @@ function exportTime(segment: SponsorTime): string {
export function importTimes(data: string, videoDuration: number): SponsorTime[] {
const lines = data.split("\n");
const timeRegex = /(?:((?:\d+:)?\d+:\d+)+(?:\.\d+)?)|(?:\d+(?=s| second))/g;
const anyLineHasTime = lines.some((line) => timeRegex.test(line));
const result: SponsorTime[] = [];
for (const line of lines) {
const match = line.match(/(?:((?:\d+:)?\d+:\d+)+(?:\.\d+)?)|(?:\d+(?=s| second))/g);
const match = line.match(timeRegex);
if (match) {
const startTime = getFormattedTimeToSeconds(match[0]);
if (startTime !== null) {
@@ -53,25 +56,36 @@ export function importTimes(data: string, videoDuration: number): SponsorTime[]
titleRight = removeIf(split2[split2.length - 1], specialCharMatchers)
const title = titleLeft?.length > titleRight?.length ? titleLeft : titleRight;
if (title) {
const determinedCategory = chapterNames.find(c => c.names.includes(title))?.code as Category;
const determinedCategory = chapterNames.find(c => c.names.includes(title))?.code as Category;
const segment: SponsorTime = {
segment: [startTime, getFormattedTimeToSeconds(match[1])],
category: determinedCategory ?? ("chapter" as Category),
actionType: determinedCategory ? ActionType.Skip : ActionType.Chapter,
description: title,
source: SponsorSourceType.Local,
UUID: generateUserID() as SegmentUUID
};
const category = title ? (determinedCategory ?? ("chapter" as Category)) : "chooseACategory" as Category;
const segment: SponsorTime = {
segment: [startTime, getFormattedTimeToSeconds(match[1])],
category,
actionType: category === "chapter" ? ActionType.Chapter : ActionType.Skip,
description: category === "chapter" ? title : null,
source: SponsorSourceType.Local,
UUID: generateUserID() as SegmentUUID
};
if (result.length > 0 && result[result.length - 1].segment[1] === null) {
result[result.length - 1].segment[1] = segment.segment[0];
}
result.push(segment);
if (result.length > 0 && result[result.length - 1].segment[1] === null) {
result[result.length - 1].segment[1] = segment.segment[0];
}
result.push(segment);
}
} else if (!anyLineHasTime) {
// Adding chapters with placeholder times
const segment: SponsorTime = {
segment: [0, 0],
category: "chapter" as Category,
actionType: ActionType.Chapter,
description: line,
source: SponsorSourceType.Local,
UUID: generateUserID() as SegmentUUID
};
result.push(segment);
}
}

View File

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

View File

@@ -1,16 +1,23 @@
import { ActionType, Category, SponsorSourceType, SponsorTime, VideoID } from "../types";
import { getFormattedTimeToSeconds } from "../../maze-utils/src/formating";
import Config from "../config";
export function getControls(): HTMLElement {
const controlsSelectors = [
// YouTube
".ytp-right-controls",
// New YouTube (2025 April)
".ytp-right-controls-right",
// Mobile YouTube
".player-controls-top",
// Invidious/videojs video element's controls element
".vjs-control-bar",
// Piped shaka player
".shaka-bottom-controls"
".shaka-bottom-controls",
// Vorapis v3
".html5-player-chrome",
// tv.youtube.com
".ypcs-control-buttons-right"
];
for (const controlsSelector of controlsSelectors) {
@@ -53,10 +60,15 @@ export function getHashParams(): Record<string, unknown> {
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[] {
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 (!Config.config.showAutogeneratedChapters && hasAutogeneratedChapters()) return [];
const chapters: SponsorTime[] = [];
// .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 callback
*/
export function asyncRequestToCustomServer(type: string, url: string, data = {}): Promise<FetchResponse> {
return sendRequestToCustomServer(type, url, data);
export function asyncRequestToCustomServer(type: string, url: string, data = {}, headers = {}): Promise<FetchResponse> {
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 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;
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, 5) as VideoID & HashedValue;
const hasDownvotedSegments = !!Config.local.downvotedSegments[hashPrefix.slice(0, 4)];
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 Config from "../config";
import { getVideoLabel } from "./videoLabels";
import { setThumbnailListener } from "../../maze-utils/src/thumbnailManagement";
import { getHasStartSegment, getVideoLabel } from "./videoLabels";
import { getThumbnailSelector, setThumbnailListener } from "../../maze-utils/src/thumbnailManagement";
import { VideoID } from "../types";
import { getSegmentsForVideo } from "./segmentData";
export async function labelThumbnails(thumbnails: HTMLImageElement[]): Promise<void> {
await Promise.all(thumbnails.map((t) => labelThumbnail(t)));
export async function handleThumbnails(thumbnails: HTMLImageElement[]): Promise<void> {
await Promise.all(thumbnails.map((t) => {
labelThumbnail(t);
setupThumbnailHover(t);
}));
}
export async function labelThumbnail(thumbnail: HTMLImageElement): Promise<HTMLElement | null> {
@@ -13,9 +18,7 @@ export async function labelThumbnail(thumbnail: HTMLImageElement): Promise<HTMLE
return null;
}
const link = (isOnInvidious() ? thumbnail.parentElement : thumbnail.querySelector("#thumbnail")) as HTMLAnchorElement
if (!link || link.nodeName !== "A" || !link.href) return null; // no link found
const videoID = parseYouTubeVideoIDFromURL(link.href)?.videoID;
const videoID = extractVideoID(thumbnail);
if (!videoID) {
hideThumbnailLabel(thumbnail);
return null;
@@ -37,6 +40,64 @@ export async function labelThumbnail(thumbnail: HTMLImageElement): Promise<HTMLE
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 {
return thumbnail.querySelector(".sponsorThumbnailLabel") as HTMLElement | null;
}
@@ -109,7 +170,7 @@ function insertSBIconDefinition() {
}
export function setupThumbnailListener(): void {
setThumbnailListener(labelThumbnails, () => {
setThumbnailListener(handleThumbnails, () => {
insertSBIconDefinition();
}, () => Config.isReady());
}

View File

@@ -6,9 +6,14 @@ import { asyncRequestToServer } from "./requests";
const utils = new Utils();
interface VideoLabelsCacheData {
category: Category;
hasStartSegment: boolean;
}
export interface LabelCacheEntry {
timestamp: number;
videos: Record<VideoID, Category>;
videos: Record<VideoID, VideoLabelsCacheData>;
}
const labelCache: Record<string, LabelCacheEntry> = {};
@@ -21,7 +26,7 @@ async function getLabelHashBlock(hashPrefix: string): Promise<LabelCacheEntry |
return cachedEntry;
}
const response = await asyncRequestToServer("GET", `/api/videoLabels/${hashPrefix}`);
const response = await asyncRequestToServer("GET", `/api/videoLabels/${hashPrefix}?hasStartSegment=true`);
if (response.status !== 200) {
// No video labels or server down
labelCache[hashPrefix] = {
@@ -36,7 +41,10 @@ async function getLabelHashBlock(hashPrefix: string): Promise<LabelCacheEntry |
const newEntry: LabelCacheEntry = {
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;
@@ -55,11 +63,11 @@ async function getLabelHashBlock(hashPrefix: string): Promise<LabelCacheEntry |
}
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);
if (result) {
const category = result.videos[videoID];
const category = result.videos[videoID]?.category;
if (category && utils.getCategorySelection(category).option !== CategorySkipOption.Disabled) {
return category;
} else {
@@ -67,5 +75,16 @@ export async function getVideoLabel(videoID: VideoID): Promise<Category | 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

@@ -1,9 +1,13 @@
/**
* @jest-environment jsdom
*/
import PreviewBar, { PreviewBarSegment } from "../src/js-components/previewBar";
describe("createChapterRenderGroups", () => {
let previewBar: PreviewBar;
beforeEach(() => {
previewBar = new PreviewBar(null, null, null, null, null, true);
previewBar = new PreviewBar(null, null, null, null, null, null, true);
})
it("Two unrelated times", () => {

View File

@@ -170,6 +170,13 @@ module.exports = env => {
parsed.Description.message = parsed.Description.message.slice(0, 77) + "...";
}
}
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));
}
@@ -185,6 +192,12 @@ module.exports = env => {
stream: env.stream
}),
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 betaManifestExtra = require("../manifest/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
const schema = {
@@ -41,13 +42,16 @@ class BuildManifest {
// Add missing manifest elements
if (this.options.browser.toLowerCase() === "firefox") {
mergeObjects(manifest, manifestV2ManifestExtra);
mergeObjects(manifest, firefoxManifestExtra);
} else if (this.options.browser.toLowerCase() === "chrome"
|| this.options.browser.toLowerCase() === "chromium"
|| this.options.browser.toLowerCase() === "edge") {
mergeObjects(manifest, chromeManifestExtra);
} else if (this.options.browser.toLowerCase() === "safari") {
mergeObjects(manifest, manifestV2ManifestExtra);
mergeObjects(manifest, safariManifestExtra);
manifest.optional_permissions = manifest.optional_permissions.filter((a) => a !== "*://*/*");
}
if (this.options.stream === "beta") {