Files
LorenEteval_Furious/Furious/Library/Configuration.py
T
2026-04-04 23:32:40 +08:00

2259 lines
65 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Copyright (C) 2024–present Loren Eteval & contributors <loren.eteval@proton.me>
#
# This file is part of Furious.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from __future__ import annotations
from Furious.Frozenlib import *
from Furious.Interface import *
from Furious.Library.Encoder import *
from typing import Union, Tuple
import copy
import functools
import urllib.parse
quote = functools.partial(urllib.parse.quote, safe='')
unquote = functools.partial(urllib.parse.unquote)
parse_qsl = functools.partial(urllib.parse.parse_qsl)
urlparse = functools.partial(urllib.parse.urlparse)
urlunparse = functools.partial(urllib.parse.urlunparse)
__all__ = [
'BLANK_CONFIG_XRAY',
'BLANK_CONFIG_HYSTERIA1',
'BLANK_CONFIG_HYSTERIA2',
'ConfigXray',
'ConfigHysteria1',
'ConfigHysteria2',
'configXrayEmptyProxyOutboundObject',
'configFactoryFromDict',
'configFactoryFromAny',
'configFactoryBlank',
]
class ConfigXrayProxyOutboundObjectV(dict):
def __init__(
self,
protocol,
remote_host,
remote_port,
uuid_,
encryption,
type_,
security,
**kwargs,
):
(
networkObjectArgs,
securityArgs,
TLSObjectArgs,
finalmaskArgs,
) = (
{},
{},
{},
{},
)
if type_ == 'h2':
networkKey = 'httpSettings'
else:
networkKey = f'{type_}Settings'
networkObjectArgs[networkKey] = (
ConfigXray.kwargs2ProxyStreamSettingsNetworkObject(
type_, remote_host, kwargs
)
)
if security:
securityArgs['security'] = security
if security != 'none':
TLSObjectArgs[
# tlsSettings, realitySettings
f'{security}Settings'
] = ConfigXray.kwargs2ProxyStreamSettingsTLSObject(
protocol, remote_host, security, kwargs
)
try:
if kwargs.get('fm', ''):
finalmask = UJSONEncoder.decode(unquote(kwargs.get('fm', '')))
if finalmask:
finalmaskArgs['finalmask'] = finalmask
except Exception:
# Any non-exit exceptions
pass
super().__init__(
**{
'tag': 'proxy',
'protocol': protocol,
'settings': {
'vnext': [
{
'address': remote_host,
'port': int(remote_port),
'users': [
ConfigXray.kwargs2ProxyUserObject(
protocol, uuid_, encryption, kwargs
),
],
},
]
},
'streamSettings': {
'network': type_,
**networkObjectArgs,
**securityArgs,
**TLSObjectArgs,
**finalmaskArgs,
},
'mux': {
'enabled': False,
'concurrency': -1,
},
},
)
class ConfigXrayProxyOutboundObjectSS(dict):
def __init__(self, method, password, address, port):
super().__init__(
**{
'tag': 'proxy',
'protocol': 'shadowsocks',
'settings': {
'servers': [
{
'address': address,
'port': int(port),
'method': method,
'password': password,
'email': PROXY_OUTBOUND_USER_EMAIL,
'ota': False,
},
]
},
'streamSettings': {
'network': 'tcp',
},
'mux': {
'enabled': False,
'concurrency': -1,
},
}
)
class ConfigXrayProxyOutboundObjectTrojan(dict):
def __init__(self, password, address, port, type_, security, **kwargs):
networkObjectArgs, securityArgs, TLSObjectArgs = {}, {}, {}
if type_ == 'h2':
networkKey = 'httpSettings'
else:
networkKey = f'{type_}Settings'
networkObjectArgs[networkKey] = (
ConfigXray.kwargs2ProxyStreamSettingsNetworkObject(type_, address, kwargs)
)
if security:
securityArgs['security'] = security
if security != 'none':
TLSObjectArgs[
# tlsSettings, realitySettings
f'{security}Settings'
] = ConfigXray.kwargs2ProxyStreamSettingsTLSObject(
'trojan', address, security, kwargs
)
super().__init__(
**{
'tag': 'proxy',
'protocol': 'trojan',
'settings': {
'servers': [
{
'address': address,
'port': int(port),
'password': password,
'email': PROXY_OUTBOUND_USER_EMAIL,
},
]
},
'streamSettings': {
'network': type_,
**networkObjectArgs,
**securityArgs,
**TLSObjectArgs,
},
'mux': {
'enabled': False,
'concurrency': -1,
},
},
)
BLANK_CONFIG_XRAY = {
# log
'log': {
'access': '',
'error': '',
'loglevel': 'warning',
},
# inbounds
'inbounds': [
{
'tag': 'socks',
'port': 10808,
'listen': '127.0.0.1',
'protocol': 'socks',
'sniffing': {
'enabled': True,
'destOverride': [
'http',
'tls',
],
},
'settings': {
'auth': 'noauth',
'udp': True,
'allowTransparent': False,
},
},
{
'tag': 'http',
'port': 10809,
'listen': '127.0.0.1',
'protocol': 'http',
'sniffing': {
'enabled': True,
'destOverride': [
'http',
'tls',
],
},
'settings': {
'auth': 'noauth',
'udp': True,
'allowTransparent': False,
},
},
],
# outbounds
'outbounds': [
# proxy
{
'tag': 'proxy',
'protocol': '',
'settings': {},
'streamSettings': {
'network': 'tcp',
},
'mux': {
'enabled': False,
'concurrency': -1,
},
},
# direct
{
'tag': 'direct',
'protocol': 'freedom',
'settings': {},
},
# block
{
'tag': 'block',
'protocol': 'blackhole',
'settings': {
'response': {
'type': 'http',
}
},
},
],
# routing
'routing': {},
}
class ConfigXray(ConfigFactory):
def __init__(self, config: Union[str, dict] = '', **kwargs):
super().__init__(config, **kwargs)
def coreName(self) -> str:
return 'Xray-core'
@staticmethod
def getProxyOutboundObject(config: dict, default=None) -> dict:
if not isinstance(config.get('outbounds'), list):
config['outbounds'] = []
for outbound in config['outbounds']:
if isinstance(outbound, dict):
tag = outbound.get('tag')
if isinstance(tag, str) and tag == 'proxy':
return outbound
if isinstance(default, dict):
# Proxy outbound not found. Add it
config['outbounds'].append(default)
return {}
@staticmethod
def getProxyOutboundStream(config: dict, **kwargs) -> dict:
proxyOutbound = ConfigXray.getProxyOutboundObject(config, **kwargs)
if not isinstance(proxyOutbound.get('streamSettings'), dict):
proxyOutbound['streamSettings'] = {}
return proxyOutbound['streamSettings']
@staticmethod
def getProxyOutboundServer(config: dict, protocol: Protocol, **kwargs) -> dict:
value, proxyOutbound = (
protocol.value.lower(),
ConfigXray.getProxyOutboundObject(config, **kwargs),
)
try:
if value == 'vless' or value == 'vmess':
server = proxyOutbound['settings']['vnext'][0]
elif value == 'shadowsocks' or value == 'trojan':
server = proxyOutbound['settings']['servers'][0]
else:
server = {}
except Exception:
# Any non-exit exceptions
server = {}
if not isinstance(server, dict):
server = {}
return server
@staticmethod
def getProxyOutboundUser(config: dict, protocol: Protocol, **kwargs) -> dict:
proxyOutboundServer = ConfigXray.getProxyOutboundServer(
config, protocol, **kwargs
)
try:
user = proxyOutboundServer['users'][0]
except Exception:
# Any non-exit exceptions
user = {}
if not isinstance(user, dict):
user = {}
return user
@property
def proxyOutboundObject(self) -> dict:
try:
return ConfigXray.getProxyOutboundObject(self)
except Exception:
# Any non-exit exceptions
return {}
@property
def proxyProtocol(self) -> str:
try:
return self.proxyOutboundObject['protocol']
except Exception:
# Any non-exit exceptions
return ''
@property
def proxyServerObject(self) -> dict:
try:
proxyProtocol = self.proxyProtocol.lower()
if proxyProtocol == 'vmess' or proxyProtocol == 'vless':
return self.proxyOutboundObject['settings']['vnext'][0]
if proxyProtocol == 'shadowsocks' or proxyProtocol == 'trojan':
return self.proxyOutboundObject['settings']['servers'][0]
return {}
except Exception:
# Any non-exit exceptions
return {}
@property
def proxyUserObject(self) -> dict:
try:
return self.proxyServerObject['users'][0]
except Exception:
# Any non-exit exceptions
return {}
@property
def proxyStreamSettingsObject(self) -> dict:
try:
return ConfigXray.getProxyOutboundStream(self)
except Exception:
# Any non-exit exceptions
return {}
@property
def proxyStreamSettingsTLS(self) -> str:
try:
return self.proxyStreamSettingsObject.get('security', 'none')
except Exception:
# Any non-exit exceptions
return ''
@property
def proxyStreamSettingsTLSObject(self) -> dict:
try:
# tlsSettings, realitySettings
TLSKey = f'{self.proxyStreamSettingsTLS}Settings'
return self.proxyStreamSettingsObject[TLSKey]
except Exception:
# Any non-exit exceptions
return {}
@property
def proxyStreamSettingsNetwork(self) -> str:
try:
return self.proxyStreamSettingsObject['network']
except Exception:
# Any non-exit exceptions
return ''
@property
def proxyStreamSettingsNetworkObject(self) -> dict:
try:
if self.proxyStreamSettingsNetwork == 'h2':
networkKey = 'httpSettings'
else:
networkKey = f'{self.proxyStreamSettingsNetwork}Settings'
# Automatically use 'tcp' or 'raw' if needed
if (
networkKey == 'tcpSettings'
and networkKey not in self.proxyStreamSettingsObject
):
networkKey = 'rawSettings'
elif (
networkKey == 'rawSettings'
and networkKey not in self.proxyStreamSettingsObject
):
networkKey = 'tcpSettings'
return self.proxyStreamSettingsObject[networkKey]
except Exception:
# Any non-exit exceptions
return {}
@property
def kwargsFromVMessProxyStreamSettingsNetworkObject(self) -> dict:
kwargs = {}
try:
proxyStream = self.proxyStreamSettingsObject
if 'finalmask' in proxyStream:
kwargs['fm'] = quote(UJSONEncoder.encode(proxyStream['finalmask']))
except Exception:
# Any non-exit exceptions
pass
network = self.proxyStreamSettingsNetwork
networkObject = self.proxyStreamSettingsNetworkObject
if not networkObject:
return kwargs
def hasKey(key):
return networkObject.get(key) is not None
if network == 'tcp' or network == 'raw':
try:
kwargs['type'] = networkObject['header']['type']
except Exception:
# Any non-exit exceptions
pass
elif network == 'kcp':
try:
# Get order matters here
if hasKey('seed'):
kwargs['path'] = networkObject['seed']
kwargs['type'] = networkObject['header']['type']
except Exception:
# Any non-exit exceptions
pass
elif network == 'ws':
try:
# Get order matters here
if hasKey('path'):
kwargs['path'] = quote(networkObject['path'])
if networkObject['headers']['Host']:
kwargs['host'] = quote(networkObject['headers']['Host'])
except Exception:
# Any non-exit exceptions
pass
elif network == 'h2' or network == 'http':
try:
# Get order matters here
if hasKey('path'):
kwargs['path'] = quote(networkObject['path'])
kwargs['host'] = quote(','.join(networkObject['host']))
except Exception:
# Any non-exit exceptions
pass
elif network == 'quic':
try:
# Get order matters here
if hasKey('security'):
kwargs['host'] = networkObject['security']
if hasKey('key'):
kwargs['path'] = quote(networkObject['key'])
kwargs['type'] = networkObject['header']['type']
except Exception:
# Any non-exit exceptions
pass
elif network == 'grpc':
if hasKey('serviceName'):
kwargs['path'] = networkObject['serviceName']
elif network == 'httpupgrade':
try:
# Get order matters here
if hasKey('path'):
kwargs['path'] = quote(networkObject['path'])
kwargs['host'] = quote(networkObject['host'])
except Exception:
# Any non-exit exceptions
pass
elif network == 'splithttp':
try:
# Get order matters here
if hasKey('path'):
kwargs['path'] = quote(networkObject['path'])
kwargs['host'] = quote(networkObject['host'])
except Exception:
# Any non-exit exceptions
pass
elif network == 'xhttp':
try:
if hasKey('host'):
kwargs['host'] = quote(networkObject['host'])
if hasKey('path'):
kwargs['path'] = quote(networkObject['path'])
if hasKey('mode'):
kwargs['mode'] = networkObject['mode']
if hasKey('extra'):
kwargs['extra'] = quote(UJSONEncoder.encode(networkObject['extra']))
except Exception:
# Any non-exit exceptions
pass
return kwargs
@property
def kwargsFromVLESSProxyStreamSettingsNetworkObject(self) -> dict:
kwargs = {}
try:
proxyStream = self.proxyStreamSettingsObject
if 'finalmask' in proxyStream:
kwargs['fm'] = quote(UJSONEncoder.encode(proxyStream['finalmask']))
except Exception:
# Any non-exit exceptions
pass
network = self.proxyStreamSettingsNetwork
networkObject = self.proxyStreamSettingsNetworkObject
if not networkObject:
return kwargs
def hasKey(key):
return networkObject.get(key) is not None
if network == 'tcp' or network == 'raw':
try:
kwargs['headerType'] = networkObject['header']['type']
except Exception:
# Any non-exit exceptions
pass
elif network == 'kcp':
try:
# Get order matters here
if hasKey('seed'):
kwargs['seed'] = networkObject['seed']
kwargs['headerType'] = networkObject['header']['type']
except Exception:
# Any non-exit exceptions
pass
elif network == 'ws':
try:
# Get order matters here
if hasKey('path'):
kwargs['path'] = quote(networkObject['path'])
if networkObject['headers']['Host']:
kwargs['host'] = quote(networkObject['headers']['Host'])
except Exception:
# Any non-exit exceptions
pass
elif network == 'h2' or network == 'http':
try:
# Get order matters here
if hasKey('path'):
kwargs['path'] = quote(networkObject['path'])
kwargs['host'] = quote(','.join(networkObject['host']))
except Exception:
# Any non-exit exceptions
pass
elif network == 'quic':
try:
# Get order matters here
if hasKey('security'):
kwargs['quicSecurity'] = networkObject['security']
if hasKey('key'):
kwargs['path'] = quote(networkObject['key'])
kwargs['headerType'] = networkObject['header']['type']
except Exception:
# Any non-exit exceptions
pass
elif network == 'grpc':
if hasKey('serviceName'):
kwargs['serviceName'] = networkObject['serviceName']
if hasKey('multiMode'):
if networkObject['multiMode']:
kwargs['mode'] = 'multi'
if hasKey('authority'):
kwargs['authority'] = networkObject['authority']
elif network == 'httpupgrade':
try:
# Get order matters here
if hasKey('path'):
kwargs['path'] = quote(networkObject['path'])
kwargs['host'] = quote(networkObject['host'])
except Exception:
# Any non-exit exceptions
pass
elif network == 'splithttp':
try:
# Get order matters here
if hasKey('path'):
kwargs['path'] = quote(networkObject['path'])
kwargs['host'] = quote(networkObject['host'])
except Exception:
# Any non-exit exceptions
pass
elif network == 'xhttp':
try:
if hasKey('host'):
kwargs['host'] = quote(networkObject['host'])
if hasKey('path'):
kwargs['path'] = quote(networkObject['path'])
if hasKey('mode'):
kwargs['mode'] = networkObject['mode']
if hasKey('extra'):
kwargs['extra'] = quote(UJSONEncoder.encode(networkObject['extra']))
except Exception:
# Any non-exit exceptions
pass
return kwargs
@staticmethod
def kwargs2ProxyStreamSettingsNetworkObject(type_, remote_host, kwargs) -> dict:
# Note:
# v2rayN share standard doesn't require unquote. Still
# unquote value according to (VMess AEAD / VLESS) standard
if type_ == 'tcp' or type_ == 'raw':
TcpObject = {}
if kwargs.get('headerType', 'none'):
headerType = kwargs.get('headerType', 'none')
# Some dumb share link set this value to 'auto'. Protect it
if headerType != 'auto':
TcpObject['header'] = {
'type': headerType,
}
# Request settings for HTTP
if headerType == 'http' and kwargs.get('host'):
TcpObject['header']['request'] = {
'version': '1.1',
'method': 'GET',
'path': [kwargs.get('path', '/')],
'headers': {
'Host': [unquote(kwargs.get('host'))],
'User-Agent': [
(
'Mozilla/5.0 (Windows NT 10.0; WOW64) '
'AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/53.0.2785.143 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',
},
}
return TcpObject
elif type_ == 'kcp':
KcpObject = {
# Extension. From v2rayN
'uplinkCapacity': 12,
'downlinkCapacity': 100,
}
if kwargs.get('headerType', 'none'):
headerType = kwargs.get('headerType', 'none')
# Some dumb share link set this value to 'auto'. Protect it
if headerType != 'auto':
KcpObject['header'] = {
'type': headerType,
}
if kwargs.get('seed'):
KcpObject['seed'] = kwargs.get('seed')
return KcpObject
elif type_ == 'ws':
WebSocketObject = {}
if kwargs.get('path', '/'):
WebSocketObject['path'] = unquote(kwargs.get('path', '/'))
if kwargs.get('host'):
WebSocketObject['headers'] = {
'Host': unquote(kwargs.get('host')),
}
return WebSocketObject
elif type_ == 'h2' or type_ == 'http':
HttpObject = {}
if kwargs.get('host', remote_host):
# Protect "xxx," format
HttpObject['host'] = list(
filter(
lambda x: x != '',
unquote(kwargs.get('host', remote_host)).split(','),
)
)
if kwargs.get('path', '/'):
HttpObject['path'] = unquote(kwargs.get('path', '/'))
return HttpObject
elif type_ == 'quic':
QuicObject = {}
if kwargs.get('quicSecurity', 'none'):
QuicObject['security'] = kwargs.get('quicSecurity', 'none')
if kwargs.get('key'):
QuicObject['key'] = unquote(kwargs.get('key'))
if kwargs.get('headerType', 'none'):
headerType = kwargs.get('headerType', 'none')
# Some dumb share link set this value to 'auto'. Protect it
if headerType != 'auto':
QuicObject['header'] = {
'type': headerType,
}
return QuicObject
elif type_ == 'grpc':
GRPCObject = {}
if kwargs.get('serviceName'):
GRPCObject['serviceName'] = kwargs.get('serviceName')
if kwargs.get('mode', 'gun'):
GRPCObject['multiMode'] = kwargs.get('mode', 'gun') == 'multi'
if kwargs.get('authority'):
GRPCObject['authority'] = kwargs.get('authority')
return GRPCObject
elif type_ == 'httpupgrade':
httpUpgradeObject = {}
if kwargs.get('path', '/'):
httpUpgradeObject['path'] = unquote(kwargs.get('path', '/'))
if kwargs.get('host'):
httpUpgradeObject['host'] = unquote(kwargs.get('host'))
return httpUpgradeObject
elif type_ == 'splithttp':
splitHttpObject = {}
if kwargs.get('path', '/'):
splitHttpObject['path'] = unquote(kwargs.get('path', '/'))
if kwargs.get('host'):
splitHttpObject['host'] = unquote(kwargs.get('host'))
return splitHttpObject
elif type_ == 'xhttp':
xhttpObject = {}
if kwargs.get('host', ''):
xhttpObject['host'] = unquote(kwargs.get('host', ''))
if kwargs.get('path', '/'):
xhttpObject['path'] = unquote(kwargs.get('path', '/'))
if kwargs.get('mode', ''):
xhttpObject['mode'] = kwargs.get('mode', '')
try:
if kwargs.get('extra', ''):
xhttpObject['extra'] = UJSONEncoder.decode(
unquote(kwargs.get('extra', ''))
)
except Exception:
# Any non-exit exceptions
pass
return xhttpObject
else:
return {}
@property
def kwargsFromProxyStreamSettingsTLSObject(self) -> dict:
kwargs = {}
TLS = self.proxyStreamSettingsTLS
TLSObject = self.proxyStreamSettingsTLSObject
if TLS == '' or TLS == 'none':
return kwargs
if TLS == 'reality' or TLS == 'tls':
if TLSObject.get('fingerprint'):
kwargs['fp'] = TLSObject['fingerprint']
if TLSObject.get('serverName'):
kwargs['sni'] = TLSObject['serverName']
if TLSObject.get('alpn'):
kwargs['alpn'] = quote(','.join(TLSObject['alpn']))
if TLSObject.get('echConfigList'):
kwargs['ech'] = quote(TLSObject['echConfigList'])
if TLSObject.get('pinnedPeerCertSha256'):
kwargs['pcs'] = quote(TLSObject['pinnedPeerCertSha256'])
if TLSObject.get('verifyPeerCertByName'):
kwargs['vcn'] = quote(TLSObject['verifyPeerCertByName'])
if TLS == 'reality':
# More kwargs for reality
if TLSObject.get('publicKey'):
kwargs['pbk'] = TLSObject['publicKey']
if TLSObject.get('shortId'):
kwargs['sid'] = TLSObject['shortId']
if TLSObject.get('mldsa65Verify'):
kwargs['pqv'] = TLSObject['mldsa65Verify']
if TLSObject.get('spiderX'):
kwargs['spx'] = quote(TLSObject['spiderX'])
return kwargs
@staticmethod
def kwargs2ProxyStreamSettingsTLSObject(
protocol, remote_host, security, kwargs
) -> dict:
TLSObject = {}
if security == 'reality' or security == 'tls':
# Note: If specify default value 'chrome', some share link fails.
# Leave default value as empty
fp = kwargs.get('fp')
sni = kwargs.get('sni')
# Protect "xxx," format
alpn = list(
filter(
lambda x: x != '',
unquote(kwargs.get('alpn', '')).split(','),
)
)
ech = unquote(kwargs.get('ech', ''))
pcs = unquote(kwargs.get('pcs', ''))
vcn = unquote(kwargs.get('vcn', ''))
if fp:
TLSObject['fingerprint'] = fp
if sni:
TLSObject['serverName'] = sni
else:
host = ''
if protocol == 'vmess':
host = kwargs.get('host')
if protocol == 'vless':
host = remote_host
if host:
TLSObject['serverName'] = host
if alpn:
TLSObject['alpn'] = alpn
if ech:
TLSObject['echConfigList'] = ech
if pcs:
TLSObject['pinnedPeerCertSha256'] = pcs
if vcn:
TLSObject['verifyPeerCertByName'] = vcn
if security == 'reality':
# More args for reality
pbk = kwargs.get('pbk')
sid = kwargs.get('sid', '')
pqv = kwargs.get('pqv', '')
spx = kwargs.get('spx', '')
if pbk:
TLSObject['publicKey'] = pbk
TLSObject['shortId'] = sid
TLSObject['mldsa65Verify'] = pqv
TLSObject['spiderX'] = unquote(spx)
return TLSObject
@staticmethod
def kwargs2ProxyUserObject(protocol, uuid_, encryption, kwargs) -> dict:
if protocol == 'vmess':
UserObject = {
'id': uuid_,
'security': encryption,
'email': PROXY_OUTBOUND_USER_EMAIL,
}
# For VMess(v2rayN share standard) only.
if kwargs.get('aid') is not None:
# int: extra guard
UserObject['alterId'] = int(kwargs.get('aid'))
return UserObject
if protocol == 'vless':
UserObject = {
'id': uuid_,
'encryption': encryption,
'email': PROXY_OUTBOUND_USER_EMAIL,
}
if kwargs.get('flow'):
# flow is empty, TLS. Otherwise, XTLS.
UserObject['flow'] = kwargs.get('flow')
return UserObject
return {}
@staticmethod
def URI2ProxyOutboundObjectVMess(URI: str) -> Tuple[str, dict]:
try:
myHead, myBody = URI.split('://')
except Exception:
# Any non-exit exceptions
return '', {}
try:
myData = PyBase64Encoder.decode(myBody)
myJSON = UJSONEncoder.decode(myData)
except Exception:
# Any non-exit exceptions
return ConfigXray.URI2ProxyOutboundObjectVLESS(URI, protocol='vmess')
else:
def getOrDefault(key, default=''):
return myJSON.get(key, default)
remark = unquote(getOrDefault('ps'))
return (
remark,
# Ignore: v
ConfigXrayProxyOutboundObjectV(
'vmess',
getOrDefault('add'),
int(getOrDefault('port', '0')),
getOrDefault('id'),
getOrDefault('scy', 'auto'),
getOrDefault('net', 'tcp'),
getOrDefault('tls', 'none'),
# kwargs. v2rayN share standard -> (VMess AEAD / VLESS) standard
aid=int(getOrDefault('aid', '0')),
headerType=getOrDefault('type', 'none'),
host=getOrDefault('host'),
quicSecurity=getOrDefault('host', 'none'),
path=getOrDefault('path'),
key=getOrDefault('path'),
seed=getOrDefault('path'),
serviceName=getOrDefault('path'),
sni=getOrDefault('sni'),
alpn=getOrDefault('alpn'),
# Note: If specify default value 'chrome', some share link fails.
# Leave default value as empty
fp=getOrDefault('fp'),
),
)
@staticmethod
def URI2ProxyOutboundObjectVLESS(URI: str, **kwargs) -> Tuple[str, dict]:
result = urlparse(URI)
remark = unquote(result.fragment)
queryObject = {key: value for key, value in parse_qsl(result.query)}
uuid_, server = result.netloc.split('@')
remote_host, remote_port = parseHostPort(server)
encryption = queryObject.pop('encryption', 'none')
type_ = queryObject.pop('type', 'tcp')
security = queryObject.pop('security', 'none')
return (
remark,
ConfigXrayProxyOutboundObjectV(
kwargs.pop('protocol', 'vless'),
remote_host,
int(remote_port),
uuid_,
encryption,
type_,
security,
# kwargs
**queryObject,
),
)
@staticmethod
def URI2ProxyOutboundObjectSS(URI: str) -> Tuple[str, dict]:
try:
result = urlparse(URI)
remark = unquote(result.fragment)
except Exception:
# Any non-exit exceptions
return '', {}
def getSSParams():
# Begin SIP002...
try:
# Try pack with 3 element
userinfo, server = result.netloc.split('@')
# Some old SS share link doesn't add padding
# in base64 encoding. Add padding to userinfo
return [
*PyBase64Encoder.decode(userinfo + '===').decode().split(':', 1),
*parseHostPort(server),
]
except Exception:
# Any non-exit exceptions
pass
try:
# Try pack with 4 element
userinfo, server = result.netloc.split('@')
return [*userinfo.split(':', 1), *parseHostPort(server)]
except Exception:
# Any non-exit exceptions
pass
try:
# ss://base64...#fragment
userinfo, server = (
PyBase64Encoder.decode(result.netloc).decode().split('@')
)
return [*userinfo.split(':', 1), *parseHostPort(server)]
except Exception:
# Any non-exit exceptions
pass
raise ValueError(f'Invalid SS URI format {URI}')
return (
remark,
ConfigXrayProxyOutboundObjectSS(*getSSParams()),
)
@staticmethod
def URI2ProxyOutboundObjectTrojan(URI: str) -> Tuple[str, dict]:
result = urlparse(URI)
remark = unquote(result.fragment)
queryObject = {key: value for key, value in parse_qsl(result.query)}
password, server = result.netloc.split('@')
address, port = parseHostPort(server)
type_ = queryObject.pop('type', 'tcp')
# For Trojan: Assign tls by default
security = queryObject.pop('security', 'tls')
return (
remark,
ConfigXrayProxyOutboundObjectTrojan(
password, address, port, type_, security, **queryObject
),
)
@staticmethod
def URI2ProxyOutboundObject(URI: str) -> Tuple[str, dict]:
if URI.startswith('vmess://'):
return ConfigXray.URI2ProxyOutboundObjectVMess(URI)
if URI.startswith('vless://'):
return ConfigXray.URI2ProxyOutboundObjectVLESS(URI)
if URI.startswith('ss://'):
return ConfigXray.URI2ProxyOutboundObjectSS(URI)
if URI.startswith('trojan://'):
return ConfigXray.URI2ProxyOutboundObjectTrojan(URI)
raise ValueError(f'Unrecognized URI scheme {URI}')
@property
def itemProtocol(self) -> str:
return Protocol.toEnum(self.proxyProtocol).value
@property
def itemAddress(self) -> str:
addr = self.proxyServerObject.get('address', '')
return str(addr)
@property
def itemPort(self) -> str:
port = self.proxyServerObject.get('port', '')
return str(port)
@property
def itemTransport(self) -> str:
return self.proxyStreamSettingsNetwork
@property
def itemTLS(self) -> str:
return self.proxyStreamSettingsTLS
@property
def itemLatency(self) -> str:
# Backward compatibility
return self.getExtras('delayResult')
@property
def itemSpeed(self) -> str:
# Backward compatibility
return self.getExtras('speedResult')
def toJSONString(self, **kwargs) -> str:
indent = kwargs.pop('indent', 2)
return super().toJSONString(indent=indent)
def toURI(self, remark: str = '') -> str:
if remark == '':
override = self.itemRemark
else:
override = remark
protocol = self.proxyProtocol.lower()
if protocol == 'vmess':
netloc = PyBase64Encoder.encode(
UJSONEncoder.encode(
{
'v': '2',
'ps': override,
**{
key: str(self.proxyServerObject[value])
for key, value in {
'add': 'address',
'port': 'port',
}.items()
},
**{
key: str(self.proxyUserObject[value])
for key, value in {
'id': 'id',
'aid': 'alterId',
'scy': 'security',
}.items()
},
'net': self.proxyStreamSettingsNetwork,
'tls': self.proxyStreamSettingsTLS,
# kwargs
**self.kwargsFromVMessProxyStreamSettingsNetworkObject,
**self.kwargsFromProxyStreamSettingsTLSObject,
}
).encode()
).decode()
return urlunparse(['vmess', netloc, '', '', {}, ''])
if protocol == 'vless':
flowArg = {}
if self.proxyUserObject.get('flow'):
flowArg['flow'] = self.proxyUserObject['flow']
netloc = (
self.proxyUserObject['id']
+ '@'
+ self.proxyServerObject['address']
+ ':'
+ str(self.proxyServerObject['port'])
)
query = '&'.join(
f'{key}={value}'
for key, value in {
'encryption': self.proxyUserObject['encryption'],
'type': self.proxyStreamSettingsNetwork,
'security': self.proxyStreamSettingsTLS,
# kwargs
**flowArg,
**self.kwargsFromVLESSProxyStreamSettingsNetworkObject,
**self.kwargsFromProxyStreamSettingsTLSObject,
}.items()
)
return urlunparse(['vless', netloc, '', '', query, quote(override)])
if protocol == 'shadowsocks':
method, password, address, port = list(
self.proxyServerObject[value]
for value in ['method', 'password', 'address', 'port']
)
netloc = f'{quote(method)}:{quote(password)}@{address}:{port}'
return urlunparse(['ss', netloc, '', '', '', quote(override)])
if protocol == 'trojan':
password, address, port = list(
self.proxyServerObject[value]
for value in ['password', 'address', 'port']
)
netloc = f'{quote(password)}@{address}:{port}'
query = '&'.join(
f'{key}={value}'
for key, value in {
'type': self.proxyStreamSettingsNetwork,
'security': self.proxyStreamSettingsTLS,
# kwargs
**self.kwargsFromVLESSProxyStreamSettingsNetworkObject,
**self.kwargsFromProxyStreamSettingsTLSObject,
}.items()
)
return urlunparse(['trojan', netloc, '', '', query, quote(override)])
# Unrecognized protocol
return ''
def fromURI(self, URI: str) -> bool:
try:
remark, proxyOutboundObject = ConfigXray.URI2ProxyOutboundObject(URI)
factory = copy.deepcopy(BLANK_CONFIG_XRAY)
factory['outbounds'] = [
# proxy
proxyOutboundObject,
# direct
{
'tag': 'direct',
'protocol': 'freedom',
'settings': {},
},
# block
{
'tag': 'block',
'protocol': 'blackhole',
'settings': {
'response': {
'type': 'http',
}
},
},
]
dict.__init__(self, **factory)
self.setExtras('remark', remark)
return True
except Exception:
# Any non-exit exceptions
return False
def httpProxy(self) -> str:
try:
for inbound in self['inbounds']:
if inbound['protocol'] == 'http':
# Note: If there are multiple http inbounds
# satisfied, the first one will be chosen.
return str(inbound['listen']) + ':' + str(inbound['port'])
return ''
except Exception:
# Any non-exit exceptions
return ''
def socksProxy(self) -> str:
try:
for inbound in self['inbounds']:
if inbound['protocol'] == 'socks':
# Note: If there are multiple socks inbounds
# satisfied, the first one will be chosen.
return str(inbound['listen']) + ':' + str(inbound['port'])
return ''
except Exception:
# Any non-exit exceptions
return ''
def setHttpProxy(self, endpoint: str) -> bool:
try:
if self.get('inbounds') is None:
self['inbounds'] = []
if not isinstance(self['inbounds'], list):
self['inbounds'] = []
if endpoint == '':
for inbound in self['inbounds']:
if inbound['protocol'] == 'http':
self['inbounds'].remove(inbound)
# Remove first chosen
return True
# None satisfied. Return success
return True
listen, port = parseHostPort(endpoint)
for inbound in self['inbounds']:
if inbound['protocol'] == 'http':
# Note: If there are multiple http inbounds
# satisfied, the first one will be chosen.
inbound['listen'], inbound['port'] = listen, int(port)
return True
# No entry. Add new one
self['inbounds'].append(
{
'tag': 'http',
'port': int(port),
'listen': listen,
'protocol': 'http',
'sniffing': {
'enabled': True,
'destOverride': [
'http',
'tls',
],
},
'settings': {
'auth': 'noauth',
'udp': True,
'allowTransparent': False,
},
}
)
return True
except Exception:
# Any non-exit exceptions
return False
def setSocksProxy(self, endpoint: str) -> bool:
try:
if self.get('inbounds') is None:
self['inbounds'] = []
if not isinstance(self['inbounds'], list):
self['inbounds'] = []
if endpoint == '':
for inbound in self['inbounds']:
if inbound['protocol'] == 'socks':
self['inbounds'].remove(inbound)
# Remove first chosen
return True
# None satisfied. Return success
return True
listen, port = parseHostPort(endpoint)
for inbound in self['inbounds']:
if inbound['protocol'] == 'socks':
# Note: If there are multiple socks inbounds
# satisfied, the first one will be chosen.
inbound['listen'], inbound['port'] = listen, int(port)
return True
# No entry. Add new one
self['inbounds'].append(
{
'tag': 'socks',
'port': int(port),
'listen': listen,
'protocol': 'socks',
'sniffing': {
'enabled': True,
'destOverride': [
'http',
'tls',
],
},
'settings': {
'auth': 'noauth',
'udp': True,
'allowTransparent': False,
},
}
)
return True
except Exception:
# Any non-exit exceptions
return False
BLANK_CONFIG_HYSTERIA1 = {
'server': '',
'protocol': 'udp',
'socks5': {
'listen': '127.0.0.1:10808',
},
'http': {
'listen': '127.0.0.1:10809',
},
}
class ConfigHysteria1(ConfigFactory):
def __init__(self, config: Union[str, dict] = '', **kwargs):
super().__init__(config, **kwargs)
def coreName(self) -> str:
return 'Hysteria1'
@property
def itemProtocol(self) -> str:
return Protocol.Hysteria1.value
@property
def itemAddress(self) -> str:
server = self.get('server', '')
pos = server.rfind(':')
if pos == -1:
return server
else:
return server[:pos]
@property
def itemPort(self) -> str:
server = self.get('server', '')
pos = server.rfind(':')
if pos == -1:
return ''
else:
return server[pos + 1 :]
@property
def itemTransport(self) -> str:
return ''
@property
def itemTLS(self) -> str:
return ''
@property
def itemLatency(self) -> str:
return self.getExtras('delayResult')
@property
def itemSpeed(self) -> str:
return self.getExtras('speedResult')
def toJSONString(self, **kwargs) -> str:
indent = kwargs.pop('indent', 4)
return super().toJSONString(indent=indent)
def toURI(self, remark: str = '') -> str:
if remark == '':
override = self.itemRemark
else:
override = remark
try:
netloc, mport = self['server'].split(',')
except Exception:
# Any non-exit exceptions
netloc, mport = self['server'], ''
if mport:
mportArgs = {'mport': mport}
else:
mportArgs = {}
protocol = self.get('protocol', 'udp')
if self.get('auth_str'):
# auth in string
authArgs = {'auth': self['auth_str']}
else:
authArgs = {}
if self.get('server_name'):
peerArgs = {'peer': self['server_name']}
else:
peerArgs = {}
if self.get('insecure') is True:
insecureArgs = {'insecure': '1'}
else:
insecureArgs = {}
upmbps = self['up_mbps']
downmbps = self['down_mbps']
if self.get('alpn'):
alpnArgs = {'alpn': self['alpn']}
else:
alpnArgs = {}
if self.get('obfs'):
obfsParamArgs = {'obfsParam': self['obfs']}
else:
obfsParamArgs = {}
query = '&'.join(
f'{key}={value}'
for key, value in {
**mportArgs,
'protocol': protocol,
**authArgs,
**peerArgs,
**insecureArgs,
'upmbps': upmbps,
'downmbps': downmbps,
**alpnArgs,
**obfsParamArgs,
}.items()
)
return urlunparse(['hysteria', netloc, '', '', query, quote(override)])
def fromURI(self, URI: str) -> bool:
try:
result = urlparse(URI)
remark = unquote(result.fragment)
queryObject = {key: value for key, value in parse_qsl(result.query)}
if result.scheme != 'hysteria':
raise ValueError('Invalid hysteria1 URI scheme')
server = result.netloc
mport = queryObject.get('mport', '')
if mport:
server += f',{mport}'
protocol = queryObject.get('protocol', 'udp')
if queryObject.get('auth', ''):
# auth in string
authArgs = {'auth_str': queryObject['auth']}
else:
authArgs = {}
if queryObject.get('peer', ''):
peerArgs = {'server_name': queryObject['peer']}
else:
peerArgs = {}
insecure = queryObject.get('insecure', False)
if isinstance(insecure, bool):
pass
elif insecure == '1':
insecure = True
else:
insecure = False
upmbps = queryObject.get('upmbps', 24)
downmbps = queryObject.get('downmbps', 96)
if queryObject.get('alpn', ''):
alpnArgs = {'alpn': queryObject['alpn']}
else:
alpnArgs = {}
# obfs mode seems not required, ignored...
if queryObject.get('obfsParam', ''):
obfsParamArgs = {'obfs': queryObject['obfsParam']}
else:
obfsParamArgs = {}
dict.__init__(
self,
**{
'server': server,
'protocol': protocol,
**authArgs,
**obfsParamArgs,
'up_mbps': int(upmbps),
'down_mbps': int(downmbps),
**peerArgs,
**alpnArgs,
'insecure': insecure,
'socks5': {
'listen': '127.0.0.1:10808',
},
'http': {
'listen': '127.0.0.1:10809',
},
},
)
self.setExtras('remark', remark)
return True
except Exception:
# Any non-exit exceptions
return False
def httpProxy(self) -> str:
try:
return self['http']['listen']
except Exception:
# Any non-exit exceptions
return ''
def socksProxy(self) -> str:
try:
return self['socks5']['listen']
except Exception:
# Any non-exit exceptions
return ''
def setHttpProxy(self, endpoint: str) -> bool:
try:
if endpoint == '':
self.pop('http', None)
return True
if self.get('http') is None:
self['http'] = {}
self['http']['listen'] = endpoint
return True
except Exception:
# Any non-exit exceptions
return False
def setSocksProxy(self, endpoint: str) -> bool:
try:
if endpoint == '':
self.pop('socks5', None)
return True
if self.get('socks5') is None:
self['socks5'] = {}
self['socks5']['listen'] = endpoint
return True
except Exception:
# Any non-exit exceptions
return False
BLANK_CONFIG_HYSTERIA2 = {
'server': '',
'tls': {
'insecure': False,
},
'socks5': {
'listen': '127.0.0.1:10808',
},
'http': {
'listen': '127.0.0.1:10809',
},
}
class ConfigHysteria2(ConfigFactory):
def __init__(self, config: Union[str, dict] = '', **kwargs):
super().__init__(config, **kwargs)
def coreName(self) -> str:
return 'Hysteria2'
@property
def itemProtocol(self) -> str:
return Protocol.Hysteria2.value
@property
def itemAddress(self) -> str:
server = self.get('server', '')
pos = server.rfind(':')
if pos == -1:
return server
else:
return server[:pos]
@property
def itemPort(self) -> str:
server = self.get('server', '')
pos = server.rfind(':')
if pos == -1:
return ''
else:
return server[pos + 1 :]
@property
def itemTransport(self) -> str:
return ''
@property
def itemTLS(self) -> str:
return ''
@property
def itemLatency(self) -> str:
return self.getExtras('delayResult')
@property
def itemSpeed(self) -> str:
return self.getExtras('speedResult')
def toJSONString(self, **kwargs) -> str:
indent = kwargs.pop('indent', 4)
return super().toJSONString(indent=indent)
def toURI(self, remark: str = '') -> str:
if remark == '':
override = self.itemRemark
else:
override = remark
netloc = self['auth'] + '@' + self['server']
TLSArg, obfsArg = {}, {}
if self.get('tls'):
if self['tls'].get('sni'):
TLSArg['sni'] = self['tls']['sni']
if self['tls'].get('insecure') is True:
TLSArg['insecure'] = '1'
else:
TLSArg['insecure'] = '0'
if self['tls'].get('pinSHA256'):
TLSArg['pinSHA256'] = self['tls']['pinSHA256']
if self.get('obfs'):
obfsType = self['obfs'].get('type', 'salamander')
obfsArg['obfs'] = obfsType
obfsArg['obfs-password'] = self['obfs'][obfsType]['password']
query = '&'.join(
f'{key}={value}'
for key, value in {
**TLSArg,
**obfsArg,
}.items()
)
return urlunparse(['hysteria2', netloc, '', '', query, quote(override)])
def fromURI(self, URI: str) -> bool:
try:
result = urlparse(URI)
remark = unquote(result.fragment)
queryObject = {key: value for key, value in parse_qsl(result.query)}
if result.scheme != 'hysteria2' and result.scheme != 'hy2':
raise ValueError('Invalid hysteria2 URI scheme')
auth, server = result.netloc.split('@')
obfs = queryObject.get('obfs', '')
obfsPassword = queryObject.get('obfs-password', '')
sni = queryObject.get('sni', '')
insecure = queryObject.get('insecure', False)
if isinstance(insecure, bool):
pass
elif insecure == '1':
insecure = True
else:
insecure = False
pinSHA256 = queryObject.get('pinSHA256', '')
obfsArg = {}
pinSHA256Arg = {}
if obfs and obfsPassword:
obfsArg['obfs'] = {
'type': obfs,
obfs: {
'password': obfsPassword,
},
}
if pinSHA256:
pinSHA256Arg['pinSHA256'] = pinSHA256
dict.__init__(
self,
**{
'server': server,
'auth': auth,
**obfsArg,
'tls': {
'sni': sni,
'insecure': insecure,
**pinSHA256Arg,
},
'socks5': {
'listen': '127.0.0.1:10808',
},
'http': {
'listen': '127.0.0.1:10809',
},
},
)
self.setExtras('remark', remark)
return True
except Exception:
# Any non-exit exceptions
return False
def httpProxy(self) -> str:
try:
return self['http']['listen']
except Exception:
# Any non-exit exceptions
return ''
def socksProxy(self) -> str:
try:
return self['socks5']['listen']
except Exception:
# Any non-exit exceptions
return ''
def setHttpProxy(self, endpoint: str) -> bool:
try:
if endpoint == '':
self.pop('http', None)
return True
if self.get('http') is None:
self['http'] = {}
self['http']['listen'] = endpoint
return True
except Exception:
# Any non-exit exceptions
return False
def setSocksProxy(self, endpoint: str) -> bool:
try:
if endpoint == '':
self.pop('socks5', None)
return True
if self.get('socks5') is None:
self['socks5'] = {}
self['socks5']['listen'] = endpoint
return True
except Exception:
# Any non-exit exceptions
return False
def configXrayEmptyProxyOutboundObject(protocol: Protocol) -> dict:
value = protocol.value.lower()
if value == 'vless' or value == 'vmess':
return {
'tag': 'proxy',
'protocol': value,
'settings': {
'vnext': [
{
'address': '',
'port': 0,
'users': [
{
'email': PROXY_OUTBOUND_USER_EMAIL,
},
],
},
]
},
'streamSettings': {
'network': 'tcp',
},
'mux': {
'enabled': False,
'concurrency': -1,
},
}
elif value == 'shadowsocks':
return {
'tag': 'proxy',
'protocol': 'shadowsocks',
'settings': {
'servers': [
{
'address': '',
'port': 0,
'method': '',
'password': '',
'email': PROXY_OUTBOUND_USER_EMAIL,
'ota': False,
}
]
},
'streamSettings': {
'network': 'tcp',
},
'mux': {
'enabled': False,
'concurrency': -1,
},
}
elif value == 'trojan':
return {
'tag': 'proxy',
'protocol': 'trojan',
'settings': {
'servers': [
{
'address': '',
'port': 0,
'password': '',
'email': PROXY_OUTBOUND_USER_EMAIL,
}
]
},
'streamSettings': {
'network': 'tcp',
},
'mux': {
'enabled': False,
'concurrency': -1,
},
}
else:
return {}
def configFactoryFromDict(config: dict, **kwargs) -> ConfigFactory:
if not isinstance(config, dict):
return ConfigFactory()
def hasField(field):
return config.get(field) is not None
if hasField('inbounds') or hasField('outbounds'):
# Assuming is Xray-Core
return ConfigXray(config, **kwargs)
if hasField('server'):
if (
hasField('protocol')
or hasField('up_mbps')
or hasField('down_mbps')
or hasField('auth_str')
or hasField('alpn')
or hasField('server_name')
or hasField('insecure')
or hasField('recv_window_conn')
or hasField('recv_window')
or isinstance(config.get('obfs'), str)
or hasField('fast_open')
or hasField('lazy_start')
):
return ConfigHysteria1(config, **kwargs)
if (
hasField('tls')
or hasField('transport')
or hasField('quic')
or hasField('bandwidth')
or hasField('tcpForwarding')
or hasField('udpForwarding')
or hasField('tcpTProxy')
or hasField('udpTProxy')
or isinstance(config.get('obfs'), dict)
or hasField('fastOpen')
or hasField('lazy')
):
return ConfigHysteria2(config, **kwargs)
# Copied to factory unrecognized
return ConfigFactory(config, **kwargs)
def configFactoryFromAny(config: Union[str, dict], **kwargs) -> ConfigFactory:
if isinstance(config, str):
if (
config.startswith('vmess://')
or config.startswith('vless://')
or config.startswith('ss://')
or config.startswith('trojan://')
):
return ConfigXray(config, **kwargs)
if config.startswith('hysteria://'):
return ConfigHysteria1(config, **kwargs)
if config.startswith('hy2://') or config.startswith('hysteria2://'):
return ConfigHysteria2(config, **kwargs)
try:
# Try to construct from JSON string
return configFactoryFromDict(UJSONEncoder.decode(config), **kwargs)
except Exception:
# Any non-exit exceptions
return ConfigFactory(**kwargs)
if isinstance(config, dict):
return configFactoryFromDict(config, **kwargs)
return ConfigFactory(**kwargs)
def configFactoryBlank(protocol: Protocol) -> ConfigFactory:
if protocol == Protocol.VMess or protocol == Protocol.VLESS:
factory = configFactoryFromDict(BLANK_CONFIG_XRAY)
factory['outbounds'][0]['protocol'] = protocol.value.lower()
factory['outbounds'][0]['settings']['vnext'] = [
{
'address': '',
'port': 0,
'users': [{'email': PROXY_OUTBOUND_USER_EMAIL}],
},
]
return factory
if protocol == Protocol.Shadowsocks or protocol == Protocol.Trojan:
factory = configFactoryFromDict(BLANK_CONFIG_XRAY)
factory['outbounds'][0]['protocol'] = protocol.value.lower()
factory['outbounds'][0]['settings']['servers'] = [
{
'address': '',
'port': 0,
'email': PROXY_OUTBOUND_USER_EMAIL,
},
]
return factory
if protocol == Protocol.Hysteria1:
factory = configFactoryFromDict(BLANK_CONFIG_HYSTERIA1)
return factory
if protocol == Protocol.Hysteria2:
factory = configFactoryFromDict(BLANK_CONFIG_HYSTERIA2)
return factory
return ConfigFactory()