分类目录归档:规则与策略

分流规则、策略组、脚本等进阶内容

Clash JavaScript 脚本教程

Clash 的规则系统虽然强大,但毕竟是固定的几种类型,遇到一些复杂的需求就有点力不从心了。比如你想根据时间来决定走不走代理、想根据请求的多个特征综合判断、想动态修改请求信息——这些用普通规则都很难实现。

这时候就轮到 JavaScript 脚本出场了。Clash Premium 和 Clash Meta 内核支持用 JS 脚本来写规则,想怎么判断就怎么判断,灵活性直接拉满。

我当初研究 JS 脚本的时候也是踩了不少坑,文档少、例子也不多,全靠自己摸索。今天就把我总结的经验全分享出来,从基础语法到实用例子,手把手教你写 Clash JS 脚本。

什么是 Script 规则?

Script 规则就是用 JavaScript 代码来决定一个请求走哪个策略。普通规则是”如果满足条件就走这个策略”,而脚本规则是”你自己写代码判断,返回策略名就行”。

打个比方:普通规则像是选择题,给你几个选项选一个;脚本规则像是问答题,你自己写答案。

有了脚本规则,你可以实现很多普通规则搞不定的功能:

  • 根据时间决定走不走代理(比如上班时间直连,摸鱼时间走代理)
  • 复杂的多条件判断(域名包含某个关键词 + IP 是某个国家 + 是工作日才走代理)
  • 动态修改请求(改 Host、加 Header 什么的)
  • 随机分流(一定概率走代理,一定概率直连)
  • 统计分析(记录每个域名的访问次数什么的)

总之就是,只要你能写出来,啥都能干。

前提条件

不是所有 Clash 内核都支持 JS 脚本的。目前支持的有:

  • Clash Premium —— 官方付费版内核,支持
  • Clash Meta / Mihomo —— 开源增强版内核,支持
  • 普通开源版 Clash 不支持

所以你得先确认你的客户端用的是什么内核。现在大部分新的客户端(Clash Verge、Nyanpasu 这些)默认都是 Meta 内核,应该都支持。

脚本规则的基本写法

先来看最简单的例子,感受一下脚本规则长啥样。

在配置文件里写

可以直接在配置文件里用 script 规则:

rules:
  - SCRIPT,quicker,Proxy

不对,这是调用脚本的方式。脚本本身的内容呢?有两种方式:

方式一:内联脚本

直接在配置文件里写脚本内容(短的还行,长了不好维护):

script:
  code: |
    function main(params) {
      if (params.host.indexOf("google") > -1) {
        return "Proxy";
      }
      return "DIRECT";
    }

方式二:外部脚本文件(推荐)

把脚本单独存成 .js 文件,然后在配置里引用:

script:
  path: ./scripts/my-rules.js

这样维护起来方便多了,脚本写多长都没事。

脚本的基本结构

一个最简单的脚本长这样:

function main(params) {
  // 你的逻辑写在这里
  // 返回策略组的名字
  return "Proxy";
}

就一个 main 函数,接收一个 params 参数,返回一个策略组名字。

params 里面包含了当前请求的所有信息,比如:

  • params.host —— 请求的域名
  • params.port —— 请求的端口
  • params.network —— 网络类型,tcp 还是 udp
  • params.type —— 规则类型(一般用不上)
  • params.src_ip —— 源 IP(不一定有)
  • params.src_port —— 源端口
  • params.inbound_ip —— 入站 IP
  • params.inbound_port —— 入站端口
  • params.dns_mode —— DNS 模式
  • params.process —— 进程名(如果支持的话)
  • params.process_path —— 进程路径

有了这些信息,你想怎么判断就怎么判断。

常用写法和示例

光说理论没用,来看几个实际的例子。

示例一:根据时间决定走不走代理

这是我觉得最实用的一个脚本。工作日上班时间走直连(防止摸鱼),其他时间走代理。

function main(params) {
  const now = new Date();
  const day = now.getDay(); // 0 = 周日, 1-5 = 工作日
  const hour = now.getHours();

  // 周末全走代理
  if (day === 0 || day === 6) {
    return "Proxy";
  }

  // 工作日 9:00 - 18:00 走直连
  if (hour >= 9 && hour < 18) {
    return "DIRECT";
  }

  // 其他时间走代理
  return "Proxy";
}

这个脚本简单实用,摸鱼神器(不是)。

示例二:复杂的多条件判断

普通规则只能判断一个条件,脚本可以判断好几个。比如:域名包含 google 且端口是 443 且不是内网 IP 才走代理。

function main(params) {
  const host = params.host;
  const port = params.port;

  // 域名包含 google
  const hasGoogle = host.indexOf("google") > -1;

  // 端口是 443
  const isHttps = port === 443;

  // 不是内网 IP(简单判断)
  const isNotLocal = !host.startsWith("192.168.") && 
                     !host.startsWith("10.") && 
                     !host.startsWith("127.");

  if (hasGoogle && isHttps && isNotLocal) {
    return "Proxy";
  }

  return "DIRECT";
}

虽然这个例子有点刻意,但至少说明脚本的灵活性——多个条件随便组合。

示例三:随机分流

一定概率走代理,一定概率直连。这个用处不大,但挺有意思的。

function main(params) {
  // 30% 概率走代理,70% 直连
  if (Math.random() < 0.3) {
    return "Proxy";
  }
  return "DIRECT";
}

真想用的话,比如你有两个线路,可以按概率分流,实现简单的负载均衡。

示例四:按域名长度匹配

虽然没啥实际用处,但展示了脚本的灵活性。比如你想让特别长的域名走代理:

function main(params) {
  if (params.host.length > 30) {
    return "Proxy";
  }
  return "DIRECT";
}

示例五:域名后缀匹配的另一种写法

其实普通的 DOMAIN-SUFFIX 就够用了,但用脚本也能写,而且更灵活:

function main(params) {
  const host = params.host;

  // 需要走代理的后缀列表
  const proxySuffixes = [
    "google.com",
    "youtube.com",
    "twitter.com",
    "github.com"
  ];

  for (const suffix of proxySuffixes) {
    if (host.endsWith(suffix) || host === suffix) {
      return "Proxy";
    }
  }

  // 需要直连的后缀列表
  const directSuffixes = [
    "baidu.com",
    "qq.com",
    "taobao.com"
  ];

  for (const suffix of directSuffixes) {
    if (host.endsWith(suffix) || host === suffix) {
      return "DIRECT";
    }
  }

  // 兜底
  return "Proxy";
}

不过这种简单的后缀匹配用普通规则就行,没必要写脚本。脚本留着实现复杂逻辑就好。

示例六:根据进程名判断

如果你内核支持 PROCESS-NAME,用脚本也能实现,而且可以写更复杂的逻辑:

function main(params) {
  const process = params.process || "";

  // 浏览器走代理
  const browsers = ["chrome.exe", "msedge.exe", "firefox.exe"];
  if (browsers.includes(process)) {
    return "Proxy";
  }

  // 下载工具直连(省流量)
  const downloaders = ["thunder.exe", "qbittorrent.exe"];
  if (downloaders.includes(process)) {
    return "DIRECT";
  }

  // 游戏走游戏节点
  if (process.indexOf("game") > -1) {
    return "游戏组";
  }

  return "Proxy";
}

这个比一条条写 PROCESS-NAME 规则清爽多了,而且方便维护。

脚本的执行效率

很多人担心,每条请求都跑一遍 JS 脚本,会不会很慢?

其实还好。Clash 用的是嵌入式的 JS 引擎,执行速度很快,简单的脚本基本没什么性能开销。

但也要注意几点:

  1. 别写太复杂的逻辑。几千行的脚本、复杂的循环什么的,肯定会慢。简单的判断完全没问题。
  2. 别在脚本里发网络请求。脚本是同步执行的,你发个请求等半天,整个代理都卡住了。
  3. 能用普通规则解决的就别用脚本。普通规则是原生实现的,肯定比 JS 快。脚本是补普通规则的短板的,不是替代。

一般来说,脚本规则放几条就够了,大部分流量还是用普通规则处理。把脚本用在刀刃上。

怎么调试脚本?

脚本写好了,怎么知道对不对?有没有报错?

说几个调试的方法:

方法一:看日志

脚本出错了,Clash 的日志里会有报错信息。把日志级别调到 info 或者 debug,然后触发一下脚本规则,看日志里有没有报错。

方法二:用 console.log

脚本里可以用 console.log() 打印信息,然后在日志里看输出。比如:

function main(params) {
  console.log("当前域名:", params.host);
  console.log("当前端口:", params.port);
  return "Proxy";
}

这样你就能看到传进来的参数是什么样的,方便排查问题。

调试完了记得把 console.log 删掉或者注释掉,不然日志里全是调试信息,看着乱。

方法三:本地测试

你可以把脚本拿到本地浏览器或者 Node.js 里测试,先确保逻辑没问题,再放到 Clash 里用。

比如写个测试函数:

// 测试用例
const testCases = [
  { host: "google.com", port: 443 },
  { host: "baidu.com", port: 80 },
  { host: "192.168.1.1", port: 80 }
];

for (const tc of testCases) {
  console.log(`${tc.host}:${tc.port} -> ${main(tc)}`);
}

在本地跑一下,看看每个测试用例返回的策略对不对,没问题了再放到 Clash 里。

注意事项和坑

写脚本的时候有几个容易踩的坑,我给你提个醒。

坑一:返回的策略名必须存在

return 的那个策略组名字,必须在配置文件里真实存在,不然会报错或者走默认的。

别拼错了,大小写也要注意。

坑二:脚本是单线程同步的

脚本是同步执行的,不能写异步代码。什么 setTimeoutfetchPromise 这些都不能用,用了也没用,甚至会报错。

就老老实实写同步的判断逻辑。

坑三:不是所有 API 都能用

嵌入式 JS 引擎跟浏览器或者 Node.js 不一样,很多 API 是没有的。比如 windowdocumentrequire 这些都没有。

能用的基本就是原生 JS 的那些东西:字符串处理、数组操作、Math、Date、JSON 这些。够用了,别搞太复杂的。

坑四:脚本执行顺序

脚本规则也是规则的一种,同样遵循”从上往下匹配,匹配即停”的原则。所以脚本规则放的位置很重要,放前面了后面的规则就没机会了。

一般建议把脚本规则放中间或者后面,让普通规则先处理大部分流量,脚本处理那些特殊情况。

坑五:注意性能

虽然脚本很快,但也架不住量多。如果你有几千条请求每秒都过脚本,那还是会有开销的。

所以我的建议是:先用普通规则把大部分流量处理掉,只有那些普通规则搞不定的,才走脚本。别所有流量都走脚本,没必要。

我常用的脚本模板

最后给大家一个我自己常用的脚本模板,比较通用,可以照着改:

function main(params) {
  const host = params.host;
  const port = params.port;
  const process = params.process || "";

  // ========== 直连规则 ==========

  // 内网 IP 直连
  if (host.startsWith("192.168.") || 
      host.startsWith("10.") || 
      host.startsWith("127.") ||
      host.startsWith("172.16.")) {
    return "DIRECT";
  }

  // 下载工具直连(省流量)
  const downloaders = ["thunder.exe", "qbittorrent.exe", "aria2c.exe"];
  if (downloaders.includes(process)) {
    return "DIRECT";
  }

  // ========== 代理规则 ==========

  // 浏览器走代理
  const browsers = ["chrome.exe", "msedge.exe", "firefox.exe", "safari"];
  if (browsers.includes(process)) {
    return "浏览组";
  }

  // 视频网站走视频组
  const videoSuffixes = ["youtube.com", "netflix.com", "disneyplus.com"];
  for (const suffix of videoSuffixes) {
    if (host.endsWith(suffix)) {
      return "视频组";
    }
  }

  // ========== 特殊规则 ==========

  // 工作日白天社交网站走直连(防摸鱼)
  const now = new Date();
  const day = now.getDay();
  const hour = now.getHours();

  if (day >= 1 && day <= 5 && hour >= 9 && hour < 18) {
    const socialSites = ["twitter.com", "facebook.com", "instagram.com"];
    for (const s of socialSites) {
      if (host.indexOf(s) > -1) {
        return "DIRECT"; // 上班时间不让刷,哈哈
      }
    }
  }

  // ========== 兜底 ==========
  return "Proxy";
}

这个模板涵盖了几个常见的使用场景,你可以根据自己的需求增删修改。

常见问题 FAQ

所有 Clash 客户端都支持 JS 脚本吗?

不是。只有用 Clash Premium 或者 Clash Meta(Mihomo)内核的才支持。普通开源版内核没有这个功能。

现在新出的客户端基本都是 Meta 内核了,应该都支持。不确定的话看看你客户端的设置里有没有内核信息。

脚本规则和普通规则哪个快?

肯定是普通规则快。普通规则是原生实现的,性能很好。脚本是 JS 引擎执行的,虽然也不慢,但肯定比原生的差一点。

所以建议普通规则能搞定的就用普通规则,搞不定的再用脚本。

脚本可以发 HTTP 请求吗?

不行。脚本是同步执行的,不能发网络请求,也不能用异步 API。就老老实实写判断逻辑。

脚本里能用第三方库吗?

不行。嵌入式 JS 环境里没有 npm,也不能 require 模块。只能用 JS 原生的那些 API。

不过原生的也够用了,字符串、数组、Date、Math、JSON,这些写判断逻辑完全够了。

脚本出错了会怎么样?

如果脚本执行报错了,Clash 一般会记录错误日志,然后这条规则相当于没匹配上,继续往下匹配其他规则。不会导致整个 Clash 崩溃,放心。

但还是建议写好测试好再用,老报错也挺烦的。

怎么让脚本只对某些域名生效?

在规则里控制就行,比如:

rules:
  - DOMAIN-SUFFIX,google.com,Proxy
  - SCRIPT,my-script.js,Proxy  # 只有前面没匹配到的才走脚本
  - GEOIP,CN,DIRECT
  - MATCH,Proxy

脚本规则也是规则的一种,按顺序来。放后面一点,只有前面规则都没匹配到的才走脚本,这样性能更好。

写在最后

JavaScript 脚本是 Clash 一个非常强大的功能,给了用户极大的自由度。普通规则搞不定的需求,用脚本基本都能实现。

但话说回来,大部分人其实用不上这个功能。普通的规则 + 策略组已经能满足 99% 的需求了。脚本是给那些有特殊需求、喜欢折腾的人准备的。

如果你是刚接触 Clash 的新手,先把普通规则和策略组搞明白再说,别急着玩脚本。等你觉得普通规则不够用了,再来研究脚本也不迟。

我自己写脚本也都是一些简单的逻辑,太复杂的也懒得折腾。够用就行,没必要为了写脚本而写脚本。

希望这篇文章能帮你入门 Clash 的 JS 脚本。有啥好玩的想法,动手试试就是了,折腾的过程也是学习的过程嘛。