突然看到了这个方案(参见原帖),之前一直默认配置,导致要使用本地docker部署的Sub2Api里的free号池时,总是要去切换节点(日常习惯用HK)。但是原帖是FIClash的,我也不清楚有多大区别,但是Clash Party用不了,可能Clash Verge也用不了,故让Claude 搓了一下。Clash Party具体使用方式:
导入步骤:
- 打开 Clash Party → 左侧"覆写"
- 点"+"→ 类型选 JavaScript → 导入本地文件
sing-mix.js(即下面的js源码) - 回到"订阅"→ 目标订阅 → “编辑信息”→ 在"覆写"里勾选刚导入的脚本
原帖:
终于找到了究极配置,佬太强了,这份配置超级棒,感谢分享!
// Clash Party / Mihomo Party 覆写脚本
// 入口: function main(config) { ... return config }
// 导入方式: Clash Party → 覆写 → 导入(本地文件 / URL)→ 在订阅"编辑信息"里挂载
// ====================
// 0. 特殊域名处理(手动添加)
// ====================
// 强制直连
const BYPASS_DOMAINS = [
"example.com", "example.org"
];
// 强制代理
const FORCE_PROXY_DOMAINS = [
"test.com", "test.org"
];
// ====================
// 1. 常量配置
// ====================
const SETTINGS = {
ICON_BASE: "https://cdn.jsdelivr.net/gh/Koolson/Qure@master/IconSet/Color/",
GEOIP_URL: "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.dat",
GEOSITE_URL: "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geosite.dat",
REGION_ORDER: ["HK", "TW", "SG", "JP", "KR", "AS", "US"],
URL_TEST_EXTRA: {
hidden: true,
url: "https://www.g.cn/generate_204",
interval: 900,
tolerance: 50,
lazy: true,
timeout: 1000
},
BETTER_FB_EXTRA: {
hidden: true,
url: "https://www.g.cn/generate_204",
interval: 900,
tolerance: 750,
lazy: true,
timeout: 1000,
"max-failed-times": 1
},
FILTER_REGEX: /群|邀请|返利|官网|官方|网址|订阅|购买|续费|剩余|到期|过期|流量|备用|邮箱|客服|联系|工单|倒卖|防止|梯子|tg|telegram|电报|发布|重置/i
};
// ====================
// 2. 基础工具
// ====================
const uniq = (arr = []) => [...new Set(arr.filter(Boolean))];
const escapeRegex = (s = "") =>
String(s).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const normalizeName = (name = "") =>
String(name)
.replace(/(IEPL|IPLC|BGP|RELAY|PRO|V\d+)/ig, " $1 ")
.replace(/[【】\[\]()()|_\-.,/:~]/g, " ")
.replace(/🇭🇰/g, " HK ")
.replace(/🇹🇼/g, " TW ")
.replace(/🇸🇬/g, " SG ")
.replace(/🇯🇵/g, " JP ")
.replace(/🇰🇷/g, " KR ")
.replace(/🇻🇳|🇹🇭|🇲🇾|🇮🇩|🇵🇭/g, " AS ")
.replace(/🇺🇸/g, " US ")
.toUpperCase()
.replace(/\s+/g, " ")
.trim();
const buildRegex = (arr = []) =>
new RegExp(
arr
.map((raw) => {
const token = String(raw).trim().toUpperCase();
const escaped = escapeRegex(token);
return /^[A-Z]{2,3}$/.test(token)
? `(?:^|[^A-Z])${escaped}(?:[^A-Z]|$)`
: escaped;
})
.join("|"),
"i"
);
const buildRegions = () =>
([
{ name: "HK", pattern: ["香港", "HK", "HKG", "HONGKONG", "HONG KONG"], icon: "Hong_Kong.png" },
{ name: "TW", pattern: ["台湾", "台北", "新北", "TW", "TWN", "TAIWAN", "TAIPEI", "TPE"], icon: "Taiwan.png" },
{ name: "SG", pattern: ["新加坡", "狮城", "SG", "SGP", "SINGAPORE", "SIN"], icon: "Singapore.png" },
{ name: "JP", pattern: ["日本", "东京", "大阪", "JP", "JPN", "JAPAN", "TOKYO", "OSAKA", "NRT", "HND", "TYO"], icon: "Japan.png" },
{ name: "KR", pattern: ["韩国", "首尔", "KR", "KOR", "KOREA", "SEOUL", "ICN"], icon: "Korea.png" },
{
name: "AS",
pattern: [
"越南", "泰国", "马来西亚", "印尼", "菲律宾",
"VN", "TH", "MY", "ID", "PH",
"VIETNAM", "THAILAND", "MALAYSIA", "INDONESIA", "PHILIPPINES", "MANILA"
],
icon: "Available.png"
},
{
name: "US",
pattern: [
"美国", "纽约", "洛杉矶", "旧金山", "圣何塞", "西雅图", "芝加哥", "达拉斯", "硅谷",
"US", "USA",
"UNITEDSTATES", "UNITED STATES",
"NEWYORK", "NEW YORK",
"LOSANGELES", "LOS ANGELES",
"SANFRANCISCO", "SAN FRANCISCO",
"SANJOSE", "SAN JOSE",
"SEATTLE", "CHICAGO", "DALLAS", "LAX", "SJC", "SFO"
],
icon: "United_States.png"
}
]).map((r) => ({ ...r, regex: buildRegex(r.pattern) }));
const REGIONS = buildRegions();
const buildFakeIpFilter = (bypass = []) =>
uniq([
"geosite:private",
"geosite:google-cn",
"geosite:synology",
"geosite:cn",
...uniq(
bypass.flatMap((domain) => {
const d = String(domain || "").trim();
if (!d) return [];
return d.includes("*") || d.startsWith("+.") ? [d] : [`+.${d}`];
})
)
]);
const mergeRules = (baseRules = [], extraRules = []) => {
const extra = Array.isArray(extraRules) ? extraRules.filter(Boolean) : [];
if (!extra.length) return baseRules.slice();
const matchIndex = baseRules.findIndex(
(rule) => String(rule).trim().toUpperCase() === "MATCH,MAIN"
);
if (matchIndex === -1) return uniq([...baseRules, ...extra]);
return uniq([
...baseRules.slice(0, matchIndex),
...extra,
...baseRules.slice(matchIndex)
]);
};
const pickDirectRules = (rules = []) =>
rules.filter((rule) => {
const r = String(rule || "").trim();
if (!r || r.startsWith("#")) return false;
return /,DIRECT(?:,|$)/i.test(r);
});
// ====================
// 3. 固定规则
// ====================
const STATIC_RULES = [
"GEOSITE,category-ads-all,REJECT",
...uniq(FORCE_PROXY_DOMAINS).map((d) => `DOMAIN,${d},main`),
"GEOSITE,private,DIRECT",
"GEOIP,private,DIRECT,no-resolve",
"GEOSITE,google-cn,DIRECT",
"GEOSITE,synology,DIRECT",
"DOMAIN-SUFFIX,sharepoint.com,DIRECT",
...uniq(BYPASS_DOMAINS).map((d) => `DOMAIN-SUFFIX,${d},DIRECT`),
"GEOSITE,category-ai-!cn,ai",
"GEOSITE,telegram,tg",
"GEOSITE,gfw,main",
"GEOSITE,cn,DIRECT",
"GEOIP,CN,DIRECT,no-resolve",
"MATCH,main"
];
const STATIC_FAKE_IP_FILTER = buildFakeIpFilter(BYPASS_DOMAINS);
// ====================
// 4. 节点处理
// ====================
const ensureConfigObject = (input) =>
input && typeof input === "object" ? input : {};
const getOriginalProxies = (input) =>
Array.isArray(input.proxies) ? input.proxies : [];
const makeProxyNamesUnique = (proxies = []) => {
const used = new Set();
const nextIdx = new Map();
proxies.forEach((p) => {
if (!p || !p.name) return;
const base = String(p.name);
if (!used.has(base)) {
used.add(base);
nextIdx.set(base, 1);
return;
}
let idx = nextIdx.get(base) ?? 1;
let candidate = `${base}_${idx}`;
while (used.has(candidate)) candidate = `${base}_${++idx}`;
p.name = candidate;
used.add(candidate);
nextIdx.set(base, idx + 1);
});
};
const splitInfoAndNormalProxies = (proxies = [], filterRegex) =>
proxies.reduce(
(acc, proxy) => {
if (!proxy || !proxy.name) return acc;
(filterRegex.test(proxy.name) ? acc.infoProxies : acc.normalProxies).push(proxy);
return acc;
},
{ infoProxies: [], normalProxies: [] }
);
const classifyProxiesByRegion = (normalProxies = [], regions = []) => {
const regionGroupsData = regions.map((r) => ({ name: r.name, icon: r.icon, proxies: [] }));
const regionGroupMap = new Map(regionGroupsData.map((r) => [r.name, r]));
const regionSeen = new Map(regionGroupsData.map((r) => [r.name, new Set()]));
const otherProxyNames = [];
const otherSeen = new Set();
normalProxies.forEach((proxy) => {
const proxyName = proxy.name;
const normName = normalizeName(proxyName);
const matchedRegion = regions.find((r) => r.regex.test(normName));
if (matchedRegion) {
const group = regionGroupMap.get(matchedRegion.name);
const seen = regionSeen.get(matchedRegion.name);
if (group && seen && !seen.has(proxyName)) {
group.proxies.push(proxyName);
seen.add(proxyName);
}
} else if (!otherSeen.has(proxyName)) {
otherProxyNames.push(proxyName);
otherSeen.add(proxyName);
}
});
const activeRegions = regionGroupsData
.map((r) => ({ ...r, proxies: uniq(r.proxies) }))
.filter((r) => r.proxies.length > 0);
const activeRegionNameSet = new Set(activeRegions.map((r) => r.name));
const activeRegionMap = new Map(activeRegions.map((r) => [r.name, r]));
return {
activeRegions,
activeRegionNameSet,
activeRegionMap,
otherProxyNames: uniq(otherProxyNames)
};
};
const buildAiProxyList = (activeRegions = [], otherProxyNames = [], allNames = []) => {
const nonHk = uniq([
...activeRegions.filter((r) => r.name !== "HK").flatMap((r) => r.proxies),
...otherProxyNames
]);
return nonHk.length ? nonHk : allNames;
};
// ====================
// 5. 策略组
// ====================
const buildProxyGroups = ({
allNames,
aiNames,
activeRegionMap,
activeRegionNameSet,
otherProxyNames,
infoNames
}) => {
const groups = [];
const add = (name, type, proxies, icon = "Available.png", extra = {}) => {
proxies = uniq(proxies);
if (name && proxies.length) {
groups.push({
name,
type,
proxies,
icon: SETTINGS.ICON_BASE + icon,
...extra
});
}
};
const regionEntries = SETTINGS.REGION_ORDER.filter((rName) => activeRegionNameSet.has(rName));
if (allNames.length) {
const mainEntries = ["All", ...regionEntries];
if (otherProxyNames.length) mainEntries.push("Other");
add("main", "select", mainEntries, "Available.png");
add("URL Test - All", "url-test", allNames, "Available.png", SETTINGS.URL_TEST_EXTRA);
add("BetterFB - All", "url-test", allNames, "Available.png", SETTINGS.BETTER_FB_EXTRA);
add("All", "select", ["BetterFB - All", "URL Test - All", ...allNames], "Available.png");
}
if (aiNames.length) {
add("URL Test - ai", "url-test", aiNames, "ChatGPT.png", SETTINGS.URL_TEST_EXTRA);
add("BetterFB - ai", "url-test", aiNames, "ChatGPT.png", SETTINGS.BETTER_FB_EXTRA);
add("ai", "select", ["BetterFB - ai", "URL Test - ai", ...aiNames], "ChatGPT.png");
}
if (allNames.length) {
const hasSG = activeRegionNameSet.has("SG");
const tgProxies = hasSG ? ["SG", "main"] : ["main"];
add("tg", "select", tgProxies, "Telegram.png");
}
SETTINGS.REGION_ORDER.forEach((rName) => {
const region = activeRegionMap.get(rName);
if (!region) return;
add(`URL Test - ${region.name}`, "url-test", region.proxies, region.icon, SETTINGS.URL_TEST_EXTRA);
add(`BetterFB - ${region.name}`, "url-test", region.proxies, region.icon, SETTINGS.BETTER_FB_EXTRA);
add(region.name, "select", [`BetterFB - ${region.name}`, `URL Test - ${region.name}`, ...region.proxies], region.icon);
});
if (otherProxyNames.length) {
add("URL Test - Other", "url-test", otherProxyNames, "Available.png", SETTINGS.URL_TEST_EXTRA);
add("BetterFB - Other", "url-test", otherProxyNames, "Available.png", SETTINGS.BETTER_FB_EXTRA);
add("Other", "select", ["BetterFB - Other", "URL Test - Other", ...otherProxyNames], "Available.png");
}
if (infoNames.length) {
add("info", "select", infoNames, "Available.png");
}
add(
"GLOBAL",
"select", [
...(allNames.length ? ["main", "All"] : []),
...(aiNames.length ? ["ai"] : []),
...(allNames.length ? ["tg"] : []),
...regionEntries,
...(otherProxyNames.length ? ["Other"] : []),
...(infoNames.length ? ["info"] : [])
],
"Global.png"
);
return groups;
};
// ====================
// 6. 网络配置
// ====================
const applyGeoData = (cfg) => {
cfg["geodata-mode"] = true;
cfg["geo-auto-update"] = true;
cfg["geo-update-interval"] = 24;
cfg["geox-url"] = {
...(cfg["geox-url"] || {}),
geoip: SETTINGS.GEOIP_URL,
geosite: SETTINGS.GEOSITE_URL
};
};
const applySniffer = (cfg) => {
cfg.sniffer = {
...(cfg.sniffer || {}),
enable: true,
"force-dns-mapping": true,
"parse-pure-ip": true,
"override-destination": true,
sniff: {
HTTP: { ports: [80, "8080-8880"], "override-destination": true },
TLS: { ports: [443, 8443] },
QUIC: { ports: [443, 8443] }
}
};
};
const applyTun = (cfg) => {
cfg.tun = {
...(cfg.tun || {}),
enable: true,
stack: "system",
"auto-route": true,
"auto-detect-interface": true,
"strict-route": true,
"dns-hijack": ["any:53", "tcp://any:53"]
};
};
const applyDns = (cfg) => {
const dns = cfg.dns || {};
const fakeIpFilterFromCfg = Array.isArray(dns["fake-ip-filter"]) ? dns["fake-ip-filter"] : [];
const nameserverPolicyFromCfg = dns["nameserver-policy"] && typeof dns["nameserver-policy"] === "object" ? dns["nameserver-policy"] : {};
const chinaDNS = [
"system",
"https://dns.alidns.com/dns-query",
"https://doh.pub/dns-query"
];
const foreignDNS = ["https://1.1.1.1/dns-query#main"];
const directGeoForChinaDNS = [
"geosite:cn",
"geosite:google-cn",
"geosite:synology",
"geosite:googlefcm",
"geosite:epicgames",
"geosite:nvidia@cn",
"geosite:microsoft@cn",
"geosite:cloudflare@cn",
"geosite:steam@cn",
"geosite:category-ntp",
"geosite:connectivity-check",
"geosite:apple",
"geosite:spotify",
"geosite:microsoft"
];
const directPolicy = {};
directGeoForChinaDNS.forEach(geo => {
directPolicy[geo] = chinaDNS;
});
directPolicy["geosite:private"] = "system";
const forceProxyPolicy = {};
uniq(FORCE_PROXY_DOMAINS).forEach(d => {
const domain = String(d || "").trim();
if (domain) forceProxyPolicy[domain] = foreignDNS;
});
const fullFakeIpFilter = uniq([
"+.cn",
"geosite:private",
...directGeoForChinaDNS,
...STATIC_FAKE_IP_FILTER,
...fakeIpFilterFromCfg
]);
cfg.dns = {
...dns,
enable: true,
listen: "0.0.0.0:1053",
ipv6: false,
"cache-algorithm": "arc",
"prefer-h3": false,
"use-hosts": true,
"use-system-hosts": true,
"respect-rules": false,
"enhanced-mode": "fake-ip",
"fake-ip-filter-mode": "blacklist",
"fake-ip-filter": fullFakeIpFilter,
"default-nameserver": ["223.5.5.5", "119.29.29.29"],
"nameserver-policy": {
...nameserverPolicyFromCfg,
...directPolicy,
"*": "system",
"+.arpa": "system",
...forceProxyPolicy
},
nameserver: foreignDNS,
"proxy-server-nameserver": [
"https://doh.pub/dns-query#DIRECT",
"https://dns.alidns.com/dns-query#DIRECT"
],
"direct-nameserver": ["system", "223.5.5.5", "119.29.29.29"],
"direct-nameserver-follow-policy": true
};
cfg.hosts = {
...(cfg.hosts || {}),
"dns.alidns.com": ["223.5.5.5", "223.6.6.6"],
"doh.pub": ["1.12.12.12", "120.53.53.53"],
// 解决谷歌商店无法下载的问题
"services.googleapis.cn": ["services.googleapis.com"],
// 屏蔽哔哩哔哩 PCDN
"+.mcdn.bilivideo.com": ["0.0.0.0"],
"+.mcdn.bilivideo.cn": ["0.0.0.0"]
};
};
const applyProfile = (cfg) => {
cfg.profile = {
...(cfg.profile || {}),
"store-selected": true,
"store-fake-ip": false
};
};
const applyRuntime = (cfg) => {
cfg.mode = "rule";
cfg["log-level"] = "warning";
};
// ====================
// 7. 主流程 — Clash Party 入口
// ====================
function main(config) {
config = ensureConfigObject(config);
const originalProxies = getOriginalProxies(config);
const existingRules = Array.isArray(config.rules) ? config.rules : [];
delete config["rule-providers"];
config.rules = mergeRules(STATIC_RULES, pickDirectRules(existingRules));
if (originalProxies.length) {
makeProxyNamesUnique(originalProxies);
const { infoProxies, normalProxies } = splitInfoAndNormalProxies(
originalProxies,
SETTINGS.FILTER_REGEX
);
const baseProxies = normalProxies;
const allNames = uniq(baseProxies.map((p) => p.name));
const infoNames = uniq(infoProxies.map((p) => p.name));
const {
activeRegions,
activeRegionNameSet,
activeRegionMap,
otherProxyNames
} = classifyProxiesByRegion(baseProxies, REGIONS);
const aiNames = buildAiProxyList(activeRegions, otherProxyNames, allNames);
config["proxy-groups"] = buildProxyGroups({
allNames,
aiNames,
activeRegionMap,
activeRegionNameSet,
otherProxyNames,
infoNames
});
config.proxies = originalProxies;
}
applyGeoData(config);
applyRuntime(config);
applySniffer(config);
applyTun(config);
applyDns(config);
applyProfile(config);
return config;
}
1 个帖子 - 1 位参与者