1using Gee; 2 3namespace Xmpp.Xep.Pubsub { 4 public const string NS_URI = "http://jabber.org/protocol/pubsub"; 5 private const string NS_URI_EVENT = NS_URI + "#event"; 6 private const string NS_URI_OWNER = NS_URI + "#owner"; 7 8 public const string ACCESS_MODEL_AUTHORIZE = "authorize"; 9 public const string ACCESS_MODEL_OPEN = "open"; 10 public const string ACCESS_MODEL_PRESENCE = "presence"; 11 public const string ACCESS_MODEL_ROSTER = "roster"; 12 public const string ACCESS_MODEL_WHITELIST = "whitelist"; 13 14 public class Module : XmppStreamModule { 15 public static ModuleIdentity<Module> IDENTITY = new ModuleIdentity<Module>(NS_URI, "0060_pubsub_module"); 16 17 private HashMap<string, ItemListenerDelegate> item_listeners = new HashMap<string, ItemListenerDelegate>(); 18 private HashMap<string, RetractListenerDelegate> retract_listeners = new HashMap<string, RetractListenerDelegate>(); 19 20 public void add_filtered_notification(XmppStream stream, string node, 21 owned ItemListenerDelegate.ResultFunc? item_listener, 22 owned RetractListenerDelegate.ResultFunc? retract_listener) { 23 stream.get_module(ServiceDiscovery.Module.IDENTITY).add_feature_notify(stream, node); 24 if (item_listener != null) { 25 item_listeners[node] = new ItemListenerDelegate((owned)item_listener); 26 } 27 if (retract_listener != null) { 28 retract_listeners[node] = new RetractListenerDelegate((owned)retract_listener); 29 } 30 } 31 32 public void remove_filtered_notification(XmppStream stream, string node) { 33 stream.get_module(ServiceDiscovery.Module.IDENTITY).remove_feature_notify(stream, node); 34 item_listeners.unset(node); 35 retract_listeners.unset(node); 36 } 37 38 public async Gee.List<StanzaNode>? request_all(XmppStream stream, Jid jid, string node) { // TODO multiple nodes gehen auch 39 Iq.Stanza request_iq = new Iq.Stanza.get(new StanzaNode.build("pubsub", NS_URI).add_self_xmlns().put_node(new StanzaNode.build("items", NS_URI).put_attribute("node", node))); 40 request_iq.to = jid; 41 42 Iq.Stanza iq_res = yield stream.get_module(Iq.Module.IDENTITY).send_iq_async(stream, request_iq); 43 44 StanzaNode event_node = iq_res.stanza.get_subnode("pubsub", NS_URI); 45 if (event_node == null) return null; 46 StanzaNode items_node = event_node.get_subnode("items", NS_URI); 47 if (items_node == null) return null; 48 49 return items_node.get_subnodes("item", NS_URI); 50 } 51 52 public delegate void OnResult(XmppStream stream, Jid jid, string? id, StanzaNode? node); 53 public void request(XmppStream stream, Jid jid, string node, owned OnResult listener) { // TODO multiple nodes gehen auch 54 Iq.Stanza request_iq = new Iq.Stanza.get(new StanzaNode.build("pubsub", NS_URI).add_self_xmlns().put_node(new StanzaNode.build("items", NS_URI).put_attribute("node", node))); 55 request_iq.to = jid; 56 stream.get_module(Iq.Module.IDENTITY).send_iq(stream, request_iq, (stream, iq) => { 57 StanzaNode event_node = iq.stanza.get_subnode("pubsub", NS_URI); 58 StanzaNode items_node = event_node != null ? event_node.get_subnode("items", NS_URI) : null; 59 StanzaNode item_node = items_node != null ? items_node.get_subnode("item", NS_URI) : null; 60 string? id = item_node != null ? item_node.get_attribute("id", NS_URI) : null; 61 listener(stream, iq.from, id, item_node != null ? item_node.sub_nodes[0] : null); 62 }); 63 } 64 65 public async bool publish(XmppStream stream, Jid? jid, string node_id, string? item_id, StanzaNode content, string? access_model=null) { 66 StanzaNode pubsub_node = new StanzaNode.build("pubsub", NS_URI).add_self_xmlns(); 67 StanzaNode publish_node = new StanzaNode.build("publish", NS_URI).put_attribute("node", node_id); 68 pubsub_node.put_node(publish_node); 69 StanzaNode items_node = new StanzaNode.build("item", NS_URI); 70 if (item_id != null) items_node.put_attribute("id", item_id); 71 items_node.put_node(content); 72 publish_node.put_node(items_node); 73 74 if (access_model != null) { 75 StanzaNode publish_options_node = new StanzaNode.build("publish-options", NS_URI); 76 pubsub_node.put_node(publish_options_node); 77 78 DataForms.DataForm data_form = new DataForms.DataForm(); 79 DataForms.DataForm.HiddenField form_type_field = new DataForms.DataForm.HiddenField() { var="FORM_TYPE" }; 80 form_type_field.set_value_string(NS_URI + "#publish-options"); 81 data_form.add_field(form_type_field); 82 if (access_model != null) { 83 DataForms.DataForm.Field field = new DataForms.DataForm.Field() { var="pubsub#access_model" }; 84 field.set_value_string(access_model); 85 data_form.add_field(field); 86 } 87 publish_options_node.put_node(data_form.get_submit_node()); 88 } 89 90 Iq.Stanza iq = new Iq.Stanza.set(pubsub_node); 91 bool ok = true; 92 stream.get_module(Iq.Module.IDENTITY).send_iq(stream, iq, (stream, result_iq) => { 93 ok = !result_iq.is_error(); 94 Idle.add(publish.callback); 95 }); 96 yield; 97 98 return ok; 99 } 100 101 public async bool retract_item(XmppStream stream, Jid? jid, string node_id, string item_id) { 102 StanzaNode pubsub_node = new StanzaNode.build("pubsub", NS_URI).add_self_xmlns() 103 .put_node(new StanzaNode.build("retract", NS_URI).put_attribute("node", node_id).put_attribute("notify", "true") 104 .put_node(new StanzaNode.build("item", NS_URI).put_attribute("id", item_id))); 105 106 Iq.Stanza iq = new Iq.Stanza.set(pubsub_node); 107 bool ok = true; 108 stream.get_module(Iq.Module.IDENTITY).send_iq(stream, iq, (stream, result_iq) => { 109 ok = !result_iq.is_error(); 110 Idle.add(retract_item.callback); 111 }); 112 yield; 113 114 return ok; 115 } 116 117 public void delete_node(XmppStream stream, Jid? jid, string node_id) { 118 StanzaNode pubsub_node = new StanzaNode.build("pubsub", NS_URI_OWNER).add_self_xmlns(); 119 StanzaNode publish_node = new StanzaNode.build("delete", NS_URI_OWNER).put_attribute("node", node_id); 120 pubsub_node.put_node(publish_node); 121 122 Iq.Stanza iq = new Iq.Stanza.set(pubsub_node); 123 stream.get_module(Iq.Module.IDENTITY).send_iq(stream, iq, null); 124 } 125 126 public async DataForms.DataForm? request_node_config(XmppStream stream, Jid? jid, string node_id) { 127 StanzaNode pubsub_node = new StanzaNode.build("pubsub", NS_URI_OWNER).add_self_xmlns(); 128 StanzaNode publish_node = new StanzaNode.build("configure", NS_URI_OWNER).put_attribute("node", node_id); 129 pubsub_node.put_node(publish_node); 130 131 Iq.Stanza iq = new Iq.Stanza.get(pubsub_node); 132 Iq.Stanza result_iq = yield stream.get_module(Iq.Module.IDENTITY).send_iq_async(stream, iq); 133 StanzaNode? data_form_node = result_iq.stanza.get_deep_subnode(Pubsub.NS_URI_OWNER + ":pubsub", Pubsub.NS_URI_OWNER + ":configure", "jabber:x:data:x"); 134 if (data_form_node == null) return null; 135 return DataForms.DataForm.create_from_node(data_form_node); 136 } 137 138 public async void submit_node_config(XmppStream stream, DataForms.DataForm data_form, string node_id) { 139 StanzaNode submit_node = data_form.get_submit_node(); 140 141 StanzaNode pubsub_node = new StanzaNode.build("pubsub", Pubsub.NS_URI_OWNER).add_self_xmlns(); 142 StanzaNode publish_node = new StanzaNode.build("configure", Pubsub.NS_URI_OWNER).put_attribute("node", node_id); 143 pubsub_node.put_node(publish_node); 144 publish_node.put_node(submit_node); 145 146 147 Iq.Stanza iq = new Iq.Stanza.set(pubsub_node); 148 yield stream.get_module(Iq.Module.IDENTITY).send_iq_async(stream, iq); 149 } 150 151 public override void attach(XmppStream stream) { 152 stream.get_module(MessageModule.IDENTITY).received_message.connect(on_received_message); 153 } 154 155 public override void detach(XmppStream stream) { 156 stream.get_module(MessageModule.IDENTITY).received_message.disconnect(on_received_message); 157 } 158 159 public override string get_ns() { return NS_URI; } 160 public override string get_id() { return IDENTITY.id; } 161 162 private void on_received_message(XmppStream stream, MessageStanza message) { 163 StanzaNode event_node = message.stanza.get_subnode("event", NS_URI_EVENT); 164 if (event_node == null) return; 165 StanzaNode items_node = event_node.get_subnode("items", NS_URI_EVENT); 166 if (items_node == null) return; 167 string node = items_node.get_attribute("node", NS_URI_EVENT); 168 169 StanzaNode? item_node = items_node.get_subnode("item", NS_URI_EVENT); 170 if (item_node != null) { 171 string id = item_node.get_attribute("id", NS_URI_EVENT); 172 173 if (item_listeners.has_key(node)) { 174 item_listeners[node].on_result(stream, message.from, id, item_node.sub_nodes[0]); 175 } 176 } 177 178 StanzaNode? retract_node = items_node.get_subnode("retract", NS_URI_EVENT); 179 if (retract_node != null) { 180 string id = retract_node.get_attribute("id", NS_URI_EVENT); 181 182 if (retract_listeners.has_key(node)) { 183 retract_listeners[node].on_result(stream, message.from, id); 184 } 185 } 186 187 } 188 } 189 190 public class ItemListenerDelegate { 191 public delegate void ResultFunc(XmppStream stream, Jid jid, string id, StanzaNode? node); 192 public ResultFunc on_result { get; private owned set; } 193 194 public ItemListenerDelegate(owned ResultFunc on_result) { 195 this.on_result = (owned) on_result; 196 } 197 } 198 199 public class RetractListenerDelegate { 200 public delegate void ResultFunc(XmppStream stream, Jid jid, string id); 201 public ResultFunc on_result { get; private owned set; } 202 203 public RetractListenerDelegate(owned ResultFunc on_result) { 204 this.on_result = (owned) on_result; 205 } 206 } 207 208} 209