mirror of
https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot.git
synced 2026-03-01 07:42:30 +00:00
fix: use accessible nodes API and fix date format for node usage
- Add get_user_accessible_nodes() to fetch user's available nodes - Fix date format from ISO datetime to date-only (Y-m-d) for bandwidth stats - Show all accessible nodes (with zero traffic if no stats) - Add country_code to node usage response
This commit is contained in:
@@ -686,48 +686,67 @@ async def get_user_node_usage(
|
||||
start_date = end_date - timedelta(days=days)
|
||||
|
||||
async with service.get_api_client() as api:
|
||||
# Get bandwidth stats for user
|
||||
stats = await api.get_bandwidth_stats_user(
|
||||
user.remnawave_uuid,
|
||||
start_date.strftime('%Y-%m-%dT%H:%M:%S.000Z'),
|
||||
end_date.strftime('%Y-%m-%dT%H:%M:%S.000Z'),
|
||||
)
|
||||
# Get user's accessible nodes
|
||||
accessible_nodes = await api.get_user_accessible_nodes(user.remnawave_uuid)
|
||||
if not accessible_nodes:
|
||||
return UserNodeUsageResponse(items=[], period_days=days)
|
||||
|
||||
# Get all nodes for name resolution
|
||||
nodes = await api.get_all_nodes()
|
||||
node_map = {n.uuid: n.name for n in nodes}
|
||||
node_name_map = {n.uuid: n.node_name for n in accessible_nodes}
|
||||
node_cc_map = {n.uuid: n.country_code for n in accessible_nodes}
|
||||
|
||||
# Get bandwidth stats for user (use date-only format)
|
||||
start_str = start_date.strftime('%Y-%m-%d')
|
||||
end_str = end_date.strftime('%Y-%m-%d')
|
||||
|
||||
items = []
|
||||
# Stats response contains per-node breakdown
|
||||
if isinstance(stats, list):
|
||||
for entry in stats:
|
||||
node_uuid = entry.get('nodeUuid', '')
|
||||
total = entry.get('totalBytes', 0) or entry.get('total', 0)
|
||||
if node_uuid and total > 0:
|
||||
items.append(
|
||||
UserNodeUsageItem(
|
||||
node_uuid=node_uuid,
|
||||
node_name=node_map.get(node_uuid, node_uuid[:8]),
|
||||
total_bytes=total,
|
||||
try:
|
||||
stats = await api.get_bandwidth_stats_user(user.remnawave_uuid, start_str, end_str)
|
||||
|
||||
if isinstance(stats, list):
|
||||
for entry in stats:
|
||||
node_uuid = entry.get('nodeUuid', '')
|
||||
total = entry.get('totalBytes', 0) or entry.get('total', 0)
|
||||
if node_uuid and total > 0:
|
||||
items.append(
|
||||
UserNodeUsageItem(
|
||||
node_uuid=node_uuid,
|
||||
node_name=node_name_map.get(node_uuid, node_uuid[:8]),
|
||||
country_code=node_cc_map.get(node_uuid, ''),
|
||||
total_bytes=total,
|
||||
)
|
||||
)
|
||||
)
|
||||
elif isinstance(stats, dict):
|
||||
# Handle dict format with node entries
|
||||
for node_uuid, data in stats.items():
|
||||
if isinstance(data, dict):
|
||||
total = data.get('totalBytes', 0) or data.get('total', 0)
|
||||
elif isinstance(data, (int, float)):
|
||||
total = int(data)
|
||||
else:
|
||||
continue
|
||||
if total > 0:
|
||||
items.append(
|
||||
UserNodeUsageItem(
|
||||
node_uuid=node_uuid,
|
||||
node_name=node_map.get(node_uuid, node_uuid[:8]),
|
||||
total_bytes=total,
|
||||
elif isinstance(stats, dict):
|
||||
for node_uuid, data in stats.items():
|
||||
if isinstance(data, dict):
|
||||
total = data.get('totalBytes', 0) or data.get('total', 0)
|
||||
elif isinstance(data, (int, float)):
|
||||
total = int(data)
|
||||
else:
|
||||
continue
|
||||
if total > 0:
|
||||
items.append(
|
||||
UserNodeUsageItem(
|
||||
node_uuid=node_uuid,
|
||||
node_name=node_name_map.get(node_uuid, node_uuid[:8]),
|
||||
country_code=node_cc_map.get(node_uuid, ''),
|
||||
total_bytes=total,
|
||||
)
|
||||
)
|
||||
except Exception:
|
||||
logger.warning(f'Failed to get bandwidth stats for user {user_id}, returning nodes without traffic')
|
||||
|
||||
# Add accessible nodes with zero traffic if not in stats
|
||||
seen_uuids = {item.node_uuid for item in items}
|
||||
for node in accessible_nodes:
|
||||
if node.uuid not in seen_uuids:
|
||||
items.append(
|
||||
UserNodeUsageItem(
|
||||
node_uuid=node.uuid,
|
||||
node_name=node.node_name,
|
||||
country_code=node.country_code,
|
||||
total_bytes=0,
|
||||
)
|
||||
)
|
||||
|
||||
# Sort by traffic descending
|
||||
items.sort(key=lambda x: x.total_bytes, reverse=True)
|
||||
|
||||
@@ -229,6 +229,7 @@ class UserNodeUsageItem(BaseModel):
|
||||
|
||||
node_uuid: str
|
||||
node_name: str
|
||||
country_code: str = ''
|
||||
total_bytes: int
|
||||
|
||||
|
||||
|
||||
27
app/external/remnawave_api.py
vendored
27
app/external/remnawave_api.py
vendored
@@ -564,6 +564,33 @@ class RemnaWaveAPI:
|
||||
user = self._parse_user(response['response'])
|
||||
return await self.enrich_user_with_happ_link(user)
|
||||
|
||||
async def get_user_accessible_nodes(self, uuid: str) -> list[RemnaWaveAccessibleNode]:
|
||||
"""Получает список доступных нод для пользователя"""
|
||||
try:
|
||||
response = await self._make_request('GET', f'/api/users/{uuid}/accessible-nodes')
|
||||
nodes_data = response.get('response', {}).get('activeNodes', [])
|
||||
result = []
|
||||
for node in nodes_data:
|
||||
# Collect inbounds from activeSquads
|
||||
inbounds: list[str] = []
|
||||
for squad in node.get('activeSquads', []):
|
||||
inbounds.extend(squad.get('activeInbounds', []))
|
||||
result.append(
|
||||
RemnaWaveAccessibleNode(
|
||||
uuid=node['uuid'],
|
||||
node_name=node['nodeName'],
|
||||
country_code=node['countryCode'],
|
||||
config_profile_uuid=node.get('configProfileUuid', ''),
|
||||
config_profile_name=node.get('configProfileName', ''),
|
||||
active_inbounds=inbounds,
|
||||
)
|
||||
)
|
||||
return result
|
||||
except RemnaWaveAPIError as e:
|
||||
if e.status_code == 404:
|
||||
return []
|
||||
raise
|
||||
|
||||
async def get_all_users(self, start: int = 0, size: int = 100, enrich_happ_links: bool = False) -> dict[str, Any]:
|
||||
params = {'start': start, 'size': size}
|
||||
response = await self._make_request('GET', '/api/users', params=params)
|
||||
|
||||
Reference in New Issue
Block a user