hiddifypanel 10.11.1__py3-none-any.whl → 10.12.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hiddifypanel/VERSION +1 -1
- hiddifypanel/VERSION.py +2 -2
- hiddifypanel/auth.py +15 -5
- hiddifypanel/hutils/encode.py +0 -1
- hiddifypanel/hutils/proxy/__init__.py +1 -0
- hiddifypanel/hutils/proxy/shared.py +18 -10
- hiddifypanel/hutils/proxy/singbox.py +22 -21
- hiddifypanel/hutils/proxy/xray.py +26 -352
- hiddifypanel/hutils/proxy/xrayjson.py +391 -0
- hiddifypanel/hutils/random.py +4 -0
- hiddifypanel/models/config.py +7 -2
- hiddifypanel/models/config_enum.py +9 -5
- hiddifypanel/panel/admin/DomainAdmin.py +3 -2
- hiddifypanel/panel/admin/templates/model/user_list.html +44 -20
- hiddifypanel/panel/commercial/restapi/v1/tgmsg.py +14 -10
- hiddifypanel/panel/commercial/restapi/v2/user/apps_api.py +17 -23
- hiddifypanel/panel/common_bp/login.py +2 -2
- hiddifypanel/panel/user/templates/base_xray_config.json.j2 +2 -2
- hiddifypanel/panel/user/user.py +1 -1
- hiddifypanel/static/images/hiddify.png +0 -0
- hiddifypanel/static/images/hiddify1.png +0 -0
- hiddifypanel/static/new/assets/hiddify-logo-7617d937.png +0 -0
- hiddifypanel/static/new/assets/hiddify-logo-7617d937_old.png +0 -0
- hiddifypanel/templates/admin-layout.html +22 -11
- hiddifypanel/templates/master.html +48 -25
- hiddifypanel/translations/en/LC_MESSAGES/messages.mo +0 -0
- hiddifypanel/translations/en/LC_MESSAGES/messages.po +25 -16
- hiddifypanel/translations/fa/LC_MESSAGES/messages.mo +0 -0
- hiddifypanel/translations/fa/LC_MESSAGES/messages.po +12 -5
- hiddifypanel/translations/pt/LC_MESSAGES/messages.mo +0 -0
- hiddifypanel/translations/pt/LC_MESSAGES/messages.po +8 -5
- hiddifypanel/translations/ru/LC_MESSAGES/messages.mo +0 -0
- hiddifypanel/translations/ru/LC_MESSAGES/messages.po +108 -47
- hiddifypanel/translations/zh/LC_MESSAGES/messages.mo +0 -0
- hiddifypanel/translations/zh/LC_MESSAGES/messages.po +108 -55
- hiddifypanel/translations.i18n/en.json +15 -13
- hiddifypanel/translations.i18n/fa.json +4 -2
- hiddifypanel/translations.i18n/pt.json +3 -1
- hiddifypanel/translations.i18n/ru.json +50 -48
- hiddifypanel/translations.i18n/zh.json +58 -56
- {hiddifypanel-10.11.1.dist-info → hiddifypanel-10.12.0.dist-info}/METADATA +1 -1
- {hiddifypanel-10.11.1.dist-info → hiddifypanel-10.12.0.dist-info}/RECORD +46 -43
- {hiddifypanel-10.11.1.dist-info → hiddifypanel-10.12.0.dist-info}/LICENSE.md +0 -0
- {hiddifypanel-10.11.1.dist-info → hiddifypanel-10.12.0.dist-info}/WHEEL +0 -0
- {hiddifypanel-10.11.1.dist-info → hiddifypanel-10.12.0.dist-info}/entry_points.txt +0 -0
- {hiddifypanel-10.11.1.dist-info → hiddifypanel-10.12.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,391 @@
|
|
1
|
+
import datetime
|
2
|
+
import json
|
3
|
+
import copy
|
4
|
+
from flask import render_template, request, g
|
5
|
+
from hiddifypanel import hutils
|
6
|
+
from hiddifypanel.models import Proxy, ProxyTransport, ProxyL3, ProxyCDN, ProxyProto, Domain, ConfigEnum, DomainType
|
7
|
+
from flask_babel import gettext as _
|
8
|
+
from .xray import is_muxable_agent
|
9
|
+
OUTBOUND_LEVEL = 8
|
10
|
+
|
11
|
+
|
12
|
+
def configs_as_json(domains: list[Domain], remarks: str) -> str:
|
13
|
+
'''Returns xray configs as json'''
|
14
|
+
outbounds = []
|
15
|
+
for proxy in hutils.proxy.get_valid_proxies(domains):
|
16
|
+
outbound = to_xray(proxy)
|
17
|
+
if 'msg' not in outbound:
|
18
|
+
outbounds.append(outbound)
|
19
|
+
|
20
|
+
outbounds_len = len(outbounds)
|
21
|
+
# reutrn no outbound
|
22
|
+
if outbounds_len < 1:
|
23
|
+
return ''
|
24
|
+
|
25
|
+
all_configs = []
|
26
|
+
base_config = json.loads(render_template('base_xray_config.json.j2', remarks=remarks))
|
27
|
+
# multiple outbounds needs multiple whole base config not just one with multiple outbounds (at least for v2rayng)
|
28
|
+
# https://github.com/2dust/v2rayNG/pull/2827#issue-2127534078
|
29
|
+
if outbounds_len > 1:
|
30
|
+
for out in outbounds:
|
31
|
+
base_config['remarks'] = out['tag']
|
32
|
+
base_config['outbounds'].insert(0, out)
|
33
|
+
all_configs.append(copy.deepcopy(base_config))
|
34
|
+
del base_config['outbounds'][0]
|
35
|
+
else: # single outbound
|
36
|
+
base_config['outbounds'].insert(0, outbounds[0])
|
37
|
+
all_configs = base_config
|
38
|
+
|
39
|
+
json_configs = json.dumps(all_configs, indent=2, cls=hutils.proxy.ProxyJsonEncoder)
|
40
|
+
return json_configs
|
41
|
+
|
42
|
+
|
43
|
+
def to_xray(proxy: dict) -> dict:
|
44
|
+
outbound = {
|
45
|
+
'tag': f'{proxy["extra_info"]} {proxy["name"]} § {proxy["port"]} {proxy["dbdomain"].id}',
|
46
|
+
'protocol': str(proxy['proto']),
|
47
|
+
'settings': {},
|
48
|
+
'streamSettings': {},
|
49
|
+
'mux': { # default value
|
50
|
+
'enabled': False,
|
51
|
+
'concurrency': -1
|
52
|
+
}
|
53
|
+
}
|
54
|
+
# add multiplex to outbound
|
55
|
+
add_multiplex(outbound, proxy)
|
56
|
+
|
57
|
+
# add stream setting to outbound
|
58
|
+
add_stream_settings(outbound, proxy)
|
59
|
+
|
60
|
+
# add protocol settings to outbound
|
61
|
+
add_proto_settings(outbound, proxy)
|
62
|
+
|
63
|
+
return outbound
|
64
|
+
|
65
|
+
# region proto settings
|
66
|
+
|
67
|
+
|
68
|
+
def add_proto_settings(base: dict, proxy: dict):
|
69
|
+
if proxy['proto'] == ProxyProto.wireguard:
|
70
|
+
add_wireguard_settings(base, proxy)
|
71
|
+
elif proxy['proto'] == ProxyProto.ss:
|
72
|
+
add_shadowsocks_settings(base, proxy)
|
73
|
+
elif proxy['proto'] == ProxyProto.vless:
|
74
|
+
add_vless_settings(base, proxy)
|
75
|
+
elif proxy['proto'] == ProxyProto.vmess:
|
76
|
+
add_vmess_settings(base, proxy)
|
77
|
+
elif proxy['proto'] == ProxyProto.trojan:
|
78
|
+
proxy['password'] = proxy['uuid']
|
79
|
+
add_trojan_settings(base, proxy)
|
80
|
+
|
81
|
+
|
82
|
+
def add_wireguard_settings(base: dict, proxy: dict):
|
83
|
+
|
84
|
+
base['settings']['secretKey'] = proxy['wg_pk']
|
85
|
+
base['settings']['reversed'] = [0, 0, 0]
|
86
|
+
base['settings']['mtu'] = 1380 # optional
|
87
|
+
base['settings']['peers'] = [{
|
88
|
+
'endpoint': f'{proxy["server"]}:{int(proxy["port"])}',
|
89
|
+
'publicKey': proxy["wg_server_pub"]
|
90
|
+
# 'allowedIPs':'', 'preSharedKey':'', 'keepAlive':'' # optionals
|
91
|
+
}]
|
92
|
+
|
93
|
+
# optionals
|
94
|
+
# base['settings']['address'] = [f'{proxy["wg_ipv4"]}/32',f'{proxy["wg_ipv6"]}/128']
|
95
|
+
# base['settings']['workers'] = 4
|
96
|
+
# base['settings']['domainStrategy'] = 'ForceIP' # default
|
97
|
+
|
98
|
+
|
99
|
+
def add_vless_settings(base: dict, proxy: dict):
|
100
|
+
base['settings']['vnext'] = [
|
101
|
+
{
|
102
|
+
'address': proxy['server'],
|
103
|
+
'port': proxy['port'],
|
104
|
+
"users": [
|
105
|
+
{
|
106
|
+
'id': proxy['uuid'],
|
107
|
+
'encryption': 'none',
|
108
|
+
# 'security': 'auto',
|
109
|
+
'flow': 'xtls-rprx-vision' if (proxy['transport'] == ProxyTransport.XTLS or base['streamSettings']['security'] == 'reality') else '',
|
110
|
+
'level': OUTBOUND_LEVEL
|
111
|
+
}
|
112
|
+
]
|
113
|
+
}
|
114
|
+
]
|
115
|
+
|
116
|
+
|
117
|
+
def add_vmess_settings(base: dict, proxy: dict):
|
118
|
+
base['settings']['vnext'] = [
|
119
|
+
{
|
120
|
+
"address": proxy['server'],
|
121
|
+
"port": proxy['port'],
|
122
|
+
"users": [
|
123
|
+
{
|
124
|
+
"id": proxy['uuid'],
|
125
|
+
"security": proxy['cipher'],
|
126
|
+
"level": OUTBOUND_LEVEL
|
127
|
+
}
|
128
|
+
]
|
129
|
+
}
|
130
|
+
]
|
131
|
+
|
132
|
+
|
133
|
+
def add_trojan_settings(base: dict, proxy: dict):
|
134
|
+
base['settings']['servers'] = [
|
135
|
+
{
|
136
|
+
# 'email': proxy['uuid'], optional
|
137
|
+
'address': proxy['server'],
|
138
|
+
'port': proxy['port'],
|
139
|
+
'password': proxy['password'],
|
140
|
+
'level': OUTBOUND_LEVEL
|
141
|
+
}
|
142
|
+
]
|
143
|
+
|
144
|
+
|
145
|
+
def add_shadowsocks_settings(base: dict, proxy: dict):
|
146
|
+
base['settings']['servers'] = [
|
147
|
+
{
|
148
|
+
'address': proxy['server'],
|
149
|
+
'port': proxy['port'],
|
150
|
+
'method': proxy['cipher'],
|
151
|
+
'password': proxy['password'],
|
152
|
+
'uot': True,
|
153
|
+
'level': OUTBOUND_LEVEL
|
154
|
+
# 'email': '', optional
|
155
|
+
}
|
156
|
+
]
|
157
|
+
|
158
|
+
# endregion
|
159
|
+
|
160
|
+
|
161
|
+
# region stream settings
|
162
|
+
|
163
|
+
def add_stream_settings(base: dict, proxy: dict):
|
164
|
+
ss = base['streamSettings']
|
165
|
+
ss['security'] = 'none' # default
|
166
|
+
|
167
|
+
# security
|
168
|
+
if proxy['l3'] == ProxyL3.reality:
|
169
|
+
ss['security'] = 'reality'
|
170
|
+
elif proxy['l3'] in [ProxyL3.tls, ProxyL3.tls_h2, ProxyL3.tls_h2_h1]:
|
171
|
+
ss['security'] = 'tls'
|
172
|
+
|
173
|
+
# network and transport settings
|
174
|
+
if ss['security'] == 'tls' or 'xtls':
|
175
|
+
ss['tlsSettings'] = {
|
176
|
+
'serverName': proxy['sni'],
|
177
|
+
'allowInsecure': proxy['allow_insecure'],
|
178
|
+
'fingerprint': proxy['fingerprint'],
|
179
|
+
'alpn': [proxy['alpn']],
|
180
|
+
# 'minVersion': '1.2',
|
181
|
+
# 'disableSystemRoot': '',
|
182
|
+
# 'enableSessionResumption': '',
|
183
|
+
# 'pinnedPeerCertificateChainSha256': '',
|
184
|
+
# 'certificates': '',
|
185
|
+
# 'maxVersion': '1.3', # Go lang sets
|
186
|
+
# 'cipherSuites': '', # Go lang sets
|
187
|
+
# 'rejectUnknownSni': '', # default is false
|
188
|
+
}
|
189
|
+
if ss['security'] == 'reality':
|
190
|
+
ss['network'] = proxy['transport']
|
191
|
+
add_reality_stream(ss, proxy)
|
192
|
+
if proxy['l3'] == ProxyL3.kcp:
|
193
|
+
ss['network'] = 'kcp'
|
194
|
+
add_kcp_stream(ss, proxy)
|
195
|
+
|
196
|
+
if proxy['l3'] == ProxyL3.h3_quic:
|
197
|
+
add_quic_stream(ss, proxy)
|
198
|
+
|
199
|
+
if proxy['transport'] == 'tcp' or ss['security'] == 'reality' or (ss['security'] == 'none' and proxy['transport'] not in [ProxyTransport.httpupgrade, ProxyTransport.WS]):
|
200
|
+
ss['network'] = proxy['transport']
|
201
|
+
add_tcp_stream(ss, proxy)
|
202
|
+
if proxy['transport'] == ProxyTransport.h2 and ss['security'] == 'none' and ss['security'] != 'reality':
|
203
|
+
ss['network'] = proxy['transport']
|
204
|
+
add_http_stream(ss, proxy)
|
205
|
+
if proxy['transport'] == ProxyTransport.grpc:
|
206
|
+
ss['network'] = proxy['transport']
|
207
|
+
add_grpc_stream(ss, proxy)
|
208
|
+
if proxy['transport'] == ProxyTransport.httpupgrade:
|
209
|
+
ss['network'] = proxy['transport']
|
210
|
+
add_httpupgrade_stream(ss, proxy)
|
211
|
+
if proxy['transport'] == 'ws':
|
212
|
+
ss['network'] = proxy['transport']
|
213
|
+
add_ws_stream(ss, proxy)
|
214
|
+
|
215
|
+
# tls fragmentaion
|
216
|
+
add_tls_fragmentation_stream_settings(base, proxy)
|
217
|
+
|
218
|
+
|
219
|
+
def add_tcp_stream(ss: dict, proxy: dict):
|
220
|
+
if proxy['l3'] == ProxyL3.http:
|
221
|
+
ss['tcpSettings'] = {
|
222
|
+
'header': {
|
223
|
+
'type': 'http',
|
224
|
+
'request': {
|
225
|
+
'path': [proxy['path']]
|
226
|
+
}
|
227
|
+
}
|
228
|
+
# 'acceptProxyProtocol': False
|
229
|
+
}
|
230
|
+
else:
|
231
|
+
ss['tcpSettings'] = {
|
232
|
+
'header': {
|
233
|
+
'type': 'none'
|
234
|
+
}
|
235
|
+
# 'acceptProxyProtocol': False
|
236
|
+
}
|
237
|
+
|
238
|
+
|
239
|
+
def add_http_stream(ss: dict, proxy: dict):
|
240
|
+
ss['httpSettings'] = {
|
241
|
+
'host': proxy['host'],
|
242
|
+
'path': proxy['path'],
|
243
|
+
# 'read_idle_timeout': 10, # default disabled
|
244
|
+
# 'health_check_timeout': 15, # default is 15
|
245
|
+
# 'method': 'PUT', # default is 15
|
246
|
+
# 'headers': {
|
247
|
+
|
248
|
+
# }
|
249
|
+
}
|
250
|
+
|
251
|
+
|
252
|
+
def add_ws_stream(ss: dict, proxy: dict):
|
253
|
+
ss['wsSettings'] = {
|
254
|
+
'path': proxy['path'],
|
255
|
+
'headers': {
|
256
|
+
"Host": proxy['host']
|
257
|
+
}
|
258
|
+
# 'acceptProxyProtocol': False,
|
259
|
+
}
|
260
|
+
|
261
|
+
|
262
|
+
def add_grpc_stream(ss: dict, proxy: dict):
|
263
|
+
ss['grpcSettings'] = {
|
264
|
+
'serviceName': proxy['path'], # proxy['path'] is equal toproxy['grpc_service_name']
|
265
|
+
'idle_timeout': 115, # by default, the health check is not enabled. may solve some "connection drop" issues
|
266
|
+
'health_check_timeout': 20, # default is 20
|
267
|
+
# 'initial_windows_size': 0, # 0 means disabled. greater than 65535 means Dynamic Window mechanism will be disabled
|
268
|
+
# 'permit_without_stream': False, # health check performed when there are no sub-connections
|
269
|
+
# 'multiMode': false, # experimental
|
270
|
+
}
|
271
|
+
|
272
|
+
|
273
|
+
def add_httpupgrade_stream(ss: dict, proxy: dict):
|
274
|
+
ss['httpupgradeSettings'] = {
|
275
|
+
'path': proxy['path'],
|
276
|
+
'host': proxy['host'],
|
277
|
+
# 'acceptProxyProtocol': '', for inbounds only
|
278
|
+
}
|
279
|
+
|
280
|
+
|
281
|
+
def add_kcp_stream(ss: dict, proxy: dict):
|
282
|
+
# TODO: fix server side configs first
|
283
|
+
ss['kcpSettings'] = {}
|
284
|
+
return
|
285
|
+
ss['kcpSettings'] = {
|
286
|
+
'seed': proxy['path'],
|
287
|
+
# 'mtu': 1350, # optional, default value is written
|
288
|
+
# 'tti': 50, # optional, default value is written
|
289
|
+
# 'uplinkCapacity': 5, # optional, default value is written
|
290
|
+
# 'downlinkCapacity': 20, # optional, default value is written
|
291
|
+
# 'congestion':False, # optional, default value is written
|
292
|
+
# 'readBufferSize': 2,# optional, default value is written
|
293
|
+
# 'writeBufferSize':2 # optional, default value is written
|
294
|
+
# 'header': { # must be same as server (hiddify doesn't use yet)
|
295
|
+
# 'type': 'none' # choices: none(default), srtp, utp, wechat-video, dtls, wireguards
|
296
|
+
# }
|
297
|
+
}
|
298
|
+
|
299
|
+
|
300
|
+
def add_quic_stream(ss: dict, proxy: dict):
|
301
|
+
# TODO: fix server side configs first
|
302
|
+
ss['quicSettings'] = {}
|
303
|
+
return
|
304
|
+
ss['quicSettings'] = {
|
305
|
+
'security': 'chacha20-poly1305',
|
306
|
+
'key': proxy['path'],
|
307
|
+
'header': {
|
308
|
+
'type': 'none'
|
309
|
+
}
|
310
|
+
}
|
311
|
+
|
312
|
+
|
313
|
+
def add_reality_stream(ss: dict, proxy: dict):
|
314
|
+
ss['realitySettings'] = {
|
315
|
+
'serverName': proxy['sni'],
|
316
|
+
'fingerprint': proxy['fingerprint'],
|
317
|
+
'shortId': proxy['reality_short_id'],
|
318
|
+
'publicKey': proxy['reality_pbk'],
|
319
|
+
'show': False,
|
320
|
+
}
|
321
|
+
|
322
|
+
|
323
|
+
def add_tls_fragmentation_stream_settings(base: dict, proxy: dict):
|
324
|
+
'''Adds tls fragment in the outbounds if tls fragmentation is enabled'''
|
325
|
+
if base['streamSettings']['security'] in ['tls', 'reality']:
|
326
|
+
if proxy.get('tls_fragment_enable'):
|
327
|
+
base['streamSettings']['sockopt'] = {
|
328
|
+
'dialerProxy': 'fragment',
|
329
|
+
'tcpKeepAliveIdle': 100,
|
330
|
+
'tcpNoDelay': True, # recommended to be enabled with "tcpMptcp": true.
|
331
|
+
"mark": 255
|
332
|
+
# 'tcpFastOpen': True, # the system default setting be used.
|
333
|
+
# 'tcpKeepAliveInterval': 0, # 0 means default GO lang settings, -1 means not enable
|
334
|
+
# 'tcpcongestion': bbr, # Not configuring means using the system default value
|
335
|
+
# 'tcpMptcp': True, # need to be enabled in both server and client configuration (not supported by panel yet)
|
336
|
+
}
|
337
|
+
|
338
|
+
# endregion
|
339
|
+
|
340
|
+
|
341
|
+
def add_multiplex(base: dict, proxy: dict):
|
342
|
+
if proxy.get('mux_enable') != "xray":
|
343
|
+
return
|
344
|
+
|
345
|
+
concurrency = proxy['mux_max_connections']
|
346
|
+
if concurrency and concurrency > 0:
|
347
|
+
base['mux']['enabled'] = True
|
348
|
+
base['mux']['concurrency'] = concurrency
|
349
|
+
base['mux']['xudpConcurrency'] = concurrency
|
350
|
+
base['mux']['xudpProxyUDP443'] = 'reject'
|
351
|
+
|
352
|
+
|
353
|
+
def add_tls_tricks_to_dict(d: dict, proxy: dict):
|
354
|
+
if proxy.get('tls_fragment_enable'):
|
355
|
+
if g.user_agent.get('is_shadowrocket'):
|
356
|
+
d['fragment'] = f'1,{proxy["tls_fragment_size"]},{proxy["tls_fragment_sleep"]}'
|
357
|
+
else:
|
358
|
+
d['fragment'] = f'{proxy["tls_fragment_size"]},{proxy["tls_fragment_sleep"]},tlshello'
|
359
|
+
|
360
|
+
if proxy.get("tls_mixed_case"):
|
361
|
+
d['mc'] = 1
|
362
|
+
if proxy.get("tls_padding_enable"):
|
363
|
+
d['padsize'] = proxy["tls_padding_length"]
|
364
|
+
|
365
|
+
|
366
|
+
def add_mux_to_dict(d: dict, proxy):
|
367
|
+
if not is_muxable_agent(proxy):
|
368
|
+
return
|
369
|
+
|
370
|
+
# according to github.com/hiddify/ray2sing/
|
371
|
+
d['muxtype'] = proxy["mux_protocol"]
|
372
|
+
d['muxmaxc'] = proxy["mux_max_connections"]
|
373
|
+
d['mux'] = proxy['mux_min_streams']
|
374
|
+
d['muxsmax'] = proxy["mux_max_streams"]
|
375
|
+
d['muxpad'] = proxy["mux_padding_enable"]
|
376
|
+
|
377
|
+
if proxy.get('mux_brutal_enable'):
|
378
|
+
d['muxup'] = proxy["mux_brutal_up_mbps"]
|
379
|
+
d['muxdown'] = proxy["mux_brutal_down_mbps"]
|
380
|
+
|
381
|
+
|
382
|
+
def add_tls_tricks_to_link(proxy: dict) -> str:
|
383
|
+
out = {}
|
384
|
+
add_tls_tricks_to_dict(out, proxy)
|
385
|
+
return hutils.encode.convert_dict_to_url(out)
|
386
|
+
|
387
|
+
|
388
|
+
def add_mux_to_link(proxy: dict) -> str:
|
389
|
+
out = {}
|
390
|
+
add_mux_to_dict(out, proxy)
|
391
|
+
return hutils.encode.convert_dict_to_url(out)
|
hiddifypanel/hutils/random.py
CHANGED
hiddifypanel/models/config.py
CHANGED
@@ -63,14 +63,19 @@ def hconfig(key: ConfigEnum, child_id: int | None = None) -> str | int | None:
|
|
63
63
|
except BaseException:
|
64
64
|
error(f'{key} error!')
|
65
65
|
raise
|
66
|
-
|
66
|
+
if key.type == int and value != None:
|
67
|
+
return int(value)
|
67
68
|
return value
|
68
69
|
|
69
70
|
|
70
71
|
def set_hconfig(key: ConfigEnum, value: str | int | bool, child_id: int | None = None, commit: bool = True):
|
71
72
|
if child_id is None:
|
72
73
|
child_id = Child.current.id
|
74
|
+
|
73
75
|
print(f"chainging .... {key}---{value}---{child_id}---{commit}")
|
76
|
+
if key.type == int and value != None:
|
77
|
+
int(value) # for testing int
|
78
|
+
|
74
79
|
# hconfig.invalidate(key, child_id)
|
75
80
|
# get_hconfigs.invalidate(child_id)
|
76
81
|
hconfig.invalidate(key, child_id)
|
@@ -113,7 +118,7 @@ def get_hconfigs(child_id: int | None = None, json=False) -> dict:
|
|
113
118
|
child_id = Child.current.id
|
114
119
|
|
115
120
|
return {**{f'{u.key}' if json else u.key: u.value for u in BoolConfig.query.filter(BoolConfig.child_id == child_id).all() if u.key.type == bool},
|
116
|
-
**{f'{u.key}' if json else u.key: u.value for u in StrConfig.query.filter(StrConfig.child_id == child_id).all() if u.key.type != bool},
|
121
|
+
**{f'{u.key}' if json else u.key: int(u.value) if u.key.type == int and u.value != None else u.value for u in StrConfig.query.filter(StrConfig.child_id == child_id).all() if u.key.type != bool},
|
117
122
|
# ConfigEnum.telegram_fakedomain:hdomain(DomainType.telegram_faketls),
|
118
123
|
# ConfigEnum.ssfaketls_fakedomain:hdomain(DomainType.ss_faketls),
|
119
124
|
# ConfigEnum.fake_cdn_domain:hdomain(DomainType.fake_cdn)
|
@@ -55,6 +55,10 @@ def _StrConfigDscr(category: ConfigCategory, apply_mode: ApplyMode = ApplyMode.n
|
|
55
55
|
return category, apply_mode, str, show_in_parent
|
56
56
|
|
57
57
|
|
58
|
+
def _IntConfigDscr(category: ConfigCategory, apply_mode: ApplyMode = ApplyMode.nothing, show_in_parent: bool = True, hide_in_virtual_child=False):
|
59
|
+
return category, apply_mode, int, show_in_parent
|
60
|
+
|
61
|
+
|
58
62
|
class ConfigEnum(metaclass=FastEnum):
|
59
63
|
# category: ConfigCategory
|
60
64
|
__slots__ = ('category', 'apply_mode', 'type', 'show_in_parent', 'hide_in_virtual_child')
|
@@ -128,13 +132,13 @@ class ConfigEnum(metaclass=FastEnum):
|
|
128
132
|
# mux
|
129
133
|
mux_enable = _BoolConfigDscr(ConfigCategory.mux, ApplyMode.apply)
|
130
134
|
mux_protocol = _StrConfigDscr(ConfigCategory.mux, ApplyMode.apply)
|
131
|
-
mux_max_connections =
|
132
|
-
mux_min_streams =
|
133
|
-
mux_max_streams =
|
135
|
+
mux_max_connections = _IntConfigDscr(ConfigCategory.mux, ApplyMode.apply)
|
136
|
+
mux_min_streams = _IntConfigDscr(ConfigCategory.mux, ApplyMode.apply)
|
137
|
+
mux_max_streams = _IntConfigDscr(ConfigCategory.mux, ApplyMode.apply)
|
134
138
|
mux_padding_enable = _BoolConfigDscr(ConfigCategory.mux, ApplyMode.apply)
|
135
139
|
mux_brutal_enable = _BoolConfigDscr(ConfigCategory.mux, ApplyMode.apply)
|
136
|
-
mux_brutal_up_mbps =
|
137
|
-
mux_brutal_down_mbps =
|
140
|
+
mux_brutal_up_mbps = _IntConfigDscr(ConfigCategory.mux, ApplyMode.apply)
|
141
|
+
mux_brutal_down_mbps = _IntConfigDscr(ConfigCategory.mux, ApplyMode.apply)
|
138
142
|
|
139
143
|
http_ports = _StrConfigDscr(ConfigCategory.http, ApplyMode.apply)
|
140
144
|
kcp_ports = _StrConfigDscr(ConfigCategory.hidden, ApplyMode.apply)
|
@@ -234,8 +234,9 @@ class DomainAdmin(AdminLTEModelView):
|
|
234
234
|
if not hutils.network.is_in_same_asn(d, ipv4_list[0]):
|
235
235
|
server_asn = hutils.network.get_ip_asn_name(ipv4_list[0])
|
236
236
|
domain_asn = hutils.network.get_ip_asn_name(dip) # type: ignore
|
237
|
-
|
238
|
-
|
237
|
+
msg = _("selected domain for REALITY is not in the same ASN. To better use of the protocol, it is better to find a domain in the same ASN.") + \
|
238
|
+
(f"<br> Server ASN={server_asn}<br>{d}_ASN={domain_asn}" if server_asn or domain_asn else "")
|
239
|
+
hutils.flask.flash(msg, 'warning')
|
239
240
|
|
240
241
|
for d in model.servernames.split(","):
|
241
242
|
if not hutils.network.fallback_domain_compatible_with_servernames(model.domain, d):
|
@@ -7,26 +7,37 @@
|
|
7
7
|
<summary>
|
8
8
|
{{_("Send Message to User's Telegram")}}
|
9
9
|
</summary>
|
10
|
-
<
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
10
|
+
<div style="margin-top: 10px;">
|
11
|
+
<button onclick="show_send_message('all')" type="button" class="btn hbtn bg-h-green">
|
12
|
+
{{_("All Users")}}
|
13
|
+
</button>
|
14
|
+
<button onclick="show_send_message('active')" type="button" class="btn hbtn bg-h-green" style="margin-left: 5px;margin-right: 5px;">
|
15
|
+
{{_("Active Users")}}
|
16
|
+
</button>
|
17
|
+
<button onclick="show_send_message('selected')" type="button" class="btn hbtn bg-h-green">
|
18
|
+
{{_("Seleted Users")}}
|
19
|
+
</button>
|
20
|
+
</div>
|
21
|
+
|
22
|
+
<div style="margin-top: 10px;">
|
23
|
+
<button onclick="show_send_message('offline 1h')" type="button" class="btn hbtn bg-h-red">
|
24
|
+
{{_("Offline more than 1 hour")}}</button>
|
25
|
+
<button onclick="show_send_message('offline 1d')" type="button" class="btn hbtn bg-h-red" style="margin-left: 5px;">
|
26
|
+
{{_("Offline more than 1 day")}}</button>
|
27
|
+
<button onclick="show_send_message('offline 1w')" type="button" class="btn hbtn bg-h-red" style="margin-left: 5px;margin-right: 5px;">
|
28
|
+
{{_("Offline more than 1 week")}}
|
29
|
+
</button>
|
30
|
+
<button onclick="show_send_message('expired')" type="button" class="btn hbtn bg-h-red">
|
31
|
+
{{_("Expired Users")}}
|
32
|
+
</button>
|
33
|
+
</div>
|
22
34
|
</details>
|
23
35
|
</div>
|
24
36
|
{% endif %}
|
25
37
|
{{super()}}
|
26
38
|
|
27
39
|
<div class="callout callout-success">
|
28
|
-
{{_('User usage will be updated every 6 minutes. To update it now click <a href="%(link)s"
|
29
|
-
class="btn btn-info">here</a>',link=hurl_for("admin.Actions:update_usage"))}}
|
40
|
+
{{_('User usage will be updated every 6 minutes. To update it now click <a href="%(link)s" class="btn btn-info">here</a>',link=hurl_for("admin.Actions:update_usage"))}}
|
30
41
|
|
31
42
|
</div>
|
32
43
|
<style>
|
@@ -69,23 +80,36 @@
|
|
69
80
|
{% block tail_js%}
|
70
81
|
{{super()}}
|
71
82
|
<script>
|
83
|
+
function get_selected_users_id() {
|
84
|
+
let ids = [];
|
85
|
+
$('input[type="checkbox"][name="rowid"]:checked[value][class="action-checkbox"]').each(function () {
|
86
|
+
ids.push($(this).val());
|
87
|
+
});
|
88
|
+
return ids
|
89
|
+
}
|
72
90
|
function show_send_message(id) {
|
91
|
+
if (id == 'selected') {
|
92
|
+
id = get_selected_users_id();
|
93
|
+
if (id.length == 0) {
|
94
|
+
alert(`{{_("Please select at least one user")}}`);
|
95
|
+
return
|
96
|
+
}
|
97
|
+
}
|
73
98
|
bootbox.prompt({
|
74
99
|
title: '{{_("Please type your message to send to the telegram:")}}',
|
75
100
|
inputType: 'textarea',
|
76
101
|
locale: '{{get_locale()}}',
|
77
|
-
callback: function (
|
78
|
-
console.log(
|
79
|
-
if (!
|
102
|
+
callback: function (msg) {
|
103
|
+
console.log(msg);
|
104
|
+
if (!msg) return
|
80
105
|
// let dialog = bootbox.dialog({
|
81
106
|
// message: '<p><i class="fas fa-spin fa-spinner"></i> {{_("Sending")}}</p>'
|
82
107
|
// });
|
83
|
-
|
84
108
|
$.ajax({
|
85
109
|
type: "POST",
|
86
110
|
url: '{{hurl_for("api_v1.sendmsgresource")}}',
|
87
111
|
|
88
|
-
data: JSON.stringify({ 'text':
|
112
|
+
data: JSON.stringify({ 'text': msg, 'id': id }),
|
89
113
|
contentType: "application/json",
|
90
114
|
dataType: "json",
|
91
115
|
error: function (jqx, status, error) {
|
@@ -131,4 +155,4 @@
|
|
131
155
|
}
|
132
156
|
|
133
157
|
</script>
|
134
|
-
{% endblock %}
|
158
|
+
{% endblock %}
|
@@ -39,30 +39,34 @@ class SendMsgResource(Resource):
|
|
39
39
|
else:
|
40
40
|
return {'msg': 'error', 'res': res}
|
41
41
|
|
42
|
-
def get_users_by_identifier(self, identifier: str) -> List[User]:
|
42
|
+
def get_users_by_identifier(self, identifier: str | list) -> List[User]:
|
43
43
|
'''Returns all users that match the identifier for sending a message to them'''
|
44
44
|
# when we are here we must have g.account but ...
|
45
45
|
if not hasattr(g, 'account'):
|
46
46
|
return []
|
47
|
+
|
47
48
|
query = User.query.filter(User.added_by.in_(g.account.recursive_sub_admins_ids()))
|
48
49
|
query = query.filter(User.telegram_id is not None, User.telegram_id != 0)
|
49
50
|
|
50
|
-
|
51
|
+
# user selected many ids as users identifier
|
52
|
+
if isinstance(identifier, list):
|
53
|
+
return query.filter(User.id.in_(identifier)).all()
|
54
|
+
|
55
|
+
if hutils.convert.is_int(identifier): # type: ignore
|
51
56
|
return [query.filter(User.id == int(identifier)).first() or abort(404, 'The user not found')] # type: ignore
|
52
|
-
|
57
|
+
if identifier == 'all':
|
53
58
|
return query.all()
|
54
|
-
|
59
|
+
if identifier == 'expired':
|
55
60
|
return [u for u in query.all() if not u.is_active]
|
56
|
-
|
61
|
+
if identifier == 'active':
|
57
62
|
return [u for u in query.all() if u.is_active]
|
58
|
-
|
63
|
+
if identifier == 'offline 1h':
|
59
64
|
h1 = datetime.datetime.now() - datetime.timedelta(hours=1)
|
60
65
|
return [u for u in query.all() if u.is_active and u.last_online < h1]
|
61
|
-
|
66
|
+
if identifier == 'offline 1d':
|
62
67
|
d1 = datetime.datetime.now() - datetime.timedelta(hours=24)
|
63
68
|
return [u for u in query.all() if u.is_active and u.last_online < d1]
|
64
|
-
|
69
|
+
if identifier == 'offline 1w':
|
65
70
|
d7 = datetime.datetime.now() - datetime.timedelta(days=7)
|
66
71
|
return [u for u in query.all() if u.is_active and u.last_online < d7]
|
67
|
-
|
68
|
-
return []
|
72
|
+
return []
|