Mercurial > prosody-modules
comparison mod_muclogging/mod_muclogging.lua @ 50:a96d3f37d845
mod_muclogging: with http_server part for viewing
| author | Thilo Cestonaro <thilo@cestona.ro> |
|---|---|
| date | Sun, 18 Oct 2009 22:39:36 +0200 |
| parents | 99ff520519fe |
| children | 6a40e44a2b8a |
comparison
equal
deleted
inserted
replaced
| 49:59f490390528 | 50:a96d3f37d845 |
|---|---|
| 5 -- | 5 -- |
| 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"; | |
| 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", "muclogging 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", "muclogging folder doesn't exist. create it please!"); | |
| 33 return false; | |
| 34 elseif attributes.mode ~= "directory" then | |
| 35 module:log("warn", "muclogging 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 | |
| 10 | 42 |
| 11 function logIfNeeded(e) | 43 function logIfNeeded(e) |
| 12 local stanza, origin = e.stanza, e.origin; | 44 local stanza, origin = e.stanza, e.origin; |
| 45 if validateLogFolder() == false then | |
| 46 return; | |
| 47 end | |
| 48 | |
| 13 if (stanza.name == "presence") or | 49 if (stanza.name == "presence") or |
| 14 (stanza.name == "message" and tostring(stanza.attr.type) == "groupchat") | 50 (stanza.name == "message" and tostring(stanza.attr.type) == "groupchat") |
| 15 then | 51 then |
| 16 local node, host, resource = splitJid(stanza.attr.to); | 52 local node, host, resource = splitJid(stanza.attr.to); |
| 17 if node ~= nil and host ~= nil then | 53 if node ~= nil and host ~= nil then |
| 18 local bare = node .. "@" .. host; | 54 local bare = node .. "@" .. host; |
| 19 if prosody.hosts[host] ~= nil and prosody.hosts[host].muc ~= nil and prosody.hosts[host].muc.rooms[bare] ~= nil then | 55 if prosody.hosts[host] ~= nil and prosody.hosts[host].muc ~= nil and prosody.hosts[host].muc.rooms[bare] ~= nil then |
| 20 local room = prosody.hosts[host].muc.rooms[bare] | 56 local room = prosody.hosts[host].muc.rooms[bare] |
| 21 local logFolder = config_get(host, "core", "logFolder"); | 57 local logging = config_get(host, "core", "logging"); |
| 22 if logFolder ~= nil then | 58 if logging == true then |
| 23 local today = os.date("%y%m%d"); | 59 local today = os.date("%y%m%d"); |
| 24 local now = os.date("%X") | 60 local now = os.date("%X") |
| 25 local fn = logFolder .. "/" .. today .. "_" .. bare .. ".log"; | 61 local fn = config.folder .. "/" .. today .. "_" .. bare .. ".log"; |
| 26 local mucFrom = nil; | 62 local mucFrom = nil; |
| 27 | 63 |
| 28 if stanza.name == "presence" and stanza.attr.type == nil then | 64 if stanza.name == "presence" and stanza.attr.type == nil then |
| 29 mucFrom = stanza.attr.to; | 65 mucFrom = stanza.attr.to; |
| 30 else | 66 else |
| 52 end | 88 end |
| 53 end | 89 end |
| 54 return; | 90 return; |
| 55 end | 91 end |
| 56 | 92 |
| 93 function createDoc(body) | |
| 94 return [[<html> | |
| 95 <head> | |
| 96 <title>muclogging</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("/muclogging/") + 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=\"/muclogging/" .. 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=\"/muclogging/" .. 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\"><" .. nick .. "></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 * "muclogging" and configure the value for the logging "folder".<br /> | |
| 274 Like:<br /> | |
| 275 Host "*"<br /> | |
| 276 ....<br /> | |
| 277 muclogging = {<br /> | |
| 278 folder = "/opt/local/var/log/prosody/rooms";<br /> | |
| 279 }<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", "muclogging"); | |
| 307 module:log("debug", "muclogging config: \n%s", dump(config)); | |
| 308 | |
| 309 if config.http_port ~= nil then | |
| 310 httpserver.new_from_config({ config.http_port }, "muclogging", handle_request); | |
| 311 end | |
| 312 return validateLogFolder(); | |
| 313 end | |
| 314 | |
| 57 module:hook("pre-message/bare", logIfNeeded, 500); | 315 module:hook("pre-message/bare", logIfNeeded, 500); |
| 58 module:hook("pre-presence/full", logIfNeeded, 500); | 316 module:hook("pre-presence/full", logIfNeeded, 500); |
| 59 | |
| 60 module:log("debug", "loaded ..."); |
