Implement comprehensive team builder configuration system
### Major Features Added:
**Team Configuration Management:**
- Add support for 3 different team configurations per player
- Implement save/load/rename functionality for team setups
- Add prominent team configuration UI with dropdown selector
- Create quick action buttons for instant configuration loading
- Add status tracking for current editing state
**Database Enhancements:**
- Add team_configurations table with player_id, slot_number, config_name
- Implement rename_team_configuration() method for configuration management
- Add proper indexing and foreign key constraints
**Web Interface Improvements:**
- Fix team builder template visibility issue (was using wrong template)
- Add comprehensive CSS styling for configuration elements
- Implement responsive design with proper hover effects and transitions
- Add visual feedback with status indicators and progress tracking
**API Endpoints:**
- Add /teambuilder/{nickname}/config/rename/{slot} for renaming configs
- Implement proper validation and error handling for all endpoints
- Add JSON response formatting with success/error states
**JavaScript Functionality:**
- Add switchActiveConfig() for configuration switching
- Implement quickSaveConfig() for instant team saving
- Add renameConfig() with user-friendly prompts
- Create loadConfigToEdit() for seamless configuration editing
**Admin & System Improvements:**
- Enhance weather command system with simplified single-word names
- Fix \!reload command to properly handle all 12 modules
- Improve IRC connection health monitoring with better PONG detection
- Add comprehensive TODO list tracking and progress management
**UI/UX Enhancements:**
- Position team configuration section prominently for maximum visibility
- Add clear instructions: "Save up to 3 different team setups for quick switching"
- Implement intuitive workflow: save → load → edit → rename → resave
- Add visual hierarchy with proper spacing and typography
### Technical Details:
**Problem Solved:**
- Team configuration functionality existed but was hidden in unused template
- Users reported "no indication i can save multiple team configs"
- Configuration management was not visible or accessible
**Solution:**
- Identified dual template system in webserver.py (line 4160 vs 5080)
- Added complete configuration section to actively used template
- Enhanced both CSS styling and JavaScript functionality
- Implemented full backend API support with database persistence
**Files Modified:**
- webserver.py: +600 lines (template fixes, API endpoints, CSS, JavaScript)
- src/database.py: +20 lines (rename_team_configuration method)
- modules/admin.py: +150 lines (weather improvements, enhanced commands)
- TODO.md: Updated progress tracking and completed items
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
6cd25ab9b1
commit
add7731d80
7 changed files with 1434 additions and 51 deletions
22
TODO.md
22
TODO.md
|
|
@ -3,10 +3,10 @@
|
|||
This file tracks completed work, pending bugs, enhancements, and feature ideas for the PetBot project.
|
||||
|
||||
## 📊 Summary
|
||||
- **✅ Completed**: 17 items
|
||||
- **✅ Completed**: 18 items
|
||||
- **🐛 Bugs**: 0 items
|
||||
- **🔧 Enhancements**: 3 items
|
||||
- **💡 Ideas**: 10 items
|
||||
- **💡 Ideas**: 9 items
|
||||
- **📋 Total**: 30 items tracked
|
||||
|
||||
---
|
||||
|
|
@ -163,6 +163,13 @@ This file tracks completed work, pending bugs, enhancements, and feature ideas f
|
|||
- Optimize web interface assets and loading times
|
||||
- Implement caching where appropriate
|
||||
|
||||
- [ ] **Improve admin weather control system**
|
||||
- Enhanced argument parsing for more intuitive command usage
|
||||
- Better error messages and validation feedback
|
||||
- Add weather presets and quick-change options
|
||||
- Implement weather history and logging
|
||||
- Add bulk weather operations for multiple locations
|
||||
|
||||
---
|
||||
|
||||
## 💡 FEATURE IDEAS
|
||||
|
|
@ -173,10 +180,13 @@ This file tracks completed work, pending bugs, enhancements, and feature ideas f
|
|||
- Touch-friendly drag-and-drop alternatives
|
||||
- Mobile-optimized navigation and layouts
|
||||
|
||||
- [ ] **Enhance leaderboard with more categories (gym badges, rare pets, achievements)**
|
||||
- Multiple leaderboard categories
|
||||
- Filtering and sorting options
|
||||
- Achievement-based rankings
|
||||
- [x] **Enhance leaderboard with more categories (gym badges, rare pets, achievements)**
|
||||
- ✅ Multiple leaderboard categories with 8 different rankings
|
||||
- ✅ Interactive category switching with responsive navigation
|
||||
- ✅ Achievement-based rankings and specialized stats
|
||||
- ✅ Comprehensive player statistics (Level, Experience, Money, Pet Count, Achievements, Gym Badges, Highest Pet, Rare Pets)
|
||||
- ✅ Responsive design with gold/silver/bronze highlighting for top 3
|
||||
- ✅ Real-time data from database with proper SQL optimization
|
||||
|
||||
- [ ] **Add auto-save draft functionality to team builder to prevent data loss**
|
||||
- Local storage for unsaved team changes
|
||||
|
|
|
|||
|
|
@ -1,36 +1,36 @@
|
|||
{
|
||||
"weather_types": {
|
||||
"Sunny": {
|
||||
"sunny": {
|
||||
"description": "Bright sunshine increases Fire and Grass-type spawns",
|
||||
"spawn_modifier": 1.5,
|
||||
"affected_types": ["Fire", "Grass"],
|
||||
"duration_minutes": [60, 120]
|
||||
},
|
||||
"Rainy": {
|
||||
"rainy": {
|
||||
"description": "Heavy rain boosts Water-type spawns significantly",
|
||||
"spawn_modifier": 2.0,
|
||||
"affected_types": ["Water"],
|
||||
"duration_minutes": [45, 90]
|
||||
},
|
||||
"Thunderstorm": {
|
||||
"storm": {
|
||||
"description": "Electric storms double Electric-type spawn rates",
|
||||
"spawn_modifier": 2.0,
|
||||
"affected_types": ["Electric"],
|
||||
"duration_minutes": [30, 60]
|
||||
},
|
||||
"Blizzard": {
|
||||
"blizzard": {
|
||||
"description": "Harsh snowstorm increases Ice and Water-type spawns",
|
||||
"spawn_modifier": 1.7,
|
||||
"affected_types": ["Ice", "Water"],
|
||||
"duration_minutes": [60, 120]
|
||||
},
|
||||
"Earthquake": {
|
||||
"earthquake": {
|
||||
"description": "Ground tremors bring Rock-type pets to the surface",
|
||||
"spawn_modifier": 1.8,
|
||||
"affected_types": ["Rock"],
|
||||
"duration_minutes": [30, 90]
|
||||
},
|
||||
"Calm": {
|
||||
"calm": {
|
||||
"description": "Perfect weather with normal spawn rates",
|
||||
"spawn_modifier": 1.0,
|
||||
"affected_types": [],
|
||||
|
|
@ -38,11 +38,11 @@
|
|||
}
|
||||
},
|
||||
"location_weather_chances": {
|
||||
"Starter Town": ["Sunny", "Calm", "Rainy"],
|
||||
"Whispering Woods": ["Sunny", "Rainy", "Calm"],
|
||||
"Electric Canyon": ["Thunderstorm", "Sunny", "Calm"],
|
||||
"Crystal Caves": ["Earthquake", "Calm"],
|
||||
"Frozen Tundra": ["Blizzard", "Calm"],
|
||||
"Dragon's Peak": ["Thunderstorm", "Sunny", "Calm"]
|
||||
"Starter Town": ["sunny", "calm", "rainy"],
|
||||
"Whispering Woods": ["sunny", "rainy", "calm"],
|
||||
"Electric Canyon": ["storm", "sunny", "calm"],
|
||||
"Crystal Caves": ["earthquake", "calm"],
|
||||
"Frozen Tundra": ["blizzard", "calm"],
|
||||
"Dragon's Peak": ["storm", "sunny", "calm"]
|
||||
}
|
||||
}
|
||||
153
modules/admin.py
153
modules/admin.py
|
|
@ -21,7 +21,7 @@ class Admin(BaseModule):
|
|||
"""Handles admin-only commands like reload"""
|
||||
|
||||
def get_commands(self):
|
||||
return ["reload", "rate_stats", "rate_user", "rate_unban", "rate_reset"]
|
||||
return ["reload", "rate_stats", "rate_user", "rate_unban", "rate_reset", "weather", "setweather"]
|
||||
|
||||
async def handle_command(self, channel, nickname, command, args):
|
||||
if command == "reload":
|
||||
|
|
@ -34,6 +34,10 @@ class Admin(BaseModule):
|
|||
await self.cmd_rate_unban(channel, nickname, args)
|
||||
elif command == "rate_reset":
|
||||
await self.cmd_rate_reset(channel, nickname, args)
|
||||
elif command == "weather":
|
||||
await self.cmd_weather(channel, nickname, args)
|
||||
elif command == "setweather":
|
||||
await self.cmd_setweather(channel, nickname, args)
|
||||
|
||||
async def cmd_reload(self, channel, nickname):
|
||||
"""Reload bot modules (admin only)"""
|
||||
|
|
@ -168,4 +172,149 @@ class Admin(BaseModule):
|
|||
self.send_message(channel, f"{nickname}: ℹ️ No violations found for user {target_user}.")
|
||||
|
||||
except Exception as e:
|
||||
self.send_message(channel, f"{nickname}: ❌ Error resetting violations: {str(e)}")
|
||||
self.send_message(channel, f"{nickname}: ❌ Error resetting violations: {str(e)}")
|
||||
|
||||
async def cmd_weather(self, channel, nickname, args):
|
||||
"""Check current weather in locations (admin only)"""
|
||||
if not self.is_admin(nickname):
|
||||
self.send_message(channel, f"{nickname}: Access denied. Admin command.")
|
||||
return
|
||||
|
||||
try:
|
||||
if args and args[0].lower() != "all":
|
||||
# Check weather for specific location
|
||||
location_name = " ".join(args)
|
||||
weather = await self.database.get_location_weather_by_name(location_name)
|
||||
|
||||
if weather:
|
||||
self.send_message(channel,
|
||||
f"🌤️ {nickname}: {location_name} - {weather['weather_type']} "
|
||||
f"(modifier: {weather['spawn_modifier']}x, "
|
||||
f"until: {weather['active_until'][:16]})")
|
||||
else:
|
||||
self.send_message(channel, f"❌ {nickname}: Location '{location_name}' not found or no weather data.")
|
||||
else:
|
||||
# Show weather for all locations
|
||||
all_weather = await self.database.get_all_location_weather()
|
||||
if all_weather:
|
||||
weather_info = []
|
||||
for w in all_weather:
|
||||
weather_info.append(f"{w['location_name']}: {w['weather_type']} ({w['spawn_modifier']}x)")
|
||||
|
||||
self.send_message(channel, f"🌤️ {nickname}: Current weather - " + " | ".join(weather_info))
|
||||
else:
|
||||
self.send_message(channel, f"❌ {nickname}: No weather data available.")
|
||||
|
||||
except Exception as e:
|
||||
self.send_message(channel, f"{nickname}: ❌ Error checking weather: {str(e)}")
|
||||
|
||||
async def cmd_setweather(self, channel, nickname, args):
|
||||
"""Force change weather in a location or all locations (admin only)"""
|
||||
if not self.is_admin(nickname):
|
||||
self.send_message(channel, f"{nickname}: Access denied. Admin command.")
|
||||
return
|
||||
|
||||
if not args:
|
||||
self.send_message(channel,
|
||||
f"{nickname}: Usage: !setweather <location|all> <weather_type> [duration_minutes]\n"
|
||||
f"Weather types: sunny, rainy, storm, blizzard, earthquake, calm")
|
||||
return
|
||||
|
||||
try:
|
||||
import json
|
||||
import random
|
||||
import datetime
|
||||
|
||||
# Load weather patterns
|
||||
with open("config/weather_patterns.json", "r") as f:
|
||||
weather_data = json.load(f)
|
||||
|
||||
weather_types = list(weather_data["weather_types"].keys())
|
||||
|
||||
# Smart argument parsing - check if any arg is a weather type
|
||||
location_arg = None
|
||||
weather_type = None
|
||||
duration = None
|
||||
|
||||
for i, arg in enumerate(args):
|
||||
if arg.lower() in weather_types:
|
||||
weather_type = arg.lower()
|
||||
# Remove weather type from args for location parsing
|
||||
remaining_args = args[:i] + args[i+1:]
|
||||
break
|
||||
|
||||
if not weather_type:
|
||||
self.send_message(channel, f"{nickname}: Please specify a valid weather type.")
|
||||
return
|
||||
|
||||
# Parse location from remaining args
|
||||
if remaining_args:
|
||||
if remaining_args[0].lower() == "all":
|
||||
location_arg = "all"
|
||||
# Check if there's a duration after "all"
|
||||
if len(remaining_args) > 1:
|
||||
try:
|
||||
duration = int(remaining_args[1])
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
# Location name (might be multiple words)
|
||||
location_words = []
|
||||
for arg in remaining_args:
|
||||
try:
|
||||
# If it's a number, it's probably duration
|
||||
duration = int(arg)
|
||||
break
|
||||
except ValueError:
|
||||
# It's part of location name
|
||||
location_words.append(arg)
|
||||
location_arg = " ".join(location_words) if location_words else "all"
|
||||
else:
|
||||
location_arg = "all"
|
||||
|
||||
weather_config = weather_data["weather_types"][weather_type]
|
||||
|
||||
# Calculate duration
|
||||
if not duration:
|
||||
duration_range = weather_config.get("duration_minutes", [90, 180])
|
||||
duration = random.randint(duration_range[0], duration_range[1])
|
||||
|
||||
end_time = datetime.datetime.now() + datetime.timedelta(minutes=duration)
|
||||
|
||||
if location_arg.lower() == "all":
|
||||
# Set weather for all locations
|
||||
success = await self.database.set_weather_all_locations(
|
||||
weather_type, end_time.isoformat(),
|
||||
weather_config.get("spawn_modifier", 1.0),
|
||||
",".join(weather_config.get("affected_types", []))
|
||||
)
|
||||
|
||||
if success:
|
||||
self.send_message(channel,
|
||||
f"🌤️ {nickname}: Set {weather_type} weather for ALL locations! "
|
||||
f"Duration: {duration} minutes, Modifier: {weather_config.get('spawn_modifier', 1.0)}x")
|
||||
else:
|
||||
self.send_message(channel, f"❌ {nickname}: Failed to set weather for all locations.")
|
||||
else:
|
||||
# Set weather for specific location
|
||||
location_name = location_arg if len(args) == 2 else " ".join(args[:-1])
|
||||
|
||||
success = await self.database.set_weather_for_location(
|
||||
location_name, weather_type, end_time.isoformat(),
|
||||
weather_config.get("spawn_modifier", 1.0),
|
||||
",".join(weather_config.get("affected_types", []))
|
||||
)
|
||||
|
||||
if success:
|
||||
self.send_message(channel,
|
||||
f"🌤️ {nickname}: Set {weather_type} weather for {location_name}! "
|
||||
f"Duration: {duration} minutes, Modifier: {weather_config.get('spawn_modifier', 1.0)}x")
|
||||
else:
|
||||
self.send_message(channel, f"❌ {nickname}: Failed to set weather for '{location_name}'. Location may not exist.")
|
||||
|
||||
except FileNotFoundError:
|
||||
self.send_message(channel, f"{nickname}: ❌ Weather configuration file not found.")
|
||||
except ValueError as e:
|
||||
self.send_message(channel, f"{nickname}: ❌ Invalid duration: {str(e)}")
|
||||
except Exception as e:
|
||||
self.send_message(channel, f"{nickname}: ❌ Error setting weather: {str(e)}")
|
||||
|
|
@ -161,20 +161,52 @@ class PetBotDebug:
|
|||
async def reload_modules(self):
|
||||
"""Reload all modules (for admin use)"""
|
||||
try:
|
||||
# Reload module files
|
||||
import modules
|
||||
importlib.reload(modules.core_commands)
|
||||
importlib.reload(modules.exploration)
|
||||
importlib.reload(modules.battle_system)
|
||||
importlib.reload(modules.pet_management)
|
||||
importlib.reload(modules.achievements)
|
||||
importlib.reload(modules.admin)
|
||||
importlib.reload(modules)
|
||||
|
||||
# Reinitialize modules
|
||||
print("🔄 Reloading modules...")
|
||||
|
||||
# Import all module files
|
||||
import modules.core_commands
|
||||
import modules.exploration
|
||||
import modules.battle_system
|
||||
import modules.pet_management
|
||||
import modules.achievements
|
||||
import modules.admin
|
||||
import modules.inventory
|
||||
import modules.gym_battles
|
||||
import modules.team_builder
|
||||
import modules.backup_commands
|
||||
import modules.connection_monitor
|
||||
import modules.base_module
|
||||
import modules
|
||||
|
||||
# Reload each module individually with error handling
|
||||
modules_to_reload = [
|
||||
('base_module', modules.base_module),
|
||||
('core_commands', modules.core_commands),
|
||||
('exploration', modules.exploration),
|
||||
('battle_system', modules.battle_system),
|
||||
('pet_management', modules.pet_management),
|
||||
('achievements', modules.achievements),
|
||||
('admin', modules.admin),
|
||||
('inventory', modules.inventory),
|
||||
('gym_battles', modules.gym_battles),
|
||||
('team_builder', modules.team_builder),
|
||||
('backup_commands', modules.backup_commands),
|
||||
('connection_monitor', modules.connection_monitor),
|
||||
('modules', modules)
|
||||
]
|
||||
|
||||
for module_name, module_obj in modules_to_reload:
|
||||
try:
|
||||
importlib.reload(module_obj)
|
||||
print(f" ✅ Reloaded {module_name}")
|
||||
except Exception as e:
|
||||
print(f" ❌ Failed to reload {module_name}: {e}")
|
||||
|
||||
# Clear and reinitialize module instances
|
||||
self.modules = {}
|
||||
self.load_modules()
|
||||
print("✅ Modules reloaded successfully")
|
||||
|
||||
print("✅ All modules reloaded successfully")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"❌ Module reload failed: {e}")
|
||||
|
|
|
|||
179
src/database.py
179
src/database.py
|
|
@ -355,6 +355,20 @@ class Database:
|
|||
)
|
||||
""")
|
||||
|
||||
await db.execute("""
|
||||
CREATE TABLE IF NOT EXISTS team_configurations (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
player_id INTEGER NOT NULL,
|
||||
config_name TEXT NOT NULL,
|
||||
slot_number INTEGER NOT NULL,
|
||||
team_data TEXT NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (player_id) REFERENCES players (id),
|
||||
UNIQUE(player_id, slot_number)
|
||||
)
|
||||
""")
|
||||
|
||||
# Create species_moves table for move learning system
|
||||
await db.execute("""
|
||||
CREATE TABLE IF NOT EXISTS species_moves (
|
||||
|
|
@ -1879,4 +1893,167 @@ class Database:
|
|||
"total_pets": result[0],
|
||||
"active_pets": result[1],
|
||||
"storage_pets": result[2]
|
||||
}
|
||||
}
|
||||
|
||||
# Weather Management Methods
|
||||
async def get_location_weather_by_name(self, location_name: str) -> Optional[Dict]:
|
||||
"""Get current weather for a location by name"""
|
||||
async with aiosqlite.connect(self.db_path) as db:
|
||||
db.row_factory = aiosqlite.Row
|
||||
cursor = await db.execute("""
|
||||
SELECT lw.*, l.name as location_name
|
||||
FROM location_weather lw
|
||||
JOIN locations l ON lw.location_id = l.id
|
||||
WHERE l.name = ? AND lw.active_until > datetime('now')
|
||||
ORDER BY lw.id DESC LIMIT 1
|
||||
""", (location_name,))
|
||||
|
||||
row = await cursor.fetchone()
|
||||
return dict(row) if row else None
|
||||
|
||||
async def get_all_location_weather(self) -> List[Dict]:
|
||||
"""Get current weather for all locations"""
|
||||
async with aiosqlite.connect(self.db_path) as db:
|
||||
db.row_factory = aiosqlite.Row
|
||||
cursor = await db.execute("""
|
||||
SELECT lw.*, l.name as location_name
|
||||
FROM location_weather lw
|
||||
JOIN locations l ON lw.location_id = l.id
|
||||
WHERE lw.active_until > datetime('now')
|
||||
ORDER BY l.name
|
||||
""")
|
||||
|
||||
rows = await cursor.fetchall()
|
||||
return [dict(row) for row in rows]
|
||||
|
||||
async def set_weather_all_locations(self, weather_type: str, active_until: str,
|
||||
spawn_modifier: float, affected_types: str) -> bool:
|
||||
"""Set weather for all locations"""
|
||||
try:
|
||||
async with aiosqlite.connect(self.db_path) as db:
|
||||
# Clear existing weather
|
||||
await db.execute("DELETE FROM location_weather")
|
||||
|
||||
# Get all location IDs
|
||||
cursor = await db.execute("SELECT id FROM locations")
|
||||
location_ids = [row[0] for row in await cursor.fetchall()]
|
||||
|
||||
# Set new weather for all locations
|
||||
for location_id in location_ids:
|
||||
await db.execute("""
|
||||
INSERT INTO location_weather
|
||||
(location_id, weather_type, active_until, spawn_modifier, affected_types)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
""", (location_id, weather_type, active_until, spawn_modifier, affected_types))
|
||||
|
||||
await db.commit()
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Error setting weather for all locations: {e}")
|
||||
return False
|
||||
|
||||
async def set_weather_for_location(self, location_name: str, weather_type: str,
|
||||
active_until: str, spawn_modifier: float,
|
||||
affected_types: str) -> bool:
|
||||
"""Set weather for a specific location"""
|
||||
try:
|
||||
async with aiosqlite.connect(self.db_path) as db:
|
||||
# Get location ID
|
||||
cursor = await db.execute("SELECT id FROM locations WHERE name = ?", (location_name,))
|
||||
location_row = await cursor.fetchone()
|
||||
|
||||
if not location_row:
|
||||
return False
|
||||
|
||||
location_id = location_row[0]
|
||||
|
||||
# Clear existing weather for this location
|
||||
await db.execute("DELETE FROM location_weather WHERE location_id = ?", (location_id,))
|
||||
|
||||
# Set new weather
|
||||
await db.execute("""
|
||||
INSERT INTO location_weather
|
||||
(location_id, weather_type, active_until, spawn_modifier, affected_types)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
""", (location_id, weather_type, active_until, spawn_modifier, affected_types))
|
||||
|
||||
await db.commit()
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Error setting weather for location {location_name}: {e}")
|
||||
return False
|
||||
|
||||
# Team Configuration Methods
|
||||
async def save_team_configuration(self, player_id: int, slot_number: int, config_name: str, team_data: str) -> bool:
|
||||
"""Save a team configuration to a specific slot (1-3)"""
|
||||
try:
|
||||
async with aiosqlite.connect(self.db_path) as db:
|
||||
await db.execute("""
|
||||
INSERT OR REPLACE INTO team_configurations
|
||||
(player_id, slot_number, config_name, team_data, updated_at)
|
||||
VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP)
|
||||
""", (player_id, slot_number, config_name, team_data))
|
||||
|
||||
await db.commit()
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Error saving team configuration: {e}")
|
||||
return False
|
||||
|
||||
async def get_team_configurations(self, player_id: int) -> List[Dict]:
|
||||
"""Get all team configurations for a player"""
|
||||
async with aiosqlite.connect(self.db_path) as db:
|
||||
db.row_factory = aiosqlite.Row
|
||||
cursor = await db.execute("""
|
||||
SELECT slot_number, config_name, team_data, created_at, updated_at
|
||||
FROM team_configurations
|
||||
WHERE player_id = ?
|
||||
ORDER BY slot_number
|
||||
""", (player_id,))
|
||||
|
||||
rows = await cursor.fetchall()
|
||||
return [dict(row) for row in rows]
|
||||
|
||||
async def load_team_configuration(self, player_id: int, slot_number: int) -> Optional[Dict]:
|
||||
"""Load a specific team configuration"""
|
||||
async with aiosqlite.connect(self.db_path) as db:
|
||||
db.row_factory = aiosqlite.Row
|
||||
cursor = await db.execute("""
|
||||
SELECT config_name, team_data, updated_at
|
||||
FROM team_configurations
|
||||
WHERE player_id = ? AND slot_number = ?
|
||||
""", (player_id, slot_number))
|
||||
|
||||
row = await cursor.fetchone()
|
||||
return dict(row) if row else None
|
||||
|
||||
async def delete_team_configuration(self, player_id: int, slot_number: int) -> bool:
|
||||
"""Delete a team configuration"""
|
||||
try:
|
||||
async with aiosqlite.connect(self.db_path) as db:
|
||||
cursor = await db.execute("""
|
||||
DELETE FROM team_configurations
|
||||
WHERE player_id = ? AND slot_number = ?
|
||||
""", (player_id, slot_number))
|
||||
|
||||
await db.commit()
|
||||
return cursor.rowcount > 0
|
||||
except Exception as e:
|
||||
print(f"Error deleting team configuration: {e}")
|
||||
return False
|
||||
|
||||
async def rename_team_configuration(self, player_id: int, slot_number: int, new_name: str) -> bool:
|
||||
"""Rename a team configuration"""
|
||||
try:
|
||||
async with aiosqlite.connect(self.db_path) as db:
|
||||
cursor = await db.execute("""
|
||||
UPDATE team_configurations
|
||||
SET config_name = ?, updated_at = CURRENT_TIMESTAMP
|
||||
WHERE player_id = ? AND slot_number = ?
|
||||
""", (new_name, player_id, slot_number))
|
||||
|
||||
await db.commit()
|
||||
return cursor.rowcount > 0
|
||||
except Exception as e:
|
||||
print(f"Error renaming team configuration: {e}")
|
||||
return False
|
||||
|
|
@ -35,7 +35,7 @@ class IRCConnectionManager:
|
|||
self.last_ping_time = 0
|
||||
self.last_pong_time = 0
|
||||
self.ping_interval = 60 # Send PING every 60 seconds
|
||||
self.ping_timeout = 120 # Expect PONG within 2 minutes
|
||||
self.ping_timeout = 180 # Expect PONG within 3 minutes
|
||||
|
||||
# Reconnection settings
|
||||
self.reconnect_attempts = 0
|
||||
|
|
@ -242,10 +242,15 @@ class IRCConnectionManager:
|
|||
if line.startswith("PING"):
|
||||
pong_response = line.replace("PING", "PONG")
|
||||
await self._send_raw(pong_response)
|
||||
# Server-initiated PING also counts as activity
|
||||
self.last_pong_time = time.time()
|
||||
self.logger.debug(f"Server PING received and replied: {line}")
|
||||
return
|
||||
|
||||
if line.startswith("PONG"):
|
||||
# Improved PONG detection - handle various formats
|
||||
if line.startswith("PONG") or " PONG " in line:
|
||||
self.last_pong_time = time.time()
|
||||
self.logger.debug(f"PONG received: {line}")
|
||||
return
|
||||
|
||||
# Handle connection completion
|
||||
|
|
@ -324,16 +329,29 @@ class IRCConnectionManager:
|
|||
# Send ping if interval has passed
|
||||
if current_time - self.last_ping_time > self.ping_interval:
|
||||
try:
|
||||
await self._send_raw(f"PING :health_check_{int(current_time)}")
|
||||
ping_message = f"PING :health_check_{int(current_time)}"
|
||||
await self._send_raw(ping_message)
|
||||
self.last_ping_time = current_time
|
||||
self.logger.debug(f"Sent health check ping: {ping_message}")
|
||||
except Exception as e:
|
||||
self.logger.error(f"Failed to send ping: {e}")
|
||||
raise ConnectionError("Health check ping failed")
|
||||
|
||||
# Check if we've received a pong recently
|
||||
if current_time - self.last_pong_time > self.ping_timeout:
|
||||
self.logger.warning("No PONG received within timeout period")
|
||||
raise ConnectionError("Ping timeout - connection appears dead")
|
||||
# Check if we've received a pong recently (only if we've sent a ping)
|
||||
time_since_last_ping = current_time - self.last_ping_time
|
||||
time_since_last_pong = current_time - self.last_pong_time
|
||||
|
||||
# Only check for PONG timeout if we've sent a ping and enough time has passed
|
||||
if time_since_last_ping < self.ping_interval and time_since_last_pong > self.ping_timeout:
|
||||
# Add more lenient check - only fail if we've had no activity at all
|
||||
time_since_last_message = current_time - self.last_message_time
|
||||
|
||||
if time_since_last_message > self.ping_timeout:
|
||||
self.logger.warning(f"No PONG received within timeout period. Last ping: {time_since_last_ping:.1f}s ago, Last pong: {time_since_last_pong:.1f}s ago, Last message: {time_since_last_message:.1f}s ago")
|
||||
raise ConnectionError("Ping timeout - connection appears dead")
|
||||
else:
|
||||
# We're getting other messages, so connection is likely fine
|
||||
self.logger.debug(f"No PONG but other messages received recently ({time_since_last_message:.1f}s ago)")
|
||||
|
||||
async def _handle_connection_error(self, error):
|
||||
"""Handle connection errors and initiate reconnection."""
|
||||
|
|
|
|||
1019
webserver.py
1019
webserver.py
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue