File

plugins/adhoc.lua @ 445:b119dc4d8bc2

plugins.smacks: Don't warn about zero stanzas acked It's only if the count somehow goes backwards that something is really wrong. An ack for zero stanzas is fine and we don't need to do anything.
author Kim Alvefur <zash@zash.se>
date Thu, 10 Jun 2021 11:58:23 +0200 (2021-06-10)
parent 380:0891b4e27766
child 490:6b2f31da9610
line wrap: on
line source
local verse = require "verse";
local adhoc = require "lib.adhoc";

local xmlns_commands = "http://jabber.org/protocol/commands";
local xmlns_data = "jabber:x:data";

local command_mt = {};
command_mt.__index = command_mt;

-- Table of commands we provide
local commands = {};

function verse.plugins.adhoc(stream)
	stream:add_plugin("disco");
	stream:add_disco_feature(xmlns_commands);

	function stream:query_commands(jid, callback)
		stream:disco_items(jid, xmlns_commands, function (items)
			stream:debug("adhoc list returned")
			local command_list = {};
			for _, item in ipairs(items) do
				command_list[item.node] = item.name;
			end
			stream:debug("adhoc calling callback")
			return callback(command_list);
		end);
	end

	function stream:execute_command(jid, command, callback)
		local cmd = setmetatable({
			stream = stream, jid = jid,
			command = command, callback = callback
		}, command_mt);
		return cmd:execute();
	end

	-- ACL checker for commands we provide
	local function has_affiliation(jid, aff)
		if not(aff) or aff == "user" then return true; end
		if type(aff) == "function" then
			return aff(jid);
		end
		-- TODO: Support 'roster', etc.
	end

	function stream:add_adhoc_command(name, node, handler, permission)
		commands[node] = adhoc.new(name, node, handler, permission);
		stream:add_disco_item({ jid = stream.jid, node = node, name = name }, xmlns_commands);
		return commands[node];
	end

	local function handle_command(stanza)
		local command_tag = stanza.tags[1];
		local node = command_tag.attr.node;

		local handler = commands[node];
		if not handler then return; end

		if not has_affiliation(stanza.attr.from, handler.permission) then
			stream:send(verse.error_reply(stanza, "auth", "forbidden", "You don't have permission to execute this command"):up()
			:add_child(handler:cmdtag("canceled")
				:tag("note", {type="error"}):text("You don't have permission to execute this command")));
			return true
		end

		-- User has permission now execute the command
		return adhoc.handle_cmd(handler, { send = function (d) return stream:send(d) end }, stanza);
	end

	stream:hook("iq/"..xmlns_commands, function (stanza)
		local type = stanza.attr.type;
		local name = stanza.tags[1].name;
		if type == "set" and name == "command" then
			return handle_command(stanza);
		end
	end);
end

function command_mt:_process_response(result)
	if result.attr.type == "error" then
		self.status = "canceled";
		self.callback(self, {});
		return;
	end
	local command = result:get_child("command", xmlns_commands);
	self.status = command.attr.status;
	self.sessionid = command.attr.sessionid;
	self.form = command:get_child("x", xmlns_data);
	self.note = command:get_child("note"); --FIXME handle multiple <note/>s
	self.callback(self);
end

-- Initial execution of a command
function command_mt:execute()
	local iq = verse.iq({ to = self.jid, type = "set" })
		:tag("command", { xmlns = xmlns_commands, node = self.command });
	self.stream:send_iq(iq, function (result)
		self:_process_response(result);
	end);
end

function command_mt:next(form)
	local iq = verse.iq({ to = self.jid, type = "set" })
		:tag("command", {
			xmlns = xmlns_commands,
			node = self.command,
			sessionid = self.sessionid
		});

	if form then iq:add_child(form); end

	self.stream:send_iq(iq, function (result)
		self:_process_response(result);
	end);
end