""" Internal API endpoints for Telegram Bot These endpoints are protected by an internal key and handle game logic """ from fastapi import APIRouter, Header, HTTPException, Depends from pydantic import BaseModel from typing import Optional, Dict, Any, List import os # Internal API key for bot authentication INTERNAL_API_KEY = os.getenv("API_INTERNAL_KEY", "internal-bot-access-key-change-me") router = APIRouter(prefix="/api/internal", tags=["internal"]) def verify_internal_key(x_internal_key: str = Header(...)): """Verify internal API key""" if x_internal_key != INTERNAL_API_KEY: raise HTTPException(status_code=403, detail="Invalid internal API key") return True # ==================== Pydantic Models ==================== class PlayerCreate(BaseModel): telegram_id: int name: str = "Survivor" class PlayerUpdate(BaseModel): name: Optional[str] = None hp: Optional[int] = None stamina: Optional[int] = None location_id: Optional[str] = None level: Optional[int] = None xp: Optional[int] = None strength: Optional[int] = None agility: Optional[int] = None endurance: Optional[int] = None intellect: Optional[int] = None class MoveRequest(BaseModel): direction: str class CombatStart(BaseModel): telegram_id: int npc_id: str class CombatAction(BaseModel): action: str # "attack", "defend", "flee" class UseItem(BaseModel): item_db_id: int class EquipItem(BaseModel): item_db_id: int # ==================== Player Endpoints ==================== @router.get("/player/telegram/{telegram_id}") async def get_player_by_telegram( telegram_id: int, _: bool = Depends(verify_internal_key) ): """Get player by Telegram ID""" from bot.database import get_player player = await get_player(telegram_id=telegram_id) if not player: raise HTTPException(status_code=404, detail="Player not found") return player @router.post("/player") async def create_player_internal( player_data: PlayerCreate, _: bool = Depends(verify_internal_key) ): """Create a new player (Telegram bot)""" from bot.database import create_player player = await create_player(telegram_id=player_data.telegram_id, name=player_data.name) if not player: raise HTTPException(status_code=500, detail="Failed to create player") return player @router.patch("/player/telegram/{telegram_id}") async def update_player_internal( telegram_id: int, updates: PlayerUpdate, _: bool = Depends(verify_internal_key) ): """Update player data""" from bot.database import update_player # Convert to dict and remove None values update_dict = {k: v for k, v in updates.dict().items() if v is not None} if not update_dict: return {"success": True, "message": "No updates provided"} await update_player(telegram_id=telegram_id, updates=update_dict) return {"success": True, "message": "Player updated"} # ==================== Location Endpoints ==================== @router.get("/location/{location_id}") async def get_location_internal( location_id: str, _: bool = Depends(verify_internal_key) ): """Get location details""" from api.main import LOCATIONS location = LOCATIONS.get(location_id) if not location: raise HTTPException(status_code=404, detail="Location not found") return { "id": location.id, "name": location.name, "description": location.description, "exits": location.exits, "interactables": {k: { "id": v.id, "name": v.name, "actions": list(v.actions.keys()) } for k, v in location.interactables.items()}, "image_path": location.image_path } @router.post("/player/telegram/{telegram_id}/move") async def move_player_internal( telegram_id: int, move_data: MoveRequest, _: bool = Depends(verify_internal_key) ): """Move player in a direction""" from bot.database import get_player, update_player from api.main import LOCATIONS player = await get_player(telegram_id=telegram_id) if not player: raise HTTPException(status_code=404, detail="Player not found") current_location = LOCATIONS.get(player['location_id']) if not current_location: raise HTTPException(status_code=400, detail="Invalid current location") # Check stamina if player['stamina'] < 1: raise HTTPException(status_code=400, detail="Not enough stamina to move") # Find exit destination_id = current_location.exits.get(move_data.direction.lower()) if not destination_id: raise HTTPException(status_code=400, detail=f"Cannot move {move_data.direction} from here") new_location = LOCATIONS.get(destination_id) if not new_location: raise HTTPException(status_code=400, detail="Invalid destination") # Update player await update_player(telegram_id=telegram_id, updates={ 'location_id': new_location.id, 'stamina': max(0, player['stamina'] - 1) }) return { "success": True, "location": { "id": new_location.id, "name": new_location.name, "description": new_location.description, "exits": new_location.exits }, "stamina": max(0, player['stamina'] - 1) } # ==================== Inventory Endpoints ==================== @router.get("/player/telegram/{telegram_id}/inventory") async def get_inventory_internal( telegram_id: int, _: bool = Depends(verify_internal_key) ): """Get player's inventory""" from bot.database import get_inventory inventory = await get_inventory(telegram_id) return {"items": inventory} @router.post("/player/telegram/{telegram_id}/use_item") async def use_item_internal( telegram_id: int, item_data: UseItem, _: bool = Depends(verify_internal_key) ): """Use an item from inventory""" from bot.logic import use_item_logic from bot.database import get_player player = await get_player(telegram_id=telegram_id) if not player: raise HTTPException(status_code=404, detail="Player not found") result = await use_item_logic(player, item_data.item_db_id) return result @router.post("/player/telegram/{telegram_id}/equip") async def equip_item_internal( telegram_id: int, item_data: EquipItem, _: bool = Depends(verify_internal_key) ): """Equip/unequip an item""" from bot.logic import toggle_equip result = await toggle_equip(telegram_id, item_data.item_db_id) return {"success": True, "message": result} # ==================== Combat Endpoints ==================== @router.post("/combat/start") async def start_combat_internal( combat_data: CombatStart, _: bool = Depends(verify_internal_key) ): """Start combat with an NPC""" from bot.combat import start_combat from bot.database import get_player player = await get_player(telegram_id=combat_data.telegram_id) if not player: raise HTTPException(status_code=404, detail="Player not found") result = await start_combat(combat_data.telegram_id, combat_data.npc_id, player['location_id']) if not result.get("success"): raise HTTPException(status_code=400, detail=result.get("message", "Failed to start combat")) return result @router.get("/combat/telegram/{telegram_id}") async def get_combat_internal( telegram_id: int, _: bool = Depends(verify_internal_key) ): """Get active combat state""" from bot.combat import get_active_combat combat = await get_active_combat(telegram_id) if not combat: raise HTTPException(status_code=404, detail="No active combat") return combat @router.post("/combat/telegram/{telegram_id}/action") async def combat_action_internal( telegram_id: int, action_data: CombatAction, _: bool = Depends(verify_internal_key) ): """Perform combat action""" from bot.combat import player_attack, player_defend, player_flee if action_data.action == "attack": result = await player_attack(telegram_id) elif action_data.action == "defend": result = await player_defend(telegram_id) elif action_data.action == "flee": result = await player_flee(telegram_id) else: raise HTTPException(status_code=400, detail="Invalid combat action") return result