Rework data models: reorganized and extended models for UserProfile, Game, and others, added new models (RoundGuess, UserStats, Achievement, SeasonStats, DailyChallenge), and updated __init__.py.
This commit is contained in:
parent
cfe4a641a6
commit
6548f11884
11 changed files with 298 additions and 73 deletions
29
src/geoguessr_mcp/models/Achievement.py
Normal file
29
src/geoguessr_mcp/models/Achievement.py
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
"""Achievement-related data models."""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
|
||||
|
||||
@dataclass
|
||||
class Achievement:
|
||||
"""Represents a user achievement."""
|
||||
id: str
|
||||
name: str
|
||||
description: str = ""
|
||||
unlocked: bool = False
|
||||
unlocked_at: Optional[str] = None
|
||||
progress: float = 0.0
|
||||
icon_url: Optional[str] = None
|
||||
|
||||
@classmethod
|
||||
def from_api_response(cls, data: dict) -> "Achievement":
|
||||
"""Create Achievement from API response."""
|
||||
return cls(
|
||||
id=data.get("id", data.get("achievementId", "")),
|
||||
name=data.get("name", data.get("title", "")),
|
||||
description=data.get("description", ""),
|
||||
unlocked=data.get("unlocked", data.get("achieved", False)),
|
||||
unlocked_at=data.get("unlockedAt", data.get("achievedAt")),
|
||||
progress=data.get("progress", 1.0 if data.get("unlocked") else 0.0),
|
||||
icon_url=data.get("icon", data.get("imageUrl")),
|
||||
)
|
||||
29
src/geoguessr_mcp/models/DailyChallenge.py
Normal file
29
src/geoguessr_mcp/models/DailyChallenge.py
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
"""DailyChallenge-related data models."""
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Optional
|
||||
|
||||
|
||||
@dataclass
|
||||
class DailyChallenge:
|
||||
"""Daily challenge information."""
|
||||
token: str
|
||||
map_name: str = ""
|
||||
date: str = ""
|
||||
time_limit: int = 0
|
||||
completed: bool = False
|
||||
score: Optional[int] = None
|
||||
raw_data: dict = field(default_factory=dict)
|
||||
|
||||
@classmethod
|
||||
def from_api_response(cls, data: dict) -> "DailyChallenge":
|
||||
"""Create DailyChallenge from API response."""
|
||||
return cls(
|
||||
token=data.get("token", data.get("challengeToken", "")),
|
||||
map_name=data.get("map", {}).get("name", "") if isinstance(data.get("map"), dict) else "",
|
||||
date=data.get("date", data.get("day", "")),
|
||||
time_limit=data.get("timeLimit", 0),
|
||||
completed=data.get("completed", data.get("played", False)),
|
||||
score=data.get("score"),
|
||||
raw_data=data,
|
||||
)
|
||||
63
src/geoguessr_mcp/models/Game.py
Normal file
63
src/geoguessr_mcp/models/Game.py
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
"""Game-related data models."""
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Optional
|
||||
|
||||
from .RoundGuess import RoundGuess
|
||||
|
||||
|
||||
@dataclass
|
||||
class Game:
|
||||
"""Represents a complete game."""
|
||||
token: str
|
||||
map_name: str
|
||||
mode: str
|
||||
total_score: int
|
||||
rounds: list[RoundGuess] = field(default_factory=list)
|
||||
created_at: Optional[str] = None
|
||||
finished: bool = False
|
||||
raw_data: dict = field(default_factory=dict)
|
||||
|
||||
@classmethod
|
||||
def from_api_response(cls, data: dict) -> "Game":
|
||||
"""Create Game from API response."""
|
||||
rounds = []
|
||||
guesses = data.get("player", {}).get("guesses", [])
|
||||
if not guesses:
|
||||
guesses = data.get("rounds", data.get("guesses", []))
|
||||
|
||||
for i, guess_data in enumerate(guesses):
|
||||
rounds.append(RoundGuess.from_api_response(guess_data, i + 1))
|
||||
|
||||
map_data = data.get("map", {})
|
||||
map_name = map_data.get("name", "Unknown") if isinstance(map_data, dict) else str(map_data)
|
||||
|
||||
return cls(
|
||||
token=data.get("token", data.get("gameToken", data.get("id", ""))),
|
||||
map_name=map_name,
|
||||
mode=data.get("type", data.get("gameType", data.get("mode", "Unknown"))),
|
||||
total_score=sum(r.score for r in rounds),
|
||||
rounds=rounds,
|
||||
created_at=data.get("created", data.get("createdAt")),
|
||||
finished=data.get("state") == "finished" or data.get("finished", False),
|
||||
raw_data=data,
|
||||
)
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
"""Convert to dictionary."""
|
||||
return {
|
||||
"token": self.token,
|
||||
"map_name": self.map_name,
|
||||
"mode": self.mode,
|
||||
"total_score": self.total_score,
|
||||
"rounds": [
|
||||
{
|
||||
"round": r.round_number,
|
||||
"score": r.score,
|
||||
"distance_m": round(r.distance_meters, 1),
|
||||
"time_s": r.time_seconds,
|
||||
}
|
||||
for r in self.rounds
|
||||
],
|
||||
"finished": self.finished,
|
||||
}
|
||||
29
src/geoguessr_mcp/models/RoundGuess.py
Normal file
29
src/geoguessr_mcp/models/RoundGuess.py
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
"""RoundGuess-related data models."""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
|
||||
|
||||
@dataclass
|
||||
class RoundGuess:
|
||||
"""Represents a single round guess in a game."""
|
||||
round_number: int
|
||||
score: int
|
||||
distance_meters: float
|
||||
time_seconds: int
|
||||
lat: Optional[float] = None
|
||||
lng: Optional[float] = None
|
||||
country: str = ""
|
||||
|
||||
@classmethod
|
||||
def from_api_response(cls, data: dict, round_num: int = 0) -> "RoundGuess":
|
||||
"""Create RoundGuess from API response."""
|
||||
return cls(
|
||||
round_number=round_num,
|
||||
score=data.get("roundScoreInPoints", data.get("score", 0)),
|
||||
distance_meters=data.get("distanceInMeters", data.get("distance", 0)),
|
||||
time_seconds=data.get("time", data.get("timeInSeconds", 0)),
|
||||
lat=data.get("lat", data.get("latitude")),
|
||||
lng=data.get("lng", data.get("longitude")),
|
||||
country=data.get("country", ""),
|
||||
)
|
||||
30
src/geoguessr_mcp/models/SeasonStats.py
Normal file
30
src/geoguessr_mcp/models/SeasonStats.py
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
"""SeasonStats-related data models."""
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
|
||||
@dataclass
|
||||
class SeasonStats:
|
||||
"""Competitive season statistics."""
|
||||
season_id: str
|
||||
season_name: str = ""
|
||||
rank: int = 0
|
||||
rating: int = 0
|
||||
games_played: int = 0
|
||||
wins: int = 0
|
||||
division: str = ""
|
||||
raw_data: dict = field(default_factory=dict)
|
||||
|
||||
@classmethod
|
||||
def from_api_response(cls, data: dict) -> "SeasonStats":
|
||||
"""Create SeasonStats from API response."""
|
||||
return cls(
|
||||
season_id=data.get("seasonId", data.get("id", "")),
|
||||
season_name=data.get("seasonName", data.get("name", "")),
|
||||
rank=data.get("rank", data.get("position", 0)),
|
||||
rating=data.get("rating", data.get("elo", data.get("score", 0))),
|
||||
games_played=data.get("gamesPlayed", data.get("games", 0)),
|
||||
wins=data.get("wins", 0),
|
||||
division=data.get("division", data.get("tier", "")),
|
||||
raw_data=data,
|
||||
)
|
||||
49
src/geoguessr_mcp/models/UserProfile.py
Normal file
49
src/geoguessr_mcp/models/UserProfile.py
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
"""UserProfile-related data models."""
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Optional
|
||||
|
||||
|
||||
@dataclass
|
||||
class UserProfile:
|
||||
"""User profile information."""
|
||||
id: str
|
||||
nick: str
|
||||
email: str = ""
|
||||
country: str = ""
|
||||
level: int = 0
|
||||
created: str = ""
|
||||
is_verified: bool = False
|
||||
is_pro: bool = False
|
||||
avatar_url: Optional[str] = None
|
||||
raw_data: dict = field(default_factory=dict)
|
||||
|
||||
@classmethod
|
||||
def from_api_response(cls, data: dict) -> "UserProfile":
|
||||
"""Create UserProfile from API response with dynamic field mapping."""
|
||||
return cls(
|
||||
id=data.get("id", ""),
|
||||
nick=data.get("nick", data.get("username", "")),
|
||||
email=data.get("email", ""),
|
||||
country=data.get("country", data.get("countryCode", "")),
|
||||
level=data.get("level", data.get("xpLevel", 0)),
|
||||
created=data.get("created", data.get("createdAt", "")),
|
||||
is_verified=data.get("isVerified", data.get("verified", False)),
|
||||
is_pro=data.get("isPro", data.get("isProUser", False)),
|
||||
avatar_url=data.get("pin", {}).get("url") if isinstance(data.get("pin"), dict) else None,
|
||||
raw_data=data,
|
||||
)
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
"""Convert to dictionary."""
|
||||
return {
|
||||
"id": self.id,
|
||||
"nick": self.nick,
|
||||
"email": self.email,
|
||||
"country": self.country,
|
||||
"level": self.level,
|
||||
"created": self.created,
|
||||
"is_verified": self.is_verified,
|
||||
"is_pro": self.is_pro,
|
||||
"avatar_url": self.avatar_url,
|
||||
}
|
||||
50
src/geoguessr_mcp/models/UserStats.py
Normal file
50
src/geoguessr_mcp/models/UserStats.py
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
"""UserStats-related data models."""
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
|
||||
@dataclass
|
||||
class UserStats:
|
||||
"""User statistics from various endpoints."""
|
||||
games_played: int = 0
|
||||
rounds_played: int = 0
|
||||
total_score: int = 0
|
||||
average_score: float = 0.0
|
||||
perfect_games: int = 0
|
||||
win_rate: float = 0.0
|
||||
streak_best: int = 0
|
||||
explorer_progress: float = 0.0
|
||||
raw_data: dict = field(default_factory=dict)
|
||||
|
||||
@classmethod
|
||||
def from_api_response(cls, data: dict) -> "UserStats":
|
||||
"""Create UserStats from API response with dynamic field mapping."""
|
||||
# Handle different response formats
|
||||
games = data.get("gamesPlayed", data.get("totalGames", data.get("games", 0)))
|
||||
rounds = data.get("roundsPlayed", data.get("totalRounds", 0))
|
||||
score = data.get("totalScore", data.get("score", 0))
|
||||
|
||||
return cls(
|
||||
games_played=games,
|
||||
rounds_played=rounds,
|
||||
total_score=score,
|
||||
average_score=data.get("averageScore", score / games if games > 0 else 0),
|
||||
perfect_games=data.get("perfectGames", data.get("fiveKs", 0)),
|
||||
win_rate=data.get("winRate", data.get("winPercentage", 0.0)),
|
||||
streak_best=data.get("bestStreak", data.get("countryStreakBest", 0)),
|
||||
explorer_progress=data.get("explorerProgress", 0.0),
|
||||
raw_data=data,
|
||||
)
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
"""Convert to dictionary."""
|
||||
return {
|
||||
"games_played": self.games_played,
|
||||
"rounds_played": self.rounds_played,
|
||||
"total_score": self.total_score,
|
||||
"average_score": round(self.average_score, 2),
|
||||
"perfect_games": self.perfect_games,
|
||||
"win_rate": round(self.win_rate, 4),
|
||||
"streak_best": self.streak_best,
|
||||
"explorer_progress": round(self.explorer_progress, 2),
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
"""Data models for GeoGuessr."""
|
||||
|
||||
from .UserProfile import UserProfile
|
||||
from .UserStats import UserStats
|
||||
from .RoundGuess import RoundGuess
|
||||
from Game import Game
|
||||
from Achievement import Achievement
|
||||
from SeasonStats import SeasonStats
|
||||
from DailyChallenge import DailyChallenge
|
||||
|
||||
__all__ = [
|
||||
"UserProfile",
|
||||
"UserStats",
|
||||
"RoundGuess",
|
||||
"Game",
|
||||
"Achievement",
|
||||
"SeasonStats",
|
||||
"DailyChallenge",
|
||||
]
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
"""Game-related data models."""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import List
|
||||
|
||||
|
||||
@dataclass
|
||||
class RoundGuess:
|
||||
"""Represents a single round guess."""
|
||||
|
||||
score: int
|
||||
distance_meters: int
|
||||
time_seconds: int
|
||||
|
||||
|
||||
@dataclass
|
||||
class Game:
|
||||
"""Represents a complete game."""
|
||||
|
||||
token: str
|
||||
map_name: str
|
||||
mode: str
|
||||
total_score: int
|
||||
rounds: List[RoundGuess]
|
||||
|
||||
@classmethod
|
||||
def from_api_response(cls, data: dict) -> "Game":
|
||||
"""Create Game from API response."""
|
||||
rounds = [
|
||||
RoundGuess(
|
||||
score=r.get("roundScoreInPoints", 0),
|
||||
distance_meters=r.get("distanceInMeters", 0),
|
||||
time_seconds=r.get("time", 0),
|
||||
)
|
||||
for r in data.get("player", {}).get("guesses", [])
|
||||
]
|
||||
|
||||
return cls(
|
||||
token=data["token"],
|
||||
map_name=data.get("map", {}).get("name", "Unknown"),
|
||||
mode=data.get("type", "Unknown"),
|
||||
total_score=sum(r.score for r in rounds),
|
||||
rounds=rounds,
|
||||
)
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
"""Profile-related data models."""
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass
|
||||
class UserProfile:
|
||||
"""User profile information."""
|
||||
|
||||
id: str
|
||||
nick: str
|
||||
email: str
|
||||
country: str
|
||||
level: int
|
||||
created: str
|
||||
is_verified: bool
|
||||
|
||||
@classmethod
|
||||
def from_api_response(cls, data: dict) -> "UserProfile":
|
||||
"""Create UserProfile from API response."""
|
||||
return cls(
|
||||
id=data["id"],
|
||||
nick=data["nick"],
|
||||
email=data.get("email", ""),
|
||||
country=data.get("country", ""),
|
||||
level=data.get("level", 0),
|
||||
created=data.get("created", ""),
|
||||
is_verified=data.get("isVerified", False),
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue