1 |
|
%%%---------------------------------------------------------------------- |
2 |
|
%%% File : mod_muc_log.erl |
3 |
|
%%% Author : Badlop@process-one.net |
4 |
|
%%% Purpose : MUC room logging |
5 |
|
%%% Created : 12 Mar 2006 by Alexey Shchepin <alexey@process-one.net> |
6 |
|
%%% |
7 |
|
%%% |
8 |
|
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne |
9 |
|
%%% |
10 |
|
%%% This program is free software; you can redistribute it and/or |
11 |
|
%%% modify it under the terms of the GNU General Public License as |
12 |
|
%%% published by the Free Software Foundation; either version 2 of the |
13 |
|
%%% License, or (at your option) any later version. |
14 |
|
%%% |
15 |
|
%%% This program is distributed in the hope that it will be useful, |
16 |
|
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of |
17 |
|
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
18 |
|
%%% General Public License for more details. |
19 |
|
%%% |
20 |
|
%%% You should have received a copy of the GNU General Public License |
21 |
|
%%% along with this program; if not, write to the Free Software |
22 |
|
%%% Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
23 |
|
%%% |
24 |
|
%%%---------------------------------------------------------------------- |
25 |
|
|
26 |
|
-module(mod_muc_log). |
27 |
|
-author('badlop@process-one.net'). |
28 |
|
|
29 |
|
-behaviour(gen_server). |
30 |
|
-behaviour(gen_mod). |
31 |
|
-behaviour(mongoose_module_metrics). |
32 |
|
|
33 |
|
%% API |
34 |
|
-export([start_link/2, |
35 |
|
start/2, |
36 |
|
stop/1, |
37 |
|
supported_features/0, |
38 |
|
check_access_log/3, |
39 |
|
add_to_log/5, |
40 |
|
set_room_occupants/4]). |
41 |
|
|
42 |
|
%% Config callbacks |
43 |
|
-export([config_spec/0, |
44 |
|
process_top_link/1]). |
45 |
|
|
46 |
|
%% gen_server callbacks |
47 |
|
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, |
48 |
|
terminate/2, code_change/3]). |
49 |
|
|
50 |
|
-ignore_xref([start_link/2]). |
51 |
|
|
52 |
|
-include("mongoose.hrl"). |
53 |
|
-include("jlib.hrl"). |
54 |
|
-include("mod_muc_room.hrl"). |
55 |
|
-include("mongoose_config_spec.hrl"). |
56 |
|
|
57 |
|
-define(T(Text), translate:translate(Lang, Text)). |
58 |
|
-define(PROCNAME, ejabberd_mod_muc_log). |
59 |
|
|
60 |
|
-record(room, {jid, title, subject, subject_author, config}). |
61 |
|
-type room() :: #room{}. |
62 |
|
|
63 |
|
-type command() :: 'join' |
64 |
|
| 'kickban' |
65 |
|
| 'leave' |
66 |
|
| 'nickchange' |
67 |
|
| 'room_existence' |
68 |
|
| 'roomconfig_change' |
69 |
|
| 'roomconfig_change_enabledlogging' |
70 |
|
| 'text'. |
71 |
|
|
72 |
|
-type jid_nick_role() :: {jid:jid(), mod_muc:nick(), mod_muc:role()}. |
73 |
|
-type jid_nick() :: {jid:jid(), mod_muc:nick()}. |
74 |
|
-type dir_type() :: 'plain' | 'subdirs'. |
75 |
|
-type dir_name() :: 'room_jid' | 'room_name'. |
76 |
|
-type file_format() :: 'html' | 'plaintext'. |
77 |
|
|
78 |
|
-record(logstate, {host :: jid:server(), |
79 |
|
out_dir :: binary(), |
80 |
|
dir_type :: dir_type(), |
81 |
|
dir_name :: dir_name(), |
82 |
|
file_format :: file_format(), |
83 |
|
css_file :: binary() | false, |
84 |
|
access, |
85 |
|
lang :: ejabberd:lang(), |
86 |
|
timezone, |
87 |
|
spam_prevention, |
88 |
|
top_link, |
89 |
|
occupants = #{} :: #{RoomJID :: binary() => [jid_nick_role()]}, |
90 |
|
room_monitors = #{} :: #{reference() => RoomJID :: binary()} |
91 |
|
}). |
92 |
|
-type logstate() :: #logstate{}. |
93 |
|
|
94 |
|
%%==================================================================== |
95 |
|
%% API |
96 |
|
%%==================================================================== |
97 |
|
|
98 |
|
%% @doc Starts the server |
99 |
|
-spec start_link(jid:server(), _) -> 'ignore' | {'error', _} | {'ok', pid()}. |
100 |
|
start_link(Host, Opts) -> |
101 |
8 |
Proc = gen_mod:get_module_proc(Host, ?PROCNAME), |
102 |
8 |
gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []). |
103 |
|
|
104 |
|
-spec start(jid:server(), _) -> {'error', _} |
105 |
|
| {'ok', 'undefined' | pid()} |
106 |
|
| {'ok', 'undefined' | pid(), _}. |
107 |
|
start(Host, Opts) -> |
108 |
8 |
Proc = gen_mod:get_module_proc(Host, ?PROCNAME), |
109 |
8 |
ChildSpec = |
110 |
|
{Proc, |
111 |
|
{?MODULE, start_link, [Host, Opts]}, |
112 |
|
temporary, |
113 |
|
1000, |
114 |
|
worker, |
115 |
|
[?MODULE]}, |
116 |
8 |
ejabberd_sup:start_child(ChildSpec). |
117 |
|
|
118 |
|
-spec stop(jid:server()) -> 'ok' |
119 |
|
| {'error', 'not_found' | 'restarting' | 'running' | 'simple_one_for_one'}. |
120 |
|
stop(Host) -> |
121 |
8 |
Proc = gen_mod:get_module_proc(Host, ?PROCNAME), |
122 |
8 |
gen_server:call(Proc, stop), |
123 |
8 |
ejabberd_sup:stop_child(Proc). |
124 |
|
|
125 |
|
-spec supported_features() -> [atom()]. |
126 |
|
supported_features() -> |
127 |
:-( |
[dynamic_domains]. |
128 |
|
|
129 |
|
-spec config_spec() -> mongoose_config_spec:config_section(). |
130 |
|
config_spec() -> |
131 |
160 |
#section{ |
132 |
|
items = #{<<"outdir">> => #option{type = string, |
133 |
|
validate = dirname}, |
134 |
|
<<"access_log">> => #option{type = atom, |
135 |
|
validate = access_rule}, |
136 |
|
<<"dirtype">> => #option{type = atom, |
137 |
|
validate = {enum, [subdirs, plain]}}, |
138 |
|
<<"dirname">> => #option{type = atom, |
139 |
|
validate = {enum, [room_jid, room_name]}}, |
140 |
|
<<"file_format">> => #option{type = atom, |
141 |
|
validate = {enum, [html, plaintext]}}, |
142 |
|
<<"css_file">> => #option{type = binary, |
143 |
|
validate = non_empty, |
144 |
|
wrap = {kv, cssfile}}, |
145 |
|
<<"timezone">> => #option{type = atom, |
146 |
|
validate = {enum, [local, universal]}}, |
147 |
|
<<"top_link">> => top_link_config_spec(), |
148 |
|
<<"spam_prevention">> => #option{type = boolean} |
149 |
|
} |
150 |
|
}. |
151 |
|
|
152 |
|
top_link_config_spec() -> |
153 |
160 |
#section{ |
154 |
|
items = #{<<"target">> => #option{type = string, |
155 |
|
validate = url}, |
156 |
|
<<"text">> => #option{type = string, |
157 |
|
validate = non_empty}}, |
158 |
|
required = all, |
159 |
|
process = fun ?MODULE:process_top_link/1 |
160 |
|
}. |
161 |
|
|
162 |
|
process_top_link(KVs) -> |
163 |
:-( |
{[[{target, Target}], [{text, Text}]], []} = proplists:split(KVs, [target, text]), |
164 |
:-( |
{Target, Text}. |
165 |
|
|
166 |
|
-spec add_to_log(mongooseim:host_type(), Type :: any(), Data :: any(), mod_muc:room(), |
167 |
|
list()) -> 'ok'. |
168 |
|
add_to_log(HostType, Type, Data, Room, Opts) -> |
169 |
9 |
gen_server:cast(get_proc_name(HostType), |
170 |
|
{add_to_log, Type, Data, Room, Opts}). |
171 |
|
|
172 |
|
|
173 |
|
-spec check_access_log(mongooseim:host_type(), jid:lserver(), jid:jid()) -> any(). |
174 |
|
check_access_log(HostType, ServerHost, From) -> |
175 |
13 |
case catch gen_server:call(get_proc_name(HostType), |
176 |
|
{check_access_log, HostType, ServerHost, From}) of |
177 |
|
{'EXIT', _Error} -> |
178 |
:-( |
deny; |
179 |
|
Res -> |
180 |
13 |
Res |
181 |
|
end. |
182 |
|
|
183 |
|
-spec set_room_occupants(jid:server(), RoomPID :: pid(), RoomJID :: jid:jid(), |
184 |
|
Occupants :: [mod_muc_room:user()]) -> ok. |
185 |
|
set_room_occupants(Host, RoomPID, RoomJID, Occupants) -> |
186 |
781 |
gen_server:cast(get_proc_name(Host), {set_room_occupants, RoomPID, RoomJID, Occupants}). |
187 |
|
|
188 |
|
%%==================================================================== |
189 |
|
%% gen_server callbacks |
190 |
|
%%==================================================================== |
191 |
|
|
192 |
|
%%-------------------------------------------------------------------- |
193 |
|
%% Function: init(Args) -> {ok, State} | |
194 |
|
%% {ok, State, Timeout} | |
195 |
|
%% ignore | |
196 |
|
%% {stop, Reason} |
197 |
|
%% Description: Initiates the server |
198 |
|
%%-------------------------------------------------------------------- |
199 |
|
-spec init([list() | jid:server(), ...]) -> {'ok', logstate()}. |
200 |
|
init([Host, Opts]) -> |
201 |
8 |
OutDir = list_to_binary(gen_mod:get_opt(outdir, Opts, "www/muc")), |
202 |
8 |
DirType = gen_mod:get_opt(dirtype, Opts, subdirs), |
203 |
8 |
DirName = gen_mod:get_opt(dirname, Opts, room_jid), |
204 |
8 |
FileFormat = gen_mod:get_opt(file_format, Opts, html), % Allowed values: html|plaintext |
205 |
8 |
CSSFile = gen_mod:get_opt(cssfile, Opts, false), |
206 |
8 |
AccessLog = gen_mod:get_opt(access_log, Opts, muc_admin), |
207 |
8 |
Timezone = gen_mod:get_opt(timezone, Opts, local), |
208 |
8 |
{TL1, TL2} = gen_mod:get_opt(top_link, Opts, {"/", "Home"}), |
209 |
8 |
TopLink = {list_to_binary(TL1), list_to_binary(TL2)}, |
210 |
8 |
NoFollow = gen_mod:get_opt(spam_prevention, Opts, true), |
211 |
8 |
{ok, #logstate{host = Host, |
212 |
|
out_dir = OutDir, |
213 |
|
dir_type = DirType, |
214 |
|
dir_name = DirName, |
215 |
|
file_format = FileFormat, |
216 |
|
css_file = CSSFile, |
217 |
|
access = AccessLog, |
218 |
|
lang = ?MYLANG, |
219 |
|
timezone = Timezone, |
220 |
|
spam_prevention = NoFollow, |
221 |
|
top_link = TopLink}}. |
222 |
|
|
223 |
|
%%-------------------------------------------------------------------- |
224 |
|
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} | |
225 |
|
%% {reply, Reply, State, Timeout} | |
226 |
|
%% {noreply, State} | |
227 |
|
%% {noreply, State, Timeout} | |
228 |
|
%% {stop, Reason, Reply, State} | |
229 |
|
%% {stop, Reason, State} |
230 |
|
%% Description: Handling call messages |
231 |
|
%%-------------------------------------------------------------------- |
232 |
|
-spec handle_call('stop' |
233 |
|
| {'check_access_log', mongooseim:host_type(), 'global' | jid:server(), jid:jid()}, |
234 |
|
From :: any(), logstate()) -> {'reply', 'allow' | 'deny', logstate()} |
235 |
|
| {'stop', 'normal', 'ok', _}. |
236 |
|
handle_call({check_access_log, HostType, ServerHost, FromJID}, _From, State) -> |
237 |
13 |
Reply = acl:match_rule(HostType, ServerHost, State#logstate.access, FromJID), |
238 |
13 |
{reply, Reply, State}; |
239 |
|
handle_call(stop, _From, State) -> |
240 |
8 |
{stop, normal, ok, State}. |
241 |
|
|
242 |
|
%%-------------------------------------------------------------------- |
243 |
|
%% Function: handle_cast(Msg, State) -> {noreply, State} | |
244 |
|
%% {noreply, State, Timeout} | |
245 |
|
%% {stop, Reason, State} |
246 |
|
%% Description: Handling cast messages |
247 |
|
%%-------------------------------------------------------------------- |
248 |
|
-spec handle_cast |
249 |
|
({add_to_log, any(), any(), mod_muc:room(), list()}, logstate()) -> {'noreply', logstate()}; |
250 |
|
({set_room_occupants, pid(), jid:jid(), [mod_muc_room:user()]}, logstate()) -> |
251 |
|
{noreply, logstate()}. |
252 |
|
handle_cast({add_to_log, Type, Data, Room, Opts}, State) -> |
253 |
9 |
try |
254 |
9 |
add_to_log2(Type, Data, Room, Opts, State) |
255 |
|
catch Class:Reason:Stacktrace -> |
256 |
:-( |
?LOG_ERROR(#{what => muc_add_to_log_failed, room => Room, |
257 |
|
class => Class, reason => Reason, stacktrace => Stacktrace, |
258 |
:-( |
log_type => Type, log_data => Data}, State) |
259 |
|
end, |
260 |
9 |
{noreply, State}; |
261 |
|
handle_cast({set_room_occupants, RoomPID, RoomJID, Users}, State) -> |
262 |
781 |
#logstate{occupants = OldOccupantsMap, room_monitors = OldMonitors} = State, |
263 |
781 |
RoomJIDBin = jid:to_binary(RoomJID), |
264 |
781 |
Monitors = |
265 |
|
case maps:is_key(RoomJIDBin, OldOccupantsMap) of |
266 |
668 |
true -> OldMonitors; |
267 |
|
false -> |
268 |
113 |
MonitorRef = monitor(process, RoomPID), |
269 |
113 |
maps:put(MonitorRef, RoomJIDBin, OldMonitors) |
270 |
|
end, |
271 |
781 |
Occupants = [{U#user.jid, U#user.nick, U#user.role} || U <- Users], |
272 |
781 |
OccupantsMap = maps:put(RoomJIDBin, Occupants, OldOccupantsMap), |
273 |
781 |
{noreply, State#logstate{occupants = OccupantsMap, room_monitors = Monitors}}; |
274 |
|
handle_cast(_Msg, State) -> |
275 |
:-( |
{noreply, State}. |
276 |
|
|
277 |
|
%%-------------------------------------------------------------------- |
278 |
|
%% Function: handle_info(Info, State) -> {noreply, State} | |
279 |
|
%% {noreply, State, Timeout} | |
280 |
|
%% {stop, Reason, State} |
281 |
|
%% Description: Handling all non call/cast messages |
282 |
|
%%-------------------------------------------------------------------- |
283 |
|
handle_info({'DOWN', MonitorRef, process, Pid, Info}, State) -> |
284 |
113 |
#logstate{occupants = OldOccupantsMap, room_monitors = OldMonitors} = State, |
285 |
113 |
case maps:find(MonitorRef, OldMonitors) of |
286 |
|
error -> |
287 |
:-( |
?LOG_WARNING(#{what => muc_unknown_monitor, |
288 |
|
text => <<"Unknown monitored process is now down">>, |
289 |
:-( |
monitor_ref => MonitorRef, monitor_pid => Pid, reason => Info}), |
290 |
:-( |
{noreply, State}; |
291 |
|
{ok, RoomJID} -> |
292 |
113 |
Monitors = maps:remove(MonitorRef, OldMonitors), |
293 |
113 |
OccupantsMap = maps:remove(RoomJID, OldOccupantsMap), |
294 |
113 |
{noreply, State#logstate{occupants = OccupantsMap, room_monitors = Monitors}} |
295 |
|
end; |
296 |
|
handle_info(_Info, State) -> |
297 |
:-( |
{noreply, State}. |
298 |
|
|
299 |
|
%%-------------------------------------------------------------------- |
300 |
|
%% Function: terminate(Reason, State) -> void() |
301 |
|
%% Description: This function is called by a gen_server when it is about to |
302 |
|
%% terminate. It should be the opposite of Module:init/1 and do any necessary |
303 |
|
%% cleaning up. When it returns, the gen_server terminates with Reason. |
304 |
|
%% The return value is ignored. |
305 |
|
%%-------------------------------------------------------------------- |
306 |
|
terminate(_Reason, _State) -> |
307 |
8 |
ok. |
308 |
|
|
309 |
|
%%-------------------------------------------------------------------- |
310 |
|
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState} |
311 |
|
%% Description: Convert process state when code is changed |
312 |
|
%%-------------------------------------------------------------------- |
313 |
|
code_change(_OldVsn, State, _Extra) -> |
314 |
:-( |
{ok, State}. |
315 |
|
|
316 |
|
%%-------------------------------------------------------------------- |
317 |
|
%%% Internal functions |
318 |
|
%%-------------------------------------------------------------------- |
319 |
|
-spec add_to_log2(command(), {mod_muc:nick(), mod_muc:packet()}, mod_muc:room(), |
320 |
|
list(), logstate()) -> 'ok'. |
321 |
|
add_to_log2(text, {Nick, Packet}, Room, Opts, State) -> |
322 |
1 |
case {xml:get_subtag(Packet, <<"subject">>), xml:get_subtag(Packet, <<"body">>)} of |
323 |
|
{false, false} -> |
324 |
:-( |
ok; |
325 |
|
{false, SubEl} -> |
326 |
1 |
Message = {body, xml:get_tag_cdata(SubEl)}, |
327 |
1 |
add_message_to_log(Nick, Message, Room, Opts, State); |
328 |
|
{SubEl, _} -> |
329 |
:-( |
Message = {subject, xml:get_tag_cdata(SubEl)}, |
330 |
:-( |
add_message_to_log(Nick, Message, Room, Opts, State) |
331 |
|
end; |
332 |
|
add_to_log2(roomconfig_change, _Occupants, Room, Opts, State) -> |
333 |
1 |
add_message_to_log(<<"">>, roomconfig_change, Room, Opts, State); |
334 |
|
add_to_log2(roomconfig_change_enabledlogging, Occupants, Room, Opts, State) -> |
335 |
1 |
add_message_to_log(<<"">>, {roomconfig_change, Occupants}, Room, Opts, State); |
336 |
|
add_to_log2(room_existence, NewStatus, Room, Opts, State) -> |
337 |
4 |
add_message_to_log(<<"">>, {room_existence, NewStatus}, Room, Opts, State); |
338 |
|
add_to_log2(nickchange, {OldNick, NewNick}, Room, Opts, State) -> |
339 |
:-( |
add_message_to_log(NewNick, {nickchange, OldNick}, Room, Opts, State); |
340 |
|
add_to_log2(join, Nick, Room, Opts, State) -> |
341 |
1 |
add_message_to_log(Nick, join, Room, Opts, State); |
342 |
|
add_to_log2(leave, {Nick, Reason}, Room, Opts, State) -> |
343 |
1 |
case Reason of |
344 |
1 |
<<"">> -> add_message_to_log(Nick, leave, Room, Opts, State); |
345 |
:-( |
_ -> add_message_to_log(Nick, {leave, Reason}, Room, Opts, State) |
346 |
|
end; |
347 |
|
add_to_log2(kickban, {Nick, Reason, Code}, Room, Opts, State) -> |
348 |
:-( |
add_message_to_log(Nick, {kickban, Code, Reason}, Room, Opts, State). |
349 |
|
|
350 |
|
|
351 |
|
%%---------------------------------------------------------------------- |
352 |
|
%% Core |
353 |
|
|
354 |
|
-spec build_filename_string(calendar:datetime(), OutDir :: binary(), |
355 |
|
RoomJID :: jid:literal_jid(), dir_type(), dir_name(), file_format()) |
356 |
|
-> {binary(), binary(), binary()}. |
357 |
|
build_filename_string(TimeStamp, OutDir, RoomJID, DirType, DirName, FileFormat) -> |
358 |
13 |
{{Year, Month, Day}, _Time} = TimeStamp, |
359 |
|
|
360 |
|
%% Directory and file names |
361 |
13 |
{Dir, Filename, Rel} = |
362 |
|
case DirType of |
363 |
|
subdirs -> |
364 |
13 |
SYear = list_to_binary(lists:flatten(io_lib:format("~4..0w", [Year]))), |
365 |
13 |
SMonth = list_to_binary(lists:flatten(io_lib:format("~2..0w", [Month]))), |
366 |
13 |
SDay = list_to_binary(lists:flatten(io_lib:format("~2..0w", [Day]))), |
367 |
13 |
{filename:join(SYear, SMonth), SDay, <<"../..">>}; |
368 |
|
plain -> |
369 |
:-( |
Date = list_to_binary(lists:flatten( |
370 |
|
io_lib:format("~4..0w-~2..0w-~2..0w", |
371 |
|
[Year, Month, Day]))), |
372 |
:-( |
{<<"">>, Date, <<".">>} |
373 |
|
end, |
374 |
|
|
375 |
13 |
RoomString = case DirName of |
376 |
13 |
room_jid -> RoomJID; |
377 |
:-( |
room_name -> get_room_name(RoomJID) |
378 |
|
end, |
379 |
13 |
Extension = case FileFormat of |
380 |
13 |
html -> <<".html">>; |
381 |
:-( |
plaintext -> <<".txt">> |
382 |
|
end, |
383 |
13 |
Fd = filename:join([OutDir, RoomString, Dir]), |
384 |
13 |
Fn = filename:join([Fd, <<Filename/binary, Extension/binary>>]), |
385 |
13 |
Fnrel = filename:join([Rel, Dir, <<Filename/binary, Extension/binary>>]), |
386 |
13 |
{Fd, Fn, Fnrel}. |
387 |
|
|
388 |
|
|
389 |
|
-spec get_room_name(jid:literal_jid()) -> mod_muc:room(). |
390 |
|
get_room_name(RoomJID) -> |
391 |
:-( |
JID = jid:from_binary(RoomJID), |
392 |
:-( |
JID#jid.user. |
393 |
|
|
394 |
|
|
395 |
|
%% @doc calculate day before |
396 |
|
-spec get_timestamp_daydiff(calendar:datetime(), integer()) -> calendar:datetime(). |
397 |
|
get_timestamp_daydiff(TimeStamp, Daydiff) -> |
398 |
4 |
{Date1, HMS} = TimeStamp, |
399 |
4 |
Date2 = calendar:gregorian_days_to_date( |
400 |
|
calendar:date_to_gregorian_days(Date1) + Daydiff), |
401 |
4 |
{Date2, HMS}. |
402 |
|
|
403 |
|
|
404 |
|
%% @doc Try to close the previous day log, if it exists |
405 |
|
-spec close_previous_log(binary(), any(), file_format()) -> 'ok' | {'error', atom()}. |
406 |
|
close_previous_log(Fn, ImagesDir, FileFormat) -> |
407 |
2 |
case file:read_file_info(Fn) of |
408 |
|
{ok, _} -> |
409 |
:-( |
{ok, F} = file:open(Fn, [append]), |
410 |
:-( |
write_last_lines(F, ImagesDir, FileFormat), |
411 |
:-( |
file:close(F); |
412 |
2 |
_ -> ok |
413 |
|
end. |
414 |
|
|
415 |
|
|
416 |
|
-spec write_last_lines(file:io_device(), binary(), file_format()) -> 'ok'. |
417 |
|
write_last_lines(_, _, plaintext) -> |
418 |
:-( |
ok; |
419 |
|
write_last_lines(F, ImagesDir, _FileFormat) -> |
420 |
:-( |
fw(F, <<"<div class=\"legend\">">>), |
421 |
:-( |
fw(F, <<" <a href=\"http://www.ejabberd.im\"><img style=\"border:0\" src=\"", ImagesDir/binary, |
422 |
|
"/powered-by-ejabberd.png\" alt=\"Powered by ejabberd\"/></a>">>), |
423 |
:-( |
fw(F, <<" <a href=\"http://www.erlang.org/\"><img style=\"border:0\" src=\"", ImagesDir/binary, |
424 |
|
"/powered-by-erlang.png\" alt=\"Powered by Erlang\"/></a>">>), |
425 |
:-( |
fw(F, <<"<span class=\"w3c\">">>), |
426 |
:-( |
fw(F, <<" <a href=\"http://validator.w3.org/check?uri=referer\">" |
427 |
|
"<img style=\"border:0;width:88px;height:31px\" src=\"", ImagesDir/binary, |
428 |
|
"/valid-xhtml10.png\" alt=\"Valid XHTML 1.0 Transitional\" /></a>">>), |
429 |
:-( |
fw(F, <<" <a href=\"http://jigsaw.w3.org/css-validator/\">" |
430 |
|
"<img style=\"border:0;width:88px;height:31px\" src=\"", ImagesDir/binary, |
431 |
|
"/vcss.png\" alt=\"Valid CSS!\"/></a>">>), |
432 |
:-( |
fw(F, <<"</span></div></body></html>">>). |
433 |
|
|
434 |
|
|
435 |
|
-spec add_message_to_log(mod_muc:nick(), Message :: atom() | tuple(), |
436 |
|
RoomJID :: jid:simple_jid() | jid:jid(), Opts :: list(), State :: logstate()) -> ok. |
437 |
|
add_message_to_log(Nick1, Message, RoomJID, Opts, State) -> |
438 |
9 |
#logstate{out_dir = OutDir, |
439 |
|
dir_type = DirType, |
440 |
|
dir_name = DirName, |
441 |
|
file_format = FileFormat, |
442 |
|
css_file = CSSFile, |
443 |
|
lang = Lang, |
444 |
|
timezone = Timezone, |
445 |
|
spam_prevention = NoFollow, |
446 |
|
top_link = TopLink, |
447 |
|
occupants = OccupantsMap} = State, |
448 |
9 |
Room = get_room_info(RoomJID, Opts), |
449 |
9 |
Nick = htmlize(Nick1, FileFormat), |
450 |
9 |
Nick2 = htmlize(<<"<", Nick1/binary, ">">>, FileFormat), |
451 |
9 |
Now = erlang:timestamp(), |
452 |
9 |
TimeStamp = case Timezone of |
453 |
9 |
local -> calendar:now_to_local_time(Now); |
454 |
:-( |
universal -> calendar:now_to_universal_time(Now) |
455 |
|
end, |
456 |
9 |
{Fd, Fn, _Dir} = build_filename_string(TimeStamp, OutDir, Room#room.jid, |
457 |
|
DirType, DirName, FileFormat), |
458 |
9 |
{Date, Time} = TimeStamp, |
459 |
|
|
460 |
|
%% Open file, create if it does not exist, create parent dirs if needed |
461 |
9 |
case file:read_file_info(Fn) of |
462 |
|
{ok, _} -> |
463 |
7 |
{ok, F} = file:open(Fn, [append]); |
464 |
|
{error, enoent} -> |
465 |
2 |
make_dir_rec(Fd), |
466 |
2 |
{ok, F} = file:open(Fn, [append]), |
467 |
2 |
Datestring = get_dateweek(Date, Lang), |
468 |
|
|
469 |
2 |
TimeStampYesterday = get_timestamp_daydiff(TimeStamp, -1), |
470 |
2 |
{_FdYesterday, FnYesterday, DatePrev} = |
471 |
|
build_filename_string( |
472 |
|
TimeStampYesterday, OutDir, Room#room.jid, DirType, DirName, FileFormat), |
473 |
|
|
474 |
2 |
TimeStampTomorrow = get_timestamp_daydiff(TimeStamp, 1), |
475 |
2 |
{_FdTomorrow, _FnTomorrow, DateNext} = |
476 |
|
build_filename_string( |
477 |
|
TimeStampTomorrow, OutDir, Room#room.jid, DirType, DirName, FileFormat), |
478 |
|
|
479 |
2 |
HourOffset = calc_hour_offset(TimeStamp), |
480 |
2 |
put_header(F, Room, Datestring, CSSFile, Lang, |
481 |
|
HourOffset, DatePrev, DateNext, TopLink, FileFormat, OccupantsMap), |
482 |
|
|
483 |
2 |
ImagesDir = <<OutDir/binary, "images">>, |
484 |
2 |
file:make_dir(ImagesDir), |
485 |
2 |
create_image_files(ImagesDir), |
486 |
2 |
ImagesUrl = case DirType of |
487 |
2 |
subdirs -> <<"../../../images">>; |
488 |
:-( |
plain -> <<"../images">> |
489 |
|
end, |
490 |
2 |
close_previous_log(FnYesterday, ImagesUrl, FileFormat) |
491 |
|
end, |
492 |
|
|
493 |
|
%% Build message |
494 |
9 |
Text |
495 |
|
= case Message of |
496 |
|
roomconfig_change -> |
497 |
1 |
RoomConfig = roomconfig_to_binary(Room#room.config, Lang, FileFormat), |
498 |
1 |
put_room_config(F, RoomConfig, Lang, FileFormat), |
499 |
1 |
<<"<font class=\"mrcm\">", (?T(<<"Chatroom configuration modified">>))/binary, |
500 |
|
"</font><br/>">>; |
501 |
|
{roomconfig_change, Occupants} -> |
502 |
1 |
RoomConfig = roomconfig_to_binary(Room#room.config, Lang, FileFormat), |
503 |
1 |
put_room_config(F, RoomConfig, Lang, FileFormat), |
504 |
1 |
RoomOccupants = roomoccupants_to_binary(Occupants, FileFormat), |
505 |
1 |
put_room_occupants(F, RoomOccupants, Lang, FileFormat), |
506 |
1 |
<<"<font class=\"mrcm\">", (?T(<<"Chatroom configuration modified">>))/binary, |
507 |
|
"</font><br/>">>; |
508 |
|
join -> |
509 |
1 |
<<"<font class=\"mj\">", Nick/binary, " ", (?T(<<"joins the room">>))/binary, |
510 |
|
"</font><br/>">>; |
511 |
|
leave -> |
512 |
1 |
<<"<font class=\"mj\">", Nick/binary, " ", (?T(<<"leaves the room">>))/binary, |
513 |
|
"</font><br/>">>; |
514 |
|
{leave, Reason} -> |
515 |
:-( |
<<"<font class=\"ml\">", Nick/binary, " ", (?T(<<"leaves the room">>))/binary, ": ", |
516 |
|
(htmlize(Reason, NoFollow, FileFormat))/binary, ": ~s</font><br/>">>; |
517 |
|
{kickban, "301", ""} -> |
518 |
:-( |
<<"<font class=\"mb\">", Nick/binary, " ", (?T(<<"has been banned">>))/binary, |
519 |
|
"</font><br/>">>; |
520 |
|
{kickban, "301", Reason} -> |
521 |
:-( |
<<"<font class=\"mb\">", Nick/binary, " ", (?T(<<"has been banned">>))/binary, ": ", |
522 |
|
(htmlize(Reason, FileFormat))/binary, "</font><br/>">>; |
523 |
|
{kickban, "307", ""} -> |
524 |
:-( |
<<"<font class=\"mk\">", Nick/binary, " ", (?T(<<"has been kicked">>))/binary, |
525 |
|
"</font><br/>">>; |
526 |
|
{kickban, "307", Reason} -> |
527 |
:-( |
<<"<font class=\"mk\">", Nick/binary, " ", (?T(<<"has been kicked">>))/binary, ": ", |
528 |
|
(htmlize(Reason, FileFormat))/binary, "</font><br/>">>; |
529 |
|
{kickban, "321", ""} -> |
530 |
:-( |
<<"<font class=\"mk\">", Nick/binary, " ", |
531 |
|
(?T(<<"has been kicked because of an affiliation change">>))/binary, |
532 |
|
"</font><br/>">>; |
533 |
|
{kickban, "322", ""} -> |
534 |
:-( |
<<"<font class=\"mk\">", Nick/binary, " ", |
535 |
|
(?T(<<"has been kicked because the room has been changed to" |
536 |
|
" members-only">>))/binary, "</font><br/>">>; |
537 |
|
{kickban, "332", ""} -> |
538 |
:-( |
<<"<font class=\"mk\">", Nick/binary, " ", |
539 |
|
(?T(<<"has been kicked because of a system shutdown">>))/binary, "</font><br/>">>; |
540 |
|
{nickchange, OldNick} -> |
541 |
:-( |
<<"<font class=\"mnc\">", (htmlize(OldNick, FileFormat))/binary, " ", |
542 |
|
(?T(<<"is now known as">>))/binary, " ", Nick/binary, "</font><br/>">>; |
543 |
|
{subject, T} -> |
544 |
:-( |
<<"<font class=\"msc\">", Nick/binary, (?T(<<" has set the subject to: ">>))/binary, |
545 |
|
(htmlize(T, NoFollow, FileFormat))/binary, "</font><br/>">>; |
546 |
|
{body, T} -> |
547 |
1 |
case {re:run(T, <<"^/me\s">>, [{capture, none}]), Nick} of |
548 |
|
{_, ""} -> |
549 |
:-( |
<<"<font class=\"msm\">", (htmlize(T, NoFollow, FileFormat))/binary, |
550 |
|
"</font><br/>">>; |
551 |
|
{match, _} -> |
552 |
|
%% Delete "/me " from the beginning. |
553 |
:-( |
<<_Pref:32, SubStr/binary>> = htmlize(T, FileFormat), |
554 |
:-( |
<<"<font class=\"mne\">", Nick/binary, " ", SubStr/binary, "</font><br/>">>; |
555 |
|
{nomatch, _} -> |
556 |
1 |
<<"<font class=\"mn\">", Nick2/binary, "</font> ", |
557 |
|
(htmlize(T, NoFollow, FileFormat))/binary, "<br/>">> |
558 |
|
end; |
559 |
|
{room_existence, RoomNewExistence} -> |
560 |
4 |
<<"<font class=\"mrcm\">", (get_room_existence_string(RoomNewExistence, Lang))/binary, |
561 |
|
"</font><br/>">> |
562 |
|
end, |
563 |
9 |
{Hour, Minute, Second} = Time, |
564 |
9 |
STime = lists:flatten( |
565 |
|
io_lib:format("~2..0w:~2..0w:~2..0w", [Hour, Minute, Second])), |
566 |
9 |
{_, _, Microsecs} = Now, |
567 |
9 |
STimeUnique = list_to_binary(lists:flatten(io_lib:format("~s.~w", [STime, Microsecs]))), |
568 |
|
|
569 |
|
%% Write message |
570 |
9 |
fw(F, <<"<a id=\"", STimeUnique/binary, "\" name=\"", STimeUnique/binary, |
571 |
|
"\" href=\"#", STimeUnique/binary, "\" class=\"ts\">[", |
572 |
|
(list_to_binary(STime))/binary, "]</a> ", Text/binary>>, FileFormat), |
573 |
|
|
574 |
|
%% Close file |
575 |
9 |
file:close(F), |
576 |
9 |
ok. |
577 |
|
|
578 |
|
|
579 |
|
%%---------------------------------------------------------------------- |
580 |
|
%% Utilities |
581 |
|
|
582 |
|
-spec get_room_existence_string('created' | 'destroyed' | 'started' | 'stopped', |
583 |
|
binary()) -> binary(). |
584 |
1 |
get_room_existence_string(created, Lang) -> ?T(<<"Chatroom is created">>); |
585 |
1 |
get_room_existence_string(destroyed, Lang) -> ?T(<<"Chatroom is destroyed">>); |
586 |
1 |
get_room_existence_string(started, Lang) -> ?T(<<"Chatroom is started">>); |
587 |
1 |
get_room_existence_string(stopped, Lang) -> ?T(<<"Chatroom is stopped">>). |
588 |
|
|
589 |
|
|
590 |
|
-spec get_dateweek(calendar:date(), binary()) -> binary(). |
591 |
|
get_dateweek(Date, Lang) -> |
592 |
2 |
Weekday = case calendar:day_of_the_week(Date) of |
593 |
:-( |
1 -> ?T(<<"Monday">>); |
594 |
:-( |
2 -> ?T(<<"Tuesday">>); |
595 |
:-( |
3 -> ?T(<<"Wednesday">>); |
596 |
2 |
4 -> ?T(<<"Thursday">>); |
597 |
:-( |
5 -> ?T(<<"Friday">>); |
598 |
:-( |
6 -> ?T(<<"Saturday">>); |
599 |
:-( |
7 -> ?T(<<"Sunday">>) |
600 |
|
end, |
601 |
2 |
{Y, M, D} = Date, |
602 |
2 |
Month = case M of |
603 |
2 |
1 -> ?T(<<"January">>); |
604 |
:-( |
2 -> ?T(<<"February">>); |
605 |
:-( |
3 -> ?T(<<"March">>); |
606 |
:-( |
4 -> ?T(<<"April">>); |
607 |
:-( |
5 -> ?T(<<"May">>); |
608 |
:-( |
6 -> ?T(<<"June">>); |
609 |
:-( |
7 -> ?T(<<"July">>); |
610 |
:-( |
8 -> ?T(<<"August">>); |
611 |
:-( |
9 -> ?T(<<"September">>); |
612 |
:-( |
10 -> ?T(<<"October">>); |
613 |
:-( |
11 -> ?T(<<"November">>); |
614 |
:-( |
12 -> ?T(<<"December">>) |
615 |
|
end, |
616 |
2 |
case Lang of |
617 |
2 |
<<"en">> -> list_to_binary( |
618 |
|
lists:flatten(io_lib:format("~s, ~s ~w, ~w", [Weekday, Month, D, Y]))); |
619 |
:-( |
<<"es">> -> list_to_binary( |
620 |
|
lists:flatten(io_lib:format("~s ~w de ~s de ~w", [Weekday, D, Month, Y]))); |
621 |
:-( |
_ -> list_to_binary( |
622 |
|
lists:flatten(io_lib:format("~s, ~w ~s ~w", [Weekday, D, Month, Y]))) |
623 |
|
end. |
624 |
|
|
625 |
|
|
626 |
|
-spec make_dir_rec(binary()) -> 'ok' | {'error', atom()}. |
627 |
|
make_dir_rec(Dir) -> |
628 |
9 |
case file:read_file_info(Dir) of |
629 |
|
{ok, _} -> |
630 |
2 |
ok; |
631 |
|
{error, enoent} -> |
632 |
7 |
DirS = filename:split(Dir), |
633 |
7 |
DirR = lists:sublist(DirS, length(DirS)-1), |
634 |
7 |
make_dir_rec(filename:join(DirR)), |
635 |
7 |
file:make_dir(Dir) |
636 |
|
end. |
637 |
|
|
638 |
|
|
639 |
|
%% {ok, F1}=file:open("valid-xhtml10.png", [read]). |
640 |
|
%% {ok, F1b}=file:read(F1, 1000000). |
641 |
|
%% c("../../ejabberd/src/jlib.erl"). |
642 |
|
%% jlib:encode_base64(F1b). |
643 |
|
|
644 |
|
image_base64(<<"powered-by-erlang.png">>) -> |
645 |
2 |
<<"iVBORw0KGgoAAAANSUhEUgAAAGUAAAAfCAYAAAD+xQNoAAADN0lEQVRo3u1a" |
646 |
|
"P0waURz+rjGRRQ+nUyRCYmJyDPTapDARaSIbTUjt1gVSh8ZW69aBAR0cWLSx" |
647 |
|
"CXWp59LR1jbdqKnGxoQuRZZrSYyHEVM6iZMbHewROA7u3fHvkr5vOn737vcu" |
648 |
|
"33ffu9/vcQz+gef5Cij6CkmSGABgFEH29r5SVvqIsTEOHo8HkiQxDBXEOjg9" |
649 |
|
"PcHc3BxuUSqsI8jR0REAUFGsCCoKFYWCBAN6AxyO0Z7cyMXFb6oGqSgAsIrJ" |
650 |
|
"ut9hMQlvdNbUhKWshLd3HtTF4jihShgVpRaBxKKmIGX5HL920/hz/BM2+zAm" |
651 |
|
"pn2YioQaxnECj0BiEYcrG0Tzzc8/rfudSm02jaVSm9Vr1MdG8rSKKXlJ7lHr" |
652 |
|
"fjouCut2IrC82BDPbe/gc+xlXez7KxEz63H4lmIN473Rh8Si1BKhRY6aEJI8" |
653 |
|
"pLmbjSPN0xOnBBILmg5RC6Lg28preKOzsNmHG8R1Bf0o7GdMucUslDy1pJLG" |
654 |
|
"2sndVVG0lq3c9vum4zmBR1kuwiYMN5ybmCYXxQg57ThFOTYznzpPO+IQi+IK" |
655 |
|
"+jXjg/YhuIJ+cIIHg+wQJoJ+2N3jYN3Olvk4ge/IU98spne+FfGtlslm16nn" |
656 |
|
"a8fduntfDscoVjGJqUgIjz686ViFUdjP4N39x9Xq638viZVtlq2tLXKncLf5" |
657 |
|
"ticuZSWU5XOUshJKxxKtfdtdvs4OyNb/68urKvlluYizgwwu5SLK8jllu1t9" |
658 |
|
"ihYOlzdwdpBBKSvh+vKKzHkCj1JW3y1m+hSj13WjqOiJKK0qpXKhSFxJAYBv" |
659 |
|
"KYaZ9TjWRu4SiWi2LyDtb6wghGmn5HfTml16ILGA/G5al2DW7URYTFYrOU7g" |
660 |
|
"icQ020sYqYDM9CbdgqFd4vzHL03JfvLjk6ZgADAVCSEsJvHsdL+utNYrm2uf" |
661 |
|
"ZDVZSkzPKaQkW8kthpyS297BvRdRzR6DdTurJbPy9Ov1K6xr3HBPQuIMowR3" |
662 |
|
"asegUyDuU9SuUG+dmIGyZ0b7FBN9St3WunyC5yMsrVv7uXzRP58s/qKn6C4q" |
663 |
|
"lQoVxVIvd4YBwzBUFKs6ZaD27U9hEdcAN98Sx2IxykafIYrizbfESoB+dd9/" |
664 |
|
"KF/d/wX3cJvREzl1vAAAAABJRU5ErkJggg==">>; |
665 |
|
image_base64(<<"valid-xhtml10.png">>) -> |
666 |
2 |
<<"iVBORw0KGgoAAAANSUhEUgAAAFgAAAAfCAMAAAEjEcpEAAACiFBMVEUAAADe" |
667 |
|
"5+fOezmtra3ejEKlhELvvWO9WlrehELOe3vepaWclHvetVLGc3PerVKcCAj3" |
668 |
|
"vVqUjHOUe1JjlL0xOUpjjL2UAAC91ueMrc7vrVKlvdbW3u+EpcbO3ufO1ucY" |
669 |
|
"WpSMKQi9SiF7e3taWkoQEAiMczkQSoxaUkpzc3O1lEoICACEazEhGAgIAACE" |
670 |
|
"YzFra2utjELWcznGnEr/7+9jY2POazHOYzGta2NShLVrlL05OUqctdacCADG" |
671 |
|
"a2ucAADGpVqUtc61ORg5OTmlUikYGAiUezl7YzEYEAiUczkxMTG9nEqtIRDe" |
672 |
|
"3t4AMXu9lEoQCACMazEAKXspKSmljFrW1ta1jELOzs7n7/fGxsa9pVqEOSkp" |
673 |
|
"Y5xznL29tZxahLXOpVr/99ZrY1L/79ZjUiljSikAOYTvxmMAMYScezmchFqU" |
674 |
|
"czGtlFp7c2utjFqUlJStxt73///39/9Ce61CSkq9xsZznMbW5+9Cc62MjIxC" |
675 |
|
"Qkrv9/fv7/fOzsbnlErWjIz/3mtCORhza1IpIRBzWjH/1mtCMRhzY1L/zmvn" |
676 |
|
"vVpSQiHOpVJrUinntVr3zmOEc1L3xmNaWlq1nFo5QkrGWim1lFoISpRSUlK1" |
677 |
|
"zt4hWpwASoz///////8xa6WUaykAQoxKe61KSkp7nMbWtWPe5+9jWlL39/f3" |
678 |
|
"9/fWrWNCQkLera3nvWPv7+85MRjntWPetVp7c1IxKRCUlHtKORh7a1IxIRCU" |
679 |
|
"jHtaSiHWrVIpIQhzWinvvVpaQiH/1mPWpVKMe1L/zmP/xmNrUiGErc4YGBj/" |
680 |
|
"73PG1ucQWpT/53O9nFoQUpS1SiEQEBC9zt69vb05c6UISoxSUko5a6UICAhS" |
681 |
|
"SkohUpS1tbXetWMAQoSUgD+kAAAA2HRSTlP/////////iP9sSf//dP//////" |
682 |
|
"//////////////////////////////////////////8M////////////ef//" |
683 |
|
"////////////////////////////////////////////////////////////" |
684 |
|
"//////////////////////9d////////////////////////////////////" |
685 |
|
"AP//////////////CP//RP//////////////////////////////////////" |
686 |
|
"//////////////////////9xPp1gAAAFvUlEQVR42pVWi18URRwfy7vsYUba" |
687 |
|
"iqBRBFmICUQGVKcZckQeaRJQUCLeycMSfKGH0uo5NELpIvGQGzokvTTA85VH" |
688 |
|
"KTpbRoeJnPno/p1+M7t3txj20e/Nzu7Ofve7v/k9Zg4Vc+wRQMW0eyLx1ZSA" |
689 |
|
"NeBDxVmxZZSwEUYkGAewm1eIBOMRvhv1UA+q8KXIVuxGdCelFYwxAnxOrxgb" |
690 |
|
"Y8Ti1t4VA0QHYz4x3FnVC8OVLXv9fkKGSWDoW/4lG6VbdtBblesOs+MjmEmz" |
691 |
|
"JKNIJWFEfEQTCWNPFKvcKEymjLO1b8bwYQd1hCiiDCl5KsrDCIlhj4fSuvcp" |
692 |
|
"fSpgJmyv6dzeZv+nMPx3dhbt94II07/JZliEtm1N2RIYPkTYshwYm245a/zk" |
693 |
|
"WjJwcyFh6ZIcYxxmqiaDSYxhOhFUsqngi3Fzcj3ljdYDNE9uzA1YD/5MhnzW" |
694 |
|
"1KRqF7mYG8jFYXLcfLpjOe2LA0fuGqQrQHl10sdK0sFcFSOSlzF0BgXQH9h3" |
695 |
|
"QZDBI0ccNEhftjXuippBDD2/eMRiETmwwNEYHyqhdDyo22w+3QHuNbdve5a7" |
696 |
|
"eOkHmDVJ0ixNmfbz1h0qo/Q6GuSB2wQJQbpOjOQAl7woWSRJ0m2ewhvAOUiY" |
697 |
|
"YtZtaZL0CZZmtmVOQttLfr/dbveLZodrfrL7W75wG/JjqkQxoNTtNsTKELQp" |
698 |
|
"QL6/D5loaSmyTT8TUhsmi8iFA0hZiyltf7OiNKdarRm5w2So2lTNdPLuIzR+" |
699 |
|
"AiLj8VTRJaj0LmX4VhJ27f/VJV/yycilWPOrk8NkXi7Qqmj5bHqVZlJKZIRk" |
700 |
|
"1wFzKrt0WUbnXMPJ1fk4TJ5oWBA61p1V76DeIs0MX+s3GxRlA1vtw83KhgNp" |
701 |
|
"hc1nyErLO5zcvbOsrq+scbZnpzc6QVFPenLwGxmC+BOfYI+DN55QYddh4Q/N" |
702 |
|
"E/yGYYj4TOGNngQavAZnzzTovEA+kcMJ+247uYexNA+4Fsvjmuv662jsWxPZ" |
703 |
|
"x2xg890bYMYnTgya7bjmCiEY0qgJ0vMF3c+NoFdPyzxz6V3Uxs3AOWCDchRv" |
704 |
|
"OsQtBrbFsrT2fhHEc7ByGzu/dA4IO0A3HdfeP9yMqAwP6NPEb6cbwn0PWVU1" |
705 |
|
"7/FDBQh/CPIrbfcg027IZrsAT/Bf3FNWyn9RSR4cvvwn3e4HFmYPDl/thYcR" |
706 |
|
"Vi8qPEoXVUWBl6FTBFTtnqmKKg5wnlF4wZ1yeLv7TiwXKektE+iDBNicWEyL" |
707 |
|
"pnFhfDkpJc3q2khSPyQBbE0dMJnOoDzTwGsI7cdyMkL5gWqUjCF6Txst/twx" |
708 |
|
"Cv1WzzHoy21ZDQ1xnuDzdPDWR4knr14v0tYn3IxaMFFdiMOlEOJHw1jOQ4sW" |
709 |
|
"t5rQopRkXZhMEi7pmeDCVWBlfUKwhMZ7rsF6elKsvbwiKxgxIdewa3ErsaYo" |
710 |
|
"mCVZFYJb0GUu3JqGUNoplBxYiYby8vLBFWef+Cri4/I1sbQ/1OtYTrNtdXS+" |
711 |
|
"rSe7kQ52eSObL99/iErCWUjCy5W4JLygmCouGfG9x9fmx17XhBuDCaOerbt5" |
712 |
|
"38erta7TFktLvdHghZcCbcPQO33zIJG9kxF5hoVXnzTzRz0r5js8oTj6uyPk" |
713 |
|
"GRf346HOLcasgFexueNUWFPtuFKzjoSFYYedhwVlhsRVYWWJpltv1XPQT1Rl" |
714 |
|
"0bjZIBlb1XujVDzY/Kj4k6Ku3+Z0jo1owjVzDpFTXe1juvBSWNFmNWGZy8Lv" |
715 |
|
"zUl5PN4JCwyNDzbQ0aAj4Zrjz0FatGJJYhvq4j7mGSpvytGFlZtHf2C4o/28" |
716 |
|
"Zu8z7wo7eYPfXysnF0i9NnPh1t1zR7VBb9GqaOXhtTmHQdgMFXE+Z608cnpO" |
717 |
|
"DdZdjL+TuDY44Q38kJXHhccWLoOd9uv1AwwvO+48uu+faCSJPJ1bmy6Thyvp" |
718 |
|
"ivBmYWgjxPDPAp7JTemY/yGKFEiRt/jG/2P79s8KCwoLCgoLC/khUBA5F0Sf" |
719 |
|
"QZ+RYfpNE/4Xosmq7jsZAJsAAAAASUVORK5CYII=">>; |
720 |
|
image_base64(<<"vcss.png">>) -> |
721 |
2 |
<<"iVBORw0KGgoAAAANSUhEUgAAAFgAAAAfCAMAAABUFvrSAAABKVBMVEUAAAAj" |
722 |
|
"Ix8MR51ZVUqAdlmdnZ3ejEWLDAuNjY1kiMG0n2d9fX19Ghfrp1FtbW3y39+3" |
723 |
|
"Ph6lIRNdXV2qJBFcVUhcVUhPT0/dsmpUfLr57+/u7u4/PDWZAACZAADOp1Gd" |
724 |
|
"GxG+SyTgvnNdSySzk16+mkuxw+BOS0BOS0DOzs7MzMy4T09RRDwsJBG+vr73" |
725 |
|
"wV6fkG6eCQRFcLSurq6/X1+ht9nXfz5sepHuwV59ZTHetFjQ2+wMCQQ2ZK5t" |
726 |
|
"WCsmWajsz8+Sq9NMPh4hVaY8MRj///////////////////////9MTEyOp9Lu" |
727 |
|
"8vhXU1A8PDyjOSTBz+YLRJ2rLy8sLCwXTaKujEUcHByDn82dfz7/zGafDw+f" |
728 |
|
"Dw+zRSlzlMcMDAyNcji1tbXf5vIcFgvATJOjAAAAY3RSTlP/8///////////" |
729 |
|
"//////8A//////P/////ov//8//////////////z///T//////////+i////" |
730 |
|
"//////////8w/////6IA/xAgMP//////////8/////////8w0/////////+z" |
731 |
|
"ehebAAACkUlEQVR42u2VfVPTQBDG19VqC6LY+lKrRIxFQaFSBPuSvhBPF8SI" |
732 |
|
"UZK2J5Yav/+HcO8uZdLqTCsU/nKnyWwvk1/unnt2D9ZmH+8/cMAaTRFy+ng6" |
733 |
|
"9/yiwC/+gy8R3McGv5zHvGJEGAdR4eBgi1IbZwevIEZE24pFtBtzG1Q4AoD5" |
734 |
|
"zvw5pEDcJvIQV/TE3/l+H9GnNJwcdABS5wAbFQLMqI98/UReoAaOTlaJsp0z" |
735 |
|
"aHx7LwZvY0BUR2xpWTzqam0gzY8KGzG4MhBCNGucha4QbpETy+Yk/BP85nt7" |
736 |
|
"34AjpQLTsE4ZFpf/dnkUCglXVNYB+OfUZJHvAqAoa45OeuPgm4+Xjtv7xm4N" |
737 |
|
"7PMV4C61+Mrz3H2WImm3ATiWrAiwZRWcUA5Ej4dgIEMxDv6yxHHcNuAutnjv" |
738 |
|
"2HZ1NeuycoVPh0mwC834zZC9Ao5dkZZKwLVGwT+WdLw0YOZ1saEkUDoT+QGW" |
739 |
|
"KZ0E2xpcrPakVW2KXwyUtYEtlEAj3GXD/fYwrryAdeiyGqidQSw1eqtJcA8c" |
740 |
|
"Zq4zXqhPuCBYE1fKJjh/5X6MwRm9c2xf7WVdLf5oSdt64esVIwVAKC1HJ2ol" |
741 |
|
"i8vj3L0YzC4zjkMagt+arDAs6bApbL1RVlWIqrJbreqKZmh4y6VR7rAJeUYD" |
742 |
|
"VRj9VqRXkErpJ9lbEwtE83KlIfeG4p52t7zWIMO1XcaGz54uUyet+hBM7BXX" |
743 |
|
"DS8Xc5+8Gmmbu1xwSoGIokA3oTptQecQ4Iimm/Ew7jwbPfMi3TM91T9XVIGo" |
744 |
|
"+W9xC8oWpugVCXLuwXijjxJ3r/6PjX7nlFua8QmyM+TO/Gja2TTc2Z95C5ua" |
745 |
|
"ewGH6cJi6bJO6Z+TY276eH3tbgy+/3ly3Js+rj66osG/AV5htgaQ9SeRAAAA" |
746 |
|
"AElFTkSuQmCC">>; |
747 |
|
image_base64(<<"powered-by-ejabberd.png">>) -> |
748 |
2 |
<<"iVBORw0KGgoAAAANSUhEUgAAAGUAAAAfCAMAAADJG/NaAAAAw1BMVEUAAAAj" |
749 |
|
"BgYtBAM5AwFCAAAYGAJNAABcAABIDQ5qAAAoJRV7AACFAAAoKSdJHByLAAAw" |
750 |
|
"Lwk1NQA1MzFJKyo4NxtDQQBEQT5KSCxSTgBSUBlgQ0JYSEpZWQJPUU5hYABb" |
751 |
|
"W0ZiYClcW1poaCVwbQRpaDhzYWNsakhuZ2VrbFZ8dwCEgAB3dnd4d2+OjACD" |
752 |
|
"hYKcmACJi4iQkpWspgCYmJm5swCmqazEwACwsbS4ub3X0QLExsPLyszW1Nnc" |
753 |
|
"3ODm5ugMBwAWAwPHm1IFAAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJ" |
754 |
|
"cEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfVCRQOBA7VBkCMAAACcElEQVRI" |
755 |
|
"x72WjXKiMBSFQalIFbNiy1pdrJZaRVYR5deGwPs/VRNBSBB2OjvQO0oYjPfj" |
756 |
|
"5J6bCcdx8i2UldxKcDhk1HbIPwFBF/kHKJfjPSVAyIRHF9rRZ4sUX3EDdWOv" |
757 |
|
"1+u2tESaavpnYTbv9zvd0WwDy3/QcGQXlH5uTxB1l07MJlRpsUei0JF6Qi+O" |
758 |
|
"HyGK7ijXxPklHe/umIllim3iUBMJDIEULxxPP0TVWhhKJoN9fUpdmQLteV8a" |
759 |
|
"DgEAg9gIcTjL4F4L+r4WVKEF+rbJdwYYAoQHY+oQjnGootyKwxapoi73WkyF" |
760 |
|
"FySQBv988naEEp4+YMMec5VUCQDJTscEy7Kc0HsLmqNE7rovDjMpIHHGYeid" |
761 |
|
"Xn4TQcaxMYqP3RV3C8oCl2WvrlSPaNpGZadRnmPGCk8ylM2okAJ4i9TEe1Ke" |
762 |
|
"rsXxSl6jUt5uayiIodirtcKLOaWblj50wiyMv1F9lm9TUDArGAD0FmEpvCUs" |
763 |
|
"VoZy6dW81Fg0aDaHogQa36ekAPG5DDGsbdZrGsrzZUnzvBo1I2tLmuL69kSi" |
764 |
|
"tAweyHKN9b3leDfQMnu3nIIKWfmXnqGVKedJT6QpICbJvf2f8aOsvn68v+k7" |
765 |
|
"/cwUQdPoxaMoRTnKFHNlKsKQphCTOa84u64vpi8bH31CqsbF6lSONRTkTyQG" |
766 |
|
"Arq49/fEvjBwz4eDS2/JpaXRNOoXRD/VmOrDVTJJRIZCTLav3VrqbPvP3vdd" |
767 |
|
"uGEhQJzilncbpSA4F3vsihErO+dayv/sY5/yRE0GDEXCu2VoNiMlo5i+P2Kl" |
768 |
|
"gMEvTNk2eYa5XEyh12Ex17Z8vzQUR3KEPbYd6XG87eC4Ly75RneS5ZYHAAAA" |
769 |
|
"AElFTkSuQmCC">>. |
770 |
|
|
771 |
|
|
772 |
|
-spec create_image_files(<<_:8, _:_*8>>) -> 'ok'. |
773 |
|
create_image_files(ImagesDir) -> |
774 |
2 |
Filenames = [<<"powered-by-ejabberd.png">>, |
775 |
|
<<"powered-by-erlang.png">>, |
776 |
|
<<"valid-xhtml10.png">>, |
777 |
|
<<"vcss.png">> |
778 |
|
], |
779 |
2 |
lists:foreach( |
780 |
|
fun(Filename) -> |
781 |
8 |
FilenameFull = filename:join([ImagesDir, Filename]), |
782 |
8 |
{ok, F} = file:open(FilenameFull, [write]), |
783 |
8 |
Image = jlib:decode_base64(image_base64(Filename)), |
784 |
8 |
io:format(F, "~s", [Image]), |
785 |
8 |
file:close(F) |
786 |
|
end, |
787 |
|
Filenames), |
788 |
2 |
ok. |
789 |
|
|
790 |
|
|
791 |
|
-spec fw(file:io_device(), binary()) -> 'ok'. |
792 |
126 |
fw(F, S) -> fw(F, S, html). |
793 |
|
|
794 |
|
|
795 |
|
-spec fw(file:io_device(), binary(), file_format()) -> 'ok'. |
796 |
|
fw(F, S, FileFormat) -> |
797 |
135 |
S1 = <<S/binary, "~n">>, |
798 |
135 |
S2 = case FileFormat of |
799 |
|
html -> |
800 |
135 |
S1; |
801 |
|
plaintext -> |
802 |
:-( |
re:replace(S1, <<"<[^>]*>">>, <<"">>, [global, {return, binary}]) |
803 |
|
end, |
804 |
135 |
io:format(F, S2, []). |
805 |
|
|
806 |
|
|
807 |
|
-spec put_header(file:io_device(), Room :: room(), Date :: binary(), |
808 |
|
CSSFile :: false | binary(), Lang :: ejabberd:lang(), HourOffset :: integer(), |
809 |
|
DatePrev :: binary(), DateNext :: binary(), TopLink :: tuple(), |
810 |
|
file_format(), OccupantsMap :: #{binary() => [jid_nick_role()]}) -> 'ok'. |
811 |
|
put_header(_, _, _, _, _, _, _, _, _, plaintext, _) -> |
812 |
:-( |
ok; |
813 |
|
put_header(F, Room, Date, CSSFile, Lang, HourOffset, DatePrev, DateNext, TopLink, FileFormat, |
814 |
|
OccupantsMap) -> |
815 |
2 |
fw(F, <<"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"" |
816 |
|
" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">">>), |
817 |
2 |
fw(F, <<"<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"", |
818 |
|
Lang/binary, "\" lang=\"", Lang/binary, "\">">>), |
819 |
2 |
fw(F, <<"<head>">>), |
820 |
2 |
fw(F, <<"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />">>), |
821 |
2 |
fw(F, <<"<title>", (htmlize(Room#room.title))/binary, " - ", Date/binary, "</title>">>), |
822 |
2 |
put_header_css(F, CSSFile), |
823 |
2 |
put_header_script(F), |
824 |
2 |
fw(F, <<"</head>">>), |
825 |
2 |
fw(F, <<"<body>">>), |
826 |
2 |
{TopUrl, TopText} = TopLink, |
827 |
2 |
fw(F, <<"<div style=\"text-align: right;\"><a style=\"color: #AAAAAA; font-family: monospace;" |
828 |
|
" text-decoration: none; font-weight: bold;\" href=\"", TopUrl/binary, "\">", |
829 |
|
TopText/binary, "</a></div>">>), |
830 |
2 |
fw(F, <<"<div class=\"roomtitle\">", (htmlize(Room#room.title))/binary, "</div>">>), |
831 |
2 |
fw(F, <<"<a class=\"roomjid\" href=\"xmpp:", (Room#room.jid)/binary, "?join\">", |
832 |
|
(Room#room.jid)/binary, "</a>">>), |
833 |
2 |
fw(F, <<"<div class=\"logdate\">", Date/binary, "<span class=\"w3c\"><a class=\"nav\" href=\"", |
834 |
|
DatePrev/binary, "\"><</a> <a class=\"nav\" href=\".\/\">^</a>" |
835 |
|
" <a class=\"nav\" href=\"", DateNext/binary, "\">></a></span></div>">>), |
836 |
2 |
case {htmlize(Room#room.subject_author), htmlize(Room#room.subject)} of |
837 |
|
{<<"">>, <<"">>} -> |
838 |
2 |
ok; |
839 |
:-( |
{SuA, Su} -> fw(F, <<"<div class=\"roomsubject\">", SuA/binary, |
840 |
|
(?T(<<" has set the subject to: ">>))/binary, Su/binary, "</div>">>) |
841 |
|
end, |
842 |
2 |
RoomConfig = roomconfig_to_binary(Room#room.config, Lang, FileFormat), |
843 |
2 |
put_room_config(F, RoomConfig, Lang, FileFormat), |
844 |
2 |
Occupants = maps:get(Room#room.jid, OccupantsMap, []), |
845 |
2 |
RoomOccupants = roomoccupants_to_binary(Occupants, FileFormat), |
846 |
2 |
put_room_occupants(F, RoomOccupants, Lang, FileFormat), |
847 |
2 |
TimeOffsetBin = case HourOffset<0 of |
848 |
:-( |
true -> list_to_binary(lists:flatten(io_lib:format("~p", [HourOffset]))); |
849 |
2 |
false -> list_to_binary(lists:flatten(io_lib:format("+~p", [HourOffset]))) |
850 |
|
end, |
851 |
2 |
fw(F, <<"<br/><a class=\"ts\">GMT", TimeOffsetBin/binary, "</a><br/>">>). |
852 |
|
|
853 |
|
|
854 |
|
-spec put_header_css(file:io_device(), 'false' | binary()) -> 'ok'. |
855 |
|
put_header_css(F, false) -> |
856 |
2 |
fw(F, <<"<style type=\"text/css\">">>), |
857 |
2 |
fw(F, <<"<!--">>), |
858 |
2 |
fw(F, <<".ts {color: #AAAAAA; text-decoration: none;}">>), |
859 |
2 |
fw(F, <<".mrcm {color: #009900; font-style: italic; font-weight: bold;}">>), |
860 |
2 |
fw(F, <<".msc {color: #009900; font-style: italic; font-weight: bold;}">>), |
861 |
2 |
fw(F, <<".msm {color: #000099; font-style: italic; font-weight: bold;}">>), |
862 |
2 |
fw(F, <<".mj {color: #009900; font-style: italic;}">>), |
863 |
2 |
fw(F, <<".ml {color: #009900; font-style: italic;}">>), |
864 |
2 |
fw(F, <<".mk {color: #009900; font-style: italic;}">>), |
865 |
2 |
fw(F, <<".mb {color: #009900; font-style: italic;}">>), |
866 |
2 |
fw(F, <<".mnc {color: #009900; font-style: italic;}">>), |
867 |
2 |
fw(F, <<".mn {color: #0000AA;}">>), |
868 |
2 |
fw(F, <<".mne {color: #AA0099;}">>), |
869 |
2 |
fw(F, <<"a.nav {color: #AAAAAA; font-family: monospace; letter-spacing: 3px;" |
870 |
|
" text-decoration: none;}">>), |
871 |
2 |
fw(F, <<"div.roomtitle {border-bottom: #224466 solid 3pt; margin-left: 20pt;}">>), |
872 |
2 |
fw(F, <<"div.roomtitle {color: #336699; font-size: 24px; font-weight: bold;" |
873 |
|
" font-family: sans-serif; letter-spacing: 3px; text-decoration: none;}">>), |
874 |
2 |
fw(F, <<"a.roomjid {color: #336699; font-size: 24px; font-weight: bold;" |
875 |
|
" font-family: sans-serif; letter-spacing: 3px; margin-left: 20pt;" |
876 |
|
" text-decoration: none;}">>), |
877 |
2 |
fw(F, <<"div.logdate {color: #663399; font-size: 20px; font-weight: bold;" |
878 |
|
" font-family: sans-serif; letter-spacing: 2px; border-bottom: #224466 solid 1pt;" |
879 |
|
" margin-left:80pt; margin-top:20px;}">>), |
880 |
2 |
fw(F, <<"div.roomsubject {color: #336699; font-size: 18px; font-family: sans-serif;" |
881 |
|
" margin-left: 80pt; margin-bottom: 10px;}">>), |
882 |
2 |
fw(F, <<"div.rc {color: #336699; font-size: 12px; font-family: sans-serif; margin-left: 50%;" |
883 |
|
" text-align: right; background: #f3f6f9; border-bottom: 1px solid #336699;" |
884 |
|
" border-right: 4px solid #336699;}">>), |
885 |
2 |
fw(F, <<"div.rct {font-weight: bold; background: #e3e6e9; padding-right: 10px;}">>), |
886 |
2 |
fw(F, <<"div.rcos {padding-right: 10px;}">>), |
887 |
2 |
fw(F, <<"div.rcoe {color: green;}">>), |
888 |
2 |
fw(F, <<"div.rcod {color: red;}">>), |
889 |
2 |
fw(F, <<"div.rcoe:after {content: \": v\";}">>), |
890 |
2 |
fw(F, <<"div.rcod:after {content: \": x\";}">>), |
891 |
2 |
fw(F, <<"div.rcot:after {}">>), |
892 |
2 |
fw(F, <<".legend {width: 100%; margin-top: 30px; border-top: #224466 solid 1pt;" |
893 |
|
" padding: 10px 0px 10px 0px; text-align: left;" |
894 |
|
" font-family: monospace; letter-spacing: 2px;}">>), |
895 |
2 |
fw(F, <<".w3c {position: absolute; right: 10px; width: 60%; text-align: right;" |
896 |
|
" font-family: monospace; letter-spacing: 1px;}">>), |
897 |
2 |
fw(F, <<"//-->">>), |
898 |
2 |
fw(F, <<"</style>">>); |
899 |
|
put_header_css(F, CSSFile) -> |
900 |
:-( |
fw(F, <<"<link rel=\"stylesheet\" type=\"text/css\" href=\"", |
901 |
|
CSSFile/binary, "\" media=\"all\">">>). |
902 |
|
|
903 |
|
put_header_script(F) -> |
904 |
2 |
fw(F, <<"<script type=\"text/javascript\">">>), |
905 |
2 |
fw(F, <<"function sh(e) // Show/Hide an element">>), |
906 |
2 |
fw(F, <<"{if(document.getElementById(e).style.display=='none')">>), |
907 |
2 |
fw(F, <<"{document.getElementById(e).style.display='block';}">>), |
908 |
2 |
fw(F, <<"else {document.getElementById(e).style.display='none';}}">>), |
909 |
2 |
fw(F, <<"</script>">>). |
910 |
|
|
911 |
|
|
912 |
|
-spec put_room_config(file:io_device(), any(), ejabberd:lang(), |
913 |
|
file_format()) -> 'ok'. |
914 |
|
put_room_config(_F, _RoomConfig, _Lang, plaintext) -> |
915 |
:-( |
ok; |
916 |
|
put_room_config(F, RoomConfig, Lang, _FileFormat) -> |
917 |
4 |
{Now1, Now2, Now3} = erlang:timestamp(), |
918 |
4 |
NowBin = list_to_binary(lists:flatten(io_lib:format("~p~p~p", [Now1, Now2, Now3]))), |
919 |
4 |
fw(F, <<"<div class=\"rc\">">>), |
920 |
4 |
fw(F, <<"<div class=\"rct\" onclick=\"sh('a", NowBin/binary, "');return false;\">", |
921 |
|
(?T(<<"Room Configuration">>))/binary, "</div>">>), |
922 |
4 |
fw(F, <<"<div class=\"rcos\" id=\"a", NowBin/binary, "\" style=\"display: none;\" ><br/>", |
923 |
|
RoomConfig/binary, "</div>">>), |
924 |
4 |
fw(F, <<"</div>">>). |
925 |
|
|
926 |
|
|
927 |
|
-spec put_room_occupants(file:io_device(), any(), ejabberd:lang(), |
928 |
|
file_format()) -> 'ok'. |
929 |
|
put_room_occupants(_F, _RoomOccupants, _Lang, plaintext) -> |
930 |
:-( |
ok; |
931 |
|
put_room_occupants(F, RoomOccupants, Lang, _FileFormat) -> |
932 |
3 |
{Now1, Now2, Now3} = erlang:timestamp(), |
933 |
3 |
NowBin = list_to_binary(lists:flatten(io_lib:format("~p~p~p", [Now1, Now2, Now3]))), |
934 |
3 |
fw(F, <<"<div class=\"rc\">">>), |
935 |
3 |
fw(F, <<"<div class=\"rct\" onclick=\"sh('o", NowBin/binary, "');return false;\">", |
936 |
|
(?T(<<"Room Occupants">>))/binary, "</div>">>), |
937 |
3 |
fw(F, <<"<div class=\"rcos\" id=\"o", NowBin/binary, "\" style=\"display: none;\" ><br/>", |
938 |
|
RoomOccupants/binary, "</div>">>), |
939 |
3 |
fw(F, <<"</div>">>). |
940 |
|
|
941 |
|
|
942 |
|
%% @doc htmlize |
943 |
|
%% The default behaviour is to ignore the nofollow spam prevention on links |
944 |
|
%% (NoFollow=false) |
945 |
|
htmlize(S1) -> |
946 |
8 |
htmlize(S1, html). |
947 |
|
|
948 |
|
htmlize(S1, FileFormat) -> |
949 |
38 |
htmlize(S1, false, FileFormat). |
950 |
|
|
951 |
|
|
952 |
|
%% @doc The NoFollow parameter tell if the spam prevention should be applied to |
953 |
|
%% the link found. true means 'apply nofollow on links'. |
954 |
|
htmlize(S1, _NoFollow, plaintext) -> |
955 |
:-( |
ReplacementRules = |
956 |
|
[{<<"<">>, <<"[">>}, |
957 |
|
{<<">">>, <<"]">>}], |
958 |
:-( |
lists:foldl(fun({RegExp, Replace}, Acc) -> |
959 |
:-( |
re:replace(Acc, RegExp, Replace, [global, {return, binary}]) |
960 |
|
end, S1, ReplacementRules); |
961 |
|
htmlize(S1, NoFollow, _FileFormat) -> |
962 |
39 |
S2List = binary:split(S1, <<"\n">>, [global]), |
963 |
39 |
lists:foldl( |
964 |
|
fun(Si, Res) -> |
965 |
39 |
Si2 = htmlize2(Si, NoFollow), |
966 |
39 |
case Res of |
967 |
39 |
<<"">> -> Si2; |
968 |
:-( |
_ -> <<Res/binary, "<br/>", Si2/binary>> |
969 |
|
end |
970 |
|
end, |
971 |
|
<<"">>, |
972 |
|
S2List). |
973 |
|
|
974 |
|
htmlize2(S1, NoFollow) -> |
975 |
39 |
ReplacementRules = |
976 |
|
[{<<"\\&">>, <<"\\&">>}, |
977 |
|
{<<"<">>, <<"\\<">>}, |
978 |
|
{<<">">>, <<"\\>">>}, |
979 |
|
{<<"((http|https|ftp)://|(mailto|xmpp):)[^] )\'\"}]+">>, link_regexp(NoFollow)}, |
980 |
|
{<<" ">>, <<"\\ \\ ">>}, |
981 |
|
{<<"\\t">>, <<"\\ \\ \\ \\ ">>}, |
982 |
|
{<<226, 128, 174>>, <<"[RLO]">>}], |
983 |
39 |
lists:foldl(fun({RegExp, Replace}, Acc) -> |
984 |
273 |
re:replace(Acc, RegExp, Replace, [global, {return, binary}]) |
985 |
|
end, S1, ReplacementRules). |
986 |
|
|
987 |
|
%% @doc Regexp link. Add the nofollow rel attribute when required |
988 |
|
link_regexp(false) -> |
989 |
38 |
<<"<a href=\"&\">&</a>">>; |
990 |
|
link_regexp(true) -> |
991 |
1 |
<<"<a href=\"&\" rel=\"nofollow\">&</a>">>. |
992 |
|
|
993 |
|
|
994 |
|
get_room_info(RoomJID, Opts) -> |
995 |
9 |
Title = |
996 |
|
case lists:keysearch(title, 1, Opts) of |
997 |
9 |
{value, {_, T}} -> T; |
998 |
:-( |
false -> <<"">> |
999 |
|
end, |
1000 |
9 |
Subject = |
1001 |
|
case lists:keysearch(subject, 1, Opts) of |
1002 |
9 |
{value, {_, S}} -> S; |
1003 |
:-( |
false -> <<"">> |
1004 |
|
end, |
1005 |
9 |
SubjectAuthor = |
1006 |
|
case lists:keysearch(subject_author, 1, Opts) of |
1007 |
9 |
{value, {_, SA}} -> SA; |
1008 |
:-( |
false -> <<"">> |
1009 |
|
end, |
1010 |
9 |
#room{jid = RoomJID, |
1011 |
|
title = Title, |
1012 |
|
subject = Subject, |
1013 |
|
subject_author = SubjectAuthor, |
1014 |
|
config = Opts |
1015 |
|
}. |
1016 |
|
|
1017 |
|
|
1018 |
|
-spec roomconfig_to_binary(list(), ejabberd:lang(), file_format()) -> binary(). |
1019 |
|
roomconfig_to_binary(Options, Lang, FileFormat) -> |
1020 |
|
%% Get title, if available |
1021 |
4 |
Title = case lists:keysearch(title, 1, Options) of |
1022 |
4 |
{value, Tuple} -> [Tuple]; |
1023 |
:-( |
false -> [] |
1024 |
|
end, |
1025 |
|
|
1026 |
|
%% Remove title from list |
1027 |
4 |
Os1 = lists:keydelete(title, 1, Options), |
1028 |
|
|
1029 |
|
%% Order list |
1030 |
4 |
Os2 = lists:sort(Os1), |
1031 |
|
|
1032 |
|
%% Add title to ordered list |
1033 |
4 |
Options2 = Title ++ Os2, |
1034 |
|
|
1035 |
4 |
lists:foldl( |
1036 |
|
fun({Opt, Val}, R) -> |
1037 |
96 |
case get_roomconfig_text(Opt) of |
1038 |
|
undefined -> |
1039 |
20 |
R; |
1040 |
|
OptT -> |
1041 |
76 |
OptText = ?T(OptT), |
1042 |
76 |
R2 = render_config_value(Opt, Val, OptText, FileFormat), |
1043 |
76 |
<<R/binary, R2/binary>> |
1044 |
|
end |
1045 |
|
end, |
1046 |
|
<<"">>, |
1047 |
|
Options2). |
1048 |
|
|
1049 |
|
-spec render_config_value(Opt :: atom(), |
1050 |
|
Val :: boolean() | string() | binary(), |
1051 |
|
OptText :: binary(), |
1052 |
|
FileFormat :: file_format()) -> binary(). |
1053 |
|
render_config_value(_Opt, false, OptText, _FileFormat) -> |
1054 |
14 |
<<"<div class=\"rcod\">", OptText/binary, "</div>">>; |
1055 |
|
render_config_value(_Opt, true, OptText, _FileFormat) -> |
1056 |
46 |
<<"<div class=\"rcoe\">", OptText/binary, "</div>">>; |
1057 |
|
render_config_value(_Opt, "", OptText, _FileFormat) -> |
1058 |
:-( |
<<"<div class=\"rcod\">", OptText/binary, "</div>">>; |
1059 |
|
render_config_value(password, _T, OptText, _FileFormat) -> |
1060 |
4 |
<<"<div class=\"rcoe\">", OptText/binary, "</div>">>; |
1061 |
|
render_config_value(max_users, T, OptText, FileFormat) -> |
1062 |
4 |
HtmlizedBin = htmlize(list_to_binary(lists:flatten(io_lib:format("~p", [T]))), FileFormat), |
1063 |
4 |
<<"<div class=\"rcot\">", OptText/binary, ": \"", HtmlizedBin/binary, "\"</div>">>; |
1064 |
|
render_config_value(title, T, OptText, FileFormat) -> |
1065 |
4 |
<<"<div class=\"rcot\">", OptText/binary, ": \"", (htmlize(T, FileFormat))/binary, "\"</div>">>; |
1066 |
|
render_config_value(description, T, OptText, FileFormat) -> |
1067 |
4 |
<<"<div class=\"rcot\">", OptText/binary, ": \"", (htmlize(T, FileFormat))/binary, "\"</div>">>; |
1068 |
:-( |
render_config_value(_, T, _OptText, _FileFormat) -> <<"\"", T/binary, "\"">>. |
1069 |
|
|
1070 |
|
-spec get_roomconfig_text(atom()) -> 'undefined' | binary(). |
1071 |
4 |
get_roomconfig_text(title) -> <<"Room title">>; |
1072 |
4 |
get_roomconfig_text(persistent) -> <<"Make room persistent">>; |
1073 |
4 |
get_roomconfig_text(public) -> <<"Make room public searchable">>; |
1074 |
4 |
get_roomconfig_text(public_list) -> <<"Make participants list public">>; |
1075 |
4 |
get_roomconfig_text(password_protected) -> <<"Make room password protected">>; |
1076 |
4 |
get_roomconfig_text(password) -> <<"Password">>; |
1077 |
4 |
get_roomconfig_text(anonymous) -> <<"This room is not anonymous">>; |
1078 |
4 |
get_roomconfig_text(members_only) -> <<"Make room members-only">>; |
1079 |
4 |
get_roomconfig_text(moderated) -> <<"Make room moderated">>; |
1080 |
4 |
get_roomconfig_text(members_by_default) -> <<"Default users as participants">>; |
1081 |
4 |
get_roomconfig_text(allow_change_subj) -> <<"Allow users to change the subject">>; |
1082 |
4 |
get_roomconfig_text(allow_private_messages) -> <<"Allow users to send private messages">>; |
1083 |
4 |
get_roomconfig_text(allow_query_users) -> <<"Allow users to query other users">>; |
1084 |
4 |
get_roomconfig_text(allow_user_invites) -> <<"Allow users to send invites">>; |
1085 |
4 |
get_roomconfig_text(logging) -> <<"Enable logging">>; |
1086 |
4 |
get_roomconfig_text(allow_visitor_nickchange) -> <<"Allow visitors to change nickname">>; |
1087 |
|
get_roomconfig_text(allow_visitor_status) -> |
1088 |
4 |
<<"Allow visitors to send status text in presence updates">>; |
1089 |
4 |
get_roomconfig_text(description) -> <<"Room description">>; |
1090 |
4 |
get_roomconfig_text(max_users) -> <<"Maximum Number of Occupants">>; |
1091 |
20 |
get_roomconfig_text(_) -> undefined. |
1092 |
|
|
1093 |
|
|
1094 |
|
%% @doc Users = [{JID, Nick, Role}] |
1095 |
|
-spec roomoccupants_to_binary([jid_nick_role()], file_format()) -> binary(). |
1096 |
|
roomoccupants_to_binary(Users, _FileFormat) -> |
1097 |
3 |
Res = [role_users_to_string(RoleS, Users1) |
1098 |
3 |
|| {RoleS, Users1} <- group_by_role(Users), Users1 /= []], |
1099 |
3 |
list_to_binary(lists:flatten(["<div class=\"rcot\">", Res, "</div>"])). |
1100 |
|
|
1101 |
|
|
1102 |
|
%% @doc Users = [{JID, Nick, Role}] |
1103 |
|
-spec group_by_role([{jid_nick_role()}]) -> [{string(), string()}]. |
1104 |
|
group_by_role(Users) -> |
1105 |
3 |
{Ms, Ps, Vs, Ns} = |
1106 |
|
lists:foldl( |
1107 |
|
fun({JID, Nick, moderator}, {Mod, Par, Vis, Non}) -> |
1108 |
:-( |
{[{JID, Nick}] ++ Mod, Par, Vis, Non}; |
1109 |
|
({JID, Nick, participant}, {Mod, Par, Vis, Non}) -> |
1110 |
4 |
{Mod, [{JID, Nick}] ++ Par, Vis, Non}; |
1111 |
|
({JID, Nick, visitor}, {Mod, Par, Vis, Non}) -> |
1112 |
:-( |
{Mod, Par, [{JID, Nick}] ++ Vis, Non}; |
1113 |
|
({JID, Nick, none}, {Mod, Par, Vis, Non}) -> |
1114 |
:-( |
{Mod, Par, Vis, [{JID, Nick}] ++ Non} |
1115 |
|
end, |
1116 |
|
{[], [], [], []}, |
1117 |
|
Users), |
1118 |
3 |
case Ms of [] -> []; _ -> [{"Moderator", Ms}] end |
1119 |
3 |
++ case Ps of [] -> []; _ -> [{"Participant", Ps}] end |
1120 |
3 |
++ case Vs of [] -> []; _ -> [{"Visitor", Vs}] end |
1121 |
3 |
++ case Ns of [] -> []; _ -> [{"None", Ns}] end. |
1122 |
|
|
1123 |
|
|
1124 |
|
%% Users = [{JID, Nick}] |
1125 |
|
-spec role_users_to_string(string(), [jid_nick()]) -> [string(), ...]. |
1126 |
|
role_users_to_string(RoleS, Users) -> |
1127 |
2 |
SortedUsers = lists:keysort(2, Users), |
1128 |
2 |
UsersString = [[Nick, "<br/>"] || {_JID, Nick} <- SortedUsers], |
1129 |
2 |
[RoleS, ": ", UsersString]. |
1130 |
|
|
1131 |
|
|
1132 |
803 |
get_proc_name(Host) -> gen_mod:get_module_proc(Host, ?PROCNAME). |
1133 |
|
|
1134 |
|
|
1135 |
|
-spec calc_hour_offset(calendar:datetime()) -> integer(). |
1136 |
|
calc_hour_offset(TimeHere) -> |
1137 |
2 |
TimeZero = calendar:now_to_universal_time(erlang:timestamp()), |
1138 |
2 |
TimeHereHour = calendar:datetime_to_gregorian_seconds(TimeHere) div 3600, |
1139 |
2 |
TimeZeroHour = calendar:datetime_to_gregorian_seconds(TimeZero) div 3600, |
1140 |
2 |
TimeHereHour - TimeZeroHour. |