├── shadowray ├── common │ ├── __init__.py │ ├── B64.py │ └── utils.py ├── config │ ├── __init__.py │ ├── v2_repo.py │ ├── v2ray.py │ └── version.py ├── core │ ├── __init__.py │ ├── execute.py │ ├── server.py │ ├── manager.py │ └── configuration.py ├── subscribe │ ├── __init__.py │ └── parser.py └── __init__.py ├── requirements.txt ├── .gitignore ├── LICENSE ├── setup.py └── README.md /shadowray/common/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /shadowray/config/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /shadowray/core/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /shadowray/subscribe/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests 2 | bullet 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | v2ray 3 | venv 4 | dist 5 | shadowray.egg* -------------------------------------------------------------------------------- /shadowray/config/v2_repo.py: -------------------------------------------------------------------------------- 1 | RELEASE_API = "https://api.github.com/repos/v2ray/v2ray-core/releases/latest" -------------------------------------------------------------------------------- /shadowray/common/B64.py: -------------------------------------------------------------------------------- 1 | import base64 2 | 3 | 4 | def decode(text): 5 | n = len(text) 6 | missing_padding = 4 - n % 4 7 | 8 | if missing_padding: 9 | text += '=' * missing_padding 10 | text = bytes(text, encoding='utf8') 11 | return str(base64.b64decode(text), encoding='utf8') 12 | -------------------------------------------------------------------------------- /shadowray/config/v2ray.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | PROJECT_PATH = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) 4 | 5 | USER_HOME = os.path.expanduser("~") 6 | 7 | SHADOWRAY_CONFIG_FOLDER = os.path.join(PROJECT_PATH, ".shadowray") 8 | 9 | V2RAY_FOLDER = os.path.join(SHADOWRAY_CONFIG_FOLDER, "v2ray") 10 | V2RAY_BINARY = os.path.join(V2RAY_FOLDER, "v2ray") 11 | V2CTL_BINARY = os.path.join(V2RAY_FOLDER, "v2ctl") 12 | EXECUTE_ARGS = "-config=stdin:" 13 | 14 | RESOURCES_FOLDER = os.path.join(SHADOWRAY_CONFIG_FOLDER, "resources") 15 | SUBSCRIBE_FILE = os.path.join(RESOURCES_FOLDER, "subscribes.json") 16 | SERVER_FILE = os.path.join(RESOURCES_FOLDER, "servers.json") 17 | 18 | PROJECT_CONFIG_FILE = os.path.join(SHADOWRAY_CONFIG_FOLDER, "config.json") 19 | 20 | SERVER_KEY_FROM_ORIGINAL = "servers_original" 21 | SERVER_KEY_FROM_SUBSCRIBE = "servers_subscribe" 22 | 23 | V2RAY_PID_FILE = os.path.join(SHADOWRAY_CONFIG_FOLDER, "pid") 24 | 25 | CONFIG_STREAM_FILE = os.path.join(SHADOWRAY_CONFIG_FOLDER, "stdin") 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Du Rong 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 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | from shadowray.config.version import VERSION_ID 3 | 4 | with open("README.md", "r") as fh: 5 | long_description = fh.read() 6 | 7 | setuptools.setup( 8 | name="shadowray", 9 | version=VERSION_ID, 10 | author="RMT", 11 | author_email="d.rong@outlook.com", 12 | description="A useful client of v2ray for linux", 13 | long_description=long_description, 14 | long_description_content_type="text/markdown", 15 | url="https://github.com/RMTT/Shadowray", 16 | packages=setuptools.find_packages(), 17 | keywords=("pip", "v2ray", "shadowsocks", "shadowray"), 18 | install_requires=["requests", "bullet"], 19 | python_requires='>=3', 20 | license="MIT", 21 | platform=['any'], 22 | project_urls={ 23 | 'Tracker': 'https://github.com/RMTT/Shadowray/issues', 24 | }, 25 | classifiers=[ 26 | "Programming Language :: Python :: 3", 27 | "License :: OSI Approved :: MIT License", 28 | "Operating System :: POSIX :: Linux", 29 | ], 30 | entry_points={ 31 | 'console_scripts': [ 32 | 'shadowray=shadowray:main', 33 | ], 34 | }, 35 | ) 36 | -------------------------------------------------------------------------------- /shadowray/core/execute.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | from shadowray.config.v2ray import EXECUTE_ARGS, V2RAY_PID_FILE, CONFIG_STREAM_FILE 3 | from shadowray.common.utils import write_to_file 4 | import multiprocessing 5 | import time 6 | import os, sys 7 | 8 | 9 | class Execute: 10 | def __init__(self, binary): 11 | self.v2ray_binary = binary 12 | self.config = None 13 | 14 | def __exec(self): 15 | s = subprocess.Popen([self.v2ray_binary, EXECUTE_ARGS], stdin=subprocess.PIPE) 16 | write_to_file(V2RAY_PID_FILE, "w", str(s.pid)) 17 | s.communicate(self.config) 18 | 19 | def exec(self, config: str, daemon=False): 20 | self.config = bytes(config, encoding='utf8') 21 | 22 | process = multiprocessing.Process(target=self.__exec, daemon=daemon) 23 | process.start() 24 | 25 | if daemon is True: 26 | time.sleep(1) 27 | 28 | def stop(self): 29 | 30 | if self.v2_process is not None: 31 | self.v2_process.kill() 32 | self.v2_process = None 33 | 34 | def restart(self, config=None): 35 | 36 | if self.v2_process is not None: 37 | self.v2_process.kill() 38 | 39 | if config is not None: 40 | self.v2_process = self.exec(config) 41 | elif self.config is not None: 42 | self.v2_process = self.exec(self.config) 43 | else: 44 | print("No config!!!") 45 | -------------------------------------------------------------------------------- /shadowray/config/version.py: -------------------------------------------------------------------------------- 1 | VERSION_ID = "0.1.6" 2 | AUTHOR = "RMT" 3 | EMAIL = "d.rong@outlook.com" 4 | 5 | COMMAND_LONG = ["version", "help", "subscribe-add=", "subscribe-update", "config-v2ray=", "config-subscribe=", 6 | "config-servers=", "autoconfig", "subscribe-update", "list", "start=", "config-file=", "port=", 7 | "servers-export=", "daemon", "stop", "v2ray-update", "ping"] 8 | COMMAND_SHORT = "vhs:lf:d" 9 | 10 | HELP_INFO = ''' 11 | --help[-h] print help message 12 | --version[-v] show current version of shadowray 13 | --subscribe-add ',' add subscribe 14 | --subscribe-update update subscribe 15 | --config-v2ray setup the path of v2ray binary 16 | --config-subscribe setup the path of subscribe file 17 | --config-servers setup the path of servers file 18 | --autoconfig setup basic setting automatically 19 | --subscribe-update [--port ] update subscribe 20 | --list[-l] show all servers 21 | --start[-s] [-d|--daemon] start v2ray,the '-d or --daemon argument used to run v2ray as a daemon' 22 | --config-file[-f] run v2ray use the config file that provided by yourself 23 | --servers-export : export the config of specified index 24 | --stop stop v2ray 25 | --v2ray-update update v2ray core to latest 26 | --ping ping server 27 | ''' 28 | -------------------------------------------------------------------------------- /shadowray/common/utils.py: -------------------------------------------------------------------------------- 1 | import re 2 | import subprocess 3 | 4 | import requests 5 | import time 6 | 7 | 8 | def parse_yes_or_no(text): 9 | text = str(text).lower() 10 | 11 | if text == "yes" or text == "y": 12 | return True 13 | elif text == "no" or text == "n": 14 | return False 15 | else: 16 | return None 17 | 18 | 19 | def print_progress(percent, width=60, extra=''): 20 | if percent > 100: 21 | percent = 100 22 | 23 | format_str = ('[%%-%ds]' % width) % ('#' * int(percent * width / 100.)) 24 | print('\r%s %.2f%% %s' % (format_str, percent, extra), end='') 25 | 26 | 27 | def download_file(url, filename, show_progress=False): 28 | r = requests.get(url, stream=True) 29 | 30 | length = float(r.headers['content-length']) 31 | 32 | f = open(filename, "wb") 33 | 34 | count = 0 35 | 36 | time_s = time.time() 37 | speed = 0 38 | count_s = 0 39 | for chunk in r.iter_content(chunk_size=512): 40 | if chunk: 41 | f.write(chunk) 42 | count += len(chunk) 43 | 44 | if show_progress: 45 | percent = (count / length) * 100. 46 | 47 | time_e = time.time() 48 | if time_e - time_s > 1: 49 | speed = (count - count_s) / (time_e - time_s) / 1024 / 1024 50 | count_s = count 51 | time_s = time_e 52 | 53 | print_progress(percent, extra='%.2fM/S' % speed) 54 | 55 | f.close() 56 | 57 | 58 | def find_arg_in_opts(opts, key): 59 | for k, v in opts: 60 | if k == key: 61 | return v 62 | 63 | return None 64 | 65 | 66 | def write_to_file(filename, mode, content): 67 | f = open(filename, mode) 68 | f.write(content) 69 | f.close() 70 | 71 | 72 | def ping(host, times=3, timeout=1): 73 | command = ['ping', "-c", str(times), host, "-W", str(timeout)] 74 | 75 | r = subprocess.run(command, stdout=subprocess.PIPE) 76 | 77 | reg = r'min/avg/max/mdev = ([0-9]+.[0-9]+)/([0-9]+.[0-9]+)' 78 | s = re.search(reg, str(r.stdout)) 79 | 80 | if s is not None: 81 | return s.group(1) 82 | else: 83 | return -1 84 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Notice 2 | This project has stopped. You can use [Clash](https://github.com/Dreamacro/clash) and [subconverter](https://github.com/tindy2013/subconverter) to implement the same effect, and they can do better. 3 | # Shadowray 4 | An useful client of v2ray for linux 5 | 6 | ## Simple usage 7 | ```bash 8 | pip install shadowray 9 | shadowray --autoconfig 10 | shadowray --subscribe-add 'name,url' 11 | shadowray --list 12 | shadowray -s 1 13 | ``` 14 | for more detail about command: 15 | ```bash 16 | shadowray --help 17 | ``` 18 | ### Default config 19 | #### Inbound: 20 | + protocol : socks5 21 | + port: 1082 22 | + auth: noauth 23 | #### Outbound: 24 | + Traffic camouflage : tcp 25 | ## Usage 26 | ### Basic config 27 | + subscribe.json : the file used to save subscribes,you can specify it by using `shadowray --config-subscribe `,but the file must be created by yourself 28 | + servers.json : the file used to save servers,you can specify it by using `shadowray --config-servers `,but the file must be created by yourself 29 | + v2ray : you should specify the folder of v2ray-core,using `shadowray --config-v2ray ` 30 | 31 | ### For simplicity 32 | Using `shadowray --autoconfig`,then it will complete the basic config automatically,include that downloading the lasted v2ray-core 33 | 34 | ### Subscribe 35 | #### Add a subscribe 36 | you can use `shadowray --subscribe-add ','` to add a subscribe. 37 | > Don't forget the `''` 38 | 39 | #### Update subscribes 40 | Using `shadowray --subscribe-update` to update all subscribes.Meanwhile,you can use `--port ` to specify a port of inbound. 41 | 42 | ### Proxy 43 | #### List server 44 | To see all available servers(proxies),using `shadowray [--list|-l]`.Then you will see some index,name of proxies,protocol of proxies,the index of proxies used to start a proxy 45 | 46 | #### Start a proxy 47 | For starting a proxy,you can use `shadowray [--start|-s] ` 48 | > For running v2ray as a daemon,by using `--daemon` or `-d` 49 | #### Stop daemon 50 | Using `shadowray --stop` 51 | ### Specify a config file of v2ray 52 | You can use your config file via `[--config-self|-f] `.For example, 53 | `shadowray --config-self ~/config.json` or `shadowray -f ~/config.json` 54 | ### Export config 55 | Using `shadowray --servers-export :` to export the specified config to file that specified by 56 | -------------------------------------------------------------------------------- /shadowray/core/server.py: -------------------------------------------------------------------------------- 1 | import json 2 | from shadowray.config.v2ray import SERVER_FILE 3 | from shadowray.config.v2ray import SERVER_KEY_FROM_SUBSCRIBE, SERVER_KEY_FROM_ORIGINAL 4 | 5 | 6 | class Server: 7 | def __init__(self, filename=None): 8 | self.__servers = json.loads('{"servers_subscribe": [] ,"servers_original": []}') 9 | 10 | self.__filename = SERVER_FILE 11 | if filename is not None: 12 | f = open(filename, 'r') 13 | self.__servers = json.load(f) 14 | f.close() 15 | self.__filename = filename 16 | 17 | def save(self, filename=None): 18 | if filename is None: 19 | filename = self.__filename 20 | 21 | f = open(filename, 'w') 22 | f.write(json.dumps(self.__servers)) 23 | f.close() 24 | 25 | def add(self, protocol, config, ps, key, host): 26 | self.__servers[key].append({ 27 | "protocol": protocol, 28 | "config": config, 29 | "ps": ps, 30 | "host": host 31 | }) 32 | 33 | def get(self, index): 34 | if self.__servers is None: 35 | return None 36 | return self.__servers[index] 37 | 38 | def get_servers(self): 39 | return self.__servers 40 | 41 | @property 42 | def original_servers_number(self): 43 | return len(self.__servers[SERVER_KEY_FROM_ORIGINAL]) 44 | 45 | @property 46 | def subscribe_servers_number(self): 47 | return len(self.__servers[SERVER_KEY_FROM_SUBSCRIBE]) 48 | 49 | @property 50 | def servers_number(self): 51 | return self.subscribe_servers_number + self.original_servers_number 52 | 53 | def get_server(self, index): 54 | if index >= self.servers_number: 55 | print("Index out of range.") 56 | return None 57 | 58 | if index < self.original_servers_number: 59 | return self.__servers[SERVER_KEY_FROM_ORIGINAL][index] 60 | else: 61 | return self.__servers[SERVER_KEY_FROM_SUBSCRIBE][index - self.original_servers_number] 62 | 63 | def get_config(self, index): 64 | if index >= self.servers_number: 65 | print("Index out of range.") 66 | return None 67 | 68 | if index < self.original_servers_number: 69 | return self.__servers[SERVER_KEY_FROM_ORIGINAL][index]['config'] 70 | else: 71 | return self.__servers[SERVER_KEY_FROM_SUBSCRIBE][index - self.original_servers_number]['config'] 72 | 73 | def clear(self, key): 74 | self.__servers[key].clear() 75 | -------------------------------------------------------------------------------- /shadowray/core/manager.py: -------------------------------------------------------------------------------- 1 | from shadowray.subscribe.parser import Parser 2 | from shadowray.core.server import Server 3 | from shadowray.core.execute import Execute 4 | from shadowray.config.v2ray import SERVER_KEY_FROM_ORIGINAL, SERVER_KEY_FROM_SUBSCRIBE 5 | import json 6 | 7 | 8 | class Manager: 9 | def __init__(self, subscribe_file_name=None, server_file_name=None, binary=None): 10 | if subscribe_file_name is not None: 11 | self.__subscribe = Parser(filename=subscribe_file_name) 12 | 13 | if server_file_name is not None: 14 | self.__server = Server(filename=server_file_name) 15 | 16 | if binary is not None: 17 | self.__execute = Execute(binary=binary) 18 | 19 | def add_subscribe(self, name, url): 20 | self.__subscribe.add(name, url) 21 | 22 | def update_subscribe(self, show_info=False, **kwargs): 23 | self.__subscribe.update(show_info=show_info, **kwargs) 24 | 25 | self.__server.clear(SERVER_KEY_FROM_SUBSCRIBE) 26 | 27 | s = self.__subscribe.get_servers() 28 | 29 | for i in s: 30 | self.__server.add(protocol=i['protocol'], config=i['config'], ps=i['ps'], key=SERVER_KEY_FROM_SUBSCRIBE, 31 | host=i['host']) 32 | 33 | def delete_subscribe(self, name): 34 | self.__subscribe.delete(name) 35 | 36 | def show_servers(self): 37 | servers = self.__server.get_servers() 38 | 39 | count = 0 40 | for s in servers[SERVER_KEY_FROM_ORIGINAL]: 41 | count += 1 42 | print(str(count) + " ---- " + s['ps'] + " ---- " + s['protocol']) 43 | 44 | for s in servers[SERVER_KEY_FROM_SUBSCRIBE]: 45 | count += 1 46 | print(str(count) + " ---- " + s['ps'] + " ---- " + s['protocol']) 47 | 48 | def proxy(self, index=None, config=None, daemon=False): 49 | if config is not None: 50 | self.__execute.exec(json.dumps(config), daemon=daemon) 51 | elif index is not None: 52 | self.__execute.exec(json.dumps(self.__server.get_config(index)), daemon=daemon) 53 | 54 | def save(self): 55 | self.__server.save() 56 | self.__subscribe.save() 57 | 58 | def save_servers(self): 59 | self.__server.save() 60 | 61 | def save_subscribe(self): 62 | self.__subscribe.save() 63 | 64 | def get_server(self, index): 65 | return self.__server.get_server(index) 66 | 67 | @property 68 | def server_number(self): 69 | return self.__server.original_servers_number + self.__server.subscribe_servers_number 70 | -------------------------------------------------------------------------------- /shadowray/subscribe/parser.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | from shadowray.common.B64 import decode 4 | from shadowray.config.v2ray import SUBSCRIBE_FILE 5 | from shadowray.core.configuration import Configuration 6 | 7 | 8 | class Parser: 9 | def __init__(self, filename=None): 10 | self.servers = [] 11 | self.filename = None 12 | 13 | self.subscribes = json.loads("{}") 14 | if filename is not None: 15 | f = open(filename, "r") 16 | self.subscribes = json.load(f) 17 | f.close() 18 | 19 | self.filename = filename 20 | 21 | def get_url(self, url, **kwargs): 22 | r = requests.get(url).text 23 | text = decode(r) 24 | 25 | text = text.split('\n') 26 | 27 | for t in text: 28 | if len(t) is 0: 29 | continue 30 | t = t.split("://") 31 | 32 | t[1] = json.loads(decode(t[1])) 33 | 34 | config = Configuration() 35 | 36 | port = 1082 37 | if kwargs.get("port") is not None: 38 | port = kwargs.get("port") 39 | inbound = Configuration.Inbound(port, "127.0.0.1", "socks") 40 | socks = Configuration.ProtocolSetting.Inbound.Socks() 41 | inbound.set_settings(socks) 42 | config.add_inbound(inbound) 43 | 44 | if t[0] == "vmess": 45 | outbound = Configuration.Outbound("vmess") 46 | vmess = Configuration.ProtocolSetting.Outbound.VMess() 47 | vmess_server = Configuration.ProtocolSetting.Outbound.VMess.Server(addr=t[1]['add'], 48 | port=int(t[1]['port'])) 49 | vmess_server.add_user(id=t[1]['id'], 50 | aid=int(t[1].get('aid', 0)), 51 | security=t[1].get('type', 'auto'), 52 | level=t[1].get('v', 0)) 53 | vmess.add_server(vmess_server) 54 | outbound.set_settings(vmess) 55 | 56 | stream = Configuration.StreamSetting(type=Configuration.StreamSetting.STREAMSETTING, 57 | network=t[1]['net']) 58 | outbound.set_stream(stream) 59 | config.add_ontbound(outbound) 60 | 61 | self.servers.append({ 62 | "protocol": t[0], 63 | "config": config.json_obj, 64 | "ps": t[1]['ps'], 65 | "host": t[1]['add'] 66 | }) 67 | 68 | def update(self, name=None, show_info=False, **kwargs): 69 | self.servers.clear() 70 | if name is None: 71 | for j in self.subscribes: 72 | if show_info: 73 | print("update %s : %s" % (j, self.subscribes[j])) 74 | self.get_url(self.subscribes[j], **kwargs) 75 | else: 76 | if show_info: 77 | print("update %s : %s" % (name, self.subscribes[name])) 78 | self.get_url(self.subscribes[name], **kwargs) 79 | 80 | def save(self, filename=None): 81 | if filename is None: 82 | filename = SUBSCRIBE_FILE 83 | f = open(filename, 'w') 84 | f.write(json.dumps(self.subscribes)) 85 | f.close() 86 | 87 | def add(self, name, url): 88 | self.subscribes[name] = url 89 | 90 | def delete(self, name): 91 | del self.subscribes[name] 92 | 93 | def get_servers(self): 94 | return self.servers 95 | -------------------------------------------------------------------------------- /shadowray/__init__.py: -------------------------------------------------------------------------------- 1 | import sys, getopt 2 | from shadowray.config.version import VERSION_ID, HELP_INFO, COMMAND_LONG, COMMAND_SHORT 3 | from shadowray.config.v2ray import PROJECT_CONFIG_FILE 4 | from shadowray.config.v2_repo import RELEASE_API 5 | from shadowray.config.version import * 6 | from shadowray.common.utils import parse_yes_or_no 7 | from shadowray.common.utils import download_file 8 | from shadowray.common.utils import print_progress, find_arg_in_opts, write_to_file, ping 9 | from shadowray.config.v2ray import V2RAY_BINARY, SUBSCRIBE_FILE, SERVER_FILE, V2RAY_FOLDER, RESOURCES_FOLDER, \ 10 | PROJECT_PATH, V2CTL_BINARY, SHADOWRAY_CONFIG_FOLDER, V2RAY_PID_FILE 11 | from shadowray.core.manager import Manager 12 | import requests 13 | import json 14 | import platform 15 | import zipfile 16 | import os, stat, signal 17 | from bullet import ScrollBar 18 | 19 | 20 | def create_basic_config_file(): 21 | if os.path.exists(SHADOWRAY_CONFIG_FOLDER) is False: 22 | os.mkdir(SHADOWRAY_CONFIG_FOLDER) 23 | 24 | if os.path.exists(PROJECT_CONFIG_FILE) is False: 25 | f = open(PROJECT_CONFIG_FILE, 'w') 26 | f.write('{ \ 27 | "v2ray_binary": null, \ 28 | "servers_file": null, \ 29 | "subscribe_file": null \ 30 | }') 31 | f.close() 32 | 33 | 34 | def parse_json_from_file(path): 35 | f = open(path, 'r') 36 | j = json.load(f) 37 | f.close() 38 | return j 39 | 40 | 41 | def have_config(): 42 | create_basic_config_file() 43 | j = parse_json_from_file(PROJECT_CONFIG_FILE) 44 | 45 | if j['v2ray_binary'] is None: 46 | print( 47 | "Binary file of v2ray not config.\nYou can config it by --config-v2ray path.Also, you can use --autoconfig to config it automatic.") 48 | return False 49 | 50 | if j['subscribe_file'] is None: 51 | print( 52 | "Subscribe file not config.\nYou can config it by --config-subscribe path.Also, you can use --autoconfig to config it automatic.") 53 | return False 54 | 55 | if j['servers_file'] is None: 56 | print( 57 | "Servers file not config.\nYou can config it by --config-servers path.Also, you can use --autoconfig to config it automatic.") 58 | return False 59 | 60 | return True 61 | 62 | 63 | def add_subscribe(args): 64 | v = args.split(',') 65 | 66 | j = parse_json_from_file(PROJECT_CONFIG_FILE) 67 | 68 | manager = Manager(subscribe_file_name=j['subscribe_file']) 69 | 70 | manager.add_subscribe(v[0], v[1]) 71 | manager.save_subscribe() 72 | 73 | 74 | def basic_config_v2ray(v2ray_binary=None): 75 | create_basic_config_file() 76 | 77 | if v2ray_binary is not None: 78 | f = open(PROJECT_CONFIG_FILE, 'r') 79 | j = json.load(f) 80 | f.close() 81 | 82 | j['v2ray_binary'] = v2ray_binary 83 | 84 | write_to_file(PROJECT_CONFIG_FILE, 'w', json.dumps(j)) 85 | 86 | 87 | def basic_config_subscribe(subscribe_file=None): 88 | create_basic_config_file() 89 | 90 | if subscribe_file is not None: 91 | f = open(PROJECT_CONFIG_FILE, 'r') 92 | j = json.load(f) 93 | f.close() 94 | 95 | j['subscribe_file'] = subscribe_file 96 | 97 | write_to_file(SUBSCRIBE_FILE, "w", "{}") 98 | 99 | write_to_file(PROJECT_CONFIG_FILE, 'w', json.dumps(j)) 100 | 101 | 102 | def basic_config_servers(servers_file=None): 103 | create_basic_config_file() 104 | 105 | if servers_file is not None: 106 | f = open(PROJECT_CONFIG_FILE, 'r') 107 | j = json.load(f) 108 | f.close() 109 | 110 | j['servers_file'] = servers_file 111 | 112 | write_to_file(servers_file, "w", '{"servers_subscribe": [] ,"servers_original": []}') 113 | 114 | write_to_file(PROJECT_CONFIG_FILE, 'w', json.dumps(j)) 115 | 116 | 117 | def download_latest_v2ray(): 118 | r = json.loads(requests.get(RELEASE_API).text) 119 | print("Latest publish date of v2ray-core: " + r['published_at']) 120 | print("Latest version of v2ray-core: " + r['tag_name']) 121 | 122 | os_str = str(platform.system()) 123 | arch = str(platform.architecture()[0]) 124 | 125 | if os_str.endswith("Darwin"): 126 | os_str = "macos" 127 | 128 | print("Platform: " + os_str + " " + arch) 129 | 130 | assets = r['assets'] 131 | 132 | download_url = None 133 | for asset in assets: 134 | name = str(asset['name']) 135 | 136 | if name.endswith("zip") and name.find(os_str.lower()) != -1: 137 | if os_str.endswith("macos") or name.find(arch[0:2]) != -1: 138 | download_url = str(asset['browser_download_url']) 139 | break 140 | 141 | if download_url is None: 142 | print("Download failed,you can download by yourself.") 143 | else: 144 | print('Download from %s' % download_url) 145 | 146 | download_file_name = os.path.join(V2RAY_FOLDER, download_url.split('/')[-1]) 147 | download_file(download_url, download_file_name, show_progress=True) 148 | 149 | print("\nU=uncompression:") 150 | f = zipfile.ZipFile(download_file_name, 'r') 151 | total = len(f.filelist) 152 | count = 0 153 | for file in f.filelist: 154 | f.extract(file, V2RAY_FOLDER) 155 | count += 1 156 | print_progress(100 * count / total, extra="%d/%d" % (count, total)) 157 | 158 | print("\nSuccess!") 159 | os.remove(download_file_name) 160 | 161 | os.chmod(path=V2RAY_BINARY, mode=stat.S_IXUSR) 162 | os.chmod(path=V2CTL_BINARY, mode=stat.S_IXUSR) 163 | basic_config_v2ray(V2RAY_BINARY) 164 | 165 | 166 | def auto_config(): 167 | create_basic_config_file() 168 | 169 | if os.path.exists(RESOURCES_FOLDER) is False: 170 | os.mkdir(RESOURCES_FOLDER) 171 | print("Create resources folder.(%s)" % RESOURCES_FOLDER) 172 | 173 | if os.path.exists(SERVER_FILE) is False: 174 | # os.mknod(SERVER_FILE) 175 | open(SERVER_FILE, "w").close() # for mac os x 176 | print("Create servers file.(%s)" % SERVER_FILE) 177 | write_to_file(SERVER_FILE, "w", '{"servers_subscribe": [] ,"servers_original": []}') 178 | 179 | if os.path.exists(SUBSCRIBE_FILE) is False: 180 | # os.mknod(SUBSCRIBE_FILE) 181 | open(SUBSCRIBE_FILE, "w").close() 182 | print("Create subscribe file.(%s)" % SUBSCRIBE_FILE) 183 | write_to_file(SUBSCRIBE_FILE, "w", '{}') 184 | 185 | basic_config_subscribe(SUBSCRIBE_FILE) 186 | basic_config_servers(SERVER_FILE) 187 | 188 | if os.path.exists(V2RAY_FOLDER) is False: 189 | os.mkdir(V2RAY_FOLDER) 190 | print("Create v2ray-core folder.(%s)" % V2RAY_FOLDER) 191 | 192 | s = None 193 | while s is None: 194 | t = input("Do you want to download the v2ray-core automatically?(Y/N)\n") 195 | s = parse_yes_or_no(t) 196 | 197 | if os.path.exists("v2ray") is False: 198 | os.mkdir("v2ray") 199 | 200 | if s: 201 | download_latest_v2ray() 202 | else: 203 | print("Please setup by yourself.") 204 | 205 | 206 | def update_subscribe(**kwargs): 207 | j = parse_json_from_file(PROJECT_CONFIG_FILE) 208 | manager = Manager(server_file_name=j['servers_file'], subscribe_file_name=j['subscribe_file']) 209 | 210 | manager.update_subscribe(show_info=True, **kwargs) 211 | manager.save_servers() 212 | 213 | 214 | def show_servers(): 215 | j = parse_json_from_file(PROJECT_CONFIG_FILE) 216 | manager = Manager(server_file_name=j['servers_file']) 217 | 218 | manager.show_servers() 219 | 220 | 221 | def proxy(index=None, config_file=None): 222 | j = parse_json_from_file(PROJECT_CONFIG_FILE) 223 | 224 | manager = Manager(server_file_name=j['servers_file'], binary=j['v2ray_binary']) 225 | 226 | daemon = False 227 | 228 | if "--daemon" in sys.argv or "-d" in sys.argv: 229 | daemon = True 230 | if index is not None: 231 | index = int(index) 232 | 233 | server = manager.get_server(index - 1) 234 | print("Choose: " + server['ps']) 235 | 236 | ports = [] 237 | for c in server['config']['inbounds']: 238 | if "port" in c: 239 | ports.append(c['port']) 240 | print("Local port: " + str(ports)) 241 | manager.proxy(config=server['config'], daemon=daemon) 242 | elif config_file is not None: 243 | config = parse_json_from_file(config_file) 244 | 245 | ports = [] 246 | for c in config['inbounds']: 247 | if "port" in c: 248 | ports.append(c['port']) 249 | print("Local port: " + str(ports)) 250 | 251 | manager.proxy(config=config, daemon=daemon) 252 | 253 | 254 | def servers_export(index, path): 255 | j = parse_json_from_file(PROJECT_CONFIG_FILE) 256 | 257 | manager = Manager(server_file_name=j['servers_file'], binary=j['v2ray_binary']) 258 | s = manager.get_server(index) 259 | write_to_file(path, "w", json.dumps(s['config'])) 260 | 261 | 262 | def prepare_ping(): 263 | j = parse_json_from_file(PROJECT_CONFIG_FILE) 264 | 265 | manager = Manager(server_file_name=j['servers_file'], binary=j['v2ray_binary']) 266 | 267 | choices = ["ping all"] 268 | 269 | l = manager.server_number 270 | for i in range(l): 271 | s = manager.get_server(i) 272 | choices.append(str(i + 1) + " " + s['ps'] + " " + s['host']) 273 | 274 | prompt = ScrollBar(height=10, choices=choices) 275 | result = prompt.launch() 276 | index = choices.index(result) 277 | print("index ps time") 278 | if index == 0: 279 | for i in range(l): 280 | s = manager.get_server(i) 281 | r = ping(host=s['host']) 282 | result = str(r) + "ms" if r != -1 else "timeout" 283 | print(str(i + 1) + " " + s['ps'] + " " + result) 284 | else: 285 | s = manager.get_server(index - 1) 286 | r = ping(host=s['host']) 287 | result = str(r) + "ms" if r != -1 else "timeout" 288 | print(str(index) + " " + s['ps'] + " " + result) 289 | 290 | 291 | def main(): 292 | try: 293 | opts, args = getopt.getopt(sys.argv[1:], shortopts=COMMAND_SHORT, longopts=COMMAND_LONG) 294 | except Exception as e: 295 | print("some error occurs when parsing your command args.") 296 | return 1 297 | 298 | if len(opts) == 0: 299 | print("Use shadowray --help to get more information.") 300 | 301 | for op_name, op_value in opts: 302 | if op_name in ("-h", "--help"): 303 | print(HELP_INFO) 304 | break 305 | 306 | if op_name in ("-v", "--version"): 307 | print("Shadowray: " + VERSION_ID) 308 | break 309 | 310 | if op_name in ("--subscribe-add",): 311 | if have_config() is True: 312 | add_subscribe(op_value) 313 | break 314 | 315 | if op_name in ("--config-v2ray",): 316 | basic_config_v2ray(op_value) 317 | break 318 | 319 | if op_name in ("--config-servers",): 320 | basic_config_servers(op_value) 321 | break 322 | 323 | if op_name in ("--config-subscribe",): 324 | basic_config_subscribe(op_value) 325 | break 326 | 327 | if op_name in ("--autoconfig",): 328 | auto_config() 329 | break 330 | 331 | if op_name in ("--subscribe-update",): 332 | port = 1082 333 | if "--port" in sys.argv: 334 | port = int(find_arg_in_opts(opts, "--port")) 335 | if have_config(): 336 | update_subscribe(port=port) 337 | break 338 | 339 | if op_name in ("--list", "-l"): 340 | if have_config(): 341 | show_servers() 342 | break 343 | 344 | if op_name in ("--start", "-s"): 345 | if have_config(): 346 | proxy(op_value) 347 | break 348 | 349 | if op_name in ("--config-file", "-f"): 350 | proxy(config_file=op_value) 351 | break 352 | 353 | if op_name in ("--servers-export",): 354 | if have_config(): 355 | v = op_value.split(':') 356 | servers_export(int(v[0]), v[1]) 357 | break 358 | 359 | if op_name in ("--stop",): 360 | f = open(V2RAY_PID_FILE, "r") 361 | s = f.read().strip() 362 | f.close() 363 | 364 | if s == "": 365 | print("no running process.") 366 | else: 367 | try: 368 | os.kill(int(s), signal.SIGKILL) 369 | except ProcessLookupError: 370 | print("Process[%s] not exist" % s) 371 | write_to_file(V2RAY_PID_FILE, "w", "") 372 | 373 | if op_name in ("--v2ray-update",): 374 | if have_config(): 375 | download_latest_v2ray() 376 | 377 | if op_name in ("--ping",): 378 | if have_config(): 379 | prepare_ping() 380 | 381 | # TODO: configure single proxy by users 382 | -------------------------------------------------------------------------------- /shadowray/core/configuration.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | 4 | class BaseConfig: 5 | def __init__(self, obj, **kwargs): 6 | self.__obj = obj 7 | 8 | for key in kwargs: 9 | self.__obj[key] = kwargs[key] 10 | 11 | def __str__(self): 12 | return json.dumps(self.__obj) 13 | 14 | @property 15 | def json(self): 16 | return str(self) 17 | 18 | @property 19 | def json_obj(self): 20 | return self.__obj 21 | 22 | 23 | class Configuration(BaseConfig): 24 | def __init__(self): 25 | self.__configuration = { 26 | "inbounds": [], 27 | "outbounds": [] 28 | } 29 | 30 | super().__init__(self.__configuration) 31 | 32 | def set_log(self, log_obj): 33 | self.__configuration['log'] = log_obj.json_obj 34 | 35 | def set_api(self, api_obj): 36 | self.__configuration['api'] = api_obj.json_obj 37 | 38 | def add_inbound(self, inbound_obj): 39 | self.__configuration['inbounds'].append(inbound_obj.json_obj) 40 | 41 | def add_ontbound(self, outbound_obj): 42 | self.__configuration['outbounds'].append(outbound_obj.json_obj) 43 | 44 | class Log(BaseConfig): 45 | DEBUG = "debug" 46 | INFO = "info" 47 | WARNING = "warning" 48 | ERROR = "error" 49 | NONE = "none" 50 | 51 | def __init__(self, access, error, level): 52 | self.__log = { 53 | "access": access, 54 | "error": error, 55 | "level": level 56 | } 57 | 58 | super().__init__(self.__log) 59 | 60 | class Inbound(BaseConfig): 61 | def __init__(self, port, listen, protocol, tag=None): 62 | self.__inbound = { 63 | "port": port, 64 | "listen": listen, 65 | "protocol": protocol, 66 | "settings": {}, 67 | "streamSettings": {}, 68 | "sniffing": { 69 | "enabled": True, 70 | "destOverride": ["http", "tls"] 71 | } 72 | } 73 | 74 | if tag is not None: 75 | self.__inbound['tag'] = tag 76 | super().__init__(self.__inbound) 77 | 78 | def set_settings(self, setting_boj): 79 | self.__inbound['settings'] = setting_boj.json_obj 80 | 81 | def set_tag(self, tag): 82 | self.__inbound['tag'] = tag 83 | 84 | def set_stream(self, stream_obj): 85 | self.__inbound['streamSettings'] = stream_obj.json_obj 86 | 87 | def set_sniffing(self, sniffing_obj): 88 | self.__inbound['sniffing'] = sniffing_obj.json_obj 89 | 90 | def set_allocate(self, allocate_obj): 91 | self.__inbound['allocate'] = allocate_obj.json_obj 92 | 93 | class Allocate: 94 | def __init__(self, strategy="always", refresh=5, concurrency=3): 95 | self.__allocate = { 96 | "strategy": strategy, 97 | "refresh": refresh, 98 | "concurrency": concurrency 99 | } 100 | 101 | def __str__(self): 102 | return json.dumps(self.__allocate) 103 | 104 | @property 105 | def json(self): 106 | return str(self) 107 | 108 | @property 109 | def json_obj(self): 110 | return self.__allocate 111 | 112 | class Sniffing: 113 | def __init__(self, enable, dest_override=None): 114 | if dest_override is None: 115 | dest_override = ["http", "tls"] 116 | 117 | self.__sniffing = { 118 | "enabled": enable, 119 | "destOverride": dest_override 120 | } 121 | 122 | def add_dest_override(self, p): 123 | self.__sniffing['destOverride'].append(p) 124 | 125 | def __str__(self): 126 | return json.dumps(self.__sniffing) 127 | 128 | @property 129 | def json(self): 130 | return str(self) 131 | 132 | @property 133 | def json_obj(self): 134 | return self.__sniffing 135 | 136 | class Outbound(BaseConfig): 137 | def __init__(self, protocol, tag=None, proxy_tag=None, send_through="0.0.0.0"): 138 | self.__outbound = { 139 | "sendThrough": send_through, 140 | "protocol": protocol, 141 | "settings": {}, 142 | "streamSettings": {}, 143 | } 144 | 145 | if tag is not None: 146 | self.__outbound['tag'] = tag 147 | if proxy_tag is not None: 148 | self.__outbound['proxySettings'] = proxy_tag 149 | super().__init__(self.__outbound) 150 | 151 | def set_settings(self, setting_boj): 152 | self.__outbound['settings'] = setting_boj.json_obj 153 | 154 | def set_tag(self, tag): 155 | self.__outbound['tag'] = tag 156 | 157 | def set_stream(self, stream_obj): 158 | self.__outbound['streamSettings'] = stream_obj.json_obj 159 | 160 | def set_mux(self, mux_obj): 161 | self.__outbound['mux'] = mux_obj.json_obj 162 | 163 | class Mux(BaseConfig): 164 | def __init__(self, enable, concurrency=8): 165 | self.__mux = { 166 | "enabled": enable, 167 | "concurrency": concurrency 168 | } 169 | super().__init__(self.__mux) 170 | 171 | class Api(BaseConfig): 172 | def __init__(self, tag): 173 | self.__api = { 174 | "tag": tag, 175 | "services": [] 176 | } 177 | super().__init__(self.__api) 178 | 179 | def add_service(self, service): 180 | self.__api['services'].append(service) 181 | 182 | class Dns(BaseConfig): 183 | def __init__(self, client_ip, tag): 184 | self.__dns = { 185 | "hosts": {}, 186 | "servers": [], 187 | "clientIp": client_ip, 188 | "tag": tag 189 | } 190 | super().__init__(self.__dns) 191 | 192 | def add_host(self, key, value): 193 | self.__dns['host'][key] = value 194 | 195 | def add_server(self, server_obj): 196 | self.__dns['servers'].append(server_obj.json_obj) 197 | 198 | class Server(BaseConfig): 199 | def __init__(self, addr, port): 200 | self.__server = { 201 | "address": addr, 202 | "port": port, 203 | "domains": [] 204 | } 205 | 206 | super().__init__(self.__server) 207 | 208 | def add_domain(self, domain): 209 | self.__server['domains'].append(domain) 210 | 211 | class Stats(BaseConfig): 212 | def __init__(self): 213 | self.__stats = {} 214 | super().__init__(self.__stats) 215 | 216 | class Routing(BaseConfig): 217 | def __init__(self, strategy): 218 | self.__routing = { 219 | "domainStrategy": strategy, 220 | "rules": [], 221 | "balancers": [] 222 | } 223 | super().__init__(self.__routing) 224 | 225 | def add_rule(self, rule_obj): 226 | self.__routing['rules'].append(rule_obj.json_obj) 227 | 228 | def add_balancer(self, balancer_obj): 229 | self.__routing['balancers'].append(balancer_obj.json_obj) 230 | 231 | class Rule(BaseConfig): 232 | def __init__(self, port, outbound_tag=None, balancer_tag=None, network=None, type="field"): 233 | self.__rule = { 234 | "type": type, 235 | "domain": [], 236 | "ip": [], 237 | "port": port, 238 | "network": network, 239 | "source": [], 240 | "user": [], 241 | "inboundTag": [], 242 | "protocol": [], 243 | "outboundTag": outbound_tag, 244 | "balancerTag": balancer_tag 245 | } 246 | super().__init__(self.__rule) 247 | 248 | def add_domain(self, domain): 249 | self.__rule['domain'].append(domain) 250 | 251 | def add_ip(self, ip): 252 | self.__rule['ip'].append(ip) 253 | 254 | def add_source(self, source): 255 | self.__rule['source'].append(source) 256 | 257 | def add_user(self, user): 258 | self.__rule['user'].append(user) 259 | 260 | def add_protocol(self, protocol): 261 | self.__rule['protocol'].append(protocol) 262 | 263 | def add_inbound_tag(self, tag): 264 | self.__rule['inboundTag'].append(tag) 265 | 266 | class Balancer(BaseConfig): 267 | def __init__(self, tag): 268 | self.__balancer = { 269 | "tag": tag, 270 | "selector": [] 271 | } 272 | super().__init__(self.__balancer) 273 | 274 | def add_selector(self, selector): 275 | self.__balancer['selector'].append(selector) 276 | 277 | class Policy(BaseConfig): 278 | def __init__(self): 279 | self.__policy = { 280 | "levels": {}, 281 | "system": {} 282 | } 283 | super().__init__(self.__policy) 284 | 285 | def add_level_policy(self, level, policy_obj): 286 | self.__policy['levels'][str(level)] = policy_obj.json_obj 287 | 288 | def set_system_policy(self, policy_obj): 289 | self.__policy['system'] = policy_obj.json_obj 290 | 291 | class LevelPolicy(BaseConfig): 292 | def __init__(self, stats_user_uplink, stats_user_downlink, handshake=4, conn_idle=300, uplink_only=2, 293 | downlink_only=5, buffer_size=None): 294 | self.__level_policy = { 295 | "handshake": handshake, 296 | "connIdle": conn_idle, 297 | "uplinkOnly": uplink_only, 298 | "downlinkOnly": downlink_only, 299 | "statsUserUplink": stats_user_uplink, 300 | "statsUserDownlink": stats_user_downlink, 301 | } 302 | if buffer_size is not None: 303 | self.__level_policy['bufferSize'] = buffer_size 304 | super().__init__(self.__level_policy) 305 | 306 | class SystemPolicy(BaseConfig): 307 | def __init__(self, stats_inbound_uplink, stats_inbound_downlink): 308 | self.__system_policy = { 309 | "statsInboundUplink": stats_inbound_uplink, 310 | "statsInboundDownlink": stats_inbound_downlink 311 | } 312 | super().__init__(self.__system_policy) 313 | 314 | class Reverse(BaseConfig): 315 | def __init__(self): 316 | self.__reverse = { 317 | "bridges": [], 318 | "portals": [] 319 | } 320 | 321 | super().__init__(self.__reverse) 322 | 323 | def add_bridge(self, bridge_obj): 324 | self.__reverse['bridges'].append(bridge_obj.json_obj) 325 | 326 | def add_portal(self, portal_obj): 327 | self.__reverse['portals'].append(portal_obj.json_obj) 328 | 329 | class Bridge(BaseConfig): 330 | def __init__(self, tag, domain): 331 | self.__bridge = { 332 | "tag": tag, 333 | "domain": domain 334 | } 335 | super().__init__(self.__bridge) 336 | 337 | class Portal(BaseConfig): 338 | def __init__(self, tag, domain): 339 | self.__bridge = { 340 | "tag": tag, 341 | "domain": domain 342 | } 343 | super().__init__(self.__bridge) 344 | 345 | class ProtocolSetting: 346 | class Inbound: 347 | class DokodemoDoor(BaseConfig): 348 | def __init__(self, port, network="tcp", timeout=300, **kwargs): 349 | self.__dokodemo_door = { 350 | "port": port, 351 | "network": network, 352 | "timeout": timeout 353 | } 354 | 355 | for key in kwargs: 356 | self.__dokodemo_door[key] = kwargs[key] 357 | 358 | super().__init__(self.__dokodemo_door) 359 | 360 | def set_address(self, addr): 361 | self.__dokodemo_door['address'] = addr 362 | 363 | def set_follow_redirect(self, f): 364 | self.__dokodemo_door['followRedirect'] = f 365 | 366 | def set_user_level(self, level): 367 | self.__dokodemo_door['userLevel'] = level 368 | 369 | class Http(BaseConfig): 370 | def __init__(self, user_level, timeout=300, allow_transparent=False, **kwargs): 371 | self.__http = { 372 | "timeout": timeout, 373 | "allowTransparent": allow_transparent, 374 | "userLevel": user_level, 375 | "account": [] 376 | } 377 | 378 | for key in kwargs: 379 | self.__http[key] = kwargs[key] 380 | super().__init__(self.__http) 381 | 382 | def add_account(self, username, password): 383 | self.__http['account'].append({ 384 | "user": username, 385 | "pass": password 386 | }) 387 | 388 | class Shadowsocks(BaseConfig): 389 | def __init__(self, method, password, level=0, network="tcp", **kwargs): 390 | self.__shadowsocks = { 391 | "method": method, 392 | "password": password, 393 | "level": level, 394 | "network": network 395 | } 396 | 397 | for key in kwargs: 398 | self.__shadowsocks[key] = kwargs[key] 399 | 400 | super().__init__(self.__shadowsocks) 401 | 402 | def set_email(self, email): 403 | self.__shadowsocks['email'] = email 404 | 405 | def set_ota(self, f): 406 | self.__shadowsocks['ota'] = f 407 | 408 | class VMess(BaseConfig): 409 | def __init__(self, disable_insecure_encryption=False, **kwargs): 410 | self.__vmess = { 411 | "disableInsecureEncryption": disable_insecure_encryption, 412 | "clients": [] 413 | } 414 | 415 | for key in kwargs: 416 | self.__vmess[key] = kwargs[key] 417 | 418 | super().__init__(self.__vmess) 419 | 420 | def set_detour(self, to): 421 | self.__vmess['detour'] = { 422 | "to": to 423 | } 424 | 425 | def set_default(self, level, aid): 426 | self.__vmess['default'] = { 427 | "level": level, 428 | "alterId": aid 429 | } 430 | 431 | def add_client(self, id, level, aid, email): 432 | self.__vmess['clients'].append({ 433 | "id": id, 434 | "level": level, 435 | "alterId": aid, 436 | "email": email 437 | }) 438 | 439 | class Socks(BaseConfig): 440 | def __init__(self, auth="noauth", udp=False, **kwargs): 441 | self.__socks = { 442 | "auth": auth, 443 | "udp": udp, 444 | "accounts": [] 445 | } 446 | 447 | for key in kwargs: 448 | self.__socks[key] = kwargs[key] 449 | super().__init__(self.__socks) 450 | 451 | def set_ip(self, addr): 452 | self.__socks['ip'] = addr 453 | 454 | def set_user_level(self, level): 455 | self.__socks['userLevel'] = level 456 | 457 | def add_user(self, username, password): 458 | self.__socks['accounts'].append({ 459 | "user": username, 460 | "pass": password 461 | }) 462 | 463 | class MTProto(BaseConfig): 464 | def __init__(self, **kwargs): 465 | self.__mt_proto = { 466 | "users": [] 467 | } 468 | 469 | for key in kwargs: 470 | self.__mt_proto[key] = kwargs[key] 471 | super().__init__(self.__mt_proto) 472 | 473 | def add_user(self, email, level, secret): 474 | self.__mt_proto['users'].append({ 475 | "email": email, 476 | "level": level, 477 | "secret": secret 478 | }) 479 | 480 | class Outbound: 481 | class Blackhole(BaseConfig): 482 | def __init__(self, response_type): 483 | self.__blackhole = { 484 | "response": { 485 | "type": response_type 486 | } 487 | } 488 | super().__init__(self.__blackhole) 489 | 490 | class Dns(BaseConfig): 491 | def __init__(self, network, address, port): 492 | self.__dns = { 493 | "network": network, 494 | "address": address, 495 | "port": port 496 | } 497 | 498 | super().__init__(self.__dns) 499 | 500 | class Freedom(BaseConfig): 501 | def __init__(self, domain_strategy, redirect, user_level): 502 | self.__freedom = { 503 | "domainStrategy": domain_strategy, 504 | "redirect": redirect, 505 | "userLevel": user_level 506 | } 507 | 508 | super().__init__(self.__freedom) 509 | 510 | class ShadowSocks(BaseConfig): 511 | def __init__(self): 512 | self.__shadowsocks = { 513 | "servers": [] 514 | } 515 | 516 | super().__init__(self.__shadowsocks) 517 | 518 | def add_server(self, address, port, method, password, level, ota=False, email=None): 519 | self.__shadowsocks['servers'].append({ 520 | "email": email, 521 | "address": address, 522 | "port": port, 523 | "method": method, 524 | "password": password, 525 | "ota": ota, 526 | "level": level 527 | }) 528 | 529 | class Socks(BaseConfig): 530 | def __init__(self): 531 | self.__socks = { 532 | "servers": [] 533 | } 534 | super().__init__(self.__socks) 535 | 536 | def add_server(self, server_obj): 537 | self.__socks['servers'].append(server_obj.json_obj) 538 | 539 | class Server(BaseConfig): 540 | def __init__(self, addr, port): 541 | self.__server = { 542 | "address": addr, 543 | "port": port, 544 | "users": [] 545 | } 546 | super().__init__(self.__server) 547 | 548 | def add_user(self, username, password, level): 549 | self.__server['users'].append({ 550 | "user": username, 551 | "pass": password, 552 | "level": level 553 | }) 554 | 555 | class VMess(BaseConfig): 556 | def __init__(self): 557 | self.__vmess = { 558 | "vnext": [] 559 | } 560 | super().__init__(self.__vmess) 561 | 562 | def add_server(self, server_obj): 563 | self.__vmess['vnext'].append(server_obj.json_obj) 564 | 565 | class Server(BaseConfig): 566 | def __init__(self, addr, port): 567 | self.__server = { 568 | "address": addr, 569 | "port": port, 570 | "users": [] 571 | } 572 | super().__init__(self.__server) 573 | 574 | def add_user(self, id, aid, security, level): 575 | self.__server['users'].append({ 576 | "id": id, 577 | "alterId": aid, 578 | "security": security, 579 | "level": level 580 | }) 581 | 582 | class StreamSetting(BaseConfig): 583 | STREAMSETTING = "StreamSetting" 584 | TRANSPORT = "Transport" 585 | 586 | def __init__(self, type, network="tcp", security="none", **kwargs): 587 | if type == Configuration.StreamSetting.STREAMSETTING: 588 | self.__stream_setiing = { 589 | "network": network, 590 | "security": security, 591 | "tlsSettings": {}, 592 | "tcpSettings": {}, 593 | "kcpSettings": {}, 594 | "wsSettings": {}, 595 | "httpSettings": {}, 596 | "dsSettings": {}, 597 | "quicSettings": {}, 598 | "sockopt": { 599 | "mark": 0, 600 | "tcpFastOpen": False, 601 | "tproxy": "off" 602 | } 603 | } 604 | elif type == Configuration.StreamSetting.TRANSPORT: 605 | self.__stream_setiing = { 606 | "tcpSettings": {}, 607 | "kcpSettings": {}, 608 | "wsSettings": {}, 609 | "httpSettings": {}, 610 | "dsSettings": {}, 611 | "quicSettings": {} 612 | } 613 | 614 | super().__init__(self.__stream_setiing, **kwargs) 615 | 616 | def set_tls(self, tls_obj): 617 | self.__stream_setiing['tlsSettings'] = tls_obj.json_obj 618 | 619 | def set_kcp(self, kcp_obj): 620 | self.__stream_setiing['kcpSettings'] = kcp_obj.json_obj 621 | 622 | def set_tcp(self, tcp_obj): 623 | self.__stream_setiing['tcpSettings'] = tcp_obj.json_obj 624 | 625 | def set_web_socket(self, ws_obj): 626 | self.__stream_setiing['wsSettings'] = ws_obj.json_obj 627 | 628 | def set_http(self, http_obj): 629 | self.__stream_setiing['httpSettings'] = http_obj.json_obj 630 | 631 | def set_domain_socket(self, ds_obj): 632 | self.__stream_setiing['dsSettings'] = ds_obj.json_obj 633 | 634 | def set_quic(self, quic_obj): 635 | self.__stream_setiing['quicSettings'] = quic_obj.json_obj 636 | 637 | def set_sockopt(self, sockopt_obj): 638 | self.__stream_setiing['sockopt'] = sockopt_obj.json_obj 639 | 640 | class TLS(BaseConfig): 641 | def __init__(self, alpn=None): 642 | if alpn is None: 643 | alpn = ["http/1.1"] 644 | 645 | self.__tls = { 646 | "alpn": alpn, 647 | "certificates": [] 648 | } 649 | 650 | super().__init__(self.__tls) 651 | 652 | def add_alpn(self, alpn): 653 | self.__tls['alpn'].append(alpn) 654 | 655 | def set_server_name(self, name): 656 | self.__tls['serverName'] = name 657 | 658 | def set_allow_insecure(self, f): 659 | self.__tls['allowInsecure'] = f 660 | 661 | def add_certificate(self, usage="encipherment", **kwargs): 662 | cer = { 663 | "usage": usage, 664 | } 665 | 666 | for key in kwargs: 667 | cer[key] = kwargs[key] 668 | 669 | self.__tls['certificates'].append(cer) 670 | 671 | class TCP(BaseConfig): 672 | def __init__(self, masquerading=False, type=None, **kwargs): 673 | if masquerading is False: 674 | self.__tcp = { 675 | "type": "none" 676 | } 677 | else: 678 | self.__tcp = { 679 | "type": type, 680 | "request": {}, 681 | "response": {} 682 | } 683 | super().__init__(self.__tcp, **kwargs) 684 | 685 | def set_request(self, request_obj): 686 | self.__tcp['request'] = request_obj.json_obj 687 | 688 | def set_response(self, response_obj): 689 | self.__tcp['response'] = response_obj.json_obj 690 | 691 | class HttpRequest(BaseConfig): 692 | def __init__(self, version="1.1", method="GET", path=None, headers=None, **kwargs): 693 | self.__request = { 694 | "version": version, 695 | "method": method, 696 | "path": [], 697 | "headers": {} 698 | } 699 | 700 | if path is not None: 701 | self.__request['path'].append(path) 702 | 703 | if headers is not None: 704 | self.__request['headers'] = headers 705 | 706 | super().__init__(self.__request, **kwargs) 707 | 708 | def add_path(self, path): 709 | self.__request['path'].append(path) 710 | 711 | def set_header(self, headers): 712 | self.__request['headers'] = headers 713 | 714 | class HttpResponse(BaseConfig): 715 | def __init__(self, version="1.1", status="200", reason="OK", headers=None, **kwargs): 716 | self.__response = { 717 | "version": version, 718 | "status": status, 719 | "reason": reason, 720 | "headers": {} 721 | } 722 | 723 | if headers is not None: 724 | self.__response['headers'] = headers 725 | 726 | super().__init__(self.__response, **kwargs) 727 | 728 | class KCP(BaseConfig): 729 | def __init__(self, mtu=1350, tti=50, uplink_capacity=5, download_capacity=20, congestion=False, 730 | read_buffer_size=2, write_buffer_size=2, header_type="none"): 731 | self.__kcp = { 732 | "mtu": mtu, 733 | "tti": tti, 734 | "uplinkCapacity": uplink_capacity, 735 | "downlinkCapacity": download_capacity, 736 | "congestion": congestion, 737 | "readBufferSize": read_buffer_size, 738 | "writeBufferSize": write_buffer_size, 739 | "header": { 740 | "type": header_type 741 | } 742 | } 743 | super().__init__(self.__kcp) 744 | 745 | class WebSocket(BaseConfig): 746 | def __init__(self, path="/", headers=None): 747 | if headers is None: 748 | headers = {} 749 | 750 | self.__web_socket = { 751 | "path": path, 752 | "headers": headers 753 | } 754 | 755 | super().__init__(self.__web_socket) 756 | 757 | def add_header(self, key, value): 758 | self.__web_socket['headers'][key] = value 759 | 760 | class Http(BaseConfig): 761 | def __init__(self, path="/"): 762 | self.__http = { 763 | "host": [], 764 | "path": path 765 | } 766 | super().__init__(self.__http) 767 | 768 | def add_host(self, host): 769 | self.__http['host'].append(host) 770 | 771 | class DomainSocket(BaseConfig): 772 | def __init__(self, filename): 773 | self.__domain_socket = { 774 | "path": filename 775 | } 776 | 777 | super().__init__(self.__domain_socket) 778 | 779 | class Quic(BaseConfig): 780 | def __init__(self, security="none", key="", header_type=""): 781 | self.__quic = { 782 | "security": security, 783 | "key": key, 784 | "header": { 785 | "type": header_type 786 | } 787 | } 788 | 789 | super().__init__(self.__quic) 790 | 791 | class Sockopt(BaseConfig): 792 | def __init__(self, mark=0, tcp_fast_open=False, tproxy="off"): 793 | self.__sockopt = { 794 | "mark": mark, 795 | "tcpFastOpen": tcp_fast_open, 796 | "tproxy": tproxy 797 | } 798 | 799 | super().__init__(self.__sockopt) 800 | --------------------------------------------------------------------------------