diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/components/SponsorTimeEditComponent.tsx | 11 | ||||
-rw-r--r-- | src/content.ts | 13 | ||||
-rw-r--r-- | src/js-components/previewBar.ts | 5 | ||||
-rw-r--r-- | src/types.ts | 3 | ||||
-rw-r--r-- | src/utils.ts | 20 | ||||
-rw-r--r-- | src/utils/genericUtils.ts | 21 | ||||
-rw-r--r-- | src/utils/pageUtils.ts | 45 |
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 |