mirror of
https://github.com/LorenEteval/Furious.git
synced 2026-10-01 11:28:10 +03:00
Add trojan import/export support
Signed-off-by: Loren Eteval <loren.eteval@proton.me>
This commit is contained in:
+42
-17
@@ -38,12 +38,33 @@ class ExportLinkResultBox(MessageBox):
|
||||
|
||||
self.setWindowTitle(_('Export'))
|
||||
|
||||
if len(self.failureStr):
|
||||
self.setIcon(MessageBox.Icon.Critical)
|
||||
else:
|
||||
self.setIcon(MessageBox.Icon.Information)
|
||||
|
||||
def getText(self):
|
||||
text = self.getTextAll()
|
||||
|
||||
if len(text) <= 1000:
|
||||
return text
|
||||
else:
|
||||
# Limited
|
||||
if len(self.successStr) == 0:
|
||||
return (
|
||||
_('Export as QR code failed:')
|
||||
if self.isQRCodeExport
|
||||
else _('Export share link to clipboard failed:')
|
||||
) + f'\n\n...'
|
||||
elif len(self.failureStr) == 0:
|
||||
return (
|
||||
_('Export as QR code success:')
|
||||
if self.isQRCodeExport
|
||||
else _('Export share link to clipboard success:')
|
||||
) + f'\n\n...'
|
||||
else:
|
||||
return (
|
||||
_('Export as QR code partially success:')
|
||||
if self.isQRCodeExport
|
||||
else _('Export share link to clipboard partially success:')
|
||||
) + f'\n\n...'
|
||||
|
||||
def getTextAll(self):
|
||||
if len(self.successStr) == 0:
|
||||
return (
|
||||
(
|
||||
@@ -151,6 +172,20 @@ def exportLink(selectedIndex):
|
||||
return serverLink, successStr, failureStr
|
||||
|
||||
|
||||
def showExportLinkResult(resultBox, successStr, failureStr):
|
||||
resultBox.successStr = successStr
|
||||
resultBox.failureStr = failureStr
|
||||
resultBox.setText(resultBox.getText())
|
||||
|
||||
if len(successStr) == 0:
|
||||
resultBox.setIcon(MessageBox.Icon.Critical)
|
||||
else:
|
||||
resultBox.setIcon(MessageBox.Icon.Information)
|
||||
|
||||
# Show the MessageBox and wait for user to close it
|
||||
resultBox.exec()
|
||||
|
||||
|
||||
class ExportLinkAction(Action):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(_('Export Share Link To Clipboard'), **kwargs)
|
||||
@@ -168,12 +203,7 @@ class ExportLinkAction(Action):
|
||||
|
||||
QApplication.clipboard().setText('\n'.join(serverLink))
|
||||
|
||||
self.exportLinkResult.successStr = successStr
|
||||
self.exportLinkResult.failureStr = failureStr
|
||||
self.exportLinkResult.setText(self.exportLinkResult.getText())
|
||||
|
||||
# Show the MessageBox and wait for user to close it
|
||||
self.exportLinkResult.exec()
|
||||
showExportLinkResult(self.exportLinkResult, successStr, failureStr)
|
||||
|
||||
|
||||
class ExportQRCodeAction(Action):
|
||||
@@ -201,12 +231,7 @@ class ExportQRCodeAction(Action):
|
||||
self.exportQRCode.initTabWithData(list(zip(successStr, serverLink)))
|
||||
self.exportQRCode.show()
|
||||
|
||||
self.exportLinkResult.successStr = successStr
|
||||
self.exportLinkResult.failureStr = failureStr
|
||||
self.exportLinkResult.setText(self.exportLinkResult.getText())
|
||||
|
||||
# Show the MessageBox and wait for user to close it
|
||||
self.exportLinkResult.exec()
|
||||
showExportLinkResult(self.exportLinkResult, successStr, failureStr)
|
||||
|
||||
|
||||
class ExportJSONAction(Action):
|
||||
|
||||
@@ -20,6 +20,7 @@ from Furious.Core.Configuration import (
|
||||
XrayCoreConfiguration,
|
||||
ProxyOutboundObject,
|
||||
ProxyOutboundObjectSS,
|
||||
ProxyOutboundObjectTrojan,
|
||||
)
|
||||
from Furious.Gui.Action import Action
|
||||
from Furious.Widget.Widget import Menu, MessageBox
|
||||
@@ -110,6 +111,15 @@ class ImportLinkResultBox(MessageBox):
|
||||
self.setText(self.getText())
|
||||
|
||||
def getText(self):
|
||||
text = self.getTextAll()
|
||||
|
||||
if len(text) <= 1000:
|
||||
return text
|
||||
else:
|
||||
# Limited
|
||||
return _('Import share link success: ') + f'\n\n...'
|
||||
|
||||
def getTextAll(self):
|
||||
return (
|
||||
_('Import share link success: ')
|
||||
+ f'\n\n'
|
||||
@@ -142,7 +152,9 @@ class ImportLinkAction(Action):
|
||||
|
||||
def showLinkErrorBox(self):
|
||||
self.linkErrorBox.setWindowTitle(_('Import'))
|
||||
|
||||
if len(self.clipboard) > 1000:
|
||||
# Limited
|
||||
self.linkErrorBox.setText(_('Invalid share link.'))
|
||||
self.linkErrorBox.setInformativeText('')
|
||||
else:
|
||||
@@ -359,6 +371,56 @@ class ImportLinkAction(Action):
|
||||
|
||||
return self.parseShareLinkSIP002(data)
|
||||
|
||||
@staticmethod
|
||||
def parseShareLinkTrojan(data):
|
||||
try:
|
||||
parseResult = urllib.parse.urlparse(data)
|
||||
|
||||
queryObject = {
|
||||
key: value for key, value in urllib.parse.parse_qsl(parseResult.query)
|
||||
}
|
||||
|
||||
remark = urllib.parse.unquote(parseResult.fragment)
|
||||
|
||||
password, address, port = re.split(r'[@:]', parseResult.netloc)
|
||||
|
||||
type_ = queryObject.get('type', 'tcp')
|
||||
security = queryObject.get('security', 'none')
|
||||
|
||||
# Remove redundant items
|
||||
queryObject.pop('type', '')
|
||||
queryObject.pop('security', '')
|
||||
|
||||
myJSON = XrayCoreConfiguration.build(
|
||||
ProxyOutboundObjectTrojan(
|
||||
password,
|
||||
address,
|
||||
int(port),
|
||||
type_,
|
||||
security,
|
||||
# kwargs
|
||||
**queryObject,
|
||||
)
|
||||
)
|
||||
|
||||
APP().ServerWidget.importServer(
|
||||
remark,
|
||||
ujson.dumps(
|
||||
myJSON, indent=2, ensure_ascii=False, escape_forward_slashes=False
|
||||
),
|
||||
)
|
||||
|
||||
logger.info(
|
||||
f'import share link from clipboard success. '
|
||||
f'Remark: {remark}. Protocol: {Protocol.Trojan}'
|
||||
)
|
||||
|
||||
return remark, True
|
||||
except Exception:
|
||||
# Any non-exit exceptions
|
||||
|
||||
return '', False
|
||||
|
||||
def parseShareLink(self, shareLink):
|
||||
try:
|
||||
myHead, myBody = shareLink.split('://')
|
||||
@@ -378,13 +440,17 @@ class ImportLinkAction(Action):
|
||||
else:
|
||||
return self.parseShareLinkVMess(myJSON, isV2rayN=True)
|
||||
|
||||
if myHead.lower() == 'vless':
|
||||
elif myHead.lower() == 'vless':
|
||||
return self.parseShareLinkStandard('vless', shareLink)
|
||||
|
||||
if myHead.lower() == 'ss':
|
||||
elif myHead.lower() == 'ss':
|
||||
return self.parseShareLinkSS(shareLink)
|
||||
|
||||
return '', False
|
||||
elif myHead.lower() == 'trojan':
|
||||
return self.parseShareLinkTrojan(shareLink)
|
||||
|
||||
else:
|
||||
return '', False
|
||||
|
||||
def triggeredCallback(self, checked):
|
||||
self.clipboard = QApplication.clipboard().text().strip()
|
||||
@@ -446,6 +512,7 @@ class ImportJSONAction(Action):
|
||||
self.jsonErrorBox.setWindowTitle(_('Import'))
|
||||
|
||||
if len(self.clipboard) > 1000:
|
||||
# Limited
|
||||
self.jsonErrorBox.setText(_('Invalid JSON data.'))
|
||||
self.jsonErrorBox.setInformativeText('')
|
||||
else:
|
||||
|
||||
+275
-156
@@ -18,7 +18,7 @@
|
||||
from Furious.Core.Core import XrayCore
|
||||
from Furious.Core.Intellisense import Intellisense
|
||||
from Furious.Widget.Widget import MessageBox
|
||||
from Furious.Utility.Constants import APPLICATION_NAME
|
||||
from Furious.Utility.Constants import APPLICATION_NAME, PROXY_OUTBOUND_USER_EMAIL
|
||||
from Furious.Utility.Utility import Base64Encoder, Protocol, bootstrapIcon
|
||||
from Furious.Utility.Translator import gettext as _
|
||||
|
||||
@@ -42,6 +42,7 @@ class Configuration:
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
@functools.lru_cache(32)
|
||||
def toJSON(text):
|
||||
return ujson.loads(text)
|
||||
|
||||
@@ -72,23 +73,20 @@ class Configuration:
|
||||
def export(remark, ob):
|
||||
if Intellisense.getCoreType(ob) == XrayCore.name():
|
||||
|
||||
def hasKey(obj, key):
|
||||
return obj.get(key) is not None
|
||||
|
||||
def getStreamNetSettings(protocol, streamObject, netType):
|
||||
def streamNetSettingsVMess(streamObject, netType):
|
||||
kwargs = {}
|
||||
|
||||
netobj = streamObject[
|
||||
ProxyOutboundObject.getStreamNetworkSettingsName(netType)
|
||||
]
|
||||
netobj = streamObject.get(ProxyOutboundObject.streamNetworkKey(netType))
|
||||
|
||||
if netobj is None:
|
||||
return kwargs
|
||||
|
||||
def hasKey(key):
|
||||
return netobj.get(key) is not None
|
||||
|
||||
if netType == 'tcp':
|
||||
try:
|
||||
if protocol == Protocol.VMess:
|
||||
kwargs['type'] = netobj['header']['type']
|
||||
|
||||
if protocol == Protocol.VLESS:
|
||||
kwargs['headerType'] = netobj['header']['type']
|
||||
kwargs['type'] = netobj['header']['type']
|
||||
except Exception:
|
||||
# Any non-exit exceptions
|
||||
|
||||
@@ -97,17 +95,10 @@ class Configuration:
|
||||
elif netType == 'kcp':
|
||||
try:
|
||||
# Get order matters here
|
||||
if protocol == Protocol.VMess:
|
||||
if hasKey(netobj, 'seed'):
|
||||
kwargs['path'] = netobj['seed']
|
||||
if hasKey('seed'):
|
||||
kwargs['path'] = netobj['seed']
|
||||
|
||||
kwargs['type'] = netobj['header']['type']
|
||||
|
||||
if protocol == Protocol.VLESS:
|
||||
if hasKey(netobj, 'seed'):
|
||||
kwargs['seed'] = netobj['seed']
|
||||
|
||||
kwargs['headerType'] = netobj['header']['type']
|
||||
kwargs['type'] = netobj['header']['type']
|
||||
except Exception:
|
||||
# Any non-exit exceptions
|
||||
|
||||
@@ -116,7 +107,7 @@ class Configuration:
|
||||
elif netType == 'ws':
|
||||
try:
|
||||
# Get order matters here
|
||||
if hasKey(netobj, 'path'):
|
||||
if hasKey('path'):
|
||||
kwargs['path'] = quote(netobj['path'])
|
||||
|
||||
if netobj['headers']['Host']:
|
||||
@@ -129,7 +120,7 @@ class Configuration:
|
||||
elif netType == 'h2' or netType == 'http':
|
||||
try:
|
||||
# Get order matters here
|
||||
if hasKey(netobj, 'path'):
|
||||
if hasKey('path'):
|
||||
kwargs['path'] = quote(netobj['path'])
|
||||
|
||||
kwargs['host'] = quote(','.join(netobj['host']))
|
||||
@@ -141,48 +132,108 @@ class Configuration:
|
||||
elif netType == 'quic':
|
||||
try:
|
||||
# Get order matters here
|
||||
if protocol == Protocol.VMess:
|
||||
if hasKey(netobj, 'security'):
|
||||
kwargs['host'] = netobj['security']
|
||||
if hasKey('security'):
|
||||
kwargs['host'] = netobj['security']
|
||||
|
||||
if hasKey(netobj, 'key'):
|
||||
kwargs['path'] = quote(netobj['key'])
|
||||
if hasKey('key'):
|
||||
kwargs['path'] = quote(netobj['key'])
|
||||
|
||||
kwargs['type'] = netobj['header']['type']
|
||||
|
||||
if protocol == Protocol.VLESS:
|
||||
if hasKey(netobj, 'security'):
|
||||
kwargs['quicSecurity'] = netobj['security']
|
||||
|
||||
if hasKey(netobj, 'key'):
|
||||
kwargs['path'] = quote(netobj['key'])
|
||||
|
||||
kwargs['headerType'] = netobj['header']['type']
|
||||
kwargs['type'] = netobj['header']['type']
|
||||
except Exception:
|
||||
# Any non-exit exceptions
|
||||
|
||||
pass
|
||||
|
||||
elif netType == 'grpc':
|
||||
if protocol == Protocol.VMess:
|
||||
if hasKey(netobj, 'serviceName'):
|
||||
kwargs['path'] = netobj['serviceName']
|
||||
|
||||
if protocol == Protocol.VLESS:
|
||||
if hasKey(netobj, 'serviceName'):
|
||||
kwargs['serviceName'] = netobj['serviceName']
|
||||
if hasKey('serviceName'):
|
||||
kwargs['path'] = netobj['serviceName']
|
||||
|
||||
return kwargs
|
||||
|
||||
def getStreamTLSSettings(streamObject, tlsType):
|
||||
if tlsType == 'none':
|
||||
return {}
|
||||
|
||||
def streamNetSettingsVLESS(streamObject, netType):
|
||||
kwargs = {}
|
||||
|
||||
tlsobj = streamObject[
|
||||
ProxyOutboundObject.getStreamTLSSettingsName(tlsType)
|
||||
]
|
||||
netobj = streamObject.get(ProxyOutboundObject.streamNetworkKey(netType))
|
||||
|
||||
if netobj is None:
|
||||
return kwargs
|
||||
|
||||
def hasKey(key):
|
||||
return netobj.get(key) is not None
|
||||
|
||||
if netType == 'tcp':
|
||||
try:
|
||||
kwargs['headerType'] = netobj['header']['type']
|
||||
except Exception:
|
||||
# Any non-exit exceptions
|
||||
|
||||
pass
|
||||
|
||||
elif netType == 'kcp':
|
||||
try:
|
||||
# Get order matters here
|
||||
if hasKey('seed'):
|
||||
kwargs['seed'] = netobj['seed']
|
||||
|
||||
kwargs['headerType'] = netobj['header']['type']
|
||||
except Exception:
|
||||
# Any non-exit exceptions
|
||||
|
||||
pass
|
||||
|
||||
elif netType == 'ws':
|
||||
try:
|
||||
# Get order matters here
|
||||
if hasKey('path'):
|
||||
kwargs['path'] = quote(netobj['path'])
|
||||
|
||||
if netobj['headers']['Host']:
|
||||
kwargs['host'] = quote(netobj['headers']['Host'])
|
||||
except Exception:
|
||||
# Any non-exit exceptions
|
||||
|
||||
pass
|
||||
|
||||
elif netType == 'h2' or netType == 'http':
|
||||
try:
|
||||
# Get order matters here
|
||||
if hasKey('path'):
|
||||
kwargs['path'] = quote(netobj['path'])
|
||||
|
||||
kwargs['host'] = quote(','.join(netobj['host']))
|
||||
except Exception:
|
||||
# Any non-exit exceptions
|
||||
|
||||
pass
|
||||
|
||||
elif netType == 'quic':
|
||||
try:
|
||||
# Get order matters here
|
||||
if hasKey('security'):
|
||||
kwargs['quicSecurity'] = netobj['security']
|
||||
|
||||
if hasKey('key'):
|
||||
kwargs['path'] = quote(netobj['key'])
|
||||
|
||||
kwargs['headerType'] = netobj['header']['type']
|
||||
except Exception:
|
||||
# Any non-exit exceptions
|
||||
|
||||
pass
|
||||
|
||||
elif netType == 'grpc':
|
||||
if hasKey('serviceName'):
|
||||
kwargs['serviceName'] = netobj['serviceName']
|
||||
|
||||
return kwargs
|
||||
|
||||
def streamTLSSettings(streamObject, tlsType):
|
||||
kwargs = {}
|
||||
|
||||
if tlsType == 'none':
|
||||
return kwargs
|
||||
|
||||
tlsobj = streamObject[ProxyOutboundObject.streamTLSKey(tlsType)]
|
||||
|
||||
if tlsType == 'reality' or tlsType == 'tls':
|
||||
if tlsobj.get('fingerprint'):
|
||||
@@ -207,109 +258,142 @@ class Configuration:
|
||||
|
||||
return kwargs
|
||||
|
||||
def getProxyOutboundObject():
|
||||
for outboundObject in ob['outbounds']:
|
||||
if outboundObject['tag'] == 'proxy':
|
||||
return outboundObject
|
||||
|
||||
raise Exception('No proxy outbound found')
|
||||
|
||||
# Begin export
|
||||
coreProtocol = Intellisense.getCoreProtocol(ob)
|
||||
|
||||
if coreProtocol == Protocol.VMess or coreProtocol == Protocol.VLESS:
|
||||
proxyOutbound = None
|
||||
outbound = getProxyOutboundObject()
|
||||
|
||||
for outbound in ob['outbounds']:
|
||||
if outbound['tag'] == 'proxy':
|
||||
proxyOutbound = outbound
|
||||
|
||||
break
|
||||
|
||||
if proxyOutbound is None:
|
||||
raise Exception('No proxy outbound found')
|
||||
|
||||
proxyServer = proxyOutbound['settings']['vnext'][0]
|
||||
def exportVMess():
|
||||
proxyServer = outbound['settings']['vnext'][0]
|
||||
proxyServerUser = proxyServer['users'][0]
|
||||
|
||||
proxyStream = proxyOutbound['streamSettings']
|
||||
proxyStreamNet = proxyStream['network']
|
||||
proxyStreamTLS = proxyStream['security']
|
||||
proxyStream, proxyStreamNet, proxyStreamTLS = (
|
||||
outbound['streamSettings'],
|
||||
outbound['streamSettings']['network'],
|
||||
outbound['streamSettings']['security'],
|
||||
)
|
||||
|
||||
if coreProtocol == Protocol.VMess:
|
||||
return (
|
||||
'vmess://'
|
||||
+ Base64Encoder.encode(
|
||||
ujson.dumps(
|
||||
{
|
||||
'v': '2',
|
||||
'ps': quote(remark),
|
||||
'add': proxyServer['address'],
|
||||
'port': proxyServer['port'],
|
||||
'id': proxyServerUser['id'],
|
||||
'aid': proxyServerUser['alterId'],
|
||||
'scy': proxyServerUser['security'],
|
||||
'net': proxyStreamNet,
|
||||
'tls': proxyStreamTLS,
|
||||
# kwargs
|
||||
**getStreamNetSettings(
|
||||
coreProtocol, proxyStream, proxyStreamNet
|
||||
),
|
||||
**getStreamTLSSettings(proxyStream, proxyStreamTLS),
|
||||
},
|
||||
ensure_ascii=False,
|
||||
escape_forward_slashes=False,
|
||||
).encode()
|
||||
).decode()
|
||||
)
|
||||
return (
|
||||
'vmess://'
|
||||
+ Base64Encoder.encode(
|
||||
ujson.dumps(
|
||||
{
|
||||
'v': '2',
|
||||
'ps': quote(remark),
|
||||
'add': proxyServer['address'],
|
||||
'port': proxyServer['port'],
|
||||
'id': proxyServerUser['id'],
|
||||
'aid': proxyServerUser['alterId'],
|
||||
'scy': proxyServerUser['security'],
|
||||
'net': proxyStreamNet,
|
||||
'tls': proxyStreamTLS,
|
||||
# kwargs
|
||||
**streamNetSettingsVMess(proxyStream, proxyStreamNet),
|
||||
**streamTLSSettings(proxyStream, proxyStreamTLS),
|
||||
},
|
||||
ensure_ascii=False,
|
||||
escape_forward_slashes=False,
|
||||
).encode()
|
||||
).decode()
|
||||
)
|
||||
|
||||
if coreProtocol == Protocol.VLESS:
|
||||
flowArg = {}
|
||||
def exportVLESS():
|
||||
proxyServer = outbound['settings']['vnext'][0]
|
||||
proxyServerUser = proxyServer['users'][0]
|
||||
|
||||
if proxyServerUser.get('flow'):
|
||||
flowArg['flow'] = proxyServerUser['flow']
|
||||
proxyStream, proxyStreamNet, proxyStreamTLS = (
|
||||
outbound['streamSettings'],
|
||||
outbound['streamSettings']['network'],
|
||||
outbound['streamSettings']['security'],
|
||||
)
|
||||
|
||||
netloc = f'{proxyServerUser["id"]}@{proxyServer["address"]}:{proxyServer["port"]}'
|
||||
flowArg = {}
|
||||
|
||||
query = '&'.join(
|
||||
f'{key}={value}'
|
||||
for key, value in {
|
||||
'encryption': proxyServerUser['encryption'],
|
||||
'type': proxyStreamNet,
|
||||
'security': proxyStreamTLS,
|
||||
# kwargs
|
||||
**flowArg,
|
||||
**getStreamNetSettings(
|
||||
coreProtocol, proxyStream, proxyStreamNet
|
||||
),
|
||||
**getStreamTLSSettings(proxyStream, proxyStreamTLS),
|
||||
}.items()
|
||||
)
|
||||
if proxyServerUser.get('flow'):
|
||||
flowArg['flow'] = proxyServerUser['flow']
|
||||
|
||||
return urlunparse(['vless', netloc, '', '', query, quote(remark)])
|
||||
netloc = f'{proxyServerUser["id"]}@{proxyServer["address"]}:{proxyServer["port"]}'
|
||||
|
||||
if coreProtocol == Protocol.Shadowsocks:
|
||||
proxyOutbound = None
|
||||
query = '&'.join(
|
||||
f'{key}={value}'
|
||||
for key, value in {
|
||||
'encryption': proxyServerUser['encryption'],
|
||||
'type': proxyStreamNet,
|
||||
'security': proxyStreamTLS,
|
||||
# kwargs
|
||||
**flowArg,
|
||||
**streamNetSettingsVLESS(proxyStream, proxyStreamNet),
|
||||
**streamTLSSettings(proxyStream, proxyStreamTLS),
|
||||
}.items()
|
||||
)
|
||||
|
||||
for outbound in ob['outbounds']:
|
||||
if outbound['tag'] == 'proxy':
|
||||
proxyOutbound = outbound
|
||||
return urlunparse(['vless', netloc, '', '', query, quote(remark)])
|
||||
|
||||
break
|
||||
def exportSS():
|
||||
proxyServer = outbound['settings']['servers'][0]
|
||||
|
||||
if proxyOutbound is None:
|
||||
raise Exception('No proxy outbound found')
|
||||
|
||||
proxyServer = proxyOutbound['settings']['servers'][0]
|
||||
|
||||
method = proxyServer['method']
|
||||
password = proxyServer['password']
|
||||
address = proxyServer['address']
|
||||
port = proxyServer['port']
|
||||
method, password, address, port = (
|
||||
proxyServer['method'],
|
||||
proxyServer['password'],
|
||||
proxyServer['address'],
|
||||
proxyServer['port'],
|
||||
)
|
||||
|
||||
netloc = f'{quote(method)}:{quote(password)}@{address}:{port}'
|
||||
|
||||
return urlunparse(['ss', netloc, '', '', '', quote(remark)])
|
||||
else:
|
||||
raise UnsupportedServerExport('Unsupported core protocol export')
|
||||
|
||||
def exportTrojan():
|
||||
proxyServer = outbound['settings']['servers'][0]
|
||||
|
||||
password, address, port = (
|
||||
proxyServer['password'],
|
||||
proxyServer['address'],
|
||||
proxyServer['port'],
|
||||
)
|
||||
|
||||
proxyStream, proxyStreamNet, proxyStreamTLS = (
|
||||
outbound['streamSettings'],
|
||||
outbound['streamSettings']['network'],
|
||||
outbound['streamSettings']['security'],
|
||||
)
|
||||
|
||||
netloc = f'{quote(password)}@{address}:{port}'
|
||||
|
||||
query = '&'.join(
|
||||
f'{key}={value}'
|
||||
for key, value in {
|
||||
'type': proxyStreamNet,
|
||||
'security': proxyStreamTLS,
|
||||
# kwargs
|
||||
**streamNetSettingsVLESS(proxyStream, proxyStreamNet),
|
||||
**streamTLSSettings(proxyStream, proxyStreamTLS),
|
||||
}.items()
|
||||
)
|
||||
|
||||
return urlunparse(['trojan', netloc, '', '', query, quote(remark)])
|
||||
|
||||
exportFuncMap = {
|
||||
Protocol.VMess: exportVMess,
|
||||
Protocol.VLESS: exportVLESS,
|
||||
Protocol.Shadowsocks: exportSS,
|
||||
Protocol.Trojan: exportTrojan,
|
||||
}
|
||||
|
||||
return exportFuncMap[Intellisense.getCoreProtocol(ob)]()
|
||||
|
||||
raise UnsupportedServerExport('Unsupported core protocol export')
|
||||
|
||||
|
||||
class OutboundObject:
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def build(self):
|
||||
raise NotImplementedError
|
||||
@@ -377,12 +461,13 @@ class ProxyOutboundObject(OutboundObject):
|
||||
return TLSObject
|
||||
|
||||
@staticmethod
|
||||
def getStreamTLSSettingsName(security):
|
||||
def streamTLSKey(security):
|
||||
# tlsSettings, realitySettings
|
||||
return f'{security}Settings'
|
||||
|
||||
@staticmethod
|
||||
def getStreamNetworkSettingsName(type_):
|
||||
@functools.lru_cache(None)
|
||||
def streamNetworkKey(type_):
|
||||
if type_ == 'h2':
|
||||
return 'httpSettings'
|
||||
else:
|
||||
@@ -483,7 +568,7 @@ class ProxyOutboundObject(OutboundObject):
|
||||
'id': self.uuid_,
|
||||
'security': self.encryption,
|
||||
# Extension
|
||||
'email': 'user@Furious.GUI',
|
||||
'email': PROXY_OUTBOUND_USER_EMAIL,
|
||||
}
|
||||
|
||||
# For VMess(V2rayN share standard) only.
|
||||
@@ -497,7 +582,7 @@ class ProxyOutboundObject(OutboundObject):
|
||||
'id': self.uuid_,
|
||||
'encryption': self.encryption,
|
||||
# Extension
|
||||
'email': 'user@Furious.GUI',
|
||||
'email': PROXY_OUTBOUND_USER_EMAIL,
|
||||
}
|
||||
|
||||
if self.kwargs.get('flow'):
|
||||
@@ -508,14 +593,15 @@ class ProxyOutboundObject(OutboundObject):
|
||||
|
||||
return {}
|
||||
|
||||
def build(self):
|
||||
myJSON = {
|
||||
def getDefaultJSON(self):
|
||||
return {
|
||||
'tag': 'proxy',
|
||||
'protocol': self.protocol,
|
||||
'settings': {
|
||||
'vnext': [
|
||||
{
|
||||
'address': self.remote_host,
|
||||
# self.remote_port is already an integer
|
||||
'port': self.remote_port,
|
||||
'users': [
|
||||
self.getUserObject(),
|
||||
@@ -533,40 +619,41 @@ class ProxyOutboundObject(OutboundObject):
|
||||
},
|
||||
}
|
||||
|
||||
def build(self):
|
||||
myJSON = self.getDefaultJSON()
|
||||
|
||||
if self.security != 'none':
|
||||
# tlsSettings, realitySettings
|
||||
myJSON['streamSettings'][
|
||||
ProxyOutboundObject.getStreamTLSSettingsName(self.security)
|
||||
ProxyOutboundObject.streamTLSKey(self.security)
|
||||
] = self.getTLSSettings(self.security)
|
||||
|
||||
# Stream network settings
|
||||
myJSON['streamSettings'][
|
||||
ProxyOutboundObject.getStreamNetworkSettingsName(self.type_)
|
||||
ProxyOutboundObject.streamNetworkKey(self.type_)
|
||||
] = self.getStreamNetworkSettings()
|
||||
|
||||
return myJSON
|
||||
|
||||
|
||||
class ProxyOutboundObjectSS(OutboundObject):
|
||||
def __init__(self, method, password, address, port, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
class ProxyOutboundObjectSS(ProxyOutboundObject):
|
||||
def __init__(self, method, password, address, port):
|
||||
super().__init__('shadowsocks', address, port, password, '', 'tcp', '')
|
||||
|
||||
self.method = method
|
||||
self.password = password
|
||||
self.address = address
|
||||
self.port = int(port)
|
||||
|
||||
def build(self):
|
||||
myJSON = {
|
||||
def getDefaultJSON(self):
|
||||
return {
|
||||
'tag': 'proxy',
|
||||
'protocol': 'shadowsocks',
|
||||
'protocol': self.protocol,
|
||||
'settings': {
|
||||
'servers': [
|
||||
{
|
||||
'address': self.address,
|
||||
'port': self.port,
|
||||
'address': self.remote_host,
|
||||
'port': int(self.remote_port),
|
||||
'method': self.method,
|
||||
'password': self.password,
|
||||
'password': self.uuid_,
|
||||
'email': PROXY_OUTBOUND_USER_EMAIL,
|
||||
'ota': False,
|
||||
},
|
||||
]
|
||||
@@ -580,7 +667,39 @@ class ProxyOutboundObjectSS(OutboundObject):
|
||||
},
|
||||
}
|
||||
|
||||
return myJSON
|
||||
def build(self):
|
||||
return self.getDefaultJSON()
|
||||
|
||||
|
||||
class ProxyOutboundObjectTrojan(ProxyOutboundObject):
|
||||
def __init__(self, password, address, port, type_, security, **kwargs):
|
||||
super().__init__(
|
||||
'trojan', address, port, password, '', type_, security, **kwargs
|
||||
)
|
||||
|
||||
def getDefaultJSON(self):
|
||||
return {
|
||||
'tag': 'proxy',
|
||||
'protocol': self.protocol,
|
||||
'settings': {
|
||||
'servers': [
|
||||
{
|
||||
'address': self.remote_host,
|
||||
'port': int(self.remote_port),
|
||||
'password': self.uuid_,
|
||||
'email': PROXY_OUTBOUND_USER_EMAIL,
|
||||
},
|
||||
]
|
||||
},
|
||||
'streamSettings': {
|
||||
'network': self.type_,
|
||||
'security': self.security,
|
||||
},
|
||||
'mux': {
|
||||
'enabled': False,
|
||||
'concurrency': -1,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class Outbounds:
|
||||
|
||||
@@ -70,6 +70,8 @@ class Intellisense:
|
||||
if outbound['tag'] == 'proxy':
|
||||
return protocolRepr(outbound['protocol'])
|
||||
|
||||
return ''
|
||||
|
||||
if Intellisense.getCoreType(ob) == Hysteria1.name():
|
||||
return Protocol.Hysteria1
|
||||
|
||||
@@ -86,10 +88,10 @@ class Intellisense:
|
||||
def getCoreAddr(ob):
|
||||
try:
|
||||
if Intellisense.getCoreType(ob) == XrayCore.name():
|
||||
protocol = Intellisense.getCoreProtocol(ob)
|
||||
|
||||
for outbound in ob['outbounds']:
|
||||
if outbound['tag'] == 'proxy':
|
||||
protocol = protocolRepr(outbound['protocol'])
|
||||
|
||||
if protocol == Protocol.VMess or protocol == Protocol.VLESS:
|
||||
return ';'.join(
|
||||
list(
|
||||
@@ -98,7 +100,10 @@ class Intellisense:
|
||||
)
|
||||
)
|
||||
|
||||
if protocol == Protocol.Shadowsocks:
|
||||
if (
|
||||
protocol == Protocol.Shadowsocks
|
||||
or protocol == Protocol.Trojan
|
||||
):
|
||||
return ';'.join(
|
||||
list(
|
||||
f'{server["address"]}'
|
||||
@@ -106,6 +111,8 @@ class Intellisense:
|
||||
)
|
||||
)
|
||||
|
||||
return ''
|
||||
|
||||
if Intellisense.getCoreType(ob) == Hysteria1.name():
|
||||
return ob['server'].split(':')[0]
|
||||
|
||||
@@ -122,10 +129,10 @@ class Intellisense:
|
||||
def getCorePort(ob):
|
||||
try:
|
||||
if Intellisense.getCoreType(ob) == XrayCore.name():
|
||||
protocol = Intellisense.getCoreProtocol(ob)
|
||||
|
||||
for outbound in ob['outbounds']:
|
||||
if outbound['tag'] == 'proxy':
|
||||
protocol = protocolRepr(outbound['protocol'])
|
||||
|
||||
if protocol == Protocol.VMess or protocol == Protocol.VLESS:
|
||||
return ';'.join(
|
||||
list(
|
||||
@@ -134,7 +141,10 @@ class Intellisense:
|
||||
)
|
||||
)
|
||||
|
||||
if protocol == Protocol.Shadowsocks:
|
||||
if (
|
||||
protocol == Protocol.Shadowsocks
|
||||
or protocol == Protocol.Trojan
|
||||
):
|
||||
return ';'.join(
|
||||
list(
|
||||
f'{server["port"]}'
|
||||
@@ -142,6 +152,8 @@ class Intellisense:
|
||||
)
|
||||
)
|
||||
|
||||
return ''
|
||||
|
||||
if Intellisense.getCoreType(ob) == Hysteria1.name():
|
||||
return ob['server'].split(':')[1]
|
||||
|
||||
@@ -162,6 +174,8 @@ class Intellisense:
|
||||
if outbound['tag'] == 'proxy':
|
||||
return outbound['streamSettings']['network']
|
||||
|
||||
return ''
|
||||
|
||||
return ''
|
||||
except Exception:
|
||||
# Any non-exit exceptions
|
||||
@@ -176,6 +190,8 @@ class Intellisense:
|
||||
if outbound['tag'] == 'proxy':
|
||||
return outbound['streamSettings']['security']
|
||||
|
||||
return ''
|
||||
|
||||
return ''
|
||||
except Exception:
|
||||
# Any non-exit exceptions
|
||||
|
||||
@@ -37,6 +37,8 @@ APPLICATION_REPO_NAME = 'Furious'
|
||||
ORGANIZATION_NAME = 'Furious'
|
||||
ORGANIZATION_DOMAIN = 'Furious.GUI'
|
||||
|
||||
PROXY_OUTBOUND_USER_EMAIL = f'user@{ORGANIZATION_DOMAIN}'
|
||||
|
||||
PLATFORM = platform.system()
|
||||
|
||||
LOCAL_SERVER_NAME = '891ad49d-8996-43cb-820c-d9baf42a04de'
|
||||
|
||||
@@ -45,10 +45,12 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class Base64Encoder:
|
||||
@staticmethod
|
||||
@functools.lru_cache(32)
|
||||
def encode(text):
|
||||
return pybase64.b64encode(text)
|
||||
|
||||
@staticmethod
|
||||
@functools.lru_cache(32)
|
||||
def decode(text):
|
||||
return pybase64.b64decode(text, validate=False)
|
||||
|
||||
@@ -56,7 +58,8 @@ class Base64Encoder:
|
||||
class Protocol:
|
||||
VMess = 'VMess'
|
||||
VLESS = 'VLESS'
|
||||
Shadowsocks = 'shadowsocks'
|
||||
Shadowsocks = 'Shadowsocks'
|
||||
Trojan = 'Trojan'
|
||||
Hysteria1 = 'hysteria1'
|
||||
Hysteria2 = 'hysteria2'
|
||||
|
||||
@@ -226,6 +229,7 @@ class AsyncSubprocessMessage:
|
||||
return self.msgQueue.get_nowait()
|
||||
except queue.Empty:
|
||||
# Queue is empty
|
||||
|
||||
return ''
|
||||
|
||||
|
||||
@@ -328,9 +332,13 @@ def protocolRepr(protocol):
|
||||
if protocol.lower() == 'shadowsocks':
|
||||
return Protocol.Shadowsocks
|
||||
|
||||
if protocol.lower() == 'trojan':
|
||||
return Protocol.Trojan
|
||||
|
||||
return protocol
|
||||
|
||||
|
||||
@functools.lru_cache(None)
|
||||
def isValidIPAddress(address):
|
||||
try:
|
||||
ipaddress.ip_address(address)
|
||||
|
||||
Reference in New Issue
Block a user