init: init proj
This commit is contained in:
110
server/src/services/generators/shadowrocket-conf.ts
Normal file
110
server/src/services/generators/shadowrocket-conf.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
import type { ProfileIR } from '../ir.js';
|
||||
|
||||
/** 成员名含空格/逗号时加引号 */
|
||||
function quoteName(name: string): string {
|
||||
return /[\s,]/.test(name) ? `"${name}"` : name;
|
||||
}
|
||||
|
||||
/**
|
||||
* ShadowRocket 的 [General] 只保留它认识的通用键——模板里的 Surge 专有键
|
||||
* (all-hybrid / udp-priority / http-api / compatibility-mode 等)会被过滤掉。
|
||||
*/
|
||||
const SR_GENERAL_KEYS = new Set([
|
||||
'bypass-system',
|
||||
'skip-proxy',
|
||||
'bypass-tun',
|
||||
'dns-server',
|
||||
'fallback-dns-server',
|
||||
'ipv6',
|
||||
'prefer-ipv6',
|
||||
'dns-direct-system',
|
||||
'icmp-auto-reply',
|
||||
'always-real-ip',
|
||||
'hijack-dns',
|
||||
'udp-policy-not-supported-behaviour',
|
||||
'interface-mode',
|
||||
]);
|
||||
|
||||
function shadowrocketGeneral(surgeGeneral: string): string {
|
||||
const lines: string[] = [];
|
||||
for (const raw of surgeGeneral.split(/\r?\n/)) {
|
||||
const line = raw.trim();
|
||||
if (!line || line.startsWith('#') || line.startsWith('//')) continue;
|
||||
const key = line.split('=')[0].trim();
|
||||
if (SR_GENERAL_KEYS.has(key)) lines.push(line);
|
||||
}
|
||||
// ShadowRocket 的常用默认值,模板里没有就补上
|
||||
if (!lines.some((l) => l.startsWith('bypass-system'))) lines.push('bypass-system = true');
|
||||
if (!lines.some((l) => l.startsWith('ipv6'))) lines.push('ipv6 = false');
|
||||
if (!lines.some((l) => l.startsWith('dns-server'))) lines.push('dns-server = 223.5.5.5, 119.29.29.29');
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
/** ShadowRocket 不认识的规则类型 */
|
||||
const SR_UNSUPPORTED_RULE_TYPES = new Set(['DOMAIN-SET', 'AND', 'OR', 'NOT', 'URL-REGEX']);
|
||||
|
||||
export interface ShadowrocketConfOutput {
|
||||
content: string;
|
||||
skippedRules: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成 ShadowRocket 配置文件:策略组 + 规则,不含节点。
|
||||
* 节点由「节点订阅」单独导入,策略组通过 `use=true` 引用该订阅的名称,
|
||||
* 因此用户在 App 里给订阅起的名字必须与 subscriptionName 一致。
|
||||
*/
|
||||
export function generateShadowrocketConf(
|
||||
ir: ProfileIR,
|
||||
opts: { subscriptionName: string }
|
||||
): ShadowrocketConfOutput {
|
||||
const sub = quoteName(opts.subscriptionName);
|
||||
const lines: string[] = [];
|
||||
|
||||
lines.push('# 由 Proxy Station 生成的 ShadowRocket 配置(策略组 + 规则)');
|
||||
lines.push(`# 节点请另行导入「节点订阅」,并在 App 中将其命名为:${opts.subscriptionName}`);
|
||||
lines.push('');
|
||||
lines.push('[General]');
|
||||
lines.push(shadowrocketGeneral(ir.sections['General'] ?? ''));
|
||||
|
||||
lines.push('', '[Proxy Group]');
|
||||
for (const g of ir.groups) {
|
||||
const parts: string[] = [g.type];
|
||||
if (g.filterRegex) {
|
||||
// 地区组:引用订阅 + 正则筛选(ShadowRocket 原生支持 policy-regex-filter)
|
||||
parts.push(sub, 'use=true', `policy-regex-filter=${g.filterRegex}`);
|
||||
} else if (g.includeAllNodes) {
|
||||
// 收纳全部节点:引用订阅
|
||||
parts.push(sub, 'use=true');
|
||||
} else {
|
||||
const members = g.members.map((m) => quoteName(m.name));
|
||||
parts.push(...(members.length ? members : ['DIRECT']));
|
||||
}
|
||||
if (g.type !== 'select') {
|
||||
parts.push(`url=${g.testUrl || 'http://cp.cloudflare.com/generate_204'}`);
|
||||
if (g.interval) parts.push(`interval=${g.interval}`);
|
||||
if (g.tolerance) parts.push(`tolerance=${g.tolerance}`);
|
||||
}
|
||||
lines.push(`${g.name} = ${parts.join(', ')}`);
|
||||
}
|
||||
|
||||
const skippedRules: string[] = [];
|
||||
lines.push('', '[Rule]');
|
||||
for (const r of ir.rules) {
|
||||
if (r.type === 'FINAL') {
|
||||
lines.push(['FINAL', quoteName(r.policy)].join(','));
|
||||
continue;
|
||||
}
|
||||
if (SR_UNSUPPORTED_RULE_TYPES.has(r.type)) {
|
||||
skippedRules.push(`${r.type},${r.ruleset ? r.ruleset.name : r.value},${r.policy}`);
|
||||
continue;
|
||||
}
|
||||
const value = r.ruleset ? r.ruleset.surgeUrl : r.value;
|
||||
lines.push([r.type, value, quoteName(r.policy), ...r.params].join(','));
|
||||
}
|
||||
|
||||
for (const section of ['Host', 'URL Rewrite', 'MITM']) {
|
||||
if (ir.sections[section]) lines.push('', `[${section}]`, ir.sections[section]);
|
||||
}
|
||||
|
||||
return { content: lines.join('\n') + '\n', skippedRules };
|
||||
}
|
||||
Reference in New Issue
Block a user