Software /
code /
prosody
Comparison
plugins/mod_muc_mam.lua @ 10563:e8db377a2983
Merge 0.11->trunk
author | Kim Alvefur <zash@zash.se> |
---|---|
date | Tue, 24 Dec 2019 00:39:45 +0100 |
parent | 10562:670afc079f68 |
parent | 10560:3adb6c46fbf4 |
child | 10568:b6f93babebe8 |
comparison
equal
deleted
inserted
replaced
10562:670afc079f68 | 10563:e8db377a2983 |
---|---|
2 -- Copyright (C) 2011-2017 Kim Alvefur | 2 -- Copyright (C) 2011-2017 Kim Alvefur |
3 -- | 3 -- |
4 -- This file is MIT/X11 licensed. | 4 -- This file is MIT/X11 licensed. |
5 | 5 |
6 if module:get_host_type() ~= "component" then | 6 if module:get_host_type() ~= "component" then |
7 module:log("error", "mod_%s should be loaded only on a MUC component, not normal hosts", module.name); | 7 module:log_status("error", "mod_%s should be loaded only on a MUC component, not normal hosts", module.name); |
8 return; | 8 return; |
9 end | 9 end |
10 | 10 |
11 local xmlns_mam = "urn:xmpp:mam:2"; | 11 local xmlns_mam = "urn:xmpp:mam:2"; |
12 local xmlns_delay = "urn:xmpp:delay"; | 12 local xmlns_delay = "urn:xmpp:delay"; |
19 local rsm = require "util.rsm"; | 19 local rsm = require "util.rsm"; |
20 local jid_bare = require "util.jid".bare; | 20 local jid_bare = require "util.jid".bare; |
21 local jid_split = require "util.jid".split; | 21 local jid_split = require "util.jid".split; |
22 local jid_prep = require "util.jid".prep; | 22 local jid_prep = require "util.jid".prep; |
23 local dataform = require "util.dataforms".new; | 23 local dataform = require "util.dataforms".new; |
24 local get_form_type = require "util.dataforms".get_type; | |
24 | 25 |
25 local mod_muc = module:depends"muc"; | 26 local mod_muc = module:depends"muc"; |
26 local get_room_from_jid = mod_muc.get_room_from_jid; | 27 local get_room_from_jid = mod_muc.get_room_from_jid; |
27 | 28 |
28 local is_stanza = st.is_stanza; | 29 local is_stanza = st.is_stanza; |
30 local time_now = os.time; | 31 local time_now = os.time; |
31 local m_min = math.min; | 32 local m_min = math.min; |
32 local timestamp, timestamp_parse, datestamp = import( "util.datetime", "datetime", "parse", "date"); | 33 local timestamp, timestamp_parse, datestamp = import( "util.datetime", "datetime", "parse", "date"); |
33 local default_max_items, max_max_items = 20, module:get_option_number("max_archive_query_results", 50); | 34 local default_max_items, max_max_items = 20, module:get_option_number("max_archive_query_results", 50); |
34 | 35 |
36 local cleanup_after = module:get_option_string("muc_log_expires_after", "1w"); | |
37 local cleanup_interval = module:get_option_number("muc_log_cleanup_interval", 4 * 60 * 60); | |
38 | |
35 local default_history_length = 20; | 39 local default_history_length = 20; |
36 local max_history_length = module:get_option_number("max_history_messages", math.huge); | 40 local max_history_length = module:get_option_number("max_history_messages", math.huge); |
37 | 41 |
38 local function get_historylength(room) | 42 local function get_historylength(room) |
39 return math.min(room._data.history_length or default_history_length, max_history_length); | 43 return math.min(room._data.history_length or default_history_length, max_history_length); |
46 local log_all_rooms = module:get_option_boolean("muc_log_all_rooms", false); | 50 local log_all_rooms = module:get_option_boolean("muc_log_all_rooms", false); |
47 local log_by_default = module:get_option_boolean("muc_log_by_default", true); | 51 local log_by_default = module:get_option_boolean("muc_log_by_default", true); |
48 | 52 |
49 local archive_store = "muc_log"; | 53 local archive_store = "muc_log"; |
50 local archive = module:open_store(archive_store, "archive"); | 54 local archive = module:open_store(archive_store, "archive"); |
55 | |
56 local archive_item_limit = module:get_option_number("storage_archive_item_limit", archive.caps and archive.caps.quota or 1000); | |
51 | 57 |
52 if archive.name == "null" or not archive.find then | 58 if archive.name == "null" or not archive.find then |
53 if not archive.find then | 59 if not archive.find then |
54 module:log("error", "Attempt to open archive storage returned a driver without archive API support"); | 60 module:log("error", "Attempt to open archive storage returned a driver without archive API support"); |
55 module:log("error", "mod_%s does not support archiving", | 61 module:log("error", "mod_%s does not support archiving", |
61 return false; | 67 return false; |
62 end | 68 end |
63 | 69 |
64 local function archiving_enabled(room) | 70 local function archiving_enabled(room) |
65 if log_all_rooms then | 71 if log_all_rooms then |
72 module:log("debug", "Archiving all rooms"); | |
66 return true; | 73 return true; |
67 end | 74 end |
68 local enabled = room._data.archiving; | 75 local enabled = room._data.archiving; |
69 if enabled == nil then | 76 if enabled == nil then |
77 module:log("debug", "Default is %s (for %s)", log_by_default, room.jid); | |
70 return log_by_default; | 78 return log_by_default; |
71 end | 79 end |
80 module:log("debug", "Logging in room %s is %s", room.jid, enabled); | |
72 return enabled; | 81 return enabled; |
73 end | 82 end |
74 | 83 |
75 if not log_all_rooms then | 84 if not log_all_rooms then |
76 module:hook("muc-config-form", function(event) | 85 module:hook("muc-config-form", function(event) |
133 | 142 |
134 -- Search query parameters | 143 -- Search query parameters |
135 local qstart, qend; | 144 local qstart, qend; |
136 local form = query:get_child("x", "jabber:x:data"); | 145 local form = query:get_child("x", "jabber:x:data"); |
137 if form then | 146 if form then |
138 local err; | 147 local form_type, err = get_form_type(form); |
148 if not form_type then | |
149 origin.send(st.error_reply(stanza, "modify", "bad-request", "Invalid dataform: "..err)); | |
150 return true; | |
151 elseif form_type ~= xmlns_mam then | |
152 origin.send(st.error_reply(stanza, "modify", "bad-request", "Unexpected FORM_TYPE, expected '"..xmlns_mam.."'")); | |
153 return true; | |
154 end | |
139 form, err = query_form:data(form); | 155 form, err = query_form:data(form); |
140 if err then | 156 if err then |
141 origin.send(st.error_reply(stanza, "modify", "bad-request", select(2, next(err)))); | 157 origin.send(st.error_reply(stanza, "modify", "bad-request", select(2, next(err)))); |
142 return true; | 158 return true; |
143 end | 159 end |
151 return true; | 167 return true; |
152 end | 168 end |
153 qstart, qend = vstart, vend; | 169 qstart, qend = vstart, vend; |
154 end | 170 end |
155 | 171 |
156 module:log("debug", "Archive query id %s from %s until %s)", | 172 module:log("debug", "Archive query by %s id=%s when=%s...%s", |
157 tostring(qid), | 173 origin.username, |
158 qstart and timestamp(qstart) or "the dawn of time", | 174 qid or stanza.attr.id, |
159 qend and timestamp(qend) or "now"); | 175 qstart and timestamp(qstart) or "", |
176 qend and timestamp(qend) or ""); | |
160 | 177 |
161 -- RSM stuff | 178 -- RSM stuff |
162 local qset = rsm.get(query); | 179 local qset = rsm.get(query); |
163 local qmax = m_min(qset and qset.max or default_max_items, max_max_items); | 180 local qmax = m_min(qset and qset.max or default_max_items, max_max_items); |
164 local reverse = qset and qset.before or false; | 181 local reverse = qset and qset.before or false; |
165 | 182 |
166 local before, after = qset and qset.before, qset and qset.after; | 183 local before, after = qset and qset.before, qset and qset.after; |
167 if type(before) ~= "string" then before = nil; end | 184 if type(before) ~= "string" then before = nil; end |
185 if qset then | |
186 module:log("debug", "Archive query id=%s rsm=%q", qid or stanza.attr.id, qset); | |
187 end | |
168 | 188 |
169 -- Load all the data! | 189 -- Load all the data! |
170 local data, err = archive:find(room_node, { | 190 local data, err = archive:find(room_node, { |
171 start = qstart; ["end"] = qend; -- Time range | 191 start = qstart; ["end"] = qend; -- Time range |
172 limit = qmax + 1; | 192 limit = qmax + 1; |
174 reverse = reverse; | 194 reverse = reverse; |
175 with = "message<groupchat"; | 195 with = "message<groupchat"; |
176 }); | 196 }); |
177 | 197 |
178 if not data then | 198 if not data then |
179 origin.send(st.error_reply(stanza, "cancel", "internal-server-error")); | 199 module:log("debug", "Archive query id=%s failed: %s", qid or stanza.attr.id, err); |
200 if err == "item-not-found" then | |
201 origin.send(st.error_reply(stanza, "modify", "item-not-found")); | |
202 else | |
203 origin.send(st.error_reply(stanza, "cancel", "internal-server-error")); | |
204 end | |
180 return true; | 205 return true; |
181 end | 206 end |
182 local total = tonumber(err); | 207 local total = tonumber(err); |
183 | 208 |
184 local msg_reply_attr = { to = stanza.attr.from, from = stanza.attr.to }; | 209 local msg_reply_attr = { to = stanza.attr.from, from = stanza.attr.to }; |
231 origin.send(results[i]); | 256 origin.send(results[i]); |
232 end | 257 end |
233 first, last = last, first; | 258 first, last = last, first; |
234 end | 259 end |
235 | 260 |
236 -- That's all folks! | |
237 module:log("debug", "Archive query %s completed", tostring(qid)); | |
238 | 261 |
239 origin.send(st.reply(stanza) | 262 origin.send(st.reply(stanza) |
240 :tag("fin", { xmlns = xmlns_mam, queryid = qid, complete = complete }) | 263 :tag("fin", { xmlns = xmlns_mam, queryid = qid, complete = complete }) |
241 :add_child(rsm.generate { | 264 :add_child(rsm.generate { |
242 first = first, last = last, count = total })); | 265 first = first, last = last, count = total })); |
266 | |
267 -- That's all folks! | |
268 module:log("debug", "Archive query id=%s completed, %d items returned", qid or stanza.attr.id, complete and count or count - 1); | |
243 return true; | 269 return true; |
244 end); | 270 end); |
245 | 271 |
246 module:hook("muc-get-history", function (event) | 272 module:hook("muc-get-history", function (event) |
247 local room = event.room; | 273 local room = event.room; |
272 with = "message<groupchat"; | 298 with = "message<groupchat"; |
273 } | 299 } |
274 local data, err = archive:find(jid_split(room_jid), query); | 300 local data, err = archive:find(jid_split(room_jid), query); |
275 | 301 |
276 if not data then | 302 if not data then |
277 module:log("error", "Could not fetch history: %s", tostring(err)); | 303 module:log("error", "Could not fetch history: %s", err); |
278 return | 304 return |
279 end | 305 end |
280 | 306 |
281 local history, i = {}, 1; | 307 local history, i = {}, 1; |
282 | 308 |
298 break | 324 break |
299 end | 325 end |
300 maxchars = maxchars - chars; | 326 maxchars = maxchars - chars; |
301 end | 327 end |
302 history[i], i = item, i+1; | 328 history[i], i = item, i+1; |
303 -- module:log("debug", tostring(item)); | 329 -- module:log("debug", item); |
304 end | 330 end |
305 function event.next_stanza() | 331 function event.next_stanza() |
306 i = i - 1; | 332 i = i - 1; |
307 return history[i]; | 333 return history[i]; |
308 end | 334 end |
326 | 352 |
327 end, 0); | 353 end, 0); |
328 | 354 |
329 -- Handle messages | 355 -- Handle messages |
330 local function save_to_history(self, stanza) | 356 local function save_to_history(self, stanza) |
331 local room_node, room_host = jid_split(self.jid); | 357 local room_node = jid_split(self.jid); |
332 | 358 |
333 local stored_stanza = stanza; | 359 local stored_stanza = stanza; |
334 | 360 |
335 if stanza.name == "message" and self:get_whois() == "anyone" then | 361 if stanza.name == "message" and self:get_whois() == "anyone" then |
336 stored_stanza = st.clone(stanza); | 362 stored_stanza = st.clone(stanza); |
350 if stanza.attr.type then | 376 if stanza.attr.type then |
351 with = with .. "<" .. stanza.attr.type | 377 with = with .. "<" .. stanza.attr.type |
352 end | 378 end |
353 | 379 |
354 -- And stash it | 380 -- And stash it |
355 local id = archive:append(room_node, nil, stored_stanza, time_now(), with); | 381 local time = time_now(); |
382 local id, err = archive:append(room_node, nil, stored_stanza, time, with); | |
383 | |
384 if not id and err == "quota-limit" then | |
385 if type(cleanup_after) == "number" then | |
386 module:log("debug", "Room '%s' over quota, cleaning archive", room_node); | |
387 local cleaned = archive:delete(room_node, { | |
388 ["end"] = (os.time() - cleanup_after); | |
389 }); | |
390 if cleaned then | |
391 id, err = archive:append(room_node, nil, stored_stanza, time, with); | |
392 end | |
393 end | |
394 if not id and (archive.caps and archive.caps.truncate) then | |
395 module:log("debug", "User '%s' over quota, truncating archive", room_node); | |
396 local truncated = archive:delete(room_node, { | |
397 truncate = archive_item_limit - 1; | |
398 }); | |
399 if truncated then | |
400 id, err = archive:append(room_node, nil, stored_stanza, time, with); | |
401 end | |
402 end | |
403 end | |
356 | 404 |
357 if id then | 405 if id then |
358 schedule_cleanup(room_node); | 406 schedule_cleanup(room_node); |
359 stanza:add_direct_child(st.stanza("stanza-id", { xmlns = xmlns_st_id, by = self.jid, id = id })); | 407 stanza:add_direct_child(st.stanza("stanza-id", { xmlns = xmlns_st_id, by = self.jid, id = id })); |
360 else | 408 else |
389 -- And role/affiliation changes? | 437 -- And role/affiliation changes? |
390 | 438 |
391 module:add_feature(xmlns_mam); | 439 module:add_feature(xmlns_mam); |
392 | 440 |
393 module:hook("muc-disco#info", function(event) | 441 module:hook("muc-disco#info", function(event) |
394 event.reply:tag("feature", {var=xmlns_mam}):up(); | 442 if archiving_enabled(event.room) then |
443 event.reply:tag("feature", {var=xmlns_mam}):up(); | |
444 end | |
395 end); | 445 end); |
396 | 446 |
397 -- Cleanup | 447 -- Cleanup |
398 | |
399 local cleanup_after = module:get_option_string("muc_log_expires_after", "1w"); | |
400 local cleanup_interval = module:get_option_number("muc_log_cleanup_interval", 4 * 60 * 60); | |
401 | 448 |
402 if cleanup_after ~= "never" then | 449 if cleanup_after ~= "never" then |
403 local cleanup_storage = module:open_store("muc_log_cleanup"); | 450 local cleanup_storage = module:open_store("muc_log_cleanup"); |
404 local cleanup_map = module:open_store("muc_log_cleanup", "map"); | 451 local cleanup_map = module:open_store("muc_log_cleanup", "map"); |
405 | 452 |