Last active
July 3, 2024 02:08
-
-
Save wizos/3c0bb7dd4833498a4155e97dac792b1b to your computer and use it in GitHub Desktop.
将 ss, vmess, vless, trojan, hysteria, hysteria2 等链接格式的节点转化为 sing-box 格式的配置文件。可以放在 cloudflare worker 上
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * Welcome to Cloudflare Workers! This is your first worker. | |
| * | |
| * - Run "npm run dev" in your terminal to start a development server | |
| * - Open a browser tab at http://localhost:8787/ to see your worker in action | |
| * - Run "npm run deploy" to publish your worker | |
| * | |
| * Learn more at https://developers.cloudflare.com/workers/ | |
| */ | |
| export default { | |
| /** | |
| * @param {any} request | |
| * @param {any} env | |
| * @param {any} ctx | |
| */ | |
| async fetch(request, env, ctx) { | |
| return handler(request); | |
| }, | |
| }; | |
| /** | |
| * Many more examples available at: https://developers.cloudflare.com/workers/examples | |
| * @param {Request} request | |
| * @returns {Promise<Response>} | |
| */ | |
| async function handler(request) { | |
| const url = new URL(request.url); | |
| const queryParams = url.searchParams; | |
| let text = ""; | |
| if (queryParams.get('sub')) { | |
| text = await fetchRes(base64Decode(queryParams.get('sub'))); | |
| if (isBase64(text)) { | |
| text = base64Decode(text); | |
| } | |
| } else if (queryParams.get('node')) { | |
| text = base64Decode(queryParams.get('node')); | |
| } else { | |
| return new Response('安能曲眉折腰事权贵,使我不得开心颜'); | |
| } | |
| const lines = text.split(/[(\r\n)\r\n]+/).map(uri => uri.trim()).filter(uri => uri); | |
| const uniqueLines = [...new Set(lines)]; // 使用扩展运算符将 Set 转换为数组 | |
| const results = deduplication( uniqueLines.map(parseLine).filter(config => config !== null) ); | |
| if (results.length > 0) { | |
| return new Response(JSON.stringify(echo(results), null, 2), { | |
| headers: { 'Content-Type': 'application/json' } | |
| }); | |
| } | |
| return new Response('没有节点', { | |
| status: 400, // 自定义状态码 | |
| statusText: '没有节点' | |
| }); | |
| } | |
| /** | |
| * @param {string} uri | |
| */ | |
| function parseLine(uri) { | |
| try { | |
| if (uri.startsWith('vmess://') || uri.startsWith('vless://') | |
| || uri.startsWith('trojan://') || uri.startsWith('trojango://') | |
| || uri.startsWith('hysteria://') || uri.startsWith('hy://') | |
| || uri.startsWith('hysteria2://') || uri.startsWith('hy2://') | |
| || uri.startsWith('ss://')) { | |
| const protocol = uri.split('://')[0]; | |
| let mainPart = uri.split('://')[1]; | |
| // Decode Base64 if it is encoded | |
| if (isBase64(mainPart)) { | |
| // 此时 mainPart 可能是 uri,也有可能是 json | |
| mainPart = base64Decode(mainPart); | |
| } | |
| // 参见 https://github.com/2dust/v2rayN/wiki/%E5%88%86%E4%BA%AB%E9%93%BE%E6%8E%A5%E6%A0%BC%E5%BC%8F%E8%AF%B4%E6%98%8E(ver-2) | |
| if (isJson(mainPart)) { | |
| const jsonObject = JSON.parse(mainPart); | |
| // console.log("对象转义:" + JSON.stringify(jsonObject)); | |
| return parseV2RayNGJson(jsonObject); | |
| } else { | |
| let decodedURI = protocol + "://" + mainPart; | |
| let url = new URL(decodedURI); | |
| if (protocol === 'ss' && !url.password) { | |
| const decodeUsernameAndPW = decodeURIComponent(url.username); | |
| decodedURI = decodedURI.replace(decodeUsernameAndPW, base64Decode(decodeUsernameAndPW)); | |
| // console.log("解码0:" + encodeURI(decodedURI) ); | |
| // console.log("解码1:" + url.username); | |
| // console.log("解码2:" + decodeURIComponent(url.username)); | |
| url = new URL(decodedURI); | |
| // url.username = base64Decode(decodeURIComponent(url.username)); | |
| } | |
| const result = {}; | |
| // console.log("反编码 url B: " + url); | |
| result.server = decodeURIComponent(url.hostname); | |
| if (url.port) { | |
| if ("string" === typeof url.port) { | |
| result.server_port = parseInt(url.port); | |
| } else { | |
| result.server_port = url.port; | |
| } | |
| } else if (protocol === 'ss' || protocol === 'hy' || protocol === 'hy2' || protocol === 'hysteria' || protocol === 'hysteria2') { | |
| result.server_port = 443; | |
| } else { | |
| result.server_port = 80; | |
| } | |
| if (protocol === 'ss') { | |
| result.network = "tcp"; | |
| } | |
| if (protocol === 'vless') { | |
| result.packet_encoding = "xudp"; | |
| } | |
| if (protocol === 'hy' || protocol === 'hysteria') { | |
| parseHysteria1URI(url, protocol, result); | |
| } else if (protocol === 'hy2' || protocol === 'hysteria2') { | |
| parseHysteria2URI(url, protocol, result); | |
| } else { | |
| parseDuckSoftURI(url, protocol, result); | |
| } | |
| result.tag = decodeURIComponent(url.hash).replace('#', "") || (result.server + ":" + result.server_port); | |
| return result; | |
| } | |
| } | |
| } catch (error) { | |
| } | |
| return null; | |
| } | |
| /** | |
| * @param {{ add: any; port: any; id: any; aid: number; net: string; type: string; scy: string; tls: string; sni: any; alpn: any; path: string; host: string; ps: string; }} jsonObject | |
| */ | |
| function parseV2RayNGJson(jsonObject) { | |
| const result = {}; | |
| result.type = 'vmess'; | |
| result.server = jsonObject.add; | |
| result.server_port = jsonObject.port; | |
| result.uuid = jsonObject.id; | |
| result.alter_id = jsonObject.aid || 0; | |
| // 加密方式 | |
| result.security = jsonObject.scy || "auto"; | |
| if (jsonObject.tls === 'tls' || jsonObject.tls === 'reality') { | |
| let tls = {}; | |
| tls.enabled = true; | |
| tls.server_name = jsonObject.sni || jsonObject.add; | |
| if (jsonObject.alpn) { | |
| tls.alpn = jsonObject.alpn.split(/,|\n/).map((/** @type {string} */ item) => item.trim()).filter((/** @type {string} */ item) => item); | |
| } | |
| tls.insecure = false; | |
| result.tls = tls; | |
| } | |
| let transport = {}; | |
| if (jsonObject.net === 'ws') { | |
| transport.type = 'ws'; | |
| if (!jsonObject.path) { | |
| transport.path = "/"; | |
| } else if (jsonObject.path.includes("?ed=")) { | |
| const path = jsonObject.path.split("?ed="); | |
| transport.path = path[0]; | |
| transport.max_early_data = parseInt(path[1]) || 2048; | |
| transport.early_data_header_name = "Sec-WebSocket-Protocol"; | |
| } else { | |
| transport.path = jsonObject.path; | |
| } | |
| if (jsonObject.host) { | |
| transport.headers = { Host: jsonObject.host }; | |
| } | |
| } else if (jsonObject.net === 'http' || jsonObject.net === 'tcp' && jsonObject.type === 'http') { | |
| transport.type = 'http'; | |
| if (result.security === 'tls') { | |
| transport.method = "GET"; | |
| } | |
| if (jsonObject.host) { | |
| transport.host = jsonObject.host.split(','); | |
| } | |
| transport.path = jsonObject.path || '/'; | |
| } else if (jsonObject.net === 'quic') { | |
| transport.type = 'quic'; | |
| } else if (jsonObject.net === 'grpc') { | |
| transport.type = "grpc"; | |
| transport.service_name = jsonObject.path; | |
| } else if (jsonObject.net === 'httpupgrade') { | |
| transport.type = "httpupgrade"; | |
| transport.host = jsonObject.host; | |
| transport.path = jsonObject.path; | |
| } | |
| if (Object.keys(transport).length !== 0) { | |
| result.transport = transport; | |
| } | |
| result.tag = jsonObject.ps || (result.server + ":" + result.server_port); | |
| // console.log(result); | |
| return result; | |
| } | |
| /** | |
| * @param {URL} url | |
| * @param {string} protocol | |
| * @param {{ server?: string; tag?: string; type?: any; tls?: any; up_mbps?: any; down_mbps?: any; obfs?: any; }} result | |
| */ | |
| function parseHysteria1URI(url, protocol, result) { | |
| // const result = {}; | |
| const params = new URLSearchParams(url.search); | |
| result.type = 'hysteria'; | |
| let tls = {}; | |
| tls.enabled = true; | |
| if (params.get("peer")) { | |
| tls.server_name = params.get("peer"); | |
| } | |
| if (params.get("alpn")) { | |
| tls.alpn = params.get("alpn").split(/,|\n/).map(item => item.trim()).filter(item => item); | |
| } | |
| if (params.get("insecure") || params.get("insecure") === "1") { | |
| tls.insecure = true; | |
| } else { | |
| tls.insecure = false; | |
| } | |
| result.tls = tls; | |
| if (params.get("upmbps")) { | |
| result.up_mbps = parseInt(params.get("upmbps")); | |
| } | |
| if (params.get("downmbps")) { | |
| result.down_mbps = parseInt(params.get("downmbps")); | |
| } | |
| if (params.get("obfsParam")) { | |
| result.obfs = params.get("obfsParam"); | |
| } | |
| if (params.get("obfs-password")) { | |
| result.obfs = params.get("obfsParam"); | |
| } | |
| return result; | |
| } | |
| /** | |
| * @param {URL} url | |
| * @param {string} protocol | |
| * @param {{ server?: string; tag?: string; type?: any; tls?: any; up_mbps?: any; down_mbps?: any; obfs?: any; pinSHA256?: any; }} result | |
| */ | |
| function parseHysteria2URI(url, protocol, result) { | |
| // const result = {}; | |
| const params = new URLSearchParams(url.search); | |
| result.type = 'hysteria2'; | |
| let tls = {}; | |
| tls.enabled = true; | |
| if (params.get("peer")) { | |
| tls.server_name = params.get("peer"); | |
| } | |
| if (params.get("alpn")) { | |
| tls.server_name = params.get("alpn").split(/,|\n/).map(item => item.trim()).filter(item => item); | |
| } | |
| if (params.get("insecure") || params.get("insecure") === "1") { | |
| tls.insecure = true; | |
| } else { | |
| tls.insecure = false; | |
| } | |
| result.tls = tls; | |
| if (params.get("upmbps")) { | |
| result.up_mbps = parseInt(params.get("upmbps")); | |
| } | |
| if (params.get("downmbps")) { | |
| result.down_mbps = parseInt(params.get("downmbps")); | |
| } | |
| if (params.get("obfs")) { | |
| result.obfs = params.get("obfs"); | |
| } | |
| if (params.get("obfs-password")) { | |
| result.obfs = params.get("obfs-password"); | |
| } | |
| if (params.get("pinSHA256")) { | |
| result.pinSHA256 = params.get("pinSHA256"); | |
| } | |
| return result; | |
| } | |
| const allowableShadowsocksMethod = new Set(["2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305", "none", "aes-128-gcm", "aes-192-gcm", "aes-256-gcm", "chacha20-ietf-poly1305", "xchacha20-ietf-poly1305"]); | |
| /** | |
| * @param {URL} url | |
| * @param {string} protocol | |
| * @param {{ server: any; tag?: string; type?: any; method?: any; password?: any; plugin?: any; plugin_opts?: any; uuid?: any; security?: any; tls?: any; network?: any; transport?: any; }} result | |
| */ | |
| function parseDuckSoftURI(url, protocol, result) { | |
| // console.log("反编码 url B: " + url); | |
| // const result = {}; | |
| const params = new URLSearchParams(url.search); | |
| if (protocol === 'ss') { | |
| result.type = 'shadowsocks'; | |
| result.method = decodeURIComponent(url.username); | |
| if (!allowableShadowsocksMethod.has(result.method)) { | |
| throw Error("不支持该加密方式"); | |
| } | |
| result.password = decodeURIComponent(url.password); | |
| let pluginParam = params.get("plugin"); | |
| if (pluginParam) { | |
| const plugin = pluginParam.split(';'); | |
| result.plugin = plugin[0]; | |
| result.plugin_opts = plugin[1]; | |
| } | |
| } else if (protocol === 'vmess') { | |
| result.type = 'vmess'; | |
| result.uuid = decodeURIComponent(url.username); | |
| } else if (protocol === 'vless') { | |
| result.type = 'vless'; | |
| result.uuid = decodeURIComponent(url.username); | |
| } else if (protocol === 'trojan') { | |
| result.type = 'trojan'; | |
| result.password = decodeURIComponent(url.username); | |
| } | |
| let security = ''; | |
| if (params.get("security")) { | |
| security = params.get("security"); | |
| } else if (protocol === 'trojan') { | |
| security = "tls"; | |
| } else { | |
| security = "none"; | |
| } | |
| if (protocol === "vmess") { | |
| result.security = security; | |
| } | |
| if (security === "tls" || security === "reality") { | |
| let tls = {}; | |
| tls.enabled = true; | |
| tls.server_name = params.get("sni") || decodeURIComponent(url.hostname); | |
| if (params.get("alpn")) { | |
| tls.alpn = params.get("alpn").split(/,|\n/).map(item => item.trim()).filter(item => item); | |
| } | |
| if (params.get("allowInsecure") || params.get("allowInsecure") === "1") { | |
| tls.insecure = true; | |
| } else { | |
| tls.insecure = false; | |
| } | |
| result.tls = tls; | |
| } | |
| if (params.get("type") === 'tcp') { | |
| if (params.get("headerType") === "http") { | |
| if (params.get("host")) { | |
| params.set("type", "http"); | |
| } | |
| } | |
| } | |
| let transport = {}; | |
| let network = params.get("type"); | |
| if (network === 'ws') { | |
| transport.type = 'ws'; | |
| if (!params.get("path")) { | |
| transport.path = "/"; | |
| } else { | |
| if (params.get("path") && params.get("path").indexOf("?ed=") !== -1) { | |
| const path = params.get("path").split("?ed="); | |
| transport.path = path[0]; | |
| transport.max_early_data = parseInt(path[1]) || 2048; | |
| transport.early_data_header_name = "Sec-WebSocket-Protocol"; | |
| } else { | |
| transport.path = params.get("path"); | |
| } | |
| } | |
| transport.headers = { Host: result.server }; | |
| if (params.get("ed")) { | |
| const edInt = parseInt(params.get("ed")); | |
| if (edInt > 0) { | |
| transport.max_early_data = edInt; | |
| } | |
| } | |
| if (params.get("eh")) { | |
| transport.early_data_header_name = params.get("eh"); | |
| } | |
| } else if (network === "h2" || network === "http") { | |
| transport.type = 'http'; | |
| if (security === 'tls') { | |
| transport.method = "GET"; | |
| } | |
| if (params.get("host")) { | |
| transport.host = params.get("host").split(","); | |
| } | |
| transport.path = params.get("path") || '/'; | |
| } else if (network === 'quic') { | |
| transport.type = 'quic'; | |
| } else if (network === 'grpc') { | |
| transport.type = 'grpc'; | |
| if (params.get("path")) { | |
| transport.service_name = params.get("path"); | |
| } | |
| } else if (network === 'httpupgrade') { | |
| transport.type = 'httpupgrade'; | |
| if (params.get("host")) { | |
| transport.host = params.get("host"); | |
| } | |
| if (params.get("path")) { | |
| transport.service_name = params.get("path"); | |
| } | |
| } | |
| if (Object.keys(transport).length !== 0) { | |
| result.transport = transport; | |
| } | |
| return result; | |
| } | |
| /** | |
| * @param {string} str | |
| */ | |
| function base64Decode(str) { | |
| // console.log("解密:" + str); | |
| const bytes = new Uint8Array(atob(str).split('').map(c => c.charCodeAt(0))); | |
| const decoder = new TextDecoder('utf-8'); | |
| return decoder.decode(bytes); | |
| } | |
| /** | |
| * @param {string} str | |
| */ | |
| function isBase64(str) { | |
| const base64Regex = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/; | |
| return base64Regex.test(str); | |
| } | |
| /** | |
| * @param {string} str | |
| */ | |
| function isJson(str) { | |
| try { | |
| JSON.parse(str); | |
| return true; | |
| } catch (e) { | |
| return false; | |
| } | |
| } | |
| /** | |
| * @param {RequestInfo<unknown, CfProperties<unknown>>} url | |
| */ | |
| async function fetchRes(url) { | |
| let response = await fetch(url, { | |
| headers: new Headers({ | |
| 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36', | |
| }) | |
| }); | |
| try { | |
| return await response.text(); | |
| } catch (e) { | |
| console.log("报错:" + e); | |
| return ''; | |
| } | |
| } | |
| /** | |
| * @param {any[]} nodes | |
| */ | |
| function deduplication(nodes) { | |
| const grouped = nodes.reduce((acc, obj) => { | |
| const key = obj.tag; | |
| if (!acc[key]) { | |
| acc[key] = []; | |
| } | |
| acc[key].push(obj); | |
| return acc; | |
| }, {}); | |
| // 在每个组内进行比较 | |
| for (const tag in grouped) { | |
| const group = grouped[tag]; | |
| for (let i = 0; i < group.length; i++) { | |
| const obj1 = group[i]; | |
| for (let j = i + 1; j < group.length; j++) { | |
| const obj2 = group[j]; | |
| if (obj1.server !== obj2.server || obj1.server_port !== obj2.server_port) { | |
| obj1.tag = (obj1.server + ":" + obj1.server_port + "#" + Date.now()); | |
| }else if(obj1.server === obj2.server && obj1.server_port === obj2.server_port){ | |
| group[i] = null; | |
| } | |
| } | |
| } | |
| } | |
| // 将分组后的数据拆分成原来的 list 列表 | |
| return Object.values(grouped).flat().filter(config => config !== null); | |
| } | |
| /** | |
| * @param {any[]} nodes | |
| */ | |
| function echo(nodes) { | |
| const names = nodes.map(obj => obj.tag); | |
| const pre = ["fastly", "direct"]; | |
| // console.log(names); | |
| const config = { | |
| "log": { | |
| "level": "debug", | |
| "timestamp": true | |
| }, | |
| "dns": { | |
| "servers": [ | |
| { | |
| "tag": "proxyDns", | |
| "address": "https://8.8.8.8/dns-query", | |
| "detour": "proxy" | |
| }, | |
| { | |
| "tag": "localDns", | |
| "address": "https://223.5.5.5/dns-query", | |
| "detour": "direct" | |
| }, | |
| { | |
| "tag": "block", | |
| "address": "rcode://success" | |
| } | |
| ], | |
| "rules": [ | |
| { | |
| "domain": [ | |
| "icloudnative.io", | |
| "fuckcloudnative.io", | |
| "sealos.io", | |
| "ghproxy.com", | |
| "cdn.jsdelivr.net", | |
| "testingcf.jsdelivr.net" | |
| ], | |
| "server": "localDns" | |
| }, | |
| { | |
| "domain_suffix": [ | |
| "wonderfulday27.live", | |
| "sis001.com", | |
| "dqzboy.com" | |
| ], | |
| "server": "proxyDns" | |
| }, | |
| { | |
| "process_name": [ | |
| "TencentMeeting", | |
| "NemoDesktop", | |
| "ToDesk", | |
| "ToDesk_Service", | |
| "WeChat", | |
| "Tailscale", | |
| "wireguard-go", | |
| "Tunnelblick", | |
| "softwareupdated", | |
| "kubectl" | |
| ], | |
| "server": "localDns" | |
| }, | |
| { | |
| "process_name": [ | |
| "DropboxMacUpdate", | |
| "Dropbox" | |
| ], | |
| "server": "proxyDns" | |
| }, | |
| { | |
| "package_name": [ | |
| "com.google.android.youtube", | |
| "com.android.vending", | |
| "org.telegram.messenger", | |
| "org.telegram.plus" | |
| ], | |
| "server": "proxyDns" | |
| }, | |
| { | |
| "rule_set": "geosite-category-ads-all", | |
| "server": "block" | |
| }, | |
| { | |
| "outbound": "any", | |
| "server": "localDns", | |
| "disable_cache": true | |
| }, | |
| { | |
| "rule_set": "geosite-cn", | |
| "server": "localDns" | |
| }, | |
| { | |
| "clash_mode": "direct", | |
| "server": "localDns" | |
| }, | |
| { | |
| "clash_mode": "global", | |
| "server": "proxyDns" | |
| }, | |
| { | |
| "rule_set": "geosite-geolocation-!cn", | |
| "server": "proxyDns" | |
| } | |
| ], | |
| "final": "localDns", | |
| "strategy": "ipv4_only" | |
| }, | |
| "inbounds": [ | |
| { | |
| "type": "tun", | |
| "mtu": 9000, | |
| "inet4_address": "172.19.0.1/30", | |
| "auto_route": true, | |
| "strict_route": true, | |
| "inet4_route_address": [ | |
| "0.0.0.0/1", | |
| "128.0.0.0/1" | |
| ], | |
| "inet4_route_exclude_address": [ | |
| "192.168.0.0/16", | |
| "10.0.0.0/8", | |
| "172.16.0.0/12", | |
| "100.64.0.0/10" | |
| ], | |
| "exclude_package": [ | |
| "cmb.pb", | |
| "cn.gov.pbc.dcep", | |
| "com.MobileTicket", | |
| "com.adguard.android", | |
| "com.ainemo.dragoon", | |
| "com.alibaba.android.rimet", | |
| "com.alicloud.databox", | |
| "com.amazing.cloudisk.tv", | |
| "com.autonavi.minimap", | |
| "com.bilibili.app.in", | |
| "com.bishua666.luxxx1", | |
| "com.cainiao.wireless", | |
| "com.chebada", | |
| "com.chinamworld.main", | |
| "com.cmbchina.ccd.pluto.cmbActivity", | |
| "com.coolapk.market", | |
| "com.ctrip.ct", | |
| "com.dianping.v1", | |
| "com.douban.frodo", | |
| "com.eg.android.AlipayGphone", | |
| "com.farplace.qingzhuo", | |
| "com.hanweb.android.zhejiang.activity", | |
| "com.leoao.fitness", | |
| "com.lucinhu.bili_you", | |
| "com.mikrotik.android.tikapp", | |
| "com.moji.mjweather", | |
| "com.motorola.cn.calendar", | |
| "com.motorola.cn.lrhealth", | |
| "com.netease.cloudmusic", | |
| "com.sankuai.meituan", | |
| "com.sina.weibo", | |
| "com.smartisan.notes", | |
| "com.sohu.inputmethod.sogou.moto", | |
| "com.sonelli.juicessh", | |
| "com.ss.android.article.news", | |
| "com.ss.android.lark", | |
| "com.ss.android.ugc.aweme", | |
| "com.tailscale.ipn", | |
| "com.taobao.idlefish", | |
| "com.taobao.taobao", | |
| "com.tencent.mm", | |
| "com.tencent.mp", | |
| "com.tencent.soter.soterserver", | |
| "com.tencent.wemeet.app", | |
| "com.tencent.weread", | |
| "com.tencent.wework", | |
| "com.ttxapps.wifiadb", | |
| "com.unionpay", | |
| "com.unnoo.quan", | |
| "com.wireguard.android", | |
| "com.xingin.xhs", | |
| "com.xunmeng.pinduoduo", | |
| "com.zui.zhealthy", | |
| "com.zhihu.android", | |
| "ctrip.android.view", | |
| "io.kubenav.kubenav", | |
| "org.geekbang.geekTime", | |
| "tv.danmaku.bili" | |
| ], | |
| "udp_timeout": "5m0s", | |
| "stack": "mixed", | |
| "platform": { | |
| "http_proxy": { | |
| "enabled": true, | |
| "server": "127.0.0.1", | |
| "server_port": 2080 | |
| } | |
| }, | |
| "sniff": true | |
| }, | |
| { | |
| "type": "mixed", | |
| "listen": "127.0.0.1", | |
| "listen_port": 2080, | |
| "sniff": true | |
| } | |
| ], | |
| "outbounds": [ | |
| { | |
| "type": "selector", | |
| "tag": "proxy", | |
| "outbounds": pre.concat(names) | |
| }, | |
| { | |
| "type": "urltest", | |
| "tag": "fastly", | |
| "outbounds": names, | |
| "url": "http://www.gstatic.com/generate_204", | |
| "interval": "10m0s", | |
| "tolerance": 50 | |
| }, | |
| { | |
| "type": "selector", | |
| "tag": "🤖 OpenAI", | |
| "outbounds": [ | |
| "🇹🇼 台湾节点", | |
| "🇸🇬 狮城节点", | |
| "🇯🇵 日本节点", | |
| "🇺🇸 美国节点", | |
| "✈️ 其他节点" | |
| ] | |
| }, | |
| { | |
| "type": "selector", | |
| "tag": "🌌 Google", | |
| "outbounds": [ | |
| "🇭🇰 香港节点", | |
| "🇹🇼 台湾节点", | |
| "🇸🇬 狮城节点", | |
| "🇯🇵 日本节点", | |
| "🇺🇸 美国节点", | |
| "✈️ 其他节点" | |
| ] | |
| }, | |
| { | |
| "type": "selector", | |
| "tag": "📟 Telegram", | |
| "outbounds": [ | |
| "🇭🇰 香港节点", | |
| "🇹🇼 台湾节点", | |
| "🇸🇬 狮城节点", | |
| "🇯🇵 日本节点", | |
| "🇺🇸 美国节点", | |
| "✈️ 其他节点" | |
| ] | |
| }, | |
| { | |
| "type": "selector", | |
| "tag": "🐦 Twitter", | |
| "outbounds": [ | |
| "🇭🇰 香港节点", | |
| "🇹🇼 台湾节点", | |
| "🇸🇬 狮城节点", | |
| "🇯🇵 日本节点", | |
| "🇺🇸 美国节点", | |
| "✈️ 其他节点" | |
| ] | |
| }, | |
| { | |
| "type": "selector", | |
| "tag": "👤 Facebook", | |
| "outbounds": [ | |
| "🇭🇰 香港节点", | |
| "🇹🇼 台湾节点", | |
| "🇸🇬 狮城节点", | |
| "🇯🇵 日本节点", | |
| "🇺🇸 美国节点", | |
| "✈️ 其他节点" | |
| ] | |
| }, | |
| { | |
| "type": "selector", | |
| "tag": "🛍️ Amazon", | |
| "outbounds": [ | |
| "direct", | |
| "🇭🇰 香港节点", | |
| "🇹🇼 台湾节点", | |
| "🇸🇬 狮城节点", | |
| "🇯🇵 日本节点", | |
| "🇺🇸 美国节点", | |
| "✈️ 其他节点" | |
| ] | |
| }, | |
| { | |
| "type": "selector", | |
| "tag": "🍎 Apple", | |
| "outbounds": [ | |
| "direct", | |
| "🇭🇰 香港节点", | |
| "🇹🇼 台湾节点", | |
| "🇸🇬 狮城节点", | |
| "🇯🇵 日本节点", | |
| "🇺🇸 美国节点", | |
| "✈️ 其他节点" | |
| ] | |
| }, | |
| { | |
| "type": "selector", | |
| "tag": "🧩 Microsoft", | |
| "outbounds": [ | |
| "direct", | |
| "🇭🇰 香港节点", | |
| "🇹🇼 台湾节点", | |
| "🇸🇬 狮城节点", | |
| "🇯🇵 日本节点", | |
| "🇺🇸 美国节点", | |
| "✈️ 其他节点" | |
| ] | |
| }, | |
| { | |
| "type": "selector", | |
| "tag": "🎮 Game", | |
| "outbounds": [ | |
| "direct", | |
| "🇭🇰 香港节点", | |
| "🇹🇼 台湾节点", | |
| "🇸🇬 狮城节点", | |
| "🇯🇵 日本节点", | |
| "🇺🇸 美国节点", | |
| "✈️ 其他节点" | |
| ] | |
| }, | |
| { | |
| "type": "selector", | |
| "tag": "📺 Bilibili", | |
| "outbounds": [ | |
| "direct", | |
| "🇭🇰 香港节点", | |
| "🇹🇼 台湾节点" | |
| ] | |
| }, | |
| { | |
| "type": "selector", | |
| "tag": "🎬 MediaVideo", | |
| "outbounds": [ | |
| "🇭🇰 香港节点", | |
| "🇹🇼 台湾节点", | |
| "🇸🇬 狮城节点", | |
| "🇯🇵 日本节点", | |
| "🇺🇸 美国节点", | |
| "✈️ 其他节点" | |
| ] | |
| }, | |
| { | |
| "type": "selector", | |
| "tag": "🌏 !cn", | |
| "outbounds": [ | |
| "🇭🇰 香港节点", | |
| "🇹🇼 台湾节点", | |
| "🇸🇬 狮城节点", | |
| "🇯🇵 日本节点", | |
| "🇺🇸 美国节点", | |
| "✈️ 其他节点" | |
| ] | |
| }, | |
| { | |
| "type": "selector", | |
| "tag": "🇭🇰 香港节点", | |
| "outbounds": [ | |
| "proxy" | |
| ] | |
| }, | |
| { | |
| "type": "selector", | |
| "tag": "🇹🇼 台湾节点", | |
| "outbounds": [ | |
| "proxy" | |
| ] | |
| }, | |
| { | |
| "type": "selector", | |
| "tag": "🇸🇬 狮城节点", | |
| "outbounds": [ | |
| "proxy" | |
| ] | |
| }, | |
| { | |
| "type": "selector", | |
| "tag": "🇯🇵 日本节点", | |
| "outbounds": [ | |
| "proxy" | |
| ] | |
| }, | |
| { | |
| "type": "selector", | |
| "tag": "🇺🇸 美国节点", | |
| "outbounds": [ | |
| "proxy" | |
| ] | |
| }, | |
| { | |
| "type": "selector", | |
| "tag": "✈️ 其他节点", | |
| "outbounds": [ | |
| "proxy" | |
| ] | |
| }, | |
| { | |
| "type": "selector", | |
| "tag": "🌏 cn", | |
| "outbounds": [ | |
| "direct", | |
| "proxy" | |
| ] | |
| }, | |
| { | |
| "type": "selector", | |
| "tag": "🛑 AdBlock", | |
| "outbounds": [ | |
| "block", | |
| "direct" | |
| ] | |
| }, | |
| { | |
| "type": "direct", | |
| "tag": "direct" | |
| }, | |
| { | |
| "type": "dns", | |
| "tag": "dns-out" | |
| }, | |
| { | |
| "type": "block", | |
| "tag": "block" | |
| }, | |
| ], | |
| "route": { | |
| "rules": [ | |
| { | |
| "protocol": "dns", | |
| "outbound": "dns-out" | |
| }, | |
| { | |
| "network": "udp", | |
| "port": 443, | |
| "outbound": "block" | |
| }, | |
| { | |
| "rule_set": "geosite-category-ads-all", | |
| "outbound": "🛑 AdBlock" | |
| }, | |
| { | |
| "clash_mode": "direct", | |
| "outbound": "direct" | |
| }, | |
| { | |
| "clash_mode": "global", | |
| "outbound": "proxy" | |
| }, | |
| { | |
| "domain_suffix": [ | |
| "appcenter.ms", | |
| "app-measurement.com", | |
| "firebase.io", | |
| "crashlytics.com", | |
| "google-analytics.com" | |
| ], | |
| "outbound": "block" | |
| }, | |
| { | |
| "rule_set": "geosite-openai", | |
| "outbound": "🤖 OpenAI" | |
| }, | |
| { | |
| "rule_set": [ | |
| "geosite-youtube", | |
| "geoip-google", | |
| "geosite-google", | |
| "geosite-github" | |
| ], | |
| "outbound": "🌌 Google" | |
| }, | |
| { | |
| "rule_set": "geoip-telegram", | |
| "outbound": "📟 Telegram" | |
| }, | |
| { | |
| "rule_set": "geosite-telegram", | |
| "outbound": "📟 Telegram" | |
| }, | |
| { | |
| "rule_set": "geoip-twitter", | |
| "outbound": "🐦 Twitter" | |
| }, | |
| { | |
| "rule_set": "geosite-twitter", | |
| "outbound": "🐦 Twitter" | |
| }, | |
| { | |
| "rule_set": "geoip-facebook", | |
| "outbound": "👤 Facebook" | |
| }, | |
| { | |
| "rule_set": [ | |
| "geosite-facebook", | |
| "geosite-instagram" | |
| ], | |
| "outbound": "👤 Facebook" | |
| }, | |
| { | |
| "rule_set": "geosite-amazon", | |
| "outbound": "🛍️ Amazon" | |
| }, | |
| { | |
| "rule_set": "geosite-apple", | |
| "outbound": "🍎 Apple" | |
| }, | |
| { | |
| "rule_set": "geosite-microsoft", | |
| "outbound": "🧩 Microsoft" | |
| }, | |
| { | |
| "rule_set": "geosite-category-games", | |
| "outbound": "🎮 Game" | |
| }, | |
| { | |
| "rule_set": "geosite-bilibili", | |
| "outbound": "📺 Bilibili" | |
| }, | |
| { | |
| "rule_set": "geosite-wechat", | |
| "outbound": "🌏 cn" | |
| }, | |
| { | |
| "rule_set": "geoip-netflix", | |
| "outbound": "🎬 MediaVideo" | |
| }, | |
| { | |
| "rule_set": [ | |
| "geosite-tiktok", | |
| "geosite-netflix", | |
| "geosite-hbo", | |
| "geosite-disney", | |
| "geosite-primevideo" | |
| ], | |
| "outbound": "🎬 MediaVideo" | |
| }, | |
| { | |
| "rule_set": "geosite-geolocation-!cn", | |
| "outbound": "🌏 !cn" | |
| }, | |
| { | |
| "rule_set": "gfw", | |
| "outbound": "🌏 !cn" | |
| }, | |
| { | |
| "ip_is_private": true, | |
| "outbound": "🌏 cn" | |
| }, | |
| { | |
| "rule_set": "geoip-cn", | |
| "outbound": "🌏 cn" | |
| }, | |
| { | |
| "rule_set": "geosite-cn", | |
| "outbound": "🌏 cn" | |
| } | |
| ], | |
| "rule_set": [ | |
| { | |
| "type": "remote", | |
| "tag": "geoip-google", | |
| "format": "binary", | |
| "url": "https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/sing/geo/geoip/google.srs", | |
| "download_detour": "fastly" | |
| }, | |
| { | |
| "type": "remote", | |
| "tag": "geoip-telegram", | |
| "format": "binary", | |
| "url": "https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/sing/geo/geoip/telegram.srs", | |
| "download_detour": "fastly" | |
| }, | |
| { | |
| "type": "remote", | |
| "tag": "geoip-twitter", | |
| "format": "binary", | |
| "url": "https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/sing/geo/geoip/twitter.srs", | |
| "download_detour": "fastly" | |
| }, | |
| { | |
| "type": "remote", | |
| "tag": "geoip-facebook", | |
| "format": "binary", | |
| "url": "https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/sing/geo/geoip/facebook.srs", | |
| "download_detour": "fastly" | |
| }, | |
| { | |
| "type": "remote", | |
| "tag": "geoip-netflix", | |
| "format": "binary", | |
| "url": "https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/sing/geo/geoip/netflix.srs", | |
| "download_detour": "fastly" | |
| }, | |
| { | |
| "type": "remote", | |
| "tag": "geoip-cn", | |
| "format": "binary", | |
| "url": "https://raw.githubusercontent.com/SagerNet/sing-geoip/rule-set/geoip-cn.srs", | |
| "download_detour": "fastly" | |
| }, | |
| { | |
| "type": "remote", | |
| "tag": "geosite-openai", | |
| "format": "binary", | |
| "url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-openai.srs", | |
| "download_detour": "fastly" | |
| }, | |
| { | |
| "type": "remote", | |
| "tag": "geosite-youtube", | |
| "format": "binary", | |
| "url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-youtube.srs", | |
| "download_detour": "fastly" | |
| }, | |
| { | |
| "type": "remote", | |
| "tag": "geosite-google", | |
| "format": "binary", | |
| "url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-google.srs", | |
| "download_detour": "fastly" | |
| }, | |
| { | |
| "type": "remote", | |
| "tag": "geosite-github", | |
| "format": "binary", | |
| "url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-github.srs", | |
| "download_detour": "fastly" | |
| }, | |
| { | |
| "type": "remote", | |
| "tag": "geosite-telegram", | |
| "format": "binary", | |
| "url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-telegram.srs", | |
| "download_detour": "fastly" | |
| }, | |
| { | |
| "type": "remote", | |
| "tag": "geosite-twitter", | |
| "format": "binary", | |
| "url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-twitter.srs", | |
| "download_detour": "fastly" | |
| }, | |
| { | |
| "type": "remote", | |
| "tag": "geosite-facebook", | |
| "format": "binary", | |
| "url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-facebook.srs", | |
| "download_detour": "fastly" | |
| }, | |
| { | |
| "type": "remote", | |
| "tag": "geosite-instagram", | |
| "format": "binary", | |
| "url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-instagram.srs", | |
| "download_detour": "fastly" | |
| }, | |
| { | |
| "type": "remote", | |
| "tag": "geosite-amazon", | |
| "format": "binary", | |
| "url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-amazon.srs", | |
| "download_detour": "fastly" | |
| }, | |
| { | |
| "type": "remote", | |
| "tag": "geosite-apple", | |
| "format": "binary", | |
| "url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-apple.srs", | |
| "download_detour": "fastly" | |
| }, | |
| { | |
| "type": "remote", | |
| "tag": "geosite-microsoft", | |
| "format": "binary", | |
| "url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-microsoft.srs", | |
| "download_detour": "fastly" | |
| }, | |
| { | |
| "type": "remote", | |
| "tag": "geosite-category-games", | |
| "format": "binary", | |
| "url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-category-games.srs", | |
| "download_detour": "fastly" | |
| }, | |
| { | |
| "type": "remote", | |
| "tag": "geosite-bilibili", | |
| "format": "binary", | |
| "url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-bilibili.srs", | |
| "download_detour": "fastly" | |
| }, | |
| { | |
| "type": "remote", | |
| "tag": "geosite-wechat", | |
| "format": "source", | |
| "url": "https://raw.githubusercontent.com/yangchuansheng/sing-box-geosite/main/rule/WeChat.json", | |
| "download_detour": "fastly" | |
| }, | |
| { | |
| "type": "remote", | |
| "tag": "geosite-tiktok", | |
| "format": "binary", | |
| "url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-tiktok.srs", | |
| "download_detour": "fastly" | |
| }, | |
| { | |
| "type": "remote", | |
| "tag": "geosite-netflix", | |
| "format": "binary", | |
| "url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-netflix.srs", | |
| "download_detour": "fastly" | |
| }, | |
| { | |
| "type": "remote", | |
| "tag": "geosite-hbo", | |
| "format": "binary", | |
| "url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-hbo.srs", | |
| "download_detour": "fastly" | |
| }, | |
| { | |
| "type": "remote", | |
| "tag": "geosite-disney", | |
| "format": "binary", | |
| "url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-disney.srs", | |
| "download_detour": "fastly" | |
| }, | |
| { | |
| "type": "remote", | |
| "tag": "geosite-primevideo", | |
| "format": "binary", | |
| "url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-primevideo.srs", | |
| "download_detour": "fastly" | |
| }, | |
| { | |
| "type": "remote", | |
| "tag": "geosite-cn", | |
| "format": "binary", | |
| "url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-cn.srs", | |
| "download_detour": "fastly" | |
| }, | |
| { | |
| "type": "remote", | |
| "tag": "gfw", | |
| "format": "source", | |
| "url": "https://raw.githubusercontent.com/Toperlock/sing-box-geosite/main/rule/gfw.json", | |
| "download_detour": "fastly" | |
| }, | |
| { | |
| "type": "remote", | |
| "tag": "geosite-geolocation-!cn", | |
| "format": "binary", | |
| "url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-geolocation-!cn.srs", | |
| "download_detour": "fastly" | |
| }, | |
| { | |
| "type": "remote", | |
| "tag": "geosite-category-ads-all", | |
| "format": "binary", | |
| "url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-category-ads-all.srs", | |
| "download_detour": "fastly" | |
| } | |
| ], | |
| "final": "proxy", | |
| "auto_detect_interface": true | |
| }, | |
| "experimental": { | |
| "cache_file": { | |
| "enabled": true | |
| }, | |
| "clash_api": { | |
| "external_controller": "127.0.0.1:9090", | |
| "external_ui": "ui", | |
| "default_mode": "rule" | |
| } | |
| } | |
| }; | |
| config.outbounds.push(...nodes); | |
| return config; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment