Compare commits

...

95 Commits
5.9 ... 5.11.4

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

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

@@ -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

@@ -30,7 +30,7 @@
"icons/close.png",
"icons/skipIcon.svg",
"icons/refresh.svg",
"icons/beep.ogg",
"icons/beep.oga",
"icons/pause.svg",
"icons/stop.svg",
"icons/skip.svg",

View File

@@ -22,7 +22,7 @@
"icons/close.png",
"icons/skipIcon.svg",
"icons/refresh.svg",
"icons/beep.ogg",
"icons/beep.oga",
"icons/pause.svg",
"icons/stop.svg",
"icons/skip.svg",

View File

@@ -1,7 +1,7 @@
{
"name": "__MSG_fullName__",
"short_name": "SponsorBlock",
"version": "5.9",
"version": "5.11.4",
"default_locale": "en",
"description": "__MSG_Description__",
"homepage_url": "https://sponsor.ajay.app",

View File

@@ -4,5 +4,13 @@
},
"optional_permissions": [
"webNavigation"
]
],
"browser_action": {
"default_icon": {
"16": "icons/SafariIconSponsorBlocker16px.png",
"32": "icons/SafariIconSponsorBlocker32px.png",
"64": "icons/SafariIconSponsorBlocker64px.png",
"128": "icons/SafariIconSponsorBlocker128px.png"
}
}
}

View File

@@ -11,6 +11,11 @@
display: none;
}
/* Vorapi compatibility */
#player-api_VORAPI_ELEMENT_ID #previewbar {
z-index: 999;
}
#previewbar {
overflow: visible;
padding: 0;
@@ -44,7 +49,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 {
@@ -74,7 +88,7 @@ div:hover > #previewbar.sbNotInvidious {
display: none !important;
}
.ytp-tooltip.sponsorCategoryTooltipVisible {
.ytp-tooltip.sponsorCategoryTooltipVisible:not(.sponsorTooltipHasYTChapters) {
transform: translateY(-1em) !important;
}
@@ -218,6 +232,11 @@ div:hover > .sponsorBlockChapterBar {
from { opacity: 0; }
}
@keyframes fadeInToFaded {
from { opacity: 0; }
to { opacity: 0.5; }
}
@keyframes fadeOut {
to { opacity: 0; }
}
@@ -290,6 +309,10 @@ div:hover > .sponsorBlockChapterBar {
animation: fadeIn 0.5s ease-out;
}
.sponsorSkipNoticeFadeIn.sponsorSkipNoticeFaded {
animation: fadeInToFaded 0.5s ease-out;
}
.exportCopiedNotice .sponsorSkipNoticeFadeIn {
animation: none;
}
@@ -826,6 +849,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

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,15 +195,31 @@
<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">

View File

@@ -43,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;
@@ -214,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") {
@@ -226,7 +226,7 @@ async function submitVote(type: number, UUID: string, category: string) {
const typeSection = (type !== undefined) ? "&type=" + type : "&category=" + category;
try {
const response = await asyncRequestToServer("POST", "/api/voteOnSponsorTime?UUID=" + UUID + "&userID=" + userID + typeSection);
const response = await asyncRequestToServer("POST", "/api/voteOnSponsorTime?UUID=" + UUID + "&videoID=" + videoID + "&userID=" + userID + typeSection);
if (response.ok) {
return {

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

@@ -6,7 +6,7 @@ import NoticeComponent from "./NoticeComponent";
import NoticeTextSelectionComponent from "./NoticeTextSectionComponent";
import Utils from "../utils";
const utils = new Utils();
import { getSkippingText } from "../utils/categoryUtils";
import { getSkippingText, getUpcomingText } from "../utils/categoryUtils";
import ThumbsUpSvg from "../svg-icons/thumbs_up_svg";
import ThumbsDownSvg from "../svg-icons/thumbs_down_svg";
@@ -28,12 +28,17 @@ export interface SkipNoticeProps {
autoSkip: boolean;
startReskip?: boolean;
upcomingNotice?: 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;
}
@@ -97,9 +102,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.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"));
@@ -120,7 +125,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];
@@ -171,12 +176,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
@@ -184,7 +184,8 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
amountOfPreviousNotices={this.amountOfPreviousNotices}
showInSecondSlot={this.showInSecondSlot}
idSuffix={this.idSuffix}
fadeIn={true}
fadeIn={this.props.fadeIn}
fadeOut={!this.props.upcomingNotice}
startFaded={Config.config.noticeVisibilityMode >= NoticeVisbilityMode.FadedForAll
|| (Config.config.noticeVisibilityMode >= NoticeVisbilityMode.FadedForAutoSkip && this.autoSkip)}
timed={true}
@@ -197,12 +198,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 */
@@ -365,8 +374,10 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
style.minWidth = "100px";
}
const showSkipButton = (buttonIndex !== 0 || this.props.smaller || 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}
@@ -420,7 +431,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
});
@@ -658,7 +669,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);
}

View File

@@ -234,6 +234,10 @@ 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 [{

View File

@@ -31,6 +31,7 @@ interface SBConfig {
trackDownvotes: boolean;
trackDownvotesInPrivate: boolean;
dontShowNotice: boolean;
showUpcomingNotice: boolean;
noticeVisibilityMode: NoticeVisbilityMode;
hideVideoPlayerControls: boolean;
hideInfoButtonPlayerControls: boolean;
@@ -47,7 +48,6 @@ interface SBConfig {
audioNotificationOnSkip: boolean;
checkForUnlistedVideos: boolean;
testingServer: boolean;
refetchWhenNotFound: boolean;
ytInfoPermissionGranted: boolean;
allowExpirements: boolean;
showDonationLink: boolean;
@@ -69,6 +69,7 @@ interface SBConfig {
showCategoryGuidelines: boolean;
showCategoryWithoutPermission: boolean;
showSegmentNameInChapterBar: boolean;
showAutogeneratedChapters: boolean;
useVirtualTime: boolean;
showSegmentFailedToFetchWarning: boolean;
allowScrollingToEdit: boolean;
@@ -293,6 +294,7 @@ const syncDefaults = {
trackDownvotes: true,
trackDownvotesInPrivate: false,
dontShowNotice: false,
showUpcomingNotice: false,
noticeVisibilityMode: NoticeVisbilityMode.FadedForAutoSkip,
hideVideoPlayerControls: false,
hideInfoButtonPlayerControls: false,
@@ -309,7 +311,6 @@ const syncDefaults = {
audioNotificationOnSkip: false,
checkForUnlistedVideos: false,
testingServer: false,
refetchWhenNotFound: true,
ytInfoPermissionGranted: false,
allowExpirements: true,
showDonationLink: true,
@@ -326,6 +327,7 @@ const syncDefaults = {
showCategoryGuidelines: true,
showCategoryWithoutPermission: false,
showSegmentNameInChapterBar: true,
showAutogeneratedChapters: true,
useVirtualTime: true,
showSegmentFailedToFetchWarning: true,
allowScrollingToEdit: true,

View File

@@ -20,11 +20,12 @@ import Utils from "./utils";
import PreviewBar, { PreviewBarSegment } from "./js-components/previewBar";
import SkipNotice from "./render/SkipNotice";
import SkipNoticeComponent from "./components/SkipNoticeComponent";
import UpcomingNotice from "./render/UpcomingNotice";
import SubmissionNotice from "./render/SubmissionNotice";
import { Message, MessageResponse, VoteResponse } from "./messageTypes";
import { SkipButtonControlBar } from "./js-components/skipButtonControlBar";
import { getStartTimeFromUrl } from "./utils/urlParser";
import { getControls, getExistingChapters, getHashParams, isPlayingPlaylist, isVisible } from "./utils/pageUtils";
import { getControls, getExistingChapters, getHashParams, hasAutogeneratedChapters, isPlayingPlaylist, isVisible } from "./utils/pageUtils";
import { CategoryPill } from "./render/CategoryPill";
import { AnimationUtils } from "../maze-utils/src/animationUtils";
import { GenericUtils } from "./utils/genericUtils";
@@ -34,7 +35,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, getLastNonInlineVideoID, triggerVideoIDChange, triggerVideoElementChange, getIsInline, getCurrentTime, setCurrentTime, getVideoDuration, verifyCurrentTime } from "../maze-utils/src/video";
import { getChannelIDInfo, getVideo, getIsAdPlaying, getIsLivePremiere, setIsAdPlaying, checkVideoIDChange, getVideoID, getYouTubeVideoID, setupVideoModule, checkIfNewVideoID, isOnInvidious, isOnMobileYouTube, getLastNonInlineVideoID, triggerVideoIDChange, triggerVideoElementChange, getIsInline, getCurrentTime, setCurrentTime, getVideoDuration, verifyCurrentTime, waitForVideo } from "../maze-utils/src/video";
import { Keybind, StorageChangesObject, isSafari, keybindEquals, keybindToString } from "../maze-utils/src/config";
import { findValidElement } from "../maze-utils/src/dom"
import { getHash, HashedValue } from "../maze-utils/src/hash";
@@ -42,7 +43,7 @@ import { generateUserID } from "../maze-utils/src/setup";
import { updateAll } from "../maze-utils/src/thumbnailManagement";
import { setupThumbnailListener } from "./utils/thumbnails";
import * as documentScript from "../dist/js/document.js";
import { runCompatibilityChecks } from "./utils/compatibility";
import { isVorapisInstalled, runCompatibilityChecks } from "./utils/compatibility";
import { cleanPage } from "./utils/pageCleaner";
import { addCleanupListener } from "../maze-utils/src/cleanup";
import { hideDeArrowPromotion, tryShowingDeArrowPromotion } from "./dearrowPromotion";
@@ -77,8 +78,8 @@ let importingChaptersWaitingForFocus = false;
let importingChaptersWaiting = false;
// List of open skip notices
const skipNotices: SkipNotice[] = [];
let upcomingNotice: UpcomingNotice | null = null;
let activeSkipKeybindElement: ToggleSkippable = null;
let retryFetchTimeout: NodeJS.Timeout = null;
let shownSegmentFailedToFetchWarning = false;
let selectedSegment: SegmentUUID | null = null;
let previewedSegment = false;
@@ -107,6 +108,7 @@ const lastNextChapterKeybind = {
let currentSkipSchedule: NodeJS.Timeout = null;
let currentSkipInterval: NodeJS.Timeout = null;
let currentVirtualTimeInterval: NodeJS.Timeout = null;
let currentUpcomingSchedule: NodeJS.Timeout = null;
/** Has the sponsor been skipped */
let sponsorSkipped: boolean[] = [];
@@ -170,7 +172,6 @@ let popupInitialised = false;
let submissionNotice: SubmissionNotice = null;
let lastResponseStatus: number;
let retryCount = 0;
// Contains all of the functions and variables needed by the skip notice
const skipNoticeContentContainer: ContentContainer = () => ({
@@ -388,7 +389,6 @@ if (!Config.configSyncListeners.includes(contentConfigUpdateListener)) {
function resetValues() {
lastCheckTime = 0;
lastCheckVideoTime = -1;
retryCount = 0;
previewedSegment = false;
firstPlay = true;
@@ -426,6 +426,11 @@ function resetValues() {
skipNotices.pop()?.close();
}
if (upcomingNotice) {
upcomingNotice.close();
upcomingNotice = null;
}
hideDeArrowPromotion();
}
@@ -511,7 +516,7 @@ function handleMobileControlsMutations(): void {
function getPreviewBarAttachElement(): HTMLElement | null {
const progressElementOptions = [{
// For newer mobile YouTube (Sept 2024)
selector: ".YtChapteredProgressBarHost",
selector: ".ytChapteredProgressBarHost, .ytProgressBarLineHost, .YtProgressBarLineHost, .YtChapteredProgressBarHost",
isVisibleCheck: true
}, {
// For newer mobile YouTube (May 2024)
@@ -544,6 +549,9 @@ function getPreviewBarAttachElement(): HTMLElement | null {
// For piped
selector: ".shaka-ad-markers",
isVisibleCheck: false
}, {
// For Vorapis v3
selector: ".ytp-progress-bar-container > .html5-progress-bar > .ytp-progress-list"
}
];
@@ -606,6 +614,11 @@ function cancelSponsorSchedule(): void {
clearInterval(currentSkipInterval);
currentSkipInterval = null;
}
if (currentUpcomingSchedule !== null) {
clearTimeout(currentUpcomingSchedule);
currentUpcomingSchedule = null;
}
}
/**
@@ -744,7 +757,8 @@ async function startSponsorSchedule(includeIntersectingSegments = false, current
skippingFunction(currentTime);
} else {
let delayTime = timeUntilSponsor * 1000 * (1 / getVideo().playbackRate);
if (delayTime < (isFirefoxOrSafari() && !isSafari() ? 750 : 300)) {
if (delayTime < (isFirefoxOrSafari() && !isSafari() ? 750 : 300)
&& shouldAutoSkip(skippingSegments[0])) {
let forceStartIntervalTime: number | null = null;
if (isFirefoxOrSafari() && !isSafari() && delayTime > 300) {
forceStartIntervalTime = await waitForNextTimeChange();
@@ -787,7 +801,22 @@ async function startSponsorSchedule(includeIntersectingSegments = false, current
const offset = (isFirefoxOrSafari() && !isSafari() ? 600 : 150);
// Schedule for right before to be more precise than normal timeout
currentSkipSchedule = setTimeout(skippingFunction, Math.max(0, delayTime - offset));
const offsetDelayTime = Math.max(0, delayTime - offset);
currentSkipSchedule = setTimeout(skippingFunction, offsetDelayTime);
// Show the notice only if the segment hasn't already started
if (Config.config.showUpcomingNotice && getCurrentTime() < skippingSegments[0].segment[0]
&& !sponsorTimesSubmitting?.some((segment) => segment.segment === currentSkip.segment)
&& [ActionType.Skip, ActionType.Mute].includes(skippingSegments[0].actionType)
&& !getVideo()?.paused) {
const maxPopupTime = 3000;
const timeUntilPopup = Math.max(0, offsetDelayTime - maxPopupTime);
const popupTime = offsetDelayTime - timeUntilPopup;
const autoSkip = shouldAutoSkip(skippingSegments[0]);
if (currentUpcomingSchedule) clearTimeout(currentUpcomingSchedule);
currentUpcomingSchedule = setTimeout(createUpcomingNotice, timeUntilPopup, [skippingSegments[0]], popupTime, autoSkip);
}
}
}
}
@@ -804,7 +833,7 @@ function waitForNextTimeChange(): Promise<DOMHighResTimeStamp | null> {
function getVirtualTime(): number {
const virtualTime = lastTimeFromWaitingEvent ?? (lastKnownVideoTime.videoTime !== null ?
(performance.now() - lastKnownVideoTime.preciseTime) * getVideo().playbackRate / 1000 + lastKnownVideoTime.videoTime : null);
(performance.now() - lastKnownVideoTime.preciseTime) * (getVideo()?.playbackRate || 1) / 1000 + lastKnownVideoTime.videoTime : null);
if (Config.config.useVirtualTime && !isSafari() && virtualTime
&& Math.abs(virtualTime - getCurrentTime()) < 0.2 && getCurrentTime() !== 0) {
@@ -850,8 +879,8 @@ function incorrectVideoCheck(videoID?: string, sponsorTime?: SponsorTime): boole
let playbackRateCheckInterval: NodeJS.Timeout | null = null;
let lastPlaybackSpeed = 1;
let setupVideoListenersFirstTime = true;
function setupVideoListeners() {
const video = getVideo();
function setupVideoListeners(video: HTMLVideoElement) {
if (!video) return; // Maybe video became invisible
//wait until it is loaded
video.addEventListener('loadstart', videoOnReadyListener)
@@ -1202,7 +1231,7 @@ async function sponsorsLookup(keepOldSubmissions = true, ignoreCache = false) {
if (!getVideo()) {
//there is still no video here
await waitFor(() => getVideo(), 5000, 10);
await waitForVideo();
}
startSkipScheduleCheckingForStartSponsors();
@@ -1210,11 +1239,7 @@ async function sponsorsLookup(keepOldSubmissions = true, ignoreCache = false) {
if (!isNaN(getVideoDuration())) {
updatePreviewBar();
}
} else {
retryFetch(404);
}
} else {
retryFetch(lastResponseStatus);
}
importExistingChapters(true);
@@ -1256,7 +1281,13 @@ function importExistingChapters(wait: boolean) {
existingChaptersImported = true;
updatePreviewBar();
}
}).catch(() => { importingChaptersWaiting = false; }); // eslint-disable-line @typescript-eslint/no-empty-function
}).catch(() => { importingChaptersWaiting = false; });
if (!Config.config.showAutogeneratedChapters) {
waitFor(() => hasAutogeneratedChapters(), wait ? 15000 : 0, 400).then(() => {
updateActiveSegment(getCurrentTime());
}).catch(() => { }); // eslint-disable-line @typescript-eslint/no-empty-function
}
}
}
}
@@ -1275,27 +1306,6 @@ async function lockedCategoriesLookup(): Promise<void> {
}
}
function retryFetch(errorCode: number): void {
sponsorDataFound = false;
if (!Config.config.refetchWhenNotFound) return;
if (retryFetchTimeout) clearTimeout(retryFetchTimeout);
if ((errorCode !== 404 && retryCount > 1) || (errorCode !== 404 && retryCount > 10)) {
// Too many errors (50x), give up
return;
}
retryCount++;
const delay = errorCode === 404 ? (30000 + Math.random() * 30000) : (2000 + Math.random() * 10000);
retryFetchTimeout = setTimeout(() => {
if (getVideoID() && sponsorTimes?.length === 0
|| sponsorTimes.every((segment) => segment.source !== SponsorSourceType.Server)) {
// sponsorsLookup();
}
}, delay);
}
/**
* Only should be used when it is okay to skip a sponsor when in the middle of it
*
@@ -1347,7 +1357,9 @@ function startSkipScheduleCheckingForStartSponsors() {
const fullVideoSegment = sponsorTimes.filter((time) => time.actionType === ActionType.Full)[0];
if (fullVideoSegment) {
categoryPill?.setSegment(fullVideoSegment);
waitFor(() => categoryPill).then(() => {
categoryPill?.setSegment(fullVideoSegment);
});
}
if (startingSegmentTime !== -1) {
@@ -1388,7 +1400,7 @@ function updatePreviewBar(): void {
showLarger: segment.actionType === ActionType.Poi,
description: segment.description,
source: segment.source,
requiredSegment: requiredSegment && (segment.UUID === requiredSegment || segment.UUID?.startsWith(requiredSegment)),
requiredSegment: requiredSegment && (segment.UUID === requiredSegment || segment.UUID?.startsWith(requiredSegment) || requiredSegment.startsWith(segment.UUID)),
selectedSegment: selectedSegment && segment.UUID === selectedSegment
});
});
@@ -1432,10 +1444,10 @@ async function channelIDChange(channelIDInfo: ChannelIDInfo) {
if (Config.config.forceChannelCheck && sponsorTimes?.length > 0) startSkipScheduleCheckingForStartSponsors();
}
function videoElementChange(newVideo: boolean): void {
function videoElementChange(newVideo: boolean, video: HTMLVideoElement): void {
waitFor(() => Config.isReady()).then(() => {
if (newVideo) {
setupVideoListeners();
setupVideoListeners(video);
setupSkipButtonControlBar();
setupCategoryPill();
}
@@ -1666,7 +1678,7 @@ function sendTelemetryAndCount(skippingSegments: SponsorTime[], secondsSkipped:
counted = true;
}
if (fullSkip) asyncRequestToServer("POST", "/api/viewedVideoSponsorTime?UUID=" + segment.UUID);
if (fullSkip) asyncRequestToServer("POST", "/api/viewedVideoSponsorTime?UUID=" + segment.UUID + "&videoID=" + getVideoID());
}
}
}
@@ -1720,7 +1732,7 @@ function skipToTime({v, skipTime, skippingSegments, openNotice, forceAutoSkip, u
if (autoSkip && Config.config.audioNotificationOnSkip
&& !isSubmittingSegment && !getVideo()?.muted) {
const beep = new Audio(chrome.runtime.getURL("icons/beep.ogg"));
const beep = new Audio(chrome.runtime.getURL("icons/beep.oga"));
beep.volume = getVideo().volume * 0.1;
const oldMetadata = navigator.mediaSession.metadata
beep.play();
@@ -1736,11 +1748,13 @@ function skipToTime({v, skipTime, skippingSegments, openNotice, forceAutoSkip, u
if (!autoSkip
&& skippingSegments.length === 1
&& skippingSegments[0].actionType === ActionType.Poi) {
skipButtonControlBar.enable(skippingSegments[0]);
if (isOnMobileYouTube() || Config.config.skipKeybind == null) skipButtonControlBar.setShowKeybindHint(false);
activeSkipKeybindElement?.setShowKeybindHint(false);
activeSkipKeybindElement = skipButtonControlBar;
waitFor(() => skipButtonControlBar).then(() => {
skipButtonControlBar.enable(skippingSegments[0]);
if (isOnMobileYouTube() || Config.config.skipKeybind == null) skipButtonControlBar.setShowKeybindHint(false);
activeSkipKeybindElement?.setShowKeybindHint(false);
activeSkipKeybindElement = skipButtonControlBar;
})
} else {
if (openNotice) {
//send out the message saying that a sponsor message was skipped
@@ -1773,7 +1787,12 @@ function createSkipNotice(skippingSegments: SponsorTime[], autoSkip: boolean, un
}
}
const newSkipNotice = new SkipNotice(skippingSegments, autoSkip, skipNoticeContentContainer, unskipTime, startReskip);
const upcomingNoticeShown = !!upcomingNotice && !upcomingNotice.closed;
const newSkipNotice = new SkipNotice(skippingSegments, autoSkip, skipNoticeContentContainer, () => {
upcomingNotice?.close();
upcomingNotice = null;
}, unskipTime, startReskip, upcomingNoticeShown);
if (isOnMobileYouTube() || Config.config.skipKeybind == null) newSkipNotice.setShowKeybindHint(false);
skipNotices.push(newSkipNotice);
@@ -1781,6 +1800,17 @@ function createSkipNotice(skippingSegments: SponsorTime[], autoSkip: boolean, un
activeSkipKeybindElement = newSkipNotice;
}
function createUpcomingNotice(skippingSegments: SponsorTime[], timeLeft: number, autoSkip: boolean): void {
if (upcomingNotice
&& !upcomingNotice.closed
&& upcomingNotice.sameNotice(skippingSegments)) {
return;
}
upcomingNotice?.close();
upcomingNotice = new UpcomingNotice(skippingSegments, skipNoticeContentContainer, timeLeft / 1000, autoSkip);
}
function unskipSponsorTime(segment: SponsorTime, unskipTime: number = null, forceSeek = false) {
if (segment.actionType === ActionType.Mute) {
getVideo().muted = false;
@@ -2238,7 +2268,8 @@ async function voteAsync(type: number, UUID: SegmentUUID, category?: Category):
message: "submitVote",
type: type,
UUID: UUID,
category: category
category: category,
videoID: getVideoID()
}, (response) => {
if (response.successType === 1) {
// Change the sponsor locally
@@ -2522,8 +2553,10 @@ function addHotkeyListener(): void {
}
function hotkeyListener(e: KeyboardEvent): void {
if (["textarea", "input"].includes(document.activeElement?.tagName?.toLowerCase())
|| document.activeElement?.id?.toLowerCase()?.includes("editable")) return;
if ((["textarea", "input"].includes(document.activeElement?.tagName?.toLowerCase())
|| (document.activeElement as HTMLElement)?.isContentEditable
|| document.activeElement?.id?.toLowerCase()?.match(/editable|input/))
&& document.hasFocus()) return;
const key: Keybind = {
key: e.key,
@@ -2559,7 +2592,9 @@ function hotkeyListener(e: KeyboardEvent): void {
for (let i = 0; i < skipNotices.length; i++) {
skipNotices.pop().close();
}
upcomingNotice?.close();
upcomingNotice = null;
return;
} else if (keybindEquals(key, startSponsorKey)) {
startOrEndTimingNewSegment();
@@ -2631,11 +2666,11 @@ function showTimeWithoutSkips(skippedDuration: number): void {
}
// YouTube player time display
const displayClass =
isOnInvidious() ? "vjs-duration" :
isOnMobileYouTube() ? "ytm-time-display" :
"ytp-time-display.notranslate";
const display = document.querySelector(`.${displayClass}`);
const selector =
isOnInvidious() ? ".vjs-duration" :
isOnMobileYouTube() ? ".ytwPlayerTimeDisplayContent" :
".ytp-time-display.notranslate .ytp-time-wrapper";
const display = document.querySelector(selector);
if (!display) return;
const durationID = "sponsorBlockDurationAfterSkips";
@@ -2645,9 +2680,13 @@ function showTimeWithoutSkips(skippedDuration: number): void {
if (duration === null) {
duration = document.createElement('span');
duration.id = durationID;
if (!isOnInvidious()) duration.classList.add(displayClass);
display.appendChild(duration);
if (isOnMobileYouTube()) {
duration.style.paddingLeft = "4px";
display.insertBefore(duration, display.lastChild);
} else {
display.appendChild(duration);
}
}
const durationAfterSkips = getFormattedTime(getVideoDuration() - skippedDuration);
@@ -2694,6 +2733,10 @@ function setCategoryColorCSSVariables() {
if (!styleContainer) {
styleContainer = document.createElement("style");
styleContainer.id = "sbCategoryColorStyle";
if (isVorapisInstalled()) {
// Vorapi deletes styles
styleContainer.className = 'stylus';
}
const head = (document.head || document.documentElement);
head.appendChild(styleContainer)

View File

@@ -13,7 +13,8 @@ import { DEFAULT_CATEGORY, shortCategoryName } from "../utils/categoryUtils";
import { normalizeChapterName } from "../utils/exporter";
import { findValidElement } from "../../maze-utils/src/dom";
import { addCleanupListener } from "../../maze-utils/src/cleanup";
import { isVisible } from "../utils/pageUtils";
import { hasAutogeneratedChapters, isVisible } from "../utils/pageUtils";
import { isVorapisInstalled } from "../utils/compatibility";
const TOOLTIP_VISIBLE_CLASS = 'sponsorCategoryTooltipVisible';
const MIN_CHAPTER_SIZE = 0.003;
@@ -51,6 +52,7 @@ class PreviewBar {
progressBar: HTMLElement;
segments: PreviewBarSegment[] = [];
hasYouTubeChapters = false;
existingChapters: PreviewBarSegment[] = [];
videoDuration = 0;
updateExistingChapters: () => void;
@@ -100,13 +102,15 @@ class PreviewBar {
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;
// YT, Vorapis, unknown
const tooltipTextWrapper = document.querySelector(".ytp-tooltip-text-wrapper, .ytp-progress-tooltip-text-container") ?? document.querySelector("#progress-bar-container.ytk-player > #hover-time-info");
const originalTooltip = tooltipTextWrapper.querySelector(".ytp-tooltip-title:not(.sponsorCategoryTooltip), .ytp-progress-tooltip-text:not(.sponsorCategoryTooltip)") as HTMLElement;
if (!tooltipTextWrapper || !tooltipTextWrapper.parentElement) return;
// 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
const titleTooltip = tooltipTextWrapper.querySelector(".ytp-tooltip-title, .ytp-progress-tooltip-text") as HTMLElement;
if (!this.categoryTooltipContainer || !titleTooltip) return;
tooltipTextWrapper.insertBefore(this.categoryTooltip, titleTooltip.nextSibling);
@@ -128,12 +132,12 @@ class PreviewBar {
seekBar.addEventListener("mousemove", (e: MouseEvent) => {
if (!mouseOnSeekBar || !this.categoryTooltip || !this.categoryTooltipContainer || !chrome.runtime?.id) return;
let noYoutubeChapters = !!tooltipTextWrapper.querySelector(".ytp-tooltip-text.ytp-tooltip-text-no-title");
let noYoutubeChapters = !!tooltipTextWrapper.querySelector(".ytp-tooltip-text.ytp-tooltip-text-no-title, .ytp-progress-tooltip-timestamp");
const timeInSeconds = this.decimalToTime((e.clientX - seekBar.getBoundingClientRect().x) / seekBar.clientWidth);
// 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");
@@ -142,13 +146,28 @@ 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");
}
} 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");
}
@@ -156,6 +175,11 @@ class PreviewBar {
this.setTooltipTitle(mainSegment, this.categoryTooltip);
this.setTooltipTitle(secondarySegment, this.chapterTooltip);
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)) {
if (originalTooltip.style.display !== "none") originalTooltip.style.display = "none";
@@ -164,16 +188,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;
}
// Used to prevent overlapping
this.categoryTooltip.classList.toggle("ytp-tooltip-text-no-title", noYoutubeChapters);
this.chapterTooltip.classList.toggle("ytp-tooltip-text-no-title", noYoutubeChapters);
});
}
@@ -224,7 +248,22 @@ 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
@@ -238,7 +277,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) {
@@ -342,11 +382,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;
@@ -372,6 +414,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");
@@ -647,8 +698,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);
@@ -751,7 +815,8 @@ class PreviewBar {
updateChapterText(segments: SponsorTime[], submittingSegments: SponsorTime[], currentTime: number): SponsorTime[] {
if (!Config.config.showSegmentNameInChapterBar
|| Config.config.disableSkipping
|| ((!segments || segments.length <= 0) && submittingSegments?.length <= 0)) {
|| ((!segments || segments.length <= 0) && submittingSegments?.length <= 0
&& (Config.config.showAutogeneratedChapters || !hasAutogeneratedChapters()))) {
const chaptersContainer = this.getChaptersContainer();
if (chaptersContainer) {
chaptersContainer.querySelector(".sponsorChapterText")?.remove();
@@ -791,8 +856,14 @@ 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];
@@ -833,6 +904,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;

View File

@@ -681,9 +681,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();
});

View File

@@ -43,7 +43,7 @@ export class CategoryPill {
}
private async attachToPageInternal(): Promise<void> {
let referenceNode =
let referenceNode =
await waitFor(() => getYouTubeTitleNode());
// Experimental YouTube layout with description on right
@@ -119,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,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) {
this.skipNoticeRef = React.createRef();
this.segments = segments;
@@ -53,7 +53,9 @@ class SkipNotice {
closeListener={() => this.close()}
smaller={Config.config.noticeVisibilityMode >= NoticeVisbilityMode.MiniForAll
|| (Config.config.noticeVisibilityMode >= NoticeVisbilityMode.MiniForAutoSkip && autoSkip)}
unskipTime={unskipTime} />
fadeIn={!upcomingNoticeShown}
unskipTime={unskipTime}
componentDidMount={componentDidMount} />
);
}

View File

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

View File

@@ -5,6 +5,7 @@ import { getHash, HashedValue } from "../maze-utils/src/hash";
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 {
@@ -198,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.startsWith(UUID) || UUID.startsWith(sponsorTimes[i].UUID)) {
return i;
}
}
@@ -282,6 +283,17 @@ export default class Utils {
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);

View File

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

@@ -1,5 +1,6 @@
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 = [
@@ -10,7 +11,9 @@ export function getControls(): HTMLElement {
// Invidious/videojs video element's controls element
".vjs-control-bar",
// Piped shaka player
".shaka-bottom-controls"
".shaka-bottom-controls",
// Vorapis v3
".html5-player-chrome"
];
for (const controlsSelector of controlsSelectors) {
@@ -53,10 +56,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 = 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
@@ -92,8 +100,6 @@ export function getExistingChapters(currentVideoID: VideoID, duration: number):
}
}
console.log(chapters)
return chapters;
}

View File

@@ -48,11 +48,13 @@ async function fetchSegmentsForVideo(videoID: VideoID): Promise<SegmentResponse>
const extraRequestData: Record<string, unknown> = {};
const hashParams = getHashParams();
if (hashParams.requiredSegment) extraRequestData.requiredSegment = hashParams.requiredSegment;
const hashPrefix = (await getHash(videoID, 1)).slice(0, 4) as VideoID & HashedValue;
const hasDownvotedSegments = !!Config.local.downvotedSegments[hashPrefix];
const response = await asyncRequestToServer('GET', "/api/skipSegments/" + hashPrefix, {
categories: CompileConfig.categoryList,
actionTypes: ActionTypes,
trimUUIDs: hasDownvotedSegments ? null : 5,
...extraRequestData
}, {
"X-CLIENT-NAME": `${chrome.runtime.id}/v${chrome.runtime.getManifest().version}`

View File

@@ -1,6 +1,6 @@
import { isOnInvidious, parseYouTubeVideoIDFromURL } from "../../maze-utils/src/video";
import Config from "../config";
import { getVideoLabel } from "./videoLabels";
import { getHasStartSegment, getVideoLabel } from "./videoLabels";
import { getThumbnailSelector, setThumbnailListener } from "../../maze-utils/src/thumbnailManagement";
import { VideoID } from "../types";
import { getSegmentsForVideo } from "./segmentData";
@@ -58,14 +58,41 @@ function thumbnailHoverListener(e: MouseEvent) {
if (!thumbnail) return;
// Pre-fetch data for this video
const videoID = extractVideoID(thumbnail);
if (videoID) {
void getSegmentsForVideo(videoID, false);
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 = (isOnInvidious() ? thumbnail.parentElement : thumbnail.querySelector("#thumbnail")) as HTMLAnchorElement
const link = getLink(thumbnail);
if (!link || link.nodeName !== "A" || !link.href) return null; // no link found
return parseYouTubeVideoIDFromURL(link.href)?.videoID;

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