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); |