1 -- XEP-0280: Message Carbons implementation for Prosody
2 -- Copyright (C) 2011-2016 Kim Alvefur
4 -- This file is MIT/X11 licensed.
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
;
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
));
22 module
:hook("iq-set/self/"..xmlns_carbons
..":disable", toggle_carbons
);
23 module
:hook("iq-set/self/"..xmlns_carbons
..":enable", toggle_carbons
);
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
);
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
;
36 if not(orig_type
== "chat" or (orig_type
== "normal" and stanza
:get_child("body"))) then
37 return -- Only chat type messages
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
];
46 -- Stanza about to be delivered to a local client
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
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
64 if stanza
:get_child("private", xmlns_carbons
) then
66 stanza
:maptags(function(tag)
67 if not ( tag.attr
.xmlns
== xmlns_carbons
and tag.name
== "private" ) then
72 module
:log("debug", "Message tagged private, ignoring");
74 elseif stanza
:get_child("no-copy", "urn:xmpp:hints") then
75 module
:log("debug", "Message has no-copy hint, ignoring");
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");
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();
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();
97 local carbon_really_old
= st
.clone(stanza
)
98 :tag(c2s
and "sent" or "received", { xmlns
= xmlns_carbons_really_old
}):up()
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
115 session
.send(carbon
);
120 local function c2s_message_handler(event
)
121 return message_handler(event
, true)
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);
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
);