Merge games from the profile's itch.io collections into the library (deduped against owned games) so bookmarked free games show up
This commit is contained in:
parent
9cd4cb32e3
commit
e9f25d104b
3 changed files with 106 additions and 23 deletions
|
|
@ -387,6 +387,68 @@ func _get_owned_games() -> Array:
|
||||||
return res.get("items", [])
|
return res.get("items", [])
|
||||||
|
|
||||||
|
|
||||||
|
## Returns every game present in any of the profile's collections, as full
|
||||||
|
## Game dicts (CollectionGame embeds the whole game object). These aren't
|
||||||
|
## necessarily owned: the user may have bookmarked free games in a collection.
|
||||||
|
func get_collection_games() -> Array:
|
||||||
|
return await thread_group.exec(_get_collection_games)
|
||||||
|
|
||||||
|
|
||||||
|
func _get_collection_games() -> Array:
|
||||||
|
if not is_logged_in:
|
||||||
|
return []
|
||||||
|
var params := {"profileId": profile.get("user", {}).get("id", profile.get("id", 0))}
|
||||||
|
var res := await _rpc_call("Fetch.ProfileCollections", params)
|
||||||
|
if "error" in res:
|
||||||
|
logger.warn("Fetch.ProfileCollections failed: " + str(res["error"]))
|
||||||
|
return []
|
||||||
|
if res.get("stale", false):
|
||||||
|
logger.info("Collections are stale. Refreshing from itch.io.")
|
||||||
|
params["fresh"] = true
|
||||||
|
res = await _rpc_call("Fetch.ProfileCollections", params)
|
||||||
|
if "error" in res:
|
||||||
|
logger.warn("Fetch.ProfileCollections (fresh) failed: " + str(res["error"]))
|
||||||
|
return []
|
||||||
|
return await _fetch_collection_games(
|
||||||
|
profile.get("user", {}).get("id", profile.get("id", 0)),
|
||||||
|
res.get("items", [])
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
## Paginates through Fetch.Collection.Games for every collection, collecting the
|
||||||
|
## embedded game objects. Each collection can span multiple pages (cursor), and
|
||||||
|
## a page served from butlerd's local cache is re-issued fresh.
|
||||||
|
func _fetch_collection_games(profile_id: int, collections: Array) -> Array:
|
||||||
|
var games := []
|
||||||
|
for c in collections:
|
||||||
|
var collection: Dictionary = c
|
||||||
|
var collection_id: int = collection.get("id", 0)
|
||||||
|
var cursor: Variant = null
|
||||||
|
while true:
|
||||||
|
var params := {"profileId": profile_id, "collectionId": collection_id}
|
||||||
|
if cursor != null:
|
||||||
|
params["cursor"] = cursor
|
||||||
|
var res := await _rpc_call("Fetch.Collection.Games", params)
|
||||||
|
if "error" in res:
|
||||||
|
logger.warn("Fetch.Collection.Games failed for collection " + str(collection_id) + ": " + str(res["error"]))
|
||||||
|
break
|
||||||
|
if res.get("stale", false):
|
||||||
|
params["fresh"] = true
|
||||||
|
res = await _rpc_call("Fetch.Collection.Games", params)
|
||||||
|
if "error" in res:
|
||||||
|
logger.warn("Fetch.Collection.Games (fresh) failed for collection " + str(collection_id) + ": " + str(res["error"]))
|
||||||
|
break
|
||||||
|
for cg in res.get("items", []):
|
||||||
|
var collection_game: Dictionary = cg
|
||||||
|
var game: Dictionary = collection_game.get("game", {})
|
||||||
|
if not game.is_empty():
|
||||||
|
games.append(game)
|
||||||
|
cursor = res.get("nextCursor", null)
|
||||||
|
if cursor == null:
|
||||||
|
break
|
||||||
|
return games
|
||||||
|
|
||||||
|
|
||||||
## Returns every installed game (a "cave" in butlerd terminology).
|
## Returns every installed game (a "cave" in butlerd terminology).
|
||||||
func get_caves() -> Array:
|
func get_caves() -> Array:
|
||||||
return await thread_group.exec(_get_caves)
|
return await thread_group.exec(_get_caves)
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,33 @@ func _on_install_progressed(_id: String, _current: int, _total: int) -> void:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
## Builds a LibraryLaunchItem for a game, or null when the entry isn't an
|
||||||
|
## actual game (itch.io also hosts tools, assets, soundtracks, comics, ...).
|
||||||
|
func _make_item(game: Dictionary, caves_by_game_id: Dictionary) -> Variant:
|
||||||
|
if game.is_empty():
|
||||||
|
return null
|
||||||
|
if game.get("classification", "game") != "game":
|
||||||
|
return null
|
||||||
|
var game_id: int = game.get("id", 0)
|
||||||
|
var cave: Dictionary = caves_by_game_id.get(game_id, {})
|
||||||
|
|
||||||
|
var item := LibraryLaunchItem.new()
|
||||||
|
item.provider_app_id = cave.get("id", "")
|
||||||
|
item.name = game.get("title", "Unknown itch.io game")
|
||||||
|
item.tags = ["itch"]
|
||||||
|
item.categories = ["Game"]
|
||||||
|
item.installed = not cave.is_empty()
|
||||||
|
item.metadata = {"game": game}
|
||||||
|
|
||||||
|
if not cave.is_empty():
|
||||||
|
var install_folder: String = cave.get("installInfo", {}).get("installFolder", "")
|
||||||
|
var exe := _find_executable(install_folder)
|
||||||
|
item.command = exe
|
||||||
|
item.args = []
|
||||||
|
item.cwd = install_folder
|
||||||
|
return item
|
||||||
|
|
||||||
|
|
||||||
## Builds the full itch.io library: owned games merged with install state
|
## Builds the full itch.io library: owned games merged with install state
|
||||||
## from Fetch.Caves. Uses the standard Cache system so we don't hammer
|
## from Fetch.Caves. Uses the standard Cache system so we don't hammer
|
||||||
## butlerd (and, transitively, the itch.io API) on every library refresh.
|
## butlerd (and, transitively, the itch.io API) on every library refresh.
|
||||||
|
|
@ -87,6 +114,7 @@ func _load_library(caching_flags: int = Cache.FLAGS.LOAD | Cache.FLAGS.SAVE) ->
|
||||||
logger.info("Fetching itch.io library...")
|
logger.info("Fetching itch.io library...")
|
||||||
var owned: Array = await itch.get_owned_games()
|
var owned: Array = await itch.get_owned_games()
|
||||||
var caves: Array = await itch.get_caves()
|
var caves: Array = await itch.get_caves()
|
||||||
|
var collection_games: Array = await itch.get_collection_games()
|
||||||
|
|
||||||
var caves_by_game_id := {}
|
var caves_by_game_id := {}
|
||||||
for c in caves:
|
for c in caves:
|
||||||
|
|
@ -95,34 +123,27 @@ func _load_library(caching_flags: int = Cache.FLAGS.LOAD | Cache.FLAGS.SAVE) ->
|
||||||
caves_by_game_id[game_id] = cave
|
caves_by_game_id[game_id] = cave
|
||||||
|
|
||||||
var items := [] as Array[LibraryLaunchItem]
|
var items := [] as Array[LibraryLaunchItem]
|
||||||
|
var seen_game_ids := {}
|
||||||
for o in owned:
|
for o in owned:
|
||||||
var owned_entry: Dictionary = o
|
var owned_entry: Dictionary = o
|
||||||
var game: Dictionary = owned_entry.get("game", {})
|
var game: Dictionary = owned_entry.get("game", {})
|
||||||
if game.is_empty():
|
var item := _make_item(game, caves_by_game_id)
|
||||||
continue
|
if item == null:
|
||||||
# Only surface actual games (itch.io also hosts tools, assets,
|
|
||||||
# soundtracks, comics, etc. under the same ownership API).
|
|
||||||
if game.get("classification", "game") != "game":
|
|
||||||
continue
|
continue
|
||||||
|
seen_game_ids[game.get("id", 0)] = true
|
||||||
|
items.append(item)
|
||||||
|
|
||||||
|
# Collection games aren't necessarily owned (e.g. a free game the user
|
||||||
|
# bookmarked), so merge them in too, deduped against owned games.
|
||||||
|
for g in collection_games:
|
||||||
|
var game: Dictionary = g
|
||||||
var game_id: int = game.get("id", 0)
|
var game_id: int = game.get("id", 0)
|
||||||
var cave: Dictionary = caves_by_game_id.get(game_id, {})
|
if game_id in seen_game_ids:
|
||||||
|
continue
|
||||||
var item := LibraryLaunchItem.new()
|
var item := _make_item(game, caves_by_game_id)
|
||||||
item.provider_app_id = cave.get("id", "")
|
if item == null:
|
||||||
item.name = game.get("title", "Unknown itch.io game")
|
continue
|
||||||
item.tags = ["itch"]
|
seen_game_ids[game_id] = true
|
||||||
item.categories = ["Game"]
|
|
||||||
item.installed = not cave.is_empty()
|
|
||||||
item.metadata = {"game": game}
|
|
||||||
|
|
||||||
if not cave.is_empty():
|
|
||||||
var install_folder: String = cave.get("installInfo", {}).get("installFolder", "")
|
|
||||||
var exe := _find_executable(install_folder)
|
|
||||||
item.command = exe
|
|
||||||
item.args = []
|
|
||||||
item.cwd = install_folder
|
|
||||||
|
|
||||||
items.append(item)
|
items.append(item)
|
||||||
|
|
||||||
if caching_flags & Cache.FLAGS.SAVE:
|
if caching_flags & Cache.FLAGS.SAVE:
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"plugin.id": "itch",
|
"plugin.id": "itch",
|
||||||
"plugin.name": "itch.io",
|
"plugin.name": "itch.io",
|
||||||
"plugin.version": "0.1.5",
|
"plugin.version": "0.1.6",
|
||||||
"plugin.min-api-version": "1.1.0",
|
"plugin.min-api-version": "1.1.0",
|
||||||
"plugin.link": "https://github.com/YOUR_USERNAME/OpenGamepadUI-itch",
|
"plugin.link": "https://github.com/YOUR_USERNAME/OpenGamepadUI-itch",
|
||||||
"plugin.source": "https://github.com/YOUR_USERNAME/OpenGamepadUI-itch",
|
"plugin.source": "https://github.com/YOUR_USERNAME/OpenGamepadUI-itch",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue