hiddifypanel 10.85.0b12__py3-none-any.whl → 10.85.0b14__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.
hiddifypanel/VERSION CHANGED
@@ -1 +1 @@
1
- 10.85.0b12
1
+ 10.85.0.b14
hiddifypanel/VERSION.py CHANGED
@@ -1,6 +1,6 @@
1
1
  # import importlib.metadata
2
2
  from datetime import datetime
3
3
 
4
- __version__ = '10.85.0b12'
5
- __release_time__= datetime.strptime('2025-04-29T02:16:29','%Y-%m-%dT%H:%M:%S')
4
+ __version__ = '10.85.0.b14'
5
+ __release_time__= datetime.strptime('2025-06-28T13:07:47','%Y-%m-%dT%H:%M:%S')
6
6
  is_released_version=True
hiddifypanel/database.py CHANGED
@@ -79,6 +79,7 @@ def init_app(app):
79
79
 
80
80
 
81
81
  def db_execute(query: str, return_val: bool = False, commit: bool = False, **params: dict):
82
+ # print(params)
82
83
  q = db.session.execute(text(query), params)
83
84
  if commit:
84
85
  db.session.commit()
@@ -1,7 +1,7 @@
1
1
 
2
2
 
3
3
  class DriverABS:
4
- def get_all_usage(self, users): pass
4
+ def get_all_usage(self): pass
5
5
  def get_enabled_users(self): pass
6
6
  def add_client(self, user): pass
7
7
  def remove_client(self, user): pass
@@ -38,17 +38,17 @@ class SingboxApi(DriverABS):
38
38
  def remove_client(self, user):
39
39
  pass
40
40
 
41
- def get_all_usage(self, users):
41
+ def get_all_usage(self):
42
42
  xray_client = self.get_singbox_client()
43
43
  usages = xray_client.stats_query('user', reset=True)
44
- uuid_user_map = {u.uuid: u for u in users}
44
+
45
45
  res = defaultdict(int)
46
46
  for use in usages:
47
47
  if "user>>>" not in use.name:
48
48
  continue
49
49
  # print(use.name, use.value)
50
50
  uuid = use.name.split(">>>")[1].split("@")[0]
51
- res[uuid_user_map[uuid]] += use.value # uplink + downlink
51
+ res[uuid] += use.value # uplink + downlink
52
52
  return res
53
53
  # return {u: self.get_usage_imp(u.uuid) for u in users}
54
54
 
@@ -41,12 +41,13 @@ class SSHLibertyBridgeApi(DriverABS):
41
41
  redis_client.hdel(USERS_USAGE, f'{user.uuid}')
42
42
  redis_client.save()
43
43
 
44
- def get_all_usage(self, users):
44
+ def get_all_usage(self):
45
45
  redis_client = self.get_ssh_redis_client()
46
46
  allusage = redis_client.hgetall(USERS_USAGE)
47
47
  redis_client.delete(USERS_USAGE)
48
- return {u: int(allusage.get(u.uuid) or 0) for u in users}
49
- return {u: self.get_usage_imp(u.uuid) for u in users}
48
+ return allusage
49
+ # return {u: int(allusage.get(u.uuid) or 0) for u in users}
50
+ # return {u: self.get_usage_imp(u.uuid) for u in users}
50
51
 
51
52
  def get_usage_imp(self, client_uuid: str, reset: bool = True) -> int:
52
53
  redis_client = self.get_ssh_redis_client()
@@ -17,16 +17,17 @@ def enabled_drivers():
17
17
  def get_users_usage(reset=True):
18
18
  res = {}
19
19
  from hiddifypanel.database import db
20
-
21
- users = db.session.query(User).all()
20
+
21
+ # users = db.session.query(User).all()
22
22
  # users = list(User.query.all())
23
23
  res = defaultdict(lambda: {'usage': 0, 'devices': ''})
24
24
  for driver in enabled_drivers():
25
25
  try:
26
- all_usage = driver.get_all_usage(users)
27
- for user, usage in all_usage.items():
26
+ all_usage = driver.get_all_usage()
27
+ for uuid, usage in all_usage.items():
28
+ # print(f"{driver.__class__.__name__} {uuid} usage={usage}")
28
29
  if usage:
29
- res[user]['usage'] += usage
30
+ res[uuid]['usage'] += usage
30
31
  # res[user]['devices'] +=usage
31
32
  except Exception as e:
32
33
  print(driver)
@@ -22,7 +22,25 @@ class WireguardApi(DriverABS):
22
22
 
23
23
  def __init__(self) -> None:
24
24
  super().__init__()
25
-
25
+ self.pub_uuid_map={}
26
+ def __load_pubkey_uuid_map(self):
27
+ from hiddifypanel.database import db
28
+ users = db.session.query(User).all()
29
+ self.pub_uuid_map={u.wg_pub: u.uuid for u in users}
30
+
31
+ def __convert_pub_key_to_uuid(self,pubkeys):
32
+ res={}
33
+ can_reload_map=True
34
+ for key in pubkeys:
35
+ if uuid:=self.pub_uuid_map.get(key):
36
+ res[key]=uuid
37
+ elif can_reload_map:
38
+ self.__load_pubkey_uuid_map()
39
+ can_reload_map=False
40
+ if uuid:=self.pub_uuid_map.get(key):
41
+ res[key]=uuid
42
+ return res
43
+
26
44
  def __get_wg_usages(self) -> dict:
27
45
  raw_output = commander(Command.update_wg_usage, run_in_background=False)
28
46
  data = {}
@@ -36,6 +54,7 @@ class WireguardApi(DriverABS):
36
54
  'down': int(sections[1]),
37
55
  'up': int(sections[2]),
38
56
  }
57
+
39
58
  return data
40
59
 
41
60
  def __get_local_usage(self) -> dict:
@@ -45,22 +64,25 @@ class WireguardApi(DriverABS):
45
64
 
46
65
  return {}
47
66
 
48
- def __sync_local_usages(self, users) -> dict:
67
+ def __sync_local_usages(self) -> dict:
49
68
  local_usage = self.__get_local_usage()
50
69
  wg_usage = self.__get_wg_usages()
70
+
51
71
  res = {}
52
72
  # remove local usage that is removed from wg usage
53
73
  for local_wg_pub in local_usage.copy().keys():
54
74
  if local_wg_pub not in wg_usage:
55
75
  del local_usage[local_wg_pub]
56
- uuid_map = {u.wg_pub: u for u in users}
76
+
77
+
78
+ uuid_map = self.__convert_pub_key_to_uuid(wg_usage.keys())
57
79
  for wg_pub, wg_usage in wg_usage.items():
58
- user = uuid_map.get(wg_pub)
59
- uuid = user.uuid if user else None
80
+ uuid = uuid_map.get(wg_pub)
81
+
60
82
  if not local_usage.get(wg_pub):
61
83
  local_usage[wg_pub] = {"uuid": uuid, "usage": wg_usage}
62
84
  continue
63
- res[wg_pub] = self.calculate_reset(local_usage[wg_pub]['usage'], wg_usage)
85
+ res[uuid] = self.calculate_reset(local_usage[wg_pub]['usage'], wg_usage)
64
86
  local_usage[wg_pub] = {"uuid": uuid, "usage": wg_usage}
65
87
 
66
88
  self.get_redis_client().set(USERS_USAGE, json.dumps(local_usage))
@@ -101,14 +123,14 @@ class WireguardApi(DriverABS):
101
123
  def remove_client(self, user):
102
124
  pass
103
125
 
104
- def get_all_usage(self, users, reset=True):
126
+ def get_all_usage(self, reset=True):
105
127
  if not hconfig(ConfigEnum.wireguard_enable):
106
128
  return {}
107
- all_usages = self.__sync_local_usages(users)
129
+ all_usages = self.__sync_local_usages()
108
130
  res = {}
109
- for u in users:
110
- if use := all_usages.get(u.wg_pub):
111
- res[u] = use['up'] + use['down']
112
- else:
113
- res[u] = 0
131
+ for uuid,use in all_usages.items():
132
+ # if use := all_usages.get(u.wg_pub):
133
+ res[uuid] = use['up'] + use['down']
134
+ # else:
135
+ # res[u] = 0
114
136
  return res
@@ -16,36 +16,41 @@ class XrayApi(DriverABS):
16
16
  return self.xray_client
17
17
 
18
18
  def get_enabled_users(self):
19
- xray_client = self.get_xray_client()
20
- usages = xray_client.stats_query('user', reset=True)
21
- res = defaultdict(int)
22
- tags = set(self.get_inbound_tags())
23
- for use in usages:
24
- if "user>>>" not in use.name:
25
- continue
26
- uuid = use.name.split(">>>")[1].split("@")[0]
27
-
28
- for t in tags.copy():
29
- try:
30
- self.__add_uuid_to_tag(uuid, t)
31
- self._remove_client(uuid, [t], False)
32
- # print(f"Success add {uuid} {t}")
33
- res[uuid] = 0
34
- except ValueError:
35
- # tag invalid
36
- tags.remove(t)
37
- pass
38
- except xtlsapi.xtlsapi.exceptions.EmailAlreadyExists as e:
39
- res[uuid] = 1
40
- except Exception as e:
41
- print(f"error {e}")
42
- res[uuid] = 0
43
-
44
- return res
19
+ return {u:1 for u in self.get_enabled_users_terminal()}
20
+ # xray_client = self.get_xray_client()
21
+ # usages = xray_client.stats_query('user', reset=True)
22
+ # res = defaultdict(int)
23
+ # tags = set(self.get_inbound_tags())
24
+ # for use in usages:
25
+ # if "user>>>" not in use.name:
26
+ # continue
27
+ # uuid = use.name.split(">>>")[1].split("@")[0]
28
+ # res[uuid]=1
29
+
30
+ # #TODO use xtls api
31
+ # for t in tags.copy():
32
+ # try:
33
+ # self.__add_uuid_to_tag(uuid, t)
34
+ # self._remove_client(uuid, [t], False)
35
+ # # print(f"Success add {uuid} {t}")
36
+ # res[uuid] = 0
37
+ # break
38
+ # except ValueError:
39
+ # # tag invalid
40
+ # tags.remove(t)
41
+ # pass
42
+ # except xtlsapi.xtlsapi.exceptions.EmailAlreadyExists as e:
43
+ # res[uuid] = 1
44
+ # break
45
+ # except Exception as e:
46
+ # print(f"error {e}")
47
+ # # res[uuid] = 1
48
+ # break
49
+
50
+ # return res
45
51
 
46
52
  # xray_client = self.get_xray_client()
47
- # users = User.query.all()
48
- # t = "xtls"
53
+ # users = User.query.all() # t = "xtls"
49
54
  # protocol = "vless"
50
55
  # enabled = {}
51
56
  # for u in users:
@@ -101,8 +106,7 @@ class XrayApi(DriverABS):
101
106
  if (protocol == "vless" and p != "xtls" and p != "realityin") or "realityingrpc" in t:
102
107
  xray_client.add_client(t, f'{uuid}', f'{uuid}@hiddify.com', protocol=protocol, flow='\0',)
103
108
  else:
104
- xray_client.add_client(t, f'{uuid}', f'{uuid}@hiddify.com', protocol=protocol,
105
- flow='xtls-rprx-vision', alter_id=0, cipher='chacha20_poly1305')
109
+ xray_client.add_client(t, f'{uuid}', f'{uuid}@hiddify.com', protocol=protocol, flow='xtls-rprx-vision', alter_id=0, cipher='chacha20_poly1305')
106
110
 
107
111
  def add_client(self, user):
108
112
  uuid = user.uuid
@@ -130,26 +134,26 @@ class XrayApi(DriverABS):
130
134
  for t in tags:
131
135
  try:
132
136
  xray_client.remove_client(t, f'{uuid}@hiddify.com')
133
- if dolog:
134
- logger.info(f"Success remove {uuid} {t}")
137
+ # if dolog:
138
+ # logger.info(f"Success remove {uuid} {t}")
135
139
  except Exception as e:
136
140
  if dolog:
137
141
  logger.info(f"error in remove {uuid} {t} {e}")
138
142
  pass
139
143
 
140
- def get_all_usage(self, users):
144
+ def get_all_usage(self)->dict:
141
145
  xray_client = self.get_xray_client()
142
146
  usages = xray_client.stats_query('user', reset=True)
143
- uuid_user_map = {u.uuid: u for u in users}
147
+ # uuid_user_map = {u.uuid: u for u in users}
144
148
  res = defaultdict(int)
145
149
  for use in usages:
146
150
  if "user>>>" not in use.name:
147
151
  continue
148
152
  uuid = use.name.split(">>>")[1].split("@")[0]
149
- if u := uuid_user_map.get(uuid):
150
- res[u] += use.value
151
- else:
152
- self._remove_client(uuid)
153
+ # if u := uuid_user_map.get(uuid):
154
+ res[uuid] += use.value
155
+ # else:
156
+ # self._remove_client(uuid)
153
157
  return res
154
158
 
155
159
  def get_usage_imp(self, uuid):
@@ -167,3 +171,36 @@ class XrayApi(DriverABS):
167
171
  if res:
168
172
  logger.debug(f"Xray usage {uuid} d={d} u={u} sum={res}")
169
173
  return res
174
+
175
+
176
+
177
+
178
+ def get_enabled_users_terminal(self):
179
+ import subprocess
180
+ import json
181
+ tags=self.get_inbound_tags()
182
+ for t in tags:
183
+ # Command to execute
184
+ cmd = [
185
+ 'xray',
186
+ 'api',
187
+ 'inbounduser',
188
+ '--server=127.0.0.1:10085',
189
+ f'-tag={t}'
190
+ ]
191
+
192
+ try:
193
+ result = subprocess.run(cmd, capture_output=True, text=True, check=True)
194
+
195
+ # Parse JSON output
196
+ data = json.loads(result.stdout)
197
+ users= [splt[0] for splt in [u.get('email','').split("@") for u in data.get('users',[])] if len(splt)==2 and splt[1]=="hiddify.com"]
198
+ if len(data)>0:
199
+ return users
200
+
201
+ except subprocess.CalledProcessError as e:
202
+ print("Command failed:", e)
203
+ print("Error output:", e.stderr)
204
+ except json.JSONDecodeError as e:
205
+ print("Failed to parse JSON:", e)
206
+ return []
@@ -244,7 +244,7 @@ def validate_domain_exist(form, field):
244
244
 
245
245
 
246
246
  def get_proxy_stats_url():
247
- proxy_stats_url = f'{request.host_url}{g.proxy_path}/proxy-stats/'
247
+ proxy_stats_url = f'{request.host_url}{g.proxy_path}/proxy-stats/'.replace("http://","https://")
248
248
  params = f'hostname={proxy_stats_url}api/&port=443&secret=hiddify'
249
249
  return f'{proxy_stats_url}?{params}'
250
250
 
@@ -361,7 +361,7 @@ def make_proxy(hconfigs: dict, proxy: Proxy, domain_db: Domain, phttp=80, ptls=4
361
361
  return base
362
362
  elif "shadowsocks" in proxy.transport:
363
363
  return base
364
- if ProxyTransport.XTLS in proxy.transport:
364
+ if proxy.l3 in [ProxyL3.reality] and proxy.transport in [ProxyTransport.XTLS,ProxyTransport.xhttp]:
365
365
  base['flow'] = 'xtls-rprx-vision'
366
366
  return {**base, 'transport': 'tcp'}
367
367
 
@@ -17,6 +17,63 @@ from loguru import logger
17
17
  MAX_DB_VERSION = 120
18
18
 
19
19
 
20
+ def _v102(child_id):
21
+
22
+ add_usage_proc= """
23
+ DROP PROCEDURE IF EXISTS add_usage_json;
24
+
25
+ CREATE PROCEDURE add_usage_json(IN usage_data JSON)
26
+ BEGIN
27
+ DECLARE u_id INT DEFAULT NULL;
28
+ DECLARE u_uuid CHAR(36) DEFAULT NULL;
29
+ DECLARE u_usage BIGINT;
30
+ DECLARE done BOOL DEFAULT FALSE;
31
+
32
+ DECLARE cur CURSOR FOR
33
+ SELECT jt.id, jt.uuid, jt.usage FROM JSON_TABLE(
34
+ usage_data,
35
+ '$[*]' COLUMNS (
36
+ id INT PATH '$.id' NULL ON ERROR,
37
+ uuid CHAR(36) PATH '$.uuid' NULL ON ERROR,
38
+ `usage` BIGINT PATH '$.usage'
39
+ )
40
+ ) AS jt;
41
+
42
+ DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
43
+
44
+ OPEN cur;
45
+
46
+ read_loop: LOOP
47
+ FETCH cur INTO u_id, u_uuid, u_usage;
48
+ IF done THEN
49
+ LEAVE read_loop;
50
+ END IF;
51
+
52
+ IF u_id IS NOT NULL THEN
53
+ UPDATE user
54
+ SET current_usage = current_usage + u_usage,
55
+ last_online = NOW(),
56
+ start_date = CASE WHEN start_date IS NULL THEN CURDATE() ELSE start_date END
57
+ WHERE id = u_id;
58
+ ELSEIF u_uuid IS NOT NULL THEN
59
+ UPDATE user
60
+ SET current_usage = current_usage + u_usage,
61
+ last_online = NOW(),
62
+ start_date = CASE WHEN start_date IS NULL THEN CURDATE() ELSE start_date END
63
+
64
+ WHERE uuid = u_uuid;
65
+ END IF;
66
+
67
+ COMMIT;
68
+ END LOOP;
69
+
70
+ CLOSE cur;
71
+ END
72
+
73
+ """
74
+
75
+ db_execute(add_usage_proc,commit=True)
76
+
20
77
 
21
78
  def _v101(child_id):
22
79
  add_config_if_not_exist(ConfigEnum.path_xhttp, hutils.random.get_random_string(7, 15))
@@ -660,7 +717,7 @@ def make_proxy_rows(cfgs):
660
717
  continue
661
718
  if l3 in ["kcp", 'reality'] and cdn != "direct":
662
719
  continue
663
- if l3 == "reality" and ((transport not in ['tcp', 'grpc', 'XTLS']) or proto != 'vless'):
720
+ if l3 == "reality" and ((transport not in ['tcp', 'grpc', 'XTLS',ProxyTransport.xhttp]) or proto != 'vless'):
664
721
  continue
665
722
  if proto == "trojan" and l3 not in ["tls", 'xtls', 'tls_h2', 'h3_quic']:
666
723
  continue
@@ -668,6 +725,11 @@ def make_proxy_rows(cfgs):
668
725
  continue
669
726
  if transport in ["h2"] and l3 != "reality":
670
727
  continue
728
+ if l3 in [ProxyL3.h3_quic,ProxyL3.tls_h2] and transport in [ProxyTransport.httpupgrade, ProxyTransport.WS]:
729
+ continue
730
+
731
+
732
+
671
733
  # if l3 == "tls_h2" and transport =="grpc":
672
734
  # continue
673
735
  enable = l3 != "http" or proto == "vmess"
@@ -805,6 +867,7 @@ def upgrade_database():
805
867
  def init_db():
806
868
  # set_hconfig(ConfigEnum.db_version, 71)
807
869
  db_version = current_db_version()
870
+ # set_hconfig(ConfigEnum.db_version,101)
808
871
  if db_version == latest_db_version():
809
872
  return
810
873
 
@@ -1,4 +1,5 @@
1
1
 
2
+ from celery import shared_task
2
3
  from sqlalchemy import func
3
4
  from typing import Dict
4
5
  import datetime
@@ -6,12 +7,12 @@ import datetime
6
7
  from hiddifypanel.drivers import user_driver
7
8
  from hiddifypanel.models import *
8
9
  from hiddifypanel.panel import hiddify
9
- from hiddifypanel.database import db
10
+ from hiddifypanel.database import db, db_execute, text
10
11
  from hiddifypanel import cache, hutils
11
12
  from loguru import logger
13
+ import json
12
14
  to_gig_d = 1024**3
13
15
 
14
- from celery import shared_task
15
16
 
16
17
  @shared_task(ignore_result=False)
17
18
  def update_local_usage():
@@ -19,9 +20,9 @@ def update_local_usage():
19
20
  # if not cache.redis_client.set(lock_key, "locked", nx=True, ex=600):
20
21
  # return {"msg": "last update task is not finished yet."}
21
22
  try:
22
- res=update_local_usage_not_lock()
23
+ res = update_local_usage_not_lock()
23
24
  cache.redis_client.set(lock_key, "locked", nx=False, ex=60)
24
-
25
+
25
26
  return res
26
27
  except Exception as e:
27
28
  cache.redis_client.set(lock_key, "locked", nx=False, ex=60)
@@ -30,147 +31,242 @@ def update_local_usage():
30
31
  return {"msg": f"Exception in update usage: {e}"}
31
32
 
32
33
  # return {"status": 'success', "comments":res}
34
+
35
+
33
36
  def update_local_usage_not_lock():
34
-
37
+
35
38
  try:
36
39
  res = user_driver.get_users_usage(reset=True)
37
- return _add_users_usage(res, child_id=0)
40
+ return add_users_usage_new([{'uuid': uuid, "usage": uinfo['usage']} for uuid, uinfo in res.items()], child_id=0)
41
+ # add_users_usage_uuid({"66ac79b8-8c03-4084-81c7-a2b1b3e9eefe":{"usage":1000000000}},child_id=0)
42
+ # json_data=json.dumps([{ "uuid": uuid, "usage": uinfo["usage"]}for uuid, uinfo in {"66ac79b8-8c03-4084-81c7-a2b1b3e9eefe":{"usage":1000000000}}.items()])
43
+ # db_execute("CALL add_usage_json(:usage_data)", usage_data= json_data, commit=True)
38
44
  except Exception as e:
39
45
  raise
40
-
41
46
 
42
47
 
43
48
  def add_users_usage_uuid(uuids_bytes: Dict[str, Dict], child_id, sync=False):
44
- uuids_bytes = {u: v for u, v in uuids_bytes.items() if v}
49
+ uuids_bytes = {u: v for u, v in uuids_bytes.items() if v and v.get('usage', 0) > 0}
45
50
  uuids = uuids_bytes.keys()
46
51
  users = db.session.query(User).filter(User.uuid.in_(uuids))
47
- dbusers_bytes = {u: uuids_bytes.get(u.uuid, 0) for u in users}
52
+ dbusers_bytes = {u: uuids_bytes.get(u.uuid, {"usage": 0}) for u in users}
48
53
  _add_users_usage(dbusers_bytes, child_id, sync) # type: ignore
49
54
 
50
55
 
51
- def _reset_priodic_usage():
56
+ def _reset_priodic_usage() -> bool:
57
+ apply_changes = False
52
58
  last_usage_check: int = hconfig(ConfigEnum.last_priodic_usage_check) or 0
53
59
  import time
54
60
  current_time = int(time.time())
55
- if current_time - last_usage_check < 60 * 60 * 6:
56
- return
57
- # reset as soon as possible in the day
58
- if datetime.datetime.now().hour > 5 and current_time - last_usage_check < 60 * 60 * 24:
59
- return
61
+ # if current_time - last_usage_check < 60 * 60 * 6:
62
+ # return apply_changes
63
+ # # reset as soon as possible in the day
64
+ # if datetime.datetime.now().hour > 5 and current_time - last_usage_check < 60 * 60 * 24:
65
+ # return apply_changes
60
66
  logger.debug("reseting user usage if needed")
61
- for user in db.session.query(User).filter(User.mode != UserMode.no_reset).all():
67
+ # for user in db.session.query(User).filter(User.mode != UserMode.no_reset).all():
68
+ # if user.user_should_reset():
69
+ # logger.info(f"reseting user usage for {user.uuid}")
70
+ # user.reset_usage(commit=False)
71
+
72
+ today = datetime.date.today()
73
+
74
+ db_change = False
75
+ for user in db.session.query(User).filter(User.mode != UserMode.no_reset, User.start_date != None, User.start_date+User.package_days >= today).all():
62
76
  if user.user_should_reset():
63
77
  logger.info(f"reseting user usage for {user.uuid}")
78
+ old_active = user.is_active
64
79
  user.reset_usage(commit=False)
65
- set_hconfig(ConfigEnum.last_priodic_usage_check, current_time)
80
+ db_change = True
66
81
 
82
+ if not old_active and user.is_active:
83
+ logger.info(f"adding enabled client {user.uuid} ")
84
+ user_driver.add_client(user)
85
+ apply_changes = True
86
+ send_bot_message(user)
67
87
 
68
- def _add_users_usage(users_usage_data: Dict[User, Dict], child_id, sync=False):
69
- '''
70
- sync: when enabled, it means we have received usages from the parent panel
71
- '''
72
- res = {}
73
- have_change = False
88
+ if db_change:
89
+ db.session.commit()
90
+
91
+ for user in db.session.query(User).filter(User.start_date != None, User.start_date+User.package_days < today).all():
92
+ logger.info(f"Removing enabled client {user.uuid} ")
93
+ if not user.is_active:
94
+ user_driver.remove_client(user)
95
+ apply_changes = True
96
+
97
+ set_hconfig(ConfigEnum.last_priodic_usage_check, current_time, commit=True)
98
+ return apply_changes
99
+
100
+
101
+ def add_users_usage_new(usages: list[dict], child_id, sync=False):
102
+ usages = [use for use in usages if use['usage'] > 0]
103
+ # usages[0]['usage']=1000000000000
74
104
  before_enabled_users = user_driver.get_enabled_users()
75
105
 
76
106
  daily_usage = {}
77
107
  today = datetime.date.today()
78
- changes = False
108
+ db_changes = False
79
109
  for adm in db.session.query(AdminUser).all():
80
110
  daily_usage[adm.id] = db.session.query(DailyUsage).filter(DailyUsage.date == today, DailyUsage.admin_id == adm.id, DailyUsage.child_id == child_id).first()
81
111
  if daily_usage[adm.id] is None:
82
112
  logger.info(f"creating a new daily usage {today} admin={adm.id} child={child_id}")
83
- daily_usage[adm.id] = DailyUsage(date=today, admin_id=adm.id, child_id=child_id)
113
+ daily_usage[adm.id] = DailyUsage(date=today, admin_id=adm.id, child_id=child_id, usage=0)
84
114
  db.session.add(daily_usage[adm.id])
85
- changes = True
115
+ db_changes = True
86
116
  daily_usage[adm.id].online = db.session.query(User).filter(User.added_by == adm.id).filter(func.DATE(User.last_online) == today).count()
87
- if changes:
117
+ if db_changes:
88
118
  db.session.commit()
89
- _reset_priodic_usage()
90
-
91
- # userDetails = {p.user_id: p for p in UserDetail.query.filter(UserDetail.child_id == child_id).all()}
92
- for user, uinfo in users_usage_data.items():
93
- usage_bytes = uinfo['usage']
94
-
95
- # UserDetails things
96
- # detail = UserDetail(user_id=user.id, child_id=child_id)
97
- # detail = userDetails.get(user.id)
98
- # if not detail:
99
- # detail = UserDetail(user_id=user.id, child_id=child_id)
100
- # db.session.add(detail)
101
- # if uinfo['devices'] != detail.connected_devices:
102
- # detail.connected_devices = uinfo['devices']
103
-
104
- # Enable the user if isn't already
105
- if not before_enabled_users[user.uuid] and user.is_active:
119
+
120
+ apply_changes = _reset_priodic_usage()
121
+
122
+ db_execute("CALL add_usage_json(:usage_data)", usage_data=json.dumps(usages), commit=True)
123
+
124
+ usage_map = {u['uuid']: u for u in usages}
125
+
126
+ users = db.session.query(User).filter(User.uuid.in_(set(usage_map.keys()))).all()
127
+
128
+ all_users_uuids = set()
129
+ for user in users:
130
+ all_users_uuids.add(user.uuid)
131
+
132
+ user_before_active = before_enabled_users.get(user.uuid,False)
133
+ user_active = user.is_active
134
+
135
+ if not user_before_active and user_active:
106
136
  logger.info(f"Enabling disabled client {user.uuid} ")
107
137
  user_driver.add_client(user)
108
138
  send_bot_message(user)
109
- have_change = True
110
-
111
- # Check if there's new usage value
112
- if not isinstance(usage_bytes, int) or usage_bytes == 0:
113
- res[user.uuid] = "No usage"
114
- else:
115
- # Set new daily usage of the user
116
- if sync and daily_usage.get(user.added_by, daily_usage[1]).usage != usage_bytes:
117
- daily_usage.get(user.added_by, daily_usage[1]).usage = usage_bytes
118
- else:
119
- daily_usage.get(user.added_by, daily_usage[1]).usage += usage_bytes
120
-
121
- in_bytes = usage_bytes
122
-
123
- # Set new current usage of the user
124
- if sync and user.current_usage != in_bytes:
125
- user.current_usage = in_bytes
126
- # detail.current_usage_GB = in_gig
127
- else:
128
- user.current_usage += in_bytes
129
- # detail.current_usage = detail.current_usage or 0
130
- # detail.current_usage += in_bytes
131
-
132
- # Change last online time of the user
133
- user.last_online = datetime.datetime.now()
134
- # detail.last_online = datetime.datetime.now()
135
-
136
- # Set start date of user to the current datetime if it hasn't been set already
137
- if user.start_date is None:
138
- user.start_date = datetime.date.today()
139
-
140
- res[user.uuid] = f'{in_bytes/1000000:0.3f}MB'
141
-
142
- # Remove user from drivers(singbox, xray, wireguard etc.) if they're inactive
143
- # print(before_enabled_users[user.uuid], user.is_active)
144
- if before_enabled_users[user.uuid] and not user.is_active:
139
+ apply_changes = True
140
+ elif user_before_active and not user_active:
145
141
  logger.info(f"Removing enabled client {user.uuid} ")
146
-
147
142
  user_driver.remove_client(user)
148
- have_change = True
149
- res[user.uuid] = f"{res[user.uuid]} !OUT of USAGE! Client Removed"
143
+ send_bot_message(user)
144
+ apply_changes = True
145
+
146
+ daily_usage.get(user.added_by, daily_usage[1]).usage += usage_map[user.uuid]['usage']
150
147
 
151
- db.session.commit() # type: ignore
148
+ db.session.commit()
152
149
 
153
- # Remove invalid users
154
- for uuid in before_enabled_users:
155
- if uuid in res:
156
- continue
150
+ if len(users) != len(usage_map):
151
+ # check for zombie-users
152
+ check_users = set(before_enabled_users.keys())-all_users_uuids
153
+ all_db_users = {u.uuid for u in db.session.query(User).filter(User.uuid.in_(check_users)).all()}
154
+ zombie_users = check_users-all_db_users
157
155
 
158
- user = db.session.query(User).filter(User.uuid == uuid).first()
159
- if not user:
156
+ for uuid in zombie_users:
157
+ logger.info(f"Remove zombiee users {uuid} ")
160
158
  user_driver.remove_client(User(uuid=uuid))
161
- elif not user.is_active:
162
- user_driver.remove_client(user)
159
+ apply_changes = True
163
160
 
164
- # print("------------------", res)
165
- # Apply the changes to the drivers
166
- if have_change:
161
+ if apply_changes:
167
162
  hiddify.quick_apply_users()
168
163
 
169
- # 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
170
- if not sync and hutils.node.is_child():
171
- hutils.node.child.sync_users_usage_with_parent()
164
+ return {"status": 'success', "comments": usages, "date": hutils.convert.time_to_json(datetime.datetime.now())}
165
+
166
+
167
+ # def _add_users_usage(users_usage_data: Dict[User, Dict], child_id, sync=False):
168
+ # '''
169
+ # sync: when enabled, it means we have received usages from the parent panel
170
+ # '''
171
+ # res = {}
172
+ # have_change = False
173
+ # before_enabled_users = user_driver.get_enabled_users()
174
+ # daily_usage = {}
175
+ # today = datetime.date.today()
176
+ # changes = False
177
+ # for adm in db.session.query(AdminUser).all():
178
+ # daily_usage[adm.id] = db.session.query(DailyUsage).filter(DailyUsage.date == today, DailyUsage.admin_id == adm.id, DailyUsage.child_id == child_id).first()
179
+ # if daily_usage[adm.id] is None:
180
+ # logger.info(f"creating a new daily usage {today} admin={adm.id} child={child_id}")
181
+ # daily_usage[adm.id] = DailyUsage(date=today, admin_id=adm.id, child_id=child_id)
182
+ # db.session.add(daily_usage[adm.id])
183
+ # changes = True
184
+ # daily_usage[adm.id].online = db.session.query(User).filter(User.added_by == adm.id).filter(func.DATE(User.last_online) == today).count()
185
+ # if changes:
186
+ # db.session.commit()
187
+ # _reset_priodic_usage()
188
+
189
+ # # userDetails = {p.user_id: p for p in UserDetail.query.filter(UserDetail.child_id == child_id).all()}
190
+ # for user, uinfo in users_usage_data.items():
191
+ # usage_bytes = uinfo['usage']
192
+
193
+ # # UserDetails things
194
+ # # detail = UserDetail(user_id=user.id, child_id=child_id)
195
+ # # detail = userDetails.get(user.id)
196
+ # # if not detail:
197
+ # # detail = UserDetail(user_id=user.id, child_id=child_id)
198
+ # # db.session.add(detail)
199
+ # # if uinfo['devices'] != detail.connected_devices:
200
+ # # detail.connected_devices = uinfo['devices']
201
+
202
+ # # Enable the user if isn't already
203
+ # if not before_enabled_users[user.uuid] and user.is_active:
204
+ # logger.info(f"Enabling disabled client {user.uuid} ")
205
+ # user_driver.add_client(user)
206
+ # send_bot_message(user)
207
+ # have_change = True
208
+
209
+ # # Check if there's new usage value
210
+ # if not isinstance(usage_bytes, int) or usage_bytes == 0:
211
+ # res[user.uuid] = "No usage"
212
+ # else:
213
+ # # Set new daily usage of the user
214
+ # if sync and daily_usage.get(user.added_by, daily_usage[1]).usage != usage_bytes:
215
+ # daily_usage.get(user.added_by, daily_usage[1]).usage = usage_bytes
216
+ # else:
217
+ # daily_usage.get(user.added_by, daily_usage[1]).usage += usage_bytes
218
+
219
+ # # Set new current usage of the user
220
+ # if sync and user.current_usage != usage_bytes:
221
+ # user.current_usage = usage_bytes
222
+ # # detail.current_usage_GB = in_gig
223
+ # else:
224
+ # user.current_usage += usage_bytes
225
+ # # detail.current_usage = detail.current_usage or 0
226
+ # # detail.current_usage += usage_bytes
227
+
228
+ # # Change last online time of the user
229
+ # user.last_online = datetime.datetime.now()
230
+ # # detail.last_online = datetime.datetime.now()
231
+
232
+ # # Set start date of user to the current datetime if it hasn't been set already
233
+ # if user.start_date is None:
234
+ # user.start_date = datetime.date.today()
235
+
236
+ # res[user.uuid] = f'{usage_bytes/1000000:0.3f}MB'
237
+
238
+ # # Remove user from drivers(singbox, xray, wireguard etc.) if they're inactive
239
+ # # print(before_enabled_users[user.uuid], user.is_active)
240
+ # if before_enabled_users[user.uuid] and not user.is_active:
241
+ # logger.info(f"Removing enabled client {user.uuid} ")
242
+
243
+ # user_driver.remove_client(user)
244
+ # have_change = True
245
+ # res[user.uuid] = f"{res[user.uuid]} !OUT of USAGE! Client Removed"
246
+
247
+ # db.session.commit() # type: ignore
248
+
249
+ # # Remove invalid users
250
+ # for uuid in before_enabled_users:
251
+ # if uuid in res:
252
+ # continue
253
+
254
+ # user = db.session.query(User).filter(User.uuid == uuid).first()
255
+ # if not user:
256
+ # user_driver.remove_client(User(uuid=uuid))
257
+ # elif not user.is_active:
258
+ # user_driver.remove_client(user)
259
+
260
+ # # print("------------------", res)
261
+ # # Apply the changes to the drivers
262
+ # if have_change:
263
+ # hiddify.quick_apply_users()
264
+
265
+ # # 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
266
+ # if not sync and hutils.node.is_child():
267
+ # hutils.node.child.sync_users_usage_with_parent()
172
268
 
173
- return {"status": 'success', "comments": res, "date": hutils.convert.time_to_json(datetime.datetime.now())}
269
+ # return {"status": 'success', "comments": res, "date": hutils.convert.time_to_json(datetime.datetime.now())}
174
270
 
175
271
 
176
272
  def send_bot_message(user):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hiddifypanel
3
- Version: 10.85.0b12
3
+ Version: 10.85.0b14
4
4
  Summary: hiddifypanel multi proxy panel
5
5
  Author: hiddify
6
6
  License:
@@ -1,6 +1,6 @@
1
1
  hiddifypanel/Events.py,sha256=AlnRdjVul0jP-NCT4-zoaQgowoOo-JhdQB4ytetAFKA,723
2
- hiddifypanel/VERSION,sha256=82CenBoXd8N2vI9rzI4bIe63RTxHolQONAAbAhmWwIk,11
3
- hiddifypanel/VERSION.py,sha256=gJ3sInUzqFfnqSMFjSVEUSVljyx81zsMNzsxAEOI3OQ,190
2
+ hiddifypanel/VERSION,sha256=sKg8vnG-EE180T6hI9d4-w7XaYJpEtaQYHVpwLWzAb4,12
3
+ hiddifypanel/VERSION.py,sha256=RK10T65MIHlBlviFHQpfipvyKaXCPNJaKomq-NPI6Fs,191
4
4
  hiddifypanel/__init__.py,sha256=kigwDO8d9jXyPZLvJAWd6zo-GX3pG_xWf-q2aStz80Y,377
5
5
  hiddifypanel/__main__.py,sha256=IVchnXpK6bm8T3N--mN17HBQNLMeLAjyP7iwzULexB4,218
6
6
  hiddifypanel/auth.py,sha256=LJmH4ROqZv5ej_4m1b0xvbEw2meJTzDR1mFCDm523kE,8041
@@ -8,24 +8,24 @@ hiddifypanel/base.py,sha256=LwEQPv6QVLY6V9EUQ_-C_Sg0p64QRajLTauACIc4m-w,2705
8
8
  hiddifypanel/base_setup.py,sha256=W64zORy8BRukhpRXMnCb-SMejQc3bmuwtyWbanGiJ3M,2802
9
9
  hiddifypanel/cache.py,sha256=YBogDyZ0jze8IIdPi34YYcbYF4iHsZgpfpt4gZLha0Q,1527
10
10
  hiddifypanel/celery.py,sha256=VdU2QOJwerd7pkCQLeDsLpfMWpdARtF7sEEJwud0SDY,3200
11
- hiddifypanel/database.py,sha256=cC3-zLl320B0pPlztiUtRdGyjBE64Qej5rYPeucNBR0,2614
11
+ hiddifypanel/database.py,sha256=OkFNG9RGdtk3ZXImYjlzpVey7weu5a4Yme3Vo3qsptk,2634
12
12
  hiddifypanel/apps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
13
  hiddifypanel/apps/asgi_app.py,sha256=Wz1rbJylXxkKthWQI5kUUXkePA8wZ37QO8g1sIbKG5o,181
14
14
  hiddifypanel/apps/celery_app.py,sha256=DWDtkbWuAVIJhStnzcTsyo3ker85gm7ZZHWm-Th7cvo,81
15
15
  hiddifypanel/apps/celery_app_flask.py,sha256=OlyAUzOOdTNLtNOT_V9hZgCCnTdFOppQaF6lqW58k7g,79
16
16
  hiddifypanel/apps/wsgi_app.py,sha256=lyqB3H5jvvfDqi5P34PDw64rsJKhwE8qwYbUiya89BQ,119
17
- hiddifypanel/drivers/abstract_driver.py,sha256=HpWkgbWVWF8n3WhDe0BlxsIF1WTWO57AQEAEb2ZVCPM,223
18
- hiddifypanel/drivers/singbox_api.py,sha256=-NNVJtDsZ_e2NhczaEm3Vw7kbiylPferI-NP7vTG84c,2307
19
- hiddifypanel/drivers/ssh_liberty_bridge_api.py,sha256=J6NvRW3GQbIc2PH3aVZr73YnTGECnWE9M9iFUhVrM20,2242
20
- hiddifypanel/drivers/user_driver.py,sha256=UC0PIjnptlJwbjdIUv5zqFm4R989CoeH44BoZD7adaI,2786
21
- hiddifypanel/drivers/wireguard_api.py,sha256=pFAFe-mPUjFOM6DYx-PWhmAQrUPU2D-2aI2qe1VkB78,3619
22
- hiddifypanel/drivers/xray_api.py,sha256=fade_jDRaXRQamE_9pVKcp9ZqQBVRujEEgmIWJwqrXE,5916
17
+ hiddifypanel/drivers/abstract_driver.py,sha256=BWHWA-5btPTyUSRm0n4gulqeqA4CsEnVY3bV5OOO8U0,216
18
+ hiddifypanel/drivers/singbox_api.py,sha256=dropF7Cfi4i9fMit2omSZ62IQv30B70oH6MZfgfj5bY,2243
19
+ hiddifypanel/drivers/ssh_liberty_bridge_api.py,sha256=urB2HzFUhQQytbPz28_rMgOthwVGip2Od_tsXVbVHWQ,2263
20
+ hiddifypanel/drivers/user_driver.py,sha256=XYgvmXe2CiLAUnX3weldnGHcb8aDwQqF7TIPRjSUnIM,2856
21
+ hiddifypanel/drivers/wireguard_api.py,sha256=qeuh835tSA75npJ2AzW9NhRlAKtaljX1qK-Wjugd5bI,4291
22
+ hiddifypanel/drivers/xray_api.py,sha256=Kk7FDpNFITP085gx_yLqM7hw5vCpoF2bATrZYCXy_JE,7174
23
23
  hiddifypanel/hutils/__init__.py,sha256=P0JLvth-yJza_xpSGWlU7Hmtj_Ym9oyDimo4YpURdLQ,1447
24
24
  hiddifypanel/hutils/auth.py,sha256=Ci3_lBfLXx1yi2M6HvYX3ceHYtOf-cfX092evcs8528,3030
25
25
  hiddifypanel/hutils/convert.py,sha256=mPEDzR64hKeQ4B_tZRk2Ci8-Ybod0bjX0BbxLHOmLZA,2075
26
26
  hiddifypanel/hutils/crypto.py,sha256=C32Wj-SpjOSycmmnWD0q8g_ZG4puzJNX9T9hSkTut08,2591
27
27
  hiddifypanel/hutils/encode.py,sha256=-A1VknNmtsw-YfV-h8mz6QLtlYjg2MB_ZWbUj6MSil4,837
28
- hiddifypanel/hutils/flask.py,sha256=5vc_VCl1ojcuDHvS7K_aXlimosv6qPLTZWsFbP6z3ZY,11919
28
+ hiddifypanel/hutils/flask.py,sha256=QE-u0FJpRdxVtg4u9hYkFafEvvL3-EwmqB46LfAKoX8,11949
29
29
  hiddifypanel/hutils/github_issue.py,sha256=LSJCDVC_UEFDIXG4JdQReLDUnF_WmfhH7eFB-iZKwdg,6609
30
30
  hiddifypanel/hutils/model.py,sha256=ajlJ-Tx0Mq68S9y5qEj0lwlDbF2xj0niZBQyw7UU670,1320
31
31
  hiddifypanel/hutils/random.py,sha256=KrsarmRNL05PYzwMxDaDyv-_QcKS0YsZR2z7BnllAqI,1789
@@ -44,7 +44,7 @@ hiddifypanel/hutils/node/parent.py,sha256=UbyfvfP4fTSn6HN9oZDjYsKYIejiqW6eApKIfP
44
44
  hiddifypanel/hutils/node/shared.py,sha256=FDSj3e-i3pb3mEv5vcUeX0Km1nxYg1CeAruIq7RwFmU,2540
45
45
  hiddifypanel/hutils/proxy/__init__.py,sha256=V2dGkYT3tji__5YOSmKOMChFYXtlENe1fX6eHqK70Pc,129
46
46
  hiddifypanel/hutils/proxy/clash.py,sha256=V9Y2UIw-CYTXD_Q73Oeq3WLw6chsPrMIiNxYnlWyNbg,7065
47
- hiddifypanel/hutils/proxy/shared.py,sha256=Zg4qMqp-fzWXHMqZsE1stluVDgT71AlFVlJAxQwMtfQ,22203
47
+ hiddifypanel/hutils/proxy/shared.py,sha256=MRfdDjfijZxJwoUA6wg9FROdf_wFp9JuJZgMo8SaLnM,22261
48
48
  hiddifypanel/hutils/proxy/singbox.py,sha256=Fmmzoake-gpnRB5yfTyQvd1dB-10WKwhJt4vhiKzJZQ,11722
49
49
  hiddifypanel/hutils/proxy/wireguard.py,sha256=gij01BYXII-RxAh3Yky0o3yce20HJKeHtu1KNwb0Uzk,934
50
50
  hiddifypanel/hutils/proxy/xray.py,sha256=0vEHL9yq5Si7W_fgI8tn9zwNAWhwBE7djun05cAUDF8,10808
@@ -72,9 +72,9 @@ hiddifypanel/panel/common.py,sha256=pMxdgt37ubIZroFBuvHfN5qXNp8kytVTIzVxzZA_X_I,
72
72
  hiddifypanel/panel/custom_widgets.py,sha256=_zA0WZRZOCyh6Z1gW62aRQLMAOM_m85B2oZoIOU59Ys,2637
73
73
  hiddifypanel/panel/hiddify.py,sha256=nwLTMYa_LyNuS26BPOO8jfyrslHX2MbQxN0o4lxCTd4,15687
74
74
  hiddifypanel/panel/hlogger.py,sha256=1AQQCs1lg0Y1AYIASRjxWAdFE92HENeg3z1rFycOoY0,1215
75
- hiddifypanel/panel/init_db.py,sha256=YfBn1c_NSvGFKC9JemUdsFrIIFAnhRGtVIMzLk_yAd0,38564
75
+ hiddifypanel/panel/init_db.py,sha256=eBh6K35zfxrvUA7t3UrIeo704ELx7lgl4NGEqfj08WQ,40133
76
76
  hiddifypanel/panel/run_commander.py,sha256=cXCFVvZ6iTzab3EOZ-Eq3aOeIqfgzgt2ppNaxm_3OJI,3205
77
- hiddifypanel/panel/usage.py,sha256=o_VPALSd_40m_PEpNnCUggP2sZXXVf1mNjHI249cJOY,7437
77
+ hiddifypanel/panel/usage.py,sha256=Cz7KRJkHIQ6XEkbzwbL4GIzG5ycT-bCDqYmsJK0Gsxk,11923
78
78
  hiddifypanel/panel/admin/Actions.py,sha256=o_ENbphriVrbRJkx9nvrkpaliuMIfp34sscMkZJ3P5s,8578
79
79
  hiddifypanel/panel/admin/AdminstratorAdmin.py,sha256=X8MI3DtW62vJqFRp97M_CxSdB-NFNMlZOSDsd5hn8HA,10482
80
80
  hiddifypanel/panel/admin/Backup.py,sha256=BKSoAZgw1j16P1Jh9vMqGj7ZfB2m-WafDK0C5vil5FY,3634
@@ -842,17 +842,17 @@ hiddifypanel/templates/redirect.html,sha256=K9x_O4P96vEkqBhOXIhoGrWw1KIqd2bL0BjI
842
842
  hiddifypanel/templates/static.html,sha256=jp6q4wtx-k2A_cjqJoNiMS7Ee30arE45qI3ev4d5ky4,165
843
843
  hiddifypanel/templates/hiddify-flask-admin/actions.html,sha256=2NeITe2e-lPKCk_o511tCIqVtrPu8LYHE1wTCtrFUrI,1331
844
844
  hiddifypanel/templates/hiddify-flask-admin/list.html,sha256=MBGrTqZpzNLe4sZy0RozvXNr8seFUQc2C6v88BJtNWc,11095
845
- hiddifypanel/translations/en/LC_MESSAGES/messages.mo,sha256=cBQIYGzYgsQBRoT-74-vW71ysHvuECbuUo2r2czTWdY,80248
845
+ hiddifypanel/translations/en/LC_MESSAGES/messages.mo,sha256=GncyHCVs1iGb4Vs7YRNX9bB1RQlQXBnbcslMKTr74uE,80248
846
846
  hiddifypanel/translations/en/LC_MESSAGES/messages.po,sha256=ZQn1-QHu0FwpHIluExU9U1p738DrpK_vBaucF1ndyvo,83564
847
- hiddifypanel/translations/fa/LC_MESSAGES/messages.mo,sha256=Np947t6zwnP8qSyc6hsGl5kT7DhgAn3mV0ikZ0zU4Cs,102665
847
+ hiddifypanel/translations/fa/LC_MESSAGES/messages.mo,sha256=155uBY6WCbMhRCW8VzOgd5OoQMeO7Awt1b6tuOTGhQg,102665
848
848
  hiddifypanel/translations/fa/LC_MESSAGES/messages.po,sha256=xEU_jSeWnc70mvoPJ7f0d_9lG5U-mvo3BQzcRRvB4o8,107420
849
- hiddifypanel/translations/my/LC_MESSAGES/messages.mo,sha256=69DQSUGkaW-9tSgyvHNUVnGFof0HsQQhLwK9dMCNFCo,139090
849
+ hiddifypanel/translations/my/LC_MESSAGES/messages.mo,sha256=S0gROMPq8mtA0QxWpf2trNWvf_PxBElc6ZaGIQolC1w,139090
850
850
  hiddifypanel/translations/my/LC_MESSAGES/messages.po,sha256=GcrOHDJC2wjo1L8lLmwLOs_dwoqcf0f6rXXjDv8JnsE,141985
851
- hiddifypanel/translations/pt/LC_MESSAGES/messages.mo,sha256=s_KKmA26HVmsB6qJIcHDLGCDfniyf9DZ__XS_foPzGw,80797
851
+ hiddifypanel/translations/pt/LC_MESSAGES/messages.mo,sha256=8pIwiaprVdRPIX6ND1ZGOtqtNn4RvZVFlVfTKWdKTs0,80797
852
852
  hiddifypanel/translations/pt/LC_MESSAGES/messages.po,sha256=pX1dG9wcQEYRBpxU3mL-gY_q2YNkVwbRmrI6wNHjkm8,85364
853
- hiddifypanel/translations/ru/LC_MESSAGES/messages.mo,sha256=85ZcJyBWVKPk2u--UJtbEqQVeOf5oxyOA5t0Xaf1c6k,108594
853
+ hiddifypanel/translations/ru/LC_MESSAGES/messages.mo,sha256=qtQ7GqRYXF1YCTk0ys9y5dlESPrE6npvzmJVjDm2iYA,108594
854
854
  hiddifypanel/translations/ru/LC_MESSAGES/messages.po,sha256=mc8TFxwLUu8iBH8iK-aGUpQoKDRCjb2sh6fZK1w1inU,113367
855
- hiddifypanel/translations/zh/LC_MESSAGES/messages.mo,sha256=X3lqgn-O1ZZJ89lwpvYLQhijeD-iVWTW40SBJdm0oRU,75296
855
+ hiddifypanel/translations/zh/LC_MESSAGES/messages.mo,sha256=cqDYHg2b5nAcsj0As6V2wQ4mmyV1Dt1hmiiub8jdKQs,75296
856
856
  hiddifypanel/translations/zh/LC_MESSAGES/messages.po,sha256=ze5ipeKdB4ISOMkT0ERufBPdlWDT5m3xUj0OSeBdkpk,79474
857
857
  hiddifypanel/translations.i18n/en.json,sha256=4rDoVjPSUqs8OCh8dHf923wjHmgJNguelTN3b1EwLqA,72605
858
858
  hiddifypanel/translations.i18n/fa.json,sha256=3WMq36rLV-1E9GTVTovDlEPIoOJ5cedOBDdke-OcyXE,96515
@@ -861,9 +861,9 @@ hiddifypanel/translations.i18n/my.json,sha256=QAXg__5TCqfx3Ck0cw5YQsSXX_3kqUhMOd
861
861
  hiddifypanel/translations.i18n/pt.json,sha256=bO1-qEZJ9yf6Pz_6Vz18xDLezp3pCYwK8_aTLMJA1Iw,74570
862
862
  hiddifypanel/translations.i18n/ru.json,sha256=kCI1TBgj1T0VnvRfrgxCnLt_fA6ZuD9kjRt0vkmyvb0,102357
863
863
  hiddifypanel/translations.i18n/zh.json,sha256=A50l-_uQ4lsZwcF3UyGcyZxsR3hAjmvnMrJLnTcVZbI,69103
864
- hiddifypanel-10.85.0b12.dist-info/licenses/LICENSE.md,sha256=oDrt-cUsyiDGnRPjEJh-3dH2ddAuK_bIVBD8ntkOtZw,19807
865
- hiddifypanel-10.85.0b12.dist-info/METADATA,sha256=WeRn6SOBIZlgIYl1miQNggtAlHLhEprpt5WiMXtAoik,25628
866
- hiddifypanel-10.85.0b12.dist-info/WHEEL,sha256=ck4Vq1_RXyvS4Jt6SI0Vz6fyVs4GWg7AINwpsaGEgPE,91
867
- hiddifypanel-10.85.0b12.dist-info/entry_points.txt,sha256=Xzpqlh3nwBtZhoV9AANJykano056VJvYzaujxPztJaM,60
868
- hiddifypanel-10.85.0b12.dist-info/top_level.txt,sha256=rv-b3qFWUZQTBy0kyBfsr7L6tPpeO7AaQlLHXn-HI5M,13
869
- hiddifypanel-10.85.0b12.dist-info/RECORD,,
864
+ hiddifypanel-10.85.0b14.dist-info/licenses/LICENSE.md,sha256=oDrt-cUsyiDGnRPjEJh-3dH2ddAuK_bIVBD8ntkOtZw,19807
865
+ hiddifypanel-10.85.0b14.dist-info/METADATA,sha256=VqhyecYpa68_bak3AEuGmztdV0EjbDgGQcsI0W5PMf0,25628
866
+ hiddifypanel-10.85.0b14.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
867
+ hiddifypanel-10.85.0b14.dist-info/entry_points.txt,sha256=Xzpqlh3nwBtZhoV9AANJykano056VJvYzaujxPztJaM,60
868
+ hiddifypanel-10.85.0b14.dist-info/top_level.txt,sha256=rv-b3qFWUZQTBy0kyBfsr7L6tPpeO7AaQlLHXn-HI5M,13
869
+ hiddifypanel-10.85.0b14.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.0.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5