Add password login and collection visibility settings
All checks were successful
Build plugin / build (push) Successful in 1m10s
All checks were successful
Build plugin / build (push) Successful in 1m10s
This commit is contained in:
parent
173aa94ea9
commit
e2a4464d0d
5 changed files with 218 additions and 26 deletions
|
|
@ -371,6 +371,21 @@ func _login_with_api_key(api_key: String) -> void:
|
|||
emit_signal.call_deferred("logged_in", LOGIN_STATUS.OK, profile)
|
||||
|
||||
|
||||
func login_with_password(username: String, password: String) -> void:
|
||||
await thread_group.exec(_login_with_password.bind(username, password))
|
||||
|
||||
|
||||
func _login_with_password(username: String, password: String) -> void:
|
||||
var res := await _rpc_call("Profile.LoginWithPassword", {"username": username, "password": password})
|
||||
if "error" in res:
|
||||
is_logged_in = false
|
||||
emit_signal.call_deferred("logged_in", LOGIN_STATUS.INVALID_KEY, {})
|
||||
return
|
||||
profile = res.get("profile", {})
|
||||
is_logged_in = true
|
||||
emit_signal.call_deferred("logged_in", LOGIN_STATUS.OK, profile)
|
||||
|
||||
|
||||
## Returns every game the logged-in profile owns a download key for.
|
||||
## Each item looks like: {"downloadKey": {...}, "game": {...}}
|
||||
func get_owned_games() -> Array:
|
||||
|
|
@ -429,6 +444,38 @@ func _get_collection_games() -> Array:
|
|||
)
|
||||
|
||||
|
||||
## Like get_collection_games, but returns {id, title, games} per collection
|
||||
## so the library can filter by collection visibility settings.
|
||||
func get_collection_groups() -> Array:
|
||||
return await thread_group.exec(_get_collection_groups)
|
||||
|
||||
|
||||
func _get_collection_groups() -> 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:
|
||||
return []
|
||||
if res.get("stale", false):
|
||||
params["fresh"] = true
|
||||
res = await _rpc_call("Fetch.ProfileCollections", params)
|
||||
if "error" in res:
|
||||
return []
|
||||
var collections: Array = res.get("items", [])
|
||||
var groups := []
|
||||
for c in collections:
|
||||
var collection: Dictionary = c
|
||||
var col_id: int = collection.get("id", 0)
|
||||
var col_title: String = collection.get("title", "")
|
||||
var games := await _fetch_collection_games(
|
||||
profile.get("user", {}).get("id", profile.get("id", 0)),
|
||||
[collection]
|
||||
)
|
||||
groups.append({"id": col_id, "title": col_title, "games": games})
|
||||
return groups
|
||||
|
||||
|
||||
## 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.
|
||||
|
|
|
|||
|
|
@ -16,21 +16,34 @@ const icon := preload("res://plugins/itch/assets/itch.svg")
|
|||
|
||||
@onready var status_label: Label = $%StatusLabel
|
||||
@onready var api_key_box: LineEdit = $%ApiKeyInput
|
||||
@onready var username_box: LineEdit = $%UsernameInput
|
||||
@onready var password_box: LineEdit = $%PasswordInput
|
||||
@onready var login_method: OptionButton = $%LoginMethod
|
||||
@onready var save_button: Button = $%SaveButton
|
||||
@onready var help_label: Label = $%HelpLabel
|
||||
@onready var filter_check: CheckBox = $%FilterUnsupported
|
||||
@onready var show_purchases_check: CheckBox = $%ShowPurchases
|
||||
@onready var collections_container: VBoxContainer = $%CollectionsContainer
|
||||
|
||||
@onready var itch: ItchClient = get_tree().get_first_node_in_group("itch_client")
|
||||
|
||||
var _collection_checks: Dictionary = {}
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
var api_key := settings_manager.get_value("plugin.itch", "api_key", "") as String
|
||||
api_key_box.text = api_key
|
||||
api_key_box.secret = true
|
||||
|
||||
filter_check.button_pressed = settings_manager.get_value("plugin.itch", "filter_unsupported", true) as bool
|
||||
var saved_method: int = settings_manager.get_value("plugin.itch", "login_method", 0) as int
|
||||
login_method.selected = saved_method
|
||||
_on_login_method_changed(saved_method)
|
||||
|
||||
help_label.text = "Get an API key from https://itch.io/user/settings/api-keys"
|
||||
var username := settings_manager.get_value("plugin.itch", "username", "") as String
|
||||
username_box.text = username
|
||||
|
||||
filter_check.button_pressed = settings_manager.get_value("plugin.itch", "filter_unsupported", true) as bool
|
||||
show_purchases_check.button_pressed = settings_manager.get_value("plugin.itch", "show_purchases", true) as bool
|
||||
|
||||
_update_status()
|
||||
itch.client_ready.connect(_update_status)
|
||||
|
|
@ -38,6 +51,11 @@ func _ready() -> void:
|
|||
|
||||
save_button.pressed.connect(_on_save_button)
|
||||
filter_check.toggled.connect(_on_filter_toggled)
|
||||
login_method.item_selected.connect(_on_login_method_changed)
|
||||
show_purchases_check.toggled.connect(_on_show_purchases_toggled)
|
||||
|
||||
# Build collection checkboxes once we're logged in.
|
||||
itch.logged_in.connect(_populate_collections.bind(), CONNECT_ONE_SHOT)
|
||||
|
||||
|
||||
func _update_status() -> void:
|
||||
|
|
@ -62,16 +80,82 @@ func _on_login(status: ItchClient.LOGIN_STATUS, _profile: Dictionary) -> void:
|
|||
if status == ItchClient.LOGIN_STATUS.OK:
|
||||
notify.text = "Successfully logged in to itch.io"
|
||||
else:
|
||||
notify.text = "itch.io login failed. Double check your API key."
|
||||
notify.text = "itch.io login failed. Double check your credentials."
|
||||
notification_manager.show(notify)
|
||||
|
||||
|
||||
func _on_login_method_changed(idx: int) -> void:
|
||||
# API key mode: show API key input, hide user/pass.
|
||||
# Password mode: show user/pass, hide API key.
|
||||
var is_api := idx == 0
|
||||
api_key_box.visible = is_api
|
||||
username_box.visible = not is_api
|
||||
password_box.visible = not is_api
|
||||
help_label.visible = is_api
|
||||
|
||||
|
||||
func _on_save_button() -> void:
|
||||
var api_key: String = api_key_box.text.strip_edges()
|
||||
settings_manager.set_value("plugin.itch", "api_key", api_key)
|
||||
if api_key == "":
|
||||
var method: int = login_method.selected
|
||||
settings_manager.set_value("plugin.itch", "login_method", method)
|
||||
|
||||
if method == 0:
|
||||
# API key login
|
||||
var api_key: String = api_key_box.text.strip_edges()
|
||||
settings_manager.set_value("plugin.itch", "api_key", api_key)
|
||||
if api_key == "":
|
||||
return
|
||||
itch.login_with_api_key(api_key)
|
||||
else:
|
||||
# Username/password login
|
||||
var username: String = username_box.text.strip_edges()
|
||||
var password: String = password_box.text
|
||||
settings_manager.set_value("plugin.itch", "username", username)
|
||||
if username == "" or password == "":
|
||||
return
|
||||
itch.login_with_password(username, password)
|
||||
|
||||
|
||||
## Fetches the user's collections and builds a checkbox for each one.
|
||||
func _populate_collections(_status: ItchClient.LOGIN_STATUS, _profile: Dictionary) -> void:
|
||||
if not itch.is_logged_in:
|
||||
return
|
||||
itch.login_with_api_key(api_key)
|
||||
var profile_id: int = itch.profile.get("user", {}).get("id", itch.profile.get("id", 0))
|
||||
var res := await itch._rpc_call("Fetch.ProfileCollections", {"profileId": profile_id})
|
||||
if "error" in res:
|
||||
return
|
||||
var collections: Array = res.get("items", [])
|
||||
|
||||
# Load saved visibility state.
|
||||
var hidden: Dictionary = settings_manager.get_value("plugin.itch", "hidden_collections", {}) as Dictionary
|
||||
|
||||
# Clear old checkboxes.
|
||||
for child in collections_container.get_children():
|
||||
child.queue_free()
|
||||
_collection_checks.clear()
|
||||
|
||||
for c in collections:
|
||||
var collection: Dictionary = c
|
||||
var col_name: String = collection.get("title", "Collection " + str(collection.get("id", "")))
|
||||
var col_id: String = str(collection.get("id", 0))
|
||||
var check := CheckBox.new()
|
||||
check.text = col_name
|
||||
check.button_pressed = not hidden.has(col_id)
|
||||
check.toggled.connect(_on_collection_toggled.bind(col_id))
|
||||
collections_container.add_child(check)
|
||||
_collection_checks[col_id] = check
|
||||
|
||||
|
||||
func _on_collection_toggled(pressed: bool, col_id: String) -> void:
|
||||
var hidden: Dictionary = settings_manager.get_value("plugin.itch", "hidden_collections", {}) as Dictionary
|
||||
if pressed:
|
||||
hidden.erase(col_id)
|
||||
else:
|
||||
hidden[col_id] = true
|
||||
settings_manager.set_value("plugin.itch", "hidden_collections", hidden)
|
||||
|
||||
|
||||
func _on_show_purchases_toggled(pressed: bool) -> void:
|
||||
settings_manager.set_value("plugin.itch", "show_purchases", pressed)
|
||||
|
||||
|
||||
## Persists the platform filter and reloads the itch.io library so the
|
||||
|
|
|
|||
|
|
@ -17,9 +17,9 @@ anchor_top = 0.5
|
|||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -200.0
|
||||
offset_top = -60.0
|
||||
offset_top = -120.0
|
||||
offset_right = 200.0
|
||||
offset_bottom = 60.0
|
||||
offset_bottom = 120.0
|
||||
|
||||
[node name="TitleLabel" type="Label" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
|
@ -31,11 +31,31 @@ unique_name_in_owner = true
|
|||
layout_mode = 2
|
||||
text = "Status: starting butlerd..."
|
||||
|
||||
[node name="LoginMethod" type="OptionButton" parent="VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
item_count = 2
|
||||
popup/item_0_text = "API Key"
|
||||
popup/item_1_text = "Username & Password"
|
||||
|
||||
[node name="ApiKeyInput" type="LineEdit" parent="VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
placeholder_text = "itch.io API key"
|
||||
|
||||
[node name="UsernameInput" type="LineEdit" parent="VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
placeholder_text = "Username"
|
||||
visible = false
|
||||
|
||||
[node name="PasswordInput" type="LineEdit" parent="VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
placeholder_text = "Password"
|
||||
secret = true
|
||||
visible = false
|
||||
|
||||
[node name="HelpLabel" type="Label" parent="VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
|
|
@ -47,6 +67,21 @@ unique_name_in_owner = true
|
|||
layout_mode = 2
|
||||
text = "Only show games available on this platform"
|
||||
|
||||
[node name="ShowPurchases" type="CheckBox" parent="VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Show main purchases"
|
||||
button_pressed = true
|
||||
|
||||
[node name="CollectionsLabel" type="Label" parent="VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Show collections:"
|
||||
|
||||
[node name="CollectionsContainer" type="VBoxContainer" parent="VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
|
||||
[node name="SaveButton" type="Button" parent="VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
|
|
|
|||
|
|
@ -558,9 +558,14 @@ 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()
|
||||
var show_purchases: bool = settings_manager.get_value("plugin.itch", "show_purchases", true) as bool
|
||||
var hidden_collections: Dictionary = settings_manager.get_value("plugin.itch", "hidden_collections", {}) as Dictionary
|
||||
|
||||
var owned: Array = []
|
||||
if show_purchases:
|
||||
owned = await itch.get_owned_games()
|
||||
var caves: Variant = await itch.get_caves()
|
||||
var collection_games: Array = await itch.get_collection_games()
|
||||
var collection_groups: Array = await itch.get_collection_groups()
|
||||
|
||||
# Clean up "orphan" caves (butler.db entries whose install folder was wiped
|
||||
# by a plugin update) in the background so a later Install.Queue doesn't
|
||||
|
|
@ -595,16 +600,21 @@ func _load_library(caching_flags: int = Cache.FLAGS.LOAD | Cache.FLAGS.SAVE) ->
|
|||
|
||||
# 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)
|
||||
if game_id in seen_game_ids:
|
||||
for group in collection_groups:
|
||||
var group_dict: Dictionary = group
|
||||
var col_id: String = str(group_dict.get("id", 0))
|
||||
if hidden_collections.has(col_id):
|
||||
continue
|
||||
var item: Variant = _make_item(game, caves_by_game_id)
|
||||
if item == null:
|
||||
continue
|
||||
seen_game_ids[game_id] = true
|
||||
items.append(item)
|
||||
for g in group_dict.get("games", []):
|
||||
var game: Dictionary = g
|
||||
var game_id: int = game.get("id", 0)
|
||||
if game_id in seen_game_ids:
|
||||
continue
|
||||
var item: Variant = _make_item(game, caves_by_game_id)
|
||||
if item == null:
|
||||
continue
|
||||
seen_game_ids[game_id] = true
|
||||
items.append(item)
|
||||
|
||||
if caching_flags & Cache.FLAGS.SAVE:
|
||||
logger.debug("Saving itch.io apps to cache.")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue