mirror of
https://github.com/ajayyy/SponsorBlock.git
synced 2025-12-09 21:17:20 +03:00
Compare commits
74 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fae7d921a7 | ||
|
|
60a2eff40a | ||
|
|
98a4a076bc | ||
|
|
a95020dda3 | ||
|
|
5e179cf9ff | ||
|
|
0b946d5ef7 | ||
|
|
f48c57b1c5 | ||
|
|
98ac2fc618 | ||
|
|
36e3333e39 | ||
|
|
15fcc971f6 | ||
|
|
32adbb9581 | ||
|
|
4ecd02510b | ||
|
|
0dbb6474b8 | ||
|
|
c974c2a6d6 | ||
|
|
a6695254b6 | ||
|
|
e8a82eddca | ||
|
|
d3684dbedd | ||
|
|
6c9a403731 | ||
|
|
3be51c89a9 | ||
|
|
5b845a56e1 | ||
|
|
7b572c3c0e | ||
|
|
56bb22d03d | ||
|
|
e1d656f43f | ||
|
|
e14b3c76f9 | ||
|
|
de830cda48 | ||
|
|
d8057fb547 | ||
|
|
2dd69d443b | ||
|
|
6732850b41 | ||
|
|
287233785e | ||
|
|
de094cb11f | ||
|
|
2307691044 | ||
|
|
2dca0d7526 | ||
|
|
a1505bcf20 | ||
|
|
25b53754ba | ||
|
|
6c71036356 | ||
|
|
f55f2606a7 | ||
|
|
e9b7abd219 | ||
|
|
63d3309605 | ||
|
|
b4615d7d79 | ||
|
|
ec7eee650b | ||
|
|
154bb1749d | ||
|
|
742eb7ef57 | ||
|
|
88526aa46e | ||
|
|
1909e66c87 | ||
|
|
5f8447ec6b | ||
|
|
e6db5b43ff | ||
|
|
6566c129c7 | ||
|
|
e7f4be2d57 | ||
|
|
9d04482d19 | ||
|
|
7c661f8e67 | ||
|
|
0b7a2fd197 | ||
|
|
3382d8a500 | ||
|
|
5d871d5fe7 | ||
|
|
b7c5737a95 | ||
|
|
0cca1c3566 | ||
|
|
e0fe0fad67 | ||
|
|
c0bc068a18 | ||
|
|
7cb413db15 | ||
|
|
fdf1a6acf9 | ||
|
|
b533c6c1c8 | ||
|
|
926423db5c | ||
|
|
e7d55d2bac | ||
|
|
16f27e5c5c | ||
|
|
88dc8db6e7 | ||
|
|
c69a574379 | ||
|
|
516d624f16 | ||
|
|
a662c3e04f | ||
|
|
985910cbf6 | ||
|
|
feae86f6ea | ||
|
|
1f96e3b117 | ||
|
|
b3b5d46e4e | ||
|
|
c996680a58 | ||
|
|
ba92e6e386 | ||
|
|
bdbe8b60cc |
@@ -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
|
||||
|
||||
@@ -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.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"
|
||||
],
|
||||
"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
|
||||
}
|
||||
|
||||
@@ -11,9 +11,6 @@
|
||||
"background": {
|
||||
"persistent": false
|
||||
},
|
||||
"permissions": [
|
||||
"scripting"
|
||||
],
|
||||
"browser_action": {
|
||||
"default_area": "navbar"
|
||||
}
|
||||
|
||||
136
manifest/manifest-v2-extra.json
Normal file
136
manifest/manifest-v2-extra.json
Normal 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.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": [
|
||||
"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
|
||||
}
|
||||
@@ -1,141 +1,10 @@
|
||||
{
|
||||
"name": "__MSG_fullName__",
|
||||
"short_name": "SponsorBlock",
|
||||
"version": "5.5.1",
|
||||
"version": "5.6.1",
|
||||
"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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,6 @@
|
||||
"background": {
|
||||
"persistent": false
|
||||
},
|
||||
"permissions": [
|
||||
"scripting"
|
||||
],
|
||||
"optional_permissions": [
|
||||
"webNavigation"
|
||||
]
|
||||
|
||||
Submodule maze-utils updated: 27db39e39b...42888f800e
1238
package-lock.json
generated
1238
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -38,7 +38,7 @@
|
||||
"ts-loader": "^9.4.2",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "4.9",
|
||||
"web-ext": "^7.6.2",
|
||||
"web-ext": "^7.10.0",
|
||||
"webpack": "^5.75.0",
|
||||
"webpack-cli": "^4.10.0",
|
||||
"webpack-merge": "^5.8.0"
|
||||
|
||||
Submodule public/_locales updated: 8f84082dde...becf2e3f14
@@ -26,6 +26,11 @@
|
||||
transition: transform .1s cubic-bezier(0,0,0.2,1);
|
||||
}
|
||||
|
||||
/* May 2024 hover preview */
|
||||
.YtPlayerProgressBarProgressBar #previewbar {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.ytp-big-mode #previewbar {
|
||||
transform: scaleY(0.625) translateY(-30%) translateY(1.5px);
|
||||
}
|
||||
@@ -780,6 +785,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;
|
||||
|
||||
@@ -219,20 +219,22 @@
|
||||
__MSG_showSkipNotice__
|
||||
</label>
|
||||
</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">
|
||||
@@ -634,7 +636,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__
|
||||
|
||||
@@ -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";
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 + "&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: ""
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -272,48 +272,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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ 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;
|
||||
}
|
||||
@@ -131,7 +131,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: (this.contentContainer().v?.offsetHeight - 200) + "px"}}
|
||||
onMouseDown={(e) => e.stopPropagation()}>
|
||||
<td style={{width: "100%"}}>
|
||||
{this.getSponsorTimeMessages()}
|
||||
@@ -239,9 +239,11 @@ class SubmissionNoticeComponent extends React.Component<SubmissionNoticeProps, S
|
||||
}
|
||||
}
|
||||
|
||||
this.props.callback();
|
||||
|
||||
this.cancel();
|
||||
this.props.callback().then((success) => {
|
||||
if (success) {
|
||||
this.cancel();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
sortSegments(): void {
|
||||
|
||||
@@ -158,7 +158,7 @@ class CategorySkipOptionsComponent extends React.Component<CategorySkipOptionsPr
|
||||
});
|
||||
}
|
||||
|
||||
Config.forceLocalUpdate("categorySelections");
|
||||
Config.forceSyncUpdate("categorySelections");
|
||||
}
|
||||
|
||||
getCategorySkipOptions(): JSX.Element[] {
|
||||
|
||||
@@ -29,6 +29,7 @@ interface SBConfig {
|
||||
trackViewCount: boolean;
|
||||
trackViewCountInPrivate: boolean;
|
||||
trackDownvotes: boolean;
|
||||
trackDownvotesInPrivate: boolean;
|
||||
dontShowNotice: boolean;
|
||||
noticeVisibilityMode: NoticeVisbilityMode;
|
||||
hideVideoPlayerControls: boolean;
|
||||
@@ -139,7 +140,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,6 +291,7 @@ const syncDefaults = {
|
||||
trackViewCount: true,
|
||||
trackViewCountInPrivate: true,
|
||||
trackDownvotes: true,
|
||||
trackDownvotesInPrivate: false,
|
||||
dontShowNotice: false,
|
||||
noticeVisibilityMode: NoticeVisbilityMode.FadedForAutoSkip,
|
||||
hideVideoPlayerControls: false,
|
||||
|
||||
299
src/content.ts
299
src/content.ts
@@ -26,7 +26,7 @@ import { SkipButtonControlBar } from "./js-components/skipButtonControlBar";
|
||||
import { getStartTimeFromUrl } from "./utils/urlParser";
|
||||
import { getControls, getExistingChapters, getHashParams, isPlayingPlaylist, isVisible } from "./utils/pageUtils";
|
||||
import { CategoryPill } from "./render/CategoryPill";
|
||||
import { AnimationUtils } from "./utils/animationUtils";
|
||||
import { AnimationUtils } from "../maze-utils/src/animationUtils";
|
||||
import { GenericUtils } from "./utils/genericUtils";
|
||||
import { logDebug, logWarn } from "./utils/logger";
|
||||
import { importTimes } from "./utils/exporter";
|
||||
@@ -34,7 +34,7 @@ import { ChapterVote } from "./render/ChapterVote";
|
||||
import { openWarningDialog } from "./utils/warnings";
|
||||
import { isFirefoxOrSafari, waitFor } from "../maze-utils/src";
|
||||
import { getErrorMessage, getFormattedTime } from "../maze-utils/src/formating";
|
||||
import { getChannelIDInfo, getVideo, getIsAdPlaying, getIsLivePremiere, setIsAdPlaying, checkVideoIDChange, getVideoID, getYouTubeVideoID, setupVideoModule, checkIfNewVideoID, isOnInvidious, isOnMobileYouTube } from "../maze-utils/src/video";
|
||||
import { getChannelIDInfo, getVideo, getIsAdPlaying, getIsLivePremiere, setIsAdPlaying, checkVideoIDChange, getVideoID, getYouTubeVideoID, setupVideoModule, checkIfNewVideoID, isOnInvidious, isOnMobileYouTube, getLastNonInlineVideoID, triggerVideoIDChange, triggerVideoElementChange, getIsInline } from "../maze-utils/src/video";
|
||||
import { Keybind, StorageChangesObject, isSafari, keybindEquals, keybindToString } from "../maze-utils/src/config";
|
||||
import { findValidElement } from "../maze-utils/src/dom"
|
||||
import { getHash, HashedValue } from "../maze-utils/src/hash";
|
||||
@@ -49,6 +49,7 @@ import { hideDeArrowPromotion, tryShowingDeArrowPromotion } from "./dearrowPromo
|
||||
import { asyncRequestToServer } from "./utils/requests";
|
||||
import { isMobileControlsOpen } from "./utils/mobileUtils";
|
||||
import { defaultPreviewTime } from "./utils/constants";
|
||||
import { onVideoPage } from "../maze-utils/src/pageInfo";
|
||||
|
||||
cleanPage();
|
||||
|
||||
@@ -72,6 +73,8 @@ let sponsorDataFound = false;
|
||||
let sponsorTimes: SponsorTime[] = [];
|
||||
let existingChaptersImported = false;
|
||||
let importingChaptersWaitingForFocus = false;
|
||||
let importingChaptersWaiting = false;
|
||||
let triedImportingChapters = false;
|
||||
// List of open skip notices
|
||||
const skipNotices: SkipNotice[] = [];
|
||||
let activeSkipKeybindElement: ToggleSkippable = null;
|
||||
@@ -124,13 +127,10 @@ setupVideoModule({
|
||||
updateVisibilityOfPlayerControlsButton();
|
||||
},
|
||||
resetValues,
|
||||
documentScript
|
||||
documentScript: chrome.runtime.getManifest().manifest_version === 2 ? documentScript : undefined
|
||||
}, () => Config);
|
||||
setupThumbnailListener();
|
||||
|
||||
//the video id of the last preview bar update
|
||||
let lastPreviewBarUpdate: VideoID;
|
||||
|
||||
// Is the video currently being switched
|
||||
let switchingVideos = null;
|
||||
|
||||
@@ -168,7 +168,6 @@ let submissionNotice: SubmissionNotice = null;
|
||||
|
||||
let lastResponseStatus: number;
|
||||
let retryCount = 0;
|
||||
let lookupWaiting = false;
|
||||
|
||||
// Contains all of the functions and variables needed by the skip notice
|
||||
const skipNoticeContentContainer: ContentContainer = () => ({
|
||||
@@ -219,7 +218,7 @@ function messageListener(request: Message, sender: unknown, sendResponse: (respo
|
||||
found: sponsorDataFound,
|
||||
status: lastResponseStatus,
|
||||
sponsorTimes: sponsorTimes,
|
||||
time: getVideo().currentTime,
|
||||
time: getVideo()?.currentTime ?? 0,
|
||||
onMobileYouTube: isOnMobileYouTube()
|
||||
});
|
||||
|
||||
@@ -258,9 +257,16 @@ function messageListener(request: Message, sender: unknown, sendResponse: (respo
|
||||
break;
|
||||
case "refreshSegments":
|
||||
// update video on refresh if videoID invalid
|
||||
if (!getVideoID()) checkVideoIDChange();
|
||||
if (!getVideoID()) {
|
||||
checkVideoIDChange();
|
||||
}
|
||||
// if popup rescieves no response, or the videoID is invalid,
|
||||
// it will assume the page is not a video page and stop the refresh animation
|
||||
sendResponse({ hasVideo: getVideoID() != null });
|
||||
// fetch segments
|
||||
sponsorsLookup(false);
|
||||
if (getVideoID()) {
|
||||
sponsorsLookup(false);
|
||||
}
|
||||
|
||||
break;
|
||||
case "unskip":
|
||||
@@ -298,7 +304,9 @@ function messageListener(request: Message, sender: unknown, sendResponse: (respo
|
||||
if (!sponsorTimesSubmitting.some(
|
||||
(s) => Math.abs(s.segment[0] - segment.segment[0]) < 1
|
||||
&& Math.abs(s.segment[1] - segment.segment[1]) < 1)) {
|
||||
if (segment.category === "chapter" && !utils.getCategorySelection("chapter")) {
|
||||
const hasChaptersPermission = (Config.config.showCategoryWithoutPermission
|
||||
|| Config.config.permissions["chapter"]);
|
||||
if (segment.category === "chapter" && (!utils.getCategorySelection("chapter") || !hasChaptersPermission)) {
|
||||
segment.category = "chooseACategory" as Category;
|
||||
segment.actionType = ActionType.Skip;
|
||||
segment.description = "";
|
||||
@@ -377,6 +385,7 @@ function resetValues() {
|
||||
|
||||
sponsorTimes = [];
|
||||
existingChaptersImported = false;
|
||||
triedImportingChapters = false;
|
||||
sponsorSkipped = [];
|
||||
lastResponseStatus = 0;
|
||||
shownSegmentFailedToFetchWarning = false;
|
||||
@@ -393,8 +402,9 @@ function resetValues() {
|
||||
//reset sponsor data found check
|
||||
sponsorDataFound = false;
|
||||
|
||||
if (switchingVideos === null) {
|
||||
// When first loading a video, it is not switching videos
|
||||
// When first loading a video, it is not switching videos
|
||||
// Hover play also doesn't need this check
|
||||
if (switchingVideos === null || !onVideoPage()) {
|
||||
switchingVideos = false;
|
||||
} else {
|
||||
switchingVideos = true;
|
||||
@@ -451,10 +461,23 @@ function videoIDChange(): void {
|
||||
updateSponsorTimesSubmitting();
|
||||
|
||||
tryShowingDeArrowPromotion().catch(logWarn);
|
||||
|
||||
checkPreviewbarState();
|
||||
|
||||
if (getIsInline()) {
|
||||
// Hover preview progress bar can take some time to appear
|
||||
// and if the miniplayer is also active then it would
|
||||
// attach to the wrong one
|
||||
setTimeout(checkPreviewbarState, 500);
|
||||
setTimeout(checkPreviewbarState, 1000);
|
||||
setTimeout(checkPreviewbarState, 3000);
|
||||
}
|
||||
}
|
||||
|
||||
function handleMobileControlsMutations(): void {
|
||||
if (!chrome.runtime?.id) return;
|
||||
// Don't update while scrubbing
|
||||
if (!chrome.runtime?.id
|
||||
|| document.querySelector(".YtProgressBarProgressBarPlayheadDotInDragging")) return;
|
||||
|
||||
updateVisibilityOfPlayerControlsButton();
|
||||
|
||||
@@ -477,12 +500,7 @@ function handleMobileControlsMutations(): void {
|
||||
createPreviewBar();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a preview bar on the video
|
||||
*/
|
||||
function createPreviewBar(): void {
|
||||
if (previewBar !== null) return;
|
||||
|
||||
function getPreviewBarAttachElement(): HTMLElement | null {
|
||||
const progressElementOptions = [{
|
||||
// For new mobile YouTube (#1287)
|
||||
selector: ".progress-bar-line",
|
||||
@@ -492,11 +510,22 @@ function createPreviewBar(): void {
|
||||
selector: ".YtProgressBarProgressBarLine",
|
||||
isVisibleCheck: true
|
||||
}, {
|
||||
// For Desktop YouTube
|
||||
// For newer mobile YouTube (May 2024)
|
||||
selector: ".YtmProgressBarProgressBarLine",
|
||||
isVisibleCheck: true
|
||||
}, {
|
||||
// For desktop YouTube hover play
|
||||
// Priority is given to the hover play progress bar over the main progress bar
|
||||
// for miniplayer + hover preview case
|
||||
// Second is new hover play selector
|
||||
selector: "#video-preview .ytp-progress-bar, #video-preview .YtProgressBarLineHost",
|
||||
isVisibleCheck: true
|
||||
}, {
|
||||
// For desktop YouTube
|
||||
selector: ".ytp-progress-bar",
|
||||
isVisibleCheck: true
|
||||
}, {
|
||||
// For Desktop YouTube
|
||||
// For desktop YouTube
|
||||
selector: ".no-model.cue-range-marker",
|
||||
isVisibleCheck: true
|
||||
}, {
|
||||
@@ -519,14 +548,27 @@ function createPreviewBar(): void {
|
||||
const el = option.isVisibleCheck ? findValidElement(allElements) : allElements[0];
|
||||
|
||||
if (el) {
|
||||
const chapterVote = new ChapterVote(voteAsync);
|
||||
previewBar = new PreviewBar(el, isOnMobileYouTube(), isOnInvidious(), chapterVote, () => importExistingChapters(true));
|
||||
|
||||
updatePreviewBar();
|
||||
|
||||
break;
|
||||
return el;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a preview bar on the video
|
||||
*/
|
||||
function createPreviewBar(): void {
|
||||
if (previewBar !== null) return;
|
||||
|
||||
const el = getPreviewBarAttachElement();
|
||||
|
||||
if (el) {
|
||||
const chapterVote = new ChapterVote(voteAsync);
|
||||
previewBar = new PreviewBar(el, isOnMobileYouTube(), isOnInvidious(), chapterVote, () => importExistingChapters(true));
|
||||
|
||||
updatePreviewBar();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -770,6 +812,7 @@ function getVirtualTime(): number {
|
||||
|
||||
function inMuteSegment(currentTime: number, includeOverlap: boolean): boolean {
|
||||
const checkFunction = (segment) => segment.actionType === ActionType.Mute
|
||||
&& segment.hidden === SponsorHideType.Visible
|
||||
&& segment.segment[0] <= currentTime
|
||||
&& (segment.segment[1] > currentTime || (includeOverlap && segment.segment[1] + 0.02 > currentTime));
|
||||
return sponsorTimes?.some(checkFunction) || sponsorTimesSubmitting.some(checkFunction);
|
||||
@@ -779,6 +822,8 @@ function inMuteSegment(currentTime: number, includeOverlap: boolean): boolean {
|
||||
* This makes sure the videoID is still correct and if the sponsorTime is included
|
||||
*/
|
||||
function incorrectVideoCheck(videoID?: string, sponsorTime?: SponsorTime): boolean {
|
||||
if (!onVideoPage()) return false;
|
||||
|
||||
const currentVideoID = getYouTubeVideoID();
|
||||
const recordedVideoID = videoID || getVideoID();
|
||||
if (currentVideoID !== recordedVideoID || (sponsorTime
|
||||
@@ -802,14 +847,16 @@ let playbackRateCheckInterval: NodeJS.Timeout | null = null;
|
||||
let lastPlaybackSpeed = 1;
|
||||
let setupVideoListenersFirstTime = true;
|
||||
function setupVideoListeners() {
|
||||
const video = getVideo();
|
||||
|
||||
//wait until it is loaded
|
||||
getVideo().addEventListener('loadstart', videoOnReadyListener)
|
||||
getVideo().addEventListener('durationchange', durationChangeListener);
|
||||
video.addEventListener('loadstart', videoOnReadyListener)
|
||||
video.addEventListener('durationchange', durationChangeListener);
|
||||
|
||||
if (setupVideoListenersFirstTime) {
|
||||
addCleanupListener(() => {
|
||||
getVideo().removeEventListener('loadstart', videoOnReadyListener);
|
||||
getVideo().removeEventListener('durationchange', durationChangeListener);
|
||||
video.removeEventListener('loadstart', videoOnReadyListener);
|
||||
video.removeEventListener('durationchange', durationChangeListener);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -825,18 +872,20 @@ function setupVideoListeners() {
|
||||
|
||||
startSponsorSchedule();
|
||||
};
|
||||
getVideo().addEventListener('ratechange', rateChangeListener);
|
||||
video.addEventListener('ratechange', rateChangeListener);
|
||||
// Used by videospeed extension (https://github.com/igrigorik/videospeed/pull/740)
|
||||
getVideo().addEventListener('videoSpeed_ratechange', rateChangeListener);
|
||||
video.addEventListener('videoSpeed_ratechange', rateChangeListener);
|
||||
|
||||
const playListener = () => {
|
||||
// If it is not the first event, then the only way to get to 0 is if there is a seek event
|
||||
// This check makes sure that changing the video resolution doesn't cause the extension to think it
|
||||
// gone back to the begining
|
||||
if (getVideo().readyState <= HTMLMediaElement.HAVE_CURRENT_DATA
|
||||
&& getVideo().currentTime === 0) return;
|
||||
if (video.readyState <= HTMLMediaElement.HAVE_CURRENT_DATA
|
||||
&& video.currentTime === 0) return;
|
||||
|
||||
|
||||
updateVirtualTime();
|
||||
checkForMiniplayerPlaying();
|
||||
|
||||
if (switchingVideos || lastPausedAtZero) {
|
||||
switchingVideos = false;
|
||||
@@ -852,15 +901,15 @@ function setupVideoListeners() {
|
||||
updateAdFlag();
|
||||
|
||||
// Make sure it doesn't get double called with the playing event
|
||||
if (Math.abs(lastCheckVideoTime - getVideo().currentTime) > 0.3
|
||||
|| (lastCheckVideoTime !== getVideo().currentTime && Date.now() - lastCheckTime > 2000)) {
|
||||
if (Math.abs(lastCheckVideoTime - video.currentTime) > 0.3
|
||||
|| (lastCheckVideoTime !== video.currentTime && Date.now() - lastCheckTime > 2000)) {
|
||||
lastCheckTime = Date.now();
|
||||
lastCheckVideoTime = getVideo().currentTime;
|
||||
lastCheckVideoTime = video.currentTime;
|
||||
|
||||
startSponsorSchedule();
|
||||
}
|
||||
};
|
||||
getVideo().addEventListener('play', playListener);
|
||||
video.addEventListener('play', playListener);
|
||||
|
||||
const playingListener = () => {
|
||||
updateVirtualTime();
|
||||
@@ -868,8 +917,8 @@ function setupVideoListeners() {
|
||||
|
||||
if (startedWaiting) {
|
||||
startedWaiting = false;
|
||||
logDebug(`[SB] Playing event after buffering: ${Math.abs(lastCheckVideoTime - getVideo().currentTime) > 0.3
|
||||
|| (lastCheckVideoTime !== getVideo().currentTime && Date.now() - lastCheckTime > 2000)}`);
|
||||
logDebug(`[SB] Playing event after buffering: ${Math.abs(lastCheckVideoTime - video.currentTime) > 0.3
|
||||
|| (lastCheckVideoTime !== video.currentTime && Date.now() - lastCheckTime > 2000)}`);
|
||||
}
|
||||
|
||||
if (switchingVideos) {
|
||||
@@ -881,63 +930,63 @@ function setupVideoListeners() {
|
||||
}
|
||||
|
||||
// Make sure it doesn't get double called with the play event
|
||||
if (Math.abs(lastCheckVideoTime - getVideo().currentTime) > 0.3
|
||||
|| (lastCheckVideoTime !== getVideo().currentTime && Date.now() - lastCheckTime > 2000)) {
|
||||
if (Math.abs(lastCheckVideoTime - video.currentTime) > 0.3
|
||||
|| (lastCheckVideoTime !== video.currentTime && Date.now() - lastCheckTime > 2000)) {
|
||||
lastCheckTime = Date.now();
|
||||
lastCheckVideoTime = getVideo().currentTime;
|
||||
lastCheckVideoTime = video.currentTime;
|
||||
|
||||
startSponsorSchedule();
|
||||
}
|
||||
|
||||
if (playbackRateCheckInterval) clearInterval(playbackRateCheckInterval);
|
||||
lastPlaybackSpeed = getVideo().playbackRate;
|
||||
lastPlaybackSpeed = video.playbackRate;
|
||||
|
||||
// Video speed controller compatibility
|
||||
// That extension makes rate change events not propagate
|
||||
if (document.body.classList.contains("vsc-initialized")) {
|
||||
playbackRateCheckInterval = setInterval(() => {
|
||||
if ((!getVideoID() || getVideo().paused) && playbackRateCheckInterval) {
|
||||
if ((!getVideoID() || video.paused) && playbackRateCheckInterval) {
|
||||
// Video is gone, stop checking
|
||||
clearInterval(playbackRateCheckInterval);
|
||||
return;
|
||||
}
|
||||
|
||||
if (getVideo().playbackRate !== lastPlaybackSpeed) {
|
||||
lastPlaybackSpeed = getVideo().playbackRate;
|
||||
if (video.playbackRate !== lastPlaybackSpeed) {
|
||||
lastPlaybackSpeed = video.playbackRate;
|
||||
|
||||
rateChangeListener();
|
||||
}
|
||||
}, 2000);
|
||||
}
|
||||
};
|
||||
getVideo().addEventListener('playing', playingListener);
|
||||
video.addEventListener('playing', playingListener);
|
||||
|
||||
const seekingListener = () => {
|
||||
lastKnownVideoTime.fromPause = false;
|
||||
|
||||
if (!getVideo().paused){
|
||||
if (!video.paused){
|
||||
// Reset lastCheckVideoTime
|
||||
lastCheckTime = Date.now();
|
||||
lastCheckVideoTime = getVideo().currentTime;
|
||||
lastCheckVideoTime = video.currentTime;
|
||||
|
||||
updateVirtualTime();
|
||||
clearWaitingTime();
|
||||
|
||||
// Sometimes looped videos loop back to almost zero, but not quite
|
||||
if (getVideo().loop && getVideo().currentTime < 0.2) {
|
||||
if (video.loop && video.currentTime < 0.2) {
|
||||
startSponsorSchedule(false, 0);
|
||||
} else {
|
||||
startSponsorSchedule();
|
||||
}
|
||||
} else {
|
||||
updateActiveSegment(getVideo().currentTime);
|
||||
updateActiveSegment(video.currentTime);
|
||||
|
||||
if (getVideo().currentTime === 0) {
|
||||
if (video.currentTime === 0) {
|
||||
lastPausedAtZero = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
getVideo().addEventListener('seeking', seekingListener);
|
||||
video.addEventListener('seeking', seekingListener);
|
||||
|
||||
const stoppedPlayback = () => {
|
||||
// Reset lastCheckVideoTime
|
||||
@@ -948,7 +997,7 @@ function setupVideoListeners() {
|
||||
|
||||
lastKnownVideoTime.videoTime = null;
|
||||
lastKnownVideoTime.preciseTime = null;
|
||||
updateWaitingTime();
|
||||
updateWaitingTime(video);
|
||||
|
||||
cancelSponsorSchedule();
|
||||
};
|
||||
@@ -957,26 +1006,26 @@ function setupVideoListeners() {
|
||||
|
||||
stoppedPlayback();
|
||||
};
|
||||
getVideo().addEventListener('pause', pauseListener);
|
||||
video.addEventListener('pause', pauseListener);
|
||||
const waitingListener = () => {
|
||||
logDebug("[SB] Not skipping due to buffering");
|
||||
startedWaiting = true;
|
||||
|
||||
stoppedPlayback();
|
||||
};
|
||||
getVideo().addEventListener('waiting', waitingListener);
|
||||
video.addEventListener('waiting', waitingListener);
|
||||
|
||||
startSponsorSchedule();
|
||||
|
||||
if (setupVideoListenersFirstTime) {
|
||||
addCleanupListener(() => {
|
||||
getVideo().removeEventListener('play', playListener);
|
||||
getVideo().removeEventListener('playing', playingListener);
|
||||
getVideo().removeEventListener('seeking', seekingListener);
|
||||
getVideo().removeEventListener('ratechange', rateChangeListener);
|
||||
getVideo().removeEventListener('videoSpeed_ratechange', rateChangeListener);
|
||||
getVideo().removeEventListener('pause', pauseListener);
|
||||
getVideo().removeEventListener('waiting', waitingListener);
|
||||
video.removeEventListener('play', playListener);
|
||||
video.removeEventListener('playing', playingListener);
|
||||
video.removeEventListener('seeking', seekingListener);
|
||||
video.removeEventListener('ratechange', rateChangeListener);
|
||||
video.removeEventListener('videoSpeed_ratechange', rateChangeListener);
|
||||
video.removeEventListener('pause', pauseListener);
|
||||
video.removeEventListener('waiting', waitingListener);
|
||||
|
||||
if (playbackRateCheckInterval) clearInterval(playbackRateCheckInterval);
|
||||
});
|
||||
@@ -1027,8 +1076,8 @@ function updateVirtualTime() {
|
||||
}
|
||||
}
|
||||
|
||||
function updateWaitingTime(): void {
|
||||
lastTimeFromWaitingEvent = getVideo().currentTime;
|
||||
function updateWaitingTime(video: HTMLVideoElement): void {
|
||||
lastTimeFromWaitingEvent = video.currentTime;
|
||||
}
|
||||
|
||||
function clearWaitingTime(): void {
|
||||
@@ -1062,24 +1111,18 @@ function setupCategoryPill() {
|
||||
}
|
||||
|
||||
async function sponsorsLookup(keepOldSubmissions = true) {
|
||||
if (lookupWaiting) return;
|
||||
//there is still no video here
|
||||
if (!getVideo()) {
|
||||
lookupWaiting = true;
|
||||
setTimeout(() => {
|
||||
lookupWaiting = false;
|
||||
sponsorsLookup()
|
||||
}, 100);
|
||||
return;
|
||||
}
|
||||
|
||||
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(getVideoID(), 1)).slice(0, 4) as VideoID & HashedValue;
|
||||
const videoID = getVideoID()
|
||||
if (!videoID) {
|
||||
console.error("[SponsorBlock] Attempted to fetch segments with a null/undefined videoID.");
|
||||
return;
|
||||
}
|
||||
const hashPrefix = (await getHash(videoID, 1)).slice(0, 4) as VideoID & HashedValue;
|
||||
const response = await asyncRequestToServer('GET', "/api/skipSegments/" + hashPrefix, {
|
||||
categories,
|
||||
actionTypes: getEnabledActionTypes(),
|
||||
@@ -1149,13 +1192,14 @@ async function sponsorsLookup(keepOldSubmissions = true) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!getVideo()) {
|
||||
//there is still no video here
|
||||
await waitFor(() => getVideo(), 5000, 10);
|
||||
}
|
||||
|
||||
startSkipScheduleCheckingForStartSponsors();
|
||||
|
||||
//update the preview bar
|
||||
//leave the type blank for now until categories are added
|
||||
if (lastPreviewBarUpdate == getVideoID() || (lastPreviewBarUpdate == null && !isNaN(getVideo().duration))) {
|
||||
//set it now
|
||||
//otherwise the listener can handle it
|
||||
if (!isNaN(getVideo().duration)) {
|
||||
updatePreviewBar();
|
||||
}
|
||||
} else {
|
||||
@@ -1173,7 +1217,7 @@ async function sponsorsLookup(keepOldSubmissions = true) {
|
||||
found: sponsorDataFound,
|
||||
status: lastResponseStatus,
|
||||
sponsorTimes: sponsorTimes,
|
||||
time: getVideo().currentTime,
|
||||
time: getVideo()?.currentTime ?? 0,
|
||||
onMobileYouTube: isOnMobileYouTube()
|
||||
});
|
||||
|
||||
@@ -1183,10 +1227,10 @@ async function sponsorsLookup(keepOldSubmissions = true) {
|
||||
}
|
||||
|
||||
function importExistingChapters(wait: boolean) {
|
||||
if (!existingChaptersImported) {
|
||||
if (!existingChaptersImported && !importingChaptersWaiting && !triedImportingChapters && onVideoPage() && !isOnMobileYouTube()) {
|
||||
const waitCondition = () => getVideo()?.duration && getExistingChapters(getVideoID(), getVideo().duration);
|
||||
|
||||
if (!waitCondition() && wait && !document.hasFocus() && !importingChaptersWaitingForFocus) {
|
||||
if (wait && !document.hasFocus() && !importingChaptersWaitingForFocus && !waitCondition()) {
|
||||
importingChaptersWaitingForFocus = true;
|
||||
const listener = () => {
|
||||
importExistingChapters(wait);
|
||||
@@ -1194,14 +1238,17 @@ function importExistingChapters(wait: boolean) {
|
||||
};
|
||||
window.addEventListener("focus", listener);
|
||||
} else {
|
||||
importingChaptersWaiting = true;
|
||||
waitFor(waitCondition,
|
||||
wait ? 15000 : 0, 400, (c) => c?.length > 0).then((chapters) => {
|
||||
importingChaptersWaiting = false;
|
||||
|
||||
if (!existingChaptersImported && chapters?.length > 0) {
|
||||
sponsorTimes = (sponsorTimes ?? []).concat(...chapters).sort((a, b) => a.segment[0] - b.segment[0]);
|
||||
existingChaptersImported = true;
|
||||
updatePreviewBar();
|
||||
}
|
||||
}).catch(() => {}); // eslint-disable-line @typescript-eslint/no-empty-function
|
||||
}).catch(() => { importingChaptersWaiting = false; triedImportingChapters = true; }); // eslint-disable-line @typescript-eslint/no-empty-function
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1373,9 +1420,6 @@ function updatePreviewBar(): void {
|
||||
|
||||
showTimeWithoutSkips(skippedDuration);
|
||||
}
|
||||
|
||||
// Update last video id
|
||||
lastPreviewBarUpdate = getVideoID();
|
||||
}
|
||||
|
||||
//checks if this channel is whitelisted, should be done only after the channelID has been loaded
|
||||
@@ -1399,7 +1443,8 @@ function videoElementChange(newVideo: boolean): void {
|
||||
setupSkipButtonControlBar();
|
||||
setupCategoryPill();
|
||||
}
|
||||
|
||||
|
||||
updatePreviewBar();
|
||||
checkPreviewbarState();
|
||||
|
||||
// Incase the page is still transitioning, check again in a few seconds
|
||||
@@ -1409,8 +1454,19 @@ function videoElementChange(newVideo: boolean): void {
|
||||
})
|
||||
}
|
||||
|
||||
let checkingPreviewbarAgain = false;
|
||||
function checkPreviewbarState(): void {
|
||||
if (previewBar && !utils.findReferenceNode()?.contains(previewBar.container)) {
|
||||
if (!getPreviewBarAttachElement() && !checkingPreviewbarAgain && getVideo() && getVideoID()) {
|
||||
checkingPreviewbarAgain = true;
|
||||
setTimeout(() => {
|
||||
checkingPreviewbarAgain = false;
|
||||
checkPreviewbarState();
|
||||
}, 500);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (previewBar && !getPreviewBarAttachElement()?.contains(previewBar.container)) {
|
||||
previewBar.remove();
|
||||
previewBar = null;
|
||||
}
|
||||
@@ -1592,6 +1648,13 @@ function previewTime(time: number, unpause = true) {
|
||||
|
||||
//send telemetry and count skip
|
||||
function sendTelemetryAndCount(skippingSegments: SponsorTime[], secondsSkipped: number, fullSkip: boolean) {
|
||||
for (const segment of skippingSegments) {
|
||||
if (!previewedSegment && sponsorTimesSubmitting.some((s) => s.segment === segment.segment)) {
|
||||
// Count that as a previewed segment
|
||||
previewedSegment = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!Config.config.trackViewCount || (!Config.config.trackViewCountInPrivate && chrome.extension.inIncognitoContext)) return;
|
||||
|
||||
let counted = false;
|
||||
@@ -1618,8 +1681,9 @@ function skipToTime({v, skipTime, skippingSegments, openNotice, forceAutoSkip, u
|
||||
|
||||
// There will only be one submission if it is manual skip
|
||||
const autoSkip: boolean = forceAutoSkip || shouldAutoSkip(skippingSegments[0]);
|
||||
const isSubmittingSegment = sponsorTimesSubmitting.some((time) => time.segment === skippingSegments[0].segment);
|
||||
|
||||
if ((autoSkip || sponsorTimesSubmitting.some((time) => time.segment === skippingSegments[0].segment))
|
||||
if ((autoSkip || isSubmittingSegment)
|
||||
&& v.currentTime !== skipTime[1]) {
|
||||
switch(skippingSegments[0].actionType) {
|
||||
case ActionType.Poi:
|
||||
@@ -1700,7 +1764,7 @@ function skipToTime({v, skipTime, skippingSegments, openNotice, forceAutoSkip, u
|
||||
}
|
||||
|
||||
//send telemetry that a this sponsor was skipped
|
||||
if (autoSkip) sendTelemetryAndCount(skippingSegments, skipTime[1] - skipTime[0], true);
|
||||
if (autoSkip || isSubmittingSegment) sendTelemetryAndCount(skippingSegments, skipTime[1] - skipTime[0], true);
|
||||
}
|
||||
|
||||
function createSkipNotice(skippingSegments: SponsorTime[], autoSkip: boolean, unskipTime: number, startReskip: boolean) {
|
||||
@@ -1790,7 +1854,8 @@ function shouldAutoSkip(segment: SponsorTime): boolean {
|
||||
return (!Config.config.manualSkipOnFullVideo || !sponsorTimes?.some((s) => s.category === segment.category && s.actionType === ActionType.Full))
|
||||
&& (utils.getCategorySelection(segment.category)?.option === CategorySkipOption.AutoSkip ||
|
||||
(Config.config.autoSkipOnMusicVideos && sponsorTimes?.some((s) => s.category === "music_offtopic")
|
||||
&& segment.actionType === ActionType.Skip));
|
||||
&& segment.actionType === ActionType.Skip)
|
||||
|| sponsorTimesSubmitting.some((s) => s.segment === segment.segment));
|
||||
}
|
||||
|
||||
function shouldSkip(segment: SponsorTime): boolean {
|
||||
@@ -1983,6 +2048,9 @@ function updateSponsorTimesSubmitting(getFromConfig = true) {
|
||||
}
|
||||
|
||||
if (sponsorTimesSubmitting.length > 0) {
|
||||
// Assume they already previewed a segment
|
||||
previewedSegment = true;
|
||||
|
||||
importExistingChapters(true);
|
||||
}
|
||||
}
|
||||
@@ -2257,20 +2325,22 @@ function submitSegments() {
|
||||
|
||||
//send the message to the background js
|
||||
//called after all the checks have been made that it's okay to do so
|
||||
async function sendSubmitMessage() {
|
||||
async function sendSubmitMessage(): Promise<boolean> {
|
||||
// check if all segments are full video
|
||||
const onlyFullVideo = sponsorTimesSubmitting.every((segment) => segment.actionType === ActionType.Full);
|
||||
// Block if submitting on a running livestream or premiere
|
||||
if (!onlyFullVideo && (getIsLivePremiere() || isVisible(document.querySelector(".ytp-live-badge")))) {
|
||||
alert(chrome.i18n.getMessage("liveOrPremiere"));
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!previewedSegment
|
||||
&& !sponsorTimesSubmitting.every((segment) =>
|
||||
[ActionType.Full, ActionType.Chapter, ActionType.Poi].includes(segment.actionType))) {
|
||||
[ActionType.Full, ActionType.Chapter, ActionType.Poi].includes(segment.actionType)
|
||||
|| segment.segment[1] >= getVideo()?.duration
|
||||
|| segment.segment[0] === 0)) {
|
||||
alert(`${chrome.i18n.getMessage("previewSegmentRequired")} ${keybindToString(Config.config.previewKeybind)}`);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Add loading animation
|
||||
@@ -2296,7 +2366,7 @@ async function sendSubmitMessage() {
|
||||
const confirmShort = chrome.i18n.getMessage("shortCheck") + "\n\n" +
|
||||
getSegmentsMessage(sponsorTimesSubmitting);
|
||||
|
||||
if(!confirm(confirmShort)) return;
|
||||
if(!confirm(confirmShort)) return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2346,6 +2416,8 @@ async function sendSubmitMessage() {
|
||||
if (fullVideoSegment) {
|
||||
categoryPill?.setSegment(fullVideoSegment);
|
||||
}
|
||||
|
||||
return true;
|
||||
} else {
|
||||
// Show that the upload failed
|
||||
playerButtons.submit.button.style.animation = "unset";
|
||||
@@ -2357,6 +2429,8 @@ async function sendSubmitMessage() {
|
||||
alert(getErrorMessage(response.status, response.responseText));
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//get the message that visually displays the video times
|
||||
@@ -2382,6 +2456,8 @@ function getSegmentsMessage(sponsorTimes: SponsorTime[]): string {
|
||||
}
|
||||
|
||||
function updateActiveSegment(currentTime: number): void {
|
||||
previewBar?.updateChapterText(sponsorTimes, sponsorTimesSubmitting, currentTime);
|
||||
|
||||
chrome.runtime.sendMessage({
|
||||
message: "time",
|
||||
time: currentTime
|
||||
@@ -2646,3 +2722,22 @@ function setCategoryColorCSSVariables() {
|
||||
|
||||
styleContainer.innerText = css;
|
||||
}
|
||||
|
||||
/**
|
||||
* If mini player starts playing, then videoID change might have to be called
|
||||
*/
|
||||
function checkForMiniplayerPlaying() {
|
||||
const miniPlayerUI = document.querySelector(".miniplayer") as HTMLElement;
|
||||
if (!onVideoPage() && isVisible(miniPlayerUI)) {
|
||||
const videoID = getLastNonInlineVideoID();
|
||||
if (videoID) {
|
||||
triggerVideoIDChange(videoID);
|
||||
|
||||
// treat as if video element has changed
|
||||
const video = miniPlayerUI.querySelector("video") as HTMLVideoElement;
|
||||
if (video && getVideo() !== video) {
|
||||
triggerVideoElementChange(video);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
15
src/globals.d.ts
vendored
15
src/globals.d.ts
vendored
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,9 +11,9 @@ 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 { isVisible } from "../utils/pageUtils";
|
||||
|
||||
const TOOLTIP_VISIBLE_CLASS = 'sponsorCategoryTooltipVisible';
|
||||
const MIN_CHAPTER_SIZE = 0.003;
|
||||
@@ -125,34 +125,11 @@ 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");
|
||||
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] =
|
||||
@@ -198,15 +175,6 @@ class PreviewBar {
|
||||
this.chapterTooltip.style.textAlign = titleTooltip.style.textAlign;
|
||||
}
|
||||
});
|
||||
|
||||
observer.observe(tooltipTextWrapper, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
});
|
||||
|
||||
addCleanupListener(() => {
|
||||
observer.disconnect();
|
||||
});
|
||||
}
|
||||
|
||||
private setTooltipTitle(segment: PreviewBarSegment, tooltip: HTMLElement): void {
|
||||
@@ -258,10 +226,12 @@ class PreviewBar {
|
||||
this.segments = segments ?? [];
|
||||
this.videoDuration = videoDuration ?? 0;
|
||||
|
||||
|
||||
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 +239,7 @@ class PreviewBar {
|
||||
}
|
||||
|
||||
private updatePageElements(): void {
|
||||
const allProgressBars = document.querySelectorAll('.ytp-progress-bar') as NodeListOf<HTMLElement>;
|
||||
const allProgressBars = document.querySelectorAll(".ytp-progress-bar") as NodeListOf<HTMLElement>;
|
||||
this.progressBar = findValidElement(allProgressBars) ?? allProgressBars?.[0];
|
||||
|
||||
if (this.progressBar) {
|
||||
@@ -307,6 +277,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,7 +317,7 @@ 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`;
|
||||
@@ -780,6 +751,7 @@ class PreviewBar {
|
||||
|
||||
updateChapterText(segments: SponsorTime[], submittingSegments: SponsorTime[], currentTime: number): SponsorTime[] {
|
||||
if (!Config.config.showSegmentNameInChapterBar
|
||||
|| Config.config.disableSkipping
|
||||
|| ((!segments || segments.length <= 0) && submittingSegments?.length <= 0)) {
|
||||
const chaptersContainer = this.getChaptersContainer();
|
||||
if (chaptersContainer) {
|
||||
@@ -919,7 +891,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 +916,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 +932,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);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -103,7 +103,8 @@ export type MessageResponse =
|
||||
| IsChannelWhitelistedResponse
|
||||
| Record<string, never> // empty object response {}
|
||||
| VoteResponse
|
||||
| ImportSegmentsResponse;
|
||||
| ImportSegmentsResponse
|
||||
| RefreshSegmentsResponse;
|
||||
|
||||
export interface VoteResponse {
|
||||
successType: number;
|
||||
@@ -115,6 +116,10 @@ interface ImportSegmentsResponse {
|
||||
importedSegments: SponsorTime[];
|
||||
}
|
||||
|
||||
export interface RefreshSegmentsResponse {
|
||||
hasVideo: boolean;
|
||||
}
|
||||
|
||||
export interface TimeUpdateMessage {
|
||||
message: "time";
|
||||
time: number;
|
||||
|
||||
@@ -286,7 +286,7 @@ async function init() {
|
||||
break;
|
||||
case "resetToDefault":
|
||||
Config.resetToDefault();
|
||||
window.location.reload();
|
||||
setTimeout(() => window.location.reload(), 200);
|
||||
break;
|
||||
}
|
||||
});
|
||||
@@ -632,8 +632,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"));
|
||||
}
|
||||
|
||||
61
src/popup.ts
61
src/popup.ts
@@ -15,11 +15,12 @@ import {
|
||||
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";
|
||||
@@ -459,37 +460,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
|
||||
@@ -562,7 +561,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
|
||||
@@ -980,9 +979,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 {
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
41
src/utils.ts
41
src/utils.ts
@@ -2,7 +2,7 @@ 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";
|
||||
|
||||
@@ -46,10 +46,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 +64,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 +83,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 +107,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 +131,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(),
|
||||
@@ -281,7 +279,8 @@ 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;
|
||||
|
||||
const hashedVideoID = (await getHash(videoID, 1)).slice(0, 4) as VideoID & HashedValue;
|
||||
const UUIDHash = await getHash(segmentUUID, 1);
|
||||
|
||||
@@ -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
|
||||
};
|
||||
@@ -53,24 +53,23 @@ 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}`)
|
||||
}
|
||||
}
|
||||
@@ -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,12 +42,14 @@ 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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user