hiddifypanel 10.10.20__py3-none-any.whl → 10.11.1__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.
Files changed (68) hide show
  1. hiddifypanel/VERSION +1 -1
  2. hiddifypanel/VERSION.py +2 -2
  3. hiddifypanel/hutils/__init__.py +3 -0
  4. hiddifypanel/hutils/crypto.py +29 -0
  5. hiddifypanel/hutils/encode.py +4 -0
  6. hiddifypanel/hutils/flask.py +4 -0
  7. hiddifypanel/hutils/github_issue.py +1 -1
  8. hiddifypanel/{models/utils.py → hutils/model.py} +14 -4
  9. hiddifypanel/hutils/network/net.py +46 -2
  10. hiddifypanel/hutils/proxy/__init__.py +4 -0
  11. hiddifypanel/hutils/proxy/clash.py +161 -0
  12. hiddifypanel/hutils/proxy/shared.py +414 -0
  13. hiddifypanel/hutils/proxy/singbox.py +338 -0
  14. hiddifypanel/hutils/proxy/xray.py +561 -0
  15. hiddifypanel/models/admin.py +16 -14
  16. hiddifypanel/models/base_account.py +4 -4
  17. hiddifypanel/models/config.py +1 -1
  18. hiddifypanel/models/proxy.py +17 -17
  19. hiddifypanel/models/user.py +15 -13
  20. hiddifypanel/panel/admin/AdminstratorAdmin.py +1 -1
  21. hiddifypanel/panel/admin/DomainAdmin.py +13 -27
  22. hiddifypanel/panel/admin/ProxyAdmin.py +3 -3
  23. hiddifypanel/panel/admin/SettingAdmin.py +22 -11
  24. hiddifypanel/panel/admin/UserAdmin.py +9 -7
  25. hiddifypanel/panel/cf_api.py +1 -2
  26. hiddifypanel/panel/commercial/ProxyDetailsAdmin.py +5 -6
  27. hiddifypanel/panel/commercial/restapi/v2/user/apps_api.py +1 -1
  28. hiddifypanel/panel/commercial/restapi/v2/user/configs_api.py +4 -7
  29. hiddifypanel/panel/common.py +5 -3
  30. hiddifypanel/panel/hiddify.py +0 -90
  31. hiddifypanel/panel/init_db.py +14 -9
  32. hiddifypanel/panel/user/__init__.py +0 -1
  33. hiddifypanel/panel/user/templates/all_configs copy.txt +2 -2
  34. hiddifypanel/panel/user/templates/all_configs.txt +2 -2
  35. hiddifypanel/panel/user/templates/base_singbox_config.json.j2 +2 -1
  36. hiddifypanel/panel/user/templates/base_xray_config.json.j2 +125 -0
  37. hiddifypanel/panel/user/templates/clash_config copy.yml +1 -1
  38. hiddifypanel/panel/user/templates/clash_config.yml +4 -4
  39. hiddifypanel/panel/user/templates/clash_proxies.yml +1 -1
  40. hiddifypanel/panel/user/templates/home/all-configs.html +2 -2
  41. hiddifypanel/panel/user/templates/home/all-configs_old.html +1 -1
  42. hiddifypanel/panel/user/templates/home/ios copy.html +2 -2
  43. hiddifypanel/panel/user/user.py +50 -44
  44. hiddifypanel/templates/admin-layout.html +16 -24
  45. hiddifypanel/templates/fake.html +0 -276
  46. hiddifypanel/templates/flaskadmin-layout.html +2 -1
  47. hiddifypanel/translations/en/LC_MESSAGES/messages.mo +0 -0
  48. hiddifypanel/translations/en/LC_MESSAGES/messages.po +16 -9
  49. hiddifypanel/translations/fa/LC_MESSAGES/messages.mo +0 -0
  50. hiddifypanel/translations/fa/LC_MESSAGES/messages.po +12 -6
  51. hiddifypanel/translations/pt/LC_MESSAGES/messages.mo +0 -0
  52. hiddifypanel/translations/pt/LC_MESSAGES/messages.po +9 -4
  53. hiddifypanel/translations/ru/LC_MESSAGES/messages.mo +0 -0
  54. hiddifypanel/translations/ru/LC_MESSAGES/messages.po +12 -7
  55. hiddifypanel/translations/zh/LC_MESSAGES/messages.mo +0 -0
  56. hiddifypanel/translations/zh/LC_MESSAGES/messages.po +9 -4
  57. hiddifypanel/translations.i18n/en.json +8 -7
  58. hiddifypanel/translations.i18n/fa.json +5 -4
  59. hiddifypanel/translations.i18n/pt.json +3 -2
  60. hiddifypanel/translations.i18n/ru.json +6 -5
  61. hiddifypanel/translations.i18n/zh.json +3 -2
  62. {hiddifypanel-10.10.20.dist-info → hiddifypanel-10.11.1.dist-info}/METADATA +1 -1
  63. {hiddifypanel-10.10.20.dist-info → hiddifypanel-10.11.1.dist-info}/RECORD +67 -61
  64. {hiddifypanel-10.10.20.dist-info → hiddifypanel-10.11.1.dist-info}/WHEEL +1 -1
  65. hiddifypanel/panel/user/link_maker.py +0 -1089
  66. {hiddifypanel-10.10.20.dist-info → hiddifypanel-10.11.1.dist-info}/LICENSE.md +0 -0
  67. {hiddifypanel-10.10.20.dist-info → hiddifypanel-10.11.1.dist-info}/entry_points.txt +0 -0
  68. {hiddifypanel-10.10.20.dist-info → hiddifypanel-10.11.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,561 @@
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, hconfig, ConfigEnum, DomainType
7
+ from flask_babel import gettext as _
8
+
9
+ OUTBOUND_LEVEL = 8
10
+
11
+
12
+ def to_link(proxy: dict) -> str | dict:
13
+ if 'error' in proxy:
14
+ return proxy
15
+
16
+ orig_name_link = (proxy['extra_info'] + " " + proxy["name"]).strip()
17
+ name_link = hutils.encode.url_encode(orig_name_link)
18
+ if proxy['proto'] == 'vmess':
19
+ # print(proxy)
20
+ vmess_type = None
21
+ if proxy["transport"] == 'tcp':
22
+ vmess_type = 'http'
23
+ if 'grpc_mode' in proxy:
24
+ vmess_type = proxy['grpc_mode']
25
+ vmess_data = {"v": "2",
26
+ "ps": orig_name_link,
27
+ "add": proxy['server'],
28
+ "port": proxy['port'],
29
+ "id": proxy["uuid"],
30
+ "aid": 0,
31
+ "scy": proxy['cipher'],
32
+ "net": proxy["transport"],
33
+ "type": vmess_type or "none",
34
+ "host": proxy.get("host", ""),
35
+ "alpn": proxy.get("alpn", "h2,http/1.1"),
36
+ "path": proxy["path"] if "path" in proxy else "",
37
+ "tls": "tls" if "tls" in proxy["l3"] else "",
38
+ "sni": proxy["sni"],
39
+ "fp": proxy["fingerprint"]
40
+ }
41
+ if 'reality' in proxy["l3"]:
42
+ vmess_data['tls'] = "reality"
43
+ vmess_data['pbk'] = proxy['reality_pbk']
44
+ vmess_data['sid'] = proxy['reality_short_id']
45
+
46
+ add_tls_tricks_to_dict(vmess_data, proxy)
47
+ add_mux_to_dict(vmess_data, proxy)
48
+
49
+ return "vmess://" + hutils.encode.do_base_64(f'{json.dumps(vmess_data,cls=hutils.proxy.ProxyJsonEncoder)}')
50
+ if proxy['proto'] == 'ssh':
51
+ baseurl = 'ssh://'
52
+ if g.user_agent.get('is_streisand'):
53
+ streisand_ssh = hutils.encode.do_base_64(f'{proxy["uuid"]}:0:{proxy["private_key"]}::@{proxy["server"]}:{proxy["port"]}')
54
+ baseurl += f'{streisand_ssh}#{name_link}'
55
+ else:
56
+ hk = ",".join(proxy["host_key"])
57
+ pk = proxy["private_key"].replace('\n', '')
58
+ baseurl += f'{proxy["uuid"]}@{proxy["server"]}:{proxy["port"]}/?file=ssh&pk={pk}&hk={hk}#{name_link}'
59
+
60
+ return baseurl
61
+ if proxy['proto'] == "ssr":
62
+ baseurl = f'ssr://{proxy["cipher"]}:{proxy["uuid"]}@{proxy["server"]}:{proxy["port"]}'
63
+ return baseurl
64
+ if proxy['proto'] in ['ss', 'v2ray']:
65
+ baseurl = f'ss://{proxy["cipher"]}:{proxy["password"]}@{proxy["server"]}:{proxy["port"]}'
66
+ if proxy['mode'] == 'faketls':
67
+ return f'{baseurl}?plugin=obfs-local%3Bobfs%3Dtls%3Bobfs-host%3D{proxy["fakedomain"]}%3Budp-over-tcp=true#{name_link}'
68
+ # if proxy['mode'] == 'shadowtls':
69
+ # return f'{baseurl}?plugin=shadow-tls%3Bpassword%3D{proxy["proxy_path"]}%3Bhost%3D{proxy["fakedomain"]}%3Budp-over-tcp=true#{name_link}'
70
+ if proxy['proto'] == 'v2ray':
71
+ return f'{baseurl}?plugin=v2ray-plugin%3Bmode%3Dwebsocket%3Bpath%3D{proxy["path"]}%3Bhost%3D{proxy["host"]}%3Btls%3Budp-over-tcp=true#{name_link}'
72
+ if proxy['transport'] == 'shadowsocks':
73
+ return baseurl
74
+ if proxy['proto'] == 'tuic':
75
+ baseurl = f'tuic://{proxy["uuid"]}:{proxy["uuid"]}@{proxy["server"]}:{proxy["port"]}?congestion_control=cubic&udp_relay_mode=native&sni={proxy["sni"]}&alpn=h3'
76
+ if proxy['mode'] == 'Fake' or proxy['allow_insecure']:
77
+ baseurl += "&allow_insecure=1"
78
+ return f"{baseurl}#{name_link}"
79
+ if proxy['proto'] == 'hysteria2':
80
+ baseurl = f'hysteria2://{proxy["uuid"]}@{proxy["server"]}:{proxy["port"]}?hiddify=1&obfs=salamander&obfs-password={hconfig(ConfigEnum.proxy_path)}&sni={proxy["sni"]}'
81
+ if proxy['mode'] == 'Fake' or proxy['allow_insecure']:
82
+ baseurl += "&insecure=1"
83
+ return f"{baseurl}#{name_link}"
84
+ if proxy['proto'] == ProxyProto.wireguard:
85
+ if g.user_agent.get('is_streisand'):
86
+ return f'wireguard://{proxy["server"]}:{proxy["port"]}?private_key={proxy["wg_pk"]}&peer_public_key={proxy["wg_server_pub"]}&pre_shared_key={proxy["wg_psk"]}&reserved=0,0,0#{name_link}'
87
+ else:
88
+ # hiddify_format =
89
+ # f'wg://{proxy["server"]}:{proxy["port"]}/?pk={proxy["wg_pk"]}&local_address={proxy["wg_ipv4"]}/32&peer_pk={proxy["wg_server_pub"]}&pre_shared_key={proxy["wg_psk"]}&workers=4&mtu=1380&reserved=0,0,0&ifp={proxy["wg_noise_trick"]}#{name_link}'
90
+ return f'wg://{proxy["server"]}:{proxy["port"]}?publicKey={proxy["wg_pub"]}&privateKey={proxy["wg_pk"]}=&presharedKey={proxy["wg_psk"]}&ip=10.0.0.1&mtu=1380&keepalive=30&udp=1&reserved=0,0,0&ifp={proxy["wg_noise_trick"]}#{name_link}'
91
+
92
+ baseurl = f'{proxy["proto"]}://{proxy["uuid"]}@{proxy["server"]}:{proxy["port"]}?hiddify=1'
93
+ baseurl += f'&sni={proxy["sni"]}&type={proxy["transport"]}'
94
+ baseurl += f"&alpn={proxy['alpn']}"
95
+
96
+ # the ray2sing supports vless, vmess and trojan tls tricks and mux
97
+ # the vmess handled already
98
+
99
+ baseurl += add_mux_to_link(proxy)
100
+ baseurl += add_tls_tricks_to_link(proxy)
101
+
102
+ # infos+=f'&alpn={proxy["alpn"]}'
103
+ baseurl += f'&path={proxy["path"]}' if "path" in proxy else ""
104
+ baseurl += f'&host={proxy["host"]}' if "host" in proxy else ""
105
+ if "grpc" == proxy["transport"]:
106
+ baseurl += f'&serviceName={proxy["grpc_service_name"]}&mode={proxy["grpc_mode"]}'
107
+ # print(proxy['cdn'],proxy["transport"])
108
+ if request.args.get("fragment"):
109
+ baseurl += f'&fragment=' + request.args.get("fragment") # type: ignore
110
+ if "ws" == proxy["transport"] and proxy['cdn'] and request.args.get("fragment_v1"):
111
+ baseurl += f'&fragment_v1=' + request.args.get("fragment_v1") # type: ignore
112
+ if 'vless' == proxy['proto']:
113
+ baseurl += "&encryption=none"
114
+ if proxy.get('fingerprint', 'none') != 'none':
115
+ baseurl += "&fp=" + proxy['fingerprint']
116
+ if proxy['l3'] != 'quic':
117
+ if proxy.get('l3') != ProxyL3.reality and (proxy.get('transport') == ProxyTransport.tcp or proxy.get('transport') == ProxyTransport.httpupgrade) and proxy['proto'] in [ProxyProto.vless, ProxyProto.trojan]:
118
+ baseurl += '&headerType=http'
119
+ else:
120
+ baseurl += '&headerType=None'
121
+ if proxy['mode'] == 'Fake' or proxy['allow_insecure']:
122
+ baseurl += "&allowInsecure=true"
123
+ if proxy.get('flow'):
124
+ baseurl += f'&flow={proxy["flow"]}'
125
+
126
+ infos = f'#{name_link}'
127
+
128
+ if 'reality' in proxy["l3"]:
129
+ return f"{baseurl}&security=reality&pbk={proxy['reality_pbk']}&sid={proxy['reality_short_id']}{infos}"
130
+ if 'tls' in proxy['l3']:
131
+ return f'{baseurl}&security=tls{infos}'
132
+ if proxy['l3'] == 'http':
133
+ return f'{baseurl}&security=none{infos}'
134
+ return proxy
135
+
136
+
137
+ def make_v2ray_configs(user, user_activate, domains: list[Domain], expire_days, ip_debug, db_domain, has_auto_cdn, asn, profile_title, **kwargs) -> str:
138
+ res = []
139
+
140
+ ua = hutils.flask.get_user_agent()
141
+ if hconfig(ConfigEnum.show_usage_in_sublink):
142
+
143
+ if not ua['is_hiddify']:
144
+
145
+ fake_ip_for_sub_link = datetime.datetime.now().strftime(f"%H.%M--%Y.%m.%d.time:%H%M")
146
+ # if ua['app'] == "Fair1":
147
+ # res.append(f'trojan://1@{fake_ip_for_sub_link}?sni=fake_ip_for_sub_link&security=tls#{round(user.current_usage_GB,3)}/{user.usage_limit_GB}GB_Remain:{expire_days}days')
148
+ # else:
149
+
150
+ # res.append(f'trojan://1@{fake_ip_for_sub_link}?sni=fake_ip_for_sub_link&security=tls#{hutils.encode.url_encode(profile_title)}')
151
+
152
+ name = '⏳' if user_activate else '✖'
153
+ if user.usage_limit_GB < 1000:
154
+ name += f'{round(user.current_usage_GB,3)}/{str(user.usage_limit_GB).replace(".0","")}GB'
155
+ elif user.usage_limit_GB < 100000:
156
+ name += f'{round(user.current_usage_GB/1000,3)}/{str(round(user.usage_limit_GB/1000,1)).replace(".0","")}TB'
157
+ else:
158
+ res.append("#No Usage Limit")
159
+ if expire_days < 1000:
160
+ name += " " + _(f'📅%(expire_days)s days', expire_days=expire_days)
161
+ else:
162
+ res.append("#No Time Limit")
163
+
164
+ name = name.strip()
165
+ if len(name) > 3:
166
+ res.append(f'trojan://1@{fake_ip_for_sub_link}?sni=fake_ip_for_sub_link&security=tls#{hutils.encode.url_encode(name)}')
167
+
168
+ if ua['is_browser']:
169
+ res.append(f'#Hiddify auto ip: {ip_debug}')
170
+
171
+ if not user_activate:
172
+
173
+ if hconfig(ConfigEnum.lang) == 'fa':
174
+ res.append('trojan://1@1.1.1.1#' + hutils.encode.url_encode('✖بسته شما به پایان رسید'))
175
+ else:
176
+ res.append('trojan://1@1.1.1.1#' + hutils.encode.url_encode('✖Package_Ended'))
177
+ return "\n".join(res)
178
+
179
+ for pinfo in hutils.proxy.get_valid_proxies(domains):
180
+ link = to_link(pinfo)
181
+ if 'msg' not in link:
182
+ res.append(link)
183
+ return "\n".join(res)
184
+
185
+
186
+ def configs_as_json(domains: list[Domain], remarks: str) -> str:
187
+ '''Returns xray configs as json'''
188
+ outbounds = []
189
+ for proxy in hutils.proxy.get_valid_proxies(domains):
190
+ outbound = to_xray(proxy)
191
+ if 'msg' not in outbound:
192
+ outbounds.append(outbound)
193
+
194
+ outbounds_len = len(outbounds)
195
+ # reutrn no outbound
196
+ if outbounds_len < 1:
197
+ return ''
198
+
199
+ all_configs = []
200
+ base_config = json.loads(render_template('base_xray_config.json.j2', remarks=remarks))
201
+ # multiple outbounds needs multiple whole base config not just one with multiple outbounds (at least for v2rayng)
202
+ # https://github.com/2dust/v2rayNG/pull/2827#issue-2127534078
203
+ if outbounds_len > 1:
204
+ for out in outbounds:
205
+ base_config['remarks'] = out['tag']
206
+ base_config['outbounds'].insert(0, out)
207
+ all_configs.append(copy.deepcopy(base_config))
208
+ del base_config['outbounds'][0]
209
+ else: # single outbound
210
+ base_config['outbounds'].insert(0, outbounds[0])
211
+ all_configs = base_config
212
+
213
+ json_configs = json.dumps(all_configs, indent=2, cls=hutils.proxy.ProxyJsonEncoder)
214
+ return json_configs
215
+
216
+
217
+ def to_xray(proxy: dict) -> dict:
218
+ outbound = {
219
+ 'tag': f'{proxy["extra_info"]} {proxy["name"]} § {proxy["port"]} {proxy["dbdomain"].id}',
220
+ 'protocol': str(proxy['proto']),
221
+ 'settings': {},
222
+ 'streamSettings': {},
223
+ 'mux': { # default value
224
+ 'enabled': False,
225
+ 'concurrency': -1
226
+ }
227
+ }
228
+ # add multiplex to outbound
229
+ add_multiplex(outbound)
230
+
231
+ # add stream setting to outbound
232
+ add_stream_settings(outbound, proxy)
233
+
234
+ # add protocol settings to outbound
235
+ add_proto_settings(outbound, proxy)
236
+
237
+ return outbound
238
+
239
+ # region proto settings
240
+
241
+
242
+ def add_proto_settings(base: dict, proxy: dict):
243
+ if proxy['proto'] == ProxyProto.wireguard:
244
+ add_wireguard_settings(base, proxy)
245
+ elif proxy['proto'] == ProxyProto.ss:
246
+ add_shadowsocks_settings(base, proxy)
247
+ elif proxy['proto'] == ProxyProto.vless:
248
+ add_vless_settings(base, proxy)
249
+ elif proxy['proto'] == ProxyProto.vmess:
250
+ add_vmess_settings(base, proxy)
251
+ elif proxy['proto'] == ProxyProto.trojan:
252
+ proxy['password'] = proxy['uuid']
253
+ add_trojan_settings(base, proxy)
254
+
255
+
256
+ def add_wireguard_settings(base: dict, proxy: dict):
257
+
258
+ base['settings']['secretKey'] = proxy['wg_pk']
259
+ base['settings']['reversed'] = [0, 0, 0]
260
+ base['settings']['mtu'] = 1380 # optional
261
+ base['settings']['peers'] = [{
262
+ 'endpoint': f'{proxy["server"]}:{int(proxy["port"])}',
263
+ 'publicKey': proxy["wg_server_pub"]
264
+ # 'allowedIPs':'', 'preSharedKey':'', 'keepAlive':'' # optionals
265
+ }]
266
+
267
+ # optionals
268
+ # base['settings']['address'] = [f'{proxy["wg_ipv4"]}/32',f'{proxy["wg_ipv6"]}/128']
269
+ # base['settings']['workers'] = 4
270
+ # base['settings']['domainStrategy'] = 'ForceIP' # default
271
+
272
+
273
+ def add_vless_settings(base: dict, proxy: dict):
274
+ base['settings']['vnext'] = [
275
+ {
276
+ 'address': proxy['server'],
277
+ 'port': proxy['port'],
278
+ "users": [
279
+ {
280
+ 'id': proxy['uuid'],
281
+ 'encryption': 'none',
282
+ # 'security': 'auto',
283
+ 'flow': 'xtls-rprx-vision' if (proxy['transport'] == ProxyTransport.XTLS or base['streamSettings']['security'] == 'reality') else '',
284
+ 'level': OUTBOUND_LEVEL
285
+ }
286
+ ]
287
+ }
288
+ ]
289
+
290
+
291
+ def add_vmess_settings(base: dict, proxy: dict):
292
+ base['settings']['vnext'] = [
293
+ {
294
+ "address": proxy['server'],
295
+ "port": proxy['port'],
296
+ "users": [
297
+ {
298
+ "id": proxy['uuid'],
299
+ "security": proxy['cipher'],
300
+ "level": OUTBOUND_LEVEL
301
+ }
302
+ ]
303
+ }
304
+ ]
305
+
306
+
307
+ def add_trojan_settings(base: dict, proxy: dict):
308
+ base['settings']['servers'] = [
309
+ {
310
+ # 'email': proxy['uuid'], optional
311
+ 'address': proxy['server'],
312
+ 'port': proxy['port'],
313
+ 'password': proxy['password'],
314
+ 'level': OUTBOUND_LEVEL
315
+ }
316
+ ]
317
+
318
+
319
+ def add_shadowsocks_settings(base: dict, proxy: dict):
320
+ base['settings']['servers'] = [
321
+ {
322
+ 'address': proxy['server'],
323
+ 'port': proxy['port'],
324
+ 'method': proxy['cipher'],
325
+ 'password': proxy['password'],
326
+ 'uot': True,
327
+ 'level': OUTBOUND_LEVEL
328
+ # 'email': '', optional
329
+ }
330
+ ]
331
+
332
+ # endregion
333
+
334
+
335
+ # region stream settings
336
+
337
+ def add_stream_settings(base: dict, proxy: dict):
338
+ ss = base['streamSettings']
339
+ ss['security'] = 'none' # default
340
+
341
+ # security
342
+ if proxy['l3'] == ProxyL3.reality:
343
+ ss['security'] = 'reality'
344
+ elif proxy['l3'] in [ProxyL3.tls, ProxyL3.tls_h2, ProxyL3.tls_h2_h1]:
345
+ ss['security'] = 'tls'
346
+
347
+ # network and transport settings
348
+ if ss['security'] == 'tls' or 'xtls':
349
+ ss['tlsSettings'] = {
350
+ 'serverName': proxy['sni'],
351
+ 'allowInsecure': proxy['allow_insecure'],
352
+ 'fingerprint': proxy['fingerprint'],
353
+ 'alpn': [proxy['alpn']],
354
+ # 'minVersion': '1.2',
355
+ # 'disableSystemRoot': '',
356
+ # 'enableSessionResumption': '',
357
+ # 'pinnedPeerCertificateChainSha256': '',
358
+ # 'certificates': '',
359
+ # 'maxVersion': '1.3', # Go lang sets
360
+ # 'cipherSuites': '', # Go lang sets
361
+ # 'rejectUnknownSni': '', # default is false
362
+ }
363
+ if ss['security'] == 'reality':
364
+ ss['network'] = proxy['transport']
365
+ add_reality_stream(ss, proxy)
366
+ if proxy['l3'] == ProxyL3.kcp:
367
+ ss['network'] = 'kcp'
368
+ add_kcp_stream(ss, proxy)
369
+
370
+ if proxy['l3'] == ProxyL3.h3_quic:
371
+ add_quic_stream(ss, proxy)
372
+
373
+ if proxy['transport'] == 'tcp' or ss['security'] == 'reality' or (ss['security'] == 'none' and proxy['transport'] not in [ProxyTransport.httpupgrade, ProxyTransport.WS]):
374
+ ss['network'] = proxy['transport']
375
+ add_tcp_stream(ss, proxy)
376
+ if proxy['transport'] == ProxyTransport.h2 and ss['security'] == 'none' and ss['security'] != 'reality':
377
+ ss['network'] = proxy['transport']
378
+ add_http_stream(ss, proxy)
379
+ if proxy['transport'] == ProxyTransport.grpc:
380
+ ss['network'] = proxy['transport']
381
+ add_grpc_stream(ss, proxy)
382
+ if proxy['transport'] == ProxyTransport.httpupgrade:
383
+ ss['network'] = proxy['transport']
384
+ add_httpupgrade_stream(ss, proxy)
385
+ if proxy['transport'] == 'ws':
386
+ ss['network'] = proxy['transport']
387
+ add_ws_stream(ss, proxy)
388
+
389
+ # tls fragmentaion
390
+ add_tls_fragmentation_stream_settings(base)
391
+
392
+
393
+ def add_tcp_stream(ss: dict, proxy: dict):
394
+ if proxy['l3'] == ProxyL3.http:
395
+ ss['tcpSettings'] = {
396
+ 'header': {
397
+ 'type': 'http',
398
+ 'request': {
399
+ 'path': [proxy['path']]
400
+ }
401
+ }
402
+ # 'acceptProxyProtocol': False
403
+ }
404
+ else:
405
+ ss['tcpSettings'] = {
406
+ 'header': {
407
+ 'type': 'none'
408
+ }
409
+ # 'acceptProxyProtocol': False
410
+ }
411
+
412
+
413
+ def add_http_stream(ss: dict, proxy: dict):
414
+ ss['httpSettings'] = {
415
+ 'host': proxy['host'],
416
+ 'path': proxy['path'],
417
+ # 'read_idle_timeout': 10, # default disabled
418
+ # 'health_check_timeout': 15, # default is 15
419
+ # 'method': 'PUT', # default is 15
420
+ # 'headers': {
421
+
422
+ # }
423
+ }
424
+
425
+
426
+ def add_ws_stream(ss: dict, proxy: dict):
427
+ ss['wsSettings'] = {
428
+ 'path': proxy['path'],
429
+ 'headers': {
430
+ "Host": proxy['host']
431
+ }
432
+ # 'acceptProxyProtocol': False,
433
+ }
434
+
435
+
436
+ def add_grpc_stream(ss: dict, proxy: dict):
437
+ ss['grpcSettings'] = {
438
+ 'serviceName': proxy['path'], # proxy['path'] is equal toproxy['grpc_service_name']
439
+ 'idle_timeout': 115, # by default, the health check is not enabled. may solve some "connection drop" issues
440
+ 'health_check_timeout': 20, # default is 20
441
+ # 'initial_windows_size': 0, # 0 means disabled. greater than 65535 means Dynamic Window mechanism will be disabled
442
+ # 'permit_without_stream': False, # health check performed when there are no sub-connections
443
+ # 'multiMode': false, # experimental
444
+ }
445
+
446
+
447
+ def add_httpupgrade_stream(ss: dict, proxy: dict):
448
+ ss['httpupgradeSettings'] = {
449
+ 'path': proxy['path'],
450
+ 'host': proxy['host'],
451
+ # 'acceptProxyProtocol': '', for inbounds only
452
+ }
453
+
454
+
455
+ def add_kcp_stream(ss: dict, proxy: dict):
456
+ # TODO: fix server side configs first
457
+ ss['kcpSettings'] = {}
458
+ return
459
+ ss['kcpSettings'] = {
460
+ 'seed': proxy['path'],
461
+ # 'mtu': 1350, # optional, default value is written
462
+ # 'tti': 50, # optional, default value is written
463
+ # 'uplinkCapacity': 5, # optional, default value is written
464
+ # 'downlinkCapacity': 20, # optional, default value is written
465
+ # 'congestion':False, # optional, default value is written
466
+ # 'readBufferSize': 2,# optional, default value is written
467
+ # 'writeBufferSize':2 # optional, default value is written
468
+ # 'header': { # must be same as server (hiddify doesn't use yet)
469
+ # 'type': 'none' # choices: none(default), srtp, utp, wechat-video, dtls, wireguards
470
+ # }
471
+ }
472
+
473
+
474
+ def add_quic_stream(ss: dict, proxy: dict):
475
+ # TODO: fix server side configs first
476
+ ss['quicSettings'] = {}
477
+ return
478
+ ss['quicSettings'] = {
479
+ 'security': 'chacha20-poly1305',
480
+ 'key': proxy['path'],
481
+ 'header': {
482
+ 'type': 'none'
483
+ }
484
+ }
485
+
486
+
487
+ def add_reality_stream(ss: dict, proxy: dict):
488
+ ss['realitySettings'] = {
489
+ 'serverName': proxy['sni'],
490
+ 'fingerprint': proxy['fingerprint'],
491
+ 'shortId': proxy['reality_short_id'],
492
+ 'publicKey': proxy['reality_pbk'],
493
+ 'show': False,
494
+ }
495
+
496
+
497
+ def add_tls_fragmentation_stream_settings(base: dict):
498
+ '''Adds tls fragment in the outbounds if tls fragmentation is enabled'''
499
+ if base['streamSettings']['security'] in ['tls', 'reality']:
500
+ if hconfig(ConfigEnum.tls_fragment_enable):
501
+ base['streamSettings']['sockopt'] = {
502
+ 'dialerProxy': 'fragment',
503
+ 'tcpKeepAliveIdle': 100,
504
+ 'tcpNoDelay': True, # recommended to be enabled with "tcpMptcp": true.
505
+ "mark": 255
506
+ # 'tcpFastOpen': True, # the system default setting be used.
507
+ # 'tcpKeepAliveInterval': 0, # 0 means default GO lang settings, -1 means not enable
508
+ # 'tcpcongestion': bbr, # Not configuring means using the system default value
509
+ # 'tcpMptcp': True, # need to be enabled in both server and client configuration (not supported by panel yet)
510
+ }
511
+
512
+ # endregion
513
+
514
+
515
+ def add_multiplex(base: dict):
516
+ if hconfig(ConfigEnum.mux_enable):
517
+ concurrency = hutils.convert.to_int(hconfig(ConfigEnum.mux_max_connections))
518
+ if concurrency and concurrency > 0:
519
+ base['mux']['enabled'] = True
520
+ base['mux']['concurrency'] = concurrency
521
+ base['mux']['xudpConcurrency'] = concurrency
522
+ base['mux']['xudpProxyUDP443'] = 'reject'
523
+
524
+
525
+ def add_tls_tricks_to_dict(d: dict, proxy: dict):
526
+ if proxy.get('tls_fragment_enable'):
527
+ if g.user_agent.get('is_shadowrocket'):
528
+ d['fragment'] = f'1,{proxy["tls_fragment_size"]},{proxy["tls_fragment_sleep"]}'
529
+ else:
530
+ d['fragment'] = f'{proxy["tls_fragment_size"]},{proxy["tls_fragment_sleep"]},tlshello'
531
+
532
+ if proxy.get("tls_mixed_case"):
533
+ d['mc'] = 1
534
+ if proxy.get("tls_padding_enable"):
535
+ d['padsize'] = proxy["tls_padding_length"]
536
+
537
+
538
+ def add_mux_to_dict(d: dict, proxy):
539
+ if proxy.get('mux_enable'):
540
+ # according to github.com/hiddify/ray2sing/
541
+ d['muxtype'] = proxy["mux_protocol"]
542
+ d['muxmaxc'] = proxy["mux_max_connections"]
543
+ d['mux'] = proxy['mux_min_streams']
544
+ d['muxsmax'] = proxy["mux_max_streams"]
545
+ d['muxpad'] = proxy["mux_padding_enable"]
546
+
547
+ if proxy.get('mux_brutal_enable'):
548
+ d['muxup'] = proxy["mux_brutal_up_mbps"]
549
+ d['muxdown'] = proxy["mux_brutal_down_mbps"]
550
+
551
+
552
+ def add_tls_tricks_to_link(proxy: dict) -> str:
553
+ out = {}
554
+ add_tls_tricks_to_dict(out, proxy)
555
+ return hutils.encode.convert_dict_to_url(out)
556
+
557
+
558
+ def add_mux_to_link(proxy: dict) -> str:
559
+ out = {}
560
+ add_mux_to_dict(out, proxy)
561
+ return hutils.encode.convert_dict_to_url(out)
@@ -2,8 +2,7 @@ from enum import auto
2
2
  import uuid
3
3
  from flask import g
4
4
  from hiddifypanel.models.usage import DailyUsage
5
- from hiddifypanel.models.utils import fill_username, fill_password
6
- from sqlalchemy import event
5
+ from sqlalchemy import event, Column, Integer, Enum, Boolean, ForeignKey
7
6
  from strenum import StrEnum
8
7
  from apiflask import abort
9
8
  from flask_babel import gettext as __
@@ -30,15 +29,15 @@ class AdminUser(BaseAccount):
30
29
  This is a model class for a user in a database that includes columns for their ID, UUID, name, online status,
31
30
  account expiration date, usage limit, package days, mode, start date, current usage, last reset time, and comment.
32
31
  """
33
- id = db.Column(db.Integer, primary_key=True, autoincrement=True)
34
- mode = db.Column(db.Enum(AdminMode), default=AdminMode.agent, nullable=False)
35
- can_add_admin = db.Column(db.Boolean, default=False, nullable=False)
36
- max_users = db.Column(db.Integer, default=100, nullable=False)
37
- max_active_users = db.Column(db.Integer, default=100, nullable=False)
38
- users = db.relationship('User', backref='admin')
39
- usages = db.relationship('DailyUsage', backref='admin')
40
- parent_admin_id = db.Column(db.Integer, db.ForeignKey('admin_user.id'), default=1)
41
- parent_admin = db.relationship('AdminUser', remote_side=[id], backref='sub_admins')
32
+ id = Column(Integer, primary_key=True, autoincrement=True)
33
+ mode = Column(Enum(AdminMode), default=AdminMode.agent, nullable=False)
34
+ can_add_admin = Column(Boolean, default=False, nullable=False)
35
+ max_users = Column(Integer, default=100, nullable=False)
36
+ max_active_users = Column(Integer, default=100, nullable=False)
37
+ users = db.relationship('User', backref='admin') # type: ignore
38
+ usages = db.relationship('DailyUsage', backref='admin') # type: ignore
39
+ parent_admin_id = Column(Integer, ForeignKey('admin_user.id'), default=1)
40
+ parent_admin = db.relationship('AdminUser', remote_side=[id], backref='sub_admins') # type: ignore
42
41
 
43
42
  @property
44
43
  def role(self) -> Role | None:
@@ -54,8 +53,10 @@ class AdminUser(BaseAccount):
54
53
  def get_id(self) -> str | None:
55
54
  return f'admin_{self.id}'
56
55
 
57
- def to_dict(self, convert_date=True) -> dict:
56
+ def to_dict(self, convert_date=True, dump_id=False) -> dict:
58
57
  base = super().to_dict()
58
+ if dump_id:
59
+ base['id'] = self.id
59
60
  return {**base,
60
61
  'mode': self.mode,
61
62
  'can_add_admin': self.can_add_admin,
@@ -161,5 +162,6 @@ class AdminUser(BaseAccount):
161
162
 
162
163
  @event.listens_for(AdminUser, "before_insert")
163
164
  def before_insert(mapper, connection, target):
164
- fill_username(target)
165
- fill_password(target)
165
+ from hiddifypanel import hutils
166
+ hutils.model.gen_username(target)
167
+ hutils.model.gen_password(target)
@@ -61,12 +61,12 @@ class BaseAccount(db.Model, SerializerMixin, FlaskLoginUserMixin): # type: igno
61
61
  def add_or_update(cls, commit: bool = True, **data):
62
62
  from hiddifypanel import hutils
63
63
  db_account = cls.by_uuid(data['uuid'], create=True)
64
- db_account.name = data.get('name') or ''
64
+ db_account.name = data.get('name', '')
65
65
  db_account.comment = data.get('comment', '')
66
66
  db_account.telegram_id = hutils.convert.to_int(data.get('telegram_id'))
67
67
  db_account.lang = data.get('lang')
68
68
  if commit:
69
- db.session.commit()
69
+ db.session.commit() # type: ignore
70
70
  return db_account
71
71
 
72
72
  @classmethod
@@ -77,6 +77,6 @@ class BaseAccount(db.Model, SerializerMixin, FlaskLoginUserMixin): # type: igno
77
77
  dd = {u['uuid']: 1 for u in accounts}
78
78
  for d in cls.query.all():
79
79
  if d.uuid not in dd:
80
- db.session.delete(d)
80
+ db.session.delete(d) # type: ignore
81
81
  if commit:
82
- db.session.commit()
82
+ db.session.commit() # type: ignore
@@ -108,7 +108,7 @@ def set_hconfig(key: ConfigEnum, value: str | int | bool, child_id: int | None =
108
108
 
109
109
 
110
110
  @cache.cache(ttl=500,)
111
- def get_hconfigs(child_id: int | None = None, json=False):
111
+ def get_hconfigs(child_id: int | None = None, json=False) -> dict:
112
112
  if child_id is None:
113
113
  child_id = Child.current.id
114
114