plugins/disco.lua

changeset 266
ad8a918fa6e6
parent 250
a5ac643a7fd6
child 267
d30897e4f5c2
equal deleted inserted replaced
265:2a2326a8f9e8 266:ad8a918fa6e6
15 local xmlns_disco_info = xmlns_disco.."#info"; 15 local xmlns_disco_info = xmlns_disco.."#info";
16 local xmlns_disco_items = xmlns_disco.."#items"; 16 local xmlns_disco_items = xmlns_disco.."#items";
17 17
18 function verse.plugins.disco(stream) 18 function verse.plugins.disco(stream)
19 stream:add_plugin("presence"); 19 stream:add_plugin("presence");
20 stream.disco = { cache = {}, info = {} } 20 local disco_info_mt = {
21 stream.disco.info.identities = { 21 __index = function(t, k)
22 {category = 'client', type='pc', name='Verse'}, 22 local node = { identities = {}, features = {} };
23 } 23 if k == "identities" or k == "features" then
24 stream.disco.info.features = { 24 return t[false][k]
25 {var = xmlns_caps}, 25 end
26 {var = xmlns_disco_info}, 26 t[k] = node;
27 {var = xmlns_disco_items}, 27 return node;
28 } 28 end,
29 stream.disco.items = {} 29 };
30 stream.disco.nodes = {} 30 local disco_items_mt = {
31 __index = function(t, k)
32 local node = { };
33 t[k] = node;
34 return node;
35 end,
36 };
37 stream.disco = {
38 cache = {},
39 info = setmetatable({
40 [false] = {
41 identities = {
42 {category = 'client', type='pc', name='Verse'},
43 },
44 features = {
45 [xmlns_caps] = true,
46 [xmlns_disco_info] = true,
47 [xmlns_disco_items] = true,
48 },
49 },
50 }, disco_info_mt);
51 items = setmetatable({[false]={}}, disco_items_mt);
52 };
31 53
32 stream.caps = {} 54 stream.caps = {}
33 stream.caps.node = 'http://code.matthewwild.co.uk/verse/' 55 stream.caps.node = 'http://code.matthewwild.co.uk/verse/'
34 56
35 local function cmp_identity(item1, item2) 57 local function cmp_identity(item1, item2)
52 74
53 local function cmp_feature(item1, item2) 75 local function cmp_feature(item1, item2)
54 return item1.var < item2.var 76 return item1.var < item2.var
55 end 77 end
56 78
57 local function calculate_hash() 79 local function calculate_hash(node)
58 table.sort(stream.disco.info.identities, cmp_identity) 80 local identities = stream.disco.info[node or false].identities;
59 table.sort(stream.disco.info.features, cmp_feature) 81 table.sort(identities, cmp_identity)
60 local S = '' 82 local features = {};
61 for key,identity in pairs(stream.disco.info.identities) do 83 for var in pairs(stream.disco.info[node or false].features) do
62 S = S .. string.format( 84 features[#features+1] = { var = var };
63 '%s/%s/%s/%s', identity.category, identity.type, 85 end
86 table.sort(features, cmp_feature)
87 local S = {};
88 for key,identity in pairs(identities) do
89 S[#S+1] = table.concat({
90 identity.category, identity.type or '',
64 identity['xml:lang'] or '', identity.name or '' 91 identity['xml:lang'] or '', identity.name or ''
65 ) .. '<' 92 }, '/');
66 end 93 end
67 for key,feature in pairs(stream.disco.info.features) do 94 for key,feature in pairs(features) do
68 S = S .. feature.var .. '<' 95 S[#S+1] = feature.var
69 end 96 end
97 S[#S+1] = '';
98 S = table.concat(S,'<');
70 -- FIXME: make sure S is utf8-encoded 99 -- FIXME: make sure S is utf8-encoded
71 --stream:debug("Computed hash string: "..S); 100 --stream:debug("Computed hash string: "..S);
72 --stream:debug("Computed hash string (sha1): "..sha1(S, true)); 101 --stream:debug("Computed hash string (sha1): "..sha1(S, true));
73 --stream:debug("Computed hash string (sha1+b64): "..b64(sha1(S))); 102 --stream:debug("Computed hash string (sha1+b64): "..b64(sha1(S)));
74 return (b64(sha1(S))) 103 return (b64(sha1(S)))
77 setmetatable(stream.caps, { 106 setmetatable(stream.caps, {
78 __call = function (...) -- vararg: allow calling as function or member 107 __call = function (...) -- vararg: allow calling as function or member
79 -- retrieve the c stanza to insert into the 108 -- retrieve the c stanza to insert into the
80 -- presence stanza 109 -- presence stanza
81 local hash = calculate_hash() 110 local hash = calculate_hash()
111 stream.caps.hash = hash;
112 -- TODO proper caching.... some day
82 return verse.stanza('c', { 113 return verse.stanza('c', {
83 xmlns = xmlns_caps, 114 xmlns = xmlns_caps,
84 hash = 'sha-1', 115 hash = 'sha-1',
85 node = stream.caps.node, 116 node = stream.caps.node,
86 ver = hash 117 ver = hash
87 }) 118 })
88 end 119 end
89 }) 120 })
90 121
91 function stream:add_disco_feature(feature) 122 function stream:add_disco_feature(feature, node)
92 table.insert(self.disco.info.features, {var=feature}); 123 local feature = feature.var or feature;
124 self.disco.info[node or false].features[feature] = true;
93 stream:resend_presence(); 125 stream:resend_presence();
94 end 126 end
95 127
96 function stream:remove_disco_feature(feature) 128 function stream:remove_disco_feature(feature, node)
97 for idx, disco_feature in ipairs(self.disco.info.features) do 129 local feature = feature.var or feature;
98 if disco_feature.var == feature then 130 self.disco.info[node or false].features[feature] = nil;
99 table.remove(self.disco.info.features, idx); 131 stream:resend_presence();
100 stream:resend_presence();
101 return true;
102 end
103 end
104 end 132 end
105 133
106 function stream:add_disco_item(item, node) 134 function stream:add_disco_item(item, node)
107 local disco_items = self.disco.items; 135 local items = self.disco.items[node or false];
108 if node then 136 items[#items +1] = item;
109 disco_items = self.disco.nodes[node]; 137 end
110 if not disco_items then 138
111 disco_items = { features = {}, items = {} }; 139 function stream:remove_disco_item(item, node)
112 self.disco.nodes[node] = disco_items; 140 local items = self.disco.items[node or false];
113 disco_items = disco_items.items; 141 for i=#items,1,-1 do
114 else 142 if items[i] == item then
115 disco_items = disco_items.items; 143 table.remove(items, i);
116 end 144 end
117 end 145 end
118 table.insert(disco_items, item); 146 end
119 end 147
120 148 -- TODO Node?
121 function stream:jid_has_identity(jid, category, type) 149 function stream:jid_has_identity(jid, category, type)
122 local cached_disco = self.disco.cache[jid]; 150 local cached_disco = self.disco.cache[jid];
123 if not cached_disco then 151 if not cached_disco then
124 return nil, "no-cache"; 152 return nil, "no-cache";
125 end 153 end
253 return callback(disco_items); 281 return callback(disco_items);
254 end); 282 end);
255 end 283 end
256 284
257 stream:hook("iq/"..xmlns_disco_info, function (stanza) 285 stream:hook("iq/"..xmlns_disco_info, function (stanza)
258 if stanza.attr.type == 'get' then 286 local query = stanza.tags[1];
259 local query = stanza:child_with_name('query') 287 if stanza.attr.type == 'get' and query.name == "query" then
260 if not query then return; end 288 local query_node = query.attr.node;
261 -- figure out what identities/features to send 289 local node = stream.disco.info[query_node or false];
262 local identities 290 if query_node and query_node == stream.caps.node .. "#" .. stream.caps.hash then
263 local features 291 node = stream.disco.info[false];
264 if query.attr.node then 292 end
265 local hash = calculate_hash() 293 local identities, features = node.identities, node.features
266 local node = stream.disco.nodes[query.attr.node] 294
267 if node and node.info then
268 identities = node.info.identities or {}
269 features = node.info.identities or {}
270 elseif query.attr.node == stream.caps.node..'#'..hash then
271 -- matches caps hash, so use the main info
272 identities = stream.disco.info.identities
273 features = stream.disco.info.features
274 else
275 -- unknown node: give an error
276 local response = verse.stanza('iq',{
277 to = stanza.attr.from,
278 from = stanza.attr.to,
279 id = stanza.attr.id,
280 type = 'error'
281 })
282 response:tag('query',{xmlns = xmlns_disco_info}):reset()
283 response:tag('error',{type = 'cancel'}):tag(
284 'item-not-found',{xmlns = 'urn:ietf:params:xml:ns:xmpp-stanzas'}
285 )
286 stream:send(response)
287 return true
288 end
289 else
290 identities = stream.disco.info.identities
291 features = stream.disco.info.features
292 end
293 -- construct the response 295 -- construct the response
294 local result = verse.stanza('query',{ 296 local result = verse.reply(stanza):tag("query", {
295 xmlns = xmlns_disco_info, 297 xmlns = xmlns_disco_info,
296 node = query.attr.node 298 node = query_node,
297 }) 299 });
298 for key,identity in pairs(identities) do 300 for _,identity in pairs(identities) do
299 result:tag('identity', identity):reset() 301 result:tag('identity', identity):up()
300 end 302 end
301 for key,feature in pairs(features) do 303 for feature in pairs(features) do
302 result:tag('feature', feature):reset() 304 result:tag('feature', { var = feature }):up()
303 end 305 end
304 stream:send(verse.stanza('iq',{ 306 stream:send(result);
305 to = stanza.attr.from,
306 from = stanza.attr.to,
307 id = stanza.attr.id,
308 type = 'result'
309 }):add_child(result))
310 return true 307 return true
311 end 308 end
312 end); 309 end);
313 310
314 stream:hook("iq/"..xmlns_disco_items, function (stanza) 311 stream:hook("iq/"..xmlns_disco_items, function (stanza)
315 if stanza.attr.type == 'get' then 312 local query = stanza.tags[1];
316 local query = stanza:child_with_name('query') 313 if stanza.attr.type == 'get' and query.name == "query" then
317 if not query then return; end
318 -- figure out what items to send 314 -- figure out what items to send
319 local items 315 local items = stream.disco.items[query.attr.node or false];
320 if query.attr.node then 316
321 local node = stream.disco.nodes[query.attr.node]
322 if node then
323 items = node.items or {}
324 else
325 -- unknown node: give an error
326 local response = verse.stanza('iq',{
327 to = stanza.attr.from,
328 from = stanza.attr.to,
329 id = stanza.attr.id,
330 type = 'error'
331 })
332 response:tag('query',{xmlns = xmlns_disco_items}):reset()
333 response:tag('error',{type = 'cancel'}):tag(
334 'item-not-found',{xmlns = 'urn:ietf:params:xml:ns:xmpp-stanzas'}
335 )
336 stream:send(response)
337 return true
338 end
339 else
340 items = stream.disco.items
341 end
342 -- construct the response 317 -- construct the response
343 local result = verse.stanza('query',{ 318 local result = verse.reply(stanza):tag('query',{
344 xmlns = xmlns_disco_items, 319 xmlns = xmlns_disco_items,
345 node = query.attr.node 320 node = query.attr.node
346 }) 321 })
347 for key,item in pairs(items) do 322 for i=1,#items do
348 result:tag('item', item):reset() 323 result:tag('item', items[i]):up()
349 end 324 end
350 stream:send(verse.stanza('iq',{ 325 stream:send(result);
351 to = stanza.attr.from,
352 from = stanza.attr.to,
353 id = stanza.attr.id,
354 type = 'result'
355 }):add_child(result))
356 return true 326 return true
357 end 327 end
358 end); 328 end);
359 329
360 local initial_disco_started; 330 local initial_disco_started;

mercurial