File

plugins/pubsub.lua @ 498:50d0bd035bb7

util.sasl.oauthbearer: Don't send authzid It's not needed and not recommended in XMPP unless we want to act as someone other than who we authenticate as. We find out the JID during resource binding.
author Kim Alvefur <zash@zash.se>
date Fri, 23 Jun 2023 12:09:49 +0200
parent 490:6b2f31da9610
line wrap: on
line source

local verse = require "verse";

local t_insert = table.insert;

local xmlns_pubsub = "http://jabber.org/protocol/pubsub";
local xmlns_pubsub_owner = "http://jabber.org/protocol/pubsub#owner";
local xmlns_pubsub_event = "http://jabber.org/protocol/pubsub#event";
-- local xmlns_pubsub_errors = "http://jabber.org/protocol/pubsub#errors";

local pubsub = {};
local pubsub_mt = { __index = pubsub };

function verse.plugins.pubsub(stream)
	stream.pubsub = setmetatable({ stream = stream }, pubsub_mt);
	stream:hook("message", function (message)
		local m_from = message.attr.from;
		for pubsub_event in message:childtags("event", xmlns_pubsub_event) do
			local items = pubsub_event:get_child("items");
			if items then
				local node = items.attr.node;
				for item in items:childtags("item") do
					stream:event("pubsub/event", {
						from = m_from;
						node = node;
						item = item;
					});
				end
				for retract in items:childtags("retract") do
					stream:event("pubsub/retraction", {
						from = m_from;
						node = node;
						item = retract;
					});
				end
			end
		end
	end);
	return true;
end

-- COMPAT
function pubsub:create(server, node, callback)
	return self:service(server):node(node):create(nil, callback);
end

function pubsub:subscribe(server, node, jid, callback)
	return self:service(server):node(node):subscribe(jid, nil, callback);
end

function pubsub:publish(server, node, id, item, callback)
	return self:service(server):node(node):publish(id, nil, item, callback);
end

--------------------------------------------------------------------------
---------------------New and improved PubSub interface--------------------
--------------------------------------------------------------------------

local pubsub_service = {};
local pubsub_service_mt = { __index = pubsub_service };

-- TODO should the property be named 'jid' instead?
function pubsub:service(service)
	return setmetatable({ stream = self.stream, service = service }, pubsub_service_mt)
end

-- Helper function for iq+pubsub tags

local function pubsub_iq(iq_type, to, ns, op, node, jid, item_id, op_attr_extra)
	local st = verse.iq{ type = iq_type or "get", to = to }
		:tag("pubsub", { xmlns = ns or xmlns_pubsub }) -- ns would be ..#owner
			local op_attr = { node = node, jid = jid };
			if op_attr_extra then
				for k, v in pairs(op_attr_extra) do
					op_attr[k] = v;
				end
			end
			if op then st:tag(op, op_attr); end
			if item_id then
				st:tag("item", { id = item_id ~= true and item_id or nil });
			end
	return st;
end

-- http://xmpp.org/extensions/xep-0060.html#entity-subscriptions
function pubsub_service:subscriptions(callback)
	self.stream:send_iq(pubsub_iq(nil, self.service, nil, "subscriptions")
	, callback and function (result)
		if result.attr.type == "result" then
			local ps = result:get_child("pubsub", xmlns_pubsub);
			local subs = ps and ps:get_child("subscriptions");
			local nodes = {};
			if subs then
				for sub in subs:childtags("subscription") do
					local node = self:node(sub.attr.node)
					node.subscription = sub;
					node.subscribed_jid = sub.attr.jid;
					t_insert(nodes, node);
					-- FIXME Good enough?
					-- Or how about:
					-- nodes[node] = sub;
				end
			end
			callback(nodes);
		else
			callback(false, result:get_error());
		end
	end or nil);
end

-- http://xmpp.org/extensions/xep-0060.html#entity-affiliations
function pubsub_service:affiliations(callback)
	self.stream:send_iq(pubsub_iq(nil, self.service, nil, "affiliations")
	, callback and function (result)
		if result.attr.type == "result" then
			local ps = result:get_child("pubsub", xmlns_pubsub);
			local affils = ps and ps:get_child("affiliations") or {};
			local nodes = {};
			if affils then
				for affil in affils:childtags("affiliation") do
					local node = self:node(affil.attr.node)
					node.affiliation = affil;
					t_insert(nodes, node);
					-- nodes[node] = affil;
				end
			end
			callback(nodes);
		else
			callback(false, result:get_error());
		end
	end or nil);
end

function pubsub_service:nodes(callback)
	self.stream:disco_items(self.service, nil, function(items, ...)
		if items then
			for i=1,#items do
				items[i] = self:node(items[i].node);
			end
		end
		callback(items, ...)
	end);
end

local pubsub_node = {};
local pubsub_node_mt = { __index = pubsub_node };

function pubsub_service:node(node)
	return setmetatable({ stream = self.stream, service = self.service, node = node }, pubsub_node_mt)
end

function pubsub_mt:__call(service, node)
	local s = self:service(service);
	return node and s:node(node) or s;
end

function pubsub_node:hook(callback, prio)
	self._hooks = self._hooks or setmetatable({}, { __mode = 'kv' });
	local function hook(event)
		-- FIXME service == nil would mean anyone,
		-- publishing would be go to your bare jid.
		-- So if you're only interestied in your own
		-- events, hook your own bare jid.
		if (not event.service or event.from == self.service) and event.node == self.node then
			return callback(event)
		end
	end
	self._hooks[callback] = hook;
	self.stream:hook("pubsub/event", hook, prio);
	return hook;
end

function pubsub_node:unhook(callback)
	if callback then
		local hook = self._hooks[callback];
		self.stream:unhook("pubsub/event", hook);
	elseif self._hooks then
		for hook in pairs(self._hooks) do
			self.stream:unhook("pubsub/event", hook);
		end
	end
end

function pubsub_node:create(config, callback)
	if config ~= nil then
		error("Not implemented yet.");
	else
		self.stream:send_iq(pubsub_iq("set", self.service, nil, "create", self.node), callback);
	end
end

-- <configure/> and <default/> rolled into one
function pubsub_node:configure(config, callback)
	if config ~= nil then
		error("Not implemented yet.");
		--[[
		if config == true then
			self.stream:send_iq(pubsub_iq("get", self.service, nil, "configure", self.node)
			, function(reply)
				local form = reply:get_child("pubsub"):get_child("configure"):get_cild("x");
				local config = callback(require"prosody.util.dataforms".something(form))
				self.stream:send_iq(pubsub_iq("set", config, ...))
			end);
		end
		--]]
		-- fetch form and pass it to the callback
		-- which would process it and pass it back
		-- and then we submit it
		-- elseif type(config) == "table" then
		-- it's a form or stanza that we submit
		-- end
		-- this would be done for everything that needs a config
	end
	self.stream:send_iq(pubsub_iq("set", self.service, nil, config == nil and "default" or "configure", self.node), callback);
end

function pubsub_node:publish(id, options, node, callback)
	if options ~= nil then
		error("Node configuration is not implemented yet.");
	end
	self.stream:send_iq(pubsub_iq("set", self.service, nil, "publish", self.node, nil, id or true)
	:add_child(node)
	, callback);
end

function pubsub_node:subscribe(jid, options, callback)
	jid = jid or self.stream.jid;
	if options ~= nil then
		error("Subscription configuration is not implemented yet.");
	end
	self.stream:send_iq(pubsub_iq("set", self.service, nil, "subscribe", self.node, jid)
	, callback);
end

function pubsub_node:subscription(callback)
	error("Not implemented yet.");
end

function pubsub_node:affiliation(callback)
	error("Not implemented yet.");
end

function pubsub_node:unsubscribe(jid, callback)
	jid = jid or self.subscribed_jid or self.stream.jid;
	self.stream:send_iq(pubsub_iq("set", self.service, nil, "unsubscribe", self.node, jid)
	, callback);
end

function pubsub_node:configure_subscription(options, callback)
	error("Not implemented yet.");
end

function pubsub_node:items(full, callback)
	if full then
		return self:item(nil, callback);
	else
		self.stream:disco_items(self.service, self.node, callback);
	end
end

function pubsub_node:item(id, callback)
	self.stream:send_iq(pubsub_iq("get", self.service, nil, "items", self.node, nil, id)
	, callback);
end

function pubsub_node:retract(id, notify, callback)
	if type(notify) == "function" then -- COMPAT w/ older versions before 'notify' was added
		notify, callback = false, notify;
	end
	self.stream:send_iq(
		pubsub_iq(
			"set",
			self.service,
			nil,
			"retract",
			self.node,
			nil,
			id,
			{ notify = notify and "1" or nil }
		),
		callback
	);
end

function pubsub_node:purge(notify, callback)
	self.stream:send_iq(
		pubsub_iq(
			"set",
			self.service,
			xmlns_pubsub_owner,
			"purge",
			self.node,
			nil,
			nil,
			{ notify = notify and "1" or nil }
		),
		callback
	);
end

function pubsub_node:delete(redirect_uri, callback)
	assert(not redirect_uri, "Not implemented yet.");
	self.stream:send_iq(pubsub_iq("set", self.service, xmlns_pubsub_owner, "delete", self.node)
	, callback);
end