Skip to content

Commit 562a58b

Browse files
author
jacob.zb
committed
first push
0 parents  commit 562a58b

File tree

4 files changed

+289
-0
lines changed

4 files changed

+289
-0
lines changed

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/venv
2+
/.idea
3+
/.vscode
4+
.DS_Store
5+
config.yaml
6+
template.yaml
7+
config.json

README.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# 项目描述
2+
本项目用于获取 CordCloud SSR 订阅链接然后转为 SS 链接,写入到指定的文件中,同时将文件上传到指定的腾讯 COS 地址。
3+
4+
# 解决的问题
5+
1. 有些时候你的订阅被墙了,或者订阅本身不稳定挂了
6+
2. 你的 Mac 在使用 ClashX 而你的机场并不提供 SS 以外的订阅类型
7+
8+
# 配置文件
9+
config.json
10+
11+
## 举例
12+
```json
13+
{
14+
"rule_url": "https://raw.githubusercontent.com/Hackl0us/SS-Rule-Snippet/master/LAZY_RULES/clash.yaml",
15+
"url": "https://www.cordcord.xyz/link/f6NPtV0Kr3woDPTm?mu=0",
16+
"append_name": [
17+
"vmessBDW"
18+
],
19+
"china_city_list": [
20+
"上海",
21+
"徐州",
22+
"深圳",
23+
"北京"
24+
],
25+
"input_name": "template.yaml",
26+
"output_name": "config.yaml",
27+
"upload_key": "CordCloud/MuGpOzidudPurlyNEW.yaml",
28+
"cos_bucket": "xxxx",
29+
"cos_secret_id": "xxx",
30+
"cos_secret_key": "xxxx",
31+
"cos_region": "ap-xxx"
32+
}
33+
```
34+
35+
## 含义
36+
37+
| 配置名 | 含义 | 备注 |
38+
| --------------- | ------------------------------------------------------------ | ---- |
39+
| rule_url | 分流规则订阅地址 | |
40+
| url | SS 订阅地址(或者是 plain 的SSR) | |
41+
| append_name | 如果你在模版文件中自定义了节点,可以在这里加进去 | |
42+
| china_city_list | 中国的节点,有些订阅可能会有回国节点,过滤掉回国节点防止自动测速选择国内节点 | |
43+
| input_name | 模版文件在本地存储的文件名 | |
44+
| output_name | 转换后的本地文件名 | |
45+
| upload_key | 上传到 COS,在 COS 中 的文件名 | |
46+
| cos_bucket | COS bucket 桶名字 | |
47+
| cos_secret_id | COS 密钥信息 | |
48+
| cos_secret_key | COS 密钥信息 | |
49+
| cos_region | COS 地区信息 | |
50+
51+
# 推荐用法
52+
在自己的 VPS 上利用定时任务触发该脚本。通过 COS 更新本地的订阅

requirements.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
autopep8==1.5
2+
certifi==2019.11.28
3+
chardet==3.0.4
4+
cos-python-sdk-v5==1.7.7
5+
dicttoxml==1.7.4
6+
idna==2.8
7+
pycodestyle==2.5.0
8+
requests==2.22.0
9+
six==1.14.0
10+
urllib3==1.25.8

update_clash_config.py

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
import base64
2+
import urllib.request
3+
import os
4+
import codecs
5+
import json
6+
from qcloud_cos import CosConfig
7+
from qcloud_cos import CosS3Client
8+
import sys
9+
import logging
10+
11+
logging.basicConfig(level=logging.INFO, stream=sys.stdout)
12+
13+
14+
def parse(ssr_rul):
15+
ssr = ssr_rul.strip()
16+
17+
if ssr.startswith('ssr://'):
18+
base64_encode_str = ssr[6:]
19+
return parse_ssr(base64_encode_str)
20+
return "", ""
21+
22+
23+
def parse_ssr(base64_encode_str):
24+
decode_str = base64_decode(base64_encode_str)
25+
parts = decode_str.split(':')
26+
if len(parts) != 6:
27+
print('不能解析SSR链接: %s' % base64_encode_str)
28+
return ""
29+
30+
server = parts[0]
31+
port = parts[1]
32+
protocol = parts[2]
33+
method = parts[3]
34+
obfs = parts[4]
35+
password_and_params = parts[5]
36+
37+
password_and_params = password_and_params.split("/?")
38+
39+
password_encode_str = password_and_params[0]
40+
password = base64_decode(password_encode_str)
41+
params = password_and_params[1]
42+
43+
param_parts = params.split('&')
44+
param_dic = {}
45+
for part in param_parts:
46+
key_and_value = part.split('=')
47+
param_dic[key_and_value[0]] = key_and_value[1]
48+
49+
obfsparam = base64_decode(param_dic['obfsparam'])
50+
protoparam = base64_decode(param_dic['obfsparam'])
51+
remarks = base64_decode(param_dic['remarks'])
52+
group = base64_decode(param_dic['group'])
53+
# print('server: %s, port: %s, 协议: %s, 加密方法: %s, 密码: %s, 混淆: %s, 混淆参数: %s, 协议参数: %s, 备注: %s, 分组: %s'
54+
# % (server, port, protocol, method, password, obfs, obfsparam, protoparam, remarks, group))
55+
if remarks[:2] in config['china_city_list']:
56+
remarks = '[国内]' + remarks
57+
yml = '''- {{ name: "{0}", type: ss, server: {1}, port: {2}, cipher: {3}, password: "{4}" }}'''
58+
return yml.format(remarks, server, port, method, password), remarks
59+
60+
61+
def fill_padding(base64_encode_str):
62+
need_padding = len(base64_encode_str) % 4 != 0
63+
64+
if need_padding:
65+
missing_padding = 4 - need_padding
66+
base64_encode_str += '=' * missing_padding
67+
return base64_encode_str
68+
69+
70+
def base64_decode(base64_encode_str):
71+
base64_encode_str = fill_padding(base64_encode_str)
72+
return base64.urlsafe_b64decode(base64_encode_str).decode('utf-8')
73+
74+
75+
def base64_2_str(b64_string):
76+
b64_string += "=" * ((4 - len(b64_string) % 4) % 4)
77+
return str(base64.b64decode(b64_string), encoding="utf-8")
78+
79+
80+
def update(intput_name, output_name, name_list, yaml_list):
81+
switcher = False
82+
rule_switcher = False
83+
custom_rule_switcher = False
84+
is_first_line = False
85+
with codecs.open(intput_name, 'r', encoding='utf-8') as fi, \
86+
codecs.open(output_name, 'w', encoding='utf-8') as fo:
87+
for line in fi:
88+
if is_first_line:
89+
# fo.write(update_time + '\n')
90+
is_first_line = False
91+
continue
92+
if line.startswith(
93+
'''- { name: "Proxy", type: select, proxies:'''):
94+
proxy_list = list()
95+
proxy_list.append("auto")
96+
for name in name_list:
97+
if ('倍率:0.' not in name
98+
and not name.endswith('(倍率:1)')) or '[国内]' in name:
99+
proxy_list.append(name)
100+
newline = '''- {{ name: "Proxy", type: select, proxies: {} }}\n'''.format(
101+
proxy_list)
102+
fo.write(newline.replace("'", '"'))
103+
continue
104+
if line.startswith(
105+
'''- { name: "auto", type: url-test, proxies:'''):
106+
url_test_list = config['append_name']
107+
for name in name_list:
108+
if ('倍率:0.' in name or name.endswith('(倍率:1.')
109+
or '(倍率:2' in name
110+
) or '(倍率:2' in name and '[国内]' not in name:
111+
url_test_list.append(name)
112+
newline = '''- {{ name: "auto", type: url-test, proxies: {}, url: "http://www.gstatic.com/generate_204", interval: 300 }}\n'''.format(
113+
url_test_list)
114+
fo.write(newline.replace("'", '"'))
115+
continue
116+
if line == "## auto changed by py3 begin ##\n":
117+
switcher = True
118+
fo.write(line)
119+
if line == "Rule:\n":
120+
rule_switcher = True
121+
fo.write(rule_str_part_1)
122+
if rule_switcher and line == "# 自定义规则\n":
123+
custom_rule_switcher = True
124+
if line == "## 您可以在此处插入您补充的自定义规则\n":
125+
custom_rule_switcher = False
126+
fo.write(line)
127+
fo.write(rule_str_part_2)
128+
if rule_switcher and custom_rule_switcher:
129+
fo.write(line)
130+
if line == "## auto changed by py3 end ##\n":
131+
switcher = False
132+
new_line = ""
133+
for ss in yaml_list:
134+
new_line = new_line + ss + '\n'
135+
fo.write(new_line)
136+
if not switcher and not rule_switcher:
137+
fo.write(line)
138+
139+
140+
def init_COS(config):
141+
secret_id = config['cos_secret_id'] # 替换为用户的 secretId
142+
secret_key = config['cos_secret_key'] # 替换为用户的 secretKey
143+
region = config['cos_region'] # 替换为用户的 Region
144+
token = None # 使用临时密钥需要传入 Token,默认为空,可不填
145+
scheme = 'https' # 指定使用 http/https 协议来访问 COS,默认为 https,可不填
146+
config = CosConfig(Region=region, SecretId=secret_id,
147+
SecretKey=secret_key, Token=token, Scheme=scheme)
148+
# 2. 获取客户端对象
149+
client = CosS3Client(config)
150+
return client
151+
152+
153+
def download_template(config, client):
154+
# 获取文件到本地
155+
response = client.get_object(
156+
Bucket=config['cos_bucket'],
157+
Key=config['input_name'],
158+
)
159+
response['Body'].get_stream_to_file(config['input_name'])
160+
161+
162+
def get_config():
163+
f = open('config.json', 'r')
164+
text = f.read()
165+
f.close()
166+
config = json.loads(text)
167+
return config
168+
169+
170+
def upload_file(client, config):
171+
# 根据文件大小自动选择简单上传或分块上传,分块上传具备断点续传功能。
172+
response = client.upload_file(
173+
Bucket=config['cos_bucket'],
174+
LocalFilePath=config['output_name'],
175+
Key=config['upload_key'],
176+
PartSize=1,
177+
MAXThread=10,
178+
EnableMD5=False
179+
)
180+
print(response['ETag'])
181+
182+
183+
if __name__ == '__main__':
184+
config = get_config()
185+
client = init_COS(config)
186+
download_template(config, client)
187+
188+
# 更新 cordcould 规则 ,同时更新 https://github.com/Hackl0us/SS-Rule-Snippet 规则
189+
rule_url = config['rule_url']
190+
rule_f = urllib.request.urlopen(rule_url)
191+
rule_raw = rule_f.read().decode('utf-8')
192+
rule_str = rule_raw[rule_raw.find('Rule:\n'):]
193+
rule_str_part_1 = rule_str[:rule_str.find('# 自定义规则\n')]
194+
rule_str_part_2 = rule_str[rule_str.find('## 您可以在此处插入您补充的自定义规则\n') +
195+
len("## 您可以在此处插入您补充的自定义规则\n"):]
196+
update_time = rule_raw[:rule_raw.find('\n')]
197+
url = config['url']
198+
yaml_list = list()
199+
headers = {
200+
'User-Agent':
201+
'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6'
202+
}
203+
req = urllib.request.Request(url=url, headers=headers)
204+
f = urllib.request.urlopen(req)
205+
b64_string = f.read().decode('utf-8')
206+
result = base64_2_str(b64_string)
207+
208+
ssr_url_list = result.split("\n")
209+
name_list = list()
210+
name_list.extend(config['append_name'])
211+
for ssr_url in ssr_url_list:
212+
tmp, name = parse(ssr_url)
213+
if len(tmp) > 0:
214+
yaml_list.append(tmp)
215+
name_list.append(name)
216+
217+
input_name = config['input_name']
218+
output_name = config['output_name']
219+
update(input_name, output_name, name_list, yaml_list)
220+
upload_file(client, config)

0 commit comments

Comments
 (0)