diff --git a/app/marketguard_api/player_service.py b/app/marketguard_api/player_service.py index f37b67e..829b324 100644 --- a/app/marketguard_api/player_service.py +++ b/app/marketguard_api/player_service.py @@ -424,6 +424,10 @@ def _bank_balance(profile: dict[str, Any]) -> float | None: def _coin_purse(member: dict[str, Any]) -> float | None: + # Hypixel's v2 profile format keeps the purse under "currencies"; older payloads had it at the member root. + currencies = member.get("currencies") + if isinstance(currencies, dict) and "coin_purse" in currencies: + return _non_negative_number(currencies.get("coin_purse")) return _non_negative_number(member.get("coin_purse")) @@ -580,25 +584,29 @@ def _known_total(bank: float | None, purse: float | None, museum_value: object) def _inventory(member: dict[str, Any], *keys: str) -> list[dict[str, object]] | None: - for key in keys: - inventory = member.get(key) - if not isinstance(inventory, dict): + # Hypixel's v2 profile format nests the inventories under "inventory"; older payloads had them at the member root. + for container in (member.get("inventory"), member): + if not isinstance(container, dict): continue - encoded = inventory.get("data") - if not isinstance(encoded, str) or not encoded.strip(): - continue - parsed = parse_inventory_nbt(encoded) - if parsed is None: - return None - return [ - { - "slot": item.slot, - "id": item.item_id, - "name": item.name, - "count": item.count, - } - for item in parsed - ] + for key in keys: + inventory = container.get(key) + if not isinstance(inventory, dict): + continue + encoded = inventory.get("data") + if not isinstance(encoded, str) or not encoded.strip(): + continue + parsed = parse_inventory_nbt(encoded) + if parsed is None: + return None + return [ + { + "slot": item.slot, + "id": item.item_id, + "name": item.name, + "count": item.count, + } + for item in parsed + ] return None diff --git a/tests/test_marketguard_api.py b/tests/test_marketguard_api.py index debd155..ed54a46 100644 --- a/tests/test_marketguard_api.py +++ b/tests/test_marketguard_api.py @@ -291,16 +291,18 @@ async def _hypixel_handler(request: httpx.Request) -> httpx.Response: "banking": {"balance": 125_000_000}, "members": { player_uuid: { - "coin_purse": 4_250_000.5, - "equippment_contents": { - "data": _encode_inventory_bytes( - [("GAUNTLET_OF_CONTAGION", "Gauntlet of Contagion", 1)] - ) - }, - "inv_armor": { - "data": _encode_inventory_bytes( - [("NECRON_HELMET", "Necron's Helmet", 1)] - ) + "currencies": {"coin_purse": 4_250_000.5}, + "inventory": { + "equipment_contents": { + "data": _encode_inventory_bytes( + [("GAUNTLET_OF_CONTAGION", "Gauntlet of Contagion", 1)] + ) + }, + "inv_armor": { + "data": _encode_inventory_bytes( + [("NECRON_HELMET", "Necron's Helmet", 1)] + ) + }, }, "pets_data": { "pets": [ @@ -3562,3 +3564,18 @@ async def add_page(self, auctions) -> None: f"{live['peak']} pages were in flight at once, " f"max_parallel_pages is {settings.max_parallel_pages}" ) + + +def test_member_purse_and_inventories_accept_v2_and_legacy_layouts() -> None: + from app.marketguard_api.player_service import _coin_purse, _inventory + + armor = {"data": _encode_inventory_bytes([("NECRON_HELMET", "Necron's Helmet", 1)])} + v2_member = {"currencies": {"coin_purse": 12.5}, "inventory": {"inv_armor": armor}} + legacy_member = {"coin_purse": 7.0, "inv_armor": armor} + + assert _coin_purse(v2_member) == 12.5 + assert _coin_purse(legacy_member) == 7.0 + assert _coin_purse({"currencies": {}}) is None + assert _inventory(v2_member, "inv_armor")[0]["id"] == "NECRON_HELMET" + assert _inventory(legacy_member, "inv_armor")[0]["id"] == "NECRON_HELMET" + assert _inventory({"inventory": {}}, "inv_armor") is None