comparison mod_muc_log/mod_muc_log.lua @ 52:11d1d4ff8037

mod_muclogging: renamed to mod_muc_log; s/muclogging/muc_log/
author Thilo Cestonaro <thilo@cestona.ro>
date Mon, 19 Oct 2009 00:02:32 +0200
parents mod_muclogging/mod_muclogging.lua@6a40e44a2b8a
children 5c4dd39a1e99
comparison
equal deleted inserted replaced
51:6a40e44a2b8a 52:11d1d4ff8037
1 -- Copyright (C) 2009 Thilo Cestonaro
2 --
3 -- This project is MIT/X11 licensed. Please see the
4 -- COPYING file in the source package for more information.
5 --
6 local prosody = prosody;
7 local splitJid = require "util.jid".split;
8 local bareJid = require "util.jid".bare;
9 local config_get = require "core.configmanager".get;
10 local httpserver = require "net.httpserver";
11 -- local dump = require "util.logger".dump;
12 local config = {};
13
14 --[[ LuaFileSystem
15 * URL: http://www.keplerproject.org/luafilesystem/index.html
16 * Install: luarocks install luafilesystem
17 * ]]
18 local lfs = require "lfs";
19
20 local lom = require "lxp.lom";
21
22 function validateLogFolder()
23 module:log("debug", "validateLogFolder; Folder: %s", tostring(config.folder));
24 if config.folder == nil then
25 module:log("warn", "muc_log folder isn't configured. configure it please!");
26 return false;
27 end
28
29 -- check existance
30 local attributes = lfs.attributes(config.folder);
31 if attributes == nil then
32 module:log("warn", "muc_log folder doesn't exist. create it please!");
33 return false;
34 elseif attributes.mode ~= "directory" then
35 module:log("warn", "muc_log folder isn't a folder, it's a %s. change this please!", attributes.mode);
36 return false;
37 end --TODO: check for write rights!
38
39 module:log("debug", "Folder is validated and correct.")
40 return true;
41 end
42
43 function logIfNeeded(e)
44 local stanza, origin = e.stanza, e.origin;
45 if validateLogFolder() == false then
46 return;
47 end
48
49 if (stanza.name == "presence") or
50 (stanza.name == "message" and tostring(stanza.attr.type) == "groupchat")
51 then
52 local node, host, resource = splitJid(stanza.attr.to);
53 if node ~= nil and host ~= nil then
54 local bare = node .. "@" .. host;
55 if prosody.hosts[host] ~= nil and prosody.hosts[host].muc ~= nil and prosody.hosts[host].muc.rooms[bare] ~= nil then
56 local room = prosody.hosts[host].muc.rooms[bare]
57 local logging = config_get(host, "core", "logging");
58 if logging == true then
59 local today = os.date("%y%m%d");
60 local now = os.date("%X")
61 local fn = config.folder .. "/" .. today .. "_" .. bare .. ".log";
62 local mucFrom = nil;
63
64 if stanza.name == "presence" and stanza.attr.type == nil then
65 mucFrom = stanza.attr.to;
66 else
67 for jid, nick in pairs(room._jid_nick) do
68 if jid == stanza.attr.from then
69 mucFrom = nick;
70 end
71 end
72 end
73
74 if mucFrom ~= nil then
75 module:log("debug", "try to open room log: %s", fn);
76 local f = assert(io.open(fn, "a"));
77 local realFrom = stanza.attr.from;
78 local realTo = stanza.attr.to;
79 stanza.attr.from = mucFrom;
80 stanza.attr.to = nil;
81 f:write("<stanza time=\"".. now .. "\">" .. tostring(stanza) .. "</stanza>\n");
82 stanza.attr.from = realFrom;
83 stanza.attr.to = realTo;
84 f:close()
85 end
86 end
87 end
88 end
89 end
90 return;
91 end
92
93 function createDoc(body)
94 return [[<html>
95 <head>
96 <title>muc_log</title>
97 </head>
98 <style type="text/css">
99 <!--
100 .timestuff {color: #AAAAAA; text-decoration: none;}
101 .muc_join {color: #009900; font-style: italic;}
102 .muc_leave {color: #009900; font-style: italic;}
103 .muc_kick {color: #009900; font-style: italic;}
104 .muc_bann {color: #009900; font-style: italic;}
105 .muc_name {color: #0000AA;}
106 //-->
107 </style>
108 <body>
109 ]] .. tostring(body) .. [[
110 </body>
111 </html>]];
112 end
113
114 function splitQuery(query)
115 local ret = {};
116 if query == nil then return ret; end
117 local last = 1;
118 local idx = query:find("&", last);
119 while idx ~= nil do
120 ret[#ret + 1] = query:sub(last, idx - 1);
121 last = idx + 1;
122 idx = query:find("&", last);
123 end
124 ret[#ret + 1] = query:sub(last);
125 return ret;
126 end
127
128 function grepRoomJid(url)
129 local tmp = url:sub(string.len("/muc_log/") + 1);
130 local node = nil;
131 local host = nil;
132 local at = nil;
133 local slash = nil;
134
135 at = tmp:find("@");
136 slash = tmp:find("/");
137 if slash ~= nil then
138 slash = slash - 1;
139 end
140
141 if at ~= nil then
142 node = tmp:sub(1, at - 1);
143 host = tmp:sub(at + 1, slash);
144 end
145 return node, host;
146 end
147
148 local function generateRoomListSiteContent()
149 local ret = "<h2>Rooms hosted on this server:</h2><hr /><p>";
150 for host, config in pairs(prosody.hosts) do
151 if prosody.hosts[host].muc ~= nil then
152 local logging = config_get(host, "core", "logging");
153 if logging then
154 for jid, room in pairs(prosody.hosts[host].muc.rooms) do
155 ret = ret .. "<a href=\"/muc_log/" .. jid .. "/\">" .. jid .."</a><br />\n";
156 end
157 else
158 module:log("debug", "logging not enabled for muc component: %s", tostring(host));
159 end
160 end
161 end
162 return ret .. "</p><hr />";
163 end
164
165 local function generateDayListSiteContentByRoom(bareRoomJid)
166 local ret = "";
167
168 for file in lfs.dir(config.folder) do
169 local year, month, day = file:match("^(%d%d)(%d%d)(%d%d)_" .. bareRoomJid .. ".log");
170 module:log("debug", "year: %s, month: %s, day: %s", year, month, day);
171 if year ~= nil and month ~= nil and day ~= nil and
172 year ~= "" and month ~= "" and day ~= ""
173 then
174 ret = "<a href=\"/muc_log/" .. bareRoomJid .. "/?year=" .. year .. "&month=" .. month .. "&day=" .. day .. "\">20" .. year .. "/" .. month .. "/" .. day .. "</a><br />\n" .. ret;
175 end
176 end
177 if ret ~= "" then
178 return "<h2>available logged days of room: " .. bareRoomJid .. "</h2><hr /><p>" .. ret .. "</p><hr />";
179 else
180 return generateRoomListSiteContent(); -- fallback
181 end
182 end
183
184 local function parseDay(bareRoomJid, query)
185 local ret = "";
186 local year;
187 local month;
188 local day;
189
190 for _,str in ipairs(query) do
191 local name, value;
192 name, value = str:match("^(%a+)=(%d+)$");
193 if name == "year" then
194 year = value;
195 elseif name == "month" then
196 month = value;
197 elseif name == "day" then
198 day = value;
199 else
200 log("warn", "unknown query value");
201 end
202 end
203
204 if year ~= nil and month ~= nil and day ~= nil then
205 local file = config.folder .. "/" .. year .. month .. day .. "_" .. bareRoomJid .. ".log";
206 local f, err = io.open(file, "r");
207 if f ~= nil then
208 local content = f:read("*a");
209 local parsed = lom.parse("<xml>" .. content .. "</xml>");
210 if parsed ~= nil then
211 for _,stanza in ipairs(parsed) do
212 -- module:log("debug", "dump of stanza: \n%s", dump(stanza))
213 if stanza.attr ~= nil and stanza.attr.time ~= nil then
214 ret = ret .. "<a name=\"" .. stanza.attr.time .. "\" href=\"#" .. stanza.attr.time .. "\" class=\"timestuff\">[" .. stanza.attr.time .. "]</a> ";
215 if stanza[1] ~= nil then
216 local nick;
217 if stanza[1].attr.from ~= nil then
218 nick = stanza[1].attr.from:match("/(.+)$");
219 end
220 if stanza[1].tag == "presence" and nick ~= nil then
221 if stanza[1].attr.type == nil then
222 ret = ret .. "<font class=\"muc_join\"> *** " .. nick .. " joins the room</font><br />\n";
223 elseif stanza[1].attr.type ~= nil and stanza[1].attr.type == "unavailable" then
224 ret = ret .. "<font class=\"muc_leave\"> *** " .. nick .. " leaves the room</font><br />\n";
225 else
226 ret = ret .. "<font class=\"muc_leave\"> *** " .. nick .. " changed his/her status to: " .. stanza[1].attr.type .. "</font><br />\n";
227 end
228 elseif stanza[1].tag == "message" then
229 local body;
230 for _,tag in ipairs(stanza[1]) do
231 if tag.tag == "body" then
232 body = tag[1]:gsub("\n", "<br />\n");
233 if nick ~= nil then
234 break;
235 end
236 elseif tag.tag == "nick" and nick == nil then
237 nick = tag[1];
238 if body ~= nil then
239 break;
240 end
241 end
242 end
243 if nick ~= nil and body ~= nil then
244 ret = ret .. "<font class=\"muc_name\">&lt;" .. nick .. "&gt;</font> " .. body .. "<br />\n";
245 end
246 else
247 module:log("info", "unknown stanza subtag in log found. room: %s; day: %s", bareRoomJid, year .. "/" .. month .. "/" .. day);
248 end
249 end
250 end
251 end
252 else
253 module:log("warn", "could not parse room log. room: %s; day: %s", bareRoomJid, year .. "/" .. month .. "/" .. day);
254 end
255 f:close();
256 else
257 ret = err;
258 end
259 return "<h2>room " .. bareRoomJid .. " logging of 20" .. year .. "/" .. month .. "/" .. day .. "</h2><hr /><p>" .. ret .. "</p><hr />";
260 else
261 return generateDayListSiteContentByRoom(bareRoomJid); -- fallback
262 end
263 end
264
265 function handle_request(method, body, request)
266 module:log("debug", "method: %s, body: %s, request: %s", tostring(method), tostring(body), tostring(request));
267 -- module:log("debug", "dump of request:\n%s\n", dump(request));
268 local query = splitQuery(request.url.query);
269 local node, host = grepRoomJid(request.url.path);
270
271 if validateLogFolder() == false then
272 return createDoc([[
273 Muclogging is not configured correctly. Add a section to Host * "muc_log" and configure the value for the logging "folder".<br />
274 Like:<br />
275 Host "*"<br />
276 ....<br />
277 &nbsp;&nbsp;&nbsp;&nbsp;muc_log = {<br />
278 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;folder = "/opt/local/var/log/prosody/rooms";<br />
279 &nbsp;&nbsp;&nbsp;&nbsp;}<br />
280 ]]);
281 end
282 if node ~= nil and host ~= nil then
283 local bare = node .. "@" .. host;
284 if prosody.hosts[host] ~= nil and prosody.hosts[host].muc ~= nil and prosody.hosts[host].muc.rooms[bare] ~= nil then
285 local room = prosody.hosts[host].muc.rooms[bare];
286 local logging = config_get(host, "core", "logging");
287 if logging == true then
288 if request.url.query == nil then
289 return createDoc(generateDayListSiteContentByRoom(bare));
290 else
291 return createDoc(parseDay(bare, query));
292 end
293 else
294 module:log("debug", "logging not enabled for this room: %s", bare);
295 end
296 else
297 module:log("debug", "room instance not found. bare room jid: %s", tostring(bare));
298 end
299 else
300 return createDoc(generateRoomListSiteContent());
301 end
302 return;
303 end
304
305 function module.load()
306 config = config_get("*", "core", "muc_log");
307 -- module:log("debug", "muc_log config: \n%s", dump(config));
308
309 if config.http_port ~= nil then
310 httpserver.new_from_config({ config.http_port }, "muc_log", handle_request);
311 end
312 return validateLogFolder();
313 end
314
315 module:hook("pre-message/bare", logIfNeeded, 500);
316 module:hook("pre-presence/full", logIfNeeded, 500);