mirror of
https://github.com/ajayyy/SponsorBlock.git
synced 2025-12-06 19:47:04 +03:00
Add advanced skip options
This commit is contained in:
Submodule public/_locales updated: b6bb85b7f7...33cefd7621
@@ -732,3 +732,16 @@ svg {
|
|||||||
.dearrow-link:hover .close-button {
|
.dearrow-link:hover .close-button {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.invalid-advanced-config {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.advanced-skip-options-menu {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.advanced-config-help-message {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
@@ -140,6 +140,8 @@
|
|||||||
<div class="small-description">__MSG_whatManualSkipOnFullVideo__</div>
|
<div class="small-description">__MSG_whatManualSkipOnFullVideo__</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div data-type="react-AdvancedSkipOptionsComponent"></div>
|
||||||
|
|
||||||
<div data-type="toggle" data-sync="forceChannelCheck">
|
<div data-type="toggle" data-sync="forceChannelCheck">
|
||||||
<div class="switch-container">
|
<div class="switch-container">
|
||||||
<label class="switch">
|
<label class="switch">
|
||||||
|
|||||||
269
src/components/options/AdvancedSkipOptionsComponent.tsx
Normal file
269
src/components/options/AdvancedSkipOptionsComponent.tsx
Normal file
@@ -0,0 +1,269 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import * as CompileConfig from "../../../config.json";
|
||||||
|
|
||||||
|
import Config, { AdvancedSkipRuleSet, SkipRuleAttribute, SkipRuleOperator } from "../../config";
|
||||||
|
import { CategorySkipOption } from "../../types";
|
||||||
|
|
||||||
|
let configSaveTimeout: NodeJS.Timeout | null = null;
|
||||||
|
|
||||||
|
export function AdvancedSkipOptionsComponent() {
|
||||||
|
const [optionsOpen, setOptionsOpen] = React.useState(false);
|
||||||
|
const [config, setConfig] = React.useState(configToText(Config.local.skipRules));
|
||||||
|
const [configValid, setConfigValid] = React.useState(true);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="option-button" onClick={() => {
|
||||||
|
setOptionsOpen(!optionsOpen);
|
||||||
|
}}>
|
||||||
|
{chrome.i18n.getMessage("openAdvancedSkipOptions")}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{
|
||||||
|
optionsOpen &&
|
||||||
|
<div className="advanced-skip-options-menu">
|
||||||
|
<div className={"advanced-config-help-message"}>
|
||||||
|
<a target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
href="https://wiki.sponsor.ajay.app/w/Advanced_Skip_Options">
|
||||||
|
{chrome.i18n.getMessage("advancedSkipSettingsHelp")}
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<span className={configValid ? "hidden" : "invalid-advanced-config"}>
|
||||||
|
{" - "}
|
||||||
|
{chrome.i18n.getMessage("advancedSkipNotSaved")}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<textarea className={"option-text-box " + (configValid ? "" : "invalid-advanced-config")}
|
||||||
|
rows={10}
|
||||||
|
style={{ width: "80%" }}
|
||||||
|
value={config}
|
||||||
|
spellCheck={false}
|
||||||
|
onChange={(e) => {
|
||||||
|
setConfig(e.target.value);
|
||||||
|
|
||||||
|
const compiled = compileConfig(e.target.value);
|
||||||
|
setConfigValid(!!compiled && !(e.target.value.length > 0 && compiled.length === 0));
|
||||||
|
|
||||||
|
if (compiled) {
|
||||||
|
if (configSaveTimeout) {
|
||||||
|
clearTimeout(configSaveTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
configSaveTimeout = setTimeout(() => {
|
||||||
|
Config.local.skipRules = compiled;
|
||||||
|
}, 200);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function compileConfig(config: string): AdvancedSkipRuleSet[] | null {
|
||||||
|
const ruleSets: AdvancedSkipRuleSet[] = [];
|
||||||
|
|
||||||
|
let ruleSet: AdvancedSkipRuleSet = {
|
||||||
|
rules: [],
|
||||||
|
skipOption: null,
|
||||||
|
comment: ""
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const line of config.split("\n")) {
|
||||||
|
if (line.trim().length === 0) {
|
||||||
|
// Skip empty lines
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const comment = line.match(/^\s*\/\/(.+)$/);
|
||||||
|
if (comment) {
|
||||||
|
if (ruleSet.rules.length > 0) {
|
||||||
|
// Rule has already been created, add it to list if valid
|
||||||
|
if (ruleSet.skipOption !== null && ruleSet.rules.length > 0) {
|
||||||
|
ruleSets.push(ruleSet);
|
||||||
|
|
||||||
|
ruleSet = {
|
||||||
|
rules: [],
|
||||||
|
skipOption: null,
|
||||||
|
comment: ""
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ruleSet.comment.length > 0) {
|
||||||
|
ruleSet.comment += "; ";
|
||||||
|
}
|
||||||
|
|
||||||
|
ruleSet.comment += comment[1].trim();
|
||||||
|
|
||||||
|
// Skip comment lines
|
||||||
|
continue;
|
||||||
|
} else if (line.startsWith("if ")) {
|
||||||
|
if (ruleSet.rules.length > 0) {
|
||||||
|
// Rule has already been created, add it to list if valid
|
||||||
|
if (ruleSet.skipOption !== null && ruleSet.rules.length > 0) {
|
||||||
|
ruleSets.push(ruleSet);
|
||||||
|
|
||||||
|
ruleSet = {
|
||||||
|
rules: [],
|
||||||
|
skipOption: null,
|
||||||
|
comment: ""
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ruleTexts = [...line.matchAll(/\S+ \S+ (?:"[^"\\]*(?:\\.[^"\\]*)*"|\d+)(?= and |$)/g)];
|
||||||
|
for (const ruleText of ruleTexts) {
|
||||||
|
if (!ruleText[0]) return null;
|
||||||
|
|
||||||
|
const ruleParts = ruleText[0].match(/(\S+) (\S+) ("[^"\\]*(?:\\.[^"\\]*)*"|\d+)/);
|
||||||
|
if (ruleParts.length !== 4) {
|
||||||
|
return null; // Invalid rule format
|
||||||
|
}
|
||||||
|
|
||||||
|
const attribute = getSkipRuleAttribute(ruleParts[1]);
|
||||||
|
const operator = getSkipRuleOperator(ruleParts[2]);
|
||||||
|
const value = getSkipRuleValue(ruleParts[3]);
|
||||||
|
if (attribute === null || operator === null || value === null) {
|
||||||
|
return null; // Invalid attribute or operator
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attribute === SkipRuleAttribute.Category
|
||||||
|
&& operator === SkipRuleOperator.Equal
|
||||||
|
&& !CompileConfig.categoryList.includes(value as string)) {
|
||||||
|
return null; // Invalid category value
|
||||||
|
} else if (attribute === SkipRuleAttribute.Source
|
||||||
|
&& operator === SkipRuleOperator.Equal
|
||||||
|
&& !["local", "youtube", "server"].includes(value as string)) {
|
||||||
|
return null; // Invalid category value
|
||||||
|
}
|
||||||
|
|
||||||
|
ruleSet.rules.push({
|
||||||
|
attribute,
|
||||||
|
operator,
|
||||||
|
value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure all rules were parsed
|
||||||
|
if (ruleTexts.length === 0 || !line.endsWith(ruleTexts[ruleTexts.length - 1][0])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Only continue if a rule has been defined
|
||||||
|
if (ruleSet.rules.length === 0) {
|
||||||
|
return null; // No rules defined yet
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (line.trim().toLowerCase()) {
|
||||||
|
case "disabled":
|
||||||
|
ruleSet.skipOption = CategorySkipOption.Disabled;
|
||||||
|
break;
|
||||||
|
case "show overlay":
|
||||||
|
ruleSet.skipOption = CategorySkipOption.ShowOverlay;
|
||||||
|
break;
|
||||||
|
case "manual skip":
|
||||||
|
ruleSet.skipOption = CategorySkipOption.ManualSkip;
|
||||||
|
break;
|
||||||
|
case "auto skip":
|
||||||
|
ruleSet.skipOption = CategorySkipOption.AutoSkip;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return null; // Invalid skip option
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ruleSet.rules.length > 0 && ruleSet.skipOption !== null) {
|
||||||
|
ruleSets.push(ruleSet);
|
||||||
|
} else if (ruleSet.rules.length > 0 || ruleSet.skipOption !== null) {
|
||||||
|
// Incomplete rule set
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ruleSets;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSkipRuleAttribute(attribute: string): SkipRuleAttribute | null {
|
||||||
|
if (attribute && Object.values(SkipRuleAttribute).includes(attribute as SkipRuleAttribute)) {
|
||||||
|
return attribute as SkipRuleAttribute;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSkipRuleOperator(operator: string): SkipRuleOperator | null {
|
||||||
|
if (operator && Object.values(SkipRuleOperator).includes(operator as SkipRuleOperator)) {
|
||||||
|
return operator as SkipRuleOperator;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSkipRuleValue(value: string): string | number | null {
|
||||||
|
if (!value) return null;
|
||||||
|
|
||||||
|
if (value.startsWith('"')) {
|
||||||
|
try {
|
||||||
|
return JSON.parse(value);
|
||||||
|
} catch (e) {
|
||||||
|
return null; // Invalid JSON string
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const numValue = Number(value);
|
||||||
|
if (!isNaN(numValue)) {
|
||||||
|
return numValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function configToText(config: AdvancedSkipRuleSet[]): string {
|
||||||
|
let result = "";
|
||||||
|
|
||||||
|
for (const ruleSet of config) {
|
||||||
|
if (ruleSet.comment) {
|
||||||
|
result += "// " + ruleSet.comment + "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
result += "if ";
|
||||||
|
let firstRule = true;
|
||||||
|
for (const rule of ruleSet.rules) {
|
||||||
|
if (!firstRule) {
|
||||||
|
result += " and ";
|
||||||
|
}
|
||||||
|
|
||||||
|
result += `${rule.attribute} ${rule.operator} ${JSON.stringify(rule.value)}`;
|
||||||
|
firstRule = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (ruleSet.skipOption) {
|
||||||
|
case CategorySkipOption.Disabled:
|
||||||
|
result += "\nDisabled";
|
||||||
|
break;
|
||||||
|
case CategorySkipOption.ShowOverlay:
|
||||||
|
result += "\nShow Overlay";
|
||||||
|
break;
|
||||||
|
case CategorySkipOption.ManualSkip:
|
||||||
|
result += "\nManual Skip";
|
||||||
|
break;
|
||||||
|
case CategorySkipOption.AutoSkip:
|
||||||
|
result += "\nAuto Skip";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return null; // Invalid skip option
|
||||||
|
}
|
||||||
|
|
||||||
|
result += "\n\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.trim();
|
||||||
|
}
|
||||||
@@ -8,6 +8,38 @@ export interface Permission {
|
|||||||
canSubmit: boolean;
|
canSubmit: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum SkipRuleAttribute {
|
||||||
|
StartTime = "startTime",
|
||||||
|
EndTime = "endTime",
|
||||||
|
Duration = "duration",
|
||||||
|
Category = "category",
|
||||||
|
Description = "description",
|
||||||
|
Source = "source"
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum SkipRuleOperator {
|
||||||
|
Less = "<",
|
||||||
|
LessOrEqual = "<=",
|
||||||
|
Greater = ">",
|
||||||
|
GreaterOrEqual = ">=",
|
||||||
|
Equal = "==",
|
||||||
|
NotEqual = "!=",
|
||||||
|
Contains = "*=",
|
||||||
|
Regex = "~="
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdvancedSkipRule {
|
||||||
|
attribute: SkipRuleAttribute;
|
||||||
|
operator: SkipRuleOperator;
|
||||||
|
value: string | number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdvancedSkipRuleSet {
|
||||||
|
rules: AdvancedSkipRule[];
|
||||||
|
skipOption: CategorySkipOption;
|
||||||
|
comment: string;
|
||||||
|
}
|
||||||
|
|
||||||
interface SBConfig {
|
interface SBConfig {
|
||||||
userID: string;
|
userID: string;
|
||||||
isVip: boolean;
|
isVip: boolean;
|
||||||
@@ -149,6 +181,8 @@ interface SBStorage {
|
|||||||
|
|
||||||
/* Contains unsubmitted segments that the user has created. */
|
/* Contains unsubmitted segments that the user has created. */
|
||||||
unsubmittedSegments: Record<string, SponsorTime[]>;
|
unsubmittedSegments: Record<string, SponsorTime[]>;
|
||||||
|
|
||||||
|
skipRules: AdvancedSkipRuleSet[];
|
||||||
}
|
}
|
||||||
|
|
||||||
class ConfigClass extends ProtoConfig<SBConfig, SBStorage> {
|
class ConfigClass extends ProtoConfig<SBConfig, SBStorage> {
|
||||||
@@ -168,6 +202,15 @@ class ConfigClass extends ProtoConfig<SBConfig, SBStorage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function migrateOldSyncFormats(config: SBConfig) {
|
function migrateOldSyncFormats(config: SBConfig) {
|
||||||
|
if (!config["changeChapterColor"]) {
|
||||||
|
config.barTypes["chapter"].color = "#ffd983";
|
||||||
|
config["changeChapterColor"] = true;
|
||||||
|
chrome.storage.sync.set({
|
||||||
|
"changeChapterColor": true,
|
||||||
|
"barTypes": config.barTypes
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (config["showZoomToFillError"]) {
|
if (config["showZoomToFillError"]) {
|
||||||
chrome.storage.sync.remove("showZoomToFillError");
|
chrome.storage.sync.remove("showZoomToFillError");
|
||||||
}
|
}
|
||||||
@@ -474,7 +517,7 @@ const syncDefaults = {
|
|||||||
opacity: "0.7"
|
opacity: "0.7"
|
||||||
},
|
},
|
||||||
"chapter": {
|
"chapter": {
|
||||||
color: "#fff",
|
color: "#ffd983",
|
||||||
opacity: "0"
|
opacity: "0"
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -485,7 +528,8 @@ const localDefaults = {
|
|||||||
navigationApiAvailable: null,
|
navigationApiAvailable: null,
|
||||||
alreadyInstalled: false,
|
alreadyInstalled: false,
|
||||||
|
|
||||||
unsubmittedSegments: {}
|
unsubmittedSegments: {},
|
||||||
|
skipRules: []
|
||||||
};
|
};
|
||||||
|
|
||||||
const Config = new ConfigClass(syncDefaults, localDefaults, migrateOldSyncFormats);
|
const Config = new ConfigClass(syncDefaults, localDefaults, migrateOldSyncFormats);
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ import { asyncRequestToServer } from "./utils/requests";
|
|||||||
import { isMobileControlsOpen } from "./utils/mobileUtils";
|
import { isMobileControlsOpen } from "./utils/mobileUtils";
|
||||||
import { defaultPreviewTime } from "./utils/constants";
|
import { defaultPreviewTime } from "./utils/constants";
|
||||||
import { onVideoPage } from "../maze-utils/src/pageInfo";
|
import { onVideoPage } from "../maze-utils/src/pageInfo";
|
||||||
import { getSegmentsForVideo } from "./utils/segmentData";
|
import { getCategoryDefaultSelection, getCategorySelection, getSegmentsForVideo } from "./utils/segmentData";
|
||||||
|
|
||||||
cleanPage();
|
cleanPage();
|
||||||
|
|
||||||
@@ -299,7 +299,6 @@ function messageListener(request: Message, sender: unknown, sendResponse: (respo
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
loopedChapter = {...utils.getSponsorTimeFromUUID(sponsorTimes, request.UUID)};
|
loopedChapter = {...utils.getSponsorTimeFromUUID(sponsorTimes, request.UUID)};
|
||||||
loopedChapter.actionType = ActionType.Skip;
|
|
||||||
loopedChapter.segment = [loopedChapter.segment[1], loopedChapter.segment[0]];
|
loopedChapter.segment = [loopedChapter.segment[1], loopedChapter.segment[0]];
|
||||||
break;
|
break;
|
||||||
case "importSegments": {
|
case "importSegments": {
|
||||||
@@ -312,7 +311,7 @@ function messageListener(request: Message, sender: unknown, sendResponse: (respo
|
|||||||
&& s.description === segment.description)) {
|
&& s.description === segment.description)) {
|
||||||
const hasChaptersPermission = (Config.config.showCategoryWithoutPermission
|
const hasChaptersPermission = (Config.config.showCategoryWithoutPermission
|
||||||
|| Config.config.permissions["chapter"]);
|
|| Config.config.permissions["chapter"]);
|
||||||
if (segment.category === "chapter" && (!utils.getCategorySelection("chapter") || !hasChaptersPermission)) {
|
if (segment.category === "chapter" && (!getCategoryDefaultSelection("chapter") || !hasChaptersPermission)) {
|
||||||
segment.category = "chooseACategory" as Category;
|
segment.category = "chooseACategory" as Category;
|
||||||
segment.actionType = ActionType.Skip;
|
segment.actionType = ActionType.Skip;
|
||||||
segment.description = "";
|
segment.description = "";
|
||||||
@@ -734,7 +733,7 @@ async function startSponsorSchedule(includeIntersectingSegments = false, current
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (utils.getCategorySelection(currentSkip.category)?.option === CategorySkipOption.ManualSkip
|
if (getCategorySelection(currentSkip)?.option === CategorySkipOption.ManualSkip
|
||||||
|| currentSkip.actionType === ActionType.Mute) {
|
|| currentSkip.actionType === ActionType.Mute) {
|
||||||
forcedSkipTime = skipTime[0] + 0.001;
|
forcedSkipTime = skipTime[0] + 0.001;
|
||||||
} else {
|
} else {
|
||||||
@@ -1355,7 +1354,7 @@ function startSkipScheduleCheckingForStartSponsors() {
|
|||||||
&& time.actionType === ActionType.Poi && time.hidden === SponsorHideType.Visible)
|
&& time.actionType === ActionType.Poi && time.hidden === SponsorHideType.Visible)
|
||||||
.sort((a, b) => b.segment[0] - a.segment[0]);
|
.sort((a, b) => b.segment[0] - a.segment[0]);
|
||||||
for (const time of poiSegments) {
|
for (const time of poiSegments) {
|
||||||
const skipOption = utils.getCategorySelection(time.category)?.option;
|
const skipOption = getCategorySelection(time)?.option;
|
||||||
if (skipOption !== CategorySkipOption.ShowOverlay) {
|
if (skipOption !== CategorySkipOption.ShowOverlay) {
|
||||||
skipToTime({
|
skipToTime({
|
||||||
v: getVideo(),
|
v: getVideo(),
|
||||||
@@ -1504,12 +1503,12 @@ function getNextSkipIndex(currentTime: number, includeIntersectingSegments: bool
|
|||||||
{array: ScheduledTime[]; index: number; endIndex: number; extraIndexes: number[]; openNotice: boolean} {
|
{array: ScheduledTime[]; index: number; endIndex: number; extraIndexes: number[]; openNotice: boolean} {
|
||||||
|
|
||||||
const autoSkipSorter = (segment: ScheduledTime) => {
|
const autoSkipSorter = (segment: ScheduledTime) => {
|
||||||
const skipOption = utils.getCategorySelection(segment.category)?.option;
|
const skipOption = getCategorySelection(segment)?.option;
|
||||||
if (segment.hidden !== SponsorHideType.Visible) {
|
if (segment.hidden !== SponsorHideType.Visible) {
|
||||||
// Hidden segments sometimes end up here if another segment is at the same time, use them last
|
// Hidden segments sometimes end up here if another segment is at the same time, use them last
|
||||||
return 3;
|
return 3;
|
||||||
} else if ((skipOption === CategorySkipOption.AutoSkip || shouldAutoSkip(segment))
|
} else if ((skipOption === CategorySkipOption.AutoSkip || shouldAutoSkip(segment))
|
||||||
&& segment.actionType === ActionType.Skip) {
|
&& (segment.actionType === ActionType.Skip || segment.actionType === ActionType.Chapter)) {
|
||||||
return 0;
|
return 0;
|
||||||
} else if (skipOption !== CategorySkipOption.ShowOverlay) {
|
} else if (skipOption !== CategorySkipOption.ShowOverlay) {
|
||||||
return 1;
|
return 1;
|
||||||
@@ -1728,6 +1727,7 @@ function skipToTime({v, skipTime, skippingSegments, openNotice, forceAutoSkip, u
|
|||||||
&& getCurrentTime() !== skipTime[1]) {
|
&& getCurrentTime() !== skipTime[1]) {
|
||||||
switch(skippingSegments[0].actionType) {
|
switch(skippingSegments[0].actionType) {
|
||||||
case ActionType.Poi:
|
case ActionType.Poi:
|
||||||
|
case ActionType.Chapter:
|
||||||
case ActionType.Skip: {
|
case ActionType.Skip: {
|
||||||
// Fix for looped videos not working when skipping to the end #426
|
// Fix for looped videos not working when skipping to the end #426
|
||||||
// for some reason you also can't skip to 1 second before the end
|
// for some reason you also can't skip to 1 second before the end
|
||||||
@@ -1850,7 +1850,7 @@ function unskipSponsorTime(segment: SponsorTime, unskipTime: number = null, forc
|
|||||||
videoMuted = false;
|
videoMuted = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (forceSeek || segment.actionType === ActionType.Skip || voteNotice) {
|
if (forceSeek || segment.actionType === ActionType.Skip || segment.actionType === ActionType.Chapter || voteNotice) {
|
||||||
//add a tiny bit of time to make sure it is not skipped again
|
//add a tiny bit of time to make sure it is not skipped again
|
||||||
setCurrentTime(unskipTime ?? segment.segment[0] + 0.001);
|
setCurrentTime(unskipTime ?? segment.segment[0] + 0.001);
|
||||||
}
|
}
|
||||||
@@ -1921,7 +1921,7 @@ function shouldAutoSkip(segment: SponsorTime): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (!Config.config.manualSkipOnFullVideo || !sponsorTimes?.some((s) => s.category === segment.category && s.actionType === ActionType.Full))
|
return (!Config.config.manualSkipOnFullVideo || !sponsorTimes?.some((s) => s.category === segment.category && s.actionType === ActionType.Full))
|
||||||
&& (utils.getCategorySelection(segment.category)?.option === CategorySkipOption.AutoSkip ||
|
&& (getCategorySelection(segment)?.option === CategorySkipOption.AutoSkip ||
|
||||||
(Config.config.autoSkipOnMusicVideos && canSkipNonMusic && sponsorTimes?.some((s) => s.category === "music_offtopic")
|
(Config.config.autoSkipOnMusicVideos && canSkipNonMusic && sponsorTimes?.some((s) => s.category === "music_offtopic")
|
||||||
&& segment.actionType === ActionType.Skip)
|
&& segment.actionType === ActionType.Skip)
|
||||||
|| sponsorTimesSubmitting.some((s) => s.segment === segment.segment))
|
|| sponsorTimesSubmitting.some((s) => s.segment === segment.segment))
|
||||||
@@ -1930,15 +1930,14 @@ function shouldAutoSkip(segment: SponsorTime): boolean {
|
|||||||
|
|
||||||
function shouldSkip(segment: SponsorTime): boolean {
|
function shouldSkip(segment: SponsorTime): boolean {
|
||||||
return (segment.actionType !== ActionType.Full
|
return (segment.actionType !== ActionType.Full
|
||||||
&& segment.source !== SponsorSourceType.YouTube
|
&& getCategorySelection(segment)?.option > CategorySkipOption.ShowOverlay)
|
||||||
&& utils.getCategorySelection(segment.category)?.option !== CategorySkipOption.ShowOverlay)
|
|
||||||
|| (Config.config.autoSkipOnMusicVideos && sponsorTimes?.some((s) => s.category === "music_offtopic")
|
|| (Config.config.autoSkipOnMusicVideos && sponsorTimes?.some((s) => s.category === "music_offtopic")
|
||||||
&& segment.actionType === ActionType.Skip)
|
&& segment.actionType === ActionType.Skip)
|
||||||
|| isLoopedChapter(segment);
|
|| isLoopedChapter(segment);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isLoopedChapter(segment: SponsorTime) :boolean{
|
function isLoopedChapter(segment: SponsorTime): boolean{
|
||||||
return !!segment && !!loopedChapter && segment.actionType === ActionType.Skip && segment.segment[1] != undefined
|
return !!segment && !!loopedChapter && segment.segment[1] != undefined
|
||||||
&& segment.segment[0] === loopedChapter.segment[0] && segment.segment[1] === loopedChapter.segment[1];
|
&& segment.segment[0] === loopedChapter.segment[0] && segment.segment[1] === loopedChapter.segment[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { getHash } from "../maze-utils/src/hash";
|
|||||||
import { isFirefoxOrSafari } from "../maze-utils/src";
|
import { isFirefoxOrSafari } from "../maze-utils/src";
|
||||||
import { isDeArrowInstalled } from "./utils/crossExtension";
|
import { isDeArrowInstalled } from "./utils/crossExtension";
|
||||||
import { asyncRequestToServer } from "./utils/requests";
|
import { asyncRequestToServer } from "./utils/requests";
|
||||||
|
import AdvancedSkipOptions from "./render/AdvancedSkipOptions";
|
||||||
const utils = new Utils();
|
const utils = new Utils();
|
||||||
let embed = false;
|
let embed = false;
|
||||||
|
|
||||||
@@ -350,6 +351,9 @@ async function init() {
|
|||||||
case "react-CategoryChooserComponent":
|
case "react-CategoryChooserComponent":
|
||||||
categoryChoosers.push(new CategoryChooser(optionsElements[i]));
|
categoryChoosers.push(new CategoryChooser(optionsElements[i]));
|
||||||
break;
|
break;
|
||||||
|
case "react-AdvancedSkipOptionsComponent":
|
||||||
|
new AdvancedSkipOptions(optionsElements[i]);
|
||||||
|
break;
|
||||||
case "react-UnsubmittedVideosComponent":
|
case "react-UnsubmittedVideosComponent":
|
||||||
unsubmittedVideos.push(new UnsubmittedVideos(optionsElements[i]));
|
unsubmittedVideos.push(new UnsubmittedVideos(optionsElements[i]));
|
||||||
break;
|
break;
|
||||||
|
|||||||
15
src/render/AdvancedSkipOptions.tsx
Normal file
15
src/render/AdvancedSkipOptions.tsx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import { createRoot } from 'react-dom/client';
|
||||||
|
|
||||||
|
import { AdvancedSkipOptionsComponent } from "../components/options/AdvancedSkipOptionsComponent";
|
||||||
|
|
||||||
|
class AdvancedSkipOptions {
|
||||||
|
constructor(element: Element) {
|
||||||
|
const root = createRoot(element);
|
||||||
|
root.render(
|
||||||
|
<AdvancedSkipOptionsComponent />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AdvancedSkipOptions;
|
||||||
12
src/utils.ts
12
src/utils.ts
@@ -1,5 +1,5 @@
|
|||||||
import Config, { VideoDownvotes } from "./config";
|
import Config, { VideoDownvotes } from "./config";
|
||||||
import { CategorySelection, SponsorTime, BackgroundScriptContainer, Registration, VideoID, SponsorHideType, CategorySkipOption } from "./types";
|
import { SponsorTime, BackgroundScriptContainer, Registration, VideoID, SponsorHideType } from "./types";
|
||||||
|
|
||||||
import { getHash, HashedValue } from "../maze-utils/src/hash";
|
import { getHash, HashedValue } from "../maze-utils/src/hash";
|
||||||
import { waitFor } from "../maze-utils/src";
|
import { waitFor } from "../maze-utils/src";
|
||||||
@@ -211,15 +211,6 @@ export default class Utils {
|
|||||||
return sponsorTimes[this.getSponsorIndexFromUUID(sponsorTimes, UUID)];
|
return sponsorTimes[this.getSponsorIndexFromUUID(sponsorTimes, UUID)];
|
||||||
}
|
}
|
||||||
|
|
||||||
getCategorySelection(category: string): CategorySelection {
|
|
||||||
for (const selection of Config.config.categorySelections) {
|
|
||||||
if (selection.name === category) {
|
|
||||||
return selection;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return { name: category, option: CategorySkipOption.Disabled} as CategorySelection;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {String[]} Domains in regex form
|
* @returns {String[]} Domains in regex form
|
||||||
*/
|
*/
|
||||||
@@ -321,7 +312,6 @@ export default class Utils {
|
|||||||
|
|
||||||
allDownvotes[hashedVideoID] = currentVideoData;
|
allDownvotes[hashedVideoID] = currentVideoData;
|
||||||
}
|
}
|
||||||
console.log(allDownvotes)
|
|
||||||
|
|
||||||
const entries = Object.entries(allDownvotes);
|
const entries = Object.entries(allDownvotes);
|
||||||
if (entries.length > 10000) {
|
if (entries.length > 10000) {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ export function getSkippingText(segments: SponsorTime[], autoSkip: boolean): str
|
|||||||
if (autoSkip) {
|
if (autoSkip) {
|
||||||
let messageId = "";
|
let messageId = "";
|
||||||
switch (segments[0].actionType) {
|
switch (segments[0].actionType) {
|
||||||
|
case ActionType.Chapter:
|
||||||
case ActionType.Skip:
|
case ActionType.Skip:
|
||||||
messageId = "skipped";
|
messageId = "skipped";
|
||||||
break;
|
break;
|
||||||
@@ -21,6 +22,7 @@ export function getSkippingText(segments: SponsorTime[], autoSkip: boolean): str
|
|||||||
} else {
|
} else {
|
||||||
let messageId = "";
|
let messageId = "";
|
||||||
switch (segments[0].actionType) {
|
switch (segments[0].actionType) {
|
||||||
|
case ActionType.Chapter:
|
||||||
case ActionType.Skip:
|
case ActionType.Skip:
|
||||||
messageId = "skip_category";
|
messageId = "skip_category";
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import { DataCache } from "../../maze-utils/src/cache";
|
import { DataCache } from "../../maze-utils/src/cache";
|
||||||
import { getHash, HashedValue } from "../../maze-utils/src/hash";
|
import { getHash, HashedValue } from "../../maze-utils/src/hash";
|
||||||
import Config from "../config";
|
import Config, { AdvancedSkipRule, SkipRuleAttribute, SkipRuleOperator } from "../config";
|
||||||
import * as CompileConfig from "../../config.json";
|
import * as CompileConfig from "../../config.json";
|
||||||
import { ActionType, ActionTypes, SponsorSourceType, SponsorTime, VideoID } from "../types";
|
import { ActionType, ActionTypes, CategorySelection, CategorySkipOption, SponsorSourceType, SponsorTime, VideoID } from "../types";
|
||||||
import { getHashParams } from "./pageUtils";
|
import { getHashParams } from "./pageUtils";
|
||||||
import { asyncRequestToServer } from "./requests";
|
import { asyncRequestToServer } from "./requests";
|
||||||
import { extensionUserAgent } from "../../maze-utils/src";
|
import { extensionUserAgent } from "../../maze-utils/src";
|
||||||
|
import { VideoLabelsCacheData } from "./videoLabels";
|
||||||
|
|
||||||
const segmentDataCache = new DataCache<VideoID, SegmentResponse>(() => {
|
const segmentDataCache = new DataCache<VideoID, SegmentResponse>(() => {
|
||||||
return {
|
return {
|
||||||
@@ -44,8 +45,6 @@ export async function getSegmentsForVideo(videoID: VideoID, ignoreCache: boolean
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function fetchSegmentsForVideo(videoID: VideoID): Promise<SegmentResponse> {
|
async function fetchSegmentsForVideo(videoID: VideoID): Promise<SegmentResponse> {
|
||||||
const categories: string[] = Config.config.categorySelections.map((category) => category.name);
|
|
||||||
|
|
||||||
const extraRequestData: Record<string, unknown> = {};
|
const extraRequestData: Record<string, unknown> = {};
|
||||||
const hashParams = getHashParams();
|
const hashParams = getHashParams();
|
||||||
if (hashParams.requiredSegment) extraRequestData.requiredSegment = hashParams.requiredSegment;
|
if (hashParams.requiredSegment) extraRequestData.requiredSegment = hashParams.requiredSegment;
|
||||||
@@ -67,7 +66,8 @@ async function fetchSegmentsForVideo(videoID: VideoID): Promise<SegmentResponse>
|
|||||||
const receivedSegments: SponsorTime[] = JSON.parse(response.responseText)
|
const receivedSegments: SponsorTime[] = JSON.parse(response.responseText)
|
||||||
?.filter((video) => video.videoID === videoID)
|
?.filter((video) => video.videoID === videoID)
|
||||||
?.map((video) => video.segments)?.[0]
|
?.map((video) => video.segments)?.[0]
|
||||||
?.filter((segment) => enabledActionTypes.includes(segment.actionType) && categories.includes(segment.category))
|
?.filter((segment) => enabledActionTypes.includes(segment.actionType)
|
||||||
|
&& getCategorySelection(segment).option !== CategorySkipOption.Disabled)
|
||||||
?.map((segment) => ({
|
?.map((segment) => ({
|
||||||
...segment,
|
...segment,
|
||||||
source: SponsorSourceType.Server
|
source: SponsorSourceType.Server
|
||||||
@@ -105,3 +105,80 @@ function getEnabledActionTypes(forceFullVideo = false): ActionType[] {
|
|||||||
|
|
||||||
return actionTypes;
|
return actionTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getCategorySelection(segment: SponsorTime | VideoLabelsCacheData): CategorySelection {
|
||||||
|
for (const ruleSet of Config.local.skipRules) {
|
||||||
|
if (ruleSet.rules.every((rule) => isSkipRulePassing(segment, rule))) {
|
||||||
|
return { name: segment.category, option: ruleSet.skipOption } as CategorySelection;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const selection of Config.config.categorySelections) {
|
||||||
|
if (selection.name === segment.category) {
|
||||||
|
return selection;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { name: segment.category, option: CategorySkipOption.Disabled} as CategorySelection;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSkipRuleValue(segment: SponsorTime | VideoLabelsCacheData, rule: AdvancedSkipRule): string | number | undefined {
|
||||||
|
switch (rule.attribute) {
|
||||||
|
case SkipRuleAttribute.StartTime:
|
||||||
|
return (segment as SponsorTime).segment?.[0];
|
||||||
|
case SkipRuleAttribute.EndTime:
|
||||||
|
return (segment as SponsorTime).segment?.[1];
|
||||||
|
case SkipRuleAttribute.Duration:
|
||||||
|
return (segment as SponsorTime).segment?.[1] - (segment as SponsorTime).segment?.[0];
|
||||||
|
case SkipRuleAttribute.Category:
|
||||||
|
return segment.category;
|
||||||
|
case SkipRuleAttribute.Description:
|
||||||
|
return (segment as SponsorTime).description || "";
|
||||||
|
case SkipRuleAttribute.Source:
|
||||||
|
switch ((segment as SponsorTime).source) {
|
||||||
|
case SponsorSourceType.Local:
|
||||||
|
return "local";
|
||||||
|
case SponsorSourceType.YouTube:
|
||||||
|
return "youtube";
|
||||||
|
case SponsorSourceType.Server:
|
||||||
|
return "server";
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSkipRulePassing(segment: SponsorTime | VideoLabelsCacheData, rule: AdvancedSkipRule): boolean {
|
||||||
|
const value = getSkipRuleValue(segment, rule);
|
||||||
|
|
||||||
|
switch (rule.operator) {
|
||||||
|
case SkipRuleOperator.Less:
|
||||||
|
return typeof value === "number" && value < (rule.value as number);
|
||||||
|
case SkipRuleOperator.LessOrEqual:
|
||||||
|
return typeof value === "number" && value <= (rule.value as number);
|
||||||
|
case SkipRuleOperator.Greater:
|
||||||
|
return typeof value === "number" && value > (rule.value as number);
|
||||||
|
case SkipRuleOperator.GreaterOrEqual:
|
||||||
|
return typeof value === "number" && value >= (rule.value as number);
|
||||||
|
case SkipRuleOperator.Equal:
|
||||||
|
return value === rule.value;
|
||||||
|
case SkipRuleOperator.NotEqual:
|
||||||
|
return value !== rule.value;
|
||||||
|
case SkipRuleOperator.Contains:
|
||||||
|
return String(value).includes(String(rule.value));
|
||||||
|
case SkipRuleOperator.Regex:
|
||||||
|
return new RegExp(rule.value as string).test(String(value));
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCategoryDefaultSelection(category: string): CategorySelection {
|
||||||
|
for (const selection of Config.config.categorySelections) {
|
||||||
|
if (selection.name === category) {
|
||||||
|
return selection;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { name: category, option: CategorySkipOption.Disabled} as CategorySelection;
|
||||||
|
}
|
||||||
@@ -1,12 +1,10 @@
|
|||||||
import { Category, CategorySkipOption, VideoID } from "../types";
|
import { Category, CategorySkipOption, VideoID } from "../types";
|
||||||
import { getHash } from "../../maze-utils/src/hash";
|
import { getHash } from "../../maze-utils/src/hash";
|
||||||
import Utils from "../utils";
|
|
||||||
import { logWarn } from "./logger";
|
import { logWarn } from "./logger";
|
||||||
import { asyncRequestToServer } from "./requests";
|
import { asyncRequestToServer } from "./requests";
|
||||||
|
import { getCategorySelection } from "./segmentData";
|
||||||
|
|
||||||
const utils = new Utils();
|
export interface VideoLabelsCacheData {
|
||||||
|
|
||||||
interface VideoLabelsCacheData {
|
|
||||||
category: Category;
|
category: Category;
|
||||||
hasStartSegment: boolean;
|
hasStartSegment: boolean;
|
||||||
}
|
}
|
||||||
@@ -68,7 +66,7 @@ export async function getVideoLabel(videoID: VideoID): Promise<Category | null>
|
|||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
const category = result.videos[videoID]?.category;
|
const category = result.videos[videoID]?.category;
|
||||||
if (category && utils.getCategorySelection(category).option !== CategorySkipOption.Disabled) {
|
if (category && getCategorySelection(result.videos[videoID]).option !== CategorySkipOption.Disabled) {
|
||||||
return category;
|
return category;
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
Reference in New Issue
Block a user