fix: ShadowRocket 策略名去引号,修复规则整条失效

ShadowRocket 的 conf 语法没有引号机制,含空格/emoji 的策略名一律裸写。
此前 quoteName 给策略名加引号,引号被当作名字的一部分,规则指向不存在的
策略组而失效,流量落到默认行为走代理(小红书/微信/抖音配了直连仍走代理)。
改为 policyName 剔除引号与逗号后裸写,补回归测试,并在 CLAUDE.md 记录此约束。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-09-03 16:44:40 +08:00
parent e5a3c70773
commit a30360ab38
3 changed files with 60 additions and 8 deletions

View File

@@ -1,8 +1,12 @@
import type { ProfileIR } from '../ir.js';
/** 成员名含空格/逗号时加引号 */
function quoteName(name: string): string {
return /[\s,]/.test(name) ? `"${name}"` : name;
/**
* ShadowRocket 的 conf 语法没有引号机制:策略名一律裸写,含空格/emoji 均可;
* 加引号会被当作名字的一部分,导致规则指向不存在的策略组而整条失效。
* 逗号是唯一分隔符,名字里的逗号无法表达,只能剔除。
*/
function policyName(name: string): string {
return name.replace(/[",]/g, '').trim();
}
/**
@@ -67,7 +71,7 @@ export function generateShadowrocketConf(ir: ProfileIR): ShadowrocketConfOutput
for (const g of ir.groups) {
const parts: string[] = [g.type];
if (!g.filterRegex && !g.includeAllNodes) {
const members = g.members.map((m) => quoteName(m.name));
const members = g.members.map((m) => policyName(m.name));
parts.push(...(members.length ? members : ['DIRECT']));
}
if (g.type !== 'select') {
@@ -78,14 +82,14 @@ export function generateShadowrocketConf(ir: ProfileIR): ShadowrocketConfOutput
// policy-regex-filter 从 App 内全部节点里筛选;「收纳全部节点」用全匹配正则
if (g.filterRegex) parts.push(`policy-regex-filter=${g.filterRegex}`);
else if (g.includeAllNodes) parts.push('policy-regex-filter=.*');
lines.push(`${g.name} = ${parts.join(', ')}`);
lines.push(`${policyName(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(','));
lines.push(['FINAL', policyName(r.policy)].join(','));
continue;
}
if (SR_UNSUPPORTED_RULE_TYPES.has(r.type)) {
@@ -93,7 +97,7 @@ export function generateShadowrocketConf(ir: ProfileIR): ShadowrocketConfOutput
continue;
}
const value = r.ruleset ? r.ruleset.surgeUrl : r.value;
lines.push([r.type, value, quoteName(r.policy), ...r.params].join(','));
lines.push([r.type, value, policyName(r.policy), ...r.params].join(','));
}
for (const section of ['Host', 'URL Rewrite', 'MITM']) {