0fc0dfa3dfd5332716461a7e974c50fb060f1b46
[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 = 20;
15 local max_history_length = module:get_option_number("max_history_messages", math.huge);
16
17 local function set_max_history_length(_max_history_length)
18         max_history_length = _max_history_length or math.huge;
19 end
20
21 local function get_historylength(room)
22         return math.min(room._data.history_length or default_history_length, max_history_length);
23 end
24
25 local function set_historylength(room, length)
26         if length then
27                 length = assert(tonumber(length), "Length not a valid number");
28         end
29         if length == default_history_length then length = nil; end
30         room._data.history_length = length;
31         return true;
32 end
33
34 module:hook("muc-config-form", function(event)
35         table.insert(event.form, {
36                 name = "muc#roomconfig_historylength";
37                 type = "text-single";
38                 label = "Maximum Number of History Messages Returned by Room";
39                 value = tostring(get_historylength(event.room));
40         });
41 end);
42
43 module:hook("muc-config-submitted/muc#roomconfig_historylength", function(event)
44         if set_historylength(event.room, event.value) then
45                 event.status_codes["104"] = true;
46         end
47 end);
48
49 local function parse_history(stanza)
50         local x_tag = stanza:get_child("x", "http://jabber.org/protocol/muc");
51         local history_tag = x_tag and x_tag:get_child("history", "http://jabber.org/protocol/muc");
52         if not history_tag then
53                 return nil, default_history_length, nil;
54         end
55
56         local maxchars = tonumber(history_tag.attr.maxchars);
57
58         local maxstanzas = tonumber(history_tag.attr.maxstanzas);
59
60         -- messages received since the UTC datetime specified
61         local since = history_tag.attr.since;
62         if since then
63                 since = datetime.parse(since);
64         end
65
66         -- messages received in the last "X" seconds.
67         local seconds = tonumber(history_tag.attr.seconds);
68         if seconds then
69                 seconds = gettime() - seconds;
70                 if since then
71                         since = math.max(since, seconds);
72                 else
73                         since = seconds;
74                 end
75         end
76
77         return maxchars, maxstanzas, since;
78 end
79
80 module:hook("muc-get-history", function(event)
81         local room = event.room;
82         local history = room._history; -- send discussion history
83         if not history then return nil end
84         local history_len = #history;
85
86         local to = event.to;
87         local maxchars = event.maxchars;
88         local maxstanzas = event.maxstanzas or history_len;
89         local since = event.since;
90         local n = 0;
91         local charcount = 0;
92         for i=history_len,1,-1 do
93                 local entry = history[i];
94                 if maxchars then
95                         if not entry.chars then
96                                 entry.stanza.attr.to = "";
97                                 entry.chars = #tostring(entry.stanza);
98                         end
99                         charcount = charcount + entry.chars + #to;
100                         if charcount > maxchars then break; end
101                 end
102                 if since and since > entry.timestamp then break; end
103                 if n + 1 > maxstanzas then break; end
104                 n = n + 1;
105         end
106
107         local i = history_len-n+1
108         function event.next_stanza()
109                 if i > history_len then return nil end
110                 local entry = history[i];
111                 local msg = entry.stanza;
112                 msg.attr.to = to;
113                 i = i + 1;
114                 return msg;
115         end
116         return true;
117 end);
118
119 local function send_history(room, stanza)
120         local maxchars, maxstanzas, since = parse_history(stanza);
121         local event = {
122                 room = room;
123                 to = stanza.attr.from; -- `to` is required to calculate the character count for `maxchars`
124                 maxchars = maxchars, maxstanzas = maxstanzas, since = since;
125                 next_stanza = function() end; -- events should define this iterator
126         };
127         module:fire_event("muc-get-history", event);
128         for msg in event.next_stanza, event do
129                 room:route_stanza(msg);
130         end
131 end
132
133 -- Send history on join
134 module:hook("muc-occupant-session-new", function(event)
135         send_history(event.room, event.stanza);
136 end, 50); -- Before subject(20)
137
138 -- add to history
139 module:hook("muc-add-history", function(event)
140         local historic = event.stanza:get_child("body");
141         if historic then
142                 local room = event.room
143                 local history = room._history;
144                 if not history then history = {}; room._history = history; end
145                 local stanza = st.clone(event.stanza);
146                 stanza.attr.to = "";
147                 local ts = gettime();
148                 local stamp = datetime.datetime(ts);
149                 stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = module.host, stamp = stamp}):up(); -- XEP-0203
150                 stanza:tag("x", {xmlns = "jabber:x:delay", from = module.host, stamp = datetime.legacy()}):up(); -- XEP-0091 (deprecated)
151                 local entry = { stanza = stanza, timestamp = ts };
152                 table.insert(history, entry);
153                 while #history > get_historylength(room) do table.remove(history, 1) end
154         end
155         return true;
156 end, -1);
157
158 -- Have a single muc-add-history event, so that plugins can mark it
159 -- as handled without stopping other muc-broadcast-message handlers
160 module:hook("muc-broadcast-message", function(event)
161         module:fire_event("muc-add-history", event);
162 end);
163
164 return {
165         set_max_length = set_max_history_length;
166         parse_history = parse_history;
167         send = send_history;
168         get_length = get_historylength;
169         set_length = set_historylength;
170 };