ソースを参照

Experimental: LAN Cache Support (WIP)

SukkaW 3 週間 前
コミット
baeae2dd73

+ 85 - 1
Build/build-domestic-direct-lan-ruleset-dns-mapping-module.ts

@@ -3,9 +3,10 @@ import path from 'node:path';
 import { DOMESTICS, DOH_BOOTSTRAP, AdGuardHomeDNSMapping } from '../Source/non_ip/domestic';
 import { DOMESTICS, DOH_BOOTSTRAP, AdGuardHomeDNSMapping } from '../Source/non_ip/domestic';
 import { DIRECTS, HOSTS, LAN } from '../Source/non_ip/direct';
 import { DIRECTS, HOSTS, LAN } from '../Source/non_ip/direct';
 import type { DNSMapping } from '../Source/non_ip/direct';
 import type { DNSMapping } from '../Source/non_ip/direct';
-import { readFileIntoProcessedArray } from './lib/fetch-text-by-line';
+import { fetchRemoteTextByLine, readFileIntoProcessedArray } from './lib/fetch-text-by-line';
 import { compareAndWriteFile } from './lib/create-file';
 import { compareAndWriteFile } from './lib/create-file';
 import { task } from './trace';
 import { task } from './trace';
+import type { Span } from './trace';
 import { SHARED_DESCRIPTION } from './constants/description';
 import { SHARED_DESCRIPTION } from './constants/description';
 import { once } from 'foxts/once';
 import { once } from 'foxts/once';
 import * as yaml from 'yaml';
 import * as yaml from 'yaml';
@@ -13,6 +14,7 @@ import { appendArrayInPlace } from 'foxts/append-array-in-place';
 import { OUTPUT_INTERNAL_DIR, OUTPUT_MODULES_DIR, OUTPUT_MODULES_RULES_DIR, SOURCE_DIR } from './constants/dir';
 import { OUTPUT_INTERNAL_DIR, OUTPUT_MODULES_DIR, OUTPUT_MODULES_RULES_DIR, SOURCE_DIR } from './constants/dir';
 import { MihomoNameserverPolicyOutput, RulesetOutput } from './lib/rules/ruleset';
 import { MihomoNameserverPolicyOutput, RulesetOutput } from './lib/rules/ruleset';
 import { SurgeOnlyRulesetOutput } from './lib/rules/ruleset';
 import { SurgeOnlyRulesetOutput } from './lib/rules/ruleset';
+import { $$fetch } from './lib/fetch-retry';
 
 
 export function createGetDnsMappingRule(allowWildcard: boolean) {
 export function createGetDnsMappingRule(allowWildcard: boolean) {
   const hasWildcard = (domain: string) => {
   const hasWildcard = (domain: string) => {
@@ -112,6 +114,7 @@ export const buildDomesticRuleset = task(require.main === module, __filename)(as
       .addFromRuleset(lans)
       .addFromRuleset(lans)
       .write(),
       .write(),
 
 
+    buildLANCacheRuleset(span),
     ...dataset.map(([name, { ruleset, domains }]) => {
     ...dataset.map(([name, { ruleset, domains }]) => {
       if (!ruleset) {
       if (!ruleset) {
         return;
         return;
@@ -340,3 +343,84 @@ export const buildDomesticRuleset = task(require.main === module, __filename)(as
     )
     )
   ]);
   ]);
 });
 });
+
+async function buildLANCacheRuleset(span: Span) {
+  const childSpan = span.traceChild('build LAN cache ruleset');
+
+  const cacheDomainsData = await childSpan.traceChildAsync('fetch cache_domains.json', async () => (await $$fetch('https://cdn.jsdelivr.net/gh/uklans/cache-domains@master/cache_domains.json')).json());
+  if (!cacheDomainsData || typeof cacheDomainsData !== 'object' || !('cache_domains' in cacheDomainsData) || !Array.isArray(cacheDomainsData.cache_domains)) {
+    throw new TypeError('Invalid cache domains data');
+  }
+  const allDomainFiles = cacheDomainsData.cache_domains.reduce<string[]>((acc, { domain_files }) => {
+    if (Array.isArray(domain_files)) {
+      appendArrayInPlace(acc, domain_files);
+    }
+    return acc;
+  }, []);
+
+  const allDomains = (
+    await Promise.all(
+      allDomainFiles.map(
+        async (file) => childSpan.traceChildAsync(
+          'download ' + file,
+          async () => Array.fromAsync(await fetchRemoteTextByLine('https://cdn.jsdelivr.net/gh/uklans/cache-domains@master/' + file, true))
+        )
+      )
+    )
+  ).flat();
+
+  const surgeOutput = new SurgeOnlyRulesetOutput(
+    span,
+    'lancache',
+    'sukka_local_dns_mapping',
+    OUTPUT_MODULES_RULES_DIR
+  )
+    .withTitle('Sukka\'s Ruleset - Local DNS Mapping (lancache)')
+    .appendDescription(
+      SHARED_DESCRIPTION,
+      '',
+      'This is an internal rule that is only referenced by sukka_local_dns_mapping.sgmodule',
+      'Do not use this file in your Rule section.'
+    );
+
+  const mihomoOutput = new MihomoNameserverPolicyOutput(
+    span,
+    'lancache',
+    'mihomo_nameserver_policy',
+    OUTPUT_INTERNAL_DIR
+  )
+    .withTitle('Sukka\'s Ruleset - Local DNS Mapping for Mihomo NameServer Policy (lancache)')
+    .appendDescription(
+      SHARED_DESCRIPTION,
+      '',
+      'This ruleset is only used for mihomo\'s nameserver-policy feature, which',
+      'is similar to the RULE-SET referenced by sukka_local_dns_mapping.sgmodule.',
+      'Do not use this file in your Rule section.'
+    );
+
+  for (let i = 0, len = allDomains.length; i < len; i++) {
+    const domain = allDomains[i];
+
+    if (domain.includes('*')) {
+      // If only *. prefix is used, we can convert it to DOMAIN-SUFFIX
+      if (domain.startsWith('*.') && !domain.slice(2).includes('*')) {
+        const domainSuffix = domain.slice(2);
+        surgeOutput.addDomainSuffix(domainSuffix);
+        mihomoOutput.addDomainSuffix(domainSuffix);
+        continue;
+      }
+
+      surgeOutput.addDomainWildcard(domain);
+      mihomoOutput.addDomainWildcard(domain);
+      continue;
+    }
+
+    surgeOutput.addDomain(domain);
+    mihomoOutput.addDomain(domain);
+  }
+
+  return Promise.all([
+    surgeOutput.write(),
+    mihomoOutput.write()
+  ]);
+}

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

@@ -147,9 +147,14 @@ export class FileOutput {
     return this;
     return this;
   }
   }
 
 
-  bulkAddDomainWildcard(domains: string[]) {
-    for (let i = 0, len = domains.length; i < len; i++) {
-      this.wildcardSet.add(domains[i]);
+  addDomainWildcard(wildcard: string) {
+    this.wildcardSet.add(wildcard);
+    return this;
+  }
+
+  bulkAddDomainWildcard(wildcards: string[]) {
+    for (let i = 0, len = wildcards.length; i < len; i++) {
+      this.wildcardSet.add(wildcards[i]);
     }
     }
     return this;
     return this;
   }
   }