Skip to content

Commit

Permalink
Merge branch 'dev' of gozargah:Gozargah/Marzban; branch 'custom-outbo…
Browse files Browse the repository at this point in the history
…und-detail' of github.com:M03ED/Marzban into M03ED-custom-outbound-detail
  • Loading branch information
SaintShit committed Jul 29, 2024
2 parents 721f696 + 94a3f28 commit e611ed5
Show file tree
Hide file tree
Showing 11 changed files with 633 additions and 244 deletions.
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,13 @@ UVICORN_PORT = 8000
# CLASH_SUBSCRIPTION_TEMPLATE="clash/my-custom-template.yml"
# SUBSCRIPTION_PAGE_TEMPLATE="subscription/index.html"
# HOME_PAGE_TEMPLATE="home/index.html"

# V2RAY_SUBSCRIPTION_TEMPLATE="v2ray/default.json"
# V2RAY_SETTINGS_TEMPLATE="v2ray/settings.json"

# SINGBOX_SUBSCRIPTION_TEMPLATE="singbox/default.json"
# SINGBOX_SETTINGS_TEMPLATE="singbox/settings.json"

# MUX_TEMPLATE="mux/default.json"

## Enable JSON config for compatible clients to use mux, fragment, etc. Default False.
Expand Down
224 changes: 141 additions & 83 deletions app/subscription/clash.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import copy
import json
from random import choice

Expand All @@ -7,9 +8,9 @@
from app.templates import render_template
from config import (
CLASH_SUBSCRIPTION_TEMPLATE,
GRPC_USER_AGENT_TEMPLATE,
CLASH_SETTINGS_TEMPLATE,
MUX_TEMPLATE,
USER_AGENT_TEMPLATE
USER_AGENT_TEMPLATE,
)


Expand All @@ -23,21 +24,16 @@ def __init__(self):
}
self.proxy_remarks = []
self.mux_template = render_template(MUX_TEMPLATE)
temp_user_agent_data = render_template(USER_AGENT_TEMPLATE)
user_agent_data = json.loads(temp_user_agent_data)
user_agent_data = json.loads(render_template(USER_AGENT_TEMPLATE))

if 'list' in user_agent_data and isinstance(user_agent_data['list'], list):
self.user_agent_list = user_agent_data['list']
else:
self.user_agent_list = []

temp_grpc_user_agent_data = render_template(GRPC_USER_AGENT_TEMPLATE)
grpc_user_agent_data = json.loads(temp_grpc_user_agent_data)
self.settings = yaml.load(render_template(CLASH_SETTINGS_TEMPLATE), Loader=yaml.SafeLoader)

if 'list' in grpc_user_agent_data and isinstance(grpc_user_agent_data['list'], list):
self.grpc_user_agent_data = grpc_user_agent_data['list']
else:
self.grpc_user_agent_data = []
del user_agent_data

def render(self, reverse=False):
if reverse:
Expand All @@ -51,7 +47,7 @@ def render(self, reverse=False):
Loader=yaml.SafeLoader
),
sort_keys=False,
allow_unicode=True
allow_unicode=True,
)

def __str__(self) -> str:
Expand All @@ -70,6 +66,82 @@ def _remark_validation(self, remark):
return new
c += 1

def http_config(
self,
path="",
host="",
random_user_agent: bool = False,
):
config = copy.deepcopy(self.settings.get("http-opts", {
'headers': {}
}))

if path:
config["path"] = [path]
if host:
config["Host"] = host
if random_user_agent:
if "headers" not in config:
config["headers"] = {}
config["header"]["User-Agent"] = choice(self.user_agent_list)

return config

def ws_config(
self,
path="",
host="",
max_early_data=None,
early_data_header_name="",
is_httpupgrade: bool =False,
random_user_agent: bool = False,
):
config = copy.deepcopy(self.settings.get("ws-opts", {}))
if (host or random_user_agent) and "headers" not in config:
config["headers"] = {}
if path:
config["path"] = path
if host:
config["headers"]["Host"] = host
if random_user_agent:
config["headers"]["User-Agent"] = choice(self.user_agent_list)
if max_early_data:
config["max-early-data"] = max_early_data
config["early-data-header-name"] = early_data_header_name
if is_httpupgrade:
config["v2ray-http-upgrade"] = True
if max_early_data:
config["v2ray-http-upgrade-fast-open"] = True

return config

def grpc_config(self, path=""):
config = copy.deepcopy(self.settings.get("grpc-opts", {}))
if path:
config["grpc-service-name"] = path

return config

def h2_config(self, path="", host=""):
config = copy.deepcopy(self.settings.get("h2-opts", {}))
if path:
config["path"] = path
if host:
config["host"] = [host]

return config

def tcp_config(self, path="", host=""):
config = copy.deepcopy(self.settings.get("tcp-opts", {}))
if path:
config["path"] = [path]
if host:
if "headers" not in config:
config["headers"] = {}
config["headers"]["Host"] = host

return config

def make_node(self,
name: str,
type: str,
Expand All @@ -94,16 +166,19 @@ def make_node(self,
type = 'ss'
if network == 'tcp' and headers == 'http':
network = 'http'
if network == 'httpupgrade':
network = 'ws'
is_httpupgrade = True
else:
is_httpupgrade = False

remark = self._remark_validation(name)
self.proxy_remarks.append(remark)
node = {
'name': remark,
'type': type,
'server': server,
'port': port,
'network': network,
f'{network}-opts': {},
'udp': udp
}

Expand All @@ -114,6 +189,7 @@ def make_node(self,
early_data_header_name = "Sec-WebSocket-Protocol"
else:
max_early_data = None
early_data_header_name = ""

if type == 'ss': # shadowsocks
return node
Expand All @@ -129,54 +205,36 @@ def make_node(self,
if ais:
node['skip-cert-verify'] = ais

net_opts = node[f'{network}-opts']

if network == 'http':
if path:
net_opts['method'] = 'GET'
net_opts['path'] = [path]
if host:
net_opts['method'] = 'GET'
net_opts['Host'] = host
if random_user_agent:
net_opts['header'] = {"User-Agent": choice(self.user_agent_list)}

if network == 'ws' or network == 'httpupgrade':
if path:
net_opts['path'] = path
if host or random_user_agent:
net_opts['headers'] = {}
if host:
net_opts['headers']["Host"] = host
if random_user_agent:
net_opts['headers']["User-Agent"] = choice(self.user_agent_list)
if max_early_data:
net_opts['max-early-data'] = max_early_data
net_opts['early-data-header-name'] = early_data_header_name
if network == 'httpupgrade':
net_opts['v2ray-http-upgrade'] = True
if max_early_data:
net_opts['v2ray-http-upgrade-fast-open'] = True

if network == 'grpc' or network == 'gun':
if path:
net_opts['grpc-service-name'] = path
if random_user_agent:
net_opts['header'] = {"User-Agent": choice(self.user_agent_list)}

if network == 'h2':
if path:
net_opts['path'] = path
if host:
net_opts['host'] = [host]

if network == 'tcp':
if path:
net_opts['method'] = 'GET'
net_opts['path'] = [path]
if host:
net_opts['method'] = 'GET'
net_opts['headers'] = {"Host": host}
net_opts = self.http_config(
path=path,
host=host,
random_user_agent=random_user_agent,
)

elif network == 'ws':
net_opts = self.ws_config(
path=path,
host=host,
max_early_data=max_early_data,
early_data_header_name=early_data_header_name,
is_httpupgrade=is_httpupgrade,
random_user_agent=random_user_agent,
)

elif network == 'grpc' or network == 'gun':
net_opts = self.grpc_config(path=path)

elif network == 'h2':
net_opts = self.h2_config(path=path, host=host)

elif network == 'tcp':
net_opts = self.tcp_config(path=path, host=host)

else:
net_opts = {}

node[f'{network}-opts'] = net_opts

mux_json = json.loads(self.mux_template)
mux_config = mux_json["clash"]
Expand All @@ -191,7 +249,7 @@ def add(self, remark: str, address: str, inbound: dict, settings: dict):
# not supported by clash
if inbound['network'] in ("kcp", "splithttp"):
return

node = self.make_node(
name=remark,
type=inbound['protocol'],
Expand All @@ -205,25 +263,28 @@ def add(self, remark: str, address: str, inbound: dict, settings: dict):
headers=inbound['header_type'],
udp=True,
alpn=inbound.get('alpn', ''),
ais=inbound.get('ais', ''),
mux_enable=inbound.get('mux_enable', ''),
ais=inbound.get('ais', False),
mux_enable=inbound.get('mux_enable', False),
random_user_agent=inbound.get("random_user_agent")
)

if inbound['protocol'] == 'vmess':
node['uuid'] = settings['id']
node['alterId'] = 0
node['cipher'] = 'auto'
self.data['proxies'].append(node)

if inbound['protocol'] == 'trojan':
elif inbound['protocol'] == 'trojan':
node['password'] = settings['password']
self.data['proxies'].append(node)

if inbound['protocol'] == 'shadowsocks':
elif inbound['protocol'] == 'shadowsocks':
node['password'] = settings['password']
node['cipher'] = settings['method']
self.data['proxies'].append(node)

else:
return

self.data['proxies'].append(node)
self.proxy_remarks.append(remark)


class ClashMetaConfiguration(ClashConfiguration):
Expand Down Expand Up @@ -274,7 +335,7 @@ def add(self, remark: str, address: str, inbound: dict, settings: dict):
# not supported by clash-meta
if inbound['network'] in ("kcp", "splithttp"):
return

node = self.make_node(
name=remark,
type=inbound['protocol'],
Expand All @@ -291,34 +352,31 @@ def add(self, remark: str, address: str, inbound: dict, settings: dict):
fp=inbound.get('fp', ''),
pbk=inbound.get('pbk', ''),
sid=inbound.get('sid', ''),
ais=inbound.get('ais', ''),
mux_enable=inbound.get('mux_enable', ''),
ais=inbound.get('ais', False),
mux_enable=inbound.get('mux_enable', False),
random_user_agent=inbound.get("random_user_agent")
)

if inbound['protocol'] == 'vmess':
node['uuid'] = settings['id']
node['alterId'] = 0
node['cipher'] = 'auto'
self.data['proxies'].append(node)

if inbound['protocol'] == 'vless':
elif inbound['protocol'] == 'vless':
node['uuid'] = settings['id']

if inbound['network'] in ('tcp', 'kcp') and inbound['header_type'] != 'http' and inbound['tls'] != 'none':
node['flow'] = settings.get('flow', '')

self.data['proxies'].append(node)

if inbound['protocol'] == 'trojan':
elif inbound['protocol'] == 'trojan':
node['password'] = settings['password']

if inbound['network'] in ('tcp', 'kcp') and inbound['header_type'] != 'http' and inbound['tls']:
node['flow'] = settings.get('flow', '')

self.data['proxies'].append(node)

if inbound['protocol'] == 'shadowsocks':
elif inbound['protocol'] == 'shadowsocks':
node['password'] = settings['password']
node['cipher'] = settings['method']
self.data['proxies'].append(node)

else:
return

self.data['proxies'].append(node)
self.proxy_remarks.append(remark)
Loading

0 comments on commit e611ed5

Please sign in to comment.