Mercurial > 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 |
| parents | eff140d53b83 |
| children | 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 |
