Merge branch 'master' of https://github.com/ajayyy/SponsorBlock into chapters

This commit is contained in:
Ajay
2022-05-17 14:44:54 -04:00
49 changed files with 8210 additions and 2232 deletions

View File

@@ -15,10 +15,17 @@ import PencilSvg from "../svg-icons/pencil_svg";
import { downvoteButtonColor, SkipNoticeAction } from "../utils/noticeUtils";
import { GenericUtils } from "../utils/genericUtils";
enum SkipButtonState {
Undo, // Unskip
Redo, // Reskip
Start // Skip
}
export interface SkipNoticeProps {
segments: SponsorTime[];
autoSkip: boolean;
startReskip?: boolean;
// Contains functions and variables from the content script needed by the skip notice
contentContainer: ContentContainer;
@@ -39,9 +46,9 @@ export interface SkipNoticeState {
maxCountdownTime?: () => number;
countdownText?: string;
skipButtonText?: string;
skipButtonCallback?: (index: number) => void;
showSkipButton?: boolean;
skipButtonStates?: SkipButtonState[];
skipButtonCallbacks?: Array<(buttonIndex: number, index: number, forceSeek: boolean) => void>;
showSkipButton?: boolean[];
editing?: boolean;
choosingCategory?: boolean;
@@ -110,6 +117,15 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
this.unselectedColor = Config.config.colorPalette.white;
this.lockedColor = Config.config.colorPalette.locked;
const isMuteSegment = this.segments[0].actionType === ActionType.Mute;
const maxCountdownTime = isMuteSegment ? this.getFullDurationCountdown(0) : () => Config.config.skipNoticeDuration;
const defaultSkipButtonState = this.props.startReskip ? SkipButtonState.Redo : SkipButtonState.Undo;
const skipButtonStates = [defaultSkipButtonState, isMuteSegment ? SkipButtonState.Start : defaultSkipButtonState];
const defaultSkipButtonCallback = this.props.startReskip ? this.reskip.bind(this) : this.unskip.bind(this);
const skipButtonCallbacks = [defaultSkipButtonCallback, isMuteSegment ? this.reskip.bind(this) : defaultSkipButtonCallback];
// Setup state
this.state = {
noticeTitle,
@@ -117,13 +133,13 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
messageOnClick: null,
//the countdown until this notice closes
maxCountdownTime: () => Config.config.skipNoticeDuration,
countdownTime: Config.config.skipNoticeDuration,
maxCountdownTime,
countdownTime: maxCountdownTime(),
countdownText: null,
skipButtonText: this.getUnskipText(),
skipButtonCallback: (index) => this.unskip(index),
showSkipButton: true,
skipButtonStates,
skipButtonCallbacks,
showSkipButton: [true, true],
editing: false,
choosingCategory: false,
@@ -142,7 +158,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
if (!this.autoSkip) {
// Assume manual skip is only skipping 1 submission
Object.assign(this.state, this.getUnskippedModeInfo(0, this.getSkipText()));
Object.assign(this.state, this.getUnskippedModeInfo(null, 0, SkipButtonState.Start));
}
}
@@ -155,8 +171,9 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
// If it started out as smaller, always keep the
// skip button there
const firstColumn = this.props.smaller ? (
this.getSkipButton()
const showFirstSkipButton = this.props.smaller || this.segments[0].actionType === ActionType.Mute;
const firstColumn = showFirstSkipButton ? (
this.getSkipButton(0)
) : null;
return (
@@ -248,10 +265,11 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
}
{/* Unskip/Skip Button */}
{!this.props.smaller ? this.getSkipButton() : null}
{!this.props.smaller || this.segments[0].actionType === ActionType.Mute
? this.getSkipButton(1) : null}
{/* Never show button if autoSkip is enabled */}
{!this.autoSkip ? "" :
{/* Never show button */}
{!this.autoSkip || this.props.startReskip ? "" :
<td className="sponsorSkipNoticeRightSection"
key={1}>
<button className="sponsorSkipObject sponsorSkipNoticeButton sponsorSkipNoticeRightButton"
@@ -325,14 +343,17 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
];
}
getSkipButton(): JSX.Element {
if (this.state.showSkipButton && (this.segments.length > 1
getSkipButton(buttonIndex: number): JSX.Element {
if (this.state.showSkipButton[buttonIndex] && (this.segments.length > 1
|| this.segments[0].actionType !== ActionType.Poi
|| this.props.unskipTime)) {
const forceSeek = buttonIndex === 1 && this.segments[0].actionType === ActionType.Mute;
const style: React.CSSProperties = {
marginLeft: "4px",
color: (this.state.actionState === SkipNoticeAction.Unskip) ? this.selectedColor : this.unselectedColor
color: ([SkipNoticeAction.Unskip0, SkipNoticeAction.Unskip1].includes(this.state.actionState))
? this.selectedColor : this.unselectedColor
};
if (this.contentContainer().onMobileYouTube) {
style.padding = "20px";
@@ -344,8 +365,10 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
<button id={"sponsorSkipUnskipButton" + this.idSuffix}
className="sponsorSkipObject sponsorSkipNoticeButton"
style={style}
onClick={() => this.prepAction(SkipNoticeAction.Unskip)}>
{this.state.skipButtonText + (this.state.showKeybindHint ? " (" + keybindToString(Config.config.skipKeybind) + ")" : "")}
onClick={() => this.prepAction(buttonIndex === 1 ? SkipNoticeAction.Unskip1 : SkipNoticeAction.Unskip0)}>
{this.getSkipButtonText(buttonIndex, forceSeek ? ActionType.Skip : null)
+ (!forceSeek && this.state.showKeybindHint
? " (" + keybindToString(Config.config.skipKeybind) + ")" : "")}
</button>
</span>
);
@@ -446,8 +469,11 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
case SkipNoticeAction.CopyDownvote:
this.resetStateToStart(SkipNoticeAction.CopyDownvote, true);
break;
case SkipNoticeAction.Unskip:
this.resetStateToStart(SkipNoticeAction.Unskip);
case SkipNoticeAction.Unskip0:
this.resetStateToStart(SkipNoticeAction.Unskip0);
break;
case SkipNoticeAction.Unskip1:
this.resetStateToStart(SkipNoticeAction.Unskip1);
break;
}
}
@@ -475,8 +501,11 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
case SkipNoticeAction.CopyDownvote:
this.copyDownvote(index);
break;
case SkipNoticeAction.Unskip:
this.unskipAction(index);
case SkipNoticeAction.Unskip0:
this.unskipAction(0, index, false);
break;
case SkipNoticeAction.Unskip1:
this.unskipAction(1, index, true);
break;
default:
this.resetStateToStart();
@@ -538,8 +567,8 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
});
}
unskipAction(index: number): void {
this.state.skipButtonCallback(index);
unskipAction(buttonIndex: number, index: number, forceSeek: boolean): void {
this.state.skipButtonCallbacks[buttonIndex](buttonIndex, index, forceSeek);
}
openEditingOptions(): void {
@@ -566,18 +595,24 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
return this.props.contentContainer().lockedCategories.includes(category) ? "sponsorBlockLockedColor" : ""
}
unskip(index: number): void {
this.contentContainer().unskipSponsorTime(this.segments[index], this.props.unskipTime);
unskip(buttonIndex: number, index: number, forceSeek: boolean): void {
this.contentContainer().unskipSponsorTime(this.segments[index], this.props.unskipTime, forceSeek);
this.unskippedMode(index, this.getReskipText());
this.unskippedMode(buttonIndex, index, SkipButtonState.Redo);
}
reskip(index: number): void {
this.contentContainer().reskipSponsorTime(this.segments[index]);
reskip(buttonIndex: number, index: number, forceSeek: boolean): void {
this.contentContainer().reskipSponsorTime(this.segments[index], forceSeek);
const skipButtonStates = this.state.skipButtonStates;
skipButtonStates[buttonIndex] = SkipButtonState.Undo;
const skipButtonCallbacks = this.state.skipButtonCallbacks;
skipButtonCallbacks[buttonIndex] = this.unskip.bind(this);
const newState: SkipNoticeState = {
skipButtonText: this.getUnskipText(),
skipButtonCallback: this.unskip.bind(this),
skipButtonStates,
skipButtonCallbacks,
maxCountdownTime: () => Config.config.skipNoticeDuration,
countdownTime: Config.config.skipNoticeDuration
@@ -595,30 +630,54 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
}
/** Sets up notice to be not skipped yet */
unskippedMode(index: number, buttonText: string): void {
unskippedMode(buttonIndex: number, index: number, skipButtonState: SkipButtonState): void {
//setup new callback and reset countdown
this.setState(this.getUnskippedModeInfo(index, buttonText), () => {
this.setState(this.getUnskippedModeInfo(buttonIndex, index, skipButtonState), () => {
this.noticeRef.current.resetCountdown();
});
}
getUnskippedModeInfo(index: number, buttonText: string): SkipNoticeState {
getUnskippedModeInfo(buttonIndex: number, index: number, skipButtonState: SkipButtonState): SkipNoticeState {
const changeCountdown = this.segments[index].actionType !== ActionType.Poi;
const maxCountdownTime = changeCountdown ? () => {
const maxCountdownTime = changeCountdown ?
this.getFullDurationCountdown(index) : this.state.maxCountdownTime;
const skipButtonStates = this.state.skipButtonStates;
const skipButtonCallbacks = this.state.skipButtonCallbacks;
if (buttonIndex === null) {
for (let i = 0; i < this.segments.length; i++) {
skipButtonStates[i] = skipButtonState;
skipButtonCallbacks[i] = this.reskip.bind(this);
}
} else {
skipButtonStates[buttonIndex] = skipButtonState;
skipButtonCallbacks[buttonIndex] = this.reskip.bind(this);
if (buttonIndex === 1) {
// Trigger both to move at once
skipButtonStates[0] = SkipButtonState.Redo;
skipButtonCallbacks[0] = this.reskip.bind(this);
}
}
return {
skipButtonStates,
skipButtonCallbacks,
// change max duration to however much of the sponsor is left
maxCountdownTime,
countdownTime: maxCountdownTime(),
showSkipButton: buttonIndex === 1 ? [true, true] : this.state.showSkipButton
} as SkipNoticeState;
}
getFullDurationCountdown(index: number): () => number {
return () => {
const sponsorTime = this.segments[index];
const duration = Math.round((sponsorTime.segment[1] - this.contentContainer().v.currentTime) * (1 / this.contentContainer().v.playbackRate));
return Math.max(duration, Config.config.skipNoticeDuration);
} : this.state.maxCountdownTime;
return {
skipButtonText: buttonText,
skipButtonCallback: (index) => this.reskip(index),
// change max duration to however much of the sponsor is left
maxCountdownTime: maxCountdownTime,
countdownTime: maxCountdownTime()
} as SkipNoticeState;
};
}
afterVote(segment: SponsorTime, type: number, category: Category): void {
@@ -691,12 +750,12 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
}
}
unmutedListener(): void {
if (this.props.segments.length === 1
&& this.props.segments[0].actionType === ActionType.Mute
&& this.contentContainer().v.currentTime >= this.props.segments[0].segment[1]) {
unmutedListener(time: number): void {
if (this.props.segments.length === 1
&& this.props.segments[0].actionType === ActionType.Mute
&& time >= this.props.segments[0].segment[1]) {
this.setState({
showSkipButton: false
showSkipButton: [false, true]
});
}
}
@@ -711,8 +770,20 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
});
}
private getUnskipText(): string {
switch (this.props.segments[0].actionType) {
private getSkipButtonText(buttonIndex: number, forceType?: ActionType): string {
switch (this.state.skipButtonStates[buttonIndex]) {
case SkipButtonState.Undo:
return this.getUndoText(forceType);
case SkipButtonState.Redo:
return this.getRedoText(forceType);
case SkipButtonState.Start:
return this.getStartText(forceType);
}
}
private getUndoText(forceType?: ActionType): string {
const actionType = forceType || this.segments[0].actionType;
switch (actionType) {
case ActionType.Mute: {
return chrome.i18n.getMessage("unmute");
}
@@ -723,8 +794,9 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
}
}
private getReskipText(): string {
switch (this.props.segments[0].actionType) {
private getRedoText(forceType?: ActionType): string {
const actionType = forceType || this.segments[0].actionType;
switch (actionType) {
case ActionType.Mute: {
return chrome.i18n.getMessage("mute");
}
@@ -735,8 +807,9 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
}
}
private getSkipText(): string {
switch (this.props.segments[0].actionType) {
private getStartText(forceType?: ActionType): string {
const actionType = forceType || this.segments[0].actionType;
switch (actionType) {
case ActionType.Mute: {
return chrome.i18n.getMessage("mute");
}

View File

@@ -571,6 +571,11 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
const index = this.props.index;
const skipTime = sponsorTimes[index].segment[0];
// If segment starts at 0:00, start playback at the end of the segment
if (skipTime === 0) {
this.props.contentContainer().previewTime(sponsorTimes[index].segment[1]);
return;
}
let seekTime = 2;
if (ctrlPressed) seekTime = 0.5;

View File

@@ -6,7 +6,6 @@ import { keybindEquals } from "./utils/configUtils";
interface SBConfig {
userID: string,
isVip: boolean,
lastIsVipUpdate: number,
/* Contains unsubmitted segments that the user has created. */
unsubmittedSegments: Record<string, SponsorTime[]>,
defaultCategory: Category,
@@ -125,7 +124,6 @@ const Config: SBObject = {
syncDefaults: {
userID: null,
isVip: false,
lastIsVipUpdate: 0,
unsubmittedSegments: {},
defaultCategory: "chooseACategory" as Category,
renderAsChapters: true,
@@ -489,6 +487,10 @@ function migrateOldSyncFormats(config: SBConfig) {
if (!config["supportInvidious"] && config["invidiousInstances"].length !== invidiousList.length) {
config["invidiousInstances"] = invidiousList;
}
if (config["lastIsVipUpdate"]) {
chrome.storage.sync.remove("lastIsVipUpdate");
}
}
async function setupConfig() {

View File

@@ -66,6 +66,7 @@ let videoMutationObserver: MutationObserver = null;
const videosWithEventListeners: HTMLVideoElement[] = [];
const controlsWithEventListeners: HTMLElement[] = []
// This misleading variable name will be fixed soon
let onInvidious;
let onMobileYouTube;
@@ -197,14 +198,14 @@ function messageListener(request: Message, sender: unknown, sendResponse: (respo
break;
case "whitelistChange":
channelWhitelisted = request.value;
sponsorsLookup(sponsorVideoID);
sponsorsLookup();
break;
case "submitTimes":
submitSponsorTimes();
break;
case "refreshSegments":
sponsorsLookup(sponsorVideoID, false).then(() => sendResponse({
sponsorsLookup(false).then(() => sendResponse({
found: sponsorDataFound,
sponsorTimes: sponsorTimes,
onMobileYouTube
@@ -239,6 +240,9 @@ function contentConfigUpdateListener(changes: StorageChangesObject) {
case "hideDeleteButtonPlayerControls":
updateVisibilityOfPlayerControlsButton()
break;
case "categorySelections":
sponsorsLookup();
break;
}
}
}
@@ -293,7 +297,7 @@ function resetValues() {
async function videoIDChange(id) {
//if the id has not changed return unless the video element has changed
if (sponsorVideoID === id && isVisible(video)) return;
if (sponsorVideoID === id && (isVisible(video) || !video)) return;
//set the global videoID
sponsorVideoID = id;
@@ -392,18 +396,32 @@ function handleMobileControlsMutations(): void {
function createPreviewBar(): void {
if (previewBar !== null) return;
const progressElementSelectors = [
// For mobile YouTube
".progress-bar-background",
// For YouTube
".ytp-progress-bar",
".no-model.cue-range-markers",
// For Invidious/VideoJS
".vjs-progress-holder"
const progressElementOptions = [{
// For mobile YouTube
selector: ".progress-bar-background",
isVisibleCheck: true
}, {
// For new mobile YouTube (#1287)
selector: ".ytm-progress-bar",
isVisibleCheck: true
}, {
// For Desktop YouTube
selector: ".ytp-progress-bar",
isVisibleCheck: true
}, {
// For Desktop YouTube
selector: ".no-model.cue-range-marker",
isVisibleCheck: true
}, {
// For Invidious/VideoJS
selector: ".vjs-progress-holder",
isVisibleCheck: false
}
];
for (const selector of progressElementSelectors) {
const el = findValidElement(document.querySelectorAll(selector));
for (const option of progressElementOptions) {
const allElements = document.querySelectorAll(option.selector) as NodeListOf<HTMLElement>;
const el = option.isVisibleCheck ? findValidElement(allElements) : allElements[0];
if (el) {
previewBar = new PreviewBar(el, onMobileYouTube, onInvidious);
@@ -457,6 +475,7 @@ function startSponsorSchedule(includeIntersectingSegments = false, currentTime?:
// Reset lastCheckVideoTime
lastCheckVideoTime = -1;
lastCheckTime = 0;
console.warn("[SB] Ad playing, pausing skipping");
return;
}
@@ -483,7 +502,7 @@ function startSponsorSchedule(includeIntersectingSegments = false, currentTime?:
for (const notice of skipNotices) {
// So that the notice can hide buttons
notice.unmutedListener();
notice.unmutedListener(currentTime);
}
}
@@ -553,7 +572,7 @@ function startSponsorSchedule(includeIntersectingSegments = false, currentTime?:
} else {
const delayTime = timeUntilSponsor * 1000 * (1 / video.playbackRate);
if (delayTime < 300) {
// For Firefox, use interval instead of timeout near the end to combat imprecise video time
// Use interval instead of timeout near the end to combat imprecise video time
const startIntervalTime = performance.now();
const startVideoTime = Math.max(currentTime, video.currentTime);
currentSkipInterval = setInterval(() => {
@@ -716,8 +735,12 @@ function setupVideoListeners() {
cancelSponsorSchedule();
};
video.addEventListener('pause', paused);
video.addEventListener('waiting', paused);
video.addEventListener('pause', () => paused());
video.addEventListener('waiting', () => {
paused();
console.warn("[SB] Not skipping due to buffering");
});
startSponsorSchedule();
}
@@ -755,11 +778,11 @@ function setupCategoryPill() {
categoryPill.attachToPage(onMobileYouTube, onInvidious, voteAsync);
}
async function sponsorsLookup(id: string, keepOldSubmissions = true) {
async function sponsorsLookup(keepOldSubmissions = true) {
if (!video || !isVisible(video)) refreshVideoAttachments();
//there is still no video here
if (!video) {
setTimeout(() => sponsorsLookup(id), 100);
setTimeout(() => sponsorsLookup(), 100);
return;
}
@@ -773,7 +796,7 @@ async function sponsorsLookup(id: string, keepOldSubmissions = true) {
if (hashParams.requiredSegment) extraRequestData.requiredSegment = hashParams.requiredSegment;
// Check for hashPrefix setting
const hashPrefix = (await utils.getHash(id, 1)).slice(0, 4) as VideoID & HashedValue;
const hashPrefix = (await utils.getHash(sponsorVideoID, 1)).slice(0, 4) as VideoID & HashedValue;
const response = await utils.asyncRequestToServer('GET', "/api/skipSegments/" + hashPrefix, {
categories,
actionTypes: getEnabledActionTypes(),
@@ -783,7 +806,7 @@ async function sponsorsLookup(id: string, keepOldSubmissions = true) {
if (response?.ok) {
const recievedSegments: SponsorTime[] = JSON.parse(response.responseText)
?.filter((video) => video.videoID === id)
?.filter((video) => video.videoID === sponsorVideoID)
?.map((video) => video.segments)?.[0]
?.map((segment) => ({
...segment,
@@ -848,7 +871,7 @@ async function sponsorsLookup(id: string, keepOldSubmissions = true) {
//update the preview bar
//leave the type blank for now until categories are added
if (lastPreviewBarUpdate == id || (lastPreviewBarUpdate == null && !isNaN(video.duration))) {
if (lastPreviewBarUpdate == sponsorVideoID || (lastPreviewBarUpdate == null && !isNaN(video.duration))) {
//set it now
//otherwise the listener can handle it
updatePreviewBar();
@@ -859,7 +882,9 @@ async function sponsorsLookup(id: string, keepOldSubmissions = true) {
retryFetch();
}
lookupVipInformation(id);
if (Config.config.isVip) {
lockedCategoriesLookup();
}
}
function importExistingChapters(wait: boolean) {
@@ -888,46 +913,13 @@ function getEnabledActionTypes(): ActionType[] {
return actionTypes;
}
function lookupVipInformation(id: string): void {
updateVipInfo().then((isVip) => {
if (isVip) {
lockedCategoriesLookup(id);
}
});
}
async function updateVipInfo(): Promise<boolean> {
const currentTime = Date.now();
const lastUpdate = Config.config.lastIsVipUpdate;
if (currentTime - lastUpdate > 1000 * 60 * 60 * 72) { // 72 hours
Config.config.lastIsVipUpdate = currentTime;
const response = await utils.asyncRequestToServer("GET", "/api/isUserVIP", { userID: Config.config.userID});
if (response.ok) {
let isVip = false;
try {
const vipResponse = JSON.parse(response.responseText)?.vip;
if (typeof(vipResponse) === "boolean") {
isVip = vipResponse;
}
} catch (e) { } //eslint-disable-line no-empty
Config.config.isVip = isVip;
return isVip;
}
}
return Config.config.isVip;
}
async function lockedCategoriesLookup(id: string): Promise<void> {
const hashPrefix = (await utils.getHash(id, 1)).slice(0, 4);
async function lockedCategoriesLookup(): Promise<void> {
const hashPrefix = (await utils.getHash(sponsorVideoID, 1)).slice(0, 4);
const response = await utils.asyncRequestToServer("GET", "/api/lockCategories/" + hashPrefix);
if (response.ok) {
try {
const categoriesResponse = JSON.parse(response.responseText).filter((lockInfo) => lockInfo.videoID === id)[0]?.categories;
const categoriesResponse = JSON.parse(response.responseText).filter((lockInfo) => lockInfo.videoID === sponsorVideoID)[0]?.categories;
if (Array.isArray(categoriesResponse)) {
lockedCategories = categoriesResponse;
}
@@ -942,7 +934,7 @@ function retryFetch(): void {
setTimeout(() => {
if (sponsorVideoID && sponsorTimes?.length === 0) {
sponsorsLookup(sponsorVideoID);
sponsorsLookup();
}
}, 10000 + Math.random() * 30000);
}
@@ -1079,7 +1071,9 @@ function getYouTubeVideoIDFromURL(url: string): string | boolean {
utils.wait(() => Config.config !== null).then(() => videoIDChange(getYouTubeVideoIDFromURL(url)));
}
return false
return false;
} else {
onInvidious = false;
}
//Get ID from searchParam
@@ -1420,12 +1414,17 @@ function skipToTime({v, skipTime, skippingSegments, openNotice, forceAutoSkip, u
if (openNotice) {
//send out the message saying that a sponsor message was skipped
if (!Config.config.dontShowNotice || !autoSkip) {
const newSkipNotice = new SkipNotice(skippingSegments, autoSkip, skipNoticeContentContainer, unskipTime);
if (onMobileYouTube || Config.config.skipKeybind == null) newSkipNotice.setShowKeybindHint(false);
skipNotices.push(newSkipNotice);
createSkipNotice(skippingSegments, autoSkip, unskipTime, false);
} else if (autoSkip) {
activeSkipKeybindElement?.setShowKeybindHint(false);
activeSkipKeybindElement = newSkipNotice;
activeSkipKeybindElement = {
setShowKeybindHint: () => {}, //eslint-disable-line @typescript-eslint/no-empty-function
toggleSkip: () => {
unskipSponsorTime(skippingSegments[0], unskipTime);
createSkipNotice(skippingSegments, autoSkip, unskipTime, true);
}
};
}
}
}
@@ -1434,19 +1433,38 @@ function skipToTime({v, skipTime, skippingSegments, openNotice, forceAutoSkip, u
if (autoSkip) sendTelemetryAndCount(skippingSegments, skipTime[1] - skipTime[0], true);
}
function unskipSponsorTime(segment: SponsorTime, unskipTime: number = null) {
function createSkipNotice(skippingSegments: SponsorTime[], autoSkip: boolean, unskipTime: number, startReskip: boolean) {
for (const skipNotice of skipNotices) {
if (skippingSegments.length === skipNotice.segments.length
&& skippingSegments.every((segment) => skipNotice.segments.some((s) => s.UUID === segment.UUID))) {
// Skip notice already exists
return;
}
}
const newSkipNotice = new SkipNotice(skippingSegments, autoSkip, skipNoticeContentContainer, unskipTime, startReskip);
if (onMobileYouTube || Config.config.skipKeybind == null) newSkipNotice.setShowKeybindHint(false);
skipNotices.push(newSkipNotice);
activeSkipKeybindElement?.setShowKeybindHint(false);
activeSkipKeybindElement = newSkipNotice;
}
function unskipSponsorTime(segment: SponsorTime, unskipTime: number = null, forceSeek = false) {
if (segment.actionType === ActionType.Mute) {
video.muted = false;
videoMuted = false;
} else {
}
if (forceSeek || segment.actionType === ActionType.Skip) {
//add a tiny bit of time to make sure it is not skipped again
video.currentTime = unskipTime ?? segment.segment[0] + 0.001;
}
}
function reskipSponsorTime(segment: SponsorTime) {
if (segment.actionType === ActionType.Mute) {
function reskipSponsorTime(segment: SponsorTime, forceSeek = false) {
if (segment.actionType === ActionType.Mute && !forceSeek) {
video.muted = true;
videoMuted = true;
} else {
@@ -1632,7 +1650,7 @@ function startOrEndTimingNewSegment() {
Config.forceSyncUpdate("unsubmittedSegments");
// Make sure they know if someone has already submitted something it while they were watching
sponsorsLookup(sponsorVideoID);
sponsorsLookup();
updateEditButtonsOnPlayer();
updateSponsorTimesSubmitting(false);

View File

@@ -176,8 +176,10 @@ class PreviewBar {
this.parent = parent;
if (this.onMobileYouTube) {
parent.style.backgroundColor = "rgba(255, 255, 255, 0.3)";
parent.style.opacity = "1";
if (parent.classList.contains("progress-bar-background")) {
parent.style.backgroundColor = "rgba(255, 255, 255, 0.3)";
parent.style.opacity = "1";
}
this.container.style.transform = "none";
} else if (!this.onInvidious) {

View File

@@ -151,7 +151,7 @@ export class SkipButtonControlBar {
}
disableText(): void {
if (Config.config.hideVideoPlayerControls || Config.config.hideSkipButtonPlayerControls) {
if (Config.config.hideSkipButtonPlayerControls) {
this.disable();
return;
}

View File

@@ -115,7 +115,8 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
"issueReporterTabChapters",
"sponsorTimesDonateContainer",
"sbConsiderDonateLink",
"sbCloseDonate"
"sbCloseDonate",
"sbBetaServerWarning"
].forEach(id => PageElements[id] = document.getElementById(id));
// Hide donate button if wanted (Safari, or user choice)
@@ -124,6 +125,13 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
}
PageElements.sbDonate.addEventListener("click", () => Config.config.donateClicked = Config.config.donateClicked + 1);
if (Config.config.testingServer) {
PageElements.sbBetaServerWarning.classList.remove("hidden");
PageElements.sbBetaServerWarning.addEventListener("click", function () {
openOptionsAt("advanced");
});
}
//setup click listeners
PageElements.sponsorStart.addEventListener("click", sendSponsorStartMessage);
PageElements.whitelistToggle.addEventListener("change", function () {
@@ -177,7 +185,7 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
PageElements.showNoticeAgain.style.display = "unset";
}
utils.sendRequestToServer("GET", "/api/userInfo?value=userName&value=viewCount&value=minutesSaved&userID=" + Config.config.userID, (res) => {
utils.sendRequestToServer("GET", "/api/userInfo?value=userName&value=viewCount&value=minutesSaved&value=vip&userID=" + Config.config.userID, (res) => {
if (res.status === 200) {
const userInfo = JSON.parse(res.responseText);
PageElements.usernameValue.innerText = userInfo.userName;
@@ -204,6 +212,8 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
}
PageElements.sponsorTimesOthersTimeSavedDisplay.innerText = getFormattedHours(minutesSaved);
}
Config.config.isVip = userInfo.vip;
}
});
@@ -322,7 +332,7 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
//if request is undefined, then the page currently being browsed is not YouTube
if (request != undefined) {
//remove loading text
PageElements.mainControls.style.display = "flex";
PageElements.mainControls.style.display = "block";
if (request.onMobileYouTube) PageElements.mainControls.classList.add("hidden");
PageElements.whitelistButton.classList.remove("hidden");
PageElements.loadingIndicator.style.display = "none";
@@ -488,12 +498,14 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
const upvoteButton = document.createElement("img");
upvoteButton.id = "sponsorTimesUpvoteButtonsContainer" + UUID;
upvoteButton.className = "voteButton";
upvoteButton.title = chrome.i18n.getMessage("upvote");
upvoteButton.src = chrome.runtime.getURL("icons/thumbs_up.svg");
upvoteButton.addEventListener("click", () => vote(1, UUID));
const downvoteButton = document.createElement("img");
downvoteButton.id = "sponsorTimesDownvoteButtonsContainer" + UUID;
downvoteButton.className = "voteButton";
downvoteButton.title = chrome.i18n.getMessage("downvote");
downvoteButton.src = locked && isVip ? chrome.runtime.getURL("icons/thumbs_down_locked.svg") : chrome.runtime.getURL("icons/thumbs_down.svg");
downvoteButton.addEventListener("click", () => vote(0, UUID));
@@ -501,6 +513,7 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
uuidButton.id = "sponsorTimesCopyUUIDButtonContainer" + UUID;
uuidButton.className = "voteButton";
uuidButton.src = chrome.runtime.getURL("icons/clipboard.svg");
uuidButton.title = chrome.i18n.getMessage("copySegmentID");
uuidButton.addEventListener("click", () => {
navigator.clipboard.writeText(UUID);
const stopAnimation = AnimationUtils.applyLoadingAnimation(uuidButton, 0.3);
@@ -510,6 +523,7 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
const hideButton = document.createElement("img");
hideButton.id = "sponsorTimesCopyUUIDButtonContainer" + UUID;
hideButton.className = "voteButton";
hideButton.title = chrome.i18n.getMessage("hideSegment");
if (segmentTimes[i].hidden === SponsorHideType.Hidden) {
hideButton.src = chrome.runtime.getURL("icons/not_visible.svg");
} else {
@@ -741,6 +755,7 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
PageElements.unwhitelistChannel.style.display = "unset";
document.querySelectorAll('.SBWhitelistIcon')[0].classList.add("rotated");
//show 'consider force channel check' alert
if (!Config.config.forceChannelCheck) PageElements.whitelistForceCheck.classList.remove("hidden");
//save this
@@ -788,6 +803,9 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
PageElements.unwhitelistChannel.style.display = "none";
document.querySelectorAll('.SBWhitelistIcon')[0].classList.remove("rotated");
//hide 'consider force channel check' alert
PageElements.whitelistForceCheck.classList.add("hidden");
//save this
Config.config.whitelistedChannels = whitelistedChannels;

View File

@@ -19,7 +19,7 @@ class SkipNotice {
skipNoticeRef: React.MutableRefObject<SkipNoticeComponent>;
constructor(segments: SponsorTime[], autoSkip = false, contentContainer: ContentContainer, unskipTime: number = null) {
constructor(segments: SponsorTime[], autoSkip = false, contentContainer: ContentContainer, unskipTime: number = null, startReskip = false) {
this.skipNoticeRef = React.createRef();
this.segments = segments;
@@ -44,6 +44,7 @@ class SkipNotice {
ReactDOM.render(
<SkipNoticeComponent segments={segments}
autoSkip={autoSkip}
startReskip={startReskip}
contentContainer={contentContainer}
ref={this.skipNoticeRef}
closeListener={() => this.close()}
@@ -70,11 +71,11 @@ class SkipNotice {
}
toggleSkip(): void {
this.skipNoticeRef?.current?.prepAction(SkipNoticeAction.Unskip);
this.skipNoticeRef?.current?.prepAction(SkipNoticeAction.Unskip0);
}
unmutedListener(): void {
this.skipNoticeRef?.current?.unmutedListener();
unmutedListener(time: number): void {
this.skipNoticeRef?.current?.unmutedListener(time);
}
}

View File

@@ -6,13 +6,13 @@ export interface ContentContainer {
(): {
vote: (type: number, UUID: SegmentUUID, category?: Category, skipNotice?: SkipNoticeComponent) => void,
dontShowNoticeAgain: () => void,
unskipSponsorTime: (segment: SponsorTime, unskipTime: number) => void,
unskipSponsorTime: (segment: SponsorTime, unskipTime: number, forceSeek?: boolean) => void,
sponsorTimes: SponsorTime[],
sponsorTimesSubmitting: SponsorTime[],
skipNotices: SkipNotice[],
v: HTMLVideoElement,
sponsorVideoID,
reskipSponsorTime: (segment: SponsorTime) => void,
reskipSponsorTime: (segment: SponsorTime, forceSeek?: boolean) => void,
updatePreviewBar: () => void,
onMobileYouTube: boolean,
sponsorSubmissionNotice: SubmissionNotice,

View File

@@ -7,7 +7,8 @@ export enum SkipNoticeAction {
Downvote,
CategoryVote,
CopyDownvote,
Unskip
Unskip0,
Unskip1
}
export function downvoteButtonColor(segments: SponsorTime[], actionState: SkipNoticeAction, downvoteType: SkipNoticeAction): string {