Petbot/modules/connection_monitor.py
megaproxy 915aa00bea Implement comprehensive rate limiting system and item spawn configuration
Major Features Added:
- Complete token bucket rate limiting for IRC commands and web interface
- Per-user rate tracking with category-based limits (Basic, Gameplay, Management, Admin, Web)
- Admin commands for rate limit management (\!rate_stats, \!rate_user, \!rate_unban, \!rate_reset)
- Automatic violation tracking and temporary bans with cleanup
- Global item spawn multiplier system with 75% spawn rate reduction
- Central admin configuration system (config.py)
- One-command bot startup script (start_petbot.sh)

Rate Limiting:
- Token bucket algorithm with burst capacity and refill rates
- Category limits: Basic (20/min), Gameplay (10/min), Management (5/min), Web (60/min)
- Graceful violation handling with user-friendly error messages
- Admin exemption and override capabilities
- Background cleanup of old violations and expired bans

Item Spawn System:
- Added global_spawn_multiplier to config/items.json for easy adjustment
- Reduced all individual spawn rates by 75% (multiplied by 0.25)
- Admins can fine-tune both global multiplier and individual item rates
- Game engine integration applies multiplier to all spawn calculations

Infrastructure:
- Single admin user configuration in config.py
- Enhanced startup script with dependency management and verification
- Updated documentation and help system with rate limiting guide
- Comprehensive test suite for rate limiting functionality

Security:
- Rate limiting protects against command spam and abuse
- IP-based tracking for web interface requests
- Proper error handling and status codes (429 for rate limits)

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-15 20:10:43 +00:00

236 lines
No EOL
9.5 KiB
Python

from modules.base_module import BaseModule
from datetime import datetime, timedelta
import asyncio
import json
class ConnectionMonitor(BaseModule):
"""Module for monitoring IRC connection status and providing connection commands."""
def __init__(self, bot, database, game_engine=None):
super().__init__(bot, database)
self.game_engine = game_engine
self.start_time = datetime.now()
def get_commands(self):
"""Return list of available connection monitoring commands."""
return [
"status", "uptime", "ping", "reconnect", "connection_stats"
]
async def handle_command(self, channel, nickname, command, args):
"""Handle connection monitoring commands."""
if command == "status":
await self.cmd_status(channel, nickname)
elif command == "uptime":
await self.cmd_uptime(channel, nickname)
elif command == "ping":
await self.cmd_ping(channel, nickname)
elif command == "reconnect":
await self.cmd_reconnect(channel, nickname)
elif command == "connection_stats":
await self.cmd_connection_stats(channel, nickname)
async def cmd_status(self, channel, nickname):
"""Show bot connection status."""
try:
# Get connection manager if available
connection_manager = getattr(self.bot, 'connection_manager', None)
if not connection_manager:
self.send_message(channel, f"{nickname}: Connection manager not available")
return
stats = connection_manager.get_connection_stats()
state = stats.get("state", "unknown")
connected = stats.get("connected", False)
# Status emoji
status_emoji = "🟢" if connected else "🔴"
# Build status message
status_msg = f"{status_emoji} {nickname}: Bot Status - {state.upper()}"
if connected:
uptime = stats.get("uptime", "unknown")
message_count = stats.get("message_count", 0)
status_msg += f" | Uptime: {uptime} | Messages: {message_count}"
else:
reconnect_attempts = stats.get("reconnect_attempts", 0)
status_msg += f" | Reconnect attempts: {reconnect_attempts}"
self.send_message(channel, status_msg)
except Exception as e:
self.send_message(channel, f"{nickname}: Error getting status: {str(e)}")
async def cmd_uptime(self, channel, nickname):
"""Show bot uptime."""
try:
uptime = datetime.now() - self.start_time
# Format uptime
days = uptime.days
hours, remainder = divmod(uptime.seconds, 3600)
minutes, seconds = divmod(remainder, 60)
uptime_str = f"{days}d {hours}h {minutes}m {seconds}s"
# Get additional stats if available
connection_manager = getattr(self.bot, 'connection_manager', None)
if connection_manager:
stats = connection_manager.get_connection_stats()
reconnections = stats.get("total_reconnections", 0)
failures = stats.get("connection_failures", 0)
uptime_str += f" | Reconnections: {reconnections} | Failures: {failures}"
self.send_message(channel, f"{nickname}: Bot uptime: {uptime_str}")
except Exception as e:
self.send_message(channel, f"{nickname}: Error getting uptime: {str(e)}")
async def cmd_ping(self, channel, nickname):
"""Test bot responsiveness."""
try:
start_time = datetime.now()
# Get connection status
connection_manager = getattr(self.bot, 'connection_manager', None)
connected = False
if connection_manager:
connected = connection_manager.is_connected()
end_time = datetime.now()
response_time = (end_time - start_time).total_seconds() * 1000
# Status indicators
connection_status = "🟢 Connected" if connected else "🔴 Disconnected"
ping_emoji = "🏓" if response_time < 100 else "🐌"
self.send_message(channel,
f"{ping_emoji} {nickname}: Pong! Response time: {response_time:.1f}ms | {connection_status}"
)
except Exception as e:
self.send_message(channel, f"{nickname}: Error during ping: {str(e)}")
async def cmd_reconnect(self, channel, nickname):
"""Force reconnection (admin only)."""
try:
# Check if user is admin
if not await self._is_admin(nickname):
self.send_message(channel, f"{nickname}: This command requires admin privileges.")
return
connection_manager = getattr(self.bot, 'connection_manager', None)
if not connection_manager:
self.send_message(channel, f"{nickname}: Connection manager not available.")
return
self.send_message(channel, f"{nickname}: Initiating manual reconnection...")
# Force reconnection by stopping and starting
await connection_manager.stop()
await asyncio.sleep(2)
# Start connection manager in background
asyncio.create_task(connection_manager.start())
self.send_message(channel, f"{nickname}: Reconnection initiated.")
except Exception as e:
self.send_message(channel, f"{nickname}: Error during reconnection: {str(e)}")
async def cmd_connection_stats(self, channel, nickname):
"""Show detailed connection statistics."""
try:
connection_manager = getattr(self.bot, 'connection_manager', None)
if not connection_manager:
self.send_message(channel, f"{nickname}: Connection manager not available")
return
stats = connection_manager.get_connection_stats()
# Build detailed stats message
lines = [
f"📊 {nickname}: Connection Statistics",
f"State: {stats.get('state', 'unknown').upper()}",
f"Connected: {'Yes' if stats.get('connected') else 'No'}",
f"Uptime: {stats.get('uptime', 'unknown')}",
f"Messages: {stats.get('message_count', 0)}",
f"Reconnections: {stats.get('total_reconnections', 0)}",
f"Failures: {stats.get('connection_failures', 0)}",
f"Reconnect attempts: {stats.get('reconnect_attempts', 0)}"
]
# Add timing information
last_message = stats.get('last_message_time')
if last_message:
lines.append(f"Last message: {last_message}")
last_ping = stats.get('last_ping_time')
if last_ping:
lines.append(f"Last ping: {last_ping}")
# Send each line
for line in lines:
self.send_message(channel, line)
await asyncio.sleep(0.3) # Small delay to prevent flooding
except Exception as e:
self.send_message(channel, f"{nickname}: Error getting connection stats: {str(e)}")
async def _is_admin(self, nickname):
"""Check if user has admin privileges."""
# This should match the admin system in other modules
admin_users = ["admin", "megaproxy", "megasconed"]
return nickname.lower() in admin_users
async def get_connection_health(self):
"""Get connection health status for monitoring."""
try:
connection_manager = getattr(self.bot, 'connection_manager', None)
if not connection_manager:
return {
"healthy": False,
"error": "Connection manager not available"
}
stats = connection_manager.get_connection_stats()
connected = stats.get("connected", False)
# Check if connection is healthy
healthy = connected and stats.get("reconnect_attempts", 0) < 5
return {
"healthy": healthy,
"connected": connected,
"state": stats.get("state", "unknown"),
"uptime": stats.get("uptime"),
"message_count": stats.get("message_count", 0),
"reconnect_attempts": stats.get("reconnect_attempts", 0),
"total_reconnections": stats.get("total_reconnections", 0),
"connection_failures": stats.get("connection_failures", 0)
}
except Exception as e:
return {
"healthy": False,
"error": str(e)
}
def get_module_stats(self):
"""Get module-specific statistics."""
uptime = datetime.now() - self.start_time
return {
"module_name": "ConnectionMonitor",
"module_uptime": str(uptime),
"commands_available": len(self.get_commands()),
"start_time": self.start_time
}