Software /
code /
prosody-modules
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 |
parent | 51:mod_muclogging/mod_muclogging.lua@6a40e44a2b8a |
child | 53: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\"><" .. 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 * "muc_log" and configure the value for the logging "folder".<br /> | |
274 Like:<br /> | |
275 Host "*"<br /> | |
276 ....<br /> | |
277 muc_log = {<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", "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); |