hiddifypanel 10.80.0.dev13__py3-none-any.whl → 10.80.0.dev15__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 (33) hide show
  1. hiddifypanel/VERSION +1 -1
  2. hiddifypanel/VERSION.py +1 -1
  3. hiddifypanel/hutils/proxy/__init__.py +1 -0
  4. hiddifypanel/hutils/proxy/clash.py +2 -2
  5. hiddifypanel/hutils/proxy/shared.py +6 -6
  6. hiddifypanel/hutils/proxy/singbox.py +1 -1
  7. hiddifypanel/hutils/proxy/wireguard.py +34 -0
  8. hiddifypanel/hutils/proxy/xray.py +2 -2
  9. hiddifypanel/hutils/proxy/xrayjson.py +4 -4
  10. hiddifypanel/models/config_enum.py +2 -2
  11. hiddifypanel/models/proxy.py +1 -1
  12. hiddifypanel/panel/admin/AdminstratorAdmin.py +7 -8
  13. hiddifypanel/panel/admin/DomainAdmin.py +131 -98
  14. hiddifypanel/panel/admin/QuickSetup.py +8 -8
  15. hiddifypanel/panel/admin/UserAdmin.py +58 -33
  16. hiddifypanel/panel/admin/templates/index.html +6 -4
  17. hiddifypanel/panel/admin/templates/model/user_list.html +11 -3
  18. hiddifypanel/panel/commercial/restapi/v1/tgbot.py +19 -1
  19. hiddifypanel/panel/common.py +1 -1
  20. hiddifypanel/panel/init_db.py +19 -16
  21. hiddifypanel/panel/user/user.py +6 -18
  22. hiddifypanel/translations.i18n/en.json +342 -1
  23. hiddifypanel/translations.i18n/fa.json +342 -1
  24. hiddifypanel/translations.i18n/fr.json +2 -2
  25. hiddifypanel/translations.i18n/my.json +2 -2
  26. hiddifypanel/translations.i18n/pt.json +342 -1
  27. hiddifypanel/translations.i18n/ru.json +342 -1
  28. hiddifypanel/translations.i18n/zh.json +342 -1
  29. {hiddifypanel-10.80.0.dev13.dist-info → hiddifypanel-10.80.0.dev15.dist-info}/METADATA +1 -1
  30. {hiddifypanel-10.80.0.dev13.dist-info → hiddifypanel-10.80.0.dev15.dist-info}/RECORD +33 -32
  31. {hiddifypanel-10.80.0.dev13.dist-info → hiddifypanel-10.80.0.dev15.dist-info}/LICENSE.md +0 -0
  32. {hiddifypanel-10.80.0.dev13.dist-info → hiddifypanel-10.80.0.dev15.dist-info}/WHEEL +0 -0
  33. {hiddifypanel-10.80.0.dev13.dist-info → hiddifypanel-10.80.0.dev15.dist-info}/entry_points.txt +0 -0
@@ -27,7 +27,7 @@ from hiddifypanel import hutils
27
27
  class UserAdmin(AdminLTEModelView):
28
28
  column_default_sort = ('id', False) # Sort by username in ascending order
29
29
 
30
- column_sortable_list = ["is_active", "name", "current_usage", 'mode', "remaining_days","max_ips", "comment", 'last_online', "uuid", 'remaining_days']
30
+ column_sortable_list = ["is_active", "name", "current_usage", 'mode', "remaining_days", "max_ips", "comment", 'last_online', "uuid"]
31
31
  column_searchable_list = ["uuid", "name"]
32
32
  column_list = ["is_active", "name", "UserLinks", "current_usage", "remaining_days", "comment", 'last_online', 'mode', 'admin', "uuid"]
33
33
  column_editable_list = ["comment", "name", "uuid"]
@@ -131,7 +131,7 @@ class UserAdmin(AdminLTEModelView):
131
131
  if model.is_active:
132
132
  link = '<i class="fa-solid fa-circle-check text-success"></i> '
133
133
  elif len(model.devices):
134
- link = '<i class="fa-solid fa-users-slash text-danger" title="{_("Too many Connected IPs")}"></i>'
134
+ link = f'<i class="fa-solid fa-users-slash text-danger" title="{_("Too many Connected IPs")}"></i>'
135
135
  else:
136
136
  link = '<i class="fa-solid fa-circle-xmark text-danger"></i> '
137
137
 
@@ -161,8 +161,8 @@ class UserAdmin(AdminLTEModelView):
161
161
 
162
162
  def _usage_formatter(view, context, model, name):
163
163
  u = round(model.current_usage_GB, 3)
164
- t = round(model.usage_limit_GB, 3)
165
- rate = round(u * 100 / (t + 0.000001))
164
+ t = max(round(model.usage_limit_GB, 3), 0.001) # Prevent division by zero
165
+ rate = min(round(u * 100 / t), 100) # Cap at 100%
166
166
  state = "danger" if u >= t else ('warning' if rate > 80 else 'success')
167
167
  color = "#ff7e7e" if u >= t else ('#ffc107' if rate > 80 else '#9ee150')
168
168
  return Markup(f"""
@@ -225,38 +225,45 @@ class UserAdmin(AdminLTEModelView):
225
225
 
226
226
  def on_form_prefill(self, form, id=None):
227
227
  # print("================",form._obj.start_date)
228
- if id is None or form._obj is None or form._obj.start_date is None or form._obj.current_usage==0:
228
+ if form._obj is None:
229
+ return
230
+
231
+ if id is None or form._obj.start_date is None or form._obj.current_usage==0:
229
232
  msg = _("Package not started yet.")
230
233
  # form.reset['class']="d-none"
231
- if form._obj.start_date is None:
234
+ if form._obj.start_date is None:
235
+ if hasattr(form, 'reset_days'):
232
236
  delattr(form, 'reset_days')
233
- if form._obj.current_usage==0:
234
- delattr(form, 'reset_usage')
235
- # delattr(form,'disable_user')
236
237
  else:
237
- remaining = form._obj.remaining_days # remaining_days(form._obj)
238
+ remaining = form._obj.remaining_days
238
239
  relative_remaining = hutils.convert.format_timedelta(datetime.timedelta(days=remaining))
239
240
  msg = _("Remaining about %(relative)s, exactly %(days)s days", relative=relative_remaining, days=remaining)
240
241
  form.reset_days.label.text += f" ({msg})"
241
- usr_usage = f" ({_('user.home.usage.title')} {round(form._obj.current_usage_GB,3)}GB)"
242
- form.reset_usage.label.text += usr_usage
243
- form.reset_usage.data = False
244
242
  form.reset_days.data = False
245
243
 
246
- form.usage_limit.label.text += usr_usage
247
-
248
- # if form._obj.mode==UserMode.disable:
249
- # delattr(form,'disable_user')
250
- # form.disable_user.data=form._obj.mode==UserMode.disable
251
- if form._obj.start_date:
252
- started = form._obj.start_date - datetime.date.today()
253
- msg = _("Started from %(relative)s", relative=hutils.convert.format_timedelta(started))
254
- form.package_days.label.text += f" ({msg})"
255
- if started.days <= 0:
256
- exact_start = _("Started %(days)s days ago", days=-started.days)
257
- else:
258
- exact_start = _("Will Start in %(days)s days", days=started.days)
259
- form.package_days.description += f" ({exact_start})"
244
+ # Handle reset_usage field
245
+ if form._obj.current_usage == 0:
246
+ if hasattr(form, 'reset_usage'):
247
+ delattr(form, 'reset_usage')
248
+ else:
249
+ usr_usage = f" ({_('user.home.usage.title')} {round(form._obj.current_usage_GB,3)}GB)"
250
+ if hasattr(form, 'reset_usage'):
251
+ form.reset_usage.label.text += usr_usage
252
+ form.reset_usage.data = False
253
+
254
+ if hasattr(form, 'usage_limit'):
255
+ form.usage_limit.label.text += usr_usage
256
+
257
+ # Handle package days info
258
+ if form._obj.start_date and hasattr(form, 'package_days'):
259
+ started = form._obj.start_date - datetime.date.today()
260
+ msg = _("Started from %(relative)s", relative=hutils.convert.format_timedelta(started))
261
+ form.package_days.label.text += f" ({msg})"
262
+ if started.days <= 0:
263
+ exact_start = _("Started %(days)s days ago", days=-started.days)
264
+ else:
265
+ exact_start = _("Will Start in %(days)s days", days=started.days)
266
+ form.package_days.description += f" ({exact_start})"
260
267
 
261
268
  def get_edit_form(self):
262
269
  form = super().get_edit_form()
@@ -267,26 +274,44 @@ class UserAdmin(AdminLTEModelView):
267
274
  return form
268
275
 
269
276
  def on_model_change(self, form, model, is_created):
270
- model.max_ips = max(3, model.max_ips or 10000)
277
+ # Validate max_ips
278
+ try:
279
+ model.max_ips = max(3, min(int(model.max_ips or 10000), 10000))
280
+ except (ValueError, TypeError):
281
+ model.max_ips = 1000
282
+
283
+ # Show donation message
271
284
  if len(User.query.all()) % 4 == 0:
272
285
  hutils.flask.flash(('<div id="show-modal-donation"></div>'), ' d-none')
286
+
287
+ # Validate UUID
273
288
  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):
274
289
  raise ValidationError('Invalid UUID e.g.,' + str(uuid.uuid4()))
290
+
291
+ # Handle reset flags
275
292
  if hasattr(form, 'reset_usage') and form.reset_usage.data:
276
293
  model.current_usage_GB = 0
277
- # if model.telegram_id and model.telegram_id != '0' and not re.match(r"^[1-9]\d*$", model.telegram_id):
278
- # raise ValidationError('Invalid Telegram ID')
279
- # if form.disable_user.data:
280
- # model.mode=UserMode.disable
294
+
281
295
  if hasattr(form, 'reset_days') and form.reset_days.data:
282
296
  model.start_date = None
283
- model.package_days = min(model.package_days, 10000)
297
+
298
+ # Validate package days
299
+ try:
300
+ model.package_days = min(int(model.package_days), 10000)
301
+ except (ValueError, TypeError):
302
+ model.package_days = 10000
303
+
304
+ # Handle user ownership
284
305
  old_user = User.by_id(model.id)
285
306
  if not model.added_by or model.added_by == 1:
286
307
  model.added_by = g.account.id
308
+
309
+ # Validate user limits
287
310
  if not g.account.can_have_more_users():
288
311
  raise ValidationError(_('You have too much users! You can have only %(active)s active users and %(total)s users',
289
312
  active=g.account.max_active_users, total=g.account.max_users))
313
+
314
+ # Handle UUID changes
290
315
  if old_user and old_user.uuid != model.uuid:
291
316
  user_driver.remove_client(old_user)
292
317
 
@@ -192,10 +192,12 @@
192
192
  }
193
193
 
194
194
  function update_from_json(data) {
195
- usage_history = data['usage_history']
196
- onlines = usage_history['m5']['online']
197
- total_users = usage_history['total']['users']
198
- stats = data['stats']
195
+ // Use local variables instead of globals
196
+ const usage_history = data['usage_history'];
197
+ const onlines = usage_history['m5']['online'];
198
+ const total_users = usage_history['total']['users'];
199
+ const stats = data['stats'];
200
+
199
201
  info_box("today", "fa-solid fa-calendar", "Today Usage",
200
202
  ((usage_history['today']['usage'] / Math.pow(1024, 3)).toFixed(1)) + " GB",
201
203
  usage_history['today']['online'] / Math.max(1, total_users) * 100,
@@ -121,9 +121,17 @@
121
121
  $(document).Toasts('create', {
122
122
  class: 'bg-danger',
123
123
  position: "{{'topRight' if get_locale()=='fa' else 'topLeft'}}",
124
- title: status + " " + error,
125
- body: JSON.stringify(jqx)
126
- })
124
+ title: $('<div>').text(status + " " + error).html(),
125
+ body: $('<div>').text(JSON.stringify(jqx)).html(),
126
+ autohide: true,
127
+ delay: 5000
128
+ });
129
+
130
+ console.error('API Error:', {
131
+ status: status,
132
+ error: error,
133
+ details: jqx
134
+ });
127
135
  },
128
136
  success: function (data) {
129
137
  // dialog.modal('hide');
@@ -2,6 +2,7 @@ import telebot
2
2
  from flask import request
3
3
  from apiflask import abort
4
4
  from flask_restful import Resource
5
+ import time
5
6
 
6
7
  from hiddifypanel.models import *
7
8
  from hiddifypanel import Events
@@ -11,7 +12,24 @@ logger = telebot.logger
11
12
 
12
13
  class ExceptionHandler(telebot.ExceptionHandler):
13
14
  def handle(self, exception):
14
- logger.error(exception)
15
+ """Improved error handling for Telegram bot exceptions"""
16
+ error_msg = str(exception)
17
+ logger.error(f"Telegram bot error: {error_msg}")
18
+
19
+ try:
20
+ # Attempt recovery based on error type
21
+ if "webhook" in error_msg.lower():
22
+ if hasattr(bot, 'remove_webhook'):
23
+ bot.remove_webhook()
24
+ logger.info("Removed webhook due to error")
25
+ elif "connection" in error_msg.lower():
26
+ # Wait and retry for connection issues
27
+ time.sleep(5)
28
+ return True # Indicates retry
29
+ except Exception as e:
30
+ logger.error(f"Error during recovery attempt: {str(e)}")
31
+
32
+ return False # Don't retry for unknown errors
15
33
 
16
34
 
17
35
  bot = telebot.TeleBot("1:2", parse_mode="HTML", threaded=False, exception_handler=ExceptionHandler())
@@ -65,7 +65,7 @@ def init_app(app: APIFlask):
65
65
  'version': hiddifypanel.__version__,
66
66
  }), 500
67
67
 
68
- trace = traceback.format_exc(e)
68
+ trace = traceback.format_exc()
69
69
 
70
70
  # Create github issue link
71
71
  issue_link = hutils.github_issue.generate_github_issue_link_for_500_error(e, trace)
@@ -77,7 +77,7 @@ def _v94(child_id):
77
77
 
78
78
  def _v93(child_id):
79
79
  set_hconfig(ConfigEnum.quic_enable, True)
80
- set_hconfig(ConfigEnum.splithttp_enable, True)
80
+ set_hconfig(ConfigEnum.xhttp_enable, True)
81
81
 
82
82
 
83
83
  def _v92(child_id):
@@ -85,9 +85,9 @@ def _v92(child_id):
85
85
 
86
86
 
87
87
  def _v89(child_id):
88
- set_hconfig(ConfigEnum.path_splithttp,
88
+ set_hconfig(ConfigEnum.path_xhttp,
89
89
  hutils.random.get_random_string(7, 15))
90
- set_hconfig(ConfigEnum.splithttp_enable, False)
90
+ set_hconfig(ConfigEnum.xhttp_enable, False)
91
91
  pass
92
92
 
93
93
 
@@ -218,7 +218,7 @@ def _v65():
218
218
  add_config_if_not_exist(ConfigEnum.mux_min_streams, '4')
219
219
  add_config_if_not_exist(ConfigEnum.mux_max_streams, '0')
220
220
  add_config_if_not_exist(ConfigEnum.mux_padding_enable, False)
221
- add_config_if_not_exist(ConfigEnum.mux_brutal_enable, True)
221
+ add_config_if_not_exist(ConfigEnum.mux_brutal_enable, False)
222
222
  add_config_if_not_exist(ConfigEnum.mux_brutal_up_mbps, '100')
223
223
  add_config_if_not_exist(ConfigEnum.mux_brutal_down_mbps, '100')
224
224
 
@@ -571,9 +571,9 @@ def get_proxy_rows_v1():
571
571
  "httpupgrade direct vless",
572
572
  # "httpupgrade direct trojan",
573
573
  "httpupgrade direct vmess",
574
- "splithttp direct vless",
575
- "splithttp direct trojan",
576
- "splithttp direct vmess",
574
+ "xhttp direct vless",
575
+ "xhttp direct trojan",
576
+ "xhttp direct vmess",
577
577
  "tcp direct vless",
578
578
  "tcp direct trojan",
579
579
  "tcp direct vmess",
@@ -591,9 +591,9 @@ def get_proxy_rows_v1():
591
591
  # "httpupgrade relay trojan",
592
592
  "httpupgrade relay vmess",
593
593
 
594
- "splithttp relay vless",
595
- "splithttp relay trojan",
596
- "splithttp relay vmess",
594
+ "xhttp relay vless",
595
+ "xhttp relay trojan",
596
+ "xhttp relay vmess",
597
597
 
598
598
  "tcp relay vless",
599
599
  "tcp relay trojan",
@@ -615,9 +615,9 @@ def get_proxy_rows_v1():
615
615
  # "httpupgrade CDN trojan",
616
616
  "httpupgrade CDN vmess",
617
617
 
618
- "splithttp CDN vless",
619
- "splithttp CDN trojan",
620
- "splithttp CDN vmess",
618
+ "xhttp CDN vless",
619
+ "xhttp CDN trojan",
620
+ "xhttp CDN vmess",
621
621
 
622
622
  "grpc CDN vless",
623
623
  "grpc CDN trojan",
@@ -663,7 +663,7 @@ def make_proxy_rows(cfgs):
663
663
  for l3 in [ProxyL3.h3_quic, "tls_h2", "tls", "http", "reality"]:
664
664
  for c in cfgs:
665
665
  transport, cdn, proto = c.split(" ")
666
- if transport != ProxyTransport.splithttp and l3 == ProxyL3.h3_quic:
666
+ if transport != ProxyTransport.xhttp and l3 == ProxyL3.h3_quic:
667
667
  continue
668
668
  if l3 in ["kcp", 'reality'] and cdn != "direct":
669
669
  continue
@@ -750,8 +750,11 @@ def add_new_enum_values():
750
750
  continue
751
751
 
752
752
  # Add the new value to the enum column in the database
753
- enumstr = ','.join([f"'{a}'" for a in [*existing_values, *old_values]])
754
-
753
+ # enumstr = ','.join([f"'{a}'" for a in [*existing_values, *old_values]])
754
+ enumstr = ','.join([f"'{a}'" for a in [*existing_values]])
755
+ expired_enumstr = ','.join([f"'{a}'" for a in [*old_values]])
756
+ db_execute(
757
+ f"delete from {table_name} where `{column_name}` in ({expired_enumstr});", commit=True)
755
758
  db_execute(
756
759
  f"ALTER TABLE {table_name} MODIFY COLUMN `{column_name}` ENUM({enumstr});", commit=True)
757
760
 
@@ -86,27 +86,15 @@ class UserView(FlaskView):
86
86
  continue
87
87
  wireguards.append(pinfo)
88
88
 
89
- servers.add(pinfo['server'])
89
+
90
90
 
91
91
  if not len(wireguards):
92
92
  abort(404)
93
- wg = wireguards[0]
94
- addrs = f"{wg['wg_ipv4']}/32"
95
- if wg['wg_ipv6']:
96
- addrs += f", {wg['wg_ipv6']}/128"
97
- resp = f"""
98
- [Interface]
99
- PrivateKey = {wg['wg_pk']}
100
- Address = {addrs}
101
- DNS = 1.1.1.1
102
- MTU = 1390
103
-
104
- [Peer]
105
- PublicKey = {wg['wg_pub']}
106
- PresharedKey = {wg['wg_psk']}
107
- AllowedIPs = 0.0.0.0/1, 128.0.0.0/1, ::/1, 8000::/1
108
- Endpoint = {next(iter(servers))}:61339 #{servers}
109
- """
93
+ resp =""
94
+ for wg in wireguards:
95
+ resp +=f'#========={wg["extra_info"]} {wg["name"]}================\n'
96
+ resp+=hutils.proxy.wireguard.generate_wireguard_config(wg)
97
+ resp+="\n\n"
110
98
  return add_headers(resp, c)
111
99
 
112
100
  # return self.singbox_ssh_imp()