Comparison

mod_service_directories/mod_service_directories.lua @ 552:d1e83cb12885

mod_service_directories: Initial commit. Untested. Support for both directory and buddy use cases. Incomplete pubsub support (only getting all items supported).
author Waqas Hussain <waqas20@gmail.com>
date Sun, 15 Jan 2012 02:56:51 +0500
child 759:6531a029fce5
comparison
equal deleted inserted replaced
551:859bf77b9fbf 552:d1e83cb12885
1 -- Prosody IM
2 -- Copyright (C) 2011 Waqas Hussain
3 --
4 -- This project is MIT/X11 licensed. Please see the
5 -- COPYING file in the source package for more information.
6 --
7
8 -- An implementation of [XEP-0309: Service Directories]
9
10 -- Imports and defines
11
12 local st = require "util.stanza";
13 local jid_split = require "util.jid".split;
14 local adhoc_new = module:require "adhoc".new;
15 local to_ascii = require "util.encodings".idna.to_ascii;
16 local nameprep = require "util.encodings".stringprep.nameprep;
17 local core_post_stanza = core_post_stanza;
18 local pairs, ipairs = pairs, ipairs;
19 local module = module;
20 local hosts = hosts;
21
22 local subscription_from = {};
23 local subscription_to = {};
24 local contact_features = {};
25 local contact_vcards = {};
26
27 -- Advertise in disco
28
29 module:add_identity("server", "directory", "Prosody");
30 module:add_feature("urn:xmpp:server-presence");
31
32 -- Handle subscriptions
33
34 module:hook("presence/host", function(event) -- inbound presence to the host
35 local origin, stanza = event.origin, event.stanza;
36
37 local node, host, resource = jid_split(stanza.attr.from);
38 if stanza.attr.from ~= host then return; end -- not from a host
39
40 local t = stanza.attr.type;
41 if t == "probe" then
42 core_post_stanza(hosts[module.host], st.presence({ from = module.host, to = host, id = stanza.attr.id }));
43 elseif t == "subscribe" then
44 subscription_from[host] = true;
45 core_post_stanza(hosts[module.host], st.presence({ from = module.host, to = host, id = stanza.attr.id, type = "subscribed" }));
46 core_post_stanza(hosts[module.host], st.presence({ from = module.host, to = host, id = stanza.attr.id }));
47 add_contact(host);
48 elseif t == "subscribed" then
49 subscription_to[host] = true;
50 query_host(host);
51 elseif t == "unsubscribe" then
52 subscription_from[host] = nil;
53 core_post_stanza(hosts[module.host], st.presence({ from = module.host, to = host, id = stanza.attr.id, type = "unsubscribed" }));
54 remove_contact(host);
55 elseif t == "unsubscribed" then
56 subscription_to[host] = nil;
57 remove_contact(host);
58 end
59 return true;
60 end, 10); -- priority over mod_presence
61
62 function remove_contact(host, id)
63 contact_features[host] = nil;
64 contact_vcards[host] = nil;
65 if subscription_to[host] then
66 subscription_to[host] = nil;
67 core_post_stanza(hosts[module.host], st.presence({ from = module.host, to = host, id = id, type = "unsubscribe" }));
68 end
69 if subscription_from[host] then
70 subscription_from[host] = nil;
71 core_post_stanza(hosts[module.host], st.presence({ from = module.host, to = host, id = id, type = "unsubscribed" }));
72 end
73 end
74 function add_contact(host, id)
75 if not subscription_to[host] then
76 core_post_stanza(hosts[module.host], st.presence({ from = module.host, to = host, id = id, type = "subscribe" }));
77 end
78 end
79
80 -- Admin ad-hoc command to subscribe
81
82 local function add_contact_handler(self, data, state)
83 local layout = {
84 title = "Adding a Server Buddy";
85 instructions = "Fill out this form to add a \"server buddy\".";
86
87 { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
88 { name = "peerjid", type = "jid-single", required = true, label = "The server to add" };
89 };
90
91 if not state then
92 return { status = "executing", form = layout }, "executing";
93 elseif data.action == "canceled" then
94 return { status = "canceled" };
95 else
96 local fields = layout:data(data);
97 local peerjid = nameprep(fields.peerjid);
98 if not peerjid or peerjid == "" or #peerjid > 1023 or not to_ascii(peerjid) then
99 return { status = "completed", error = { message = "Invalid JID" } };
100 end
101 add_contact(peerjid);
102 return { status = "completed" };
103 end
104 end
105
106 local add_contact_command = adhoc_new("Adding a Server Buddy", "http://jabber.org/protocol/admin#server-buddy", add_contact_handler, "admin");
107 module:add_item("adhoc", add_contact_command);
108
109 -- Disco query remote host
110 function query_host(host)
111 local stanza = st.iq({ from = module.host, to = host, type = "get", id = "mod_service_directories:disco" })
112 :query("http://jabber.org/protocol/disco#info");
113 core_post_stanza(hosts[module.host], stanza);
114 end
115
116 -- Handle disco query result
117 module:hook("iq-result/bare/mod_service_directories:disco", function(event)
118 local origin, stanza = event.origin, event.stanza;
119
120 if not subscription_to[stanza.attr.from] then return; end -- not from a contact
121 local host = stanza.attr.from;
122
123 local query = stanza:get_child("query", "http://jabber.org/protocol/disco#info")
124 if not query then return; end
125
126 -- extract disco features
127 local features = {};
128 for _,tag in ipairs(query.tags) do
129 if tag.name == "feature" and tag.attr.var then
130 features[tag.attr.var] = true;
131 end
132 end
133 contact_features[host] = features;
134
135 if features["urn:ietf:params:xml:ns:vcard-4.0"] then
136 local stanza = st.iq({ from = module.host, to = host, type = "get", id = "mod_service_directories:vcard" })
137 :tag("vcard", { xmlns = "urn:ietf:params:xml:ns:vcard-4.0" });
138 core_post_stanza(hosts[module.host], stanza);
139 end
140 return true;
141 end);
142
143 -- Handle vcard result
144 module:hook("iq-result/bare/mod_service_directories:vcard", function(event)
145 local origin, stanza = event.origin, event.stanza;
146
147 if not subscription_to[stanza.attr.from] then return; end -- not from a contact
148 local host = stanza.attr.from;
149
150 local vcard = stanza:get_child("vcard", "urn:ietf:params:xml:ns:vcard-4.0");
151 if not vcard then return; end
152
153 contact_vcards[host] = st.clone(vcard);
154 return true;
155 end);
156
157 -- PubSub
158
159 -- TODO the following should be replaced by mod_pubsub
160
161 module:hook("iq-get/host/http://jabber.org/protocol/pubsub:pubsub", function(event)
162 local origin, stanza = event.origin, event.stanza;
163 local payload = stanza.tags[1];
164
165 local items = payload:get_child("items", "http://jabber.org/protocol/pubsub");
166 if items and items.attr.node == "urn:xmpp:contacts" then
167 local reply = st.reply(stanza)
168 :tag("pubsub", { xmlns = "http://jabber.org/protocol/pubsub" })
169 :tag("items", { node = "urn:xmpp:contacts" });
170 for host, vcard in pairs(contact_vcards) do
171 reply:tag("item", { id = host })
172 :add_child(vcard)
173 :up();
174 end
175 origin.send(reply);
176 return true;
177 end
178 end);
179