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