aboutsummaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/components/SponsorTimeEditComponent.tsx11
-rw-r--r--src/content.ts13
-rw-r--r--src/js-components/previewBar.ts5
-rw-r--r--src/types.ts3
-rw-r--r--src/utils.ts20
-rw-r--r--src/utils/genericUtils.ts21
-rw-r--r--src/utils/pageUtils.ts45
7 files changed, 90 insertions, 28 deletions
diff --git a/src/components/SponsorTimeEditComponent.tsx b/src/components/SponsorTimeEditComponent.tsx
index ed379105..b125ce72 100644
--- a/src/components/SponsorTimeEditComponent.tsx
+++ b/src/components/SponsorTimeEditComponent.tsx
@@ -6,6 +6,7 @@ import Utils from "../utils";
import SubmissionNoticeComponent from "./SubmissionNoticeComponent";
import { RectangleTooltip } from "../render/RectangleTooltip";
import SelectorComponent, { SelectorOption } from "./SelectorComponent";
+import { GenericUtils } from "../utils/genericUtils";
const utils = new Utils();
@@ -289,8 +290,8 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
const sponsorTimeEdits = this.state.sponsorTimeEdits;
// check if change is small engough to show tooltip
- const before = utils.getFormattedTimeToSeconds(sponsorTimeEdits[index]);
- const after = utils.getFormattedTimeToSeconds(targetValue);
+ const before = GenericUtils.getFormattedTimeToSeconds(sponsorTimeEdits[index]);
+ const after = GenericUtils.getFormattedTimeToSeconds(targetValue);
const difference = Math.abs(before - after);
if (0 < difference && difference < 0.5) this.showScrollToEditToolTip();
@@ -313,7 +314,7 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
}
const sponsorTimeEdits = this.state.sponsorTimeEdits;
- let timeAsNumber = utils.getFormattedTimeToSeconds(this.state.sponsorTimeEdits[index]);
+ let timeAsNumber = GenericUtils.getFormattedTimeToSeconds(this.state.sponsorTimeEdits[index]);
if (timeAsNumber !== null && e.deltaY != 0) {
if (e.deltaY < 0) {
timeAsNumber += step;
@@ -530,8 +531,8 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
const sponsorTimesSubmitting = this.props.contentContainer().sponsorTimesSubmitting;
if (this.state.editing) {
- const startTime = utils.getFormattedTimeToSeconds(this.state.sponsorTimeEdits[0]);
- const endTime = utils.getFormattedTimeToSeconds(this.state.sponsorTimeEdits[1]);
+ const startTime = GenericUtils.getFormattedTimeToSeconds(this.state.sponsorTimeEdits[0]);
+ const endTime = GenericUtils.getFormattedTimeToSeconds(this.state.sponsorTimeEdits[1]);
// Change segment time only if the format was correct
if (startTime !== null && endTime !== null) {
diff --git a/src/content.ts b/src/content.ts
index a997b2f5..ec5e80ca 100644
--- a/src/content.ts
+++ b/src/content.ts
@@ -15,7 +15,7 @@ import { Message, MessageResponse, VoteResponse } from "./messageTypes";
import * as Chat from "./js-components/chat";
import { SkipButtonControlBar } from "./js-components/skipButtonControlBar";
import { getStartTimeFromUrl } from "./utils/urlParser";
-import { findValidElement, getControls, getHashParams, isVisible } from "./utils/pageUtils";
+import { findValidElement, getControls, getExistingChapters, getHashParams, isVisible } from "./utils/pageUtils";
import { isSafari, keybindEquals } from "./utils/configUtils";
import { CategoryPill } from "./render/CategoryPill";
import { AnimationUtils } from "./utils/animationUtils";
@@ -806,6 +806,17 @@ async function sponsorsLookup(id: string, keepOldSubmissions = true) {
//otherwise the listener can handle it
updatePreviewBar();
}
+
+ // Add existing chapters if we can
+ if (utils.chaptersEnabled()) {
+ GenericUtils.wait(() => getExistingChapters(sponsorVideoID, video.duration),
+ 5000, 100, (c) => c?.length > 0).then((chapters) => {
+ if (chapters?.length > 0) {
+ sponsorTimes = sponsorTimes.concat(...chapters);
+ updatePreviewBar();
+ }
+ });
+ }
} else if (response?.status === 404) {
retryFetch();
}
diff --git a/src/js-components/previewBar.ts b/src/js-components/previewBar.ts
index 6858035b..9de31e95 100644
--- a/src/js-components/previewBar.ts
+++ b/src/js-components/previewBar.ts
@@ -9,6 +9,7 @@ import Config from "../config";
import { ActionType, Category, SegmentContainer, SponsorTime } from "../types";
import Utils from "../utils";
import { partition } from "../utils/arrayUtils";
+import { GenericUtils } from "../utils/genericUtils";
const utils = new Utils();
const TOOLTIP_VISIBLE_CLASS = 'sponsorCategoryTooltipVisible';
@@ -111,7 +112,7 @@ class PreviewBar {
const tooltipText = tooltipTextElement.textContent;
if (tooltipText === null || tooltipText.length === 0) continue;
- timeInSeconds = utils.getFormattedTimeToSeconds(tooltipText);
+ timeInSeconds = GenericUtils.getFormattedTimeToSeconds(tooltipText);
if (timeInSeconds !== null) break;
}
@@ -389,6 +390,8 @@ class PreviewBar {
const chapterBar = document.querySelector(".ytp-chapters-container:not(.sponsorBlockChapterBar)") as HTMLElement;
if (!progressBar || !chapterBar) return;
+ // todo: Can this same mutation observer be reused to import chapters
+ // maybe not, looks like it has to be imported from the skipping to chapters page
const observer = new MutationObserver((mutations) => {
const changes: Record<string, HTMLElement> = {};
for (const mutation of mutations) {
diff --git a/src/types.ts b/src/types.ts
index a9fb161c..edb566da 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -71,7 +71,8 @@ export type Category = string & { __categoryBrand: unknown };
export enum SponsorSourceType {
Server = undefined,
- Local = 1
+ Local = 1,
+ YouTube = 2
}
export interface SegmentContainer {
diff --git a/src/utils.ts b/src/utils.ts
index 7ee3be4c..7b6fb387 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -29,7 +29,7 @@ export default class Utils {
this.backgroundScriptContainer = backgroundScriptContainer;
}
- async wait<T>(condition: () => T | false, timeout = 5000, check = 100): Promise<T> {
+ async wait<T>(condition: () => T, timeout = 5000, check = 100): Promise<T> {
return GenericUtils.wait(condition, timeout, check);
}
@@ -442,20 +442,6 @@ export default class Utils {
return formatted;
}
- getFormattedTimeToSeconds(formatted: string): number | null {
- const fragments = /^(?:(?:(\d+):)?(\d+):)?(\d*(?:[.,]\d+)?)$/.exec(formatted);
-
- if (fragments === null) {
- return null;
- }
-
- const hours = fragments[1] ? parseInt(fragments[1]) : 0;
- const minutes = fragments[2] ? parseInt(fragments[2] || '0') : 0;
- const seconds = fragments[3] ? parseFloat(fragments[3].replace(',', '.')) : 0;
-
- return hours * 3600 + minutes * 60 + seconds;
- }
-
shortCategoryName(categoryName: string): string {
return chrome.i18n.getMessage("category_" + categoryName + "_short") || chrome.i18n.getMessage("category_" + categoryName);
}
@@ -529,4 +515,8 @@ export default class Utils {
Config.forceLocalUpdate("downvotedSegments");
}
+
+ chaptersEnabled(): boolean {
+ return Config.config.renderAsChapters && !!this.getCategorySelection("chapter");
+ }
}
diff --git a/src/utils/genericUtils.ts b/src/utils/genericUtils.ts
index 3451c738..dd97d8a7 100644
--- a/src/utils/genericUtils.ts
+++ b/src/utils/genericUtils.ts
@@ -1,5 +1,5 @@
/** Function that can be used to wait for a condition before returning. */
-async function wait<T>(condition: () => T | false, timeout = 5000, check = 100): Promise<T> {
+async function wait<T>(condition: () => T, timeout = 5000, check = 100, predicate?: (obj: T) => boolean): Promise<T> {
return await new Promise((resolve, reject) => {
setTimeout(() => {
clearInterval(interval);
@@ -8,7 +8,7 @@ async function wait<T>(condition: () => T | false, timeout = 5000, check = 100):
const intervalCheck = () => {
const result = condition();
- if (result) {
+ if (predicate ? predicate(result) : result) {
resolve(result);
clearInterval(interval);
}
@@ -21,6 +21,20 @@ async function wait<T>(condition: () => T | false, timeout = 5000, check = 100):
});
}
+function getFormattedTimeToSeconds(formatted: string): number | null {
+ const fragments = /^(?:(?:(\d+):)?(\d+):)?(\d*(?:[.,]\d+)?)$/.exec(formatted);
+
+ if (fragments === null) {
+ return null;
+ }
+
+ const hours = fragments[1] ? parseInt(fragments[1]) : 0;
+ const minutes = fragments[2] ? parseInt(fragments[2] || '0') : 0;
+ const seconds = fragments[3] ? parseFloat(fragments[3].replace(',', '.')) : 0;
+
+ return hours * 3600 + minutes * 60 + seconds;
+}
+
/**
* Gets the error message in a nice string
*
@@ -64,10 +78,11 @@ function hexToRgb(hex: string): {r: number, g: number, b: number} {
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : null;
- }
+}
export const GenericUtils = {
wait,
+ getFormattedTimeToSeconds,
getErrorMessage,
getLuminance
} \ No newline at end of file
diff --git a/src/utils/pageUtils.ts b/src/utils/pageUtils.ts
index 8f484d73..da3a2f00 100644
--- a/src/utils/pageUtils.ts
+++ b/src/utils/pageUtils.ts
@@ -1,4 +1,7 @@
-export function getControls(): HTMLElement | false {
+import { ActionType, Category, SponsorSourceType, SponsorTime, VideoID } from "../types";
+import { GenericUtils } from "./genericUtils";
+
+export function getControls(): HTMLElement {
const controlsSelectors = [
// YouTube
".ytp-right-controls",
@@ -16,7 +19,7 @@ export function getControls(): HTMLElement | false {
}
}
- return false;
+ return null;
}
export function isVisible(element: HTMLElement): boolean {
@@ -61,4 +64,42 @@ export function getHashParams(): Record<string, unknown> {
}
return {};
+}
+
+export function getExistingChapters(currentVideoID: VideoID, duration: number): SponsorTime[] {
+ const chaptersBox = document.querySelector("ytd-macro-markers-list-renderer");
+
+ const chapters: SponsorTime[] = [];
+ if (chaptersBox) {
+ let lastSegment: SponsorTime = null;
+ const links = chaptersBox.querySelectorAll("ytd-macro-markers-list-item-renderer > a");
+ for (const link of links) {
+ const timeElement = link.querySelector("#time") as HTMLElement;
+ const description = link.querySelector("#details h4") as HTMLElement;
+ if (timeElement && description?.innerText?.length > 0 && link.getAttribute("href")?.includes(currentVideoID)) {
+ const time = GenericUtils.getFormattedTimeToSeconds(timeElement.innerText);
+
+ if (lastSegment) {
+ lastSegment.segment[1] = time;
+ chapters.push(lastSegment);
+ }
+
+ lastSegment = {
+ segment: [time, null],
+ category: "chapter" as Category,
+ actionType: ActionType.Chapter,
+ description: description.innerText,
+ source: SponsorSourceType.YouTube,
+ UUID: null
+ };
+ }
+ }
+
+ if (lastSegment) {
+ lastSegment.segment[1] = duration;
+ chapters.push(lastSegment);
+ }
+ }
+
+ return chapters;
} \ No newline at end of file