hiddifypanel 9.0.0.dev60__py3-none-any.whl → 9.0.0.dev61__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 +5 -4
- hiddifypanel/hutils/__init__.py +8 -1
- hiddifypanel/hutils/auth.py +94 -0
- hiddifypanel/hutils/auto_ip_selector.py +1 -1
- hiddifypanel/hutils/convert.py +14 -0
- hiddifypanel/hutils/encode.py +11 -0
- hiddifypanel/hutils/flask.py +24 -0
- hiddifypanel/{panel/github_issue_generator.py → hutils/github_issue.py} +104 -14
- hiddifypanel/hutils/json.py +24 -0
- hiddifypanel/hutils/random.py +19 -0
- hiddifypanel/hutils/utils.py +0 -169
- hiddifypanel/models/__init__.py +1 -0
- hiddifypanel/models/admin.py +53 -8
- hiddifypanel/models/base_account.py +31 -169
- hiddifypanel/models/domain.py +2 -2
- hiddifypanel/models/parent_domain.py +1 -0
- hiddifypanel/models/user.py +105 -33
- hiddifypanel/models/utils.py +3 -3
- hiddifypanel/panel/admin/Actions.py +5 -6
- hiddifypanel/panel/admin/Backup.py +5 -5
- hiddifypanel/panel/admin/ChildAdmin.py +3 -3
- hiddifypanel/panel/admin/Dashboard.py +12 -10
- hiddifypanel/panel/admin/DomainAdmin.py +10 -9
- hiddifypanel/panel/admin/ProxyAdmin.py +4 -6
- hiddifypanel/panel/admin/QuickSetup.py +11 -13
- hiddifypanel/panel/admin/SettingAdmin.py +13 -11
- hiddifypanel/panel/admin/UserAdmin.py +13 -12
- hiddifypanel/panel/auth.py +42 -9
- hiddifypanel/panel/auth_back2.py +5 -5
- hiddifypanel/panel/cli.py +1 -0
- hiddifypanel/panel/commercial/ParentDomainAdmin.py +3 -3
- hiddifypanel/panel/commercial/ProxyDetailsAdmin.py +1 -0
- hiddifypanel/panel/commercial/restapi/v1/tgmsg.py +1 -1
- hiddifypanel/panel/commercial/restapi/v2/user/apps_api.py +5 -4
- hiddifypanel/panel/commercial/restapi/v2/user/info_api.py +7 -11
- hiddifypanel/panel/commercial/telegrambot/admin.py +1 -0
- hiddifypanel/panel/common.py +10 -86
- hiddifypanel/panel/common_bp/login.py +9 -8
- hiddifypanel/panel/database.py +22 -21
- hiddifypanel/panel/hiddify.py +25 -28
- hiddifypanel/panel/importer/xui.py +2 -2
- hiddifypanel/panel/init_db.py +20 -13
- hiddifypanel/panel/usage.py +2 -1
- hiddifypanel/panel/user/link_maker.py +118 -15
- hiddifypanel/panel/user/user.py +25 -19
- hiddifypanel/translations/en/LC_MESSAGES/messages.mo +0 -0
- hiddifypanel/translations/en/LC_MESSAGES/messages.po +252 -234
- hiddifypanel/translations/fa/LC_MESSAGES/messages.mo +0 -0
- hiddifypanel/translations/fa/LC_MESSAGES/messages.po +280 -228
- hiddifypanel/translations/pt/LC_MESSAGES/messages.mo +0 -0
- hiddifypanel/translations/pt/LC_MESSAGES/messages.po +240 -207
- hiddifypanel/translations/ru/LC_MESSAGES/messages.mo +0 -0
- hiddifypanel/translations/ru/LC_MESSAGES/messages.po +240 -207
- hiddifypanel/translations/zh/LC_MESSAGES/messages.mo +0 -0
- hiddifypanel/translations/zh/LC_MESSAGES/messages.po +779 -2740
- {hiddifypanel-9.0.0.dev60.dist-info → hiddifypanel-9.0.0.dev61.dist-info}/METADATA +2 -1
- {hiddifypanel-9.0.0.dev60.dist-info → hiddifypanel-9.0.0.dev61.dist-info}/RECORD +63 -57
- {hiddifypanel-9.0.0.dev60.dist-info → hiddifypanel-9.0.0.dev61.dist-info}/LICENSE.md +0 -0
- {hiddifypanel-9.0.0.dev60.dist-info → hiddifypanel-9.0.0.dev61.dist-info}/WHEEL +0 -0
- {hiddifypanel-9.0.0.dev60.dist-info → hiddifypanel-9.0.0.dev61.dist-info}/entry_points.txt +0 -0
- {hiddifypanel-9.0.0.dev60.dist-info → hiddifypanel-9.0.0.dev61.dist-info}/top_level.txt +0 -0
hiddifypanel/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
9.0.0.
|
1
|
+
9.0.0.dev61
|
hiddifypanel/VERSION.py
CHANGED
@@ -1,3 +1,3 @@
|
|
1
|
-
__version__='9.0.0.
|
1
|
+
__version__='9.0.0.dev61'
|
2
2
|
from datetime import datetime
|
3
|
-
__release_date__= datetime.strptime('2024-01-
|
3
|
+
__release_date__= datetime.strptime('2024-01-25','%Y-%m-%d')
|
hiddifypanel/base.py
CHANGED
@@ -12,6 +12,7 @@ from hiddifypanel.panel import hiddify
|
|
12
12
|
from apiflask import APIFlask
|
13
13
|
from werkzeug.middleware.proxy_fix import ProxyFix
|
14
14
|
from hiddifypanel.models import *
|
15
|
+
from hiddifypanel import hutils
|
15
16
|
from hiddifypanel.panel.init_db import init_db
|
16
17
|
from hiddifypanel.cache import redis_client
|
17
18
|
|
@@ -60,7 +61,7 @@ def create_app(cli=False, **config):
|
|
60
61
|
Session(app)
|
61
62
|
|
62
63
|
app.jinja_env.line_statement_prefix = '%'
|
63
|
-
app.jinja_env.filters['b64encode'] =
|
64
|
+
app.jinja_env.filters['b64encode'] = hutils.encode.do_base_64
|
64
65
|
app.view_functions['admin.static'] = {} # fix bug in apiflask
|
65
66
|
app.is_cli = cli
|
66
67
|
flask_bootstrap.Bootstrap4(app)
|
@@ -89,9 +90,9 @@ def create_app(cli=False, **config):
|
|
89
90
|
# user profile, cookie, session, etc.
|
90
91
|
from hiddifypanel.models import ConfigEnum, hconfig
|
91
92
|
if "admin" in request.base_url:
|
92
|
-
g.locale =
|
93
|
+
g.locale = g.account.lang or hconfig(ConfigEnum.admin_lang) or 'fa'
|
93
94
|
else:
|
94
|
-
g.locale = g.account.lang
|
95
|
+
g.locale = g.account.lang or hconfig(ConfigEnum.lang) or 'fa'
|
95
96
|
return g.locale
|
96
97
|
|
97
98
|
from flask_wtf.csrf import CSRFProtect
|
@@ -119,7 +120,7 @@ def create_app(cli=False, **config):
|
|
119
120
|
|
120
121
|
app.jinja_env.globals['get_locale'] = get_locale
|
121
122
|
app.jinja_env.globals['version'] = hiddifypanel.__version__
|
122
|
-
app.jinja_env.globals['static_url_for'] =
|
123
|
+
app.jinja_env.globals['static_url_for'] = hutils.flask.static_url_for
|
123
124
|
|
124
125
|
return app
|
125
126
|
|
hiddifypanel/hutils/__init__.py
CHANGED
@@ -1,3 +1,10 @@
|
|
1
1
|
from . import ip
|
2
2
|
from . import utils
|
3
|
-
from . import auto_ip_selector
|
3
|
+
from . import auto_ip_selector
|
4
|
+
from . import json
|
5
|
+
from . import auth
|
6
|
+
from . import encode
|
7
|
+
from . import random
|
8
|
+
from . import convert
|
9
|
+
from . import flask
|
10
|
+
from . import github_issue
|
@@ -0,0 +1,94 @@
|
|
1
|
+
import base64
|
2
|
+
from typing import Tuple, Any
|
3
|
+
from uuid import UUID
|
4
|
+
|
5
|
+
|
6
|
+
def is_uuid_valid(uuid, version):
|
7
|
+
try:
|
8
|
+
uuid_obj = UUID(uuid, version=version)
|
9
|
+
except ValueError:
|
10
|
+
return False
|
11
|
+
return str(uuid_obj) == uuid
|
12
|
+
|
13
|
+
|
14
|
+
def get_uuid_from_url_path(path: str, section_index: int = 2) -> str | None:
|
15
|
+
"""
|
16
|
+
Takes a URL path and extracts the UUID at the specified section index.
|
17
|
+
|
18
|
+
Args:
|
19
|
+
path (str): The URL path from which to extract the UUID.
|
20
|
+
section_index (int, optional): The index of the section in the URL path where the UUID is located. Defaults to 2, because in past the UUID was in the second section of path of url.
|
21
|
+
|
22
|
+
Returns:
|
23
|
+
str | None: The extracted UUID as a string if found, or None if not found.
|
24
|
+
"""
|
25
|
+
s_index = 1
|
26
|
+
for section in path.lstrip('/').split('/'):
|
27
|
+
if is_uuid_valid(section, 4):
|
28
|
+
if s_index == section_index:
|
29
|
+
return section
|
30
|
+
s_index += 1
|
31
|
+
return None
|
32
|
+
|
33
|
+
|
34
|
+
def get_apikey_from_auth_header(auth_header: str) -> str | None:
|
35
|
+
if auth_header.startswith('ApiKey'):
|
36
|
+
return auth_header.split('ApiKey ')[1].strip()
|
37
|
+
return None
|
38
|
+
|
39
|
+
|
40
|
+
def parse_login_id(raw_id: str) -> Tuple[Any | None, str | None]:
|
41
|
+
"""
|
42
|
+
Parses the given raw ID to extract the account type and ID.
|
43
|
+
Args:
|
44
|
+
raw_id (str): The raw ID to be parsed.
|
45
|
+
Returns:
|
46
|
+
Tuple[Any | None, str | None]: A tuple containing the account type and ID.
|
47
|
+
The account type is either AccountType.admin or AccountType.user
|
48
|
+
and the ID is a string. If the raw ID cannot be parsed, None is returned
|
49
|
+
for both the account type and ID.
|
50
|
+
"""
|
51
|
+
splitted = raw_id.split('_')
|
52
|
+
if len(splitted) < 2:
|
53
|
+
return None, None
|
54
|
+
admin_or_user, id = splitted
|
55
|
+
from hiddifypanel.models.role import AccountType
|
56
|
+
account_type = AccountType.admin if admin_or_user == 'admin' else AccountType.user
|
57
|
+
if not id or not account_type:
|
58
|
+
return None, None
|
59
|
+
return account_type, id
|
60
|
+
|
61
|
+
|
62
|
+
def add_basic_auth_to_url(url: str, username: str, password: str) -> str:
|
63
|
+
if 'https://' in url:
|
64
|
+
return url.replace('https://', f'https://{username}:{password}@')
|
65
|
+
elif 'http://' in url:
|
66
|
+
return url.replace('http://', f'http://{username}:{password}@')
|
67
|
+
else:
|
68
|
+
return url
|
69
|
+
|
70
|
+
# region unused(never mentioned in codebase)
|
71
|
+
|
72
|
+
|
73
|
+
# def is_uuid_in_url_path(path: str) -> bool:
|
74
|
+
# for section in path.split('/'):
|
75
|
+
# if is_uuid_valid(section, 4):
|
76
|
+
# return True
|
77
|
+
# return False
|
78
|
+
|
79
|
+
|
80
|
+
# def get_basic_auth_from_auth_header(auth_header: str) -> str | None:
|
81
|
+
# if auth_header.startswith('Basic'):
|
82
|
+
# return auth_header.split('Basic ')[1].strip()
|
83
|
+
# return None
|
84
|
+
|
85
|
+
|
86
|
+
# def parse_basic_auth_header(auth_header: str) -> tuple[str, str] | None:
|
87
|
+
# if not auth_header.startswith('Basic'):
|
88
|
+
# return None
|
89
|
+
# header_value = auth_header.split('Basic ')
|
90
|
+
# if len(header_value) < 2:
|
91
|
+
# return None
|
92
|
+
# username, password = map(lambda item: item.strip(), base64.urlsafe_b64decode(header_value[1].strip()).decode('utf-8').split(':'))
|
93
|
+
# return (username, password) if username and password else None
|
94
|
+
# endregion
|
@@ -128,7 +128,7 @@ def get_host_base_on_asn(ips, asn_short):
|
|
128
128
|
valid_hosts = [ip for ip in ips if len(ip) > 5]
|
129
129
|
|
130
130
|
if len(ips) % 2 != 0 or len(valid_hosts) == 0:
|
131
|
-
flash(_("Error! auto cdn ip can not be find, please contact admin."))
|
131
|
+
hutils.flask.flash(_("Error! auto cdn ip can not be find, please contact admin."))
|
132
132
|
if len(valid_hosts) == 0:
|
133
133
|
return
|
134
134
|
|
@@ -0,0 +1,24 @@
|
|
1
|
+
|
2
|
+
from flask import flash as flask_flash
|
3
|
+
from flask import url_for, Markup # type: ignore
|
4
|
+
from flask_babelex import lazy_gettext as _
|
5
|
+
|
6
|
+
|
7
|
+
def flash(message: str, category: str = "message"):
|
8
|
+
# print(message)
|
9
|
+
return flask_flash(Markup(message), category)
|
10
|
+
|
11
|
+
|
12
|
+
def flash_config_success(restart_mode='', domain_changed=True):
|
13
|
+
if restart_mode:
|
14
|
+
url = url_for('admin.Actions:reinstall', complete_install=restart_mode == 'reinstall', domain_changed=domain_changed)
|
15
|
+
apply_btn = f"<a href='{url}' class='btn btn-primary form_post'>" + \
|
16
|
+
_("admin.config.apply_configs")+"</a>"
|
17
|
+
flash((_('config.validation-success', link=apply_btn)), 'success') # type: ignore
|
18
|
+
else:
|
19
|
+
flash((_('config.validation-success-no-reset')), 'success') # type: ignore
|
20
|
+
|
21
|
+
|
22
|
+
def static_url_for(**values):
|
23
|
+
orig = url_for("static", **values)
|
24
|
+
return orig.split("user_secret")[0]
|
@@ -1,27 +1,32 @@
|
|
1
|
-
from json import dumps
|
2
|
-
|
3
1
|
try:
|
4
|
-
from urllib import urlencode, unquote
|
5
|
-
from urlparse import urlparse, parse_qsl, ParseResult
|
2
|
+
from urllib import urlencode, unquote # type: ignore
|
3
|
+
from urlparse import urlparse, parse_qsl, ParseResult # type: ignore
|
6
4
|
except ImportError:
|
7
5
|
# Python 3 fallback
|
8
6
|
from urllib.parse import (
|
9
7
|
urlencode, unquote, urlparse, parse_qsl, ParseResult
|
10
8
|
)
|
9
|
+
import webbrowser
|
10
|
+
from sys import version as python_version
|
11
|
+
from platform import platform
|
12
|
+
from json import dumps
|
13
|
+
from flask import g, request, render_template
|
11
14
|
|
15
|
+
import hiddifypanel
|
16
|
+
from hiddifypanel.models.config import hconfig
|
17
|
+
from hiddifypanel.models.config_enum import ConfigEnum
|
12
18
|
|
13
|
-
import webbrowser
|
14
19
|
|
15
|
-
class
|
20
|
+
class __IssueUrl:
|
16
21
|
def __init__(self, options):
|
17
|
-
|
22
|
+
|
18
23
|
repoUrl = None
|
19
|
-
|
24
|
+
|
20
25
|
self.opts = options
|
21
26
|
|
22
27
|
if "repoUrl" in self.opts:
|
23
28
|
repoUrl = self.opts["repoUrl"]
|
24
|
-
|
29
|
+
|
25
30
|
try:
|
26
31
|
del self.opts["user"]
|
27
32
|
del self.opts["repo"]
|
@@ -29,7 +34,7 @@ class IssueUrl:
|
|
29
34
|
pass
|
30
35
|
|
31
36
|
elif "user" in self.opts and "repo" in options:
|
32
|
-
|
37
|
+
|
33
38
|
try:
|
34
39
|
del self.opts["repoUrl"]
|
35
40
|
except:
|
@@ -89,7 +94,7 @@ class IssueUrl:
|
|
89
94
|
return new_url
|
90
95
|
|
91
96
|
def get_url(self):
|
92
|
-
|
97
|
+
|
93
98
|
url = self.url
|
94
99
|
|
95
100
|
for type in self.types:
|
@@ -107,10 +112,95 @@ class IssueUrl:
|
|
107
112
|
value = ",".join(map(str, value))
|
108
113
|
self.opts[type] = value
|
109
114
|
|
110
|
-
|
111
115
|
self.url = self.add_url_params(url, self.opts)
|
112
|
-
|
116
|
+
|
113
117
|
return self.url
|
114
118
|
|
115
119
|
def opn(self):
|
116
|
-
webbrowser.open(self.get_url(), 1)
|
120
|
+
webbrowser.open(self.get_url(), 1)
|
121
|
+
|
122
|
+
# region private functions
|
123
|
+
|
124
|
+
|
125
|
+
def __generate_github_issue_link(title, issue_body):
|
126
|
+
opts = {
|
127
|
+
"user": 'hiddify',
|
128
|
+
"repo": 'Hiddify-Manager',
|
129
|
+
"title": title,
|
130
|
+
"body": issue_body,
|
131
|
+
}
|
132
|
+
issue_link = str(__IssueUrl(opts).get_url())
|
133
|
+
return issue_link
|
134
|
+
|
135
|
+
|
136
|
+
def __github_issue_details():
|
137
|
+
details = {
|
138
|
+
'hiddify_version': f'{hiddifypanel.__version__}',
|
139
|
+
'python_version': f'{python_version}',
|
140
|
+
'os_details': f'{platform()}',
|
141
|
+
'user_agent': request.user_agent
|
142
|
+
}
|
143
|
+
return details
|
144
|
+
|
145
|
+
|
146
|
+
def __remove_sensetive_data_from_github_issue_link(issue_link):
|
147
|
+
if g.account.uuid:
|
148
|
+
issue_link.replace(f'{g.account.uuid}', '*******************')
|
149
|
+
|
150
|
+
issue_link.replace(request.host, '**********')
|
151
|
+
issue_link.replace(hconfig(ConfigEnum.proxy_path), '**********')
|
152
|
+
issue_link.replace(hconfig(ConfigEnum.proxy_path_admin), '**********')
|
153
|
+
issue_link.replace(hconfig(ConfigEnum.proxy_path_client), '**********')
|
154
|
+
|
155
|
+
|
156
|
+
def __remove_unrelated_traceback_details(stacktrace: str):
|
157
|
+
lines = stacktrace.splitlines()
|
158
|
+
if len(lines) < 1:
|
159
|
+
return ""
|
160
|
+
|
161
|
+
output = ''
|
162
|
+
skip_next_line = False
|
163
|
+
for i, line in enumerate(lines):
|
164
|
+
if i == 0:
|
165
|
+
output += line + '\n'
|
166
|
+
continue
|
167
|
+
if skip_next_line == True:
|
168
|
+
skip_next_line = False
|
169
|
+
continue
|
170
|
+
if line.strip().startswith('File'):
|
171
|
+
if 'hiddify' in line.lower():
|
172
|
+
output += line + '\n'
|
173
|
+
if len(lines) > i+1:
|
174
|
+
output += lines[i + 1] + '\n'
|
175
|
+
skip_next_line = True
|
176
|
+
|
177
|
+
return output
|
178
|
+
|
179
|
+
# endregion
|
180
|
+
|
181
|
+
|
182
|
+
def generate_github_issue_link_for_500_error(error, traceback, remove_sensetive_data=True, remove_unrelated_traceback_datails=True):
|
183
|
+
|
184
|
+
if remove_unrelated_traceback_datails:
|
185
|
+
traceback = __remove_unrelated_traceback_details(traceback)
|
186
|
+
|
187
|
+
issue_details = __github_issue_details()
|
188
|
+
|
189
|
+
issue_body = render_template('github_issue_body.j2', issue_details=issue_details, error=error, traceback=traceback)
|
190
|
+
|
191
|
+
# Create github issue link
|
192
|
+
issue_link = __generate_github_issue_link(f"Internal server error: {error.name if hasattr(error,'name') and error.name != None and error.name else 'Unknown'}", issue_body)
|
193
|
+
|
194
|
+
if remove_sensetive_data:
|
195
|
+
__remove_sensetive_data_from_github_issue_link(issue_link)
|
196
|
+
|
197
|
+
return issue_link
|
198
|
+
|
199
|
+
|
200
|
+
def generate_github_issue_link_for_admin_sidebar():
|
201
|
+
|
202
|
+
issue_body = render_template('github_issue_body.j2', issue_details=__github_issue_details())
|
203
|
+
|
204
|
+
# Create github issue link
|
205
|
+
issue_link = __generate_github_issue_link('Please fill the title properly', issue_body)
|
206
|
+
return issue_link
|
@@ -0,0 +1,24 @@
|
|
1
|
+
from datetime import datetime
|
2
|
+
|
3
|
+
|
4
|
+
def date_to_json(d):
|
5
|
+
return d.strftime("%Y-%m-%d") if d else None
|
6
|
+
|
7
|
+
|
8
|
+
def json_to_date(date_str):
|
9
|
+
try:
|
10
|
+
return datetime.strptime(date_str, '%Y-%m-%d')
|
11
|
+
except:
|
12
|
+
return date_str
|
13
|
+
|
14
|
+
|
15
|
+
def time_to_json(d):
|
16
|
+
|
17
|
+
return d.strftime("%Y-%m-%d %H:%M:%S") if d else None
|
18
|
+
|
19
|
+
|
20
|
+
def json_to_time(time_str):
|
21
|
+
try:
|
22
|
+
return datetime.strptime(time_str, "%Y-%m-%d %H:%M:%S")
|
23
|
+
except:
|
24
|
+
return time_str
|
@@ -0,0 +1,19 @@
|
|
1
|
+
import random
|
2
|
+
import string
|
3
|
+
|
4
|
+
|
5
|
+
def get_random_string(min_=10, max_=30):
|
6
|
+
# With combination of lower and upper case
|
7
|
+
length = random.randint(min_, max_)
|
8
|
+
characters = string.ascii_letters + string.digits
|
9
|
+
result_str = ''.join(random.choice(characters) for i in range(length))
|
10
|
+
return result_str
|
11
|
+
|
12
|
+
|
13
|
+
def get_random_password(length: int = 16) -> str:
|
14
|
+
'''Retunrns a random password with fixed length'''
|
15
|
+
characters = string.ascii_letters + string.digits # + '-'
|
16
|
+
while True:
|
17
|
+
passwd = ''.join(random.choice(characters) for i in range(length))
|
18
|
+
if (any(c.islower() for c in passwd) and any(c.isupper() for c in passwd) and sum(c.isdigit() for c in passwd) > 1):
|
19
|
+
return passwd
|
hiddifypanel/hutils/utils.py
CHANGED
@@ -3,14 +3,10 @@ from typing import Any, Tuple
|
|
3
3
|
from urllib.parse import urlparse
|
4
4
|
from uuid import UUID
|
5
5
|
from flask_babelex import lazy_gettext as _
|
6
|
-
from datetime import datetime
|
7
|
-
from flask import url_for, Markup
|
8
|
-
from flask import flash as flask_flash
|
9
6
|
import re
|
10
7
|
import requests
|
11
8
|
|
12
9
|
import string
|
13
|
-
import random
|
14
10
|
import os
|
15
11
|
import sys
|
16
12
|
|
@@ -20,29 +16,10 @@ from hiddifypanel.cache import cache
|
|
20
16
|
to_gig_d = 1000*1000*1000
|
21
17
|
|
22
18
|
|
23
|
-
def url_encode(strr):
|
24
|
-
import urllib.parse
|
25
|
-
return urllib.parse.quote(strr)
|
26
|
-
|
27
|
-
|
28
|
-
def is_uuid_valid(uuid, version):
|
29
|
-
try:
|
30
|
-
uuid_obj = UUID(uuid, version=version)
|
31
|
-
except ValueError:
|
32
|
-
return False
|
33
|
-
return str(uuid_obj) == uuid
|
34
|
-
|
35
|
-
|
36
19
|
def error(str):
|
37
|
-
|
38
20
|
print(str, file=sys.stderr)
|
39
21
|
|
40
22
|
|
41
|
-
def static_url_for(**values):
|
42
|
-
orig = url_for("static", **values)
|
43
|
-
return orig.split("user_secret")[0]
|
44
|
-
|
45
|
-
|
46
23
|
@cache.cache(ttl=60000)
|
47
24
|
def get_latest_release_url(repo):
|
48
25
|
latest_url = requests.get(f'{repo}/releases/latest').url.strip()
|
@@ -67,12 +44,6 @@ def get_latest_release_version(repo_name):
|
|
67
44
|
return None
|
68
45
|
|
69
46
|
|
70
|
-
def do_base_64(str):
|
71
|
-
import base64
|
72
|
-
resp = base64.b64encode(f'{str}'.encode("utf-8"))
|
73
|
-
return resp.decode()
|
74
|
-
|
75
|
-
|
76
47
|
def get_folder_size(folder_path):
|
77
48
|
total_size = 0
|
78
49
|
try:
|
@@ -88,23 +59,6 @@ def get_folder_size(folder_path):
|
|
88
59
|
return total_size
|
89
60
|
|
90
61
|
|
91
|
-
def get_random_string(min_=10, max_=30):
|
92
|
-
# With combination of lower and upper case
|
93
|
-
length = random.randint(min_, max_)
|
94
|
-
characters = string.ascii_letters + string.digits
|
95
|
-
result_str = ''.join(random.choice(characters) for i in range(length))
|
96
|
-
return result_str
|
97
|
-
|
98
|
-
|
99
|
-
def get_random_password(length: int = 16) -> str:
|
100
|
-
'''Retunrns a random password with fixed length'''
|
101
|
-
characters = string.ascii_letters + string.digits # + '-'
|
102
|
-
while True:
|
103
|
-
passwd = ''.join(random.choice(characters) for i in range(length))
|
104
|
-
if (any(c.islower() for c in passwd) and any(c.isupper() for c in passwd) and sum(c.isdigit() for c in passwd) > 1):
|
105
|
-
return passwd
|
106
|
-
|
107
|
-
|
108
62
|
def is_assci_alphanumeric(str):
|
109
63
|
for c in str:
|
110
64
|
if c not in string.ascii_letters + string.digits:
|
@@ -112,134 +66,11 @@ def is_assci_alphanumeric(str):
|
|
112
66
|
return True
|
113
67
|
|
114
68
|
|
115
|
-
def date_to_json(d):
|
116
|
-
return d.strftime("%Y-%m-%d") if d else None
|
117
|
-
|
118
|
-
|
119
|
-
def json_to_date(date_str):
|
120
|
-
try:
|
121
|
-
return datetime.strptime(date_str, '%Y-%m-%d')
|
122
|
-
except:
|
123
|
-
return date_str
|
124
|
-
|
125
|
-
|
126
|
-
def time_to_json(d):
|
127
|
-
|
128
|
-
return d.strftime("%Y-%m-%d %H:%M:%S") if d else None
|
129
|
-
|
130
|
-
|
131
|
-
def json_to_time(time_str):
|
132
|
-
try:
|
133
|
-
return datetime.strptime(time_str, "%Y-%m-%d %H:%M:%S")
|
134
|
-
except:
|
135
|
-
return time_str
|
136
|
-
|
137
|
-
|
138
|
-
def flash(message, category):
|
139
|
-
# print(message)
|
140
|
-
return flask_flash(Markup(message), category)
|
141
|
-
|
142
|
-
|
143
69
|
def get_proxy_path_from_url(url: str) -> str | None:
|
144
70
|
url_path = urlparse(url).path
|
145
71
|
proxy_path = url_path.lstrip('/').split('/')[0] or None
|
146
72
|
return proxy_path
|
147
73
|
|
148
74
|
|
149
|
-
def is_uuid_in_url_path(path: str) -> bool:
|
150
|
-
for section in path.split('/'):
|
151
|
-
if is_uuid_valid(section, 4):
|
152
|
-
return True
|
153
|
-
return False
|
154
|
-
|
155
|
-
|
156
|
-
def get_uuid_from_url_path(path: str, section_index: int = 2) -> str | None:
|
157
|
-
"""
|
158
|
-
Takes a URL path and extracts the UUID at the specified section index.
|
159
|
-
|
160
|
-
Args:
|
161
|
-
path (str): The URL path from which to extract the UUID.
|
162
|
-
section_index (int, optional): The index of the section in the URL path where the UUID is located. Defaults to 2, because in past the UUID was in the second section of path of url.
|
163
|
-
|
164
|
-
Returns:
|
165
|
-
str | None: The extracted UUID as a string if found, or None if not found.
|
166
|
-
"""
|
167
|
-
s_index = 1
|
168
|
-
for section in path.lstrip('/').split('/'):
|
169
|
-
if is_uuid_valid(section, 4):
|
170
|
-
if s_index == section_index:
|
171
|
-
return section
|
172
|
-
s_index += 1
|
173
|
-
return None
|
174
|
-
|
175
|
-
|
176
|
-
def get_apikey_from_auth_header(auth_header: str) -> str | None:
|
177
|
-
if auth_header.startswith('ApiKey'):
|
178
|
-
return auth_header.split('ApiKey ')[1].strip()
|
179
|
-
return None
|
180
|
-
|
181
|
-
|
182
|
-
def get_basic_auth_from_auth_header(auth_header: str) -> str | None:
|
183
|
-
if auth_header.startswith('Basic'):
|
184
|
-
return auth_header.split('Basic ')[1].strip()
|
185
|
-
return None
|
186
|
-
|
187
|
-
|
188
|
-
def parse_basic_auth_header(auth_header: str) -> tuple[str, str] | None:
|
189
|
-
if not auth_header.startswith('Basic'):
|
190
|
-
return None
|
191
|
-
header_value = auth_header.split('Basic ')
|
192
|
-
if len(header_value) < 2:
|
193
|
-
return None
|
194
|
-
username, password = map(lambda item: item.strip(), base64.urlsafe_b64decode(header_value[1].strip()).decode('utf-8').split(':'))
|
195
|
-
return (username, password) if username and password else None
|
196
|
-
|
197
|
-
|
198
|
-
def parse_login_id(raw_id) -> Tuple[Any | None, str | None]:
|
199
|
-
"""
|
200
|
-
Parses the given raw ID to extract the account type and ID.
|
201
|
-
Args:
|
202
|
-
raw_id (str): The raw ID to be parsed.
|
203
|
-
Returns:
|
204
|
-
Tuple[Any | None, str | None]: A tuple containing the account type and ID.
|
205
|
-
The account type is either AccountType.admin or AccountType.user
|
206
|
-
and the ID is a string. If the raw ID cannot be parsed, None is returned
|
207
|
-
for both the account type and ID.
|
208
|
-
"""
|
209
|
-
splitted = raw_id.split('_')
|
210
|
-
if len(splitted) < 2:
|
211
|
-
return None, None
|
212
|
-
admin_or_user, id = splitted
|
213
|
-
from hiddifypanel.models.role import AccountType
|
214
|
-
account_type = AccountType.admin if admin_or_user == 'admin' else AccountType.user
|
215
|
-
if not id or not account_type:
|
216
|
-
return None, None
|
217
|
-
return account_type, id
|
218
|
-
|
219
|
-
|
220
|
-
def add_basic_auth_to_url(url: str, username: str, password: str) -> str:
|
221
|
-
if 'https://' in url:
|
222
|
-
return url.replace('https://', f'https://{username}:{password}@')
|
223
|
-
elif 'http://' in url:
|
224
|
-
return url.replace('http://', f'http://{username}:{password}@')
|
225
|
-
else:
|
226
|
-
return url
|
227
|
-
|
228
|
-
|
229
|
-
def convert_to_int(s: str) -> int:
|
230
|
-
try:
|
231
|
-
return int(s)
|
232
|
-
except:
|
233
|
-
return 0
|
234
|
-
|
235
|
-
|
236
75
|
def is_out_of_range_port(port: int) -> bool:
|
237
76
|
return port < 1 or port > 65535
|
238
|
-
|
239
|
-
|
240
|
-
def is_int(input: str) -> bool:
|
241
|
-
try:
|
242
|
-
int(input)
|
243
|
-
return True
|
244
|
-
except:
|
245
|
-
return False
|
hiddifypanel/models/__init__.py
CHANGED