Compare commits

...

39 Commits

Author SHA1 Message Date
Ajay
be98dc7b73 update translations 2025-03-02 23:41:58 -05:00
Ajay
3174eda29a bump version 2025-03-02 23:40:30 -05:00
Ajay
6a4bb50006 Fix document script player client fetching 2025-03-02 23:34:50 -05:00
Ajay
2823e51b1d Allow importing placeholder chapters without start times 2025-02-11 20:28:10 -05:00
Ajay
19efcb5a98 Handle video being null in skip notice
Resolves #2201
2025-02-02 22:17:51 -05:00
Ajay
5c7e3b5cb3 bump version 2025-01-23 05:19:10 -05:00
Ajay
c3cbc44d28 Fix hiding segments not working on videos with YouTube chapters
Fixes #2194
2025-01-23 05:18:49 -05:00
Ajay
6965279d36 bump version 2025-01-22 14:17:15 -05:00
Ajay
eb61ad76c1 Add extension name to storage error message 2025-01-22 14:17:04 -05:00
Ajay
7a1fa601da bump version 2025-01-21 23:43:11 -05:00
Ajay
945ac5aad3 Fix local storage error appearing unnecessarily
Also adds more details to the log message
2025-01-21 23:42:58 -05:00
Ajay
617f0e25e8 bump version 2025-01-20 02:28:35 -05:00
Ajay
ac52a1cb1d update translations 2025-01-20 02:28:23 -05:00
Ajay
2cefb9822b Make copy button in popup use full segment ID 2025-01-18 03:13:40 -05:00
Ajay
b81de96277 Add support for partial segment IDs 2025-01-18 03:10:14 -05:00
Ajay
236c95d2f6 Only do pre-fetch on hover if there are segments near the start of the video 2025-01-18 00:25:33 -05:00
Ajay
0f75953ad1 bump version 2025-01-17 04:35:52 -05:00
Ajay
0f4c1c50cb Add delay to fetch on hover to make it less easy to trigger 2025-01-17 04:33:56 -05:00
Ajay
9e4939e2e3 Fix options page layout issue 2025-01-14 15:08:01 -05:00
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
18 changed files with 139 additions and 83 deletions

View File

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

View File

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

View File

@@ -88,7 +88,7 @@ div:hover > #previewbar.sbNotInvidious {
display: none !important; display: none !important;
} }
.ytp-tooltip.sponsorCategoryTooltipVisible { .ytp-tooltip.sponsorCategoryTooltipVisible:not(.sponsorTooltipHasYTChapters) {
transform: translateY(-1em) !important; transform: translateY(-1em) !important;
} }

View File

@@ -373,6 +373,7 @@ input[type='number'] {
display: inline-block; display: inline-block;
width: 40px; width: 40px;
height: 24px; height: 24px;
min-width: 40px;
} }
.switch input { .switch input {

View File

@@ -154,20 +154,6 @@
<div class="small-description">__MSG_whatForceChannelCheck__</div> <div class="small-description">__MSG_whatForceChannelCheck__</div>
</div> </div>
<div data-type="toggle" data-sync="refetchWhenNotFound">
<div class="switch-container">
<label class="switch">
<input id="refetchWhenNotFound" type="checkbox" checked>
<span class="slider round"></span>
</label>
<label class="switch-label" for="refetchWhenNotFound">
__MSG_enableRefetchWhenNotFound__
</label>
</div>
<div class="small-description">__MSG_whatRefetchWhenNotFound__</div>
</div>
<div data-type="toggle" data-sync="showCategoryWithoutPermission"> <div data-type="toggle" data-sync="showCategoryWithoutPermission">
<div class="switch-container"> <div class="switch-container">
<label class="switch"> <label class="switch">

View File

@@ -43,7 +43,7 @@ chrome.runtime.onMessage.addListener(function (request, sender, callback) {
chrome.tabs.create({url: chrome.runtime.getURL(request.url)}); chrome.tabs.create({url: chrome.runtime.getURL(request.url)});
return false; return false;
case "submitVote": case "submitVote":
submitVote(request.type, request.UUID, request.category).then(callback); submitVote(request.type, request.UUID, request.category, request.videoID).then(callback);
//this allows the callback to be called later //this allows the callback to be called later
return true; return true;
@@ -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; let userID = Config.config.userID;
if (userID == undefined || userID === "undefined") { 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; const typeSection = (type !== undefined) ? "&type=" + type : "&category=" + category;
try { try {
const response = await asyncRequestToServer("POST", "/api/voteOnSponsorTime?UUID=" + UUID + "&userID=" + userID + typeSection); const response = await asyncRequestToServer("POST", "/api/voteOnSponsorTime?UUID=" + UUID + "&videoID=" + videoID + "&userID=" + userID + typeSection);
if (response.ok) { if (response.ok) {
return { return {

View File

@@ -669,7 +669,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
const skipButtonStates = this.state.skipButtonStates; const skipButtonStates = this.state.skipButtonStates;
const skipButtonCallbacks = this.state.skipButtonCallbacks; const skipButtonCallbacks = this.state.skipButtonCallbacks;
if (buttonIndex === null) { if (buttonIndex === null) {
for (let i = 0; i < this.segments.length; i++) { for (let i = 0; i < skipButtonStates.length; i++) {
skipButtonStates[i] = skipButtonState; skipButtonStates[i] = skipButtonState;
skipButtonCallbacks[i] = this.reskip.bind(this); skipButtonCallbacks[i] = this.reskip.bind(this);
} }
@@ -697,7 +697,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
getFullDurationCountdown(index: number): () => number { getFullDurationCountdown(index: number): () => number {
return () => { return () => {
const sponsorTime = this.segments[index]; const sponsorTime = this.segments[index];
const duration = Math.round((sponsorTime.segment[1] - getCurrentTime()) * (1 / getVideo().playbackRate)); const duration = Math.round((sponsorTime.segment[1] - getCurrentTime()) * (1 / (getVideo()?.playbackRate ?? 1)));
return Math.max(duration, Config.config.skipNoticeDuration); return Math.max(duration, Config.config.skipNoticeDuration);
}; };

View File

@@ -48,7 +48,6 @@ interface SBConfig {
audioNotificationOnSkip: boolean; audioNotificationOnSkip: boolean;
checkForUnlistedVideos: boolean; checkForUnlistedVideos: boolean;
testingServer: boolean; testingServer: boolean;
refetchWhenNotFound: boolean;
ytInfoPermissionGranted: boolean; ytInfoPermissionGranted: boolean;
allowExpirements: boolean; allowExpirements: boolean;
showDonationLink: boolean; showDonationLink: boolean;
@@ -312,7 +311,6 @@ const syncDefaults = {
audioNotificationOnSkip: false, audioNotificationOnSkip: false,
checkForUnlistedVideos: false, checkForUnlistedVideos: false,
testingServer: false, testingServer: false,
refetchWhenNotFound: true,
ytInfoPermissionGranted: false, ytInfoPermissionGranted: false,
allowExpirements: true, allowExpirements: true,
showDonationLink: true, showDonationLink: true,

View File

@@ -80,7 +80,6 @@ let importingChaptersWaiting = false;
const skipNotices: SkipNotice[] = []; const skipNotices: SkipNotice[] = [];
let upcomingNotice: UpcomingNotice | null = null; let upcomingNotice: UpcomingNotice | null = null;
let activeSkipKeybindElement: ToggleSkippable = null; let activeSkipKeybindElement: ToggleSkippable = null;
let retryFetchTimeout: NodeJS.Timeout = null;
let shownSegmentFailedToFetchWarning = false; let shownSegmentFailedToFetchWarning = false;
let selectedSegment: SegmentUUID | null = null; let selectedSegment: SegmentUUID | null = null;
let previewedSegment = false; let previewedSegment = false;
@@ -173,7 +172,6 @@ let popupInitialised = false;
let submissionNotice: SubmissionNotice = null; let submissionNotice: SubmissionNotice = null;
let lastResponseStatus: number; let lastResponseStatus: number;
let retryCount = 0;
// Contains all of the functions and variables needed by the skip notice // Contains all of the functions and variables needed by the skip notice
const skipNoticeContentContainer: ContentContainer = () => ({ const skipNoticeContentContainer: ContentContainer = () => ({
@@ -308,7 +306,8 @@ function messageListener(request: Message, sender: unknown, sendResponse: (respo
for (const segment of importedSegments) { for (const segment of importedSegments) {
if (!sponsorTimesSubmitting.some( if (!sponsorTimesSubmitting.some(
(s) => Math.abs(s.segment[0] - segment.segment[0]) < 1 (s) => Math.abs(s.segment[0] - segment.segment[0]) < 1
&& Math.abs(s.segment[1] - segment.segment[1]) < 1)) { && Math.abs(s.segment[1] - segment.segment[1]) < 1
&& s.description === segment.description)) {
const hasChaptersPermission = (Config.config.showCategoryWithoutPermission const hasChaptersPermission = (Config.config.showCategoryWithoutPermission
|| Config.config.permissions["chapter"]); || Config.config.permissions["chapter"]);
if (segment.category === "chapter" && (!utils.getCategorySelection("chapter") || !hasChaptersPermission)) { if (segment.category === "chapter" && (!utils.getCategorySelection("chapter") || !hasChaptersPermission)) {
@@ -391,7 +390,6 @@ if (!Config.configSyncListeners.includes(contentConfigUpdateListener)) {
function resetValues() { function resetValues() {
lastCheckTime = 0; lastCheckTime = 0;
lastCheckVideoTime = -1; lastCheckVideoTime = -1;
retryCount = 0;
previewedSegment = false; previewedSegment = false;
firstPlay = true; firstPlay = true;
@@ -519,7 +517,7 @@ function handleMobileControlsMutations(): void {
function getPreviewBarAttachElement(): HTMLElement | null { function getPreviewBarAttachElement(): HTMLElement | null {
const progressElementOptions = [{ const progressElementOptions = [{
// For newer mobile YouTube (Sept 2024) // For newer mobile YouTube (Sept 2024)
selector: ".YtProgressBarLineHost, .YtChapteredProgressBarHost", selector: ".ytChapteredProgressBarHost, .ytProgressBarLineHost, .YtProgressBarLineHost, .YtChapteredProgressBarHost",
isVisibleCheck: true isVisibleCheck: true
}, { }, {
// For newer mobile YouTube (May 2024) // For newer mobile YouTube (May 2024)
@@ -760,7 +758,8 @@ async function startSponsorSchedule(includeIntersectingSegments = false, current
skippingFunction(currentTime); skippingFunction(currentTime);
} else { } else {
let delayTime = timeUntilSponsor * 1000 * (1 / getVideo().playbackRate); let delayTime = timeUntilSponsor * 1000 * (1 / getVideo().playbackRate);
if (delayTime < (isFirefoxOrSafari() && !isSafari() ? 750 : 300)) { if (delayTime < (isFirefoxOrSafari() && !isSafari() ? 750 : 300)
&& shouldAutoSkip(skippingSegments[0])) {
let forceStartIntervalTime: number | null = null; let forceStartIntervalTime: number | null = null;
if (isFirefoxOrSafari() && !isSafari() && delayTime > 300) { if (isFirefoxOrSafari() && !isSafari() && delayTime > 300) {
forceStartIntervalTime = await waitForNextTimeChange(); forceStartIntervalTime = await waitForNextTimeChange();
@@ -1241,11 +1240,7 @@ async function sponsorsLookup(keepOldSubmissions = true, ignoreCache = false) {
if (!isNaN(getVideoDuration())) { if (!isNaN(getVideoDuration())) {
updatePreviewBar(); updatePreviewBar();
} }
} else {
retryFetch(404);
} }
} else {
retryFetch(lastResponseStatus);
} }
importExistingChapters(true); importExistingChapters(true);
@@ -1312,27 +1307,6 @@ async function lockedCategoriesLookup(): Promise<void> {
} }
} }
function retryFetch(errorCode: number): void {
sponsorDataFound = false;
if (!Config.config.refetchWhenNotFound) return;
if (retryFetchTimeout) clearTimeout(retryFetchTimeout);
if ((errorCode !== 404 && retryCount > 1) || (errorCode !== 404 && retryCount > 10)) {
// Too many errors (50x), give up
return;
}
retryCount++;
const delay = errorCode === 404 ? (30000 + Math.random() * 30000) : (2000 + Math.random() * 10000);
retryFetchTimeout = setTimeout(() => {
if (getVideoID() && sponsorTimes?.length === 0
|| sponsorTimes.every((segment) => segment.source !== SponsorSourceType.Server)) {
// sponsorsLookup();
}
}, delay);
}
/** /**
* Only should be used when it is okay to skip a sponsor when in the middle of it * Only should be used when it is okay to skip a sponsor when in the middle of it
* *
@@ -1427,7 +1401,7 @@ function updatePreviewBar(): void {
showLarger: segment.actionType === ActionType.Poi, showLarger: segment.actionType === ActionType.Poi,
description: segment.description, description: segment.description,
source: segment.source, source: segment.source,
requiredSegment: requiredSegment && (segment.UUID === requiredSegment || segment.UUID?.startsWith(requiredSegment)), requiredSegment: requiredSegment && (segment.UUID === requiredSegment || segment.UUID?.startsWith(requiredSegment) || requiredSegment.startsWith(segment.UUID)),
selectedSegment: selectedSegment && segment.UUID === selectedSegment selectedSegment: selectedSegment && segment.UUID === selectedSegment
}); });
}); });
@@ -1705,7 +1679,7 @@ function sendTelemetryAndCount(skippingSegments: SponsorTime[], secondsSkipped:
counted = true; counted = true;
} }
if (fullSkip) asyncRequestToServer("POST", "/api/viewedVideoSponsorTime?UUID=" + segment.UUID); if (fullSkip) asyncRequestToServer("POST", "/api/viewedVideoSponsorTime?UUID=" + segment.UUID + "&videoID=" + getVideoID());
} }
} }
} }
@@ -2295,7 +2269,8 @@ async function voteAsync(type: number, UUID: SegmentUUID, category?: Category):
message: "submitVote", message: "submitVote",
type: type, type: type,
UUID: UUID, UUID: UUID,
category: category category: category,
videoID: getVideoID()
}, (response) => { }, (response) => {
if (response.successType === 1) { if (response.successType === 1) {
// Change the sponsor locally // Change the sponsor locally
@@ -2694,7 +2669,7 @@ function showTimeWithoutSkips(skippedDuration: number): void {
// YouTube player time display // YouTube player time display
const selector = const selector =
isOnInvidious() ? ".vjs-duration" : isOnInvidious() ? ".vjs-duration" :
isOnMobileYouTube() ? ".YtwPlayerTimeDisplayContent" : isOnMobileYouTube() ? ".ytwPlayerTimeDisplayContent" :
".ytp-time-display.notranslate .ytp-time-wrapper"; ".ytp-time-display.notranslate .ytp-time-wrapper";
const display = document.querySelector(selector); const display = document.querySelector(selector);
if (!display) return; if (!display) return;

View File

@@ -52,6 +52,7 @@ class PreviewBar {
progressBar: HTMLElement; progressBar: HTMLElement;
segments: PreviewBarSegment[] = []; segments: PreviewBarSegment[] = [];
hasYouTubeChapters = false;
existingChapters: PreviewBarSegment[] = []; existingChapters: PreviewBarSegment[] = [];
videoDuration = 0; videoDuration = 0;
updateExistingChapters: () => void; updateExistingChapters: () => void;
@@ -136,7 +137,7 @@ class PreviewBar {
// Find the segment at that location, using the shortest if multiple found // Find the segment at that location, using the shortest if multiple found
const [normalSegments, chapterSegments] = const [normalSegments, chapterSegments] =
partition(this.segments.filter((s) => s.source !== SponsorSourceType.YouTube), partition(this.segments,
(segment) => segment.actionType !== ActionType.Chapter); (segment) => segment.actionType !== ActionType.Chapter);
let mainSegment = this.getSmallestSegment(timeInSeconds, normalSegments, "normal"); let mainSegment = this.getSmallestSegment(timeInSeconds, normalSegments, "normal");
let secondarySegment = this.getSmallestSegment(timeInSeconds, chapterSegments, "chapter"); let secondarySegment = this.getSmallestSegment(timeInSeconds, chapterSegments, "chapter");
@@ -145,13 +146,28 @@ class PreviewBar {
secondarySegment = this.getSmallestSegment(timeInSeconds, chapterSegments.filter((s) => s !== secondarySegment)); secondarySegment = this.getSmallestSegment(timeInSeconds, chapterSegments.filter((s) => s !== secondarySegment));
} }
const hasAYouTubeChapterRemoved = this.hasYouTubeChapters
|| (!Config.config.showAutogeneratedChapters && hasAutogeneratedChapters());
if (hasAYouTubeChapterRemoved) {
// Hide original tooltip if some chapter has been filtered out
originalTooltip.style.display = "none";
noYoutubeChapters = true;
originalTooltip.classList.add("sponsorTooltipHasYTChapters");
} else {
originalTooltip.classList.remove("sponsorTooltipHasYTChapters");
}
if (mainSegment === null && secondarySegment === null) { if (mainSegment === null && secondarySegment === null) {
this.categoryTooltipContainer.classList.remove(TOOLTIP_VISIBLE_CLASS); if (!hasAYouTubeChapterRemoved) {
originalTooltip.style.removeProperty("display"); this.categoryTooltipContainer.classList.remove(TOOLTIP_VISIBLE_CLASS);
originalTooltip.style.removeProperty("display");
}
} else { } else {
this.categoryTooltipContainer.classList.add(TOOLTIP_VISIBLE_CLASS); this.categoryTooltipContainer.classList.add(TOOLTIP_VISIBLE_CLASS);
if (mainSegment !== null && secondarySegment !== null) { if (mainSegment !== null && secondarySegment !== null) {
this.categoryTooltipContainer.classList.add("sponsorTwoTooltips"); this.categoryTooltipContainer.classList.add("sponsorTwoTooltips");
originalTooltip.classList.remove("sponsorTooltipHasYTChapters");
} else { } else {
this.categoryTooltipContainer.classList.remove("sponsorTwoTooltips"); this.categoryTooltipContainer.classList.remove("sponsorTwoTooltips");
} }
@@ -172,16 +188,16 @@ class PreviewBar {
originalTooltip.style.removeProperty("display"); originalTooltip.style.removeProperty("display");
} }
// Used to prevent overlapping
this.categoryTooltip.classList.toggle("ytp-tooltip-text-no-title", noYoutubeChapters);
this.chapterTooltip.classList.toggle("ytp-tooltip-text-no-title", noYoutubeChapters);
// To prevent offset issue // To prevent offset issue
this.categoryTooltip.style.right = titleTooltip.style.right; this.categoryTooltip.style.right = titleTooltip.style.right;
this.chapterTooltip.style.right = titleTooltip.style.right; this.chapterTooltip.style.right = titleTooltip.style.right;
this.categoryTooltip.style.textAlign = titleTooltip.style.textAlign; this.categoryTooltip.style.textAlign = titleTooltip.style.textAlign;
this.chapterTooltip.style.textAlign = titleTooltip.style.textAlign; this.chapterTooltip.style.textAlign = titleTooltip.style.textAlign;
} }
// Used to prevent overlapping
this.categoryTooltip.classList.toggle("ytp-tooltip-text-no-title", noYoutubeChapters);
this.chapterTooltip.classList.toggle("ytp-tooltip-text-no-title", noYoutubeChapters);
}); });
} }
@@ -232,6 +248,7 @@ class PreviewBar {
set(segments: PreviewBarSegment[], videoDuration: number): void { set(segments: PreviewBarSegment[], videoDuration: number): void {
this.segments = segments ?? []; this.segments = segments ?? [];
this.videoDuration = videoDuration ?? 0; this.videoDuration = videoDuration ?? 0;
this.hasYouTubeChapters = segments.some((segment) => segment.source === SponsorSourceType.YouTube);
// Remove unnecessary original chapters if submitted replacements exist // Remove unnecessary original chapters if submitted replacements exist
for (const chapter of this.segments.filter((s) => s.actionType === ActionType.Chapter && s.source === SponsorSourceType.Server)) { for (const chapter of this.segments.filter((s) => s.actionType === ActionType.Chapter && s.source === SponsorSourceType.Server)) {

View File

@@ -681,9 +681,22 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
uuidButton.className = "voteButton"; uuidButton.className = "voteButton";
uuidButton.src = chrome.runtime.getURL("icons/clipboard.svg"); uuidButton.src = chrome.runtime.getURL("icons/clipboard.svg");
uuidButton.title = chrome.i18n.getMessage("copySegmentID"); uuidButton.title = chrome.i18n.getMessage("copySegmentID");
uuidButton.addEventListener("click", () => { uuidButton.addEventListener("click", async () => {
copyToClipboard(UUID);
const stopAnimation = AnimationUtils.applyLoadingAnimation(uuidButton, 0.3); const stopAnimation = AnimationUtils.applyLoadingAnimation(uuidButton, 0.3);
if (UUID.length > 60) {
copyToClipboard(UUID);
} else {
const segmentIDData = await asyncRequestToServer("GET", "/api/segmentID", {
UUID: UUID,
videoID: currentVideoID
});
if (segmentIDData.ok && segmentIDData.responseText) {
copyToClipboard(segmentIDData.responseText);
}
}
stopAnimation(); stopAnimation();
}); });

View File

@@ -5,6 +5,7 @@ import { getHash, HashedValue } from "../maze-utils/src/hash";
import { waitFor } from "../maze-utils/src"; import { waitFor } from "../maze-utils/src";
import { findValidElementFromSelector } from "../maze-utils/src/dom"; import { findValidElementFromSelector } from "../maze-utils/src/dom";
import { isSafari } from "../maze-utils/src/config"; import { isSafari } from "../maze-utils/src/config";
import { asyncRequestToServer } from "./utils/requests";
export default class Utils { export default class Utils {
@@ -198,7 +199,7 @@ export default class Utils {
getSponsorIndexFromUUID(sponsorTimes: SponsorTime[], UUID: string): number { getSponsorIndexFromUUID(sponsorTimes: SponsorTime[], UUID: string): number {
for (let i = 0; i < sponsorTimes.length; i++) { for (let i = 0; i < sponsorTimes.length; i++) {
if (sponsorTimes[i].UUID === UUID) { if (sponsorTimes[i].UUID && (sponsorTimes[i].UUID.startsWith(UUID) || UUID.startsWith(sponsorTimes[i].UUID))) {
return i; return i;
} }
} }
@@ -282,6 +283,17 @@ export default class Utils {
if ((chrome.extension.inIncognitoContext && !Config.config.trackDownvotesInPrivate) if ((chrome.extension.inIncognitoContext && !Config.config.trackDownvotesInPrivate)
|| !Config.config.trackDownvotes) return; || !Config.config.trackDownvotes) return;
if (segmentUUID.length < 60) {
const segmentIDData = await asyncRequestToServer("GET", "/api/segmentID", {
UUID: segmentUUID,
videoID
});
if (segmentIDData.ok && segmentIDData.responseText) {
segmentUUID = segmentIDData.responseText;
}
}
const hashedVideoID = (await getHash(videoID, 1)).slice(0, 4) as VideoID & HashedValue; const hashedVideoID = (await getHash(videoID, 1)).slice(0, 4) as VideoID & HashedValue;
const UUIDHash = await getHash(segmentUUID, 1); const UUIDHash = await getHash(segmentUUID, 1);
@@ -308,6 +320,7 @@ export default class Utils {
allDownvotes[hashedVideoID] = currentVideoData; allDownvotes[hashedVideoID] = currentVideoData;
} }
console.log(allDownvotes)
const entries = Object.entries(allDownvotes); const entries = Object.entries(allDownvotes);
if (entries.length > 10000) { if (entries.length > 10000) {

View File

@@ -34,9 +34,12 @@ function exportTime(segment: SponsorTime): string {
export function importTimes(data: string, videoDuration: number): SponsorTime[] { export function importTimes(data: string, videoDuration: number): SponsorTime[] {
const lines = data.split("\n"); const lines = data.split("\n");
const timeRegex = /(?:((?:\d+:)?\d+:\d+)+(?:\.\d+)?)|(?:\d+(?=s| second))/g;
const anyLineHasTime = lines.some((line) => timeRegex.test(line));
const result: SponsorTime[] = []; const result: SponsorTime[] = [];
for (const line of lines) { for (const line of lines) {
const match = line.match(/(?:((?:\d+:)?\d+:\d+)+(?:\.\d+)?)|(?:\d+(?=s| second))/g); const match = line.match(timeRegex);
if (match) { if (match) {
const startTime = getFormattedTimeToSeconds(match[0]); const startTime = getFormattedTimeToSeconds(match[0]);
if (startTime !== null) { if (startTime !== null) {
@@ -71,6 +74,18 @@ export function importTimes(data: string, videoDuration: number): SponsorTime[]
result.push(segment); result.push(segment);
} }
} else if (!anyLineHasTime) {
// Adding chapters with placeholder times
const segment: SponsorTime = {
segment: [0, 0],
category: "chapter" as Category,
actionType: ActionType.Chapter,
description: line,
source: SponsorSourceType.Local,
UUID: generateUserID() as SegmentUUID
};
result.push(segment);
} }
} }

View File

@@ -48,11 +48,13 @@ async function fetchSegmentsForVideo(videoID: VideoID): Promise<SegmentResponse>
const extraRequestData: Record<string, unknown> = {}; const extraRequestData: Record<string, unknown> = {};
const hashParams = getHashParams(); const hashParams = getHashParams();
if (hashParams.requiredSegment) extraRequestData.requiredSegment = hashParams.requiredSegment; if (hashParams.requiredSegment) extraRequestData.requiredSegment = hashParams.requiredSegment;
const hashPrefix = (await getHash(videoID, 1)).slice(0, 4) as VideoID & HashedValue; 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, { const response = await asyncRequestToServer('GET', "/api/skipSegments/" + hashPrefix, {
categories: CompileConfig.categoryList, categories: CompileConfig.categoryList,
actionTypes: ActionTypes, actionTypes: ActionTypes,
trimUUIDs: hasDownvotedSegments ? null : 5,
...extraRequestData ...extraRequestData
}, { }, {
"X-CLIENT-NAME": `${chrome.runtime.id}/v${chrome.runtime.getManifest().version}` "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 { isOnInvidious, parseYouTubeVideoIDFromURL } from "../../maze-utils/src/video";
import Config from "../config"; import Config from "../config";
import { getVideoLabel } from "./videoLabels"; import { getHasStartSegment, getVideoLabel } from "./videoLabels";
import { getThumbnailSelector, setThumbnailListener } from "../../maze-utils/src/thumbnailManagement"; import { getThumbnailSelector, setThumbnailListener } from "../../maze-utils/src/thumbnailManagement";
import { VideoID } from "../types"; import { VideoID } from "../types";
import { getSegmentsForVideo } from "./segmentData"; import { getSegmentsForVideo } from "./segmentData";
@@ -58,10 +58,27 @@ function thumbnailHoverListener(e: MouseEvent) {
if (!thumbnail) return; if (!thumbnail) return;
// Pre-fetch data for this video // Pre-fetch data for this video
const videoID = extractVideoID(thumbnail); let fetched = false;
if (videoID) { const preFetch = async () => {
void getSegmentsForVideo(videoID, false); 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 { function getLink(thumbnail: HTMLImageElement): HTMLAnchorElement | null {

View File

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