Mercurial > prosody-modules
comparison mod_mam_muc/mod_mam_muc.lua @ 820:005037032d65
mod_mam_muc: MUC version of mod_mam
author | Kim Alvefur <zash@zash.se> |
---|---|
date | Mon, 17 Sep 2012 20:14:51 +0200 |
parents | |
children | 5c97ee75cadb |
comparison
equal
deleted
inserted
replaced
819:1e0d273bcb75 | 820:005037032d65 |
---|---|
1 -- XEP-0313: Message Archive Management for Prosody | |
2 -- Copyright (C) 2011-2012 Kim Alvefur | |
3 -- | |
4 -- This file is MIT/X11 licensed. | |
5 | |
6 local xmlns_mam = "urn:xmpp:mam:tmp"; | |
7 local xmlns_delay = "urn:xmpp:delay"; | |
8 local xmlns_forward = "urn:xmpp:forward:0"; | |
9 | |
10 local st = require "util.stanza"; | |
11 local rsm = module:require "mod_mam/rsm"; | |
12 local jid_bare = require "util.jid".bare; | |
13 local jid_split = require "util.jid".split; | |
14 local jid_prep = require "util.jid".prep; | |
15 local host = module.host; | |
16 | |
17 local dm_list_load = require "util.datamanager".list_load; | |
18 local dm_list_append = require "util.datamanager".list_append; | |
19 | |
20 local tostring = tostring; | |
21 local time_now = os.time; | |
22 local m_min = math.min; | |
23 local timestamp, timestamp_parse = require "util.datetime".datetime, require "util.datetime".parse; | |
24 local uuid = require "util.uuid".generate; | |
25 local default_max_items, max_max_items = 20, module:get_option_number("max_archive_query_results", 50); | |
26 --local rooms_to_archive = module:get_option_set("rooms_to_archive",{}); | |
27 -- TODO Should be possible to enforce it too | |
28 | |
29 local archive_store = "archive2"; | |
30 | |
31 -- Handle archive queries | |
32 module:hook("iq/bare/"..xmlns_mam..":query", function(event) | |
33 local origin, stanza = event.origin, event.stanza; | |
34 local room = jid_split(stanza.attr.to); | |
35 local query = stanza.tags[1]; | |
36 | |
37 local room_obj = hosts[module.host].modules.muc.rooms[jid_bare(stanza.attr.to)]; | |
38 if not room_obj then | |
39 return -- FIXME not found | |
40 end | |
41 local from = jid_bare(stanza.attr.from); | |
42 | |
43 if room_obj._affiliations[from] == "outcast" | |
44 or room_obj._data.members_only and not room_obj._affiliations[from] then | |
45 return -- FIXME unauth | |
46 end | |
47 | |
48 if stanza.attr.type == "get" then | |
49 local qid = query.attr.queryid; | |
50 | |
51 -- Search query parameters | |
52 local qwith = query:get_child_text("with"); | |
53 local qstart = query:get_child_text("start"); | |
54 local qend = query:get_child_text("end"); | |
55 local qset = rsm.get(query); | |
56 module:log("debug", "Archive query, id %s with %s from %s until %s)", | |
57 tostring(qid), qwith or "anyone", qstart or "the dawn of time", qend or "now"); | |
58 | |
59 if qstart or qend then -- Validate timestamps | |
60 local vstart, vend = (qstart and timestamp_parse(qstart)), (qend and timestamp_parse(qend)) | |
61 if (qstart and not vstart) or (qend and not vend) then | |
62 origin.send(st.error_reply(stanza, "modify", "bad-request", "Invalid timestamp")) | |
63 return true | |
64 end | |
65 qstart, qend = vstart, vend; | |
66 end | |
67 | |
68 local qres; | |
69 if qwith then -- Validate the 'with' jid | |
70 local pwith = qwith and jid_prep(qwith); | |
71 if pwith and not qwith then -- it failed prepping | |
72 origin.send(st.error_reply(stanza, "modify", "bad-request", "Invalid JID")) | |
73 return true | |
74 end | |
75 local _, _, resource = jid_split(qwith); | |
76 qwith = jid_bare(pwith); | |
77 qres = resource; | |
78 end | |
79 | |
80 -- Load all the data! | |
81 local data, err = dm_list_load(room, module.host, archive_store); | |
82 if not data then | |
83 if (not err) then | |
84 module:log("debug", "The archive was empty."); | |
85 origin.send(st.reply(stanza)); | |
86 else | |
87 origin.send(st.error_reply(stanza, "cancel", "internal-server-error", "Error loading archive: "..tostring(err))); | |
88 end | |
89 return true | |
90 end | |
91 | |
92 -- RSM stuff | |
93 local qmax = m_min(qset and qset.max or default_max_items, max_max_items); | |
94 local qset_matches = not (qset and qset.after); | |
95 local first, last, index; | |
96 local n = 0; | |
97 local start = qset and qset.index or 1; | |
98 | |
99 module:log("debug", "Loaded %d items, about to filter", #data); | |
100 for i=start,#data do | |
101 local item = data[i]; | |
102 local when, nick = item.when, item.resource; | |
103 local id = item.id; | |
104 --module:log("debug", "id is %s", id); | |
105 | |
106 -- RSM pre-send-checking | |
107 if qset then | |
108 if qset.before == id then | |
109 module:log("debug", "End of matching range found"); | |
110 qset_matches = false; | |
111 break; | |
112 end | |
113 end | |
114 | |
115 --module:log("debug", "message with %s at %s", with, when or "???"); | |
116 -- Apply query filter | |
117 if (not qres or (qres == nick)) | |
118 and (not qstart or when >= qstart) | |
119 and (not qend or when <= qend) | |
120 and (not qset or qset_matches) then | |
121 local fwd_st = st.message{ to = stanza.attr.from } | |
122 :tag("result", { xmlns = xmlns_mam, queryid = qid, id = id }):up() | |
123 :tag("forwarded", { xmlns = xmlns_forward }) | |
124 :tag("delay", { xmlns = xmlns_delay, stamp = timestamp(when) }):up(); | |
125 local orig_stanza = st.deserialize(item.stanza); | |
126 orig_stanza.attr.xmlns = "jabber:client"; | |
127 fwd_st:add_child(orig_stanza); | |
128 origin.send(fwd_st); | |
129 if not first then | |
130 index = i; | |
131 first = id; | |
132 end | |
133 last = id; | |
134 n = n + 1; | |
135 elseif (qend and when > qend) then | |
136 module:log("debug", "We have passed into messages more recent than requested"); | |
137 break -- We have passed into messages more recent than requested | |
138 end | |
139 | |
140 -- RSM post-send-checking | |
141 if qset then | |
142 if qset.after == id then | |
143 module:log("debug", "Start of matching range found"); | |
144 qset_matches = true; | |
145 end | |
146 end | |
147 if n >= qmax then | |
148 module:log("debug", "Max number of items matched"); | |
149 break | |
150 end | |
151 end | |
152 -- That's all folks! | |
153 module:log("debug", "Archive query %s completed", tostring(qid)); | |
154 | |
155 local reply = st.reply(stanza); | |
156 if last then | |
157 -- This is a bit redundant, isn't it? | |
158 reply:query(xmlns_mam):add_child(rsm.generate{first = first, last = last, count = n}); | |
159 end | |
160 origin.send(reply); | |
161 return true | |
162 end | |
163 end); | |
164 | |
165 -- Handle messages | |
166 local function message_handler(event) | |
167 local origin, stanza = event.origin, event.stanza; | |
168 local orig_type = stanza.attr.type or "normal"; | |
169 local orig_to = stanza.attr.to; | |
170 local orig_from = stanza.attr.from; | |
171 | |
172 -- Still needed? | |
173 if not orig_from then | |
174 orig_from = origin.full_jid; | |
175 end | |
176 | |
177 -- Only store groupchat messages | |
178 if not (orig_type == "groupchat" and (stanza:get_child("body") or stanza:get_child("subject"))) then | |
179 return; | |
180 end | |
181 | |
182 local room = jid_split(orig_to); | |
183 local room_obj = hosts[host].modules.muc.rooms[orig_to] | |
184 if not room_obj then return end | |
185 | |
186 local id = uuid(); | |
187 local when = time_now(); | |
188 local stanza = st.clone(stanza); -- Private copy | |
189 --stanza.attr.to = nil; | |
190 local nick = room_obj._jid_nick[orig_from]; | |
191 if not nick then return end | |
192 stanza.attr.from = nick; | |
193 local _, _, nick = jid_split(nick); | |
194 -- And stash it | |
195 local ok, err = dm_list_append(room, host, archive_store, { | |
196 -- WARNING This format may change. | |
197 id = id, | |
198 when = when, | |
199 resource = nick, | |
200 stanza = st.preserialize(stanza) | |
201 }); | |
202 --[[ This was dropped from the spec | |
203 if ok then | |
204 stanza:tag("archived", { xmlns = xmlns_mam, by = host, id = id }):up(); | |
205 end | |
206 --]] | |
207 end | |
208 | |
209 module:hook("message/bare", message_handler, 2); | |
210 | |
211 module:add_feature(xmlns_mam); |