comparison mod_muc_log/mod_muc_log.lua @ 56:e9de45beaf5e

mod_muc_log: templify the html stuff
author Thilo Cestonaro <thilo@cestona.ro>
date Mon, 19 Oct 2009 22:15:57 +0200
parents d9749ed44f6e
children cddcea7c091a
comparison
equal deleted inserted replaced
55:d9749ed44f6e 56:e9de45beaf5e
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">&lt;###NICK###&gt;</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 &nbsp;&nbsp;modules_enabled = {<br />
81 &nbsp;&nbsp;&nbsp;&nbsp;"muc_log";<br />
82 &nbsp;&nbsp;}<br />
83 &nbsp;&nbsp;muc_log = {<br />
84 &nbsp;&nbsp;&nbsp;&nbsp;folder = "/opt/local/var/log/prosody/rooms";<br />
85 &nbsp;&nbsp;&nbsp;&nbsp;http_port = "/opt/local/var/log/prosody/rooms";<br />
86 &nbsp;&nbsp;}<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];
227 break; 300 break;
228 end 301 end
229 end 302 end
230 end 303 end
231 if nick ~= nil and body ~= nil then 304 if nick ~= nil and body ~= nil then
232 ret = ret .. tmp .. "<font class=\"muc_name\">&lt;" .. nick .. "&gt;</font> " .. body .. "<br />\n"; 305 tmp = html.day.message:gsub("###TIME_STUFF###", timeStuff);
306 tmp = tmp:gsub("###NICK###", nick);
307 ret = ret .. tmp:gsub("###MSG###", body);
233 end 308 end
234 else 309 else
235 module:log("info", "unknown stanza subtag in log found. room: %s; day: %s", bareRoomJid, year .. "/" .. month .. "/" .. day); 310 module:log("info", "unknown stanza subtag in log found. room: %s; day: %s", bareRoomJid, year .. "/" .. month .. "/" .. day);
236 end 311 end
237 end 312 end
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 &nbsp;&nbsp;&nbsp;&nbsp;muc_log = {<br />
264 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;folder = "/opt/local/var/log/prosody/rooms";<br />
265 &nbsp;&nbsp;&nbsp;&nbsp;}<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);