Browse Source

Feat: support wildcard from adguard filter

SukkaW 9 months ago
parent
commit
6da1069147

+ 77 - 33
Build/build-reject-domainset.ts

@@ -50,11 +50,11 @@ export const buildRejectDomainSet = task(require.main === module, __filename)(as
     ...ADGUARD_FILTERS.map(filter => ` - ${Array.isArray(filter) ? filter[0] : filter}`)
   ];
 
-  const rejectOutput = new DomainsetOutput(span, 'reject')
+  const rejectDomainsetOutput = new DomainsetOutput(span, 'reject')
     .withTitle('Sukka\'s Ruleset - Reject Base')
     .withDescription(rejectBaseDescription);
 
-  const rejectExtraOutput = new DomainsetOutput(span, 'reject_extra')
+  const rejectExtraDomainsetOutput = new DomainsetOutput(span, 'reject_extra')
     .withTitle('Sukka\'s Ruleset - Reject Extra')
     .withDescription([
       ...SHARED_DESCRIPTION,
@@ -67,7 +67,7 @@ export const buildRejectDomainSet = task(require.main === module, __filename)(as
       ...ADGUARD_FILTERS_EXTRA.map(filter => ` - ${filter[0]}`)
     ]);
 
-  const rejectPhisingOutput = new DomainsetOutput(span, 'reject_phishing')
+  const rejectPhisingDomainsetOutput = new DomainsetOutput(span, 'reject_phishing')
     .withTitle('Sukka\'s Ruleset - Reject Phishing')
     .withDescription([
       ...SHARED_DESCRIPTION,
@@ -79,6 +79,16 @@ export const buildRejectDomainSet = task(require.main === module, __filename)(as
       ...PHISHING_DOMAIN_LISTS_EXTRA.map(domainList => ` - ${domainList[0]}`)
     ]);
 
+  const rejectNonIpRulesetOutput = new RulesetOutput(span, 'reject', 'non_ip')
+    .withTitle('Sukka\'s Ruleset - Reject Non-IP')
+    .withDescription([
+      ...SHARED_DESCRIPTION,
+      '',
+      'The ruleset supports AD blocking, tracking protection, privacy protection, anti-phishing, anti-mining',
+      '',
+      'The file contains wildcard domains from data source mentioned in /domainset/reject file'
+    ]);
+
   const rejectIPOutput = new RulesetOutput(span, 'reject', 'ip')
     .withTitle('Sukka\'s Ruleset - Anti Bogus Domain')
     .withDescription([
@@ -98,22 +108,25 @@ export const buildRejectDomainSet = task(require.main === module, __filename)(as
   // Collect DOMAIN, DOMAIN-SUFFIX, and DOMAIN-KEYWORD from non_ip/reject.conf for deduplication
   // DOMAIN-WILDCARD is not really useful for deduplication, it is only included in AdGuardHome output
   // It is faster to add base than add others first then whitelist
-  rejectOutput.addFromRuleset(readLocalRejectRulesetPromise);
-  rejectExtraOutput.addFromRuleset(readLocalRejectRulesetPromise);
+  rejectDomainsetOutput.addFromRuleset(readLocalRejectRulesetPromise);
+  rejectExtraDomainsetOutput.addFromRuleset(readLocalRejectRulesetPromise);
+
+  rejectNonIpRulesetOutput.addFromRuleset(readLocalRejectRulesetPromise);
 
-  rejectOutput.addFromDomainset(readLocalRejectDomainsetPromise);
-  rejectExtraOutput.addFromDomainset(readLocalRejectDomainsetPromise);
-  rejectPhisingOutput.addFromDomainset(readLocalRejectDomainsetPromise);
+  rejectDomainsetOutput.addFromDomainset(readLocalRejectDomainsetPromise);
+  rejectExtraDomainsetOutput.addFromDomainset(readLocalRejectDomainsetPromise);
+  rejectPhisingDomainsetOutput.addFromDomainset(readLocalRejectDomainsetPromise);
 
-  rejectExtraOutput.addFromDomainset(readLocalRejectExtraDomainsetPromise);
+  rejectExtraDomainsetOutput.addFromDomainset(readLocalRejectExtraDomainsetPromise);
 
   rejectIPOutput.addFromRuleset(readLocalRejectIpListPromise);
 
-  const appendArrayToRejectOutput = (source: MaybePromise<AsyncIterable<string> | Iterable<string> | string[]>) => rejectOutput.addFromDomainset(source);
-  const appendArrayToRejectExtraOutput = (source: MaybePromise<AsyncIterable<string> | Iterable<string> | string[]>) => rejectExtraOutput.addFromDomainset(source);
+  const appendArrayToRejectOutput = (source: MaybePromise<AsyncIterable<string> | Iterable<string> | string[]>) => rejectDomainsetOutput.addFromDomainset(source);
+  const appendArrayToRejectExtraOutput = (source: MaybePromise<AsyncIterable<string> | Iterable<string> | string[]>) => rejectExtraDomainsetOutput.addFromDomainset(source);
 
   /** Whitelists */
   const filterRuleWhitelistDomainSets = new Set(PREDEFINED_WHITELIST);
+  const filterRuleWhiteKeywords = new Set<string>();
 
   // Parse from AdGuard Filters
   await span
@@ -127,34 +140,49 @@ export const buildRejectDomainSet = task(require.main === module, __filename)(as
       domainListsDownloads.map(task => task(childSpan).then(appendArrayToRejectOutput)),
       domainListsExtraDownloads.map(task => task(childSpan).then(appendArrayToRejectExtraOutput)),
 
-      rejectPhisingOutput.addFromDomainset(getPhishingDomains(childSpan)),
+      rejectPhisingDomainsetOutput.addFromDomainset(getPhishingDomains(childSpan)),
 
       adguardFiltersDownloads.map(
-        task => task(childSpan).then(({ whiteDomains, whiteDomainSuffixes, blackDomains, blackDomainSuffixes, blackIPs }) => {
+        task => task(childSpan).then(({ whiteDomains, whiteDomainSuffixes, blackDomains, blackDomainSuffixes, blackIPs, blackWildcard, whiteKeyword, blackKeyword }) => {
           addArrayElementsToSet(filterRuleWhitelistDomainSets, whiteDomains);
           addArrayElementsToSet(filterRuleWhitelistDomainSets, whiteDomainSuffixes, suffix => '.' + suffix);
 
-          rejectOutput.bulkAddDomain(blackDomains);
-          rejectOutput.bulkAddDomainSuffix(blackDomainSuffixes);
+          addArrayElementsToSet(filterRuleWhiteKeywords, whiteKeyword);
+
+          rejectDomainsetOutput.bulkAddDomain(blackDomains);
+          rejectDomainsetOutput.bulkAddDomainSuffix(blackDomainSuffixes);
+
+          rejectDomainsetOutput.bulkAddDomainKeyword(blackKeyword);
+
+          rejectNonIpRulesetOutput.bulkAddDomainWildcard(blackWildcard);
+
           rejectIPOutput.bulkAddAnyCIDR(blackIPs, false);
         })
       ),
       adguardFiltersExtraDownloads.map(
-        task => task(childSpan).then(({ whiteDomains, whiteDomainSuffixes, blackDomains, blackDomainSuffixes, blackIPs }) => {
+        task => task(childSpan).then(({ whiteDomains, whiteDomainSuffixes, blackDomains, blackDomainSuffixes, blackIPs, blackWildcard, whiteKeyword, blackKeyword }) => {
           addArrayElementsToSet(filterRuleWhitelistDomainSets, whiteDomains);
           addArrayElementsToSet(filterRuleWhitelistDomainSets, whiteDomainSuffixes, suffix => '.' + suffix);
+          addArrayElementsToSet(filterRuleWhiteKeywords, whiteKeyword);
+
+          rejectExtraDomainsetOutput.bulkAddDomain(blackDomains);
+          rejectExtraDomainsetOutput.bulkAddDomainSuffix(blackDomainSuffixes);
+
+          rejectExtraDomainsetOutput.bulkAddDomainKeyword(blackKeyword);
 
-          rejectExtraOutput.bulkAddDomain(blackDomains);
-          rejectExtraOutput.bulkAddDomainSuffix(blackDomainSuffixes);
           rejectIPOutput.bulkAddAnyCIDR(blackIPs, false);
+
+          rejectNonIpRulesetOutput.bulkAddDomainWildcard(blackWildcard);
         })
       ),
       adguardFiltersWhitelistsDownloads.map(
-        task => task(childSpan).then(({ whiteDomains, whiteDomainSuffixes, blackDomains, blackDomainSuffixes }) => {
+        task => task(childSpan).then(({ whiteDomains, whiteDomainSuffixes, blackDomains, blackDomainSuffixes, whiteKeyword, blackKeyword }) => {
           addArrayElementsToSet(filterRuleWhitelistDomainSets, whiteDomains);
           addArrayElementsToSet(filterRuleWhitelistDomainSets, whiteDomainSuffixes, suffix => '.' + suffix);
           addArrayElementsToSet(filterRuleWhitelistDomainSets, blackDomains);
           addArrayElementsToSet(filterRuleWhitelistDomainSets, blackDomainSuffixes, suffix => '.' + suffix);
+          addArrayElementsToSet(filterRuleWhiteKeywords, whiteKeyword);
+          addArrayElementsToSet(filterRuleWhiteKeywords, blackKeyword);
         })
       ),
 
@@ -185,29 +213,45 @@ export const buildRejectDomainSet = task(require.main === module, __filename)(as
   }
 
   await Promise.all([
-    rejectOutput.done(),
-    rejectExtraOutput.done(),
-    rejectPhisingOutput.done(),
-    rejectIPOutput.done()
+    rejectDomainsetOutput.done(),
+    rejectExtraDomainsetOutput.done(),
+    rejectPhisingDomainsetOutput.done(),
+    rejectIPOutput.done(),
+    rejectNonIpRulesetOutput.done()
   ]);
 
   // whitelist
   span.traceChildSync('whitelist', () => {
     for (const domain of filterRuleWhitelistDomainSets) {
-      rejectOutput.whitelistDomain(domain);
-      rejectExtraOutput.whitelistDomain(domain);
-      rejectPhisingOutput.whitelistDomain(domain);
+      rejectDomainsetOutput.whitelistDomain(domain);
+      rejectExtraDomainsetOutput.whitelistDomain(domain);
+      rejectPhisingDomainsetOutput.whitelistDomain(domain);
+      rejectNonIpRulesetOutput.whitelistDomain(domain);
+    }
+
+    // we use "whitelistKeyword" method, this will be used to create kwfilter internally
+    for (const keyword of filterRuleWhiteKeywords) {
+      rejectDomainsetOutput.whitelistKeyword(keyword);
+      rejectExtraDomainsetOutput.whitelistKeyword(keyword);
+      rejectPhisingDomainsetOutput.whitelistKeyword(keyword);
+      rejectNonIpRulesetOutput.whitelistKeyword(keyword);
     }
 
-    rejectOutput.domainTrie.dump(arg => rejectExtraOutput.whitelistDomain(arg));
-    rejectOutput.domainTrie.dump(arg => rejectPhisingOutput.whitelistDomain(arg));
+    rejectDomainsetOutput.domainTrie.dump(arg => {
+      rejectExtraDomainsetOutput.whitelistDomain(arg);
+      rejectPhisingDomainsetOutput.whitelistDomain(arg);
+
+      // e.g. .data.microsort.com can strip waston*.event.data.microsort.com
+      rejectNonIpRulesetOutput.wildcardTrie.whitelist(arg);
+    });
   });
 
   await Promise.all([
-    rejectOutput.write(),
-    rejectExtraOutput.write(),
-    rejectPhisingOutput.write(),
-    rejectIPOutput.write()
+    rejectDomainsetOutput.write(),
+    rejectExtraDomainsetOutput.write(),
+    rejectPhisingDomainsetOutput.write(),
+    rejectIPOutput.write(),
+    rejectNonIpRulesetOutput.write()
   ]);
 
   // we are going to re-use rejectOutput's domainTrie and mutate it
@@ -218,7 +262,7 @@ export const buildRejectDomainSet = task(require.main === module, __filename)(as
       'The AdGuardHome ruleset supports AD blocking, tracking protection, privacy protection, anti-mining'
     ]);
 
-  rejectOutputAdGuardHome.domainTrie = rejectOutput.domainTrie;
+  rejectOutputAdGuardHome.domainTrie = rejectDomainsetOutput.domainTrie;
 
   await rejectOutputAdGuardHome
     // .addFromRuleset(readLocalMyRejectRulesetPromise)

+ 80 - 10
Build/lib/parse-filter/filters.ts

@@ -15,6 +15,9 @@ const enum ParseType {
   BlackIncludeSubdomain = 2,
   ErrorMessage = 10,
   BlackIP = 20,
+  BlackWildcard = 30,
+  BlackKeyword = 40,
+  WhiteKeyword = 50,
   Null = 1000,
   NotParsed = 2000
 }
@@ -28,7 +31,19 @@ export function processFilterRulesWithPreload(
 ) {
   const downloadPromise = fetchAssets(filterRulesUrl, fallbackUrls);
 
-  return (span: Span) => span.traceChildAsync<Record<'whiteDomains' | 'whiteDomainSuffixes' | 'blackDomains' | 'blackDomainSuffixes' | 'blackIPs', string[]>>(`process filter rules: ${filterRulesUrl}`, async (span) => {
+  return (span: Span) => span.traceChildAsync<
+    Record<
+      'whiteDomains'
+      | 'whiteDomainSuffixes'
+      | 'blackDomains'
+      | 'blackDomainSuffixes'
+      | 'blackIPs'
+      | 'blackWildcard'
+      | 'whiteKeyword'
+      | 'blackKeyword',
+      string[]
+    >
+  >(`process filter rules: ${filterRulesUrl}`, async (span) => {
     const filterRules = await span.traceChildPromise('download', downloadPromise);
 
     const whiteDomains = new Set<string>();
@@ -40,6 +55,10 @@ export function processFilterRulesWithPreload(
     const warningMessages: string[] = [];
 
     const blackIPs: string[] = [];
+    const blackWildcard = new Set<string>();
+
+    const whiteKeyword = new Set<string>();
+    const blackKeyword = new Set<string>();
 
     const MUTABLE_PARSE_LINE_RESULT: [string, ParseType] = ['', ParseType.NotParsed];
     /**
@@ -83,6 +102,15 @@ export function processFilterRulesWithPreload(
         case ParseType.BlackIP:
           blackIPs.push(hostname);
           break;
+        case ParseType.BlackWildcard:
+          blackWildcard.add(hostname);
+          break;
+        case ParseType.BlackKeyword:
+          blackKeyword.add(hostname);
+          break;
+        case ParseType.WhiteKeyword:
+          whiteKeyword.add(hostname);
+          break;
         default:
           break;
       }
@@ -113,7 +141,10 @@ export function processFilterRulesWithPreload(
       whiteDomainSuffixes: Array.from(whiteDomainSuffixes),
       blackDomains: Array.from(blackDomains),
       blackDomainSuffixes: Array.from(blackDomainSuffixes),
-      blackIPs
+      blackIPs,
+      blackWildcard: Array.from(blackWildcard),
+      whiteKeyword: Array.from(whiteKeyword),
+      blackKeyword: Array.from(blackKeyword)
     };
   });
 }
@@ -482,6 +513,7 @@ export function parse($line: string, result: [string, ParseType], includeThirdPa
   }
 
   const parsed = tldts.parse(sliced, looseTldtsOpt);
+  const hostname = parsed.hostname;
 
   /**
    * We can exclude wildcard in TLD
@@ -495,15 +527,21 @@ export function parse($line: string, result: [string, ParseType], includeThirdPa
    *
    * This also exclude non standard TLD like `.tor`, `.onion`, `.dn42`, etc.
    */
-  if (!parsed.publicSuffix || !parsed.isIcann || !parsed.hostname || !parsed.domain) {
+  if (!parsed.publicSuffix || !parsed.isIcann || !hostname || !parsed.domain) {
     result[1] = ParseType.Null;
     return result;
   }
 
   // no wildcard, we can safely normalize it˝
-  if (!parsed.hostname.includes('*')) {
+  if (!hostname.includes('*')) {
+    if (hostname.charCodeAt(0) === 45) { // 45 `-`
+      result[0] = hostname;
+      result[1] = white ? ParseType.WhiteKeyword : ParseType.BlackKeyword;
+      return result;
+    }
+
     if (white) {
-      result[0] = parsed.hostname;
+      result[0] = hostname;
       result[1] = includeAllSubDomain ? ParseType.WhiteIncludeSubdomain : ParseType.WhiteAbsolute;
       return result;
     }
@@ -522,14 +560,46 @@ export function parse($line: string, result: [string, ParseType], includeThirdPa
       }
     }
 
-    result[0] = parsed.hostname;
+    result[0] = hostname;
     result[1] = includeAllSubDomain ? ParseType.BlackIncludeSubdomain : ParseType.BlackAbsolute;
     return result;
   }
 
-  result[0] = `[parse-filter E0010] (${white ? 'white' : 'black'}) invalid domain: ${JSON.stringify({
-    line, sliced, sliceStart, sliceEnd, parsed
-  })}`;
-  result[1] = ParseType.ErrorMessage;
+  // now we only have wildcard domain left
+  if (white) {
+    // we don't support wildcard in whitelist
+    // result[1] = ParseType.Null;
+    // return result;
+    result[0] = `[parse-filter E0021] wildcard whitelist not supported: ${JSON.stringify({
+      line, sliced, sliceStart, sliceEnd, parsed
+    })}`;
+    result[1] = ParseType.ErrorMessage;
+    return result;
+  }
+
+  for (let i = 0, len = hostname.length; i < len; i++) {
+    const char = hostname.charCodeAt(i);
+
+    if (
+      (char >= 97 && char <= 122) // 97-122 `a-z`
+      || char === 46 // 46 `.`
+      || char === 45 // 45 `-`
+      || (char >= 48 && char <= 57) // 48-57 `0-9`
+      || char === 42 // 42 `*`
+      || char === 95 // 95 `_`
+      // || (char >= 65 && char <= 90) // 65-90 `A-Z`
+    ) {
+      continue;
+    }
+
+    result[0] = `[parse-filter E0020] (black) invalid wildcard domain: ${JSON.stringify({
+      line, sliced, sliceStart, sliceEnd, parsed
+    })}`;
+    result[1] = ParseType.ErrorMessage;
+    return result;
+  }
+
+  result[0] = hostname;
+  result[1] = ParseType.BlackWildcard;
   return result;
 }

+ 51 - 8
Build/lib/rules/base.ts

@@ -17,8 +17,12 @@ export class FileOutput {
   protected strategies: BaseWriteStrategy[] = [];
 
   public domainTrie = new HostnameSmolTrie(null);
+  public wildcardTrie: HostnameSmolTrie = new HostnameSmolTrie(null);
+
   protected domainKeywords = new Set<string>();
-  protected domainWildcard = new Set<string>();
+
+  private whitelistKeywords = new Set<string>();
+
   protected userAgent = new Set<string>();
   protected processName = new Set<string>();
   protected processPath = new Set<string>();
@@ -43,6 +47,12 @@ export class FileOutput {
 
   whitelistDomain = (domain: string) => {
     this.domainTrie.whitelist(domain);
+    this.wildcardTrie.whitelist(domain);
+    return this;
+  };
+
+  whitelistKeyword = (keyword: string) => {
+    this.whitelistKeywords.add(keyword);
     return this;
   };
 
@@ -112,6 +122,20 @@ export class FileOutput {
     return this;
   }
 
+  bulkAddDomainKeyword(keywords: string[]) {
+    for (let i = 0, len = keywords.length; i < len; i++) {
+      this.domainKeywords.add(keywords[i]);
+    }
+    return this;
+  }
+
+  bulkAddDomainWildcard(domains: string[]) {
+    for (let i = 0, len = domains.length; i < len; i++) {
+      this.wildcardTrie.add(domains[i]);
+    }
+    return this;
+  }
+
   addIPASN(asn: string) {
     this.ipasn.add(asn);
     return this;
@@ -161,7 +185,7 @@ export class FileOutput {
           this.addDomainKeyword(value);
           break;
         case 'DOMAIN-WILDCARD':
-          this.domainWildcard.add(value);
+          this.wildcardTrie.add(value);
           break;
         case 'USER-AGENT':
           this.userAgent.add(value);
@@ -318,7 +342,11 @@ export class FileOutput {
 
     this.strategiesWritten = true;
 
-    const kwfilter = createKeywordFilter(Array.from(this.domainKeywords));
+    // We use both DOMAIN-KEYWORD and whitelisted keyword to whitelist DOMAIN and DOMAIN-SUFFIX
+    const kwfilter = createKeywordFilter(
+      Array.from(this.domainKeywords)
+        .concat(Array.from(this.whitelistKeywords))
+    );
 
     if (this.strategies.filter(not(false)).length === 0) {
       throw new Error('No strategies to write ' + this.id);
@@ -330,6 +358,8 @@ export class FileOutput {
         return;
       }
 
+      this.wildcardTrie.whitelist(domain, includeAllSubdomain);
+
       for (let i = 0; i < strategiesLen; i++) {
         const strategy = this.strategies[i];
         if (includeAllSubdomain) {
@@ -340,14 +370,27 @@ export class FileOutput {
       }
     }, true);
 
-    for (let i = 0, len = this.strategies.length; i < len; i++) {
+    // Now, we whitelisted out DOMAIN-KEYWORD
+    const whiteKwfilter = createKeywordFilter(Array.from(this.whitelistKeywords));
+    const whitelistedKeywords = Array.from(this.domainKeywords).filter(kw => !whiteKwfilter(kw));
+
+    this.wildcardTrie.dumpWithoutDot((wildcard) => {
+      if (kwfilter(wildcard)) {
+        return;
+      }
+
+      for (let i = 0; i < strategiesLen; i++) {
+        const strategy = this.strategies[i];
+        strategy.writeDomainWildcard(wildcard);
+      }
+    });
+
+    for (let i = 0; i < strategiesLen; i++) {
       const strategy = this.strategies[i];
-      if (this.domainKeywords.size) {
+      if (whitelistedKeywords.length) {
         strategy.writeDomainKeywords(this.domainKeywords);
       }
-      if (this.domainWildcard.size) {
-        strategy.writeDomainWildcards(this.domainWildcard);
-      }
+
       if (this.protocol.size) {
         strategy.writeProtocols(this.protocol);
       }

+ 6 - 8
Build/lib/writing-strategy/adguardhome.ts

@@ -52,14 +52,12 @@ export class AdGuardHome extends BaseWriteStrategy {
     }
   }
 
-  writeDomainWildcards(wildcards: Set<string>): void {
-    for (const wildcard of wildcards) {
-      const processed = wildcard.replaceAll('?', '*');
-      if (processed.startsWith('*.')) {
-        this.result.push(`||${processed.slice(2)}^`);
-      } else {
-        this.result.push(`|${processed}^`);
-      }
+  writeDomainWildcard(wildcard: string): void {
+    const processed = wildcard.replaceAll('?', '*');
+    if (processed.startsWith('*.')) {
+      this.result.push(`||${processed.slice(2)}^`);
+    } else {
+      this.result.push(`|${processed}^`);
     }
   }
 

+ 1 - 1
Build/lib/writing-strategy/base.ts

@@ -30,7 +30,7 @@ export abstract class BaseWriteStrategy {
   abstract writeDomain(domain: string): void;
   abstract writeDomainSuffix(domain: string): void;
   abstract writeDomainKeywords(keyword: Set<string>): void;
-  abstract writeDomainWildcards(wildcard: Set<string>): void;
+  abstract writeDomainWildcard(wildcard: string): void;
   abstract writeUserAgents(userAgent: Set<string>): void;
   abstract writeProcessNames(processName: Set<string>): void;
   abstract writeProcessPaths(processPath: Set<string>): void;

+ 4 - 4
Build/lib/writing-strategy/clash.ts

@@ -30,7 +30,7 @@ export class ClashDomainSet extends BaseWriteStrategy {
   }
 
   writeDomainKeywords = noop;
-  writeDomainWildcards = noop;
+  writeDomainWildcard = noop;
   writeUserAgents = noop;
   writeProcessNames = noop;
   writeProcessPaths = noop;
@@ -64,7 +64,7 @@ export class ClashIPSet extends BaseWriteStrategy {
   writeDomain = notSupported('writeDomain');
   writeDomainSuffix = notSupported('writeDomainSuffix');
   writeDomainKeywords = notSupported('writeDomainKeywords');
-  writeDomainWildcards = notSupported('writeDomainWildcards');
+  writeDomainWildcard = notSupported('writeDomainWildcards');
   writeUserAgents = notSupported('writeUserAgents');
   writeProcessNames = notSupported('writeProcessNames');
   writeProcessPaths = notSupported('writeProcessPaths');
@@ -111,8 +111,8 @@ export class ClashClassicRuleSet extends BaseWriteStrategy {
     appendSetElementsToArray(this.result, keyword, i => `DOMAIN-KEYWORD,${i}`);
   }
 
-  writeDomainWildcards(wildcard: Set<string>): void {
-    appendSetElementsToArray(this.result, wildcard, i => `DOMAIN-REGEX,${ClashClassicRuleSet.domainWildCardToRegex(i)}`);
+  writeDomainWildcard(wildcard: string): void {
+    this.result.push(`DOMAIN-REGEX,${ClashClassicRuleSet.domainWildCardToRegex(wildcard)}`);
   }
 
   writeUserAgents = noop;

+ 1 - 1
Build/lib/writing-strategy/legacy-clash-premium.ts

@@ -14,6 +14,6 @@ export class LegacyClashPremiumClassicRuleSet extends ClashClassicRuleSet {
     super(type, outputDir);
   }
 
-  override writeDomainWildcards = noop;
+  override writeDomainWildcard = noop;
   override writeIpAsns = noop;
 }

+ 3 - 5
Build/lib/writing-strategy/singbox.ts

@@ -71,11 +71,9 @@ export class SingboxSource extends BaseWriteStrategy {
     );
   }
 
-  writeDomainWildcards(wildcard: Set<string>): void {
-    appendArrayInPlace(
-      this.singbox.domain_regex ??= [],
-      Array.from(wildcard, SingboxSource.domainWildCardToRegex)
-    );
+  writeDomainWildcard(wildcard: string): void {
+    this.singbox.domain_regex ??= [];
+    this.singbox.domain_regex.push(SingboxSource.domainWildCardToRegex(wildcard));
   }
 
   writeUserAgents = noop;

+ 1 - 1
Build/lib/writing-strategy/surfboard.ts

@@ -12,7 +12,7 @@ export class SurfboardRuleSet extends SurgeRuleSet {
     super(type, outputDir);
   }
 
-  override writeDomainWildcards = noop;
+  override writeDomainWildcard = noop;
   override writeUserAgents = noop;
   override writeUrlRegexes = noop;
   override writeIpAsns = noop;

+ 4 - 4
Build/lib/writing-strategy/surge.ts

@@ -33,7 +33,7 @@ export class SurgeDomainSet extends BaseWriteStrategy {
   }
 
   writeDomainKeywords = noop;
-  writeDomainWildcards = noop;
+  writeDomainWildcard = noop;
   writeUserAgents = noop;
   writeProcessNames = noop;
   writeProcessPaths = noop;
@@ -78,8 +78,8 @@ export class SurgeRuleSet extends BaseWriteStrategy {
     appendSetElementsToArray(this.result, keyword, i => `DOMAIN-KEYWORD,${i}`);
   }
 
-  writeDomainWildcards(wildcard: Set<string>): void {
-    appendSetElementsToArray(this.result, wildcard, i => `DOMAIN-WILDCARD,${i}`);
+  writeDomainWildcard(wildcard: string): void {
+    this.result.push(`DOMAIN-WILDCARD,${wildcard}`);
   }
 
   writeUserAgents(userAgent: Set<string>): void {
@@ -176,7 +176,7 @@ export class SurgeMitmSgmodule extends BaseWriteStrategy {
   writeDomainSuffix = noop;
 
   writeDomainKeywords = noop;
-  writeDomainWildcards = noop;
+  writeDomainWildcard = noop;
   writeUserAgents = noop;
   writeProcessNames = noop;
   writeProcessPaths = noop;

+ 2 - 2
Source/non_ip/reject.conf

@@ -1,5 +1,5 @@
-# $ meta_title Sukka's Ruleset - Reject Domains
-# $ meta_description The ruleset supports AD blocking, tracking protection, privacy protection, anti-phishing, anti-mining
+# Sukka's Ruleset - Reject Domains
+# $ custom_build_script
 # $ skip_dedupe_src enforce some blocking to reduce file size
 
 DOMAIN,this_rule_set_is_made_by_sukkaw.skk.moe