comparison plugins/mod_muc_mam.lua @ 10411:db2a06b9ff98

Merge 0.11->trunk
author Kim Alvefur <zash@zash.se>
date Sat, 16 Nov 2019 16:52:31 +0100
parents 29733134c76c
children 360d574517b6
comparison
equal deleted inserted replaced
10410:659b577f280c 10411:db2a06b9ff98
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 form_type ~= xmlns_mam then
149 origin.send(st.error_reply(stanza, "modify", "bad-request", "Unexpected FORM_TYPE, expected '"..xmlns_mam.."'"));
150 return true;
151 end
139 form, err = query_form:data(form); 152 form, err = query_form:data(form);
140 if err then 153 if err then
141 origin.send(st.error_reply(stanza, "modify", "bad-request", select(2, next(err)))); 154 origin.send(st.error_reply(stanza, "modify", "bad-request", select(2, next(err))));
142 return true; 155 return true;
143 end 156 end
174 reverse = reverse; 187 reverse = reverse;
175 with = "message<groupchat"; 188 with = "message<groupchat";
176 }); 189 });
177 190
178 if not data then 191 if not data then
179 origin.send(st.error_reply(stanza, "cancel", "internal-server-error")); 192 if err == "item-not-found" then
193 origin.send(st.error_reply(stanza, "modify", "item-not-found"));
194 else
195 origin.send(st.error_reply(stanza, "cancel", "internal-server-error"));
196 end
180 return true; 197 return true;
181 end 198 end
182 local total = tonumber(err); 199 local total = tonumber(err);
183 200
184 local msg_reply_attr = { to = stanza.attr.from, from = stanza.attr.to }; 201 local msg_reply_attr = { to = stanza.attr.from, from = stanza.attr.to };
232 end 249 end
233 first, last = last, first; 250 first, last = last, first;
234 end 251 end
235 252
236 -- That's all folks! 253 -- That's all folks!
237 module:log("debug", "Archive query %s completed", tostring(qid)); 254 module:log("debug", "Archive query %s completed", qid);
238 255
239 origin.send(st.reply(stanza) 256 origin.send(st.reply(stanza)
240 :tag("fin", { xmlns = xmlns_mam, queryid = qid, complete = complete }) 257 :tag("fin", { xmlns = xmlns_mam, queryid = qid, complete = complete })
241 :add_child(rsm.generate { 258 :add_child(rsm.generate {
242 first = first, last = last, count = total })); 259 first = first, last = last, count = total }));
272 with = "message<groupchat"; 289 with = "message<groupchat";
273 } 290 }
274 local data, err = archive:find(jid_split(room_jid), query); 291 local data, err = archive:find(jid_split(room_jid), query);
275 292
276 if not data then 293 if not data then
277 module:log("error", "Could not fetch history: %s", tostring(err)); 294 module:log("error", "Could not fetch history: %s", err);
278 return 295 return
279 end 296 end
280 297
281 local history, i = {}, 1; 298 local history, i = {}, 1;
282 299
298 break 315 break
299 end 316 end
300 maxchars = maxchars - chars; 317 maxchars = maxchars - chars;
301 end 318 end
302 history[i], i = item, i+1; 319 history[i], i = item, i+1;
303 -- module:log("debug", tostring(item)); 320 -- module:log("debug", item);
304 end 321 end
305 function event.next_stanza() 322 function event.next_stanza()
306 i = i - 1; 323 i = i - 1;
307 return history[i]; 324 return history[i];
308 end 325 end
350 if stanza.attr.type then 367 if stanza.attr.type then
351 with = with .. "<" .. stanza.attr.type 368 with = with .. "<" .. stanza.attr.type
352 end 369 end
353 370
354 -- And stash it 371 -- And stash it
355 local id = archive:append(room_node, nil, stored_stanza, time_now(), with); 372 local time = time_now();
373 local id, err = archive:append(room_node, nil, stored_stanza, time, with);
374
375 if not id and err == "quota-limit" then
376 if type(cleanup_after) == "number" then
377 module:log("debug", "Room '%s' over quota, cleaning archive", room_node);
378 local cleaned = archive:delete(room_node, {
379 ["end"] = (os.time() - cleanup_after);
380 });
381 if cleaned then
382 id, err = archive:append(room_node, nil, stored_stanza, time, with);
383 end
384 end
385 if not id and (archive.caps and archive.caps.truncate) then
386 module:log("debug", "User '%s' over quota, truncating archive", room_node);
387 local truncated = archive:delete(room_node, {
388 truncate = archive_item_limit - 1;
389 });
390 if truncated then
391 id, err = archive:append(room_node, nil, stored_stanza, time, with);
392 end
393 end
394 end
356 395
357 if id then 396 if id then
358 schedule_cleanup(room_node); 397 schedule_cleanup(room_node);
359 stanza:add_direct_child(st.stanza("stanza-id", { xmlns = xmlns_st_id, by = self.jid, id = id })); 398 stanza:add_direct_child(st.stanza("stanza-id", { xmlns = xmlns_st_id, by = self.jid, id = id }));
360 end 399 end
387 -- And role/affiliation changes? 426 -- And role/affiliation changes?
388 427
389 module:add_feature(xmlns_mam); 428 module:add_feature(xmlns_mam);
390 429
391 module:hook("muc-disco#info", function(event) 430 module:hook("muc-disco#info", function(event)
392 event.reply:tag("feature", {var=xmlns_mam}):up(); 431 if archiving_enabled(event.room) then
432 event.reply:tag("feature", {var=xmlns_mam}):up();
433 end
393 end); 434 end);
394 435
395 -- Cleanup 436 -- Cleanup
396
397 local cleanup_after = module:get_option_string("muc_log_expires_after", "1w");
398 local cleanup_interval = module:get_option_number("muc_log_cleanup_interval", 4 * 60 * 60);
399 437
400 if cleanup_after ~= "never" then 438 if cleanup_after ~= "never" then
401 local cleanup_storage = module:open_store("muc_log_cleanup"); 439 local cleanup_storage = module:open_store("muc_log_cleanup");
402 local cleanup_map = module:open_store("muc_log_cleanup", "map"); 440 local cleanup_map = module:open_store("muc_log_cleanup", "map");
403 441