# HG changeset patch # User Matthew Wild # Date 1368092360 -3600 # Node ID ba220790a59cd89842ff96d490ea936ce02b19e7 # Parent 9c88960b0f811cbbe727bb82ccc83293e5e839df mod_message_logging: New module to log user conversations to text files diff -r 9c88960b0f81 -r ba220790a59c mod_message_logging/mod_message_logging.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_message_logging/mod_message_logging.lua Thu May 09 10:39:20 2013 +0100 @@ -0,0 +1,127 @@ +module:set_global(); + +local jid_bare = require "util.jid".bare; +local jid_split = require "util.jid".split; + +local stat, mkdir = require "lfs".attributes, require "lfs".mkdir; + +-- Get a filesystem-safe string +local function fsencode_char(c) + return ("%%%02x"):format(c:byte()); +end +local function fsencode(s) + return (s:gsub("[^%w._-@]", fsencode_char):gsub("^%.", "_")); +end + +local log_base_path = module:get_option("message_logging_dir", prosody.paths.data.."/message_logs"); +mkdir(log_base_path); + +local function get_host_path(host) + return log_base_path.."/"..fsencode(host); +end + +local function get_user_path(jid) + local username, host = jid_split(jid); + local base = get_host_path(host)..os.date("/%Y-%m-%d"); + if not stat(base) then + mkdir(base); + end + return base.."/"..fsencode(username)..".msglog"; +end + +local open_files_mt = { __index = function (open_files, jid) + local f, err = io.open(get_user_path(jid), "a+"); + if not f then + module:log("error", "Failed to open message log for writing [%s]: %s", jid, err); + end + rawset(open_files, jid, f); + return f; +end }; + +-- [user@host] = filehandle +local open_files = setmetatable({}, open_files_mt); + +function close_open_files() + module:log("debug", "Closing all open files"); + for jid, filehandle in pairs(open_files) do + filehandle:close(); + open_files[jid] = nil; + end +end +module:hook_global("logging-reloaded", close_open_files); + +local function handle_incoming_message(event) + local origin, stanza = event.origin, event.stanza; + local message_type = stanza.attr.type; + + if message_type == "error" then return; end + + local from, to = jid_bare(stanza.attr.from), jid_bare(stanza.attr.to); + local body = stanza:get_child("body"); + if not body then return; end + body = body:get_text(); + + local f = open_files[to]; + if not f then return; end + if message_type == "groupchat" then + -- Add the nickname + from = from.." <"..(select(3, jid_split(stanza.attr.from)) or "")..">"; + end + body = body:gsub("\n", "\n "); -- Indent newlines + f:write("RECV: ", from, ": ", body, "\n"); + f:flush(); +end + +local function handle_outgoing_message(event) + local origin, stanza = event.origin, event.stanza; + local message_type = stanza.attr.type; + + if message_type == "error" or message_type == "groupchat" then return; end + + local from, to = jid_bare(stanza.attr.from), jid_bare(stanza.attr.to); + local body = stanza:get_child("body"); + if not body then return; end + body = body:get_text(); + + local f = open_files[from]; + if not f then return; end + body = body:gsub("\n", "\n "); -- Indent newlines + f:write("SEND: ", to, ": ", body, "\n"); + f:flush(); +end + + + +function module.add_host(module) + local host_base_path = get_host_path(module.host); + if not stat(host_base_path) then + mkdir(host_base_path); + end + + module:hook("message/bare", handle_incoming_message, 1); + module:hook("message/full", handle_incoming_message, 1); + + module:hook("pre-message/bare", handle_outgoing_message, 1); + module:hook("pre-message/full", handle_outgoing_message, 1); + module:hook("pre-message/host", handle_outgoing_message, 1); + +end + +function module.command(arg) + local command = table.remove(arg, 1); + if command == "path" then + print(get_user_path(arg[1])); + else + io.stderr:write("Unrecognised command: ", command); + return 1; + end + return 0; +end + +function module.save() + return { open_files = open_files }; +end + +function module.restore(saved) + open_files = setmetatable(saved.open_files or {}, open_files_mt); +end