diff --git a/src/geoguessr_mcp/models/Achievement.py b/src/geoguessr_mcp/models/Achievement.py new file mode 100644 index 0000000..6360866 --- /dev/null +++ b/src/geoguessr_mcp/models/Achievement.py @@ -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")), + ) diff --git a/src/geoguessr_mcp/models/DailyChallenge.py b/src/geoguessr_mcp/models/DailyChallenge.py new file mode 100644 index 0000000..ddd3fa9 --- /dev/null +++ b/src/geoguessr_mcp/models/DailyChallenge.py @@ -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, + ) diff --git a/src/geoguessr_mcp/models/Game.py b/src/geoguessr_mcp/models/Game.py new file mode 100644 index 0000000..d4dc48d --- /dev/null +++ b/src/geoguessr_mcp/models/Game.py @@ -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, + } diff --git a/src/geoguessr_mcp/models/RoundGuess.py b/src/geoguessr_mcp/models/RoundGuess.py new file mode 100644 index 0000000..6d20bcd --- /dev/null +++ b/src/geoguessr_mcp/models/RoundGuess.py @@ -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", ""), + ) diff --git a/src/geoguessr_mcp/models/SeasonStats.py b/src/geoguessr_mcp/models/SeasonStats.py new file mode 100644 index 0000000..5b575f8 --- /dev/null +++ b/src/geoguessr_mcp/models/SeasonStats.py @@ -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, + ) diff --git a/src/geoguessr_mcp/models/UserProfile.py b/src/geoguessr_mcp/models/UserProfile.py new file mode 100644 index 0000000..8856d38 --- /dev/null +++ b/src/geoguessr_mcp/models/UserProfile.py @@ -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, + } diff --git a/src/geoguessr_mcp/models/UserStats.py b/src/geoguessr_mcp/models/UserStats.py new file mode 100644 index 0000000..26c7744 --- /dev/null +++ b/src/geoguessr_mcp/models/UserStats.py @@ -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), + } diff --git a/src/geoguessr_mcp/models/__init__.py b/src/geoguessr_mcp/models/__init__.py index e69de29..1f3b05c 100644 --- a/src/geoguessr_mcp/models/__init__.py +++ b/src/geoguessr_mcp/models/__init__.py @@ -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", +] \ No newline at end of file diff --git a/src/geoguessr_mcp/models/game.py b/src/geoguessr_mcp/models/game.py deleted file mode 100644 index a2c9724..0000000 --- a/src/geoguessr_mcp/models/game.py +++ /dev/null @@ -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, - ) diff --git a/src/geoguessr_mcp/models/profile.py b/src/geoguessr_mcp/models/profile.py deleted file mode 100644 index 48389a9..0000000 --- a/src/geoguessr_mcp/models/profile.py +++ /dev/null @@ -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), - ) diff --git a/src/geoguessr_mcp/models/stats.py b/src/geoguessr_mcp/models/stats.py deleted file mode 100644 index e69de29..0000000