Index: src/mod_muc/mod_muc_room.erl
===================================================================
--- src/mod_muc/mod_muc_room.erl (revisión: 470)
+++ src/mod_muc/mod_muc_room.erl (copia de trabajo)
@@ -49,7 +49,7 @@
password_protected = false,
password = "",
anonymous = true,
- logging = false % TODO
+ logging = false
}).
-record(user, {jid,
@@ -357,7 +357,11 @@
NewState =
add_user_presence_un(From, Packet, StateData),
send_new_presence(From, NewState),
- remove_online_user(From, NewState);
+ Reason = case xml:get_subtag(Packet, "status") of
+ false -> "";
+ Status_el -> xml:get_tag_cdata(Status_el)
+ end,
+ remove_online_user(From, NewState, Reason);
_ ->
StateData
end;
@@ -848,10 +852,15 @@
nick = Nick,
role = Role},
StateData#state.users),
+ mod_muc_log:add_to_log(join, {Nick, StateData}, StateData),
StateData#state{users = Users}.
remove_online_user(JID, StateData) ->
+ remove_online_user(JID, StateData, "").
+
+remove_online_user(JID, StateData, Reason) ->
LJID = jlib:jid_tolower(JID),
+ mod_muc_log:add_to_log(leave, {LJID, Reason, StateData}, StateData),
Users = ?DICT:erase(LJID, StateData#state.users),
StateData#state{users = Users}.
@@ -1278,6 +1287,7 @@
end, StateData#state.users),
NewStateData = StateData#state{users = Users},
send_nick_changing(JID, OldNick, NewStateData),
+ mod_muc_log:add_to_log(nickchange, {OldNick, Nick, StateData}, StateData),
NewStateData.
send_nick_changing(JID, OldNick, StateData) ->
@@ -1377,6 +1387,7 @@
Size = lists:flatlength(xml:element_to_string(SPacket)),
Q1 = lqueue_in({FromNick, TSPacket, HaveSubject, TimeStamp, Size},
StateData#state.history),
+ mod_muc_log:add_to_log(text, {FromNick, Packet, StateData}, StateData),
StateData#state{history = Q1}.
send_history(JID, Shift, StateData) ->
@@ -1883,6 +1894,7 @@
end
end,
lists:foreach(fun(J) ->
+ mod_muc_log:add_to_log(kickban, {J, Reason, Code, StateData}, StateData),
send_kickban_presence1(J, Reason, Code, StateData)
end, LJIDs).
@@ -1930,7 +1942,10 @@
{?NS_XDATA, "cancel"} ->
{result, [], StateData};
{?NS_XDATA, "submit"} ->
- set_config(XEl, StateData);
+ case check_allowed_log_change(XEl, StateData, From) of
+ allow -> set_config(XEl, StateData);
+ deny -> {error, ?ERR_BAD_REQUEST}
+ end;
_ ->
{error, ?ERR_BAD_REQUEST}
end;
@@ -1951,7 +1966,7 @@
{xmlelement, Name, Attrs, Els} = SubEl,
case xml:remove_cdata(Els) of
[] ->
- get_config(Lang, StateData);
+ get_config(Lang, StateData, From);
[Item] ->
case xml:get_tag_attr("affiliation", Item) of
false ->
@@ -1980,6 +1995,11 @@
{error, ?ERRT_FORBIDDEN(Lang, ErrText)}
end.
+check_allowed_log_change(XEl, StateData, From) ->
+ case lists:keymember("logging", 1, jlib:parse_xdata_submit(XEl)) of
+ false -> allow;
+ true -> mod_muc_log:check_access_log(StateData#state.server_host, From)
+ end.
-define(XFIELD(Type, Label, Var, Val),
@@ -2002,7 +2022,7 @@
?XFIELD("text-private", Label, Var, Val)).
-get_config(Lang, StateData) ->
+get_config(Lang, StateData, From) ->
Config = StateData#state.config,
Res =
[{xmlelement, "title", [],
@@ -2052,11 +2072,18 @@
end),
?BOOLXFIELD("Make room anonymous?",
"anonymous",
- Config#config.anonymous),
- ?BOOLXFIELD("Enable logging?",
- "logging",
- Config#config.logging)
- ],
+ Config#config.anonymous)
+ ] ++ case mod_muc_log:check_access_log(StateData#state.server_host, From) of
+ allow ->
+ [?BOOLXFIELD(
+ case gen_mod:get_module_opt(StateData#state.server_host, mod_muc, allow_room_log, true) of
+ true -> "Enable logging";
+ false -> "Enable logging" ++ " (disabled by the admin)"
+ end,
+ "logging",
+ Config#config.logging)];
+ _ -> []
+ end,
{result, [{xmlelement, "instructions", [],
[{xmlcdata,
translate:translate(
@@ -2070,6 +2097,7 @@
set_config(XEl, StateData) ->
XData = jlib:parse_xdata_submit(XEl),
+ mod_muc_log:add_to_log(roomconfig_change, {XData, StateData}, StateData),
case XData of
invalid ->
{error, ?ERR_BAD_REQUEST};
Index: src/mod_muc/Makefile.in
===================================================================
--- src/mod_muc/Makefile.in (revisión: 470)
+++ src/mod_muc/Makefile.in (copia de trabajo)
@@ -12,6 +12,7 @@
EFLAGS = -I .. -pz ..
OBJS = \
$(OUTDIR)/mod_muc.beam \
+ $(OUTDIR)/mod_muc_log.beam \
$(OUTDIR)/mod_muc_room.beam
all: $(OBJS)
Index: src/mod_muc/mod_muc.erl
===================================================================
--- src/mod_muc/mod_muc.erl (revisión: 470)
+++ src/mod_muc/mod_muc.erl (copia de trabajo)
@@ -45,6 +45,7 @@
Access = gen_mod:get_opt(access, Opts, all),
AccessCreate = gen_mod:get_opt(access_create, Opts, all),
AccessAdmin = gen_mod:get_opt(access_admin, Opts, none),
+ mod_muc_log:start(Opts),
register(gen_mod:get_module_proc(Host, ?PROCNAME),
spawn(?MODULE, init,
[MyHost, Host, {Access, AccessCreate, AccessAdmin}])).
@@ -258,6 +259,7 @@
ok.
stop(Host) ->
+ mod_muc_log:stop(),
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
Proc ! stop,
{wait, Proc}.
Index: src/mod_muc/mod_muc_log.erl
===================================================================
--- src/mod_muc/mod_muc_log.erl (revisión: 0)
+++ src/mod_muc/mod_muc_log.erl (revisión: 0)
@@ -0,0 +1,481 @@
+%%%----------------------------------------------------------------------
+%%% File : mod_muc_log.erl
+%%% Author : Badlop
+%%% Purpose : MUC room logging
+%%% Created :
+%%% Id :
+%%%----------------------------------------------------------------------
+
+-module(mod_muc_log).
+-author('').
+-vsn('').
+
+%-behaviour(gen_mod).
+
+-export([start/1,
+ stop/0,
+ init_log/1,
+ check_access_log/2,
+ add_to_log/3]).
+
+-include("ejabberd.hrl").
+-include("jlib.hrl").
+-define(T(Text), translate:translate("Lang", Text)).
+
+-define(PROCNAME, ejabberd_mod_muc_log).
+-record(room, {jid, title, subject, subject_author, config}).
+
+%Copied from mod_muc_room.erl
+-define(DICT, dict).
+-record(config, {title = "",
+ allow_change_subj = true,
+ allow_query_users = true,
+ allow_private_messages = true,
+ public = true,
+ public_list = true,
+ persistent = false,
+ moderated = false, % TODO
+ members_by_default = true,
+ members_only = false,
+ allow_user_invites = false,
+ password_protected = false,
+ password = "",
+ anonymous = true,
+ logging = false
+ }).
+-record(user, {jid,
+ nick,
+ role,
+ last_presence}).
+-record(state, {room,
+ host,
+ server_host,
+ access,
+ jid,
+ config = #config{},
+ users = ?DICT:new(),
+ affiliations = ?DICT:new(),
+ history = lqueue_new(20),
+ subject = "",
+ subject_author = "",
+ just_created = false}).
+
+
+%%----------------------------------------------------------------------
+%% Module control
+
+start(Opts) ->
+ case gen_mod:get_opt(allow_room_log, Opts, true) of
+ true ->
+ OutDir = gen_mod:get_opt(outdir, Opts, "www/muc"),
+ DirType = gen_mod:get_opt(dirtype, Opts, subdirs),
+ CSSFile = gen_mod:get_opt(cssfile, Opts, false),
+ CSSFile = gen_mod:get_opt(cssfile, Opts, false),
+ AccessLog = gen_mod:get_opt(access_log, Opts, muc_admin),
+ case lists:member(?PROCNAME, registered()) of
+ false ->
+ register(?PROCNAME, spawn(?MODULE, init_log, [{OutDir, DirType, CSSFile, AccessLog}]));
+ true ->
+ ok
+ end;
+ false ->
+ ok
+ end.
+
+init_log(O) ->
+ loop_log(O).
+
+loop_log(Options) ->
+ receive
+ {log, Data} ->
+ add_message_to_log2(Data, Options),
+ loop_log(Options);
+ {check_access_log, ServerHost, From, Pid} ->
+ {_, _, _, AccessLog} = Options,
+ Pid ! acl:match_rule(ServerHost, AccessLog, From),
+ loop_log(Options);
+ stop ->
+ % TODO
+ ok;
+ _ ->
+ loop_log(Options)
+ end.
+
+stop() ->
+ ?PROCNAME ! stop.
+
+check_access_log(ServerHost, From) ->
+ case whereis(?PROCNAME) of
+ undefined -> false;
+ _ ->
+ ?PROCNAME ! {check_access_log, ServerHost, From, self()},
+ receive A -> A end
+ end.
+
+
+%%----------------------------------------------------------------------
+%% Frontend
+
+% Check if this room is configured is logged
+add_to_log(A, D, StateData) ->
+ case (StateData#state.config)#config.logging of
+ true -> add_to_log(A, D);
+ false -> ok
+ end.
+
+% Check if MUC logging is allowed
+add_to_log(A, D) ->
+ case whereis(?PROCNAME) of
+ undefined -> ok;
+ _ -> add_to_log2(A, D)
+ end.
+
+add_to_log2(text, {Nick, Packet, StateData}) ->
+ case {xml:get_subtag(Packet, "subject"), xml:get_subtag(Packet, "body")} of
+ {false, false} ->
+ ok;
+ {false, SubEl} ->
+ Message = {body, htmlize(xml:get_tag_cdata(SubEl))},
+ add_message_to_log(Nick, Message, StateData);
+ {SubEl, _false} ->
+ Message = {subject, htmlize(xml:get_tag_cdata(SubEl))},
+ add_message_to_log(Nick, Message, StateData)
+ end;
+
+add_to_log2(roomconfig_change, {XData, StateData}) ->
+ add_message_to_log("eeeaaae", {roomconfig_change, XData}, StateData);
+
+add_to_log2(nickchange, {OldNick, NewNick, StateData}) ->
+ add_message_to_log(NewNick, {nickchange, OldNick}, StateData);
+
+add_to_log2(join, {Nick, StateData}) ->
+ add_message_to_log(Nick, {join}, StateData);
+
+add_to_log2(leave, {LJID, Reason, StateData}) ->
+ {ok, #user{nick = Nick}} =
+ ?DICT:find(LJID, StateData#state.users),
+ case Reason of
+ "" -> add_message_to_log(Nick, {leave}, StateData);
+ _ -> add_message_to_log(Nick, {leave, Reason}, StateData)
+ end;
+
+add_to_log2(kickban, {LJID, Reason, Code, StateData}) ->
+ {ok, #user{nick = Nick}} =
+ ?DICT:find(LJID, StateData#state.users),
+ add_message_to_log(Nick, {kickban, Code, Reason}, StateData).
+
+
+%%----------------------------------------------------------------------
+%% Core
+
+add_message_to_log(Nick, Message, StateData) ->
+ ?PROCNAME ! {log, {Nick, Message, StateData}}.
+
+add_message_to_log2({Nick, Message, StateData}, Options) ->
+ {OutDir, DirType, CSSFile, _AccessLog} = Options,
+ Room = get_room_info(StateData),
+
+ % Timestamp, Date and Time
+ TimeStamp = jlib:timestamp_to_iso(calendar:now_to_universal_time(now())),
+ Year = string:substr(TimeStamp, 1, 4),
+ Month = string:substr(TimeStamp, 5, 2),
+ Day = string:substr(TimeStamp, 7, 2),
+ A1 = string:concat(Year, "-"),
+ A2 = string:concat(A1, Month),
+ A3 = string:concat(A2, "-"),
+ Date = string:concat(A3, Day),
+
+ % Directory and file names
+ {Dir, Filename} = case DirType of
+ subdirs ->
+ {filename:join(Year, Month), Day};
+ plain ->
+ {"", Date}
+ end,
+ Fd = filename:join([OutDir, Room#room.jid, Dir]),
+ Fn = filename:join([Fd, string:concat(Filename, ".html")]),
+
+ % Open file, create if it does not exist, create parent dirs if needed
+ case file:read_file_info(Fn) of
+ {ok, _} ->
+ {ok, F} = file:open(Fn, [append]);
+ {error, enoent} ->
+ make_dir_rec(Fd),
+ {ok, F} = file:open(Fn, [append]),
+ Datestring = get_dateweek(date()),
+ put_header(F, Room, Datestring, CSSFile)
+ end,
+
+ % Build message
+ Text = case Message of
+ {roomconfig_change, XData} ->
+ RoomConfig = roomconfigwire_to_string(XData),
+ put_room_config(F, RoomConfig),
+ io_lib:format("~s
",
+ [?T("Chatroom configuration modified")]);
+ {join} ->
+ io_lib:format("~s ~s
",
+ [Nick, ?T("joins the room")]);
+ {leave} ->
+ io_lib:format("~s ~s
",
+ [Nick, ?T("leaves the room")]);
+ {leave, Reason} ->
+ io_lib:format("~s ~s: ~s
",
+ [Nick, ?T("leaves the room"), Reason]);
+ {kickban, "307", ""} ->
+ io_lib:format("~s ~s
",
+ [Nick, ?T("has been kicked")]);
+ {kickban, "307", Reason} ->
+ io_lib:format("~s ~s: ~s
",
+ [Nick, ?T("has been kicked"), Reason]);
+ {kickban, "301", ""} ->
+ io_lib:format("~s ~s
",
+ [Nick, ?T("has been banned")]);
+ {kickban, "301", Reason} ->
+ io_lib:format("~s ~s: ~s
",
+ [Nick, ?T("has been banned"), Reason]);
+ {nickchange, OldNick} ->
+ io_lib:format("~s ~s ~s
",
+ [OldNick, ?T("is now known as"), Nick]);
+ {subject, T} ->
+ io_lib:format("~s~s~s
",
+ [Nick, ?T(" has set the subject to: "), T]);
+ {body, T} ->
+ case regexp:first_match(T, "^/me\s") of
+ {match, _, _} ->
+ io_lib:format("~s ~s
",
+ [Nick, string:substr(T, 5)]);
+ nomatch ->
+ io_lib:format("<~s> ~s
",
+ [Nick, T])
+ end
+ end,
+ Time = string:substr(TimeStamp, 10, 8),
+
+ % Write message
+ file:write(F, io_lib:format("[~s] ~s~n",
+ [Time, Time, Time, Text])),
+
+ % Close file
+ file:close(F),
+
+ ok.
+
+
+%%----------------------------------------------------------------------
+%% Utilities
+
+get_dateweek(Dt) ->
+ Weekday = case calendar:day_of_the_week(Dt) of
+ 1 -> ?T("Monday");
+ 2 -> ?T("Tuesday");
+ 3 -> ?T("Wednesday");
+ 4 -> ?T("Thursday");
+ 5 -> ?T("Friday");
+ 6 -> ?T("Saturday");
+ 7 -> ?T("Sunday")
+ end,
+ {Y, M, D} = Dt,
+ Month = case M of
+ 1 -> ?T("January");
+ 2 -> ?T("February");
+ 3 -> ?T("March");
+ 4 -> ?T("April");
+ 5 -> ?T("May");
+ 6 -> ?T("June");
+ 7 -> ?T("July");
+ 8 -> ?T("August");
+ 9 -> ?T("September");
+ 10 -> ?T("October");
+ 11 -> ?T("November");
+ 12 -> ?T("December")
+ end,
+ case ?MYLANG of
+ "en" -> io_lib:format("~s, ~s ~w, ~w", [Weekday, Month, D, Y]);
+ "es" -> io_lib:format("~s ~w de ~s de ~w", [Weekday, D, Month, Y]);
+ _ -> io_lib:format("~s, ~w ~s ~w", [Weekday, D, Month, Y])
+ end.
+
+make_dir_rec(Dir) ->
+ case file:read_file_info(Dir) of
+ {ok, _} ->
+ ok;
+ {error, enoent} ->
+ DirS = filename:split(Dir),
+ DirR = lists:sublist(DirS, length(DirS)-1),
+ make_dir_rec(filename:join(DirR)),
+ file:make_dir(Dir)
+ end.
+
+fw(F, S, O) -> file:write(F, io_lib:format(S++"~n", O)).
+fw(F, S) -> fw(F, S, []).
+
+put_header(F, Room, Date, CSSFile) ->
+ fw(F, ""),
+ fw(F, ""),
+ %fw(F, ""),
+ fw(F, "