import igdb import library_api STATUSES = ["played", "playing", "planning", "replayable"] STATUS_ORDER = {"playing": 0, "replayable": 1, "played": 2, "planning": 3} PROFILE_FLAGS = {"favorite": "favorite", "liked": "liked"} SUGGESTED_TAGS = ["Casual", "Competitive", "Love it", "Open to play", "Looking to discuss"] def check_rating(rating: int | None) -> int | None: if rating is not None and not 0 <= rating <= 10: raise ValueError("Rating must be between 0 and 10") return rating def check_status(status: str) -> str: if status not in STATUSES: raise ValueError(f"Status must be one of {', '.join(STATUSES)}") return status def list_games() -> list[dict]: games = library_api.fetch_library()["games"] rating = lambda game: game["rating"] if game.get("rating") is not None else -1 return sorted(games, key=lambda game: (STATUS_ORDER[game["status"]], -rating(game), game["name"].lower())) def lookup(link: str | None, name: str | None) -> dict: return igdb.details(igdb.find_game(link or None, name or None)) def add_game(link: str | None, name: str | None, status: str = "played", rating: int | None = None, image: str | None = None) -> dict: if not link and not name: raise ValueError("Enter a store link, an IGDB link or a name") check_status(status) check_rating(rating) found = lookup(link, name) identifier = found.pop("igdbId") if any(game["igdbId"] == identifier for game in list_games()): raise ValueError(f"{found['name']} is already listed") is_store_link = bool(link) and not igdb.IGDB_SLUG_PATTERN.search(link) fields = { **found, "name": name or found["name"], "url": link if is_store_link else None, "status": status, "rating": rating, } if image: fields["imageSource"] = image return library_api.save_game(identifier, fields) def update_game(identifier: str, fields: dict) -> dict: return library_api.save_game(identifier, fields) def rate_game(identifier: str, rating: int | None) -> dict: return update_game(identifier, {"rating": check_rating(rating)}) def set_status(identifier: str, status: str) -> dict: return update_game(identifier, {"status": check_status(status)}) def set_dates(identifier: str, started: str | None, finished: str | None) -> dict: return update_game(identifier, {"started": started, "finished": finished}) def set_links(identifier: str, url: str | None, image: str | None) -> dict: fields = {"url": url} if image: fields["imageSource"] = image return update_game(identifier, fields) def toggle_flag(game: dict, flag: str) -> dict: if flag not in PROFILE_FLAGS: raise ValueError(f"{flag} is not one of {', '.join(PROFILE_FLAGS)}") return update_game(game["igdbId"], {flag: not game.get(flag, False)}) def set_tags(identifier: str, tags_text: str | None, note: str | None) -> dict: tags = [tag.strip() for tag in (tags_text or "").split(",") if tag.strip()] return update_game(identifier, {"tags": tags, "note": note or None}) def profile_text(game: dict) -> str: return ", ".join(label for flag, label in PROFILE_FLAGS.items() if game.get(flag)) or "-" def remove_game(identifier: str) -> None: library_api.delete_game(identifier) def refresh_game(game: dict) -> dict: result = igdb.game_by_id(game["igdbId"]) if not result: raise ValueError(f"{game['name']} is no longer on IGDB") found = igdb.details(result) found.pop("igdbId", None) found.pop("name", None) if game.get("image"): found.pop("imageSource", None) return update_game(game["igdbId"], found)