diff --git a/Build/build-domestic-direct-lan-ruleset-dns-mapping-module.ts b/Build/build-domestic-direct-lan-ruleset-dns-mapping-module.ts index 9ec77851..78d0e263 100644 --- a/Build/build-domestic-direct-lan-ruleset-dns-mapping-module.ts +++ b/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 { DIRECTS, HOSTS, LAN } 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 { task } from './trace'; +import type { Span } from './trace'; import { SHARED_DESCRIPTION } from './constants/description'; import { once } from 'foxts/once'; 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 { MihomoNameserverPolicyOutput, RulesetOutput } from './lib/rules/ruleset'; import { SurgeOnlyRulesetOutput } from './lib/rules/ruleset'; +import { $$fetch } from './lib/fetch-retry'; export function createGetDnsMappingRule(allowWildcard: boolean) { const hasWildcard = (domain: string) => { @@ -112,6 +114,7 @@ export const buildDomesticRuleset = task(require.main === module, __filename)(as .addFromRuleset(lans) .write(), + buildLANCacheRuleset(span), ...dataset.map(([name, { ruleset, domains }]) => { if (!ruleset) { 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((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() + ]); +} diff --git a/Build/lib/rules/base.ts b/Build/lib/rules/base.ts index a8fa6577..f91d5f37 100644 --- a/Build/lib/rules/base.ts +++ b/Build/lib/rules/base.ts @@ -147,9 +147,14 @@ export class FileOutput { 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; }