From 54427b04f9a35bd8912cb15a03cd36c44defb4a4 Mon Sep 17 00:00:00 2001 From: fiaxh Date: Thu, 4 Nov 2021 17:11:33 +0100 Subject: [PATCH] Add initial COIN (XEP-0298) implementation to xmpp-vala --- xmpp-vala/src/module/xep/0298_coin.vala | 136 ++++++++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 xmpp-vala/src/module/xep/0298_coin.vala diff --git a/xmpp-vala/src/module/xep/0298_coin.vala b/xmpp-vala/src/module/xep/0298_coin.vala new file mode 100644 index 00000000..58b3b055 --- /dev/null +++ b/xmpp-vala/src/module/xep/0298_coin.vala @@ -0,0 +1,136 @@ +using Gee; + +namespace Xmpp.Xep.Coin { + private const string NS_RFC = "urn:ietf:params:xml:ns:conference-info"; + + public class Module : XmppStreamModule, Iq.Handler { + public static ModuleIdentity IDENTITY = new ModuleIdentity(NS_RFC, "0298_coin"); + + public signal void coin_info_received(Jid jid, ConferenceInfo info); + + public async override void on_iq_set(XmppStream stream, Iq.Stanza iq) { + ConferenceInfo? info = parse_node(iq.stanza.get_subnode("conference-info", NS_RFC), null); + if (info == null) return; + + coin_info_received(iq.from, info); + } + + public override void attach(XmppStream stream) { + stream.get_module(Iq.Module.IDENTITY).register_for_namespace(NS_RFC, this); + } + + public override void detach(XmppStream stream) { } + + public override string get_ns() { return NS_RFC; } + + public override string get_id() { return IDENTITY.id; } + } + + public ConferenceInfo? parse_node(StanzaNode conference_node, ConferenceInfo? previous_conference_info) { + string? version_str = conference_node.get_attribute("version"); + string? conference_state = conference_node.get_attribute("state"); + if (version_str == null || conference_state == null) return null; + + int version = int.parse(version_str); + if (previous_conference_info != null && version <= previous_conference_info.version) return null; + + ConferenceInfo conference_info = previous_conference_info ?? new ConferenceInfo(); + conference_info.version = version; + + Gee.List user_nodes = conference_node.get_deep_subnodes(NS_RFC + ":users", NS_RFC + ":user"); + foreach (StanzaNode user_node in user_nodes) { + string? jid_string = user_node.get_attribute("entity"); + if (jid_string == null) continue; +// if (!jid_string.has_prefix("xmpp:")) continue; // silk does this wrong + Jid? jid = null; + try { + jid = new Jid(jid_string.substring(4)); + } catch (Error e) { + continue; + } + string user_state = user_node.get_attribute("state"); + if (conference_state == "full" && user_state != "full") return null; + + if (user_state == "deleted") { + conference_info.users.unset(jid); + continue; + } + + ConferenceUser user = new ConferenceUser(); + user.jid = jid; + user.display_text = user_node.get_deep_string_content(NS_RFC + ":display-text"); + + Gee.List endpoint_nodes = user_node.get_subnodes("endpoint"); + foreach (StanzaNode entpoint_node in endpoint_nodes) { + Gee.List media_nodes = entpoint_node.get_subnodes("media"); + foreach (StanzaNode media_node in media_nodes) { + string? id = media_node.get_attribute("id"); + string? ty = media_node.get_deep_string_content(NS_RFC + ":type"); + string? src_id_str = media_node.get_deep_string_content(NS_RFC + ":src-id"); + + if (id == null) continue; + + ConferenceMedia media = new ConferenceMedia(); + media.id = id; + media.src_id = src_id_str != null ? int.parse(src_id_str) : -1; + media.ty = ty; + user.medias[id] = media; + } + + conference_info.users[user.jid] = user; + } + } + return conference_info; + } + + public class ConferenceInfo { + public int version = -1; + public HashMap users = new HashMap(Jid.hash_func, Jid.equals_func); + + public StanzaNode to_xml() { + StanzaNode ret = new StanzaNode.build("conference-info", NS_RFC).add_self_xmlns() + .put_attribute("version", this.version.to_string()) + .put_attribute("state", "full"); + StanzaNode users_node = new StanzaNode.build("users", NS_RFC); + + foreach (ConferenceUser user in this.users.values) { + users_node.put_node(user.to_xml()); + } + ret.put_node(users_node); + return ret; + } + } + + public class ConferenceUser { + public Jid jid; + public string? display_text; + public HashMap medias = new HashMap(); + + public StanzaNode to_xml() { + StanzaNode user_node = new StanzaNode.build("user", NS_RFC) + .put_attribute("entity", jid.to_string()); + foreach (ConferenceMedia media in medias.values) { + user_node.put_node(media.to_xml()); + } + return user_node; + } + } + + public class ConferenceMedia { + public string id; + public string? ty; + public int src_id = -1; + + public StanzaNode to_xml() { + StanzaNode media_node = new StanzaNode.build("media", NS_RFC) + .put_attribute("id", id); + if (ty != null) { + media_node.put_node(new StanzaNode.build("type", NS_RFC).put_node(new StanzaNode.text(ty))); + } + if (src_id != -1) { + media_node.put_node(new StanzaNode.build("src-id", NS_RFC).put_node(new StanzaNode.text(src_id.to_string()))); + } + return media_node; + } + } +} \ No newline at end of file