-- mod_muc_mam_search -- -- SPDX-FileCopyrightText: 2024 John Livingston -- SPDX-License-Identifier: AGPL-3.0-only -- local archiving_enabled = module:require("archive").archiving_enabled; local item_match = module:require("filter").item_match; local jid_split = require "util.jid".split; local jid_bare = require "util.jid".bare; local st = require "util.stanza"; local datetime = require"util.datetime"; local dataform = require "util.dataforms".new; local get_form_type = require "util.dataforms".get_type; local mod_muc = module:depends"muc"; local get_room_from_jid = mod_muc.get_room_from_jid; local muc_log_archive = module:open_store("muc_log", "archive"); local xmlns_mam = "urn:xmpp:mam:2"; local xmlns_mam_search = "urn:xmpp:mam:2#x-search"; local xmlns_delay = "urn:xmpp:delay"; local xmlns_forward = "urn:xmpp:forward:0"; module:hook("muc-disco#info", function(event) if archiving_enabled(event.room) then event.reply:tag("feature", {var=xmlns_mam_search}):up(); end end); local query_form = dataform { { name = "FORM_TYPE"; type = "hidden"; value = xmlns_mam_search }; { name = "from"; type = "jid-single" }; { name = "occupant_id"; type = "text-single" }; }; -- Serve form module:hook("iq-get/bare/"..xmlns_mam_search..":query", function(event) local origin, stanza = event.origin, event.stanza; origin.send(st.reply(stanza):query(xmlns_mam_search):add_child(query_form:form())); return true; end); -- Handle archive queries module:hook("iq-set/bare/"..xmlns_mam_search..":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); -- Must be room admin or owner. local from_affiliation = room:get_affiliation(from); if (from_affiliation ~= "owner" and from_affiliation ~= "admin") then origin.send(st.error_reply(stanza, "auth", "forbidden")) return true; end local qid = query.attr.queryid; -- Search query parameters local search_from; local search_occupant_id; local form = query:get_child("x", "jabber:x:data"); if form then local form_type, err = get_form_type(form); if not form_type then origin.send(st.error_reply(stanza, "modify", "bad-request", "Invalid dataform: "..err)); return true; elseif form_type ~= xmlns_mam_search then origin.send(st.error_reply(stanza, "modify", "bad-request", "Unexpected FORM_TYPE, expected '"..xmlns_mam_search.."'")); return true; end 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 search_from = form["from"]; search_occupant_id = form["occupant_id"]; else module:log("debug", "Missing query form, forbidden.") origin.send(st.error_reply(stanza, "modify", "bad-request", "Missing dataform")); return true; end -- TODO: handle RSM (pagination)? module:log("debug", "Archive query by %s id=%s", from, qid); -- Load all the data! local data, err = muc_log_archive:find(room_node, { start = nil; ["end"] = nil; with = "message tag, containing the original senders JID, unless the room makes this public. -- but we only allow this feature to owner and admin, so we don't need to remove this. item.attr.to = nil; item.attr.xmlns = "jabber:client"; fwd_st:add_child(item); origin.send(fwd_st); end end origin.send(st.reply(stanza) -- The result uses xmlns_mam and not xmlns_mam_search, so that the frontend handles this in the same way than xmlns_mam. :tag("fin", { xmlns = xmlns_mam, complete = "true" })); -- That's all folks! module:log("debug", "Archive query id=%s completed, %d items returned", qid or stanza.attr.id, count); return true; end);