Compare commits

..

20 Commits

Author SHA1 Message Date
Ajay Ramachandran
32d3487b07 Increase version number 2021-05-19 16:25:32 -04:00
Ajay Ramachandran
3ef2673bfc Remove hash prefix option (always uses hash prefix) 2021-05-19 16:11:07 -04:00
Ajay Ramachandran
ac6cd2cec1 Add new channel ID detection logic 2021-05-19 16:08:09 -04:00
Ajay Ramachandran
995ed929ca Merge pull request #744 from wilkmaciej/master
Skip count sending and counting on manual skip
2021-05-19 11:18:25 -04:00
Ajay Ramachandran
592af4e20f Merge pull request #569 from opl-/cleanup/segment-creation
Clean up segment creation code
2021-05-16 20:48:22 -04:00
Ajay Ramachandran
ecfcb0b846 Make buttons not appear on invidious 2021-05-16 20:45:16 -04:00
Ajay Ramachandran
18d10ada5e Merge branch 'master' of https://github.com/ajayyy/SponsorBlock into pr/opl-/569 2021-05-16 20:29:31 -04:00
Ajay Ramachandran
3a7b6b27c2 Change when close info menu is called 2021-05-16 20:27:46 -04:00
Ajay Ramachandran
fea8f93b5a Semicolon 2021-05-16 18:45:06 -04:00
Ajay Ramachandran
daa7a653c9 Merge pull request #747 from wilkmaciej/fix_saved_time_rounding
fix wrong saved time rounding
2021-05-16 18:43:54 -04:00
Ajay Ramachandran
59f63f1b4b Add semi colon 2021-05-14 21:15:36 -04:00
Ajay Ramachandran
e432abe79d Formatting changes + prevent negative value 2021-05-14 21:15:03 -04:00
Maciej Wilk
08a063b612 if(config.trackViewCount) changed 2021-05-15 01:05:45 +02:00
Maciej Wilk
2d14176542 added .DS_Store to not commit by mistake 2021-05-15 01:02:58 +02:00
Maciej Wilk
5fad4509f0 applied proposed fixes 2021-05-15 00:24:27 +02:00
Maciej Wilk
bd44c4721b Update public/help/index_en.html
Co-authored-by: Ajay Ramachandran <dev@ajay.app>
2021-05-15 00:10:30 +02:00
Maciej Wilk
606b2fbee3 fix wrong saved time rounding 2021-05-14 20:18:02 +02:00
Maciej Wilk
f18aa19172 updated help
changed few confusing names
deleted auto upvote information
fixed minor typos
2021-05-14 19:23:58 +02:00
Maciej Wilk
8337b54a44 telemetry sending and counting on manual skip 2021-05-14 14:32:32 +02:00
opl-
3879cc6de3 Clean up segment creation code
Noteworthy changes:
- Adds ability to cancel creating a segment
- Makes segment creation fully the responsibility of the content script, with the popup script buttons simply doing RPC
- Adds types to the Utils.wait function
- Fixes segment timestamps backwards if user marks segment end earlier than the start
- Makes the info menu (in-page popup) workaround clearer and easier to remove in the future.
2021-02-20 15:52:13 +01:00
14 changed files with 312 additions and 396 deletions

1
.gitignore vendored
View File

@@ -6,3 +6,4 @@ web-ext-artifacts
.vscode/ .vscode/
dist/ dist/
tmp/ tmp/
.DS_Store

View File

@@ -1,7 +1,7 @@
{ {
"name": "__MSG_fullName__", "name": "__MSG_fullName__",
"short_name": "SponsorBlock", "short_name": "SponsorBlock",
"version": "2.0.14.2", "version": "2.0.15",
"default_locale": "en", "default_locale": "en",
"description": "__MSG_Description__", "description": "__MSG_Description__",
"content_scripts": [{ "content_scripts": [{

View File

@@ -79,6 +79,9 @@
"sponsorEnd": { "sponsorEnd": {
"message": "Segment Ends Now" "message": "Segment Ends Now"
}, },
"sponsorCancel": {
"message": "Cancel Creating Segment"
},
"noVideoID": { "noVideoID": {
"message": "No YouTube video found.\nIf this is incorrect, refresh the tab." "message": "No YouTube video found.\nIf this is incorrect, refresh the tab."
}, },
@@ -407,15 +410,6 @@
"areYouSureReset": { "areYouSureReset": {
"message": "Are you sure you would like to reset this?" "message": "Are you sure you would like to reset this?"
}, },
"confirmPrivacy": {
"message": "The video has been detected as unlisted. Click cancel if you do not want to check for skip segments."
},
"unlistedCheck": {
"message": "Ignore Unlisted/Private Videos"
},
"whatUnlistedCheck": {
"message": "This setting will slightly slow down SponsorBlock. Skip segment lookups require sending the video ID to the server. If you are concerned about unlisted video IDs being sent over the internet, enable this option."
},
"mobileUpdateInfo": { "mobileUpdateInfo": {
"message": "m.youtube.com is now supported" "message": "m.youtube.com is now supported"
}, },
@@ -606,9 +600,6 @@
"permissionRequestFailed": { "permissionRequestFailed": {
"message": "Permission request failed, did you click deny?" "message": "Permission request failed, did you click deny?"
}, },
"adblockerIssueUnlistedVideosInfo": {
"message": "If you are unable to resolve this, then disable the setting 'Ignore unlisted/private videos', as SponsorBlock is unable to retrieve the visibility information for this video"
},
"adblockerIssueWhitelist": { "adblockerIssueWhitelist": {
"message": "If you are unable to resolve this, then disable the setting 'Force Channel Check Before Skipping', as SponsorBlock is unable to retrieve the channel information for this video" "message": "If you are unable to resolve this, then disable the setting 'Force Channel Check Before Skipping', as SponsorBlock is unable to retrieve the channel information for this video"
}, },

View File

@@ -79,6 +79,9 @@
"sponsorEnd": { "sponsorEnd": {
"message": "Koniec segmentu" "message": "Koniec segmentu"
}, },
"sponsorCancel": {
"message": "Anuluj tworzenie segmentu"
},
"noVideoID": { "noVideoID": {
"message": "Nie znaleziono filmu YouTube.\nJeżeli to błąd, odśwież stronę." "message": "Nie znaleziono filmu YouTube.\nJeżeli to błąd, odśwież stronę."
}, },

View File

@@ -42,12 +42,12 @@
<img src="https://i.imgur.com/caf5Bju.png"> <img src="https://i.imgur.com/caf5Bju.png">
</span> </span>
Videos will automatically be skipped if they are found in the database. You can open the popup by clicking the extension icon to get a preview of what they are. Video segments will automatically be skipped if they are found in the database. You can open the popup by clicking the extension icon to get a preview of what they are.
<br/> <br/>
<br/> <br/>
Whenever you skip a video, you will get a notice report that submission. If the timing seems wrong, report it! You can also vote in the popup. The extension auto upvotes it if you don't report it, so make sure to report when necessary (this can be disabled in the options). Whenever you skip a segment, you will get notice. If the timing seems wrong vote down by clicking downvote! You can also vote in the popup.
</p> </p>
<div class="center"><img height="120px" src="https://user-images.githubusercontent.com/12688112/63067735-5a638700-bede-11e9-8147-f321b57527ec.gif"></div> <div class="center"><img height="120px" src="https://user-images.githubusercontent.com/12688112/63067735-5a638700-bede-11e9-8147-f321b57527ec.gif"></div>
@@ -81,8 +81,8 @@
<h1>This is too slow</h1> <h1>This is too slow</h1>
<p> <p>
There are hotkeys if you want to use them. You must be focused on the YouTube player to use them. Press the semicolon key to indicate the start/end of a sponsor segment and click the appostrophe to submit. There are hotkeys if you want to use them. You must be focused on the YouTube player to use them. Press the semicolon key to indicate the start/end of a sponsor segment and click the apostrophe to submit.
These can be changed in the options. If you don't use QWERTY, you should probably change the keybinds. These can be changed in the options. If you don't use QWERTY, you should probably change the keybinding.
</p> </p>
<h1>I hate these buttons, they are so ugly</h1> <h1>I hate these buttons, they are so ugly</h1>

View File

@@ -309,23 +309,6 @@
<br/> <br/>
<br/> <br/>
<div option-type="toggle" sync-option="hashPrefix">
<label class="switch-container" label-name="__MSG_enableQueryByHashPrefix__">
<label class="switch">
<input type="checkbox" checked>
<span class="slider round"></span>
</label>
</label>
<br/>
<br/>
<div class="small-description">__MSG_whatQueryByHashPrefix__</div>
</div>
<br/>
<br/>
<div option-type="toggle" sync-option="refetchWhenNotFound"> <div option-type="toggle" sync-option="refetchWhenNotFound">
<label class="switch-container" label-name="__MSG_enableRefetchWhenNotFound__"> <label class="switch-container" label-name="__MSG_enableRefetchWhenNotFound__">
<label class="switch"> <label class="switch">
@@ -343,23 +326,6 @@
<br/> <br/>
<br/> <br/>
<div option-type="toggle" sync-option="checkForUnlistedVideos">
<label class="switch-container" label-name="__MSG_unlistedCheck__">
<label class="switch">
<input type="checkbox">
<span class="slider round"></span>
</label>
</label>
<br/>
<br/>
<div class="small-description">__MSG_whatUnlistedCheck__</div>
</div>
<br/>
<br/>
<div option-type="private-text-change" sync-option="userID" confirm-message="userIDChangeWarning"> <div option-type="private-text-change" sync-option="userID" confirm-message="userIDChangeWarning">
<div class="option-button trigger-button"> <div class="option-button trigger-button">
__MSG_changeUserID__ __MSG_changeUserID__

View File

@@ -71,7 +71,7 @@
</div> </div>
<div id="submissionSection" style="display: none"> <div id="submissionSection" style="display: none">
<b style="display: block; margin-top: 12px;">__MSG_submissionEditHint__</b> <b style="display: block; margin-top: 12px;">__MSG_submissionEditHint__</b>
<div id="submitTimesContainer" style="display: none; margin-top: 12px;"> <div id="submitTimesContainer" style="margin-top: 12px;">
<button id="submitTimes" class="mediumButton">__MSG_submitTimesButton__</button> <button id="submitTimes" class="mediumButton">__MSG_submitTimesButton__</button>
</div> </div>
</div> </div>

View File

@@ -344,7 +344,7 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
//if it is not a complete sponsor time //if it is not a complete sponsor time
if (sponsorTimes[index].segment.length < 2) { if (sponsorTimes[index].segment.length < 2) {
//update video player //update video player
this.props.contentContainer().changeStartSponsorButton(true, false); this.props.contentContainer().updateEditButtonsOnPlayer();
} }
sponsorTimes.splice(index, 1); sponsorTimes.splice(index, 1);
@@ -359,7 +359,7 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
this.props.submissionNotice.cancel(); this.props.submissionNotice.cancel();
//update video player //update video player
this.props.contentContainer().changeStartSponsorButton(true, false); this.props.contentContainer().updateEditButtonsOnPlayer();
} else { } else {
//update display //update display
this.props.submissionNotice.forceUpdate(); this.props.submissionNotice.forceUpdate();

View File

@@ -6,6 +6,7 @@ const utils = new Utils();
interface SBConfig { interface SBConfig {
userID: string, userID: string,
/** Contains unsubmitted segments that the user has created. */
segmentTimes: SBMap<string, SponsorTime[]>, segmentTimes: SBMap<string, SponsorTime[]>,
defaultCategory: string, defaultCategory: string,
whitelistedChannels: string[], whitelistedChannels: string[],
@@ -34,7 +35,6 @@ interface SBConfig {
audioNotificationOnSkip, audioNotificationOnSkip,
checkForUnlistedVideos: boolean, checkForUnlistedVideos: boolean,
testingServer: boolean, testingServer: boolean,
hashPrefix: boolean,
refetchWhenNotFound: boolean, refetchWhenNotFound: boolean,
ytInfoPermissionGranted: boolean, ytInfoPermissionGranted: boolean,
@@ -168,7 +168,6 @@ const Config: SBObject = {
audioNotificationOnSkip: false, audioNotificationOnSkip: false,
checkForUnlistedVideos: false, checkForUnlistedVideos: false,
testingServer: false, testingServer: false,
hashPrefix: true,
refetchWhenNotFound: true, refetchWhenNotFound: true,
ytInfoPermissionGranted: false, ytInfoPermissionGranted: false,

View File

@@ -1,6 +1,6 @@
import Config from "./config"; import Config from "./config";
import { SponsorTime, CategorySkipOption, VideoID, SponsorHideType, FetchResponse, VideoInfo, StorageChangesObject } from "./types"; import { SponsorTime, IncompleteSponsorTime, CategorySkipOption, VideoID, SponsorHideType, FetchResponse, VideoInfo, StorageChangesObject } from "./types";
import { ContentContainer } from "./types"; import { ContentContainer } from "./types";
import Utils from "./utils"; import Utils from "./utils";
@@ -71,8 +71,11 @@ let channelWhitelisted = false;
// create preview bar // create preview bar
let previewBar: PreviewBar = null; let previewBar: PreviewBar = null;
//the player controls on the YouTube player /** Element containing the player controls on the YouTube player. */
let controls = null; let controls: HTMLElement | null = null;
/** Contains buttons created by `createButton()`. */
const playerButtons: Record<string, {button: HTMLButtonElement, image: HTMLImageElement}> = {};
// Direct Links after the config is loaded // Direct Links after the config is loaded
utils.wait(() => Config.config !== null, 1000, 1).then(() => videoIDChange(getYouTubeVideoID(document.URL))); utils.wait(() => Config.config !== null, 1000, 1).then(() => videoIDChange(getYouTubeVideoID(document.URL)));
@@ -81,10 +84,10 @@ utils.wait(() => Config.config !== null, 1000, 1).then(() => videoIDChange(getYo
//this only happens if there is an error //this only happens if there is an error
let sponsorLookupRetries = 0; let sponsorLookupRetries = 0;
//if showing the start sponsor button or the end sponsor button on the player /** Currently timed segment, which will be added to the unsubmitted segments when ready. */
let showingStartSponsor = true; let currentlyTimedSegment: IncompleteSponsorTime | null = null;
//the sponsor times being prepared to be submitted /** Segments created by the user which have not yet been submitted. */
let sponsorTimesSubmitting: SponsorTime[] = []; let sponsorTimesSubmitting: SponsorTime[] = [];
//becomes true when isInfoFound is called //becomes true when isInfoFound is called
@@ -111,12 +114,15 @@ const skipNoticeContentContainer: ContentContainer = () => ({
onMobileYouTube, onMobileYouTube,
sponsorSubmissionNotice: submissionNotice, sponsorSubmissionNotice: submissionNotice,
resetSponsorSubmissionNotice, resetSponsorSubmissionNotice,
changeStartSponsorButton, updateEditButtonsOnPlayer,
previewTime, previewTime,
videoInfo, videoInfo,
getRealCurrentTime: getRealCurrentTime getRealCurrentTime: getRealCurrentTime
}); });
// value determining when to count segment as skipped and send telemetry to server (percent based)
const manualSkipPercentCount = 0.5;
//get messages from the background script and the popup //get messages from the background script and the popup
chrome.runtime.onMessage.addListener(messageListener); chrome.runtime.onMessage.addListener(messageListener);
@@ -127,11 +133,11 @@ function messageListener(request: Message, sender: unknown, sendResponse: (respo
videoIDChange(getYouTubeVideoID(document.URL)); videoIDChange(getYouTubeVideoID(document.URL));
break; break;
case "sponsorStart": case "sponsorStart":
sponsorMessageStarted(sendResponse); startOrEndTimingNewSegment()
break; sendResponse({
case "sponsorDataChanged": creatingSegment: currentlyTimedSegment !== null,
updateSponsorTimesSubmitting(); });
break; break;
case "isInfoFound": case "isInfoFound":
@@ -150,7 +156,8 @@ function messageListener(request: Message, sender: unknown, sendResponse: (respo
break; break;
case "getVideoID": case "getVideoID":
sendResponse({ sendResponse({
videoID: sponsorVideoID videoID: sponsorVideoID,
creatingSegment: currentlyTimedSegment !== null,
}); });
break; break;
@@ -170,10 +177,6 @@ function messageListener(request: Message, sender: unknown, sendResponse: (respo
channelWhitelisted = request.value; channelWhitelisted = request.value;
sponsorsLookup(sponsorVideoID); sponsorsLookup(sponsorVideoID);
break;
case "changeStartSponsorButton":
changeStartSponsorButton(request.showStartSponsor, request.uploadButtonVisible);
break; break;
case "submitTimes": case "submitTimes":
submitSponsorTimes(); submitSponsorTimes();
@@ -250,29 +253,25 @@ async function videoIDChange(id) {
// Wait for options to be ready // Wait for options to be ready
await utils.wait(() => Config.config !== null, 5000, 1); await utils.wait(() => Config.config !== null, 5000, 1);
// If enabled, it will check if this video is private or unlisted and double check with the user if the sponsors should be looked up
if (Config.config.checkForUnlistedVideos) {
const shouldContinue = confirm("SponsorBlock: You have the setting 'Ignore Unlisted/Private Videos' enabled."
+ " Due to a change in how segment fetching works, this setting is not needed anymore as it cannot leak your video ID to the server."
+ " It instead sends just the first 4 characters of a longer hash of the videoID to the server, and filters through a subset of the database."
+ " More info about this implementation can be found here: https://github.com/ajayyy/SponsorBlockServer/issues/25"
+ "\n\nPlease click okay to confirm that you acknowledge this and continue using SponsorBlock.");
if (shouldContinue) {
Config.config.checkForUnlistedVideos = false;
} else {
return;
}
}
// Get new video info // Get new video info
getVideoInfo(); getVideoInfo();
// If enabled, it will check if this video is private or unlisted and double check with the user if the sponsors should be looked up
if (Config.config.checkForUnlistedVideos) {
try {
await utils.wait(() => !!videoInfo, 5000, 1);
} catch (err) {
await videoInfoFetchFailed("adblockerIssueUnlistedVideosInfo");
}
if (isUnlisted()) {
const shouldContinue = confirm(chrome.i18n.getMessage("confirmPrivacy"));
if(!shouldContinue) return;
}
}
// Update whitelist data when the video data is loaded // Update whitelist data when the video data is loaded
utils.wait(() => !!videoInfo, 5000, 10).then(whitelistCheck).catch(() => { whitelistCheck();
if (Config.config.forceChannelCheck) {
videoInfoFetchFailed("adblockerIssueWhitelist");
}
});
//setup the preview bar //setup the preview bar
if (previewBar === null) { if (previewBar === null) {
@@ -301,30 +300,18 @@ async function videoIDChange(id) {
sponsorsLookup(id); sponsorsLookup(id);
//make sure everything is properly added // Make sure all player buttons are properly added
updateVisibilityOfPlayerControlsButton().then(() => {
//see if the onvideo control image needs to be changed
const segments = Config.config.segmentTimes.get(sponsorVideoID);
if (segments != null && segments.length > 0 && segments[segments.length - 1].segment.length >= 2) {
changeStartSponsorButton(true, true);
} else if (segments != null && segments.length > 0 && segments[segments.length - 1].segment.length < 2) {
changeStartSponsorButton(false, true);
} else {
changeStartSponsorButton(true, false);
}
});
//reset sponsor times submitting
sponsorTimesSubmitting = [];
updateSponsorTimesSubmitting();
//see if video controls buttons should be added
if (!onInvidious) {
updateVisibilityOfPlayerControlsButton(); updateVisibilityOfPlayerControlsButton();
}
// Clear unsubmitted segments from the previous video
sponsorTimesSubmitting = [];
currentlyTimedSegment = null;
updateSponsorTimesSubmitting();
} }
function handleMobileControlsMutations(): void { function handleMobileControlsMutations(): void {
updateVisibilityOfPlayerControlsButton();
if (previewBar !== null) { if (previewBar !== null) {
if (document.body.contains(previewBar.container)) { if (document.body.contains(previewBar.container)) {
const progressBarBackground = document.querySelector<HTMLElement>(".progress-bar-background"); const progressBarBackground = document.querySelector<HTMLElement>(".progress-bar-background");
@@ -577,22 +564,12 @@ async function sponsorsLookup(id: string) {
} }
// Check for hashPrefix setting // Check for hashPrefix setting
let getRequest;
if (Config.config.hashPrefix) {
const hashPrefix = (await utils.getHash(id, 1)).substr(0, 4); const hashPrefix = (await utils.getHash(id, 1)).substr(0, 4);
getRequest = utils.asyncRequestToServer('GET', "/api/skipSegments/" + hashPrefix, { utils.asyncRequestToServer('GET', "/api/skipSegments/" + hashPrefix, {
categories categories
}); }).then(async (response: FetchResponse) => {
} else {
getRequest = utils.asyncRequestToServer('GET', "/api/skipSegments", {
videoID: id,
categories
});
}
getRequest.then(async (response: FetchResponse) => {
if (response?.ok) { if (response?.ok) {
let result = JSON.parse(response.responseText); let result = JSON.parse(response.responseText);
if (Config.config.hashPrefix) {
result = result.filter((video) => video.videoID === id); result = result.filter((video) => video.videoID === id);
if (result.length > 0) { if (result.length > 0) {
result = result[0].segments; result = result[0].segments;
@@ -604,7 +581,6 @@ async function sponsorsLookup(id: string) {
retryFetch(id); retryFetch(id);
return; return;
} }
}
const recievedSegments: SponsorTime[] = result; const recievedSegments: SponsorTime[] = result;
if (!recievedSegments.length) { if (!recievedSegments.length) {
@@ -842,8 +818,31 @@ function updatePreviewBar(): void {
} }
//checks if this channel is whitelisted, should be done only after the channelID has been loaded //checks if this channel is whitelisted, should be done only after the channelID has been loaded
function whitelistCheck() { async function whitelistCheck() {
channelID = videoInfo?.videoDetails?.channelId; const whitelistedChannels = Config.config.whitelistedChannels;
const getChannelID = () => videoInfo?.videoDetails?.channelId
?? document.querySelector(".ytd-channel-name a")?.getAttribute("href")?.replace(/\/.+\//, "") // YouTube
?? document.querySelector(".ytp-title-channel-logo")?.getAttribute("href")?.replace(/https:\/.+\//, "") // YouTube Embed
?? document.querySelector("a > .channel-profile")?.parentElement?.getAttribute("href")?.replace(/\/.+\//, ""); // Invidious
try {
await utils.wait(() => !!getChannelID(), 6000, 20);
} catch {
if (Config.config.forceChannelCheck) {
// treat as not whitelisted
channelID = "";
// Don't warn for Invidious embeds
if (!(onInvidious && document.URL.includes("/embed/"))) {
videoInfoFetchFailed("adblockerIssueWhitelist");
}
}
return;
}
channelID = getChannelID();
if (!channelID) { if (!channelID) {
channelID = null; channelID = null;
@@ -851,8 +850,6 @@ function whitelistCheck() {
} }
//see if this is a whitelisted channel //see if this is a whitelisted channel
const whitelistedChannels = Config.config.whitelistedChannels;
if (whitelistedChannels != undefined && whitelistedChannels.includes(channelID)) { if (whitelistedChannels != undefined && whitelistedChannels.includes(channelID)) {
channelWhitelisted = true; channelWhitelisted = true;
} }
@@ -982,6 +979,26 @@ function previewTime(time: number, unpause = true) {
} }
} }
//send telemetry and count skip
function sendTelemetryAndCount(skippingSegments: SponsorTime[], secondsSkipped: number, fullSkip: boolean) {
if (!Config.config.trackViewCount) return;
let counted = false;
for (const segment of skippingSegments) {
const index = sponsorTimes.indexOf(segment);
if (index !== -1 && !sponsorSkipped[index]) {
sponsorSkipped[index] = true;
if (!counted) {
Config.config.minutesSaved = Config.config.minutesSaved + secondsSkipped / 60;
Config.config.skipCount = Config.config.skipCount + 1;
counted = true;
}
if (fullSkip) utils.asyncRequestToServer("POST", "/api/viewedVideoSponsorTime?UUID=" + segment.UUID);
}
}
}
//skip from the start time to the end time for a certain index sponsor time //skip from the start time to the end time for a certain index sponsor time
function skipToTime(v: HTMLVideoElement, skipTime: number[], skippingSegments: SponsorTime[], openNotice: boolean) { function skipToTime(v: HTMLVideoElement, skipTime: number[], skippingSegments: SponsorTime[], openNotice: boolean) {
// There will only be one submission if it is manual skip // There will only be one submission if it is manual skip
@@ -1005,29 +1022,7 @@ function skipToTime(v: HTMLVideoElement, skipTime: number[], skippingSegments: S
} }
//send telemetry that a this sponsor was skipped //send telemetry that a this sponsor was skipped
if (Config.config.trackViewCount && autoSkip) { if (autoSkip) sendTelemetryAndCount(skippingSegments, skipTime[1] - skipTime[0], true);
let alreadySkipped = false;
let isPreviewSegment = false;
for (const segment of skippingSegments) {
const index = sponsorTimes.indexOf(segment);
if (index !== -1 && !sponsorSkipped[index]) {
utils.asyncRequestToServer("POST", "/api/viewedVideoSponsorTime?UUID=" + segment.UUID);
sponsorSkipped[index] = true;
} else if (sponsorSkipped[index]) {
alreadySkipped = true;
}
if (index === -1) isPreviewSegment = true;
}
// Count this as a skip
if (!alreadySkipped && !isPreviewSegment) {
Config.config.minutesSaved = Config.config.minutesSaved + (skipTime[1] - skipTime[0]) / 60;
Config.config.skipCount = Config.config.skipCount + 1;
}
}
} }
function unskipSponsorTime(segment: SponsorTime) { function unskipSponsorTime(segment: SponsorTime) {
@@ -1038,13 +1033,18 @@ function unskipSponsorTime(segment: SponsorTime) {
} }
function reskipSponsorTime(segment: SponsorTime) { function reskipSponsorTime(segment: SponsorTime) {
video.currentTime = segment.segment[1]; const skippedTime = Math.max(segment.segment[1] - video.currentTime, 0);
const segmentDuration = segment.segment[1] - segment.segment[0];
const fullSkip = skippedTime / segmentDuration > manualSkipPercentCount;
video.currentTime = segment.segment[1];
sendTelemetryAndCount([segment], skippedTime, fullSkip);
startSponsorSchedule(true, segment.segment[1], false); startSponsorSchedule(true, segment.segment[1], false);
} }
function createButton(baseID, title, callback, imageName, isDraggable=false): boolean { function createButton(baseID: string, title: string, callback: () => void, imageName: string, isDraggable = false): HTMLElement {
if (document.getElementById(baseID + "Button") != null) return false; const existingElement = document.getElementById(baseID + "Button");
if (existingElement !== null) return existingElement;
// Button HTML // Button HTML
const newButton = document.createElement("button"); const newButton = document.createElement("button");
@@ -1068,9 +1068,15 @@ function createButton(baseID, title, callback, imageName, isDraggable=false): bo
newButton.appendChild(newButtonImage); newButton.appendChild(newButtonImage);
// Add the button to player // Add the button to player
controls.prepend(newButton); if (controls) controls.prepend(newButton);
return true; // Store the elements to prevent unnecessary querying
playerButtons[baseID] = {
button: newButton,
image: newButtonImage,
};
return newButton;
} }
function getControls(): HTMLElement | false { function getControls(): HTMLElement | false {
@@ -1080,8 +1086,8 @@ function getControls(): HTMLElement | false {
// Mobile YouTube // Mobile YouTube
".player-controls-top", ".player-controls-top",
// Invidious/videojs video element's controls element // Invidious/videojs video element's controls element
".vjs-control-bar" ".vjs-control-bar",
] ];
for (const controlsSelector of controlsSelectors) { for (const controlsSelector of controlsSelectors) {
const controls = document.querySelectorAll(controlsSelector); const controls = document.querySelectorAll(controlsSelector);
@@ -1094,53 +1100,75 @@ function getControls(): HTMLElement | false {
return false; return false;
} }
//adds all the player controls buttons /** Creates any missing buttons on the YouTube player if possible. */
async function createButtons(): Promise<boolean> { async function createButtons(): Promise<void> {
if (onMobileYouTube) return; if (onMobileYouTube) return;
const result = await utils.wait(getControls).catch(); controls = await utils.wait(getControls).catch();
//set global controls variable
controls = result;
let createdButton = false;
// Add button if does not already exist in html // Add button if does not already exist in html
createdButton = createButton("startSponsor", "sponsorStart", startSponsorClicked, "PlayerStartIconSponsorBlocker256px.png") || createdButton; createButton("startSponsor", "sponsorStart", () => closeInfoMenuAnd(() => startOrEndTimingNewSegment()), "PlayerStartIconSponsorBlocker256px.png");
createdButton = createButton("info", "openPopup", openInfoMenu, "PlayerInfoIconSponsorBlocker256px.png") || createdButton; createButton("cancelSponsor", "sponsorCancel", () => closeInfoMenuAnd(() => cancelCreatingSegment()), "PlayerUploadFailedIconSponsorBlocker256px.png");
createdButton = createButton("delete", "clearTimes", clearSponsorTimes, "PlayerDeleteIconSponsorBlocker256px.png") || createdButton; createButton("info", "openPopup", openInfoMenu, "PlayerInfoIconSponsorBlocker256px.png");
createdButton = createButton("submit", "SubmitTimes", submitSponsorTimes, "PlayerUploadIconSponsorBlocker256px.png") || createdButton; createButton("delete", "clearTimes", () => closeInfoMenuAnd(() => clearSponsorTimes()), "PlayerDeleteIconSponsorBlocker256px.png");
createButton("submit", "SubmitTimes", submitSponsorTimes, "PlayerUploadIconSponsorBlocker256px.png");
return createdButton;
} }
//adds or removes the player controls button to what it should be /** Creates any missing buttons on the player and updates their visiblity. */
async function updateVisibilityOfPlayerControlsButton(): Promise<boolean> { async function updateVisibilityOfPlayerControlsButton(): Promise<void> {
//not on a proper video yet // Not on a proper video yet
if (!sponsorVideoID) return false; if (!sponsorVideoID) return;
const createdButtons = await createButtons(); await createButtons();
if (!createdButtons) return;
if (Config.config.hideVideoPlayerControls || onInvidious) { updateEditButtonsOnPlayer();
document.getElementById("startSponsorButton").style.display = "none";
document.getElementById("submitButton").style.display = "none";
} else {
document.getElementById("startSponsorButton").style.removeProperty("display");
}
//don't show the info button on embeds // Don't show the info button on embeds
if (Config.config.hideInfoButtonPlayerControls || document.URL.includes("/embed/") || onInvidious) { if (Config.config.hideInfoButtonPlayerControls || document.URL.includes("/embed/") || onInvidious) {
document.getElementById("infoButton").style.display = "none"; playerButtons.info.button.style.display = "none";
} else { } else {
document.getElementById("infoButton").style.removeProperty("display"); playerButtons.info.button.style.removeProperty("display");
}
} }
if (Config.config.hideDeleteButtonPlayerControls || onInvidious) { /** Updates the visibility of buttons on the player related to creating segments. */
document.getElementById("deleteButton").style.display = "none"; function updateEditButtonsOnPlayer(): void {
// Don't try to update the buttons if we aren't on a YouTube video page
if (!sponsorVideoID) return;
const buttonsEnabled = !Config.config.hideVideoPlayerControls && !onInvidious;
let creatingSegment = false;
let submitButtonVisible = false;
let deleteButtonVisible = false;
// Only check if buttons should be visible if they're enabled
if (buttonsEnabled) {
creatingSegment = currentlyTimedSegment !== null;
// Show only if there are any segments to submit
submitButtonVisible = sponsorTimesSubmitting.length > 0;
// Show only if there are any segments to delete
deleteButtonVisible = sponsorTimesSubmitting.length > 0;
} }
return createdButtons; // Update the elements
playerButtons.startSponsor.button.style.display = buttonsEnabled ? "unset" : "none";
playerButtons.cancelSponsor.button.style.display = buttonsEnabled && creatingSegment ? "unset" : "none";
if (buttonsEnabled) {
if (creatingSegment) {
playerButtons.startSponsor.image.src = chrome.extension.getURL("icons/PlayerStopIconSponsorBlocker256px.png");
playerButtons.startSponsor.button.setAttribute("title", chrome.i18n.getMessage("sponsorEnd"));
} else {
playerButtons.startSponsor.image.src = chrome.extension.getURL("icons/PlayerStartIconSponsorBlocker256px.png");
playerButtons.startSponsor.button.setAttribute("title", chrome.i18n.getMessage("sponsorStart"));
}
}
playerButtons.submit.button.style.display = submitButtonVisible && !Config.config.hideUploadButtonPlayerControls ? "unset" : "none";
playerButtons.delete.button.style.display = deleteButtonVisible && !Config.config.hideDeleteButtonPlayerControls ? "unset" : "none";
} }
/** /**
@@ -1161,30 +1189,40 @@ function getRealCurrentTime(): number {
} }
} }
function startSponsorClicked() { function startOrEndTimingNewSegment() {
//it can't update to this info yet if (!currentlyTimedSegment) {
closeInfoMenu(); // Start a new segment
currentlyTimedSegment = {
toggleStartSponsorButton();
//add to sponsorTimes
if (sponsorTimesSubmitting.length > 0 && sponsorTimesSubmitting[sponsorTimesSubmitting.length - 1].segment.length < 2) {
//it is an end time
sponsorTimesSubmitting[sponsorTimesSubmitting.length - 1].segment[1] = getRealCurrentTime();
sponsorTimesSubmitting[sponsorTimesSubmitting.length - 1].segment.sort((a, b) => a > b ? 1 : (a < b ? -1 : 0));
} else {
//it is a start time
sponsorTimesSubmitting.push({
segment: [getRealCurrentTime()], segment: [getRealCurrentTime()],
UUID: null, UUID: null,
category: Config.config.defaultCategory category: Config.config.defaultCategory,
};
} else {
// Finish creating the new segment
const existingTime = currentlyTimedSegment.segment[0];
const currentTime = getRealCurrentTime();
sponsorTimesSubmitting.push({
...currentlyTimedSegment,
// Swap timestamps if the user put the segment end before the start
segment: [Math.min(existingTime, currentTime), Math.max(existingTime, currentTime)],
}); });
currentlyTimedSegment = null;
// Save the newly created segment
Config.config.segmentTimes.set(sponsorVideoID, sponsorTimesSubmitting);
} }
//save this info updateEditButtonsOnPlayer();
Config.config.segmentTimes.set(sponsorVideoID, sponsorTimesSubmitting);
updateSponsorTimesSubmitting(false) updateSponsorTimesSubmitting(false);
}
function cancelCreatingSegment() {
currentlyTimedSegment = null;
updateEditButtonsOnPlayer();
} }
function updateSponsorTimesSubmitting(getFromConfig = true) { function updateSponsorTimesSubmitting(getFromConfig = true) {
@@ -1213,38 +1251,6 @@ function updateSponsorTimesSubmitting(getFromConfig = true) {
} }
} }
async function changeStartSponsorButton(showStartSponsor: boolean, uploadButtonVisible: boolean): Promise<boolean> {
if(!sponsorVideoID || onMobileYouTube) return false;
//if it isn't visible, there is no data
const shouldHide = (uploadButtonVisible && !(Config.config.hideDeleteButtonPlayerControls || onInvidious)) ? "unset" : "none"
document.getElementById("deleteButton").style.display = shouldHide;
if (showStartSponsor) {
showingStartSponsor = true;
(<HTMLImageElement> document.getElementById("startSponsorImage")).src = chrome.extension.getURL("icons/PlayerStartIconSponsorBlocker256px.png");
document.getElementById("startSponsorButton").setAttribute("title", chrome.i18n.getMessage("sponsorStart"));
if (document.getElementById("startSponsorImage").style.display != "none" && uploadButtonVisible && !Config.config.hideUploadButtonPlayerControls && !onInvidious) {
document.getElementById("submitButton").style.display = "unset";
} else if (!uploadButtonVisible || onInvidious) {
//disable submit button
document.getElementById("submitButton").style.display = "none";
}
} else {
showingStartSponsor = false;
(<HTMLImageElement> document.getElementById("startSponsorImage")).src = chrome.extension.getURL("icons/PlayerStopIconSponsorBlocker256px.png");
document.getElementById("startSponsorButton").setAttribute("title", chrome.i18n.getMessage("sponsorEND"));
//disable submit button
document.getElementById("submitButton").style.display = "none";
}
}
function toggleStartSponsorButton() {
changeStartSponsorButton(!showingStartSponsor, true);
}
function openInfoMenu() { function openInfoMenu() {
if (document.getElementById("sponsorBlockPopupContainer") != null) { if (document.getElementById("sponsorBlockPopupContainer") != null) {
//it's already added //it's already added
@@ -1254,7 +1260,7 @@ function openInfoMenu() {
popupInitialised = false; popupInitialised = false;
//hide info button //hide info button
document.getElementById("infoButton").style.display = "none"; if (playerButtons.info) playerButtons.info.button.style.display = "none";
sendRequestToCustomServer('GET', chrome.extension.getURL("popup.html"), function(xmlhttp) { sendRequestToCustomServer('GET', chrome.extension.getURL("popup.html"), function(xmlhttp) {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) { if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
@@ -1316,20 +1322,28 @@ function openInfoMenu() {
function closeInfoMenu() { function closeInfoMenu() {
const popup = document.getElementById("sponsorBlockPopupContainer"); const popup = document.getElementById("sponsorBlockPopupContainer");
if (popup != null) { if (popup === null) return;
popup.remove(); popup.remove();
//show info button if it's not an embed // Show info button if it's not an embed
if (!document.URL.includes("/embed/")) { if (!document.URL.includes("/embed/") && playerButtons.info) {
document.getElementById("infoButton").style.display = "unset"; playerButtons.info.button.style.display = "unset";
} }
} }
/**
* The content script currently has no way to notify the info menu of changes. As a workaround we close it, thus making it query the new information when reopened.
*
* This function and all its uses should be removed when this issue is fixed.
* */
function closeInfoMenuAnd<T>(func: () => T): T {
closeInfoMenu();
return func();
} }
function clearSponsorTimes() { function clearSponsorTimes() {
//it can't update to this info yet
closeInfoMenu();
const currentVideoID = sponsorVideoID; const currentVideoID = sponsorVideoID;
const sponsorTimes = Config.config.segmentTimes.get(currentVideoID); const sponsorTimes = Config.config.segmentTimes.get(currentVideoID);
@@ -1347,8 +1361,7 @@ function clearSponsorTimes() {
updatePreviewBar(); updatePreviewBar();
//set buttons to be correct updateEditButtonsOnPlayer();
changeStartSponsorButton(true, false);
} }
} }
@@ -1414,18 +1427,6 @@ function dontShowNoticeAgain() {
closeAllSkipNotices(); closeAllSkipNotices();
} }
function sponsorMessageStarted(callback: (response: MessageResponse) => void) {
video = document.querySelector('video');
//send back current time
callback({
time: video.currentTime
})
//update button
toggleStartSponsorButton();
}
/** /**
* Helper method for the submission notice to clear itself when it closes * Helper method for the submission notice to clear itself when it closes
*/ */
@@ -1436,9 +1437,6 @@ function resetSponsorSubmissionNotice() {
function submitSponsorTimes() { function submitSponsorTimes() {
if (submissionNotice !== null) return; if (submissionNotice !== null) return;
//it can't update to this info yet
closeInfoMenu();
if (sponsorTimesSubmitting !== undefined && sponsorTimesSubmitting.length > 0) { if (sponsorTimesSubmitting !== undefined && sponsorTimesSubmitting.length > 0) {
submissionNotice = new SubmissionNotice(skipNoticeContentContainer, sendSubmitMessage); submissionNotice = new SubmissionNotice(skipNoticeContentContainer, sendSubmitMessage);
} }
@@ -1447,10 +1445,10 @@ function submitSponsorTimes() {
//send the message to the background js //send the message to the background js
//called after all the checks have been made that it's okay to do so //called after all the checks have been made that it's okay to do so
async function sendSubmitMessage(): Promise<void> { async function sendSubmitMessage() {
//add loading animation // Add loading animation
(<HTMLImageElement> document.getElementById("submitImage")).src = chrome.extension.getURL("icons/PlayerUploadIconSponsorBlocker256px.png"); playerButtons.submit.image.src = chrome.extension.getURL("icons/PlayerUploadIconSponsorBlocker256px.png");
document.getElementById("submitButton").style.animation = "rotate 1s 0s infinite"; playerButtons.submit.button.style.animation = "rotate 1s 0s infinite";
//check if a sponsor exceeds the duration of the video //check if a sponsor exceeds the duration of the video
for (let i = 0; i < sponsorTimesSubmitting.length; i++) { for (let i = 0; i < sponsorTimesSubmitting.length; i++) {
@@ -1477,17 +1475,19 @@ async function sendSubmitMessage(): Promise<void> {
const response = await utils.asyncRequestToServer("POST", "/api/skipSegments", { const response = await utils.asyncRequestToServer("POST", "/api/skipSegments", {
videoID: sponsorVideoID, videoID: sponsorVideoID,
userID: Config.config.userID, userID: Config.config.userID,
segments: sponsorTimesSubmitting segments: sponsorTimesSubmitting,
}); });
if (response.status === 200) { if (response.status === 200) {
//hide loading message // Handle submission success
const submitButton = document.getElementById("submitButton"); const submitButton = playerButtons.submit.button;
// Make the animation finite
submitButton.style.animation = "rotate 1s"; submitButton.style.animation = "rotate 1s";
//finish this animation
//when the animation is over, hide the button // When the animation is over, hide the button
const animationEndListener = function() { const animationEndListener = () => {
changeStartSponsorButton(true, false); updateEditButtonsOnPlayer();
submitButton.style.animation = "none"; submitButton.style.animation = "none";
@@ -1496,13 +1496,12 @@ async function sendSubmitMessage(): Promise<void> {
submitButton.addEventListener("animationend", animationEndListener); submitButton.addEventListener("animationend", animationEndListener);
//clear the sponsor times // Remove segments from storage since they've already been submitted
Config.config.segmentTimes.delete(sponsorVideoID); Config.config.segmentTimes.delete(sponsorVideoID);
//add submissions to current sponsors list // Add submissions to current sponsors list
if (sponsorTimes === null) sponsorTimes = []; // FIXME: segments from sponsorTimesSubmitting do not contain UUIDs .-.
sponsorTimes = (sponsorTimes || []).concat(sponsorTimesSubmitting);
sponsorTimes = sponsorTimes.concat(sponsorTimesSubmitting);
// Increase contribution count // Increase contribution count
Config.config.sponsorTimesContributed = Config.config.sponsorTimesContributed + sponsorTimesSubmitting.length; Config.config.sponsorTimesContributed = Config.config.sponsorTimesContributed + sponsorTimesSubmitting.length;
@@ -1512,13 +1511,14 @@ async function sendSubmitMessage(): Promise<void> {
Config.config.submissionCountSinceCategories = Config.config.submissionCountSinceCategories + 1; Config.config.submissionCountSinceCategories = Config.config.submissionCountSinceCategories + 1;
// Empty the submitting times // Empty the submitting times
currentlyTimedSegment = null;
sponsorTimesSubmitting = []; sponsorTimesSubmitting = [];
updatePreviewBar(); updatePreviewBar();
} else { } else {
//show that the upload failed // Show that the upload failed
document.getElementById("submitButton").style.animation = "unset"; playerButtons.submit.button.style.animation = "unset";
(<HTMLImageElement> document.getElementById("submitImage")).src = chrome.extension.getURL("icons/PlayerUploadFailedIconSponsorBlocker256px.png"); playerButtons.submit.image.src = chrome.extension.getURL("icons/PlayerUploadFailedIconSponsorBlocker256px.png");
alert(utils.getErrorMessage(response.status, response.responseText)); alert(utils.getErrorMessage(response.status, response.responseText));
} }
@@ -1575,7 +1575,7 @@ function hotkeyListener(e: KeyboardEvent): void {
} }
break; break;
case startSponsorKey: case startSponsorKey:
startSponsorClicked(); startOrEndTimingNewSegment();
break; break;
case submitKey: case submitKey:
submitSponsorTimes(); submitSponsorTimes();
@@ -1583,14 +1583,6 @@ function hotkeyListener(e: KeyboardEvent): void {
} }
} }
/**
* Is this an unlisted YouTube video.
* Assumes that the the privacy info is available.
*/
function isUnlisted(): boolean {
return videoInfo?.microformat?.playerMicroformatRenderer?.isUnlisted || videoInfo?.videoDetails?.isPrivate;
}
/** /**
* Adds the CSS to the page if needed. Required on optional sites with Chrome. * Adds the CSS to the page if needed. Required on optional sites with Chrome.
*/ */

View File

@@ -12,7 +12,6 @@ interface DefaultMessage {
message: message:
"update" "update"
| "sponsorStart" | "sponsorStart"
| "sponsorDataChanged"
| "isInfoFound" | "isInfoFound"
| "getVideoID" | "getVideoID"
| "getChannelID" | "getChannelID"
@@ -25,13 +24,7 @@ interface BoolValueMessage {
value: boolean; value: boolean;
} }
interface ChangeStartSponsorButtonMessage { export type Message = BaseMessage & (DefaultMessage | BoolValueMessage);
message: "changeStartSponsorButton";
showStartSponsor: boolean;
uploadButtonVisible: boolean;
}
export type Message = BaseMessage & (DefaultMessage | BoolValueMessage | ChangeStartSponsorButtonMessage);
interface IsInfoFoundMessageResponse { interface IsInfoFoundMessageResponse {
found: boolean; found: boolean;
@@ -47,7 +40,7 @@ interface GetChannelIDResponse {
} }
interface SponsorStartResponse { interface SponsorStartResponse {
time: number; creatingSegment: boolean;
} }
interface IsChannelWhitelistedResponse { interface IsChannelWhitelistedResponse {

View File

@@ -126,8 +126,8 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
PageElements.optionsButton.addEventListener("click", openOptions); PageElements.optionsButton.addEventListener("click", openOptions);
PageElements.helpButton.addEventListener("click", openHelp); PageElements.helpButton.addEventListener("click", openHelp);
//if true, the button now selects the end time /** If true, the content script is in the process of creating a new segment. */
let startTimeChosen = false; let creatingSegment = false;
//the start and end time pairs (2d) //the start and end time pairs (2d)
let sponsorTimes: SponsorTime[] = []; let sponsorTimes: SponsorTime[] = [];
@@ -233,10 +233,12 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
function onTabs(tabs) { function onTabs(tabs) {
messageHandler.sendMessage(tabs[0].id, {message: 'getVideoID'}, function(result) { messageHandler.sendMessage(tabs[0].id, {message: 'getVideoID'}, function(result) {
if (result != undefined && result.videoID) { if (result !== undefined && result.videoID) {
currentVideoID = result.videoID; currentVideoID = result.videoID;
creatingSegment = result.creatingSegment;
loadTabData(tabs); loadTabData(tabs);
} else if (result == undefined && chrome.runtime.lastError) { } else if (result === undefined && chrome.runtime.lastError) {
//this isn't a YouTube video then, or at least the content script is not loaded //this isn't a YouTube video then, or at least the content script is not loaded
displayNoVideo(); displayNoVideo();
} }
@@ -253,19 +255,11 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
//load video times for this video //load video times for this video
const sponsorTimesStorage = Config.config.segmentTimes.get(currentVideoID); const sponsorTimesStorage = Config.config.segmentTimes.get(currentVideoID);
if (sponsorTimesStorage != undefined && sponsorTimesStorage.length > 0) { if (sponsorTimesStorage != undefined && sponsorTimesStorage.length > 0) {
if (sponsorTimesStorage[sponsorTimesStorage.length - 1] != undefined && sponsorTimesStorage[sponsorTimesStorage.length - 1].segment.length < 2) {
startTimeChosen = true;
PageElements.sponsorStart.innerHTML = chrome.i18n.getMessage("sponsorEnd");
}
sponsorTimes = sponsorTimesStorage; sponsorTimes = sponsorTimesStorage;
//show submission section
PageElements.submissionSection.style.display = "unset";
showSubmitTimesIfNecessary();
} }
updateSegmentEditingUI();
//check if this video's sponsors are known //check if this video's sponsors are known
messageHandler.sendMessage( messageHandler.sendMessage(
tabs[0].id, tabs[0].id,
@@ -321,51 +315,44 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
//the content script will get the message if a YouTube page is open //the content script will get the message if a YouTube page is open
messageHandler.query({ messageHandler.query({
active: true, active: true,
currentWindow: true currentWindow: true,
}, tabs => { }, (tabs) => {
messageHandler.sendMessage( messageHandler.sendMessage(
tabs[0].id, tabs[0].id,
{from: 'popup', message: 'sponsorStart'}, {from: 'popup', message: 'sponsorStart'},
startSponsorCallback async (response) => {
); startSponsorCallback(response);
});
}
function startSponsorCallback(response) { // Perform a second update after the config changes take effect as a workaround for a race condition
const sponsorTimesIndex = sponsorTimes.length - (startTimeChosen ? 1 : 0); const removeListener = (listener: typeof lateUpdate) => {
const index = Config.configListeners.indexOf(listener);
if (sponsorTimes[sponsorTimesIndex] == undefined) { if (index !== -1) Config.configListeners.splice(index, 1);
sponsorTimes[sponsorTimesIndex] = {
segment: [],
category: Config.config.defaultCategory,
UUID: null
}; };
}
sponsorTimes[sponsorTimesIndex].segment[startTimeChosen ? 1 : 0] = response.time; const lateUpdate = () => {
startSponsorCallback(response);
removeListener(lateUpdate);
};
const localStartTimeChosen = startTimeChosen; Config.configListeners.push(lateUpdate);
Config.config.segmentTimes.set(currentVideoID, sponsorTimes);
//send a message to the client script // Remove the listener after 200ms in case the changes were propagated by the time we got the response
if (localStartTimeChosen) { setTimeout(() => removeListener(lateUpdate), 200);
messageHandler.query({ },
active: true,
currentWindow: true
}, tabs => {
messageHandler.sendMessage(
tabs[0].id,
{message: "sponsorDataChanged"}
); );
}); });
} }
updateStartTimeChosen(); function startSponsorCallback(response: {creatingSegment: boolean}) {
creatingSegment = response.creatingSegment;
//show submission section // Only update the segments after a segment was created
PageElements.submissionSection.style.display = "unset"; if (!creatingSegment) {
sponsorTimes = Config.config.segmentTimes.get(currentVideoID) || [];
}
showSubmitTimesIfNecessary(); // Update the UI
updateSegmentEditingUI();
} }
//display the video times from the array at the top, in a different section //display the video times from the array at the top, in a different section
@@ -484,32 +471,11 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
PageElements.showNoticeAgain.style.display = "none"; PageElements.showNoticeAgain.style.display = "none";
} }
function updateStartTimeChosen() { /** Updates any UI related to segment editing and submission according to the current state. */
//update startTimeChosen letiable function updateSegmentEditingUI() {
if (!startTimeChosen) { PageElements.sponsorStart.innerText = chrome.i18n.getMessage(creatingSegment ? "sponsorEnd" : "sponsorStart");
startTimeChosen = true;
PageElements.sponsorStart.innerHTML = chrome.i18n.getMessage("sponsorEnd");
} else {
resetStartTimeChosen();
}
}
//set it to false PageElements.submissionSection.style.display = sponsorTimes && sponsorTimes.length > 0 ? "unset" : "none";
function resetStartTimeChosen() {
startTimeChosen = false;
PageElements.sponsorStart.innerHTML = chrome.i18n.getMessage("sponsorStart");
}
//hides and shows the submit times button when needed
function showSubmitTimesIfNecessary() {
//check if an end time has been specified for the latest sponsor time
if (sponsorTimes.length > 0 && sponsorTimes[sponsorTimes.length - 1].segment.length > 1) {
//show submit times button
document.getElementById("submitTimesContainer").style.display = "flex";
} else {
//hide submit times button
document.getElementById("submitTimesContainer").style.display = "none";
}
} }
//make the options div visible //make the options div visible
@@ -726,9 +692,10 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
* @param {float} seconds * @param {float} seconds
* @returns {string} * @returns {string}
*/ */
function getFormattedHours(minues) { function getFormattedHours(minutes) {
const hours = Math.floor(minues / 60); minutes = Math.round(minutes * 10) / 10
return (hours > 0 ? hours + "h " : "") + (minues % 60).toFixed(1); const hours = Math.floor(minutes / 60);
return (hours > 0 ? hours + "h " : "") + (minutes % 60).toFixed(1);
} }
//end of function //end of function

View File

@@ -17,7 +17,7 @@ export interface ContentContainer {
onMobileYouTube: boolean, onMobileYouTube: boolean,
sponsorSubmissionNotice: SubmissionNotice, sponsorSubmissionNotice: SubmissionNotice,
resetSponsorSubmissionNotice: () => void, resetSponsorSubmissionNotice: () => void,
changeStartSponsorButton: (showStartSponsor: boolean, uploadButtonVisible: boolean) => Promise<boolean>, updateEditButtonsOnPlayer: () => void,
previewTime: (time: number, unpause?: boolean) => void, previewTime: (time: number, unpause?: boolean) => void,
videoInfo: VideoInfo, videoInfo: VideoInfo,
getRealCurrentTime: () => number getRealCurrentTime: () => number
@@ -60,6 +60,10 @@ export interface SponsorTime {
hidden?: SponsorHideType; hidden?: SponsorHideType;
} }
export type IncompleteSponsorTime = Omit<SponsorTime, 'segment'> & {
segment: [number];
};
export interface PreviewBarOption { export interface PreviewBarOption {
color: string, color: string,
opacity: string opacity: string

View File

@@ -23,8 +23,8 @@ export default class Utils {
this.backgroundScriptContainer = backgroundScriptContainer; this.backgroundScriptContainer = backgroundScriptContainer;
} }
// Function that can be used to wait for a condition before returning /** Function that can be used to wait for a condition before returning. */
async wait(condition: () => HTMLElement | boolean, timeout = 5000, check = 100): Promise<HTMLElement | boolean> { async wait<T>(condition: () => T | false, timeout = 5000, check = 100): Promise<T> {
return await new Promise((resolve, reject) => { return await new Promise((resolve, reject) => {
setTimeout(() => reject("TIMEOUT"), timeout); setTimeout(() => reject("TIMEOUT"), timeout);