hiddifypanel 9.0.0.dev92__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 +58 -50
  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 -1848
  133. hiddifypanel/translations/ru/LC_MESSAGES/messages.mo +0 -0
  134. hiddifypanel/translations/ru/LC_MESSAGES/messages.po +2019 -1874
  135. hiddifypanel/translations/zh/LC_MESSAGES/messages.mo +0 -0
  136. hiddifypanel/translations/zh/LC_MESSAGES/messages.po +1873 -1742
  137. hiddifypanel/translations.i18n/en.json +992 -933
  138. hiddifypanel/translations.i18n/fa.json +994 -935
  139. hiddifypanel/translations.i18n/pt.json +1031 -972
  140. hiddifypanel/translations.i18n/ru.json +994 -935
  141. hiddifypanel/translations.i18n/zh.json +971 -912
  142. {hiddifypanel-9.0.0.dev92.dist-info → hiddifypanel-10.5.0.dev0.dist-info}/METADATA +47 -47
  143. {hiddifypanel-9.0.0.dev92.dist-info → hiddifypanel-10.5.0.dev0.dist-info}/RECORD +147 -120
  144. {hiddifypanel-9.0.0.dev92.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.dev92.dist-info → hiddifypanel-10.5.0.dev0.dist-info}/LICENSE.md +0 -0
  151. {hiddifypanel-9.0.0.dev92.dist-info → hiddifypanel-10.5.0.dev0.dist-info}/entry_points.txt +0 -0
  152. {hiddifypanel-9.0.0.dev92.dist-info → hiddifypanel-10.5.0.dev0.dist-info}/top_level.txt +0 -0
hiddifypanel/VERSION CHANGED
@@ -1 +1 @@
1
- 9.0.0.dev92
1
+ 10.5.0.dev0
hiddifypanel/VERSION.py CHANGED
@@ -1,3 +1,3 @@
1
- __version__='9.0.0.dev92'
1
+ __version__='10.5.0.dev0'
2
2
  from datetime import datetime
3
- __release_date__= datetime.strptime('2024-03-03','%Y-%m-%d')
3
+ __release_date__= datetime.strptime('2024-04-05','%Y-%m-%d')
hiddifypanel/auth.py CHANGED
@@ -4,7 +4,7 @@ from hiddifypanel.hutils.flask import hurl_for
4
4
  from flask_login.utils import _get_user
5
5
  from functools import wraps
6
6
  from hiddifypanel.models import *
7
-
7
+ from apiflask import abort as json_abort
8
8
  from hiddifypanel import hutils
9
9
  from werkzeug.local import LocalProxy
10
10
  current_account: "BaseAccount" = LocalProxy(lambda: _get_user())
@@ -97,14 +97,17 @@ def login_user(user: AdminUser | User, remember=False, duration=None, force=Fals
97
97
  return True
98
98
 
99
99
 
100
- def login_required(roles: set[Role] | None = None):
100
+ def login_required(roles: set[Role] | None = None, node_auth: bool = False):
101
+ '''When both roles and node_auth is set, means authentication can be done by either uuid or unique_id'''
101
102
  def wrapper(fn):
102
103
  @wraps(fn)
103
104
  def decorated_view(*args, **kwargs):
104
105
  # print('xxxx', current_account)
105
- if not current_account:
106
+ if node_auth and not Child.node and not roles:
107
+ json_abort(403, 'Unauthorized node')
108
+ if not current_account and not node_auth:
106
109
  return redirect_to_login() # type: ignore
107
- if roles:
110
+ if roles and not Child.node:
108
111
  account_role = current_account.role
109
112
  if account_role not in roles:
110
113
  return redirect_to_login() # type: ignore
@@ -143,12 +146,20 @@ def auth_before_request():
143
146
  # print(account)
144
147
  if not account:
145
148
  return logout_redirect()
146
-
147
- next_url = request.url.replace(f'/{g.uuid}/', '/admin/' if is_admin_path else '/client/').replace("/admin/admin/",
148
- '/admin/').replace("http://", "https://")
149
+ if is_admin_path:
150
+ next_url = request.url
151
+ next_url = next_url.replace(f'/{g.uuid}/', '/admin/')
152
+ next_url = next_url.replace("/admin/admin/", '/admin/')
153
+ next_url = next_url.replace("http://", "https://")
149
154
 
150
155
  elif apikey := request.headers.get("Hiddify-API-Key"):
151
156
  account = get_account_by_api_key(apikey, is_admin_path)
157
+ if not account:
158
+ # when parent/child panel needs to call another parent/child api, it will pass its unique id in the header as apikey
159
+ if node := Child.by_unique_id(apikey):
160
+ g.node = node
161
+ return
162
+
152
163
  if not account:
153
164
  return logout_redirect()
154
165
  elif request.authorization:
@@ -180,8 +191,16 @@ def auth_before_request():
180
191
  g.is_admin = hutils.flask.is_admin_role(account.role) # type: ignore
181
192
  login_user(account, force=True)
182
193
  # print("loggining in")
183
- if next_url is not None and g.user_agent['is_browser'] and ".webmanifest" not in request.path:
184
- return redirect(next_url)
194
+ if not g.is_admin:
195
+ return
196
+ if next_url is None:
197
+ return
198
+ if not g.user_agent['is_browser']:
199
+ return
200
+ if ".webmanifest" in request.path:
201
+ return
202
+
203
+ return redirect(next_url)
185
204
 
186
205
 
187
206
  def logout_redirect():
@@ -191,6 +210,8 @@ def logout_redirect():
191
210
 
192
211
 
193
212
  def redirect_to_login():
213
+ if hutils.flask.is_api_call(request.path):
214
+ json_abort(403, 'Unathorized')
194
215
  # if g.user_agent['is_browser']:
195
216
  # return redirect(hurl_for('common_bp.LoginView:basic_0', force=1, next=request.path))
196
217
  return redirect(hurl_for('common_bp.LoginView:index', force=1, next=request.path))
hiddifypanel/base.py CHANGED
@@ -11,55 +11,57 @@ import datetime
11
11
 
12
12
  from dotenv import dotenv_values
13
13
  import os
14
+ import sys
14
15
  from apiflask import APIFlask
15
16
  from werkzeug.middleware.proxy_fix import ProxyFix
16
-
17
+ from loguru import logger
17
18
  from hiddifypanel.panel.init_db import init_db
18
- from hiddifypanel.cache import redis_client
19
- from hiddifypanel import auth
20
- from hiddifypanel.panel import hiddify
21
- from hiddifypanel import hutils
22
19
 
23
20
 
24
21
  def create_app(*args, cli=False, **config):
25
22
 
26
23
  app = APIFlask(__name__, static_url_path="/<proxy_path>/static/", instance_relative_config=True, version='2.0.0', title="Hiddify API",
27
- openapi_blueprint_url_prefix="/<proxy_path>/api", docs_ui='elements', json_errors=False, enable_openapi=True)
24
+ openapi_blueprint_url_prefix="/<proxy_path>/api", docs_ui='elements', json_errors=False, enable_openapi=not cli)
28
25
  # app = Flask(__name__, static_url_path="/<proxy_path>/static/", instance_relative_config=True)
29
26
 
30
- app.config["PREFERRED_URL_SCHEME"] = "https"
31
- app.wsgi_app = ProxyFix(
32
- app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_prefix=1,
33
- )
34
- app.servers = {
35
- 'name': 'current',
36
- 'url': '',
37
- } # type: ignore
38
- app.info = {
39
- 'description': 'Hiddify is a free and open source software. It is as it is.',
40
- 'termsOfService': 'https://hiddify.com',
41
- 'contact': {
42
- 'name': 'API Support',
43
- 'url': 'https://www.hiddify.com/support',
44
- 'email': 'panel@hiddify.com'
45
- },
46
- 'license': {
47
- 'name': 'Creative Commons Zero v1.0 Universal',
48
- 'url': 'https://github.com/hiddify/Hiddify-Manager/blob/main/LICENSE'
27
+ if not cli:
28
+ from hiddifypanel.cache import redis_client
29
+ from hiddifypanel import auth
30
+ app.config["PREFERRED_URL_SCHEME"] = "https"
31
+ app.wsgi_app = ProxyFix(
32
+ app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_prefix=1,
33
+ )
34
+ app.servers = {
35
+ 'name': 'current',
36
+ 'url': '',
37
+ } # type: ignore
38
+ app.info = {
39
+ 'description': 'Hiddify is a free and open source software. It is as it is.',
40
+ 'termsOfService': 'https://hiddify.com',
41
+ 'contact': {
42
+ 'name': 'API Support',
43
+ 'url': 'https://www.hiddify.com/support',
44
+ 'email': 'panel@hiddify.com'
45
+ },
46
+ 'license': {
47
+ 'name': 'Creative Commons Zero v1.0 Universal',
48
+ 'url': 'https://github.com/hiddify/Hiddify-Manager/blob/main/LICENSE'
49
+ }
49
50
  }
50
- }
51
- # setup flask server-side session
52
- # app.config['APPLICATION_ROOT'] = './'
53
- # app.config['SESSION_COOKIE_DOMAIN'] = '/'
54
- app.config['SESSION_TYPE'] = 'redis'
55
- app.config['SESSION_REDIS'] = redis_client
56
- app.config['SESSION_PERMANENT'] = True
57
- app.config['PERMANENT_SESSION_LIFETIME'] = datetime.timedelta(days=10)
58
- Session(app)
59
- app.jinja_env.line_statement_prefix = '%'
60
- app.jinja_env.filters['b64encode'] = hutils.encode.do_base_64
61
- app.view_functions['admin.static'] = {} # fix bug in apiflask
62
- flask_bootstrap.Bootstrap4(app)
51
+ # setup flask server-side session
52
+ # app.config['APPLICATION_ROOT'] = './'
53
+ # app.config['SESSION_COOKIE_DOMAIN'] = '/'
54
+ app.config['SESSION_TYPE'] = 'redis'
55
+ app.config['SESSION_REDIS'] = redis_client
56
+ app.config['SESSION_PERMANENT'] = True
57
+ app.config['PERMANENT_SESSION_LIFETIME'] = datetime.timedelta(days=10)
58
+ Session(app)
59
+
60
+ app.jinja_env.line_statement_prefix = '%'
61
+ from hiddifypanel import hutils
62
+ app.jinja_env.filters['b64encode'] = hutils.encode.do_base_64
63
+ app.view_functions['admin.static'] = {} # fix bug in apiflask
64
+ flask_bootstrap.Bootstrap4(app)
63
65
 
64
66
  for c, v in dotenv_values(os.environ.get("HIDDIFY_CFG_PATH", 'app.cfg')).items():
65
67
  if v.isdecimal():
@@ -72,27 +74,34 @@ def create_app(*args, cli=False, **config):
72
74
  hiddifypanel.database.init_app(app)
73
75
  with app.app_context():
74
76
  init_db()
75
- # flaskbabel = FlaskBabel(app)
76
77
 
78
+ # configure logger
79
+ from hiddifypanel.models import ConfigEnum, hconfig
80
+ logger.remove()
81
+ log_format = '<green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level: <8}</level> | <cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - <level>{message}</level> | <level>{extra}</level>'
82
+ logger.add(sys.stderr, format=log_format, level=hconfig(ConfigEnum.log_level), colorize=True, catch=True, enqueue=True, diagnose=False, backtrace=True)
83
+
84
+ # flaskbabel = FlaskBabel(app)
77
85
  # @babel.localeselector
86
+
78
87
  def get_locale():
79
88
  # Put your logic here. Application can store locale in
80
89
  # user profile, cookie, session, etc.
81
- from hiddifypanel.models import ConfigEnum, hconfig
82
90
  if "admin" in request.base_url:
83
91
  g.locale = auth.current_account.lang or hconfig(ConfigEnum.admin_lang) or 'fa'
84
92
  else:
85
93
  g.locale = auth.current_account.lang or hconfig(ConfigEnum.lang) or 'fa'
86
94
  return g.locale
95
+ app.jinja_env.globals['get_locale'] = get_locale
87
96
  babel = Babel(app, locale_selector=get_locale)
97
+ if not cli:
98
+ hiddifypanel.panel.common.init_app(app)
99
+ hiddifypanel.panel.common_bp.init_app(app)
88
100
 
89
- hiddifypanel.panel.common.init_app(app)
90
- hiddifypanel.panel.common_bp.init_app(app)
91
-
92
- from hiddifypanel.panel import user, commercial, admin
93
- admin.init_app(app)
94
- user.init_app(app)
95
- commercial.init_app(app)
101
+ from hiddifypanel.panel import user, commercial, admin
102
+ admin.init_app(app)
103
+ user.init_app(app)
104
+ commercial.init_app(app)
96
105
 
97
106
  app.config.update(config) # Override with passed config
98
107
  # app.config['WTF_CSRF_CHECK_DEFAULT'] = False
@@ -115,8 +124,6 @@ def create_app(*args, cli=False, **config):
115
124
  # return
116
125
  # csrf.protect()
117
126
 
118
- app.jinja_env.globals['get_locale'] = get_locale
119
-
120
127
  hiddifypanel.panel.cli.init_app(app)
121
128
  return app
122
129
 
@@ -126,7 +133,8 @@ def create_app_wsgi(*args, **kwargs):
126
133
  # that doesn't allow **config
127
134
  # to be passed to create_app
128
135
  # https://github.com/pallets/flask/issues/4170
129
- app = create_app()
136
+ cli = ("hiddifypanel" in sys.argv[0]) or (sys.argv[1] in ["update-usage", "all-configs", "admin_links", "admin_path"])
137
+ app = create_app(cli=cli)
130
138
  return app
131
139
 
132
140
 
hiddifypanel/cache.py CHANGED
@@ -1,47 +1,65 @@
1
- from redis_cache import RedisCache
2
- from functools import wraps
1
+ from redis_cache import RedisCache, CacheDecorator, compact_dump, chunks
2
+ from redis_cache import loads as redis_cache_loads
3
3
  import redis
4
4
  from pickle import dumps, loads
5
+ from loguru import logger
6
+
5
7
  redis_client = redis.from_url('unix:///opt/hiddify-manager/other/redis/run.sock?db=0')
6
8
 
7
9
 
8
- def exception_handler(e, original_fn, args, kwargs):
9
- print("cache exception occur", e, original_fn, args, kwargs)
10
- return original_fn(*args, **kwargs)
11
- pass
10
+ class CustomRedisCache(RedisCache):
11
+
12
+ def invalidate_all_cached_functions(self):
13
+ try:
14
+ logger.info("Invalidating all cached functions")
15
+ chunks_gen = chunks(f'{self.prefix}*', 500)
16
+ for keys in chunks_gen:
17
+ self.client.delete(*keys)
18
+ logger.success("Successfully invalidated all cached functions")
19
+ return True
20
+ except Exception as err:
21
+ with logger.contextualize(error=err):
22
+ logger.error("Failed to invalidate all cached functions")
23
+ return False
24
+
25
+
26
+ cache = CustomRedisCache(redis_client=redis_client, prefix="h", serializer=dumps, deserializer=loads)
12
27
 
13
28
 
14
29
  # cache = RedisCache(redis_client=redis_client, exception_handler=exception_handler)
15
30
  # cache = RedisCache(redis_client=redis_client, prefix="h", serializer=dumps, deserializer=loads, exception_handler=exception_handler)
16
- cache = RedisCache(redis_client=redis_client, prefix="h", serializer=dumps, deserializer=loads)
17
31
 
32
+ # def exception_handler(e, original_fn, args, kwargs):
33
+ # print("cache exception occur", e, original_fn, args, kwargs)
34
+ # return original_fn(*args, **kwargs)
35
+ # pass
18
36
 
19
- class CacheDecorator:
20
- def __init__(self, *args, **kwargs):
21
- pass
37
+ # class CacheDecorator:
38
+ # def __init__(self, *args, **kwargs):
39
+ # pass
22
40
 
23
- def __call__(self, fn):
24
- @wraps(fn)
25
- def inner(*args, **kwargs):
41
+ # def __call__(self, fn):
42
+ # @wraps(fn)
43
+ # def inner(*args, **kwargs):
26
44
 
27
- parsed_result = fn(*args, **kwargs)
45
+ # parsed_result = fn(*args, **kwargs)
28
46
 
29
- return parsed_result
47
+ # return parsed_result
30
48
 
31
- inner.invalidate = self.invalidate
32
- inner.invalidate_all = self.invalidate_all
33
- inner.instance = self
34
- return inner
49
+ # inner.invalidate = self.invalidate
50
+ # inner.invalidate_all = self.invalidate_all
51
+ # inner.instance = self
52
+ # return inner
35
53
 
36
- def invalidate(self, *args, **kwargs):
37
- pass
54
+ # def invalidate(self, *args, **kwargs):
55
+ # pass
38
56
 
39
- def invalidate_all(self, *args, **kwargs):
40
- pass
57
+ # def invalidate_all(self, *args, **kwargs):
58
+ # pass
41
59
 
42
60
 
43
- class DisableCache:
44
- cache = CacheDecorator
61
+ # class DisableCache:
62
+ # cache = CacheDecorator
45
63
 
46
64
 
47
65
  # cache = DisableCache()
hiddifypanel/database.py CHANGED
@@ -2,6 +2,8 @@ from flask_sqlalchemy import SQLAlchemy
2
2
  from sqlalchemy_utils import UUIDType
3
3
  import re
4
4
  import os
5
+ from sqlalchemy import text
6
+
5
7
 
6
8
  db: SQLAlchemy = SQLAlchemy()
7
9
  db.UUID = UUIDType # type: ignore
@@ -10,3 +12,10 @@ db.UUID = UUIDType # type: ignore
10
12
  def init_app(app):
11
13
  app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
12
14
  db.init_app(app)
15
+
16
+
17
+ def db_execute(query: str, **params: dict):
18
+ with db.engine.connect() as connection:
19
+ res = connection.execute(text(query), params)
20
+ connection.commit()
21
+ return res
@@ -5,3 +5,5 @@ class DriverABS:
5
5
  def get_enabled_users(self): pass
6
6
  def add_client(self, user): pass
7
7
  def remove_client(self, user): pass
8
+
9
+ def is_enabled(self) -> bool: return False
@@ -3,27 +3,22 @@ from hiddifypanel.models import *
3
3
  from .abstract_driver import DriverABS
4
4
  from flask import current_app
5
5
  import json
6
+ from collections import defaultdict
6
7
 
7
8
 
8
9
  class SingboxApi(DriverABS):
10
+ def is_enabled(self) -> bool: return True
11
+
9
12
  def get_singbox_client(self):
10
- if hconfig(ConfigEnum.is_parent):
11
- return
12
13
  return xtlsapi.SingboxClient('127.0.0.1', 10086)
13
14
 
14
15
  def get_enabled_users(self):
15
- if hconfig(ConfigEnum.is_parent):
16
- return
17
16
  config_dir = current_app.config['HIDDIFY_CONFIG_PATH']
18
17
  with open(f"{config_dir}/singbox/configs/01_api.json") as f:
19
18
  json_data = json.load(f)
20
19
  return {u.split("@")[0]: 1 for u in json_data['experimental']['v2ray_api']['stats']['users']}
21
- # raise NotImplementedError()
22
- #
23
20
 
24
21
  def get_inbound_tags(self):
25
- if hconfig(ConfigEnum.is_parent):
26
- return
27
22
  try:
28
23
  xray_client = self.get_singbox_client()
29
24
  inbounds = [inb.name.split(">>>")[1] for inb in xray_client.stats_query('inbound')]
@@ -34,17 +29,24 @@ class SingboxApi(DriverABS):
34
29
  return list(set(inbounds))
35
30
 
36
31
  def add_client(self, user):
37
- if hconfig(ConfigEnum.is_parent):
38
- return
39
- # raise NotImplementedError()
32
+ pass
40
33
 
41
34
  def remove_client(self, user):
42
- if hconfig(ConfigEnum.is_parent):
43
- return
44
- # raise NotImplementedError()
35
+ pass
45
36
 
46
37
  def get_all_usage(self, users):
47
- return {u: self.get_usage_imp(u.uuid) for u in users}
38
+ xray_client = self.get_singbox_client()
39
+ usages = xray_client.stats_query('user', reset=True)
40
+ uuid_user_map = {u.uuid: u for u in users}
41
+ res = defaultdict(int)
42
+ for use in usages:
43
+ if "user>>>" not in use.name:
44
+ continue
45
+ # print(use.name, use.value)
46
+ uuid = use.name.split(">>>")[1].split("@")[0]
47
+ res[uuid_user_map[uuid]] += use.value # uplink + downlink
48
+ return res
49
+ # return {u: self.get_usage_imp(u.uuid) for u in users}
48
50
 
49
51
  def get_usage_imp(self, uuid):
50
52
  xray_client = self.get_singbox_client()
@@ -7,10 +7,12 @@ USERS_USAGE = "ssh-server:users-usage"
7
7
 
8
8
 
9
9
  class SSHLibertyBridgeApi(DriverABS):
10
+ def is_enabled(self) -> bool:
11
+ return hconfig(ConfigEnum.ssh_server_enable)
10
12
 
11
13
  def get_ssh_redis_client(self):
12
14
  if not hasattr(self, 'redis_client'):
13
- self.redis_client = redis.from_url(hconfig(ConfigEnum.ssh_server_redis_url), decode_responses=True)
15
+ self.redis_client = redis.from_url('unix:///opt/hiddify-manager/other/redis/run.sock?db=1', decode_responses=True)
14
16
 
15
17
  return self.redis_client
16
18
 
@@ -4,19 +4,25 @@ from .singbox_api import SingboxApi
4
4
  from .wireguard_api import WireguardApi
5
5
  from hiddifypanel.models import *
6
6
  from hiddifypanel.panel import hiddify
7
+ from collections import defaultdict
8
+
7
9
  drivers = [XrayApi(), SingboxApi(), SSHLibertyBridgeApi(), WireguardApi()]
8
10
 
9
11
 
12
+ def enabled_drivers():
13
+ return [d for d in drivers if d.is_enabled()]
14
+
15
+
10
16
  def get_users_usage(reset=True):
11
17
  res = {}
12
18
  users = list(User.query.all())
13
- res = {u: {'usage': 0, 'ips': ''} for u in users}
14
- for driver in drivers:
19
+ res = defaultdict(lambda: {'usage': 0, 'devices': ''})
20
+ for driver in enabled_drivers():
15
21
  all_usage = driver.get_all_usage(users)
16
22
  for user, usage in all_usage.items():
17
23
  if usage:
18
24
  res[user]['usage'] += usage
19
- # res[user]['ip'] +=usage
25
+ # res[user]['devices'] +=usage
20
26
  return res
21
27
 
22
28
 
@@ -24,7 +30,7 @@ def get_enabled_users():
24
30
  from collections import defaultdict
25
31
  d = defaultdict(int)
26
32
  total = 0
27
- for driver in drivers:
33
+ for driver in enabled_drivers():
28
34
  try:
29
35
  for u, v in driver.get_enabled_users().items():
30
36
  if not v:
@@ -41,7 +47,7 @@ def get_enabled_users():
41
47
 
42
48
 
43
49
  def add_client(user: User):
44
- for driver in drivers:
50
+ for driver in enabled_drivers():
45
51
  try:
46
52
  driver.add_client(user)
47
53
  except Exception as e:
@@ -49,7 +55,7 @@ def add_client(user: User):
49
55
 
50
56
 
51
57
  def remove_client(user: User):
52
- for driver in drivers:
58
+ for driver in enabled_drivers():
53
59
  try:
54
60
  driver.remove_client(user)
55
61
  except Exception as e:
@@ -1,13 +1,14 @@
1
1
  import json
2
2
  import os
3
- import time
4
3
 
5
4
  from .abstract_driver import DriverABS
6
- from hiddifypanel.models import User
5
+ from hiddifypanel.models import User, hconfig, ConfigEnum
7
6
  from hiddifypanel.panel.run_commander import Command, commander
8
7
 
9
8
 
10
9
  class WireguardApi(DriverABS):
10
+ def is_enabled(self) -> bool:
11
+ return hconfig(ConfigEnum.wireguard_enable)
11
12
  WG_LOCAL_USAGE_FILE_PATH = './hiddify_usages.json'
12
13
 
13
14
  def __init__(self) -> None:
@@ -71,6 +72,8 @@ class WireguardApi(DriverABS):
71
72
  return res
72
73
 
73
74
  def get_enabled_users(self):
75
+ if not hconfig(ConfigEnum.wireguard_enable):
76
+ return {}
74
77
  usages = self.__get_wg_usages()
75
78
  wg_pubs = set(usages.keys())
76
79
 
@@ -90,6 +93,8 @@ class WireguardApi(DriverABS):
90
93
  pass
91
94
 
92
95
  def get_all_usage(self, users, reset=True):
96
+ if not hconfig(ConfigEnum.wireguard_enable):
97
+ return {}
93
98
  all_usages = self.__sync_local_usages()
94
99
  res = {}
95
100
  for u in users:
@@ -1,17 +1,17 @@
1
1
  import xtlsapi
2
2
  from hiddifypanel.models import *
3
3
  from .abstract_driver import DriverABS
4
+ from collections import defaultdict
4
5
 
5
6
 
6
7
  class XrayApi(DriverABS):
8
+ def is_enabled(self) -> bool:
9
+ return hconfig(ConfigEnum.core_type) == "xray"
10
+
7
11
  def get_xray_client(self):
8
- if hconfig(ConfigEnum.is_parent):
9
- return
10
12
  return xtlsapi.XrayClient('127.0.0.1', 10085)
11
13
 
12
14
  def get_enabled_users(self):
13
- if hconfig(ConfigEnum.is_parent):
14
- return
15
15
  xray_client = self.get_xray_client()
16
16
  users = User.query.all()
17
17
  t = "xtls"
@@ -31,8 +31,6 @@ class XrayApi(DriverABS):
31
31
  return enabled
32
32
 
33
33
  def get_inbound_tags(self):
34
- if hconfig(ConfigEnum.is_parent):
35
- return
36
34
  try:
37
35
  xray_client = self.get_xray_client()
38
36
  inbounds = [inb.name.split(">>>")[1] for inb in xray_client.stats_query('inbound')]
@@ -43,8 +41,6 @@ class XrayApi(DriverABS):
43
41
  return list(set(inbounds))
44
42
 
45
43
  def add_client(self, user):
46
- if hconfig(ConfigEnum.is_parent):
47
- return
48
44
  uuid = user.uuid
49
45
  xray_client = self.get_xray_client()
50
46
  tags = self.get_inbound_tags()
@@ -97,7 +93,16 @@ class XrayApi(DriverABS):
97
93
  pass
98
94
 
99
95
  def get_all_usage(self, users):
100
- return {u: self.get_usage_imp(u.uuid) for u in users}
96
+ xray_client = self.get_xray_client()
97
+ usages = xray_client.stats_query('user', reset=True)
98
+ uuid_user_map = {u.uuid: u for u in users}
99
+ res = defaultdict(int)
100
+ for use in usages:
101
+ if "user>>>" not in use.name:
102
+ continue
103
+ uuid = use.name.split(">>>")[1].split("@")[0]
104
+ res[uuid_user_map[uuid]] += use.value
105
+ return res
101
106
 
102
107
  def get_usage_imp(self, uuid):
103
108
  xray_client = self.get_xray_client()
@@ -8,3 +8,7 @@ from . import random
8
8
  from . import encode
9
9
  from . import auth
10
10
  from . import utils
11
+ from . import model
12
+ from . import crypto
13
+ from . import proxy
14
+ from . import node
@@ -38,13 +38,24 @@ def json_to_date(date_str: str) -> datetime | str:
38
38
 
39
39
 
40
40
  def time_to_json(d: datetime) -> str | None:
41
+ return __fix_time_format(d.strftime("%Y-%m-%d %H:%M:%S")) if d else None
41
42
 
42
- return d.strftime("%Y-%m-%d %H:%M:%S") if d else None
43
+
44
+ def __fix_time_format(time_str):
45
+ 'Convert "1-00-00 00:00:00" to "0001-00-00 00:00:00"'
46
+ t = time_str
47
+ char_index = t.find('-')
48
+ year_part = t[:char_index]
49
+
50
+ if len(year_part) < 4:
51
+ t = year_part.zfill(4) + t[char_index:]
52
+
53
+ return t
43
54
 
44
55
 
45
56
  def json_to_time(time_str: str) -> datetime | str:
46
57
  try:
47
- return datetime.strptime(time_str, "%Y-%m-%d %H:%M:%S")
58
+ return datetime.strptime(__fix_time_format(time_str), "%Y-%m-%d %H:%M:%S")
48
59
  except BaseException:
49
60
  return time_str
50
61