From 6858cd63b46aa66e3f40fe2e4092167c98145ab5 Mon Sep 17 00:00:00 2001 From: SukkaW Date: Fri, 17 Jan 2025 14:43:35 +0800 Subject: [PATCH] Feat: `sukka_local_dns_mapping.sgmodule` now uses new rule-set syntax --- ...c-direct-lan-ruleset-dns-mapping-module.ts | 76 ++++++++++++++----- Build/constants/dir.ts | 1 + Build/lib/rules/base.ts | 46 +++++++---- Build/lib/rules/ruleset.ts | 2 +- Source/non_ip/direct.ts | 6 ++ Source/non_ip/domestic.ts | 40 +++++----- 6 files changed, 119 insertions(+), 52 deletions(-) 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 e501aded..cc16a071 100644 --- a/Build/build-domestic-direct-lan-ruleset-dns-mapping-module.ts +++ b/Build/build-domestic-direct-lan-ruleset-dns-mapping-module.ts @@ -10,7 +10,7 @@ import { SHARED_DESCRIPTION } from './constants/description'; import { createMemoizedPromise } from './lib/memo-promise'; import * as yaml from 'yaml'; import { appendArrayInPlace } from './lib/append-array-in-place'; -import { OUTPUT_INTERNAL_DIR, OUTPUT_MODULES_DIR, SOURCE_DIR } from './constants/dir'; +import { OUTPUT_INTERNAL_DIR, OUTPUT_MODULES_DIR, OUTPUT_MODULES_RULES_DIR, SOURCE_DIR } from './constants/dir'; import { RulesetOutput } from './lib/create-file'; export function createGetDnsMappingRule(allowWildcard: boolean) { @@ -78,7 +78,7 @@ export const getDomesticAndDirectDomainsRulesetPromise = createMemoizedPromise(a export const buildDomesticRuleset = task(require.main === module, __filename)(async (span) => { const [domestics, directs, lans] = await getDomesticAndDirectDomainsRulesetPromise(); - const dataset: DNSMapping[] = ([DOH_BOOTSTRAP, DOMESTICS, DIRECTS] as const).flatMap(Object.values); + const dataset: Array<[name: string, DNSMapping]> = ([DOH_BOOTSTRAP, DOMESTICS, DIRECTS, LAN] as const).flatMap(Object.entries); return Promise.all([ new RulesetOutput(span, 'domestic', 'non_ip') @@ -108,6 +108,41 @@ export const buildDomesticRuleset = task(require.main === module, __filename)(as ]) .addFromRuleset(lans) .write(), + + ...dataset.map(([name, { ruleset, domains }]) => { + if (!ruleset) { + return; + } + + const output = new RulesetOutput(span, name.toLowerCase(), 'sukka_local_dns_mapping').withTitle(`Sukka's Ruleset - Local DNS Mapping (${name})`).withDescription([ + ...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, all rules are included in non_ip/domestic.conf already.' + ]); + + domains.forEach((domain) => { + switch (domain[0]) { + case '$': + output.addDomain(domain.slice(1)); + break; + case '+': + output.addDomainSuffix(domain.slice(1)); + break; + default: + output.addDomainSuffix(domain); + break; + } + }); + + return output.write({ + surge: true, + clash: false, + singbox: false, + surgeDir: OUTPUT_MODULES_RULES_DIR + }); + }), + compareAndWriteFile( span, [ @@ -119,26 +154,31 @@ export const buildDomesticRuleset = task(require.main === module, __filename)(as // I use an object to deduplicate the domains // Otherwise I could just construct an array directly dataset.reduce>((acc, cur) => { - const { domains, dns, hosts } = cur; + const ruleset_name = cur[0].toLowerCase(); + const { domains, dns, hosts, ruleset } = cur[1]; Object.entries(hosts).forEach(([dns, ips]) => { acc[dns] ||= ips.join(', '); }); - domains.forEach((domain) => { - switch (domain[0]) { - case '$': - acc[domain.slice(1)] ||= `server:${dns}`; - break; - case '+': - acc[`*.${domain.slice(1)}`] ||= `server:${dns}`; - break; - default: - acc[domain] ||= `server:${dns}`; - acc[`*.${domain}`] ||= `server:${dns}`; - break; - } - }); + if (ruleset) { + acc[`RULE-SET:https://ruleset.skk.moe/Modules/Rules/sukka_local_dns_mapping/${ruleset_name}.conf`] ||= `server:${dns}`; + } else { + domains.forEach((domain) => { + switch (domain[0]) { + case '$': + acc[domain.slice(1)] ||= `server:${dns}`; + break; + case '+': + acc[`*.${domain.slice(1)}`] ||= `server:${dns}`; + break; + default: + acc[domain] ||= `server:${dns}`; + acc[`*.${domain}`] ||= `server:${dns}`; + break; + } + }); + } return acc; }, {}) @@ -153,7 +193,7 @@ export const buildDomesticRuleset = task(require.main === module, __filename)(as dns: { 'nameserver-policy': Record }, hosts: Record }>((acc, cur) => { - const { domains, dns, ...rest } = cur; + const { domains, dns, ...rest } = cur[1]; domains.forEach((domain) => { let domainWildcard = domain; if (domain[0] === '$') { diff --git a/Build/constants/dir.ts b/Build/constants/dir.ts index 335bb5cd..95615426 100644 --- a/Build/constants/dir.ts +++ b/Build/constants/dir.ts @@ -11,5 +11,6 @@ export const OUTPUT_SURGE_DIR = path.join(PUBLIC_DIR, 'List'); export const OUTPUT_CLASH_DIR = path.resolve(PUBLIC_DIR, 'Clash'); export const OUTPUT_SINGBOX_DIR = path.resolve(PUBLIC_DIR, 'sing-box'); export const OUTPUT_MODULES_DIR = path.resolve(PUBLIC_DIR, 'Modules'); +export const OUTPUT_MODULES_RULES_DIR = path.resolve(OUTPUT_MODULES_DIR, 'Rules'); export const OUTPUT_INTERNAL_DIR = path.resolve(PUBLIC_DIR, 'Internal'); export const OUTPUT_MOCK_DIR = path.resolve(PUBLIC_DIR, 'Mock'); diff --git a/Build/lib/rules/base.ts b/Build/lib/rules/base.ts index 1d681a38..b0ddf289 100644 --- a/Build/lib/rules/base.ts +++ b/Build/lib/rules/base.ts @@ -34,7 +34,7 @@ export abstract class RuleOutput { protected destPort = new Set(); protected otherRules: string[] = []; - protected abstract type: 'domainset' | 'non_ip' | 'ip'; + protected abstract type: 'domainset' | 'non_ip' | 'ip' | (string & {}); private pendingPromise: Promise | null = null; @@ -295,13 +295,29 @@ export abstract class RuleOutput { ); } - write(): Promise { + write({ + surge = true, + clash = true, + singbox = true, + surgeDir = OUTPUT_SURGE_DIR, + clashDir = OUTPUT_CLASH_DIR, + singboxDir = OUTPUT_SINGBOX_DIR + }: { + surge?: boolean, + clash?: boolean, + singbox?: boolean, + surgeDir?: string, + clashDir?: string, + singboxDir?: string + } = {}): Promise { return this.done().then(() => this.span.traceChildAsync('write all', async () => { invariant(this.title, 'Missing title'); invariant(this.description, 'Missing description'); - const promises = [ - compareAndWriteFile( + const promises: Array> = []; + + if (surge) { + promises.push(compareAndWriteFile( this.span, withBannerArray( this.title, @@ -309,9 +325,11 @@ export abstract class RuleOutput { this.date, this.surge() ), - path.join(OUTPUT_SURGE_DIR, this.type, this.id + '.conf') - ), - compareAndWriteFile( + path.join(surgeDir, this.type, this.id + '.conf') + )); + } + if (clash) { + promises.push(compareAndWriteFile( this.span, withBannerArray( this.title, @@ -319,14 +337,16 @@ export abstract class RuleOutput { this.date, this.clash() ), - path.join(OUTPUT_CLASH_DIR, this.type, this.id + '.txt') - ), - compareAndWriteFile( + path.join(clashDir, this.type, this.id + '.txt') + )); + } + if (singbox) { + promises.push(compareAndWriteFile( this.span, this.singbox(), - path.join(OUTPUT_SINGBOX_DIR, this.type, this.id + '.json') - ) - ]; + path.join(singboxDir, this.type, this.id + '.json') + )); + } if (this.mitmSgmodule) { const sgmodule = this.mitmSgmodule(); diff --git a/Build/lib/rules/ruleset.ts b/Build/lib/rules/ruleset.ts index 87da0865..c45e1cf5 100644 --- a/Build/lib/rules/ruleset.ts +++ b/Build/lib/rules/ruleset.ts @@ -12,7 +12,7 @@ import { isProbablyIpv4, isProbablyIpv6 } from 'foxts/is-probably-ip'; type Preprocessed = [domain: string[], domainSuffix: string[], sortedDomainRules: string[]]; export class RulesetOutput extends RuleOutput { - constructor(span: Span, id: string, protected type: 'non_ip' | 'ip') { + constructor(span: Span, id: string, protected type: 'non_ip' | 'ip' | (string & {})) { super(span, id); } diff --git a/Source/non_ip/direct.ts b/Source/non_ip/direct.ts index 1217d37c..4b55af00 100644 --- a/Source/non_ip/direct.ts +++ b/Source/non_ip/direct.ts @@ -4,6 +4,8 @@ export interface DNSMapping { }, /** which also disallows wildcard */ realip: boolean, + /** should convert to ruleset */ + ruleset: boolean, dns: string, /** * domain[0] @@ -20,6 +22,7 @@ export const DIRECTS = { dns: 'system', hosts: {}, realip: false, + ruleset: false, domains: [ 'securelogin.com.cn', '$captive.apple.com', @@ -30,6 +33,7 @@ export const DIRECTS = { dns: 'system', hosts: {}, realip: true, + ruleset: false, domains: [ '+m2m', // '+ts.net', // TailScale Magic DNS @@ -47,6 +51,7 @@ export const LAN = { dns: 'system', hosts: {}, realip: false, + ruleset: true, domains: [ '+home', // 'zte.home', // ZTE CPE @@ -106,6 +111,7 @@ export const LAN = { localhost: ['127.0.0.1'] }, realip: true, + ruleset: true, domains: [ '+lan', // 'amplifi.lan', diff --git a/Source/non_ip/domestic.ts b/Source/non_ip/domestic.ts index 9e16f18e..62fb2569 100644 --- a/Source/non_ip/domestic.ts +++ b/Source/non_ip/domestic.ts @@ -5,6 +5,7 @@ export const DOMESTICS: Record = { hosts: {}, dns: 'quic://dns.alidns.com:853', realip: false, + ruleset: true, domains: [ 'uc.cn', // 'ucweb.com', // UC International @@ -82,13 +83,18 @@ export const DOMESTICS: Record = { 'tanx.com', 'hellobike.com', '+hichina.com', - '+yunos.com' + '+yunos.com', + + // Bilibili Aliyun CDN + '$upos-sz-mirrorali.bilivideo.com', + '$upos-sz-estgoss.bilivideo.com' ] }, TENCENT: { hosts: {}, dns: 'https://doh.pub/dns-query', realip: false, + ruleset: true, domains: [ // 'dns.pub', // 'doh.pub', @@ -144,28 +150,11 @@ export const DOMESTICS: Record = { '+codehub.cn' ] }, - BILIBILI_ALI: { - dns: 'quic://dns.alidns.com:853', - hosts: {}, - realip: false, - domains: [ - '$upos-sz-mirrorali.bilivideo.com', - '$upos-sz-estgoss.bilivideo.com' - ] - }, - BILIBILI_BD: { - dns: '180.76.76.76', - hosts: {}, - realip: false, - domains: [ - '$upos-sz-mirrorbd.bilivideo.com', - '$upos-sz-mirrorbos.bilivideo.com' - ] - }, BILIBILI: { dns: 'https://doh.pub/dns-query', hosts: {}, realip: false, + ruleset: true, domains: [ // '$upos-sz-mirrorcoso1.bilivideo.com', // already included in bilivideo.com // '$upos-sz-estgcos.bilivideo.com', // already included in bilivideo.com, tencent cloud cdn @@ -196,6 +185,7 @@ export const DOMESTICS: Record = { dns: 'https://doh.pub/dns-query', hosts: {}, realip: false, + ruleset: true, domains: [ 'mi.com', 'duokan.com', @@ -218,6 +208,7 @@ export const DOMESTICS: Record = { dns: '180.184.2.2', hosts: {}, realip: false, + ruleset: true, domains: [ 'bytedance.com', '+bytecdn.cn', @@ -271,6 +262,7 @@ export const DOMESTICS: Record = { dns: '180.76.76.76', hosts: {}, realip: false, + ruleset: true, domains: [ '91.com', 'hao123.com', @@ -295,13 +287,18 @@ export const DOMESTICS: Record = { '+bdydns.com', '+jomoxc.com', // Baidu PCDN, of sort '+duapp.com', - '+antpcdn.com' // Baidu PCDN + '+antpcdn.com', // Baidu PCDN + + // Bilibili Baidu CDN + '$upos-sz-mirrorbd.bilivideo.com', + '$upos-sz-mirrorbos.bilivideo.com' ] }, QIHOO360: { hosts: {}, dns: 'https://doh.360.cn/dns-query', realip: false, + ruleset: true, domains: [ '+qhimg.com', '+qhimgs.com', @@ -350,6 +347,7 @@ export const DOH_BOOTSTRAP: Record = { 'dns.alidns.com': ['223.5.5.5', '223.6.6.6', '2400:3200:baba::1', '2400:3200::1'] }, realip: false, + ruleset: false, dns: 'quic://223.5.5.5:853', domains: [ '$dns.alidns.com' @@ -362,6 +360,7 @@ export const DOH_BOOTSTRAP: Record = { // 'dns.pub': ['120.53.53.53', '1.12.12.12'] }, realip: false, + ruleset: false, dns: 'https://1.12.12.12/dns-query', domains: [ // '$dot.pub', @@ -382,6 +381,7 @@ export const DOH_BOOTSTRAP: Record = { // dot.360.net CNAME dns.360.net }, realip: false, + ruleset: false, // Surge only supports UDP 53 or Hosts as the bootstrap server of domain DoH dns: '101.198.198.198', // 'https://101.198.198.198/dns-query', // https://101.198.199.200/dns-query domains: [