# HG changeset patch # User Emmanuel Gil Peyrot # Date 1625848305 -7200 # Node ID 016e9c7733bc9d8fb5bf39fcf9838e5497945390 # Parent 05bea2f43d97bfba92d43e47083939b0f2f20195 mod_mam_muc: Obsolete this module, it is included in Prosody since 0.11 under the name mod_muc_mam diff -r 05bea2f43d97 -r 016e9c7733bc mod_mam_muc/README.markdown --- a/mod_mam_muc/README.markdown Fri Jul 09 18:30:13 2021 +0200 +++ b/mod_mam_muc/README.markdown Fri Jul 09 18:31:45 2021 +0200 @@ -1,61 +1,8 @@ --- labels: -- 'Stage-Merged' +- 'Stage-Obsolete' summary: 'XEP-0313: Message Archive Management for MUC' +superseded_by: mod_muc_mam ... -Introduction -============ - -This module logs the conversation of chatrooms running on the server to -Prosody's archive storage. To access them you will need a client with -support for [XEP-0313: Message Archive Management] **version 0.5** or -a module such as [mod_http_muc_log]. - -Usage -===== - -First copy the module to the prosody plugins directory. - -Then add "mam\_muc" to your modules\_enabled list: - -``` {.lua} -Component "conference.example.org" "muc" -modules_enabled = { - "mam_muc", -} -``` - -mod\_mam\_muc needs an archive-capable storage module, see -[Prosodys storage documentation][doc:storage] for how to select one. -The store is called "muc\_log". - -Configuration -============= - -Logging needs to be enabled for each room in the room configuration -dialog. - -``` {.lua} -muc_log_by_default = true; -- Enable logging by default (can be disabled in room config) - -muc_log_all_rooms = false; -- set to true to force logging of all rooms - --- This is the largest number of messages that are allowed to be retrieved when joining a room. -max_history_messages = 20; -``` - -Compatibility -============= - - ------- ----------------------------------------------------------------------------- - trunk Use mod\_muc\_mam (included with Prosody) - 0.11 Use mod\_muc\_mam (included with Prosody) - 0.10 Works partially, only legacy XEP-0313 (version 0.5, namespace urn:xmpp:mam:1) - 0.9 Does not work - 0.8 Does not work - ------- ----------------------------------------------------------------------------- - -Prosody trunk (after April 2014) has a major rewrite of the MUC module, -allowing easier integration, but this module is not compatible with -that. +Since Prosody 0.11, this module is [included in Prosody](https://prosody.im/doc/modules/mod_muc_mam), you will be redirected there shortly. diff -r 05bea2f43d97 -r 016e9c7733bc mod_mam_muc/mod_mam_muc.lua --- a/mod_mam_muc/mod_mam_muc.lua Fri Jul 09 18:30:13 2021 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,441 +0,0 @@ --- XEP-0313: Message Archive Management for Prosody MUC --- Copyright (C) 2011-2017 Kim Alvefur --- --- This file is MIT/X11 licensed. - -if module:get_host_type() ~= "component" then - module:log("error", "mod_%s should be loaded only on a MUC component, not normal hosts", module.name); - return; -end - --- Note: Can't implement urn:xmpp:mam:2 because we catch messages after --- they have already been broadcast, so they can no longer be modified. - -local xmlns_mam = "urn:xmpp:mam:1"; -local xmlns_delay = "urn:xmpp:delay"; -local xmlns_forward = "urn:xmpp:forward:0"; -local xmlns_st_id = "urn:xmpp:sid:0"; -local xmlns_muc_user = "http://jabber.org/protocol/muc#user"; -local muc_form_enable = "muc#roomconfig_enablearchiving" - -local st = require "util.stanza"; -local rsm = require "util.rsm"; -local jid_bare = require "util.jid".bare; -local jid_split = require "util.jid".split; -local jid_prep = require "util.jid".prep; -local dataform = require "util.dataforms".new; -local it = require"util.iterators"; - --- Support both old and new MUC code -local mod_muc = module:depends"muc"; -local rooms = rawget(mod_muc, "rooms"); -if not rooms then - module:log("warn", "mod_mam_muc is compatible with Prosody up to 0.10.x, use mod_muc_mam with later versions"); - module:depends("muc_mam"); - return; -end -local each_room = function() return it.values(rooms); end; -local get_room_from_jid = function (jid) return rooms[jid]; end - -local is_stanza = st.is_stanza; -local tostring = tostring; -local time_now = os.time; -local m_min = math.min; -local timestamp, timestamp_parse = require "util.datetime".datetime, require "util.datetime".parse; -local default_max_items, max_max_items = 20, module:get_option_number("max_archive_query_results", 50); - -local default_history_length = 20; -local max_history_length = module:get_option_number("max_history_messages", math.huge); - -local function get_historylength(room) - return math.min(room._data.history_length or default_history_length, max_history_length); -end - -local log_all_rooms = module:get_option_boolean("muc_log_all_rooms", false); -local log_by_default = module:get_option_boolean("muc_log_by_default", true); - -local archive_store = "muc_log"; -local archive = module:open_store(archive_store, "archive"); - -if archive.name == "null" or not archive.find then - if not archive.find then - module:log("error", "Attempt to open archive storage returned a driver without archive API support"); - module:log("error", "mod_%s does not support archiving", - archive._provided_by or archive.name and "storage_"..archive.name.."(?)" or ""); - else - module:log("error", "Attempt to open archive storage returned null driver"); - end - module:log("info", "See https://prosody.im/doc/storage and https://prosody.im/doc/archiving for more information"); - return false; -end - -local function archiving_enabled(room) - if log_all_rooms then - return true; - end - local enabled = room._data.archiving; - if enabled == nil then - return log_by_default; - end - return enabled; -end - -local send_history, save_to_history; - - -- Override history methods for all rooms. -do -- 0.10 or older - module:hook("muc-room-created", function (event) - local room = event.room; - if archiving_enabled(room) then - room.send_history = send_history; - room.save_to_history = save_to_history; - end - end); - - function module.load() - for room in each_room() do - if archiving_enabled(room) then - room.send_history = send_history; - room.save_to_history = save_to_history; - end - end - end - function module.unload() - for room in each_room() do - if room.send_history == send_history then - room.send_history = nil; - room.save_to_history = nil; - end - end - end -end - -if not log_all_rooms then - module:hook("muc-config-form", function(event) - local room, form = event.room, event.form; - table.insert(form, - { - name = muc_form_enable, - type = "boolean", - label = "Enable archiving?", - value = archiving_enabled(room), - } - ); - end); - - module:hook("muc-config-submitted", function(event) - local room, fields, changed = event.room, event.fields, event.changed; - local new = fields[muc_form_enable]; - if new ~= room._data.archiving then - room._data.archiving = new; - if type(changed) == "table" then - changed[muc_form_enable] = true; - else - event.changed = true; - end - if new then - room.send_history = send_history; - room.save_to_history = save_to_history; - else - room.send_history = nil; - room.save_to_history = nil; - end - end - end); -end - --- Note: We ignore the 'with' field as this is internally used for stanza types -local query_form = dataform { - { name = "FORM_TYPE"; type = "hidden"; value = xmlns_mam; }; - { name = "with"; type = "jid-single"; }; - { name = "start"; type = "text-single" }; - { name = "end"; type = "text-single"; }; -}; - --- Serve form -module:hook("iq-get/bare/"..xmlns_mam..":query", function(event) - local origin, stanza = event.origin, event.stanza; - origin.send(st.reply(stanza):add_child(query_form:form())); - return true; -end); - --- Handle archive queries -module:hook("iq-set/bare/"..xmlns_mam..":query", function(event) - local origin, stanza = event.origin, event.stanza; - local room_jid = stanza.attr.to; - local room_node = jid_split(room_jid); - local orig_from = stanza.attr.from; - local query = stanza.tags[1]; - - local room = get_room_from_jid(room_jid); - if not room then - origin.send(st.error_reply(stanza, "cancel", "item-not-found")) - return true; - end - local from = jid_bare(orig_from); - - -- Banned or not a member of a members-only room? - local from_affiliation = room:get_affiliation(from); - if from_affiliation == "outcast" -- banned - or room:get_members_only() and not from_affiliation then -- members-only, not a member - origin.send(st.error_reply(stanza, "auth", "forbidden")) - return true; - end - - local qid = query.attr.queryid; - - -- Search query parameters - local qstart, qend; - local form = query:get_child("x", "jabber:x:data"); - if form then - local err; - form, err = query_form:data(form); - if err then - origin.send(st.error_reply(stanza, "modify", "bad-request", select(2, next(err)))); - return true; - end - qstart, qend = form["start"], form["end"]; - end - - if qstart or qend then -- Validate timestamps - local vstart, vend = (qstart and timestamp_parse(qstart)), (qend and timestamp_parse(qend)) - if (qstart and not vstart) or (qend and not vend) then - origin.send(st.error_reply(stanza, "modify", "bad-request", "Invalid timestamp")) - return true; - end - qstart, qend = vstart, vend; - end - - module:log("debug", "Archive query id %s from %s until %s)", - tostring(qid), - qstart and timestamp(qstart) or "the dawn of time", - qend and timestamp(qend) or "now"); - - -- RSM stuff - local qset = rsm.get(query); - local qmax = m_min(qset and qset.max or default_max_items, max_max_items); - local reverse = qset and qset.before or false; - - local before, after = qset and qset.before, qset and qset.after; - if type(before) ~= "string" then before = nil; end - - -- Load all the data! - local data, err = archive:find(room_node, { - start = qstart; ["end"] = qend; -- Time range - limit = qmax + 1; - before = before; after = after; - reverse = reverse; - with = "message qmax then - complete = nil; - break; - end - local fwd_st = st.message(msg_reply_attr) - :tag("result", { xmlns = xmlns_mam, queryid = qid, id = id }) - :tag("forwarded", { xmlns = xmlns_forward }) - :tag("delay", { xmlns = xmlns_delay, stamp = timestamp(when) }):up(); - - -- Strip tag, containing the original senders JID, unless the room makes this public - if room:get_whois() ~= "anyone" then - item:maptags(function (tag) - if tag.name == "x" and tag.attr.xmlns == xmlns_muc_user then - return nil; - end - return tag; - end); - end - if not is_stanza(item) then - item = st.deserialize(item); - end - item.attr.xmlns = "jabber:client"; - fwd_st:add_child(item); - - if not first then first = id; end - last = id; - - if reverse then - results[count] = fwd_st; - else - origin.send(fwd_st); - end - end - - if reverse then - for i = #results, 1, -1 do - origin.send(results[i]); - end - first, last = last, first; - end - - -- That's all folks! - module:log("debug", "Archive query %s completed", tostring(qid)); - - origin.send(st.reply(stanza) - :tag("fin", { xmlns = xmlns_mam, queryid = qid, complete = complete }) - :add_child(rsm.generate { - first = first, last = last, count = total })); - return true; -end); - -module:hook("muc-get-history", function (event) - local room = event.room; - if not archiving_enabled(room) then return end - local room_jid = room.jid; - local maxstanzas = event.maxstanzas or math.huge; - local maxchars = event.maxchars; - local since = event.since; - local to = event.to; - - -- Load all the data! - local query = { - limit = math.min(maxstanzas, get_historylength(room)); - start = since; - reverse = true; - with = "message that claim to be from us - stanza:maptags(function (tag) - if tag.name == "stanza-id" and tag.attr.xmlns == xmlns_st_id - and jid_prep(tag.attr.by) == self.jid then - return nil; - end - if tag.name == "x" and tag.attr.xmlns == xmlns_muc_user then - return nil; - end - return tag; - end); - - local stored_stanza = stanza; - - if stanza.name == "message" and self:get_whois() == "anyone" then - stored_stanza = st.clone(stanza); - local actor = jid_bare(self._occupants[stanza.attr.from].jid); - local affiliation = self:get_affiliation(actor) or "none"; - local role = self:get_role(actor) or self:get_default_role(affiliation); - stored_stanza:add_direct_child(st.stanza("x", { xmlns = xmlns_muc_user }) - :tag("item", { affiliation = affiliation; role = role; jid = actor })); - end - - -- Policy check - if not archiving_enabled(self) then -- Don't log - module:log("debug", "Not archiving %s", stanza:top_tag()); - return; - end - module:log("debug", "Archiving %s", stanza:top_tag()); - - -- And stash it - local with = stanza.name - if stanza.attr.type then - with = with .. "<" .. stanza.attr.type - end - - archive:append(room_node, nil, stored_stanza, time_now(), with); -end - -if not archive.delete then - module:log("warn", "Storage driver %s does not support deletion", archive._provided_by); - module:log("warn", "Archived message will persist after a room has been destroyed"); -else - module:hook("muc-room-destroyed", function(event) - local room_node = jid_split(event.room.jid); - archive:delete(room_node); - end); -end - --- And role/affiliation changes? - -module:add_feature(xmlns_mam); - -module:hook("muc-disco#info", function(event) - event.reply:tag("feature", {var=xmlns_mam}):up(); -end);