├── LICENSE ├── README.md ├── check_v2ray.py ├── ss2ssr.py ├── ssr_dup_remover.py └── winhttp_tools ├── no_winhttp_proxy.cmd ├── set_winhttp_proxy.cmd └── show_winhttp_proxy.cmd /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Justsoos 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ss-ssr-v2ray-gadget 2 | ======================= 3 | ## ss2ssr.py 工具 4 | 5 | 批量将 ss:// 链接,或者 shadowsocks 的config文件内账号,转换成 ssr:// 链接 uri 的工具,同时实现去重,查错,备份的功能。 6 | 支持 json文件,ss 原生 gui-config.json 账号配置文件,以及 ss:// uri 格式。三种用法: 7 | 8 | * $py ss2ssr.py -s #后接 ss:// 的链接,命令输出即为 ssr:// 链接,支持输入多个链接,空格隔开。与下面两个处理文件的参数互相排斥 9 | * $py ss2ssr.py -j #后接 json 文件或 ss 配置文件 gui-config.json,输出默认保存当前目录。支持以上两种文件、多文件混合合并输入 10 | * $py ss2ssr.py -l #后接 ss:// link 多个文件,空格隔开,文件格式要求为每行一个ss://记录,输出默认保存本地目录 11 | 12 | 支持同一个参数后接多文件,支持 -l 和 -j 同时使用,多个文件混合一起去重、备份,转换格式,支持多文件 * 通配符。-s 选项支持多链接。自动生成文件名类似 SS_to_SSR_links_2018-03-01_19-07-59.txt 的文件,文件内部是 ssr:// uri,每行一条记录。 13 | 14 | ## ssr_dup_remover.py 工具 15 | 16 | 支持批量去重、查错,备份处理 json 格式的 ssr 账户文件,包括 gui-config.json 和以列表-字典方式存储的 json 账户文件。 17 | 用法: 18 | 19 | * $py ssr_dup_remover.py -j #输入文件选项。后接 json 或 ssr gui-config.json 文件。支持混合多个文件输入。生成 "de_Dup_时间戳.json" 的去重文件,和 "Dup_时间戳.json" 的重复账号备份文件。如果没有输入-j 和 -o 文件参数,此命令会在当前目录下寻找名为 "gui-config.json" 的文件进行处理 20 | * $py ssr_dup_remover.py -o #指定模板选项。读取你正在使用的 ssr 配置文件,使用此参数,要配合 -j 同时使用,用来指定你已经在使用的 ssr gui-config.json 文件,这样将生成一个**替换掉**其中所有账户,而不修改其他配置,名为 "de_Dup_时间戳_gui-config.json" 的新配置文件,可以将其复制到 ssr 目录,重命名为 "gui-config.json" 即可使用。重复的账户将如前所述,生成名为 "Dup_时间戳.json 备份文件 21 | * $py ssr_dup_remover.py -t #设置 -t 后只测试,不输出任何文件 22 | 23 | **建议用法**:将工具放置在 ssr 工作目录,无参数运行,"$py ssr_dup_remover.py" 即可生成去重后的配置文件。备份或删除、重命名替换掉原来的配置文件 "gui-config.json" 即可应用。请注意**备份原配置文件**。 24 | 如果处理多个输入文件,必须 -j 和 -o (用来指定你现在使用的ssr配置文件)联合使用,才会生成 gui-config.json 配置文件,单独的 -j 只会生成去重的备份 json 文件 -- 这个文件不能直接作为 ssr 配置文件应用。 25 | 26 | ## check_v2ray.py 工具 27 | 28 | 配合 [v2ray](https://github.com/v2ray), [v2rayN https://github.com/2dust/v2rayN](https://github.com/2dust/v2rayN) 使用,多进程,对账号批量去重,测试,benchmark。 29 | 使用很简单,放置文件 check_v2ray.py 到 v2rayN 目录(关闭 v2rayN 和 v2ray 与否都没问题,脚本会主动关闭),**以管理员或等同身份**执行,将生成一个包含去重、测速后的 guiNConfig_2018-xxxxxx_.json 带时间戳的配置文件,把原文件备份,删除,将此文件改名为 guiNConfig.json。重新运行 v2rayN.exe 30 | 可以看到每个账号别名前面都加上了类似这样的数字: 31 | * 0_0.68_ 这表示,经过10次连接到 Google 首页的测速,其中0次 (第一个数字) 连接失败,平均每次获取 Google 的302首页需要0.68秒 (第二个数字) 32 | * 或者 9_9.99_HCR_,这表示,10次连接都没有成功,这个节点暂时无法使用(被反科学上网了),或者已经废弃。 33 | 简单方便,选择数字最小的去使用就好了! 34 | 35 | 命令选项使用: 36 | * -j选项,后接 json 格式配置文件名,支持多文件合并处理 37 | * -t选项,只测试,不保存文件 38 | 39 | 暂时不支持 v2ray 下使用的 shadowsocks 账号。 40 | 建议你 将 ss 导入到 ssr 下使用,ssr-csharp 有更强大的内置数据支持,参考这里: 41 | [shadowsocksrr/shadowsocksr-csharp#33 (comment)](https://github.com/shadowsocksrr/shadowsocksr-csharp/issues/33#issuecomment-355440457) 42 | 43 | ss, ssr 的配置文件是扁平的,uri 也简单粗放,所有数据都可以列在一个二层树上列出来,v2ray 则完全不同,即便 v2rayN 用一个二层树文件,凑合表达,造成不同语义复用键值,而且没有一个通用标准配置文件可用。[逻辑很乱,感觉还需要改很多 ...](https://github.com/v2ray/v2ray-core/issues/990) 44 | 45 | * v2ray(N) Windows 客户端,推荐用这个:https://github.com/Jrohy/v2rayN 有很多方便的新功能,开发改造还很活跃。 46 | * v2ray Android 客户端,推荐用这个:https://play.google.com/store/apps/details?id=com.github.dawndiy.bifrostv 不仅支持 socks5 协议代理,还有很多方便好用的功能。 47 | 48 | ## Winhttp_tools: M$ windows ~~系统~~ WinHTTP 代理取消、设置、显示,方便的鼠标敲一下就搞定的命令行 49 | 50 | 由于众多软件设计的美国佬的~~不知道网络审查而自由的富贵病~~ 微软、Google 大企业病,chrome browser 访问网站,[更新,内置 google translate 三件简单的事,能搞成九件事](https://docs.google.com/document/d/e/2PACX-1vTMoDzlLl3wgJSd4PcrLhVUeAOCid1XFtIOvrWHIONbf-AHMfGhhCxFna_kG3UlAZiE4pr-YnvwxaGw/pub),加上 DNS poison,复杂度翻一倍,乱!~~只~~走winhttp,wininet系统代理,[很多人迷惑为什么我设置了 chrome 的 http 和 socks 代理,却无法升级 chrome ?不能用 chrome 内置的 google 翻译?](https://github.com/feliscatus/switchyomega/issues/264) 是的,你需要设置 windows 系统代理(而且还是相当乱套的好几个代理...),这些独立于浏览器之外进程需要走~~系统~~代理,他们并不走浏览器内设置的代理。 51 | 52 | 其他工具待更新... 53 | -------------------------------------------------------------------------------- /check_v2ray.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | 3 | __author__ = 'JZ' 4 | __webpage__ = 'https://github.com/Justsoos' 5 | 6 | import re 7 | import sys,os 8 | # import socks 9 | import socket 10 | import multiprocessing 11 | import threading 12 | import logging 13 | import json 14 | import time 15 | import requests 16 | import argparse 17 | import subprocess 18 | import base64 19 | 20 | from glob import glob 21 | 22 | from pprint import pprint 23 | from requests.adapters import HTTPAdapter 24 | from multiprocessing import Pool 25 | 26 | logging.getLogger().setLevel(logging.DEBUG) 27 | logging.debug('') 28 | 29 | def run_v(conf, t_conf): 30 | if int(conf.get('configType')) != 1: 31 | return None 32 | try: 33 | users = {} 34 | users['id'] = conf.get('id') 35 | users['alterId'] = int(conf.get('alterId', '0')) 36 | users['security'] = conf.get('security', 'aes-128-gcm') 37 | u = [] 38 | u.append(users) 39 | 40 | vnext = {} 41 | vnext['address'] = conf.get('address') 42 | vnext['port'] = int(conf.get('port')) 43 | vnext['users'] = u 44 | 45 | v = [] 46 | v.append(vnext) 47 | 48 | t_conf['outbound']['settings']['vnext'] = v 49 | 50 | t_conf['outbound']['streamSettings']['network'] = conf.get('network') 51 | 52 | t_conf['outbound']['streamSettings']['wsSettings'] = None 53 | t_conf['outbound']['streamSettings']['kcpSettings'] = None 54 | t_conf['outbound']['streamSettings']['tcpSettings'] = None 55 | 56 | network = conf.get('network') 57 | if network == 'ws': 58 | t_conf['outbound']['streamSettings']['wsSettings'] = \ 59 | { 60 | 'connectionReuse': True, 61 | 'path': None, 62 | 'headers': None 63 | } 64 | t_conf['outbound']['streamSettings']['wsSettings']['headers'] = (conf.get('headerType'), None)[conf.get('headerType') == 'none'] 65 | 66 | re = conf.get('requestHost') 67 | if ';' in re: 68 | r = re.split(';') 69 | t_conf['outbound']['streamSettings']['wsSettings']['path'] = r[0] 70 | t_conf['outbound']['streamSettings']['wsSettings']['headers']['Host'] = r[1] 71 | else: 72 | t_conf['outbound']['streamSettings']['wsSettings']['path'] = re 73 | 74 | elif network == 'kcp': 75 | t_conf['outbound']['streamSettings']['kcpSettings'] = \ 76 | { 77 | 'mtu': 1350, 78 | 'tti': 10, 79 | 'uplinkCapacity': 20, 80 | 'downlinkCapacity': 100, 81 | 'congestion': True, 82 | 'readBufferSize': 4, 83 | 'writeBufferSize': 4, 84 | 'header': { 85 | 'type': None, 86 | 'request': None, 87 | 'response': None 88 | } 89 | } 90 | 91 | t_conf['outbound']['streamSettings']['kcpSettings']['header']['type'] = (conf.get('headerType'), 'none')[conf.get('headerType') == 'none'] 92 | 93 | elif network == 'tcp': 94 | t_conf['outbound']['streamSettings']['tcpSettings'] = \ 95 | { 96 | 'connectionReuse': True, 97 | 'header': { 98 | 'type': None, 99 | 'request': { 100 | 'version': '1.1', 101 | 'method': 'GET', 102 | 'path': [ 103 | '/' 104 | ], 105 | 'headers': { 106 | 'Host': [ 107 | '' 108 | ], 109 | 'User-Agent': [ 110 | 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.75 Safari/537.36', 111 | 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_0_2 like Mac OS X) AppleWebKit/601.1 (KHTML, like Gecko) CriOS/53.0.2785.109 Mobile/14A456 Safari/601.1.46' 112 | ], 113 | 'Accept-Encoding': [ 114 | 'gzip,deflate' 115 | ], 116 | 'Connection': [ 117 | 'keep-alive' 118 | ], 119 | 'Pragma': 'no-cache' 120 | } 121 | }, 122 | 'response': { 123 | 'version': '1.1', 124 | 'status': '200', 125 | 'reason': 'OK', 126 | 'headers': { 127 | 'Content-Type': [ 128 | 'application/octet-stream', 129 | 'video/mpeg' 130 | ], 131 | 'Transfer-Encoding': [ 132 | 'chunked' 133 | ], 134 | 'Connection': [ 135 | 'keep-alive' 136 | ], 137 | 'Pragma': 'no-cache' 138 | } 139 | } 140 | } 141 | } 142 | if conf.get('headerType') == 'none' or conf.get('headerType') == '': 143 | t_conf['outbound']['streamSettings']['tcpSettings'] = None 144 | else: 145 | t_conf['outbound']['streamSettings']['tcpSettings']['header']['type'] = (conf.get('headerType'), None)[conf.get('headerType') == 'none'] 146 | t_conf['outbound']['streamSettings']['tcpSettings']['header']['request']['headers']['Host'] = conf.get('requestHost', '').split(',') 147 | 148 | elif network == 'h2' or network == 'http': 149 | 150 | t_conf['outbound']['streamSettings']['httpSettings'] = \ 151 | { 152 | 'path': None, 153 | 'host': [] 154 | } 155 | 156 | t_conf['outbound']['streamSettings']['httpSettings']['path'] = conf.get('path') 157 | t_conf['outbound']['streamSettings']['httpSettings']['Host'] = list(conf.get('requestHost')) 158 | 159 | else: 160 | raise NameError("unkonwn network", network) 161 | 162 | t_conf['outbound']['streamSettings']['security'] = (conf.get('streamSecurity'), '')[conf.get('streamSecurity') == None] 163 | 164 | t_conf['outbound']['protocol'] = 'vmess' 165 | 166 | port = get_free_tcp_port() 167 | if not port: 168 | raise Exception('Error getting local port.') 169 | t_conf['inbound']['port'] = int(port) 170 | 171 | t_conf['inbound']['listen'] = '127.0.0.1' 172 | t_conf['inbound']['protocol'] = 'socks' 173 | t_conf['inbound']['settings']['auth'] = 'noauth' 174 | t_conf['inbound']['settings']['ip'] = '127.0.0.1' 175 | except: 176 | raise 177 | #remarks = conf.get('remarks', None) 178 | temp_file = 'temp_file_{}_{}.json'.format(int(round(time.time() * 1000)), port) 179 | with open(temp_file, 'w') as f: 180 | json.dump(t_conf, f) 181 | 182 | if not os.path.isfile(temp_file): 183 | logging.debug('here missing missing: '.format(temp_file)) 184 | 185 | cmd_line = 'v2ray.exe --config={}'.format(temp_file) 186 | logging.debug('checking id: {} with port {}'.format(users['id'], port)) 187 | try: 188 | p = subprocess.Popen(cmd_line, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.DEVNULL) 189 | except: 190 | raise 191 | time.sleep(0.8) 192 | return p, port, temp_file 193 | 194 | def test_connect(port): 195 | perfect = 9 196 | sum_r = 0 197 | time.sleep(2) 198 | get_latency(port) 199 | time.sleep(2) 200 | for i in range(1,10): 201 | time.sleep(0.1) 202 | r, p = get_latency(port) 203 | if p is True: 204 | print('.', end='') 205 | sum_r += r 206 | perfect -= 1 207 | if perfect != 9: 208 | times = 9 - int(perfect) 209 | s = sum_r / times 210 | latency = format(s, '0.2f') 211 | else: 212 | latency = 0 213 | return perfect, latency 214 | 215 | def get_latency(port): 216 | test_urls = 'https://www.google.com/' 217 | headers = {'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36'} 218 | proxies = {} 219 | proxies['http'] = 'socks5h://127.0.0.1:{}'.format(port) 220 | proxies['https'] = 'socks5h://127.0.0.1:{}'.format(port) 221 | 222 | start_time = time.time() 223 | s = requests.Session() 224 | try: 225 | s.mount(test_urls, HTTPAdapter(max_retries=0)) 226 | r = s.get(test_urls, proxies=proxies, headers=headers, verify=True, timeout=(7,15), allow_redirects=False, cookies={'':''}) 227 | r.raise_for_status() 228 | connectivity = True 229 | except Exception as err: 230 | print(err) 231 | connectivity = False 232 | end_time = time.time() 233 | 234 | latency_time = end_time - start_time 235 | return latency_time, connectivity 236 | 237 | def sub_proc(proc, single_json, t_conf): 238 | proc, port, temp_file = run_v(single_json, t_conf) 239 | 240 | try: 241 | perfect, latency = test_connect(port) 242 | except: 243 | raise 244 | 245 | proc.kill() 246 | 247 | logging.debug('process what ? {}'.format(proc)) 248 | stdout, stderr = proc.communicate() 249 | logging.debug('stdout of process{}'.format(stdout)) 250 | logging.debug('stdERR of process{}'.format(stderr)) 251 | 252 | if not os.path.isfile(temp_file): 253 | logging.debug('missing temp config file: {}'.format(temp_file)) 254 | raise ValueError('config file missing...') 255 | 256 | os.remove(temp_file) 257 | 258 | return single_json, perfect, latency 259 | 260 | def multi_proc(configs): 261 | global t_conf 262 | multiprocessing.freeze_support() 263 | proc = multiprocessing.Pool(16) 264 | 265 | proc_result = [] 266 | if isinstance(configs, dict): 267 | t = [] 268 | t.append(configs.copy()) 269 | configs = t 270 | 271 | for i, ei in enumerate(configs): 272 | r = proc.apply_async(sub_proc, args=(i, ei, t_conf)) 273 | proc_result.append(r) 274 | 275 | proc.close() 276 | proc.join() 277 | 278 | configs_all = [] 279 | for k in proc_result: 280 | configs_all.append(k.get()) 281 | 282 | info = [] 283 | configs_good_temp = [] 284 | configs_bad_temp = [] 285 | configs_bad = [] 286 | configs_good = [] 287 | for j in configs_all: 288 | info.append((j[1],j[2])) 289 | if j[1] == 9: 290 | configs_bad_temp.append(j[0]) 291 | else: 292 | configs_good_temp.append(j) 293 | 294 | if configs_good_temp: 295 | configs_good_temp.sort(key = lambda x:x[2]) 296 | configs_good_temp.sort(key = lambda x:x[1]) 297 | for i in configs_good_temp: 298 | r = re.match('^\d_\d\.\d{2}_(.*)', i[0].get('remarks')) 299 | if r: 300 | remarks = r.group(1) 301 | else: 302 | remarks = i[0].get('remarks') 303 | remarks = '{}_{}_{}'.format(i[1], i[2], remarks) 304 | i[0]['remarks'] = remarks[:60] 305 | configs_good.append(i[0]) 306 | 307 | if configs_bad_temp: 308 | for k in configs_bad_temp: 309 | r = re.match('^\d_\d\.\d{2}_(.*)', k.get('remarks')) 310 | if r: 311 | remarks = r.group(1) 312 | else: 313 | remarks = k.get('remarks') 314 | remarks = '{}_{}_HCR_{}'.format('9', '9.99', remarks) 315 | k['remarks'] = remarks[:60] 316 | configs_bad.append(k) 317 | 318 | return configs_good, configs_bad, info 319 | 320 | def deDup(conf): 321 | dest_list = [] 322 | dup_list = [] 323 | global other_list 324 | other_list = [] 325 | try: 326 | for i, ei in enumerate(conf): 327 | if int(ei.get('configType')) != 1: 328 | other_list.append(ei) 329 | continue 330 | for j, ej in enumerate(conf[i+1:]): 331 | if ( 332 | (ei['address'] == ej['address']) and 333 | (int(ei['port']) == int(ej['port'])) and 334 | (ei['id'] == ej['id']) and 335 | (int(ei['alterId']) == int(ej['alterId'])) and 336 | (ei['network'] == ej['network']) and 337 | (ei['headerType'] == ej['headerType']) and 338 | (ei['requestHost'] == ej['requestHost']) and 339 | (ei['streamSecurity'] == ej['streamSecurity']) and 340 | (int(ei['configType']) == int(ej['configType'])) 341 | ): 342 | dup_list.append(ei) 343 | dest_found = False 344 | break 345 | else: 346 | dest_found = True 347 | if dest_found: 348 | dest_list.append(ei) 349 | except KeyError as err: 350 | print('The No.{} record seems wrong with {}...'.format((i+j), err)) 351 | raise 352 | deDup_info = '**** All records: {}, found dups: {}, unique VMESS records: {}, non VMESS records: {}'.format(len(conf), len(dup_list), len(dest_list), len(other_list)) 353 | return dest_list, dup_list, deDup_info 354 | 355 | def get_free_tcp_port(): 356 | tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 357 | tcp.bind(('', 0)) 358 | addr, port = tcp.getsockname() 359 | tcp.close() 360 | return port 361 | 362 | def rewrite_socks_dns(address, timeout=None, source_address=None): 363 | sock = socks.socksocket() 364 | sock.connect(address) 365 | return sock 366 | 367 | def kill(): 368 | cmd_task = 'taskkill /f /t /im' 369 | kill_list = ['v2rayN.exe', 'v2ray.exe', 'v2ctl.exe', 'wv2ray.exe'] 370 | try: 371 | for i in kill_list: 372 | cmd = '{} {}'.format(cmd_task, i) 373 | s = subprocess.call(cmd) 374 | print(s) 375 | except: 376 | pass 377 | 378 | def files(wildcard_file_args): 379 | if wildcard_file_args: 380 | file_list = (glob(name) for name in wildcard_file_args) 381 | file_list = [i for j in file_list for i in j] 382 | file_list = list(set(file_list)) 383 | else: 384 | file_list = None 385 | return file_list 386 | 387 | def main_dev(): 388 | 389 | global json_files 390 | global uri_files 391 | global only_test 392 | 393 | parser = argparse.ArgumentParser( description='de-duplicate, merge, test, benchmark and backup tools for v2ray with v2rayN, M$ Windows') 394 | parser.add_argument('-j', metavar='input JSON filenames', dest='json_files', default=False, type=str, nargs='+', help='Input json filenames') 395 | parser.add_argument('-l', metavar='input Link filenames', dest='uri_files', default=False, type=str, nargs='+', help='Input uri link filenames') 396 | parser.add_argument('-t', dest='only_test', action='store_true', default=False, help='Just test, no output file') 397 | parser.add_argument('-v',action='version', version='0.2') 398 | args = parser.parse_args() 399 | 400 | json_files = files(args.json_files) 401 | uri_files = files(args.uri_files) 402 | only_test = args.only_test 403 | 404 | if not args.json_files and not args.uri_files: 405 | json_files = ['guiNConfig.json'] 406 | 407 | def main(): 408 | # socket.socket = socks.socksocket 409 | # socket.create_connection = rewrite_socks_dns 410 | 411 | global json_files 412 | global uri_files 413 | global only_test 414 | global other_list 415 | 416 | main_dev() 417 | 418 | kill() 419 | 420 | global t_conf 421 | t_conf = \ 422 | { 423 | 'log': { 424 | 'access': None, 425 | 'error': None, 426 | 'loglevel': None 427 | }, 428 | 'inbound': { 429 | 'port': 48080, 430 | 'listen': '127.0.0.1', 431 | 'protocol': 'socks', 432 | 'settings': { 433 | 'auth': 'noauth', 434 | 'udp': False, 435 | 'ip': '127.0.0.1', 436 | 'clients': None 437 | }, 438 | 'streamSettings': None 439 | }, 440 | 'outbound': { 441 | 'tag': 'agentout', 442 | 'protocol': 'vmess', 443 | 'settings': { 444 | 'vnext': [ 445 | { 446 | 'address': '213.213.213.213', 447 | 'port': 23333, 448 | 'users': [ 449 | { 450 | 'id': 'dddda000-bbbb-4444-2222-fffff6666666', 451 | 'alterId': 100, 452 | 'security': 'aes-128-gcm' 453 | } 454 | ] 455 | } 456 | ], 457 | 'servers': None 458 | }, 459 | 'streamSettings': { 460 | 'network': None, 461 | 'security': None, 462 | 'tcpSettings': None, 463 | 'kcpSettings': None, 464 | 'wsSettings': None 465 | }, 466 | 'mux': { 467 | 'enabled': False 468 | } 469 | }, 470 | 'inboundDetour': None, 471 | 'outboundDetour': [ 472 | { 473 | 'protocol': 'freedom', 474 | 'settings': { 475 | 'response': None 476 | }, 477 | 'tag': 'direct' 478 | }, 479 | { 480 | 'protocol': 'blackhole', 481 | 'settings': { 482 | 'response': { 483 | 'type': 'http' 484 | } 485 | }, 486 | 'tag': 'blockout' 487 | } 488 | ], 489 | 'dns': { 490 | 'servers': [ 491 | '8.8.8.8', 492 | '8.8.4.4', 493 | '114.114.114.114' 494 | ] 495 | }, 496 | 'routing': { 497 | 'strategy': 'rules', 498 | 'settings': { 499 | 'domainStrategy': 'IPIfNonMatch', 500 | 'rules': [ 501 | { 502 | 'type': 'field', 503 | 'port': None, 504 | 'outboundTag': 'direct', 505 | 'ip': [ 506 | '0.0.0.0/8', 507 | '10.0.0.0/8', 508 | '100.64.0.0/10', 509 | '127.0.0.0/8', 510 | '169.254.0.0/16', 511 | '172.16.0.0/12', 512 | '192.0.0.0/24', 513 | '192.0.2.0/24', 514 | '192.168.0.0/16', 515 | '198.18.0.0/15', 516 | '198.51.100.0/24', 517 | '203.0.113.0/24', 518 | '::1/128', 519 | 'fc00::/7', 520 | 'fe80::/10' 521 | ], 522 | 'domain': None 523 | } 524 | ] 525 | } 526 | } 527 | } 528 | 529 | t_guiNConfig = \ 530 | { 531 | "inbound": [{ 532 | "localPort": 28080, 533 | "protocol": "socks", 534 | "udpEnabled": False 535 | }], 536 | "logEnabled": False, 537 | "loglevel": "error", 538 | "index": 78, 539 | "vmess": [], 540 | "muxEnabled": False, 541 | "chinasites": False, 542 | "chinaip": False, 543 | "useragent": [], 544 | "userdirect": [], 545 | "userblock": [], 546 | "kcpItem": { 547 | "mtu": 1350, 548 | "tti": 10, 549 | "uplinkCapacity": 20, 550 | "downlinkCapacity": 100, 551 | "congestion": True, 552 | "readBufferSize": 4, 553 | "writeBufferSize": 4 554 | }, 555 | "autoSyncTime": False, 556 | "sysAgentEnabled": False, 557 | "listenerType": 1, 558 | "urlGFWList": "" 559 | } 560 | 561 | configs = [] 562 | links = [] 563 | guiNConfig = None 564 | data = None 565 | vmess = None 566 | 567 | if json_files: 568 | try: 569 | for i in json_files: 570 | if not os.path.isfile(i): 571 | print('****Bad file path or name: {} ...'.format(i)) 572 | break 573 | else: 574 | with open(i, 'r', encoding='utf-8') as f: 575 | data = json.load(f) 576 | if not data: 577 | print('can not found specified format on {}'.format(i)) 578 | break 579 | 580 | if isinstance(data, dict) and data.get('inbound') and data.get('vmess'): 581 | vmess = data.get('vmess') 582 | configs.extend(vmess) 583 | guiNConfig = data 584 | elif isinstance(data, list) and data[0].get('address'): 585 | configs.extend(data) 586 | else: 587 | print('can not found specified format on {}'.format(i)) 588 | except: 589 | raise 590 | 591 | guiNConfig = t_guiNConfig if not guiNConfig else guiNConfig 592 | 593 | if configs: 594 | conf, _, deDup_info = deDup(configs) 595 | else: 596 | print('No legal data input...') 597 | sys.exit() 598 | 599 | print(deDup_info) 600 | try: 601 | input('**** Waiting for comfirm, Ctrl+C to interrupt or continue with Enter...') 602 | except KeyboardInterrupt: 603 | sys.exit() 604 | 605 | configs_good, configs_bad, _ = multi_proc(conf) 606 | 607 | kill() 608 | 609 | all_list = [] 610 | if configs_good: 611 | all_list.extend(configs_good) 612 | if configs_bad: 613 | all_list.extend(configs_bad) 614 | if other_list: 615 | all_list.extend(other_list) 616 | 617 | if deDup_info: 618 | print(deDup_info) 619 | 620 | if only_test: 621 | print('**** Only test, result: deDuped records {}, good ones {}, bad ones {}.'.format(len(all_list), len(configs_good), len(configs_bad))) 622 | else: 623 | guiNConfig['vmess'] = all_list 624 | guiNConfig_new = 'guiNConfig_{}_.json'.format(time.strftime('%Y-%m-%d_%H-%M-%S')) 625 | with open(guiNConfig_new, 'w') as f: 626 | json.dump(guiNConfig, f) 627 | print('***** Output to {}, {} records. good ones {}, bad ones {}. *****'.format(guiNConfig_new, len(all_list), len(configs_good), len(configs_bad))) 628 | 629 | 630 | if __name__ == '__main__': 631 | run_start = time.time() 632 | main() 633 | run_end = time.time() 634 | duration = run_end - run_start 635 | m, s = divmod(duration, 60) 636 | print('Cost time: {:.0f} mins {:.0f} seconds. '.format(m, s)) 637 | -------------------------------------------------------------------------------- /ss2ssr.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | 3 | __author__ = 'JZ' 4 | __webpage__ = 'https://github.com/Justsoos' 5 | 6 | import re 7 | import sys,os 8 | import logging 9 | import json 10 | import time 11 | import argparse 12 | import base64 13 | import traceback 14 | 15 | from glob import glob 16 | 17 | 18 | def remove_dup_jsons(json_list): 19 | l = json_list 20 | if not l: 21 | return 22 | dest_list = [] 23 | dup_list = [] 24 | dup_times = 0 25 | num = len(l) 26 | bad_records = {} 27 | good_records = [] 28 | # Looking for None record 29 | for ie in range(num): 30 | if not ie: 31 | continue 32 | try: 33 | if ( 34 | l[ie]['server'] and 35 | l[ie]['server_port'] and 36 | l[ie]['password'] and 37 | l[ie]['method'] 38 | ): 39 | good_records.append(ie) 40 | l[ie]['server_port'] = int(l[ie]['server_port']) 41 | except KeyError as err: 42 | bad_records[ie] = err 43 | 44 | if num <= 1: 45 | return l 46 | #print(bad_records, good_records) 47 | if bad_records and good_records: 48 | n = good_records 49 | else: 50 | n = list(range(num)) 51 | # Looking for duplicate 52 | l_max = len(n) 53 | for i in range(l_max): 54 | for j in range((i+1), l_max): 55 | try: 56 | if ( 57 | (l[n[i]]['server'] == l[n[j]]['server']) and 58 | (int(l[n[i]]['server_port']) == int(l[n[j]]['server_port'])) and 59 | (l[n[i]]['password'] == l[n[j]]['password']) and 60 | (l[n[i]]['method'] == l[n[j]]['method']) 61 | ): 62 | dup_times += 1 63 | #dup_list.append(l[n[i]]) 64 | dest_found = False 65 | break 66 | else: 67 | dest_found = True 68 | except: 69 | raise 70 | if dest_found: 71 | dest_list.append(l[n[i]]) 72 | 73 | if bad_records: 74 | for line, erro in bad_records.items(): 75 | print('***The No.{} record seems wrong with {}...'.format(line+1, erro)) 76 | #print('There are {} records inputed.'.format(num)) 77 | #print('Found {} times of duplicate records.'.format(dup_times)) 78 | 79 | return dest_list 80 | 81 | def remove_dup_links(url_list): 82 | l = list({}.fromkeys(url_list).keys()) 83 | dest_list = [] 84 | for line in l: 85 | ls = line.strip() 86 | if len(ls) > 10: 87 | dest_list.append(ls) 88 | return dest_list 89 | 90 | def sslink2json(single_link): 91 | if not single_link: 92 | return None 93 | remarks = '' 94 | obfs = 'plain' 95 | link = to_str(single_link) 96 | #print(link) 97 | try: 98 | if link[:5] == 'ss://': 99 | t = link[5:] 100 | elif link[:6] == 'ssr://': 101 | raise ValueError('Not SS, but SSR address: {}'.format(link)) 102 | else: 103 | return 104 | 105 | if '#' in t: 106 | s = t.split('#',1) 107 | t = s[0] 108 | remarks = s[1] 109 | 110 | if '@' not in t: 111 | t = b64decode(t) 112 | elif ':' not in (t.split('@',1)[0]): 113 | odd_ss = b64decode(t.split('@',1)[0]) 114 | t = '{}@{}'.format(odd_ss, t.split('@',1)[1]) 115 | else: 116 | pass 117 | 118 | t.strip('/') 119 | except Exception as e: 120 | raise e 121 | 122 | result = {} 123 | d = t.split(':') 124 | 125 | if len(d) == 3: 126 | result['server'] = d[1].rsplit('@',1)[1] 127 | result['server_port'] = int(d[2]) 128 | result['password'] = d[1].rsplit('@',1)[0] 129 | result['method'] = d[0] 130 | result['plugin'] = '' 131 | result['plugin_opts'] = '' 132 | result['obfs'] = 'plain' 133 | result['remarks'] = remarks 134 | result['timeout'] = 5 135 | elif len(d) >= 4: 136 | print('!!! THERE IS A BAD SS URI ??!!{} {}'.format(d, link)) 137 | return None 138 | else: 139 | pass 140 | 141 | return result 142 | 143 | def ssjsons2ssrlinks(jsons): 144 | d = jsons 145 | links = [] 146 | try: 147 | if (isinstance(d, dict)) and d['configs']: 148 | j = d['configs'] 149 | else: 150 | j = d 151 | 152 | for i in j: 153 | server = i.get('server') 154 | server_port = i.get('server_port') 155 | password = i.get('password') 156 | method = i.get('method') 157 | obfs = i.get('obfs', 'plain') 158 | remarks = i.get('remarks') 159 | uri = '{}:{}:origin:{}:{}:{}'.format(server, server_port, method, obfs, b64encode(password)) 160 | group = b64encode('SS-2-SSR_{}'.format(time.strftime('%Y.%m.%d_%H.%M'))) 161 | stern = '/?obfsparam=&remarks={}&group={}'.format(b64encode(remarks), group) 162 | links.append('ssr://{}'.format(b64encode(('{}{}'.format(uri, stern))))) 163 | except: 164 | raise 165 | return links 166 | 167 | def to_bytes(s): 168 | if type(s) == str: 169 | return s.encode('utf-8') 170 | return s 171 | 172 | def to_str(s): 173 | if type(s) == bytes: 174 | return s.decode('utf-8') 175 | return s 176 | 177 | def b64encode(data): 178 | if type(data) == bytes: 179 | return to_str(base64.urlsafe_b64encode(data)).strip('=') 180 | elif type(data) == str: 181 | return to_str(base64.urlsafe_b64encode(to_bytes(data))).strip('=') 182 | else: 183 | return data 184 | 185 | def b64decode(data): 186 | if type(data) == str: 187 | return to_str(base64.urlsafe_b64decode(data+'='*(4-len(data)%4))) 188 | return data 189 | 190 | def read_links_file(file): 191 | with open(file, 'rb') as f: 192 | links = f.readlines() 193 | return links 194 | 195 | def read_configs_file(file): 196 | try: 197 | with open(file, 'rb') as f: 198 | d = json.load(f) 199 | if (isinstance(d, dict)) and d['configs']: 200 | l = d['configs'] 201 | elif (isinstance(d, list)): 202 | l = d 203 | else: 204 | raise Exception('Wrong or Damaged SS config file...Pls check check {}'.format(file)) 205 | except KeyError: 206 | pass 207 | return l 208 | 209 | def func_1st(configs, links): 210 | if configs: 211 | j0 = configs 212 | t0 = len(j0) 213 | else: 214 | t0 = 0 215 | j0 = [] 216 | 217 | if links: 218 | l1 = links 219 | l2 = remove_dup_links(l1) 220 | t1 = len(l1) 221 | else: 222 | t1 = 0 223 | l2 = [] 224 | t12 = 0 225 | 226 | for li in l2: 227 | if sslink2json(li): 228 | j0.append(sslink2json(li)) 229 | 230 | t2 = len(j0) 231 | 232 | #print(j0) 233 | 234 | j2 = remove_dup_jsons(j0) 235 | if j2: 236 | t3 = len(j2) 237 | links_result = ssjsons2ssrlinks(j2) 238 | else: 239 | t3 = 0 240 | links_result = None 241 | 242 | info = '*** From configs JSON file: {} records, from link URI file: {} records. Total: {}. After json-inside duplicate remove, {} links here. '.format(t0, t1, t2, t3) 243 | print(info) 244 | 245 | if links_result: 246 | links_file = 'SS_to_SSR_links_{}.txt'.format(time.strftime('%Y-%m-%d_%H-%M-%S')) 247 | with open(links_file, 'w', encoding='utf-8') as f: 248 | for i in links_result: 249 | print(i, file=f) 250 | print('*** {} SS to SSR Links records saved to {} ...'.format(len(links_result), links_file)) 251 | 252 | 253 | def main_dev(): 254 | global json_files 255 | global uri_files 256 | global link_ss 257 | 258 | parser = argparse.ArgumentParser( description='de-duplicate, merge, convert SS to SSR uri and backup tools for Shadowsocks, working under Python3') 259 | parser.add_argument('-j', metavar='input JSON filenames', dest='json_files', type=str, nargs='+', help='Input json or SS config filenames') 260 | parser.add_argument('-l', metavar='input Link filenames', dest='uri_files', type=str, nargs='+', help='Input uri link filenames') 261 | parser.add_argument('-s', metavar='ss links', dest='link_ss', type=str, nargs='+', help='Input SS links...single or more') 262 | parser.add_argument('-v',action='version', version='ss to ssr 0.2') 263 | args = parser.parse_args() 264 | 265 | if args.json_files: 266 | json_files = (glob(name) for name in args.json_files) 267 | json_files = [i for j in json_files for i in j] 268 | json_files = list(set(json_files)) 269 | else: 270 | json_files = None 271 | 272 | if args.uri_files: 273 | uri_files = (glob(name) for name in args.uri_files) 274 | uri_files = [i for j in uri_files for i in j] 275 | uri_files = list(set(uri_files)) 276 | else: 277 | uri_files = None 278 | 279 | if args.link_ss: 280 | sign = set(((x.strip('\''))[:5] == 'ss://' for x in args.link_ss)) 281 | if len(sign) == 1 and True in sign: 282 | link_ss = [y.strip('\'') for y in args.link_ss] 283 | else: 284 | parser.print_help() 285 | print('*** Please input correct ss:// link') 286 | sys.exit(1) 287 | elif ((len(sys.argv) < 2) or ((not json_files) and (not uri_files))): 288 | parser.print_help() 289 | sys.exit(1) 290 | else: 291 | link_ss = None 292 | 293 | print(args) 294 | print('*** Working on ... ', json_files, uri_files, link_ss) 295 | 296 | 297 | def main(): 298 | global json_files 299 | global uri_files 300 | global link_ss 301 | 302 | main_dev() 303 | 304 | if link_ss: 305 | for i in link_ss: 306 | js = sslink2json(i) 307 | ls = ssjsons2ssrlinks([js]) 308 | print(''.join(ls)) 309 | sys.exit(1) 310 | 311 | configs = [] 312 | links = [] 313 | 314 | if json_files: 315 | for i in json_files: 316 | configs.extend(read_configs_file(i)) 317 | 318 | if uri_files: 319 | for j in uri_files: 320 | links.extend(read_links_file(j)) 321 | 322 | func_1st(configs, links) 323 | 324 | 325 | if __name__ == '__main__': 326 | main() 327 | -------------------------------------------------------------------------------- /ssr_dup_remover.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | 3 | __author__ = 'JZ' 4 | __webpage__ = 'https://github.com/Justsoos' 5 | 6 | import os 7 | import sys 8 | import json 9 | import time 10 | import argparse 11 | 12 | from glob import glob 13 | 14 | def remove_jsons_dups(input_jsons): 15 | l = input_jsons 16 | dest_list = [] 17 | dup_list = [] 18 | 19 | try: 20 | num = len(l) 21 | for i in range(num): 22 | for j in range((i+1),num): 23 | if ( 24 | (l[i]['server'] == l[j]['server']) and 25 | (int(l[i]['server_port']) == int(l[j]['server_port'])) and 26 | (l[i]['password'] == l[j]['password']) and 27 | (l[i]['method'] == l[j]['method']) and 28 | (l[i]['protocol'] == l[j]['protocol']) and 29 | (l[i]['protocolparam'] == l[j]['protocolparam']) and 30 | (l[i]['obfs'] == l[j]['obfs']) and 31 | (l[i]['obfsparam'] == l[j]['obfsparam']) 32 | ): 33 | dup_list.append(l[i]) 34 | dest_found = False 35 | break 36 | else: 37 | dest_found = True 38 | if dest_found: 39 | dest_list.append(l[i]) 40 | except KeyError as err: 41 | print('The No.{} record seems wrong with {}...'.format(j, err)) 42 | raise 43 | 44 | print('There are {} total input records.'.format(num)) 45 | print('Found {} times of duplicate records.'.format(len(dup_list))) 46 | 47 | return dest_list, dup_list 48 | 49 | def read_configs_file(file): 50 | try: 51 | with open(file, 'rb') as f: 52 | d = json.load(f) 53 | if (isinstance(d, dict)) and d['configs']: 54 | l = d['configs'] 55 | elif (isinstance(d, list)): 56 | l = d 57 | else: 58 | raise Exception('Wrong or Damaged SSR config/json file...Pls check check "{}"'.format(file)) 59 | except KeyError as e: 60 | print('*** Some Config file seems get wrong ... ', e) 61 | return None 62 | except json.decoder.JSONDecodeError as err: 63 | print('*** {} is not correct json/config file... pls check check... '.format(file)) 64 | raise 65 | return l 66 | 67 | def main_dev(): 68 | global only_test 69 | global input_files 70 | global target 71 | 72 | parser = argparse.ArgumentParser(description='gui-config.json de-duplicate and backup tool for ShadowsocksR-csharp, working under Python3') 73 | parser.add_argument('-j', dest='input_files', metavar='SSR json files', type=str, nargs='+', help='SSR gui-config.json or json-stored files') 74 | parser.add_argument('-o', dest='target', metavar='Target SSR gui-config.json file', type=str, nargs=1, help='Assign your own SSR gui-config.json file') 75 | parser.add_argument('-t', dest='only_test', action='store_true', default=False, help='Set it for just testing, no output file') 76 | parser.add_argument('-v',action='version', version='SSR de-duplicate 0.1') 77 | args = parser.parse_args() 78 | 79 | #print(args) 80 | if args.only_test: 81 | only_test = True 82 | else: 83 | only_test = False 84 | print(args) 85 | 86 | if (not args.input_files) and (not args.target) and (os.path.exists('gui-config.json')): 87 | input_files = ['gui-config.json'] 88 | target = 'gui-config.json' 89 | print('*** No input file name, looking for "gui-config.json" file in current dir and do sth...') 90 | elif (not args.input_files) and args.target and (os.path.exists(args.target[0])): 91 | input_files = [args.target[0]] 92 | target = args.target[0] 93 | check_config_file(target) 94 | elif args.input_files: 95 | t = (glob(x) for x in args.input_files) 96 | s = [i for j in t for i in j] 97 | input_files = list(set(s)) 98 | if args.target and os.path.exists(args.target[0]) and check_config_file(args.target[0]): 99 | target = args.target[0] 100 | elif not args.target: 101 | target = None 102 | else: 103 | pass 104 | else: 105 | print('*** Can not find SSR named "gui-config.json" file, pls assign with -o option....') 106 | parser.print_help() 107 | sys.exit(1) 108 | 109 | def check_config_file(file): 110 | with open(file, 'rb') as f: 111 | d = json.load(f) 112 | if (isinstance(d, dict)) and d.get('configs'): 113 | return True 114 | else: 115 | print('*** Wrong or Bad SSR gui-config.json file -- {}!! Pls check check...'.format(args.target[0])) 116 | sys.exit(1) 117 | 118 | def end(dest_list, dup_list, target, only_test): 119 | if len(dup_list) == 0: 120 | print('No duplicate found, Good luck! guys...') 121 | if only_test: 122 | print('Just TEST, no output file...') 123 | return 124 | if dest_list and isinstance(dest_list, list): 125 | pass 126 | else: 127 | print('*** Sth wrong... no processing result ... exit...') 128 | return 129 | 130 | try: 131 | if target: 132 | with open(target, 'rb') as f: 133 | d = json.load(f) 134 | d['configs'] = dest_list 135 | output_file = 'de_Dup_{}_gui-config.json'.format(time.strftime('%Y-%m-%d_%H-%M-%S')) 136 | print('*** {} de-duplicate SSR Congfig file saved as \"{}\", just copy & paste then raname it to apply to SSR...'.format(len(dest_list), output_file)) 137 | else: 138 | output_file = 'de_Dup_{}.json'.format(time.strftime('%Y-%m-%d_%H-%M-%S')) 139 | d = dest_list 140 | print('*** {} de-duplicate SSR records saved to \"{}\". '.format(len(dest_list), output_file)) 141 | with open(output_file, 'w', encoding='utf-8') as o: 142 | json.dump(d, o, sort_keys=True, indent=4, separators=(',', ': '), ensure_ascii=False) 143 | except: 144 | raise 145 | 146 | try: 147 | if dup_list: 148 | dup_file = 'Dup_{}.json'.format(time.strftime('%Y-%m-%d_%H-%M-%S')) 149 | with open(dup_file, 'w', encoding='utf-8') as p: 150 | json.dump(dup_list, p, sort_keys=True, indent=4, separators=(',', ': '), ensure_ascii=False) 151 | print('*** {} Duplicate SSR records saved to \"{}\" ...'.format(len(dup_list), dup_file)) 152 | except: 153 | raise 154 | ''' 155 | out = json.dumps(d, sort_keys=True, indent=4, separators=(',', ': '), ensure_ascii=False) 156 | with open(output_file, 'wb') as o: 157 | o.write(out.encode('utf-8')) 158 | ''' 159 | 160 | def main(): 161 | global only_test 162 | global input_files 163 | global target 164 | main_dev() 165 | 166 | input_jsons = [] 167 | try: 168 | for i in input_files: 169 | l = read_configs_file(i) 170 | if l: 171 | input_jsons.extend(l) 172 | except: 173 | raise 174 | 175 | if input_jsons and isinstance(input_jsons, list): 176 | dest_list, dup_list = remove_jsons_dups(input_jsons) 177 | end(dest_list, dup_list, target, only_test) 178 | else: 179 | print('Something wrong, pls check check...') 180 | parser.print_help() 181 | sys.exit(1) 182 | 183 | if __name__ == '__main__': 184 | main() 185 | -------------------------------------------------------------------------------- /winhttp_tools/no_winhttp_proxy.cmd: -------------------------------------------------------------------------------- 1 | @echo on 2 | netsh winhttp reset proxy 3 | netsh winhttp show proxy 4 | 5 | pause 6 | -------------------------------------------------------------------------------- /winhttp_tools/set_winhttp_proxy.cmd: -------------------------------------------------------------------------------- 1 | @echo on 2 | netsh winhttp set proxy 127.0.0.1:1080 3 | netsh winhttp show proxy 4 | 5 | pause 6 | -------------------------------------------------------------------------------- /winhttp_tools/show_winhttp_proxy.cmd: -------------------------------------------------------------------------------- 1 | @echo on 2 | netsh winhttp show proxy 3 | 4 | pause 5 | --------------------------------------------------------------------------------