plugins/disco.lua

changeset 423
e98cef597393
parent 392
cdea6a28369e
child 429
46897d7769c3
equal deleted inserted replaced
422:ff59e4a1a600 423:e98cef597393
7 -- 7 --
8 8
9 local verse = require "verse"; 9 local verse = require "verse";
10 local b64 = require("mime").b64; 10 local b64 = require("mime").b64;
11 local sha1 = require("util.hashes").sha1; 11 local sha1 = require("util.hashes").sha1;
12 local calculate_hash = require "util.caps".calculate_hash;
12 13
13 local xmlns_caps = "http://jabber.org/protocol/caps"; 14 local xmlns_caps = "http://jabber.org/protocol/caps";
14 local xmlns_disco = "http://jabber.org/protocol/disco"; 15 local xmlns_disco = "http://jabber.org/protocol/disco";
15 local xmlns_disco_info = xmlns_disco.."#info"; 16 local xmlns_disco_info = xmlns_disco.."#info";
16 local xmlns_disco_items = xmlns_disco.."#items"; 17 local xmlns_disco_items = xmlns_disco.."#items";
52 }; 53 };
53 54
54 stream.caps = {} 55 stream.caps = {}
55 stream.caps.node = 'http://code.matthewwild.co.uk/verse/' 56 stream.caps.node = 'http://code.matthewwild.co.uk/verse/'
56 57
57 local function cmp_identity(item1, item2) 58 local function build_self_disco_info_stanza(query_node)
58 if item1.category < item2.category then 59 local node = stream.disco.info[query_node or false];
59 return true; 60 if query_node and query_node == stream.caps.node .. "#" .. stream.caps.hash then
60 elseif item2.category < item1.category then 61 node = stream.disco.info[false];
61 return false; 62 end
62 end 63 local identities, features = node.identities, node.features
63 if item1.type < item2.type then 64
64 return true; 65 -- construct the response
65 elseif item2.type < item1.type then 66 local result = verse.stanza("query", {
66 return false; 67 xmlns = xmlns_disco_info,
67 end 68 node = query_node,
68 if (not item1['xml:lang'] and item2['xml:lang']) or 69 });
69 (item2['xml:lang'] and item1['xml:lang'] < item2['xml:lang']) then 70 for _,identity in pairs(identities) do
70 return true 71 result:tag('identity', identity):up()
71 end 72 end
72 return false 73 for feature in pairs(features) do
73 end 74 result:tag('feature', { var = feature }):up()
74 75 end
75 local function cmp_feature(item1, item2) 76 return result;
76 return item1.var < item2.var
77 end
78
79 local function calculate_hash(node)
80 local identities = stream.disco.info[node or false].identities;
81 table.sort(identities, cmp_identity)
82 local features = {};
83 for var in pairs(stream.disco.info[node or false].features) do
84 features[#features+1] = { var = var };
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 '',
91 identity['xml:lang'] or '', identity.name or ''
92 }, '/');
93 end
94 for key,feature in pairs(features) do
95 S[#S+1] = feature.var
96 end
97 S[#S+1] = '';
98 S = table.concat(S,'<');
99 -- FIXME: make sure S is utf8-encoded
100 --stream:debug("Computed hash string: "..S);
101 --stream:debug("Computed hash string (sha1): "..sha1(S, true));
102 --stream:debug("Computed hash string (sha1+b64): "..b64(sha1(S)));
103 return (b64(sha1(S)))
104 end 77 end
105 78
106 setmetatable(stream.caps, { 79 setmetatable(stream.caps, {
107 __call = function (...) -- vararg: allow calling as function or member 80 __call = function (...) -- vararg: allow calling as function or member
108 -- retrieve the c stanza to insert into the 81 -- retrieve the c stanza to insert into the
109 -- presence stanza 82 -- presence stanza
110 local hash = calculate_hash() 83 local hash = calculate_hash(build_self_disco_info_stanza())
111 stream.caps.hash = hash; 84 stream.caps.hash = hash;
112 -- TODO proper caching.... some day 85 -- TODO proper caching.... some day
113 return verse.stanza('c', { 86 return verse.stanza('c', {
114 xmlns = xmlns_caps, 87 xmlns = xmlns_caps,
115 hash = 'sha-1', 88 hash = 'sha-1',
294 end 267 end
295 268
296 stream:hook("iq/"..xmlns_disco_info, function (stanza) 269 stream:hook("iq/"..xmlns_disco_info, function (stanza)
297 local query = stanza.tags[1]; 270 local query = stanza.tags[1];
298 if stanza.attr.type == 'get' and query.name == "query" then 271 if stanza.attr.type == 'get' and query.name == "query" then
299 local query_node = query.attr.node; 272 local query_tag = build_self_disco_info_stanza(query.attr.node);
300 local node = stream.disco.info[query_node or false]; 273 local result = verse.reply(stanza):add_child(query_tag);
301 if query_node and query_node == stream.caps.node .. "#" .. stream.caps.hash then
302 node = stream.disco.info[false];
303 end
304 local identities, features = node.identities, node.features
305
306 -- construct the response
307 local result = verse.reply(stanza):tag("query", {
308 xmlns = xmlns_disco_info,
309 node = query_node,
310 });
311 for _,identity in pairs(identities) do
312 result:tag('identity', identity):up()
313 end
314 for feature in pairs(features) do
315 result:tag('feature', { var = feature }):up()
316 end
317 stream:send(result); 274 stream:send(result);
318 return true 275 return true
319 end 276 end
320 end); 277 end);
321 278
340 297
341 local initial_disco_started; 298 local initial_disco_started;
342 stream:hook("ready", function () 299 stream:hook("ready", function ()
343 if initial_disco_started then return; end 300 if initial_disco_started then return; end
344 initial_disco_started = true; 301 initial_disco_started = true;
302
303 -- Using the disco cache, fires events for each identity of a given JID
304 local function scan_identities_for_service(service_jid)
305 local service_disco_info = stream.disco.cache[service_jid];
306 if service_disco_info then
307 for identity in pairs(service_disco_info.identities) do
308 local category, type = identity:match("^(.*)/(.*)$");
309 print(service_jid, category, type)
310 stream:event("disco/service-discovered/"..category, {
311 type = type, jid = service_jid;
312 });
313 end
314 end
315 end
316
317 stream:disco_info(stream.host, nil, function ()
318 scan_identities_for_service(stream.host);
319 end);
320
345 stream:disco_local_services(function (services) 321 stream:disco_local_services(function (services)
346 for _, service in ipairs(services) do 322 for _, service in ipairs(services) do
347 local service_disco_info = stream.disco.cache[service.jid]; 323 scan_identities_for_service(service.jid);
348 if service_disco_info then
349 for identity in pairs(service_disco_info.identities) do
350 local category, type = identity:match("^(.*)/(.*)$");
351 stream:event("disco/service-discovered/"..category, {
352 type = type, jid = service.jid;
353 });
354 end
355 end
356 end 324 end
357 stream:event("ready"); 325 stream:event("ready");
358 end); 326 end);
359 return true; 327 return true;
360 end, 50); 328 end, 50);

mercurial