267 lines
9.3 KiB
Erlang
267 lines
9.3 KiB
Erlang
%%%----------------------------------------------------------------------
|
|
%%% File : mod_log_chat.erl
|
|
%%% Author : Jérôme Sautret <jerome.sautret@process-one.net>
|
|
%%% Purpose : Log 2 ways chat messages in files
|
|
%%% Id : $Id: mod_log_chat.erl 412 2007-11-15 10:10:09Z mremond $
|
|
%%%----------------------------------------------------------------------
|
|
|
|
-module(mod_log_chat).
|
|
-author('jerome.sautret@process-one.net').
|
|
|
|
-behaviour(gen_mod).
|
|
|
|
-export([start/2,
|
|
init/1,
|
|
stop/1,
|
|
log_packet_send/3,
|
|
log_packet_receive/4]).
|
|
|
|
-define(LAGER, 1).
|
|
|
|
-include("ejabberd.hrl").
|
|
-include("logger.hrl").
|
|
-include("jlib.hrl").
|
|
|
|
-define(PROCNAME, ?MODULE).
|
|
-define(DEFAULT_PATH, ".").
|
|
-define(DEFAULT_FORMAT, text).
|
|
|
|
-record(config, {path=?DEFAULT_PATH, format=?DEFAULT_FORMAT}).
|
|
|
|
start(Host, Opts) ->
|
|
?DEBUG(" ~p ~p~n", [Host, Opts]),
|
|
case gen_mod:get_opt(host_config, Opts, fun(L) when is_list(L) -> L end, []) of
|
|
[] ->
|
|
start_vh(Host, Opts);
|
|
HostConfig ->
|
|
start_vhs(Host, HostConfig)
|
|
end.
|
|
|
|
start_vhs(_, []) ->
|
|
ok;
|
|
start_vhs(Host, [{Host, Opts}| Tail]) ->
|
|
?DEBUG("start_vhs ~p ~p~n", [Host, [{Host, Opts}| Tail]]),
|
|
start_vh(Host, Opts),
|
|
start_vhs(Host, Tail);
|
|
start_vhs(Host, [{_VHost, _Opts}| Tail]) ->
|
|
?DEBUG("start_vhs ~p ~p~n", [Host, [{_VHost, _Opts}| Tail]]),
|
|
start_vhs(Host, Tail).
|
|
start_vh(Host, Opts) ->
|
|
Path = gen_mod:get_opt(path, Opts, fun iolist_to_binary/1, ?DEFAULT_PATH),
|
|
Format = gen_mod:get_opt(format, Opts, fun(A) when is_atom(A) -> A end, ?DEFAULT_FORMAT),
|
|
ejabberd_hooks:add(user_send_packet, Host, ?MODULE, log_packet_send, 55),
|
|
ejabberd_hooks:add(user_receive_packet, Host, ?MODULE, log_packet_receive, 55),
|
|
register(gen_mod:get_module_proc(Host, ?PROCNAME),
|
|
spawn(?MODULE, init, [#config{path=Path, format=Format}])).
|
|
|
|
init(Config)->
|
|
?DEBUG("Starting ~p with config ~p~n", [?MODULE, Config]),
|
|
loop(Config).
|
|
|
|
loop(Config) ->
|
|
receive
|
|
{call, Caller, get_config} ->
|
|
Caller ! {config, Config},
|
|
loop(Config);
|
|
stop ->
|
|
exit(normal)
|
|
end.
|
|
|
|
stop(Host) ->
|
|
ejabberd_hooks:delete(user_send_packet, Host,
|
|
?MODULE, log_packet_send, 55),
|
|
ejabberd_hooks:delete(user_receive_packet, Host,
|
|
?MODULE, log_packet_receive, 55),
|
|
gen_mod:get_module_proc(Host, ?PROCNAME) ! stop,
|
|
ok.
|
|
|
|
log_packet_send(From, To, Packet) ->
|
|
log_packet(From, To, Packet, From#jid.lserver).
|
|
|
|
log_packet_receive(_JID, From, To, _Packet) when From#jid.lserver == To#jid.lserver->
|
|
ok; % only log at send time if the message is local to the server
|
|
log_packet_receive(_JID, From, To, Packet) ->
|
|
log_packet(From, To, Packet, To#jid.lserver).
|
|
|
|
log_packet(From, To, Packet = #xmlel{name = <<"message">>, attrs = Attrs}, Host) ->
|
|
case xml:get_attr_s(<<"type">>, Attrs) of
|
|
<<"groupchat">> -> %% mod_muc_log already does it
|
|
?DEBUG("dropping groupchat: ~s", [xml:element_to_string(Packet)]),
|
|
ok;
|
|
<<"error">> -> %% we don't log errors
|
|
?DEBUG("dropping error: ~s", [xml:element_to_string(Packet)]),
|
|
ok;
|
|
_ ->
|
|
write_packet(From, To, Packet, Host)
|
|
end;
|
|
log_packet(_From, _To, _Packet, _Host) ->
|
|
ok.
|
|
|
|
write_packet(From, To, Packet, Host) ->
|
|
gen_mod:get_module_proc(Host, ?PROCNAME) ! {call, self(), get_config},
|
|
Config = receive
|
|
{config, Result} ->
|
|
Result
|
|
end,
|
|
Format = Config#config.format,
|
|
{Subject, Body} = {case xml:get_subtag(Packet, <<"subject">>) of
|
|
false ->
|
|
"";
|
|
SubjEl ->
|
|
escape(Format, xml:get_tag_cdata(SubjEl))
|
|
end,
|
|
escape(Format, xml:get_path_s(Packet, [{elem, <<"body">>}, cdata]))},
|
|
case Subject == [] andalso Body == [] of
|
|
true -> %% don't log empty messages
|
|
?DEBUG("not logging empty message from ~s",[jlib:jid_to_string(From)]),
|
|
ok;
|
|
false ->
|
|
Path = Config#config.path,
|
|
FromJid = [From#jid.luser, "@", From#jid.lserver],
|
|
ToJid = [To#jid.luser, "@", To#jid.lserver],
|
|
{FilenameTemplate, DateString, Header, MessageType} =
|
|
case calendar:local_time() of
|
|
{{Y, M, D}, {H, Min, S}} ->
|
|
SortedJid = lists:sort([FromJid, ToJid]),
|
|
Title = io_lib:format(template(Format, title), [FromJid, ToJid, Y, M, D]),
|
|
{lists:flatten(io_lib:format("~s/~~p-~~2.2.0w-~~2.2.0w ~s - ~s~s",
|
|
[Path | SortedJid]++[template(Format, extension)])),
|
|
io_lib:format(template(Format, date), [Y, M, D, H, Min, S]),
|
|
|
|
io_lib:format(template(Format, header),
|
|
lists:duplicate(count(template(Format, header), "~s"),
|
|
Title)
|
|
),
|
|
case hd(SortedJid) of
|
|
FromJid ->
|
|
message1;
|
|
ToJid ->
|
|
message2
|
|
end
|
|
}
|
|
end,
|
|
?DEBUG("FilenameTemplate ~p~n",[FilenameTemplate]),
|
|
Filename = io_lib:format(FilenameTemplate, [Y, M, D]),
|
|
?DEBUG("logging message from ~s into ~s~n",[jlib:jid_to_string(From), Filename]),
|
|
File = case file:read_file_info(Filename) of
|
|
{ok, _} ->
|
|
open_logfile(Filename);
|
|
{error, enoent} ->
|
|
close_previous_logfile(FilenameTemplate, Format, {Y, M, D}),
|
|
NewFile = open_logfile(Filename),
|
|
io:format(NewFile, Header, []),
|
|
NewFile
|
|
end,
|
|
MessageText = case Subject == [] of
|
|
true ->
|
|
Body;
|
|
false ->
|
|
io_lib:format(template(Format, subject), [Subject])++Body
|
|
end,
|
|
?DEBUG("MessageTemplate ~s~n",[template(Format, MessageType)]),
|
|
?DEBUG("Data: ~s ~s ~s ~s ~s ~s ~n",[DateString, FromJid, From#jid.lresource, ToJid, To#jid.lresource, MessageText]),
|
|
io:format(File, lists:flatten(template(Format, MessageType)), [DateString, FromJid, From#jid.lresource, ToJid,
|
|
To#jid.lresource, MessageText]),
|
|
file:close(File)
|
|
end.
|
|
|
|
open_logfile(Filename) ->
|
|
case file:open(Filename, [append]) of
|
|
{ok, File} ->
|
|
File;
|
|
{error, Reason} ->
|
|
?ERROR_MSG("Cannot write into file ~s: ~p~n", [Filename, Reason])
|
|
end.
|
|
|
|
close_previous_logfile(FilenameTemplate, Format, Date) ->
|
|
Yesterday = calendar:gregorian_days_to_date(calendar:date_to_gregorian_days(Date) - 1),
|
|
Filename = io_lib:format(FilenameTemplate, tuple_to_list(Yesterday)),
|
|
case file:read_file_info(Filename) of
|
|
{ok, _} ->
|
|
File = open_logfile(Filename),
|
|
io:format(File, template(Format, footer), []),
|
|
file:close(File);
|
|
{error, enoent} ->
|
|
ok
|
|
end.
|
|
|
|
escape(html, <<$<, Text/binary>>, Acc) ->
|
|
escape(html,Text,<<Acc/binary,"<">>);
|
|
escape(html, <<$&, Text/binary>>, Acc) ->
|
|
escape(html,Text,<<Acc/binary,"&">>);
|
|
escape(html, <<Char, Text/binary>>, Acc) ->
|
|
escape(html,Text,<<Acc/binary,Char>>);
|
|
escape(html, <<>>, Acc) ->
|
|
Acc.
|
|
|
|
escape(html, Text) -> escape(html,Text,<<>>);
|
|
escape(text, Text) ->
|
|
Text;
|
|
escape(_, "") ->
|
|
"".
|
|
|
|
% return the number of occurence of Word in String
|
|
count(String, Word) ->
|
|
case string:str(String, Word) of
|
|
0 ->
|
|
0;
|
|
N ->
|
|
1+count(string:substr(String, N+length(Word)), Word)
|
|
end.
|
|
|
|
|
|
|
|
template(text, extension) ->
|
|
".log";
|
|
template(text, title) ->
|
|
"Messages log between ~s and ~s on ~p-~2.2.0w-~2.2.0w";
|
|
template(text, header) ->
|
|
"~s~n-----------------------------------------------------------------------~n";
|
|
template(text, subject) ->
|
|
"Subject: ~s~n";
|
|
template(text, message) ->
|
|
"~~s ~~s/~~s -> ~~s/~~s~n~s~~s~n";
|
|
template(text, message1) ->
|
|
io_lib:format(template(text, message), ["> "]);
|
|
template(text, message2) ->
|
|
io_lib:format(template(text, message), ["< "]);
|
|
template(text, date) ->
|
|
"~p-~2.2.0w-~2.2.0w ~2.2.0w:~2.2.0w:~2.2.0w";
|
|
template(text, footer) ->
|
|
"---- End ----~n";
|
|
|
|
template(html, extension) ->
|
|
".html";
|
|
template(html, title) ->
|
|
template(text, title);
|
|
template(html, header) ->
|
|
"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">~n"++
|
|
"<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\"><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" /><title>~s</title>"++
|
|
css()++
|
|
"</head><body>~n<h1>~s</h1>~n";
|
|
template(html, subject) ->
|
|
"<div class=\"subject\"><span>Subject:</span> ~s</div>";
|
|
template(html, message) ->
|
|
"<div class=\"message~w\"><span class=\"date\">~~s</span> <span class=\"jid\">~~s</span>/<span class=\"ressource\">~~s</span> -> <span class=\"jid\">~~s</span>/<span class=\"ressource\">~~s</span>~~n<span class=\"messagetext\">~~s</span></div>~~n";
|
|
template(html, message1) ->
|
|
io_lib:format(template(html, message), [1]);
|
|
template(html, message2) ->
|
|
io_lib:format(template(html, message), [2]);
|
|
template(html, date) ->
|
|
"~p-~2.2.0w-~2.2.0w ~2.2.0w:~2.2.0w:~2.2.0w";
|
|
template(html, footer) ->
|
|
"</body></html>".
|
|
|
|
css() ->
|
|
"<style type=\"text/css\">~n<!--~n"++
|
|
"h1 {border-bottom: #224466 solid 3pt; margin-left: 20pt; color: #336699; font-size: 1.5em; font-weight: bold; font-family: sans-serif; letter-spacing: 0.1empx; text-decoration: none;}~n"++
|
|
".message1 {border: black solid 1pt; background-color: #d7e4f1; margin: 0.3em}~n"++
|
|
".message2 {border: black solid 1pt; background-color: #b39ccb; margin: 0.3em}~n"++
|
|
".subject {margin-left: 0.5em}~n"++
|
|
".subject span {font-weight: bold;}~n"++
|
|
".date {color: #663399; font-size: 0.8em; font-weight: bold; font-family: sans-serif; letter-spacing: 0.05em; margin-left:0.5em; margin-top:20px;}~n"++
|
|
".jid {color: #336699; font-weight: bold; font-size: 1em; }~n"++
|
|
".ressource {color: #336699; }~n"++
|
|
".messagetext {color: black; margin: 0.2em; clear: both; display: block;}~n"++
|
|
"//-->~n</style>~n".
|