Initial commit

This commit is contained in:
scorpion21211-cmd
2026-10-03 23:29:41 -04:00
commit e24bbbda5d
10586 changed files with 1030090 additions and 0 deletions
+117
View File
@@ -0,0 +1,117 @@
@tool
extends Node
class_name CghApiClient
## Thin wrapper over HTTPRequest for the Strapi proxy endpoints.
## Must live in the editor tree (added as a child of the dock) or HTTPRequest
## returns ERR_UNCONFIGURED.
signal assets_loaded(assets: Array, meta: Dictionary)
signal categories_loaded(cats: Array)
signal request_failed(message: String)
var _http: HTTPRequest
var _cat_http: HTTPRequest
var _session_token := ""
func _ready() -> void:
_http = HTTPRequest.new()
_http.use_threads = true
_http.timeout = 20.0 # never hang forever (a stuck request blocks all later ones -> ERR_BUSY 44)
add_child(_http)
_http.request_completed.connect(_on_completed)
# Separate request node for the category tree so it never cancels an asset fetch.
_cat_http = HTTPRequest.new()
_cat_http.use_threads = true
_cat_http.timeout = 20.0
add_child(_cat_http)
_cat_http.request_completed.connect(_on_cat_completed)
func set_session_token(tok: String) -> void:
_session_token = tok
func _headers() -> PackedStringArray:
var h := PackedStringArray(["Content-Type: application/json"])
if _session_token != "":
h.append("Authorization: Bearer " + _session_token)
return h
## page is 1-based. category_slug "" = all. subcat is a SUBcategory slug (e.g. "props")
## which lives on the asset's `subcategories[]` relation, NOT `categorie` — so it needs
## a different Strapi filter. sort = Strapi sort param.
func fetch_assets(page: int, page_size: int, category_slug: String, search: String, sort := "createdAt:desc", subcat := "") -> void:
var base := CghConfig.authed_assets_url() if _session_token != "" else CghConfig.public_assets_url()
var q := "?addonSlug=%s&pagination[page]=%d&pagination[pageSize]=%d&populate=*" % [
CghConfig.ADDON_SLUG, page, page_size]
if sort != "":
q += "&sort=" + sort.uri_encode()
# Blender-exact: one slug, matched against EITHER the top-level category OR a
# subcategory ($or). So "props" (a 3d-models subcategory) is found correctly.
var sel := subcat if subcat != "" else category_slug
if sel != "":
var es := sel.uri_encode()
q += "&filters[$or][0][categorie][Slug][$eq]=" + es
q += "&filters[$or][1][subcategories][Slug][$eq]=" + es
else:
# "All" tab — the Godot-usable categories only (3d-models / hdri / flipbooks + their
# subcategories). Match EITHER the top-level categorie OR the subcategories relation for
# EACH usable slug, exactly like the individual tabs do (those work reliably). The old
# `categorie.Slug $in [...]` form matched only the top level and behaved inconsistently on
# the authenticated proxy, so "All" showed only a handful of assets.
var slugs: Array = []
var usable := {}
for cat in CghConfig.CATEGORIES:
if cat["slug"] != "":
usable[cat["slug"]] = true
slugs.append(cat["slug"])
for parent in CghConfig.SUBCATEGORIES.keys():
if not usable.has(parent):
continue
for s in CghConfig.SUBCATEGORIES[parent]:
slugs.append(s["slug"])
var oi := 0
for cs in slugs:
var es := str(cs).uri_encode()
q += "&filters[$or][%d][categorie][Slug][$eq]=%s" % [oi, es]; oi += 1
q += "&filters[$or][%d][subcategories][Slug][$eq]=%s" % [oi, es]; oi += 1
if search.strip_edges() != "":
q += "&filters[Title][$containsi]=" + search.strip_edges().uri_encode()
# Drop any stale/in-flight request first, otherwise a previous (slow) request
# leaves the node busy and this one fails with ERR_BUSY (44).
_http.cancel_request()
var err := _http.request(base + q, _headers(), HTTPClient.METHOD_GET)
if err != OK:
request_failed.emit("Request could not start (err %d)" % err)
func _on_completed(result: int, code: int, _headers_in: PackedStringArray, body: PackedByteArray) -> void:
if result != HTTPRequest.RESULT_SUCCESS:
request_failed.emit("Network error (result %d)" % result)
return
if code < 200 or code >= 300:
request_failed.emit("Server returned HTTP %d" % code)
return
var parsed = JSON.parse_string(body.get_string_from_utf8())
if typeof(parsed) != TYPE_DICTIONARY:
request_failed.emit("Bad JSON from server")
return
var data = parsed.get("data", [])
var meta = parsed.get("meta", {})
assets_loaded.emit(data if data is Array else [], meta if meta is Dictionary else {})
## Fetch the category tree so the UI can build an accurate subcategory dropdown from the
## live backend (like the Blender addon), not a hardcoded list. Non-fatal on failure.
func fetch_categories() -> void:
var base := CghConfig.authed_categories_url() if _session_token != "" else CghConfig.public_categories_url()
var q := "?addonSlug=%s&pagination[pageSize]=200&populate=*" % CghConfig.ADDON_SLUG
_cat_http.cancel_request()
var err := _cat_http.request(base + q, _headers(), HTTPClient.METHOD_GET)
if err != OK:
return # keep the hardcoded subcategory fallback
func _on_cat_completed(result: int, code: int, _h: PackedStringArray, body: PackedByteArray) -> void:
if result != HTTPRequest.RESULT_SUCCESS or code < 200 or code >= 300:
return
var parsed = JSON.parse_string(body.get_string_from_utf8())
if typeof(parsed) != TYPE_DICTIONARY:
return
var data = parsed.get("data", [])
categories_loaded.emit(data if data is Array else [])