Skip to content
This repository was archived by the owner on Jul 31, 2026. It is now read-only.

Commit bb337aa

Browse files
authored
Merge pull request #49 from remnawave:development
Introduce metadata management and enhance validation
2 parents db12689 + 9c1b478 commit bb337aa

52 files changed

Lines changed: 2121 additions & 215 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.vscode/settings.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,7 @@
33
"tests"
44
],
55
"python.testing.unittestEnabled": false,
6-
"python.testing.pytestEnabled": true
6+
"python.testing.pytestEnabled": true,
7+
"python-envs.defaultEnvManager": "ms-python.python:poetry",
8+
"python-envs.defaultPackageManager": "ms-python.python:poetry"
79
}

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ pip install git+https://github.com/remnawave/python-sdk.git@development
5050

5151
| Contract Version | Remnawave Panel Version |
5252
| ---------------- | ----------------------- |
53+
| 2.7.0 | >=2.7.0 |
5354
| 2.6.3 | >=2.6.3 |
5455
| 2.6.2 | >=2.6.2 |
5556
| 2.6.1 | >=2.6.0 |

pyproject.toml

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

remnawave/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
RemnawaveSettingsController,
3434
SubscriptionPageConfigController,
3535
IpControlController,
36+
NodePluginsController,
37+
MetadataController,
3638
)
3739

3840

@@ -101,6 +103,8 @@ def __init__(
101103
self.remnawave_settings = RemnawaveSettingsController(self._client)
102104
self.subscription_page_config = SubscriptionPageConfigController(self._client)
103105
self.ip_control = IpControlController(self._client)
106+
self.node_plugins = NodePluginsController(self._client)
107+
self.metadata = MetadataController(self._client)
104108

105109
def _validate_params(self) -> None:
106110
if self._client is None:

remnawave/controllers/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
from .remnawave_settings import RemnawaveSettingsController
2828
from .subscription_page import SubscriptionPageConfigController
2929
from .ip_control import IpControlController
30+
from .node_plugins import NodePluginsController
31+
from .metadata import MetadataController
3032

3133
__all__ = [
3234
"APITokensManagementController",
@@ -58,4 +60,6 @@
5860
"RemnawaveSettingsController",
5961
"SubscriptionPageConfigController",
6062
"IpControlController",
63+
"NodePluginsController",
64+
"MetadataController",
6165
]

remnawave/controllers/hwid.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,10 @@ async def delete_all_hwid_user(
5656
"""Delete all user HWID devices"""
5757
...
5858

59-
@get("/hwid/devices/{uuid}", response_class=GetUserHwidDevicesResponseDto)
59+
@get("/hwid/devices/{userUuid}", response_class=GetUserHwidDevicesResponseDto)
6060
async def get_hwid_user(
6161
self,
62-
uuid: Annotated[str, Path(description="UUID of the User")],
62+
uuid: Annotated[str, Path(description="UUID of the User", alias="userUuid")],
6363
) -> GetUserHwidDevicesResponseDto:
6464
"""Get a user HWID device"""
6565
...

remnawave/controllers/ip_control.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
DropConnectionsResponseDto,
99
FetchIpsResponseDto,
1010
FetchIpsResultResponseDto,
11+
FetchUsersIpsResponseDto,
12+
FetchUsersIpsResultResponseDto,
1113
)
1214
from remnawave.rapid import BaseController, get, post
1315

@@ -41,6 +43,33 @@ async def get_fetch_ips_result(
4143
"""
4244
...
4345

46+
@post("/ip-control/fetch-users-ips/{nodeUuid}", response_class=FetchUsersIpsResponseDto)
47+
async def fetch_users_ips(
48+
self,
49+
nodeUuid: Annotated[str, Path(description="UUID of the node")],
50+
) -> FetchUsersIpsResponseDto:
51+
"""Request IP List for all users on a node.
52+
53+
Starts a background job that queries the specified node for the IPs
54+
of all connected users. The returned ``job_id`` must be passed to
55+
:meth:`get_fetch_users_ips_result` to retrieve the actual list once
56+
the job is complete.
57+
"""
58+
...
59+
60+
@get("/ip-control/fetch-users-ips/result/{jobId}", response_class=FetchUsersIpsResultResponseDto)
61+
async def get_fetch_users_ips_result(
62+
self,
63+
jobId: Annotated[str, Path(description="Job ID returned by fetch_users_ips")],
64+
) -> FetchUsersIpsResultResponseDto:
65+
"""Get Users IP List Result by Job ID.
66+
67+
Poll this endpoint after calling :meth:`fetch_users_ips`. When
68+
``is_completed`` is ``True`` the ``result`` field contains per-user
69+
IP lists.
70+
"""
71+
...
72+
4473
@post("/ip-control/drop-connections", response_class=DropConnectionsResponseDto)
4574
async def drop_connections(
4675
self,

remnawave/controllers/metadata.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
from typing import Annotated
2+
3+
from rapid_api_client import Path
4+
from rapid_api_client.annotations import PydanticBody
5+
6+
from remnawave.models import (
7+
GetNodeMetadataResponseDto,
8+
GetUserMetadataResponseDto,
9+
UpsertNodeMetadataRequestBodyDto,
10+
UpsertNodeMetadataResponseDto,
11+
UpsertUserMetadataRequestBodyDto,
12+
UpsertUserMetadataResponseDto,
13+
)
14+
from remnawave.rapid import BaseController, get, put
15+
16+
17+
class MetadataController(BaseController):
18+
@get("/metadata/user/{uuid}", response_class=GetUserMetadataResponseDto)
19+
async def get_user_metadata(
20+
self,
21+
uuid: Annotated[str, Path(description="User UUID")],
22+
) -> GetUserMetadataResponseDto:
23+
"""Get user metadata"""
24+
...
25+
26+
@put("/metadata/user/{uuid}", response_class=UpsertUserMetadataResponseDto)
27+
async def upsert_user_metadata(
28+
self,
29+
uuid: Annotated[str, Path(description="User UUID")],
30+
body: Annotated[UpsertUserMetadataRequestBodyDto, PydanticBody()],
31+
) -> UpsertUserMetadataResponseDto:
32+
"""Update or create User Metadata"""
33+
...
34+
35+
@get("/metadata/node/{uuid}", response_class=GetNodeMetadataResponseDto)
36+
async def get_node_metadata(
37+
self,
38+
uuid: Annotated[str, Path(description="Node UUID")],
39+
) -> GetNodeMetadataResponseDto:
40+
"""Get node metadata"""
41+
...
42+
43+
@put("/metadata/node/{uuid}", response_class=UpsertNodeMetadataResponseDto)
44+
async def upsert_node_metadata(
45+
self,
46+
uuid: Annotated[str, Path(description="Node UUID")],
47+
body: Annotated[UpsertNodeMetadataRequestBodyDto, PydanticBody()],
48+
) -> UpsertNodeMetadataResponseDto:
49+
"""Update or create Node Metadata"""
50+
...
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
from typing import Annotated, Optional
2+
3+
from rapid_api_client import Path, PydanticBody, Query
4+
5+
6+
from remnawave.models import (
7+
CloneNodePluginRequestDto,
8+
CloneNodePluginResponseDto,
9+
CreateNodePluginRequestDto,
10+
CreateNodePluginResponseDto,
11+
DeleteNodePluginResponseDto,
12+
GetNodePluginResponseDto,
13+
GetNodePluginsResponseDto,
14+
GetTorrentBlockerReportsResponseDto,
15+
GetTorrentBlockerReportsStatsResponseDto,
16+
PluginExecutorRequestDto,
17+
PluginExecutorResponseDto,
18+
ReorderNodePluginsRequestDto,
19+
ReorderNodePluginsResponseDto,
20+
TruncateTorrentBlockerReportsResponseDto,
21+
UpdateNodePluginRequestDto,
22+
UpdateNodePluginResponseDto,
23+
)
24+
from remnawave.rapid import BaseController, delete, get, patch, post
25+
26+
27+
class NodePluginsController(BaseController):
28+
@get("/node-plugins/torrent-blocker", response_class=GetTorrentBlockerReportsResponseDto)
29+
async def get_torrent_blocker_reports(
30+
self,
31+
size: Annotated[Optional[int], Query(default=None, ge=1, description="Page size")] = None,
32+
start: Annotated[Optional[int], Query(default=None, ge=0, description="Offset")] = None,
33+
) -> GetTorrentBlockerReportsResponseDto:
34+
"""Get Torrent Blocker Reports"""
35+
...
36+
37+
@get("/node-plugins/torrent-blocker/stats", response_class=GetTorrentBlockerReportsStatsResponseDto)
38+
async def get_torrent_blocker_reports_stats(
39+
self,
40+
) -> GetTorrentBlockerReportsStatsResponseDto:
41+
"""Get Torrent Blocker Reports Stats"""
42+
...
43+
44+
@delete("/node-plugins/torrent-blocker/truncate", response_class=TruncateTorrentBlockerReportsResponseDto)
45+
async def truncate_torrent_blocker_reports(
46+
self,
47+
) -> TruncateTorrentBlockerReportsResponseDto:
48+
"""Truncate Torrent Blocker Reports"""
49+
...
50+
51+
@get("/node-plugins", response_class=GetNodePluginsResponseDto)
52+
async def get_all_node_plugins(self) -> GetNodePluginsResponseDto:
53+
"""Get all Node Plugins"""
54+
...
55+
56+
@patch("/node-plugins", response_class=UpdateNodePluginResponseDto)
57+
async def update_node_plugin(
58+
self,
59+
body: Annotated[UpdateNodePluginRequestDto, PydanticBody()],
60+
) -> UpdateNodePluginResponseDto:
61+
"""Update Node Plugin"""
62+
...
63+
64+
@post("/node-plugins", response_class=CreateNodePluginResponseDto)
65+
async def create_node_plugin(
66+
self,
67+
body: Annotated[CreateNodePluginRequestDto, PydanticBody()],
68+
) -> CreateNodePluginResponseDto:
69+
"""Create Node Plugin"""
70+
...
71+
72+
@get("/node-plugins/{uuid}", response_class=GetNodePluginResponseDto)
73+
async def get_node_plugin_by_uuid(
74+
self,
75+
uuid: Annotated[str, Path(description="Node plugin UUID")],
76+
) -> GetNodePluginResponseDto:
77+
"""Get Node Plugin by uuid"""
78+
...
79+
80+
@delete("/node-plugins/{uuid}", response_class=DeleteNodePluginResponseDto)
81+
async def delete_node_plugin(
82+
self,
83+
uuid: Annotated[str, Path(description="Node plugin UUID")],
84+
) -> DeleteNodePluginResponseDto:
85+
"""Delete Node Plugin"""
86+
...
87+
88+
@post("/node-plugins/actions/reorder", response_class=ReorderNodePluginsResponseDto)
89+
async def reorder_node_plugins(
90+
self,
91+
body: Annotated[ReorderNodePluginsRequestDto, PydanticBody()],
92+
) -> ReorderNodePluginsResponseDto:
93+
"""Reorder Node Plugins"""
94+
...
95+
96+
@post("/node-plugins/actions/clone", response_class=CloneNodePluginResponseDto)
97+
async def clone_node_plugin(
98+
self,
99+
body: Annotated[CloneNodePluginRequestDto, PydanticBody()],
100+
) -> CloneNodePluginResponseDto:
101+
"""Clone Node Plugin"""
102+
...
103+
104+
@post("/node-plugins/executor", response_class=PluginExecutorResponseDto)
105+
async def plugin_executor(
106+
self,
107+
body: Annotated[PluginExecutorRequestDto, PydanticBody()],
108+
) -> PluginExecutorResponseDto:
109+
"""Execute command on node plugins"""
110+
...

remnawave/controllers/nodes.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
ProfileModificationResponseDto,
2626
NodesBulkActionsRequestDto,
2727
NodesBulkActionsResponseDto,
28+
BulkNodesUpdateRequestDto,
29+
BulkNodesUpdateResponseDto,
2830
)
2931
from remnawave.rapid import BaseController, delete, get, patch, post
3032

@@ -146,4 +148,12 @@ async def nodes_bulk_actions(
146148
body: Annotated[NodesBulkActionsRequestDto, PydanticBody()],
147149
) -> NodesBulkActionsResponseDto:
148150
"""Perform actions for many nodes (ENABLE, DISABLE, RESTART, RESET_TRAFFIC)"""
151+
...
152+
153+
@post("/nodes/bulk-actions/update", response_class=BulkNodesUpdateResponseDto)
154+
async def bulk_nodes_update(
155+
self,
156+
body: Annotated[BulkNodesUpdateRequestDto, PydanticBody()],
157+
) -> BulkNodesUpdateResponseDto:
158+
"""Update many nodes"""
149159
...

0 commit comments

Comments
 (0)