Fix database constraints and team builder save functionality
🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
60dbcae113
commit
08f7aa8ea8
3 changed files with 382 additions and 20 deletions
|
|
@ -70,17 +70,30 @@ Web: Enter PIN → Validate PIN → Apply Database Changes → Clear Request
|
||||||
|
|
||||||
## 🐛 Known Issues & Bugs
|
## 🐛 Known Issues & Bugs
|
||||||
|
|
||||||
### None Currently Known ✅
|
### Current Issues - CRITICAL
|
||||||
All major bugs have been resolved:
|
- **Team Builder Auto-Move** - Active pets get moved to storage on page load automatically
|
||||||
- ✅ Fixed gym battle syntax errors
|
- **Save Button Not Working** - Team builder save returns 501 "not implemented" error
|
||||||
- ✅ Fixed player profile crashes with encounter data
|
- **Experience Not Saving** - Pet EXP doesn't persist or isn't visible in profiles
|
||||||
- ✅ Fixed database column mapping issues
|
- **Achievement Travel Bug** - Players can't travel despite having achievements until catching a pet
|
||||||
- ✅ Fixed indentation and import errors
|
- **Team Builder Save Auth** - PIN verification system not implemented in handlers
|
||||||
|
|
||||||
|
### Current Issues - HIGH PRIORITY
|
||||||
|
- **Item Spawn Rate** - Items too common, need lower spawn rates
|
||||||
|
- **Location Wild Pets** - Same pets listed multiple times, broken "+(number) more" button
|
||||||
|
- **Petdex Accuracy** - Missing types in summary, showing pet instances instead of locations
|
||||||
|
- **Pet Spawns Logic** - Common pets should spawn in most compatible locations
|
||||||
|
- **Missing Database Tables** - No species_moves table for move learning system
|
||||||
|
|
||||||
|
### Current Issues - MEDIUM PRIORITY
|
||||||
|
- **Team Command Navigation** - !team should link to web team page instead of IRC output
|
||||||
|
- **Pet Rename UX** - Should be done via team builder with edit buttons + PIN auth
|
||||||
|
- **Max Team Size** - Need to implement and enforce team size limits
|
||||||
|
|
||||||
### Recently Fixed
|
### Recently Fixed
|
||||||
- **Player Profile Crash** - Fixed 'int' object has no attribute 'split' error
|
- **Player Profile Crash** - Fixed 'int' object has no attribute 'split' error
|
||||||
- **Battle System Syntax** - Resolved indentation issues in gym battle completion
|
- **Battle System Syntax** - Resolved indentation issues in gym battle completion
|
||||||
- **Encounter Data Mapping** - Corrected SQL column indices
|
- **Encounter Data Mapping** - Corrected SQL column indices
|
||||||
|
- **Team Builder Drag-Drop** - Fixed drag and drop functionality with backup double-click
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
159
src/database.py
159
src/database.py
|
|
@ -315,6 +315,58 @@ class Database:
|
||||||
)
|
)
|
||||||
""")
|
""")
|
||||||
|
|
||||||
|
# Create species_moves table for move learning system
|
||||||
|
await db.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS species_moves (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
species_id INTEGER NOT NULL,
|
||||||
|
move_id INTEGER NOT NULL,
|
||||||
|
learn_level INTEGER NOT NULL,
|
||||||
|
learn_method TEXT DEFAULT 'level',
|
||||||
|
FOREIGN KEY (species_id) REFERENCES pet_species (id),
|
||||||
|
FOREIGN KEY (move_id) REFERENCES moves (id),
|
||||||
|
UNIQUE(species_id, move_id, learn_level)
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
|
||||||
|
# Create location type compatibility table
|
||||||
|
await db.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS location_type_compatibility (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
location_id INTEGER NOT NULL,
|
||||||
|
pet_type TEXT NOT NULL,
|
||||||
|
compatibility_modifier REAL DEFAULT 1.0,
|
||||||
|
FOREIGN KEY (location_id) REFERENCES locations (id),
|
||||||
|
UNIQUE(location_id, pet_type)
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
|
||||||
|
# Create indexes for performance optimization
|
||||||
|
await db.execute("CREATE INDEX IF NOT EXISTS idx_pets_player_active ON pets (player_id, is_active)")
|
||||||
|
await db.execute("CREATE INDEX IF NOT EXISTS idx_pets_species ON pets (species_id)")
|
||||||
|
await db.execute("CREATE INDEX IF NOT EXISTS idx_player_encounters_player ON player_encounters (player_id)")
|
||||||
|
await db.execute("CREATE INDEX IF NOT EXISTS idx_player_encounters_species ON player_encounters (species_id)")
|
||||||
|
await db.execute("CREATE INDEX IF NOT EXISTS idx_location_spawns_location ON location_spawns (location_id)")
|
||||||
|
await db.execute("CREATE INDEX IF NOT EXISTS idx_species_moves_species ON species_moves (species_id)")
|
||||||
|
await db.execute("CREATE INDEX IF NOT EXISTS idx_species_moves_level ON species_moves (learn_level)")
|
||||||
|
|
||||||
|
# Add team size validation trigger
|
||||||
|
await db.execute("""
|
||||||
|
CREATE TRIGGER IF NOT EXISTS validate_team_size_on_update
|
||||||
|
AFTER UPDATE OF is_active ON pets
|
||||||
|
WHEN NEW.is_active != OLD.is_active
|
||||||
|
BEGIN
|
||||||
|
UPDATE pets SET is_active = OLD.is_active
|
||||||
|
WHERE id = NEW.id AND (
|
||||||
|
SELECT COUNT(*) FROM pets
|
||||||
|
WHERE player_id = NEW.player_id AND is_active = TRUE
|
||||||
|
) < 1;
|
||||||
|
END
|
||||||
|
""")
|
||||||
|
|
||||||
|
# Enable foreign key constraints
|
||||||
|
await db.execute("PRAGMA foreign_keys = ON")
|
||||||
|
|
||||||
await db.commit()
|
await db.commit()
|
||||||
|
|
||||||
async def get_player(self, nickname: str) -> Optional[Dict]:
|
async def get_player(self, nickname: str) -> Optional[Dict]:
|
||||||
|
|
@ -863,10 +915,10 @@ class Database:
|
||||||
"levels_gained": new_level - old_level
|
"levels_gained": new_level - old_level
|
||||||
}
|
}
|
||||||
|
|
||||||
# Update experience
|
# Update experience and level
|
||||||
await db.execute("""
|
await db.execute("""
|
||||||
UPDATE pets SET experience = ? WHERE id = ?
|
UPDATE pets SET experience = ?, level = ? WHERE id = ?
|
||||||
""", (new_exp, pet_id))
|
""", (new_exp, new_level, pet_id))
|
||||||
|
|
||||||
# Handle level up if it occurred
|
# Handle level up if it occurred
|
||||||
if new_level > old_level:
|
if new_level > old_level:
|
||||||
|
|
@ -1586,3 +1638,104 @@ class Database:
|
||||||
"pins_cleaned": pins_cleaned,
|
"pins_cleaned": pins_cleaned,
|
||||||
"changes_cleaned": changes_cleaned
|
"changes_cleaned": changes_cleaned
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Species Moves System Methods
|
||||||
|
async def get_species_moves(self, species_id: int, max_level: int = None) -> List[Dict]:
|
||||||
|
"""Get moves that a species can learn up to a certain level"""
|
||||||
|
async with aiosqlite.connect(self.db_path) as db:
|
||||||
|
db.row_factory = aiosqlite.Row
|
||||||
|
|
||||||
|
query = """
|
||||||
|
SELECT sm.*, m.name, m.type, m.category, m.power, m.accuracy, m.pp, m.description
|
||||||
|
FROM species_moves sm
|
||||||
|
JOIN moves m ON sm.move_id = m.id
|
||||||
|
WHERE sm.species_id = ?
|
||||||
|
"""
|
||||||
|
params = [species_id]
|
||||||
|
|
||||||
|
if max_level:
|
||||||
|
query += " AND sm.learn_level <= ?"
|
||||||
|
params.append(max_level)
|
||||||
|
|
||||||
|
query += " ORDER BY sm.learn_level ASC"
|
||||||
|
|
||||||
|
cursor = await db.execute(query, params)
|
||||||
|
rows = await cursor.fetchall()
|
||||||
|
return [dict(row) for row in rows]
|
||||||
|
|
||||||
|
async def get_learnable_moves_for_pet(self, pet_id: int) -> List[Dict]:
|
||||||
|
"""Get moves a specific pet can learn at their current level"""
|
||||||
|
async with aiosqlite.connect(self.db_path) as db:
|
||||||
|
db.row_factory = aiosqlite.Row
|
||||||
|
|
||||||
|
# Get pet info
|
||||||
|
cursor = await db.execute("""
|
||||||
|
SELECT p.species_id, p.level, p.id
|
||||||
|
FROM pets p WHERE p.id = ?
|
||||||
|
""", (pet_id,))
|
||||||
|
pet = await cursor.fetchone()
|
||||||
|
|
||||||
|
if not pet:
|
||||||
|
return []
|
||||||
|
|
||||||
|
# Get moves the pet can learn but doesn't know yet
|
||||||
|
cursor = await db.execute("""
|
||||||
|
SELECT sm.*, m.name, m.type, m.category, m.power, m.accuracy, m.pp, m.description
|
||||||
|
FROM species_moves sm
|
||||||
|
JOIN moves m ON sm.move_id = m.id
|
||||||
|
WHERE sm.species_id = ? AND sm.learn_level <= ?
|
||||||
|
AND sm.move_id NOT IN (
|
||||||
|
SELECT pm.move_id FROM pet_moves pm WHERE pm.pet_id = ?
|
||||||
|
)
|
||||||
|
ORDER BY sm.learn_level DESC
|
||||||
|
""", (pet["species_id"], pet["level"], pet_id))
|
||||||
|
|
||||||
|
rows = await cursor.fetchall()
|
||||||
|
return [dict(row) for row in rows]
|
||||||
|
|
||||||
|
async def validate_team_composition(self, player_id: int, proposed_changes: Dict) -> Dict:
|
||||||
|
"""Validate team changes before applying them"""
|
||||||
|
async with aiosqlite.connect(self.db_path) as db:
|
||||||
|
db.row_factory = aiosqlite.Row
|
||||||
|
|
||||||
|
# Get current pet states
|
||||||
|
cursor = await db.execute("""
|
||||||
|
SELECT id, is_active FROM pets WHERE player_id = ?
|
||||||
|
""", (player_id,))
|
||||||
|
current_pets = {str(row["id"]): bool(row["is_active"]) for row in await cursor.fetchall()}
|
||||||
|
|
||||||
|
# Apply proposed changes to current state
|
||||||
|
new_state = current_pets.copy()
|
||||||
|
for pet_id, new_active_state in proposed_changes.items():
|
||||||
|
if pet_id in new_state:
|
||||||
|
new_state[pet_id] = new_active_state
|
||||||
|
|
||||||
|
# Count active pets in new state
|
||||||
|
active_count = sum(1 for is_active in new_state.values() if is_active)
|
||||||
|
|
||||||
|
# Validate constraints
|
||||||
|
if active_count < 1:
|
||||||
|
return {"valid": False, "error": "Must have at least 1 active pet"}
|
||||||
|
|
||||||
|
if active_count > 6: # Pokemon-style 6 pet limit
|
||||||
|
return {"valid": False, "error": "Cannot have more than 6 active pets"}
|
||||||
|
|
||||||
|
return {"valid": True, "active_count": active_count}
|
||||||
|
|
||||||
|
async def get_team_composition(self, player_id: int) -> Dict:
|
||||||
|
"""Get current team composition stats"""
|
||||||
|
async with aiosqlite.connect(self.db_path) as db:
|
||||||
|
cursor = await db.execute("""
|
||||||
|
SELECT
|
||||||
|
COUNT(*) as total_pets,
|
||||||
|
SUM(CASE WHEN is_active THEN 1 ELSE 0 END) as active_pets,
|
||||||
|
SUM(CASE WHEN NOT is_active THEN 1 ELSE 0 END) as storage_pets
|
||||||
|
FROM pets WHERE player_id = ?
|
||||||
|
""", (player_id,))
|
||||||
|
|
||||||
|
result = await cursor.fetchone()
|
||||||
|
return {
|
||||||
|
"total_pets": result[0],
|
||||||
|
"active_pets": result[1],
|
||||||
|
"storage_pets": result[2]
|
||||||
|
}
|
||||||
216
webserver.py
216
webserver.py
|
|
@ -20,6 +20,11 @@ from src.database import Database
|
||||||
class PetBotRequestHandler(BaseHTTPRequestHandler):
|
class PetBotRequestHandler(BaseHTTPRequestHandler):
|
||||||
"""HTTP request handler for PetBot web server"""
|
"""HTTP request handler for PetBot web server"""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def database(self):
|
||||||
|
"""Get database instance from server"""
|
||||||
|
return self.server.database
|
||||||
|
|
||||||
def do_GET(self):
|
def do_GET(self):
|
||||||
"""Handle GET requests"""
|
"""Handle GET requests"""
|
||||||
parsed_path = urlparse(self.path)
|
parsed_path = urlparse(self.path)
|
||||||
|
|
@ -2442,9 +2447,23 @@ class PetBotRequestHandler(BaseHTTPRequestHandler):
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
.back-link {{
|
||||||
|
color: var(--text-accent);
|
||||||
|
text-decoration: none;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
display: inline-block;
|
||||||
|
font-weight: 500;
|
||||||
|
}}
|
||||||
|
|
||||||
|
.back-link:hover {{
|
||||||
|
text-decoration: underline;
|
||||||
|
}}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<a href="/player/{nickname}" class="back-link">← Back to {nickname}'s Profile</a>
|
||||||
|
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<h1>🐾 Team Builder</h1>
|
<h1>🐾 Team Builder</h1>
|
||||||
<p>Drag pets between Active and Storage to build your perfect team</p>
|
<p>Drag pets between Active and Storage to build your perfect team</p>
|
||||||
|
|
@ -2560,14 +2579,20 @@ class PetBotRequestHandler(BaseHTTPRequestHandler):
|
||||||
}});
|
}});
|
||||||
}}
|
}}
|
||||||
|
|
||||||
// Initialize team state
|
// Initialize team state with debugging
|
||||||
document.querySelectorAll('.pet-card').forEach(card => {{
|
console.log('Initializing team state...');
|
||||||
|
document.querySelectorAll('.pet-card').forEach((card, index) => {{
|
||||||
const petId = card.dataset.petId;
|
const petId = card.dataset.petId;
|
||||||
const isActive = card.dataset.active === 'true';
|
const isActive = card.dataset.active === 'true';
|
||||||
originalTeam[petId] = isActive;
|
originalTeam[petId] = isActive;
|
||||||
currentTeam[petId] = isActive;
|
currentTeam[petId] = isActive;
|
||||||
|
|
||||||
|
console.log(`Pet ${{index}}: ID=${{petId}}, isActive=${{isActive}}, parentContainer=${{card.parentElement.id}}`);
|
||||||
}});
|
}});
|
||||||
|
|
||||||
|
console.log('Original team state:', originalTeam);
|
||||||
|
console.log('Current team state:', currentTeam);
|
||||||
|
|
||||||
// Completely rewritten drag and drop - simpler approach
|
// Completely rewritten drag and drop - simpler approach
|
||||||
function initializeDragAndDrop() {{
|
function initializeDragAndDrop() {{
|
||||||
console.log('Initializing drag and drop...');
|
console.log('Initializing drag and drop...');
|
||||||
|
|
@ -2682,13 +2707,21 @@ class PetBotRequestHandler(BaseHTTPRequestHandler):
|
||||||
}}
|
}}
|
||||||
|
|
||||||
function movePetToActive(petId) {{
|
function movePetToActive(petId) {{
|
||||||
|
console.log(`movePetToActive called for pet ${{petId}}`);
|
||||||
const card = document.querySelector(`[data-pet-id="${{petId}}"]`);
|
const card = document.querySelector(`[data-pet-id="${{petId}}"]`);
|
||||||
if (!card) return;
|
if (!card) {{
|
||||||
|
console.log(`No card found for pet ${{petId}}`);
|
||||||
|
return;
|
||||||
|
}}
|
||||||
|
|
||||||
const activeContainer = document.getElementById('active-container');
|
const activeContainer = document.getElementById('active-container');
|
||||||
const currentIsActive = currentTeam[petId];
|
const currentIsActive = currentTeam[petId];
|
||||||
|
|
||||||
|
console.log(`Pet ${{petId}} current state: ${{currentIsActive ? 'active' : 'storage'}}`);
|
||||||
|
|
||||||
if (!currentIsActive) {{
|
if (!currentIsActive) {{
|
||||||
|
console.log(`Moving pet ${{petId}} to active...`);
|
||||||
|
|
||||||
// Update state
|
// Update state
|
||||||
currentTeam[petId] = true;
|
currentTeam[petId] = true;
|
||||||
|
|
||||||
|
|
@ -2704,17 +2737,27 @@ class PetBotRequestHandler(BaseHTTPRequestHandler):
|
||||||
updateDropZoneVisibility();
|
updateDropZoneVisibility();
|
||||||
|
|
||||||
console.log('Pet moved to active successfully');
|
console.log('Pet moved to active successfully');
|
||||||
|
}} else {{
|
||||||
|
console.log(`Pet ${{petId}} is already active, no move needed`);
|
||||||
}}
|
}}
|
||||||
}}
|
}}
|
||||||
|
|
||||||
function movePetToStorage(petId) {{
|
function movePetToStorage(petId) {{
|
||||||
|
console.log(`movePetToStorage called for pet ${{petId}}`);
|
||||||
const card = document.querySelector(`[data-pet-id="${{petId}}"]`);
|
const card = document.querySelector(`[data-pet-id="${{petId}}"]`);
|
||||||
if (!card) return;
|
if (!card) {{
|
||||||
|
console.log(`No card found for pet ${{petId}}`);
|
||||||
|
return;
|
||||||
|
}}
|
||||||
|
|
||||||
const storageContainer = document.getElementById('storage-container');
|
const storageContainer = document.getElementById('storage-container');
|
||||||
const currentIsActive = currentTeam[petId];
|
const currentIsActive = currentTeam[petId];
|
||||||
|
|
||||||
|
console.log(`Pet ${{petId}} current state: ${{currentIsActive ? 'active' : 'storage'}}`);
|
||||||
|
|
||||||
if (currentIsActive) {{
|
if (currentIsActive) {{
|
||||||
|
console.log(`Moving pet ${{petId}} to storage...`);
|
||||||
|
|
||||||
// Update state
|
// Update state
|
||||||
currentTeam[petId] = false;
|
currentTeam[petId] = false;
|
||||||
|
|
||||||
|
|
@ -2730,6 +2773,8 @@ class PetBotRequestHandler(BaseHTTPRequestHandler):
|
||||||
updateDropZoneVisibility();
|
updateDropZoneVisibility();
|
||||||
|
|
||||||
console.log('Pet moved to storage successfully');
|
console.log('Pet moved to storage successfully');
|
||||||
|
}} else {{
|
||||||
|
console.log(`Pet ${{petId}} is already in storage, no move needed`);
|
||||||
}}
|
}}
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
|
@ -2740,8 +2785,23 @@ class PetBotRequestHandler(BaseHTTPRequestHandler):
|
||||||
const activeDrop = document.getElementById('active-drop');
|
const activeDrop = document.getElementById('active-drop');
|
||||||
const storageDrop = document.getElementById('storage-drop');
|
const storageDrop = document.getElementById('storage-drop');
|
||||||
|
|
||||||
activeDrop.style.display = activeContainer.children.length > 0 ? 'none' : 'flex';
|
// Use CSS classes instead of direct style manipulation
|
||||||
storageDrop.style.display = storageContainer.children.length > 0 ? 'none' : 'flex';
|
if (activeContainer.children.length > 0) {{
|
||||||
|
activeDrop.classList.add('has-pets');
|
||||||
|
}} else {{
|
||||||
|
activeDrop.classList.remove('has-pets');
|
||||||
|
}}
|
||||||
|
|
||||||
|
if (storageContainer.children.length > 0) {{
|
||||||
|
storageDrop.classList.add('has-pets');
|
||||||
|
}} else {{
|
||||||
|
storageDrop.classList.remove('has-pets');
|
||||||
|
}}
|
||||||
|
|
||||||
|
console.log('Drop zone visibility updated:', {{
|
||||||
|
activeContainerPets: activeContainer.children.length,
|
||||||
|
storageContainerPets: storageContainer.children.length
|
||||||
|
}});
|
||||||
}}
|
}}
|
||||||
|
|
||||||
function updateSaveButton() {{
|
function updateSaveButton() {{
|
||||||
|
|
@ -2850,16 +2910,31 @@ class PetBotRequestHandler(BaseHTTPRequestHandler):
|
||||||
}}
|
}}
|
||||||
}});
|
}});
|
||||||
|
|
||||||
// Initialize interface
|
// Initialize interface with debugging
|
||||||
|
console.log('Starting initialization...');
|
||||||
|
|
||||||
|
// Debug initial state
|
||||||
|
const activeContainer = document.getElementById('active-container');
|
||||||
|
const storageContainer = document.getElementById('storage-container');
|
||||||
|
console.log('Initial state:', {{
|
||||||
|
activePets: activeContainer.children.length,
|
||||||
|
storagePets: storageContainer.children.length
|
||||||
|
}});
|
||||||
|
|
||||||
initializeDragAndDrop();
|
initializeDragAndDrop();
|
||||||
addClickToMoveBackup(); // Add double-click as backup
|
addClickToMoveBackup(); // Add double-click as backup
|
||||||
updateSaveButton();
|
updateSaveButton();
|
||||||
|
|
||||||
|
console.log('Before updateDropZoneVisibility...');
|
||||||
updateDropZoneVisibility();
|
updateDropZoneVisibility();
|
||||||
|
|
||||||
|
console.log('Initialization complete.');
|
||||||
|
|
||||||
// Run test to verify everything is working
|
// Run test to verify everything is working
|
||||||
setTimeout(() => {{
|
setTimeout(() => {{
|
||||||
|
console.log('Running delayed test...');
|
||||||
runDragDropTest();
|
runDragDropTest();
|
||||||
}}, 500);
|
}}, 1000);
|
||||||
|
|
||||||
// Add test button for manual debugging
|
// Add test button for manual debugging
|
||||||
const testButton = document.createElement('button');
|
const testButton = document.createElement('button');
|
||||||
|
|
@ -2897,11 +2972,132 @@ class PetBotRequestHandler(BaseHTTPRequestHandler):
|
||||||
|
|
||||||
def handle_team_save(self, nickname):
|
def handle_team_save(self, nickname):
|
||||||
"""Handle team save request and generate PIN"""
|
"""Handle team save request and generate PIN"""
|
||||||
self.send_json_response({"success": False, "error": "Team save not fully implemented yet"}, 501)
|
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')
|
||||||
|
|
||||||
|
# Parse JSON data
|
||||||
|
import json
|
||||||
|
try:
|
||||||
|
team_data = json.loads(post_data)
|
||||||
|
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_team_save_async(nickname, team_data))
|
||||||
|
|
||||||
|
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_team_save: {e}")
|
||||||
|
self.send_json_response({"success": False, "error": "Internal server error"}, 500)
|
||||||
|
|
||||||
|
async def _handle_team_save_async(self, nickname, team_data):
|
||||||
|
"""Async handler for team save"""
|
||||||
|
try:
|
||||||
|
# Get player
|
||||||
|
player = await self.database.get_player(nickname)
|
||||||
|
if not player:
|
||||||
|
return {"success": False, "error": "Player not found"}
|
||||||
|
|
||||||
|
# Validate team composition
|
||||||
|
validation = await self.database.validate_team_composition(player["id"], team_data)
|
||||||
|
if not validation["valid"]:
|
||||||
|
return {"success": False, "error": validation["error"]}
|
||||||
|
|
||||||
|
# Create pending team change with PIN
|
||||||
|
import json
|
||||||
|
result = await self.database.create_pending_team_change(
|
||||||
|
player["id"],
|
||||||
|
json.dumps(team_data)
|
||||||
|
)
|
||||||
|
|
||||||
|
if result["success"]:
|
||||||
|
# Send PIN via IRC
|
||||||
|
self.send_pin_via_irc(nickname, result["pin_code"])
|
||||||
|
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"message": "PIN sent to your IRC private messages",
|
||||||
|
"expires_in_minutes": 10
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return result
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error in _handle_team_save_async: {e}")
|
||||||
|
return {"success": False, "error": str(e)}
|
||||||
|
|
||||||
def handle_team_verify(self, nickname):
|
def handle_team_verify(self, nickname):
|
||||||
"""Handle PIN verification and apply team changes"""
|
"""Handle PIN verification and apply team changes"""
|
||||||
self.send_json_response({"success": False, "error": "PIN verification not fully implemented yet"}, 501)
|
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 PIN provided"}, 400)
|
||||||
|
return
|
||||||
|
|
||||||
|
post_data = self.rfile.read(content_length).decode('utf-8')
|
||||||
|
|
||||||
|
# Parse JSON data
|
||||||
|
import json
|
||||||
|
try:
|
||||||
|
data = json.loads(post_data)
|
||||||
|
pin_code = data.get("pin", "").strip()
|
||||||
|
except (json.JSONDecodeError, AttributeError):
|
||||||
|
self.send_json_response({"success": False, "error": "Invalid data format"}, 400)
|
||||||
|
return
|
||||||
|
|
||||||
|
if not pin_code:
|
||||||
|
self.send_json_response({"success": False, "error": "PIN code is required"}, 400)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Run async operations
|
||||||
|
import asyncio
|
||||||
|
result = asyncio.run(self._handle_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_team_verify: {e}")
|
||||||
|
self.send_json_response({"success": False, "error": "Internal server error"}, 500)
|
||||||
|
|
||||||
|
async def _handle_team_verify_async(self, nickname, pin_code):
|
||||||
|
"""Async handler for PIN verification"""
|
||||||
|
try:
|
||||||
|
# Get player
|
||||||
|
player = await self.database.get_player(nickname)
|
||||||
|
if not player:
|
||||||
|
return {"success": False, "error": "Player not found"}
|
||||||
|
|
||||||
|
# Apply team changes with PIN verification
|
||||||
|
result = await self.database.apply_team_change(player["id"], pin_code)
|
||||||
|
|
||||||
|
if result["success"]:
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"message": f"Team changes applied successfully! {result['changes_applied']} pets updated.",
|
||||||
|
"changes_applied": result["changes_applied"]
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return result
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error in _handle_team_verify_async: {e}")
|
||||||
|
return {"success": False, "error": str(e)}
|
||||||
|
|
||||||
def send_pin_via_irc(self, nickname, pin_code):
|
def send_pin_via_irc(self, nickname, pin_code):
|
||||||
"""Send PIN to player via IRC private message"""
|
"""Send PIN to player via IRC private message"""
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue