diff --git a/core/itch_client.gd b/core/itch_client.gd index b67a2c5..60c8c02 100644 --- a/core/itch_client.gd +++ b/core/itch_client.gd @@ -184,7 +184,7 @@ func _thread_process(_delta: float) -> void: var line: String = lines[i].strip_edges() if line == "": continue - var parsed: Variant = JSON.parse_string(line) + var parsed: Variant = _normalize_json(JSON.parse_string(line)) if typeof(parsed) != TYPE_DICTIONARY: continue var msg: Dictionary = parsed @@ -210,7 +210,7 @@ func _thread_process(_delta: float) -> void: _recv_buffer = _recv_buffer.substr(idx + 1) if line.strip_edges() == "": continue - var parsed: Variant = JSON.parse_string(line) + var parsed: Variant = _normalize_json(JSON.parse_string(line)) if typeof(parsed) != TYPE_DICTIONARY: continue var msg: Dictionary = parsed @@ -261,6 +261,33 @@ func _send_line(text: String) -> void: socket.put_data((text + "\n").to_utf8_buffer()) +## Godot's JSON.parse decodes every JSON number as a float (so 7670 becomes +## 7670.0), and JSON.stringify then re-serializes it as "7670.0". butlerd's Go +## structs use int64 for ids, and Go's encoding/json rejects "7670.0" for an +## int64 field (RPC error -32700). Normalizing integral floats back to ints at +## the parse boundary keeps every id (profileId, game.id, upload.id, ...) +## typed correctly so downstream RPC params serialize without the trailing +## ".0". +func _normalize_json(value: Variant) -> Variant: + match typeof(value): + TYPE_DICTIONARY: + var out := {} + for key in value: + out[key] = _normalize_json(value[key]) + return out + TYPE_ARRAY: + var out := [] + for item in value: + out.append(_normalize_json(item)) + return out + TYPE_FLOAT: + if is_finite(value) and value == floor(value): + return int(value) + return value + _: + return value + + ## Sends a JSON-RPC request and waits (via signal await, not a busy loop, so ## [method _thread_process] keeps ticking and can deliver the response) for ## the matching response. Mirrors the `_wait_for_command` pattern used by the @@ -306,16 +333,15 @@ func _login_with_api_key(api_key: String) -> void: ## Returns every game the logged-in profile owns a download key for. ## Each item looks like: {"downloadKey": {...}, "game": {...}} -func get_owned_games(fresh: bool = false) -> Array: - return await thread_group.exec(_get_owned_games.bind(fresh)) +func get_owned_games() -> Array: + return await thread_group.exec(_get_owned_games) -func _get_owned_games(fresh: bool) -> Array: +func _get_owned_games() -> Array: if not is_logged_in: return [] var res := await _rpc_call("Fetch.ProfileOwnedKeys", { "profileId": profile.get("user", {}).get("id", profile.get("id", 0)), - "fresh": fresh, }) if "error" in res: logger.warn("Fetch.ProfileOwnedKeys failed: " + str(res["error"])) diff --git a/core/library_itch.gd b/core/library_itch.gd index f55ca06..a6fa8b1 100644 --- a/core/library_itch.gd +++ b/core/library_itch.gd @@ -84,7 +84,7 @@ func _load_library(caching_flags: int = Cache.FLAGS.LOAD | Cache.FLAGS.SAVE) -> return [] logger.info("Fetching itch.io library...") - var owned: Array = await itch.get_owned_games(false) + var owned: Array = await itch.get_owned_games() var caves: Array = await itch.get_caves() var caves_by_game_id := {}