"""
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"""
{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
Rank
Player
Level
Experience
Money
Pets
Active
Achievements
Location
{players_html}
π‘ 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"""
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"""
{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"""
"""
# 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!
"""
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
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()