plugins/muc/muc.lib: Move sending of occupant list to joining user out of hook, and...
[prosody.git] / plugins / muc / history.lib.lua
1 -- Prosody IM
2 -- Copyright (C) 2008-2010 Matthew Wild
3 -- Copyright (C) 2008-2010 Waqas Hussain
4 -- Copyright (C) 2014 Daurnimator
5 --
6 -- This project is MIT/X11 licensed. Please see the
7 -- COPYING file in the source package for more information.
8 --
9
10 local gettime = os.time;
11 local datetime = require "util.datetime";
12 local st = require "util.stanza";
13
14 local default_history_length, max_history_length = 20, math.huge;
15
16 local function set_max_history_length(_max_history_length)
17         max_history_length = _max_history_length or math.huge;
18 end
19
20 local function get_historylength(room)
21         return math.min(room._data.history_length or default_history_length, max_history_length);
22 end
23
24 local function set_historylength(room, length)
25         length = assert(tonumber(length), "Length not a valid number");
26         if length == default_history_length then length = nil; end
27         room._data.history_length = length;
28         return true;
29 end
30
31 module:hook("muc-config-form", function(event)
32         table.insert(event.form, {
33                 name = "muc#roomconfig_historylength";
34                 type = "text-single";
35                 label = "Maximum Number of History Messages Returned by Room";
36                 value = tostring(get_historylength(event.room));
37         });
38 end);
39
40 module:hook("muc-config-submitted", function(event)
41         local new = event.fields["muc#roomconfig_historylength"];
42         if new ~= nil and set_historylength(event.room, new) then
43                 event.status_codes["104"] = true;
44         end
45 end);
46
47 local function parse_history(stanza)
48         local x_tag = stanza:get_child("x", "http://jabber.org/protocol/muc");
49         local history_tag = x_tag and x_tag:get_child("history", "http://jabber.org/protocol/muc");
50         if not history_tag then
51                 return nil, default_history_length, nil;
52         end
53
54         local maxchars = tonumber(history_tag.attr.maxchars);
55
56         local maxstanzas = tonumber(history_tag.attr.maxstanzas);
57
58         -- messages received since the UTC datetime specified
59         local since = history_tag.attr.since;
60         if since then
61                 since = datetime.parse(since);
62         end
63
64         -- messages received in the last "X" seconds.
65         local seconds = tonumber(history_tag.attr.seconds);
66         if seconds then
67                 seconds = gettime() - seconds;
68                 if since then
69                         since = math.max(since, seconds);
70                 else
71                         since = seconds;
72                 end
73         end
74
75         return maxchars, maxstanzas, since;
76 end
77
78 module:hook("muc-get-history", function(event)
79         local room = event.room;
80         local history = room._data["history"]; -- send discussion history
81         if not history then return nil end
82         local history_len = #history;
83
84         local to = event.to;
85         local maxchars = event.maxchars;
86         local maxstanzas = event.maxstanzas or history_len;
87         local since = event.since;
88         local n = 0;
89         local charcount = 0;
90         for i=history_len,1,-1 do
91                 local entry = history[i];
92                 if maxchars then
93                         if not entry.chars then
94                                 entry.stanza.attr.to = "";
95                                 entry.chars = #tostring(entry.stanza);
96                         end
97                         charcount = charcount + entry.chars + #to;
98                         if charcount > maxchars then break; end
99                 end
100                 if since and since > entry.timestamp then break; end
101                 if n + 1 > maxstanzas then break; end
102                 n = n + 1;
103         end
104
105         local i = history_len-n+1
106         function event:next_stanza()
107                 if i > history_len then return nil end
108                 local entry = history[i];
109                 local msg = entry.stanza;
110                 msg.attr.to = to;
111                 i = i + 1;
112                 return msg;
113         end
114         return true;
115 end);
116
117 local function send_history(room, stanza)
118         local maxchars, maxstanzas, since = parse_history(stanza);
119         local event = {
120                 room = room;
121                 to = stanza.attr.from; -- `to` is required to calculate the character count for `maxchars`
122                 maxchars = maxchars, maxstanzas = maxstanzas, since = since;
123                 next_stanza = function() end; -- events should define this iterator
124         };
125         module:fire_event("muc-get-history", event);
126         for msg in event.next_stanza, event do
127                 room:route_stanza(msg);
128         end
129 end
130
131 -- Send history on join
132 module:hook("muc-occupant-joined", function(event)
133         send_history(event.room, event.stanza);
134 end, 50); -- Before subject(20)
135
136 -- add to history
137 module:hook("muc-broadcast-message", function(event)
138         local historic = event.stanza:get_child("body");
139         if historic then
140                 local room = event.room
141                 local history = room._data["history"];
142                 if not history then history = {}; room._data["history"] = history; end
143                 local stanza = st.clone(event.stanza);
144                 stanza.attr.to = "";
145                 local ts = gettime();
146                 local stamp = datetime.datetime(ts);
147                 stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = module.host, stamp = stamp}):up(); -- XEP-0203
148                 stanza:tag("x", {xmlns = "jabber:x:delay", from = module.host, stamp = datetime.legacy()}):up(); -- XEP-0091 (deprecated)
149                 local entry = { stanza = stanza, timestamp = ts };
150                 table.insert(history, entry);
151                 while #history > get_historylength(room) do table.remove(history, 1) end
152         end
153 end);
154
155 return {
156         set_max_length = set_max_history_length;
157         parse_history = parse_history;
158         send = send_history;
159         get_length = get_historylength;
160         set_length = set_historylength;
161 };