Skip to content

Commit a3a8f14

Browse files
committed
Add requests to UserSubscription
1 parent 9448f57 commit a3a8f14

File tree

5 files changed

+111
-19
lines changed

5 files changed

+111
-19
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,4 +71,5 @@ venv/
7171
#openapi
7272
openapi.json
7373
temp.py
74-
bump.sh
74+
bump.sh
75+
test.py

example/example.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import asyncio
22
from marzban import MarzbanAPI, AdminCreate, UserCreate, NodeCreate, UserTemplateCreate, AdminModify, UserModify, UserTemplateModify, NodeModify, ProxySettings
3+
from datetime import datetime
34

45
async def main():
56
# Create an API client
@@ -77,11 +78,11 @@ async def main():
7778
print("Set Owner:", owner_set)
7879

7980
## Get list of expired users
80-
expired_users = await api.get_expired_users(token=token.access_token, expired_before="2024-01-01T00:00:00Z")
81+
expired_users = await api.get_expired_users(token=token.access_token, expired_before=datetime.fromisoformat("2024-10-10T00:00:00+00:00"))
8182
print("Expired Users:", expired_users)
8283

8384
## Delete expired users
84-
deleted_expired_users = await api.delete_expired_users(token=token.access_token, expired_before="2024-01-01T00:00:00Z")
85+
deleted_expired_users = await api.delete_expired_users(token=token.access_token, expired_before=datetime.fromisoformat("2024-10-10T00:00:00+00:00"))
8586
print("Deleted Expired Users:", deleted_expired_users)
8687

8788
# User templates management
@@ -136,6 +137,23 @@ async def main():
136137
nodes_usage = await api.get_usage(token=token.access_token, start="2023-01-01", end="2023-12-31")
137138
print("Nodes Usage:", nodes_usage)
138139

140+
## Getting node settings
141+
node_settings = await api.get_node_settings(token=token.access_token)
142+
print("Node Settings:", node_settings)
143+
144+
## Getting user subscription information with token
145+
user_subscription_info = await api.get_user_subscription_info(url=user_info.subscription_url)
146+
print("User Subscription Info:", user_subscription_info)
147+
148+
## Getting user usage statistics
149+
user_usage = await api.get_user_usage(url=user_info.subscription_url, start="2023-01-01")
150+
print("User Usage:", user_usage)
151+
152+
## Getting user subscription by client type
153+
# Accepts only clients that require a JSON response. Example: sing-box.
154+
user_subscription_client = await api.get_user_subscription_with_client_type(url=user_info.subscription_url, client_type="sing-box")
155+
print("User Subscription with Client Type:", user_subscription_client)
156+
139157
# Closing the API client
140158
await api.close()
141159

marzban/__init__.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@
2525
ProxyInbound,
2626
Token,
2727
HTTPValidationError,
28-
ValidationError
28+
ValidationError,
29+
SubscriptionUserResponse,
30+
SystemStats
2931
)
3032

3133
__all__ = (
@@ -57,6 +59,8 @@
5759
"Token",
5860
"HTTPValidationError",
5961
"ValidationError",
62+
"SubscriptionUserResponse",
63+
"SystemStats"
6064
)
6165

6266
__version__ = "0.2.8"

marzban/api.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,5 +223,45 @@ async def get_usage(self, token: str, start: Optional[str] = None, end: Optional
223223
response = await self._request("GET", url, token, params=params)
224224
return NodesUsageResponse(**response.json())
225225

226+
async def get_user_subscription_info(self, url: str = None, token: str = None) -> SubscriptionUserResponse:
227+
if url:
228+
# Use the provided URL if it is given
229+
final_url = url + "/info"
230+
elif token:
231+
# Form the URL using the token if it is provided
232+
final_url = f"/sub/{token}/info"
233+
else:
234+
raise ValueError("Either url or token must be provided")
235+
236+
response = await self._request("GET", final_url)
237+
return SubscriptionUserResponse(**response.json())
238+
239+
async def get_user_usage(self, url: str = None, token: str = None, start: Optional[str] = None, end: Optional[str] = None) -> Any:
240+
if url:
241+
# Use the provided URL if it is given
242+
final_url = url + "/usage"
243+
elif token:
244+
# Form the URL using the token if it is provided
245+
final_url = f"/sub/{token}/usage"
246+
else:
247+
raise ValueError("Either url or token must be provided")
248+
249+
params = {"start": start, "end": end}
250+
response = await self._request("GET", final_url, params=params)
251+
return response.json()
252+
253+
async def get_user_subscription_with_client_type(self, client_type: str, url: str = None, token: str = None) -> Any:
254+
if url:
255+
# Use the provided URL if it is given
256+
final_url = url + f"/{client_type}"
257+
elif token:
258+
# Form the URL using the token if it is provided
259+
final_url = f"/sub/{token}/{client_type}"
260+
else:
261+
raise ValueError("Either url or token must be provided")
262+
263+
response = await self._request("GET", final_url)
264+
return response.json()
265+
226266
async def close(self):
227267
await self.client.aclose()

marzban/models.py

Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
from pydantic import BaseModel
2-
from typing import Optional, List, Dict, Any, ClassVar
1+
from pydantic import BaseModel, field_validator, ValidationInfo, AfterValidator, ValidationError
2+
from typing import Optional, List, Dict, Any, ClassVar, Annotated
33

44
class Token(BaseModel):
55
access_token: str
@@ -23,19 +23,6 @@ class AdminModify(BaseModel):
2323
class HTTPValidationError(BaseModel):
2424
detail: Optional[List[Dict[str, Any]]] = None
2525

26-
class SystemStats(BaseModel):
27-
version: str
28-
mem_total: int
29-
mem_used: int
30-
cpu_cores: int
31-
cpu_usage: float
32-
total_user: int
33-
users_active: int
34-
incoming_bandwidth: int
35-
outgoing_bandwidth: int
36-
incoming_bandwidth_speed: int
37-
outgoing_bandwidth_speed: int
38-
3926
class ProxySettings(BaseModel):
4027
id: Optional[str] = None
4128
flow: Optional[str] = None
@@ -75,7 +62,13 @@ class UserResponse(BaseModel):
7562
created_at: Optional[str] = None
7663
links: Optional[List[str]] = []
7764
subscription_url: Optional[str] = None
65+
subscription_token: Optional[str] = None
7866
excluded_inbounds: Optional[Dict[str, List[str]]] = None
67+
68+
def __init__(self, **data):
69+
super().__init__(**data)
70+
if not self.subscription_token and self.subscription_url:
71+
self.subscription_token = self.subscription_url.split('/')[-1]
7972

8073
class NodeCreate(BaseModel):
8174
name: str
@@ -197,3 +190,39 @@ class ValidationError(BaseModel):
197190
loc: List[Any]
198191
msg: str
199192
type: str
193+
194+
class SubscriptionUserResponse(BaseModel):
195+
proxies: Dict[str, Any]
196+
expire: Optional[int] = None
197+
data_limit: Optional[int] = None
198+
data_limit_reset_strategy: str = "no_reset"
199+
inbounds: Dict[str, List[str]] = {}
200+
note: Optional[str] = None
201+
sub_updated_at: Optional[str] = None
202+
sub_last_user_agent: Optional[str] = None
203+
online_at: Optional[str] = None
204+
on_hold_expire_duration: Optional[int] = None
205+
on_hold_timeout: Optional[str] = None
206+
auto_delete_in_days: Optional[int] = None
207+
username: str
208+
status: str
209+
used_traffic: int
210+
lifetime_used_traffic: int = 0
211+
created_at: str
212+
links: List[str] = []
213+
subscription_url: str = ""
214+
excluded_inbounds: Dict[str, List[str]] = {}
215+
admin: Optional[Admin] = None
216+
217+
class SystemStats(BaseModel):
218+
version: Optional[str] = None
219+
mem_total: Optional[int] = None
220+
mem_used: Optional[int] = None
221+
cpu_cores: Optional[int] = None
222+
cpu_usage: Optional[float] = None
223+
total_user: Optional[int] = None
224+
users_active: Optional[int] = None
225+
incoming_bandwidth: Optional[int] = None
226+
outgoing_bandwidth: Optional[int] = None
227+
incoming_bandwidth_speed: Optional[int] = None
228+
outgoing_bandwidth_speed: Optional[int] = None

0 commit comments

Comments
 (0)