From 285a7c4a7e828e7c1d4e92c0842ea7363f458f43 Mon Sep 17 00:00:00 2001 From: megaproxy Date: Thu, 17 Jul 2025 14:14:01 +0000 Subject: [PATCH] Complete team builder enhancement with active team display and pet counts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix pet count display for all saved teams (handles both list and dict formats) - Add comprehensive active team display with individual pet cards on hub - Show detailed pet information: stats, HP bars, happiness, types, levels - Implement responsive grid layout for active pet cards with hover effects - Add proper data format handling between active and saved teams - Create dedicated team hub with both overview and detailed sections - Standardize team data pipeline for consistent display across all interfaces šŸ¤– Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- src/database.py | 11 +- src/team_management.py | 6 +- webserver.py | 1602 +++++++++++++++++++++++++++++++++++++++- 3 files changed, 1602 insertions(+), 17 deletions(-) diff --git a/src/database.py b/src/database.py index e69abe1..f3ff3f7 100644 --- a/src/database.py +++ b/src/database.py @@ -2543,12 +2543,21 @@ class Database: if row: import json team_data = json.loads(row['team_data']) + + # Handle both new format (list) and old format (dict) + if isinstance(team_data, list): + pet_count = len(team_data) + elif isinstance(team_data, dict): + pet_count = len([p for p in team_data.values() if p]) + else: + pet_count = 0 + configs.append({ 'slot': slot, 'name': row['config_name'], 'team_data': team_data, 'updated_at': row['updated_at'], - 'pet_count': len([p for p in team_data.values() if p]) + 'pet_count': pet_count }) else: configs.append({ diff --git a/src/team_management.py b/src/team_management.py index af68f0e..aecab65 100644 --- a/src/team_management.py +++ b/src/team_management.py @@ -40,10 +40,14 @@ class TeamManagementService: if config: # team_data is already parsed by get_player_team_configurations team_data = config["team_data"] if config["team_data"] else {} + + # Use pet_count from database method which handles both formats + pet_count = config.get("pet_count", 0) + teams[f"slot_{i}"] = { "name": config.get("name", f"Team {i}"), "pets": team_data, - "count": len(team_data), + "count": pet_count, "last_updated": config.get("updated_at"), "is_active": False } diff --git a/webserver.py b/webserver.py index cade31a..a93d42f 100644 --- a/webserver.py +++ b/webserver.py @@ -746,9 +746,32 @@ class PetBotRequestHandler(BaseHTTPRequestHandler): self.serve_locations() elif path == '/petdex': self.serve_petdex() + elif path.startswith('/teambuilder/') and '/config/load/' in path: + # Handle team configuration load: /teambuilder/{nickname}/config/load/{slot} + parts = path.split('/') + if len(parts) >= 6: + nickname = parts[2] + slot = parts[5] + self.handle_team_config_load(nickname, slot) + else: + self.send_error(400, "Invalid configuration load path") + elif path.startswith('/teambuilder/') and '/team/' in path: + # Handle individual team editor: /teambuilder/{nickname}/team/{slot} + parts = path.split('/') + if len(parts) >= 5: + nickname = parts[2] + team_identifier = parts[4] # Could be 1, 2, 3, or 'active' + self.serve_individual_team_editor(nickname, team_identifier) + else: + self.send_error(400, "Invalid team editor path") elif path.startswith('/teambuilder/'): - nickname = path[13:] # Remove '/teambuilder/' prefix - self.serve_teambuilder(nickname) + # Check if it's just the base teambuilder path (hub) + path_parts = path[13:].split('/') # Remove '/teambuilder/' prefix + if len(path_parts) == 1 and path_parts[0]: # Just nickname + nickname = path_parts[0] + self.serve_team_selection_hub(nickname) + else: + self.send_error(404, "Invalid teambuilder path") elif path.startswith('/testteambuilder/'): nickname = path[17:] # Remove '/testteambuilder/' prefix self.serve_test_teambuilder(nickname) @@ -779,7 +802,25 @@ class PetBotRequestHandler(BaseHTTPRequestHandler): parsed_path = urlparse(self.path) path = parsed_path.path - if path.startswith('/teambuilder/') and path.endswith('/save'): + if path.startswith('/teambuilder/') and '/team/' in path and path.endswith('/save'): + # Handle individual team save: /teambuilder/{nickname}/team/{slot}/save + parts = path.split('/') + if len(parts) >= 6: + nickname = parts[2] + team_slot = parts[4] + self.handle_individual_team_save(nickname, team_slot) + else: + self.send_error(400, "Invalid individual team save path") + elif path.startswith('/teambuilder/') and '/team/' in path and path.endswith('/verify'): + # Handle individual team verify: /teambuilder/{nickname}/team/{slot}/verify + parts = path.split('/') + if len(parts) >= 6: + nickname = parts[2] + team_slot = parts[4] + self.handle_individual_team_verify(nickname, team_slot) + else: + self.send_error(400, "Invalid individual team verify path") + elif path.startswith('/teambuilder/') and path.endswith('/save'): nickname = path[13:-5] # Remove '/teambuilder/' prefix and '/save' suffix self.handle_team_save(nickname) elif path.startswith('/teambuilder/') and path.endswith('/verify'): @@ -835,6 +876,33 @@ class PetBotRequestHandler(BaseHTTPRequestHandler): self.handle_team_config_apply(nickname, slot) else: self.send_error(400, "Invalid configuration apply path") + elif path.startswith('/teambuilder/') and '/swap/' in path: + # Handle team swapping: /teambuilder/{nickname}/swap/{slot} + parts = path.split('/') + if len(parts) >= 5: + nickname = parts[2] + slot = parts[4] + self.handle_team_swap_request(nickname, slot) + else: + self.send_error(400, "Invalid team swap path") + elif path.startswith('/teambuilder/') and '/team/' in path and path.endswith('/save'): + # Handle individual team save: /teambuilder/{nickname}/team/{slot}/save + parts = path.split('/') + if len(parts) >= 6: + nickname = parts[2] + team_slot = parts[4] + self.handle_individual_team_save(nickname, team_slot) + else: + self.send_error(400, "Invalid individual team save path") + elif path.startswith('/teambuilder/') and '/team/' in path and path.endswith('/verify'): + # Handle individual team verify: /teambuilder/{nickname}/team/{slot}/verify + parts = path.split('/') + if len(parts) >= 6: + nickname = parts[2] + team_slot = parts[4] + self.handle_individual_team_verify(nickname, team_slot) + else: + self.send_error(400, "Invalid individual team verify path") elif path == '/admin/auth': self.handle_admin_auth() elif path == '/admin/verify': @@ -5941,6 +6009,10 @@ class PetBotRequestHandler(BaseHTTPRequestHandler): } }); + // Reset team state BEFORE loading new data + currentTeam = {}; + originalTeam = {}; + // Load team data from server for the selected slot if (teamSlot === 1) { // For Team 1, load current active pets (default behavior) @@ -5950,10 +6022,6 @@ class PetBotRequestHandler(BaseHTTPRequestHandler): loadSavedTeamConfiguration(teamSlot); } - // Reset team state - currentTeam = {}; - originalTeam = {}; - // Re-initialize team state updateTeamState(); } @@ -7256,12 +7324,12 @@ class PetBotRequestHandler(BaseHTTPRequestHandler): if (!currentEditingTeam) return; try {{ - const response = await fetch(`/testteambuilder/{nickname}/save`, {{ + const response = await fetch(`/teambuilder/{nickname}/save`, {{ method: 'POST', headers: {{ 'Content-Type': 'application/json' }}, body: JSON.stringify({{ - team_slot: currentEditingTeam, - team_data: currentTeamData + teamSlot: currentEditingTeam, + teamData: currentTeamData }}) }}); @@ -7285,7 +7353,7 @@ class PetBotRequestHandler(BaseHTTPRequestHandler): }} try {{ - const response = await fetch(`/testteambuilder/{nickname}/verify`, {{ + const response = await fetch(`/teambuilder/{nickname}/verify`, {{ method: 'POST', headers: {{ 'Content-Type': 'application/json' }}, body: JSON.stringify({{ pin: pin }}) @@ -7417,6 +7485,10 @@ class PetBotRequestHandler(BaseHTTPRequestHandler): team_data = save_data team_slot = 1 + # Validate team slot + if not isinstance(team_slot, int) or team_slot < 1 or team_slot > 3: + return {"success": False, "error": "Invalid team slot. Must be 1, 2, or 3"} + # Get player player = await self.database.get_player(nickname) if not player: @@ -7787,8 +7859,9 @@ class PetBotRequestHandler(BaseHTTPRequestHandler): team_slot = data.get('team_slot') team_data = data.get('team_data', {}) - if not team_slot or team_slot not in [1, 2, 3]: - self.send_json_response({"success": False, "error": "Invalid team slot"}, 400) + # Validate team slot + if not isinstance(team_slot, int) or team_slot < 1 or team_slot > 3: + self.send_json_response({"success": False, "error": "Invalid team slot. Must be 1, 2, or 3"}, 400) return except json.JSONDecodeError: @@ -7901,6 +7974,167 @@ class PetBotRequestHandler(BaseHTTPRequestHandler): print(f"Error in _handle_test_team_verify_async: {e}") return {"success": False, "error": str(e)} + def handle_individual_team_save(self, nickname, team_slot): + """Handle individual team save request and generate PIN""" + try: + # Get POST data + content_length = int(self.headers.get('Content-Length', 0)) + if content_length == 0: + self.send_json_response({"success": False, "error": "No data provided"}, 400) + return + + post_data = self.rfile.read(content_length).decode('utf-8') + + try: + import json + data = json.loads(post_data) + team_identifier = data.get('team_identifier', team_slot) + is_active_team = data.get('is_active_team', False) + pets = data.get('pets', []) + + # Validate team slot + if team_slot not in ['1', '2', '3', 'active']: + self.send_json_response({"success": False, "error": "Invalid team slot"}, 400) + return + + # Convert team_slot to numeric for database operations + if team_slot == 'active': + team_slot_num = 1 + is_active_team = True + else: + team_slot_num = int(team_slot) + + except json.JSONDecodeError: + self.send_json_response({"success": False, "error": "Invalid JSON data"}, 400) + return + + # Run async operations + import asyncio + result = asyncio.run(self._handle_individual_team_save_async(nickname, team_slot_num, pets, is_active_team)) + + if result["success"]: + self.send_json_response({"requires_pin": True, "message": "PIN sent to IRC"}, 200) + else: + self.send_json_response(result, 400) + + except Exception as e: + print(f"Error in handle_individual_team_save: {e}") + self.send_json_response({"success": False, "error": "Internal server error"}, 500) + + async def _handle_individual_team_save_async(self, nickname, team_slot, pets, is_active_team): + """Async handler for individual team save""" + try: + # Get player + player = await self.server.database.get_player(nickname) + + if not player: + return {"success": False, "error": "Player not found"} + + # Validate pets exist and belong to player + if pets: + player_pets = await self.server.database.get_player_pets(player['id']) + player_pet_ids = [pet['id'] for pet in player_pets] + + for pet_data in pets: + pet_id = pet_data.get('pet_id') + if not pet_id: + continue + + # Convert pet_id to int for comparison with database IDs + try: + pet_id_int = int(pet_id) + except (ValueError, TypeError): + return {"success": False, "error": f"Invalid pet ID: {pet_id}"} + + # Check if pet belongs to player + if pet_id_int not in player_pet_ids: + return {"success": False, "error": f"Pet {pet_id} not found or doesn't belong to you"} + + # Convert pets array to the expected format for database + # Expected format: {"pet_id": position, "pet_id": position, ...} + team_changes = {} + if pets: # Ensure pets is not None or empty + for pet_data in pets: + if isinstance(pet_data, dict): # Ensure pet_data is a dictionary + pet_id = str(pet_data.get('pet_id')) # Ensure pet_id is string + position = pet_data.get('position', False) # Position or False for inactive + if pet_id and pet_id != 'None': # Only add valid pet IDs + team_changes[pet_id] = position + + + # Generate PIN and store pending change + import json + team_data = { + 'teamSlot': int(team_slot), # Convert to int and use expected key name + 'teamData': team_changes, # Use the dictionary format expected by database + 'is_active_team': is_active_team + } + + # Generate PIN + pin_result = await self.server.database.generate_verification_pin(player["id"], "team_change", json.dumps(team_data)) + pin_code = pin_result.get("pin_code") + + # Send PIN via IRC + self.send_pin_via_irc(nickname, pin_code) + + return {"success": True, "requires_pin": True} + + except Exception as e: + print(f"Error in _handle_individual_team_save_async: {e}") + return {"success": False, "error": str(e)} + + def handle_individual_team_verify(self, nickname, team_slot): + """Handle individual team PIN verification""" + try: + # Get PIN from request body + content_length = int(self.headers['Content-Length']) + post_data = self.rfile.read(content_length) + + import json + try: + data = json.loads(post_data.decode('utf-8')) + pin_code = data.get('pin', '').strip() + except json.JSONDecodeError: + self.send_json_response({"success": False, "error": "Invalid request data"}, 400) + return + + if not pin_code or len(pin_code) != 6: + self.send_json_response({"success": False, "error": "PIN must be 6 digits"}, 400) + return + + # Run async verification + import asyncio + result = asyncio.run(self._handle_individual_team_verify_async(nickname, pin_code)) + + if result["success"]: + self.send_json_response(result, 200) + else: + self.send_json_response(result, 400) + + except Exception as e: + print(f"Error in handle_individual_team_verify: {e}") + self.send_json_response({"success": False, "error": "Internal server error"}, 500) + + async def _handle_individual_team_verify_async(self, nickname, pin_code): + """Async handler for individual team PIN verification""" + try: + # Get player + player = await self.server.database.get_player(nickname) + if not player: + return {"success": False, "error": "Player not found"} + + # Verify PIN and apply changes using simplified method + result = await self.server.database.apply_individual_team_change(player["id"], pin_code) + + if result["success"]: + return {"success": True, "message": "Team saved successfully!"} + else: + return {"success": False, "error": result.get("error", "Invalid PIN")} + + except Exception as e: + print(f"Error in _handle_individual_team_verify_async: {e}") + return {"success": False, "error": str(e)} + def handle_pet_rename_request(self, nickname): """Handle pet rename request and generate PIN""" try: @@ -9927,6 +10161,1244 @@ class PetBotRequestHandler(BaseHTTPRequestHandler): print(f"Error resetting rate limits: {e}") self.send_json_response({"success": False, "error": str(e)}, 500) + # ================================================================ + # NEW TEAM BUILDER METHODS - Separated Team Management + # ================================================================ + + def serve_team_selection_hub(self, nickname): + """Serve the team selection hub showing all teams with swap options.""" + try: + # Get database and bot from server + database = self.server.database if hasattr(self.server, 'database') else None + bot = self.server.bot if hasattr(self.server, 'bot') else None + + if not database: + self.send_error(500, "Database not available") + return + + # Get team management service + if not hasattr(self, 'team_service'): + from src.team_management import TeamManagementService + from src.pin_authentication import PinAuthenticationService + pin_service = PinAuthenticationService(database, bot) + self.team_service = TeamManagementService(database, pin_service) + + # Get team overview + import asyncio + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + try: + player = loop.run_until_complete(database.get_player(nickname)) + if not player: + self.send_error(404, "Player not found") + return + + team_overview = loop.run_until_complete(self.team_service.get_team_overview(player["id"])) + if not team_overview["success"]: + self.send_error(500, f"Failed to load teams: {team_overview['error']}") + return + + teams = team_overview["teams"] + finally: + loop.close() + + # Generate team hub HTML + content = self.generate_team_hub_content(nickname, teams) + full_page = self.get_page_template(f"Team Management - {nickname}", content, "teambuilder") + + self.send_response(200) + self.send_header('Content-type', 'text/html; charset=utf-8') + self.end_headers() + self.wfile.write(full_page.encode('utf-8')) + + except Exception as e: + print(f"Error serving team selection hub: {e}") + self.send_error(500, "Internal server error") + + def serve_individual_team_editor(self, nickname, team_identifier): + """Serve individual team editor page.""" + try: + # Get database and bot from server + database = self.server.database if hasattr(self.server, 'database') else None + bot = self.server.bot if hasattr(self.server, 'bot') else None + + if not database: + self.send_error(500, "Database not available") + return + + # Get team management service + if not hasattr(self, 'team_service'): + from src.team_management import TeamManagementService + from src.pin_authentication import PinAuthenticationService + pin_service = PinAuthenticationService(database, bot) + self.team_service = TeamManagementService(database, pin_service) + + # Get team data + import asyncio + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + try: + player = loop.run_until_complete(database.get_player(nickname)) + if not player: + self.send_error(404, "Player not found") + return + + team_data = loop.run_until_complete( + self.team_service.get_individual_team_data(player["id"], team_identifier) + ) + if not team_data["success"]: + self.send_error(500, f"Failed to load team: {team_data['error']}") + return + + # Get player's pets for the editor + player_pets = loop.run_until_complete(database.get_player_pets(player["id"])) + finally: + loop.close() + + # Generate individual team editor HTML + content = self.generate_individual_team_editor_content(nickname, team_identifier, team_data, player_pets) + full_page = self.get_page_template(f"{team_data['team_name']} - {nickname}", content, "teambuilder") + + self.send_response(200) + self.send_header('Content-type', 'text/html; charset=utf-8') + self.end_headers() + self.wfile.write(full_page.encode('utf-8')) + + except Exception as e: + print(f"Error serving individual team editor: {e}") + self.send_error(500, "Internal server error") + + def generate_team_hub_content(self, nickname, teams): + """Generate HTML content for team selection hub.""" + return f''' +
+
+

šŸ† Team Management Hub

+

Manage your teams and swap configurations with PIN verification

+
+ +
+

⚔ Current Battle Team

+
+ {self._generate_active_team_display(teams.get('active', {}), nickname)} +
+
+ +
+

šŸ’¾ Saved Team Configurations

+
+ {self._generate_team_preview(teams.get('slot_1', {}), '1')} + {self._generate_team_preview(teams.get('slot_2', {}), '2')} + {self._generate_team_preview(teams.get('slot_3', {}), '3')} +
+
+ + +
+ + + ''' + + def _generate_team_preview(self, team_data, team_identifier): + """Generate preview for a single team.""" + team_name = team_data.get('name', f'Team {team_identifier}') + pet_count = team_data.get('count', 0) + is_active = team_data.get('is_active', False) + + if is_active: + actions = f''' + + āœļø Edit Active Team + + ''' + else: + actions = f''' + + āœļø Edit Team {team_identifier} + + + ''' + + status = "šŸ† ACTIVE TEAM" if is_active else f"šŸ’¾ Saved Team" + + return f''' +
+

{team_name}

+
{status}
+
🐾 {pet_count} pets
+
+ {actions} +
+
+ ''' + + def _generate_active_team_display(self, active_team_data, nickname): + """Generate detailed display for active team with individual pet cards.""" + pets_dict = active_team_data.get('pets', {}) + pet_count = active_team_data.get('count', 0) + + # Convert dictionary format to list for consistent processing + pets = [] + if isinstance(pets_dict, dict): + # Active team format: {"1": {pet_data}, "2": {pet_data}} + for position, pet_data in pets_dict.items(): + if pet_data: + pet_data['team_order'] = int(position) + pets.append(pet_data) + elif isinstance(pets_dict, list): + # Saved team format: [{pet_data}, {pet_data}] + pets = pets_dict + + if not pets or pet_count == 0: + return f''' +
+

šŸ† Active Team

+
No pets in active team
+ +
+ ''' + + # Generate individual pet cards for active team + pet_cards = [] + for pet in pets: + # Handle both active team format and saved team format + name = pet.get('nickname') or pet.get('name') or pet.get('species_name', 'Unknown') + level = pet.get('level', 1) + hp = pet.get('hp', 0) + max_hp = pet.get('max_hp', 0) + attack = pet.get('attack', 0) + defense = pet.get('defense', 0) + speed = pet.get('speed', 0) + happiness = pet.get('happiness', 50) + species_name = pet.get('species_name', 'Unknown') + + # Handle type field variations between active and saved teams + type1 = (pet.get('type1') or pet.get('type_primary') or + pet.get('type1', 'Normal')) + type2 = (pet.get('type2') or pet.get('type_secondary') or + pet.get('type2')) + + team_order = pet.get('team_order', 0) + + # Calculate HP percentage for health bar + hp_percent = (hp / max_hp) * 100 if max_hp > 0 else 0 + hp_color = "#4CAF50" if hp_percent > 60 else "#FF9800" if hp_percent > 25 else "#f44336" + + # Happiness emoji + if happiness >= 80: + happiness_emoji = "😊" + elif happiness >= 60: + happiness_emoji = "šŸ™‚" + elif happiness >= 40: + happiness_emoji = "😐" + elif happiness >= 20: + happiness_emoji = "šŸ˜•" + else: + happiness_emoji = "😢" + + # Type display + type_display = type1 + if type2: + type_display += f"/{type2}" + + pet_card = f''' +
+
+

#{team_order} {name}

+
Lv.{level}
+
+
{species_name}
+
{type_display}
+ +
+
+ HP + {hp}/{max_hp} +
+
+
+
+
+ +
+
+ ATK + {attack} +
+
+ DEF + {defense} +
+
+ SPD + {speed} +
+
+ +
+ {happiness_emoji} + Happiness: {happiness}/100 +
+
+ ''' + pet_cards.append(pet_card) + + pets_html = "".join(pet_cards) + + return f''' +
+
+

šŸ† Active Battle Team ({pet_count} pets)

+ + āœļø Edit Active Team + +
+
+ {pets_html} +
+
+ + + ''' + + def generate_individual_team_editor_content(self, nickname, team_identifier, team_data, player_pets): + """Generate HTML content for individual team editor.""" + team_name = team_data.get('team_name', 'Unknown Team') + is_active_team = team_data.get('is_active_team', False) + team_pets = team_data.get('pets', []) + + # Separate pets into team and storage + team_pet_ids = [p['id'] for p in team_pets] + storage_pets = [p for p in player_pets if p['id'] not in team_pet_ids] + + # Helper function to create detailed pet card + def make_pet_card(pet, in_team=False): + name = pet.get('nickname') or pet.get('species_name', 'Unknown') + pet_id = pet.get('id', 0) + level = pet.get('level', 1) + species = pet.get('species_name', 'Unknown') + + # Type info + type_str = pet.get('type1', 'Normal') + if pet.get('type2'): + type_str += f"/{pet['type2']}" + + # HP calculation + hp = pet.get('hp', 0) + max_hp = pet.get('max_hp', 1) + hp_percent = (hp / max_hp * 100) if max_hp > 0 else 0 + hp_color = "#4CAF50" if hp_percent > 60 else "#FF9800" if hp_percent > 25 else "#f44336" + + # Get pet moves + moves = pet.get('moves', []) + moves_html = '' + if moves: + moves_html = '
Moves: ' + ', '.join([m.get('name', 'Unknown') for m in moves[:4]]) + '
' + + return f""" +
+
+

{pet.get('emoji', '🐾')} {name}

+
Lv.{level}
+
+
{species}
+
{type_str}
+ +
+
+ HP + {hp}/{max_hp} +
+
+
+
+
+ +
+
+ ATK + {pet.get('attack', 0)} +
+
+ DEF + {pet.get('defense', 0)} +
+
+ SPD + {pet.get('speed', 0)} +
+
+ + {moves_html} + +
+ {'😊' if pet.get('happiness', 50) > 70 else '😐' if pet.get('happiness', 50) > 40 else 'šŸ˜ž'} + Happiness: {pet.get('happiness', 50)}/100 +
+
+ """ + + # Create team slots (6 slots) + team_slots_html = '' + for i in range(1, 7): + # Find pet in this slot + slot_pet = None + for pet in team_pets: + if pet.get('team_order') == i or (i == 1 and not any(p.get('team_order') == 1 for p in team_pets) and team_pets and pet == team_pets[0]): + slot_pet = pet + break + + if slot_pet: + team_slots_html += f""" +
+
#{i}
+ {make_pet_card(slot_pet, True)} +
+ """ + else: + team_slots_html += f""" +
+
#{i}
+
+ āž• + Drop pet here +
+
+ """ + + # Create storage pet cards + storage_cards_html = ''.join([make_pet_card(pet, False) for pet in storage_pets]) + + if not storage_cards_html: + storage_cards_html = '
No pets in storage. All your pets are in teams!
' + + return f''' +
+
+

āœļø {team_name}

+

{"⚔ Active battle team" if is_active_team else f"šŸ’¾ Saved team configuration (Slot {team_identifier})"}

+ ← Back to Hub +
+ +
+
+

šŸ† Team Composition

+
+ {team_slots_html} +
+
+ + +
+ +
+ +
+

🐾 Pet Storage ({len(storage_pets)} available)

+
+ {storage_cards_html} +
+
+
+
+ + + + + ''' + class PetBotWebServer: """Standalone web server for PetBot""" @@ -9976,7 +11448,7 @@ def run_standalone(): print('Usage: python webserver.py [port]') sys.exit(1) - server = PetBotWebServer(port) + server = PetBotWebServer(port=port) print('🌐 PetBot Web Server') print('=' * 50) @@ -10003,6 +11475,106 @@ def run_standalone(): except KeyboardInterrupt: print('\nāœ… Web server stopped') + + +class PetBotWebServer: + """Standalone web server for PetBot""" + + def __init__(self, database=None, port=8080, bot=None): + self.database = database or Database() + self.port = port + self.bot = bot + self.server = None + + def run(self): + """Start the web server""" + self.server = HTTPServer(('0.0.0.0', self.port), PetBotRequestHandler) + self.server.database = self.database + self.server.bot = self.bot + + print(f'🌐 Starting PetBot web server on http://0.0.0.0:{self.port}') + print(f'šŸ“” Accessible from WSL at: http://172.27.217.61:{self.port}') + print(f'šŸ“” Accessible from Windows at: http://localhost:{self.port}') + print('') + + try: + self.server.serve_forever() + except KeyboardInterrupt: + print('\nšŸ›‘ Server stopped') + finally: + self.server.server_close() + + def start_in_thread(self): + """Start the web server in a separate thread""" + import threading + + def run_server(): + self.server = HTTPServer(('0.0.0.0', self.port), PetBotRequestHandler) + self.server.database = self.database + self.server.bot = self.bot + + try: + self.server.serve_forever() + except Exception as e: + print(f"Web server error: {e}") + finally: + self.server.server_close() + + self.server_thread = threading.Thread(target=run_server, daemon=True) + self.server_thread.start() + + def stop(self): + """Stop the web server""" + if self.server: + print('šŸ›‘ Stopping web server...') + self.server.shutdown() + self.server.server_close() + + +def run_standalone(): + """Run the web server in standalone mode""" + import sys + + port = 8080 + if len(sys.argv) > 1: + try: + port = int(sys.argv[1]) + except ValueError: + print('Usage: python webserver.py [port]') + sys.exit(1) + + server = PetBotWebServer(port=port) + + print('🌐 PetBot Web Server') + print('=' * 50) + print(f'Port: {port}') + print('') + print('šŸ”— Local URLs:') + print(f' http://localhost:{port}/ - Game Hub (local)') + print(f' http://localhost:{port}/help - Command Help (local)') + print(f' http://localhost:{port}/players - Player List (local)') + print(f' http://localhost:{port}/leaderboard - Leaderboard (local)') + print(f' http://localhost:{port}/locations - Locations (local)') + print('') + print('🌐 Public URLs:') + print(' http://petz.rdx4.com/ - Game Hub') + print(' http://petz.rdx4.com/help - Command Help') + print(' http://petz.rdx4.com/players - Player List') + print(' http://petz.rdx4.com/leaderboard - Leaderboard') + print(' http://petz.rdx4.com/locations - Locations') + print('') + print('šŸ“± Example Player Profile:') + print(' http://petz.rdx4.com/player/megasconed') + print('') + print('āš™ļø Press Ctrl+C to stop the server') + print('') + + try: + server.run() + except KeyboardInterrupt: + print('\nšŸ›‘ Shutting down web server...') + server.stop() + + if __name__ == '__main__': run_standalone() -