author | Ben Smith <bens@effortlessis.com> |
Tue, 14 May 2024 07:31:34 -0700 | |
changeset 5912 | dcea4b4c415d |
parent 5906 | 8ff308fad9fd |
permissions | -rw-r--r-- |
1471 | 1 |
-- Prosody IM |
2 |
-- |
|
3 |
-- This project is MIT/X11 licensed. Please see the |
|
4 |
-- COPYING file in the source package for more information. |
|
5 |
-- |
|
6 |
local get_prefs = module:require"mod_mam/mamprefs".get; |
|
7 |
local set_prefs = module:require"mod_mam/mamprefs".set; |
|
2710
3e97dae28215
mod_mam_archive: Use util.rsm (fixes #877, depends on recent 0.10+)
Dennis Schridde <devurandom@gmx.net>
parents:
1586
diff
changeset
|
8 |
local rsm = require "util.rsm"; |
1471 | 9 |
local jid_bare = require "util.jid".bare; |
10 |
local jid_prep = require "util.jid".prep; |
|
11 |
local date_parse = require "util.datetime".parse; |
|
1498
e82592ed744b
mod_mam_archive: Applying @vstakhov 's patch (https://gist.github.com/vstakhov/306ea813a38021dcf3d4).
syn@syn.im
parents:
1477
diff
changeset
|
12 |
local date_format = require "util.datetime".datetime; |
1471 | 13 |
|
14 |
local st = require "util.stanza"; |
|
15 |
local archive_store = "archive2"; |
|
16 |
local archive = module:open_store(archive_store, "archive"); |
|
17 |
local global_default_policy = module:get_option("default_archive_policy", false); |
|
18 |
local default_max_items, max_max_items = 20, module:get_option_number("max_archive_query_results", 50); |
|
1476
08ca6dd36e39
mod_mam_archive: Fixing issues noted in code review for 153df603f73d3b69c434f2790cff0270de14bb75
syn@syn.im
parents:
1471
diff
changeset
|
19 |
local conversation_interval = tonumber(module:get_option_number("archive_conversation_interval", 86400)); |
1498
e82592ed744b
mod_mam_archive: Applying @vstakhov 's patch (https://gist.github.com/vstakhov/306ea813a38021dcf3d4).
syn@syn.im
parents:
1477
diff
changeset
|
20 |
local resolve_relative_path = require "core.configmanager".resolve_relative_path; |
1471 | 21 |
|
22 |
-- Feature discovery |
|
23 |
module:add_feature("urn:xmpp:archive:auto"); |
|
24 |
module:add_feature("urn:xmpp:archive:manage"); |
|
25 |
module:add_feature("urn:xmpp:archive:pref"); |
|
26 |
module:add_feature("http://jabber.org/protocol/rsm"); |
|
27 |
-- -------------------------------------------------- |
|
28 |
||
29 |
local function prefs_to_stanza(prefs) |
|
1476
08ca6dd36e39
mod_mam_archive: Fixing issues noted in code review for 153df603f73d3b69c434f2790cff0270de14bb75
syn@syn.im
parents:
1471
diff
changeset
|
30 |
local prefstanza = st.stanza("pref", { xmlns="urn:xmpp:archive" }); |
1471 | 31 |
local default = prefs[false] ~= nil and prefs[false] or global_default_policy; |
32 |
||
1476
08ca6dd36e39
mod_mam_archive: Fixing issues noted in code review for 153df603f73d3b69c434f2790cff0270de14bb75
syn@syn.im
parents:
1471
diff
changeset
|
33 |
prefstanza:tag("default", {otr="oppose", save=default and "true" or "false"}):up(); |
08ca6dd36e39
mod_mam_archive: Fixing issues noted in code review for 153df603f73d3b69c434f2790cff0270de14bb75
syn@syn.im
parents:
1471
diff
changeset
|
34 |
prefstanza:tag("method", {type="auto", use="concede"}):up(); |
08ca6dd36e39
mod_mam_archive: Fixing issues noted in code review for 153df603f73d3b69c434f2790cff0270de14bb75
syn@syn.im
parents:
1471
diff
changeset
|
35 |
prefstanza:tag("method", {type="local", use="concede"}):up(); |
08ca6dd36e39
mod_mam_archive: Fixing issues noted in code review for 153df603f73d3b69c434f2790cff0270de14bb75
syn@syn.im
parents:
1471
diff
changeset
|
36 |
prefstanza:tag("method", {type="manual", use="concede"}):up(); |
1471 | 37 |
|
38 |
for jid, choice in pairs(prefs) do |
|
39 |
if jid then |
|
1476
08ca6dd36e39
mod_mam_archive: Fixing issues noted in code review for 153df603f73d3b69c434f2790cff0270de14bb75
syn@syn.im
parents:
1471
diff
changeset
|
40 |
prefstanza:tag("item", {jid=jid, otr="prefer", save=choice and "message" or "false" }):up() |
1471 | 41 |
end |
42 |
end |
|
43 |
||
44 |
return prefstanza; |
|
45 |
end |
|
1476
08ca6dd36e39
mod_mam_archive: Fixing issues noted in code review for 153df603f73d3b69c434f2790cff0270de14bb75
syn@syn.im
parents:
1471
diff
changeset
|
46 |
local function prefs_from_stanza(stanza, username) |
08ca6dd36e39
mod_mam_archive: Fixing issues noted in code review for 153df603f73d3b69c434f2790cff0270de14bb75
syn@syn.im
parents:
1471
diff
changeset
|
47 |
local current_prefs = get_prefs(username); |
1471 | 48 |
|
49 |
-- "default" | "item" | "session" | "method" |
|
50 |
for elem in stanza:children() do |
|
51 |
if elem.name == "default" then |
|
1476
08ca6dd36e39
mod_mam_archive: Fixing issues noted in code review for 153df603f73d3b69c434f2790cff0270de14bb75
syn@syn.im
parents:
1471
diff
changeset
|
52 |
current_prefs[false] = elem.attr["save"] == "true"; |
1471 | 53 |
elseif elem.name == "item" then |
1476
08ca6dd36e39
mod_mam_archive: Fixing issues noted in code review for 153df603f73d3b69c434f2790cff0270de14bb75
syn@syn.im
parents:
1471
diff
changeset
|
54 |
current_prefs[elem.attr["jid"]] = not elem.attr["save"] == "false"; |
1471 | 55 |
elseif elem.name == "session" then |
56 |
module:log("info", "element is not supported: " .. tostring(elem)); |
|
57 |
-- local found = false; |
|
58 |
-- for child in data:children() do |
|
59 |
-- if child.name == elem.name and child.attr["thread"] == elem.attr["thread"] then |
|
60 |
-- for k, v in pairs(elem.attr) do |
|
61 |
-- child.attr[k] = v; |
|
62 |
-- end |
|
63 |
-- found = true; |
|
64 |
-- break; |
|
65 |
-- end |
|
66 |
-- end |
|
67 |
-- if not found then |
|
68 |
-- data:tag(elem.name, elem.attr):up(); |
|
69 |
-- end |
|
70 |
elseif elem.name == "method" then |
|
71 |
module:log("info", "element is not supported: " .. tostring(elem)); |
|
72 |
-- local newpref = stanza.tags[1]; -- iq:pref |
|
73 |
-- for _, e in ipairs(newpref.tags) do |
|
74 |
-- -- if e.name ~= "method" then continue end |
|
75 |
-- local found = false; |
|
76 |
-- for child in data:children() do |
|
77 |
-- if child.name == "method" and child.attr["type"] == e.attr["type"] then |
|
78 |
-- child.attr["use"] = e.attr["use"]; |
|
79 |
-- found = true; |
|
80 |
-- break; |
|
81 |
-- end |
|
82 |
-- end |
|
83 |
-- if not found then |
|
84 |
-- data:tag(e.name, e.attr):up(); |
|
85 |
-- end |
|
86 |
-- end |
|
87 |
end |
|
88 |
end |
|
89 |
end |
|
90 |
||
91 |
------------------------------------------------------------ |
|
92 |
-- Preferences |
|
93 |
------------------------------------------------------------ |
|
94 |
local function preferences_handler(event) |
|
95 |
local origin, stanza = event.origin, event.stanza; |
|
96 |
local user = origin.username; |
|
97 |
local reply = st.reply(stanza); |
|
98 |
||
99 |
if stanza.attr.type == "get" then |
|
100 |
reply:add_child(prefs_to_stanza(get_prefs(user))); |
|
101 |
end |
|
102 |
if stanza.attr.type == "set" then |
|
103 |
local new_prefs = stanza:get_child("pref", xmlns_archive); |
|
104 |
if not new_prefs then return false; end |
|
105 |
||
1476
08ca6dd36e39
mod_mam_archive: Fixing issues noted in code review for 153df603f73d3b69c434f2790cff0270de14bb75
syn@syn.im
parents:
1471
diff
changeset
|
106 |
local prefs = prefs_from_stanza(stanza, origin.username); |
1471 | 107 |
local ok, err = set_prefs(user, prefs); |
108 |
||
109 |
if not ok then |
|
110 |
return origin.send(st.error_reply(stanza, "cancel", "internal-server-error", "Error storing preferences: "..tostring(err))); |
|
111 |
end |
|
112 |
end |
|
113 |
return origin.send(reply); |
|
114 |
end |
|
115 |
local function auto_handler(event) |
|
116 |
local origin, stanza = event.origin, event.stanza; |
|
1476
08ca6dd36e39
mod_mam_archive: Fixing issues noted in code review for 153df603f73d3b69c434f2790cff0270de14bb75
syn@syn.im
parents:
1471
diff
changeset
|
117 |
if not stanza.attr["type"] == "set" then return false; end |
1471 | 118 |
|
119 |
local user = origin.username; |
|
120 |
local prefs = get_prefs(user); |
|
1476
08ca6dd36e39
mod_mam_archive: Fixing issues noted in code review for 153df603f73d3b69c434f2790cff0270de14bb75
syn@syn.im
parents:
1471
diff
changeset
|
121 |
local auto = stanza:get_child("auto", xmlns_archive); |
1471 | 122 |
|
1476
08ca6dd36e39
mod_mam_archive: Fixing issues noted in code review for 153df603f73d3b69c434f2790cff0270de14bb75
syn@syn.im
parents:
1471
diff
changeset
|
123 |
prefs[false] = auto.attr["save"] ~= nil and auto.attr["save"] == "true" or false; |
1471 | 124 |
set_prefs(user, prefs); |
125 |
||
126 |
return origin.send(st.reply(stanza)); |
|
127 |
end |
|
128 |
||
129 |
-- excerpt from mod_storage_sql2 |
|
130 |
local function get_db() |
|
131 |
local mod_sql = module:require("sql"); |
|
132 |
local params = module:get_option("sql"); |
|
133 |
local engine; |
|
134 |
||
135 |
params = params or { driver = "SQLite3" }; |
|
136 |
if params.driver == "SQLite3" then |
|
137 |
params.database = resolve_relative_path(prosody.paths.data or ".", params.database or "prosody.sqlite"); |
|
138 |
end |
|
139 |
||
140 |
assert(params.driver and params.database, "Both the SQL driver and the database need to be specified"); |
|
141 |
engine = mod_sql:create_engine(params); |
|
142 |
engine:set_encoding(); |
|
143 |
||
144 |
return engine; |
|
145 |
end |
|
146 |
||
147 |
------------------------------------------------------------ |
|
148 |
-- Collections. In our case there is one conversation with each contact for the whole day for simplicity |
|
149 |
------------------------------------------------------------ |
|
150 |
local function list_stanza_to_query(origin, list_el) |
|
151 |
local sql = "SELECT `with`, `when` / ".. conversation_interval .." as `day`, COUNT(0) FROM `prosodyarchive` WHERE `host`=? AND `user`=? AND `store`=? "; |
|
152 |
local args = {origin.host, origin.username, archive_store}; |
|
153 |
||
1476
08ca6dd36e39
mod_mam_archive: Fixing issues noted in code review for 153df603f73d3b69c434f2790cff0270de14bb75
syn@syn.im
parents:
1471
diff
changeset
|
154 |
local with = list_el.attr["with"]; |
1471 | 155 |
if with ~= nil then |
156 |
sql = sql .. "AND `with` = ? "; |
|
157 |
table.insert(args, jid_bare(with)); |
|
158 |
end |
|
159 |
||
1476
08ca6dd36e39
mod_mam_archive: Fixing issues noted in code review for 153df603f73d3b69c434f2790cff0270de14bb75
syn@syn.im
parents:
1471
diff
changeset
|
160 |
local after = list_el.attr["start"]; |
1471 | 161 |
if after ~= nil then |
1586 | 162 |
sql = sql .. "AND `when` >= ? "; |
1471 | 163 |
table.insert(args, date_parse(after)); |
164 |
end |
|
165 |
||
1476
08ca6dd36e39
mod_mam_archive: Fixing issues noted in code review for 153df603f73d3b69c434f2790cff0270de14bb75
syn@syn.im
parents:
1471
diff
changeset
|
166 |
local before = list_el.attr["end"]; |
1471 | 167 |
if before ~= nil then |
168 |
sql = sql .. "AND `when` <= ? "; |
|
169 |
table.insert(args, date_parse(before)); |
|
170 |
end |
|
171 |
||
172 |
sql = sql .. "GROUP BY `with`, `when` / ".. conversation_interval .." ORDER BY `when` / ".. conversation_interval .." ASC "; |
|
173 |
||
174 |
local qset = rsm.get(list_el); |
|
175 |
local limit = math.min(qset and qset.max or default_max_items, max_max_items); |
|
1476
08ca6dd36e39
mod_mam_archive: Fixing issues noted in code review for 153df603f73d3b69c434f2790cff0270de14bb75
syn@syn.im
parents:
1471
diff
changeset
|
176 |
sql = sql.."LIMIT ?"; |
1471 | 177 |
table.insert(args, limit); |
178 |
||
179 |
table.insert(args, 1, sql); |
|
180 |
return args; |
|
181 |
end |
|
182 |
local function list_handler(event) |
|
183 |
local db = get_db(); |
|
184 |
local origin, stanza = event.origin, event.stanza; |
|
185 |
local reply = st.reply(stanza); |
|
186 |
||
187 |
local query = list_stanza_to_query(origin, stanza.tags[1]); |
|
1476
08ca6dd36e39
mod_mam_archive: Fixing issues noted in code review for 153df603f73d3b69c434f2790cff0270de14bb75
syn@syn.im
parents:
1471
diff
changeset
|
188 |
local list = reply:tag("list", {xmlns=xmlns_archive}); |
1471 | 189 |
|
190 |
for row in db:select(unpack(query)) do |
|
1476
08ca6dd36e39
mod_mam_archive: Fixing issues noted in code review for 153df603f73d3b69c434f2790cff0270de14bb75
syn@syn.im
parents:
1471
diff
changeset
|
191 |
list:tag("chat", { |
1471 | 192 |
xmlns=xmlns_archive, |
193 |
with=row[1], |
|
194 |
start=date_format(row[2] * conversation_interval), |
|
195 |
version=row[3] |
|
196 |
}):up(); |
|
197 |
end |
|
198 |
||
199 |
origin.send(reply); |
|
200 |
return true; |
|
201 |
end |
|
202 |
||
203 |
------------------------------------------------------------ |
|
204 |
-- Message archive retrieval |
|
205 |
------------------------------------------------------------ |
|
206 |
||
207 |
local function retrieve_handler(event) |
|
208 |
local origin, stanza = event.origin, event.stanza; |
|
209 |
local reply = st.reply(stanza); |
|
210 |
||
1476
08ca6dd36e39
mod_mam_archive: Fixing issues noted in code review for 153df603f73d3b69c434f2790cff0270de14bb75
syn@syn.im
parents:
1471
diff
changeset
|
211 |
local retrieve = stanza:get_child("retrieve", xmlns_archive); |
1471 | 212 |
|
1476
08ca6dd36e39
mod_mam_archive: Fixing issues noted in code review for 153df603f73d3b69c434f2790cff0270de14bb75
syn@syn.im
parents:
1471
diff
changeset
|
213 |
local qwith = retrieve.attr["with"]; |
08ca6dd36e39
mod_mam_archive: Fixing issues noted in code review for 153df603f73d3b69c434f2790cff0270de14bb75
syn@syn.im
parents:
1471
diff
changeset
|
214 |
local qstart = retrieve.attr["start"]; |
1471 | 215 |
|
216 |
module:log("debug", "Archive query, with %s from %s)", |
|
217 |
qwith or "anyone", qstart or "the dawn of time"); |
|
218 |
||
219 |
if qstart then -- Validate timestamps |
|
220 |
local vstart = (qstart and date_parse(qstart)); |
|
221 |
if (qstart and not vstart) then |
|
222 |
origin.send(st.error_reply(stanza, "modify", "bad-request", "Invalid timestamp")) |
|
223 |
return true |
|
224 |
end |
|
225 |
qstart = vstart; |
|
226 |
end |
|
227 |
||
1476
08ca6dd36e39
mod_mam_archive: Fixing issues noted in code review for 153df603f73d3b69c434f2790cff0270de14bb75
syn@syn.im
parents:
1471
diff
changeset
|
228 |
if qwith then -- Validate the "with" jid |
1471 | 229 |
local pwith = qwith and jid_prep(qwith); |
230 |
if pwith and not qwith then -- it failed prepping |
|
231 |
origin.send(st.error_reply(stanza, "modify", "bad-request", "Invalid JID")) |
|
232 |
return true |
|
233 |
end |
|
234 |
qwith = jid_bare(pwith); |
|
235 |
end |
|
236 |
||
237 |
-- RSM stuff |
|
238 |
local qset = rsm.get(retrieve); |
|
239 |
local qmax = math.min(qset and qset.max or default_max_items, max_max_items); |
|
240 |
local reverse = qset and qset.before or false; |
|
241 |
local before, after = qset and qset.before, qset and qset.after; |
|
242 |
if type(before) ~= "string" then before = nil; end |
|
243 |
||
244 |
-- Load all the data! |
|
245 |
local data, err = archive:find(origin.username, { |
|
246 |
start = qstart; ["end"] = qstart + conversation_interval; |
|
247 |
with = qwith; |
|
248 |
limit = qmax; |
|
249 |
before = before; after = after; |
|
250 |
reverse = reverse; |
|
1522 | 251 |
total = true; |
1471 | 252 |
}); |
253 |
||
254 |
if not data then |
|
255 |
return origin.send(st.error_reply(stanza, "cancel", "internal-server-error", err)); |
|
256 |
end |
|
257 |
local count = err; |
|
258 |
||
1476
08ca6dd36e39
mod_mam_archive: Fixing issues noted in code review for 153df603f73d3b69c434f2790cff0270de14bb75
syn@syn.im
parents:
1471
diff
changeset
|
259 |
local chat = reply:tag("chat", {xmlns=xmlns_archive, with=qwith, start=date_format(qstart), version=count}); |
1498
e82592ed744b
mod_mam_archive: Applying @vstakhov 's patch (https://gist.github.com/vstakhov/306ea813a38021dcf3d4).
syn@syn.im
parents:
1477
diff
changeset
|
260 |
local first, last; |
1522 | 261 |
|
262 |
module:log("debug", "Count "..count); |
|
1471 | 263 |
for id, item, when in data do |
1477
db870913e1cf
mod_mam_archive: Doing stanza deserialization after mod_storage the right way
syn@syn.im
parents:
1476
diff
changeset
|
264 |
if not getmetatable(item) == st.stanza_mt then |
db870913e1cf
mod_mam_archive: Doing stanza deserialization after mod_storage the right way
syn@syn.im
parents:
1476
diff
changeset
|
265 |
item = st.deserialize(item); |
db870913e1cf
mod_mam_archive: Doing stanza deserialization after mod_storage the right way
syn@syn.im
parents:
1476
diff
changeset
|
266 |
end |
db870913e1cf
mod_mam_archive: Doing stanza deserialization after mod_storage the right way
syn@syn.im
parents:
1476
diff
changeset
|
267 |
module:log("debug", tostring(item)); |
db870913e1cf
mod_mam_archive: Doing stanza deserialization after mod_storage the right way
syn@syn.im
parents:
1476
diff
changeset
|
268 |
|
1498
e82592ed744b
mod_mam_archive: Applying @vstakhov 's patch (https://gist.github.com/vstakhov/306ea813a38021dcf3d4).
syn@syn.im
parents:
1477
diff
changeset
|
269 |
local tag = jid_bare(item.attr["from"]) == jid_bare(origin.full_jid) and "to" or "from"; |
1471 | 270 |
tag = chat:tag(tag, {secs = when - qstart}); |
1477
db870913e1cf
mod_mam_archive: Doing stanza deserialization after mod_storage the right way
syn@syn.im
parents:
1476
diff
changeset
|
271 |
tag:add_child(item:get_child("body")):up(); |
1498
e82592ed744b
mod_mam_archive: Applying @vstakhov 's patch (https://gist.github.com/vstakhov/306ea813a38021dcf3d4).
syn@syn.im
parents:
1477
diff
changeset
|
272 |
if not first then first = id; end |
e82592ed744b
mod_mam_archive: Applying @vstakhov 's patch (https://gist.github.com/vstakhov/306ea813a38021dcf3d4).
syn@syn.im
parents:
1477
diff
changeset
|
273 |
last = id; |
1471 | 274 |
end |
1498
e82592ed744b
mod_mam_archive: Applying @vstakhov 's patch (https://gist.github.com/vstakhov/306ea813a38021dcf3d4).
syn@syn.im
parents:
1477
diff
changeset
|
275 |
reply:add_child(rsm.generate{ first = first, last = last, count = count }) |
1471 | 276 |
|
277 |
origin.send(reply); |
|
278 |
return true; |
|
279 |
end |
|
280 |
||
281 |
local function not_implemented(event) |
|
282 |
local origin, stanza = event.origin, event.stanza; |
|
1476
08ca6dd36e39
mod_mam_archive: Fixing issues noted in code review for 153df603f73d3b69c434f2790cff0270de14bb75
syn@syn.im
parents:
1471
diff
changeset
|
283 |
local reply = st.reply(stanza):tag("error", {type="cancel"}); |
08ca6dd36e39
mod_mam_archive: Fixing issues noted in code review for 153df603f73d3b69c434f2790cff0270de14bb75
syn@syn.im
parents:
1471
diff
changeset
|
284 |
reply:tag("feature-not-implemented", {xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"}):up(); |
1471 | 285 |
origin.send(reply); |
286 |
end |
|
287 |
||
288 |
-- Preferences |
|
289 |
module:hook("iq/self/urn:xmpp:archive:pref", preferences_handler); |
|
290 |
module:hook("iq/self/urn:xmpp:archive:auto", auto_handler); |
|
291 |
module:hook("iq/self/urn:xmpp:archive:itemremove", not_implemented); |
|
292 |
module:hook("iq/self/urn:xmpp:archive:sessionremove", not_implemented); |
|
293 |
||
294 |
-- Message Archive Management |
|
295 |
module:hook("iq/self/urn:xmpp:archive:list", list_handler); |
|
296 |
module:hook("iq/self/urn:xmpp:archive:retrieve", retrieve_handler); |
|
297 |
module:hook("iq/self/urn:xmpp:archive:remove", not_implemented); |
|
298 |
||
299 |
-- manual archiving |
|
300 |
module:hook("iq/self/urn:xmpp:archive:save", not_implemented); |
|
301 |
-- replication |
|
302 |
module:hook("iq/self/urn:xmpp:archive:modified", not_implemented); |