File

plugins/roster.lua @ 472:864c9dc27c60

buildscripts: Update included squish (now supports Lua 5.2)
author Matthew Wild <mwild1@gmail.com>
date Fri, 17 Mar 2023 11:07:02 +0000
parent 395:e86144a4eaa1
child 490:6b2f31da9610
line wrap: on
line source

local verse = require "verse";
local bare_jid = require "util.jid".bare;

local xmlns_roster = "jabber:iq:roster";
local xmlns_rosterver = "urn:xmpp:features:rosterver";
local t_insert = table.insert;

function verse.plugins.roster(stream)
	local ver_supported = false;
	local roster = {
		items = {};
		ver = "";
		-- TODO:
		-- groups = {};
	};
	stream.roster = roster;

	stream:hook("stream-features", function(features_stanza)
		if features_stanza:get_child("ver", xmlns_rosterver) then
			ver_supported = true;
		end
	end);

	local function item_lua2xml(item_table)
		local xml_item = verse.stanza("item", { xmlns = xmlns_roster });
		for k, v in pairs(item_table) do
			if k ~= "groups" then
				xml_item.attr[k] = v;
			else
				for i = 1,#v do
					xml_item:tag("group"):text(v[i]):up();
				end
			end
		end
		return xml_item;
	end

	local function item_xml2lua(xml_item)
		local item_table = { };
		local groups = {};
		item_table.groups = groups;

		for k, v in pairs(xml_item.attr) do
			if k ~= "xmlns" then
				item_table[k] = v
			end
		end

		for group in xml_item:childtags("group") do
			t_insert(groups, group:get_text())
		end
		return item_table;
	end

	function roster:load(r)
		roster.ver, roster.items = r.ver, r.items;
	end

	function roster:dump()
		return {
			ver = roster.ver,
			items = roster.items,
		};
	end

	-- should this be add_contact(item, callback) instead?
	function roster:add_contact(jid, name, groups, callback)
		local item = { jid = jid, name = name, groups = groups };
		local stanza = verse.iq({ type = "set" })
			:tag("query", { xmlns = xmlns_roster })
				:add_child(item_lua2xml(item));
		stream:send_iq(stanza, function (reply)
			if not callback then return end
			if reply.attr.type == "result" then
				callback(true);
			else
				callback(nil, reply);
			end
		end);
	end
	-- What about subscriptions?

	function roster:delete_contact(jid, callback)
		jid = (type(jid) == "table" and jid.jid) or jid;
		local item = { jid = jid, subscription = "remove" }
		if not roster.items[jid] then return false, "item-not-found"; end
		stream:send_iq(verse.iq({ type = "set" })
			:tag("query", { xmlns = xmlns_roster })
				:add_child(item_lua2xml(item)),
			function (reply)
				if not callback then return end
				if reply.attr.type == "result" then
					callback(true);
				else
					callback(nil, reply);
				end
			end);
	end

	local function add_item(item) -- Takes one roster <item/>
		local roster_item = item_xml2lua(item);
		roster.items[roster_item.jid] = roster_item;
	end

	-- Private low level
	local function delete_item(jid)
		local deleted_item = roster.items[jid];
		roster.items[jid] = nil;
		return deleted_item;
	end

	function roster:fetch(callback)
		stream:send_iq(verse.iq({type="get"}):tag("query", { xmlns = xmlns_roster, ver = ver_supported and roster.ver or nil }),
			function (result)
				if result.attr.type == "result" then
					local query = result:get_child("query", xmlns_roster);
					if query then
						roster.items = {};
						for item in query:childtags("item") do
							add_item(item)
						end
						roster.ver = query.attr.ver or "";
					end
					callback(roster);
				else
					callback(nil, result);
				end
			end);
	end

	stream:hook("iq/"..xmlns_roster, function(stanza)
		local type, from = stanza.attr.type, stanza.attr.from;
		if type == "set" and (not from or from == bare_jid(stream.jid)) then
			local query = stanza:get_child("query", xmlns_roster);
			local item = query and query:get_child("item");
			if item then
				local event, target;
				local jid = item.attr.jid;
				if item.attr.subscription == "remove" then
					event = "removed"
					target = delete_item(jid);
				else
					event = roster.items[jid] and "changed" or "added";
					add_item(item)
					target = roster.items[jid];
				end
				roster.ver = query.attr.ver;
				if target then
					stream:event("roster/item-"..event, target);
				end
			-- TODO else return error? Events?
			end
			stream:send(verse.reply(stanza))
			return true;
		end
	end);
end