hiddifypanel 9.0.0.dev54__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 (72) 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 -161
  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/config_enum.py +23 -1
  18. hiddifypanel/models/domain.py +2 -2
  19. hiddifypanel/models/parent_domain.py +1 -0
  20. hiddifypanel/models/user.py +105 -33
  21. hiddifypanel/models/utils.py +3 -3
  22. hiddifypanel/panel/admin/Actions.py +5 -6
  23. hiddifypanel/panel/admin/AdminstratorAdmin.py +1 -1
  24. hiddifypanel/panel/admin/Backup.py +5 -5
  25. hiddifypanel/panel/admin/ChildAdmin.py +3 -3
  26. hiddifypanel/panel/admin/Dashboard.py +12 -10
  27. hiddifypanel/panel/admin/DomainAdmin.py +10 -9
  28. hiddifypanel/panel/admin/ProxyAdmin.py +4 -6
  29. hiddifypanel/panel/admin/QuickSetup.py +11 -13
  30. hiddifypanel/panel/admin/SettingAdmin.py +20 -10
  31. hiddifypanel/panel/admin/UserAdmin.py +14 -12
  32. hiddifypanel/panel/auth.py +43 -10
  33. hiddifypanel/panel/auth_back2.py +5 -5
  34. hiddifypanel/panel/cli.py +1 -0
  35. hiddifypanel/panel/commercial/ParentDomainAdmin.py +3 -3
  36. hiddifypanel/panel/commercial/ProxyDetailsAdmin.py +1 -0
  37. hiddifypanel/panel/commercial/restapi/v1/tgbot.py +1 -2
  38. hiddifypanel/panel/commercial/restapi/v1/tgmsg.py +37 -29
  39. hiddifypanel/panel/commercial/restapi/v2/user/apps_api.py +5 -4
  40. hiddifypanel/panel/commercial/restapi/v2/user/configs_api.py +15 -7
  41. hiddifypanel/panel/commercial/restapi/v2/user/info_api.py +7 -11
  42. hiddifypanel/panel/commercial/telegrambot/Usage.py +2 -1
  43. hiddifypanel/panel/commercial/telegrambot/admin.py +1 -0
  44. hiddifypanel/panel/common.py +12 -89
  45. hiddifypanel/panel/common_bp/login.py +12 -12
  46. hiddifypanel/panel/database.py +22 -21
  47. hiddifypanel/panel/hiddify.py +27 -29
  48. hiddifypanel/panel/importer/xui.py +2 -2
  49. hiddifypanel/panel/init_db.py +32 -13
  50. hiddifypanel/panel/usage.py +2 -1
  51. hiddifypanel/panel/user/link_maker.py +118 -15
  52. hiddifypanel/panel/user/templates/new.html +4 -2
  53. hiddifypanel/panel/user/user.py +83 -38
  54. hiddifypanel/static/new/assets/{index-bd9ba5e9.js → index-2cd90979.js} +1 -1
  55. hiddifypanel/templates/fake.html +2 -2
  56. hiddifypanel/templates/master.html +1 -1
  57. hiddifypanel/translations/en/LC_MESSAGES/messages.mo +0 -0
  58. hiddifypanel/translations/en/LC_MESSAGES/messages.po +317 -189
  59. hiddifypanel/translations/fa/LC_MESSAGES/messages.mo +0 -0
  60. hiddifypanel/translations/fa/LC_MESSAGES/messages.po +346 -206
  61. hiddifypanel/translations/pt/LC_MESSAGES/messages.mo +0 -0
  62. hiddifypanel/translations/pt/LC_MESSAGES/messages.po +315 -195
  63. hiddifypanel/translations/ru/LC_MESSAGES/messages.mo +0 -0
  64. hiddifypanel/translations/ru/LC_MESSAGES/messages.po +315 -195
  65. hiddifypanel/translations/zh/LC_MESSAGES/messages.mo +0 -0
  66. hiddifypanel/translations/zh/LC_MESSAGES/messages.po +866 -2739
  67. {hiddifypanel-9.0.0.dev54.dist-info → hiddifypanel-9.0.0.dev61.dist-info}/METADATA +2 -1
  68. {hiddifypanel-9.0.0.dev54.dist-info → hiddifypanel-9.0.0.dev61.dist-info}/RECORD +72 -66
  69. {hiddifypanel-9.0.0.dev54.dist-info → hiddifypanel-9.0.0.dev61.dist-info}/LICENSE.md +0 -0
  70. {hiddifypanel-9.0.0.dev54.dist-info → hiddifypanel-9.0.0.dev61.dist-info}/WHEEL +0 -0
  71. {hiddifypanel-9.0.0.dev54.dist-info → hiddifypanel-9.0.0.dev61.dist-info}/entry_points.txt +0 -0
  72. {hiddifypanel-9.0.0.dev54.dist-info → hiddifypanel-9.0.0.dev61.dist-info}/top_level.txt +0 -0
@@ -7,7 +7,7 @@ from strenum import StrEnum
7
7
  from apiflask import abort
8
8
  from flask_babelex import gettext as __
9
9
  from flask_babelex import lazy_gettext as _
10
-
10
+ from hiddifypanel.models.role import Role
11
11
  from hiddifypanel.panel.database import db
12
12
  from .base_account import BaseAccount
13
13
 
@@ -38,13 +38,58 @@ class AdminUser(BaseAccount):
38
38
  usages = db.relationship('DailyUsage', backref='admin')
39
39
  parent_admin_id = db.Column(db.Integer, db.ForeignKey('admin_user.id'), default=1)
40
40
  parent_admin = db.relationship('AdminUser', remote_side=[id], backref='sub_admins')
41
- # These columns are created by BaseAccount
42
- # uuid = db.Column(db.String(36), default=lambda: str(uuid_mod.uuid4()), nullable=False, unique=True)
43
- # name = db.Column(db.String(512), nullable=False)
44
- # username = db.Column(db.String(16), nullable=True, default='')
45
- # password = db.Column(db.String(16), nullable=True, default='')
46
- # comment = db.Column(db.String(512))
47
- # telegram_id = db.Column(db.String(512))
41
+
42
+ @property
43
+ def role(self) -> Role | None:
44
+ match self.mode:
45
+ case AdminMode.super_admin:
46
+ return Role.super_admin
47
+ case AdminMode.admin:
48
+ return Role.admin
49
+ case AdminMode.agent:
50
+ return Role.agent
51
+ return None
52
+
53
+ def get_id(self) -> str | None:
54
+ return f'admin_{self.id}'
55
+
56
+ def to_dict(self, convert_date=True) -> dict:
57
+ base = super().to_dict()
58
+ return {**base,
59
+ 'mode': self.mode,
60
+ 'can_add_admin': self.can_add_admin,
61
+ 'parent_admin_uuid': self.parent_admin.uuid if self.parent_admin else None,
62
+ }
63
+
64
+ @classmethod
65
+ def by_uuid(cls, uuid: str, create: bool = False):
66
+ account = AdminUser.query.filter(AdminUser.uuid == uuid).first()
67
+ if not account and create:
68
+ dbuser = AdminUser(uuid=uuid, name="unknown", parent_admin_id=AdminUser.current_admin_or_owner().id)
69
+ db.session.add(dbuser)
70
+ db.session.commit()
71
+ account = AdminUser.by_uuid(uuid, False)
72
+
73
+ return account
74
+
75
+ @classmethod
76
+ def add_or_update(cls, commit: bool = True, **data):
77
+
78
+ dbuser = super().add_or_update(commit=commit, **data)
79
+
80
+ if dbuser.id != 1:
81
+ parent = data.get('parent_admin_uuid')
82
+ if parent == data['uuid'] or not parent:
83
+ parent_admin = cls.current_admin_or_owner()
84
+ else:
85
+ parent_admin = cls.by_uuid(parent, create=True)
86
+ dbuser.parent_admin_id = parent_admin.id # type: ignore
87
+
88
+ dbuser.mode = data.get('mode', AdminMode.agent)
89
+ dbuser.can_add_admin = data.get('can_add_admin') == True
90
+ if commit:
91
+ db.session.commit()
92
+ return dbuser
48
93
 
49
94
  def recursive_users_query(self):
50
95
  from .user import User
@@ -2,10 +2,10 @@ import datetime
2
2
  import uuid as uuid_mod
3
3
  from hiddifypanel import hutils
4
4
  from hiddifypanel.models.role import Role
5
- from sqlalchemy import Column, String
5
+ from sqlalchemy import Column, String, BigInteger, Integer
6
6
  from sqlalchemy_serializer import SerializerMixin
7
7
  from flask_login import UserMixin as FlaskLoginUserMixin
8
-
8
+ from hiddifypanel.models import Lang
9
9
  from hiddifypanel.panel.database import db
10
10
 
11
11
 
@@ -16,77 +16,31 @@ class BaseAccount(db.Model, SerializerMixin, FlaskLoginUserMixin): # type: igno
16
16
  username = Column(String(100), nullable=True, default='')
17
17
  password = Column(String(100), nullable=True, default='')
18
18
  comment = Column(String(512), nullable=True, default='')
19
- telegram_id = Column(String(512), nullable=True, default='')
19
+ telegram_id = Column(BigInteger, nullable=True, default='')
20
+ lang = db.Column(db.Enum(Lang), default=None)
20
21
 
21
22
  @property
22
23
  def role(self) -> Role | None:
23
- from hiddifypanel.models.user import User
24
- from hiddifypanel.models.admin import AdminUser, AdminMode
25
- if isinstance(self, AdminUser):
26
- match self.mode:
27
- case AdminMode.super_admin:
28
- return Role.super_admin
29
- case AdminMode.admin:
30
- return Role.admin
31
- case AdminMode.agent:
32
- return Role.agent
33
- if isinstance(self, User):
34
- return Role.user
24
+ return None
35
25
 
36
26
  def get_id(self) -> str | None:
37
- """
38
- Get the ID of the account. (*only for flask_login)
39
- """
40
- from hiddifypanel.models.user import User
41
- from hiddifypanel.models.admin import AdminUser
42
- if isinstance(self, AdminUser):
43
- return f'admin_{self.id}'
44
- if isinstance(self, User):
45
- return f'user_{self.id}'
27
+ return '{self.__class__.name}_{self.id if self.hasattr("id") else "-"}'
46
28
 
47
29
  def is_username_unique(self) -> bool:
48
- from hiddifypanel.models.user import User
49
- from hiddifypanel.models.admin import AdminUser
50
-
51
- model = None
52
- if isinstance(self, AdminUser):
53
- model = AdminUser.query.filter(AdminUser.username == self.username).first()
54
- else:
55
- model = User.query.filter(User.username == self.username).first()
56
-
57
- if model and model.id != self.id:
30
+ cls = self.__class__()
31
+ model = cls.query.filter(cls.username == self.username, cls.id != self.id).first()
32
+ if model:
58
33
  return False
59
34
  return True
60
35
 
61
36
  def to_dict(self, convert_date=True) -> dict:
62
- from hiddifypanel.models.admin import AdminUser
63
- if isinstance(self, AdminUser):
64
- return {
65
- 'name': self.name,
66
- 'comment': self.comment,
67
- 'uuid': self.uuid,
68
- 'mode': self.mode,
69
- 'can_add_admin': self.can_add_admin,
70
- 'parent_admin_uuid': self.parent_admin.uuid if self.parent_admin else None,
71
- 'telegram_id': hutils.utils.convert_to_int(self.telegram_id),
72
- }
73
- else:
74
- return {
75
- 'uuid': self.uuid,
76
- 'name': self.name,
77
- 'last_online': hutils.utils.time_to_json(self.last_online) if convert_date else self.last_online,
78
- 'usage_limit_GB': self.usage_limit_GB,
79
- 'package_days': self.package_days,
80
- 'mode': self.mode,
81
- 'start_date': hutils.utils.date_to_json(self.start_date)if convert_date else self.start_date,
82
- 'current_usage_GB': self.current_usage_GB,
83
- 'last_reset_time': hutils.utils.date_to_json(self.last_reset_time) if convert_date else self.last_reset_time,
84
- 'comment': self.comment,
85
- 'added_by_uuid': self.admin.uuid,
86
- 'telegram_id': hutils.utils.convert_to_int(self.telegram_id),
87
- 'ed25519_private_key': self.ed25519_private_key,
88
- 'ed25519_public_key': self.ed25519_public_key
89
- }
37
+ return {
38
+ 'name': self.name,
39
+ 'comment': self.comment,
40
+ 'uuid': self.uuid,
41
+ 'telegram_id': self.telegram_id,
42
+ 'lang': self.lang
43
+ }
90
44
 
91
45
  @classmethod
92
46
  def by_id(cls, id: int):
@@ -94,24 +48,9 @@ class BaseAccount(db.Model, SerializerMixin, FlaskLoginUserMixin): # type: igno
94
48
 
95
49
  @classmethod
96
50
  def by_uuid(cls, uuid: str, create: bool = False):
97
- from hiddifypanel.models.user import User
98
- from hiddifypanel.models.admin import AdminUser
99
- account = None
100
- if cls.__subclasscheck__(User) or cls.__subclasscheck__(AdminUser):
101
- account = cls.query.filter(cls.uuid == uuid).first()
102
-
51
+ account = cls.query.filter(cls.uuid == uuid).first()
103
52
  if not account and create:
104
- if cls.__subclasscheck__(User):
105
- dbuser = User(uuid=uuid, name="unknown", added_by=AdminUser.current_admin_or_owner().id)
106
- db.session.add(dbuser)
107
- db.session.commit()
108
- account = cls.by_uuid(uuid)
109
- elif cls.__subclasscheck__(AdminUser):
110
- dbuser = AdminUser(uuid=uuid, name="unknown", parent_admin_id=cls.current_admin_or_owner().id)
111
- db.session.add(dbuser)
112
- db.session.commit()
113
- account = cls.by_uuid(uuid) # AdminUser.query.filter(AdminUser.uuid == uuid).first()
114
-
53
+ raise NotImplementedError
115
54
  return account
116
55
 
117
56
  @classmethod
@@ -120,100 +59,23 @@ class BaseAccount(db.Model, SerializerMixin, FlaskLoginUserMixin): # type: igno
120
59
 
121
60
  @classmethod
122
61
  def add_or_update(cls, commit: bool = True, **data):
123
- from hiddifypanel.models.user import User, UserMode
124
- from hiddifypanel.models.admin import AdminUser, AdminMode
125
-
126
- dbuser: AdminUser | User = None # type: ignore
127
- if cls.__subclasscheck__(User):
128
- dbuser = User.by_uuid(data['uuid'], create=True)
129
-
130
- if data.get('added_by_uuid'):
131
- admin = AdminUser.by_uuid(data.get('added_by_uuid'), create=True) # type: ignore
132
- dbuser.added_by = admin.id # type: ignore
133
- else:
134
- dbuser.added_by = 1
135
-
136
- if data.get('expiry_time', ''):
137
- last_reset_time = hutils.utils.json_to_date(data.get('last_reset_time', '')) or datetime.date.today()
138
-
139
- expiry_time = hutils.utils.json_to_date(data['expiry_time'])
140
- dbuser.start_date = last_reset_time
141
- dbuser.package_days = (expiry_time-last_reset_time).days # type: ignore
142
-
143
- elif 'package_days' in data:
144
- dbuser.package_days = data['package_days']
145
- if data.get('start_date', ''):
146
- dbuser.start_date = hutils.utils.json_to_date(data['start_date'])
147
- else:
148
- dbuser.start_date = None
149
- dbuser.current_usage_GB = data['current_usage_GB']
150
-
151
- dbuser.usage_limit_GB = data['usage_limit_GB']
152
- dbuser.name = data.get('name') or ''
153
- dbuser.comment = data.get('comment', '')
154
- dbuser.enable = data.get('enable', True)
155
- if data.get('ed25519_private_key', ''):
156
- dbuser.ed25519_private_key = data.get('ed25519_private_key', '')
157
- dbuser.ed25519_public_key = data.get('ed25519_public_key', '')
158
- if not dbuser.ed25519_private_key:
159
- from hiddifypanel.panel import hiddify
160
- priv, publ = hiddify.get_ed25519_private_public_pair()
161
- dbuser.ed25519_private_key = priv
162
- dbuser.ed25519_public_key = publ
163
-
164
- mode = data.get('mode', UserMode.no_reset)
165
- if mode == 'disable':
166
- mode = UserMode.no_reset
167
- dbuser.enable = False
168
-
169
- dbuser.mode = mode
170
-
171
- dbuser.telegram_id = data.get('telegram_id') or 0
172
-
173
- dbuser.last_online = hutils.utils.json_to_time(data.get('last_online')) or datetime.datetime.min
174
- elif cls.__subclasscheck__(AdminUser):
175
- # if not is_valid():return
176
- dbuser = cls.by_uuid(data['uuid']) # type: ignore
177
-
178
- if not dbuser:
179
- dbuser = AdminUser(uuid=data['uuid'])
180
- # if not is_valid():
181
- # return
182
- db.session.add(dbuser)
183
- dbuser.name = data['name']
184
- if dbuser.id != 1:
185
- parent = data.get('parent_admin_uuid')
186
- if parent == data['uuid'] or not parent:
187
- parent_admin = cls.current_admin_or_owner()
188
- else:
189
- parent_admin = cls.by_uuid(parent, create=True)
190
- dbuser.parent_admin_id = parent_admin.id # type: ignore
191
-
192
- dbuser.comment = data.get('comment', '')
193
- dbuser.mode = data.get('mode', AdminMode.agent)
194
- dbuser.telegram_id = data.get('telegram_id')
195
- dbuser.can_add_admin = data.get('can_add_admin') == True
196
-
197
- # dbuser.last_online=user.get('last_online','')
198
-
62
+ db_account = cls.by_uuid(data['uuid'], create=True)
63
+ db_account.name = data.get('name') or ''
64
+ db_account.comment = data.get('comment', '')
65
+ db_account.telegram_id = hutils.convert.to_int(data.get('telegram_id'))
66
+ db_account.lang = data.get('lang')
199
67
  if commit:
200
68
  db.session.commit()
201
- return dbuser
69
+ return db_account
202
70
 
203
71
  @classmethod
204
72
  def bulk_register(cls, accounts: list = [], commit: bool = True, remove: bool = False):
205
- from hiddifypanel.models.user import User
206
- from hiddifypanel.models.admin import AdminUser
207
- if cls.__subclasscheck__(User):
208
- for u in accounts:
209
- cls.add_or_update(commit=False, **u)
210
- if remove:
211
- dd = {u['uuid']: 1 for u in accounts}
212
- for d in cls.query.all():
213
- if d.uuid not in dd:
214
- db.session.delete(d)
215
- elif cls.__subclasscheck__(AdminUser):
216
- for u in accounts:
217
- cls.add_or_update(commit=False, **u)
73
+ for u in accounts:
74
+ cls.add_or_update(commit=False, **u)
75
+ if remove:
76
+ dd = {u['uuid']: 1 for u in accounts}
77
+ for d in cls.query.all():
78
+ if d.uuid not in dd:
79
+ db.session.delete(d)
218
80
  if commit:
219
81
  db.session.commit()
@@ -20,6 +20,7 @@ class ConfigCategory(StrEnum):
20
20
  telegram = auto()
21
21
  http = auto()
22
22
  tls = auto()
23
+ mux = auto()
23
24
  tls_trick = auto()
24
25
  ssh = auto()
25
26
  ssfaketls = auto()
@@ -81,6 +82,17 @@ class ConfigEnum(StrEnum):
81
82
  tls_padding_enable = auto()
82
83
  tls_padding_length = auto()
83
84
 
85
+ # mux
86
+ mux_enable = auto()
87
+ mux_protocol = auto()
88
+ mux_max_connections = auto()
89
+ mux_min_streams = auto()
90
+ mux_max_streams = auto()
91
+ mux_padding_enable = auto()
92
+ mux_brutal_enable = auto()
93
+ mux_brutal_up_mbps = auto()
94
+ mux_brutal_down_mbps = auto()
95
+
84
96
  http_ports = auto()
85
97
  kcp_ports = auto()
86
98
  kcp_enable = auto()
@@ -219,6 +231,16 @@ class ConfigEnum(StrEnum):
219
231
  self.tls_padding_enable: {'category': ConfigCategory.tls_trick, 'apply_mode': 'apply', 'type': bool},
220
232
  self.tls_padding_length: {'category': ConfigCategory.tls_trick, 'apply_mode': 'apply'},
221
233
 
234
+ # mux
235
+ self.mux_enable: {'category': ConfigCategory.mux, 'apply_mode': 'apply', 'type': bool},
236
+ self.mux_protocol: {'category': ConfigCategory.mux, 'apply_mode': 'apply'},
237
+ self.mux_max_connections: {'category': ConfigCategory.mux, 'apply_mode': 'apply'},
238
+ self.mux_min_streams: {'category': ConfigCategory.mux, 'apply_mode': 'apply'},
239
+ self.mux_max_streams: {'category': ConfigCategory.mux, 'apply_mode': 'apply'},
240
+ self.mux_padding_enable: {'category': ConfigCategory.mux, 'apply_mode': 'apply', 'type': bool},
241
+ self.mux_brutal_enable: {'category': ConfigCategory.mux, 'apply_mode': 'apply', 'type': bool},
242
+ self.mux_brutal_up_mbps: {'category': ConfigCategory.mux, 'apply_mode': 'apply'},
243
+ self.mux_brutal_down_mbps: {'category': ConfigCategory.mux, 'apply_mode': 'apply'},
222
244
 
223
245
  self.http_ports: {'category': ConfigCategory.http, 'apply_mode': 'apply'}, # http
224
246
  self.kcp_ports: {'category': ConfigCategory.hidden, 'apply_mode': 'apply'},
@@ -257,7 +279,7 @@ class ConfigEnum(StrEnum):
257
279
  self.tuic_port: {'category': ConfigCategory.hidden, 'apply_mode': 'apply'},
258
280
 
259
281
  self.hysteria_enable: {'category': ConfigCategory.hidden, 'type': bool, 'apply_mode': 'apply'},
260
- self.hysteria_port: {'category': ConfigCategory.hysteria, 'apply_mode': 'apply'},
282
+ self.hysteria_port: {'category': ConfigCategory.hidden, 'apply_mode': 'apply'},
261
283
  self.hysteria_obfs_enable: {'category': ConfigCategory.hysteria, 'type': bool, 'apply_mode': 'apply'},
262
284
  self.hysteria_up_mbps: {'category': ConfigCategory.hysteria, 'apply_mode': 'apply'},
263
285
  self.hysteria_down_mbps: {'category': ConfigCategory.hysteria, 'apply_mode': 'apply'},
@@ -1,7 +1,7 @@
1
1
  from enum import auto
2
2
  from urllib.parse import urlparse
3
3
 
4
- from flask import flash, request
4
+ from flask import request
5
5
  from sqlalchemy_serializer import SerializerMixin
6
6
  from strenum import StrEnum
7
7
 
@@ -162,7 +162,7 @@ def get_proxy_domains_db(db_domain):
162
162
  domain = urlparse(request.base_url).hostname
163
163
  db_domain = Domain(domain=domain, mode=DomainType.direct, show_domains=[])
164
164
  # print("no domain")
165
- flash(_("This domain does not exist in the panel!" + domain))
165
+ hutils.flask.flash(_("This domain does not exist in the panel!" + domain)) #type: ignore
166
166
 
167
167
  return db_domain.show_domains or Domain.query.all()
168
168
 
@@ -1,3 +1,4 @@
1
+ from hiddifypanel.models.domain import Domain
1
2
  from sqlalchemy.orm import backref
2
3
  from sqlalchemy_serializer import SerializerMixin
3
4
 
@@ -1,6 +1,6 @@
1
1
  import datetime
2
2
  from enum import auto
3
-
3
+ from hiddifypanel.models.role import Role
4
4
  from dateutil import relativedelta
5
5
  from sqlalchemy_serializer import SerializerMixin
6
6
  from strenum import StrEnum
@@ -10,7 +10,8 @@ from hiddifypanel.panel.database import db
10
10
  from hiddifypanel.models import Lang
11
11
  from hiddifypanel.models.utils import fill_password, fill_username
12
12
  from .base_account import BaseAccount
13
-
13
+ from .admin import AdminUser
14
+ from hiddifypanel import hutils
14
15
  ONE_GIG = 1024*1024*1024
15
16
 
16
17
 
@@ -26,6 +27,7 @@ class UserMode(StrEnum):
26
27
  weekly = auto()
27
28
  daily = auto()
28
29
 
30
+
29
31
  # disable = auto()
30
32
  package_mode_dic = {
31
33
  UserMode.daily: 1,
@@ -75,16 +77,15 @@ class User(BaseAccount):
75
77
  max_ips = db.Column(db.Integer, default=1000, nullable=False)
76
78
  details = db.relationship('UserDetail', cascade="all,delete", backref='user', lazy='dynamic',)
77
79
  enable = db.Column(db.Boolean, default=True, nullable=False)
78
- lang = db.Column(db.Enum(Lang), default=None)
79
80
  ed25519_private_key = db.Column(db.String(500))
80
81
  ed25519_public_key = db.Column(db.String(100))
81
- # These columns are created by BaseAccount
82
- # uuid = db.Column(db.String(36), default=lambda: str(uuid_mod.uuid4()), nullable=False, unique=True)
83
- # name = db.Column(db.String(512), nullable=False, default='')
84
- # username = db.Column(db.String(16), nullable=True, default='')
85
- # password = db.Column(db.String(16), nullable=True, default='')
86
- # comment = db.Column(db.String(512))
87
- # telegram_id = db.Column(db.String(512))
82
+
83
+ @property
84
+ def role(self) -> Role | None:
85
+ return Role.user
86
+
87
+ def get_id(self) -> str | None:
88
+ return f'user_{self.id}'
88
89
 
89
90
  @property
90
91
  def current_usage_GB(self):
@@ -176,26 +177,99 @@ class User(BaseAccount):
176
177
  res = self.package_days
177
178
  return min(res, 10000)
178
179
 
179
- @staticmethod
180
- def from_dict(data):
181
- """
182
- Returns a new User object created from a dictionary.
183
- """
184
-
185
- return User(
186
- name=data.get('name', ''),
187
- expiry_time=data.get('expiry_time', datetime.date.today() + relativedelta.relativedelta(months=6)),
188
- usage_limit_GB=data.get('usage_limit_GB', 1000),
189
- package_days=data.get('package_days', 90),
190
- mode=UserMode[data.get('mode', 'no_reset')],
191
- monthly=data.get('monthly', False),
192
- start_date=data.get('start_date', None),
193
- current_usage_GB=data.get('current_usage_GB', 0),
194
- last_reset_time=data.get('last_reset_time', datetime.date.today()),
195
- comment=data.get('comment', None),
196
- telegram_id=data.get('telegram_id', None),
197
- added_by=data.get('added_by', 1)
198
- )
180
+ @classmethod
181
+ def by_uuid(cls, uuid: str, create: bool = False):
182
+ account = User.query.filter(User.uuid == uuid).first()
183
+ if not account and create:
184
+ dbuser = User(uuid=uuid, name="unknown", added_by=AdminUser.current_admin_or_owner().id)
185
+ db.session.add(dbuser)
186
+ db.session.commit()
187
+ account = User.by_uuid(uuid, False)
188
+ return account
189
+
190
+ @classmethod
191
+ def add_or_update(cls, commit: bool = True, **data):
192
+
193
+ dbuser = super().add_or_update(commit=commit, **data)
194
+ if data.get('added_by_uuid'):
195
+ admin = AdminUser.by_uuid(data.get('added_by_uuid'), create=True) or AdminUser.current_admin_or_owner()
196
+ dbuser.added_by = admin.id
197
+ else:
198
+ dbuser.added_by = 1
199
+
200
+ if data.get('expiry_time', ''):
201
+ last_reset_time = hutils.json.json_to_date(data.get('last_reset_time', '')) or datetime.date.today()
202
+
203
+ expiry_time = hutils.json.json_to_date(data['expiry_time'])
204
+ dbuser.start_date = last_reset_time
205
+ dbuser.package_days = (expiry_time-last_reset_time).days # type: ignore
206
+
207
+ elif 'package_days' in data:
208
+ dbuser.package_days = data['package_days']
209
+ if data.get('start_date', ''):
210
+ dbuser.start_date = hutils.json.json_to_date(data['start_date'])
211
+ else:
212
+ dbuser.start_date = None
213
+ dbuser.current_usage_GB = data['current_usage_GB']
214
+
215
+ dbuser.usage_limit_GB = data['usage_limit_GB']
216
+ dbuser.enable = data.get('enable', True)
217
+ if data.get('ed25519_private_key', ''):
218
+ dbuser.ed25519_private_key = data.get('ed25519_private_key', '')
219
+ dbuser.ed25519_public_key = data.get('ed25519_public_key', '')
220
+ if not dbuser.ed25519_private_key:
221
+ from hiddifypanel.panel import hiddify
222
+ priv, publ = hiddify.get_ed25519_private_public_pair()
223
+ dbuser.ed25519_private_key = priv
224
+ dbuser.ed25519_public_key = publ
225
+
226
+ mode = data.get('mode', UserMode.no_reset)
227
+ if mode == 'disable':
228
+ mode = UserMode.no_reset
229
+ dbuser.enable = False
230
+
231
+ dbuser.mode = mode
232
+
233
+ dbuser.last_online = hutils.json.json_to_time(data.get('last_online')) or datetime.datetime.min
234
+ if commit:
235
+ db.session.commit()
236
+ return dbuser
237
+
238
+ def to_dict(self, convert_date=True) -> dict:
239
+ base = super().to_dict()
240
+ return {**base,
241
+ 'last_online': hutils.json.time_to_json(self.last_online) if convert_date else self.last_online,
242
+ 'usage_limit_GB': self.usage_limit_GB,
243
+ 'package_days': self.package_days,
244
+ 'mode': self.mode,
245
+ 'start_date': hutils.json.date_to_json(self.start_date)if convert_date else self.start_date,
246
+ 'current_usage_GB': self.current_usage_GB,
247
+ 'last_reset_time': hutils.json.date_to_json(self.last_reset_time) if convert_date else self.last_reset_time,
248
+ 'added_by_uuid': self.admin.uuid,
249
+ 'ed25519_private_key': self.ed25519_private_key,
250
+ 'ed25519_public_key': self.ed25519_public_key
251
+ }
252
+
253
+ # @staticmethod
254
+ # def from_dict(data):
255
+ # """
256
+ # Returns a new User object created from a dictionary.
257
+ # """
258
+
259
+ # return User(
260
+ # name=data.get('name', ''),
261
+ # expiry_time=data.get('expiry_time', datetime.date.today() + relativedelta.relativedelta(months=6)),
262
+ # usage_limit_GB=data.get('usage_limit_GB', 1000),
263
+ # package_days=data.get('package_days', 90),
264
+ # mode=UserMode[data.get('mode', 'no_reset')],
265
+ # monthly=data.get('monthly', False),
266
+ # start_date=data.get('start_date', None),
267
+ # current_usage_GB=data.get('current_usage_GB', 0),
268
+ # last_reset_time=data.get('last_reset_time', datetime.date.today()),
269
+ # comment=data.get('comment', None),
270
+ # telegram_id=data.get('telegram_id', None),
271
+ # added_by=data.get('added_by', 1)
272
+ # )
199
273
 
200
274
 
201
275
  # TODO: refactor this function too
@@ -211,9 +285,7 @@ def remove(user: User, commit=True) -> None:
211
285
 
212
286
  def remove_user(uuid: str, commit=True):
213
287
  dbuser = User.by_uuid(uuid)
214
- db.session.delete(dbuser)
215
- if commit:
216
- db.session.commit()
288
+ remove(dbuser, commit)
217
289
 
218
290
 
219
291
  @event.listens_for(User, 'before_insert')
@@ -9,18 +9,18 @@ def fill_username(model) -> None:
9
9
  minimum_username_length = 10
10
10
 
11
11
  if len(base_username) < minimum_username_length:
12
- base_username += hutils.utils.get_random_string(minimum_username_length-len(base_username), minimum_username_length)
12
+ base_username += hutils.random.get_random_string(minimum_username_length-len(base_username), minimum_username_length)
13
13
 
14
14
  if len(base_username) > 100:
15
15
  base_username = base_username[0:100]
16
16
  model.username = base_username
17
17
 
18
18
  while not model.is_username_unique():
19
- rand_str = hutils.utils.get_random_string(2, 4)
19
+ rand_str = hutils.random.get_random_string(2, 4)
20
20
  model.username += rand_str
21
21
 
22
22
 
23
23
  def fill_password(model) -> None:
24
24
  # TODO: hash the password
25
25
  if not model.password or len(model.password) < 16:
26
- model.password=hutils.utils.get_random_password(length = 16)
26
+ model.password = hutils.random.get_random_password(length=16)
@@ -13,7 +13,6 @@ from hiddifypanel import hutils
13
13
  from hiddifypanel.models import *
14
14
  from hiddifypanel.panel import hiddify, usage
15
15
  from hiddifypanel.panel.run_commander import commander, Command
16
- from hiddifypanel.panel.hiddify import flash
17
16
 
18
17
 
19
18
  class Actions(FlaskView):
@@ -99,13 +98,13 @@ class Actions(FlaskView):
99
98
  # try:
100
99
  # hiddify_api.sync_child_to_parent()
101
100
  # except e as Exception:
102
- # flash(_('can not sync child with parent panel')+" "+e)
101
+ # hutils.flask.flash(_('can not sync child with parent panel')+" "+e)
103
102
 
104
103
  domain_changed = request.args.get("domain_changed", str(domain_changed)).lower() == "true"
105
104
  complete_install = request.args.get("complete_install", str(complete_install)).lower() == "true"
106
105
  if domain_changed:
107
- flash((_('Your domains changed. Please do not forget to copy admin links, otherwise you can not access to the panel anymore.')), 'info')
108
- # flash(f'complete_install={complete_install} domain_changed={domain_changed} ', 'info')
106
+ hutils.flask.flash((_('Your domains changed. Please do not forget to copy admin links, otherwise you can not access to the panel anymore.')), 'info')
107
+ # hutils.flask.flash(f'complete_install={complete_install} domain_changed={domain_changed} ', 'info')
109
108
  # return render_template("result.html")
110
109
  # hiddify.add_temporary_access()
111
110
  file = "install.sh" if complete_install else "apply_configs.sh"
@@ -147,7 +146,7 @@ class Actions(FlaskView):
147
146
  key = hiddify.generate_x25519_keys()
148
147
  set_hconfig(ConfigEnum.reality_private_key, key['private_key'])
149
148
  set_hconfig(ConfigEnum.reality_public_key, key['public_key'])
150
- hiddify.flash_config_success(restart_mode='apply', domain_changed=False)
149
+ hutils.flask.flash_config_success(restart_mode='apply', domain_changed=False)
151
150
  return redirect(url_for('admin.SettingAdmin:index'))
152
151
 
153
152
  @login_required(roles={Role.super_admin})
@@ -232,4 +231,4 @@ def get_log_api_url():
232
231
 
233
232
 
234
233
  def get_domains():
235
- return [str(d.domain).replace("*", hiddify.get_random_string(3, 6)) for d in get_panel_domains(always_add_all_domains=True, always_add_ip=True)]
234
+ return [str(d.domain).replace("*", hutils.random.get_random_string(3, 6)) for d in get_panel_domains(always_add_all_domains=True, always_add_ip=True)]
@@ -168,7 +168,7 @@ class AdminstratorAdmin(AdminLTEModelView):
168
168
 
169
169
  # @login_required(roles={Role.super_admin, Role.admin})
170
170
  def is_accessible(self):
171
- if login_required(roles={Role.super_admin, Role.admin})(lambda: True)() != True:
171
+ if login_required(roles={Role.super_admin, Role.admin, Role.agent})(lambda: True)() != True:
172
172
  return False
173
173
  return True
174
174