Software /
code /
prosody-modules
Comparison
mod_mam/mod_mam.lua @ 558:66de25ffc8d9
mod_mam: Implement archiving preferences.
author | Kim Alvefur <zash@zash.se> |
---|---|
date | Sun, 15 Jan 2012 03:58:54 +0100 |
parent | 523:eff140d53b83 |
child | 559:6be3a130c810 |
comparison
equal
deleted
inserted
replaced
557:14f39769c9e0 | 558:66de25ffc8d9 |
---|---|
3 -- | 3 -- |
4 -- This file is MIT/X11 licensed. | 4 -- This file is MIT/X11 licensed. |
5 -- | 5 -- |
6 -- Based on MAM ProtoXEP Version 0.1 (2010-07-01) | 6 -- Based on MAM ProtoXEP Version 0.1 (2010-07-01) |
7 | 7 |
8 local xmlns_mam = "urn:xmpp:mam:tmp"; | |
9 local xmlns_delay = "urn:xmpp:delay"; | |
10 local xmlns_forward = "urn:xmpp:forward:0"; | |
11 | |
8 local st = require "util.stanza"; | 12 local st = require "util.stanza"; |
9 local jid_bare = require "util.jid".bare; | 13 local jid_bare = require "util.jid".bare; |
10 local jid_split = require "util.jid".split; | 14 local jid_split = require "util.jid".split; |
11 local xmlns_mam = "urn:xmpp:mam:tmp"; | 15 local host = module.host; |
12 local xmlns_delay = "urn:xmpp:delay"; | 16 local host_sessions = hosts[host].sessions; |
13 local xmlns_forward = "urn:xmpp:forward:0"; | 17 |
14 local host_sessions = hosts[module.host].sessions; | 18 local dm_load = require "util.datamanager".load; |
15 local dm_list_load = require "util.datamanager".list_load | 19 local dm_store = require "util.datamanager".store; |
16 local dm_list_append = require "util.datamanager".list_append | 20 local dm_list_load = require "util.datamanager".list_load; |
21 local dm_list_append = require "util.datamanager".list_append; | |
22 local rm_load_roster = require "core.rostermanager".load_roster; | |
23 | |
17 local time_now = os.time; | 24 local time_now = os.time; |
18 local timestamp, timestamp_parse = require "util.datetime".datetime, require "util.datetime".parse; | 25 local timestamp, timestamp_parse = require "util.datetime".datetime, require "util.datetime".parse; |
19 local uuid = require "util.uuid".generate; | 26 local uuid = require "util.uuid".generate; |
20 | 27 local global_default_policy = module:get_option("default_archive_policy", false); |
21 -- TODO This, and appropritate filtering in message_handler() | 28 -- TODO Should be possible to enforce it too |
29 | |
30 local default_attrs = { | |
31 always = true, [true] = "always", | |
32 never = false, [false] = "never", | |
33 roster = "roster", | |
34 } | |
35 | |
36 do | |
37 local prefs_format = { | |
38 [false] = "roster", | |
39 -- default ::= true | false | "roster" | |
40 -- true = always, false = never, nil = global default | |
41 ["romeo@montague.net"] = true, -- always | |
42 ["montague@montague.net"] = false, -- newer | |
43 }; | |
44 end | |
45 | |
46 local prefs_store = "archive2_prefs"; | |
47 local function get_prefs(user) | |
48 return dm_load(user, host, prefs_store) or | |
49 { [false] = global_default_policy }; | |
50 end | |
51 local function set_prefs(user, prefs) | |
52 return dm_store(user, host, prefs_store, prefs); | |
53 end | |
54 | |
55 | |
56 -- Handle prefs. | |
22 module:hook("iq/self/"..xmlns_mam..":prefs", function(event) | 57 module:hook("iq/self/"..xmlns_mam..":prefs", function(event) |
23 local origin, stanza = event.origin, event.stanza; | 58 local origin, stanza = event.origin, event.stanza; |
59 local user = origin.username; | |
24 if stanza.attr.type == "get" then | 60 if stanza.attr.type == "get" then |
25 -- Not implemented yet, hardcoded to store everything. | 61 local prefs = get_prefs(user); |
26 origin.send(st.reply(stanza) | 62 local default = prefs[false]; |
27 :tag("prefs", { xmlns = xmlns_mam, default = "always" })); | 63 default = default ~= nil and default_attrs[default] or global_default_policy; |
64 local reply = st.reply(stanza):tag("prefs", { xmlns = xmlns_mam, default = default }) | |
65 --module:log("debug", "get_prefs(%q) => %s", user, require"util.serialization".serialize(prefs)); | |
66 local always = st.stanza("always"); | |
67 local never = st.stanza("never"); | |
68 for k,v in pairs(prefs) do | |
69 if k then | |
70 (v and always or never):tag("jid"):text(k):up(); | |
71 end | |
72 end | |
73 reply:add_child(always):add_child(never); | |
74 origin.send(reply); | |
28 return true | 75 return true |
29 else -- type == "set" | 76 else -- type == "set" |
30 -- TODO | 77 local prefs = {}; |
78 local new_prefs = stanza:get_child("prefs", xmlns_mam); | |
79 local new_default = new_prefs.attr.default; | |
80 if new_default then | |
81 prefs[false] = default_attrs[new_default]; | |
82 end | |
83 | |
84 local always = new_prefs:get_child("always"); | |
85 if always then | |
86 for rule in always:childtags("jid") do | |
87 local jid = rule:get_text(); | |
88 prefs[jid] = true; | |
89 end | |
90 end | |
91 | |
92 local never = new_prefs:get_child("never"); | |
93 if never then | |
94 for rule in never:childtags("jid") do | |
95 local jid = rule:get_text(); | |
96 prefs[jid] = false; | |
97 end | |
98 end | |
99 | |
100 --module:log("debug", "set_prefs(%q, %s)", user, require"util.serialization".serialize(prefs)); | |
101 local ok, err = set_prefs(user, prefs); | |
102 if not ok then | |
103 origin.send(st.error_reply(stanza, "cancel", "internal-server-error", "Error storing preferences: "..tostring(err))); | |
104 else | |
105 origin.send(st.reply(stanza)); | |
106 end | |
107 return true | |
31 end | 108 end |
32 end); | 109 end); |
33 | 110 |
111 -- Handle archive queries | |
34 module:hook("iq/self/"..xmlns_mam..":query", function(event) | 112 module:hook("iq/self/"..xmlns_mam..":query", function(event) |
35 local origin, stanza = event.origin, event.stanza; | 113 local origin, stanza = event.origin, event.stanza; |
36 local query = stanza.tags[1]; | 114 local query = stanza.tags[1]; |
37 if stanza.attr.type == "get" then | 115 if stanza.attr.type == "get" then |
38 local qid = query.attr.queryid; | 116 local qid = query.attr.queryid; |
41 local qwith = query:get_child_text("with"); | 119 local qwith = query:get_child_text("with"); |
42 local qstart = query:get_child_text("start"); | 120 local qstart = query:get_child_text("start"); |
43 local qend = query:get_child_text("end"); | 121 local qend = query:get_child_text("end"); |
44 module:log("debug", "Archive query, id %s with %s from %s until %s)", | 122 module:log("debug", "Archive query, id %s with %s from %s until %s)", |
45 tostring(qid), qwith or "anyone", qstart or "the dawn of time", qend or "now"); | 123 tostring(qid), qwith or "anyone", qstart or "the dawn of time", qend or "now"); |
46 | 124 |
47 local qwith = qwith and jid_bare(qwith); -- FIXME Later, full vs bare query. | |
48 qstart, qend = (qstart and timestamp_parse(qstart)), (qend and timestamp_parse(qend)) | 125 qstart, qend = (qstart and timestamp_parse(qstart)), (qend and timestamp_parse(qend)) |
49 | 126 |
50 -- Load all the data! | 127 -- Load all the data! |
51 local data, err = dm_list_load(origin.username, origin.host, "archive2"); --FIXME Decide storage name. achive2, [sm]am, archive_ng, for_fra_and_nsa | 128 local data, err = dm_list_load(origin.username, origin.host, "archive2"); |
52 module:log("debug", "Loaded %d items, about to filter", #(data or {})); | 129 if not data then |
130 if (not err) then | |
131 module:log("debug", "The archive was empty."); | |
132 origin.send(st.reply(stanza)); | |
133 else | |
134 origin.send(st.error_reply(stanza, "cancel", "internal-server-error", "Error loading archive: "..tostring(err))); | |
135 end | |
136 return true | |
137 end | |
138 | |
139 module:log("debug", "Loaded %d items, about to filter", #data); | |
53 for i=1,#data do | 140 for i=1,#data do |
54 local item = data[i]; | 141 local item = data[i]; |
55 local when, with = item.when, item.with_bare; | 142 local when, with, with_bare = item.when, item.with, item.with_bare; |
56 local ts = item.timestamp; | 143 local ts = item.timestamp; |
57 -- FIXME Premature optimization: Bare JIDs only | 144 --module:log("debug", "message with %s at %s", with, when or "???"); |
58 --module:log("debug", "message with %s when %s", with, when or "???"); | |
59 -- Apply query filter | 145 -- Apply query filter |
60 if (not qwith or qwith == with) | 146 if (not qwith or ((qwith == with) or (qwith == with_bare))) |
61 and (not qstart or when >= qstart) | 147 and (not qstart or when >= qstart) |
62 and (not qend or when <= qend) then | 148 and (not qend or when <= qend) then |
63 -- Optimizable? Do this when archiving? | 149 -- Optimizable? Do this when archiving? |
64 --module:log("debug", "sending"); | 150 --module:log("debug", "sending"); |
65 local fwd_st = st.message{ to = origin.full_jid } | 151 local fwd_st = st.message{ to = origin.full_jid } |
77 origin.send(st.reply(stanza)); | 163 origin.send(st.reply(stanza)); |
78 return true | 164 return true |
79 end | 165 end |
80 end); | 166 end); |
81 | 167 |
168 local function has_in_roster(user, who) | |
169 local roster = rm_load_roster(user, host); | |
170 module:log("debug", "%s has %s in roster? %s", user, who, roster[who] and "yes" or "no"); | |
171 return roster and roster[who]; | |
172 end | |
173 | |
174 local function shall_store(user, who) | |
175 -- TODO Cache this? | |
176 local prefs = get_prefs(user); | |
177 local rule = prefs[who]; | |
178 module:log("debug", "%s's rule for %s is %s", user, who, tostring(rule)) | |
179 if rule ~= nil then | |
180 return rule; | |
181 else -- Below could be done by a metatable | |
182 local default = prefs[false]; | |
183 module:log("debug", "%s's default rule is %s", user, tostring(default)) | |
184 if default == nil then | |
185 default = global_default_policy; | |
186 module:log("debug", "Using global default rule, %s", tostring(default)) | |
187 end | |
188 if default == "roster" then | |
189 return has_in_roster(user, who); | |
190 end | |
191 return default; | |
192 end | |
193 end | |
194 | |
195 -- Handle messages | |
82 local function message_handler(event, c2s) | 196 local function message_handler(event, c2s) |
83 local origin, stanza = event.origin, event.stanza; | 197 local origin, stanza = event.origin, event.stanza; |
84 local orig_type = stanza.attr.type or "normal"; | 198 local orig_type = stanza.attr.type or "normal"; |
85 local orig_to = stanza.attr.to; | 199 local orig_to = stanza.attr.to; |
86 local orig_from = stanza.attr.from; | 200 local orig_from = stanza.attr.from; |
96 return; | 210 return; |
97 -- TODO Maybe headlines should be configurable? | 211 -- TODO Maybe headlines should be configurable? |
98 -- TODO Write a mod_mam_muc for groupchat messages. | 212 -- TODO Write a mod_mam_muc for groupchat messages. |
99 end | 213 end |
100 | 214 |
101 -- Stamp "We archived this" on the message | |
102 stanza:tag("archived", { xmlns = xmlns_mam, by = module.host, id = uuid() }); | |
103 local store_user, store_host = jid_split(c2s and orig_from or orig_to); | 215 local store_user, store_host = jid_split(c2s and orig_from or orig_to); |
104 | 216 local target_jid = c2s and orig_to or orig_from; |
105 local when = time_now(); | 217 local target_bare = jid_bare(target_jid); |
106 -- And stash it | 218 |
107 dm_list_append(store_user, store_host, "archive2", { | 219 assert(store_host == host, "This should not happen."); |
108 when = when, -- This might be an UNIX timestamp. Probably. | 220 |
109 timestamp = timestamp(when), -- Textual timestamp. But I'll assume that comparing numbers is faster and less annoying in case of timezones. | 221 if shall_store(store_user, target_bare) then |
110 with = c2s and orig_to or orig_from, | 222 module:log("debug", "Archiving stanza: %s", stanza:top_tag()); |
111 with_bare = jid_bare(c2s and orig_to or orig_from), -- Premature optimization, to avoid loads of jid_bare() calls when filtering. | 223 |
112 stanza = st.preserialize(stanza) | 224 -- Stamp "We archived this" on the message |
113 }); | 225 stanza:tag("archived", { xmlns = xmlns_mam, by = host, id = uuid() }); |
226 | |
227 local when = time_now(); | |
228 -- And stash it | |
229 dm_list_append(store_user, store_host, "archive2", { | |
230 -- WARNING This format may change. | |
231 when = when, -- This might be an UNIX timestamp. Probably. | |
232 timestamp = timestamp(when), -- Textual timestamp. But I'll assume that comparing numbers is faster and less annoying in case of timezones. | |
233 with = target_jid, | |
234 with_bare = target_bare, -- Optimization, to avoid loads of jid_bare() calls when filtering. | |
235 stanza = st.preserialize(stanza) | |
236 }); | |
237 else | |
238 module:log("debug", "Not archiving stanza: %s", stanza:top_tag()); | |
239 end | |
114 end | 240 end |
115 | 241 |
116 local function c2s_message_handler(event) | 242 local function c2s_message_handler(event) |
117 return message_handler(event, true); | 243 return message_handler(event, true); |
118 end | 244 end |
123 -- Stanszas to local clients | 249 -- Stanszas to local clients |
124 module:hook("message/bare", message_handler, 1); | 250 module:hook("message/bare", message_handler, 1); |
125 module:hook("message/full", message_handler, 1); | 251 module:hook("message/full", message_handler, 1); |
126 | 252 |
127 module:add_feature(xmlns_mam); | 253 module:add_feature(xmlns_mam); |
254 |