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.
Files changed (63) hide show
  1. hiddifypanel/VERSION +1 -1
  2. hiddifypanel/VERSION.py +2 -2
  3. hiddifypanel/base.py +5 -4
  4. hiddifypanel/hutils/__init__.py +8 -1
  5. hiddifypanel/hutils/auth.py +94 -0
  6. hiddifypanel/hutils/auto_ip_selector.py +1 -1
  7. hiddifypanel/hutils/convert.py +14 -0
  8. hiddifypanel/hutils/encode.py +11 -0
  9. hiddifypanel/hutils/flask.py +24 -0
  10. hiddifypanel/{panel/github_issue_generator.py → hutils/github_issue.py} +104 -14
  11. hiddifypanel/hutils/json.py +24 -0
  12. hiddifypanel/hutils/random.py +19 -0
  13. hiddifypanel/hutils/utils.py +0 -169
  14. hiddifypanel/models/__init__.py +1 -0
  15. hiddifypanel/models/admin.py +53 -8
  16. hiddifypanel/models/base_account.py +31 -169
  17. hiddifypanel/models/domain.py +2 -2
  18. hiddifypanel/models/parent_domain.py +1 -0
  19. hiddifypanel/models/user.py +105 -33
  20. hiddifypanel/models/utils.py +3 -3
  21. hiddifypanel/panel/admin/Actions.py +5 -6
  22. hiddifypanel/panel/admin/Backup.py +5 -5
  23. hiddifypanel/panel/admin/ChildAdmin.py +3 -3
  24. hiddifypanel/panel/admin/Dashboard.py +12 -10
  25. hiddifypanel/panel/admin/DomainAdmin.py +10 -9
  26. hiddifypanel/panel/admin/ProxyAdmin.py +4 -6
  27. hiddifypanel/panel/admin/QuickSetup.py +11 -13
  28. hiddifypanel/panel/admin/SettingAdmin.py +13 -11
  29. hiddifypanel/panel/admin/UserAdmin.py +13 -12
  30. hiddifypanel/panel/auth.py +42 -9
  31. hiddifypanel/panel/auth_back2.py +5 -5
  32. hiddifypanel/panel/cli.py +1 -0
  33. hiddifypanel/panel/commercial/ParentDomainAdmin.py +3 -3
  34. hiddifypanel/panel/commercial/ProxyDetailsAdmin.py +1 -0
  35. hiddifypanel/panel/commercial/restapi/v1/tgmsg.py +1 -1
  36. hiddifypanel/panel/commercial/restapi/v2/user/apps_api.py +5 -4
  37. hiddifypanel/panel/commercial/restapi/v2/user/info_api.py +7 -11
  38. hiddifypanel/panel/commercial/telegrambot/admin.py +1 -0
  39. hiddifypanel/panel/common.py +10 -86
  40. hiddifypanel/panel/common_bp/login.py +9 -8
  41. hiddifypanel/panel/database.py +22 -21
  42. hiddifypanel/panel/hiddify.py +25 -28
  43. hiddifypanel/panel/importer/xui.py +2 -2
  44. hiddifypanel/panel/init_db.py +20 -13
  45. hiddifypanel/panel/usage.py +2 -1
  46. hiddifypanel/panel/user/link_maker.py +118 -15
  47. hiddifypanel/panel/user/user.py +25 -19
  48. hiddifypanel/translations/en/LC_MESSAGES/messages.mo +0 -0
  49. hiddifypanel/translations/en/LC_MESSAGES/messages.po +252 -234
  50. hiddifypanel/translations/fa/LC_MESSAGES/messages.mo +0 -0
  51. hiddifypanel/translations/fa/LC_MESSAGES/messages.po +280 -228
  52. hiddifypanel/translations/pt/LC_MESSAGES/messages.mo +0 -0
  53. hiddifypanel/translations/pt/LC_MESSAGES/messages.po +240 -207
  54. hiddifypanel/translations/ru/LC_MESSAGES/messages.mo +0 -0
  55. hiddifypanel/translations/ru/LC_MESSAGES/messages.po +240 -207
  56. hiddifypanel/translations/zh/LC_MESSAGES/messages.mo +0 -0
  57. hiddifypanel/translations/zh/LC_MESSAGES/messages.po +779 -2740
  58. {hiddifypanel-9.0.0.dev60.dist-info → hiddifypanel-9.0.0.dev61.dist-info}/METADATA +2 -1
  59. {hiddifypanel-9.0.0.dev60.dist-info → hiddifypanel-9.0.0.dev61.dist-info}/RECORD +63 -57
  60. {hiddifypanel-9.0.0.dev60.dist-info → hiddifypanel-9.0.0.dev61.dist-info}/LICENSE.md +0 -0
  61. {hiddifypanel-9.0.0.dev60.dist-info → hiddifypanel-9.0.0.dev61.dist-info}/WHEEL +0 -0
  62. {hiddifypanel-9.0.0.dev60.dist-info → hiddifypanel-9.0.0.dev61.dist-info}/entry_points.txt +0 -0
  63. {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.dev60
1
+ 9.0.0.dev61
hiddifypanel/VERSION.py CHANGED
@@ -1,3 +1,3 @@
1
- __version__='9.0.0.dev60'
1
+ __version__='9.0.0.dev61'
2
2
  from datetime import datetime
3
- __release_date__= datetime.strptime('2024-01-21','%Y-%m-%d')
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'] = hiddify.do_base_64
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 = hconfig(ConfigEnum.admin_lang) or hconfig(ConfigEnum.lang) or 'fa'
93
+ g.locale = g.account.lang or hconfig(ConfigEnum.admin_lang) or 'fa'
93
94
  else:
94
- g.locale = g.account.lang if isinstance(g.get('account'), User) and g.account.lang else hconfig(ConfigEnum.lang) or 'fa'
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'] = hiddify.static_url_for
123
+ app.jinja_env.globals['static_url_for'] = hutils.flask.static_url_for
123
124
 
124
125
  return app
125
126
 
@@ -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,14 @@
1
+ def is_int(input: str) -> bool:
2
+ try:
3
+ int(input)
4
+ return True
5
+ except:
6
+ return False
7
+
8
+
9
+ def to_int(s: str) -> int:
10
+ '''Returns 0 if <s> is not a number'''
11
+ try:
12
+ return int(s)
13
+ except:
14
+ return 0
@@ -0,0 +1,11 @@
1
+ import urllib.parse
2
+ import base64
3
+
4
+
5
+ def url_encode(strr):
6
+ return urllib.parse.quote(strr)
7
+
8
+
9
+ def do_base_64(str):
10
+ resp = base64.b64encode(f'{str}'.encode("utf-8"))
11
+ return resp.decode()
@@ -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 IssueUrl:
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
@@ -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
@@ -10,3 +10,4 @@ from .admin import AdminUser, AdminMode
10
10
  from .child import Child
11
11
  from .usage import DailyUsage
12
12
  from .base_account import BaseAccount
13
+ # from .report import Report, ReportDetail