#! /usr/bin/env python3 __author__ = 'JZ' __webpage__ = 'https://github.com/Justsoos' import re import sys,os # import socks import socket import multiprocessing import threading import logging import json import time import requests import argparse import subprocess import base64 from glob import glob from pprint import pprint from requests.adapters import HTTPAdapter from multiprocessing import Pool logging.getLogger().setLevel(logging.DEBUG) logging.debug('') def run_v(conf, t_conf): if int(conf.get('configType')) != 1: return None try: users = {} users['id'] = conf.get('id') users['alterId'] = int(conf.get('alterId', '0')) users['security'] = conf.get('security', 'aes-128-gcm') u = [] u.append(users) vnext = {} vnext['address'] = conf.get('address') vnext['port'] = int(conf.get('port')) vnext['users'] = u v = [] v.append(vnext) t_conf['outbound']['settings']['vnext'] = v t_conf['outbound']['streamSettings']['network'] = conf.get('network') t_conf['outbound']['streamSettings']['wsSettings'] = None t_conf['outbound']['streamSettings']['kcpSettings'] = None t_conf['outbound']['streamSettings']['tcpSettings'] = None network = conf.get('network') if network == 'ws': t_conf['outbound']['streamSettings']['wsSettings'] = \ { 'connectionReuse': True, 'path': None, 'headers': None } t_conf['outbound']['streamSettings']['wsSettings']['headers'] = (conf.get('headerType'), None)[conf.get('headerType') == 'none'] re = conf.get('requestHost') if ';' in re: r = re.split(';') t_conf['outbound']['streamSettings']['wsSettings']['path'] = r[0] t_conf['outbound']['streamSettings']['wsSettings']['headers']['Host'] = r[1] else: t_conf['outbound']['streamSettings']['wsSettings']['path'] = re elif network == 'kcp': t_conf['outbound']['streamSettings']['kcpSettings'] = \ { 'mtu': 1350, 'tti': 10, 'uplinkCapacity': 20, 'downlinkCapacity': 100, 'congestion': True, 'readBufferSize': 4, 'writeBufferSize': 4, 'header': { 'type': None, 'request': None, 'response': None } } t_conf['outbound']['streamSettings']['kcpSettings']['header']['type'] = (conf.get('headerType'), 'none')[conf.get('headerType') == 'none'] elif network == 'tcp': t_conf['outbound']['streamSettings']['tcpSettings'] = \ { 'connectionReuse': True, 'header': { 'type': None, 'request': { 'version': '1.1', 'method': 'GET', 'path': [ '/' ], 'headers': { 'Host': [ '' ], 'User-Agent': [ 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.75 Safari/537.36', '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' ], 'Accept-Encoding': [ 'gzip,deflate' ], 'Connection': [ 'keep-alive' ], 'Pragma': 'no-cache' } }, 'response': { 'version': '1.1', 'status': '200', 'reason': 'OK', 'headers': { 'Content-Type': [ 'application/octet-stream', 'video/mpeg' ], 'Transfer-Encoding': [ 'chunked' ], 'Connection': [ 'keep-alive' ], 'Pragma': 'no-cache' } } } } if conf.get('headerType') == 'none' or conf.get('headerType') == '': t_conf['outbound']['streamSettings']['tcpSettings'] = None else: t_conf['outbound']['streamSettings']['tcpSettings']['header']['type'] = (conf.get('headerType'), None)[conf.get('headerType') == 'none'] t_conf['outbound']['streamSettings']['tcpSettings']['header']['request']['headers']['Host'] = conf.get('requestHost', '').split(',') elif network == 'h2' or network == 'http': t_conf['outbound']['streamSettings']['httpSettings'] = \ { 'path': None, 'host': [] } t_conf['outbound']['streamSettings']['httpSettings']['path'] = conf.get('path') t_conf['outbound']['streamSettings']['httpSettings']['Host'] = list(conf.get('requestHost')) else: raise NameError("unkonwn network", network) t_conf['outbound']['streamSettings']['security'] = (conf.get('streamSecurity'), '')[conf.get('streamSecurity') == None] t_conf['outbound']['protocol'] = 'vmess' port = get_free_tcp_port() if not port: raise Exception('Error getting local port.') t_conf['inbound']['port'] = int(port) t_conf['inbound']['listen'] = '127.0.0.1' t_conf['inbound']['protocol'] = 'socks' t_conf['inbound']['settings']['auth'] = 'noauth' t_conf['inbound']['settings']['ip'] = '127.0.0.1' except: raise #remarks = conf.get('remarks', None) temp_file = 'temp_file_{}_{}.json'.format(int(round(time.time() * 1000)), port) with open(temp_file, 'w') as f: json.dump(t_conf, f) if not os.path.isfile(temp_file): logging.debug('here missing missing: '.format(temp_file)) cmd_line = 'v2ray.exe --config={}'.format(temp_file) logging.debug('checking id: {} with port {}'.format(users['id'], port)) try: p = subprocess.Popen(cmd_line, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.DEVNULL) except: raise time.sleep(0.8) return p, port, temp_file def test_connect(port): perfect = 9 sum_r = 0 time.sleep(2) get_latency(port) time.sleep(2) for i in range(1,10): time.sleep(0.1) r, p = get_latency(port) if p is True: print('.', end='') sum_r += r perfect -= 1 if perfect != 9: times = 9 - int(perfect) s = sum_r / times latency = format(s, '0.2f') else: latency = 0 return perfect, latency def get_latency(port): test_urls = 'https://www.google.com/' 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'} proxies = {} proxies['http'] = 'socks5h://127.0.0.1:{}'.format(port) proxies['https'] = 'socks5h://127.0.0.1:{}'.format(port) start_time = time.time() s = requests.Session() try: s.mount(test_urls, HTTPAdapter(max_retries=0)) r = s.get(test_urls, proxies=proxies, headers=headers, verify=True, timeout=(7,15), allow_redirects=False, cookies={'':''}) r.raise_for_status() connectivity = True except Exception as err: print(err) connectivity = False end_time = time.time() latency_time = end_time - start_time return latency_time, connectivity def sub_proc(proc, single_json, t_conf): proc, port, temp_file = run_v(single_json, t_conf) try: perfect, latency = test_connect(port) except: raise proc.kill() logging.debug('process what ? {}'.format(proc)) stdout, stderr = proc.communicate() logging.debug('stdout of process{}'.format(stdout)) logging.debug('stdERR of process{}'.format(stderr)) if not os.path.isfile(temp_file): logging.debug('missing temp config file: {}'.format(temp_file)) raise ValueError('config file missing...') os.remove(temp_file) return single_json, perfect, latency def multi_proc(configs): global t_conf multiprocessing.freeze_support() proc = multiprocessing.Pool(16) proc_result = [] if isinstance(configs, dict): t = [] t.append(configs.copy()) configs = t for i, ei in enumerate(configs): r = proc.apply_async(sub_proc, args=(i, ei, t_conf)) proc_result.append(r) proc.close() proc.join() configs_all = [] for k in proc_result: configs_all.append(k.get()) info = [] configs_good_temp = [] configs_bad_temp = [] configs_bad = [] configs_good = [] for j in configs_all: info.append((j[1],j[2])) if j[1] == 9: configs_bad_temp.append(j[0]) else: configs_good_temp.append(j) if configs_good_temp: configs_good_temp.sort(key = lambda x:x[2]) configs_good_temp.sort(key = lambda x:x[1]) for i in configs_good_temp: r = re.match('^\d_\d\.\d{2}_(.*)', i[0].get('remarks')) if r: remarks = r.group(1) else: remarks = i[0].get('remarks') remarks = '{}_{}_{}'.format(i[1], i[2], remarks) i[0]['remarks'] = remarks[:60] configs_good.append(i[0]) if configs_bad_temp: for k in configs_bad_temp: r = re.match('^\d_\d\.\d{2}_(.*)', k.get('remarks')) if r: remarks = r.group(1) else: remarks = k.get('remarks') remarks = '{}_{}_HCR_{}'.format('9', '9.99', remarks) k['remarks'] = remarks[:60] configs_bad.append(k) return configs_good, configs_bad, info def deDup(conf): dest_list = [] dup_list = [] global other_list other_list = [] try: for i, ei in enumerate(conf): if int(ei.get('configType')) != 1: other_list.append(ei) continue for j, ej in enumerate(conf[i+1:]): if ( (ei['address'] == ej['address']) and (int(ei['port']) == int(ej['port'])) and (ei['id'] == ej['id']) and (int(ei['alterId']) == int(ej['alterId'])) and (ei['network'] == ej['network']) and (ei['headerType'] == ej['headerType']) and (ei['requestHost'] == ej['requestHost']) and (ei['streamSecurity'] == ej['streamSecurity']) and (int(ei['configType']) == int(ej['configType'])) ): dup_list.append(ei) dest_found = False break else: dest_found = True if dest_found: dest_list.append(ei) except KeyError as err: print('The No.{} record seems wrong with {}...'.format((i+j), err)) raise deDup_info = '**** All records: {}, found dups: {}, unique VMESS records: {}, non VMESS records: {}'.format(len(conf), len(dup_list), len(dest_list), len(other_list)) return dest_list, dup_list, deDup_info def get_free_tcp_port(): tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM) tcp.bind(('', 0)) addr, port = tcp.getsockname() tcp.close() return port def rewrite_socks_dns(address, timeout=None, source_address=None): sock = socks.socksocket() sock.connect(address) return sock def kill(): cmd_task = 'taskkill /f /t /im' kill_list = ['v2rayN.exe', 'v2ray.exe', 'v2ctl.exe', 'wv2ray.exe'] try: for i in kill_list: cmd = '{} {}'.format(cmd_task, i) s = subprocess.call(cmd) print(s) except: pass def files(wildcard_file_args): if wildcard_file_args: file_list = (glob(name) for name in wildcard_file_args) file_list = [i for j in file_list for i in j] file_list = list(set(file_list)) else: file_list = None return file_list def main_dev(): global json_files global uri_files global only_test parser = argparse.ArgumentParser( description='de-duplicate, merge, test, benchmark and backup tools for v2ray with v2rayN, M$ Windows') parser.add_argument('-j', metavar='input JSON filenames', dest='json_files', default=False, type=str, nargs='+', help='Input json filenames') parser.add_argument('-l', metavar='input Link filenames', dest='uri_files', default=False, type=str, nargs='+', help='Input uri link filenames') parser.add_argument('-t', dest='only_test', action='store_true', default=False, help='Just test, no output file') parser.add_argument('-v',action='version', version='0.2') args = parser.parse_args() json_files = files(args.json_files) uri_files = files(args.uri_files) only_test = args.only_test if not args.json_files and not args.uri_files: json_files = ['guiNConfig.json'] def main(): # socket.socket = socks.socksocket # socket.create_connection = rewrite_socks_dns global json_files global uri_files global only_test global other_list main_dev() kill() global t_conf t_conf = \ { 'log': { 'access': None, 'error': None, 'loglevel': None }, 'inbound': { 'port': 48080, 'listen': '127.0.0.1', 'protocol': 'socks', 'settings': { 'auth': 'noauth', 'udp': False, 'ip': '127.0.0.1', 'clients': None }, 'streamSettings': None }, 'outbound': { 'tag': 'agentout', 'protocol': 'vmess', 'settings': { 'vnext': [ { 'address': '213.213.213.213', 'port': 23333, 'users': [ { 'id': 'dddda000-bbbb-4444-2222-fffff6666666', 'alterId': 100, 'security': 'aes-128-gcm' } ] } ], 'servers': None }, 'streamSettings': { 'network': None, 'security': None, 'tcpSettings': None, 'kcpSettings': None, 'wsSettings': None }, 'mux': { 'enabled': False } }, 'inboundDetour': None, 'outboundDetour': [ { 'protocol': 'freedom', 'settings': { 'response': None }, 'tag': 'direct' }, { 'protocol': 'blackhole', 'settings': { 'response': { 'type': 'http' } }, 'tag': 'blockout' } ], 'dns': { 'servers': [ '8.8.8.8', '8.8.4.4', '114.114.114.114' ] }, 'routing': { 'strategy': 'rules', 'settings': { 'domainStrategy': 'IPIfNonMatch', 'rules': [ { 'type': 'field', 'port': None, 'outboundTag': 'direct', 'ip': [ '0.0.0.0/8', '10.0.0.0/8', '100.64.0.0/10', '127.0.0.0/8', '169.254.0.0/16', '172.16.0.0/12', '192.0.0.0/24', '192.0.2.0/24', '192.168.0.0/16', '198.18.0.0/15', '198.51.100.0/24', '203.0.113.0/24', '::1/128', 'fc00::/7', 'fe80::/10' ], 'domain': None } ] } } } t_guiNConfig = \ { "inbound": [{ "localPort": 28080, "protocol": "socks", "udpEnabled": False }], "logEnabled": False, "loglevel": "error", "index": 78, "vmess": [], "muxEnabled": False, "chinasites": False, "chinaip": False, "useragent": [], "userdirect": [], "userblock": [], "kcpItem": { "mtu": 1350, "tti": 10, "uplinkCapacity": 20, "downlinkCapacity": 100, "congestion": True, "readBufferSize": 4, "writeBufferSize": 4 }, "autoSyncTime": False, "sysAgentEnabled": False, "listenerType": 1, "urlGFWList": "" } configs = [] links = [] guiNConfig = None data = None vmess = None if json_files: try: for i in json_files: if not os.path.isfile(i): print('****Bad file path or name: {} ...'.format(i)) break else: with open(i, 'r', encoding='utf-8') as f: data = json.load(f) if not data: print('can not found specified format on {}'.format(i)) break if isinstance(data, dict) and data.get('inbound') and data.get('vmess'): vmess = data.get('vmess') configs.extend(vmess) guiNConfig = data elif isinstance(data, list) and data[0].get('address'): configs.extend(data) else: print('can not found specified format on {}'.format(i)) except: raise guiNConfig = t_guiNConfig if not guiNConfig else guiNConfig if configs: conf, _, deDup_info = deDup(configs) else: print('No legal data input...') sys.exit() print(deDup_info) try: input('**** Waiting for comfirm, Ctrl+C to interrupt or continue with Enter...') except KeyboardInterrupt: sys.exit() configs_good, configs_bad, _ = multi_proc(conf) kill() all_list = [] if configs_good: all_list.extend(configs_good) if configs_bad: all_list.extend(configs_bad) if other_list: all_list.extend(other_list) if deDup_info: print(deDup_info) if only_test: print('**** Only test, result: deDuped records {}, good ones {}, bad ones {}.'.format(len(all_list), len(configs_good), len(configs_bad))) else: guiNConfig['vmess'] = all_list guiNConfig_new = 'guiNConfig_{}_.json'.format(time.strftime('%Y-%m-%d_%H-%M-%S')) with open(guiNConfig_new, 'w') as f: json.dump(guiNConfig, f) print('***** Output to {}, {} records. good ones {}, bad ones {}. *****'.format(guiNConfig_new, len(all_list), len(configs_good), len(configs_bad))) if __name__ == '__main__': run_start = time.time() main() run_end = time.time() duration = run_end - run_start m, s = divmod(duration, 60) print('Cost time: {:.0f} mins {:.0f} seconds. '.format(m, s))