Compare commits
No commits in common. "c50857661f88c4183d86c7a85dedfd1076ca7545" and "2d970c59145d2d2f5bdaa42cbebd39dfca601323" have entirely different histories.
c50857661f
...
2d970c5914
@ -8,46 +8,6 @@
|
|||||||
|
|
||||||
config_version=4
|
config_version=4
|
||||||
|
|
||||||
_global_script_classes=[ {
|
|
||||||
"base": "Node",
|
|
||||||
"class": "Connections",
|
|
||||||
"language": "GDScript",
|
|
||||||
"path": "res://scenes/Connections.gd"
|
|
||||||
}, {
|
|
||||||
"base": "Reference",
|
|
||||||
"class": "MucService",
|
|
||||||
"language": "GDScript",
|
|
||||||
"path": "res://scenes/MucService.gd"
|
|
||||||
}, {
|
|
||||||
"base": "Node",
|
|
||||||
"class": "PolyService",
|
|
||||||
"language": "GDScript",
|
|
||||||
"path": "res://scenes/PolyService.gd"
|
|
||||||
}, {
|
|
||||||
"base": "Reference",
|
|
||||||
"class": "PolyServiceBuilder",
|
|
||||||
"language": "GDScript",
|
|
||||||
"path": "res://scenes/PolyServiceBuilder.gd"
|
|
||||||
}, {
|
|
||||||
"base": "Node",
|
|
||||||
"class": "Stanza",
|
|
||||||
"language": "GDScript",
|
|
||||||
"path": "res://scenes/Stanza.gd"
|
|
||||||
}, {
|
|
||||||
"base": "Node",
|
|
||||||
"class": "Xml",
|
|
||||||
"language": "GDScript",
|
|
||||||
"path": "res://scenes/Xml.gd"
|
|
||||||
} ]
|
|
||||||
_global_script_class_icons={
|
|
||||||
"Connections": "",
|
|
||||||
"MucService": "",
|
|
||||||
"PolyService": "",
|
|
||||||
"PolyServiceBuilder": "",
|
|
||||||
"Stanza": "",
|
|
||||||
"Xml": ""
|
|
||||||
}
|
|
||||||
|
|
||||||
[application]
|
[application]
|
||||||
|
|
||||||
config/name="tochie"
|
config/name="tochie"
|
||||||
@ -57,7 +17,7 @@ config/icon="res://icon.png"
|
|||||||
|
|
||||||
[autoload]
|
[autoload]
|
||||||
|
|
||||||
Sums="*res://scenes/Sums.gd"
|
Xml="*res://scenes/Xml.gd"
|
||||||
|
|
||||||
[gui]
|
[gui]
|
||||||
|
|
||||||
|
@ -2,32 +2,34 @@ extends Node
|
|||||||
|
|
||||||
var _connection
|
var _connection
|
||||||
|
|
||||||
onready var n_Connection := get_node("Connections")
|
func _service_discovery():
|
||||||
|
var iq := yield() as Xml.XmlElement
|
||||||
|
|
||||||
|
var feature_promises := Array()
|
||||||
|
for item in iq.children[0].children:
|
||||||
|
feature_promises.push_back(_connection.promise_iq(item.attributes["jid"], "get",
|
||||||
|
"<query xmlns='http://jabber.org/protocol/disco#items'/>",
|
||||||
|
_connection.iq_as_is()))
|
||||||
|
|
||||||
|
while not _connection.are_promises_done(feature_promises):
|
||||||
|
yield()
|
||||||
|
|
||||||
|
for feature_promise in feature_promises:
|
||||||
|
if not feature_promise.is_ok:
|
||||||
|
push_error("Connection failed")
|
||||||
|
return
|
||||||
|
|
||||||
|
var feature := feature_promise.value as Xml.XmlElement
|
||||||
|
print(feature.as_string())
|
||||||
|
|
||||||
func _ready():
|
func _ready():
|
||||||
_connection = n_Connection.establish_new_connection("poto.cafe", "veclavtalica", "-")
|
_connection = $Connections.establish_new_connection("poto.cafe", "veclavtalica", "-")
|
||||||
if _connection == null:
|
if _connection == null:
|
||||||
push_error("Connection failed")
|
push_error("Connection failed")
|
||||||
return
|
return
|
||||||
|
|
||||||
var service_request = load("res://scenes/PolyServiceBuilder.gd").new().request(_connection)
|
if _connection.push_iq(_connection.domain, "get",
|
||||||
if not service_request.is_done:
|
"<query xmlns='http://jabber.org/protocol/disco#items'/>",
|
||||||
yield(service_request, "done")
|
_service_discovery()) != OK:
|
||||||
|
push_error("Connection failed")
|
||||||
if not service_request.is_ok:
|
|
||||||
push_error("Service builder errored out: %s" % [service_request.value])
|
|
||||||
return
|
return
|
||||||
|
|
||||||
for service in service_request.value.value:
|
|
||||||
if service.muc == null: continue
|
|
||||||
|
|
||||||
var rooms_request = service.muc.request_rooms()
|
|
||||||
if not rooms_request.is_done:
|
|
||||||
yield(rooms_request, "done")
|
|
||||||
|
|
||||||
if not rooms_request.is_ok:
|
|
||||||
push_error("Room request errored out: %s" % [rooms_request.value])
|
|
||||||
return
|
|
||||||
|
|
||||||
for room in rooms_request.value.value:
|
|
||||||
print(room.as_string())
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
extends Node
|
extends Node
|
||||||
class_name Connections
|
|
||||||
|
|
||||||
class Connection extends Reference:
|
class Connection extends Reference:
|
||||||
var stream: StreamPeer
|
var stream: StreamPeer
|
||||||
@ -7,26 +6,46 @@ class Connection extends Reference:
|
|||||||
var domain: String
|
var domain: String
|
||||||
var bare_jid: String
|
var bare_jid: String
|
||||||
var jid: String
|
var jid: String
|
||||||
|
|
||||||
var _id_counter: int = 0
|
var _id_counter: int = 0
|
||||||
var _pending_iqs: Dictionary # of id to GDScriptFunctionState
|
var _pending_iqs: Dictionary # of id to GDScriptFunctionState
|
||||||
var _pending_tail_iqs: Array # of GDScriptFunctionState
|
var _pending_tail_iqs: Array # of GDScriptFunctionState
|
||||||
var _xml_parser := Xml.Parser.new()
|
var _xml_parser := Xml.Parser.new()
|
||||||
|
|
||||||
# todo: Route signals to particular receivers, based on 'from' or 'to'
|
# warning-ignore:unused_signal
|
||||||
# warning-ignore:unused_signal
|
|
||||||
signal presence_received(presence)
|
signal presence_received(presence)
|
||||||
# warning-ignore:unused_signal
|
# warning-ignore:unused_signal
|
||||||
signal message_received(message)
|
signal message_received(message)
|
||||||
# warning-ignore:unused_signal
|
# warning-ignore:unused_signal
|
||||||
signal iq_received(iq)
|
signal iq_received(iq)
|
||||||
|
|
||||||
# todo: Separate incremental hash to its own class.
|
|
||||||
func generate_id() -> int:
|
func generate_id() -> int:
|
||||||
self._id_counter += 1
|
self._id_counter += 1
|
||||||
return hash(self._id_counter)
|
return hash(self._id_counter)
|
||||||
|
|
||||||
func promise_iq(to: String, action: String, payload: String, capture: GDScriptFunctionState):
|
class Promise extends Reference:
|
||||||
|
var is_done: bool = false
|
||||||
|
var is_ok: bool
|
||||||
|
var value = null
|
||||||
|
|
||||||
|
static func from(capture: GDScriptFunctionState) -> Promise:
|
||||||
|
var result := Promise.new()
|
||||||
|
if capture.connect("completed", result, "result_arrived") != OK:
|
||||||
|
assert("Bruh")
|
||||||
|
return result
|
||||||
|
|
||||||
|
static func make_error(error: int) -> Promise:
|
||||||
|
var result := Promise.new()
|
||||||
|
result.is_done = true
|
||||||
|
result.is_ok = false
|
||||||
|
result.value = error
|
||||||
|
return result
|
||||||
|
|
||||||
|
func result_arrived(p_result) -> void:
|
||||||
|
self.is_done = true
|
||||||
|
self.is_ok = true
|
||||||
|
self.value = p_result
|
||||||
|
|
||||||
|
func promise_iq(to: String, action: String, payload: String, capture: GDScriptFunctionState) -> Promise:
|
||||||
assert(action in ["get", "set"])
|
assert(action in ["get", "set"])
|
||||||
|
|
||||||
var id := self.generate_id()
|
var id := self.generate_id()
|
||||||
@ -38,12 +57,41 @@ class Connection extends Reference:
|
|||||||
"action": action,
|
"action": action,
|
||||||
"payload": payload
|
"payload": payload
|
||||||
}).to_utf8()) != OK:
|
}).to_utf8()) != OK:
|
||||||
return Sums.Promise.make_error(ERR_CONNECTION_ERROR)
|
return Promise.make_error(ERR_CONNECTION_ERROR)
|
||||||
|
|
||||||
assert(not id in self._pending_iqs)
|
assert(not id in self._pending_iqs)
|
||||||
self._pending_iqs[id] = capture
|
self._pending_iqs[id] = capture
|
||||||
|
|
||||||
return Sums.Promise.from(capture)
|
return Promise.from(capture)
|
||||||
|
|
||||||
|
func push_iq(to: String, action: String, payload: String, capture: GDScriptFunctionState) -> int: # Error
|
||||||
|
assert(action in ["get", "set"])
|
||||||
|
|
||||||
|
var id := self.generate_id()
|
||||||
|
if self.stream.put_data(
|
||||||
|
"""<iq from='{from}' id='{id}' to='{to}' type='{action}'>{payload}</iq>""".format({
|
||||||
|
"from": self.jid.xml_escape(),
|
||||||
|
"id": id,
|
||||||
|
"to": to.xml_escape(),
|
||||||
|
"action": action,
|
||||||
|
"payload": payload
|
||||||
|
}).to_utf8()) != OK:
|
||||||
|
return ERR_CONNECTION_ERROR
|
||||||
|
|
||||||
|
assert(not id in self._pending_iqs)
|
||||||
|
self._pending_iqs[id] = capture
|
||||||
|
|
||||||
|
return OK
|
||||||
|
|
||||||
|
static func are_promises_done(promises: Array) -> bool:
|
||||||
|
for promise in promises:
|
||||||
|
assert(promise is Promise)
|
||||||
|
if not promise.is_done:
|
||||||
|
return false
|
||||||
|
return true
|
||||||
|
|
||||||
|
static func iq_as_is():
|
||||||
|
return yield()
|
||||||
|
|
||||||
## Registry of connections used for poking of pending iqs.
|
## Registry of connections used for poking of pending iqs.
|
||||||
var _connections: Array # of WeakRef to Connection
|
var _connections: Array # of WeakRef to Connection
|
||||||
@ -53,7 +101,6 @@ func _ready():
|
|||||||
if get_tree().connect("physics_frame", self, "_process_connections") != OK:
|
if get_tree().connect("physics_frame", self, "_process_connections") != OK:
|
||||||
assert("Bruh")
|
assert("Bruh")
|
||||||
|
|
||||||
# todo: Collapse Result inside Promise.
|
|
||||||
func _process_connections() -> void:
|
func _process_connections() -> void:
|
||||||
var to_remove := PoolIntArray()
|
var to_remove := PoolIntArray()
|
||||||
|
|
||||||
@ -67,19 +114,17 @@ func _process_connections() -> void:
|
|||||||
if response == null:
|
if response == null:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# todo: Move Result error into closure on connection failure?
|
|
||||||
|
|
||||||
while connection._xml_parser.parse_a_bit(response):
|
while connection._xml_parser.parse_a_bit(response):
|
||||||
response = null
|
response = null
|
||||||
|
|
||||||
var stanza := connection._xml_parser.take_root()
|
var stanza := connection._xml_parser.take_root()
|
||||||
if stanza.name == "iq":
|
if stanza.node_name == "iq":
|
||||||
if "to" in stanza.attributes and stanza.attributes["to"] != connection.jid:
|
if "to" in stanza.attributes and stanza.attributes["to"] != connection.jid:
|
||||||
push_error("Stanza is not addressed to assigned jid")
|
push_error("Stanza is not addressed to assigned jid")
|
||||||
|
|
||||||
if stanza.attributes["type"] in ["result", "error"]:
|
if stanza.attributes["type"] in ["result", "error"]:
|
||||||
var id := int(stanza.attributes["id"]) # todo: Use strings directly instead?
|
var id := int(stanza.attributes["id"]) # todo: Use strings directly instead?
|
||||||
var result = connection._pending_iqs[id].resume(Sums.Result.make_value(stanza))
|
var result = connection._pending_iqs[id].resume(stanza)
|
||||||
if result is GDScriptFunctionState and result.is_valid():
|
if result is GDScriptFunctionState and result.is_valid():
|
||||||
connection._pending_tail_iqs.push_back(result)
|
connection._pending_tail_iqs.push_back(result)
|
||||||
# elif result != null:
|
# elif result != null:
|
||||||
@ -90,10 +135,10 @@ func _process_connections() -> void:
|
|||||||
elif stanza.attributes["type"] in ["set", "get"]:
|
elif stanza.attributes["type"] in ["set", "get"]:
|
||||||
connection.emit_signal("iq_received", stanza)
|
connection.emit_signal("iq_received", stanza)
|
||||||
|
|
||||||
elif stanza.name == "message":
|
elif stanza.node_name == "message":
|
||||||
connection.emit_signal("message_received", stanza)
|
connection.emit_signal("message_received", stanza)
|
||||||
|
|
||||||
elif stanza.name == "presence":
|
elif stanza.node_name == "presence":
|
||||||
connection.emit_signal("presence_received", stanza)
|
connection.emit_signal("presence_received", stanza)
|
||||||
|
|
||||||
var to_remove_tails := PoolIntArray()
|
var to_remove_tails := PoolIntArray()
|
||||||
@ -112,17 +157,17 @@ func _process_connections() -> void:
|
|||||||
for idx in range(to_remove.size() - 1, 0, -1):
|
for idx in range(to_remove.size() - 1, 0, -1):
|
||||||
_connections.remove(to_remove[idx])
|
_connections.remove(to_remove[idx])
|
||||||
|
|
||||||
func establish_new_connection(domain: String, identity: String, password: String):
|
func establish_new_connection(domain: String, identity: String, password: String) -> Connection:
|
||||||
var stream := StreamPeerTCP.new()
|
var stream := StreamPeerTCP.new()
|
||||||
if stream.connect_to_host(domain, 5222) != OK:
|
if stream.connect_to_host(domain, 5222) != OK:
|
||||||
push_error("Cannot establish connection to " + domain)
|
push_error("Cannot establish client->server pipe to " + domain)
|
||||||
return null
|
return null
|
||||||
|
|
||||||
while stream.get_status() == StreamPeerTCP.STATUS_CONNECTING:
|
while stream.get_status() == StreamPeerTCP.STATUS_CONNECTING:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if stream.get_status() == StreamPeerTCP.STATUS_ERROR:
|
if stream.get_status() == StreamPeerTCP.STATUS_ERROR:
|
||||||
push_error("Cannot establish connection to " + domain)
|
push_error("Cannot establish client->server pipe to " + domain)
|
||||||
return null
|
return null
|
||||||
|
|
||||||
var result := Connection.new()
|
var result := Connection.new()
|
||||||
@ -180,7 +225,7 @@ func _negotiate_tls(connection: Connection) -> int:
|
|||||||
|
|
||||||
## Check that server response corresponds to what we ask for.
|
## Check that server response corresponds to what we ask for.
|
||||||
# todo: For conformity client must send closing stream tag on error.
|
# todo: For conformity client must send closing stream tag on error.
|
||||||
if parsed_response._root.name != "stream:stream": return ERR_CANT_CONNECT
|
if parsed_response._root.node_name != "stream:stream": return ERR_CANT_CONNECT
|
||||||
if parsed_response._root.attributes["from"] != connection.domain: return ERR_CANT_CONNECT
|
if parsed_response._root.attributes["from"] != connection.domain: return ERR_CANT_CONNECT
|
||||||
if "to" in parsed_response._root.attributes:
|
if "to" in parsed_response._root.attributes:
|
||||||
if parsed_response._root.attributes["to"] != connection.bare_jid: return ERR_CANT_CONNECT
|
if parsed_response._root.attributes["to"] != connection.bare_jid: return ERR_CANT_CONNECT
|
||||||
@ -251,7 +296,7 @@ func _negotiate_sasl(connection: Connection, password: String) -> int:
|
|||||||
|
|
||||||
## Check that server response corresponds to what we ask for.
|
## Check that server response corresponds to what we ask for.
|
||||||
# todo: For conformity client must send closing stream tag on error.
|
# todo: For conformity client must send closing stream tag on error.
|
||||||
if parsed_response._root.name != "stream:stream": return ERR_CANT_CONNECT
|
if parsed_response._root.node_name != "stream:stream": return ERR_CANT_CONNECT
|
||||||
if parsed_response._root.attributes["from"] != connection.domain: return ERR_CANT_CONNECT
|
if parsed_response._root.attributes["from"] != connection.domain: return ERR_CANT_CONNECT
|
||||||
if "to" in parsed_response._root.attributes:
|
if "to" in parsed_response._root.attributes:
|
||||||
if parsed_response._root.attributes["to"] != connection.bare_jid: return ERR_CANT_CONNECT
|
if parsed_response._root.attributes["to"] != connection.bare_jid: return ERR_CANT_CONNECT
|
||||||
@ -273,7 +318,7 @@ func _negotiate_sasl(connection: Connection, password: String) -> int:
|
|||||||
## We only support PLAIN mechanism as it's sufficient over TLS.
|
## We only support PLAIN mechanism as it's sufficient over TLS.
|
||||||
var plain_found := false
|
var plain_found := false
|
||||||
for machanism in mechanisms.children:
|
for machanism in mechanisms.children:
|
||||||
if machanism.is_element() and machanism.name == "mechanism":
|
if machanism.is_element() and machanism.node_name == "mechanism":
|
||||||
if machanism.children[0].data == "PLAIN":
|
if machanism.children[0].data == "PLAIN":
|
||||||
plain_found = true
|
plain_found = true
|
||||||
break
|
break
|
||||||
@ -319,7 +364,7 @@ func _bind_resource(connection: Connection, resource: String = "tochie-facade")
|
|||||||
|
|
||||||
## Check that server response corresponds to what we ask for.
|
## Check that server response corresponds to what we ask for.
|
||||||
# todo: For conformity client must send closing stream tag on error.
|
# todo: For conformity client must send closing stream tag on error.
|
||||||
if parsed_response._root.name != "stream:stream": return ERR_CANT_CONNECT
|
if parsed_response._root.node_name != "stream:stream": return ERR_CANT_CONNECT
|
||||||
if parsed_response._root.attributes["from"] != connection.domain: return ERR_CANT_CONNECT
|
if parsed_response._root.attributes["from"] != connection.domain: return ERR_CANT_CONNECT
|
||||||
if "to" in parsed_response._root.attributes:
|
if "to" in parsed_response._root.attributes:
|
||||||
if parsed_response._root.attributes["to"] != connection.bare_jid: return ERR_CANT_CONNECT
|
if parsed_response._root.attributes["to"] != connection.bare_jid: return ERR_CANT_CONNECT
|
||||||
|
@ -1,61 +0,0 @@
|
|||||||
extends Reference
|
|
||||||
class_name MucService
|
|
||||||
|
|
||||||
## https://xmpp.org/extensions/xep-0045.html
|
|
||||||
|
|
||||||
class MucRoom extends Reference:
|
|
||||||
var jid: String
|
|
||||||
var name: String
|
|
||||||
|
|
||||||
func as_string() -> String:
|
|
||||||
return "MucRoom \"%s\", Jid: \"%s\"" % [name, jid]
|
|
||||||
|
|
||||||
var jid: String
|
|
||||||
|
|
||||||
var _connection: Connections.Connection
|
|
||||||
var _rooms_promise: Sums.Promise = null
|
|
||||||
|
|
||||||
func try_init(connection: Connections.Connection, p_jid: String, disco_info: Xml.XmlElement):
|
|
||||||
var is_muc := false
|
|
||||||
for item in disco_info.children:
|
|
||||||
if item.is_element() and item.name == "feature":
|
|
||||||
if item.attributes["var"] == "http://jabber.org/protocol/muc":
|
|
||||||
is_muc = true
|
|
||||||
break
|
|
||||||
|
|
||||||
if not is_muc:
|
|
||||||
return null
|
|
||||||
|
|
||||||
self.jid = p_jid
|
|
||||||
self._connection = connection
|
|
||||||
return self
|
|
||||||
|
|
||||||
func request_rooms() -> Sums.Promise:
|
|
||||||
if self._rooms_promise != null:
|
|
||||||
return self._rooms_promise
|
|
||||||
|
|
||||||
self._rooms_promise = _connection.promise_iq(
|
|
||||||
jid, "get",
|
|
||||||
Stanza.disco_items_queury,
|
|
||||||
_iq_rooms())
|
|
||||||
|
|
||||||
return self._rooms_promise
|
|
||||||
|
|
||||||
func _iq_rooms():
|
|
||||||
var response = yield()
|
|
||||||
if not response.is_ok:
|
|
||||||
return response
|
|
||||||
|
|
||||||
var query := Stanza.unwrap_query_result(response.value)
|
|
||||||
if not query.is_ok:
|
|
||||||
return query
|
|
||||||
|
|
||||||
var rooms := Array()
|
|
||||||
for item in query.value.children:
|
|
||||||
if item.is_element() and item.name == "item":
|
|
||||||
var muc_room := MucRoom.new()
|
|
||||||
muc_room.jid = item.attributes["jid"]
|
|
||||||
muc_room.name = item.attributes["name"]
|
|
||||||
rooms.push_back(muc_room)
|
|
||||||
|
|
||||||
return Sums.Result.make_value(rooms)
|
|
@ -1,13 +0,0 @@
|
|||||||
extends Node
|
|
||||||
class_name PolyService
|
|
||||||
|
|
||||||
class Identity extends Reference:
|
|
||||||
var category: String
|
|
||||||
var name: String
|
|
||||||
var type: String
|
|
||||||
|
|
||||||
var jid: String
|
|
||||||
var identity: Identity
|
|
||||||
|
|
||||||
## Interfaces are populated based on features exposed on any given resource.
|
|
||||||
var muc = null
|
|
@ -1,56 +0,0 @@
|
|||||||
extends Reference
|
|
||||||
class_name PolyServiceBuilder
|
|
||||||
|
|
||||||
## https://xmpp.org/extensions/xep-0030.html
|
|
||||||
|
|
||||||
func request(connection: Connections.Connection) -> Sums.Promise:
|
|
||||||
return connection.promise_iq(connection.domain, "get",
|
|
||||||
Stanza.disco_items_queury,
|
|
||||||
_service_discovery(connection))
|
|
||||||
|
|
||||||
func _service_discovery(connection: Connections.Connection) -> Sums.Result:
|
|
||||||
var response = yield()
|
|
||||||
if not response.is_ok:
|
|
||||||
return response
|
|
||||||
|
|
||||||
var query := Stanza.unwrap_query_result(response.value)
|
|
||||||
if not query.is_ok:
|
|
||||||
return query.value
|
|
||||||
|
|
||||||
var feature_promises := Array()
|
|
||||||
var poly_services := Dictionary() # of Jid to PolyService
|
|
||||||
for item in query.value.children:
|
|
||||||
if not item.is_element() or item.name != "item":
|
|
||||||
continue
|
|
||||||
|
|
||||||
var poly_service = load("res://scenes/PolyService.gd").new()
|
|
||||||
poly_service.jid = item.attributes["jid"]
|
|
||||||
if "name" in item.attributes:
|
|
||||||
poly_service.name = item.attributes["name"]
|
|
||||||
poly_services[item.attributes["jid"]] = poly_service
|
|
||||||
|
|
||||||
feature_promises.push_back(connection.promise_iq(
|
|
||||||
item.attributes["jid"], "get",
|
|
||||||
Stanza.disco_info_queury,
|
|
||||||
Stanza.yield_as_is()))
|
|
||||||
|
|
||||||
while not Sums.are_promises_done(feature_promises):
|
|
||||||
yield()
|
|
||||||
|
|
||||||
# todo: Allow partial success.
|
|
||||||
if not Sums.are_promises_ok(feature_promises):
|
|
||||||
return Sums.Result.make_error(Sums.collect_promise_errors(feature_promises))
|
|
||||||
|
|
||||||
for feature_response in Sums.collect_promise_values(feature_promises):
|
|
||||||
var jid = feature_response.attributes["from"]
|
|
||||||
|
|
||||||
var features := Stanza.unwrap_query_result(feature_response)
|
|
||||||
if not features.is_ok:
|
|
||||||
# todo: Signal the error.
|
|
||||||
continue
|
|
||||||
|
|
||||||
var poly_service = poly_services[jid]
|
|
||||||
poly_service.muc = load("res://scenes/MucService.gd").new().try_init(
|
|
||||||
connection, jid, features.value)
|
|
||||||
|
|
||||||
return Sums.Result.make_value(poly_services.values())
|
|
@ -1,22 +0,0 @@
|
|||||||
extends Node
|
|
||||||
class_name Stanza
|
|
||||||
|
|
||||||
const disco_info_queury := "<query xmlns='http://jabber.org/protocol/disco#info'/>"
|
|
||||||
const disco_items_queury := "<query xmlns='http://jabber.org/protocol/disco#items'/>"
|
|
||||||
|
|
||||||
static func yield_as_is():
|
|
||||||
return yield()
|
|
||||||
|
|
||||||
static func unwrap_query_result(iq: Xml.XmlElement) -> Sums.Result:
|
|
||||||
if iq == null or iq.name != "iq" or not "type" in iq.attributes:
|
|
||||||
return Sums.Result.make_error(ERR_INVALID_DATA)
|
|
||||||
|
|
||||||
# todo: Delegate strctured XMPP error.
|
|
||||||
if iq.attributes["type"] != "result":
|
|
||||||
return Sums.Result.make_error(ERR_INVALID_DATA)
|
|
||||||
|
|
||||||
for child in iq.children:
|
|
||||||
if child.is_element() and child.name == "query":
|
|
||||||
return Sums.Result.make_value(child)
|
|
||||||
|
|
||||||
return Sums.Result.make_error(ERR_INVALID_DATA)
|
|
@ -1,91 +0,0 @@
|
|||||||
extends Node
|
|
||||||
|
|
||||||
class Result extends Reference:
|
|
||||||
var is_ok: bool
|
|
||||||
var value = null
|
|
||||||
|
|
||||||
static func make_value(p_value):
|
|
||||||
var result := Result.new()
|
|
||||||
result.is_ok = true
|
|
||||||
result.value = p_value
|
|
||||||
return result
|
|
||||||
|
|
||||||
static func make_error(error):
|
|
||||||
var result := Result.new()
|
|
||||||
result.is_ok = false
|
|
||||||
# todo: Proper error object / convention.
|
|
||||||
result.value = [error, get_stack().slice(1, -1)]
|
|
||||||
return result
|
|
||||||
|
|
||||||
class Promise extends Reference:
|
|
||||||
var is_done: bool = false
|
|
||||||
var is_ok: bool
|
|
||||||
var value = null
|
|
||||||
|
|
||||||
signal done()
|
|
||||||
|
|
||||||
static func from(capture: GDScriptFunctionState):
|
|
||||||
var result := Promise.new()
|
|
||||||
if capture.connect("completed", result, "_result_arrived") != OK:
|
|
||||||
assert("Bruh")
|
|
||||||
return result
|
|
||||||
|
|
||||||
static func make_value(p_value):
|
|
||||||
var result := Promise.new()
|
|
||||||
result.is_done = true
|
|
||||||
result.is_ok = true
|
|
||||||
result.value = p_value
|
|
||||||
return result
|
|
||||||
|
|
||||||
static func make_error(error):
|
|
||||||
var result := Promise.new()
|
|
||||||
result.is_done = true
|
|
||||||
result.is_ok = false
|
|
||||||
result.value = [error, get_stack().slice(1, -1)]
|
|
||||||
return result
|
|
||||||
|
|
||||||
# todo: Use Error class value for erroneous state instead.
|
|
||||||
func _result_arrived(p_result) -> void:
|
|
||||||
self.is_done = true
|
|
||||||
self.is_ok = true
|
|
||||||
self.value = p_result
|
|
||||||
emit_signal("done")
|
|
||||||
|
|
||||||
static func are_promises_done(promises: Array) -> bool:
|
|
||||||
for promise in promises:
|
|
||||||
assert(promise is Promise)
|
|
||||||
if not promise.is_done:
|
|
||||||
return false
|
|
||||||
return true
|
|
||||||
|
|
||||||
static func are_promises_ok(promises: Array) -> bool:
|
|
||||||
for promise in promises:
|
|
||||||
assert(promise is Promise)
|
|
||||||
assert(promise.is_done)
|
|
||||||
if not promise.is_ok:
|
|
||||||
return false
|
|
||||||
return true
|
|
||||||
|
|
||||||
static func collect_promise_values(promises: Array) -> Array:
|
|
||||||
var result := Array()
|
|
||||||
for promise in promises:
|
|
||||||
assert(promise is Promise)
|
|
||||||
assert(promise.is_done)
|
|
||||||
# todo: Don't do this.
|
|
||||||
if promise.value is Result:
|
|
||||||
assert(promise.value.is_ok)
|
|
||||||
result.push_back(promise.value.value)
|
|
||||||
else: result.push_back(promise.value)
|
|
||||||
return result
|
|
||||||
|
|
||||||
static func collect_promise_errors(promises: Array) -> Array:
|
|
||||||
var result := Array()
|
|
||||||
for promise in promises:
|
|
||||||
assert(promise is Promise)
|
|
||||||
assert(promise.is_done)
|
|
||||||
if not promise.is_ok:
|
|
||||||
result.push_back(promise.value)
|
|
||||||
# todo: Don't do this.
|
|
||||||
elif promise.value is Result and not promise.value.is_ok:
|
|
||||||
result.push_back(promise.value.value)
|
|
||||||
return result
|
|
@ -1,27 +1,26 @@
|
|||||||
extends Node
|
extends Node
|
||||||
class_name Xml
|
|
||||||
|
|
||||||
class XmlNode extends Reference:
|
class XmlNode extends Reference:
|
||||||
func is_element() -> bool: return false
|
func is_element() -> bool: return false
|
||||||
func is_text() -> bool: return false
|
func is_text() -> bool: return false
|
||||||
|
|
||||||
class XmlElement extends XmlNode:
|
class XmlElement extends XmlNode:
|
||||||
var name: String
|
var node_name: String
|
||||||
var attributes: Dictionary # String to String
|
var attributes: Dictionary # String to String
|
||||||
var children: Array # of XmlNode
|
var children: Array # of XmlNode
|
||||||
|
|
||||||
func is_element() -> bool: return true
|
func is_element() -> bool: return true
|
||||||
|
|
||||||
func take_named_child_element(p_name: String) -> XmlElement:
|
func take_named_child_element(p_node_name: String) -> XmlElement:
|
||||||
for idx in range(self.children.size()):
|
for idx in range(self.children.size()):
|
||||||
var child := self.children[idx] as XmlElement
|
var child := self.children[idx] as XmlElement
|
||||||
if child != null and child.name == p_name:
|
if child != null and child.node_name == p_node_name:
|
||||||
self.children.remove(idx)
|
self.children.remove(idx)
|
||||||
return child
|
return child
|
||||||
return null
|
return null
|
||||||
|
|
||||||
func as_string(level: int = 0) -> String:
|
func as_string(level: int = 0) -> String:
|
||||||
var result = '\t'.repeat(level) + "XmlElement \"%s\"" % self.name
|
var result = '\t'.repeat(level) + "XmlElement \"%s\"" % self.node_name
|
||||||
if self.attributes.size() > 0:
|
if self.attributes.size() > 0:
|
||||||
result += ", Attributes: " + String(self.attributes)
|
result += ", Attributes: " + String(self.attributes)
|
||||||
for child in self.children:
|
for child in self.children:
|
||||||
@ -70,7 +69,7 @@ class Parser extends Reference:
|
|||||||
while parser.read() == OK:
|
while parser.read() == OK:
|
||||||
if parser.get_node_type() == XMLParser.NODE_ELEMENT:
|
if parser.get_node_type() == XMLParser.NODE_ELEMENT:
|
||||||
var element := XmlElement.new()
|
var element := XmlElement.new()
|
||||||
element.name = parser.get_node_name()
|
element.node_name = parser.get_node_name()
|
||||||
var attribute_count := parser.get_attribute_count()
|
var attribute_count := parser.get_attribute_count()
|
||||||
for idx in range(attribute_count):
|
for idx in range(attribute_count):
|
||||||
element.attributes[parser.get_attribute_name(idx)] = parser.get_attribute_value(idx)
|
element.attributes[parser.get_attribute_name(idx)] = parser.get_attribute_value(idx)
|
||||||
@ -89,8 +88,8 @@ class Parser extends Reference:
|
|||||||
return ERR_PARSE_ERROR
|
return ERR_PARSE_ERROR
|
||||||
|
|
||||||
var popped := self._element_stack.pop_back() as XmlElement
|
var popped := self._element_stack.pop_back() as XmlElement
|
||||||
if popped.name != parser.get_node_name():
|
if popped.node_name != parser.get_node_name():
|
||||||
push_error("Element <%s> closes sooner than <%s>" % [parser.get_node_name(), popped.name])
|
push_error("Element <%s> closes sooner than <%s>" % [parser.get_node_name(), popped.node_name])
|
||||||
return ERR_PARSE_ERROR
|
return ERR_PARSE_ERROR
|
||||||
|
|
||||||
if self._element_stack.size() == 0:
|
if self._element_stack.size() == 0:
|
||||||
|
Loading…
Reference in New Issue
Block a user