Mercurial > prosody-modules
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_muc_log/mod_muc_log.lua Mon Oct 19 00:02:32 2009 +0200 @@ -0,0 +1,316 @@ +-- Copyright (C) 2009 Thilo Cestonaro +-- +-- This project is MIT/X11 licensed. Please see the +-- COPYING file in the source package for more information. +-- +local prosody = prosody; +local splitJid = require "util.jid".split; +local bareJid = require "util.jid".bare; +local config_get = require "core.configmanager".get; +local httpserver = require "net.httpserver"; +-- local dump = require "util.logger".dump; +local config = {}; + +--[[ LuaFileSystem +* URL: http://www.keplerproject.org/luafilesystem/index.html +* Install: luarocks install luafilesystem +* ]] +local lfs = require "lfs"; + +local lom = require "lxp.lom"; + +function validateLogFolder() + module:log("debug", "validateLogFolder; Folder: %s", tostring(config.folder)); + if config.folder == nil then + module:log("warn", "muc_log folder isn't configured. configure it please!"); + return false; + end + + -- check existance + local attributes = lfs.attributes(config.folder); + if attributes == nil then + module:log("warn", "muc_log folder doesn't exist. create it please!"); + return false; + elseif attributes.mode ~= "directory" then + module:log("warn", "muc_log folder isn't a folder, it's a %s. change this please!", attributes.mode); + return false; + end --TODO: check for write rights! + + module:log("debug", "Folder is validated and correct.") + return true; +end + +function logIfNeeded(e) + local stanza, origin = e.stanza, e.origin; + if validateLogFolder() == false then + return; + end + + if (stanza.name == "presence") or + (stanza.name == "message" and tostring(stanza.attr.type) == "groupchat") + then + local node, host, resource = splitJid(stanza.attr.to); + if node ~= nil and host ~= nil then + local bare = node .. "@" .. host; + if prosody.hosts[host] ~= nil and prosody.hosts[host].muc ~= nil and prosody.hosts[host].muc.rooms[bare] ~= nil then + local room = prosody.hosts[host].muc.rooms[bare] + local logging = config_get(host, "core", "logging"); + if logging == true then + local today = os.date("%y%m%d"); + local now = os.date("%X") + local fn = config.folder .. "/" .. today .. "_" .. bare .. ".log"; + local mucFrom = nil; + + if stanza.name == "presence" and stanza.attr.type == nil then + mucFrom = stanza.attr.to; + else + for jid, nick in pairs(room._jid_nick) do + if jid == stanza.attr.from then + mucFrom = nick; + end + end + end + + if mucFrom ~= nil then + module:log("debug", "try to open room log: %s", fn); + local f = assert(io.open(fn, "a")); + local realFrom = stanza.attr.from; + local realTo = stanza.attr.to; + stanza.attr.from = mucFrom; + stanza.attr.to = nil; + f:write("<stanza time=\"".. now .. "\">" .. tostring(stanza) .. "</stanza>\n"); + stanza.attr.from = realFrom; + stanza.attr.to = realTo; + f:close() + end + end + end + end + end + return; +end + +function createDoc(body) + return [[<html> + <head> + <title>muc_log</title> + </head> + <style type="text/css"> + <!-- + .timestuff {color: #AAAAAA; text-decoration: none;} + .muc_join {color: #009900; font-style: italic;} + .muc_leave {color: #009900; font-style: italic;} + .muc_kick {color: #009900; font-style: italic;} + .muc_bann {color: #009900; font-style: italic;} + .muc_name {color: #0000AA;} + //--> + </style> + <body> + ]] .. tostring(body) .. [[ + </body> + </html>]]; +end + +function splitQuery(query) + local ret = {}; + if query == nil then return ret; end + local last = 1; + local idx = query:find("&", last); + while idx ~= nil do + ret[#ret + 1] = query:sub(last, idx - 1); + last = idx + 1; + idx = query:find("&", last); + end + ret[#ret + 1] = query:sub(last); + return ret; +end + +function grepRoomJid(url) + local tmp = url:sub(string.len("/muc_log/") + 1); + local node = nil; + local host = nil; + local at = nil; + local slash = nil; + + at = tmp:find("@"); + slash = tmp:find("/"); + if slash ~= nil then + slash = slash - 1; + end + + if at ~= nil then + node = tmp:sub(1, at - 1); + host = tmp:sub(at + 1, slash); + end + return node, host; +end + +local function generateRoomListSiteContent() + local ret = "<h2>Rooms hosted on this server:</h2><hr /><p>"; + for host, config in pairs(prosody.hosts) do + if prosody.hosts[host].muc ~= nil then + local logging = config_get(host, "core", "logging"); + if logging then + for jid, room in pairs(prosody.hosts[host].muc.rooms) do + ret = ret .. "<a href=\"/muc_log/" .. jid .. "/\">" .. jid .."</a><br />\n"; + end + else + module:log("debug", "logging not enabled for muc component: %s", tostring(host)); + end + end + end + return ret .. "</p><hr />"; +end + +local function generateDayListSiteContentByRoom(bareRoomJid) + local ret = ""; + + for file in lfs.dir(config.folder) do + local year, month, day = file:match("^(%d%d)(%d%d)(%d%d)_" .. bareRoomJid .. ".log"); + module:log("debug", "year: %s, month: %s, day: %s", year, month, day); + if year ~= nil and month ~= nil and day ~= nil and + year ~= "" and month ~= "" and day ~= "" + then + ret = "<a href=\"/muc_log/" .. bareRoomJid .. "/?year=" .. year .. "&month=" .. month .. "&day=" .. day .. "\">20" .. year .. "/" .. month .. "/" .. day .. "</a><br />\n" .. ret; + end + end + if ret ~= "" then + return "<h2>available logged days of room: " .. bareRoomJid .. "</h2><hr /><p>" .. ret .. "</p><hr />"; + else + return generateRoomListSiteContent(); -- fallback + end +end + +local function parseDay(bareRoomJid, query) + local ret = ""; + local year; + local month; + local day; + + for _,str in ipairs(query) do + local name, value; + name, value = str:match("^(%a+)=(%d+)$"); + if name == "year" then + year = value; + elseif name == "month" then + month = value; + elseif name == "day" then + day = value; + else + log("warn", "unknown query value"); + end + end + + if year ~= nil and month ~= nil and day ~= nil then + local file = config.folder .. "/" .. year .. month .. day .. "_" .. bareRoomJid .. ".log"; + local f, err = io.open(file, "r"); + if f ~= nil then + local content = f:read("*a"); + local parsed = lom.parse("<xml>" .. content .. "</xml>"); + if parsed ~= nil then + for _,stanza in ipairs(parsed) do + -- module:log("debug", "dump of stanza: \n%s", dump(stanza)) + if stanza.attr ~= nil and stanza.attr.time ~= nil then + ret = ret .. "<a name=\"" .. stanza.attr.time .. "\" href=\"#" .. stanza.attr.time .. "\" class=\"timestuff\">[" .. stanza.attr.time .. "]</a> "; + if stanza[1] ~= nil then + local nick; + if stanza[1].attr.from ~= nil then + nick = stanza[1].attr.from:match("/(.+)$"); + end + if stanza[1].tag == "presence" and nick ~= nil then + if stanza[1].attr.type == nil then + ret = ret .. "<font class=\"muc_join\"> *** " .. nick .. " joins the room</font><br />\n"; + elseif stanza[1].attr.type ~= nil and stanza[1].attr.type == "unavailable" then + ret = ret .. "<font class=\"muc_leave\"> *** " .. nick .. " leaves the room</font><br />\n"; + else + ret = ret .. "<font class=\"muc_leave\"> *** " .. nick .. " changed his/her status to: " .. stanza[1].attr.type .. "</font><br />\n"; + end + elseif stanza[1].tag == "message" then + local body; + for _,tag in ipairs(stanza[1]) do + if tag.tag == "body" then + body = tag[1]:gsub("\n", "<br />\n"); + if nick ~= nil then + break; + end + elseif tag.tag == "nick" and nick == nil then + nick = tag[1]; + if body ~= nil then + break; + end + end + end + if nick ~= nil and body ~= nil then + ret = ret .. "<font class=\"muc_name\"><" .. nick .. "></font> " .. body .. "<br />\n"; + end + else + module:log("info", "unknown stanza subtag in log found. room: %s; day: %s", bareRoomJid, year .. "/" .. month .. "/" .. day); + end + end + end + end + else + module:log("warn", "could not parse room log. room: %s; day: %s", bareRoomJid, year .. "/" .. month .. "/" .. day); + end + f:close(); + else + ret = err; + end + return "<h2>room " .. bareRoomJid .. " logging of 20" .. year .. "/" .. month .. "/" .. day .. "</h2><hr /><p>" .. ret .. "</p><hr />"; + else + return generateDayListSiteContentByRoom(bareRoomJid); -- fallback + end +end + +function handle_request(method, body, request) + module:log("debug", "method: %s, body: %s, request: %s", tostring(method), tostring(body), tostring(request)); + -- module:log("debug", "dump of request:\n%s\n", dump(request)); + local query = splitQuery(request.url.query); + local node, host = grepRoomJid(request.url.path); + + if validateLogFolder() == false then + return createDoc([[ + Muclogging is not configured correctly. Add a section to Host * "muc_log" and configure the value for the logging "folder".<br /> + Like:<br /> + Host "*"<br /> + ....<br /> + muc_log = {<br /> + folder = "/opt/local/var/log/prosody/rooms";<br /> + }<br /> + ]]); + end + if node ~= nil and host ~= nil then + local bare = node .. "@" .. host; + if prosody.hosts[host] ~= nil and prosody.hosts[host].muc ~= nil and prosody.hosts[host].muc.rooms[bare] ~= nil then + local room = prosody.hosts[host].muc.rooms[bare]; + local logging = config_get(host, "core", "logging"); + if logging == true then + if request.url.query == nil then + return createDoc(generateDayListSiteContentByRoom(bare)); + else + return createDoc(parseDay(bare, query)); + end + else + module:log("debug", "logging not enabled for this room: %s", bare); + end + else + module:log("debug", "room instance not found. bare room jid: %s", tostring(bare)); + end + else + return createDoc(generateRoomListSiteContent()); + end + return; +end + +function module.load() + config = config_get("*", "core", "muc_log"); + -- module:log("debug", "muc_log config: \n%s", dump(config)); + + if config.http_port ~= nil then + httpserver.new_from_config({ config.http_port }, "muc_log", handle_request); + end + return validateLogFolder(); +end + +module:hook("pre-message/bare", logIfNeeded, 500); +module:hook("pre-presence/full", logIfNeeded, 500);