Skip to content

Commit bd0be90

Browse files
authored
Merge pull request ajayyy#2342 from mschae23/rules-parser
Rewrite advanced skip options parser to add "or" operator
2 parents 590281f + f80ddc8 commit bd0be90

File tree

3 files changed

+888
-233
lines changed

3 files changed

+888
-233
lines changed
Lines changed: 7 additions & 203 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
import * as React from "react";
2-
import * as CompileConfig from "../../../config.json";
32

43
import Config from "../../config";
5-
import { AdvancedSkipRuleSet, SkipRuleAttribute, SkipRuleOperator } from "../../utils/skipRule";
6-
import { ActionType, ActionTypes, CategorySkipOption } from "../../types";
4+
import { AdvancedSkipRule, configToText, parseConfig, } from "../../utils/skipRule";
75

86
let configSaveTimeout: NodeJS.Timeout | null = null;
97

@@ -64,210 +62,16 @@ export function AdvancedSkipOptionsComponent() {
6462
);
6563
}
6664

67-
function compileConfig(config: string): AdvancedSkipRuleSet[] | null {
68-
const ruleSets: AdvancedSkipRuleSet[] = [];
65+
function compileConfig(config: string): AdvancedSkipRule[] | null {
66+
const { rules, errors } = parseConfig(config);
6967

70-
let ruleSet: AdvancedSkipRuleSet = {
71-
rules: [],
72-
skipOption: null,
73-
comment: ""
74-
};
75-
76-
for (const line of config.split("\n")) {
77-
if (line.trim().length === 0) {
78-
// Skip empty lines
79-
continue;
80-
}
81-
82-
const comment = line.match(/^\s*\/\/(.+)$/);
83-
if (comment) {
84-
if (ruleSet.rules.length > 0) {
85-
// Rule has already been created, add it to list if valid
86-
if (ruleSet.skipOption !== null && ruleSet.rules.length > 0) {
87-
ruleSets.push(ruleSet);
88-
89-
ruleSet = {
90-
rules: [],
91-
skipOption: null,
92-
comment: ""
93-
};
94-
} else {
95-
return null;
96-
}
97-
}
98-
99-
if (ruleSet.comment.length > 0) {
100-
ruleSet.comment += "; ";
101-
}
102-
103-
ruleSet.comment += comment[1].trim();
104-
105-
// Skip comment lines
106-
continue;
107-
} else if (line.startsWith("if ")) {
108-
if (ruleSet.rules.length > 0) {
109-
// Rule has already been created, add it to list if valid
110-
if (ruleSet.skipOption !== null && ruleSet.rules.length > 0) {
111-
ruleSets.push(ruleSet);
112-
113-
ruleSet = {
114-
rules: [],
115-
skipOption: null,
116-
comment: ""
117-
};
118-
} else {
119-
return null;
120-
}
121-
}
122-
123-
const ruleTexts = [...line.matchAll(/\S+ \S+ (?:"[^"\\]*(?:\\.[^"\\]*)*"|\d+)(?= and |$)/g)];
124-
for (const ruleText of ruleTexts) {
125-
if (!ruleText[0]) return null;
126-
127-
const ruleParts = ruleText[0].match(/(\S+) (\S+) ("[^"\\]*(?:\\.[^"\\]*)*"|\d+)/);
128-
if (ruleParts.length !== 4) {
129-
return null; // Invalid rule format
130-
}
131-
132-
const attribute = getSkipRuleAttribute(ruleParts[1]);
133-
const operator = getSkipRuleOperator(ruleParts[2]);
134-
const value = getSkipRuleValue(ruleParts[3]);
135-
if (attribute === null || operator === null || value === null) {
136-
return null; // Invalid attribute or operator
137-
}
138-
139-
if ([SkipRuleOperator.Equal, SkipRuleOperator.NotEqual].includes(operator)) {
140-
if (attribute === SkipRuleAttribute.Category
141-
&& !CompileConfig.categoryList.includes(value as string)) {
142-
return null; // Invalid category value
143-
} else if (attribute === SkipRuleAttribute.ActionType
144-
&& !ActionTypes.includes(value as ActionType)) {
145-
return null; // Invalid category value
146-
} else if (attribute === SkipRuleAttribute.Source
147-
&& !["local", "youtube", "autogenerated", "server"].includes(value as string)) {
148-
return null; // Invalid category value
149-
}
150-
}
151-
152-
ruleSet.rules.push({
153-
attribute,
154-
operator,
155-
value
156-
});
157-
}
158-
159-
// Make sure all rules were parsed
160-
if (ruleTexts.length === 0 || !line.endsWith(ruleTexts[ruleTexts.length - 1][0])) {
161-
return null;
162-
}
163-
} else {
164-
// Only continue if a rule has been defined
165-
if (ruleSet.rules.length === 0) {
166-
return null; // No rules defined yet
167-
}
168-
169-
switch (line.trim().toLowerCase()) {
170-
case "disabled":
171-
ruleSet.skipOption = CategorySkipOption.Disabled;
172-
break;
173-
case "show overlay":
174-
ruleSet.skipOption = CategorySkipOption.ShowOverlay;
175-
break;
176-
case "manual skip":
177-
ruleSet.skipOption = CategorySkipOption.ManualSkip;
178-
break;
179-
case "auto skip":
180-
ruleSet.skipOption = CategorySkipOption.AutoSkip;
181-
break;
182-
default:
183-
return null; // Invalid skip option
184-
}
185-
}
68+
for (const error of errors) {
69+
console.error(`[SB] Error on line ${error.span.start.line}: ${error.message}`);
18670
}
18771

188-
if (ruleSet.rules.length > 0 && ruleSet.skipOption !== null) {
189-
ruleSets.push(ruleSet);
190-
} else if (ruleSet.rules.length > 0 || ruleSet.skipOption !== null) {
191-
// Incomplete rule set
192-
return null;
193-
}
194-
195-
return ruleSets;
196-
}
197-
198-
function getSkipRuleAttribute(attribute: string): SkipRuleAttribute | null {
199-
if (attribute && Object.values(SkipRuleAttribute).includes(attribute as SkipRuleAttribute)) {
200-
return attribute as SkipRuleAttribute;
201-
}
202-
203-
return null;
204-
}
205-
206-
function getSkipRuleOperator(operator: string): SkipRuleOperator | null {
207-
if (operator && Object.values(SkipRuleOperator).includes(operator as SkipRuleOperator)) {
208-
return operator as SkipRuleOperator;
209-
}
210-
211-
return null;
212-
}
213-
214-
function getSkipRuleValue(value: string): string | number | null {
215-
if (!value) return null;
216-
217-
if (value.startsWith('"')) {
218-
try {
219-
return JSON.parse(value);
220-
} catch (e) {
221-
return null; // Invalid JSON string
222-
}
72+
if (errors.length === 0) {
73+
return rules;
22374
} else {
224-
const numValue = Number(value);
225-
if (!isNaN(numValue)) {
226-
return numValue;
227-
}
228-
22975
return null;
23076
}
23177
}
232-
233-
function configToText(config: AdvancedSkipRuleSet[]): string {
234-
let result = "";
235-
236-
for (const ruleSet of config) {
237-
if (ruleSet.comment) {
238-
result += "// " + ruleSet.comment + "\n";
239-
}
240-
241-
result += "if ";
242-
let firstRule = true;
243-
for (const rule of ruleSet.rules) {
244-
if (!firstRule) {
245-
result += " and ";
246-
}
247-
248-
result += `${rule.attribute} ${rule.operator} ${JSON.stringify(rule.value)}`;
249-
firstRule = false;
250-
}
251-
252-
switch (ruleSet.skipOption) {
253-
case CategorySkipOption.Disabled:
254-
result += "\nDisabled";
255-
break;
256-
case CategorySkipOption.ShowOverlay:
257-
result += "\nShow Overlay";
258-
break;
259-
case CategorySkipOption.ManualSkip:
260-
result += "\nManual Skip";
261-
break;
262-
case CategorySkipOption.AutoSkip:
263-
result += "\nAuto Skip";
264-
break;
265-
default:
266-
return null; // Invalid skip option
267-
}
268-
269-
result += "\n\n";
270-
}
271-
272-
return result.trim();
273-
}

src/config.ts

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import * as CompileConfig from "../config.json";
22
import * as invidiousList from "../ci/invidiouslist.json";
3-
import { Category, CategorySelection, CategorySkipOption, NoticeVisibilityMode, PreviewBarOption, SponsorTime, VideoID, SponsorHideType, SegmentListDefaultTab } from "./types";
4-
import { Keybind, ProtoConfig, keybindEquals } from "../maze-utils/src/config";
3+
import { Category, CategorySelection, CategorySkipOption, NoticeVisibilityMode, PreviewBarOption, SponsorHideType, SponsorTime, VideoID, SegmentListDefaultTab } from "./types";
4+
import { Keybind, keybindEquals, ProtoConfig } from "../maze-utils/src/config";
55
import { HashedValue } from "../maze-utils/src/hash";
6-
import { Permission, AdvancedSkipRuleSet } from "./utils/skipRule";
6+
import { AdvancedSkipCheck, AdvancedSkipPredicate, AdvancedSkipRule, Permission, PredicateOperator } from "./utils/skipRule";
77

88
interface SBConfig {
99
userID: string;
@@ -157,7 +157,7 @@ interface SBStorage {
157157
/* VideoID prefixes to UUID prefixes */
158158
downvotedSegments: Record<VideoID & HashedValue, VideoDownvotes>;
159159
navigationApiAvailable: boolean;
160-
160+
161161
// Used when sync storage disabled
162162
alreadyInstalled: boolean;
163163

@@ -168,7 +168,7 @@ interface SBStorage {
168168
skipProfileTemp: { time: number; configID: ConfigurationID } | null;
169169
skipProfiles: Record<ConfigurationID, CustomConfiguration>;
170170

171-
skipRules: AdvancedSkipRuleSet[];
171+
skipRules: AdvancedSkipRule[];
172172
}
173173

174174
class ConfigClass extends ProtoConfig<SBConfig, SBStorage> {
@@ -188,6 +188,43 @@ class ConfigClass extends ProtoConfig<SBConfig, SBStorage> {
188188
}
189189

190190
function migrateOldSyncFormats(config: SBConfig, local: SBStorage) {
191+
if (local["skipRules"] && local["skipRules"].length !== 0 && local["skipRules"][0]["rules"]) {
192+
const output: AdvancedSkipRule[] = [];
193+
194+
for (const rule of local["skipRules"]) {
195+
const rules: object[] = rule["rules"];
196+
197+
if (rules.length !== 0) {
198+
let predicate: AdvancedSkipPredicate = {
199+
kind: "check",
200+
...rules[0] as AdvancedSkipCheck,
201+
};
202+
203+
for (let i = 1; i < rules.length; i++) {
204+
predicate = {
205+
kind: "operator",
206+
operator: PredicateOperator.And,
207+
left: predicate,
208+
right: {
209+
kind: "check",
210+
...rules[i] as AdvancedSkipCheck,
211+
},
212+
};
213+
}
214+
215+
const comment = rule["comment"] as string;
216+
217+
output.push({
218+
predicate,
219+
skipOption: rule.skipOption,
220+
comments: comment.length === 0 ? [] : comment.split(/;\s*/),
221+
});
222+
}
223+
}
224+
225+
local["skipRules"] = output;
226+
}
227+
191228
if (config["whitelistedChannels"]) {
192229
// convert to skipProfiles
193230
const whitelistedChannels = config["whitelistedChannels"] as string[];
@@ -214,7 +251,7 @@ function migrateOldSyncFormats(config: SBConfig, local: SBStorage) {
214251
for (const channelID of whitelistedChannels) {
215252
local.channelSkipProfileIDs[channelID] = skipProfileID;
216253
}
217-
local.channelSkipProfileIDs = local.channelSkipProfileIDs;
254+
local.channelSkipProfileIDs = local.channelSkipProfileIDs;
218255

219256
chrome.storage.sync.remove("whitelistedChannels");
220257
}
@@ -248,7 +285,7 @@ function migrateOldSyncFormats(config: SBConfig, local: SBStorage) {
248285
name: "chapter" as Category,
249286
option: CategorySkipOption.ShowOverlay
250287
});
251-
288+
252289
config.categorySelections = config.categorySelections;
253290
}
254291
}

0 commit comments

Comments
 (0)