6 local prosody = prosody; |
6 local prosody = prosody; |
7 local splitJid = require "util.jid".split; |
7 local splitJid = require "util.jid".split; |
8 local bareJid = require "util.jid".bare; |
8 local bareJid = require "util.jid".bare; |
9 local config_get = require "core.configmanager".get; |
9 local config_get = require "core.configmanager".get; |
10 local httpserver = require "net.httpserver"; |
10 local httpserver = require "net.httpserver"; |
11 -- local dump = require "util.logger".dump; |
11 local serialize = require "util.serialization".serialize; |
12 local config = {}; |
12 local config = {}; |
|
13 |
13 |
14 |
14 --[[ LuaFileSystem |
15 --[[ LuaFileSystem |
15 * URL: http://www.keplerproject.org/luafilesystem/index.html |
16 * URL: http://www.keplerproject.org/luafilesystem/index.html |
16 * Install: luarocks install luafilesystem |
17 * Install: luarocks install luafilesystem |
17 * ]] |
18 * ]] |
18 local lfs = require "lfs"; |
19 local lfs = require "lfs"; |
19 |
20 |
20 local lom = require "lxp.lom"; |
21 local lom = require "lxp.lom"; |
|
22 |
|
23 |
|
24 --[[ |
|
25 * Default templates for the html output. |
|
26 ]]-- |
|
27 local html = {}; |
|
28 html.doc = [[<html> |
|
29 <head> |
|
30 <title>muc_log</title> |
|
31 </head> |
|
32 <style type="text/css"> |
|
33 <!-- |
|
34 .timestuff {color: #AAAAAA; text-decoration: none;} |
|
35 .muc_join {color: #009900; font-style: italic;} |
|
36 .muc_leave {color: #009900; font-style: italic;} |
|
37 .muc_statusChange {color: #009900; font-style: italic;} |
|
38 .muc_title {color: #009900;} |
|
39 .muc_titlenick {color: #009900; font-style: italic;} |
|
40 .muc_kick {color: #009900; font-style: italic;} |
|
41 .muc_bann {color: #009900; font-style: italic;} |
|
42 .muc_name {color: #0000AA;} |
|
43 //--> |
|
44 </style> |
|
45 <body> |
|
46 ###BODY_STUFF### |
|
47 </body> |
|
48 </html>]]; |
|
49 |
|
50 html.hosts = {}; |
|
51 html.hosts.bit = [[<a href="/muc_log/###JID###">###JID###</a><br />]] |
|
52 html.hosts.body = [[<h2>Rooms hosted on this server:</h2><hr /><p> |
|
53 ###HOSTS_STUFF### |
|
54 </p><hr />]]; |
|
55 |
|
56 html.days = {}; |
|
57 html.days.bit = [[<a href="/muc_log/###JID###/?year=###YEAR###&month=###MONTH###&day=###DAY###">20###YEAR###/###MONTH###/###DAY###</a><br />]]; |
|
58 html.days.body = [[<h2>available logged days of room: ###JID###</h2><hr /><p> |
|
59 ###DAYS_STUFF### |
|
60 </p><hr />]]; |
|
61 |
|
62 html.day = {}; |
|
63 html.day.time = [[<a name="###TIME###" href="####TIME###" class="timestuff">[###TIME###]</a> ]]; -- the one ####TIME### need to stay! it will evaluate to e.g. #09:10:56 which is an anker then |
|
64 html.day.presence = {}; |
|
65 html.day.presence.join = [[###TIME_STUFF###<font class="muc_join"> *** ###NICK### joins the room</font><br />]]; |
|
66 html.day.presence.leave = [[###TIME_STUFF###<font class="muc_leave"> *** ###NICK### leaves the room</font><br />]]; |
|
67 html.day.presence.statusChange = [[###TIME_STUFF###<font class="muc_statusChange"> *** ###NICK### changed his/her status to: ###STATUS###</font><br />]]; |
|
68 html.day.message = [[###TIME_STUFF###<font class="muc_name"><###NICK###></font> ###MSG###<br />]]; |
|
69 html.day.titleChange = [[###TIME_STUFF###<font class="muc_titlenick"> *** ###NICK### change title to:</font> <font class="muc_title">###MSG###</font><br />]]; |
|
70 html.day.kick = [[###TIME_STUFF###<font class="muc_titlenick"> *** ###NICK### kicked ###VICTIM###</font><br />]]; |
|
71 html.day.bann = [[###TIME_STUFF###<font class="muc_titlenick"> *** ###NICK### banned ###VICTIM###</font><br />]]; |
|
72 html.day.body = [[<h2>room ###JID### logging of 20###YEAR###/###MONTH###/###DAY###</h2><hr /><p> |
|
73 ###DAY_STUFF### |
|
74 </p><hr />]]; |
|
75 |
|
76 html.help = [[ |
|
77 MUC logging is not configured correctly.<br /> |
|
78 Here is a example config:<br /> |
|
79 Component "rooms.example.com" "muc"<br /> |
|
80 modules_enabled = {<br /> |
|
81 "muc_log";<br /> |
|
82 }<br /> |
|
83 muc_log = {<br /> |
|
84 folder = "/opt/local/var/log/prosody/rooms";<br /> |
|
85 http_port = "/opt/local/var/log/prosody/rooms";<br /> |
|
86 }<br /> |
|
87 ]]; |
21 |
88 |
22 function validateLogFolder() |
89 function validateLogFolder() |
23 if config.folder == nil then |
90 if config.folder == nil then |
24 module:log("warn", "muc_log folder isn't configured. configure it please!"); |
91 module:log("warn", "muc_log folder isn't configured. configure it please!"); |
25 return false; |
92 return false; |
84 end |
151 end |
85 return; |
152 return; |
86 end |
153 end |
87 |
154 |
88 function createDoc(body) |
155 function createDoc(body) |
89 return [[<html> |
156 return html.doc:gsub("###BODY_STUFF###", body); |
90 <head> |
157 end |
91 <title>muc_log</title> |
158 |
92 </head> |
159 local function htmlEscape(t) |
93 <style type="text/css"> |
160 t = t:gsub("\n", "<br />"); |
94 <!-- |
161 -- TODO link text into klickable link and such stuff |
95 .timestuff {color: #AAAAAA; text-decoration: none;} |
162 return t; |
96 .muc_join {color: #009900; font-style: italic;} |
|
97 .muc_leave {color: #009900; font-style: italic;} |
|
98 .muc_kick {color: #009900; font-style: italic;} |
|
99 .muc_bann {color: #009900; font-style: italic;} |
|
100 .muc_name {color: #0000AA;} |
|
101 //--> |
|
102 </style> |
|
103 <body> |
|
104 ]] .. tostring(body) .. [[ |
|
105 </body> |
|
106 </html>]]; |
|
107 end |
163 end |
108 |
164 |
109 function splitQuery(query) |
165 function splitQuery(query) |
110 local ret = {}; |
166 local ret = {}; |
111 if query == nil then return ret; end |
167 if query == nil then return ret; end |
139 end |
195 end |
140 return node, host; |
196 return node, host; |
141 end |
197 end |
142 |
198 |
143 local function generateRoomListSiteContent() |
199 local function generateRoomListSiteContent() |
144 local ret = "<h2>Rooms hosted on this server:</h2><hr /><p>"; |
200 local rooms = ""; |
145 for host, config in pairs(prosody.hosts) do |
201 for host, config in pairs(prosody.hosts) do |
146 if prosody.hosts[host].muc ~= nil then |
202 if prosody.hosts[host].muc ~= nil then |
147 for jid, room in pairs(prosody.hosts[host].muc.rooms) do |
203 for jid, room in pairs(prosody.hosts[host].muc.rooms) do |
148 ret = ret .. "<a href=\"/muc_log/" .. jid .. "/\">" .. jid .."</a><br />\n"; |
204 rooms = rooms .. html.hosts.bit:gsub("###JID###", jid); |
149 end |
205 end |
150 end |
206 end |
151 end |
207 end |
152 return ret .. "</p><hr />"; |
208 |
|
209 return html.hosts.body:gsub("###HOSTS_STUFF###", rooms); |
153 end |
210 end |
154 |
211 |
155 local function generateDayListSiteContentByRoom(bareRoomJid) |
212 local function generateDayListSiteContentByRoom(bareRoomJid) |
156 local ret = ""; |
213 local days = ""; |
|
214 local tmp; |
157 |
215 |
158 for file in lfs.dir(config.folder) do |
216 for file in lfs.dir(config.folder) do |
159 local year, month, day = file:match("^(%d%d)(%d%d)(%d%d)_" .. bareRoomJid .. ".log"); |
217 local year, month, day = file:match("^(%d%d)(%d%d)(%d%d)_" .. bareRoomJid .. ".log"); |
160 if year ~= nil and month ~= nil and day ~= nil and |
218 if year ~= nil and month ~= nil and day ~= nil and |
161 year ~= "" and month ~= "" and day ~= "" |
219 year ~= "" and month ~= "" and day ~= "" |
162 then |
220 then |
163 ret = "<a href=\"/muc_log/" .. bareRoomJid .. "/?year=" .. year .. "&month=" .. month .. "&day=" .. day .. "\">20" .. year .. "/" .. month .. "/" .. day .. "</a><br />\n" .. ret; |
221 tmp = html.days.bit; |
164 end |
222 tmp = tmp:gsub("###JID###", bareRoomJid); |
165 end |
223 tmp = tmp:gsub("###YEAR###", year); |
166 if ret ~= "" then |
224 tmp = tmp:gsub("###MONTH###", month); |
167 return "<h2>available logged days of room: " .. bareRoomJid .. "</h2><hr /><p>" .. ret .. "</p><hr />"; |
225 tmp = tmp:gsub("###DAY###", day); |
|
226 days = tmp .. days; |
|
227 end |
|
228 end |
|
229 if days ~= "" then |
|
230 tmp = html.days.body:gsub("###DAYS_STUFF###", days); |
|
231 return tmp:gsub("###JID###", bareRoomJid); |
168 else |
232 else |
169 return generateRoomListSiteContent(); -- fallback |
233 return generateRoomListSiteContent(); -- fallback |
170 end |
234 end |
171 end |
235 end |
172 |
236 |
173 local function parseDay(bareRoomJid, query) |
237 local function parseDay(bareRoomJid, query) |
174 local ret = ""; |
238 local ret = ""; |
175 local year; |
239 local year; |
176 local month; |
240 local month; |
177 local day; |
241 local day; |
|
242 local tmp; |
178 |
243 |
179 for _,str in ipairs(query) do |
244 for _,str in ipairs(query) do |
180 local name, value; |
245 local name, value; |
181 name, value = str:match("^(%a+)=(%d+)$"); |
246 name, value = str:match("^(%a+)=(%d+)$"); |
182 if name == "year" then |
247 if name == "year" then |
197 local content = f:read("*a"); |
262 local content = f:read("*a"); |
198 local parsed = lom.parse("<xml>" .. content .. "</xml>"); |
263 local parsed = lom.parse("<xml>" .. content .. "</xml>"); |
199 if parsed ~= nil then |
264 if parsed ~= nil then |
200 for _,stanza in ipairs(parsed) do |
265 for _,stanza in ipairs(parsed) do |
201 if stanza.attr ~= nil and stanza.attr.time ~= nil then |
266 if stanza.attr ~= nil and stanza.attr.time ~= nil then |
202 local tmp = "<a name=\"" .. stanza.attr.time .. "\" href=\"#" .. stanza.attr.time .. "\" class=\"timestuff\">[" .. stanza.attr.time .. "]</a> "; |
267 local timeStuff = html.day.time:gsub("###TIME###", stanza.attr.time); |
203 if stanza[1] ~= nil then |
268 if stanza[1] ~= nil then |
204 local nick; |
269 local nick; |
|
270 |
|
271 -- grep nick from "from" resource |
205 if stanza[1].attr.from ~= nil then |
272 if stanza[1].attr.from ~= nil then |
206 nick = stanza[1].attr.from:match("/(.+)$"); |
273 nick = stanza[1].attr.from:match("/(.+)$"); |
207 end |
274 end |
|
275 |
208 if stanza[1].tag == "presence" and nick ~= nil then |
276 if stanza[1].tag == "presence" and nick ~= nil then |
|
277 |
209 if stanza[1].attr.type == nil then |
278 if stanza[1].attr.type == nil then |
210 ret = ret .. tmp .. "<font class=\"muc_join\"> *** " .. nick .. " joins the room</font><br />\n"; |
279 tmp = html.day.presence.join:gsub("###TIME_STUFF###", timeStuff); |
|
280 ret = ret .. tmp:gsub("###NICK###", nick); |
211 elseif stanza[1].attr.type ~= nil and stanza[1].attr.type == "unavailable" then |
281 elseif stanza[1].attr.type ~= nil and stanza[1].attr.type == "unavailable" then |
212 ret = ret .. tmp .. "<font class=\"muc_leave\"> *** " .. nick .. " leaves the room</font><br />\n"; |
282 tmp = html.day.presence.leave:gsub("###TIME_STUFF###", timeStuff); |
|
283 ret = ret .. tmp:gsub("###NICK###", nick); |
213 else |
284 else |
214 ret = ret .. tmp .. "<font class=\"muc_leave\"> *** " .. nick .. " changed his/her status to: " .. stanza[1].attr.type .. "</font><br />\n"; |
285 tmp = html.day.presence.leave:gsub("###TIME_STUFF###", timeStuff); |
|
286 tmp = tmp:gsub("###STATUS###", stanza[1].attr.type); |
|
287 ret = ret .. tmp:gsub("###NICK###", nick); |
215 end |
288 end |
216 elseif stanza[1].tag == "message" then |
289 elseif stanza[1].tag == "message" then |
217 local body; |
290 local body; |
218 for _,tag in ipairs(stanza[1]) do |
291 for _,tag in ipairs(stanza[1]) do |
219 if tag.tag == "body" then |
292 if tag.tag == "body" then |
220 body = tag[1]:gsub("\n", "<br />\n"); |
293 body = htmlEscape(tag[1]); |
221 if nick ~= nil then |
294 if nick ~= nil then |
222 break; |
295 break; |
223 end |
296 end |
224 elseif tag.tag == "nick" and nick == nil then |
297 elseif tag.tag == "nick" and nick == nil then |
225 nick = tag[1]; |
298 nick = tag[1]; |
242 end |
317 end |
243 f:close(); |
318 f:close(); |
244 else |
319 else |
245 ret = err; |
320 ret = err; |
246 end |
321 end |
247 return "<h2>room " .. bareRoomJid .. " logging of 20" .. year .. "/" .. month .. "/" .. day .. "</h2><hr /><p>" .. ret .. "</p><hr />"; |
322 tmp = html.day.body:gsub("###DAY_STUFF###", ret); |
|
323 tmp = tmp:gsub("###JID###", bareRoomJid); |
|
324 tmp = tmp:gsub("###YEAR###", year); |
|
325 tmp = tmp:gsub("###MONTH###", month); |
|
326 tmp = tmp:gsub("###DAY###", day); |
|
327 return tmp; |
248 else |
328 else |
249 return generateDayListSiteContentByRoom(bareRoomJid); -- fallback |
329 return generateDayListSiteContentByRoom(bareRoomJid); -- fallback |
250 end |
330 end |
251 end |
331 end |
252 |
332 |
253 function handle_request(method, body, request) |
333 function handle_request(method, body, request) |
|
334 module:log("debug", "got a request ...") |
254 local query = splitQuery(request.url.query); |
335 local query = splitQuery(request.url.query); |
255 local node, host = grepRoomJid(request.url.path); |
336 local node, host = grepRoomJid(request.url.path); |
256 |
337 |
257 if validateLogFolder() == false then |
338 if validateLogFolder() == false then |
258 return createDoc([[ |
339 return createDoc(html.help); |
259 MUC logging is not configured correctly. Add a section to Host * "muc_log" and configure the value for the logging "folder".<br /> |
|
260 Like:<br /> |
|
261 Host "*"<br /> |
|
262 ....<br /> |
|
263 muc_log = {<br /> |
|
264 folder = "/opt/local/var/log/prosody/rooms";<br /> |
|
265 }<br /> |
|
266 ]]); |
|
267 end |
340 end |
268 if node ~= nil and host ~= nil then |
341 if node ~= nil and host ~= nil then |
269 local bare = node .. "@" .. host; |
342 local bare = node .. "@" .. host; |
270 if prosody.hosts[host] ~= nil and prosody.hosts[host].muc ~= nil and prosody.hosts[host].muc.rooms[bare] ~= nil then |
343 if prosody.hosts[host] ~= nil and prosody.hosts[host].muc ~= nil and prosody.hosts[host].muc.rooms[bare] ~= nil then |
271 local room = prosody.hosts[host].muc.rooms[bare]; |
344 local room = prosody.hosts[host].muc.rooms[bare]; |
282 end |
355 end |
283 return; |
356 return; |
284 end |
357 end |
285 |
358 |
286 config = config_get(module:get_host(), "core", "muc_log"); |
359 config = config_get(module:get_host(), "core", "muc_log"); |
|
360 module:log("debug", serialize(config)); |
287 |
361 |
288 httpserver.new_from_config({ config.http_port or true }, handle_request, { base = "muc_log" }); |
362 httpserver.new_from_config({ config.http_port or true }, handle_request, { base = "muc_log" }); |
289 |
363 |
290 module:hook("message/bare", logIfNeeded, 500); |
364 module:hook("message/bare", logIfNeeded, 500); |
291 module:hook("pre-message/bare", logIfNeeded, 500); |
365 module:hook("pre-message/bare", logIfNeeded, 500); |