Software /
code /
prosody-modules
Diff
mod_pep_plus/mod_pep_plus.lua @ 2801:cb2342cf3f3c
mod_pep_plus: Snapshot from Prosody trunk 910d3c3f60a6 including dependencies
author | Kim Alvefur <zash@zash.se> |
---|---|
date | Wed, 18 Oct 2017 09:56:29 +0200 |
parent | 2668:f8fc79b3051a |
line wrap: on
line diff
--- a/mod_pep_plus/mod_pep_plus.lua Tue Oct 17 22:39:37 2017 +0200 +++ b/mod_pep_plus/mod_pep_plus.lua Wed Oct 18 09:56:29 2017 +0200 @@ -1,18 +1,19 @@ -local pubsub = require "util.pubsub"; +local pubsub = module:require "util_pubsub"; local jid_bare = require "util.jid".bare; local jid_split = require "util.jid".split; +local jid_join = require "util.jid".join; local set_new = require "util.set".new; local st = require "util.stanza"; local calculate_hash = require "util.caps".calculate_hash; local is_contact_subscribed = require "core.rostermanager".is_contact_subscribed; +local cache = require "util.cache"; +local set = require "util.set"; local xmlns_pubsub = "http://jabber.org/protocol/pubsub"; local xmlns_pubsub_event = "http://jabber.org/protocol/pubsub#event"; local xmlns_pubsub_owner = "http://jabber.org/protocol/pubsub#owner"; local lib_pubsub = module:require "pubsub"; -local handlers = lib_pubsub.handlers; -local pubsub_error_reply = lib_pubsub.pubsub_error_reply; local empty_set = set_new(); @@ -20,6 +21,11 @@ local recipients = {}; local hash_map = {}; +local host = module.host; + +local known_nodes_map = module:open_store("pep", "map"); +local known_nodes = module:open_store("pep"); + function module.save() return { services = services }; end @@ -28,25 +34,41 @@ services = data.services; end -local function subscription_presence(user_bare, recipient) +local function subscription_presence(username, recipient) + local user_bare = jid_join(username, host); local recipient_bare = jid_bare(recipient); if (recipient_bare == user_bare) then return true; end - local username, host = jid_split(user_bare); return is_contact_subscribed(username, host, recipient_bare); end -local function get_broadcaster(name) +local function simple_itemstore(username) + return function (config, node) + if config["persist_items"] then + module:log("debug", "Creating new persistent item store for user %s, node %q", username, node); + known_nodes_map:set(username, node, true); + local archive = module:open_store("pep_"..node, "archive"); + return lib_pubsub.archive_itemstore(archive, config, username, node, false); + else + module:log("debug", "Creating new ephemeral item store for user %s, node %q", username, node); + known_nodes_map:set(username, node, nil); + return cache.new(tonumber(config["max_items"])); + end + end +end + +local function get_broadcaster(username) + local user_bare = jid_join(username, host); local function simple_broadcast(kind, node, jids, item) if item then item = st.clone(item); item.attr.xmlns = nil; -- Clear the pubsub namespace end - local message = st.message({ from = name, type = "headline" }) + local message = st.message({ from = user_bare, type = "headline" }) :tag("event", { xmlns = xmlns_pubsub_event }) :tag(kind, { node = node }) :add_child(item); for jid in pairs(jids) do - module:log("debug", "Sending notification to %s from %s: %s", jid, name, tostring(item)); + module:log("debug", "Sending notification to %s from %s: %s", jid, user_bare, tostring(item)); message.attr.to = jid; module:send(message); end @@ -54,8 +76,10 @@ return simple_broadcast; end -function get_pep_service(name) - local service = services[name]; +function get_pep_service(username) + module:log("debug", "get_pep_service(%q)", username); + local user_bare = jid_join(username, host); + local service = services[username]; if service then return service; end @@ -155,42 +179,51 @@ }; node_defaults = { - ["pubsub#max_items"] = "1"; + ["max_items"] = 1; + ["persist_items"] = true; }; autocreate_on_publish = true; autocreate_on_subscribe = true; - broadcaster = get_broadcaster(name); + itemstore = simple_itemstore(username); + broadcaster = get_broadcaster(username); get_affiliation = function (jid) - if jid_bare(jid) == name then + if jid_bare(jid) == user_bare then return "owner"; - elseif subscription_presence(name, jid) then + elseif subscription_presence(username, jid) then return "subscriber"; end end; normalize_jid = jid_bare; }); - services[name] = service; - module:add_item("pep-service", { service = service, jid = name }); + local nodes, err = known_nodes:get(username); + if nodes then + module:log("debug", "Restoring nodes for user %s", username); + for node in pairs(nodes) do + module:log("debug", "Restoring node %q", node); + service:create(node, true); + end + elseif err then + module:log("error", "Could not restore nodes for %s: %s", username, err); + else + module:log("debug", "No known nodes"); + end + services[username] = service; + module:add_item("pep-service", { service = service, jid = user_bare }); return service; end function handle_pubsub_iq(event) local origin, stanza = event.origin, event.stanza; - local pubsub = stanza.tags[1]; - local action = pubsub.tags[1]; - if not action then - return origin.send(st.error_reply(stanza, "cancel", "bad-request")); + local service_name = origin.username; + if stanza.attr.to ~= nil then + service_name = jid_split(stanza.attr.to); end - local service_name = stanza.attr.to or origin.username.."@"..origin.host local service = get_pep_service(service_name); - local handler = handlers[stanza.attr.type.."_"..action.name]; - if handler then - handler(origin, stanza, action, service); - return true; - end + + return lib_pubsub.handle_pubsub_iq(event, service) end module:hook("iq/bare/"..xmlns_pubsub..":pubsub", handle_pubsub_iq); @@ -225,7 +258,7 @@ local function resend_last_item(jid, node, service) local ok, items = service:get_items(node, jid); if not ok then return; end - for i, id in ipairs(items) do + for _, id in ipairs(items) do service.config.broadcaster("items", node, { [jid] = true }, items[id]); end end @@ -268,30 +301,34 @@ module:hook("presence/bare", function(event) -- inbound presence to bare JID recieved local origin, stanza = event.origin, event.stanza; - local user = stanza.attr.to or (origin.username..'@'..origin.host); local t = stanza.attr.type; - local self = not stanza.attr.to; - local service = get_pep_service(user); + local is_self = not stanza.attr.to; + local username = jid_split(stanza.attr.to); + local user_bare = jid_bare(stanza.attr.to); + if is_self then + username = origin.username; + user_bare = jid_join(username, host); + end if not t then -- available presence - if self or subscription_presence(user, stanza.attr.from) then + if is_self or subscription_presence(username, stanza.attr.from) then local recipient = stanza.attr.from; - local current = recipients[user] and recipients[user][recipient]; + local current = recipients[username] and recipients[username][recipient]; local hash, query_node = get_caps_hash_from_presence(stanza, current); if current == hash or (current and current == hash_map[hash]) then return; end if not hash then - update_subscriptions(recipient, user); + update_subscriptions(recipient, username); else - recipients[user] = recipients[user] or {}; + recipients[username] = recipients[username] or {}; if hash_map[hash] then - update_subscriptions(recipient, user, hash_map[hash]); + update_subscriptions(recipient, username, hash_map[hash]); else - recipients[user][recipient] = hash; + recipients[username][recipient] = hash; local from_bare = origin.type == "c2s" and origin.username.."@"..origin.host; - if self or origin.type ~= "c2s" or (recipients[from_bare] and recipients[from_bare][origin.full_jid]) ~= hash then + if is_self or origin.type ~= "c2s" or (recipients[from_bare] and recipients[from_bare][origin.full_jid]) ~= hash then -- COMPAT from ~= stanza.attr.to because OneTeam can't deal with missing from attribute origin.send( - st.stanza("iq", {from=user, to=stanza.attr.from, id="disco", type="get"}) + st.stanza("iq", {from=user_bare, to=stanza.attr.from, id="disco", type="get"}) :tag("query", {xmlns = "http://jabber.org/protocol/disco#info", node = query_node}) ); end @@ -299,14 +336,14 @@ end end elseif t == "unavailable" then - update_subscriptions(stanza.attr.from, user); - elseif not self and t == "unsubscribe" then + update_subscriptions(stanza.attr.from, username); + elseif not is_self and t == "unsubscribe" then local from = jid_bare(stanza.attr.from); - local subscriptions = recipients[user]; + local subscriptions = recipients[username]; if subscriptions then for subscriber in pairs(subscriptions) do if jid_bare(subscriber) == from then - update_subscriptions(subscriber, user); + update_subscriptions(subscriber, username); end end end @@ -321,10 +358,15 @@ end -- Process disco response - local self = not stanza.attr.to; - local user = stanza.attr.to or (origin.username..'@'..origin.host); + local is_self = stanza.attr.to == nil; + local user_bare = jid_bare(stanza.attr.to); + local username = jid_split(stanza.attr.to); + if is_self then + username = origin.username; + user_bare = jid_join(username, host); + end local contact = stanza.attr.from; - local current = recipients[user] and recipients[user][contact]; + local current = recipients[username] and recipients[username][contact]; if type(current) ~= "string" then return; end -- check if waiting for recipient's response local ver = current; if not string.find(current, "#") then @@ -338,20 +380,26 @@ end end hash_map[ver] = notify; -- update hash map - if self then + if is_self then + -- Optimization: Fiddle with other local users for jid, item in pairs(origin.roster) do -- for all interested contacts - if item.subscription == "both" or item.subscription == "from" then - if not recipients[jid] then recipients[jid] = {}; end - update_subscriptions(contact, jid, notify); + if jid then + local contact_node, contact_host = jid_split(jid); + if contact_host == host and item.subscription == "both" or item.subscription == "from" then + update_subscriptions(user_bare, contact_node, notify); + end end end end - update_subscriptions(contact, user, notify); + update_subscriptions(contact, username, notify); end); module:hook("account-disco-info-node", function(event) local reply, stanza, origin = event.reply, event.stanza, event.origin; - local service_name = stanza.attr.to or origin.username.."@"..origin.host + local service_name = origin.username; + if stanza.attr.to ~= nil then + service_name = jid_split(stanza.attr.to); + end local service = get_pep_service(service_name); local node = event.node; local ok = service:get_items(node, jid_bare(stanza.attr.from) or true); @@ -361,33 +409,64 @@ end); module:hook("account-disco-info", function(event) - local reply = event.reply; + local origin, reply = event.origin, event.reply; + reply:tag('identity', {category='pubsub', type='pep'}):up(); - reply:tag('feature', {var='http://jabber.org/protocol/pubsub#publish'}):up(); + + local username = jid_split(reply.attr.from) or origin.username; + local service = get_pep_service(username); + + local suppored_features = lib_pubsub.get_feature_set(service) + set.new{ + -- Features not covered by the above + "access-presence", + "auto-subscribe", + "filtered-notifications", + "last-published", + "persistent-items", + "presence-notifications", + "presence-subscribe", + }; + + for feature in suppored_features do + reply:tag('feature', {var=xmlns_pubsub.."#"..feature}):up(); + end end); module:hook("account-disco-items-node", function(event) local reply, stanza, origin = event.reply, event.stanza, event.origin; local node = event.node; - local service_name = stanza.attr.to or origin.username.."@"..origin.host - local service = get_pep_service(service_name); + local is_self = stanza.attr.to == nil; + local user_bare = jid_bare(stanza.attr.to); + local username = jid_split(stanza.attr.to); + if is_self then + username = origin.username; + user_bare = jid_join(username, host); + end + local service = get_pep_service(username); local ok, ret = service:get_items(node, jid_bare(stanza.attr.from) or true); if not ok then return; end event.exists = true; for _, id in ipairs(ret) do - reply:tag("item", { jid = service_name, name = id }):up(); + reply:tag("item", { jid = user_bare, name = id }):up(); end end); module:hook("account-disco-items", function(event) local reply, stanza, origin = event.reply, event.stanza, event.origin; - local service_name = reply.attr.from or origin.username.."@"..origin.host - local service = get_pep_service(service_name); + local is_self = stanza.attr.to == nil; + local user_bare = jid_bare(stanza.attr.to); + local username = jid_split(stanza.attr.to); + if is_self then + username = origin.username; + user_bare = jid_join(username, host); + end + local service = get_pep_service(username); + local ok, ret = service:get_nodes(jid_bare(stanza.attr.from)); if not ok then return; end for node, node_obj in pairs(ret) do - reply:tag("item", { jid = service_name, node = node, name = node_obj.config.name }):up(); + reply:tag("item", { jid = user_bare, node = node, name = node_obj.config.name }):up(); end end);