284 lines
8.3 KiB
Python
284 lines
8.3 KiB
Python
"""
|
|
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
|