Skip to content

Commit 600628b

Browse files
authored
Merge pull request #10 from remnawave/development
Bump version, fix models and add new route
2 parents da6add4 + 0fc43ee commit 600628b

8 files changed

Lines changed: 182 additions & 80 deletions

File tree

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@ pip install git+https://github.com/remnawave/python-sdk.git@development
6363

6464
| Contract Version | Remnawave Panel Version |
6565
| ---------------- | ----------------------- |
66-
| 2.1.9 | >=2.1.9 |
66+
| 2.1.13 | >=2.1.13 |
67+
| 2.1.9 | >=2.1.9, <=2.1.12 |
6768
| 2.1.8 | ==2.1.8 |
6869
| 2.1.7.post1 | ==2.1.7 |
6970
| 2.1.4 | >=2.1.4, <2.1.7 |

pyproject.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[project]
22
name = "remnawave"
3-
version = "2.1.9"
4-
description = "A Python SDK for interacting with the Remnawave API v2.1.9."
3+
version = "2.1.13"
4+
description = "A Python SDK for interacting with the Remnawave API v2.1.13."
55
authors = [
66
{name = "Artem",email = "dev@forestsnet.com"}
77
]
@@ -56,4 +56,4 @@ asyncio_default_fixture_loop_scope = "function"
5656

5757
[build-system]
5858
requires = ["poetry-core>=2.0.0,<3.0.0"]
59-
build-backend = "poetry.core.masonry.api"
59+
build-backend = "poetry.core.masonry.api"

remnawave/controllers/hwid.py

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,34 +5,60 @@
55
CreateUserHwidDeviceResponseDto,
66
DeleteUserHwidDeviceResponseDto,
77
GetUserHwidDevicesResponseDto,
8+
GetHwidStatisticsResponseDto,
89
CreateHWIDUser,
9-
HWIDDeleteRequest
10+
HWIDDeleteRequest,
11+
DeleteUserAllHwidDeviceRequestDto
1012
)
1113
from rapid_api_client import Path, PydanticBody
1214
from remnawave.rapid import AttributeBody, BaseController, post, get
1315

1416

1517
class HWIDUserController(BaseController):
18+
@get("/hwid/devices", response_class=GetUserHwidDevicesResponseDto)
19+
async def get_hwid_users(
20+
self,
21+
size: Annotated[int | None, AttributeBody()] = None,
22+
start: Annotated[int | None, AttributeBody()] = None,
23+
) -> GetUserHwidDevicesResponseDto:
24+
"""Get all user HWID devices"""
25+
...
26+
27+
@get("/hwid/devices/stats", response_class=GetHwidStatisticsResponseDto)
28+
async def get_hwid_stats(
29+
self,
30+
) -> GetHwidStatisticsResponseDto:
31+
"""Get HWID statistics"""
32+
...
33+
1634
@post("/hwid/devices", response_class=CreateUserHwidDeviceResponseDto)
1735
async def add_hwid_to_users(
1836
self,
1937
body: Annotated[CreateHWIDUser, PydanticBody()],
2038
) -> CreateUserHwidDeviceResponseDto:
2139
"""Create a user HWID device"""
2240
...
23-
41+
2442
@post("/hwid/devices/delete", response_class=DeleteUserHwidDeviceResponseDto)
2543
async def delete_hwid_to_user(
2644
self,
2745
body: Annotated[HWIDDeleteRequest, PydanticBody()],
2846
) -> DeleteUserHwidDeviceResponseDto:
2947
"""Delete a user HWID device"""
3048
...
31-
49+
50+
@post("/hwid/devices/delete-all", response_class=DeleteUserHwidDeviceResponseDto)
51+
async def delete_all_hwid_user(
52+
self,
53+
body: Annotated[DeleteUserAllHwidDeviceRequestDto, PydanticBody()],
54+
) -> DeleteUserHwidDeviceResponseDto:
55+
"""Delete all user HWID devices"""
56+
...
57+
3258
@get("/hwid/devices/{uuid}", response_class=GetUserHwidDevicesResponseDto)
3359
async def get_hwid_user(
3460
self,
3561
uuid: Annotated[str, Path(description="UUID of the User")],
3662
) -> GetUserHwidDevicesResponseDto:
3763
"""Get a user HWID device"""
38-
...
64+
...

remnawave/models/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@
7474
HWIDDeleteRequest, # Legacy alias
7575
HWIDUserResponseDto, # Legacy alias
7676
HWIDUserResponseDtoList, # Legacy alias
77+
GetHwidStatisticsResponseDto,
78+
DeleteUserAllHwidDeviceRequestDto
7779
)
7880
from .inbounds import (
7981
AllInboundsData,
@@ -346,6 +348,8 @@
346348
"HWIDDeleteRequest", # Legacy alias
347349
"HWIDUserResponseDto", # Legacy alias
348350
"HWIDUserResponseDtoList", # Legacy alias
351+
"GetHwidStatisticsResponseDto",
352+
"DeleteUserAllHwidDeviceRequestDto",
349353
# Bandwidth stats models
350354
"GetNodeUserUsageByRangeResponseDto",
351355
"GetNodesRealtimeUsageResponseDto",

remnawave/models/hwid.py

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,28 +31,54 @@ class HwidDeviceDto(BaseModel):
3131

3232

3333
class HwidDevicesData(BaseModel):
34-
total: float
34+
total: int
3535
devices: List[HwidDeviceDto]
3636

3737

3838
class CreateUserHwidDeviceResponseDto(BaseModel):
39-
total: float
39+
total: int
4040
devices: List[HwidDeviceDto]
4141

4242

4343
class DeleteUserHwidDeviceResponseDto(BaseModel):
44-
total: float
44+
total: int
4545
devices: List[HwidDeviceDto]
4646

4747

4848
class GetUserHwidDevicesResponseDto(BaseModel):
49-
total: float
49+
total: int
5050
devices: List[HwidDeviceDto]
5151

52+
class PlatformStatItem(BaseModel):
53+
platform: str
54+
count: float
55+
56+
57+
class AppStatItem(BaseModel):
58+
app: str
59+
count: float
60+
61+
62+
class HwidStats(BaseModel):
63+
total_unique_devices: float = Field(alias="totalUniqueDevices")
64+
total_hwid_devices: float = Field(alias="totalHwidDevices")
65+
average_hwid_devices_per_user: float = Field(alias="averageHwidDevicesPerUser")
66+
67+
68+
class HwidStatisticsData(BaseModel):
69+
by_platform: List[PlatformStatItem] = Field(alias="byPlatform")
70+
by_app: List[AppStatItem] = Field(alias="byApp")
71+
stats: HwidStats
72+
73+
74+
class GetHwidStatisticsResponseDto(HwidStatisticsData):
75+
pass
76+
77+
class DeleteUserAllHwidDeviceRequestDto(BaseModel):
78+
user_uuid: UUID = Field(serialization_alias="userUuid")
5279

5380
# Legacy aliases for backward compatibility
5481
CreateHWIDUser = CreateUserHwidDeviceRequestDto
5582
HWIDUserResponseDto = HwidDeviceDto
5683
HWIDUserResponseDtoList = HwidDevicesData
5784
HWIDDeleteRequest = DeleteUserHwidDeviceRequestDto
58-

remnawave/models/users.py

Lines changed: 54 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from datetime import datetime
2-
from typing import Annotated, List, Optional
2+
from typing import Annotated
33
from uuid import UUID
44

55
from pydantic import (
@@ -16,8 +16,8 @@ class UserActiveInboundsDto(BaseModel):
1616
uuid: UUID
1717
tag: str
1818
type: str
19-
network: Optional[str] = None
20-
security: Optional[str] = None
19+
network: str | None = None
20+
security: str | None = None
2121

2222

2323
class UserLastConnectedNodeDto(BaseModel):
@@ -39,119 +39,115 @@ class CreateUserRequestDto(BaseModel):
3939
username: Annotated[
4040
str, StringConstraints(pattern=r"^[a-zA-Z0-9_-]+$", min_length=3, max_length=36)
4141
]
42-
created_at: Optional[datetime] = Field(None, serialization_alias="createdAt")
43-
status: Optional[UserStatus] = None
44-
subscription_uuid: Optional[str] = Field(
45-
None, serialization_alias="subscriptionUuid"
46-
)
47-
short_uuid: Optional[str] = Field(None, serialization_alias="shortUuid")
42+
created_at: datetime | None = Field(None, serialization_alias="createdAt")
43+
status: UserStatus | None = None
44+
short_uuid: str | None = Field(None, serialization_alias="shortUuid")
4845
trojan_password: Annotated[
49-
Optional[str], StringConstraints(min_length=8, max_length=32)
46+
str | None, StringConstraints(min_length=8, max_length=32)
5047
] = Field(None, serialization_alias="trojanPassword")
51-
vless_uuid: Optional[str] = Field(None, serialization_alias="vlessUuid")
48+
vless_uuid: str | None = Field(None, serialization_alias="vlessUuid")
5249
ss_password: Annotated[
53-
Optional[str], StringConstraints(min_length=8, max_length=32)
50+
str | None, StringConstraints(min_length=8, max_length=32)
5451
] = Field(None, serialization_alias="ssPassword")
55-
traffic_limit_bytes: Optional[int] = Field(
52+
traffic_limit_bytes: int | None = Field(
5653
None, serialization_alias="trafficLimitBytes", strict=True, ge=0
5754
)
58-
traffic_limit_strategy: Optional[TrafficLimitStrategy] = Field(
55+
traffic_limit_strategy: TrafficLimitStrategy | None = Field(
5956
None, serialization_alias="trafficLimitStrategy"
6057
)
61-
last_traffic_reset_at: Optional[datetime] = Field(
58+
last_traffic_reset_at: datetime | None = Field(
6259
None, serialization_alias="lastTrafficResetAt"
6360
)
64-
description: Optional[str] = None
65-
tag: Optional[str] = None
66-
telegram_id: Optional[int] = Field(None, serialization_alias="telegramId")
67-
email: Optional[str] = None
68-
hwidDeviceLimit: Optional[int] = Field(
61+
description: str | None = None
62+
tag: str | None = None
63+
telegram_id: int | None = Field(None, serialization_alias="telegramId")
64+
email: str | None = None
65+
hwidDeviceLimit: int | None = Field(
6966
None, serialization_alias="hwidDeviceLimit", strict=True, ge=0
7067
)
71-
active_internal_squads: Optional[List[str]] = Field(
68+
active_internal_squads: list[str] | None = Field(
7269
None, serialization_alias="activeInternalSquads"
7370
)
7471

7572

7673
class UpdateUserRequestDto(BaseModel):
7774
uuid: UUID
78-
active_internal_squads: Optional[List[str]] = Field(
75+
active_internal_squads: list[str] | None = Field(
7976
None, serialization_alias="activeInternalSquads"
8077
)
81-
description: Optional[str] = None
82-
email: Optional[str] = None
83-
expire_at: Optional[datetime] = Field(None, serialization_alias="expireAt")
84-
hwidDeviceLimit: Optional[int] = Field(
78+
description: str | None = None
79+
email: str | None = None
80+
expire_at: datetime | None = Field(None, serialization_alias="expireAt")
81+
hwidDeviceLimit: int | None = Field(
8582
None, serialization_alias="hwidDeviceLimit", strict=True, ge=0
8683
)
87-
status: Optional[UserStatus] = None
88-
tag: Optional[str] = None
89-
telegram_id: Optional[int] = Field(None, serialization_alias="telegramId")
90-
traffic_limit_bytes: Optional[int] = Field(
84+
status: UserStatus | None = None
85+
tag: str | None = None
86+
telegram_id: int | None = Field(None, serialization_alias="telegramId")
87+
traffic_limit_bytes: int | None = Field(
9188
None, serialization_alias="trafficLimitBytes", strict=True, ge=0
9289
)
93-
traffic_limit_strategy: Optional[TrafficLimitStrategy] = Field(
90+
traffic_limit_strategy: TrafficLimitStrategy | None = Field(
9491
None, serialization_alias="trafficLimitStrategy"
9592
)
9693

9794

9895
class UserResponseDto(BaseModel):
9996
uuid: UUID
100-
subscription_uuid: Optional[UUID] = Field(None, alias="subscriptionUuid")
10197
short_uuid: str = Field(alias="shortUuid")
10298
username: str
103-
status: Optional[UserStatus] = None
99+
status: UserStatus | None = None
104100
used_traffic_bytes: float = Field(alias="usedTrafficBytes")
105101
lifetime_used_traffic_bytes: float = Field(alias="lifetimeUsedTrafficBytes")
106-
traffic_limit_bytes: Optional[int] = Field(None, alias="trafficLimitBytes")
107-
traffic_limit_strategy: Optional[str] = Field(None, alias="trafficLimitStrategy")
108-
sub_last_user_agent: Optional[str] = Field(None, alias="subLastUserAgent")
109-
sub_last_opened_at: Optional[datetime] = Field(None, alias="subLastOpenedAt")
110-
expire_at: Optional[datetime] = Field(None, alias="expireAt")
111-
online_at: Optional[datetime] = Field(None, alias="onlineAt")
112-
sub_revoked_at: Optional[datetime] = Field(None, alias="subRevokedAt")
113-
last_traffic_reset_at: Optional[datetime] = Field(None, alias="lastTrafficResetAt")
102+
traffic_limit_bytes: int | None = Field(None, alias="trafficLimitBytes")
103+
traffic_limit_strategy: str | None = Field(None, alias="trafficLimitStrategy")
104+
sub_last_user_agent: str | None = Field(None, alias="subLastUserAgent")
105+
sub_last_opened_at: datetime | None = Field(None, alias="subLastOpenedAt")
106+
expire_at: datetime | None = Field(None, alias="expireAt")
107+
online_at: datetime | None = Field(None, alias="onlineAt")
108+
sub_revoked_at: datetime | None = Field(None, alias="subRevokedAt")
109+
last_traffic_reset_at: datetime | None = Field(None, alias="lastTrafficResetAt")
114110
trojan_password: str = Field(alias="trojanPassword")
115111
vless_uuid: UUID = Field(alias="vlessUuid")
116112
ss_password: str = Field(alias="ssPassword")
117-
description: Optional[str] = None
118-
telegram_id: Optional[int] = Field(None, alias="telegramId")
119-
email: Optional[str] = None
120-
hwidDeviceLimit: Optional[int] = Field(
113+
description: str | None = None
114+
telegram_id: int | None = Field(None, alias="telegramId")
115+
email: str | None = None
116+
hwidDeviceLimit: int | None = Field(
121117
None, serialization_alias="hwidDeviceLimit", strict=True, ge=0
122118
)
123-
active_internal_squads: Optional[List[ActiveInternalSquadDto]] = Field(
119+
active_internal_squads: list[ActiveInternalSquadDto] | None = Field(
124120
None, alias="activeInternalSquads"
125121
)
126-
subscription_url: str = Field(alias="subscriptionUrl")
127-
first_connected: Optional[datetime] = Field(None, alias="firstConnectedAt")
128-
last_trigger_threshold: Optional[int] = Field(None, alias="lastTriggeredThreshold")
129-
last_connected_node: Optional[UserLastConnectedNodeDto] = Field(
122+
subscription_url: str | None = Field(None, alias="subscriptionUrl")
123+
first_connected: datetime | None = Field(None, alias="firstConnectedAt")
124+
last_trigger_threshold: int | None = Field(None, alias="lastTriggeredThreshold")
125+
last_connected_node: UserLastConnectedNodeDto | None = Field(
130126
None, alias="lastConnectedNode"
131127
)
132-
happ: Optional[HappCrypto] = Field(None, alias="happ")
133-
tag: Optional[str] = Field(None, alias="tag")
128+
happ: HappCrypto | None = Field(None, alias="happ")
129+
tag: str | None = Field(None, alias="tag")
134130
created_at: datetime = Field(alias="createdAt")
135131
updated_at: datetime = Field(alias="updatedAt")
136132

137133

138-
class EmailUserResponseDto(RootModel[List[UserResponseDto]]):
134+
class EmailUserResponseDto(RootModel[list[UserResponseDto]]):
139135
def __iter__(self):
140136
return iter(self.root)
141137

142138
def __getitem__(self, item):
143139
return self.root[item]
144140

145141

146-
class TagUserResponseDto(RootModel[List[UserResponseDto]]):
142+
class TagUserResponseDto(RootModel[list[UserResponseDto]]):
147143
def __iter__(self):
148144
return iter(self.root)
149145

150146
def __getitem__(self, item):
151147
return self.root[item]
152148

153149

154-
class TelegramUserResponseDto(RootModel[List[UserResponseDto]]):
150+
class TelegramUserResponseDto(RootModel[list[UserResponseDto]]):
155151
def __iter__(self):
156152
return iter(self.root)
157153

@@ -160,7 +156,7 @@ def __getitem__(self, item):
160156

161157

162158
class UsersResponseDto(BaseModel):
163-
users: List[UserResponseDto]
159+
users: list[UserResponseDto]
164160
total: float
165161

166162

@@ -169,7 +165,7 @@ class DeleteUserResponseDto(BaseModel):
169165

170166

171167
class TagsResponseDto(BaseModel):
172-
tags: List[str]
168+
tags: list[str]
173169

174170

175171
class CreateUserResponseDto(UserResponseDto):
@@ -213,7 +209,7 @@ class GetUserByUsernameResponseDto(UserResponseDto):
213209

214210

215211
class RevokeUserRequestDto(BaseModel):
216-
short_uuid: Optional[str] = Field(
212+
short_uuid: str | None = Field(
217213
None,
218214
serialization_alias="shortUuid",
219215
description="Optional. If not provided, a new short UUID will be generated by Remnawave. Please note that it is strongly recommended to allow Remnawave to generate the short UUID.",

0 commit comments

Comments
 (0)