Software /
code /
prosody-modules
Comparison
mod_mam/mod_mam.lua @ 523:eff140d53b83
mod_mam: Add experimental implementation of the Message Archive Management ProtoXEP
author | Kim Alvefur <zash@zash.se> |
---|---|
date | Fri, 06 Jan 2012 16:50:12 +0100 |
child | 558:66de25ffc8d9 |
comparison
equal
deleted
inserted
replaced
522:0c756f62758d | 523:eff140d53b83 |
---|---|
1 -- XEP-xxxx: Message Archive Management for Prosody | |
2 -- Copyright (C) 2011-2012 Kim Alvefur | |
3 -- | |
4 -- This file is MIT/X11 licensed. | |
5 -- | |
6 -- Based on MAM ProtoXEP Version 0.1 (2010-07-01) | |
7 | |
8 local st = require "util.stanza"; | |
9 local jid_bare = require "util.jid".bare; | |
10 local jid_split = require "util.jid".split; | |
11 local xmlns_mam = "urn:xmpp:mam:tmp"; | |
12 local xmlns_delay = "urn:xmpp:delay"; | |
13 local xmlns_forward = "urn:xmpp:forward:0"; | |
14 local host_sessions = hosts[module.host].sessions; | |
15 local dm_list_load = require "util.datamanager".list_load | |
16 local dm_list_append = require "util.datamanager".list_append | |
17 local time_now = os.time; | |
18 local timestamp, timestamp_parse = require "util.datetime".datetime, require "util.datetime".parse; | |
19 local uuid = require "util.uuid".generate; | |
20 | |
21 -- TODO This, and appropritate filtering in message_handler() | |
22 module:hook("iq/self/"..xmlns_mam..":prefs", function(event) | |
23 local origin, stanza = event.origin, event.stanza; | |
24 if stanza.attr.type == "get" then | |
25 -- Not implemented yet, hardcoded to store everything. | |
26 origin.send(st.reply(stanza) | |
27 :tag("prefs", { xmlns = xmlns_mam, default = "always" })); | |
28 return true | |
29 else -- type == "set" | |
30 -- TODO | |
31 end | |
32 end); | |
33 | |
34 module:hook("iq/self/"..xmlns_mam..":query", function(event) | |
35 local origin, stanza = event.origin, event.stanza; | |
36 local query = stanza.tags[1]; | |
37 if stanza.attr.type == "get" then | |
38 local qid = query.attr.queryid; | |
39 | |
40 -- Search query parameters | |
41 local qwith = query:get_child_text("with"); | |
42 local qstart = query:get_child_text("start"); | |
43 local qend = query:get_child_text("end"); | |
44 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"); | |
46 | |
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)) | |
49 | |
50 -- 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 | |
52 module:log("debug", "Loaded %d items, about to filter", #(data or {})); | |
53 for i=1,#data do | |
54 local item = data[i]; | |
55 local when, with = item.when, item.with_bare; | |
56 local ts = item.timestamp; | |
57 -- FIXME Premature optimization: Bare JIDs only | |
58 --module:log("debug", "message with %s when %s", with, when or "???"); | |
59 -- Apply query filter | |
60 if (not qwith or qwith == with) | |
61 and (not qstart or when >= qstart) | |
62 and (not qend or when <= qend) then | |
63 -- Optimizable? Do this when archiving? | |
64 --module:log("debug", "sending"); | |
65 local fwd_st = st.message{ to = origin.full_jid } | |
66 :tag("result", { xmlns = xmlns_mam, queryid = qid }):up() | |
67 :tag("forwarded", { xmlns = xmlns_forward }) | |
68 :tag("delay", { xmlns = xmlns_delay, stamp = ts or timestamp(when) }):up(); | |
69 local orig_stanza = st.deserialize(item.stanza); | |
70 orig_stanza.attr.xmlns = "jabber:client"; | |
71 fwd_st:add_child(orig_stanza); | |
72 origin.send(fwd_st); | |
73 end | |
74 end | |
75 -- That's all folks! | |
76 module:log("debug", "Archive query %s completed", tostring(qid)); | |
77 origin.send(st.reply(stanza)); | |
78 return true | |
79 end | |
80 end); | |
81 | |
82 local function message_handler(event, c2s) | |
83 local origin, stanza = event.origin, event.stanza; | |
84 local orig_type = stanza.attr.type or "normal"; | |
85 local orig_to = stanza.attr.to; | |
86 local orig_from = stanza.attr.from; | |
87 | |
88 if not orig_from and c2s then | |
89 orig_from = origin.full_jid; | |
90 end | |
91 | |
92 -- Don't store messages of these types | |
93 if orig_type == "error" | |
94 or orig_type == "headline" | |
95 or orig_type == "groupchat" then | |
96 return; | |
97 -- TODO Maybe headlines should be configurable? | |
98 -- TODO Write a mod_mam_muc for groupchat messages. | |
99 end | |
100 | |
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); | |
104 | |
105 local when = time_now(); | |
106 -- And stash it | |
107 dm_list_append(store_user, store_host, "archive2", { | |
108 when = when, -- This might be an UNIX timestamp. Probably. | |
109 timestamp = timestamp(when), -- Textual timestamp. But I'll assume that comparing numbers is faster and less annoying in case of timezones. | |
110 with = c2s and orig_to or orig_from, | |
111 with_bare = jid_bare(c2s and orig_to or orig_from), -- Premature optimization, to avoid loads of jid_bare() calls when filtering. | |
112 stanza = st.preserialize(stanza) | |
113 }); | |
114 end | |
115 | |
116 local function c2s_message_handler(event) | |
117 return message_handler(event, true); | |
118 end | |
119 | |
120 -- Stanzas sent by local clients | |
121 module:hook("pre-message/bare", c2s_message_handler, 1); | |
122 module:hook("pre-message/full", c2s_message_handler, 1); | |
123 -- Stanszas to local clients | |
124 module:hook("message/bare", message_handler, 1); | |
125 module:hook("message/full", message_handler, 1); | |
126 | |
127 module:add_feature(xmlns_mam); |