mod_muc_adhoc_bots/mod_muc_adhoc_bots.lua
changeset 5656 eade7ff9f52c
child 5850 8b868c00e38e
equal deleted inserted replaced
5655:b40750891bee 5656:eade7ff9f52c
       
     1 local jid = require "util.jid";
       
     2 local json = require "util.json";
       
     3 local promise = require "util.promise";
       
     4 local st = require "util.stanza";
       
     5 local uuid = require "util.uuid";
       
     6 
       
     7 local xmlns_cmd = "http://jabber.org/protocol/commands";
       
     8 
       
     9 module:hook("muc-disco#info", function(event)
       
    10 	event.reply:tag("feature", {var = xmlns_cmd}):up();
       
    11 end);
       
    12 
       
    13 module:hook("iq-get/bare/http://jabber.org/protocol/disco#items:query", function (event)
       
    14 	local room = prosody.hosts[module:get_host()].modules.muc.get_room_from_jid(event.stanza.attr.to);
       
    15 	local occupant = room:get_occupant_by_real_jid(event.stanza.attr.from)
       
    16 	if event.stanza.tags[1].attr.node ~= xmlns_cmd or not occupant then
       
    17 		return
       
    18 	end
       
    19 
       
    20 	local bots = module:get_option_array("adhoc_bots", {})
       
    21 	bots:map(function(bot)
       
    22 		return module:send_iq(
       
    23 			st.iq({ type = "get", id = uuid.generate(), to = bot, from = room:get_occupant_jid(event.stanza.attr.from) })
       
    24 				:tag("query", { xmlns = "http://jabber.org/protocol/disco#items", node = xmlns_cmd }):up(),
       
    25 			nil,
       
    26 			5
       
    27 		)
       
    28 	end)
       
    29 
       
    30 	promise.all_settled(bots):next(function (bot_commands)
       
    31 		local reply = st.reply(event.stanza):query("http://jabber.org/protocol/disco#items")
       
    32 		for i, one_bot_reply in ipairs(bot_commands) do
       
    33 			if one_bot_reply.status == "fulfilled"	then
       
    34 			local query = one_bot_reply.value.stanza:get_child("query", "http://jabber.org/protocol/disco#items")
       
    35 				if query then
       
    36 					-- Should use query:childtags("item") but it doesn't work
       
    37 					for j,item in ipairs(query.tags) do
       
    38 						item.attr.node = json.encode({ jid = item.attr.jid, node = item.attr.node })
       
    39 						item.attr.jid = event.stanza.attr.to
       
    40 						reply:add_child(item):up()
       
    41 					end
       
    42 				end
       
    43 			end
       
    44 		end
       
    45 		event.origin.send(reply:up())
       
    46 	end):catch(function (e)
       
    47 		module:log("error", e)
       
    48 	end)
       
    49 
       
    50 	return true;
       
    51 end, 500);
       
    52 
       
    53 local function is_adhoc_bot(jid)
       
    54 	for i, bot_jid in ipairs(module:get_option_array("adhoc_bots", {})) do
       
    55 		if jid == bot_jid then
       
    56 			return true
       
    57 		end
       
    58 	end
       
    59 
       
    60 	return false
       
    61 end
       
    62 
       
    63 module:hook("iq-set/bare/"..xmlns_cmd..":command", function (event)
       
    64 	local origin, stanza = event.origin, event.stanza;
       
    65 	local node = stanza.tags[1].attr.node
       
    66 	local meta = json.decode(node)
       
    67 	local room = prosody.hosts[module:get_host()].modules.muc.get_room_from_jid(stanza.attr.to);
       
    68 	local occupant = room:get_occupant_by_real_jid(event.stanza.attr.from)
       
    69 	if meta and occupant and is_adhoc_bot(meta.jid) then
       
    70 		local fwd = st.clone(stanza)
       
    71 		fwd.attr.to = meta.jid
       
    72 		fwd.attr.from = room:get_occupant_jid(event.stanza.attr.from)
       
    73 		local command = fwd:get_child("command", "http://jabber.org/protocol/commands")
       
    74 		command.attr.node = meta.node
       
    75 		module:send_iq(fwd):next(function(response)
       
    76 			local response_command = response.stanza:get_child("command", "http://jabber.org/protocol/commands")
       
    77 			response.stanza.attr.from = stanza.attr.to
       
    78 			response.stanza.attr.to = stanza.attr.from
       
    79 			response_command.attr.node = node
       
    80 			origin.send(response.stanza)
       
    81 		end):catch(function (e)
       
    82 			module:log("error", e)
       
    83 		end)
       
    84 
       
    85 		return true
       
    86 	end
       
    87 
       
    88 	return
       
    89 end, 500);
       
    90 
       
    91 local function clean_xmlns(node)
       
    92 		-- Recursively remove "jabber:client" attribute from node.
       
    93 		-- In Prosody internal routing, xmlns should not be set.
       
    94 		-- Keeping xmlns would lead to issues like mod_smacks ignoring the outgoing stanza,
       
    95 		-- so we remove all xmlns attributes with a value of "jabber:client"
       
    96 		if node.attr.xmlns == 'jabber:client' then
       
    97 				for childnode in node:childtags() do
       
    98 						clean_xmlns(childnode)
       
    99 				end
       
   100 				node.attr.xmlns = nil
       
   101 		end
       
   102 end
       
   103 
       
   104 module:hook("message/bare", function (event)
       
   105 	local origin, stanza = event.origin, event.stanza;
       
   106 	if not is_adhoc_bot(stanza.attr.from) then return; end
       
   107 	local room = prosody.hosts[module:get_host()].modules.muc.get_room_from_jid(stanza.attr.to);
       
   108 	if room == nil then return; end
       
   109 	local privilege = stanza:get_child("privilege", "urn:xmpp:privilege:2")
       
   110 	if privilege == nil then return; end
       
   111 	local fwd = privilege:get_child("forwarded", "urn:xmpp:forward:0")
       
   112 	if fwd == nil then return; end
       
   113 	local message = fwd:get_child("message", "jabber:client")
       
   114 	if message == nil then return; end
       
   115 	if message.attr.to ~= stanza.attr.to or jid.bare(message.attr.from) ~= stanza.attr.to then
       
   116 		return
       
   117 	end
       
   118 
       
   119 	clean_xmlns(message)
       
   120 	room:broadcast_message(message)
       
   121 	return true
       
   122 end)