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_forward
= "urn:xmpp:forward:0";
10 local full_sessions
, bare_sessions
= prosody
.full_sessions
, prosody
.bare_sessions
;
12 local function toggle_carbons(event
)
13 local origin
, stanza
= event
.origin
, event
.stanza
;
14 local state
= stanza
.tags
[1].name
;
15 module
:log("debug", "%s %sd carbons", origin
.full_jid
, state
);
16 origin
.want_carbons
= state
== "enable" and stanza
.tags
[1].attr
.xmlns
;
17 origin
.send(st
.reply(stanza
));
20 module
:hook("iq-set/self/"..xmlns_carbons
..":disable", toggle_carbons
);
21 module
:hook("iq-set/self/"..xmlns_carbons
..":enable", toggle_carbons
);
23 local function message_handler(event
, c2s
)
24 local origin
, stanza
= event
.origin
, event
.stanza
;
25 local orig_type
= stanza
.attr
.type or "normal";
26 local orig_from
= stanza
.attr
.from
;
27 local bare_from
= jid_bare(orig_from
);
28 local orig_to
= stanza
.attr
.to
;
29 local bare_to
= jid_bare(orig_to
);
31 if not(orig_type
== "chat" or (orig_type
== "normal" and stanza
:get_child("body"))) then
32 return -- Only chat type messages
35 -- Stanza sent by a local client
36 local bare_jid
= bare_from
; -- JID of the local user
37 local target_session
= origin
;
38 local top_priority
= false;
39 local user_sessions
= bare_sessions
[bare_from
];
41 -- Stanza about to be delivered to a local client
44 target_session
= full_sessions
[orig_to
];
45 user_sessions
= bare_sessions
[bare_jid
];
46 if not target_session
and user_sessions
then
47 -- The top resources will already receive this message per normal routing rules,
48 -- so we are going to skip them in order to avoid sending duplicated messages.
49 local top_resources
= user_sessions
.top_resources
;
50 top_priority
= top_resources
and top_resources
[1].priority
54 if not user_sessions
then
55 module
:log("debug", "Skip carbons for offline user");
56 return -- No use in sending carbons to an offline user
59 if stanza
:get_child("private", xmlns_carbons
) then
61 stanza
:maptags(function(tag)
62 if not ( tag.attr
.xmlns
== xmlns_carbons
and tag.name
== "private" ) then
67 module
:log("debug", "Message tagged private, ignoring");
69 elseif stanza
:get_child("no-copy", "urn:xmpp:hints") then
70 module
:log("debug", "Message has no-copy hint, ignoring");
72 elseif not c2s
and bare_jid
== orig_from
and stanza
:get_child("x", "http://jabber.org/protocol/muc#user") then
73 module
:log("debug", "MUC PM, ignoring");
77 -- Create the carbon copy and wrap it as per the Stanza Forwarding XEP
78 local copy
= st
.clone(stanza
);
79 if c2s
and not orig_to
then
80 stanza
.attr
.to
= bare_from
;
82 copy
.attr
.xmlns
= "jabber:client";
83 local carbon
= st
.message
{ from
= bare_jid
, type = orig_type
, }
84 :tag(c2s
and "sent" or "received", { xmlns
= xmlns_carbons
})
85 :tag("forwarded", { xmlns
= xmlns_forward
})
86 :add_child(copy
):reset();
88 user_sessions
= user_sessions
and user_sessions
.sessions
;
89 for _
, session
in pairs(user_sessions
) do
90 -- Carbons are sent to resources that have enabled it
91 if session
.want_carbons
92 -- but not the resource that sent the message, or the one that it's directed to
93 and session
~= target_session
94 -- and isn't among the top resources that would receive the message per standard routing rules
95 and (c2s
or session
.priority
~= top_priority
) then
96 carbon
.attr
.to
= session
.full_jid
;
97 module
:log("debug", "Sending carbon to %s", session
.full_jid
);
103 local function c2s_message_handler(event
)
104 return message_handler(event
, true)
107 -- Stanzas sent by local clients
108 module
:hook("pre-message/host", c2s_message_handler
, -0.5);
109 module
:hook("pre-message/bare", c2s_message_handler
, -0.5);
110 module
:hook("pre-message/full", c2s_message_handler
, -0.5);
111 -- Stanzas to local clients
112 module
:hook("message/bare", message_handler
, -0.5);
113 module
:hook("message/full", message_handler
, -0.5);
115 module
:add_feature(xmlns_carbons
);