mirror of
https://github.com/LorenEteval/Furious.git
synced 2026-09-25 08:18:07 +03:00
1331 lines
40 KiB
Python
1331 lines
40 KiB
Python
from __future__ import annotations
|
|
|
|
from Furious.Interface.Encoder import UJSONEncoder, PyBase64Encoder
|
|
from Furious.Interface.ConfigurationFactory import ConfigurationFactory
|
|
from Furious.Library.Utility import parseHostPort
|
|
|
|
import typing
|
|
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)
|
|
|
|
|
|
class ConfigurationXrayProxyOutboundObjectV(dict):
|
|
def __init__(
|
|
self,
|
|
protocol,
|
|
remote_host,
|
|
remote_port,
|
|
uuid_,
|
|
encryption,
|
|
type_,
|
|
security,
|
|
**kwargs,
|
|
):
|
|
networkObjectArgs, securityArgs, TLSObjectArgs = {}, {}, {}
|
|
|
|
if type_ == 'h2':
|
|
networkKey = 'httpSettings'
|
|
else:
|
|
networkKey = f'{type_}Settings'
|
|
|
|
networkObjectArgs[
|
|
networkKey
|
|
] = ConfigurationXray.kwargs2ProxyStreamSettingsNetworkObject(
|
|
type_, remote_host, kwargs
|
|
)
|
|
|
|
if security:
|
|
securityArgs['security'] = security
|
|
|
|
if security != 'none':
|
|
TLSObjectArgs[
|
|
# tlsSettings, realitySettings
|
|
f'{security}Settings'
|
|
] = ConfigurationXray.kwargs2ProxyStreamSettingsTLSObject(
|
|
protocol, remote_host, security, kwargs
|
|
)
|
|
|
|
super().__init__(
|
|
**{
|
|
'tag': 'proxy',
|
|
'protocol': protocol,
|
|
'settings': {
|
|
'vnext': [
|
|
{
|
|
'address': remote_host,
|
|
'port': int(remote_port),
|
|
'users': [
|
|
ConfigurationXray.kwargs2ProxyUserObject(
|
|
protocol, uuid_, encryption, kwargs
|
|
),
|
|
],
|
|
},
|
|
]
|
|
},
|
|
'streamSettings': {
|
|
'network': type_,
|
|
**networkObjectArgs,
|
|
**securityArgs,
|
|
**TLSObjectArgs,
|
|
},
|
|
'mux': {
|
|
'enabled': False,
|
|
'concurrency': -1,
|
|
},
|
|
},
|
|
)
|
|
|
|
|
|
class ConfigurationXrayProxyOutboundObjectSS(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,
|
|
# TODO. Extension
|
|
'email': 'example@email.com',
|
|
'ota': False,
|
|
},
|
|
]
|
|
},
|
|
'streamSettings': {
|
|
'network': 'tcp',
|
|
},
|
|
'mux': {
|
|
'enabled': False,
|
|
'concurrency': -1,
|
|
},
|
|
}
|
|
)
|
|
|
|
|
|
class ConfigurationXrayProxyOutboundObjectTrojan(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
|
|
] = ConfigurationXray.kwargs2ProxyStreamSettingsNetworkObject(
|
|
type_, address, kwargs
|
|
)
|
|
|
|
if security:
|
|
securityArgs['security'] = security
|
|
|
|
if security != 'none':
|
|
TLSObjectArgs[
|
|
# tlsSettings, realitySettings
|
|
f'{security}Settings'
|
|
] = ConfigurationXray.kwargs2ProxyStreamSettingsTLSObject(
|
|
'trojan', address, security, kwargs
|
|
)
|
|
|
|
super().__init__(
|
|
**{
|
|
'tag': 'proxy',
|
|
'protocol': 'trojan',
|
|
'settings': {
|
|
'servers': [
|
|
{
|
|
'address': address,
|
|
'port': int(port),
|
|
'password': password,
|
|
# TODO. Extension
|
|
'email': 'example@email.com',
|
|
},
|
|
]
|
|
},
|
|
'streamSettings': {
|
|
'network': type_,
|
|
**networkObjectArgs,
|
|
**securityArgs,
|
|
**TLSObjectArgs,
|
|
},
|
|
'mux': {
|
|
'enabled': False,
|
|
'concurrency': -1,
|
|
},
|
|
},
|
|
)
|
|
|
|
|
|
class ConfigurationXray(ConfigurationFactory):
|
|
def __init__(self, config: typing.Union[str, dict] = ''):
|
|
super().__init__(config)
|
|
|
|
@property
|
|
def proxyOutboundObject(self) -> dict:
|
|
try:
|
|
for outboundObject in self['outbounds']:
|
|
if outboundObject['tag'] == 'proxy':
|
|
return outboundObject
|
|
|
|
return {}
|
|
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:
|
|
if (
|
|
self.proxyProtocol.lower() == 'vmess'
|
|
or self.proxyProtocol.lower() == 'vless'
|
|
):
|
|
return self.proxyOutboundObject['settings']['vnext'][0]
|
|
|
|
if (
|
|
self.proxyProtocol.lower() == 'shadowsocks'
|
|
or self.proxyProtocol.lower() == '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 self.proxyOutboundObject['streamSettings']
|
|
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'
|
|
|
|
return self.proxyStreamSettingsObject[networkKey]
|
|
except Exception:
|
|
# Any non-exit exceptions
|
|
|
|
return {}
|
|
|
|
@property
|
|
def kwargsFromVMessProxyStreamSettingsNetworkObject(self) -> dict:
|
|
kwargs = {}
|
|
|
|
network = self.proxyStreamSettingsNetwork
|
|
networkObject = self.proxyStreamSettingsNetworkObject
|
|
|
|
if not networkObject:
|
|
return kwargs
|
|
|
|
def hasKey(key):
|
|
return networkObject.get(key) is not None
|
|
|
|
if network == 'tcp':
|
|
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']
|
|
|
|
return kwargs
|
|
|
|
@property
|
|
def kwargsFromVLESSProxyStreamSettingsNetworkObject(self) -> dict:
|
|
kwargs = {}
|
|
|
|
network = self.proxyStreamSettingsNetwork
|
|
networkObject = self.proxyStreamSettingsNetworkObject
|
|
|
|
if not networkObject:
|
|
return kwargs
|
|
|
|
def hasKey(key):
|
|
return networkObject.get(key) is not None
|
|
|
|
if network == 'tcp':
|
|
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']
|
|
|
|
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':
|
|
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'
|
|
|
|
return GRPCObject
|
|
|
|
@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 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('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(','),
|
|
)
|
|
)
|
|
|
|
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 security == 'reality':
|
|
# More args for reality
|
|
pbk = kwargs.get('pbk')
|
|
sid = kwargs.get('sid', '')
|
|
spx = kwargs.get('spx', '')
|
|
|
|
if pbk:
|
|
TLSObject['publicKey'] = pbk
|
|
|
|
TLSObject['shortId'] = sid
|
|
TLSObject['spiderX'] = unquote(spx)
|
|
|
|
return TLSObject
|
|
|
|
@staticmethod
|
|
def kwargs2ProxyUserObject(protocol, uuid_, encryption, kwargs) -> dict:
|
|
if protocol == 'vmess':
|
|
UserObject = {
|
|
'id': uuid_,
|
|
'security': encryption,
|
|
# TODO. Extension
|
|
'email': 'example@email.com',
|
|
}
|
|
|
|
# For VMess(v2rayN share standard) only.
|
|
if kwargs.get('aid') is not None:
|
|
UserObject['alterId'] = kwargs.get('aid')
|
|
|
|
return UserObject
|
|
|
|
if protocol == 'vless':
|
|
UserObject = {
|
|
'id': uuid_,
|
|
'encryption': encryption,
|
|
# TODO. Extension
|
|
'email': 'example@email.com',
|
|
}
|
|
|
|
if kwargs.get('flow'):
|
|
# flow is empty, TLS. Otherwise, XTLS.
|
|
UserObject['flow'] = kwargs.get('flow')
|
|
|
|
return UserObject
|
|
|
|
return {}
|
|
|
|
def toURI(self, remark: str = '') -> str:
|
|
if self.proxyProtocol.lower() == 'vmess':
|
|
netloc = PyBase64Encoder.encode(
|
|
UJSONEncoder.encode(
|
|
{
|
|
'v': '2',
|
|
'ps': remark,
|
|
**{
|
|
key: self.proxyServerObject[value]
|
|
for key, value in {
|
|
'add': 'address',
|
|
'port': 'port',
|
|
}.items()
|
|
},
|
|
**{
|
|
key: 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 self.proxyProtocol.lower() == '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(remark)])
|
|
|
|
if self.proxyProtocol.lower() == '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(remark)])
|
|
|
|
if self.proxyProtocol.lower() == '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(remark)])
|
|
|
|
raise ValueError(
|
|
f'Unrecognized protocol {self.proxyProtocol} when converting to URI'
|
|
)
|
|
|
|
@staticmethod
|
|
def URI2ProxyOutboundObjectVMess(URI: str) -> typing.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 ConfigurationXray.URI2ProxyOutboundObjectVLESS(URI, protocol='vmess')
|
|
else:
|
|
|
|
def getOrDefault(key, default=''):
|
|
return myJSON.get(key, default)
|
|
|
|
remark = unquote(getOrDefault('ps'))
|
|
|
|
return (
|
|
remark,
|
|
# Ignore: v
|
|
ConfigurationXrayProxyOutboundObjectV(
|
|
'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) -> typing.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,
|
|
ConfigurationXrayProxyOutboundObjectV(
|
|
kwargs.pop('protocol', 'vless'),
|
|
remote_host,
|
|
int(remote_port),
|
|
uuid_,
|
|
encryption,
|
|
type_,
|
|
security,
|
|
# kwargs
|
|
**queryObject,
|
|
),
|
|
)
|
|
|
|
@staticmethod
|
|
def URI2ProxyOutboundObjectSS(URI: str) -> typing.Tuple[str, dict]:
|
|
try:
|
|
result = urlparse(URI)
|
|
remark = unquote(result.fragment)
|
|
except Exception:
|
|
# Any non-exit exceptions
|
|
|
|
return '', {}
|
|
|
|
def getSSParams():
|
|
try:
|
|
# ss://base64...#fragment
|
|
userinfo, server = (
|
|
PyBase64Encoder.decode(result.netloc).decode().split('@')
|
|
)
|
|
|
|
return [*userinfo.split(':'), *parseHostPort(server)]
|
|
except Exception:
|
|
# Any non-exit exceptions
|
|
|
|
pass
|
|
|
|
# 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(':'),
|
|
*parseHostPort(server),
|
|
]
|
|
except Exception:
|
|
# Any non-exit exceptions
|
|
|
|
pass
|
|
|
|
try:
|
|
# Try pack with 4 element
|
|
userinfo, server = result.netloc.split('@')
|
|
|
|
return [*userinfo.split(':'), *parseHostPort(server)]
|
|
except Exception:
|
|
# Any non-exit exceptions
|
|
|
|
pass
|
|
|
|
raise ValueError(f'Invalid SS URI format {URI}')
|
|
|
|
return (
|
|
remark,
|
|
ConfigurationXrayProxyOutboundObjectSS(*getSSParams()),
|
|
)
|
|
|
|
@staticmethod
|
|
def URI2ProxyOutboundObjectTrojan(URI: str) -> typing.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,
|
|
ConfigurationXrayProxyOutboundObjectTrojan(
|
|
password, address, port, type_, security, **queryObject
|
|
),
|
|
)
|
|
|
|
@staticmethod
|
|
def URI2ProxyOutboundObject(URI: str) -> typing.Tuple[str, dict]:
|
|
try:
|
|
myHead, myBody = URI.split('://')
|
|
except Exception as ex:
|
|
# Any non-exit exceptions
|
|
|
|
raise ValueError(f'Invalid URI format {URI}') from ex
|
|
|
|
if myHead.lower() == 'vmess':
|
|
return ConfigurationXray.URI2ProxyOutboundObjectVMess(URI)
|
|
|
|
if myHead.lower() == 'vless':
|
|
return ConfigurationXray.URI2ProxyOutboundObjectVLESS(URI)
|
|
|
|
if myHead.lower() == 'ss':
|
|
return ConfigurationXray.URI2ProxyOutboundObjectSS(URI)
|
|
|
|
if myHead.lower() == 'trojan':
|
|
return ConfigurationXray.URI2ProxyOutboundObjectTrojan(URI)
|
|
|
|
raise ValueError(f'Unrecognized URI scheme {URI}')
|
|
|
|
def fromURI(self, URI: str) -> typing.Tuple[str, bool]:
|
|
try:
|
|
remark, proxyOutboundObject = ConfigurationXray.URI2ProxyOutboundObject(URI)
|
|
|
|
dict.__init__(
|
|
self,
|
|
**{
|
|
# 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
|
|
proxyOutboundObject,
|
|
# direct
|
|
{
|
|
'tag': 'direct',
|
|
'protocol': 'freedom',
|
|
'settings': {},
|
|
},
|
|
# block
|
|
{
|
|
'tag': 'block',
|
|
'protocol': 'blackhole',
|
|
'settings': {
|
|
'response': {
|
|
'type': 'http',
|
|
}
|
|
},
|
|
},
|
|
],
|
|
'routing': {},
|
|
},
|
|
)
|
|
|
|
return remark, True
|
|
except Exception:
|
|
# Any non-exit exceptions
|
|
|
|
return '', False
|
|
|
|
def httpProxyEndpoint(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 socksProxyEndpoint(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 setHttpProxyEndpoint(self, endpoint: str) -> bool:
|
|
try:
|
|
for inbound in self['inbounds']:
|
|
if inbound['protocol'] == 'http':
|
|
# Note: If there are multiple http inbounds
|
|
# satisfied, the first one will be chosen.
|
|
listen, port = parseHostPort(endpoint)
|
|
|
|
inbound['listen'], inbound['port'] = listen, int(port)
|
|
|
|
return True
|
|
|
|
return False
|
|
except Exception:
|
|
# Any non-exit exceptions
|
|
|
|
return False
|
|
|
|
def setSocksProxyEndpoint(self, endpoint: str) -> bool:
|
|
try:
|
|
for inbound in self['inbounds']:
|
|
if inbound['protocol'] == 'socks':
|
|
# Note: If there are multiple socks inbounds
|
|
# satisfied, the first one will be chosen.
|
|
listen, port = parseHostPort(endpoint)
|
|
|
|
inbound['listen'], inbound['port'] = listen, int(port)
|
|
|
|
return True
|
|
|
|
return False
|
|
except Exception:
|
|
# Any non-exit exceptions
|
|
|
|
return False
|
|
|
|
def logAccessPath(self) -> str:
|
|
try:
|
|
return self['log']['access']
|
|
except Exception:
|
|
# Any non-exit exceptions
|
|
|
|
return ''
|
|
|
|
def logErrorPath(self) -> str:
|
|
try:
|
|
return self['log']['error']
|
|
except Exception:
|
|
# Any non-exit exceptions
|
|
|
|
return ''
|
|
|
|
def setLogAccessPath(self, path) -> bool:
|
|
try:
|
|
self['log']['access'] = path
|
|
|
|
return True
|
|
except Exception:
|
|
# Any non-exit exceptions
|
|
|
|
return False
|
|
|
|
def setLogErrorPath(self, path) -> bool:
|
|
try:
|
|
self['log']['error'] = path
|
|
|
|
return True
|
|
except Exception:
|
|
# Any non-exit exceptions
|
|
|
|
return False
|
|
|
|
|
|
class ConfigurationHysteria1(ConfigurationFactory):
|
|
def __init__(self, config: typing.Union[str, dict] = ''):
|
|
super().__init__(config)
|
|
|
|
def toURI(self, remark: str = '') -> str:
|
|
raise NotImplementedError
|
|
|
|
def fromURI(self, URI: str) -> typing.Tuple[str, bool]:
|
|
raise NotImplementedError
|
|
|
|
def httpProxyEndpoint(self) -> str:
|
|
try:
|
|
return self['http']['listen']
|
|
except Exception:
|
|
# Any non-exit exceptions
|
|
|
|
return ''
|
|
|
|
def socksProxyEndpoint(self) -> str:
|
|
try:
|
|
return self['socks5']['listen']
|
|
except Exception:
|
|
# Any non-exit exceptions
|
|
|
|
return ''
|
|
|
|
def setHttpProxyEndpoint(self, endpoint: str) -> bool:
|
|
try:
|
|
self['http']['listen'] = endpoint
|
|
|
|
return True
|
|
except Exception:
|
|
# Any non-exit exceptions
|
|
|
|
return False
|
|
|
|
def setSocksProxyEndpoint(self, endpoint: str) -> bool:
|
|
try:
|
|
self['socks5']['listen'] = endpoint
|
|
|
|
return True
|
|
except Exception:
|
|
# Any non-exit exceptions
|
|
|
|
return False
|
|
|
|
|
|
class ConfigurationHysteria2(ConfigurationFactory):
|
|
def __init__(self, config: typing.Union[str, dict] = ''):
|
|
super().__init__(config)
|
|
|
|
def toURI(self, remark: str = '') -> str:
|
|
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(remark)])
|
|
|
|
def fromURI(self, URI: str) -> typing.Tuple[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,
|
|
'tls': {
|
|
'sni': sni,
|
|
'insecure': insecure,
|
|
**pinSHA256Arg,
|
|
},
|
|
**obfsArg,
|
|
'socks5': {
|
|
'listen': '127.0.0.1:10808',
|
|
},
|
|
'http': {
|
|
'listen': '127.0.0.1:10809',
|
|
},
|
|
},
|
|
)
|
|
|
|
return remark, True
|
|
except Exception:
|
|
# Any non-exit exceptions
|
|
|
|
return '', False
|
|
|
|
def httpProxyEndpoint(self) -> str:
|
|
try:
|
|
return self['http']['listen']
|
|
except Exception:
|
|
# Any non-exit exceptions
|
|
|
|
return ''
|
|
|
|
def socksProxyEndpoint(self) -> str:
|
|
try:
|
|
return self['socks5']['listen']
|
|
except Exception:
|
|
# Any non-exit exceptions
|
|
|
|
return ''
|
|
|
|
def setHttpProxyEndpoint(self, endpoint: str) -> bool:
|
|
try:
|
|
self['http']['listen'] = endpoint
|
|
|
|
return True
|
|
except Exception:
|
|
# Any non-exit exceptions
|
|
|
|
return False
|
|
|
|
def setSocksProxyEndpoint(self, endpoint: str) -> bool:
|
|
try:
|
|
self['socks5']['listen'] = endpoint
|
|
|
|
return True
|
|
except Exception:
|
|
# Any non-exit exceptions
|
|
|
|
return False
|