Some checks failed
Release / release (push) Has been cancelled
Map overhaul: - Replace colored boxes with actual game tile images (skin textures from CDN) - Overlay content icons (monsters, resources, NPCs) on tiles - Add layer switching (Overworld/Underground/Interior) - Fix API schema to parse interactions.content and layer fields - Add hover tooltips, tile search with coordinate parsing, keyboard shortcuts - Add minimap with viewport rectangle, zoom-toward-cursor, loading progress - Show tile/content images in side panel, coordinate labels at high zoom Automation gallery: - 27+ pre-built automation templates (combat, gathering, crafting, trading, utility) - Multi-character selection for batch automation creation - Gallery component with activate dialog Auth & settings: - API key gate with auth provider for token management - Enhanced settings page with token configuration UI improvements: - Game icon component for item/monster/resource images - Character automations panel on character detail page - Equipment grid and inventory grid enhancements - Automations page layout refresh - Bank, exchange page minor fixes - README update with live demo link
132 lines
4.6 KiB
Python
132 lines
4.6 KiB
Python
"""Character logs and analytics API router."""
|
|
|
|
import logging
|
|
from typing import Any
|
|
|
|
from fastapi import APIRouter, HTTPException, Query, Request
|
|
from httpx import HTTPStatusError
|
|
|
|
from app.database import async_session_factory
|
|
from app.services.analytics_service import AnalyticsService
|
|
from app.services.artifacts_client import ArtifactsClient
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
router = APIRouter(prefix="/api/logs", tags=["logs"])
|
|
|
|
|
|
def _get_client(request: Request) -> ArtifactsClient:
|
|
return request.app.state.artifacts_client
|
|
|
|
|
|
@router.get("/")
|
|
async def get_character_logs(
|
|
request: Request,
|
|
character: str = Query(default="", description="Character name to filter logs"),
|
|
limit: int = Query(default=50, ge=1, le=200, description="Max entries to return"),
|
|
) -> dict[str, Any]:
|
|
"""Get character action logs from the Artifacts API.
|
|
|
|
This endpoint retrieves the character's recent action logs directly
|
|
from the game server.
|
|
"""
|
|
client = _get_client(request)
|
|
|
|
try:
|
|
if character:
|
|
# Get logs for a specific character
|
|
char_data = await client.get_character(character)
|
|
return {
|
|
"character": character,
|
|
"logs": [], # The API doesn't have a dedicated logs endpoint per character;
|
|
# action data comes from the automation logs in our DB
|
|
"character_data": {
|
|
"name": char_data.name,
|
|
"level": char_data.level,
|
|
"xp": char_data.xp,
|
|
"gold": char_data.gold,
|
|
"x": char_data.x,
|
|
"y": char_data.y,
|
|
"task": char_data.task,
|
|
"task_progress": char_data.task_progress,
|
|
"task_total": char_data.task_total,
|
|
},
|
|
}
|
|
else:
|
|
# Get all characters as a summary
|
|
characters = await client.get_characters()
|
|
return {
|
|
"characters": [
|
|
{
|
|
"name": c.name,
|
|
"level": c.level,
|
|
"xp": c.xp,
|
|
"gold": c.gold,
|
|
"x": c.x,
|
|
"y": c.y,
|
|
}
|
|
for c in characters
|
|
],
|
|
}
|
|
except HTTPStatusError as exc:
|
|
raise HTTPException(
|
|
status_code=exc.response.status_code,
|
|
detail=f"Artifacts API error: {exc.response.text}",
|
|
) from exc
|
|
|
|
|
|
@router.get("/analytics")
|
|
async def get_analytics(
|
|
request: Request,
|
|
character: str = Query(default="", description="Character name (empty for all)"),
|
|
hours: int = Query(default=24, ge=1, le=168, description="Hours of history"),
|
|
) -> dict[str, Any]:
|
|
"""Get analytics aggregations for a character.
|
|
|
|
Returns XP history, gold history, and estimated actions per hour.
|
|
If no character is specified, aggregates across all characters with snapshots.
|
|
"""
|
|
analytics = AnalyticsService()
|
|
|
|
async with async_session_factory() as db:
|
|
if character:
|
|
characters = [character]
|
|
else:
|
|
characters = await analytics.get_tracked_characters(db)
|
|
|
|
all_xp: list[dict[str, Any]] = []
|
|
all_gold: list[dict[str, Any]] = []
|
|
total_actions_per_hour = 0.0
|
|
|
|
for char_name in characters:
|
|
xp_history = await analytics.get_xp_history(db, char_name, hours)
|
|
gold_history = await analytics.get_gold_history(db, char_name, hours)
|
|
actions_rate = await analytics.get_actions_per_hour(db, char_name)
|
|
|
|
# Transform xp_history to TimeSeriesPoint format
|
|
for point in xp_history:
|
|
all_xp.append({
|
|
"timestamp": point["timestamp"],
|
|
"value": point["xp"],
|
|
"label": f"{char_name} XP" if not character else "XP",
|
|
})
|
|
|
|
# Transform gold_history to TimeSeriesPoint format
|
|
for point in gold_history:
|
|
all_gold.append({
|
|
"timestamp": point["timestamp"],
|
|
"value": point["gold"],
|
|
"label": char_name if not character else None,
|
|
})
|
|
|
|
total_actions_per_hour += actions_rate.get("estimated_actions_per_hour", 0)
|
|
|
|
# Sort by timestamp
|
|
all_xp.sort(key=lambda p: p["timestamp"] or "")
|
|
all_gold.sort(key=lambda p: p["timestamp"] or "")
|
|
|
|
return {
|
|
"xp_history": all_xp,
|
|
"gold_history": all_gold,
|
|
"actions_per_hour": round(total_actions_per_hour, 1),
|
|
}
|