hiddifypanel 10.20.4__py3-none-any.whl → 10.30.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 (83) hide show
  1. hiddifypanel/VERSION +1 -1
  2. hiddifypanel/VERSION.py +2 -2
  3. hiddifypanel/base.py +17 -8
  4. hiddifypanel/cache.py +2 -51
  5. hiddifypanel/drivers/wireguard_api.py +24 -5
  6. hiddifypanel/hutils/convert.py +1 -1
  7. hiddifypanel/hutils/flask.py +28 -2
  8. hiddifypanel/hutils/importer/xui.py +6 -7
  9. hiddifypanel/hutils/network/__init__.py +1 -0
  10. hiddifypanel/hutils/network/cf_api.py +84 -0
  11. hiddifypanel/hutils/network/net.py +26 -49
  12. hiddifypanel/hutils/node/child.py +25 -7
  13. hiddifypanel/hutils/node/parent.py +7 -7
  14. hiddifypanel/hutils/node/shared.py +19 -6
  15. hiddifypanel/hutils/proxy/clash.py +1 -1
  16. hiddifypanel/hutils/proxy/shared.py +1 -1
  17. hiddifypanel/hutils/proxy/singbox.py +2 -3
  18. hiddifypanel/hutils/proxy/xray.py +12 -10
  19. hiddifypanel/hutils/proxy/xrayjson.py +26 -49
  20. hiddifypanel/hutils/utils.py +47 -3
  21. hiddifypanel/models/__init__.py +1 -1
  22. hiddifypanel/models/admin.py +9 -2
  23. hiddifypanel/models/base_account.py +3 -1
  24. hiddifypanel/models/config.py +5 -7
  25. hiddifypanel/models/config_enum.py +18 -6
  26. hiddifypanel/models/domain.py +82 -118
  27. hiddifypanel/models/user.py +44 -24
  28. hiddifypanel/panel/admin/Actions.py +6 -11
  29. hiddifypanel/panel/admin/AdminstratorAdmin.py +3 -9
  30. hiddifypanel/panel/admin/Backup.py +5 -8
  31. hiddifypanel/panel/admin/Dashboard.py +3 -4
  32. hiddifypanel/panel/admin/DomainAdmin.py +20 -15
  33. hiddifypanel/panel/admin/ProxyAdmin.py +3 -10
  34. hiddifypanel/panel/admin/QuickSetup.py +1 -1
  35. hiddifypanel/panel/admin/SettingAdmin.py +7 -5
  36. hiddifypanel/panel/admin/Terminal.py +0 -1
  37. hiddifypanel/panel/admin/UserAdmin.py +4 -3
  38. hiddifypanel/panel/cli.py +36 -23
  39. hiddifypanel/panel/commercial/ProxyDetailsAdmin.py +2 -4
  40. hiddifypanel/panel/commercial/restapi/v1/tgbot.py +7 -4
  41. hiddifypanel/panel/commercial/restapi/v2/admin/__init__.py +17 -13
  42. hiddifypanel/panel/commercial/restapi/v2/admin/admin_info_api.py +4 -3
  43. hiddifypanel/panel/commercial/restapi/v2/admin/admin_user_api.py +28 -10
  44. hiddifypanel/panel/commercial/restapi/v2/admin/admin_users_api.py +2 -19
  45. hiddifypanel/panel/commercial/restapi/v2/admin/schema.py +27 -4
  46. hiddifypanel/panel/commercial/restapi/v2/admin/user_api.py +28 -9
  47. hiddifypanel/panel/commercial/restapi/v2/admin/users_api.py +1 -21
  48. hiddifypanel/panel/commercial/restapi/v2/parent/register_api.py +1 -1
  49. hiddifypanel/panel/commercial/restapi/v2/parent/schema.py +8 -4
  50. hiddifypanel/panel/commercial/restapi/v2/parent/sync_api.py +19 -3
  51. hiddifypanel/panel/commercial/restapi/v2/user/configs_api.py +48 -42
  52. hiddifypanel/panel/commercial/telegrambot/Usage.py +1 -1
  53. hiddifypanel/panel/commercial/telegrambot/admin.py +1 -1
  54. hiddifypanel/panel/commercial/telegrambot/information.py +1 -1
  55. hiddifypanel/panel/common.py +5 -11
  56. hiddifypanel/panel/hiddify.py +9 -20
  57. hiddifypanel/panel/init_db.py +31 -13
  58. hiddifypanel/panel/usage.py +38 -9
  59. hiddifypanel/panel/user/user.py +52 -32
  60. hiddifypanel/templates/admin-layout.html +2 -2
  61. hiddifypanel/templates/fake.html +0 -298
  62. hiddifypanel/translations/en/LC_MESSAGES/messages.mo +0 -0
  63. hiddifypanel/translations/en/LC_MESSAGES/messages.po +80 -25
  64. hiddifypanel/translations/fa/LC_MESSAGES/messages.mo +0 -0
  65. hiddifypanel/translations/fa/LC_MESSAGES/messages.po +74 -20
  66. hiddifypanel/translations/pt/LC_MESSAGES/messages.mo +0 -0
  67. hiddifypanel/translations/pt/LC_MESSAGES/messages.po +60 -6
  68. hiddifypanel/translations/ru/LC_MESSAGES/messages.mo +0 -0
  69. hiddifypanel/translations/ru/LC_MESSAGES/messages.po +158 -78
  70. hiddifypanel/translations/zh/LC_MESSAGES/messages.mo +0 -0
  71. hiddifypanel/translations/zh/LC_MESSAGES/messages.po +60 -6
  72. hiddifypanel/translations.i18n/en.json +62 -22
  73. hiddifypanel/translations.i18n/fa.json +57 -17
  74. hiddifypanel/translations.i18n/pt.json +43 -3
  75. hiddifypanel/translations.i18n/ru.json +112 -72
  76. hiddifypanel/translations.i18n/zh.json +43 -3
  77. {hiddifypanel-10.20.4.dist-info → hiddifypanel-10.30.0.dev0.dist-info}/METADATA +2 -1
  78. {hiddifypanel-10.20.4.dist-info → hiddifypanel-10.30.0.dev0.dist-info}/RECORD +82 -82
  79. {hiddifypanel-10.20.4.dist-info → hiddifypanel-10.30.0.dev0.dist-info}/WHEEL +1 -1
  80. hiddifypanel/panel/cf_api.py +0 -37
  81. {hiddifypanel-10.20.4.dist-info → hiddifypanel-10.30.0.dev0.dist-info}/LICENSE.md +0 -0
  82. {hiddifypanel-10.20.4.dist-info → hiddifypanel-10.30.0.dev0.dist-info}/entry_points.txt +0 -0
  83. {hiddifypanel-10.20.4.dist-info → hiddifypanel-10.30.0.dev0.dist-info}/top_level.txt +0 -0
@@ -50,60 +50,66 @@ class AllConfigsAPI(MethodView):
50
50
  )
51
51
  )
52
52
 
53
- # Add Full Singbox
54
- items.append(
55
- create_item(
56
- "Full Singbox", "ALL", "", "", "", "",
57
- # f"{base_url}full-singbox.json?asn={c['asn']}"
58
- f"{base_url}singbox/?asn={c['asn']}#{config_name}"
53
+ if hconfig(ConfigEnum.sub_full_singbox_enable):
54
+ # Add Full Singbox
55
+ items.append(
56
+ create_item(
57
+ "Full Singbox", "ALL", "", "", "", "",
58
+ # f"{base_url}full-singbox.json?asn={c['asn']}"
59
+ f"{base_url}singbox/?asn={c['asn']}#{config_name}"
60
+ )
59
61
  )
60
- )
61
62
 
62
- # Add Full Xray
63
- items.append(
64
- create_item(
65
- "Full Xray", "ALL", "", "", "", "",
66
- f"{base_url}xray/#{config_name}"
63
+ if hconfig(ConfigEnum.sub_full_xray_json_enable):
64
+ # Add Full Xray
65
+ items.append(
66
+ create_item(
67
+ "Full Xray", "ALL", "", "", "", "",
68
+ f"{base_url}xray/#{config_name}"
69
+ )
67
70
  )
68
- )
69
71
 
70
- # Add Subscription link
71
- items.append(
72
- create_item(
73
- "Subscription link", "ALL", "", "", "", "",
74
- # f"{base_url}all.txt?name={c['db_domain'].alias or c['db_domain'].domain}-{c['asn']}&asn={c['asn']}&mode={c['mode']}"
75
- f"{base_url}sub/?asn={c['asn']}#{config_name}"
72
+ if hconfig(ConfigEnum.sub_full_links_enable):
73
+ # Add Subscription link
74
+ items.append(
75
+ create_item(
76
+ "Subscription link", "ALL", "", "", "", "",
77
+ # f"{base_url}all.txt?name={c['db_domain'].alias or c['db_domain'].domain}-{c['asn']}&asn={c['asn']}&mode={c['mode']}"
78
+ f"{base_url}sub/?asn={c['asn']}#{config_name}"
79
+ )
76
80
  )
77
- )
78
81
 
79
- # Add Subscription link base64
80
- items.append(
81
- create_item(
82
- "Subscription link b64", "ALL", "", "", "", "",
83
- # 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"
84
- f"{base_url}sub64/?asn={c['asn']}#{config_name}"
82
+ if hconfig(ConfigEnum.sub_full_links_b64_enable):
83
+ # Add Subscription link base64
84
+ items.append(
85
+ create_item(
86
+ "Subscription link b64", "ALL", "", "", "", "",
87
+ # 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"
88
+ f"{base_url}sub64/?asn={c['asn']}#{config_name}"
89
+ )
85
90
  )
86
- )
87
- # Add Clash Meta
88
- items.append(
89
- create_item(
90
- "Clash Meta", "ALL", "", "", "", "",
91
- # 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']}"
92
- f"clash://install-config?url={base_url}clashmeta/?asn={c['asn']}#{config_name}"
91
+ if hconfig(ConfigEnum.sub_full_clash_meta_enable):
92
+ # Add Clash Meta
93
+ items.append(
94
+ create_item(
95
+ "Clash Meta", "ALL", "", "", "", "",
96
+ # 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']}"
97
+ f"clash://install-config?url={base_url}clashmeta/?asn={c['asn']}#{config_name}"
98
+ )
93
99
  )
94
- )
95
100
 
96
- # Add Clash
97
- items.append(
98
- create_item(
99
- "Clash", "ALL", "Except VLess", "", "", "",
100
- # 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']}"
101
- f"clash://install-config?url={base_url}clash/?asn={c['asn']}#{config_name}"
101
+ if hconfig(ConfigEnum.sub_full_clash_enable):
102
+ # Add Clash
103
+ items.append(
104
+ create_item(
105
+ "Clash", "ALL", "Except VLess", "", "", "",
106
+ # 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']}"
107
+ f"clash://install-config?url={base_url}clash/?asn={c['asn']}#{config_name}"
108
+ )
102
109
  )
103
- )
104
110
 
105
111
  # Add Singbox: SSh
106
- if hconfig(ConfigEnum.ssh_server_enable):
112
+ if hconfig(ConfigEnum.sub_singbox_ssh_enable) and hconfig(ConfigEnum.ssh_server_enable):
107
113
  items.append(
108
114
  create_item(
109
115
  "Singbox: SSH", "SSH", "", "", "", "",
@@ -57,7 +57,7 @@ def get_usage_msg(uuid, domain=None):
57
57
  expire_rel = user_data['expire_rel']
58
58
  reset_day = user_data['reset_day']
59
59
 
60
- domain = domain or get_panel_domains()[0]
60
+ domain = domain or Domain.get_domains()[0]
61
61
  user_link = hiddify.get_account_panel_link(user, domain.domain)
62
62
  with force_locale(user.lang or hconfig(ConfigEnum.lang)):
63
63
  msg = f"""{_('<a href="%(user_link)s"> %(user)s</a>',user_link=user_link ,user=user.name if user.name != "default" else "")}\n\n"""
@@ -102,7 +102,7 @@ def admin_keyboard_domain(old_action):
102
102
  )
103
103
  return types.InlineKeyboardMarkup(keyboard=[
104
104
  [keyboard(d)]
105
- for d in get_panel_domains()
105
+ for d in Domain.get_domains()
106
106
  ]
107
107
  )
108
108
 
@@ -16,7 +16,7 @@ def prepare_me_info(user):
16
16
  "Your hiddify information is\n" +
17
17
  "UUID: {}\n".format(user.uuid) +
18
18
  "Last online date: {}\n".format(user.last_online) +
19
- "Expire time: {}\n".format(user.expiry_time) +
19
+ "Expire time: {}\n".format(user.remaining_days) +
20
20
  "Usage class: {}\n".format(user.mode)
21
21
  )
22
22
 
@@ -46,12 +46,8 @@ def init_app(app: APIFlask):
46
46
  return jsonify({
47
47
  'message': 'Not Found',
48
48
  }), 404
49
- # print(request.headers)
50
- last_version = hiddify.get_latest_release_version('hiddify-panel') # TODO: add dev update check
51
- if "T" in hiddifypanel.__version__:
52
- has_update = False
53
- else:
54
- has_update = "dev" not in hiddifypanel.__version__ and f'{last_version}' != hiddifypanel.__version__
49
+
50
+ has_update = hutils.utils.is_panel_outdated()
55
51
 
56
52
  if not request.accept_mimetypes.accept_html:
57
53
  if has_update:
@@ -68,6 +64,7 @@ def init_app(app: APIFlask):
68
64
 
69
65
  # Create github issue link
70
66
  issue_link = hutils.github_issue.generate_github_issue_link_for_500_error(e, trace)
67
+ last_version = hiddify.get_latest_release_version('hiddifypanel')
71
68
 
72
69
  return render_template('500.html', error=e, trace=trace, has_update=has_update, last_version=last_version, issue_link=issue_link), 500
73
70
 
@@ -83,11 +80,8 @@ def init_app(app: APIFlask):
83
80
  # Create github issue link
84
81
  issue_link = hutils.github_issue.generate_github_issue_link_for_500_error(e, trace)
85
82
 
86
- last_version = hiddify.get_latest_release_version('hiddify-panel') # TODO: add dev update check
87
- if "T" in hiddifypanel.__version__:
88
- has_update = False
89
- else:
90
- has_update = "dev" not in hiddifypanel.__version__ and f'{last_version}' != hiddifypanel.__version__
83
+ has_update = hutils.utils.is_panel_outdated()
84
+ last_version = hiddify.get_latest_release_version('hiddifypanel')
91
85
 
92
86
  return render_template('500.html', error=e, trace=trace, has_update=has_update, last_version=last_version, issue_link=issue_link), 500
93
87
 
@@ -82,7 +82,9 @@ def get_html_user_link(model: BaseAccount, domain: Domain):
82
82
  if "*" in d:
83
83
  d = d.replace("*", hutils.random.get_random_string(5, 15))
84
84
 
85
- link = f'{get_account_panel_link(model, d)}#{hutils.encode.unicode_slug(model.name)}'
85
+ # for showing child/node link (we send child_id to get_account_panel_link to get domain proxy path correctly)
86
+ d_child_id = domain.child_id
87
+ link = f'{get_account_panel_link(model, d,child_id=d_child_id)}#{hutils.encode.unicode_slug(model.name)}'
86
88
 
87
89
  text = domain.alias or domain.domain
88
90
  color_cls = 'info'
@@ -224,7 +226,7 @@ def set_db_from_json(json_data, override_child_unique_id=True, set_users=True, s
224
226
  if set_users and 'users' in json_data:
225
227
  User.bulk_register(json_data['users'], commit=False, remove=remove_users)
226
228
  if set_domains and 'domains' in json_data:
227
- bulk_register_domains(json_data['domains'], commit=False, remove=remove_domains)
229
+ Domain.bulk_register(json_data['domains'], commit=False, remove=remove_domains)
228
230
 
229
231
  if set_settings and 'hconfigs' in json_data:
230
232
  bulk_register_configs(json_data["hconfigs"], commit=True, override_unique_id=override_unique_id)
@@ -274,9 +276,11 @@ def get_account_panel_link(account: BaseAccount, host: str, is_https: bool = Tru
274
276
  link += str(host)
275
277
  proxy_path = hconfig(ConfigEnum.proxy_path_admin if is_admin else ConfigEnum.proxy_path_client, child_id)
276
278
  link += f'/{proxy_path}/'
277
- if child_id != 0:
278
- child = Child.by_id(child_id)
279
- link += f"{child.id}/"
279
+
280
+ # if child_id != 0:
281
+ # child = Child.by_id(child_id)
282
+ # link += f"{child.id}/"
283
+
280
284
  if basic_auth:
281
285
  link += "l"
282
286
  else:
@@ -332,18 +336,3 @@ def get_backup_child_unique_id(backupdata: dict) -> str:
332
336
  if len(backupdata.get('childs', [])) == 0:
333
337
  return "self"
334
338
  return backupdata['childs'][0]['unique_id']
335
-
336
-
337
- def is_hiddify_next_version(major_v: int = 0, minor_v: int = 0, patch_v: int = 0) -> bool:
338
- '''If the user agent version be equals or higher than parameters returns True'''
339
- if not g.user_agent.get('hiddify_version'):
340
- return False
341
- raw_v = g.user_agent['hiddify_version']
342
- raw_v_len = len(raw_v)
343
- u_major_v = raw_v[0] if raw_v_len > 0 else 0
344
- u_minor_v = raw_v[1] if raw_v_len > 1 else 0
345
- u_patch_v = raw_v[2] if raw_v_len > 2 else 0
346
-
347
- if u_major_v >= major_v and u_minor_v >= minor_v and u_patch_v >= patch_v:
348
- return True
349
- return False
@@ -17,6 +17,24 @@ from sqlalchemy import text
17
17
  MAX_DB_VERSION = 90
18
18
 
19
19
 
20
+ def _v88(child_id):
21
+ pass
22
+
23
+
24
+ def _v86(child_id):
25
+ set_hconfig(ConfigEnum.hiddifycli_enable, True)
26
+
27
+
28
+ def _v85(child_id):
29
+ set_hconfig(ConfigEnum.sub_full_singbox_enable, True)
30
+ set_hconfig(ConfigEnum.sub_singbox_ssh_enable, True)
31
+ set_hconfig(ConfigEnum.sub_full_xray_json_enable, True)
32
+ set_hconfig(ConfigEnum.sub_full_links_enable, True)
33
+ set_hconfig(ConfigEnum.sub_full_links_b64_enable, True)
34
+ set_hconfig(ConfigEnum.sub_full_clash_enable, True)
35
+ set_hconfig(ConfigEnum.sub_full_clash_meta_enable, True)
36
+
37
+
20
38
  def _v84(child_id):
21
39
  # the 2022-blake3-chacha20-poly1305 encryption method doesn't support multiuser config
22
40
  if hconfig(ConfigEnum.shadowsocks2022_method) == '2022-blake3-chacha20-poly1305':
@@ -246,7 +264,7 @@ def _v41():
246
264
 
247
265
  def _v38():
248
266
  add_config_if_not_exist(ConfigEnum.dns_server, "1.1.1.1")
249
- add_config_if_not_exist(ConfigEnum.warp_mode, "all" if hconfig(ConfigEnum.warp_enable) else "none")
267
+ add_config_if_not_exist(ConfigEnum.warp_mode, "all" if hconfig(ConfigEnum.warp_enable) else "disable")
250
268
  add_config_if_not_exist(ConfigEnum.warp_plus_code, '')
251
269
 
252
270
 
@@ -328,17 +346,17 @@ def _v19():
328
346
  add_config_if_not_exist(ConfigEnum.package_mode, "release")
329
347
 
330
348
 
331
- def _v17():
332
- for u in User.query.all():
333
- if u.expiry_time:
334
- if not u.package_days:
335
- if not u.last_reset_time:
336
- u.package_days = (u.expiry_time - datetime.date.today()).days
337
- u.start_date = datetime.date.today()
338
- else:
339
- u.package_days = (u.expiry_time - u.last_reset_time).days
340
- u.start_date = u.last_reset_time
341
- u.expiry_time = None
349
+ # def _v17():
350
+ # for u in User.query.all():
351
+ # if u.expiry_time:
352
+ # if not u.package_days:
353
+ # if not u.last_reset_time:
354
+ # u.package_days = (u.expiry_time - datetime.date.today()).days
355
+ # u.start_date = datetime.date.today()
356
+ # else:
357
+ # u.package_days = (u.expiry_time - u.last_reset_time).days
358
+ # u.start_date = u.last_reset_time
359
+ # u.expiry_time = None
342
360
 
343
361
 
344
362
  def _v1():
@@ -368,7 +386,7 @@ def _v1():
368
386
  BoolConfig(key=ConfigEnum.vmess_enable, value=True),
369
387
  BoolConfig(key=ConfigEnum.http_proxy_enable, value=True),
370
388
  StrConfig(key=ConfigEnum.shared_secret, value=str(uuid.uuid4())),
371
- BoolConfig(key=ConfigEnum.telegram_enable, value=True),
389
+ BoolConfig(key=ConfigEnum.telegram_enable, value=False),
372
390
  # StrConfig(key=ConfigEnum.telegram_secret,value=uuid.uuid4().hex),
373
391
  StrConfig(key=ConfigEnum.telegram_adtag, value=""),
374
392
  StrConfig(key=ConfigEnum.telegram_fakedomain, value=rnd_domains[1]),
@@ -27,6 +27,22 @@ def add_users_usage_uuid(uuids_bytes: Dict[str, Dict], child_id, sync=False):
27
27
  _add_users_usage(dbusers_bytes, child_id, sync) # type: ignore
28
28
 
29
29
 
30
+ def _reset_priodic_usage():
31
+ last_usage_check: int = hconfig(ConfigEnum.last_priodic_usage_check) or 0
32
+ import time
33
+ current_time = int(time.time())
34
+ if current_time - last_usage_check < 60 * 60 * 6:
35
+ return
36
+ # reset as soon as possible in the day
37
+ if datetime.datetime.now().hour > 5 and current_time - last_usage_check < 60 * 60 * 24:
38
+ return
39
+
40
+ for user in User.query.filter(User.mode != UserMode.no_reset).all():
41
+ if user.user_should_reset():
42
+ user.reset_usage(commit=False)
43
+ set_hconfig(ConfigEnum.last_priodic_usage_check, current_time)
44
+
45
+
30
46
  def _add_users_usage(users_usage_data: Dict[User, Dict], child_id, sync=False):
31
47
  '''
32
48
  sync: when enabled, it means we have received usages from the parent panel
@@ -42,39 +58,43 @@ def _add_users_usage(users_usage_data: Dict[User, Dict], child_id, sync=False):
42
58
  daily_usage[adm.id] = DailyUsage(admin_id=adm.id, child_id=child_id)
43
59
  db.session.add(daily_usage[adm.id])
44
60
  daily_usage[adm.id].online = User.query.filter(User.added_by == adm.id).filter(func.DATE(User.last_online) == today).count()
45
- # db.session.commit()
61
+
62
+ _reset_priodic_usage()
63
+
46
64
  userDetails = {p.user_id: p for p in UserDetail.query.filter(UserDetail.child_id == child_id).all()}
47
65
  for user, uinfo in users_usage_data.items():
48
66
  usage_bytes = uinfo['usage']
49
67
  devices = uinfo['devices']
50
- # user_active_before=user.is_active
68
+
69
+ # UserDetails things
51
70
  detail = userDetails.get(user.id)
52
71
  if not detail:
53
72
  detail = UserDetail(user_id=user.id, child_id=child_id)
54
- detail.current_usage_GB = detail.current_usage_GB or 0
55
73
  db.session.add(detail)
56
74
  detail.connected_devices = devices
57
75
  detail.current_usage_GB = detail.current_usage_GB or 0
58
- if not user.last_reset_time or user.user_should_reset():
59
- user.last_reset_time = datetime.date.today()
60
- user.current_usage_GB = 0
61
- detail.current_usage_GB = 0
62
76
 
77
+ # Enable the user if isn't already
63
78
  if not before_enabled_users[user.uuid] and user.is_active:
64
79
  print(f"Enabling disabled client {user.uuid} ")
65
80
  user_driver.add_client(user)
66
81
  send_bot_message(user)
67
82
  have_change = True
83
+
84
+ # Check if there's new usage value
68
85
  if not isinstance(usage_bytes, int) or usage_bytes == 0:
69
86
  res[user.uuid] = "No usage"
70
87
  else:
88
+ # Set new daily usage of the user
71
89
  if sync:
72
90
  if daily_usage.get(user.added_by, daily_usage[1]).usage != usage_bytes:
73
91
  daily_usage.get(user.added_by, daily_usage[1]).usage = usage_bytes
74
92
  else:
75
93
  daily_usage.get(user.added_by, daily_usage[1]).usage += usage_bytes
94
+
76
95
  in_gig = (usage_bytes) / to_gig_d
77
- res[user.uuid] = in_gig
96
+
97
+ # Set new current usage of the user
78
98
  if sync:
79
99
  if user.current_usage_GB != in_gig:
80
100
  user.current_usage_GB = in_gig
@@ -82,12 +102,18 @@ def _add_users_usage(users_usage_data: Dict[User, Dict], child_id, sync=False):
82
102
  else:
83
103
  user.current_usage_GB += in_gig
84
104
  detail.current_usage_GB += in_gig
105
+
106
+ # Change last online time of the user
85
107
  user.last_online = datetime.datetime.now()
86
108
  detail.last_online = datetime.datetime.now()
87
109
 
110
+ # Set start date of user to the current datetime if it hasn't been set already
88
111
  if user.start_date is None:
89
112
  user.start_date = datetime.date.today()
90
113
 
114
+ res[user.uuid] = in_gig
115
+
116
+ # Remove user from drivers(singbox, xray, wireguard etc.) if they're inactive
91
117
  if before_enabled_users[user.uuid] and not user.is_active:
92
118
  print(f"Removing enabled client {user.uuid} ")
93
119
  user_driver.remove_client(user)
@@ -95,13 +121,16 @@ def _add_users_usage(users_usage_data: Dict[User, Dict], child_id, sync=False):
95
121
  res[user.uuid] = f"{res[user.uuid]} !OUT of USAGE! Client Removed"
96
122
 
97
123
  db.session.commit() # type: ignore
124
+
125
+ # Apply the changes to the drivers
98
126
  if have_change:
99
127
  hiddify.quick_apply_users()
100
128
 
101
- # sync with the parent
129
+ # Sync the new data with the parent node if the data has not been set by the parent node itself and the current panel is a child panel
102
130
  if not sync:
103
131
  if hutils.node.is_child():
104
132
  hutils.node.child.sync_users_usage_with_parent()
133
+
105
134
  return {"status": 'success', "comments": res}
106
135
 
107
136
 
@@ -18,45 +18,43 @@ from hiddifypanel import hutils
18
18
 
19
19
  class UserView(FlaskView):
20
20
 
21
- @route('/useragent/')
22
- @login_required(roles={Role.user})
23
- def test(self):
24
- ua = request.user_agent.string
25
- print(ua)
26
- return ua
27
-
28
21
  def index(self):
29
22
  return self.auto_sub()
30
23
 
31
24
  def auto_sub(self):
32
25
  if g.user_agent['is_browser']:
33
26
  return self.new()
34
- return self.get_proper_config() or self.all_configs(base64=True)
27
+ return self.get_proper_config() or self.links_imp(base64=True)
35
28
 
36
29
  # former /sub/ or /sub (it was auto actually but we named it as /sub/)
37
30
  @route('/auto/')
38
31
  @route('/auto')
39
32
  @login_required(roles={Role.user})
40
33
  def force_sub(self):
41
- return self.get_proper_config() or self.all_configs(base64=False)
34
+ return self.get_proper_config() or self.links_imp(base64=False)
42
35
 
43
36
  # region new endpoints
44
37
  @route("/sub/")
45
38
  @route("/sub")
46
39
  @login_required(roles={Role.user})
47
40
  def sub(self):
48
- return self.all_configs(base64=False)
41
+ '''Returns proxy links (not base64 encoded)'''
42
+ return self.links_imp(base64=False)
49
43
 
50
44
  @route("/sub64/")
51
45
  @route("/sub64")
52
46
  @login_required(roles={Role.user})
53
47
  def sub64(self):
54
- return self.all_configs(base64=True)
48
+ '''Returns proxy links (base64 encoded)'''
49
+ return self.links_imp(base64=True)
55
50
 
56
51
  @route("/xray/")
57
52
  @route("/xray")
58
53
  @login_required(roles={Role.user})
59
54
  def xray(self):
55
+ '''Returns Xray JSON proxy config'''
56
+ # if not hconfig(ConfigEnum.sub_full_xray_json_enable):
57
+ # return 'The Full Xray subscription is disabled'
60
58
  c = get_common_data(g.account.uuid, mode="new")
61
59
  configs = hutils.proxy.xrayjson.configs_as_json(c['domains'], c['user'], c['expire_days'], c['profile_title'])
62
60
  return add_headers(configs, c, 'application/json')
@@ -65,25 +63,29 @@ class UserView(FlaskView):
65
63
  @route("/singbox")
66
64
  @login_required(roles={Role.user})
67
65
  def singbox_full(self):
68
- return self.full_singbox()
66
+ '''Returns singbox client JSON config'''
67
+ return self.full_singbox_imp()
69
68
 
70
69
  @route("/singbox-ssh/")
71
70
  @route("/singbox-ssh")
72
71
  @login_required(roles={Role.user})
73
72
  def singbox_ssh(self):
74
- return self.singbox()
73
+ '''Returns singbox client JSON config (ssh)'''
74
+ return self.singbox_ssh_imp()
75
75
 
76
76
  @route("/clash/")
77
77
  @route("/clash")
78
78
  @login_required(roles={Role.user})
79
79
  def clash(self):
80
- return self.clash_config(meta_or_normal="normal")
80
+ '''Returns clash client config'''
81
+ return self.clash_config_imp(meta_or_normal="normal")
81
82
 
82
83
  @route("/clashmeta/")
83
84
  @route("/clashmeta")
84
85
  @login_required(roles={Role.user})
85
86
  def clashmeta(self):
86
- return self.clash_config(meta_or_normal="meta")
87
+ '''Returns clash meta client config'''
88
+ return self.clash_config_imp(meta_or_normal="meta")
87
89
  # endregion
88
90
 
89
91
  @ route('/new/')
@@ -100,26 +102,28 @@ class UserView(FlaskView):
100
102
  return render_template('new.html', **c, ua=user_agent)
101
103
 
102
104
  def get_proper_config(self):
105
+ '''Returns proper config based on user agent'''
103
106
  if g.user_agent['is_browser']:
104
107
  return None
108
+
105
109
  ua = request.user_agent.string
106
110
  if g.user_agent['is_singbox'] or re.match('^(HiddifyNext|Dart|SFI|SFA)', ua, re.IGNORECASE):
107
- return self.full_singbox()
111
+ return self.full_singbox_imp()
108
112
 
109
113
  if re.match('^(Clash-verge|Clash-?Meta|Stash|NekoBox|NekoRay|Pharos|hiddify-desktop)', ua, re.IGNORECASE):
110
- return self.clash_config(meta_or_normal="meta")
114
+ return self.clash_config_imp(meta_or_normal="meta")
111
115
  if re.match('^(Clash|Stash)', ua, re.IGNORECASE):
112
- return self.clash_config(meta_or_normal="normal")
116
+ return self.clash_config_imp(meta_or_normal="normal")
113
117
 
114
- if g.user_agent.get('is_v2rayng'):
115
- return self.xray()
118
+ if hconfig(ConfigEnum.sub_full_xray_json_enable):
119
+ # return the old "Subscription link b64" sub if the "Full Xray" sub is disabled (wanted by user)
120
+ if g.user_agent.get('is_v2rayng') and hutils.flask.is_client_version(hutils.flask.ClientVersion.v2ryang, 1, 8, 17):
121
+ return self.xray()
122
+ elif g.user_agent.get('is_streisand'):
123
+ return self.xray()
116
124
 
117
- # if 'HiddifyNext' in ua or 'Dart' in ua:
118
- # return self.clash_config(meta_or_normal="meta")
119
-
120
- # if any([p in ua for p in ['FoXray', 'HiddifyNG','Fair%20VPN' ,'v2rayNG', 'SagerNet']]):
121
- if re.match('^(Hiddify|FoXray|Fair|v2rayNG|SagerNet|Shadowrocket|V2Box|Loon|Liberty)', ua, re.IGNORECASE):
122
- return self.all_configs(base64=True)
125
+ if re.match('^(Hiddify|FoXray|Fair|v2rayNG|SagerNet|Shadowrocket|V2Box|Loon|Liberty|Streisand)', ua, re.IGNORECASE):
126
+ return self.links_imp(base64=True)
123
127
 
124
128
  @route('/clash/<meta_or_normal>/proxies.yml')
125
129
  @route('/clash/proxies.yml')
@@ -176,8 +180,12 @@ class UserView(FlaskView):
176
180
  @ route('/clash/<typ>.yml', methods=["GET", "HEAD"])
177
181
  @ route('/clash/<meta_or_normal>/<typ>.yml', methods=["GET", "HEAD"])
178
182
  @login_required(roles={Role.user})
179
- def clash_config(self, meta_or_normal="normal", typ="all.yml"):
183
+ def clash_config_imp(self, meta_or_normal="normal", typ="all.yml"):
180
184
  mode = request.args.get("mode")
185
+ if meta_or_normal == 'meta' and not hconfig(ConfigEnum.sub_full_clash_meta_enable):
186
+ return 'The Clash meta subscription is disabled'
187
+ elif meta_or_normal == 'normal' and not hconfig(ConfigEnum.sub_full_clash_enable):
188
+ return 'The Clash subscription is disabled'
181
189
 
182
190
  c = get_common_data(g.account.uuid, mode)
183
191
 
@@ -192,7 +200,10 @@ class UserView(FlaskView):
192
200
 
193
201
  @ route('/full-singbox.json', methods=["GET", "HEAD"])
194
202
  @login_required(roles={Role.user})
195
- def full_singbox(self):
203
+ def full_singbox_imp(self):
204
+ # if not hconfig(ConfigEnum.sub_full_singbox_enable):
205
+ # return 'The Full Singbox subscription is disabled'
206
+
196
207
  mode = "new" # request.args.get("mode")
197
208
  c = get_common_data(g.account.uuid, mode)
198
209
  # response.content_type = 'text/plain';
@@ -205,9 +216,12 @@ class UserView(FlaskView):
205
216
 
206
217
  @ route('/singbox.json', methods=["GET", "HEAD"])
207
218
  @login_required(roles={Role.user})
208
- def singbox(self):
219
+ def singbox_ssh_imp(self):
209
220
  if not hconfig(ConfigEnum.ssh_server_enable):
210
- return "SSH server is disable in settings"
221
+ return "The SSH server is disabled"
222
+ # elif not hconfig(ConfigEnum.sub_singbox_ssh_enable):
223
+ # return "The Singbox: SSH subscription is disabled"
224
+
211
225
  mode = "new" # request.args.get("mode")
212
226
  c = get_common_data(g.account.uuid, mode)
213
227
  # response.content_type = 'text/plain';
@@ -221,9 +235,15 @@ class UserView(FlaskView):
221
235
 
222
236
  @route('/all.txt', methods=["GET", "HEAD"])
223
237
  @login_required(roles={Role.user})
224
- def all_configs(self, base64=False):
238
+ def links_imp(self, base64=False):
239
+ '''Returns subscription links (base64 or not)'''
225
240
  mode = "new" # request.args.get("mode")
226
241
  base64 = base64 or request.args.get("base64", "").lower() == "true"
242
+ # if base64 and not hconfig(ConfigEnum.sub_full_links_b64_enable):
243
+ # return 'The Subscription link b64 is disabled'
244
+ # if not base64 and not hconfig(ConfigEnum.sub_full_links_enable):
245
+ # return 'The Subscription link is disabled'
246
+
227
247
  c = get_common_data(g.account.uuid, mode)
228
248
  # response.content_type = 'text/plain';
229
249
  if request.method == 'HEAD':
@@ -346,7 +366,7 @@ def get_common_data(user_uuid, mode, no_domain=False, filter_domain=None):
346
366
  'expire_rel': hutils.convert.format_timedelta(datetime.timedelta(days=expire_days)),
347
367
  'reset_day': reset_days,
348
368
  'hconfigs': get_hconfigs(),
349
- 'hdomains': get_hdomains(),
369
+ 'hdomains': Domain.modes_and_domains(),
350
370
  'ConfigEnum': ConfigEnum,
351
371
  'link_maker': hutils.proxy,
352
372
  'domains': domains,
@@ -85,13 +85,13 @@
85
85
  {{ render_nav_item('admin.Dashboard:index', icon('solid','house','nav-icon')+(_("Parent Panel") if hutils.node.is_parent() else _('admin.menu.home')),_use_li=True)}}
86
86
 
87
87
  {% if hutils.node.is_child() %}
88
- {{ render_nav_item(hconfig(ConfigEnum.parent_panel)+"/"+g.account.uuid+"/admin/user/",icon('solid','user-secret','nav-icon')+_('admin.menu.user'),_badge=_('in parent panel') ,_use_li=True) }}
88
+ {{ render_nav_item(hconfig(ConfigEnum.parent_panel)+"admin/user/",icon('solid','user-secret','nav-icon')+_('admin.menu.user'),_badge=_('in parent panel') ,_use_li=True) }}
89
89
  {% else %}
90
90
  {{ render_nav_item('flask.user.index_view', icon('solid','user','nav-icon')+_('admin.menu.user'),_use_li=True)}}
91
91
  {% endif %}
92
92
 
93
93
  {% if hutils.node.is_child() %}
94
- {{ render_nav_item(hconfig(ConfigEnum.parent_panel)+"/"+g.account.uuid+"/admin/adminuser/",icon('solid','user-secret','nav-icon')+_('Admins'),_badge=_('in parent panel') ,_use_li=True) }}
94
+ {{ render_nav_item(hconfig(ConfigEnum.parent_panel)+"admin/adminuser/",icon('solid','user-secret','nav-icon')+_('Admins'),_badge=_('in parent panel') ,_use_li=True) }}
95
95
  {% else %}
96
96
  {{ render_nav_item('flask.adminuser.index_view', icon('solid','user-secret','nav-icon')+_('Admins'),_use_li=True) }}
97
97
  {% endif %}