@@ -3,16 +3,18 @@ import path from 'node:path';
33import { DOMESTICS , DOH_BOOTSTRAP , AdGuardHomeDNSMapping } from '../Source/non_ip/domestic' ;
44import { DIRECTS , HOSTS , LAN } from '../Source/non_ip/direct' ;
55import type { DNSMapping } from '../Source/non_ip/direct' ;
6- import { readFileIntoProcessedArray } from './lib/fetch-text-by-line' ;
6+ import { fetchRemoteTextByLine , readFileIntoProcessedArray } from './lib/fetch-text-by-line' ;
77import { compareAndWriteFile } from './lib/create-file' ;
88import { task } from './trace' ;
9+ import type { Span } from './trace' ;
910import { SHARED_DESCRIPTION } from './constants/description' ;
1011import { once } from 'foxts/once' ;
1112import * as yaml from 'yaml' ;
1213import { appendArrayInPlace } from 'foxts/append-array-in-place' ;
1314import { OUTPUT_INTERNAL_DIR , OUTPUT_MODULES_DIR , OUTPUT_MODULES_RULES_DIR , SOURCE_DIR } from './constants/dir' ;
1415import { MihomoNameserverPolicyOutput , RulesetOutput } from './lib/rules/ruleset' ;
1516import { SurgeOnlyRulesetOutput } from './lib/rules/ruleset' ;
17+ import { $$fetch } from './lib/fetch-retry' ;
1618
1719export function createGetDnsMappingRule ( allowWildcard : boolean ) {
1820 const hasWildcard = ( domain : string ) => {
@@ -112,6 +114,7 @@ export const buildDomesticRuleset = task(require.main === module, __filename)(as
112114 . addFromRuleset ( lans )
113115 . write ( ) ,
114116
117+ buildLANCacheRuleset ( span ) ,
115118 ...dataset . map ( ( [ name , { ruleset, domains } ] ) => {
116119 if ( ! ruleset ) {
117120 return ;
@@ -340,3 +343,84 @@ export const buildDomesticRuleset = task(require.main === module, __filename)(as
340343 )
341344 ] ) ;
342345} ) ;
346+
347+ async function buildLANCacheRuleset ( span : Span ) {
348+ const childSpan = span . traceChild ( 'build LAN cache ruleset' ) ;
349+
350+ 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 ( ) ) ;
351+ if ( ! cacheDomainsData || typeof cacheDomainsData !== 'object' || ! ( 'cache_domains' in cacheDomainsData ) || ! Array . isArray ( cacheDomainsData . cache_domains ) ) {
352+ throw new TypeError ( 'Invalid cache domains data' ) ;
353+ }
354+ const allDomainFiles = cacheDomainsData . cache_domains . reduce < string [ ] > ( ( acc , { domain_files } ) => {
355+ if ( Array . isArray ( domain_files ) ) {
356+ appendArrayInPlace ( acc , domain_files ) ;
357+ }
358+ return acc ;
359+ } , [ ] ) ;
360+
361+ const allDomains = (
362+ await Promise . all (
363+ allDomainFiles . map (
364+ async ( file ) => childSpan . traceChildAsync (
365+ 'download ' + file ,
366+ async ( ) => Array . fromAsync ( await fetchRemoteTextByLine ( 'https://cdn.jsdelivr.net/gh/uklans/cache-domains@master/' + file , true ) )
367+ )
368+ )
369+ )
370+ ) . flat ( ) ;
371+
372+ const surgeOutput = new SurgeOnlyRulesetOutput (
373+ span ,
374+ 'lancache' ,
375+ 'sukka_local_dns_mapping' ,
376+ OUTPUT_MODULES_RULES_DIR
377+ )
378+ . withTitle ( 'Sukka\'s Ruleset - Local DNS Mapping (lancache)' )
379+ . appendDescription (
380+ SHARED_DESCRIPTION ,
381+ '' ,
382+ 'This is an internal rule that is only referenced by sukka_local_dns_mapping.sgmodule' ,
383+ 'Do not use this file in your Rule section.'
384+ ) ;
385+
386+ const mihomoOutput = new MihomoNameserverPolicyOutput (
387+ span ,
388+ 'lancache' ,
389+ 'mihomo_nameserver_policy' ,
390+ OUTPUT_INTERNAL_DIR
391+ )
392+ . withTitle ( 'Sukka\'s Ruleset - Local DNS Mapping for Mihomo NameServer Policy (lancache)' )
393+ . appendDescription (
394+ SHARED_DESCRIPTION ,
395+ '' ,
396+ 'This ruleset is only used for mihomo\'s nameserver-policy feature, which' ,
397+ 'is similar to the RULE-SET referenced by sukka_local_dns_mapping.sgmodule.' ,
398+ 'Do not use this file in your Rule section.'
399+ ) ;
400+
401+ for ( let i = 0 , len = allDomains . length ; i < len ; i ++ ) {
402+ const domain = allDomains [ i ] ;
403+
404+ if ( domain . includes ( '*' ) ) {
405+ // If only *. prefix is used, we can convert it to DOMAIN-SUFFIX
406+ if ( domain . startsWith ( '*.' ) && ! domain . slice ( 2 ) . includes ( '*' ) ) {
407+ const domainSuffix = domain . slice ( 2 ) ;
408+ surgeOutput . addDomainSuffix ( domainSuffix ) ;
409+ mihomoOutput . addDomainSuffix ( domainSuffix ) ;
410+ continue ;
411+ }
412+
413+ surgeOutput . addDomainWildcard ( domain ) ;
414+ mihomoOutput . addDomainWildcard ( domain ) ;
415+ continue ;
416+ }
417+
418+ surgeOutput . addDomain ( domain ) ;
419+ mihomoOutput . addDomain ( domain ) ;
420+ }
421+
422+ return Promise . all ( [
423+ surgeOutput . write ( ) ,
424+ mihomoOutput . write ( )
425+ ] ) ;
426+ }
0 commit comments