Feat: sukka_local_dns_mapping.sgmodule now uses new rule-set syntax

This commit is contained in:
SukkaW 2025-01-17 14:43:35 +08:00
parent 8d5da9776a
commit 6858cd63b4
6 changed files with 119 additions and 52 deletions

View File

@ -10,7 +10,7 @@ import { SHARED_DESCRIPTION } from './constants/description';
import { createMemoizedPromise } from './lib/memo-promise'; import { createMemoizedPromise } from './lib/memo-promise';
import * as yaml from 'yaml'; import * as yaml from 'yaml';
import { appendArrayInPlace } from './lib/append-array-in-place'; 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'; import { RulesetOutput } from './lib/create-file';
export function createGetDnsMappingRule(allowWildcard: boolean) { export function createGetDnsMappingRule(allowWildcard: boolean) {
@ -78,7 +78,7 @@ export const getDomesticAndDirectDomainsRulesetPromise = createMemoizedPromise(a
export const buildDomesticRuleset = task(require.main === module, __filename)(async (span) => { export const buildDomesticRuleset = task(require.main === module, __filename)(async (span) => {
const [domestics, directs, lans] = await getDomesticAndDirectDomainsRulesetPromise(); 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([ return Promise.all([
new RulesetOutput(span, 'domestic', 'non_ip') new RulesetOutput(span, 'domestic', 'non_ip')
@ -108,6 +108,41 @@ export const buildDomesticRuleset = task(require.main === module, __filename)(as
]) ])
.addFromRuleset(lans) .addFromRuleset(lans)
.write(), .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( compareAndWriteFile(
span, span,
[ [
@ -119,26 +154,31 @@ export const buildDomesticRuleset = task(require.main === module, __filename)(as
// I use an object to deduplicate the domains // I use an object to deduplicate the domains
// Otherwise I could just construct an array directly // Otherwise I could just construct an array directly
dataset.reduce<Record<string, string>>((acc, cur) => { dataset.reduce<Record<string, string>>((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]) => { Object.entries(hosts).forEach(([dns, ips]) => {
acc[dns] ||= ips.join(', '); acc[dns] ||= ips.join(', ');
}); });
domains.forEach((domain) => { if (ruleset) {
switch (domain[0]) { acc[`RULE-SET:https://ruleset.skk.moe/Modules/Rules/sukka_local_dns_mapping/${ruleset_name}.conf`] ||= `server:${dns}`;
case '$': } else {
acc[domain.slice(1)] ||= `server:${dns}`; domains.forEach((domain) => {
break; switch (domain[0]) {
case '+': case '$':
acc[`*.${domain.slice(1)}`] ||= `server:${dns}`; acc[domain.slice(1)] ||= `server:${dns}`;
break; break;
default: case '+':
acc[domain] ||= `server:${dns}`; acc[`*.${domain.slice(1)}`] ||= `server:${dns}`;
acc[`*.${domain}`] ||= `server:${dns}`; break;
break; default:
} acc[domain] ||= `server:${dns}`;
}); acc[`*.${domain}`] ||= `server:${dns}`;
break;
}
});
}
return acc; return acc;
}, {}) }, {})
@ -153,7 +193,7 @@ export const buildDomesticRuleset = task(require.main === module, __filename)(as
dns: { 'nameserver-policy': Record<string, string | string[]> }, dns: { 'nameserver-policy': Record<string, string | string[]> },
hosts: Record<string, string> hosts: Record<string, string>
}>((acc, cur) => { }>((acc, cur) => {
const { domains, dns, ...rest } = cur; const { domains, dns, ...rest } = cur[1];
domains.forEach((domain) => { domains.forEach((domain) => {
let domainWildcard = domain; let domainWildcard = domain;
if (domain[0] === '$') { if (domain[0] === '$') {

View File

@ -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_CLASH_DIR = path.resolve(PUBLIC_DIR, 'Clash');
export const OUTPUT_SINGBOX_DIR = path.resolve(PUBLIC_DIR, 'sing-box'); 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_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_INTERNAL_DIR = path.resolve(PUBLIC_DIR, 'Internal');
export const OUTPUT_MOCK_DIR = path.resolve(PUBLIC_DIR, 'Mock'); export const OUTPUT_MOCK_DIR = path.resolve(PUBLIC_DIR, 'Mock');

View File

@ -34,7 +34,7 @@ export abstract class RuleOutput<TPreprocessed = unknown> {
protected destPort = new Set<string>(); protected destPort = new Set<string>();
protected otherRules: string[] = []; protected otherRules: string[] = [];
protected abstract type: 'domainset' | 'non_ip' | 'ip'; protected abstract type: 'domainset' | 'non_ip' | 'ip' | (string & {});
private pendingPromise: Promise<any> | null = null; private pendingPromise: Promise<any> | null = null;
@ -295,13 +295,29 @@ export abstract class RuleOutput<TPreprocessed = unknown> {
); );
} }
write(): Promise<void> { 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<void> {
return this.done().then(() => this.span.traceChildAsync('write all', async () => { return this.done().then(() => this.span.traceChildAsync('write all', async () => {
invariant(this.title, 'Missing title'); invariant(this.title, 'Missing title');
invariant(this.description, 'Missing description'); invariant(this.description, 'Missing description');
const promises = [ const promises: Array<Promise<void>> = [];
compareAndWriteFile(
if (surge) {
promises.push(compareAndWriteFile(
this.span, this.span,
withBannerArray( withBannerArray(
this.title, this.title,
@ -309,9 +325,11 @@ export abstract class RuleOutput<TPreprocessed = unknown> {
this.date, this.date,
this.surge() this.surge()
), ),
path.join(OUTPUT_SURGE_DIR, this.type, this.id + '.conf') path.join(surgeDir, this.type, this.id + '.conf')
), ));
compareAndWriteFile( }
if (clash) {
promises.push(compareAndWriteFile(
this.span, this.span,
withBannerArray( withBannerArray(
this.title, this.title,
@ -319,14 +337,16 @@ export abstract class RuleOutput<TPreprocessed = unknown> {
this.date, this.date,
this.clash() this.clash()
), ),
path.join(OUTPUT_CLASH_DIR, this.type, this.id + '.txt') path.join(clashDir, this.type, this.id + '.txt')
), ));
compareAndWriteFile( }
if (singbox) {
promises.push(compareAndWriteFile(
this.span, this.span,
this.singbox(), this.singbox(),
path.join(OUTPUT_SINGBOX_DIR, this.type, this.id + '.json') path.join(singboxDir, this.type, this.id + '.json')
) ));
]; }
if (this.mitmSgmodule) { if (this.mitmSgmodule) {
const sgmodule = this.mitmSgmodule(); const sgmodule = this.mitmSgmodule();

View File

@ -12,7 +12,7 @@ import { isProbablyIpv4, isProbablyIpv6 } from 'foxts/is-probably-ip';
type Preprocessed = [domain: string[], domainSuffix: string[], sortedDomainRules: string[]]; type Preprocessed = [domain: string[], domainSuffix: string[], sortedDomainRules: string[]];
export class RulesetOutput extends RuleOutput<Preprocessed> { export class RulesetOutput extends RuleOutput<Preprocessed> {
constructor(span: Span, id: string, protected type: 'non_ip' | 'ip') { constructor(span: Span, id: string, protected type: 'non_ip' | 'ip' | (string & {})) {
super(span, id); super(span, id);
} }

View File

@ -4,6 +4,8 @@ export interface DNSMapping {
}, },
/** which also disallows wildcard */ /** which also disallows wildcard */
realip: boolean, realip: boolean,
/** should convert to ruleset */
ruleset: boolean,
dns: string, dns: string,
/** /**
* domain[0] * domain[0]
@ -20,6 +22,7 @@ export const DIRECTS = {
dns: 'system', dns: 'system',
hosts: {}, hosts: {},
realip: false, realip: false,
ruleset: false,
domains: [ domains: [
'securelogin.com.cn', 'securelogin.com.cn',
'$captive.apple.com', '$captive.apple.com',
@ -30,6 +33,7 @@ export const DIRECTS = {
dns: 'system', dns: 'system',
hosts: {}, hosts: {},
realip: true, realip: true,
ruleset: false,
domains: [ domains: [
'+m2m', '+m2m',
// '+ts.net', // TailScale Magic DNS // '+ts.net', // TailScale Magic DNS
@ -47,6 +51,7 @@ export const LAN = {
dns: 'system', dns: 'system',
hosts: {}, hosts: {},
realip: false, realip: false,
ruleset: true,
domains: [ domains: [
'+home', '+home',
// 'zte.home', // ZTE CPE // 'zte.home', // ZTE CPE
@ -106,6 +111,7 @@ export const LAN = {
localhost: ['127.0.0.1'] localhost: ['127.0.0.1']
}, },
realip: true, realip: true,
ruleset: true,
domains: [ domains: [
'+lan', '+lan',
// 'amplifi.lan', // 'amplifi.lan',

View File

@ -5,6 +5,7 @@ export const DOMESTICS: Record<string, DNSMapping> = {
hosts: {}, hosts: {},
dns: 'quic://dns.alidns.com:853', dns: 'quic://dns.alidns.com:853',
realip: false, realip: false,
ruleset: true,
domains: [ domains: [
'uc.cn', 'uc.cn',
// 'ucweb.com', // UC International // 'ucweb.com', // UC International
@ -82,13 +83,18 @@ export const DOMESTICS: Record<string, DNSMapping> = {
'tanx.com', 'tanx.com',
'hellobike.com', 'hellobike.com',
'+hichina.com', '+hichina.com',
'+yunos.com' '+yunos.com',
// Bilibili Aliyun CDN
'$upos-sz-mirrorali.bilivideo.com',
'$upos-sz-estgoss.bilivideo.com'
] ]
}, },
TENCENT: { TENCENT: {
hosts: {}, hosts: {},
dns: 'https://doh.pub/dns-query', dns: 'https://doh.pub/dns-query',
realip: false, realip: false,
ruleset: true,
domains: [ domains: [
// 'dns.pub', // 'dns.pub',
// 'doh.pub', // 'doh.pub',
@ -144,28 +150,11 @@ export const DOMESTICS: Record<string, DNSMapping> = {
'+codehub.cn' '+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: { BILIBILI: {
dns: 'https://doh.pub/dns-query', dns: 'https://doh.pub/dns-query',
hosts: {}, hosts: {},
realip: false, realip: false,
ruleset: true,
domains: [ domains: [
// '$upos-sz-mirrorcoso1.bilivideo.com', // already included in bilivideo.com // '$upos-sz-mirrorcoso1.bilivideo.com', // already included in bilivideo.com
// '$upos-sz-estgcos.bilivideo.com', // already included in bilivideo.com, tencent cloud cdn // '$upos-sz-estgcos.bilivideo.com', // already included in bilivideo.com, tencent cloud cdn
@ -196,6 +185,7 @@ export const DOMESTICS: Record<string, DNSMapping> = {
dns: 'https://doh.pub/dns-query', dns: 'https://doh.pub/dns-query',
hosts: {}, hosts: {},
realip: false, realip: false,
ruleset: true,
domains: [ domains: [
'mi.com', 'mi.com',
'duokan.com', 'duokan.com',
@ -218,6 +208,7 @@ export const DOMESTICS: Record<string, DNSMapping> = {
dns: '180.184.2.2', dns: '180.184.2.2',
hosts: {}, hosts: {},
realip: false, realip: false,
ruleset: true,
domains: [ domains: [
'bytedance.com', 'bytedance.com',
'+bytecdn.cn', '+bytecdn.cn',
@ -271,6 +262,7 @@ export const DOMESTICS: Record<string, DNSMapping> = {
dns: '180.76.76.76', dns: '180.76.76.76',
hosts: {}, hosts: {},
realip: false, realip: false,
ruleset: true,
domains: [ domains: [
'91.com', '91.com',
'hao123.com', 'hao123.com',
@ -295,13 +287,18 @@ export const DOMESTICS: Record<string, DNSMapping> = {
'+bdydns.com', '+bdydns.com',
'+jomoxc.com', // Baidu PCDN, of sort '+jomoxc.com', // Baidu PCDN, of sort
'+duapp.com', '+duapp.com',
'+antpcdn.com' // Baidu PCDN '+antpcdn.com', // Baidu PCDN
// Bilibili Baidu CDN
'$upos-sz-mirrorbd.bilivideo.com',
'$upos-sz-mirrorbos.bilivideo.com'
] ]
}, },
QIHOO360: { QIHOO360: {
hosts: {}, hosts: {},
dns: 'https://doh.360.cn/dns-query', dns: 'https://doh.360.cn/dns-query',
realip: false, realip: false,
ruleset: true,
domains: [ domains: [
'+qhimg.com', '+qhimg.com',
'+qhimgs.com', '+qhimgs.com',
@ -350,6 +347,7 @@ export const DOH_BOOTSTRAP: Record<string, DNSMapping> = {
'dns.alidns.com': ['223.5.5.5', '223.6.6.6', '2400:3200:baba::1', '2400:3200::1'] 'dns.alidns.com': ['223.5.5.5', '223.6.6.6', '2400:3200:baba::1', '2400:3200::1']
}, },
realip: false, realip: false,
ruleset: false,
dns: 'quic://223.5.5.5:853', dns: 'quic://223.5.5.5:853',
domains: [ domains: [
'$dns.alidns.com' '$dns.alidns.com'
@ -362,6 +360,7 @@ export const DOH_BOOTSTRAP: Record<string, DNSMapping> = {
// 'dns.pub': ['120.53.53.53', '1.12.12.12'] // 'dns.pub': ['120.53.53.53', '1.12.12.12']
}, },
realip: false, realip: false,
ruleset: false,
dns: 'https://1.12.12.12/dns-query', dns: 'https://1.12.12.12/dns-query',
domains: [ domains: [
// '$dot.pub', // '$dot.pub',
@ -382,6 +381,7 @@ export const DOH_BOOTSTRAP: Record<string, DNSMapping> = {
// dot.360.net CNAME dns.360.net // dot.360.net CNAME dns.360.net
}, },
realip: false, realip: false,
ruleset: false,
// Surge only supports UDP 53 or Hosts as the bootstrap server of domain DoH // 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 dns: '101.198.198.198', // 'https://101.198.198.198/dns-query', // https://101.198.199.200/dns-query
domains: [ domains: [