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