DNS泄露 & AI分流覆写JS Clash Party 版本

突然看到了这个方案(参见原帖),之前一直默认配置,导致要使用本地docker部署的Sub2Api里的free号池时,总是要去切换节点(日常习惯用HK)。但是原帖是FIClash的,我也不清楚有多大区别,但是Clash Party用不了,可能Clash Verge也用不了,故让Claude 搓了一下。...
DNS泄露 & AI分流覆写JS  Clash Party 版本
DNS泄露 & AI分流覆写JS Clash Party 版本

突然看到了这个方案(参见原帖),之前一直默认配置,导致要使用本地docker部署的Sub2Api里的free号池时,总是要去切换节点(日常习惯用HK)。但是原帖是FIClash的,我也不清楚有多大区别,但是Clash Party用不了,可能Clash Verge也用不了,故让Claude 搓了一下。Clash Party具体使用方式:

导入步骤:

  1. 打开 Clash Party → 左侧"覆写"
  2. 点"+"→ 类型选 JavaScript → 导入本地文件 sing-mix.js(即下面的js源码)
  3. 回到"订阅"→ 目标订阅 → “编辑信息”→ 在"覆写"里勾选刚导入的脚本

原帖:

[Mihomo覆写] DNS泄露 & AI分流 の 最终解决方案 —— sing-mix 开发调优
终于找到了究极配置,佬太强了,这份配置超级棒,感谢分享!
// 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 位参与者

阅读完整话题

来源: LinuxDo 最新话题查看原文