mirror of
https://github.com/SukkaW/Surge.git
synced 2025-12-12 17:20:35 +08:00
Add sing-box support
This commit is contained in:
parent
12665a743d
commit
75f188f1c1
1
.gitignore
vendored
1
.gitignore
vendored
@ -10,6 +10,7 @@ tmp*
|
|||||||
List/
|
List/
|
||||||
Clash/
|
Clash/
|
||||||
Internal/
|
Internal/
|
||||||
|
sing-box/
|
||||||
Modules/sukka_local_dns_mapping.sgmodule
|
Modules/sukka_local_dns_mapping.sgmodule
|
||||||
Modules/sukka_url_redirect.sgmodule
|
Modules/sukka_url_redirect.sgmodule
|
||||||
Modules/sukka_common_always_realip.sgmodule
|
Modules/sukka_common_always_realip.sgmodule
|
||||||
|
|||||||
@ -43,7 +43,8 @@ export const buildAppleCdn = task(require.main === module, __filename)(async (sp
|
|||||||
ruleset,
|
ruleset,
|
||||||
'ruleset',
|
'ruleset',
|
||||||
path.resolve(__dirname, '../List/non_ip/apple_cdn.conf'),
|
path.resolve(__dirname, '../List/non_ip/apple_cdn.conf'),
|
||||||
path.resolve(__dirname, '../Clash/non_ip/apple_cdn.txt')
|
path.resolve(__dirname, '../Clash/non_ip/apple_cdn.txt'),
|
||||||
|
path.resolve(__dirname, '../sing-box/non_ip/apple_cdn.json')
|
||||||
),
|
),
|
||||||
createRuleset(
|
createRuleset(
|
||||||
span,
|
span,
|
||||||
@ -54,6 +55,7 @@ export const buildAppleCdn = task(require.main === module, __filename)(async (sp
|
|||||||
'domainset',
|
'domainset',
|
||||||
path.resolve(__dirname, '../List/domainset/apple_cdn.conf'),
|
path.resolve(__dirname, '../List/domainset/apple_cdn.conf'),
|
||||||
path.resolve(__dirname, '../Clash/domainset/apple_cdn.txt'),
|
path.resolve(__dirname, '../Clash/domainset/apple_cdn.txt'),
|
||||||
|
path.resolve(__dirname, '../sing-box/domainset/apple_cdn.json'),
|
||||||
path.resolve(__dirname, '../Clash/clash_mrs_domain/apple_cdn.mrs')
|
path.resolve(__dirname, '../Clash/clash_mrs_domain/apple_cdn.mrs')
|
||||||
)
|
)
|
||||||
]);
|
]);
|
||||||
|
|||||||
@ -79,6 +79,7 @@ export const buildCdnDownloadConf = task(require.main === module, __filename)(as
|
|||||||
'domainset',
|
'domainset',
|
||||||
path.resolve(__dirname, '../List/domainset/cdn.conf'),
|
path.resolve(__dirname, '../List/domainset/cdn.conf'),
|
||||||
path.resolve(__dirname, '../Clash/domainset/cdn.txt'),
|
path.resolve(__dirname, '../Clash/domainset/cdn.txt'),
|
||||||
|
path.resolve(__dirname, '../sing-box/domainset/cdn.json'),
|
||||||
path.resolve(__dirname, '../Clash/clash_mrs_domain/cdn.mrs')
|
path.resolve(__dirname, '../Clash/clash_mrs_domain/cdn.mrs')
|
||||||
),
|
),
|
||||||
createRuleset(
|
createRuleset(
|
||||||
@ -94,6 +95,7 @@ export const buildCdnDownloadConf = task(require.main === module, __filename)(as
|
|||||||
'domainset',
|
'domainset',
|
||||||
path.resolve(__dirname, '../List/domainset/download.conf'),
|
path.resolve(__dirname, '../List/domainset/download.conf'),
|
||||||
path.resolve(__dirname, '../Clash/domainset/download.txt'),
|
path.resolve(__dirname, '../Clash/domainset/download.txt'),
|
||||||
|
path.resolve(__dirname, '../sing-box/domainset/download.json'),
|
||||||
path.resolve(__dirname, '../Clash/clash_mrs_domain/download.mrs')
|
path.resolve(__dirname, '../Clash/clash_mrs_domain/download.mrs')
|
||||||
)
|
)
|
||||||
]);
|
]);
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { task } from './trace';
|
|||||||
|
|
||||||
const outputSurgeDir = path.resolve(__dirname, '../List');
|
const outputSurgeDir = path.resolve(__dirname, '../List');
|
||||||
const outputClashDir = path.resolve(__dirname, '../Clash');
|
const outputClashDir = path.resolve(__dirname, '../Clash');
|
||||||
|
const outputSingboxDir = path.resolve(__dirname, '../sing-box');
|
||||||
|
|
||||||
export const buildCloudMounterRules = task(require.main === module, __filename)(async (span) => {
|
export const buildCloudMounterRules = task(require.main === module, __filename)(async (span) => {
|
||||||
// AND,((SRC-IP,192.168.1.110), (DOMAIN, example.com))
|
// AND,((SRC-IP,192.168.1.110), (DOMAIN, example.com))
|
||||||
@ -24,6 +25,7 @@ export const buildCloudMounterRules = task(require.main === module, __filename)(
|
|||||||
results,
|
results,
|
||||||
'ruleset',
|
'ruleset',
|
||||||
path.resolve(outputSurgeDir, 'non_ip', 'cloudmounter.conf'),
|
path.resolve(outputSurgeDir, 'non_ip', 'cloudmounter.conf'),
|
||||||
path.resolve(outputClashDir, 'non_ip', 'cloudmounter.txt')
|
path.resolve(outputClashDir, 'non_ip', 'cloudmounter.txt'),
|
||||||
|
path.resolve(outputSingboxDir, 'non_ip', 'cloudmounter.json')
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -18,6 +18,7 @@ const MAGIC_COMMAND_DESCRIPTION = '# $ meta_description ';
|
|||||||
const sourceDir = path.resolve(__dirname, '../Source');
|
const sourceDir = path.resolve(__dirname, '../Source');
|
||||||
const outputSurgeDir = path.resolve(__dirname, '../List');
|
const outputSurgeDir = path.resolve(__dirname, '../List');
|
||||||
const outputClashDir = path.resolve(__dirname, '../Clash');
|
const outputClashDir = path.resolve(__dirname, '../Clash');
|
||||||
|
const outputSingboxDir = path.resolve(__dirname, '../sing-box');
|
||||||
|
|
||||||
const domainsetSrcFolder = 'domainset' + path.sep;
|
const domainsetSrcFolder = 'domainset' + path.sep;
|
||||||
|
|
||||||
@ -137,7 +138,8 @@ function transformDomainset(parentSpan: Span, sourcePath: string, relativePath:
|
|||||||
deduped,
|
deduped,
|
||||||
'domainset',
|
'domainset',
|
||||||
path.resolve(outputSurgeDir, relativePath),
|
path.resolve(outputSurgeDir, relativePath),
|
||||||
path.resolve(outputClashDir, `${clashFileBasename}.txt`)
|
path.resolve(outputClashDir, `${clashFileBasename}.txt`),
|
||||||
|
path.resolve(outputSingboxDir, `${clashFileBasename}.json`)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -164,6 +166,8 @@ async function transformRuleset(parentSpan: Span, sourcePath: string, relativePa
|
|||||||
description = SHARED_DESCRIPTION;
|
description = SHARED_DESCRIPTION;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const clashFileBasename = relativePath.slice(0, -path.extname(relativePath).length);
|
||||||
|
|
||||||
return createRuleset(
|
return createRuleset(
|
||||||
span,
|
span,
|
||||||
title,
|
title,
|
||||||
@ -172,7 +176,8 @@ async function transformRuleset(parentSpan: Span, sourcePath: string, relativePa
|
|||||||
lines,
|
lines,
|
||||||
'ruleset',
|
'ruleset',
|
||||||
path.resolve(outputSurgeDir, relativePath),
|
path.resolve(outputSurgeDir, relativePath),
|
||||||
path.resolve(outputClashDir, `${relativePath.slice(0, -path.extname(relativePath).length)}.txt`)
|
path.resolve(outputClashDir, `${clashFileBasename}.txt`),
|
||||||
|
path.resolve(outputSingboxDir, `${clashFileBasename}.json`)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -49,7 +49,8 @@ export const buildDomesticRuleset = task(require.main === module, __filename)(as
|
|||||||
res[0],
|
res[0],
|
||||||
'ruleset',
|
'ruleset',
|
||||||
path.resolve(__dirname, '../List/non_ip/domestic.conf'),
|
path.resolve(__dirname, '../List/non_ip/domestic.conf'),
|
||||||
path.resolve(__dirname, '../Clash/non_ip/domestic.txt')
|
path.resolve(__dirname, '../Clash/non_ip/domestic.txt'),
|
||||||
|
path.resolve(__dirname, '../sing-box/non_ip/domestic.json')
|
||||||
),
|
),
|
||||||
createRuleset(
|
createRuleset(
|
||||||
span,
|
span,
|
||||||
@ -63,7 +64,8 @@ export const buildDomesticRuleset = task(require.main === module, __filename)(as
|
|||||||
res[1],
|
res[1],
|
||||||
'ruleset',
|
'ruleset',
|
||||||
path.resolve(__dirname, '../List/non_ip/direct.conf'),
|
path.resolve(__dirname, '../List/non_ip/direct.conf'),
|
||||||
path.resolve(__dirname, '../Clash/non_ip/direct.txt')
|
path.resolve(__dirname, '../Clash/non_ip/direct.txt'),
|
||||||
|
path.resolve(__dirname, '../sing-box/non_ip/direct.json')
|
||||||
),
|
),
|
||||||
createRuleset(
|
createRuleset(
|
||||||
span,
|
span,
|
||||||
@ -77,7 +79,8 @@ export const buildDomesticRuleset = task(require.main === module, __filename)(as
|
|||||||
res[2],
|
res[2],
|
||||||
'ruleset',
|
'ruleset',
|
||||||
path.resolve(__dirname, '../List/non_ip/lan.conf'),
|
path.resolve(__dirname, '../List/non_ip/lan.conf'),
|
||||||
path.resolve(__dirname, '../Clash/non_ip/lan.txt')
|
path.resolve(__dirname, '../Clash/non_ip/lan.txt'),
|
||||||
|
path.resolve(__dirname, '../sing-box/non_ip/lan.json')
|
||||||
),
|
),
|
||||||
compareAndWriteFile(
|
compareAndWriteFile(
|
||||||
span,
|
span,
|
||||||
|
|||||||
@ -64,6 +64,7 @@ export const buildMicrosoftCdn = task(require.main === module, __filename)(async
|
|||||||
res,
|
res,
|
||||||
'ruleset',
|
'ruleset',
|
||||||
path.resolve(__dirname, '../List/non_ip/microsoft_cdn.conf'),
|
path.resolve(__dirname, '../List/non_ip/microsoft_cdn.conf'),
|
||||||
path.resolve(__dirname, '../Clash/non_ip/microsoft_cdn.txt')
|
path.resolve(__dirname, '../Clash/non_ip/microsoft_cdn.txt'),
|
||||||
|
path.resolve(__dirname, '../sing-box/non_ip/microsoft_cdn.json')
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -192,6 +192,7 @@ export const buildRejectDomainSet = task(require.main === module, __filename)(as
|
|||||||
'domainset',
|
'domainset',
|
||||||
path.resolve(__dirname, '../List/domainset/reject.conf'),
|
path.resolve(__dirname, '../List/domainset/reject.conf'),
|
||||||
path.resolve(__dirname, '../Clash/domainset/reject.txt'),
|
path.resolve(__dirname, '../Clash/domainset/reject.txt'),
|
||||||
|
path.resolve(__dirname, '../sing-box/domainset/reject.json'),
|
||||||
path.resolve(__dirname, '../Clash/clash_mrs_domain/reject.mrs')
|
path.resolve(__dirname, '../Clash/clash_mrs_domain/reject.mrs')
|
||||||
),
|
),
|
||||||
createRuleset(
|
createRuleset(
|
||||||
@ -213,6 +214,7 @@ export const buildRejectDomainSet = task(require.main === module, __filename)(as
|
|||||||
'domainset',
|
'domainset',
|
||||||
path.resolve(__dirname, '../List/domainset/reject_extra.conf'),
|
path.resolve(__dirname, '../List/domainset/reject_extra.conf'),
|
||||||
path.resolve(__dirname, '../Clash/domainset/reject_extra.txt'),
|
path.resolve(__dirname, '../Clash/domainset/reject_extra.txt'),
|
||||||
|
path.resolve(__dirname, '../sing-box/domainset/reject_extra.json'),
|
||||||
path.resolve(__dirname, '../Clash/clash_mrs_domain/reject_extra.mrs')
|
path.resolve(__dirname, '../Clash/clash_mrs_domain/reject_extra.mrs')
|
||||||
),
|
),
|
||||||
compareAndWriteFile(
|
compareAndWriteFile(
|
||||||
|
|||||||
@ -101,6 +101,7 @@ export const buildRejectIPList = task(require.main === module, __filename)(async
|
|||||||
result,
|
result,
|
||||||
'ruleset',
|
'ruleset',
|
||||||
path.resolve(__dirname, '../List/ip/reject.conf'),
|
path.resolve(__dirname, '../List/ip/reject.conf'),
|
||||||
path.resolve(__dirname, '../Clash/ip/reject.txt')
|
path.resolve(__dirname, '../Clash/ip/reject.txt'),
|
||||||
|
path.resolve(__dirname, '../sing-box/ip/reject.json')
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -252,6 +252,7 @@ export const buildSpeedtestDomainSet = task(require.main === module, __filename)
|
|||||||
'domainset',
|
'domainset',
|
||||||
path.resolve(__dirname, '../List/domainset/speedtest.conf'),
|
path.resolve(__dirname, '../List/domainset/speedtest.conf'),
|
||||||
path.resolve(__dirname, '../Clash/domainset/speedtest.txt'),
|
path.resolve(__dirname, '../Clash/domainset/speedtest.txt'),
|
||||||
|
path.resolve(__dirname, '../sing-box/domainset/speedtest.json'),
|
||||||
path.resolve(__dirname, '../Clash/clash_mrs_domain/speedtest.mrs')
|
path.resolve(__dirname, '../Clash/clash_mrs_domain/speedtest.mrs')
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -23,7 +23,8 @@ export const createRulesetForStreamService = (span: Span, fileId: string, title:
|
|||||||
streamServices.flatMap((i) => i.rules),
|
streamServices.flatMap((i) => i.rules),
|
||||||
'ruleset',
|
'ruleset',
|
||||||
path.resolve(__dirname, `../List/non_ip/${fileId}.conf`),
|
path.resolve(__dirname, `../List/non_ip/${fileId}.conf`),
|
||||||
path.resolve(__dirname, `../Clash/non_ip/${fileId}.txt`)
|
path.resolve(__dirname, `../Clash/non_ip/${fileId}.txt`),
|
||||||
|
path.resolve(__dirname, `../sing-box/non_ip/${fileId}.json`)
|
||||||
),
|
),
|
||||||
// IP
|
// IP
|
||||||
createRuleset(
|
createRuleset(
|
||||||
@ -45,7 +46,8 @@ export const createRulesetForStreamService = (span: Span, fileId: string, title:
|
|||||||
)),
|
)),
|
||||||
'ruleset',
|
'ruleset',
|
||||||
path.resolve(__dirname, `../List/ip/${fileId}.conf`),
|
path.resolve(__dirname, `../List/ip/${fileId}.conf`),
|
||||||
path.resolve(__dirname, `../Clash/ip/${fileId}.txt`)
|
path.resolve(__dirname, `../Clash/ip/${fileId}.txt`),
|
||||||
|
path.resolve(__dirname, `../sing-box/ip/${fileId}.json`)
|
||||||
)
|
)
|
||||||
]));
|
]));
|
||||||
};
|
};
|
||||||
|
|||||||
@ -53,6 +53,7 @@ export const buildTelegramCIDR = task(require.main === module, __filename)(async
|
|||||||
results,
|
results,
|
||||||
'ruleset',
|
'ruleset',
|
||||||
path.resolve(__dirname, '../List/ip/telegram.conf'),
|
path.resolve(__dirname, '../List/ip/telegram.conf'),
|
||||||
path.resolve(__dirname, '../Clash/ip/telegram.txt')
|
path.resolve(__dirname, '../Clash/ip/telegram.txt'),
|
||||||
|
path.resolve(__dirname, '../sing-box/ip/telegram.json')
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -6,6 +6,8 @@ import path from 'path';
|
|||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import { fastStringArrayJoin, writeFile } from './misc';
|
import { fastStringArrayJoin, writeFile } from './misc';
|
||||||
import { readFileByLine } from './fetch-text-by-line';
|
import { readFileByLine } from './fetch-text-by-line';
|
||||||
|
import stringify from 'json-stringify-pretty-compact';
|
||||||
|
import { surgeDomainsetToSingbox, surgeRulesetToSingbox } from './singbox';
|
||||||
|
|
||||||
export async function compareAndWriteFile(span: Span, linesA: string[], filePath: string) {
|
export async function compareAndWriteFile(span: Span, linesA: string[], filePath: string) {
|
||||||
let isEqual = true;
|
let isEqual = true;
|
||||||
@ -152,14 +154,13 @@ export const createRuleset = (
|
|||||||
parentSpan: Span,
|
parentSpan: Span,
|
||||||
title: string, description: string[] | readonly string[], date: Date, content: string[],
|
title: string, description: string[] | readonly string[], date: Date, content: string[],
|
||||||
type: ('ruleset' | 'domainset' | string & {}),
|
type: ('ruleset' | 'domainset' | string & {}),
|
||||||
surgePath: string, clashPath: string,
|
surgePath: string, clashPath: string, singBoxPath: string, _clashMrsPath?: string
|
||||||
clashMrsPath?: string
|
|
||||||
) => parentSpan.traceChild(`create ruleset: ${path.basename(surgePath, path.extname(surgePath))}`).traceAsyncFn(async (childSpan) => {
|
) => parentSpan.traceChild(`create ruleset: ${path.basename(surgePath, path.extname(surgePath))}`).traceAsyncFn(async (childSpan) => {
|
||||||
const surgeContent = withBannerArray(
|
const surgeContent = withBannerArray(
|
||||||
title, description, date,
|
title, description, date,
|
||||||
sortRuleSet(type === 'domainset'
|
type === 'domainset'
|
||||||
? [MARK, ...content]
|
? [MARK, ...content]
|
||||||
: [`DOMAIN,${MARK}`, ...content])
|
: sortRuleSet([`DOMAIN,${MARK}`, ...content])
|
||||||
);
|
);
|
||||||
const clashContent = childSpan.traceChildSync('convert incoming ruleset to clash', () => {
|
const clashContent = childSpan.traceChildSync('convert incoming ruleset to clash', () => {
|
||||||
let _clashContent;
|
let _clashContent;
|
||||||
@ -175,10 +176,25 @@ export const createRuleset = (
|
|||||||
}
|
}
|
||||||
return withBannerArray(title, description, date, _clashContent);
|
return withBannerArray(title, description, date, _clashContent);
|
||||||
});
|
});
|
||||||
|
const singboxContent = childSpan.traceChildSync('convert incoming ruleset to singbox', () => {
|
||||||
|
let _singBoxContent;
|
||||||
|
switch (type) {
|
||||||
|
case 'domainset':
|
||||||
|
_singBoxContent = surgeDomainsetToSingbox([MARK, ...content]);
|
||||||
|
break;
|
||||||
|
case 'ruleset':
|
||||||
|
_singBoxContent = surgeRulesetToSingbox([`DOMAIN,${MARK}`, ...content]);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new TypeError(`Unknown type: ${type}`);
|
||||||
|
}
|
||||||
|
return stringify(_singBoxContent).split('\n');
|
||||||
|
});
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
compareAndWriteFile(childSpan, surgeContent, surgePath),
|
compareAndWriteFile(childSpan, surgeContent, surgePath),
|
||||||
compareAndWriteFile(childSpan, clashContent, clashPath)
|
compareAndWriteFile(childSpan, clashContent, clashPath),
|
||||||
|
compareAndWriteFile(childSpan, singboxContent, singBoxPath)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// if (clashMrsPath) {
|
// if (clashMrsPath) {
|
||||||
|
|||||||
94
Build/lib/singbox.ts
Normal file
94
Build/lib/singbox.ts
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
import picocolors from 'picocolors';
|
||||||
|
|
||||||
|
const unsupported = Symbol('unsupported');
|
||||||
|
|
||||||
|
// https://sing-box.sagernet.org/configuration/rule-set/source-format/
|
||||||
|
const PROCESSOR: Record<string, ((raw: string, type: string, value: string) => [key: keyof SingboxHeadlessRule, value: string]) | typeof unsupported> = {
|
||||||
|
DOMAIN: (_1, _2, value) => ['domain', value],
|
||||||
|
'DOMAIN-SUFFIX': (_1, _2, value) => ['domain_suffix', value],
|
||||||
|
'DOMAIN-KEYWORD': (_1, _2, value) => ['domain_keyword', value],
|
||||||
|
GEOIP: unsupported,
|
||||||
|
'IP-CIDR': (_1, _2, value) => ['ip_cidr', value.endsWith(',no-resolve') ? value.slice(0, -11) : value],
|
||||||
|
'IP-CIDR6': (_1, _2, value) => ['ip_cidr', value.endsWith(',no-resolve') ? value.slice(0, -11) : value],
|
||||||
|
'IP-ASN': unsupported,
|
||||||
|
'SRC-IP-CIDR': (_1, _2, value) => ['source_ip_cidr', value.endsWith(',no-resolve') ? value.slice(0, -11) : value],
|
||||||
|
'SRC-PORT': (_1, _2, value) => ['source_port', value],
|
||||||
|
'DST-PORT': (_1, _2, value) => ['port', value],
|
||||||
|
'PROCESS-NAME': (_1, _2, value) => ['process_name', value],
|
||||||
|
'PROCESS-PATH': (_1, _2, value) => ['process_path', value],
|
||||||
|
'DEST-PORT': (_1, _2, value) => ['port', value],
|
||||||
|
'IN-PORT': (_1, _2, value) => ['source_port', value],
|
||||||
|
'URL-REGEX': unsupported,
|
||||||
|
'USER-AGENT': unsupported
|
||||||
|
};
|
||||||
|
|
||||||
|
interface SingboxHeadlessRule {
|
||||||
|
domain?: string[],
|
||||||
|
domain_suffix?: string[],
|
||||||
|
domain_keyword?: string[],
|
||||||
|
domain_regex?: string[],
|
||||||
|
source_ip_cidr?: string[],
|
||||||
|
ip_cidr?: string[],
|
||||||
|
source_port?: string[],
|
||||||
|
source_port_range?: string[],
|
||||||
|
port?: string[],
|
||||||
|
port_range?: string[],
|
||||||
|
process_name?: string[],
|
||||||
|
process_path?: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SingboxSourceFormat {
|
||||||
|
version: 2 | number & {},
|
||||||
|
rules: SingboxHeadlessRule[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const surgeRulesetToSingbox = (rules: string[] | Set<string>): SingboxSourceFormat => {
|
||||||
|
const rule: SingboxHeadlessRule = Array.from(rules).reduce<SingboxHeadlessRule>((acc, cur) => {
|
||||||
|
let buf = '';
|
||||||
|
let type = '';
|
||||||
|
let i = 0;
|
||||||
|
for (const len = cur.length; i < len; i++) {
|
||||||
|
if (cur[i] === ',') {
|
||||||
|
type = buf;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
buf += cur[i];
|
||||||
|
}
|
||||||
|
if (type === '') {
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
const value = cur.slice(i + 1);
|
||||||
|
if (type in PROCESSOR) {
|
||||||
|
const proc = PROCESSOR[type];
|
||||||
|
if (proc !== unsupported) {
|
||||||
|
const [k, v] = proc(cur, type, value);
|
||||||
|
acc[k] ||= [];
|
||||||
|
acc[k].push(v);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log(picocolors.yellow(`[sing-box] unknown rule type: ${type}`), cur);
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
return {
|
||||||
|
version: 2,
|
||||||
|
rules: [rule]
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const surgeDomainsetToSingbox = (domainset: string[]) => {
|
||||||
|
const rule = domainset.reduce((acc, cur) => {
|
||||||
|
if (cur[0] === '.') {
|
||||||
|
acc.domain_suffix.push(cur.slice(1));
|
||||||
|
} else {
|
||||||
|
acc.domain.push(cur);
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, { domain: [] as string[], domain_suffix: [] as string[] } satisfies SingboxHeadlessRule);
|
||||||
|
|
||||||
|
return {
|
||||||
|
version: 2,
|
||||||
|
rules: [rule]
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -30,6 +30,7 @@
|
|||||||
"fast-cidr-tools": "^0.2.5",
|
"fast-cidr-tools": "^0.2.5",
|
||||||
"fdir": "^6.2.0",
|
"fdir": "^6.2.0",
|
||||||
"foxact": "^0.2.36",
|
"foxact": "^0.2.36",
|
||||||
|
"json-stringify-pretty-compact": "^3.0.0",
|
||||||
"mnemonist": "^0.39.8",
|
"mnemonist": "^0.39.8",
|
||||||
"path-scurry": "^1.11.1",
|
"path-scurry": "^1.11.1",
|
||||||
"picocolors": "^1.0.1",
|
"picocolors": "^1.0.1",
|
||||||
|
|||||||
8
pnpm-lock.yaml
generated
8
pnpm-lock.yaml
generated
@ -41,6 +41,9 @@ importers:
|
|||||||
foxact:
|
foxact:
|
||||||
specifier: ^0.2.36
|
specifier: ^0.2.36
|
||||||
version: 0.2.36
|
version: 0.2.36
|
||||||
|
json-stringify-pretty-compact:
|
||||||
|
specifier: ^3.0.0
|
||||||
|
version: 3.0.0
|
||||||
mnemonist:
|
mnemonist:
|
||||||
specifier: ^0.39.8
|
specifier: ^0.39.8
|
||||||
version: 0.39.8
|
version: 0.39.8
|
||||||
@ -1101,6 +1104,9 @@ packages:
|
|||||||
json-stable-stringify-without-jsonify@1.0.1:
|
json-stable-stringify-without-jsonify@1.0.1:
|
||||||
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
|
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
|
||||||
|
|
||||||
|
json-stringify-pretty-compact@3.0.0:
|
||||||
|
resolution: {integrity: sha512-Rc2suX5meI0S3bfdZuA7JMFBGkJ875ApfVyq2WHELjBiiG22My/l7/8zPpH/CfFVQHuVLd8NLR0nv6vi0BYYKA==}
|
||||||
|
|
||||||
jsonc-eslint-parser@2.4.0:
|
jsonc-eslint-parser@2.4.0:
|
||||||
resolution: {integrity: sha512-WYDyuc/uFcGp6YtM2H0uKmUwieOuzeE/5YocFJLnLfclZ4inf3mRn8ZVy1s7Hxji7Jxm6Ss8gqpexD/GlKoGgg==}
|
resolution: {integrity: sha512-WYDyuc/uFcGp6YtM2H0uKmUwieOuzeE/5YocFJLnLfclZ4inf3mRn8ZVy1s7Hxji7Jxm6Ss8gqpexD/GlKoGgg==}
|
||||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||||
@ -2618,6 +2624,8 @@ snapshots:
|
|||||||
|
|
||||||
json-stable-stringify-without-jsonify@1.0.1: {}
|
json-stable-stringify-without-jsonify@1.0.1: {}
|
||||||
|
|
||||||
|
json-stringify-pretty-compact@3.0.0: {}
|
||||||
|
|
||||||
jsonc-eslint-parser@2.4.0:
|
jsonc-eslint-parser@2.4.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
acorn: 8.12.1
|
acorn: 8.12.1
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user