mirror of
https://github.com/ajayyy/SponsorBlock.git
synced 2025-12-06 11:37:02 +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 {
|
||||
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>
|
||||
|
||||
<div data-type="react-AdvancedSkipOptionsComponent"></div>
|
||||
|
||||
<div data-type="toggle" data-sync="forceChannelCheck">
|
||||
<div class="switch-container">
|
||||
<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;
|
||||
}
|
||||
|
||||
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 {
|
||||
userID: string;
|
||||
isVip: boolean;
|
||||
@@ -149,6 +181,8 @@ interface SBStorage {
|
||||
|
||||
/* Contains unsubmitted segments that the user has created. */
|
||||
unsubmittedSegments: Record<string, SponsorTime[]>;
|
||||
|
||||
skipRules: AdvancedSkipRuleSet[];
|
||||
}
|
||||
|
||||
class ConfigClass extends ProtoConfig<SBConfig, SBStorage> {
|
||||
@@ -168,6 +202,15 @@ class ConfigClass extends ProtoConfig<SBConfig, SBStorage> {
|
||||
}
|
||||
|
||||
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"]) {
|
||||
chrome.storage.sync.remove("showZoomToFillError");
|
||||
}
|
||||
@@ -474,7 +517,7 @@ const syncDefaults = {
|
||||
opacity: "0.7"
|
||||
},
|
||||
"chapter": {
|
||||
color: "#fff",
|
||||
color: "#ffd983",
|
||||
opacity: "0"
|
||||
},
|
||||
}
|
||||
@@ -485,7 +528,8 @@ const localDefaults = {
|
||||
navigationApiAvailable: null,
|
||||
alreadyInstalled: false,
|
||||
|
||||
unsubmittedSegments: {}
|
||||
unsubmittedSegments: {},
|
||||
skipRules: []
|
||||
};
|
||||
|
||||
const Config = new ConfigClass(syncDefaults, localDefaults, migrateOldSyncFormats);
|
||||
|
||||
@@ -51,7 +51,7 @@ import { asyncRequestToServer } from "./utils/requests";
|
||||
import { isMobileControlsOpen } from "./utils/mobileUtils";
|
||||
import { defaultPreviewTime } from "./utils/constants";
|
||||
import { onVideoPage } from "../maze-utils/src/pageInfo";
|
||||
import { getSegmentsForVideo } from "./utils/segmentData";
|
||||
import { getCategoryDefaultSelection, getCategorySelection, getSegmentsForVideo } from "./utils/segmentData";
|
||||
|
||||
cleanPage();
|
||||
|
||||
@@ -299,7 +299,6 @@ function messageListener(request: Message, sender: unknown, sendResponse: (respo
|
||||
break;
|
||||
}
|
||||
loopedChapter = {...utils.getSponsorTimeFromUUID(sponsorTimes, request.UUID)};
|
||||
loopedChapter.actionType = ActionType.Skip;
|
||||
loopedChapter.segment = [loopedChapter.segment[1], loopedChapter.segment[0]];
|
||||
break;
|
||||
case "importSegments": {
|
||||
@@ -312,7 +311,7 @@ function messageListener(request: Message, sender: unknown, sendResponse: (respo
|
||||
&& s.description === segment.description)) {
|
||||
const hasChaptersPermission = (Config.config.showCategoryWithoutPermission
|
||||
|| 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.actionType = ActionType.Skip;
|
||||
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) {
|
||||
forcedSkipTime = skipTime[0] + 0.001;
|
||||
} else {
|
||||
@@ -1355,7 +1354,7 @@ function startSkipScheduleCheckingForStartSponsors() {
|
||||
&& time.actionType === ActionType.Poi && time.hidden === SponsorHideType.Visible)
|
||||
.sort((a, b) => b.segment[0] - a.segment[0]);
|
||||
for (const time of poiSegments) {
|
||||
const skipOption = utils.getCategorySelection(time.category)?.option;
|
||||
const skipOption = getCategorySelection(time)?.option;
|
||||
if (skipOption !== CategorySkipOption.ShowOverlay) {
|
||||
skipToTime({
|
||||
v: getVideo(),
|
||||
@@ -1504,12 +1503,12 @@ function getNextSkipIndex(currentTime: number, includeIntersectingSegments: bool
|
||||
{array: ScheduledTime[]; index: number; endIndex: number; extraIndexes: number[]; openNotice: boolean} {
|
||||
|
||||
const autoSkipSorter = (segment: ScheduledTime) => {
|
||||
const skipOption = utils.getCategorySelection(segment.category)?.option;
|
||||
const skipOption = getCategorySelection(segment)?.option;
|
||||
if (segment.hidden !== SponsorHideType.Visible) {
|
||||
// Hidden segments sometimes end up here if another segment is at the same time, use them last
|
||||
return 3;
|
||||
} else if ((skipOption === CategorySkipOption.AutoSkip || shouldAutoSkip(segment))
|
||||
&& segment.actionType === ActionType.Skip) {
|
||||
&& (segment.actionType === ActionType.Skip || segment.actionType === ActionType.Chapter)) {
|
||||
return 0;
|
||||
} else if (skipOption !== CategorySkipOption.ShowOverlay) {
|
||||
return 1;
|
||||
@@ -1728,6 +1727,7 @@ function skipToTime({v, skipTime, skippingSegments, openNotice, forceAutoSkip, u
|
||||
&& getCurrentTime() !== skipTime[1]) {
|
||||
switch(skippingSegments[0].actionType) {
|
||||
case ActionType.Poi:
|
||||
case ActionType.Chapter:
|
||||
case ActionType.Skip: {
|
||||
// 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
|
||||
@@ -1850,7 +1850,7 @@ function unskipSponsorTime(segment: SponsorTime, unskipTime: number = null, forc
|
||||
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
|
||||
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))
|
||||
&& (utils.getCategorySelection(segment.category)?.option === CategorySkipOption.AutoSkip ||
|
||||
&& (getCategorySelection(segment)?.option === CategorySkipOption.AutoSkip ||
|
||||
(Config.config.autoSkipOnMusicVideos && canSkipNonMusic && sponsorTimes?.some((s) => s.category === "music_offtopic")
|
||||
&& segment.actionType === ActionType.Skip)
|
||||
|| sponsorTimesSubmitting.some((s) => s.segment === segment.segment))
|
||||
@@ -1930,15 +1930,14 @@ function shouldAutoSkip(segment: SponsorTime): boolean {
|
||||
|
||||
function shouldSkip(segment: SponsorTime): boolean {
|
||||
return (segment.actionType !== ActionType.Full
|
||||
&& segment.source !== SponsorSourceType.YouTube
|
||||
&& utils.getCategorySelection(segment.category)?.option !== CategorySkipOption.ShowOverlay)
|
||||
&& getCategorySelection(segment)?.option > CategorySkipOption.ShowOverlay)
|
||||
|| (Config.config.autoSkipOnMusicVideos && sponsorTimes?.some((s) => s.category === "music_offtopic")
|
||||
&& segment.actionType === ActionType.Skip)
|
||||
|| isLoopedChapter(segment);
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import { getHash } from "../maze-utils/src/hash";
|
||||
import { isFirefoxOrSafari } from "../maze-utils/src";
|
||||
import { isDeArrowInstalled } from "./utils/crossExtension";
|
||||
import { asyncRequestToServer } from "./utils/requests";
|
||||
import AdvancedSkipOptions from "./render/AdvancedSkipOptions";
|
||||
const utils = new Utils();
|
||||
let embed = false;
|
||||
|
||||
@@ -350,6 +351,9 @@ async function init() {
|
||||
case "react-CategoryChooserComponent":
|
||||
categoryChoosers.push(new CategoryChooser(optionsElements[i]));
|
||||
break;
|
||||
case "react-AdvancedSkipOptionsComponent":
|
||||
new AdvancedSkipOptions(optionsElements[i]);
|
||||
break;
|
||||
case "react-UnsubmittedVideosComponent":
|
||||
unsubmittedVideos.push(new UnsubmittedVideos(optionsElements[i]));
|
||||
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 { 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 { waitFor } from "../maze-utils/src";
|
||||
@@ -211,15 +211,6 @@ export default class Utils {
|
||||
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
|
||||
*/
|
||||
@@ -321,7 +312,6 @@ export default class Utils {
|
||||
|
||||
allDownvotes[hashedVideoID] = currentVideoData;
|
||||
}
|
||||
console.log(allDownvotes)
|
||||
|
||||
const entries = Object.entries(allDownvotes);
|
||||
if (entries.length > 10000) {
|
||||
|
||||
@@ -6,6 +6,7 @@ export function getSkippingText(segments: SponsorTime[], autoSkip: boolean): str
|
||||
if (autoSkip) {
|
||||
let messageId = "";
|
||||
switch (segments[0].actionType) {
|
||||
case ActionType.Chapter:
|
||||
case ActionType.Skip:
|
||||
messageId = "skipped";
|
||||
break;
|
||||
@@ -21,6 +22,7 @@ export function getSkippingText(segments: SponsorTime[], autoSkip: boolean): str
|
||||
} else {
|
||||
let messageId = "";
|
||||
switch (segments[0].actionType) {
|
||||
case ActionType.Chapter:
|
||||
case ActionType.Skip:
|
||||
messageId = "skip_category";
|
||||
break;
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { DataCache } from "../../maze-utils/src/cache";
|
||||
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 { ActionType, ActionTypes, SponsorSourceType, SponsorTime, VideoID } from "../types";
|
||||
import { ActionType, ActionTypes, CategorySelection, CategorySkipOption, SponsorSourceType, SponsorTime, VideoID } from "../types";
|
||||
import { getHashParams } from "./pageUtils";
|
||||
import { asyncRequestToServer } from "./requests";
|
||||
import { extensionUserAgent } from "../../maze-utils/src";
|
||||
import { VideoLabelsCacheData } from "./videoLabels";
|
||||
|
||||
const segmentDataCache = new DataCache<VideoID, SegmentResponse>(() => {
|
||||
return {
|
||||
@@ -44,8 +45,6 @@ export async function getSegmentsForVideo(videoID: VideoID, ignoreCache: boolean
|
||||
}
|
||||
|
||||
async function fetchSegmentsForVideo(videoID: VideoID): Promise<SegmentResponse> {
|
||||
const categories: string[] = Config.config.categorySelections.map((category) => category.name);
|
||||
|
||||
const extraRequestData: Record<string, unknown> = {};
|
||||
const hashParams = getHashParams();
|
||||
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)
|
||||
?.filter((video) => video.videoID === videoID)
|
||||
?.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) => ({
|
||||
...segment,
|
||||
source: SponsorSourceType.Server
|
||||
@@ -105,3 +105,80 @@ function getEnabledActionTypes(forceFullVideo = false): ActionType[] {
|
||||
|
||||
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 { getHash } from "../../maze-utils/src/hash";
|
||||
import Utils from "../utils";
|
||||
import { logWarn } from "./logger";
|
||||
import { asyncRequestToServer } from "./requests";
|
||||
import { getCategorySelection } from "./segmentData";
|
||||
|
||||
const utils = new Utils();
|
||||
|
||||
interface VideoLabelsCacheData {
|
||||
export interface VideoLabelsCacheData {
|
||||
category: Category;
|
||||
hasStartSegment: boolean;
|
||||
}
|
||||
@@ -68,7 +66,7 @@ export async function getVideoLabel(videoID: VideoID): Promise<Category | null>
|
||||
|
||||
if (result) {
|
||||
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;
|
||||
} else {
|
||||
return null;
|
||||
|
||||
Reference in New Issue
Block a user