mirror of
https://github.com/remnawave/python-sdk.git
synced 2026-05-13 12:16:42 +00:00
Merge pull request #28 from remnawave:development
Update API versions and add new models for HWID and users
This commit is contained in:
commit
95a93d72d5
13 changed files with 220 additions and 19 deletions
|
|
@ -63,7 +63,8 @@ pip install git+https://github.com/remnawave/python-sdk.git@development
|
|||
|
||||
| Contract Version | Remnawave Panel Version |
|
||||
| ---------------- | ----------------------- |
|
||||
| 2.3.0 | >=2.3.0 |
|
||||
| 2.3.2 | >=2.3.0 |
|
||||
| 2.3.0 | >=2.3.0, <2.3.2 |
|
||||
| 2.2.6 | ==2.2.6 |
|
||||
| 2.2.3 | >=2.2.13 |
|
||||
| 2.1.19 | >=2.1.19, <2.2.0 |
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
[project]
|
||||
name = "remnawave"
|
||||
version = "2.3.0"
|
||||
description = "A Python SDK for interacting with the Remnawave API v2.3.0."
|
||||
version = "2.3.2"
|
||||
description = "A Python SDK for interacting with the Remnawave API v2.3.2."
|
||||
authors = [
|
||||
{name = "Artem",email = "dev@forestsnet.com"}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from typing import Annotated
|
||||
from typing import Annotated, Optional
|
||||
from uuid import UUID
|
||||
|
||||
from remnawave.models import (
|
||||
|
|
@ -8,9 +8,10 @@ from remnawave.models import (
|
|||
GetHwidStatisticsResponseDto,
|
||||
CreateHWIDUser,
|
||||
HWIDDeleteRequest,
|
||||
DeleteUserAllHwidDeviceRequestDto
|
||||
DeleteUserAllHwidDeviceRequestDto,
|
||||
GetTopUsersByHwidDevicesResponseDto
|
||||
)
|
||||
from rapid_api_client import Path, PydanticBody
|
||||
from rapid_api_client import Path, PydanticBody, Query
|
||||
from remnawave.rapid import AttributeBody, BaseController, post, get
|
||||
|
||||
|
||||
|
|
@ -62,3 +63,12 @@ class HWIDUserController(BaseController):
|
|||
) -> GetUserHwidDevicesResponseDto:
|
||||
"""Get a user HWID device"""
|
||||
...
|
||||
|
||||
@get("/hwid/devices/top-users", response_class=GetTopUsersByHwidDevicesResponseDto)
|
||||
async def get_top_users_by_hwid_devices(
|
||||
self,
|
||||
size: Annotated[Optional[int], Query(default=None, description="Page size for pagination")] = None,
|
||||
start: Annotated[Optional[int], Query(default=None, description="Offset for pagination")] = None,
|
||||
) -> GetTopUsersByHwidDevicesResponseDto:
|
||||
"""Get top users by HWID devices"""
|
||||
...
|
||||
|
|
@ -84,7 +84,10 @@ from .hwid import (
|
|||
HWIDUserResponseDto, # Legacy alias
|
||||
HWIDUserResponseDtoList, # Legacy alias
|
||||
GetHwidStatisticsResponseDto,
|
||||
DeleteUserAllHwidDeviceRequestDto
|
||||
DeleteUserAllHwidDeviceRequestDto,
|
||||
GetTopUsersByHwidDevicesResponseDto,
|
||||
TopUserByHwidDevicesDto,
|
||||
TopUsersByHwidDevicesData,
|
||||
)
|
||||
from .inbounds import (
|
||||
AllInboundsData,
|
||||
|
|
@ -351,6 +354,7 @@ from .webhook import (
|
|||
CustomErrorEventDto,
|
||||
CrmEventDto,
|
||||
WebhookPayloadDto,
|
||||
UserTrafficDto
|
||||
)
|
||||
from .passkeys import (
|
||||
DeletePasskeyRequestDto,
|
||||
|
|
@ -545,6 +549,9 @@ __all__ = [
|
|||
"HWIDUserResponseDtoList", # Legacy alias
|
||||
"GetHwidStatisticsResponseDto",
|
||||
"DeleteUserAllHwidDeviceRequestDto",
|
||||
"GetTopUsersByHwidDevicesResponseDto",
|
||||
"TopUserByHwidDevicesDto",
|
||||
"TopUsersByHwidDevicesData",
|
||||
# Bandwidth stats models
|
||||
"GetNodeUserUsageByRangeResponseDto",
|
||||
"GetNodesRealtimeUsageResponseDto",
|
||||
|
|
@ -712,6 +719,7 @@ __all__ = [
|
|||
"BaseUserDto",
|
||||
"UserDto",
|
||||
"UserEventDto",
|
||||
"UserTrafficDto",
|
||||
|
||||
# HWID DEVICES
|
||||
"HwidUserDeviceDto",
|
||||
|
|
|
|||
|
|
@ -23,6 +23,14 @@ class NodesUsageResponseDto(RootModel[List[NodeUsageResponseDto]]):
|
|||
|
||||
def __getitem__(self, item):
|
||||
return self.root[item]
|
||||
|
||||
def __bool__(self):
|
||||
"""Return True if list is not empty"""
|
||||
return bool(self.root)
|
||||
|
||||
def __len__(self):
|
||||
"""Return length of list"""
|
||||
return len(self.root)
|
||||
|
||||
|
||||
class GetNodesUsageByRangeResponseDto(RootModel[List[NodeUsageResponseDto]]):
|
||||
|
|
@ -31,6 +39,14 @@ class GetNodesUsageByRangeResponseDto(RootModel[List[NodeUsageResponseDto]]):
|
|||
|
||||
def __getitem__(self, item):
|
||||
return self.root[item]
|
||||
|
||||
def __bool__(self):
|
||||
"""Return True if list is not empty"""
|
||||
return bool(self.root)
|
||||
|
||||
def __len__(self):
|
||||
"""Return length of list"""
|
||||
return len(self.root)
|
||||
|
||||
|
||||
class NodeRealtimeUsageResponseDto(BaseModel):
|
||||
|
|
@ -51,6 +67,14 @@ class NodesRealtimeUsageResponseDto(RootModel[List[NodeRealtimeUsageResponseDto]
|
|||
|
||||
def __getitem__(self, item):
|
||||
return self.root[item]
|
||||
|
||||
def __bool__(self):
|
||||
"""Return True if list is not empty"""
|
||||
return bool(self.root)
|
||||
|
||||
def __len__(self):
|
||||
"""Return length of list"""
|
||||
return len(self.root)
|
||||
|
||||
|
||||
class GetNodesRealtimeUsageResponseDto(RootModel[List[NodeRealtimeUsageResponseDto]]):
|
||||
|
|
@ -59,6 +83,14 @@ class GetNodesRealtimeUsageResponseDto(RootModel[List[NodeRealtimeUsageResponseD
|
|||
|
||||
def __getitem__(self, item):
|
||||
return self.root[item]
|
||||
|
||||
def __bool__(self):
|
||||
"""Return True if list is not empty"""
|
||||
return bool(self.root)
|
||||
|
||||
def __len__(self):
|
||||
"""Return length of list"""
|
||||
return len(self.root)
|
||||
|
||||
|
||||
class UserUsageByRangeItem(BaseModel):
|
||||
|
|
@ -75,6 +107,14 @@ class GetUserUsageByRangeResponseDto(RootModel[List[UserUsageByRangeItem]]):
|
|||
|
||||
def __getitem__(self, item):
|
||||
return self.root[item]
|
||||
|
||||
def __bool__(self):
|
||||
"""Return True if list is not empty"""
|
||||
return bool(self.root)
|
||||
|
||||
def __len__(self):
|
||||
"""Return length of list"""
|
||||
return len(self.root)
|
||||
|
||||
|
||||
class NodeUserUsageItem(BaseModel):
|
||||
|
|
@ -91,3 +131,11 @@ class GetNodeUserUsageByRangeResponseDto(RootModel[List[NodeUserUsageItem]]):
|
|||
|
||||
def __getitem__(self, item):
|
||||
return self.root[item]
|
||||
|
||||
def __bool__(self):
|
||||
"""Return True if list is not empty"""
|
||||
return bool(self.root)
|
||||
|
||||
def __len__(self):
|
||||
"""Return length of list"""
|
||||
return len(self.root)
|
||||
|
|
|
|||
|
|
@ -39,8 +39,6 @@ class ExternalSquadSubscriptionSettingsDto(BaseModel):
|
|||
happ_routing: Optional[str] = Field(None, alias="happRouting")
|
||||
randomize_hosts: bool = Field(alias="randomizeHosts")
|
||||
|
||||
|
||||
# НОВЫЕ МОДЕЛИ
|
||||
class ExternalSquadHostOverridesDto(BaseModel):
|
||||
"""External squad host overrides"""
|
||||
server_description: Optional[str] = Field(None, alias="serverDescription", max_length=30)
|
||||
|
|
@ -61,7 +59,7 @@ class ExternalSquadDto(BaseModel):
|
|||
|
||||
|
||||
# Request/Response models
|
||||
class GetExternalSquadsResponseDto(ExternalSquadDto):
|
||||
class GetExternalSquadsResponseDto(BaseModel):
|
||||
"""Response with all external squads"""
|
||||
total: int = Field(alias="total")
|
||||
external_squads: List[ExternalSquadDto] = Field(alias="externalSquads")
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ class UpdateHostRequestDto(BaseModel):
|
|||
tag: Optional[Annotated[str, StringConstraints(max_length=32, pattern=r"^[A-Z0-9_:]+$")]] = None
|
||||
is_hidden: Optional[bool] = Field(None, serialization_alias="isHidden")
|
||||
override_sni_from_address: Optional[bool] = Field(None, serialization_alias="overrideSniFromAddress")
|
||||
keep_blank_sni: Optional[bool] = Field(None, serialization_alias="keepBlankSni")
|
||||
vless_route_id: Optional[int] = Field(None, serialization_alias="vlessRouteId", ge=0, le=65535)
|
||||
shuffle_host: Optional[bool] = Field(None, serialization_alias="shuffleHost")
|
||||
mihomo_x25519: Optional[bool] = Field(None, serialization_alias="mihomoX25519")
|
||||
|
|
@ -83,6 +84,7 @@ class HostResponseDto(BaseModel):
|
|||
security_layer: SecurityLayer = Field(SecurityLayer.DEFAULT, alias="securityLayer")
|
||||
is_hidden: bool = Field(False, alias="isHidden")
|
||||
override_sni_from_address: bool = Field(False, alias="overrideSniFromAddress")
|
||||
keep_blank_sni: bool = Field(False, alias="keepBlankSni")
|
||||
allow_insecure: bool = Field(False, alias="allowInsecure")
|
||||
xray_json_template_uuid: Optional[UUID] = Field(None, alias="xrayJsonTemplateUuid")
|
||||
excluded_internal_squads: List[UUID] = Field(default_factory=list, alias="excludedInternalSquads")
|
||||
|
|
@ -116,6 +118,7 @@ class CreateHostRequestDto(BaseModel):
|
|||
security_layer: SecurityLayer = Field(SecurityLayer.DEFAULT, serialization_alias="securityLayer")
|
||||
is_hidden: bool = Field(False, serialization_alias="isHidden")
|
||||
override_sni_from_address: bool = Field(False, serialization_alias="overrideSniFromAddress")
|
||||
keep_blank_sni: bool = Field(False, serialization_alias="keepBlankSni")
|
||||
xray_json_template_uuid: Optional[UUID] = Field(None, serialization_alias="xrayJsonTemplateUuid")
|
||||
excluded_internal_squads: List[UUID] = Field(default_factory=list, serialization_alias="excludedInternalSquads")
|
||||
|
||||
|
|
@ -161,6 +164,14 @@ class GetAllHostsResponseDto(RootModel[List[HostResponseDto]]):
|
|||
|
||||
def __getitem__(self, item):
|
||||
return self.root[item]
|
||||
|
||||
def __bool__(self):
|
||||
"""Return True if list is not empty"""
|
||||
return bool(self.root)
|
||||
|
||||
def __len__(self):
|
||||
"""Return length of list"""
|
||||
return len(self.root)
|
||||
|
||||
|
||||
class GetOneHostResponseDto(HostResponseDto):
|
||||
|
|
|
|||
|
|
@ -76,9 +76,27 @@ class GetHwidStatisticsResponseDto(HwidStatisticsData):
|
|||
|
||||
class DeleteUserAllHwidDeviceRequestDto(BaseModel):
|
||||
user_uuid: UUID = Field(serialization_alias="userUuid")
|
||||
|
||||
class TopUserByHwidDevicesDto(BaseModel):
|
||||
"""Top user by HWID devices"""
|
||||
user_uuid: UUID = Field(alias="userUuid")
|
||||
id: int
|
||||
username: str
|
||||
devices_count: int = Field(alias="devicesCount")
|
||||
|
||||
|
||||
class TopUsersByHwidDevicesData(BaseModel):
|
||||
"""Top users by HWID devices data"""
|
||||
users: list[TopUserByHwidDevicesDto]
|
||||
total: int
|
||||
|
||||
|
||||
class GetTopUsersByHwidDevicesResponseDto(TopUsersByHwidDevicesData):
|
||||
"""Response for get top users by HWID devices"""
|
||||
pass
|
||||
|
||||
# Legacy aliases for backward compatibility
|
||||
CreateHWIDUser = CreateUserHwidDeviceRequestDto
|
||||
HWIDUserResponseDto = HwidDeviceDto
|
||||
HWIDUserResponseDtoList = HwidDevicesData
|
||||
HWIDDeleteRequest = DeleteUserHwidDeviceRequestDto
|
||||
HWIDDeleteRequest = DeleteUserHwidDeviceRequestDto
|
||||
|
|
@ -41,6 +41,14 @@ class GetInboundsResponseDto(RootModel[List[InboundResponseDto]]):
|
|||
|
||||
def __getitem__(self, item):
|
||||
return self.root[item]
|
||||
|
||||
def __bool__(self):
|
||||
"""Return True if list is not empty"""
|
||||
return bool(self.root)
|
||||
|
||||
def __len__(self):
|
||||
"""Return length of list"""
|
||||
return len(self.root)
|
||||
|
||||
|
||||
class FullInboundStatistic(BaseModel):
|
||||
|
|
@ -66,6 +74,14 @@ class GetFullInboundsResponseDto(RootModel[List[FullInboundResponseDto]]):
|
|||
|
||||
def __getitem__(self, item):
|
||||
return self.root[item]
|
||||
|
||||
def __bool__(self):
|
||||
"""Return True if list is not empty"""
|
||||
return bool(self.root)
|
||||
|
||||
def __len__(self):
|
||||
"""Return length of list"""
|
||||
return len(self.root)
|
||||
|
||||
|
||||
# Legacy aliases for backward compatibility
|
||||
|
|
|
|||
|
|
@ -169,6 +169,15 @@ class GetAllNodesResponseDto(RootModel[List[NodeResponseDto]]):
|
|||
|
||||
def __getitem__(self, item):
|
||||
return self.root[item]
|
||||
|
||||
def __bool__(self):
|
||||
"""Return True if list is not empty"""
|
||||
return bool(self.root)
|
||||
|
||||
def __len__(self):
|
||||
"""Return length of list"""
|
||||
return len(self.root)
|
||||
|
||||
|
||||
|
||||
class EnableNodeResponseDto(NodeResponseDto):
|
||||
|
|
@ -195,6 +204,14 @@ class ReorderNodeResponseDto(RootModel[List[NodeResponseDto]]):
|
|||
|
||||
def __getitem__(self, item):
|
||||
return self.root[item]
|
||||
|
||||
def __bool__(self):
|
||||
"""Return True if list is not empty"""
|
||||
return bool(self.root)
|
||||
|
||||
def __len__(self):
|
||||
"""Return length of list"""
|
||||
return len(self.root)
|
||||
|
||||
|
||||
class DeleteNodeResponseDto(BaseModel):
|
||||
|
|
|
|||
|
|
@ -38,6 +38,14 @@ class GetNodesUsageByRangeResponseDto(RootModel[List[NodeUsageDto]]):
|
|||
|
||||
def __getitem__(self, item):
|
||||
return self.root[item]
|
||||
|
||||
def __bool__(self):
|
||||
"""Return True if list is not empty"""
|
||||
return bool(self.root)
|
||||
|
||||
def __len__(self):
|
||||
"""Return length of list"""
|
||||
return len(self.root)
|
||||
|
||||
|
||||
class UserUsageDto(BaseModel):
|
||||
|
|
@ -53,3 +61,11 @@ class GetNodeUserUsageByRangeResponseDto(RootModel[List[UserUsageDto]]):
|
|||
|
||||
def __getitem__(self, item):
|
||||
return self.root[item]
|
||||
|
||||
def __bool__(self):
|
||||
"""Return True if list is not empty"""
|
||||
return bool(self.root)
|
||||
|
||||
def __len__(self):
|
||||
"""Return length of list"""
|
||||
return len(self.root)
|
||||
|
|
|
|||
|
|
@ -304,6 +304,14 @@ class EmailUserResponseDto(RootModel[list[UserResponseDto]]):
|
|||
|
||||
def __getitem__(self, item):
|
||||
return self.root[item]
|
||||
|
||||
def __bool__(self):
|
||||
"""Return True if list is not empty"""
|
||||
return bool(self.root)
|
||||
|
||||
def __len__(self):
|
||||
"""Return length of list"""
|
||||
return len(self.root)
|
||||
|
||||
|
||||
class TagUserResponseDto(RootModel[list[UserResponseDto]]):
|
||||
|
|
@ -313,6 +321,14 @@ class TagUserResponseDto(RootModel[list[UserResponseDto]]):
|
|||
|
||||
def __getitem__(self, item):
|
||||
return self.root[item]
|
||||
|
||||
def __bool__(self):
|
||||
"""Return True if list is not empty"""
|
||||
return bool(self.root)
|
||||
|
||||
def __len__(self):
|
||||
"""Return length of list"""
|
||||
return len(self.root)
|
||||
|
||||
|
||||
class TelegramUserResponseDto(RootModel[list[UserResponseDto]]):
|
||||
|
|
@ -321,4 +337,12 @@ class TelegramUserResponseDto(RootModel[list[UserResponseDto]]):
|
|||
return iter(self.root)
|
||||
|
||||
def __getitem__(self, item):
|
||||
return self.root[item]
|
||||
return self.root[item]
|
||||
|
||||
def __bool__(self):
|
||||
"""Return True if list is not empty"""
|
||||
return bool(self.root)
|
||||
|
||||
def __len__(self):
|
||||
"""Return length of list"""
|
||||
return len(self.root)
|
||||
|
|
@ -23,13 +23,24 @@ class InternalSquadDto(BaseModel):
|
|||
model_config = {"alias_generator": to_camel, "populate_by_name": True}
|
||||
|
||||
|
||||
|
||||
class UserTrafficDto(BaseModel):
|
||||
"""User traffic information for webhooks"""
|
||||
used_traffic_bytes: int
|
||||
lifetime_used_traffic_bytes: int
|
||||
online_at: Optional[datetime] = None
|
||||
first_connected_at: Optional[datetime] = None
|
||||
last_connected_node_uuid: Optional[UUID] = None
|
||||
|
||||
model_config = {"alias_generator": to_camel, "populate_by_name": True}
|
||||
|
||||
|
||||
class BaseUserDto(BaseModel):
|
||||
uuid: UUID
|
||||
short_uuid: str
|
||||
username: str
|
||||
status: TUsersStatus
|
||||
used_traffic_bytes: int
|
||||
lifetime_used_traffic_bytes: int
|
||||
user_traffic: UserTrafficDto
|
||||
|
||||
traffic_limit_bytes: int
|
||||
traffic_limit_strategy: TResetPeriods
|
||||
|
|
@ -50,18 +61,41 @@ class BaseUserDto(BaseModel):
|
|||
email: Optional[str] = None
|
||||
|
||||
hwid_device_limit: Optional[int] = None
|
||||
|
||||
first_connected_at: Optional[datetime] = None
|
||||
last_triggered_threshold: int
|
||||
|
||||
online_at: Optional[datetime] = None
|
||||
last_connected_node_uuid: Optional[UUID] = None
|
||||
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
|
||||
model_config = {"alias_generator": to_camel, "populate_by_name": True}
|
||||
|
||||
# Backward compatibility properties
|
||||
@property
|
||||
def used_traffic_bytes(self) -> int:
|
||||
"""Backward compatibility property"""
|
||||
return self.user_traffic.used_traffic_bytes
|
||||
|
||||
@property
|
||||
def lifetime_used_traffic_bytes(self) -> int:
|
||||
"""Backward compatibility property"""
|
||||
return self.user_traffic.lifetime_used_traffic_bytes
|
||||
|
||||
@property
|
||||
def online_at(self) -> Optional[datetime]:
|
||||
"""Backward compatibility property"""
|
||||
return self.user_traffic.online_at
|
||||
|
||||
@property
|
||||
def first_connected_at(self) -> Optional[datetime]:
|
||||
"""Backward compatibility property"""
|
||||
return self.user_traffic.first_connected_at
|
||||
|
||||
@property
|
||||
def last_connected_node_uuid(self) -> Optional[UUID]:
|
||||
"""Backward compatibility property"""
|
||||
return self.user_traffic.last_connected_node_uuid
|
||||
|
||||
|
||||
|
||||
class UserDto(BaseUserDto):
|
||||
active_internal_squads: List[InternalSquadDto] = Field(default_factory=list)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue