2026-05-29 19:54:56 +08:00

179 lines
4.9 KiB
GDScript

class_name ApiClient
extends Node
const DEFAULT_TIMEOUT_SECONDS := 12.0
var base_url: String = ""
var timeout_seconds: float = DEFAULT_TIMEOUT_SECONDS
func _ready() -> void:
base_url = str(ProjectSettings.get_setting("farm_game/api/base_url", ""))
func post_json(path: String, payload: Dictionary) -> Dictionary:
var base_error := _ensure_base_url()
if not base_error.is_empty():
return base_error
var request := HTTPRequest.new()
request.timeout = timeout_seconds
add_child(request)
var url := _join_url(base_url, path)
var body := _to_egret_msg_body(payload)
var headers := PackedStringArray(["Content-Type: application/x-www-form-urlencoded"])
var error := request.request(url, headers, HTTPClient.METHOD_POST, body)
if error != OK:
request.queue_free()
return {
"ok": false,
"offline": false,
"status": 0,
"body": {},
"text": "HTTPRequest failed to start: %s" % error
}
var response: Array = await request.request_completed
request.queue_free()
return _to_result(response)
func get_query(path: String, params: Dictionary = {}) -> Dictionary:
var base_error := _ensure_base_url()
if not base_error.is_empty():
return base_error
var request := HTTPRequest.new()
request.timeout = timeout_seconds
add_child(request)
var url := _join_url(base_url, path)
var query := _to_form_body(params)
if not query.is_empty():
url = "%s%s%s" % [url, "&" if url.contains("?") else "?", query]
var error := request.request(url, PackedStringArray(), HTTPClient.METHOD_GET)
if error != OK:
request.queue_free()
return {
"ok": false,
"offline": false,
"status": 0,
"body": {},
"text": "HTTPRequest failed to start: %s" % error
}
var response: Array = await request.request_completed
request.queue_free()
return _to_result(response)
func post_form(path: String, payload: Dictionary) -> Dictionary:
var base_error := _ensure_base_url()
if not base_error.is_empty():
return base_error
var request := HTTPRequest.new()
request.timeout = timeout_seconds
add_child(request)
var url := _join_url(base_url, path)
var headers := PackedStringArray(["Content-Type: application/x-www-form-urlencoded"])
var error := request.request(url, headers, HTTPClient.METHOD_POST, _to_form_body(payload))
if error != OK:
request.queue_free()
return {
"ok": false,
"offline": false,
"status": 0,
"body": {},
"text": "HTTPRequest failed to start: %s" % error
}
var response: Array = await request.request_completed
request.queue_free()
return _to_result(response)
func _ensure_base_url() -> Dictionary:
if base_url.strip_edges().is_empty():
base_url = str(ProjectSettings.get_setting("farm_game/api/base_url", ""))
if not base_url.strip_edges().is_empty():
return {}
return {
"ok": false,
"offline": true,
"status": 0,
"body": {},
"text": "API base URL is not configured."
}
func _join_url(root: String, path: String) -> String:
var normalized_root := root.strip_edges().trim_suffix("/")
var normalized_path := path.strip_edges()
if normalized_root.ends_with("=") or normalized_root.ends_with("?") or normalized_root.ends_with("&"):
return normalized_root + normalized_path.trim_prefix("/")
if not normalized_path.begins_with("/"):
normalized_path = "/" + normalized_path
return normalized_root + normalized_path
func _to_egret_msg_body(payload: Dictionary) -> String:
var json := JSON.stringify(payload)
var encoded := Marshalls.raw_to_base64(json.to_utf8_buffer()).uri_encode()
return "msg=%s" % encoded
func _to_form_body(payload: Dictionary) -> String:
var parts: Array[String] = []
for key in payload.keys():
var encoded_key := str(key).uri_encode()
var encoded_value := str(payload.get(key, "")).uri_encode()
parts.append("%s=%s" % [encoded_key, encoded_value])
return "&".join(parts)
func _to_result(response: Array) -> Dictionary:
var result: int = response[0]
var status: int = response[1]
var headers: PackedStringArray = response[2]
var body: PackedByteArray = response[3]
var text := body.get_string_from_utf8()
var parsed = JSON.parse_string(text)
if parsed == null:
parsed = {}
_decode_embedded_data(parsed)
var http_ok := result == HTTPRequest.RESULT_SUCCESS and status >= 200 and status < 300
var game_ok := true
var game_status = null
if parsed is Dictionary and parsed.has("status"):
game_status = parsed.get("status")
game_ok = int(game_status) == 0
elif parsed is Dictionary and parsed.has("code"):
game_status = parsed.get("code")
game_ok = int(game_status) == 0
return {
"ok": http_ok and game_ok,
"offline": false,
"status": status,
"game_status": game_status,
"headers": headers,
"body": parsed,
"text": text
}
func _decode_embedded_data(parsed: Variant) -> void:
if not parsed is Dictionary:
return
if not parsed.has("data") or not parsed["data"] is String:
return
var decoded_text := Marshalls.base64_to_utf8(str(parsed["data"]).strip_edges())
var decoded = JSON.parse_string(decoded_text)
if decoded != null:
parsed["data"] = decoded