plugins/adhoc.lua

1
local verse = require "verse";
2
local adhoc = require "verse.lib.adhoc";
3
 
4
local xmlns_commands = "http://jabber.org/protocol/commands";
5
local xmlns_data = "jabber:x:data";
6
 
7
local command_mt = {};
8
command_mt.__index = command_mt;
9
 
10
-- Table of commands we provide
11
local commands = {};
12
 
13
function verse.plugins.adhoc(stream)
14
	stream:add_plugin("disco");
15
	stream:add_disco_feature(xmlns_commands);
16
 
17
	function stream:query_commands(jid, callback)
18
		stream:disco_items(jid, xmlns_commands, function (items)
19
			stream:debug("adhoc list returned")
20
			local command_list = {};
21
			for _, item in ipairs(items) do
22
				command_list[item.node] = item.name;
23
			end
24
			stream:debug("adhoc calling callback")
25
			return callback(command_list);
26
		end);
27
	end
28
 
29
	function stream:execute_command(jid, command, callback)
30
		local cmd = setmetatable({
31
			stream = stream, jid = jid,
32
			command = command, callback = callback
33
		}, command_mt);
34
		return cmd:execute();
35
	end
36
 
37
	-- ACL checker for commands we provide
38
	local function has_affiliation(jid, aff)
39
		if not(aff) or aff == "user" then return true; end
40
		if type(aff) == "function" then
41
			return aff(jid);
42
		end
43
		-- TODO: Support 'roster', etc.
44
	end
45
 
46
	function stream:add_adhoc_command(name, node, handler, permission)
47
		commands[node] = adhoc.new(name, node, handler, permission);
48
		stream:add_disco_item({ jid = stream.jid, node = node, name = name }, xmlns_commands);
49
		return commands[node];
50
	end
51
 
52
	local function handle_command(stanza)
53
		local command_tag = stanza.tags[1];
54
		local node = command_tag.attr.node;
55
 
56
		local handler = commands[node];
57
		if not handler then return; end
58
 
59
		if not has_affiliation(stanza.attr.from, handler.permission) then
60
			stream:send(verse.error_reply(stanza, "auth", "forbidden", "You don't have permission to execute this command"):up()
61
			:add_child(handler:cmdtag("canceled")
62
				:tag("note", {type="error"}):text("You don't have permission to execute this command")));
63
			return true
64
		end
65
 
66
		-- User has permission now execute the command
67
		return adhoc.handle_cmd(handler, { send = function (d) return stream:send(d) end }, stanza);
68
	end
69
 
70
	stream:hook("iq/"..xmlns_commands, function (stanza)
71
		local type = stanza.attr.type;
72
		local name = stanza.tags[1].name;
73
		if type == "set" and name == "command" then
74
			return handle_command(stanza);
75
		end
76
	end);
77
end
78
 
79
function command_mt:_process_response(result)
80
	if result.attr.type == "error" then
81
		self.status = "canceled";
82
		self.callback(self, {});
83
		return;
84
	end
85
	local command = result:get_child("command", xmlns_commands);
86
	self.status = command.attr.status;
87
	self.sessionid = command.attr.sessionid;
88
	self.form = command:get_child("x", xmlns_data);
89
	self.note = command:get_child("note"); --FIXME handle multiple <note/>s
90
	self.callback(self);
91
end
92
 
93
-- Initial execution of a command
94
function command_mt:execute()
95
	local iq = verse.iq({ to = self.jid, type = "set" })
96
		:tag("command", { xmlns = xmlns_commands, node = self.command });
97
	self.stream:send_iq(iq, function (result)
98
		self:_process_response(result);
99
	end);
100
end
101
 
102
function command_mt:next(form)
103
	local iq = verse.iq({ to = self.jid, type = "set" })
104
		:tag("command", {
105
			xmlns = xmlns_commands,
106
			node = self.command,
107
			sessionid = self.sessionid
108
		});
109
 
110
	if form then iq:add_child(form); end
111
 
112
	self.stream:send_iq(iq, function (result)
113
		self:_process_response(result);
114
	end);
115
end