mirror of
https://github.com/ajayyy/SponsorBlock.git
synced 2025-12-11 05:57:07 +03:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c06b7857f8 | ||
|
|
e798cfdfe3 | ||
|
|
0e76342b04 | ||
|
|
d91e38fec9 | ||
|
|
3316072f5d | ||
|
|
4c568212ac | ||
|
|
eaa119f152 | ||
|
|
e7deabe8d9 | ||
|
|
6d47700ebd | ||
|
|
93c616de23 | ||
|
|
ee25b41d7e | ||
|
|
00f134029a | ||
|
|
00d625013b | ||
|
|
e81ff66dd3 | ||
|
|
97af12416e | ||
|
|
bf191dab92 | ||
|
|
f8c61b7848 | ||
|
|
5b136f2da8 | ||
|
|
8b80b33810 | ||
|
|
e3c36ae6e2 | ||
|
|
533b15f44b |
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "__MSG_fullName__",
|
||||
"short_name": "SponsorBlock",
|
||||
"version": "5.0",
|
||||
"version": "5.0.1",
|
||||
"default_locale": "en",
|
||||
"description": "__MSG_Description__",
|
||||
"homepage_url": "https://sponsor.ajay.app",
|
||||
|
||||
@@ -463,6 +463,12 @@
|
||||
"minDurationDescription": {
|
||||
"message": "Segments shorter than the set value will not be skipped or show in the player."
|
||||
},
|
||||
"enableManualSkipOnFullVideo": {
|
||||
"message": "Use manual skip when a full video label exists"
|
||||
},
|
||||
"whatManualSkipOnFullVideo": {
|
||||
"message": "For people who want to watch the video uninterrupted if it is fully sponsored or self promotion."
|
||||
},
|
||||
"skipNoticeDuration": {
|
||||
"message": "Skip notice duration (seconds):"
|
||||
},
|
||||
@@ -1134,7 +1140,7 @@
|
||||
},
|
||||
"cantAfford": {
|
||||
"message": "If you can't afford to purchase a license, click {here} to see if you are eligible for a discount",
|
||||
"description": "Keep the curly braces"
|
||||
"description": "Keep the curly braces. The word 'here' should be translated as well."
|
||||
},
|
||||
"patreonSignIn": {
|
||||
"message": "Sign in with Patreon"
|
||||
|
||||
@@ -126,6 +126,10 @@ div:hover > .sponsorBlockChapterBar {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.playerButton.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Removes auto width from being a ytp-player-button */
|
||||
.sbPlayerDownvote {
|
||||
width: auto !important;
|
||||
|
||||
@@ -98,6 +98,20 @@
|
||||
|
||||
<div class="small-description">__MSG_minDurationDescription__</div>
|
||||
</div>
|
||||
|
||||
<div data-type="toggle" data-sync="manualSkipOnFullVideo">
|
||||
<div class="switch-container">
|
||||
<label class="switch">
|
||||
<input id="manualSkipOnFullVideo" type="checkbox" checked>
|
||||
<span class="slider round"></span>
|
||||
</label>
|
||||
<label class="switch-label" for="manualSkipOnFullVideo">
|
||||
__MSG_enableManualSkipOnFullVideo__
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="small-description">__MSG_whatManualSkipOnFullVideo__</div>
|
||||
</div>
|
||||
|
||||
<div data-type="toggle" data-sync="forceChannelCheck">
|
||||
<div class="switch-container">
|
||||
|
||||
@@ -32,6 +32,11 @@ class ChapterVoteComponent extends React.Component<ChapterVoteProps, ChapterVote
|
||||
}
|
||||
|
||||
render(): React.ReactElement {
|
||||
if (this.tooltip && !this.state.show) {
|
||||
this.tooltip.close();
|
||||
this.tooltip = null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Upvote Button */}
|
||||
@@ -42,7 +47,7 @@ class ChapterVoteComponent extends React.Component<ChapterVoteProps, ChapterVote
|
||||
onClick={(e) => this.vote(e, 1)}>
|
||||
<ThumbsUpSvg className="playerButtonImage"
|
||||
fill={Config.config.colorPalette.white}
|
||||
width={"inherit"} height={"inherit"} />
|
||||
width={null} height={null} />
|
||||
</button>
|
||||
|
||||
{/* Downvote Button */}
|
||||
@@ -92,8 +97,8 @@ class ChapterVoteComponent extends React.Component<ChapterVoteProps, ChapterVote
|
||||
<ThumbsDownSvg
|
||||
className="playerButtonImage"
|
||||
fill={downvoteButtonColor(this.state.segment ? [this.state.segment] : null, SkipNoticeAction.Downvote, SkipNoticeAction.Downvote)}
|
||||
width={"inherit"}
|
||||
height={"inherit"} />
|
||||
width={null}
|
||||
height={null} />
|
||||
</button>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -41,7 +41,10 @@ class SelectorComponent extends React.Component<SelectorProps, SelectorState> {
|
||||
for (const option of this.props.options) {
|
||||
result.push(
|
||||
<div className="sbSelectorOption"
|
||||
onClick={() => this.props.onChange(option.label)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
this.props.onChange(option.label);
|
||||
}}
|
||||
key={option.label}>
|
||||
{option.label}
|
||||
</div>
|
||||
|
||||
@@ -237,6 +237,7 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
|
||||
ref={this.descriptionOptionRef}
|
||||
type="text"
|
||||
value={this.state.description}
|
||||
onContextMenu={(e) => e.stopPropagation()}
|
||||
onChange={(e) => this.descriptionUpdate(e.target.value)}
|
||||
onFocus={() => this.setState({chapterNameSelectorOpen: true})}>
|
||||
</input>
|
||||
@@ -477,7 +478,8 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
|
||||
}
|
||||
|
||||
this.previousSkipType = ActionType.Full;
|
||||
} else if ((category === "chooseACategory" || (CompileConfig.categorySupport[category]?.includes(ActionType.Skip)
|
||||
} else if ((category === "chooseACategory" || ((CompileConfig.categorySupport[category]?.includes(ActionType.Skip)
|
||||
|| CompileConfig.categorySupport[category]?.includes(ActionType.Chapter))
|
||||
&& ![ActionType.Poi, ActionType.Full].includes(this.getNextActionType(category, actionType))))
|
||||
&& this.previousSkipType !== ActionType.Skip) {
|
||||
if (this.timesBeforeChanging[0]) {
|
||||
|
||||
@@ -25,6 +25,7 @@ interface SBConfig {
|
||||
disableSkipping: boolean,
|
||||
muteSegments: boolean,
|
||||
fullVideoSegments: boolean,
|
||||
manualSkipOnFullVideo: boolean,
|
||||
trackViewCount: boolean,
|
||||
trackViewCountInPrivate: boolean,
|
||||
trackDownvotes: boolean,
|
||||
@@ -159,6 +160,7 @@ const Config: SBObject = {
|
||||
disableSkipping: false,
|
||||
muteSegments: true,
|
||||
fullVideoSegments: true,
|
||||
manualSkipOnFullVideo: false,
|
||||
trackViewCount: true,
|
||||
trackViewCountInPrivate: true,
|
||||
trackDownvotes: true,
|
||||
|
||||
@@ -1287,7 +1287,7 @@ function updatePreviewBar(): void {
|
||||
});
|
||||
|
||||
previewBar.set(previewBarSegments.filter((segment) => segment.actionType !== ActionType.Full), video?.duration)
|
||||
updateActiveSegment(video.currentTime);
|
||||
if (video) updateActiveSegment(video.currentTime);
|
||||
|
||||
if (Config.config.showTimeWithSkips) {
|
||||
const skippedDuration = utils.getTimestampsDuration(previewBarSegments
|
||||
@@ -1690,9 +1690,10 @@ function createButton(baseID: string, title: string, callback: () => void, image
|
||||
}
|
||||
|
||||
function shouldAutoSkip(segment: SponsorTime): boolean {
|
||||
return utils.getCategorySelection(segment.category)?.option === CategorySkipOption.AutoSkip ||
|
||||
return (!Config.config.manualSkipOnFullVideo || !sponsorTimes?.some((s) => s.category === segment.category && s.actionType === ActionType.Full))
|
||||
&& (utils.getCategorySelection(segment.category)?.option === CategorySkipOption.AutoSkip ||
|
||||
(Config.config.autoSkipOnMusicVideos && sponsorTimes?.some((s) => s.category === "music_offtopic")
|
||||
&& segment.actionType !== ActionType.Poi);
|
||||
&& segment.actionType !== ActionType.Poi));
|
||||
}
|
||||
|
||||
function shouldSkip(segment: SponsorTime): boolean {
|
||||
@@ -2275,7 +2276,8 @@ function addPageListeners(): void {
|
||||
// inject into document
|
||||
const docScript = document.createElement("script");
|
||||
docScript.src = chrome.runtime.getURL("js/document.js");
|
||||
(document.head || document.documentElement).appendChild(docScript);
|
||||
// Not injected on invidious
|
||||
(document.head || document.documentElement)?.appendChild(docScript);
|
||||
|
||||
document.addEventListener("yt-navigate-start", resetValues);
|
||||
document.addEventListener("yt-navigate-finish", refreshListners);
|
||||
|
||||
@@ -54,27 +54,40 @@ document.addEventListener("yt-navigate-finish", navigateFinishSend);
|
||||
|
||||
function navigationParser(event: CustomEvent): StartMessage {
|
||||
const pageType: PageType = event.detail.pageType;
|
||||
const result: StartMessage = { type: "navigation", pageType, videoID: null };
|
||||
if (pageType === "shorts" || pageType === "watch") {
|
||||
const endpoint = event.detail.endpoint
|
||||
result.videoID = (pageType === "shorts" ? endpoint.reelWatchEndpoint : endpoint.watchEndpoint).videoId;
|
||||
}
|
||||
if (pageType) {
|
||||
const result: StartMessage = { type: "navigation", pageType, videoID: null };
|
||||
if (pageType === "shorts" || pageType === "watch") {
|
||||
const endpoint = event.detail.endpoint
|
||||
if (!endpoint) return null;
|
||||
|
||||
result.videoID = (pageType === "shorts" ? endpoint.reelWatchEndpoint : endpoint.watchEndpoint).videoId;
|
||||
}
|
||||
|
||||
return result;
|
||||
return result;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function navigationStartSend(event: CustomEvent): void {
|
||||
sendMessage(navigationParser(event) as StartMessage);
|
||||
const message = navigationParser(event) as StartMessage;
|
||||
if (message) {
|
||||
sendMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
function navigateFinishSend(event: CustomEvent): void {
|
||||
sendVideoData(); // arrived at new video, send video data
|
||||
const videoDetails = event.detail?.response?.playerResponse?.videoDetails;
|
||||
sendMessage({ channelID: videoDetails.channelId, channelTitle: videoDetails.author, ...navigationParser(event) } as FinishMessage);
|
||||
if (videoDetails) {
|
||||
sendMessage({ channelID: videoDetails.channelId, channelTitle: videoDetails.author, ...navigationParser(event) } as FinishMessage);
|
||||
}
|
||||
}
|
||||
|
||||
function sendVideoData(): void {
|
||||
if (!playerClient) return;
|
||||
const { video_id, isLive, isPremiere } = playerClient.getVideoData();
|
||||
sendMessage({ type: "data", videoID: video_id, isLive, isPremiere } as VideoData);
|
||||
const videoData = playerClient.getVideoData();
|
||||
if (videoData) {
|
||||
sendMessage({ type: "data", videoID: videoData.video_id, isLive: videoData.isLive, isPremiere: videoData.isPremiere } as VideoData);
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import { ActionType, Category, SegmentContainer, SponsorHideType, SponsorSourceT
|
||||
import { partition } from "../utils/arrayUtils";
|
||||
import { shortCategoryName } from "../utils/categoryUtils";
|
||||
import { GenericUtils } from "../utils/genericUtils";
|
||||
import { findValidElement } from "../utils/pageUtils";
|
||||
|
||||
const TOOLTIP_VISIBLE_CLASS = 'sponsorCategoryTooltipVisible';
|
||||
const MIN_CHAPTER_SIZE = 0.003;
|
||||
@@ -39,6 +40,7 @@ class PreviewBar {
|
||||
parent: HTMLElement;
|
||||
onMobileYouTube: boolean;
|
||||
onInvidious: boolean;
|
||||
progressBar: HTMLElement;
|
||||
|
||||
segments: PreviewBarSegment[] = [];
|
||||
existingChapters: PreviewBarSegment[] = [];
|
||||
@@ -62,6 +64,7 @@ class PreviewBar {
|
||||
this.onInvidious = onInvidious;
|
||||
this.chapterVote = chapterVote;
|
||||
|
||||
this.updatePageElements();
|
||||
this.createElement(parent);
|
||||
this.createChapterMutationObservers();
|
||||
|
||||
@@ -105,7 +108,7 @@ class PreviewBar {
|
||||
if (!mouseOnSeekBar || !this.categoryTooltip || !this.categoryTooltipContainer) return;
|
||||
|
||||
// If the mutation observed is only for our tooltip text, ignore
|
||||
if (mutations.length === 1 && (mutations[0].target as HTMLElement).classList.contains("sponsorCategoryTooltip")) {
|
||||
if (mutations.some((mutation) => (mutation.target as HTMLElement).classList.contains("sponsorCategoryTooltip"))) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -208,9 +211,9 @@ class PreviewBar {
|
||||
this.segments = segments ?? [];
|
||||
this.videoDuration = videoDuration ?? 0;
|
||||
|
||||
const progressBar = document.querySelector('.ytp-progress-bar') as HTMLElement;
|
||||
this.updatePageElements();
|
||||
// Sometimes video duration is inaccurate, pull from accessibility info
|
||||
const ariaDuration = parseInt(progressBar?.getAttribute('aria-valuemax')) ?? 0;
|
||||
const ariaDuration = parseInt(this.progressBar?.getAttribute('aria-valuemax')) ?? 0;
|
||||
if (ariaDuration && Math.abs(ariaDuration - this.videoDuration) > 3) {
|
||||
this.videoDuration = ariaDuration;
|
||||
}
|
||||
@@ -218,13 +221,21 @@ class PreviewBar {
|
||||
this.update();
|
||||
}
|
||||
|
||||
private updatePageElements(): void {
|
||||
const allProgressBars = document.querySelectorAll('.ytp-progress-bar') as NodeListOf<HTMLElement>;
|
||||
this.progressBar = findValidElement(allProgressBars) ?? allProgressBars?.[0];
|
||||
|
||||
this.originalChapterBar = this.progressBar.querySelector(".ytp-chapters-container:not(.sponsorBlockChapterBar)") as HTMLElement;
|
||||
}
|
||||
|
||||
private update(): void {
|
||||
this.clear();
|
||||
if (!this.segments) return;
|
||||
|
||||
this.originalChapterBar = document.querySelector(".ytp-chapters-container:not(.sponsorBlockChapterBar)") as HTMLElement;
|
||||
this.originalChapterBarBlocks = this.originalChapterBar.querySelectorAll(":scope > div") as NodeListOf<HTMLElement>
|
||||
this.existingChapters = this.segments.filter((s) => s.source === SponsorSourceType.YouTube).sort((a, b) => a.segment[0] - b.segment[0])
|
||||
if (this.originalChapterBar) {
|
||||
this.originalChapterBarBlocks = this.originalChapterBar.querySelectorAll(":scope > div") as NodeListOf<HTMLElement>
|
||||
this.existingChapters = this.segments.filter((s) => s.source === SponsorSourceType.YouTube).sort((a, b) => a.segment[0] - b.segment[0]);
|
||||
}
|
||||
|
||||
const sortedSegments = this.segments.sort(({ segment: a }, { segment: b }) => {
|
||||
// Sort longer segments before short segments to make shorter segments render later
|
||||
@@ -239,11 +250,13 @@ class PreviewBar {
|
||||
this.createChaptersBar(this.segments.sort((a, b) => a.segment[0] - b.segment[0]));
|
||||
|
||||
const chapterChevron = this.getChapterChevron();
|
||||
if (this.segments.some((segment) => segment.actionType !== ActionType.Chapter
|
||||
if (chapterChevron) {
|
||||
if (this.segments.some((segment) => segment.actionType !== ActionType.Chapter
|
||||
&& segment.source === SponsorSourceType.YouTube)) {
|
||||
chapterChevron.style.removeProperty("display");
|
||||
} else {
|
||||
chapterChevron.style.display = "none";
|
||||
chapterChevron.style.removeProperty("display");
|
||||
} else {
|
||||
chapterChevron.style.display = "none";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -274,8 +287,7 @@ class PreviewBar {
|
||||
}
|
||||
|
||||
createChaptersBar(segments: PreviewBarSegment[]): void {
|
||||
const progressBar = document.querySelector('.ytp-progress-bar') as HTMLElement;
|
||||
if (!progressBar || !this.originalChapterBar || this.originalChapterBar.childElementCount <= 0) return;
|
||||
if (!this.progressBar || !this.originalChapterBar || this.originalChapterBar.childElementCount <= 0) return;
|
||||
|
||||
if (segments.every((segments) => segments.source === SponsorSourceType.YouTube)
|
||||
|| (!Config.config.renderSegmentsAsChapters
|
||||
@@ -298,17 +310,19 @@ class PreviewBar {
|
||||
|
||||
// Create it from cloning
|
||||
let createFromScratch = false;
|
||||
if (!this.customChaptersBar) {
|
||||
if (!this.customChaptersBar || !this.progressBar.contains(this.customChaptersBar)) {
|
||||
// Clear anything remaining
|
||||
document.querySelectorAll(".sponsorBlockChapterBar").forEach((element) => element.remove());
|
||||
|
||||
createFromScratch = true;
|
||||
this.customChaptersBar = this.originalChapterBar.cloneNode(true) as HTMLElement;
|
||||
this.customChaptersBar.classList.add("sponsorBlockChapterBar");
|
||||
}
|
||||
this.customChaptersBar.style.removeProperty("display");
|
||||
|
||||
this.customChaptersBar.style.display = "none";
|
||||
const originalSections = this.customChaptersBar.querySelectorAll(".ytp-chapter-hover-container");
|
||||
const originalSection = originalSections[0];
|
||||
|
||||
this.customChaptersBar = this.customChaptersBar;
|
||||
|
||||
// For switching to a video with less chapters
|
||||
if (originalSections.length > chaptersToRender.length) {
|
||||
for (let i = originalSections.length - 1; i >= chaptersToRender.length; i--) {
|
||||
@@ -332,16 +346,17 @@ class PreviewBar {
|
||||
|
||||
// Hide old bar
|
||||
this.originalChapterBar.style.display = "none";
|
||||
this.customChaptersBar.style.removeProperty("display");
|
||||
|
||||
if (createFromScratch) {
|
||||
if (this.container?.parentElement === progressBar) {
|
||||
progressBar.insertBefore(this.customChaptersBar, this.container.nextSibling);
|
||||
if (this.container?.parentElement === this.progressBar) {
|
||||
this.progressBar.insertBefore(this.customChaptersBar, this.container.nextSibling);
|
||||
} else {
|
||||
progressBar.prepend(this.customChaptersBar);
|
||||
this.progressBar.prepend(this.customChaptersBar);
|
||||
}
|
||||
}
|
||||
|
||||
this.updateChapterAllMutation(this.originalChapterBar, progressBar, true);
|
||||
this.updateChapterAllMutation(this.originalChapterBar, this.progressBar, true);
|
||||
}
|
||||
|
||||
createChapterRenderGroups(segments: PreviewBarSegment[]): ChapterGroup[] {
|
||||
@@ -455,9 +470,7 @@ class PreviewBar {
|
||||
}
|
||||
|
||||
private createChapterMutationObservers(): void {
|
||||
const progressBar = document.querySelector('.ytp-progress-bar') as HTMLElement;
|
||||
const chapterBar = document.querySelector(".ytp-chapters-container:not(.sponsorBlockChapterBar)") as HTMLElement;
|
||||
if (!progressBar || !chapterBar) return;
|
||||
if (!this.progressBar || !this.originalChapterBar) return;
|
||||
|
||||
const attributeObserver = new MutationObserver((mutations) => {
|
||||
const changes: Record<string, HTMLElement> = {};
|
||||
@@ -469,10 +482,10 @@ class PreviewBar {
|
||||
}
|
||||
}
|
||||
|
||||
this.updateChapterMutation(changes, progressBar);
|
||||
this.updateChapterMutation(changes, this.progressBar);
|
||||
});
|
||||
|
||||
attributeObserver.observe(chapterBar, {
|
||||
attributeObserver.observe(this.originalChapterBar, {
|
||||
subtree: true,
|
||||
attributes: true,
|
||||
attributeFilter: ["style", "class"]
|
||||
@@ -486,11 +499,11 @@ class PreviewBar {
|
||||
}
|
||||
}
|
||||
|
||||
this.updateChapterMutation(changes, progressBar);
|
||||
this.updateChapterMutation(changes, this.progressBar);
|
||||
});
|
||||
|
||||
// Only direct children, no subtree
|
||||
childListObserver.observe(chapterBar, {
|
||||
childListObserver.observe(this.originalChapterBar, {
|
||||
childList: true
|
||||
});
|
||||
}
|
||||
@@ -665,6 +678,11 @@ class PreviewBar {
|
||||
const chapterVoteContainer = this.chapterVote.getContainer();
|
||||
if (chosenSegment.source === SponsorSourceType.Server) {
|
||||
if (!chapterButton.contains(chapterVoteContainer)) {
|
||||
const oldVoteContainers = document.querySelectorAll("#chapterVote");
|
||||
if (oldVoteContainers.length > 0) {
|
||||
oldVoteContainers.forEach((oldVoteContainer) => oldVoteContainer.remove());
|
||||
}
|
||||
|
||||
chapterButton.insertBefore(chapterVoteContainer, this.getChapterChevron());
|
||||
}
|
||||
|
||||
@@ -676,6 +694,7 @@ class PreviewBar {
|
||||
} else {
|
||||
// Hide chapters menu again
|
||||
chaptersContainer.style.display = "none";
|
||||
this.chapterVote.setVisibility(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -498,7 +498,7 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
||||
//display the video times from the array at the top, in a different section
|
||||
function displayDownloadedSponsorTimes(sponsorTimes: SponsorTime[], time: number) {
|
||||
let currentSegmentTab = segmentTab;
|
||||
if (!sponsorTimes.some((segment) => segment.actionType === ActionType.Chapter)) {
|
||||
if (!sponsorTimes.some((segment) => segment.actionType === ActionType.Chapter && segment.source !== SponsorSourceType.YouTube)) {
|
||||
PageElements.issueReporterTabs.classList.add("hidden");
|
||||
currentSegmentTab = SegmentTab.Segments;
|
||||
} else {
|
||||
|
||||
@@ -37,6 +37,7 @@ export class ChapterVote {
|
||||
setVisibility(show: boolean): void {
|
||||
const newState = {
|
||||
show,
|
||||
...(!show ? { segment: null } : {})
|
||||
};
|
||||
|
||||
if (this.ref.current) {
|
||||
|
||||
@@ -70,7 +70,7 @@ export default class GenericNotice {
|
||||
|
||||
<tr id={"sponsorSkipNoticeMiddleRow" + this.idSuffix}
|
||||
className="sponsorTimeMessagesRow"
|
||||
style={{maxHeight: (this.contentContainer().v.offsetHeight - 200) + "px"}}>
|
||||
style={{maxHeight: this.contentContainer ? (this.contentContainer().v.offsetHeight - 200) + "px" : null}}>
|
||||
<td style={{width: "100%"}}>
|
||||
{this.getMessageBoxes(this.idSuffix, options.textBoxes)}
|
||||
</td>
|
||||
|
||||
@@ -35,7 +35,7 @@ export function importTimes(data: string, videoDuration: number): SponsorTime[]
|
||||
const lines = data.split("\n");
|
||||
const result: SponsorTime[] = [];
|
||||
for (const line of lines) {
|
||||
const match = line.match(/(?:(\d+:\d+)+(?:\.\d+)?)|(?:\d+(?=s| second))/g);
|
||||
const match = line.match(/(?:((?:\d+:)?\d+:\d+)+(?:\.\d+)?)|(?:\d+(?=s| second))/g);
|
||||
if (match) {
|
||||
const startTime = GenericUtils.getFormattedTimeToSeconds(match[0]);
|
||||
if (startTime) {
|
||||
|
||||
@@ -238,4 +238,20 @@ describe("Import segments", () => {
|
||||
category: "chapter" as Category
|
||||
}]);
|
||||
});
|
||||
|
||||
it ("22. 2:04:22 some name", () => {
|
||||
const input = ` 22. 2:04:22 some name
|
||||
23. 2:04:22.23 some other name`;
|
||||
|
||||
const result = importTimes(input, 8000);
|
||||
expect(result).toMatchObject([{
|
||||
segment: [7462, 7462.23],
|
||||
description: "some name",
|
||||
category: "chapter" as Category
|
||||
}, {
|
||||
segment: [7462.23, 8000],
|
||||
description: "some other name",
|
||||
category: "chapter" as Category
|
||||
}]);
|
||||
});
|
||||
});
|
||||
@@ -202,6 +202,8 @@ async function muteSkipSegment(driver: WebDriver, startTime: number, endTime: nu
|
||||
|
||||
async function toggleWhitelist(driver: WebDriver): Promise<void> {
|
||||
const popupButton = await driver.findElement(By.id("infoButton"));
|
||||
const rightControls = await driver.findElement(By.css(".ytp-right-controls"));
|
||||
await driver.actions().move({ origin: rightControls }).perform();
|
||||
if ((await popupButton.getCssValue("display")) !== "none") {
|
||||
await driver.actions().move({ origin: popupButton }).perform();
|
||||
await popupButton.click();
|
||||
|
||||
Reference in New Issue
Block a user