Aktualizacja do wersji 2.1.8:

- Zmieniono wersję pakietu z 2.1.7.post1 na 2.1.8 w pliku pyproject.toml oraz README.md.
- Przeniesiono endpoint `/sub/{short_uuid}/raw` z `SubscriptionController` do `SubscriptionsController`.
- Rozszerzono modele subskrypcji o dodatkowe informacje o użytkowniku, połączeniach, hasłach i wykorzystaniu ruchu.
- Dodano nowy endpoint `/system/tools/x25519/generate` do generowania pary kluczy X25519.
- Uzupełniono klasyfikatory projektu o nowe tematy w `pyproject.toml`.

Zmiany poprawiają strukturę API, rozszerzają możliwości diagnostyczne oraz wprowadzają nową funkcjonalność kryptograficzną.
This commit is contained in:
Artem 2025-09-04 01:13:16 +02:00
parent c1ac9903dd
commit c8e13c41af
No known key found for this signature in database
GPG key ID: 833485276B7902CE
8 changed files with 102 additions and 23 deletions

View file

@ -63,7 +63,8 @@ pip install git+https://github.com/remnawave/python-sdk.git@development
| Contract Version | Remnawave Panel Version |
| ---------------- | ----------------------- |
| 2.1.7.post1 | >=2.1.7 |
| 2.1.8 | >=2.1.8 |
| 2.1.7.post1 | ==2.1.7 |
| 2.1.4 | >=2.1.4, <2.1.7 |
| 2.1.1 | >=2.1.1, <2.1.4 |
| 2.0.0 | >=2.0.0,<2.1.0 |

View file

@ -1,7 +1,7 @@
[project]
name = "remnawave"
version = "2.1.7.post1"
description = "A Python SDK for interacting with the Remnawave API v2.1.7."
version = "2.1.8"
description = "A Python SDK for interacting with the Remnawave API v2.1.8."
authors = [
{name = "Artem",email = "dev@forestsnet.com"}
]
@ -28,6 +28,10 @@ classifiers = [
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
"Framework :: AsyncIO",
"Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: Internet :: WWW/HTTP",
"Topic :: Utilities",
"Topic :: Rest APIs",
]
[project.urls]

View file

@ -3,11 +3,12 @@ from typing import Annotated
from rapid_api_client import Path
from remnawave.enums import ClientType
from remnawave.models import GetSubscriptionInfoResponseDto, GetRawSubscriptionByShortUuidResponseDto
from remnawave.models import GetSubscriptionInfoResponseDto
from remnawave.rapid import BaseController, get
class SubscriptionController(BaseController):
# Public endpoints below
@get("/sub/{short_uuid}/info", response_class=GetSubscriptionInfoResponseDto)
async def get_subscription_info_by_short_uuid(
self,
@ -50,15 +51,5 @@ class SubscriptionController(BaseController):
),
] = "VGVzdGVy",
) -> str:
"""None"""
...
# get raw sub by short uuid
@get("/sub/{short_uuid}/raw", response_class=GetRawSubscriptionByShortUuidResponseDto)
async def get_raw_subscription(
self,
short_uuid: Annotated[str, Path(description="Short UUID of the user")],
withDisabledHosts: Annotated[Annotated[bool, Path(description="Include disabled hosts")], bool] = False,
) -> GetRawSubscriptionByShortUuidResponseDto:
"""None"""
...

View file

@ -3,11 +3,13 @@ from typing import Annotated
from rapid_api_client import Path, Query
from remnawave.enums import ClientType
from remnawave.models.subscription import GetRawSubscriptionByShortUuidResponseDto
from remnawave.rapid import BaseController, get
from remnawave.models import GetAllSubscriptionsResponseDto, GetSubscriptionByUsernameResponseDto, GetSubscriptionByShortUUIDResponseDto, GetSubscriptionByUUIDResponseDto
class SubscriptionsController(BaseController):
# Protected endpoints below
@get("/subscriptions", response_class=GetAllSubscriptionsResponseDto)
async def get_all_subscriptions(
self,
@ -42,5 +44,14 @@ class SubscriptionsController(BaseController):
self,
uuid: Annotated[str, Path(description="UUID of the user")],
) -> GetSubscriptionByUUIDResponseDto:
"""None"""
...
@get("/subscriptions/by-short-uuid/{short_uuid}/raw", response_class=GetRawSubscriptionByShortUuidResponseDto)
async def get_raw_subscription(
self,
short_uuid: Annotated[str, Path(description="Short UUID of the user")],
withDisabledHosts: Annotated[Annotated[bool, Path(description="Include disabled hosts")], bool] = False,
) -> GetRawSubscriptionByShortUuidResponseDto:
"""None"""
...

View file

@ -4,6 +4,7 @@ from remnawave.models import (
GetStatsResponseDto,
GetNodesMetricsResponseDto,
GetRemnawaveHealthResponseDto,
GetX25519KeyPairResponseDto
)
from remnawave.rapid import BaseController, get
@ -43,3 +44,10 @@ class SystemController(BaseController):
) -> GetNodesMetricsResponseDto:
"""Get Nodes Metrics"""
...
@get("/system/tools/x25519/generate", response_class=GetX25519KeyPairResponseDto)
async def get_x25519_key_pair(
self,
) -> GetX25519KeyPairResponseDto:
"""Get X25519 Key Pair"""
...

View file

@ -199,6 +199,8 @@ from .system import (
StatusCounts,
UsersStatistic,
GetNodesMetricsResponseDto,
GetX25519KeyPairResponseDto,
X25519KeyPair,
)
from .users import (
ActiveInternalSquadDto,
@ -327,6 +329,8 @@ __all__ = [
"StatusCounts",
"UsersStatistic",
"GetNodesMetricsResponseDto",
"GetX25519KeyPairResponseDto",
"X25519KeyPair",
# XRay config models
"ConfigResponseDto", # Legacy alias
"GetConfigResponseDto",

View file

@ -1,5 +1,6 @@
from datetime import datetime
from typing import Any, Dict, List, Optional
from uuid import UUID
from pydantic import BaseModel, Field
@ -25,7 +26,6 @@ class UserSubscription(BaseModel):
user_status: UserStatus = Field(alias="userStatus")
is_active: bool = Field(alias="isActive")
class SubscriptionInfoData(BaseModel):
is_found: bool = Field(alias="isFound")
user: UserSubscription
@ -43,6 +43,63 @@ class GetSubscriptionInfoResponseDto(BaseModel):
subscription_url: str = Field(alias="subscriptionUrl")
happ: HappCrypto
class UserLastConnectedNodeDto(BaseModel):
connected_at: datetime = Field(alias="connectedAt")
node_name: str = Field(alias="nodeName")
country_code: Optional[str] = Field(None, alias="countryCode") # новое поле
class ActiveInternalSquadDto(BaseModel):
uuid: UUID
name: str
class HappCrypto(BaseModel):
crypto_link: str = Field(alias="cryptoLink")
class UserResponseDto(BaseModel):
uuid: UUID
short_uuid: str = Field(alias="shortUuid")
username: str
status: str
used_traffic_bytes: float = Field(alias="usedTrafficBytes")
lifetime_used_traffic_bytes: float = Field(alias="lifetimeUsedTrafficBytes")
traffic_limit_bytes: int = Field(alias="trafficLimitBytes")
traffic_limit_strategy: str = Field(alias="trafficLimitStrategy")
sub_last_user_agent: Optional[str] = Field(None, alias="subLastUserAgent")
sub_last_opened_at: Optional[datetime] = Field(None, alias="subLastOpenedAt")
expire_at: datetime = Field(alias="expireAt")
online_at: Optional[datetime] = Field(None, alias="onlineAt")
sub_revoked_at: Optional[datetime] = Field(None, alias="subRevokedAt")
last_traffic_reset_at: Optional[datetime] = Field(None, alias="lastTrafficResetAt")
trojan_password: str = Field(alias="trojanPassword")
vless_uuid: UUID = Field(alias="vlessUuid")
ss_password: str = Field(alias="ssPassword")
description: Optional[str] = None
tag: Optional[str] = None
telegram_id: Optional[int] = Field(None, alias="telegramId")
email: Optional[str] = None
hwidDeviceLimit: Optional[int] = Field(None, alias="hwidDeviceLimit")
first_connected_at: Optional[datetime] = Field(None, alias="firstConnectedAt")
last_triggered_threshold: int = Field(alias="lastTriggeredThreshold")
created_at: datetime = Field(alias="createdAt")
updated_at: datetime = Field(alias="updatedAt")
active_internal_squads: List[ActiveInternalSquadDto] = Field(alias="activeInternalSquads")
subscription_url: str = Field(alias="subscriptionUrl")
last_connected_node: Optional[UserLastConnectedNodeDto] = Field(None, alias="lastConnectedNode")
happ: Optional[HappCrypto] = Field(None, alias="happ")
class ConvertedUserInfo(BaseModel):
days_left: int = Field(alias="daysLeft")
traffic_limit: str = Field(alias="trafficLimit")
traffic_used: str = Field(alias="trafficUsed")
lifetime_traffic_used: str = Field(alias="lifetimeTrafficUsed")
is_hwid_limited: bool = Field(alias="isHwidLimited")
class Passwords(BaseModel):
ss_password: Optional[str] = Field(None, alias="ssPassword")
trojan_password: Optional[str] = Field(None, alias="trojanPassword")
vless_password: Optional[str] = Field(None, alias="vlessPassword")
class RawHostAdditionalParams(BaseModel):
mode: Optional[str] = None
@ -68,14 +125,13 @@ class RawHostDbData(BaseModel):
is_hidden: bool = Field(alias="isHidden")
tag: Optional[str] = None
class RawHost(BaseModel):
address: Optional[str] = None
alpn: Optional[str] = None
fingerprint: Optional[str] = None
host: Optional[str] = None
network: Optional[str] = None
password: Optional[str] = None
password: Optional[Passwords] = None
path: Optional[str] = None
public_key: Optional[str] = Field(None, alias="publicKey")
port: Optional[int] = None
@ -95,14 +151,11 @@ class RawHost(BaseModel):
protocol_options: Optional[RawHostProtocolOptions] = Field(None, alias="protocolOptions")
db_data: RawHostDbData = Field(alias="dbData")
class RawSubscriptionResponse(BaseModel):
user: UserSubscription
subscription_url: str = Field(alias="subscriptionUrl")
raw_hosts: List[RawHost] = Field(alias="rawHosts")
user: UserResponseDto
converted_user_info: ConvertedUserInfo = Field(alias="convertedUserInfo")
headers: Dict[str, str]
is_hwid_limited: bool = Field(alias="isHwidLimited")
raw_hosts: List[RawHost] = Field(alias="rawHosts")
class GetRawSubscriptionByShortUuidResponseDto(RawSubscriptionResponse):
pass

View file

@ -121,3 +121,10 @@ class NodeMetric(BaseModel):
class GetNodesMetricsResponseDto(BaseModel):
response: List[NodeMetric]
class X25519KeyPair(BaseModel):
public_key: str = Field(alias="publicKey")
private_key: str = Field(alias="privateKey")
class GetX25519KeyPairResponseDto(BaseModel):
key_pairs: List[X25519KeyPair] = Field(alias="keyPairs")