一直用一个很小众的 VPN 服务,从 2014 年用到现在,这也快 10 年了。vpnso 提供最原始的 https 的方式代理,也没有各种流行的订阅服务,但是好在稳定,不跑路。

今天做个 sing-box 的模版,自动按照国家分组,测速,自动选择,指定 GPT 服务。因为提供的 JP 服务器最多,所以默认就节点使用 jp-自动选择。最后效果

  • 自动按照 group_keys = ['sg', 'hk', 'jp', 'us','de','uk'] 分组
  • 规则比较简单,cnsite、cnip 会走直连,其它全部走手工选择的节点。
  • 默认使用 JP 延迟最小的节点
  • GPT 使用额外的 2 台服务
  • 可以按照地区随时切换

vpnso-templage.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
{
"log": {
"disabled": false,
"level": "warn",
"output": "sing-box.log",
"timestamp": true
},
"dns": {
"servers": [
{
"tag": "default-dns",
"address": "223.5.5.5",
"detour": "direct-out"
},
{
"tag": "system-dns",
"address": "local",
"detour": "direct-out"
},
{
"tag": "block-dns",
"address": "rcode://name_error"
},
{
"tag": "google",
"address": "https://dns.google/dns-query",
"address_resolver": "default-dns",
"address_strategy": "ipv4_only",
"strategy": "ipv4_only",
"client_subnet": "1.0.1.0"
}
],
"rules": [
{
"outbound": "any",
"server": "default-dns"
},
{
"query_type": "HTTPS",
"server": "block-dns"
},
{
"clash_mode": "direct",
"server": "default-dns"
},
{
"clash_mode": "global",
"server": "google"
},
{
"rule_set": "cnsite",
"server": "default-dns"
}
],
"strategy": "ipv4_only",
"disable_cache": false,
"disable_expire": false,
"independent_cache": false,
"final": "google"
},
"inbounds": [
{
"type": "tun",
"tag": "tun-in",
"interface_name": "utun0",
"inet4_address": "172.19.0.1/30",
"inet6_address": "fdfe:dcba:9876::1/126",
"mtu": 9000,
"auto_route": true,
"strict_route": true,
"sniff": true,
"stack": "system",
"platform": {
"http_proxy": {
"enabled": true,
"server": "127.0.0.1",
"server_port": 2080
}
}
},
{
"type": "mixed",
"tag": "mixed-in",
"listen": "127.0.0.1",
"listen_port": 2080,
"sniff": true,
"users": []
}
],
"outbounds": [
{
"type": "direct",
"tag": "direct-out",
"routing_mark": 100
},
{
"type": "block",
"tag": "block-out"
},
{
"type": "dns",
"tag": "dns-out"
},
{
"tag": "chatgpt1",
"type": "http",
"server": "xxxxxxxxxxxxxxxxxx",
"server_port": 443,
"username": "xxxxxxxxxxxxxxxxxx",
"password": "xxxxxxxxxxxxxxxxxx",
"path": "",
"headers": {},
"tls": {
"enabled": true
}
},
{
"tag": "chatgpt2",
"type": "http",
"server": "xxxxxxxxxxxxxxxxxx",
"server_port": 443,
"username": "xxxxxxxxxxxxxxxxxx",
"password": "xxxxxxxxxxxxxxxxxx",
"path": "",
"headers": {},
"tls": {
"enabled": true
}
},
{
"type": "urltest",
"tag": "gpt-自动选择",
"outbounds": [
"chatgpt1",
"chatgpt2"
]
}
],
"route": {
"rules": [
{
"inbound": "dns-in",
"outbound": "dns-out"
},
{
"protocol": "dns",
"outbound": "dns-out"
},
{
"protocol": "quic",
"outbound": "block-out"
},
{
"clash_mode": "direct",
"outbound": "direct-out"
},
{
"clash_mode": "global",
"outbound": "GLOBAL"
},
{
"rule_set": [
"cnip",
"cnsite"
],
"outbound": "direct-out"
},
{
"rule_set": [
"openai"
],
"outbound": "gpt-自动选择"
}
],
"rule_set": [
{
"type": "remote",
"tag": "cnip",
"format": "binary",
"url": "https://github.com/MetaCubeX/meta-rules-dat/raw/sing/geo-lite/geoip/cn.srs",
"download_detour": "jp-自动选择"
},
{
"type": "remote",
"tag": "openai",
"format": "binary",
"url": "https://github.com/MetaCubeX/meta-rules-dat/raw/sing/geo-lite/geosite/openai.srs",
"download_detour": "jp-自动选择"
},
{
"type": "remote",
"tag": "cnsite",
"format": "binary",
"url": "https://github.com/MetaCubeX/meta-rules-dat/raw/sing/geo-lite/geosite/cn.srs",
"download_detour": "jp-自动选择"
}
],
"auto_detect_interface": true,
"final": "手动选择"
},
"experimental": {
"cache_file": {
"enabled": true,
},
"clash_api": {
"external_controller": "127.0.0.1:9090",
"external_ui": "ui",
"external_ui_download_detour": "jp-自动选择",
"default_mode": "rule"
}
}
}

处理脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82

import requests
import re
import json


url = "https://xxxxxxxxxxxxxxxxxx" # 订阅地址
data = requests.get(url).text


with open('vpnso-template-openwrt.json','r') as f:
vpnso = json.loads(f.read())


# 找到 [Proxy] 部分的索引
match = re.search(r'\[Proxy\](.*?)\n\n', data, re.DOTALL)
proxy_lines = match.group(1).strip().split('\n')

# 初始化一个空列表存储结果
service_list = []

# 处理每行数据
for line in proxy_lines:
# 拆分每行数据
parts = line.split(',')
# 创建字典
proxy_dict = {
'tag': parts[0].split('=')[0],
'type': 'http',
'server': parts[1],
'server_port': int(parts[2]),
'username': parts[3],
'password': parts[4],
"path": "",
"headers": {},
"tls": {
"enabled": True
}
}
# 添加到结果列表
service_list.append(proxy_dict)

# 分组关键字列表
group_keys = ['sg', 'hk', 'jp', 'us','de','uk']

# 初始化分组字典
groups = {key: [] for key in group_keys}

# 遍历数据并分组
for entry in service_list:
for key in group_keys:
if key in entry.get('tag', ''):
groups[key].append(entry['tag'])
break

# 构建目标数据结构
service_auto_list = [
{"type": "urltest", "tag": f"{key}-自动选择", "outbounds": groups[key]}
for key in group_keys if groups[key]
]

# 全自动选择
auto_select = {"type": "urltest","tag": "自动选择","outbounds": []}
auto_select['outbounds'] = [f"{key}-自动选择" for key in group_keys[:4]]

# 手动选择
manu_select = {"type":"selector","tag":"手动选择","outbounds":["direct-out","block-out","自动选择"],"default":"自动选择"}
manu_select['outbounds']+=[f"{key}-自动选择" for key in group_keys]

# GLOBAL
global_select = {"type":"selector","tag":"GLOBAL","outbounds":["direct-out","手动选择"],"default":"手动选择"}
global_select['outbounds']+=[f"{key}-自动选择" for key in group_keys]

# 拼接一下
outbounds = service_list+service_auto_list
outbounds += [auto_select,manu_select,global_select]

# 写入配置
vpnso['outbounds'] += outbounds
data_json = json.dumps(vpnso, ensure_ascii=False, indent=4)
with open("config.json", 'w', encoding='utf-8') as f:
f.write(data_json)