前言
技术要点:Surgio+Github+Netlify+Redis+CICD
项目地址
1.安装Node.js(已安装可跳过)
2.安装Surgio
通过命令生成项目文件夹,第一次用才安装,你可以拉取现成的配置仓库,再npm install
npm init surgio-store surgio-to-yaml
cd surgio-to-yaml
2.5.主要操作内容
provider文件夹 / surgio.conf.js文件 / template文件夹
3.provider文件夹使用说明
provider文件夹中主要存放订阅链接
资料查询
模板案例:
① Clash模板
module.exports = {
// 订阅类型
type: 'clash',
// 订阅链接
url: '>>>>订阅地址<<<<<',
// 是否添加旗帜
addFlag: true,
requestUserAgent: "clash-verge/v2.0.0",
// 钩子函数
hooks: {
...
② V2ray模板【vmess相关的节点可能会无效】
module.exports = {
// 订阅类型
type: 'v2rayn_subscribe',
// 订阅链接
url: '>>>>订阅地址<<<<<',
// 是否添加旗帜
addFlag: true,
requestUserAgent: "clash-verge/v2.0.0",
// 钩子函数
hooks: {
...
3.5.原生节点自动改名
可有效避免v2ray节点名称相同,及达到统一节点名称的效果
资料参考
...
// 钩子函数
hooks: {
// 通过订阅链接获取节点内容失败后,返回一个带失败信息的 nodeList 节点列表
onError: async error => {
// error: Error
// 自定义前缀
const customPrefix = "";
return [
{
nodeName: `${customPrefix}-Fallback: ${error.name}`,
type: 'shadowsocks',
hostname: 'fallback.example.com',
port: 443,
method: 'chacha20-ietf-poly1305',
password: 'password',
},
];
},
// 在获取到远程订阅内容后,修改节点的内容
afterNodeListResponse: async (nodeList, customParams) => {
// nodeList: NodeConfig[]
// customerParams: {}
// 自定义前缀
const customPrefix = "";
// 国家正则替换规则
const countryRegexRules = [
{ regex: /^(?=.*(香港|HK|Hong|🇭🇰))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "香港" },
{ regex: /^(?=.*(日本|JP|Japan|🇯🇵))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "日本" },
{ regex: /^(?=.*(韩国|韓|KR|Korea|🇰🇷))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "韩国" },
{ regex: /^(?=.*(新加坡|狮城|SG|Singapore|🇸🇬))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "新加坡" },
{ regex: /^(?=.*(美国|佛罗里达|US|United States|America|🇺🇸))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "美国" },
{ regex: /^(?=.*(英国|UK|United Kingdom|🇬🇧))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "英国" },
{ regex: /^(?=.*(法国|FR|France|🇫🇷))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "法国" },
{ regex: /^(?=.*(德国|DE|Germany|🇩🇪))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "德国" },
{ regex: /^(?=.*(台湾|台灣|TW|Taiwan|Wan|🇹🇼))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "台湾" },
{ regex: /^(?=.*(阿根廷|ARG|Argentina|🇦🇷))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "阿根廷" },
{ regex: /^(?=.*(澳大利亚|AUS|Australia|🇦🇺))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "澳大利亚" },
{ regex: /^(?=.*(巴西|BRA|Brazil|🇧🇷))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "巴西" },
{ regex: /^(?=.*(加拿大|CAN|Canada|🇨🇦))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "加拿大" },
{ regex: /^(?=.*(瑞士|CHE|Switzerland|🇨🇭))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "瑞士" },
{ regex: /^(?=.*(智利|CHL|Chile|🇨🇱))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "智利" },
{ regex: /^(?=.*(西班牙|ESP|Spain|🇪🇸))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "西班牙" },
{ regex: /^(?=.*(以色列|ISR|Israel|🇮🇱))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "以色列" },
{ regex: /^(?=.*(印度|IND|India|🇮🇳))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "印度" },
{ regex: /^(?=.*(意大利|ITA|Italy|🇮🇹))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "意大利" },
{ regex: /^(?=.*(马来西亚|MYS|Malaysia|🇲🇾))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "马来西亚" },
{ regex: /^(?=.*(荷兰|NLD|Netherlands|🇳🇱))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "荷兰" },
{ regex: /^(?=.*(菲律宾|PHL|Philippines|🇵🇭))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "菲律宾" },
{ regex: /^(?=.*(俄罗斯|莫斯科|RUS|Russia|🇷🇺))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "俄罗斯" },
{ regex: /^(?=.*(泰国|THA|Thailand|🇹🇭))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "泰国" },
{ regex: /^(?=.*(土耳其|TUR|Turkey|🇹🇷))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "土耳其" },
{ regex: /^(?=.*(乌克兰|UKR|Ukraine|🇺🇦))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "乌克兰" },
{ regex: /^(?=.*(越南|VNM|Vietnam|🇻🇳))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "越南" },
{ regex: /^(?=.*(南非|约翰内斯堡|ZAF|South Africa|🇿🇦))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "南非" },
{ regex: /^(?=.*(埃及|EGY|Egypt|🇪🇬))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "埃及" },
{ regex: /^(?=.*(新西兰|NZL|New Zealand|🇳🇿))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "新西兰" },
{ regex: /^(?=.*(朝鲜|PRK|North Korea|🇰🇵))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "朝鲜" },
{ regex: /^(?=.*(波兰|POL|Poland|🇵🇱))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "波兰" },
{ regex: /^(?=.*(缅甸|MMR|Myanmar|🇲🇲))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "缅甸" },
{ regex: /^(?=.*(蒙古|MNG|Mongolia|🇲🇳))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "蒙古" },
{ regex: /^(?=.*(芬兰|FIN|Finland|🇫🇮))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "芬兰" },
{ regex: /^(?=.*(希腊|GRC|Greece|🇬🇷))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "希腊" },
{ regex: /^(?=.*(立陶宛|LTU|Lithuania|🇱🇹))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "立陶宛" },
{ regex: /^(?=.*(阿联酋|迪拜|Dubai|ARE|United Arab Emirates|🇦🇪))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "阿联酋" },
{ regex: /^(?=.*(阿富汗|AFG|Afghanistan|🇦🇫))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "阿富汗" },
{ regex: /^(?=.*(罗马尼亚|ROU|Romania|🇷🇴))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "罗马尼亚" },
{ regex: /^(?=.*(奥地利|维也纳|AUT|Austria|🇦🇹))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "奥地利" },
{ regex: /^(?=.*(瑞典|SWE|Sweden|🇸🇪))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "瑞典" },
{ regex: /^(?=.*(葡萄牙|PRT|Portugal|🇵🇹))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "葡萄牙" },
{ regex: /^(?=.*(秘鲁|PER|Peru|🇵🇪))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "秘鲁" },
{ regex: /^(?=.*(沙特阿拉伯|SAU|Saudi Arabia|🇸🇦))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "沙特阿拉伯" },
{ regex: /^(?=.*(挪威|NOR|Norway|🇳🇴))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "挪威" },
{ regex: /^(?=.*(津巴布韦|ZWE|Zimbabwe|🇿🇼))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "津巴布韦" },
{ regex: /^(?=.*(摩尔多瓦|MDA|Moldova|🇲🇩))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "摩尔多瓦" },
{ regex: /^(?=.*(斯洛伐克|SVK|Slovakia|🇸🇰))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "斯洛伐克" },
{ regex: /^(?=.*(尼泊尔|NPL|Nepal|🇳🇵))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "尼泊尔" },
{ regex: /^(?=.*(孟加拉|BGD|Bangladesh|🇧🇩))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "孟加拉" },
{ regex: /^(?=.*(突尼斯|TUN|Tunisia|🇹🇳))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "突尼斯" },
{ regex: /^(?=.*(哥伦比亚|COL|Colombia|🇨🇴))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "哥伦比亚" },
{ regex: /^(?=.*(冰岛|ISL|Iceland|🇮🇸))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "冰岛" },
{ regex: /^(?=.*(匈牙利|HUN|Hungary|🇭🇺))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "匈牙利" },
{ regex: /^(?=.*(卡塔尔|QAT|Qatar|🇶🇦))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "卡塔尔" },
{ regex: /^(?=.*(北马其顿|北马|MKD|North Macedonia|🇲🇰))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "北马其顿" },
{ regex: /^(?=.*(伊拉克|IRQ|Iraq|🇮🇶))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "伊拉克" },
{ regex: /^(?=.*(柬埔寨|KHM|Cambodia|🇰🇭))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "柬埔寨" },
{ regex: /^(?=.*(阿塞拜疆|AZE|Azerbaijan|🇦🇿))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "阿塞拜疆" },
{ regex: /^(?=.*(哈萨克斯坦|KAZ|Kazakhstan|🇰🇿))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "哈萨克斯坦" },
{ regex: /^(?=.*(摩洛哥|MAR|Morocco|🇲🇦))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "摩洛哥" },
{ regex: /^(?=.*(尼日利亚|NGA|Nigeria|🇳🇬))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "尼日利亚" },
{ regex: /^(?=.*(比利时|BEL|Belgium|🇧🇪))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "比利时" },
{ regex: /^(?=.*(保加利亚|索菲亚|BGR|Bulgaria|🇧🇬))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "保加利亚" },
{ regex: /^(?=.*(巴基斯坦|PAK|Pakistan|🇵🇰))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "巴基斯坦" },
{ regex: /^(?=.*(安哥拉|AGO|Angola|🇦🇴))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "安哥拉" },
{ regex: /^(?=.*(捷克|CZE|Czech Republic|🇨🇿))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "捷克" },
{ regex: /^(?=.*(爱尔兰|IRL|Ireland|🇮🇪))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "爱尔兰" },
{ regex: /^(?=.*(丹麦|DNK|Denmark|🇩🇰))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "丹麦" },
{ regex: /^(?=.*(墨西哥|MEX|Mexico|🇲🇽))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "墨西哥" },
{ regex: /^(?=.*(塞尔维亚|SRB|Serbia|🇷🇸))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "塞尔维亚" },
{ regex: /^(?=.*(克罗地亚|HRV|Croatia|🇭🇷))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "克罗地亚" },
{ regex: /^(?=.*(乌兹别克|UZB|Uzbekistan|🇺🇿))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/, replace: "乌兹别克" }
];
// 用于存储已经使用的名称,避免重复
const usedNames = new Set();
// 处理数据
const NewNodeList = nodeList.map(item => {
let name = item.nodeName;
// 首先筛选出有特殊字样的内容
// 1.专线
const regex1 = /^(?=.*(IPLC|IEPL|CN2|GAPN|CUVIP|AIA))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/;
// 2.倍率
const regex2 = /^(?!.*(?:网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*(?:0[.](0[1-9]|[1-8][0-9]?|9[Xx])|[1-9][0-9]?[Xx]).*/;
// 3.Emby|家宽 等特殊字样
const regex3 = /^(?=.*(Emby|emby|家宽))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$/;
// 提取匹配国家的逻辑为函数
function replaceCountry(name, rules) {
for (const rule of rules) {
if (rule.regex.test(name)) {
return rule.replace;
}
}
return name;
}
// 提取匹配特殊关键字的逻辑为函数
function matchKeywords(name, regex) {
const keywords = name.match(regex) || [];
return keywords.join(" ");
}
// 主逻辑
if (regex3.test(name)) {
// 匹配特殊字样3
const otherSpecialWordsStr = matchKeywords(name, /(Emby|emby|家宽)/g);
if (regex1.test(name)) {
// 保留 IPLC|IEPL|CN2|GAPN|CUVIP|AIA 字样
const specialKeywordsStr = matchKeywords(name, /(IPLC|IEPL|CN2|GAPN|CUVIP|AIA)/g);
name = replaceCountry(name, countryRegexRules);
name = `${customPrefix} (${otherSpecialWordsStr}) ${name} [${specialKeywordsStr}]`.trim();
} else if (regex2.test(name)) {
// 保留 0.1|0.2X|0.3x|3x|5X 等字样
const specialKeywords = name.match(/(0\.\d{1,2}(?:[Xx]?)|[1-9]\d?[Xx])/g) || [];
const cleanedKeywords = specialKeywords.map(keyword => keyword.replace(/[Xx]/g, ''));
const specialKeywordsStr = cleanedKeywords.join(" ");
name = replaceCountry(name, countryRegexRules);
name = `${customPrefix} (${otherSpecialWordsStr}) ${name} [${specialKeywordsStr}x]`.trim();
} else {
name = replaceCountry(name, countryRegexRules);
name = `${customPrefix} (${otherSpecialWordsStr}) ${name}`.trim();
}
} else {
if (regex1.test(name)) {
// 保留 IPLC|IEPL|CN2|GAPN|CUVIP|AIA 字样
const specialKeywordsStr = matchKeywords(name, /(IPLC|IEPL|CN2|GAPN|CUVIP|AIA)/g);
name = replaceCountry(name, countryRegexRules);
name = `${customPrefix} ${name} [${specialKeywordsStr}]`.trim();
} else if (regex2.test(name)) {
// 保留 0.1|0.2X|0.3x|3x|5X 等字样
const specialKeywords = name.match(/(0\.\d{1,2}(?:[Xx]?)|[1-9]\d?[Xx])/g) || [];
const cleanedKeywords = specialKeywords.map(keyword => keyword.replace(/[Xx]/g, ''));
const specialKeywordsStr = cleanedKeywords.join(" ");
name = replaceCountry(name, countryRegexRules);
name = `${customPrefix} ${name} [${specialKeywordsStr}x]`.trim();
} else {
name = replaceCountry(name, countryRegexRules);
name = `${customPrefix} ${name}`.trim();
}
}
// 处理重复名称
let suffix = 1;
let finalName = `${name} ${suffix}`;
while (usedNames.has(finalName)) {
suffix++;
finalName = `${name} ${suffix}`;
}
usedNames.add(finalName);
return {
...item,
nodeName: finalName
};
});
return NewNodeList
},
},
...
3.6.获取订阅内容失败后返回自定义节点
...
// 钩子函数
hooks: {
// 通过订阅链接获取节点内容失败后,返回一个带失败信息的 nodeList 节点列表
onError: async error => {
// error: Error
// 自定义前缀
const customPrefix = "Nekoda";
return [
{
nodeName: `${customPrefix}-Fallback: ${error.name}`,
type: 'shadowsocks',
hostname: 'fallback.example.com',
port: 443,
method: 'chacha20-ietf-poly1305',
password: 'password',
},
];
},
...
4.template文件夹使用说明
template文件夹中主要存放生成的订阅文件的格式模板
资料查询
格式及案例参考
分流规则参考
完整案例参考
模板中主要替换条目:
...
proxies: {{ getClashNodes(nodeList) | json }}
...
4.5.片段的使用
使用参考
① template 文件夹下创建 rules.tpl (仅供参考)
{% macro main(jp_rule,change_rule,extranet_rule) %}
#### 私人-直连
- DOMAIN,keylol.com,DIRECT
#### 私人-仅限日本IP
- DOMAIN-SUFFIX,erogamescape.dyndns.org,{{ jp_rule }}
#### 私人-可切换
- DOMAIN,steamdb.info,{{ change_rule }}
#### 私人-默认外网
- DOMAIN,zi0.cc,{{ extranet_rule }}
- DOMAIN-SUFFIX,api.steampowered.com,{{ extranet_rule }}
{% endmacro %}
② 修改 template 文件夹下的主 tpl 文件
...
rules:
{% import './rules.tpl' as rules %}
{{ rules.main('🇯🇵 - 自动选择','🎯 节点选择','自动选择') }}
...
4.6.基本配置参考
# 全局配置
http-port: 7890
socks-port: 7891
redir-port: 7892
tproxy-port: 7893
allow-lan: true
geodata-mode: true
unified-delay: true
mode: rule
log-level: info
ipv6: true
tcp-concurrent: true
keep-alive-interval: 30
geo-auto-update: true
geo-update-interval: 24
geox-url:
geoip: "https://gcore.jsdelivr.net/gh/Loyalsoldier/geoip@release/geoip.dat"
geosite: "https://gcore.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geosite.dat"
mmdb: "https://gcore.jsdelivr.net/gh/Loyalsoldier/geoip@release/Country.mmdb"
find-process-mode: strict
global-client-fingerprint: chrome
# 域名嗅探
sniffer:
enable: true
parse-pure-ip: true
force-dns-mapping: true
override-destination: true
sniff:
HTTP:
ports: [80, 443, 8080-8880]
override-destination: true
TLS:
ports: [80, 443, 853]
QUIC:
ports: [80, 443, 853]
skip-domain:
- "+.push.apple.com"
- "Mijia Cloud"
skip-dst-address:
- 91.105.192.0/23
- 91.108.4.0/22
- 91.108.8.0/21
- 91.108.16.0/21
- 91.108.56.0/22
- 95.161.64.0/20
- 149.154.160.0/20
- 185.76.151.0/24
- 2001:67c:4e8::/48
- 2001:b28:f23c::/47
- 2001:b28:f23f::/48
- 2a0a:f280:203::/48
profile:
store-fake-ip: true
store-selected: true
# 裸核配置
# external-controller: 127.0.0.1:9090
# external-ui: zashborad
# external-ui-url: "https://github.com/Zephyruso/zashboard/releases/latest/download/dist.zip"
bind-address: "*"
# Tunnel 配置
tun:
enable: true
device: kk
stack: mixed
dns-hijack:
- any:53
auto-route: true
auto-detect-interface: true
# DNS 配置
dns:
enable: true
ipv6: true
listen: 0.0.0.0:1053
prefer-h3: true
enhanced-mode: fake-ip
fake-ip-range: 28.0.0.0/8
fake-ip-filter:
- '*'
- localhost.ptlogin2.qq.com
- dns.msftncsi.com
- www.msftncsi.com
- www.msftconnecttest.com
- time1.cloud.tencent.com
- '+.lan'
- '+.invalid.*'
- '+.localhost'
- '+.local.*'
- '+.time.*'
- '+.ntp.*'
- '+.time.edu.cn'
- '+.ntp.org.cn'
- '+.pool.ntp.org'
- '+.qpic.cn'
default-nameserver:
- 223.5.5.5
- 119.29.29.29
- '[2402:4e00::]'
- '[2400:3200::1]'
proxy-server-nameserver:
- https://1.12.12.12/dns-query
direct-nameserver:
- system
direct-nameserver-follow-policy: true
nameserver:
- tls://8.8.8.8#🎯 节点选择
- tls://208.67.222.222#🎯 节点选择
- 'tls://[2001:4860:4860::8888]#🎯 节点选择'
- 'tls://[2620:119:35::35]#🎯 节点选择'
nameserver-policy:
"geosite:cn,private":
- https://1.12.12.12/dns-query
- https://223.5.5.5/dns-query
- https://120.53.53.53/dns-query
"geosite:!cn,!private,!gfw":
- https://8.8.8.8/dns-query
- https://208.67.222.222/dns-query
- https://146.112.71.71/dns-query
# 节点筛选
## 特殊过滤
FilterEmby: &FilterEmby '^(?=.*(Emby|emby))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$'
FilterJK: &FilterJK '^(?=.*(家宽))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$'
FilterZX: &FilterZX '^(?=.*(IPLC|IEPL|CN2|GAPN|CUVIP|AIA))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*$'
FilterDB: &FilterDB '^(?!.*(?:网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*(?:0[.](0[1-9]|[1-8][0-9]?|9[Xx])).*'
FilterGB: &FilterGB '^(?!.*(?:网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地)).*(?:[3-9][Xx]|[1-9][0-9][Xx]|游戏).*'
## 按国家过滤
FilterHK: &FilterHK '^(?=.*(香港|HK|Hong|🇭🇰))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地|Emby|emby|家宽|([4-9]|[1-9]\d+)[Xx]))(?!.*0[.](0[1-9]|[1-8][0-9]?|9[Xx]?)).*$'
FilterJP: &FilterJP '^(?=.*(日本|JP|Japan|🇯🇵))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地|Emby|emby|家宽|([4-9]|[1-9]\d+)[Xx]))(?!.*0[.](0[1-9]|[1-8][0-9]?|9[Xx]?)).*$'
FilterKR: &FilterKR '^(?=.*(韩国|韓|KR|Korea|🇰🇷))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地|Emby|emby|家宽|([4-9]|[1-9]\d+)[Xx]))(?!.*0[.](0[1-9]|[1-8][0-9]?|9[Xx]?)).*$'
FilterSG: &FilterSG '^(?=.*(新加坡|狮城|SG|Singapore|🇸🇬))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地|Emby|emby|家宽|([4-9]|[1-9]\d+)[Xx]))(?!.*0[.](0[1-9]|[1-8][0-9]?|9[Xx]?)).*$'
FilterUS: &FilterUS '^(?=.*(美国|US|United States|America|🇺🇸))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地|Emby|emby|家宽|([4-9]|[1-9]\d+)[Xx]))(?!.*0[.](0[1-9]|[1-8][0-9]?|9[Xx]?)).*$'
FilterUK: &FilterUK '^(?=.*(英国|UK|United Kingdom|🇬🇧))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地|Emby|emby|家宽|([4-9]|[1-9]\d+)[Xx]))(?!.*0[.](0[1-9]|[1-8][0-9]?|9[Xx]?)).*$'
FilterFR: &FilterFR '^(?=.*(法国|FR|France|🇫🇷))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地|Emby|emby|家宽|([4-9]|[1-9]\d+)[Xx]))(?!.*0[.](0[1-9]|[1-8][0-9]?|9[Xx]?)).*$'
FilterDE: &FilterDE '^(?=.*(德国|DE|Germany|🇩🇪))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地|Emby|emby|家宽|([4-9]|[1-9]\d+)[Xx]))(?!.*0[.](0[1-9]|[1-8][0-9]?|9[Xx]?)).*$'
FilterTW: &FilterTW '^(?=.*(台湾|台灣|TW|Taiwan|Wan|🇹🇼))^(?!.*(网站|地址|剩余|过期|时间|有效|网址|禁止|邮箱|发布|客服|订阅|节点|Expire|Traffic|GB|教学|机场|账号|硬盘|资源|落地|Emby|emby|家宽|([4-9]|[1-9]\d+)[Xx]))(?!.*0[.](0[1-9]|[1-8][0-9]?|9[Xx]?)).*$'
## 其他过滤
FilterOthers: &FilterOthers '^(?!.*(🇭🇰|HK|Hong|香港|台湾|台灣|🇹🇼|TW|Taiwan|Wan|🇯🇵|JP|Japan|日本|🇸🇬|SG|Singapore|狮城|🇺🇸|US|United States|America|美国|🇩🇪|DE|Germany|德国|🇬🇧|UK|United Kingdom|英国|🇰🇷|KR|Korea|韩国|韓|🇫🇷|FR|France|法国)).*$'
FilterAll: &FilterAll '^(?=.*(.))(?!.*((?i)群|邀请|返利|循环|官网|客服|网站|网址|获取|订阅|流量|到期|机场|下次|版本|官址|备用|过期|已用|联系|邮箱|工单|贩卖|通知|倒卖|防止|国内|地址|频道|无法|说明|使用|提示|特别|访问|支持|教程|关注|更新|作者|加入|Expire|Traffic|教学|机场|账号|硬盘|资源|落地|Emby|emby|家宽|(\b(USE|USED|TOTAL|EXPIRE|EMAIL|Panel|Channel|Author)\b|(\d{4}-\d{2}-\d{2}|\d+G)))).*$'
Select: &Select {type: select, url: 'https://www.gstatic.com/generate_204', disable-udp: false, hidden: false, include-all: true}
Auto: &Auto {type: url-test, url: 'https://www.gstatic.com/generate_204', interval: 300, tolerance: 50, disable-udp: false, hidden: true, include-all: true}
Balance: &Balance {type: load-balance, url: 'https://www.gstatic.com/generate_204', interval: 300, disable-udp: false, hidden: true, include-all: true}
# 自定义节点
proxies: {{ getClashNodes(nodeList) | json }}
# 策略组
proxy-groups:
- {name: 🎯 节点选择, type: select, proxies: [直连, 自动选择, 手动选择], url: https://cp.cloudflare.com, icon: https://raw.githubusercontent.com/Orz-3/mini/master/Color/Static.png}
- {name: 手动选择, type: select, proxies: [直连, 🇭🇰 - 手动选择, 🇯🇵 - 手动选择, 🇰🇷 - 手动选择, 🇸🇬 - 手动选择, 🇺🇸 - 手动选择, 🇬🇧 - 手动选择, 🇫🇷 - 手动选择, 🇩🇪 - 手动选择, 🇹🇼 - 手动选择, Others - 手动选择, 专线 - 手动选择, 低倍率 - 手动选择, 高倍率 - 手动选择, 家宽 - 手动选择, Emby专用 - 手动选择], url: https://cp.cloudflare.com, icon: https://raw.githubusercontent.com/Orz-3/mini/master/Color/Cylink.png}
- {name: 自动选择, type: select, proxies: [🇭🇰 - 自动选择, 🇯🇵 - 自动选择, 🇰🇷 - 自动选择, 🇸🇬 - 自动选择, 🇺🇸 - 自动选择, 🇬🇧 - 自动选择, 🇫🇷 - 自动选择, 🇩🇪 - 自动选择, 🇹🇼 - 自动选择], url: https://cp.cloudflare.com, icon: https://raw.githubusercontent.com/Orz-3/mini/master/Color/Urltest.png}
- {name: Steam, type: select, proxies: [直连, 自动选择, 🇭🇰 - 手动选择, 🇯🇵 - 手动选择, 🇰🇷 - 手动选择, 🇸🇬 - 手动选择, 🇺🇸 - 手动选择, 🇬🇧 - 手动选择, 🇫🇷 - 手动选择, 🇩🇪 - 手动选择, 🇹🇼 - 手动选择, Others - 手动选择, 专线 - 手动选择], icon: https://raw.githubusercontent.com/Orz-3/mini/master/Color/steam.png}
- {name: Emby, type: select, proxies: [直连, 自动选择, 🇭🇰 - 手动选择, 🇯🇵 - 手动选择, 🇰🇷 - 手动选择, 🇸🇬 - 手动选择, 🇺🇸 - 手动选择, 🇬🇧 - 手动选择, 🇫🇷 - 手动选择, 🇩🇪 - 手动选择, 🇹🇼 - 手动选择, Others - 手动选择, 专线 - 手动选择, 低倍率 - 手动选择, Emby专用 - 手动选择], icon: https://raw.githubusercontent.com/Orz-3/mini/master/Color/Emby.png}
- {name: Telegram, type: select, proxies: [直连, 自动选择], icon: https://raw.githubusercontent.com/Orz-3/mini/master/Color/Telegram.png}
- {name: 下载选择, type: select, proxies: [直连, 自动选择, 🇭🇰 - 手动选择, 🇯🇵 - 手动选择, 🇰🇷 - 手动选择, 🇸🇬 - 手动选择, 🇺🇸 - 手动选择, 🇬🇧 - 手动选择, 🇫🇷 - 手动选择, 🇩🇪 - 手动选择, 🇹🇼 - 手动选择, Others - 手动选择, 专线 - 手动选择], icon: https://raw.githubusercontent.com/Orz-3/mini/master/Color/Socloud.png}
# 自动选择
- {name: 🇭🇰 - 自动选择, <<: *Balance, filter: *FilterHK}
- {name: 🇯🇵 - 自动选择, <<: *Balance, filter: *FilterJP}
- {name: 🇰🇷 - 自动选择, <<: *Balance, filter: *FilterKR}
- {name: 🇸🇬 - 自动选择, <<: *Balance, filter: *FilterSG}
- {name: 🇺🇸 - 自动选择, <<: *Balance, filter: *FilterUS}
- {name: 🇬🇧 - 自动选择, <<: *Balance, filter: *FilterUK}
- {name: 🇫🇷 - 自动选择, <<: *Balance, filter: *FilterFR}
- {name: 🇩🇪 - 自动选择, <<: *Balance, filter: *FilterDE}
- {name: 🇹🇼 - 自动选择, <<: *Balance, filter: *FilterTW}
# 手动选择
- {name: 🇭🇰 - 手动选择, <<: *Select, filter: *FilterHK}
- {name: 🇯🇵 - 手动选择, <<: *Select, filter: *FilterJP}
- {name: 🇰🇷 - 手动选择, <<: *Select, filter: *FilterKR}
- {name: 🇸🇬 - 手动选择, <<: *Select, filter: *FilterSG}
- {name: 🇺🇸 - 手动选择, <<: *Select, filter: *FilterUS}
- {name: 🇬🇧 - 手动选择, <<: *Select, filter: *FilterUK}
- {name: 🇫🇷 - 手动选择, <<: *Select, filter: *FilterFR}
- {name: 🇩🇪 - 手动选择, <<: *Select, filter: *FilterDE}
- {name: 🇹🇼 - 手动选择, <<: *Select, filter: *FilterTW}
- {name: Others - 手动选择, <<: *Select, filter: *FilterOthers}
# 全部节点
- {name: AllIn - 手动选择, <<: *Select, filter: *FilterAll}
- {name: AllIn - 自动选择, <<: *Balance, filter: *FilterAll}
# 其他
- {name: 专线 - 手动选择, <<: *Select, filter: *FilterZX}
- {name: 高倍率 - 手动选择, <<: *Select, filter: *FilterGB}
- {name: 低倍率 - 手动选择, <<: *Select, filter: *FilterDB}
- {name: 家宽 - 手动选择, <<: *Select, filter: *FilterJK}
- {name: Emby专用 - 手动选择, <<: *Select, filter: *FilterEmby}
- {name: 直连, type: select, proxies: [DIRECT]}
- {name: 拦截, type: select, proxies: [REJECT]}
- {name: 跳过匹配, type: select, proxies: [PASS]}
- {name: 广告, type: select, proxies: [REJECT-DROP,REJECT,PASS]}
# 规则配置
## 通用分流
RuleSet_classical: &RuleSet_classical {type: http, behavior: classical, interval: 86400}
## 域名分流
RuleSet_domain: &RuleSet_domain {type: http, behavior: domain, interval: 86400}
## IP分流
RuleSet_ipcidr: &RuleSet_ipcidr {type: http, behavior: ipcidr, interval: 86400}
# 订阅规则
rule-providers:
Google:
<<: *RuleSet_classical
url: "https://raw.githubusercontent.com/Ctory-Nily/rule-script/main/rules/Clash/Google/Google.yaml"
path: ./rule_providers/Google.yaml
...
...
...
## 配置广告分流域名
秋风广告规则:
<<: *RuleSet_domain
format: yaml
url: "https://raw.githubusercontent.com/TG-Twilight/AWAvenue-Ads-Rule/main/Filters/AWAvenue-Ads-Rule-Clash.yaml"
path: ./rule_providers/AWAvenue-Ads-Rule-Clash.yaml
AD-Clash:
<<: *RuleSet_domain
format: yaml
url: "https://raw.githubusercontent.com/Loyalsoldier/clash-rules/release/reject.txt"
path: ./rule_providers/AD-Clash.yaml
## 本地规则分流
userDirect:
type: file
behavior: classical
path: ./rule_providers/userDirect.yaml
userProxy:
type: file
behavior: classical
path: ./rule_providers/userProxy.yaml
userReject:
type: file
behavior: classical
path: ./rule_providers/userReject.yaml
# 分流规则
rules:
- IP-CIDR,127.0.0.1/32,REJECT,no-resolve
- IP-CIDR,192.168.0.0/16,DIRECT
- AND,((DST-PORT,5228-5230),(NETWORK,TCP),(DOMAIN-KEYWORD,google)),DIRECT
- IP-CIDR,95.161.76.100/31,REJECT,no-resolve
### 广告拦截策略
- RULE-SET,AD-Clash,广告
- RULE-SET,秋风广告规则,广告
### 本地分流策略
- RULE-SET,userDirect,直连
- RULE-SET,userProxy,自动选择
- RULE-SET,userReject,拦截
### 私人分流策略
{% import './rules.tpl' as rules %}
{{ rules.main('🇯🇵 - 自动选择','🎯 节点选择','自动选择') }}
...
...
...
- RULE-SET,Google,自动选择
## GEO分流策略
- GEOSITE,CN,直连
- GEOIP,CN,直连
## 默认策略
- MATCH,🎯 节点选择
4.7.扩展-目前可用的全球 DNS 解析服务 [2025.3.6]
208.67.222.222
208.67.220.220
208.67.222.123
208.67.220.123
208.67.222.2
208.67.220.2
146.112.41.2
146.112.41.3
146.112.41.4
146.112.41.5
204.194.232.200
204.194.234.200
208.67.222.64
208.67.220.64
146.112.71.71
146.112.70.70
5.surgio.conf.js文件使用说明
surgio.conf.js文件主要用于配置和生成订阅文件
配置文件内容修改
Artifacts部分修改
模板参考:
module.exports = {
artifacts: [
{
"name": "Clash_JiChang.yaml",
"template": "clash",
"provider": "JiChang1",
"subscriptionUserInfoProvider": "JiChang1",
"combineProviders": [
"JiChang2",
"JiChang3"
]
},
{
"name": "Clash_Free.yaml",
"template": "clash",
"provider": "Free1",
"combineProviders": [
"Free2",
"Free3"
]
}
],
clashConfig: {
enableTuic: true,
enableShadowTls: true,
enableHysteria2: true,
enableVless: true,
clashCore: 'clash.meta'
}
}
6. 测试生成及检查是否有报错
编写完以后在cmd中使用此代码,来生成订阅文件,订阅文件生成在dist文件夹下
npx surgio generate
7.安装git(已安装可跳过)
7.1.配置github公钥私钥
① 安装完git后输入,生成公钥私钥
邮箱要与后面git配置的邮箱一致
ssh-keygen -t rsa -C “邮箱”
② 查看公钥并配置到github中
公私钥生成路径: C:\Users\用户名\.ssh
id_rsa: 私钥
id_rsa.pub: 公钥
查看公钥 > 打开Github > Setting > SSH and GPG keys > New SSH key > 输入名称 > 输入公钥内容 > Add SSH key
③ 验证配置
ssh git@github.com
7.5.在github上创建仓库并将修改好的项目上传到github上
第一次 上传到github:
git config --global user.name “用户名”
git config --global user.email “邮箱”
git init
git add .
git commit -m "第一次提交"
git remote add origin ssh://git@ssh.github.com:443/Ctory-Nily/surgio-to-yaml.git
git push -u origin main
更新本地仓库后上传:
git add .
git commit -m "更新信息"
git push -u origin main
第一次 拉取最新的github仓库:
git clone ssh://git@ssh.github.com:443/Ctory-Nily/surgio-to-yaml.git
npm install
更新拉取最新的github仓库内容:
git pull origin main
8.将github项目托管到Netlify上
参考资料1
参考资料2
参考资料3
Netlify官网
① 开启接口授权 surgio.conf.js
...
gateway: {
auth: true,
accessToken: '后台面板密码',
viewerToken: '订阅链接密码'
},
...
② 项目根目录创建netlify.toml
[build]
command = "exit 0"
functions = "netlify/functions"
publish = "."
[functions]
included_files = [
"node_modules/surgio/**",
"node_modules/@surgio/**",
"provider/**",
"template/**",
"*.js",
"*.json"
]
[[redirects]]
from = "/*"
to = "/.netlify/functions/index"
status = 200
force = true
③ 项目根目录创建 netlify/functions 并新建文件 netlify/functions/index.js
'use strict';
const gateway = require('@surgio/gateway');
module.exports.handler = gateway.createLambdaHandler();
④ 更新surgio.conf.js内urlBase的值
将urlBase设置为你面板上的链接
...
urlBase: 'https://surgio-demo.netlify.app/get-artifact/'
...
⑤ 重新推送到仓库,此时Netlify的配置就部署好了
git add .
git commit -m "重新推送"
git push -u origin main
⑥ 在 Netlify 中选择新建项目,并选择此github项目, 然后就会自动部署了
点击 Open production deploy 就可以查看到流量面板了,访问密码就是 surgio.conf.js 中的 accessToken
此时就可以在这里 查看剩余流量 和 获取到订阅链接了
9.配置Redis
① 获取到免费的在线redis服务
Redis在线服务网站upstash
② 新建redis项目
需要注意,假如你的 Surgio 服务部署在美西,那 Redis 也最好在美西。
Railway 默认的部署区域是 us-west-1,Vercel 的默认部署区域是 us-east-1,Netlify 的默认部署区域是 us-east-2
默认链接类似:
redis://:xxx...@some-thing-like-35533.upstash.io:35533
如果你redis的TLS/SSL是Enabled那么格式要改成:
rediss://:xxx...@some-thing-like-35533.upstash.io:35533
③ 在surgio.conf.js内新增redis的内容
...
cache: process.env.REDIS_URL || process.env.REDIS_URI
? {
type: 'redis',
redisUrl: process.env.REDIS_URL || process.env.REDIS_URI,
}
: undefined,
...
④ 在 Netlify 中设置环境变量 REDIS_URL 的值
进入自己的项目 > Site configuration > Environment variables > 新建REDIS_URL环境变量 > 写入value值
⑤ 重新推送到仓库,此时Netlify在部署时,就会去加载Redis的缓存
git add .
git commit -m "加载缓存"
git push -u origin main
10.配置CICD
① 生成 Personal Access Token
点击右上角头像 > 选择 Settings > Developer settings > Personal access tokens > Generate new token
勾选以下权限:
repo(完全控制仓库)
workflow(允许操作工作流)
点击 Generate token,复制生成的 Token
② 将 Token 添加到仓库的 Secrets
打开你的GitHub仓库 > 点击Settings > Secrets and variables > Actions > New repository secret
输入以下内容:
Name: PUSH_EVERYDAY(或其他你喜欢的名字)
Value: 粘贴刚才生成的 Personal Access Token
点击 Add secret
③ 设置 Workflow的写入权限 打开你的GitHub仓库 > 点击Settings > Actions > General > Workflow permissions > 选择 Read and write permissions
④ 在你的项目根目录下创建 .github/workflows 文件夹
新建 update-everyday.yml
name: Daily Auto Push
on:
schedule:
# 每天 UTC 时间 20:00 触发(北京时间 04:00)
- cron: '0 20 * * *'
workflow_dispatch: # 允许手动触发
jobs:
auto-push:
runs-on: ubuntu-latest
steps:
# 检出代码
- name: Checkout repository
uses: actions/checkout@v3
# 模拟修改(例如更新文件)
- name: Make changes
run: |
date > timestamp.txt
# 推送更改
- name: Commit and push changes
env:
GH_TOKEN: ${{ secrets.PUSH_EVERYDAY }} # 使用之前保存的 Token
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
git add .
git commit -m "Auto-update: Daily changes"
git remote set-url origin https://x-access-token:$GH_TOKEN@github.com/$GITHUB_REPOSITORY.git
git push origin main
⑤ 重新推送到仓库,此时在Actions选项卡中就可以看到 Daily Auto Push 工作流
git add .
git commit -m "加载工作流"
git push -u origin main
⑥ 工作流每日提交之后,本地文件更新前,需要先把仓库同步到本地
git pull origin main
10.5.配置清空Action工作流(可选)
在你的项目根目录下创建 .github/workflows 文件夹
① 新建 clear-commit.yml
name: Clear Commits
on:
workflow_dispatch: # 允许手动触发
jobs:
clear-commits:
runs-on: ubuntu-latest
permissions:
contents: write
actions: write
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
ref: main
fetch-depth: 0
- name: Configure Git
run: |
git config --global user.name "GitHub Action"
git config --global user.email "action@github.com"
git config --global advice.detachedHead false
- name: Rewrite Git History
run: |
git checkout --orphan temp-branch
git add -A
git commit -m "Initial commit after history clear"
git branch -D main || true
git branch -m main
git push -f origin main
- name: Cleanup Workflow Runs
run: |
# 使用 GitHub API 删除工作流记录
echo "Cleaning up workflow runs..."
RUNS_URL="https://api.github.com/repos/${{ github.repository }}/actions/runs"
RUNS_RESPONSE=$(curl -s -H "Authorization: Bearer ${{ secrets.PUSH_EVERYDAY }}" "$RUNS_URL")
RUN_IDS=$(echo "$RUNS_RESPONSE" | jq -r '.workflow_runs[].id')
for RUN_ID in $RUN_IDS; do
echo "Deleting run $RUN_ID..."
curl -s -X DELETE -H "Authorization: Bearer ${{ secrets.PUSH_EVERYDAY }}" "$RUNS_URL/$RUN_ID"
done
echo "Workflow runs cleanup completed."
- name: Post-Cleanup Check
run: echo "History cleared successfully at $(date)"
② 清除工作流之后会把github项目内的所有文件提交到最新分支,此时需要将本地的项目合并到新的分支
git pull --rebase
11.整体流程
① 购买机场获取订阅链接,添加链接到surgio
② 使用surgio提取节点,合并订阅链接,配置订阅规则
③ 上传到github上,配置CICD自动更新项目,触发Netlify的更新,触发Redis缓存机制
④ Netlify上会自动更新节点内容,获取自己的订阅链接
⑤ 在Mihomo上填写自己的订阅链接
⑥ 管理好本地的规则文件
12.本地自动更新脚本 “提交更新.bat”
无论本地文件先修改还是后修改都可以同步上传上去,注意bat文件格式要为ANSI
@echo off
echo 正在同步github仓库
git pull origin main
echo 正在添加所有文件到暂存区
git add .
:: 格式化日期和时间
for /f "tokens=1-3 delims=/- " %%a in ("%date%") do (
set year=%%a
set month=%%b
set day=%%c
)
for /f "tokens=1-3 delims=:.," %%a in ("%time%") do (
set hour=%%a
set minute=%%b
)
:: 去掉小时前面的空格(如果小时是单数)
set hour=%hour: =%
:: 组合成 "年 月 日 时 分" 格式
set formatted_time=%year%年%month%月%day%日%hour%时%minute%分
:: 询问是否自定义提交信息
:input_confirm
set /p confirm=是否自定义提交信息?(输入 y 或 n):
if "%confirm%"=="" (
echo 输入不能为空,请重新输入!
goto input_confirm
)
if "%confirm%"=="y" (
:input_msg
set /p commit_msg=请输入提交信息:
if "%commit_msg%"=="" (
echo 输入不能为空,请重新输入!
goto input_msg
)
git commit -m "%commit_msg%"
) else (
git commit -m "%formatted_time%"
)
echo 正在推送到远程仓库
git push -u origin main
echo 操作完成!
pause
13.扩展-添加工作流自动生成 provider 文件并修改配置文件
① 项目目录下新建 script 文件夹, 创建 provider_list.json
[
{
"provider_type": "clash",
"provider_url": ">>>订阅链接<<<",
"custom_prefix": "Test"
},
{
"provider_type": "clash",
"provider_url": ">>>订阅链接<<<",
"custom_prefix": "Free1"
},
{
"provider_type": "v2rayn_subscribe",
"provider_url": ">>>订阅链接<<<",
"custom_prefix": "Free2"
},
]
② 在 script 文件夹下创建名为 template.js 的 provider 文件模板
(将原先 provider 文件夹下的 .js 文件复制过来后, 更改名称, 将文件内的 type、url 和 customPrefix 清空)
...
// 订阅类型
type: '',
// 订阅链接
url: '',
// 是否添加旗帜
addFlag: true,
requestUserAgent: "clash-verge/v2.0.0",
// 钩子函数
hooks: {
// 通过订阅链接获取节点内容失败后,返回一个带失败信息的 nodeList 节点列表
onError: async error => {
// error: Error
// 自定义前缀
const customPrefix = "";
return [
{
...
...
...
];
},
// 在获取到远程订阅内容后,修改节点的内容
afterNodeListResponse: async (nodeList, customParams) => {
// nodeList: NodeConfig[]
// customerParams: {}
// 自定义前缀
const customPrefix = "";
...
③ 在 script 文件夹下创建 process_json_and_config.py
根据 provider_list.json 中的内容
1.生成 provider 文件
2.删除多余的 provider 文件
3.更改 surgio.conf.js 配置文件的内容
import os
import logging
import json
from typing import List, Dict, Optional, Union
import shutil
# 初始化 artifacts 数据
config_artifacts = [
{
"name": "Clash_JiChang.yaml",
"template": "clash",
"provider": "", # 非 Free 开头的第一个 custom_prefix
"subscriptionUserInfoProvider": "",
"combineProviders": [] # 非 Free 开头的后续 custom_prefix
},
{
"name": "Clash_Free.yaml",
"template": "clash",
"provider": "", # Free 开头的第一个 custom_prefix
"combineProviders": [] # Free 开头的后续 custom_prefix
}
]
def delete_unnecessary_files(provider_list_data: List[Dict[str, str]], provider_path: str) -> None:
"""
删除 provider 文件夹下不匹配的文件
:param provider_list_data: 列表数据
:param provider_path: 删除路径
"""
# 获取所有 custom_prefix 值,并加上 .js 后缀
valid_files = {f"{provider['custom_prefix']}.js" for provider in provider_list_data}
# 遍历 provider 文件夹,删除不匹配的文件
for filename in os.listdir(provider_path):
file_path = os.path.join(provider_path, filename)
# 检查文件名是否在 valid_files 中
if os.path.isfile(file_path) and filename not in valid_files:
os.remove(file_path)
logging.info(f"已删除不匹配的文件: {filename}")
def update_artifacts(provider_list_data: List[Dict[str, str]]) -> List[Dict[str, Union[str, List[str]]]]:
"""
更新 artifacts 列表数据
:param provider_list_data: 列表数据
:return: 更改后的列表数据
"""
for provider in provider_list_data:
custom_prefix = provider["custom_prefix"]
# 处理 Free 开头的 custom_prefix
if custom_prefix.startswith("Free"):
# 如果 provider 为空时 第一次赋值
if not config_artifacts[1]["provider"]:
config_artifacts[1]["provider"] = custom_prefix
# 如果 provider 不为空时 第二次开始赋值
else:
config_artifacts[1]["combineProviders"].append(custom_prefix)
# 处理非 Free 开头的 custom_prefix
else:
# 如果 provider 为空时 第一次赋值
if not config_artifacts[0]["provider"]:
config_artifacts[0]["provider"] = custom_prefix
config_artifacts[0]["subscriptionUserInfoProvider"] = custom_prefix
# 如果 provider 不为空时 第二次开始赋值
else:
config_artifacts[0]["combineProviders"].append(custom_prefix)
# 如果 combineProviders 为空,则删除该字段
for artifact in config_artifacts:
if not artifact["combineProviders"]:
artifact.pop("combineProviders", None)
return config_artifacts
def write_provider(provider_type: str, provider_url: str, custom_prefix: str, provider_template_path: str, provider_path: str) -> None:
"""
复制 js 模板文件并重命名, 并修改内容
:param provider_type: 订阅链接的书写类型
:param provider_url: 订阅链接
:param custom_prefix: 订阅名称
:param provider_template_path: 模板路径
:param provider_path: 生成路径
"""
# 定义以 custom_prefix 命名的 .js 文件
target_file = os.path.join(provider_path, f"{custom_prefix}.js")
source_file = os.path.join(provider_template_path, "template.js")
# 检查模板文件是否存在
if not os.path.exists(source_file):
logging.error(f"模板文件 {source_file} 不存在,跳过")
exit(1)
# 复制并重命名文件 (覆盖或创建)
shutil.copy(source_file, target_file)
# 替换文件内容
with open(target_file, "r", encoding="utf-8") as file:
content = file.read()
# 根据 provider_type 替换 provider 的链接订阅类型
if provider_type == "clash":
content = content.replace('type: \'\'', f'type: \'{provider_type}\'')
elif provider_type == "v2rayn_subscribe":
content = content.replace('type: \'\'', f'type: \'{provider_type}\'')
else:
logging.error(f"未知 provider 链接订阅类型: {provider_type}")
exit(1)
# 替换 url 和 customPrefix
content = content.replace('url: \'\'', f'url: \'{provider_url}\'')
content = content.replace('const customPrefix = "";', f'const customPrefix = "{custom_prefix}";')
# 写回文件
with open(target_file, "w", encoding="utf-8") as file:
file.write(content)
def modify_config(provider_list_data: List[Dict[str, str]], provider_path: str) -> None:
"""
修改 surgio.conf.js 配置文件中的内容
:param provider_list_data: 列表数据
:param provider_path: 生成路径
"""
# 删除 provider 文件夹下不匹配的文件
delete_unnecessary_files(provider_list_data, provider_path)
# 更新 artifacts 列表数据
config_artifacts = update_artifacts(provider_list_data)
# 读取原始配置文件
with open("surgio.conf.js", "r", encoding="utf-8") as file:
config_content = file.read()
# 替换 artifacts 部分
start_index = config_content.find("artifacts: [")
end_index = config_content.find("],", start_index) + 2
update_config_content = (
config_content[:start_index] +
"artifacts: " + json.dumps(config_artifacts, indent=2) + "," +
config_content[end_index:]
)
# 写回配置文件
with open("surgio.conf.js", "w", encoding="utf-8") as file:
file.write(update_config_content)
if __name__ == "__main__":
provider_template_path = 'script/'
provider_path = "provider/"
os.makedirs(provider_template_path, exist_ok=True)
os.makedirs(provider_path, exist_ok=True)
# 获取 provider_list.json 的路径
json_file_path = os.path.join(os.path.dirname(__file__), 'provider_list.json')
# 读取 provider_list.json 文件
try:
with open(json_file_path, "r", encoding="utf-8") as json_file:
provider_list_data = json.load(json_file)
except FileNotFoundError:
logging.error(f"文件未找到: {json_file_path}")
exit(1)
except json.JSONDecodeError:
logging.error(f"JSON 文件格式错误: {json_file_path}")
exit(1)
# 生成 provider 文件
for item in provider_list_data:
write_provider(item["provider_type"], item["provider_url"], item["custom_prefix"], provider_template_path, provider_path)
# 处理 配置文件
modify_config(provider_list_data, provider_path)
④ 在 .github/workflows 文件夹下创建工作流文件 process-json-and-config.yml
name: Process JSON and Config File
on:
# 手动触发
workflow_dispatch:
push:
paths:
# 当 script 文件夹下 任意文件有更新时触发
- 'script/**'
jobs:
process_json_and_config:
runs-on: ubuntu-latest
# 将 has_changes 作为作业输出
outputs:
has_changes: ${{ steps.check-changes.outputs.has_changes }}
steps:
- name: Checkout code
uses: actions/checkout@v3
with:
fetch-depth: 0 # 获取完整的历史记录
- name: Check for changes in script directory
id: check-changes
run: |
# 检查 script 文件夹下 任意文件是否有更改
if git diff --quiet HEAD~1 HEAD -- script; then
echo "No changes in script directory."
echo "has_changes=false" >> $GITHUB_OUTPUT
else
echo "Changes detected in script directory."
echo "has_changes=true" >> $GITHUB_OUTPUT
fi
- name: Set up Python
if: steps.check-changes.outputs.has_changes == 'true'
uses: actions/setup-python@v4
with:
python-version: '3.9'
# 运行 process_json_and_config.py 脚本
- name: Run fetch and convert script
if: steps.check-changes.outputs.has_changes == 'true'
run: |
python script/process_json_and_config.py
# 提交推送
- name: Commit and push changes
if: steps.check-changes.outputs.has_changes == 'true'
env:
GH_TOKEN: ${{ secrets.PUSH_EVERYDAY }}
run: |
git config --local user.email "actions@github.com"
git config --local user.name "GitHub Actions"
git add .
if git diff-index --quiet HEAD; then
echo "没有文件更改,跳过提交。"
else
git commit -m "Process Json file and Update Config file"
git pull origin main --rebase
git remote set-url origin https://x-access-token:$GH_TOKEN@github.com/$GITHUB_REPOSITORY.git
if git push origin main; then
echo "推送成功。"
else
echo "推送失败,请检查远程分支是否有冲突。"
exit 1
fi
fi
14.扩展-个人的规则使用参考
1.配置 file 类型的规则 (直接在本地文件中添加规则, 不需要任何提交, 但可能会丢失)
userDirect:
type: file
behavior: classical
path: ./rule_providers/userDirect.yaml
- 临时不常用的规则使用这个方式
2.配置 http 类型的规则 (修改 rule-script 仓库, 修改完后提交仓库, 提交完后需要 Mihomo 同步规则文件)
Google:
type: http,
behavior: classical,
interval: 86400,
url: "https://raw.githubusercontent.com/Ctory-Nily/rule-script/main/rules/Clash/Google/Google.yaml"
path: ./rule_providers/Google.yaml
- 大量常用的规则使用这个方式
3.直接将规则写入 rules (需要修改 surgio 仓库 template 文件夹下的 clash.tpl, 修改完后提交仓库, Netlify 会同步)
rules:
#### 私人-直连
- DOMAIN,keylol.com,DIRECT
#### 私人-仅限日本IP
- DOMAIN-SUFFIX,erogamescape.dyndns.org,🇯🇵 - 自动选择
- 少量常用的规则使用这个方式
15.之后的使用方法
仓库每天都在更新, 在更改本地仓库内容之前首先拉取最新的内容
git pull origin main
① 如果要修改机场链接 或者 新增机场链接
打开 script 文件夹下的 provider_list.json 复制新增一行并修改 provider_type、provider_url 和 custom_prefix
② 如果要修改规则、新增规则、修改 DNS 就需要修改 template 文件夹下的 .tpl 文件
修改 rule-providers、rules、dns 后的内容
③ 修改完成后打开命令行, 验证修改的文件是否有问题
npx surgio generate
能够在 dist 文件夹下正常输出 .yaml 文件就没有问题了
④ 通过 git 上传并更新 github 仓库
git add .
git commit -m "Initial commit"
git push -u origin main
⑤ 提交之后如果 provider_list.json 有内容更新, 那么就会自动执行工作流, 自动生成新的 provider 文件 并 修改 surgio.conf.js 文件
⑥ 当 Github 仓库有内容更新时, Netlify 就会检测到并重新构建, 订阅内容就会被更新
只要 surgio.conf.js 文件中的 name 和 viewerToken 没有发生变化,订阅链接基本不会改变
你也可以在 Netlify 上更改此项目的域名