hiddifypanel 9.0.0.dev53__py3-none-any.whl → 9.0.0.dev60__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 (40) hide show
  1. hiddifypanel/VERSION +1 -1
  2. hiddifypanel/VERSION.py +2 -2
  3. hiddifypanel/base.py +1 -1
  4. hiddifypanel/hutils/utils.py +8 -0
  5. hiddifypanel/models/config_enum.py +23 -1
  6. hiddifypanel/panel/admin/AdminstratorAdmin.py +1 -1
  7. hiddifypanel/panel/admin/SettingAdmin.py +9 -1
  8. hiddifypanel/panel/admin/UserAdmin.py +3 -2
  9. hiddifypanel/panel/auth.py +1 -1
  10. hiddifypanel/panel/commercial/restapi/v1/tgbot.py +1 -2
  11. hiddifypanel/panel/commercial/restapi/v1/tgmsg.py +37 -29
  12. hiddifypanel/panel/commercial/restapi/v2/user/configs_api.py +15 -7
  13. hiddifypanel/panel/commercial/telegrambot/Usage.py +2 -1
  14. hiddifypanel/panel/common.py +2 -3
  15. hiddifypanel/panel/common_bp/login.py +3 -4
  16. hiddifypanel/panel/hiddify.py +2 -1
  17. hiddifypanel/panel/init_db.py +12 -0
  18. hiddifypanel/panel/user/link_maker.py +1 -1
  19. hiddifypanel/panel/user/templates/{base_singbox_config.json → base_singbox_config.json.j2} +12 -6
  20. hiddifypanel/panel/user/templates/new.html +4 -2
  21. hiddifypanel/panel/user/user.py +59 -20
  22. hiddifypanel/static/new/assets/{index-bd9ba5e9.js → index-2cd90979.js} +1 -1
  23. hiddifypanel/templates/fake.html +2 -2
  24. hiddifypanel/templates/master.html +1 -1
  25. hiddifypanel/translations/en/LC_MESSAGES/messages.mo +0 -0
  26. hiddifypanel/translations/en/LC_MESSAGES/messages.po +145 -35
  27. hiddifypanel/translations/fa/LC_MESSAGES/messages.mo +0 -0
  28. hiddifypanel/translations/fa/LC_MESSAGES/messages.po +118 -31
  29. hiddifypanel/translations/pt/LC_MESSAGES/messages.mo +0 -0
  30. hiddifypanel/translations/pt/LC_MESSAGES/messages.po +116 -29
  31. hiddifypanel/translations/ru/LC_MESSAGES/messages.mo +0 -0
  32. hiddifypanel/translations/ru/LC_MESSAGES/messages.po +116 -29
  33. hiddifypanel/translations/zh/LC_MESSAGES/messages.mo +0 -0
  34. hiddifypanel/translations/zh/LC_MESSAGES/messages.po +118 -31
  35. {hiddifypanel-9.0.0.dev53.dist-info → hiddifypanel-9.0.0.dev60.dist-info}/METADATA +1 -1
  36. {hiddifypanel-9.0.0.dev53.dist-info → hiddifypanel-9.0.0.dev60.dist-info}/RECORD +40 -40
  37. {hiddifypanel-9.0.0.dev53.dist-info → hiddifypanel-9.0.0.dev60.dist-info}/LICENSE.md +0 -0
  38. {hiddifypanel-9.0.0.dev53.dist-info → hiddifypanel-9.0.0.dev60.dist-info}/WHEEL +0 -0
  39. {hiddifypanel-9.0.0.dev53.dist-info → hiddifypanel-9.0.0.dev60.dist-info}/entry_points.txt +0 -0
  40. {hiddifypanel-9.0.0.dev53.dist-info → hiddifypanel-9.0.0.dev60.dist-info}/top_level.txt +0 -0
hiddifypanel/VERSION CHANGED
@@ -1 +1 @@
1
- 9.0.0.dev53
1
+ 9.0.0.dev60
hiddifypanel/VERSION.py CHANGED
@@ -1,3 +1,3 @@
1
- __version__='9.0.0.dev53'
1
+ __version__='9.0.0.dev60'
2
2
  from datetime import datetime
3
- __release_date__= datetime.strptime('2024-01-15','%Y-%m-%d')
3
+ __release_date__= datetime.strptime('2024-01-21','%Y-%m-%d')
hiddifypanel/base.py CHANGED
@@ -91,7 +91,7 @@ def create_app(cli=False, **config):
91
91
  if "admin" in request.base_url:
92
92
  g.locale = hconfig(ConfigEnum.admin_lang) or hconfig(ConfigEnum.lang) or 'fa'
93
93
  else:
94
- g.locale = hconfig(ConfigEnum.lang) or "fa"
94
+ g.locale = g.account.lang if isinstance(g.get('account'), User) and g.account.lang else hconfig(ConfigEnum.lang) or 'fa'
95
95
  return g.locale
96
96
 
97
97
  from flask_wtf.csrf import CSRFProtect
@@ -235,3 +235,11 @@ def convert_to_int(s: str) -> int:
235
235
 
236
236
  def is_out_of_range_port(port: int) -> bool:
237
237
  return port < 1 or port > 65535
238
+
239
+
240
+ def is_int(input: str) -> bool:
241
+ try:
242
+ int(input)
243
+ return True
244
+ except:
245
+ return False
@@ -20,6 +20,7 @@ class ConfigCategory(StrEnum):
20
20
  telegram = auto()
21
21
  http = auto()
22
22
  tls = auto()
23
+ mux = auto()
23
24
  tls_trick = auto()
24
25
  ssh = auto()
25
26
  ssfaketls = auto()
@@ -81,6 +82,17 @@ class ConfigEnum(StrEnum):
81
82
  tls_padding_enable = auto()
82
83
  tls_padding_length = auto()
83
84
 
85
+ # mux
86
+ mux_enable = auto()
87
+ mux_protocol = auto()
88
+ mux_max_connections = auto()
89
+ mux_min_streams = auto()
90
+ mux_max_streams = auto()
91
+ mux_padding_enable = auto()
92
+ mux_brutal_enable = auto()
93
+ mux_brutal_up_mbps = auto()
94
+ mux_brutal_down_mbps = auto()
95
+
84
96
  http_ports = auto()
85
97
  kcp_ports = auto()
86
98
  kcp_enable = auto()
@@ -219,6 +231,16 @@ class ConfigEnum(StrEnum):
219
231
  self.tls_padding_enable: {'category': ConfigCategory.tls_trick, 'apply_mode': 'apply', 'type': bool},
220
232
  self.tls_padding_length: {'category': ConfigCategory.tls_trick, 'apply_mode': 'apply'},
221
233
 
234
+ # mux
235
+ self.mux_enable: {'category': ConfigCategory.mux, 'apply_mode': 'apply', 'type': bool},
236
+ self.mux_protocol: {'category': ConfigCategory.mux, 'apply_mode': 'apply'},
237
+ self.mux_max_connections: {'category': ConfigCategory.mux, 'apply_mode': 'apply'},
238
+ self.mux_min_streams: {'category': ConfigCategory.mux, 'apply_mode': 'apply'},
239
+ self.mux_max_streams: {'category': ConfigCategory.mux, 'apply_mode': 'apply'},
240
+ self.mux_padding_enable: {'category': ConfigCategory.mux, 'apply_mode': 'apply', 'type': bool},
241
+ self.mux_brutal_enable: {'category': ConfigCategory.mux, 'apply_mode': 'apply', 'type': bool},
242
+ self.mux_brutal_up_mbps: {'category': ConfigCategory.mux, 'apply_mode': 'apply'},
243
+ self.mux_brutal_down_mbps: {'category': ConfigCategory.mux, 'apply_mode': 'apply'},
222
244
 
223
245
  self.http_ports: {'category': ConfigCategory.http, 'apply_mode': 'apply'}, # http
224
246
  self.kcp_ports: {'category': ConfigCategory.hidden, 'apply_mode': 'apply'},
@@ -257,7 +279,7 @@ class ConfigEnum(StrEnum):
257
279
  self.tuic_port: {'category': ConfigCategory.hidden, 'apply_mode': 'apply'},
258
280
 
259
281
  self.hysteria_enable: {'category': ConfigCategory.hidden, 'type': bool, 'apply_mode': 'apply'},
260
- self.hysteria_port: {'category': ConfigCategory.hysteria, 'apply_mode': 'apply'},
282
+ self.hysteria_port: {'category': ConfigCategory.hidden, 'apply_mode': 'apply'},
261
283
  self.hysteria_obfs_enable: {'category': ConfigCategory.hysteria, 'type': bool, 'apply_mode': 'apply'},
262
284
  self.hysteria_up_mbps: {'category': ConfigCategory.hysteria, 'apply_mode': 'apply'},
263
285
  self.hysteria_down_mbps: {'category': ConfigCategory.hysteria, 'apply_mode': 'apply'},
@@ -168,7 +168,7 @@ class AdminstratorAdmin(AdminLTEModelView):
168
168
 
169
169
  # @login_required(roles={Role.super_admin, Role.admin})
170
170
  def is_accessible(self):
171
- if login_required(roles={Role.super_admin, Role.admin})(lambda: True)() != True:
171
+ if login_required(roles={Role.super_admin, Role.admin, Role.agent})(lambda: True)() != True:
172
172
  return False
173
173
  return True
174
174
 
@@ -211,6 +211,9 @@ def get_config_form():
211
211
  libs = [("python", _("lib.telegram.python")), ("tgo", _("lib.telegram.go")), ("orig", _("lib.telegram.orignal")), ("erlang", _("lib.telegram.erlang"))]
212
212
  field = wtf.fields.SelectField(_("config.telegram_lib.label"), choices=libs, description=_(
213
213
  "config.telegram_lib.description"), default=hconfig(ConfigEnum.telegram_lib))
214
+ elif c.key == ConfigEnum.mux_protocol:
215
+ choices = [("smux", 'smux'), ("yamux", "yamux"), ("h2mux", "h2mux")]
216
+ field = wtf.fields.SelectField(_(f"config.{c.key}.label"), choices=choices, description=_(f"config.{c.key}.description"))
214
217
  elif c.key == ConfigEnum.warp_sites:
215
218
  validators = [wtf.validators.Length(max=2048)]
216
219
  render_kw = {'class': "ltr", 'maxlength': 2048}
@@ -265,9 +268,14 @@ def get_config_form():
265
268
  render_kw['required'] = ""
266
269
  else:
267
270
  validators.append(wtf.validators.Regexp("^(\d+)(,\d+)*$|^$", re.IGNORECASE, _("config.Invalid port")))
268
-
269
271
  # validators.append(wtf.validators.Regexp("^(\d+)(,\d+)*$",re.IGNORECASE,_("config.port is required")))
270
272
 
273
+ # tls tricks validations
274
+ if c.key in [ConfigEnum.tls_fragment_size, ConfigEnum.tls_fragment_sleep, ConfigEnum.tls_padding_length]:
275
+ validators.append(wtf.validators.Regexp("^\d+-\d+$", re.IGNORECASE, _("config.Invalid! The pattern is number-number")+f' {c.key}'))
276
+ # mux and hysteria validations
277
+ if c.key in [ConfigEnum.hysteria_up_mbps, ConfigEnum.hysteria_down_mbps, ConfigEnum.mux_max_connections, ConfigEnum.mux_min_streams, ConfigEnum.mux_max_streams, ConfigEnum.mux_brutal_down_mbps, ConfigEnum.mux_brutal_up_mbps]:
278
+ validators.append(wtf.validators.Regexp("^\d+$", re.IGNORECASE, _("config.Invalid! it should be a number only")+f' {c.key}'))
271
279
  for val in validators:
272
280
  if hasattr(val, "regex"):
273
281
  render_kw['pattern'] = val.regex.pattern
@@ -121,7 +121,7 @@ class UserAdmin(AdminLTEModelView):
121
121
  # print("model.telegram_id",model.telegram_id)
122
122
  extra = ""
123
123
  if hconfig(ConfigEnum.telegram_bot_token):
124
- if model.telegram_id:
124
+ if model.telegram_id and model.telegram_id != '0':
125
125
  extra = f'<button class="btn hbtn bg-h-blue btn-xs " onclick="show_send_message({model.id})" ><i class="fa-solid fa-paper-plane"></i></button> '
126
126
  else:
127
127
  extra = f'<button class="btn hbtn bg-h-grey btn-xs disabled"><i class="fa-solid fa-paper-plane"></i></button> '
@@ -249,9 +249,10 @@ class UserAdmin(AdminLTEModelView):
249
249
  flash(('<div id="show-modal-donation"></div>'), ' d-none')
250
250
  if not re.match("^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$", model.uuid):
251
251
  raise ValidationError('Invalid UUID e.g.,' + str(uuid.uuid4()))
252
-
253
252
  if form.reset_usage.data:
254
253
  model.current_usage_GB = 0
254
+ # if model.telegram_id and model.telegram_id != '0' and not re.match(r"^[1-9]\d*$", model.telegram_id):
255
+ # raise ValidationError('Invalid Telegram ID')
255
256
  # if form.disable_user.data:
256
257
  # model.mode=UserMode.disable
257
258
  if form.reset_days.data:
@@ -155,7 +155,7 @@ def init_app(app):
155
155
  g.is_admin = hiddify.is_admin_role(account.role) # type: ignore
156
156
  login_user(account, force=True)
157
157
  # print("loggining in")
158
- if next_url is not None and g.user_agent['is_browser']:
158
+ if next_url is not None and g.user_agent['is_browser'] and ".webmanifest" not in request.path:
159
159
  return redirect(next_url)
160
160
 
161
161
  @app.url_value_preprocessor
@@ -56,8 +56,7 @@ def init_app(app):
56
56
 
57
57
 
58
58
  class TGBotResource(Resource):
59
- @hiddify.api_v1_auth
60
- def post(self, admin_uuid):
59
+ def post(self, admin_uuid=None):
61
60
  try:
62
61
  if request.headers.get('content-type') == 'application/json':
63
62
  json_string = request.get_data().decode('utf-8')
@@ -1,46 +1,27 @@
1
- from flask import request
1
+ from typing import List
2
+ from flask import g, request
2
3
  from apiflask import abort
3
4
  from flask_restful import Resource
4
5
  # from flask_simplelogin import login_required
5
6
  import datetime
6
- from hiddifypanel.panel.database import db
7
+
8
+ from hiddifypanel import hutils
7
9
  from hiddifypanel.models import *
8
- from hiddifypanel.panel.auth import login_required
9
- from hiddifypanel.panel import hiddify, usage
10
10
  from .tgbot import bot
11
11
 
12
12
 
13
13
  class SendMsgResource(Resource):
14
14
  # decorators = [login_required({Role.super_admin})]
15
- @hiddify.api_v1_auth
16
- def post(self, admin_uuid):
15
+ def post(self, admin_uuid=None):
17
16
 
18
17
  if not hconfig(ConfigEnum.telegram_bot_token) or not bot:
19
18
  abort(400, 'invalid request')
20
19
 
21
20
  msg = request.json
22
- users = User.query.filter(User.telegram_id != None)
23
- id = msg['id']
24
- if type(id) == int or id.isnumeric():
25
- users = [users.filter(User.id == int(msg['id'])).first() or abort(403)]
26
- elif id == 'all':
27
- users = users.all()
28
- else:
29
- users = users.all()
30
- if id == 'expired':
31
- users = [u for u in users if not u.is_active]
32
- elif id == 'active':
33
- users = [u for u in users if u.is_active]
34
- elif id == 'offline 1h':
35
- h1 = datetime.datetime.now()-datetime.timedelta(hours=1)
36
- users = [u for u in users if u.is_active and u.last_online < h1]
37
- elif id == 'offline 1d':
38
- d1 = datetime.datetime.now()-datetime.timedelta(hours=24)
39
- users = [u for u in users if u.is_active and u.last_online < d1]
40
-
41
- elif id == 'offline 1w':
42
- d7 = datetime.datetime.now()-datetime.timedelta(days=7)
43
- users = [u for u in users if u.is_active and u.last_online < d7]
21
+ if not msg or not msg.get('id') or not msg.get('text'):
22
+ abort(400, 'invalid request')
23
+
24
+ users = self.get_users_by_identifier(msg['id'])
44
25
 
45
26
  res = {}
46
27
  for user in users:
@@ -51,9 +32,36 @@ class SendMsgResource(Resource):
51
32
  print('sending to ', user)
52
33
  bot.send_message(user.telegram_id, txt, reply_markup=keyboard)
53
34
  except Exception as e:
54
- import traceback
55
35
  res[user.uuid] = {'name': user.name, 'error': f'{e}'}
56
36
  if len(res) == 0:
57
37
  return {'msg': "success"}
58
38
  else:
59
39
  return {'msg': 'error', 'res': res}
40
+
41
+ def get_users_by_identifier(self, identifier: str) -> List[User]:
42
+ '''Returns all users that match the identifier for sending a message to them'''
43
+ # when we are here we must have g.account but ...
44
+ if not hasattr(g, 'account'):
45
+ return []
46
+ query = User.query.filter(User.added_by.in_(g.account.recursive_sub_admins_ids()))
47
+ query = query.filter(User.telegram_id != None, User.telegram_id != 0)
48
+
49
+ if hutils.utils.is_int(identifier):
50
+ return [query.filter(User.id == int(identifier)).first() or abort(404, 'The user not found')] # type: ignore
51
+ elif identifier == 'all':
52
+ return query.all()
53
+ elif identifier == 'expired':
54
+ return [u for u in query.all() if not u.is_active]
55
+ elif identifier == 'active':
56
+ return [u for u in query.all() if u.is_active]
57
+ elif identifier == 'offline 1h':
58
+ h1 = datetime.datetime.now()-datetime.timedelta(hours=1)
59
+ return [u for u in query.all() if u.is_active and u.last_online < h1]
60
+ elif identifier == 'offline 1d':
61
+ d1 = datetime.datetime.now()-datetime.timedelta(hours=24)
62
+ return [u for u in query.all() if u.is_active and u.last_online < d1]
63
+ elif identifier == 'offline 1w':
64
+ d7 = datetime.datetime.now()-datetime.timedelta(days=7)
65
+ return [u for u in query.all() if u.is_active and u.last_online < d7]
66
+ else:
67
+ return []
@@ -46,14 +46,17 @@ class AllConfigsAPI(MethodView):
46
46
  items.append(
47
47
  create_item(
48
48
  "Auto", "ALL", "ALL", "", "", "",
49
- f"{base_url}sub/?asn={c['asn']}")
49
+ # f"{base_url}sub/?asn={c['asn']}"
50
+ f"{base_url}auto/?asn={c['asn']}"
51
+ )
50
52
  )
51
53
 
52
54
  # Add Full Singbox
53
55
  items.append(
54
56
  create_item(
55
57
  "Full Singbox", "ALL", "ALL", "", "", "",
56
- f"{base_url}full-singbox.json?asn={c['asn']}"
58
+ # f"{base_url}full-singbox.json?asn={c['asn']}"
59
+ f"{base_url}singbox/?asn={c['asn']}"
57
60
  )
58
61
  )
59
62
 
@@ -61,7 +64,8 @@ class AllConfigsAPI(MethodView):
61
64
  items.append(
62
65
  create_item(
63
66
  "Clash Meta", "ALL", "ALL", "", "", "",
64
- f"clashmeta://install-config?url={base_url}clash/meta/all.yml&name=mnormal_{c['db_domain'].alias or c['db_domain'].domain}-{c['asn']}-{c['mode']}&asn={c['asn']}&mode={c['mode']}"
67
+ # f"clashmeta://install-config?url={base_url}clash/meta/all.yml&name=mnormal_{c['db_domain'].alias or c['db_domain'].domain}-{c['asn']}-{c['mode']}&asn={c['asn']}&mode={c['mode']}"
68
+ f"clash://install-config?url={base_url}clashmeta/?asn={c['asn']}"
65
69
  )
66
70
  )
67
71
 
@@ -69,7 +73,8 @@ class AllConfigsAPI(MethodView):
69
73
  items.append(
70
74
  create_item(
71
75
  "Clash", "ALL", "Except VLess", "", "", "",
72
- f"clash://install-config?url={base_url}clash/all.yml&name=new_normal_{c['db_domain'].alias or c['db_domain'].domain}-{c['asn']}-{c['mode']}&asn={c['asn']}&mode={c['mode']}"
76
+ # f"clash://install-config?url={base_url}clash/all.yml&name=new_normal_{c['db_domain'].alias or c['db_domain'].domain}-{c['asn']}-{c['mode']}&asn={c['asn']}&mode={c['mode']}"
77
+ f"clash://install-config?url={base_url}clash/?asn={c['asn']}"
73
78
  )
74
79
  )
75
80
 
@@ -78,7 +83,8 @@ class AllConfigsAPI(MethodView):
78
83
  items.append(
79
84
  create_item(
80
85
  "Singbox: SSH", "SSH", "SHH", "", "", "",
81
- f"{base_url}singbox.json?name={c['db_domain'].alias or c['db_domain'].domain}-{c['asn']}&asn={c['asn']}&mode={c['mode']}"
86
+ # f"{base_url}singbox.json?name={c['db_domain'].alias or c['db_domain'].domain}-{c['asn']}&asn={c['asn']}&mode={c['mode']}"
87
+ f"{base_url}singbox-ssh/?asn={c['asn']}"
82
88
  )
83
89
  )
84
90
 
@@ -86,7 +92,8 @@ class AllConfigsAPI(MethodView):
86
92
  items.append(
87
93
  create_item(
88
94
  "Subscription link", "ALL", "ALL", "", "", "",
89
- f"{base_url}all.txt?name={c['db_domain'].alias or c['db_domain'].domain}-{c['asn']}&asn={c['asn']}&mode={c['mode']}"
95
+ # f"{base_url}all.txt?name={c['db_domain'].alias or c['db_domain'].domain}-{c['asn']}&asn={c['asn']}&mode={c['mode']}"
96
+ f"{base_url}sub/?asn={c['asn']}"
90
97
  )
91
98
  )
92
99
 
@@ -94,7 +101,8 @@ class AllConfigsAPI(MethodView):
94
101
  items.append(
95
102
  create_item(
96
103
  "Subscription link b64", "ALL", "ALL", "", "", "",
97
- f"{base_url}all.txt?name=new_link_{c['db_domain'].alias or c['db_domain'].domain}-{c['asn']}-{c['mode']}&asn={c['asn']}&mode={c['mode']}&base64=True"
104
+ # f"{base_url}all.txt?name=new_link_{c['db_domain'].alias or c['db_domain'].domain}-{c['asn']}-{c['mode']}&asn={c['asn']}&mode={c['mode']}&base64=True"
105
+ f"{base_url}sub64/?asn={c['asn']}"
98
106
  )
99
107
  )
100
108
 
@@ -1,3 +1,4 @@
1
+ from hiddifypanel.panel import hiddify
1
2
  from telebot import types
2
3
  from flask_babelex import gettext as _
3
4
  from flask import current_app as app
@@ -52,7 +53,7 @@ def get_usage_msg(uuid, domain=None):
52
53
  reset_day = user_data['reset_day']
53
54
 
54
55
  domain = domain or get_panel_domains()[0]
55
- user_link = hiddify.get_account_panel_link(user,domain.domain)
56
+ user_link = hiddify.get_account_panel_link(user, domain.domain)
56
57
  msg = f"""{_('<a href="%(user_link)s"> %(user)s</a>',user_link=user_link ,user=user.name if user.name != "default" else "")}\n\n"""
57
58
 
58
59
  msg += f"""{_('user.home.usage.title')} {round(user.current_usage_GB, 3)}GB <b>{_('user.home.usage.from')}</b> {user.usage_limit_GB}GB {_('user.home.usage.monthly') if user.monthly else ''}\n"""
@@ -100,7 +100,6 @@ def init_app(app: APIFlask):
100
100
  @app.url_defaults
101
101
  def add_proxy_path_user(endpoint, values):
102
102
  if 'proxy_path' not in values:
103
-
104
103
  if hasattr(g, 'account') and isinstance(g.account, AdminUser):
105
104
  values['proxy_path'] = hconfig(ConfigEnum.proxy_path_admin)
106
105
  # elif 'static' in endpoint:
@@ -114,8 +113,8 @@ def init_app(app: APIFlask):
114
113
 
115
114
  if hiddify.is_api_v1_call(endpoint=endpoint) and 'admin_uuid' not in values:
116
115
  values['admin_uuid'] = AdminUser.get_super_admin_uuid()
117
- # if 'secret_uuid' not in values:
118
- # values['secret_uuid'] = AdminUser.get_super_admin_uuid()
116
+ # if 'secret_uuid' not in values and g.account and ".webmanifest" in request.path:
117
+ # values['secret_uuid'] = g.account.uuid
119
118
 
120
119
  @app.route("/<proxy_path>/videos/<file>")
121
120
  @app.doc(hide=True)
@@ -1,6 +1,6 @@
1
1
  from flask_classful import FlaskView, route
2
2
  from hiddifypanel.panel.auth import login_required, current_account, login_user, logout_user, login_by_uuid
3
- from flask import redirect, request, g, url_for, render_template, flash
3
+ from flask import redirect, request, g, url_for, render_template, flash, jsonify
4
4
  from flask import current_app as app
5
5
  from flask_babelex import lazy_gettext as _
6
6
  from apiflask import abort
@@ -118,8 +118,7 @@ class LoginView(FlaskView):
118
118
 
119
119
  # return redirect(f"/{proxy_path}/{path}")
120
120
 
121
- @route('/manifest.webmanifest')
122
- @login_required()
121
+ @route('/<secret_uuid>/manifest.webmanifest')
123
122
  def create_pwa_manifest(self):
124
123
  domain = request.host
125
124
  name = (domain if hiddify.is_admin_panel_call() else g.account.name)
@@ -130,7 +129,7 @@ class LoginView(FlaskView):
130
129
  "background_color": "#1a1b21",
131
130
  "display": "standalone",
132
131
  "scope": f"/",
133
- "start_url": hiddify.get_account_panel_link(g.account, domain) +"?pwa=true",
132
+ "start_url": hiddify.get_account_panel_link(g.account, domain) + "?pwa=true",
134
133
  "description": "Hiddify, for a free Internet",
135
134
  "orientation": "any",
136
135
  "icons": [
@@ -1,4 +1,5 @@
1
1
  import glob
2
+ import uuid
2
3
  import user_agents
3
4
  import json
4
5
  import subprocess
@@ -876,7 +877,7 @@ def get_direct_host_or_ip(prefer_version: int):
876
877
  else:
877
878
  direct = hutils.ip.get_ip(prefer_version)
878
879
  if not direct:
879
- direct = hutils.ip.get_ip(socket.AF_INET if prefer_version == socket.AF_INET6 else socket.AF_INET6)
880
+ direct = hutils.ip.get_ip(4 if prefer_version == 6 else 6)
880
881
  return direct
881
882
 
882
883
 
@@ -148,6 +148,18 @@ def init_db():
148
148
  # add_config_if_not_exist(ConfigEnum.hysteria_enable, True)
149
149
  # add_config_if_not_exist(ConfigEnum.hysteria_port, random.randint(5000, 20000))
150
150
 
151
+ def _v65():
152
+ add_config_if_not_exist(ConfigEnum.mux_enable, False)
153
+ add_config_if_not_exist(ConfigEnum.mux_protocol, 'smux')
154
+ add_config_if_not_exist(ConfigEnum.mux_max_connections, '4')
155
+ add_config_if_not_exist(ConfigEnum.mux_min_streams, '4')
156
+ add_config_if_not_exist(ConfigEnum.mux_max_streams, '0')
157
+ add_config_if_not_exist(ConfigEnum.mux_padding_enable, False)
158
+ add_config_if_not_exist(ConfigEnum.mux_brutal_enable, True)
159
+ add_config_if_not_exist(ConfigEnum.mux_brutal_up_mbps, '100')
160
+ add_config_if_not_exist(ConfigEnum.mux_brutal_down_mbps, '100')
161
+
162
+
151
163
  def _v64():
152
164
  set_hconfig(ConfigEnum.ssh_server_redis_url, "unix:///opt/hiddify-manager/other/redis/run.sock?db=1")
153
165
 
@@ -720,7 +720,7 @@ def add_singbox_ssh(all_base, proxy):
720
720
 
721
721
  def make_full_singbox_config(domains, **kwargs):
722
722
  ua = hiddify.get_user_agent()
723
- base_config = json.loads(render_template('base_singbox_config.json'))
723
+ base_config = json.loads(render_template('base_singbox_config.json.j2'))
724
724
  allphttp = [p for p in request.args.get("phttp", "").split(',') if p]
725
725
  allptls = [p for p in request.args.get("ptls", "").split(',') if p]
726
726
 
@@ -47,7 +47,7 @@
47
47
  "format": "binary",
48
48
  "url": "https:\/\/raw.githubusercontent.com\/Chocolate4U\/Iran-sing-box-rules\/rule-set\/geoip-ir.srs",
49
49
  "download_detour": "bypass"
50
- },
50
+ }
51
51
  {%endif%}
52
52
  {# {
53
53
  "tag": "geosite-category-ads-all",
@@ -57,7 +57,7 @@
57
57
  "download_detour": {% if hconfig(ConfigEnum.country)=='cn' %}"Select" {%else%}"bypass"{%endif%}
58
58
  } #}
59
59
  ],
60
- {%endif%}
60
+ {%endif%}
61
61
  "rules": [
62
62
  {
63
63
  "outbound": "dns-out",
@@ -71,7 +71,7 @@
71
71
  ],
72
72
  "outbound": "dns-out"
73
73
  },
74
- {%if hconfig(ConfigEnum.country)in ["ir","cn","ru"]%}
74
+ {%if hconfig(ConfigEnum.country) in ["ir","cn","ru"]%}
75
75
  {
76
76
  "domain_suffix": [
77
77
  "{{hconfig(ConfigEnum.country)}}"
@@ -89,16 +89,22 @@
89
89
  "outbound": "block"
90
90
  }, #}
91
91
  {%if hconfig(ConfigEnum.country) in ["ir","cn","ru"]%}
92
+ {% if V1_7 %}
92
93
  {
93
- {% if V1_7 %}
94
94
  "geoip": ["{{hconfig(ConfigEnum.country)}}"],
95
- {%else%}
95
+ "outbound": "bypass"
96
+ },
97
+ {%else%}
98
+ {
96
99
  "rule_set": "geoip-{{hconfig(ConfigEnum.country)}}",
100
+ "outbound": "bypass"
101
+ },
102
+ {
97
103
  "rule_set": "geosite-{{hconfig(ConfigEnum.country)}}",
98
- {%endif%}
99
104
  "outbound": "bypass"
100
105
  },
101
106
  {%endif%}
107
+ {%endif%}
102
108
  {
103
109
  "protocol": "quic",
104
110
  "port": [
@@ -9,9 +9,11 @@
9
9
  <link rel="stylesheet" href="node_modules/smartbanner.js/dist/smartbanner.min.css">
10
10
  <link href="https://cdn.jsdelivr.net/gh/rastikerdar/vazirmatn@v33.003/Vazirmatn-font-face.css" rel="stylesheet" type="text/css" />
11
11
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
12
-
12
+ <link rel="icon" type="image/x-icon" href="{{ static_url_for( filename='images/favicon.ico')}}">
13
+ <link rel="manifest" href="{{ url_for('common_bp.LoginView:create_pwa_manifest',secret_uuid=g.account.uuid or '')}}">
14
+
13
15
  <title>Hiddify | Panel</title>
14
- <script type="module" crossorigin src="../static/new/assets/index-bd9ba5e9.js"></script>
16
+ <script type="module" crossorigin src="../static/new/assets/index-2cd90979.js"></script>
15
17
  <link rel="stylesheet" href="../static/new/assets/index-d9bbf489.css">
16
18
  </head>
17
19
  <body>
@@ -24,7 +24,7 @@ class UserView(FlaskView):
24
24
  ua = request.user_agent.string
25
25
  print(ua)
26
26
  print(hiddify.get_user_agent())
27
- return ua, 400
27
+ return ua
28
28
 
29
29
  def index(self):
30
30
  return self.auto_sub()
@@ -34,12 +34,65 @@ class UserView(FlaskView):
34
34
  return self.new()
35
35
  return self.get_proper_config() or self.all_configs(base64=True)
36
36
 
37
- @route('/sub')
38
- @route('/sub/')
37
+ # former /sub/ or /sub (it was auto actually but we named it as /sub/)
38
+ # TODO: @hiddify: check this out
39
+ @route('/auto/')
40
+ @route('/auto')
39
41
  @login_required(roles={Role.user})
40
42
  def force_sub(self):
41
43
  return self.get_proper_config() or self.all_configs(base64=False)
42
44
 
45
+ # region new endpoints
46
+ @route("/sub/")
47
+ @route("/sub")
48
+ @login_required(roles={Role.user})
49
+ def sub(self):
50
+ return self.all_configs(base64=False)
51
+
52
+ @route("/sub64/")
53
+ @route("/sub64")
54
+ @login_required(roles={Role.user})
55
+ def sub64(self):
56
+ return self.all_configs(base64=True)
57
+
58
+ @route("/singbox/")
59
+ @route("/singbox")
60
+ @login_required(roles={Role.user})
61
+ def singbox_full(self):
62
+ return self.full_singbox()
63
+
64
+ @route("/singbox-ssh/")
65
+ @route("/singbox-ssh")
66
+ @login_required(roles={Role.user})
67
+ def singbox_ssh(self):
68
+ return self.singbox()
69
+
70
+ @route("/clash/")
71
+ @route("/clash")
72
+ @login_required(roles={Role.user})
73
+ def clash(self):
74
+ return self.clash_config(meta_or_normal="normal")
75
+
76
+ @route("/clashmeta/")
77
+ @route("/clashmeta")
78
+ @login_required(roles={Role.user})
79
+ def clashmeta(self):
80
+ return self.clash_config(meta_or_normal="meta")
81
+ # endregion
82
+
83
+ @ route('/new/')
84
+ @ route('/new')
85
+ @login_required(roles={Role.user})
86
+ def new(self):
87
+ conf = self.get_proper_config()
88
+ if conf:
89
+ return conf
90
+
91
+ c = get_common_data(g.account.uuid, mode="new")
92
+ user_agent = user_agents.parse(request.user_agent.string)
93
+ # return render_template('home/multi.html', **c, ua=user_agent)
94
+ return render_template('new.html', **c, ua=user_agent)
95
+
43
96
  def get_proper_config(self):
44
97
  if g.user_agent['is_browser']:
45
98
  return None
@@ -59,20 +112,6 @@ class UserView(FlaskView):
59
112
  if re.match('^(Hiddify|FoXray|Fair|v2rayNG|SagerNet|Shadowrocket|V2Box|Loon|Liberty)', ua, re.IGNORECASE):
60
113
  return self.all_configs(base64=True)
61
114
 
62
- @ route('/new/')
63
- @ route('/new')
64
- @login_required(roles={Role.user})
65
- # @ route('/')
66
- def new(self):
67
- conf = self.get_proper_config()
68
- if conf:
69
- return conf
70
-
71
- c = get_common_data(g.account.uuid, mode="new")
72
- user_agent = user_agents.parse(request.user_agent.string)
73
- # return render_template('home/multi.html', **c, ua=user_agent)
74
- return render_template('new.html', **c, ua=user_agent)
75
-
76
115
  @ route('/clash/<meta_or_normal>/proxies.yml')
77
116
  @ route('/clash/proxies.yml')
78
117
  @login_required(roles={Role.user})
@@ -185,8 +224,8 @@ class UserView(FlaskView):
185
224
  resp = do_base_64(resp)
186
225
  return add_headers(resp, c)
187
226
 
188
- @login_required(roles={Role.user})
189
227
  @ route("/offline.html")
228
+ @login_required(roles={Role.user})
190
229
  def offline():
191
230
  return f"Not Connected <a href='{hiddify.get_account_panel_link(g.account, request.host)}'>click for reload</a>"
192
231
 
@@ -307,7 +346,7 @@ def get_common_data(user_uuid, mode, no_domain=False, filter_domain=None):
307
346
  'ConfigEnum': ConfigEnum,
308
347
  'link_maker': link_maker,
309
348
  'domains': domains,
310
- "bot": g.bot,
349
+ "bot": g.get('bot', None),
311
350
  "db_domain": db_domain,
312
351
  "telegram_enable": hconfig(ConfigEnum.telegram_enable) and any([d for d in domains if d.mode in [DomainType.direct, DomainType.relay, DomainType.old_xtls_direct]]),
313
352
  "ip": user_ip,
@@ -315,7 +354,7 @@ def get_common_data(user_uuid, mode, no_domain=False, filter_domain=None):
315
354
  "asn": asn,
316
355
  "country": auto_ip_selector.get_country(user_ip),
317
356
  'has_auto_cdn': has_auto_cdn,
318
- 'profile_url': hiddify.get_account_panel_link(g.account, domain)
357
+ 'profile_url': hiddify.get_account_panel_link(g.account if g.get('account') else user, domain)
319
358
  }
320
359
 
321
360