|
from librouteros import connect |
|
from librouteros.query import Key |
|
import logging |
|
from typing import Optional |
|
from datetime import datetime |
|
from ..schema import * |
|
from ..schema import UserListData |
|
from .helperfx import PhoneValidator |
|
from .Config import RouterConfig |
|
from time import sleep |
|
|
|
|
|
class MikrotikAPI: |
|
"""Class to handle MikroTik router API operations, inherits VPNManager.""" |
|
|
|
def __init__( |
|
self, |
|
config: RouterConfig, |
|
): |
|
"""Initialize with router configuration and VPN configuration.""" |
|
self.config = config |
|
self.api = None |
|
self.logger = self._setup_logging() |
|
self.phone_validator = PhoneValidator() |
|
|
|
def connect(self) -> bool: |
|
"""Establish connection to VPN and MikroTik router.""" |
|
|
|
try: |
|
self.api = connect( |
|
username=self.config.username, |
|
password=self.config.password, |
|
host=self.config.host, |
|
port=8728, |
|
) |
|
self.logger.info("Successfully connected to MikroTik router.") |
|
return True |
|
except Exception as e: |
|
self.logger.error(f"Failed to connect to MikroTik router: {e}") |
|
return False |
|
|
|
def close(self): |
|
"""Close the router connection and disconnect VPN.""" |
|
if self.api: |
|
self.api.close() |
|
self.logger.info("Router connection closed.") |
|
|
|
def add_hotspot_user( |
|
self, phone: str, password: str, profile: str = "default" |
|
) -> BaseResponse: |
|
"""Add a hotspot user to the router""" |
|
if not self.api: |
|
return ResponseBuilder.error( |
|
BaseResponse, "Registration failed", "Not connected to router" |
|
) |
|
|
|
|
|
is_valid, formatted_phone = self.phone_validator.validate_and_format(phone) |
|
if not is_valid: |
|
return ResponseBuilder.error( |
|
BaseResponse, |
|
"Registration failed", |
|
f"Invalid phone number format: {phone}", |
|
) |
|
|
|
users = self.api.path("ip", "hotspot", "user") |
|
existing_users = users.select(Key("name")) |
|
|
|
|
|
if any(str(user["name"]) == formatted_phone for user in existing_users): |
|
return ResponseBuilder.error( |
|
BaseResponse, |
|
"Registration failed", |
|
f"User {formatted_phone} already exists", |
|
) |
|
|
|
try: |
|
|
|
users.add( |
|
name=formatted_phone, |
|
password=password, |
|
profile=profile, |
|
disabled=True, |
|
) |
|
return ResponseBuilder.success(BaseResponse, "User registered successfully") |
|
|
|
except Exception as e: |
|
self.logger.error(f"Failed to add user {formatted_phone}: {e}") |
|
return ResponseBuilder.error(BaseResponse, "Registration failed", str(e)) |
|
|
|
def login_hotspot_user( |
|
self, phone: str, password: str, mac_address: str, ip_address: str |
|
) -> LoginResponse: |
|
"""Login a hotspot user""" |
|
if not self.api: |
|
return ResponseBuilder.error( |
|
LoginResponse, "Login failed", "Not connected to router" |
|
) |
|
|
|
is_valid, formatted_phone = self.phone_validator.validate_and_format(phone) |
|
if not is_valid: |
|
return ResponseBuilder.error( |
|
LoginResponse, "Login failed", f"Invalid phone number format: {phone}" |
|
) |
|
|
|
try: |
|
users = self.api.path("ip", "hotspot", "user") |
|
active = self.api.path("ip", "hotspot", "active") |
|
|
|
|
|
existing_users = users.select(Key("name"), Key("password"), Key("profile")) |
|
user_data = next( |
|
( |
|
user |
|
for user in existing_users |
|
if str(user["name"]) == formatted_phone |
|
and user["password"] == password |
|
), |
|
None, |
|
) |
|
|
|
if not user_data: |
|
return ResponseBuilder.error( |
|
LoginResponse, "Login failed", "Invalid credentials" |
|
) |
|
|
|
|
|
active_users = active.select(Key("user")) |
|
if any(str(user["user"]) == formatted_phone for user in active_users): |
|
return ResponseBuilder.error( |
|
LoginResponse, |
|
"Login failed", |
|
f"User {formatted_phone} is already logged in", |
|
) |
|
|
|
|
|
self.api.path("ip", "hotspot", "host").call( |
|
"login", |
|
user=formatted_phone, |
|
password=password, |
|
mac_address=mac_address, |
|
ip_address=ip_address, |
|
) |
|
login_time = datetime.now() |
|
return LoginResponse( |
|
code=200, message="Login successful", login_time=login_time |
|
) |
|
|
|
except Exception as e: |
|
self.logger.error(f"Login failed for {formatted_phone}: {e}") |
|
return ResponseBuilder.error(LoginResponse, "Login failed", str(e)) |
|
|
|
def logout_hotspot_user(self, phone: str) -> LogoutResponse: |
|
"""Logout a hotspot user""" |
|
if not self.api: |
|
return ResponseBuilder.error( |
|
LogoutResponse, "Logout failed", "Not connected to router" |
|
) |
|
|
|
is_valid, formatted_phone = self.phone_validator.validate_and_format(phone) |
|
if not is_valid: |
|
return ResponseBuilder.error( |
|
LogoutResponse, "Logout failed", f"Invalid phone number format: {phone}" |
|
) |
|
|
|
active = self.api.path("ip", "hotspot", "active") |
|
|
|
try: |
|
active_users = active.select(Key("user"), Key(".id")) |
|
user_session = next( |
|
( |
|
session |
|
for session in active_users |
|
if str(session["user"]) == formatted_phone |
|
), |
|
None, |
|
) |
|
|
|
if not user_session: |
|
return ResponseBuilder.error( |
|
LogoutResponse, |
|
"Logout failed", |
|
f"User {formatted_phone} is not logged in", |
|
) |
|
|
|
|
|
active.remove(user_session[".id"]) |
|
logout_time = datetime.now() |
|
return LogoutResponse( |
|
code=200, message="Logout successful", logout_time=logout_time |
|
) |
|
|
|
except Exception as e: |
|
self.logger.error(f"Logout failed for user {formatted_phone}: {e}") |
|
return ResponseBuilder.error(LogoutResponse, "Logout failed", str(e)) |
|
|
|
def get_active_users(self) -> ActiveUsersResponse: |
|
"""Get list of active hotspot users""" |
|
if not self.api: |
|
return ResponseBuilder.error( |
|
ActiveUsersResponse, |
|
"Failed to get active users", |
|
"Not connected to router", |
|
) |
|
|
|
try: |
|
active = self.api.path("ip", "hotspot", "active") |
|
active_users = active.select( |
|
Key("user"), |
|
Key("uptime"), |
|
Key("mac-address"), |
|
Key("address"), |
|
Key("bytes-in"), |
|
Key("bytes-out"), |
|
|
|
) |
|
|
|
users_list = [ |
|
UserSessionData( |
|
phone=str(user["user"]), |
|
uptime=user["uptime"], |
|
mac_address=user["mac-address"], |
|
ip_address=user["address"], |
|
bytes_in=int(user["bytes-in"]), |
|
bytes_out=int(user["bytes-out"]), |
|
status="active", |
|
profile=str("2mbps_profile"), |
|
) |
|
for user in active_users |
|
] |
|
print(users_list) |
|
return ActiveUsersResponse( |
|
code=200, |
|
message="Active users retrieved successfully", |
|
active_users=users_list, |
|
) |
|
|
|
except Exception as e: |
|
self.logger.error(f"Failed to get active users: {e}") |
|
return ResponseBuilder.error( |
|
ActiveUsersResponse, "Failed to get active users", str(e) |
|
) |
|
|
|
def get_user_stats(self, phone: Optional[str] = None) -> UserStatsResponse: |
|
"""Get statistics for all users or a specific user""" |
|
if not self.api: |
|
return ResponseBuilder.error( |
|
UserStatsResponse, |
|
"Failed to get user statistics", |
|
"Not connected to router", |
|
) |
|
|
|
try: |
|
users = self.api.path("ip", "hotspot", "user") |
|
all_users = users.select( |
|
Key("name"), |
|
Key("profile"), |
|
Key("disabled"), |
|
Key("uptime"), |
|
Key("bytes-in"), |
|
Key("bytes-out"), |
|
) |
|
|
|
user_stats = [ |
|
UserStats( |
|
phone=str(user["name"]), |
|
profile=user.get("profile", "default"), |
|
status=( |
|
"disabled" |
|
if user.get("disabled", "false") == "true" |
|
else "enabled" |
|
), |
|
uptime=user.get("uptime", "0s"), |
|
bytes_in=int(user.get("bytes-in", 0)), |
|
bytes_out=int(user.get("bytes-out", 0)), |
|
) |
|
for user in all_users |
|
if not phone or str(user["name"]) == phone |
|
] |
|
|
|
if phone and not user_stats: |
|
return ResponseBuilder.error( |
|
UserStatsResponse, "User not found", f"User {phone} does not exist" |
|
) |
|
|
|
return UserStatsResponse( |
|
code=200, |
|
message="User statistics retrieved successfully", |
|
user_stats=user_stats, |
|
) |
|
|
|
except Exception as e: |
|
self.logger.error(f"Failed to get user statistics: {e}") |
|
return ResponseBuilder.error( |
|
UserStatsResponse, "Failed to get user statistics", str(e) |
|
) |
|
|
|
|
|
|
|
def set_user_status(self, phone: str, disabled: bool) -> UserStatusResponse: |
|
"""Enable or disable a hotspot user.""" |
|
if not self.api: |
|
return ResponseBuilder.error( |
|
UserStatusResponse, "Status update failed", "Not connected to router" |
|
) |
|
|
|
|
|
is_valid, formatted_phone = self.phone_validator.validate_and_format(phone) |
|
if not is_valid: |
|
return ResponseBuilder.error( |
|
UserStatusResponse, |
|
"Status update failed", |
|
f"Invalid phone number format: {phone}", |
|
) |
|
users = self.api.path("ip", "hotspot", "user") |
|
|
|
try: |
|
|
|
existing_users = users.select( |
|
Key("name"), |
|
Key(".id"), |
|
Key("mbonea"), |
|
) |
|
for user in existing_users: |
|
if str(user["name"]) == formatted_phone: |
|
user_data = user |
|
break |
|
|
|
if not user_data: |
|
return ResponseBuilder.error( |
|
UserStatusResponse, |
|
"Status update failed", |
|
f"User {formatted_phone} does not exist", |
|
) |
|
|
|
user_id = user_data[".id"] |
|
status_value = "true" if disabled else "false" |
|
users.update(**{".id": user_id, "disabled": status_value}) |
|
status = "disabled" if disabled else "enabled" |
|
self.logger.info(f"User {formatted_phone} successfully {status}") |
|
|
|
|
|
if disabled: |
|
self.logout_hotspot_user(formatted_phone) |
|
|
|
return UserStatusResponse( |
|
code=200, message=f"User successfully {status}", user_status=status |
|
) |
|
|
|
except Exception as e: |
|
print(f"Failed to update status for user {formatted_phone}: {e}") |
|
self.logger.error( |
|
f"Failed to update status for user {formatted_phone}: {e}" |
|
) |
|
return ResponseBuilder.error( |
|
UserStatusResponse, "Status update failed", str(e) |
|
) |
|
|
|
def activate_user(self, phone: str) -> UserStatusResponse: |
|
"""Activate a user (set disabled to false).""" |
|
return self.set_user_status(phone, disabled=False) |
|
|
|
def deactivate_user(self, phone: str) -> UserStatusResponse: |
|
"""Deactivate a user (set disabled to true and log them out).""" |
|
return self.set_user_status(phone, disabled=True) |
|
|
|
def get_users_list(self) -> UsersListResponse: |
|
"""Get list of all hotspot users (both active and inactive)""" |
|
if not self.api: |
|
return UsersListResponse( |
|
success=False, |
|
message="Failed to get users list", |
|
error="Not connected to router", |
|
) |
|
|
|
try: |
|
users = self.api.path("ip", "hotspot", "user") |
|
active = self.api.path("ip", "hotspot", "active") |
|
|
|
|
|
all_users = users.select( |
|
Key("name"), |
|
Key("profile"), |
|
Key("disabled"), |
|
Key("comment"), |
|
Key("limit-bytes-total"), |
|
Key("bytes-in"), |
|
Key("bytes-out"), |
|
) |
|
|
|
|
|
active_users = active.select(Key("user")) |
|
active_phones = [str(user.get("user", "")) for user in active_users] |
|
|
|
|
|
users_list = [] |
|
for user in all_users: |
|
phone_number = str(user["name"]) |
|
bytes_in = int(user.get("bytes-in", 0)) |
|
bytes_out = int(user.get("bytes-out", 0)) |
|
data_limit = ( |
|
int(user.get("limit-bytes-total", 0)) |
|
if user.get("limit-bytes-total") |
|
else None |
|
) |
|
|
|
user_data = UserListData( |
|
phoneNumber=phone_number, |
|
profile=user.get("profile", "default"), |
|
status="active" if phone_number in active_phones else "inactive", |
|
disabled=user.get("disabled", "false") == "true", |
|
comment=user.get("comment", ""), |
|
data_limit=data_limit, |
|
) |
|
users_list.append(user_data) |
|
|
|
return UsersListResponse( |
|
code=200, |
|
message=f"Retrieved {len(users_list)} users", |
|
data=users, |
|
) |
|
|
|
except Exception as e: |
|
return UsersListResponse( |
|
success=False, message="Failed to get users list", error=str(e) |
|
) |
|
|