hiddifypanel 9.0.0.dev90__py3-none-any.whl → 10.5.0.dev0__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 (152) hide show
  1. hiddifypanel/VERSION +1 -1
  2. hiddifypanel/VERSION.py +2 -2
  3. hiddifypanel/auth.py +30 -9
  4. hiddifypanel/base.py +60 -52
  5. hiddifypanel/cache.py +43 -25
  6. hiddifypanel/database.py +9 -0
  7. hiddifypanel/drivers/abstract_driver.py +2 -0
  8. hiddifypanel/drivers/singbox_api.py +17 -15
  9. hiddifypanel/drivers/ssh_liberty_bridge_api.py +3 -1
  10. hiddifypanel/drivers/user_driver.py +12 -6
  11. hiddifypanel/drivers/wireguard_api.py +7 -2
  12. hiddifypanel/drivers/xray_api.py +14 -9
  13. hiddifypanel/hutils/__init__.py +4 -0
  14. hiddifypanel/hutils/convert.py +13 -2
  15. hiddifypanel/hutils/crypto.py +48 -0
  16. hiddifypanel/hutils/encode.py +4 -1
  17. hiddifypanel/hutils/flask.py +38 -5
  18. hiddifypanel/hutils/github_issue.py +1 -1
  19. hiddifypanel/hutils/importer/xui.py +5 -2
  20. hiddifypanel/{models/utils.py → hutils/model.py} +14 -4
  21. hiddifypanel/hutils/network/auto_ip_selector.py +2 -0
  22. hiddifypanel/hutils/network/net.py +46 -2
  23. hiddifypanel/hutils/node/__init__.py +3 -0
  24. hiddifypanel/hutils/node/api_client.py +76 -0
  25. hiddifypanel/hutils/node/child.py +147 -0
  26. hiddifypanel/hutils/node/parent.py +100 -0
  27. hiddifypanel/hutils/node/shared.py +65 -0
  28. hiddifypanel/hutils/proxy/__init__.py +5 -0
  29. hiddifypanel/hutils/proxy/clash.py +161 -0
  30. hiddifypanel/hutils/proxy/shared.py +434 -0
  31. hiddifypanel/hutils/proxy/singbox.py +339 -0
  32. hiddifypanel/hutils/proxy/xray.py +235 -0
  33. hiddifypanel/hutils/proxy/xrayjson.py +391 -0
  34. hiddifypanel/hutils/random.py +4 -0
  35. hiddifypanel/hutils/utils.py +4 -1
  36. hiddifypanel/models/__init__.py +2 -2
  37. hiddifypanel/models/admin.py +31 -17
  38. hiddifypanel/models/base_account.py +7 -7
  39. hiddifypanel/models/child.py +30 -16
  40. hiddifypanel/models/config.py +45 -16
  41. hiddifypanel/models/config_enum.py +68 -17
  42. hiddifypanel/models/domain.py +28 -20
  43. hiddifypanel/models/parent_domain.py +2 -2
  44. hiddifypanel/models/proxy.py +29 -20
  45. hiddifypanel/models/report.py +2 -3
  46. hiddifypanel/models/usage.py +2 -2
  47. hiddifypanel/models/user.py +33 -22
  48. hiddifypanel/panel/admin/Actions.py +13 -19
  49. hiddifypanel/panel/admin/AdminstratorAdmin.py +14 -3
  50. hiddifypanel/panel/admin/Dashboard.py +5 -10
  51. hiddifypanel/panel/admin/DomainAdmin.py +35 -48
  52. hiddifypanel/panel/admin/NodeAdmin.py +6 -2
  53. hiddifypanel/panel/admin/ProxyAdmin.py +6 -5
  54. hiddifypanel/panel/admin/QuickSetup.py +21 -20
  55. hiddifypanel/panel/admin/SettingAdmin.py +107 -62
  56. hiddifypanel/panel/admin/UserAdmin.py +22 -21
  57. hiddifypanel/panel/admin/templates/index.html +1 -1
  58. hiddifypanel/panel/admin/templates/model/user_list.html +44 -20
  59. hiddifypanel/panel/admin/templates/parent_dash.html +2 -4
  60. hiddifypanel/panel/admin/templates/result.html +2 -3
  61. hiddifypanel/panel/cf_api.py +1 -2
  62. hiddifypanel/panel/cli.py +16 -16
  63. hiddifypanel/panel/commercial/ProxyDetailsAdmin.py +16 -12
  64. hiddifypanel/panel/commercial/__init__.py +7 -5
  65. hiddifypanel/panel/commercial/restapi/v1/__init__.py +1 -1
  66. hiddifypanel/panel/commercial/restapi/v1/tgbot.py +1 -1
  67. hiddifypanel/panel/commercial/restapi/v1/tgmsg.py +14 -10
  68. hiddifypanel/panel/commercial/restapi/v2/admin/__init__.py +0 -5
  69. hiddifypanel/panel/commercial/restapi/v2/admin/admin_info_api.py +2 -2
  70. hiddifypanel/panel/commercial/restapi/v2/admin/admin_log_api.py +4 -5
  71. hiddifypanel/panel/commercial/restapi/v2/admin/admin_user_api.py +8 -25
  72. hiddifypanel/panel/commercial/restapi/v2/admin/admin_users_api.py +4 -4
  73. hiddifypanel/panel/commercial/restapi/v2/admin/schema.py +157 -0
  74. hiddifypanel/panel/commercial/restapi/v2/admin/server_status_api.py +3 -3
  75. hiddifypanel/panel/commercial/restapi/v2/admin/user_api.py +9 -66
  76. hiddifypanel/panel/commercial/restapi/v2/admin/users_api.py +1 -1
  77. hiddifypanel/panel/commercial/restapi/v2/child/__init__.py +18 -0
  78. hiddifypanel/panel/commercial/restapi/v2/child/actions.py +63 -0
  79. hiddifypanel/panel/commercial/restapi/v2/child/register_parent_api.py +34 -0
  80. hiddifypanel/panel/commercial/restapi/v2/child/schema.py +7 -0
  81. hiddifypanel/panel/commercial/restapi/v2/child/sync_parent_api.py +21 -0
  82. hiddifypanel/panel/commercial/restapi/v2/panel/__init__.py +13 -0
  83. hiddifypanel/panel/commercial/restapi/v2/panel/info.py +18 -0
  84. hiddifypanel/panel/commercial/restapi/v2/panel/ping_pong.py +23 -0
  85. hiddifypanel/panel/commercial/restapi/v2/panel/schema.py +7 -0
  86. hiddifypanel/panel/commercial/restapi/v2/parent/__init__.py +16 -0
  87. hiddifypanel/panel/commercial/restapi/v2/parent/register_api.py +65 -0
  88. hiddifypanel/panel/commercial/restapi/v2/parent/schema.py +115 -0
  89. hiddifypanel/panel/commercial/restapi/v2/parent/status_api.py +26 -0
  90. hiddifypanel/panel/commercial/restapi/v2/parent/sync_api.py +53 -0
  91. hiddifypanel/panel/commercial/restapi/v2/parent/usage_api.py +57 -0
  92. hiddifypanel/panel/commercial/restapi/v2/user/apps_api.py +17 -23
  93. hiddifypanel/panel/commercial/restapi/v2/user/configs_api.py +23 -26
  94. hiddifypanel/panel/commercial/telegrambot/admin.py +1 -2
  95. hiddifypanel/panel/common.py +25 -8
  96. hiddifypanel/panel/common_bp/login.py +2 -2
  97. hiddifypanel/panel/hiddify.py +22 -185
  98. hiddifypanel/panel/init_db.py +102 -55
  99. hiddifypanel/panel/usage.py +33 -18
  100. hiddifypanel/panel/user/__init__.py +0 -1
  101. hiddifypanel/panel/user/templates/all_configs copy.txt +2 -2
  102. hiddifypanel/panel/user/templates/all_configs.txt +2 -2
  103. hiddifypanel/panel/user/templates/base_singbox_config.json.j2 +2 -1
  104. hiddifypanel/panel/user/templates/base_xray_config.json.j2 +125 -0
  105. hiddifypanel/panel/user/templates/clash_config copy.yml +1 -1
  106. hiddifypanel/panel/user/templates/clash_config.yml +4 -4
  107. hiddifypanel/panel/user/templates/clash_proxies.yml +1 -1
  108. hiddifypanel/panel/user/templates/home/all-configs.html +2 -2
  109. hiddifypanel/panel/user/templates/home/all-configs_old.html +1 -1
  110. hiddifypanel/panel/user/templates/home/ios copy.html +2 -2
  111. hiddifypanel/panel/user/templates/home/usage.html +1 -1
  112. hiddifypanel/panel/user/templates/new.html +2 -2
  113. hiddifypanel/panel/user/user.py +56 -50
  114. hiddifypanel/static/css/custom.css +31 -0
  115. hiddifypanel/static/images/favicon.ico +0 -0
  116. hiddifypanel/static/images/hiddify-old.png +0 -0
  117. hiddifypanel/static/images/hiddify.png +0 -0
  118. hiddifypanel/static/images/hiddify2.png +0 -0
  119. hiddifypanel/static/new/assets/{index-1b891a7c.js → index-ccb9873c.js} +56 -56
  120. hiddifypanel/static/new/assets/index-fa00de9a.css +1 -0
  121. hiddifypanel/static/new/i18n/en.json +6 -6
  122. hiddifypanel/static/new/i18n/fa.json +2 -2
  123. hiddifypanel/templates/admin-layout.html +30 -43
  124. hiddifypanel/templates/fake.html +0 -4
  125. hiddifypanel/templates/flaskadmin-layout.html +7 -3
  126. hiddifypanel/templates/master.html +11 -6
  127. hiddifypanel/translations/en/LC_MESSAGES/messages.mo +0 -0
  128. hiddifypanel/translations/en/LC_MESSAGES/messages.po +2082 -1977
  129. hiddifypanel/translations/fa/LC_MESSAGES/messages.mo +0 -0
  130. hiddifypanel/translations/fa/LC_MESSAGES/messages.po +2035 -1924
  131. hiddifypanel/translations/pt/LC_MESSAGES/messages.mo +0 -0
  132. hiddifypanel/translations/pt/LC_MESSAGES/messages.po +1911 -1840
  133. hiddifypanel/translations/ru/LC_MESSAGES/messages.mo +0 -0
  134. hiddifypanel/translations/ru/LC_MESSAGES/messages.po +2036 -1881
  135. hiddifypanel/translations/zh/LC_MESSAGES/messages.mo +0 -0
  136. hiddifypanel/translations/zh/LC_MESSAGES/messages.po +1857 -1720
  137. hiddifypanel/translations.i18n/en.json +992 -933
  138. hiddifypanel/translations.i18n/fa.json +994 -935
  139. hiddifypanel/translations.i18n/pt.json +994 -935
  140. hiddifypanel/translations.i18n/ru.json +994 -935
  141. hiddifypanel/translations.i18n/zh.json +971 -912
  142. {hiddifypanel-9.0.0.dev90.dist-info → hiddifypanel-10.5.0.dev0.dist-info}/METADATA +47 -47
  143. {hiddifypanel-9.0.0.dev90.dist-info → hiddifypanel-10.5.0.dev0.dist-info}/RECORD +147 -120
  144. {hiddifypanel-9.0.0.dev90.dist-info → hiddifypanel-10.5.0.dev0.dist-info}/WHEEL +1 -1
  145. hiddifypanel/panel/commercial/restapi/v2/DTO.py +0 -9
  146. hiddifypanel/panel/commercial/restapi/v2/hello/__init__.py +0 -16
  147. hiddifypanel/panel/commercial/restapi/v2/hello/hello.py +0 -32
  148. hiddifypanel/panel/user/link_maker.py +0 -1083
  149. hiddifypanel/static/new/assets/index-669b32c8.css +0 -1
  150. {hiddifypanel-9.0.0.dev90.dist-info → hiddifypanel-10.5.0.dev0.dist-info}/LICENSE.md +0 -0
  151. {hiddifypanel-9.0.0.dev90.dist-info → hiddifypanel-10.5.0.dev0.dist-info}/entry_points.txt +0 -0
  152. {hiddifypanel-9.0.0.dev90.dist-info → hiddifypanel-10.5.0.dev0.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)
@@ -48,3 +48,7 @@ def get_random_unused_port():
48
48
  while __is_in_used_port(port):
49
49
  port = random.randint(11000, 60000)
50
50
  return port
51
+
52
+
53
+ def random_case(string):
54
+ return ''.join(random.choice((str.upper, str.lower))(c) for c in string)
@@ -31,7 +31,10 @@ def get_latest_release_version(repo_name):
31
31
  if location_header:
32
32
  version = re.search(r"/([^/]+)/?$", location_header)
33
33
  if version:
34
- return version.group(1).replace('v', '')
34
+ ver = version.group(1).replace('v', '')
35
+ if ver == "latest":
36
+ return get_latest_release_version(repo_name.replace("-", ""))
37
+ return ver
35
38
  except Exception as e:
36
39
  return f'{e}'
37
40
 
@@ -1,10 +1,10 @@
1
1
  from .role import Role, AccountType
2
2
  from .child import Child, ChildMode
3
- from .config_enum import ConfigCategory, ConfigEnum, Lang, ApplyMode
3
+ from .config_enum import ConfigCategory, ConfigEnum, Lang, ApplyMode, PanelMode, LogLevel
4
4
  from .config import StrConfig, BoolConfig, get_hconfigs, hconfig, set_hconfig, add_or_update_config, bulk_register_configs, get_hconfigs_childs
5
5
 
6
6
  # from .parent_domain import ParentDomain
7
- from .domain import Domain, DomainType, ShowDomain, get_domain, get_current_proxy_domains, get_panel_domains, get_proxy_domains, get_proxy_domains_db, get_hdomains, hdomain, add_or_update_domain, bulk_register_domains
7
+ from .domain import Domain, DomainType, ShowDomain, get_domain, get_current_proxy_domains, get_panel_domains, get_proxy_domains, get_proxy_domains_db, get_hdomains, hdomain, add_or_update_domain, bulk_register_domains, get_panel_link
8
8
  from .proxy import Proxy, ProxyL3, ProxyCDN, ProxyProto, ProxyTransport
9
9
  from .user import User, UserMode, UserDetail, ONE_GIG
10
10
  from .admin import AdminUser, AdminMode
@@ -2,13 +2,12 @@ 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 __
10
9
  from flask_babel import lazy_gettext as _
11
- from hiddifypanel.database import db
10
+ from hiddifypanel.database import db, db_execute
12
11
  from hiddifypanel.models.role import Role
13
12
  from hiddifypanel.models.base_account import BaseAccount
14
13
 
@@ -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:
@@ -51,15 +50,28 @@ class AdminUser(BaseAccount):
51
50
  return Role.agent
52
51
  return None
53
52
 
53
+ @staticmethod
54
+ def form_schema(schema):
55
+ return schema.dump(AdminUser())
56
+
57
+ def to_schema(self):
58
+ admin_dict = self.to_dict()
59
+ from hiddifypanel.panel.commercial.restapi.v2.admin.admin_user_api import AdminSchema
60
+ return AdminSchema().load(admin_dict)
61
+
54
62
  def get_id(self) -> str | None:
55
63
  return f'admin_{self.id}'
56
64
 
57
- def to_dict(self, convert_date=True) -> dict:
65
+ def to_dict(self, convert_date=True, dump_id=False) -> dict:
58
66
  base = super().to_dict()
67
+ if dump_id:
68
+ base['id'] = self.id
69
+ from hiddifypanel.models import hconfig, ConfigEnum
59
70
  return {**base,
60
71
  'mode': self.mode,
61
72
  'can_add_admin': self.can_add_admin,
62
73
  'parent_admin_uuid': self.parent_admin.uuid if self.parent_admin else None,
74
+ 'lang': hconfig(ConfigEnum.admin_lang)
63
75
  }
64
76
 
65
77
  @classmethod
@@ -87,7 +99,7 @@ class AdminUser(BaseAccount):
87
99
  dbuser.parent_admin_id = parent_admin.id # type: ignore
88
100
 
89
101
  dbuser.mode = data.get('mode', AdminMode.agent)
90
- dbuser.can_add_admin = data.get('can_add_admin')
102
+ dbuser.can_add_admin = data.get('can_add_admin') or False
91
103
  if commit:
92
104
  db.session.commit()
93
105
  return dbuser
@@ -143,7 +155,8 @@ class AdminUser(BaseAccount):
143
155
  if not admin:
144
156
  db.session.add(AdminUser(id=1, uuid=str(uuid.uuid4()), name="Owner", mode=AdminMode.super_admin, comment=""))
145
157
  db.session.commit()
146
- db.engine.execute("update admin_user set id=1 where name='Owner'")
158
+
159
+ db_execute("update admin_user set id=1 where name='Owner'")
147
160
  admin = AdminUser.by_id(1)
148
161
 
149
162
  return admin
@@ -161,5 +174,6 @@ class AdminUser(BaseAccount):
161
174
 
162
175
  @event.listens_for(AdminUser, "before_insert")
163
176
  def before_insert(mapper, connection, target):
164
- fill_username(target)
165
- fill_password(target)
177
+ from hiddifypanel import hutils
178
+ hutils.model.gen_username(target)
179
+ hutils.model.gen_password(target)
@@ -2,13 +2,13 @@ import datetime
2
2
  import uuid
3
3
  from hiddifypanel.models.role import Role
4
4
  from sqlalchemy import Column, String, BigInteger, Enum
5
- from sqlalchemy_serializer import SerializerMixin
5
+
6
6
  from flask_login import UserMixin as FlaskLoginUserMixin
7
7
  from hiddifypanel.models import Lang
8
8
  from hiddifypanel.database import db
9
9
 
10
10
 
11
- class BaseAccount(db.Model, SerializerMixin, FlaskLoginUserMixin): # type: ignore
11
+ class BaseAccount(db.Model, FlaskLoginUserMixin): # type: ignore
12
12
  __abstract__ = True
13
13
  uuid = Column(String(36), default=lambda: str(uuid.uuid4()), nullable=False, unique=True, index=True)
14
14
  name = Column(String(512), nullable=False, default='')
@@ -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
@@ -74,9 +74,9 @@ class BaseAccount(db.Model, SerializerMixin, FlaskLoginUserMixin): # type: igno
74
74
  for u in accounts:
75
75
  cls.add_or_update(commit=False, **u)
76
76
  if remove:
77
- dd = {u['uuid']: 1 for u in accounts}
77
+ dd = {str(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
@@ -1,30 +1,34 @@
1
1
  from __future__ import annotations
2
2
  import uuid
3
- from sqlalchemy_serializer import SerializerMixin
3
+
4
+ from sqlalchemy import Column, Integer, String, Enum, text
4
5
  from enum import auto
5
6
  from strenum import StrEnum
6
7
  from flask import g, has_app_context
7
8
 
8
9
 
9
- from hiddifypanel.database import db
10
+ from hiddifypanel.database import db, db_execute
10
11
 
11
12
 
12
13
  class ChildMode(StrEnum):
13
14
  virtual = auto()
14
- remote = auto()
15
+ remote = auto() # it's child
16
+ parent = auto()
17
+
18
+ # the child model is node
15
19
 
16
20
 
17
- class Child(db.Model, SerializerMixin):
18
- id = db.Column(db.Integer, primary_key=True, autoincrement=True)
19
- name = db.Column(db.String(200), nullable=False, unique=True)
20
- mode = db.Column(db.Enum(ChildMode), nullable=False, default=ChildMode.virtual)
21
+ class Child(db.Model): # type: ignore
22
+ id = Column(Integer, primary_key=True, autoincrement=True)
23
+ name = Column(String(200), nullable=False, unique=False)
24
+ mode = Column(Enum(ChildMode), nullable=False, default=ChildMode.virtual)
21
25
  # ip = db.Column(db.String(200), nullable=False, unique=True)
22
- unique_id = db.Column(db.String(200), nullable=False, default=lambda: str(uuid.uuid4()), unique=True)
23
- domains = db.relationship('Domain', cascade="all,delete", backref='child')
24
- proxies = db.relationship('Proxy', cascade="all,delete", backref='child')
25
- boolconfigs = db.relationship('BoolConfig', cascade="all,delete", backref='child')
26
- strconfigs = db.relationship('StrConfig', cascade="all,delete", backref='child')
27
- dailyusages = db.relationship('DailyUsage', cascade="all,delete", backref='child')
26
+ unique_id = Column(String(200), nullable=False, default=lambda: str(uuid.uuid4()), unique=True)
27
+ domains = db.relationship('Domain', cascade="all,delete", backref='child') # type: ignore
28
+ proxies = db.relationship('Proxy', cascade="all,delete", backref='child') # type: ignore
29
+ boolconfigs = db.relationship('BoolConfig', cascade="all,delete", backref='child') # type: ignore
30
+ strconfigs = db.relationship('StrConfig', cascade="all,delete", backref='child') # type: ignore
31
+ dailyusages = db.relationship('DailyUsage', cascade="all,delete", backref='child') # type: ignore
28
32
 
29
33
  def to_dict(self):
30
34
  return {
@@ -55,11 +59,14 @@ class Child(db.Model, SerializerMixin):
55
59
  db.session.commit()
56
60
 
57
61
  @classmethod
58
- def by_id(cls, id: int) -> "Child":
62
+ def by_id(cls, id: int) -> 'Child':
59
63
  return Child.query.filter(Child.id == id).first()
60
64
 
61
65
  @classmethod
62
- @property
66
+ def by_unique_id(cls, unique_id: str) -> 'Child':
67
+ return Child.query.filter(Child.unique_id == unique_id).first()
68
+
69
+ @classmethod
63
70
  def current(cls) -> "Child":
64
71
  if has_app_context() and hasattr(g, "child"):
65
72
  return g.child
@@ -68,6 +75,13 @@ class Child(db.Model, SerializerMixin):
68
75
  tmp_uuid = str(uuid.uuid4())
69
76
  db.session.add(Child(id=0, unique_id=tmp_uuid, name="Root"))
70
77
  db.session.commit()
71
- db.engine.execute(f'update child set id=0 where unique_id="{tmp_uuid}"')
78
+ db_execute(f"update child set id=0 where unique_id='{tmp_uuid}'")
72
79
  child = Child.by_id(0)
80
+ print("child-=======", child)
73
81
  return child
82
+
83
+ @classmethod
84
+ @property
85
+ def node(cls) -> "Child | None":
86
+ if has_app_context() and hasattr(g, "node"):
87
+ return g.node