hiddifypanel 10.30.6.dev0__py3-none-any.whl → 10.30.7.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.
- hiddifypanel/VERSION +1 -1
- hiddifypanel/VERSION.py +2 -2
- hiddifypanel/drivers/singbox_api.py +2 -0
- hiddifypanel/drivers/ssh_liberty_bridge_api.py +6 -0
- hiddifypanel/drivers/user_driver.py +5 -2
- hiddifypanel/drivers/xray_api.py +16 -2
- hiddifypanel/hutils/proxy/xray.py +1 -1
- hiddifypanel/hutils/proxy/xrayjson.py +20 -22
- hiddifypanel/panel/admin/DomainAdmin.py +1 -1
- hiddifypanel/panel/admin/UserAdmin.py +20 -11
- hiddifypanel/panel/usage.py +27 -16
- hiddifypanel/translations.i18n/en.json +74 -33
- hiddifypanel/translations.i18n/fa.json +80 -118
- hiddifypanel/translations.i18n/pt.json +68 -315
- hiddifypanel/translations.i18n/ru.json +73 -168
- hiddifypanel/translations.i18n/zh.json +68 -296
- {hiddifypanel-10.30.6.dev0.dist-info → hiddifypanel-10.30.7.dev0.dist-info}/METADATA +1 -1
- {hiddifypanel-10.30.6.dev0.dist-info → hiddifypanel-10.30.7.dev0.dist-info}/RECORD +22 -22
- {hiddifypanel-10.30.6.dev0.dist-info → hiddifypanel-10.30.7.dev0.dist-info}/LICENSE.md +0 -0
- {hiddifypanel-10.30.6.dev0.dist-info → hiddifypanel-10.30.7.dev0.dist-info}/WHEEL +0 -0
- {hiddifypanel-10.30.6.dev0.dist-info → hiddifypanel-10.30.7.dev0.dist-info}/entry_points.txt +0 -0
- {hiddifypanel-10.30.6.dev0.dist-info → hiddifypanel-10.30.7.dev0.dist-info}/top_level.txt +0 -0
hiddifypanel/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
10.30.
|
1
|
+
10.30.7.dev0
|
hiddifypanel/VERSION.py
CHANGED
@@ -1,3 +1,3 @@
|
|
1
|
-
__version__='10.30.
|
1
|
+
__version__='10.30.7.dev0'
|
2
2
|
from datetime import datetime
|
3
|
-
__release_date__= datetime.strptime('2024-07-
|
3
|
+
__release_date__= datetime.strptime('2024-07-09','%Y-%m-%d')
|
@@ -4,6 +4,7 @@ from .abstract_driver import DriverABS
|
|
4
4
|
from flask import current_app
|
5
5
|
import json
|
6
6
|
from collections import defaultdict
|
7
|
+
from hiddifypanel.cache import cache
|
7
8
|
|
8
9
|
|
9
10
|
class SingboxApi(DriverABS):
|
@@ -18,6 +19,7 @@ class SingboxApi(DriverABS):
|
|
18
19
|
json_data = json.load(f)
|
19
20
|
return {u.split("@")[0]: 1 for u in json_data['experimental']['v2ray_api']['stats']['users']}
|
20
21
|
|
22
|
+
@cache.cache(ttl=300)
|
21
23
|
def get_inbound_tags(self):
|
22
24
|
try:
|
23
25
|
xray_client = self.get_singbox_client()
|
@@ -29,6 +29,12 @@ class SSHLibertyBridgeApi(DriverABS):
|
|
29
29
|
|
30
30
|
def remove_client(self, user):
|
31
31
|
redis_client = self.get_ssh_redis_client()
|
32
|
+
if user.ed25519_public_key is None:
|
33
|
+
members = redis_client.smembers(USERS_SET)
|
34
|
+
for member in members:
|
35
|
+
if member.startswith(user.uuid):
|
36
|
+
redis_client.srem(USERS_SET, member)
|
37
|
+
|
32
38
|
redis_client.srem(USERS_SET, f'{user.uuid}::{user.ed25519_public_key}')
|
33
39
|
redis_client.hdel(USERS_USAGE, f'{user.uuid}')
|
34
40
|
redis_client.save()
|
@@ -33,16 +33,19 @@ def get_enabled_users():
|
|
33
33
|
for driver in enabled_drivers():
|
34
34
|
try:
|
35
35
|
for u, v in driver.get_enabled_users().items():
|
36
|
+
# print(u, "enabled", v, driver)
|
36
37
|
if not v:
|
37
38
|
continue
|
38
39
|
d[u] += 1
|
39
40
|
total += 1
|
40
41
|
except Exception as e:
|
42
|
+
print(driver)
|
41
43
|
hiddify.error(f'ERROR! {driver.__class__.__name__} has error in get_enabled users')
|
42
|
-
|
44
|
+
# print(d, total)
|
43
45
|
res = defaultdict(bool)
|
44
46
|
for u, v in d.items():
|
45
|
-
res[u] = v >= total # ignore singbox
|
47
|
+
# res[u] = v >= total # ignore singbox
|
48
|
+
res[u] = v >= 1
|
46
49
|
return res
|
47
50
|
|
48
51
|
|
hiddifypanel/drivers/xray_api.py
CHANGED
@@ -2,6 +2,7 @@ import xtlsapi
|
|
2
2
|
from hiddifypanel.models import *
|
3
3
|
from .abstract_driver import DriverABS
|
4
4
|
from collections import defaultdict
|
5
|
+
from hiddifypanel.cache import cache
|
5
6
|
|
6
7
|
|
7
8
|
class XrayApi(DriverABS):
|
@@ -19,7 +20,18 @@ class XrayApi(DriverABS):
|
|
19
20
|
if "user>>>" not in use.name:
|
20
21
|
continue
|
21
22
|
uuid = use.name.split(">>>")[1].split("@")[0]
|
22
|
-
|
23
|
+
try:
|
24
|
+
t = "xtls"
|
25
|
+
protocol = "vless"
|
26
|
+
xray_client.add_client(t, f'{uuid}', f'{uuid}@hiddify.com', protocol=protocol, flow='xtls-rprx-vision', alter_id=0, cipher='chacha20_poly1305')
|
27
|
+
xray_client.remove_client(t, f'{uuid}@hiddify.com')
|
28
|
+
res[uuid] = 0
|
29
|
+
except xtlsapi.xtlsapi.exceptions.EmailAlreadyExists as e:
|
30
|
+
res[uuid] = 1
|
31
|
+
except Exception as e:
|
32
|
+
print(f"error {e}")
|
33
|
+
res[uuid] = 0
|
34
|
+
|
23
35
|
return res
|
24
36
|
|
25
37
|
# xray_client = self.get_xray_client()
|
@@ -40,10 +52,11 @@ class XrayApi(DriverABS):
|
|
40
52
|
# enabled[uuid] = e
|
41
53
|
# return enabled
|
42
54
|
|
55
|
+
# @cache.cache(ttl=300)
|
43
56
|
def get_inbound_tags(self):
|
44
57
|
try:
|
45
58
|
xray_client = self.get_xray_client()
|
46
|
-
inbounds = [inb.name.split(">>>")[1] for inb in xray_client.stats_query('
|
59
|
+
inbounds = [inb.name.split(">>>")[1] for inb in xray_client.stats_query('')]
|
47
60
|
print(f"Success in get inbound tags {inbounds}")
|
48
61
|
except Exception as e:
|
49
62
|
print(f"error in get inbound tags {e}")
|
@@ -65,6 +78,7 @@ class XrayApi(DriverABS):
|
|
65
78
|
'v2ray': 'shadowsocks',
|
66
79
|
'kcp': 'vless',
|
67
80
|
'dispatcher': 'trojan',
|
81
|
+
'reality': 'vless'
|
68
82
|
}
|
69
83
|
|
70
84
|
def proto(t):
|
@@ -82,7 +82,7 @@ def to_link(proxy: dict) -> str | dict:
|
|
82
82
|
return "ShadowTLS is Not Supported for this platform"
|
83
83
|
# return f'{baseurl}?plugin=v2ray-plugin&path={proxy["proxy_path"]}&host={proxy["fakedomain"]}&udp-over-tcp=true#{name_link}'
|
84
84
|
if proxy['proto'] == 'v2ray':
|
85
|
-
return f'{baseurl}?plugin=v2ray-plugin&mode=websocket&path={proxy["proxy_path"]}&host={proxy["
|
85
|
+
return f'{baseurl}?plugin=v2ray-plugin&mode=websocket&path={proxy["proxy_path"]}&host={proxy["sni"]}&tls&udp-over-tcp=true#{name_link}'
|
86
86
|
|
87
87
|
if proxy['proto'] == 'tuic':
|
88
88
|
baseurl = f'tuic://{proxy["uuid"]}:{proxy["uuid"]}@{proxy["server"]}:{proxy["port"]}?congestion_control=cubic&udp_relay_mode=native&sni={proxy["sni"]}&alpn=h3'
|
@@ -8,7 +8,7 @@ from hiddifypanel.models import hconfig, ConfigEnum
|
|
8
8
|
from .xray import is_muxable_agent, OUTBOUND_LEVEL
|
9
9
|
|
10
10
|
|
11
|
-
def configs_as_json(domains: list[Domain], user: User, expire_days: int,
|
11
|
+
def configs_as_json(domains: list[Domain], user: User, expire_days: int, remarks: str) -> str:
|
12
12
|
'''Returns xray configs as json'''
|
13
13
|
all_configs = []
|
14
14
|
|
@@ -35,50 +35,48 @@ def configs_as_json(domains: list[Domain], user: User, expire_days: int, remark
|
|
35
35
|
)
|
36
36
|
# endregion
|
37
37
|
|
38
|
-
# region show status (active/disable)
|
39
|
-
active = True
|
40
38
|
if not user.is_active:
|
39
|
+
# region show status (active/disable)
|
41
40
|
tag = '✖ ' + (hutils.encode.url_encode('بسته شما به پایان رسید') if hconfig(ConfigEnum.lang) == 'fa' else 'Package Ended')
|
42
41
|
# add user status
|
43
42
|
all_configs.append(
|
44
43
|
null_config(tag)
|
45
44
|
)
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
if active:
|
45
|
+
# endregion
|
46
|
+
else:
|
50
47
|
# TODO: seperate codes to small functions
|
51
48
|
# TODO: check what are unsupported protocols in other apps
|
52
|
-
unsupported_protos =
|
53
|
-
unsupported_transport =
|
49
|
+
unsupported_protos = {}
|
50
|
+
unsupported_transport = {}
|
54
51
|
if g.user_agent.get('is_v2rayng'):
|
55
52
|
# TODO: ensure which protocols are not supported in v2rayng
|
56
|
-
unsupported_protos =
|
53
|
+
unsupported_protos = {ProxyProto.wireguard, ProxyProto.hysteria, ProxyProto.hysteria2,
|
54
|
+
ProxyProto.tuic, ProxyProto.ss, ProxyProto.ssr, ProxyProto.ssh}
|
57
55
|
if not hutils.flask.is_client_version(hutils.flask.ClientVersion.v2ryang, 1, 8, 18):
|
58
|
-
unsupported_transport =
|
56
|
+
unsupported_transport = {ProxyTransport.httpupgrade}
|
59
57
|
|
60
58
|
# multiple outbounds needs multiple whole base config not just one with multiple outbounds (at least for v2rayng)
|
61
59
|
# https://github.com/2dust/v2rayNG/pull/2827#issue-2127534078
|
62
60
|
outbounds = []
|
63
61
|
for proxy in hutils.proxy.get_valid_proxies(domains):
|
64
|
-
if
|
62
|
+
if proxy['proto'] in unsupported_protos:
|
65
63
|
continue
|
66
|
-
if
|
64
|
+
if proxy['transport'] in unsupported_transport:
|
67
65
|
continue
|
68
66
|
outbound = to_xray(proxy)
|
69
|
-
|
70
|
-
outbounds.append(outbound)
|
67
|
+
outbounds.append(outbound)
|
71
68
|
|
72
69
|
base_config = json.loads(render_template('base_xray_config.json.j2', remarks=remarks))
|
73
70
|
if len(outbounds) > 1:
|
74
71
|
for out in outbounds:
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
72
|
+
base = copy.deepcopy(base_config)
|
73
|
+
base['remarks'] = out['tag']
|
74
|
+
base['outbounds'].insert(0, out)
|
75
|
+
# if all_configs:
|
76
|
+
# all_configs.insert(0, copy.deepcopy(base_config))
|
77
|
+
# else:
|
78
|
+
all_configs.append(base)
|
79
|
+
|
82
80
|
else: # single outbound
|
83
81
|
base_config['outbounds'].insert(0, outbounds[0])
|
84
82
|
all_configs = base_config
|
@@ -114,7 +114,7 @@ class DomainAdmin(AdminLTEModelView):
|
|
114
114
|
if not dip:
|
115
115
|
dip = hutils.network.resolve_domain_with_api(model.domain)
|
116
116
|
myip = hutils.network.get_ip(4)
|
117
|
-
if myip == dip and model.mode
|
117
|
+
if myip == dip and model.mode in [DomainType.direct, DomainType.sub_link_only]:
|
118
118
|
badge_type = ''
|
119
119
|
elif dip and model.mode != DomainType.direct and myip != dip:
|
120
120
|
badge_type = 'warning'
|
@@ -10,6 +10,7 @@ from wtforms.validators import NumberRange
|
|
10
10
|
from flask_babel import lazy_gettext as _
|
11
11
|
from flask import g, request # type: ignore
|
12
12
|
from markupsafe import Markup
|
13
|
+
from sqlalchemy import desc
|
13
14
|
|
14
15
|
from hiddifypanel.hutils.flask import hurl_for
|
15
16
|
from wtforms.validators import Regexp, ValidationError
|
@@ -148,7 +149,7 @@ class UserAdmin(AdminLTEModelView):
|
|
148
149
|
href = f'{hiddify.get_account_panel_link(model, request.host, is_https=True)}#{hutils.encode.unicode_slug(model.name)}'
|
149
150
|
|
150
151
|
link = f"""<a target='_blank' class='share-link btn btn-xs btn-primary' data-copy='{href}' href='{href}'>
|
151
|
-
<i class='fa-solid fa-arrow-up-right-from-square'></i>
|
152
|
+
<i class='fa-solid fa-arrow-up-right-from-square'></i>
|
152
153
|
{_("Current Domain")} </a>"""
|
153
154
|
|
154
155
|
domains = [d for d in Domain.get_domains() if d.domain != request.host]
|
@@ -171,7 +172,7 @@ class UserAdmin(AdminLTEModelView):
|
|
171
172
|
</div>
|
172
173
|
""")
|
173
174
|
|
174
|
-
def _expire_formatter(view, context, model, name):
|
175
|
+
def _expire_formatter(view, context, model: User, name):
|
175
176
|
remaining = model.remaining_days
|
176
177
|
|
177
178
|
diff = datetime.timedelta(days=remaining)
|
@@ -231,7 +232,8 @@ class UserAdmin(AdminLTEModelView):
|
|
231
232
|
# delattr(form,'disable_user')
|
232
233
|
else:
|
233
234
|
remaining = form._obj.remaining_days # remaining_days(form._obj)
|
234
|
-
|
235
|
+
relative_remaining = hutils.convert.format_timedelta(datetime.timedelta(days=remaining))
|
236
|
+
msg = _("Remaining about %(relative)s, exactly %(days)s days", relative=relative_remaining, days=remaining)
|
235
237
|
form.reset_days.label.text += f" ({msg})"
|
236
238
|
usr_usage = f" ({_('user.home.usage.title')} {round(form._obj.current_usage_GB,3)}GB)"
|
237
239
|
form.reset_usage.label.text += usr_usage
|
@@ -240,10 +242,18 @@ class UserAdmin(AdminLTEModelView):
|
|
240
242
|
|
241
243
|
form.usage_limit.label.text += usr_usage
|
242
244
|
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
245
|
+
# if form._obj.mode==UserMode.disable:
|
246
|
+
# delattr(form,'disable_user')
|
247
|
+
# form.disable_user.data=form._obj.mode==UserMode.disable
|
248
|
+
if form._obj.start_date:
|
249
|
+
started = form._obj.start_date - datetime.date.today()
|
250
|
+
msg = _("Started from %(relative)s", relative=hutils.convert.format_timedelta(started))
|
251
|
+
form.package_days.label.text += f" ({msg})"
|
252
|
+
if started.days <= 0:
|
253
|
+
exact_start = _("Started %(days)s days ago", days=-started.days)
|
254
|
+
else:
|
255
|
+
exact_start = _("Will Start in %(days)s days", days=started.days)
|
256
|
+
form.package_days.description += f" ({exact_start})"
|
247
257
|
|
248
258
|
def get_edit_form(self):
|
249
259
|
form = super().get_edit_form()
|
@@ -295,7 +305,7 @@ class UserAdmin(AdminLTEModelView):
|
|
295
305
|
def after_model_change(self, form, model, is_created):
|
296
306
|
if hconfig(ConfigEnum.first_setup):
|
297
307
|
set_hconfig(ConfigEnum.first_setup, False)
|
298
|
-
user = User.query.filter(User.uuid == model.uuid).first()
|
308
|
+
user = User.query.filter(User.uuid == model.uuid).first() or abort(404)
|
299
309
|
if user.is_active:
|
300
310
|
user_driver.add_client(model)
|
301
311
|
else:
|
@@ -314,6 +324,7 @@ class UserAdmin(AdminLTEModelView):
|
|
314
324
|
|
315
325
|
def get_list(self, page, sort_column, sort_desc, search, filters, page_size=50, *args, **kwargs):
|
316
326
|
res = None
|
327
|
+
self._auto_joins = {}
|
317
328
|
# print('aaa',args, kwargs)
|
318
329
|
if sort_column in ['remaining_days', 'is_active']:
|
319
330
|
query = self.get_query()
|
@@ -365,14 +376,12 @@ class UserAdmin(AdminLTEModelView):
|
|
365
376
|
abort(403)
|
366
377
|
|
367
378
|
query = query.filter(User.added_by.in_(admin.recursive_sub_admins_ids()))
|
368
|
-
|
379
|
+
query = query.order_by(desc(User.id))
|
369
380
|
return query
|
370
381
|
|
371
382
|
# Override get_count_query() to include the filter condition in the count query
|
372
383
|
def get_count_query(self):
|
373
384
|
# Get the base count query
|
374
|
-
query = self.get_query()
|
375
|
-
from sqlalchemy import func
|
376
385
|
|
377
386
|
# query = query.session.query(func.count(User.id))
|
378
387
|
query = super().get_count_query()
|
hiddifypanel/panel/usage.py
CHANGED
@@ -50,6 +50,7 @@ def _add_users_usage(users_usage_data: Dict[User, Dict], child_id, sync=False):
|
|
50
50
|
res = {}
|
51
51
|
have_change = False
|
52
52
|
before_enabled_users = user_driver.get_enabled_users()
|
53
|
+
|
53
54
|
daily_usage = {}
|
54
55
|
today = datetime.date.today()
|
55
56
|
for adm in AdminUser.query.all():
|
@@ -64,15 +65,14 @@ def _add_users_usage(users_usage_data: Dict[User, Dict], child_id, sync=False):
|
|
64
65
|
userDetails = {p.user_id: p for p in UserDetail.query.filter(UserDetail.child_id == child_id).all()}
|
65
66
|
for user, uinfo in users_usage_data.items():
|
66
67
|
usage_bytes = uinfo['usage']
|
67
|
-
devices = uinfo['devices']
|
68
68
|
|
69
69
|
# UserDetails things
|
70
70
|
detail = userDetails.get(user.id)
|
71
71
|
if not detail:
|
72
72
|
detail = UserDetail(user_id=user.id, child_id=child_id)
|
73
73
|
db.session.add(detail)
|
74
|
-
detail.connected_devices
|
75
|
-
|
74
|
+
if uinfo['devices'] != detail.connected_devices:
|
75
|
+
detail.connected_devices = uinfo['devices']
|
76
76
|
|
77
77
|
# Enable the user if isn't already
|
78
78
|
if not before_enabled_users[user.uuid] and user.is_active:
|
@@ -86,22 +86,21 @@ def _add_users_usage(users_usage_data: Dict[User, Dict], child_id, sync=False):
|
|
86
86
|
res[user.uuid] = "No usage"
|
87
87
|
else:
|
88
88
|
# Set new daily usage of the user
|
89
|
-
if sync:
|
90
|
-
|
91
|
-
daily_usage.get(user.added_by, daily_usage[1]).usage = usage_bytes
|
89
|
+
if sync and daily_usage.get(user.added_by, daily_usage[1]).usage != usage_bytes:
|
90
|
+
daily_usage.get(user.added_by, daily_usage[1]).usage = usage_bytes
|
92
91
|
else:
|
93
92
|
daily_usage.get(user.added_by, daily_usage[1]).usage += usage_bytes
|
94
93
|
|
95
|
-
|
94
|
+
in_bytes = usage_bytes
|
96
95
|
|
97
96
|
# Set new current usage of the user
|
98
|
-
if sync:
|
99
|
-
|
100
|
-
user.current_usage_GB = in_gig
|
97
|
+
if sync and user.current_usage != in_bytes:
|
98
|
+
user.current_usage = in_bytes
|
101
99
|
# detail.current_usage_GB = in_gig
|
102
100
|
else:
|
103
|
-
user.
|
104
|
-
detail.
|
101
|
+
user.current_usage += in_bytes
|
102
|
+
detail.current_usage = detail.current_usage or 0
|
103
|
+
detail.current_usage += in_bytes
|
105
104
|
|
106
105
|
# Change last online time of the user
|
107
106
|
user.last_online = datetime.datetime.now()
|
@@ -111,9 +110,10 @@ def _add_users_usage(users_usage_data: Dict[User, Dict], child_id, sync=False):
|
|
111
110
|
if user.start_date is None:
|
112
111
|
user.start_date = datetime.date.today()
|
113
112
|
|
114
|
-
res[user.uuid] =
|
113
|
+
res[user.uuid] = f'{in_bytes/1000000:0.3f}MB'
|
115
114
|
|
116
115
|
# Remove user from drivers(singbox, xray, wireguard etc.) if they're inactive
|
116
|
+
# print(before_enabled_users[user.uuid], user.is_active)
|
117
117
|
if before_enabled_users[user.uuid] and not user.is_active:
|
118
118
|
print(f"Removing enabled client {user.uuid} ")
|
119
119
|
user_driver.remove_client(user)
|
@@ -122,14 +122,25 @@ def _add_users_usage(users_usage_data: Dict[User, Dict], child_id, sync=False):
|
|
122
122
|
|
123
123
|
db.session.commit() # type: ignore
|
124
124
|
|
125
|
+
# Remove invalid users
|
126
|
+
for uuid in before_enabled_users:
|
127
|
+
if uuid in res:
|
128
|
+
continue
|
129
|
+
|
130
|
+
user = User.query.filter(User.uuid == uuid).first()
|
131
|
+
if not user:
|
132
|
+
user_driver.remove_client(User(uuid=uuid))
|
133
|
+
elif not user.is_active:
|
134
|
+
user_driver.remove_client(user)
|
135
|
+
|
136
|
+
# print("------------------", res)
|
125
137
|
# Apply the changes to the drivers
|
126
138
|
if have_change:
|
127
139
|
hiddify.quick_apply_users()
|
128
140
|
|
129
141
|
# 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
|
130
|
-
if not sync:
|
131
|
-
|
132
|
-
hutils.node.child.sync_users_usage_with_parent()
|
142
|
+
if not sync and hutils.node.is_child():
|
143
|
+
hutils.node.child.sync_users_usage_with_parent()
|
133
144
|
|
134
145
|
return {"status": 'success', "comments": res}
|
135
146
|
|