mod_carbons/mod_carbons.lua
changeset 4788 9a41cf08de24
parent 4787 6ca1117b81a5
child 4789 05bea2f43d97
equal deleted inserted replaced
4787:6ca1117b81a5 4788:9a41cf08de24
     1 -- XEP-0280: Message Carbons implementation for Prosody
       
     2 -- Copyright (C) 2011-2016 Kim Alvefur
       
     3 --
       
     4 -- This file is MIT/X11 licensed.
       
     5 
       
     6 local st = require "util.stanza";
       
     7 local jid_bare = require "util.jid".bare;
       
     8 local xmlns_carbons = "urn:xmpp:carbons:2";
       
     9 local xmlns_carbons_old = "urn:xmpp:carbons:1";
       
    10 local xmlns_carbons_really_old = "urn:xmpp:carbons:0";
       
    11 local xmlns_forward = "urn:xmpp:forward:0";
       
    12 local full_sessions, bare_sessions = prosody.full_sessions, prosody.bare_sessions;
       
    13 
       
    14 local function toggle_carbons(event)
       
    15 	local origin, stanza = event.origin, event.stanza;
       
    16 	local state = stanza.tags[1].attr.mode or stanza.tags[1].name;
       
    17 	module:log("debug", "%s %sd carbons", origin.full_jid, state);
       
    18 	origin.want_carbons = state == "enable" and stanza.tags[1].attr.xmlns;
       
    19 	origin.send(st.reply(stanza));
       
    20 	return true;
       
    21 end
       
    22 module:hook("iq-set/self/"..xmlns_carbons..":disable", toggle_carbons);
       
    23 module:hook("iq-set/self/"..xmlns_carbons..":enable", toggle_carbons);
       
    24 
       
    25 -- COMPAT
       
    26 module:hook("iq-set/self/"..xmlns_carbons_old..":disable", toggle_carbons);
       
    27 module:hook("iq-set/self/"..xmlns_carbons_old..":enable", toggle_carbons);
       
    28 module:hook("iq-set/self/"..xmlns_carbons_really_old..":carbons", toggle_carbons);
       
    29 
       
    30 local function message_handler(event, c2s)
       
    31 	local origin, stanza = event.origin, event.stanza;
       
    32 	local orig_type = stanza.attr.type or "normal";
       
    33 	local orig_from = stanza.attr.from;
       
    34 	local orig_to = stanza.attr.to;
       
    35 
       
    36 	if not(orig_type == "chat" or (orig_type == "normal" and stanza:get_child("body"))) then
       
    37 		return -- Only chat type messages
       
    38 	end
       
    39 
       
    40 	-- Stanza sent by a local client
       
    41 	local bare_jid = jid_bare(orig_from);
       
    42 	local target_session = origin;
       
    43 	local top_priority = false;
       
    44 	local user_sessions = bare_sessions[bare_jid];
       
    45 
       
    46 	-- Stanza about to be delivered to a local client
       
    47 	if not c2s then
       
    48 		bare_jid = jid_bare(orig_to);
       
    49 		target_session = full_sessions[orig_to];
       
    50 		user_sessions = bare_sessions[bare_jid];
       
    51 		if not target_session and user_sessions then
       
    52 			-- The top resources will already receive this message per normal routing rules,
       
    53 			-- so we are going to skip them in order to avoid sending duplicated messages.
       
    54 			local top_resources = user_sessions.top_resources;
       
    55 			top_priority = top_resources and top_resources[1].priority
       
    56 		end
       
    57 	end
       
    58 
       
    59 	if not user_sessions then
       
    60 		module:log("debug", "Skip carbons for offline user");
       
    61 		return -- No use in sending carbons to an offline user
       
    62 	end
       
    63 
       
    64 	if stanza:get_child("private", xmlns_carbons) then
       
    65 		if not c2s then
       
    66 			stanza:maptags(function(tag)
       
    67 				if not ( tag.attr.xmlns == xmlns_carbons and tag.name == "private" ) then
       
    68 					return tag;
       
    69 				end
       
    70 			end);
       
    71 		end
       
    72 		module:log("debug", "Message tagged private, ignoring");
       
    73 		return
       
    74 	elseif stanza:get_child("no-copy", "urn:xmpp:hints") then
       
    75 		module:log("debug", "Message has no-copy hint, ignoring");
       
    76 		return
       
    77 	elseif not c2s and bare_jid == orig_from and stanza:get_child("x", "http://jabber.org/protocol/muc#user") then
       
    78 		module:log("debug", "MUC PM, ignoring");
       
    79 		return
       
    80 	end
       
    81 
       
    82 	-- Create the carbon copy and wrap it as per the Stanza Forwarding XEP
       
    83 	local copy = st.clone(stanza);
       
    84 	copy.attr.xmlns = "jabber:client";
       
    85 	local carbon = st.message{ from = bare_jid, type = orig_type, }
       
    86 		:tag(c2s and "sent" or "received", { xmlns = xmlns_carbons })
       
    87 			:tag("forwarded", { xmlns = xmlns_forward })
       
    88 				:add_child(copy):reset();
       
    89 
       
    90 	-- COMPAT
       
    91 	local carbon_old = st.message{ from = bare_jid, type = orig_type, }
       
    92 		:tag(c2s and "sent" or "received", { xmlns = xmlns_carbons_old }):up()
       
    93 		:tag("forwarded", { xmlns = xmlns_forward })
       
    94 			:add_child(copy):reset();
       
    95 
       
    96 	-- COMPAT
       
    97 	local carbon_really_old = st.clone(stanza)
       
    98 		:tag(c2s and "sent" or "received", { xmlns = xmlns_carbons_really_old }):up()
       
    99 
       
   100 	user_sessions = user_sessions and user_sessions.sessions;
       
   101 	for _, session in pairs(user_sessions) do
       
   102 		-- Carbons are sent to resources that have enabled it
       
   103 		if session.want_carbons
       
   104 		-- but not the resource that sent the message, or the one that it's directed to
       
   105 		and session ~= target_session
       
   106 		-- and isn't among the top resources that would receive the message per standard routing rules
       
   107 		and (c2s or session.priority ~= top_priority)
       
   108 		-- don't send v0 carbons (or copies) for c2s
       
   109 		and (not c2s or session.want_carbons ~= xmlns_carbons_really_old) then
       
   110 			carbon.attr.to = session.full_jid;
       
   111 			module:log("debug", "Sending carbon to %s", session.full_jid);
       
   112 			local carbon = session.want_carbons == xmlns_carbons_old and carbon_old -- COMPAT
       
   113 			or session.want_carbons == xmlns_carbons_really_old and carbon_really_old -- COMPAT
       
   114 			or carbon;
       
   115 			session.send(carbon);
       
   116 		end
       
   117 	end
       
   118 end
       
   119 
       
   120 local function c2s_message_handler(event)
       
   121 	return message_handler(event, true)
       
   122 end
       
   123 
       
   124 -- Stanzas sent by local clients
       
   125 module:hook("pre-message/host", c2s_message_handler, 0.05); -- priority between mod_message (0 in 0.9) and mod_firewall (0.1)
       
   126 module:hook("pre-message/bare", c2s_message_handler, 0.05);
       
   127 module:hook("pre-message/full", c2s_message_handler, 0.05);
       
   128 -- Stanzas to local clients
       
   129 module:hook("message/bare", message_handler, 0.05);
       
   130 module:hook("message/full", message_handler, 0.05);
       
   131 
       
   132 module:add_feature(xmlns_carbons);
       
   133 module:add_feature(xmlns_carbons_old);
       
   134 if module:get_option_boolean("carbons_v0") then
       
   135 	module:add_feature(xmlns_carbons_really_old);
       
   136 end