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.
- hiddifypanel/VERSION +1 -1
- hiddifypanel/VERSION.py +2 -2
- hiddifypanel/base.py +17 -8
- hiddifypanel/cache.py +2 -51
- hiddifypanel/drivers/wireguard_api.py +24 -5
- hiddifypanel/hutils/convert.py +1 -1
- hiddifypanel/hutils/flask.py +28 -2
- hiddifypanel/hutils/importer/xui.py +6 -7
- hiddifypanel/hutils/network/__init__.py +1 -0
- hiddifypanel/hutils/network/cf_api.py +84 -0
- hiddifypanel/hutils/network/net.py +26 -49
- hiddifypanel/hutils/node/child.py +25 -7
- hiddifypanel/hutils/node/parent.py +7 -7
- hiddifypanel/hutils/node/shared.py +19 -6
- hiddifypanel/hutils/proxy/clash.py +1 -1
- hiddifypanel/hutils/proxy/shared.py +1 -1
- hiddifypanel/hutils/proxy/singbox.py +2 -3
- hiddifypanel/hutils/proxy/xray.py +12 -10
- hiddifypanel/hutils/proxy/xrayjson.py +26 -49
- hiddifypanel/hutils/utils.py +47 -3
- hiddifypanel/models/__init__.py +1 -1
- hiddifypanel/models/admin.py +9 -2
- hiddifypanel/models/base_account.py +3 -1
- hiddifypanel/models/config.py +5 -7
- hiddifypanel/models/config_enum.py +18 -6
- hiddifypanel/models/domain.py +82 -118
- hiddifypanel/models/user.py +44 -24
- hiddifypanel/panel/admin/Actions.py +6 -11
- hiddifypanel/panel/admin/AdminstratorAdmin.py +3 -9
- hiddifypanel/panel/admin/Backup.py +5 -8
- hiddifypanel/panel/admin/Dashboard.py +3 -4
- hiddifypanel/panel/admin/DomainAdmin.py +20 -15
- hiddifypanel/panel/admin/ProxyAdmin.py +3 -10
- hiddifypanel/panel/admin/QuickSetup.py +1 -1
- hiddifypanel/panel/admin/SettingAdmin.py +7 -5
- hiddifypanel/panel/admin/Terminal.py +0 -1
- hiddifypanel/panel/admin/UserAdmin.py +4 -3
- hiddifypanel/panel/cli.py +36 -23
- hiddifypanel/panel/commercial/ProxyDetailsAdmin.py +2 -4
- hiddifypanel/panel/commercial/restapi/v1/tgbot.py +7 -4
- hiddifypanel/panel/commercial/restapi/v2/admin/__init__.py +17 -13
- hiddifypanel/panel/commercial/restapi/v2/admin/admin_info_api.py +4 -3
- hiddifypanel/panel/commercial/restapi/v2/admin/admin_user_api.py +28 -10
- hiddifypanel/panel/commercial/restapi/v2/admin/admin_users_api.py +2 -19
- hiddifypanel/panel/commercial/restapi/v2/admin/schema.py +27 -4
- hiddifypanel/panel/commercial/restapi/v2/admin/user_api.py +28 -9
- hiddifypanel/panel/commercial/restapi/v2/admin/users_api.py +1 -21
- hiddifypanel/panel/commercial/restapi/v2/parent/register_api.py +1 -1
- hiddifypanel/panel/commercial/restapi/v2/parent/schema.py +8 -4
- hiddifypanel/panel/commercial/restapi/v2/parent/sync_api.py +19 -3
- hiddifypanel/panel/commercial/restapi/v2/user/configs_api.py +48 -42
- hiddifypanel/panel/commercial/telegrambot/Usage.py +1 -1
- hiddifypanel/panel/commercial/telegrambot/admin.py +1 -1
- hiddifypanel/panel/commercial/telegrambot/information.py +1 -1
- hiddifypanel/panel/common.py +5 -11
- hiddifypanel/panel/hiddify.py +9 -20
- hiddifypanel/panel/init_db.py +31 -13
- hiddifypanel/panel/usage.py +38 -9
- hiddifypanel/panel/user/user.py +52 -32
- hiddifypanel/templates/admin-layout.html +2 -2
- hiddifypanel/templates/fake.html +0 -298
- hiddifypanel/translations/en/LC_MESSAGES/messages.mo +0 -0
- hiddifypanel/translations/en/LC_MESSAGES/messages.po +80 -25
- hiddifypanel/translations/fa/LC_MESSAGES/messages.mo +0 -0
- hiddifypanel/translations/fa/LC_MESSAGES/messages.po +74 -20
- hiddifypanel/translations/pt/LC_MESSAGES/messages.mo +0 -0
- hiddifypanel/translations/pt/LC_MESSAGES/messages.po +60 -6
- hiddifypanel/translations/ru/LC_MESSAGES/messages.mo +0 -0
- hiddifypanel/translations/ru/LC_MESSAGES/messages.po +158 -78
- hiddifypanel/translations/zh/LC_MESSAGES/messages.mo +0 -0
- hiddifypanel/translations/zh/LC_MESSAGES/messages.po +60 -6
- hiddifypanel/translations.i18n/en.json +62 -22
- hiddifypanel/translations.i18n/fa.json +57 -17
- hiddifypanel/translations.i18n/pt.json +43 -3
- hiddifypanel/translations.i18n/ru.json +112 -72
- hiddifypanel/translations.i18n/zh.json +43 -3
- {hiddifypanel-10.20.4.dist-info → hiddifypanel-10.30.0.dev0.dist-info}/METADATA +2 -1
- {hiddifypanel-10.20.4.dist-info → hiddifypanel-10.30.0.dev0.dist-info}/RECORD +82 -82
- {hiddifypanel-10.20.4.dist-info → hiddifypanel-10.30.0.dev0.dist-info}/WHEEL +1 -1
- hiddifypanel/panel/cf_api.py +0 -37
- {hiddifypanel-10.20.4.dist-info → hiddifypanel-10.30.0.dev0.dist-info}/LICENSE.md +0 -0
- {hiddifypanel-10.20.4.dist-info → hiddifypanel-10.30.0.dev0.dist-info}/entry_points.txt +0 -0
- {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
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
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
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
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
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
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
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
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
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
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
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
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
|
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"""
|
@@ -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.
|
19
|
+
"Expire time: {}\n".format(user.remaining_days) +
|
20
20
|
"Usage class: {}\n".format(user.mode)
|
21
21
|
)
|
22
22
|
|
hiddifypanel/panel/common.py
CHANGED
@@ -46,12 +46,8 @@ def init_app(app: APIFlask):
|
|
46
46
|
return jsonify({
|
47
47
|
'message': 'Not Found',
|
48
48
|
}), 404
|
49
|
-
|
50
|
-
|
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
|
-
|
87
|
-
|
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
|
|
hiddifypanel/panel/hiddify.py
CHANGED
@@ -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
|
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
|
-
|
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
|
-
|
278
|
-
|
279
|
-
|
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
|
hiddifypanel/panel/init_db.py
CHANGED
@@ -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 "
|
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
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
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=
|
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]),
|
hiddifypanel/panel/usage.py
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
#
|
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
|
|
hiddifypanel/panel/user/user.py
CHANGED
@@ -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.
|
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.
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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.
|
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.
|
114
|
+
return self.clash_config_imp(meta_or_normal="meta")
|
111
115
|
if re.match('^(Clash|Stash)', ua, re.IGNORECASE):
|
112
|
-
return self.
|
116
|
+
return self.clash_config_imp(meta_or_normal="normal")
|
113
117
|
|
114
|
-
if
|
115
|
-
return
|
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
|
-
|
118
|
-
|
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
|
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
|
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
|
219
|
+
def singbox_ssh_imp(self):
|
209
220
|
if not hconfig(ConfigEnum.ssh_server_enable):
|
210
|
-
return "SSH server is
|
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
|
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':
|
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)+"
|
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)+"
|
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 %}
|