#!/usr/bin/env python3 """ PetBot Web Server Provides web interface for bot data including help, player stats, and pet collections """ import os import sys import asyncio from http.server import HTTPServer, BaseHTTPRequestHandler from urllib.parse import urlparse, parse_qs from threading import Thread import time # Add the project directory to the path sys.path.append(os.path.dirname(os.path.abspath(__file__))) from src.database import Database class PetBotRequestHandler(BaseHTTPRequestHandler): """HTTP request handler for PetBot web server""" def do_GET(self): """Handle GET requests""" parsed_path = urlparse(self.path) path = parsed_path.path # Route handling if path == '/': self.serve_index() elif path == '/help': self.serve_help() elif path == '/players': self.serve_players() elif path.startswith('/player/'): nickname = path[8:] # Remove '/player/' prefix self.serve_player_profile(nickname) elif path == '/leaderboard': self.serve_leaderboard() elif path == '/locations': self.serve_locations() elif path == '/petdex': self.serve_petdex() elif path.startswith('/teambuilder/'): nickname = path[13:] # Remove '/teambuilder/' prefix self.serve_teambuilder(nickname) else: self.send_error(404, "Page not found") def do_POST(self): """Handle POST requests""" parsed_path = urlparse(self.path) path = parsed_path.path if 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'): nickname = path[13:-7] # Remove '/teambuilder/' prefix and '/verify' suffix self.handle_team_verify(nickname) else: self.send_error(404, "Page not found") def serve_index(self): """Serve the main index page""" html = """ PetBot Game Hub

🐾 PetBot Game Hub

Welcome to the PetBot web interface!

Connect to irc.libera.chat #petz to play

πŸ€– Bot Status: Online and ready for commands!

Use !help in #petz for quick command reference

""" self.send_response(200) self.send_header('Content-type', 'text/html') self.end_headers() self.wfile.write(html.encode()) def serve_help(self): """Serve the help page""" try: with open('help.html', 'r') as f: content = f.read() self.send_response(200) self.send_header('Content-type', 'text/html') self.end_headers() self.wfile.write(content.encode()) except FileNotFoundError: self.send_error(404, "Help file not found") def serve_players(self): """Serve the players page with real data""" # Get database instance from the server class database = self.server.database if hasattr(self.server, 'database') else None if not database: self.serve_error_page("Players", "Database not available") return # Fetch players data try: import asyncio loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) players_data = loop.run_until_complete(self.fetch_players_data(database)) loop.close() self.serve_players_data(players_data) except Exception as e: print(f"Error fetching players data: {e}") self.serve_error_page("Players", f"Error loading players: {str(e)}") async def fetch_players_data(self, database): """Fetch all players data from database""" try: import aiosqlite async with aiosqlite.connect(database.db_path) as db: # Get all players with basic stats cursor = await db.execute(""" SELECT p.nickname, p.level, p.experience, p.money, p.created_at, l.name as location_name, (SELECT COUNT(*) FROM pets WHERE player_id = p.id) as pet_count, (SELECT COUNT(*) FROM pets WHERE player_id = p.id AND is_active = 1) as active_pets, (SELECT COUNT(*) FROM player_achievements WHERE player_id = p.id) as achievement_count FROM players p LEFT JOIN locations l ON p.current_location_id = l.id ORDER BY p.level DESC, p.experience DESC """) rows = await cursor.fetchall() # Convert SQLite rows to dictionaries properly players = [] for row in rows: player_dict = { 'nickname': row[0], 'level': row[1], 'experience': row[2], 'money': row[3], 'created_at': row[4], 'location_name': row[5], 'pet_count': row[6], 'active_pets': row[7], 'achievement_count': row[8] } players.append(player_dict) return players except Exception as e: print(f"Database error fetching players: {e}") return [] def serve_players_data(self, players_data): """Serve players page with real data""" # Build players table HTML if players_data: players_html = "" for i, player in enumerate(players_data, 1): rank_emoji = {"1": "πŸ₯‡", "2": "πŸ₯ˆ", "3": "πŸ₯‰"}.get(str(i), f"{i}.") players_html += f""" {rank_emoji} {player['nickname']} {player['level']} {player['experience']} ${player['money']} {player['pet_count']} {player['active_pets']} {player['achievement_count']} {player.get('location_name', 'Unknown')} """ else: players_html = """ No players found. Be the first to use !start in #petz! """ html = f""" PetBot - Players ← Back to Game Hub

πŸ‘₯ Registered Players

All trainers on their pet collection journey

πŸ“Š Server Statistics
{len(players_data)}
Total Players
{sum(p['pet_count'] for p in players_data)}
Total Pets Caught
{sum(p['achievement_count'] for p in players_data)}
Total Achievements
{max((p['level'] for p in players_data), default=0)}
Highest Level
πŸ† Player Rankings
{players_html}
Rank Player Level Experience Money Pets Active Achievements Location

πŸ’‘ Click on any player name to view their detailed profile

""" self.send_response(200) self.send_header('Content-type', 'text/html') self.end_headers() self.wfile.write(html.encode()) def serve_error_page(self, page_name, error_msg): """Serve a generic error page""" html = f""" PetBot - Error ← Back to Game Hub

⚠️ Error Loading {page_name}

Unable to load page

{error_msg}

Please try again later or contact an administrator.

""" self.send_response(500) self.send_header('Content-type', 'text/html') self.end_headers() self.wfile.write(html.encode()) def serve_leaderboard(self): """Serve the leaderboard page - redirect to players for now""" # For now, leaderboard is the same as players page since they're ranked # In the future, this could have different categories self.send_response(302) # Temporary redirect self.send_header('Location', '/players') self.end_headers() def serve_locations(self): """Serve the locations page with real data""" # Get database instance from the server class database = self.server.database if hasattr(self.server, 'database') else None if not database: self.serve_error_page("Locations", "Database not available") return # Fetch locations data try: import asyncio loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) locations_data = loop.run_until_complete(self.fetch_locations_data(database)) loop.close() self.serve_locations_data(locations_data) except Exception as e: print(f"Error fetching locations data: {e}") self.serve_error_page("Locations", f"Error loading locations: {str(e)}") async def fetch_locations_data(self, database): """Fetch all locations and their spawn data from database""" try: import aiosqlite async with aiosqlite.connect(database.db_path) as db: # Get all locations cursor = await db.execute(""" SELECT l.*, GROUP_CONCAT(ps.name || ' (' || ps.type1 || CASE WHEN ps.type2 IS NOT NULL THEN '/' || ps.type2 ELSE '' END || ')') as spawns FROM locations l LEFT JOIN location_spawns ls ON l.id = ls.location_id LEFT JOIN pet_species ps ON ls.species_id = ps.id GROUP BY l.id ORDER BY l.id """) rows = await cursor.fetchall() # Convert SQLite rows to dictionaries properly locations = [] for row in rows: location_dict = { 'id': row[0], 'name': row[1], 'description': row[2], 'level_min': row[3], 'level_max': row[4], 'spawns': row[5] if len(row) > 5 else None } locations.append(location_dict) return locations except Exception as e: print(f"Database error fetching locations: {e}") return [] def serve_locations_data(self, locations_data): """Serve locations page with real data""" # Build locations HTML locations_html = "" if locations_data: for location in locations_data: spawns = location.get('spawns', 'No pets found') if not spawns or spawns == 'None': spawns = "No pets spawn here yet" # Split spawns into a readable list spawn_list = spawns.split(',') if spawns != "No pets spawn here yet" else [] spawn_badges = "" for spawn in spawn_list[:6]: # Limit to first 6 for display spawn_badges += f'{spawn.strip()}' if len(spawn_list) > 6: spawn_badges += f'+{len(spawn_list) - 6} more' if not spawn_badges: spawn_badges = 'No pets spawn here yet' locations_html += f"""

πŸ—ΊοΈ {location['name']}

ID: {location['id']}
{location['description']}
Level Range: {location['level_min']}-{location['level_max']}
Wild Pets:
{spawn_badges}
""" else: locations_html = """

No Locations Found

No game locations are configured yet.
""" html = f""" PetBot - Locations ← Back to Game Hub

πŸ—ΊοΈ Game Locations

Explore all areas and discover what pets await you!

🎯 How Locations Work

Travel: Use !travel <location> to move between areas

Explore: Use !explore to find wild pets in your current location

Unlock: Some locations require achievements - catch specific pet types to unlock new areas!

Weather: Check !weather for conditions that boost certain pet spawn rates

{locations_html}

πŸ’‘ Use !wild <location> in #petz to see what pets spawn in a specific area

""" self.send_response(200) self.send_header('Content-type', 'text/html') self.end_headers() self.wfile.write(html.encode()) def serve_petdex(self): """Serve the petdex page with all pet species data""" # Get database instance from the server class database = self.server.database if hasattr(self.server, 'database') else None if not database: self.serve_error_page("Petdex", "Database not available") return # Fetch petdex data try: import asyncio loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) petdex_data = loop.run_until_complete(self.fetch_petdex_data(database)) loop.close() self.serve_petdex_data(petdex_data) except Exception as e: print(f"Error fetching petdex data: {e}") self.serve_error_page("Petdex", f"Error loading petdex: {str(e)}") async def fetch_petdex_data(self, database): """Fetch all pet species data from database""" try: import aiosqlite async with aiosqlite.connect(database.db_path) as db: # Get all pet species with evolution information cursor = await db.execute(""" SELECT ps.*, evolve_to.name as evolves_to_name, (SELECT COUNT(*) FROM location_spawns ls WHERE ls.species_id = ps.id) as location_count FROM pet_species ps LEFT JOIN pet_species evolve_to ON ps.evolution_species_id = evolve_to.id ORDER BY ps.rarity ASC, ps.name ASC """) rows = await cursor.fetchall() pets = [] for row in rows: pet_dict = { 'id': row[0], 'name': row[1], 'type1': row[2], 'type2': row[3], 'base_hp': row[4], 'base_attack': row[5], 'base_defense': row[6], 'base_speed': row[7], 'evolution_level': row[8], 'evolution_species_id': row[9], 'rarity': row[10], 'evolves_to_name': row[11], 'location_count': row[12] } pets.append(pet_dict) # Get spawn locations for each pet for pet in pets: cursor = await db.execute(""" SELECT l.name, ls.min_level, ls.max_level, ls.spawn_rate FROM location_spawns ls JOIN locations l ON ls.location_id = l.id WHERE ls.species_id = ? ORDER BY l.name ASC """, (pet['id'],)) spawn_rows = await cursor.fetchall() pet['spawn_locations'] = [] for spawn_row in spawn_rows: spawn_dict = { 'location_name': spawn_row[0], 'min_level': spawn_row[1], 'max_level': spawn_row[2], 'spawn_rate': spawn_row[3] } pet['spawn_locations'].append(spawn_dict) return pets except Exception as e: print(f"Database error fetching petdex: {e}") return [] def serve_petdex_data(self, petdex_data): """Serve petdex page with all pet species data""" # Build pet cards HTML grouped by rarity rarity_names = {1: "Common", 2: "Uncommon", 3: "Rare", 4: "Epic", 5: "Legendary"} rarity_colors = {1: "#ffffff", 2: "#1eff00", 3: "#0070dd", 4: "#a335ee", 5: "#ff8000"} pets_by_rarity = {} for pet in petdex_data: rarity = pet['rarity'] if rarity not in pets_by_rarity: pets_by_rarity[rarity] = [] pets_by_rarity[rarity].append(pet) petdex_html = "" total_species = len(petdex_data) for rarity in sorted(pets_by_rarity.keys()): pets_in_rarity = pets_by_rarity[rarity] rarity_name = rarity_names.get(rarity, f"Rarity {rarity}") rarity_color = rarity_colors.get(rarity, "#ffffff") petdex_html += f"""

{rarity_name} ({len(pets_in_rarity)} species)

""" for pet in pets_in_rarity: # Build type display type_str = pet['type1'] if pet['type2']: type_str += f"/{pet['type2']}" # Build evolution info evolution_info = "" if pet['evolution_level'] and pet['evolves_to_name']: evolution_info = f"
Evolves: Level {pet['evolution_level']} β†’ {pet['evolves_to_name']}" elif pet['evolution_level']: evolution_info = f"
Evolves: Level {pet['evolution_level']}" # Build spawn locations spawn_info = "" if pet['spawn_locations']: locations = [f"{loc['location_name']} (Lv.{loc['min_level']}-{loc['max_level']})" for loc in pet['spawn_locations'][:3]] if len(pet['spawn_locations']) > 3: locations.append(f"+{len(pet['spawn_locations']) - 3} more") spawn_info = f"
Found in: {', '.join(locations)}" else: spawn_info = "
Found in: Not yet available" # Calculate total base stats total_stats = pet['base_hp'] + pet['base_attack'] + pet['base_defense'] + pet['base_speed'] petdex_html += f"""

{pet['name']}

{type_str}
HP: {pet['base_hp']} ATK: {pet['base_attack']}
DEF: {pet['base_defense']} SPD: {pet['base_speed']}
Total: {total_stats}
Rarity: {rarity_name}{evolution_info}{spawn_info}
""" petdex_html += """
""" if not petdex_data: petdex_html = """

No pet species found!

The petdex appears to be empty. Contact an administrator.

""" html = f""" PetBot - Petdex ← Back to Game Hub

πŸ“– Petdex - Complete Pet Encyclopedia

Comprehensive guide to all available pet species

{total_species}
Total Species
{len([p for p in petdex_data if p['type1'] == 'Fire' or p['type2'] == 'Fire'])}
Fire Types
{len([p for p in petdex_data if p['type1'] == 'Water' or p['type2'] == 'Water'])}
Water Types
{len([p for p in petdex_data if p['evolution_level']])}
Can Evolve

🎯 Pets are organized by rarity. Use !wild <location> in #petz to see what spawns where!

{petdex_html} """ self.send_response(200) self.send_header('Content-type', 'text/html') self.end_headers() self.wfile.write(html.encode()) def serve_player_profile(self, nickname): """Serve individual player profile page""" # URL decode the nickname in case it has special characters from urllib.parse import unquote nickname = unquote(nickname) # Get database instance from the server class database = self.server.database if hasattr(self.server, 'database') else None if not database: self.serve_player_error(nickname, "Database not available") return # Fetch player data try: import asyncio loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) player_data = loop.run_until_complete(self.fetch_player_data(database, nickname)) loop.close() if player_data is None: self.serve_player_not_found(nickname) return self.serve_player_data(nickname, player_data) except Exception as e: print(f"Error fetching player data for {nickname}: {e}") self.serve_player_error(nickname, f"Error loading player data: {str(e)}") return async def fetch_player_data(self, database, nickname): """Fetch all player data from database""" try: # Get player info import aiosqlite async with aiosqlite.connect(database.db_path) as db: # Get player basic info cursor = await db.execute(""" SELECT p.*, l.name as location_name, l.description as location_desc FROM players p LEFT JOIN locations l ON p.current_location_id = l.id WHERE p.nickname = ? """, (nickname,)) player = await cursor.fetchone() if not player: return None # Convert to dict manually player_dict = { 'id': player[0], 'nickname': player[1], 'created_at': player[2], 'last_active': player[3], 'level': player[4], 'experience': player[5], 'money': player[6], 'current_location_id': player[7], 'location_name': player[8], 'location_desc': player[9] } # Get player pets cursor = await db.execute(""" SELECT p.*, ps.name as species_name, ps.type1, ps.type2 FROM pets p JOIN pet_species ps ON p.species_id = ps.id WHERE p.player_id = ? ORDER BY p.is_active DESC, p.level DESC, p.id ASC """, (player_dict['id'],)) pets_rows = await cursor.fetchall() pets = [] for row in pets_rows: pet_dict = { 'id': row[0], 'player_id': row[1], 'species_id': row[2], 'nickname': row[3], 'level': row[4], 'experience': row[5], 'hp': row[6], 'max_hp': row[7], 'attack': row[8], 'defense': row[9], 'speed': row[10], 'happiness': row[11], 'caught_at': row[12], 'is_active': row[13], 'species_name': row[14], 'type1': row[15], 'type2': row[16] } pets.append(pet_dict) # Get player achievements cursor = await db.execute(""" SELECT pa.*, a.name as achievement_name, a.description as achievement_desc FROM player_achievements pa JOIN achievements a ON pa.achievement_id = a.id WHERE pa.player_id = ? ORDER BY pa.completed_at DESC """, (player_dict['id'],)) achievements_rows = await cursor.fetchall() achievements = [] for row in achievements_rows: achievement_dict = { 'id': row[0], 'player_id': row[1], 'achievement_id': row[2], 'completed_at': row[3], 'achievement_name': row[4], 'achievement_desc': row[5] } achievements.append(achievement_dict) # Get player inventory cursor = await db.execute(""" SELECT i.name, i.description, i.category, i.rarity, pi.quantity FROM player_inventory pi JOIN items i ON pi.item_id = i.id WHERE pi.player_id = ? ORDER BY i.rarity DESC, i.name ASC """, (player_dict['id'],)) inventory_rows = await cursor.fetchall() inventory = [] for row in inventory_rows: item_dict = { 'name': row[0], 'description': row[1], 'category': row[2], 'rarity': row[3], 'quantity': row[4] } inventory.append(item_dict) # Get player gym badges cursor = await db.execute(""" SELECT g.name, g.badge_name, g.badge_icon, l.name as location_name, pgb.victories, pgb.first_victory_date, pgb.highest_difficulty FROM player_gym_battles pgb JOIN gyms g ON pgb.gym_id = g.id JOIN locations l ON g.location_id = l.id WHERE pgb.player_id = ? AND pgb.victories > 0 ORDER BY pgb.first_victory_date ASC """, (player_dict['id'],)) gym_badges_rows = await cursor.fetchall() gym_badges = [] for row in gym_badges_rows: badge_dict = { 'gym_name': row[0], 'badge_name': row[1], 'badge_icon': row[2], 'location_name': row[3], 'victories': row[4], 'first_victory_date': row[5], 'highest_difficulty': row[6] } gym_badges.append(badge_dict) # Get player encounters using database method encounters = [] try: # Use the existing database method which handles row factory properly temp_encounters = await database.get_player_encounters(player_dict['id']) for enc in temp_encounters: encounter_dict = { 'species_name': enc['species_name'], 'type1': enc['type1'], 'type2': enc['type2'], 'rarity': enc['rarity'], 'total_encounters': enc['total_encounters'], 'caught_count': enc['caught_count'], 'first_encounter_date': enc['first_encounter_date'] } encounters.append(encounter_dict) except Exception as e: print(f"Error fetching encounters: {e}") encounters = [] # Get encounter stats try: encounter_stats = await database.get_encounter_stats(player_dict['id']) except Exception as e: print(f"Error fetching encounter stats: {e}") encounter_stats = { 'species_encountered': 0, 'total_encounters': 0, 'total_species': 0, 'completion_percentage': 0.0 } return { 'player': player_dict, 'pets': pets, 'achievements': achievements, 'inventory': inventory, 'gym_badges': gym_badges, 'encounters': encounters, 'encounter_stats': encounter_stats } except Exception as e: print(f"Database error fetching player {nickname}: {e}") return None def serve_player_not_found(self, nickname): """Serve player not found page""" html = f""" PetBot - Player Not Found ← Back to Game Hub

🚫 Player Not Found

Player "{nickname}" not found

This player hasn't started their journey yet or doesn't exist.

Players can use !start in #petz to begin their adventure!

""" self.send_response(404) self.send_header('Content-type', 'text/html') self.end_headers() self.wfile.write(html.encode()) def serve_player_error(self, nickname, error_msg): """Serve player error page""" html = f""" PetBot - Error ← Back to Game Hub

⚠️ Error

Unable to load player data

{error_msg}

Please try again later or contact an administrator.

""" self.send_response(500) self.send_header('Content-type', 'text/html') self.end_headers() self.wfile.write(html.encode()) def serve_player_data(self, nickname, player_data): """Serve player profile page with real data""" player = player_data['player'] pets = player_data['pets'] achievements = player_data['achievements'] inventory = player_data.get('inventory', []) gym_badges = player_data.get('gym_badges', []) encounters = player_data.get('encounters', []) encounter_stats = player_data.get('encounter_stats', {}) # Calculate stats active_pets = [pet for pet in pets if pet['is_active']] total_pets = len(pets) active_count = len(active_pets) # Build pets table HTML pets_html = "" if pets: for pet in pets: status = "⭐ Active" if pet['is_active'] else "πŸ“¦ Storage" status_class = "pet-active" if pet['is_active'] else "pet-stored" name = pet['nickname'] or pet['species_name'] type_str = pet['type1'] if pet['type2']: type_str += f"/{pet['type2']}" pets_html += f""" {status} {name} {pet['species_name']} {type_str} {pet['level']} {pet['hp']}/{pet['max_hp']} ATK: {pet['attack']} | DEF: {pet['defense']} | SPD: {pet['speed']} """ else: pets_html = """ No pets found. Use !explore and !catch to start your collection! """ # Build achievements HTML achievements_html = "" if achievements: for achievement in achievements: achievements_html += f"""
πŸ† {achievement['achievement_name']}
{achievement['achievement_desc']}
Earned: {achievement['completed_at']}
""" else: achievements_html = """
No achievements yet. Keep exploring and catching pets to earn achievements!
""" # Build inventory HTML inventory_html = "" if inventory: rarity_symbols = { "common": "β—‹", "uncommon": "β—‡", "rare": "β—†", "epic": "β˜…", "legendary": "✦" } rarity_colors = { "common": "#ffffff", "uncommon": "#1eff00", "rare": "#0070dd", "epic": "#a335ee", "legendary": "#ff8000" } for item in inventory: symbol = rarity_symbols.get(item['rarity'], "β—‹") color = rarity_colors.get(item['rarity'], "#ffffff") quantity_str = f" x{item['quantity']}" if item['quantity'] > 1 else "" inventory_html += f"""
{symbol} {item['name']}{quantity_str}
{item['description']}
Category: {item['category'].replace('_', ' ').title()} | Rarity: {item['rarity'].title()}
""" else: inventory_html = """
No items yet. Try exploring to find useful items!
""" # Build gym badges HTML badges_html = "" if gym_badges: for badge in gym_badges: # Safely handle date formatting try: if badge['first_victory_date'] and isinstance(badge['first_victory_date'], str): badge_date = badge['first_victory_date'].split()[0] else: badge_date = 'Unknown' except (AttributeError, IndexError): badge_date = 'Unknown' badges_html += f"""
{badge['badge_icon']} {badge['badge_name']}
Earned from {badge['gym_name']} ({badge['location_name']})
First victory: {badge_date} | Total victories: {badge['victories']} | Highest difficulty: Level {badge['highest_difficulty']}
""" else: badges_html = """
No gym badges yet. Challenge gyms to earn badges and prove your training skills!
""" # Build encounters HTML encounters_html = "" if encounters: rarity_colors = {1: "#ffffff", 2: "#1eff00", 3: "#0070dd", 4: "#a335ee", 5: "#ff8000"} for encounter in encounters: rarity_color = rarity_colors.get(encounter['rarity'], "#ffffff") type_str = encounter['type1'] if encounter['type2']: type_str += f"/{encounter['type2']}" # Safely handle date formatting try: if encounter['first_encounter_date'] and isinstance(encounter['first_encounter_date'], str): encounter_date = encounter['first_encounter_date'].split()[0] else: encounter_date = 'Unknown' except (AttributeError, IndexError): encounter_date = 'Unknown' encounters_html += f"""
{encounter['species_name']} {type_str}
Encountered {encounter['total_encounters']} times | Caught {encounter['caught_count']} times
First seen: {encounter_date}
""" else: encounters_html = """
No pets encountered yet. Use !explore to discover wild pets!
""" html = f""" PetBot - {nickname}'s Profile ← Back to Game Hub

🐾 {nickname}'s Profile

Level {player['level']} Trainer

Currently in {player.get('location_name', 'Unknown Location')}

πŸ”§ Team Builder
πŸ“Š Player Statistics
{player['level']}
Level
{player['experience']}
Experience
${player['money']}
Money
{total_pets}
Pets Caught
{active_count}
Active Pets
{len(achievements)}
Achievements
{encounter_stats.get('species_encountered', 0)}
Species Seen
{encounter_stats.get('completion_percentage', 0)}%
Petdex Complete
🐾 Pet Collection
{pets_html}
Status Name Species Type Level HP Stats
πŸ† Achievements
{achievements_html}
πŸŽ’ Inventory
{inventory_html}
πŸ† Gym Badges
{badges_html}
πŸ‘οΈ Pet Encounters

Species discovered: {encounter_stats.get('species_encountered', 0)}/{encounter_stats.get('total_species', 0)} ({encounter_stats.get('completion_percentage', 0)}% complete)

Total encounters: {encounter_stats.get('total_encounters', 0)}

{encounters_html}
""" self.send_response(200) self.send_header('Content-type', 'text/html') self.end_headers() self.wfile.write(html.encode()) def log_message(self, format, *args): """Override to reduce logging noise""" pass def serve_teambuilder(self, nickname): """Serve the team builder interface""" from urllib.parse import unquote nickname = unquote(nickname) try: from src.database import Database database = Database() # Get event loop try: loop = asyncio.get_running_loop() except RuntimeError: loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) player_data = loop.run_until_complete(self.fetch_player_data(database, nickname)) if player_data is None: self.serve_player_not_found(nickname) return pets = player_data['pets'] if not pets: self.serve_teambuilder_no_pets(nickname) return self.serve_teambuilder_interface(nickname, pets) except Exception as e: print(f"Error loading team builder for {nickname}: {e}") self.serve_player_error(nickname, f"Error loading team builder: {str(e)}") def serve_teambuilder_no_pets(self, nickname): """Show message when player has no pets""" html = f""" Team Builder - {nickname}

🐾 No Pets Found

{nickname}, you need to catch some pets before using the team builder!

← Back to Profile

""" self.send_response(200) self.send_header('Content-type', 'text/html') self.end_headers() self.wfile.write(html.encode()) def serve_teambuilder_interface(self, nickname, pets): """Serve the full interactive team builder interface""" active_pets = [pet for pet in pets if pet['is_active']] inactive_pets = [pet for pet in pets if not pet['is_active']] # Generate detailed pet cards def make_pet_card(pet, is_active): name = pet['nickname'] or pet['species_name'] status = "Active" if is_active else "Storage" status_class = "active" if is_active else "storage" type_str = pet['type1'] if pet['type2']: type_str += f"/{pet['type2']}" # Calculate HP percentage for health bar hp_percent = (pet['hp'] / pet['max_hp']) * 100 if pet['max_hp'] > 0 else 0 hp_color = "#4CAF50" if hp_percent > 60 else "#FF9800" if hp_percent > 25 else "#f44336" return f"""

{name}

{status}
Level {pet['level']} {pet['species_name']}
{type_str}
HP: {pet['hp']}/{pet['max_hp']}
ATK {pet['attack']}
DEF {pet['defense']}
SPD {pet['speed']}
EXP {pet['experience']}
{'😊' if pet['happiness'] > 70 else '😐' if pet['happiness'] > 40 else '😞'} Happiness: {pet['happiness']}/100
""" active_cards = ''.join(make_pet_card(pet, True) for pet in active_pets) storage_cards = ''.join(make_pet_card(pet, False) for pet in inactive_pets) html = f""" Team Builder - {nickname}

🐾 Team Builder

Drag pets between Active and Storage to build your perfect team

{nickname} | Active: {len(active_pets)} pets | Storage: {len(inactive_pets)} pets

⭐ Active Team
{active_cards}
Drop pets here to add to your active team
πŸ“¦ Storage
{storage_cards}
Drop pets here to store them
← Back to Profile
Changes are saved securely with PIN verification via IRC

πŸ” PIN Verification Required

A 6-digit PIN has been sent to you via IRC private message.

Enter the PIN below to confirm your team changes:

""" self.send_response(200) self.send_header('Content-type', 'text/html') self.end_headers() self.wfile.write(html.encode()) def handle_team_save(self, nickname): """Handle team save request and generate PIN""" self.send_json_response({"success": False, "error": "Team save not fully implemented yet"}, 501) def handle_team_verify(self, nickname): """Handle PIN verification and apply team changes""" self.send_json_response({"success": False, "error": "PIN verification not fully implemented yet"}, 501) def send_pin_via_irc(self, nickname, pin_code): """Send PIN to player via IRC private message""" print(f"πŸ” PIN for {nickname}: {pin_code}") # Try to send via IRC bot if available try: # Check if the bot instance is accessible via global state import sys if hasattr(sys.modules.get('__main__'), 'bot_instance'): bot = sys.modules['__main__'].bot_instance if hasattr(bot, 'send_team_builder_pin'): # Use asyncio to run the async method import asyncio loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) loop.run_until_complete(bot.send_team_builder_pin(nickname, pin_code)) loop.close() return except Exception as e: print(f"Could not send PIN via IRC bot: {e}") # Fallback: just print to console for now print(f"⚠️ IRC bot not available - PIN displayed in console only") def send_json_response(self, data, status_code=200): """Send JSON response""" import json response = json.dumps(data) self.send_response(status_code) self.send_header('Content-type', 'application/json') self.end_headers() self.wfile.write(response.encode()) class PetBotWebServer: def __init__(self, database, port=8080): self.database = database self.port = port self.server = None def run(self): """Start the HTTP web server""" 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}") self.server = HTTPServer(('0.0.0.0', self.port), PetBotRequestHandler) # Pass database to the server so handlers can access it self.server.database = self.database self.server.serve_forever() def start_in_thread(self): """Start the web server in a background thread""" thread = Thread(target=self.run, daemon=True) thread.start() print(f"βœ… Web server started at http://localhost:{self.port}") print(f"🌐 Public access at: http://petz.rdx4.com/") return thread def run_standalone(): """Run the web server standalone for testing""" print("🐾 PetBot Web Server (Standalone Mode)") print("=" * 40) # Initialize database database = Database() # Note: In standalone mode, we can't easily run async init # This is mainly for testing the web routes # Start web server server = PetBotWebServer(database) print("πŸš€ Starting web server...") print("πŸ“ Available routes:") print(" http://localhost:8080/ - Game Hub (local)") print(" http://localhost:8080/help - Command Help (local)") print(" http://localhost:8080/players - Player List (local)") print(" http://localhost:8080/leaderboard - Leaderboard (local)") print(" http://localhost:8080/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("Press Ctrl+C to stop") try: server.run() except KeyboardInterrupt: print("\nβœ… Web server stopped") if __name__ == "__main__": run_standalone()