1 |
%%%----------------------------------------------------------------------
|
2 |
%%% File : mod_ctlextra.erl
|
3 |
%%% Author : Badlop <badlop@ono.com>
|
4 |
%%% Purpose : Adds more commands to ejabberd_ctl
|
5 |
%%% Created : 30 Nov 2006 by Badlop <badlop@ono.com>
|
6 |
%%% Id : $Id: mod_ctlextra.erl 483 2008-01-20 12:37:04Z badlop $
|
7 |
%%%----------------------------------------------------------------------
|
8 |
|
9 |
-module(mod_ctlextra).
|
10 |
-author('badlop@ono.com').
|
11 |
|
12 |
-behaviour(gen_mod).
|
13 |
|
14 |
-export([start/2,
|
15 |
stop/1,
|
16 |
ctl_process/2,
|
17 |
ctl_process/3
|
18 |
]).
|
19 |
|
20 |
-include("ejabberd.hrl").
|
21 |
-include("ejabberd_ctl.hrl").
|
22 |
-include("jlib.hrl").
|
23 |
-include("mod_roster.hrl").
|
24 |
|
25 |
%% Copied from ejabberd_sm.erl
|
26 |
-record(session, {sid, usr, us, priority, info}).
|
27 |
|
28 |
|
29 |
%%-------------
|
30 |
%% gen_mod
|
31 |
%%-------------
|
32 |
|
33 |
start(Host, _Opts) ->
|
34 |
ejabberd_ctl:register_commands(commands_global(), ?MODULE, ctl_process),
|
35 |
ejabberd_ctl:register_commands(Host, commands_host(), ?MODULE, ctl_process).
|
36 |
|
37 |
stop(Host) ->
|
38 |
ejabberd_ctl:unregister_commands(commands_global(), ?MODULE, ctl_process),
|
39 |
ejabberd_ctl:unregister_commands(Host, commands_host(), ?MODULE, ctl_process).
|
40 |
|
41 |
commands_global() ->
|
42 |
[
|
43 |
{"compile file", "recompile and reload file"},
|
44 |
{"load-config file", "load config from file"},
|
45 |
{"remove-node nodename", "remove an ejabberd node from the database"},
|
46 |
|
47 |
%% ejabberd_auth
|
48 |
{"delete-older-users days", "delete users that have not logged in the last 'days'"},
|
49 |
{"set-password user server password", "set password to user@server"},
|
50 |
|
51 |
%% ejd2odbc
|
52 |
{"export2odbc server output", "export Mnesia tables on server to files on output directory"},
|
53 |
|
54 |
%% mod_offline
|
55 |
{"delete-older-messages days", "delete offline messages older than 'days'"},
|
56 |
|
57 |
%% mod_shared_roster
|
58 |
{"srg-create group host name description display", "create the group with options"},
|
59 |
{"srg-delete group host", "delete the group"},
|
60 |
{"srg-user-add user server group host", "add user@server to group on host"},
|
61 |
{"srg-user-del user server group host", "delete user@server from group on host"},
|
62 |
|
63 |
%% mod_vcard
|
64 |
{"vcard-get user host data [data2]", "get data from the vCard of the user"},
|
65 |
{"vcard-set user host data [data2] content", "set data to content on the vCard"},
|
66 |
|
67 |
%% mod_announce
|
68 |
%% announce_send_online host message
|
69 |
%% announce_send_all host, message
|
70 |
|
71 |
%% mod_roster
|
72 |
{"add-rosteritem user1 server1 user2 server2 nick group subs", "Add user2@server2 to user1@server1's roster"},
|
73 |
%%{"", "subs= none, from, to or both"},
|
74 |
%%{"", "example: add-roster peter localhost mike server.com MiKe Employees both"},
|
75 |
%%{"", "will add mike@server.com to peter@localhost roster"},
|
76 |
{"rem-rosteritem user1 server1 user2 server2", "Remove user2@server2 from user1@server1's roster"},
|
77 |
{"pushroster file user server", "push template roster in file to user@server"},
|
78 |
{"pushroster-all file", "push template roster in file to all those users"},
|
79 |
{"push-alltoall server group", "adds all the users to all the users in Group"},
|
80 |
|
81 |
{"status-list status", "list the logged users with status"},
|
82 |
{"status-num status", "number of logged users with status"},
|
83 |
|
84 |
{"stats registeredusers", "number of registered users"},
|
85 |
{"stats onlineusers", "number of logged users"},
|
86 |
{"stats uptime-seconds", "uptime of ejabberd node in seconds"},
|
87 |
|
88 |
%% misc
|
89 |
{"get-cookie", "get the Erlang cookie of this node"},
|
90 |
{"killsession user server resource", "kill a user session"}
|
91 |
].
|
92 |
|
93 |
commands_host() ->
|
94 |
[
|
95 |
%% mod_last
|
96 |
{"num-active-users days", "number of users active in the last 'days'"},
|
97 |
{"status-list status", "list the logged users with status"},
|
98 |
{"status-num status", "number of logged users with status"},
|
99 |
{"stats registeredusers", "number of registered users"},
|
100 |
{"stats onlineusers", "number of logged users"}
|
101 |
].
|
102 |
|
103 |
|
104 |
%%-------------
|
105 |
%% Commands global
|
106 |
%%-------------
|
107 |
|
108 |
ctl_process(_Val, ["blo"]) ->
|
109 |
FResources = "eeeaaa aaa",
|
110 |
io:format("~s", [FResources]),
|
111 |
?STATUS_SUCCESS;
|
112 |
|
113 |
ctl_process(_Val, ["delete-older-messages", Days]) ->
|
114 |
mod_offline:remove_old_messages(list_to_integer(Days)),
|
115 |
?STATUS_SUCCESS;
|
116 |
|
117 |
ctl_process(_Val, ["delete-older-users", Days]) ->
|
118 |
{removed, N, UR} = delete_older_users(list_to_integer(Days)),
|
119 |
io:format("Deleted ~p users: ~p~n", [N, UR]),
|
120 |
?STATUS_SUCCESS;
|
121 |
|
122 |
ctl_process(_Val, ["export2odbc", Server, Output]) ->
|
123 |
Tables = [
|
124 |
{export_last, last},
|
125 |
{export_offline, offline},
|
126 |
{export_passwd, passwd},
|
127 |
{export_private_storage, private_storage},
|
128 |
{export_roster, roster},
|
129 |
{export_vcard, vcard},
|
130 |
{export_vcard_search, vcard_search}],
|
131 |
Export = fun({TableFun, Table}) ->
|
132 |
Filename = filename:join([Output, atom_to_list(Table)++".txt"]),
|
133 |
io:format("Trying to export Mnesia table '~p' on server '~s' to file '~s'~n", [Table, Server, Filename]),
|
134 |
catch ejd2odbc:TableFun(Server, Filename)
|
135 |
end,
|
136 |
lists:foreach(Export, Tables),
|
137 |
?STATUS_SUCCESS;
|
138 |
|
139 |
ctl_process(_Val, ["set-password", User, Server, Password]) ->
|
140 |
ejabberd_auth:set_password(User, Server, Password),
|
141 |
?STATUS_SUCCESS;
|
142 |
|
143 |
ctl_process(_Val, ["vcard-get", User, Server, Data]) ->
|
144 |
{ok, Res} = vcard_get(User, Server, [Data]),
|
145 |
io:format("~s~n", [Res]),
|
146 |
?STATUS_SUCCESS;
|
147 |
|
148 |
ctl_process(_Val, ["vcard-get", User, Server, Data1, Data2]) ->
|
149 |
{ok, Res} = vcard_get(User, Server, [Data1, Data2]),
|
150 |
io:format("~s~n", [Res]),
|
151 |
?STATUS_SUCCESS;
|
152 |
|
153 |
ctl_process(_Val, ["vcard-set", User, Server, Data1, Content]) ->
|
154 |
{ok, Res} = vcard_set(User, Server, [Data1], Content),
|
155 |
io:format("~s~n", [Res]),
|
156 |
?STATUS_SUCCESS;
|
157 |
|
158 |
ctl_process(_Val, ["vcard-set", User, Server, Data1, Data2, Content]) ->
|
159 |
{ok, Res} = vcard_set(User, Server, [Data1, Data2], Content),
|
160 |
io:format("~s~n", [Res]),
|
161 |
?STATUS_SUCCESS;
|
162 |
|
163 |
ctl_process(_Val, ["compile", Module]) ->
|
164 |
compile:file(Module),
|
165 |
?STATUS_SUCCESS;
|
166 |
|
167 |
ctl_process(_Val, ["remove-node", Node]) ->
|
168 |
mnesia:del_table_copy(schema, list_to_atom(Node)),
|
169 |
?STATUS_SUCCESS;
|
170 |
|
171 |
ctl_process(_Val, ["srg-create" | Parameters]) ->
|
172 |
[Group, Host, Name, Description, Display] = group_parameters(Parameters, "'"),
|
173 |
Opts = [{name, Name}, {displayed_groups, [Display]}, {description, Description}],
|
174 |
{atomic, ok} = mod_shared_roster:create_group(Host, Group, Opts),
|
175 |
?STATUS_SUCCESS;
|
176 |
|
177 |
ctl_process(_Val, ["srg-delete", Group, Host]) ->
|
178 |
{atomic, ok} = mod_shared_roster:delete_group(Host, Group),
|
179 |
?STATUS_SUCCESS;
|
180 |
|
181 |
ctl_process(_Val, ["srg-user-add", User, Server, Group, Host]) ->
|
182 |
{atomic, ok} = mod_shared_roster:add_user_to_group(Host, {User, Server}, Group),
|
183 |
?STATUS_SUCCESS;
|
184 |
|
185 |
ctl_process(_Val, ["srg-user-del", User, Server, Group, Host]) ->
|
186 |
{atomic, ok} = mod_shared_roster:remove_user_from_group(Host, {User, Server}, Group),
|
187 |
?STATUS_SUCCESS;
|
188 |
|
189 |
ctl_process(_Val, ["add-rosteritem", LocalUser, LocalServer, RemoteUser, RemoteServer, Nick, Group, Subs]) ->
|
190 |
case add_rosteritem(LocalUser, LocalServer, RemoteUser, RemoteServer, Nick, Group, list_to_atom(Subs), []) of
|
191 |
{atomic, ok} ->
|
192 |
?STATUS_SUCCESS;
|
193 |
{error, Reason} ->
|
194 |
io:format("Can't add ~p@~p to ~p@~p: ~p~n",
|
195 |
[RemoteUser, RemoteServer, LocalUser, LocalServer, Reason]),
|
196 |
?STATUS_ERROR;
|
197 |
{badrpc, Reason} ->
|
198 |
io:format("Can't add roster item to user ~p: ~p~n",
|
199 |
[LocalUser, Reason]),
|
200 |
?STATUS_BADRPC
|
201 |
end;
|
202 |
|
203 |
ctl_process(_Val, ["rem-rosteritem", LocalUser, LocalServer, RemoteUser, RemoteServer]) ->
|
204 |
case rem_rosteritem(LocalUser, LocalServer, RemoteUser, RemoteServer) of
|
205 |
{atomic, ok} ->
|
206 |
?STATUS_SUCCESS;
|
207 |
{error, Reason} ->
|
208 |
io:format("Can't remove ~p@~p to ~p@~p: ~p~n",
|
209 |
[RemoteUser, RemoteServer, LocalUser, LocalServer, Reason]),
|
210 |
?STATUS_ERROR;
|
211 |
{badrpc, Reason} ->
|
212 |
io:format("Can't remove roster item to user ~p: ~p~n",
|
213 |
[LocalUser, Reason]),
|
214 |
?STATUS_BADRPC
|
215 |
end;
|
216 |
|
217 |
ctl_process(_Val, ["pushroster", File, User, Server]) ->
|
218 |
case pushroster(File, User, Server) of
|
219 |
ok ->
|
220 |
?STATUS_SUCCESS;
|
221 |
{error, Reason} ->
|
222 |
io:format("Can't push roster ~p to ~p@~p: ~p~n",
|
223 |
[File, User, Server, Reason]),
|
224 |
?STATUS_ERROR;
|
225 |
{badrpc, Reason} ->
|
226 |
io:format("Can't push roster ~p: ~p~n",
|
227 |
[File, Reason]),
|
228 |
?STATUS_BADRPC
|
229 |
end;
|
230 |
|
231 |
ctl_process(_Val, ["pushroster-all", File]) ->
|
232 |
case pushroster_all([File]) of
|
233 |
ok ->
|
234 |
?STATUS_SUCCESS;
|
235 |
{error, Reason} ->
|
236 |
io:format("Can't push roster ~p: ~p~n",
|
237 |
[File, Reason]),
|
238 |
?STATUS_ERROR;
|
239 |
{badrpc, Reason} ->
|
240 |
io:format("Can't push roster ~p: ~p~n",
|
241 |
[File, Reason]),
|
242 |
?STATUS_BADRPC
|
243 |
end;
|
244 |
|
245 |
ctl_process(_Val, ["push-alltoall", Server, Group]) ->
|
246 |
case push_alltoall(Server, Group) of
|
247 |
ok ->
|
248 |
?STATUS_SUCCESS;
|
249 |
{error, Reason} ->
|
250 |
io:format("Can't push all to all: ~p~n",
|
251 |
[Reason]),
|
252 |
?STATUS_ERROR;
|
253 |
{badrpc, Reason} ->
|
254 |
io:format("Can't push all to all: ~p~n",
|
255 |
[Reason]),
|
256 |
?STATUS_BADRPC
|
257 |
end;
|
258 |
|
259 |
ctl_process(_Val, ["load-config", Path]) ->
|
260 |
case ejabberd_config:load_file(Path) of
|
261 |
{atomic, ok} ->
|
262 |
?STATUS_SUCCESS;
|
263 |
{error, Reason} ->
|
264 |
io:format("Can't load config file ~p: ~p~n",
|
265 |
[filename:absname(Path), Reason]),
|
266 |
?STATUS_ERROR;
|
267 |
{badrpc, Reason} ->
|
268 |
io:format("Can't load config file ~p: ~p~n",
|
269 |
[filename:absname(Path), Reason]),
|
270 |
?STATUS_BADRPC
|
271 |
end;
|
272 |
|
273 |
ctl_process(_Val, ["stats", Stat]) ->
|
274 |
Res = case Stat of
|
275 |
"uptime-seconds" -> uptime_seconds();
|
276 |
"registeredusers" -> mnesia:table_info(passwd, size);
|
277 |
"onlineusers" -> mnesia:table_info(session, size)
|
278 |
end,
|
279 |
io:format("~p~n", [Res]),
|
280 |
?STATUS_SUCCESS;
|
281 |
|
282 |
ctl_process(_Val, ["status-num", Status_required]) ->
|
283 |
ctl_process(_Val, "all", ["status-num", Status_required]);
|
284 |
|
285 |
ctl_process(_Val, ["status-list", Status_required]) ->
|
286 |
ctl_process(_Val, "all", ["status-list", Status_required]);
|
287 |
|
288 |
ctl_process(_Val, ["get-cookie"]) ->
|
289 |
io:format("~s~n", [atom_to_list(erlang:get_cookie())]),
|
290 |
?STATUS_SUCCESS;
|
291 |
|
292 |
ctl_process(_Val, ["killsession", User, Server, Resource]) ->
|
293 |
ejabberd_router:route(
|
294 |
jlib:make_jid("", "", ""),
|
295 |
jlib:make_jid(User, Server, Resource),
|
296 |
{xmlelement, "broadcast", [], [{exit, "killed"}]}),
|
297 |
?STATUS_SUCCESS;
|
298 |
|
299 |
ctl_process(Val, _Args) ->
|
300 |
Val.
|
301 |
|
302 |
|
303 |
%%-------------
|
304 |
%% Commands vhost
|
305 |
%%-------------
|
306 |
|
307 |
ctl_process(_Val, Host, ["num-active-users", Days]) ->
|
308 |
Number = num_active_users(Host, list_to_integer(Days)),
|
309 |
io:format("~p~n", [Number]),
|
310 |
?STATUS_SUCCESS;
|
311 |
|
312 |
ctl_process(_Val, Host, ["stats", Stat]) ->
|
313 |
Res = case Stat of
|
314 |
"registeredusers" -> length(ejabberd_auth:get_vh_registered_users(Host));
|
315 |
"onlineusers" -> length(ejabberd_sm:get_vh_session_list(Host))
|
316 |
end,
|
317 |
io:format("~p~n", [Res]),
|
318 |
?STATUS_SUCCESS;
|
319 |
|
320 |
ctl_process(_Val, Host, ["status-num", Status_required]) ->
|
321 |
Num = length(get_status_list(Host, Status_required)),
|
322 |
io:format("~p~n", [Num]),
|
323 |
?STATUS_SUCCESS;
|
324 |
|
325 |
ctl_process(_Val, Host, ["status-list", Status_required]) ->
|
326 |
Res = get_status_list(Host, Status_required),
|
327 |
[ io:format("~s@~s ~s ~p \"~s\"~n", [U, S, R, P, St]) || {U, S, R, P, St} <- Res],
|
328 |
?STATUS_SUCCESS;
|
329 |
|
330 |
ctl_process(Val, _Host, _Args) ->
|
331 |
Val.
|
332 |
|
333 |
|
334 |
%%-------------
|
335 |
%% Utils
|
336 |
%%-------------
|
337 |
|
338 |
uptime_seconds() ->
|
339 |
trunc(element(1, erlang:statistics(wall_clock))/1000).
|
340 |
|
341 |
get_status_list(Host, Status_required) ->
|
342 |
%% Get list of all logged users
|
343 |
Sessions = ejabberd_sm:dirty_get_my_sessions_list(),
|
344 |
%% Reformat the list
|
345 |
Sessions2 = [ {Session#session.usr, Session#session.sid, Session#session.priority} || Session <- Sessions],
|
346 |
Fhost = case Host of
|
347 |
"all" ->
|
348 |
%% All hosts are requested, so dont filter at all
|
349 |
fun(_, _) -> true end;
|
350 |
_ ->
|
351 |
%% Filter the list, only Host is interesting
|
352 |
fun(A, B) -> A == B end
|
353 |
end,
|
354 |
Sessions3 = [ {Pid, Server, Priority} || {{_User, Server, _Resource}, {_, Pid}, Priority} <- Sessions2, apply(Fhost, [Server, Host])],
|
355 |
%% For each Pid, get its presence
|
356 |
Sessions4 = [ {ejabberd_c2s:get_presence(Pid), Server, Priority} || {Pid, Server, Priority} <- Sessions3],
|
357 |
%% Filter by status
|
358 |
Fstatus = case Status_required of
|
359 |
"all" ->
|
360 |
fun(_, _) -> true end;
|
361 |
_ ->
|
362 |
fun(A, B) -> A == B end
|
363 |
end,
|
364 |
[{User, Server, Resource, Priority, stringize(Status_text)}
|
365 |
|| {{User, Resource, Status, Status_text}, Server, Priority} <- Sessions4,
|
366 |
apply(Fstatus, [Status, Status_required])].
|
367 |
|
368 |
%% Make string more print-friendly
|
369 |
stringize(String) ->
|
370 |
%% Replace newline characters with other code
|
371 |
element(2, regexp:gsub(String, "\n", "\\n")).
|
372 |
|
373 |
add_rosteritem(LU, LS, RU, RS, Nick, Group, Subscription, Xattrs) ->
|
374 |
subscribe(LU, LS, RU, RS, Nick, Group, Subscription, Xattrs),
|
375 |
route_rosteritem(LU, LS, RU, RS, Nick, Group, Subscription),
|
376 |
{atomic, ok}.
|
377 |
|
378 |
subscribe(LocalUser, LocalServer, RemoteUser, RemoteServer, Nick, Group, Subscription, Xattrs) ->
|
379 |
R = #roster{usj = {LocalUser,LocalServer,{RemoteUser,RemoteServer,[]}},
|
380 |
us = {LocalUser,LocalServer},
|
381 |
jid = {RemoteUser,RemoteServer,[]},
|
382 |
name = Nick,
|
383 |
subscription = Subscription, % none, to=you see him, from=he sees you, both
|
384 |
ask = none, % out=send request, in=somebody requests you, none
|
385 |
groups = [Group],
|
386 |
askmessage = Xattrs, % example: [{"category","conference"}]
|
387 |
xs = []},
|
388 |
mnesia:transaction(fun() -> mnesia:write(R) end).
|
389 |
|
390 |
rem_rosteritem(LU, LS, RU, RS) ->
|
391 |
unsubscribe(LU, LS, RU, RS),
|
392 |
route_rosteritem(LU, LS, RU, RS, "", "", "remove"),
|
393 |
{atomic, ok}.
|
394 |
|
395 |
unsubscribe(LocalUser, LocalServer, RemoteUser, RemoteServer) ->
|
396 |
Key = {{LocalUser,LocalServer,{RemoteUser,RemoteServer,[]}},
|
397 |
{LocalUser,LocalServer}},
|
398 |
mnesia:transaction(fun() -> mnesia:delete(roster, Key, write) end).
|
399 |
|
400 |
route_rosteritem(LocalUser, LocalServer, RemoteUser, RemoteServer, Nick, Group, Subscription) ->
|
401 |
LJID = jlib:make_jid(LocalUser, LocalServer, ""),
|
402 |
RJID = jlib:make_jid(RemoteUser, RemoteServer, ""),
|
403 |
ToS = jlib:jid_to_string(LJID),
|
404 |
ItemJIDS = jlib:jid_to_string(RJID),
|
405 |
GroupXML = {xmlelement, "group", [], [{xmlcdata, Group}]},
|
406 |
Item = {xmlelement, "item",
|
407 |
[{"jid", ItemJIDS},
|
408 |
{"name", Nick},
|
409 |
{"subscription", Subscription}],
|
410 |
[GroupXML]},
|
411 |
Query = {xmlelement, "query", [{"xmlns", ?NS_ROSTER}], [Item]},
|
412 |
Packet = {xmlelement, "iq", [{"type", "set"}, {"to", ToS}], [Query]},
|
413 |
ejabberd_router:route(LJID, LJID, Packet).
|
414 |
|
415 |
pushroster(File, User, Server) ->
|
416 |
{ok, [Roster]} = file:consult(File),
|
417 |
subscribe_roster({User, Server, "", User}, Roster).
|
418 |
|
419 |
pushroster_all(File) ->
|
420 |
{ok, [Roster]} = file:consult(File),
|
421 |
subscribe_all(Roster).
|
422 |
|
423 |
subscribe_all(Roster) ->
|
424 |
subscribe_all(Roster, Roster).
|
425 |
subscribe_all([], _) ->
|
426 |
ok;
|
427 |
subscribe_all([User1 | Users], Roster) ->
|
428 |
subscribe_roster(User1, Roster),
|
429 |
subscribe_all(Users, Roster).
|
430 |
|
431 |
subscribe_roster(_, []) ->
|
432 |
ok;
|
433 |
%% Do not subscribe a user to itself
|
434 |
subscribe_roster({Name, Server, Group, Nick}, [{Name, Server, _, _} | Roster]) ->
|
435 |
subscribe_roster({Name, Server, Group, Nick}, Roster);
|
436 |
%% Subscribe Name2 to Name1
|
437 |
subscribe_roster({Name1, Server1, Group1, Nick1}, [{Name2, Server2, Group2, Nick2} | Roster]) ->
|
438 |
subscribe(Name1, Server1, Name2, Server2, Nick2, Group2, both, []),
|
439 |
subscribe_roster({Name1, Server1, Group1, Nick1}, Roster).
|
440 |
|
441 |
push_alltoall(S, G) ->
|
442 |
Users = ejabberd_auth:get_vh_registered_users(S),
|
443 |
Users2 = build_list_users(G, Users, []),
|
444 |
subscribe_all(Users2).
|
445 |
|
446 |
build_list_users(_Group, [], Res) ->
|
447 |
Res;
|
448 |
build_list_users(Group, [{User, Server}|Users], Res) ->
|
449 |
build_list_users(Group, Users, [{User, Server, Group, User}|Res]).
|
450 |
|
451 |
vcard_get(User, Server, Data) ->
|
452 |
[{_, Module, Function, _Opts}] = ets:lookup(sm_iqtable, {?NS_VCARD, Server}),
|
453 |
JID = jlib:make_jid(User, Server, ""),
|
454 |
IQ = #iq{type = get, xmlns = ?NS_VCARD},
|
455 |
IQr = Module:Function(JID, JID, IQ),
|
456 |
Res = case IQr#iq.sub_el of
|
457 |
[A1] ->
|
458 |
case vcard_get(Data, A1) of
|
459 |
false -> no_value;
|
460 |
Elem -> xml:get_tag_cdata(Elem)
|
461 |
end;
|
462 |
[] ->
|
463 |
no_vcard
|
464 |
end,
|
465 |
{ok, Res}.
|
466 |
|
467 |
vcard_get([Data1, Data2], A1) ->
|
468 |
case xml:get_subtag(A1, Data1) of
|
469 |
false -> false;
|
470 |
A2 -> vcard_get([Data2], A2)
|
471 |
end;
|
472 |
|
473 |
vcard_get([Data], A1) ->
|
474 |
xml:get_subtag(A1, Data).
|
475 |
|
476 |
vcard_set(User, Server, Data, Content) ->
|
477 |
[{_, Module, Function, _Opts}] = ets:lookup(sm_iqtable, {?NS_VCARD, Server}),
|
478 |
JID = jlib:make_jid(User, Server, ""),
|
479 |
IQ = #iq{type = get, xmlns = ?NS_VCARD},
|
480 |
IQr = Module:Function(JID, JID, IQ),
|
481 |
|
482 |
%% Get old vcard
|
483 |
A4 = case IQr#iq.sub_el of
|
484 |
[A1] ->
|
485 |
{_, _, _, A2} = A1,
|
486 |
update_vcard_els(Data, Content, A2);
|
487 |
[] ->
|
488 |
update_vcard_els(Data, Content, [])
|
489 |
end,
|
490 |
|
491 |
%% Build new vcard
|
492 |
SubEl = {xmlelement, "vCard", [{"xmlns","vcard-temp"}], A4},
|
493 |
IQ2 = #iq{type=set, sub_el = SubEl},
|
494 |
|
495 |
Module:Function(JID, JID, IQ2),
|
496 |
{ok, "done"}.
|
497 |
|
498 |
update_vcard_els(Data, Content, Els1) ->
|
499 |
Els2 = lists:keysort(2, Els1),
|
500 |
[Data1 | Data2] = Data,
|
501 |
NewEl = case Data2 of
|
502 |
[] ->
|
503 |
{xmlelement, Data1, [], [{xmlcdata,Content}]};
|
504 |
[D2] ->
|
505 |
OldEl = case lists:keysearch(Data1, 2, Els2) of
|
506 |
{value, A} -> A;
|
507 |
false -> {xmlelement, Data1, [], []}
|
508 |
end,
|
509 |
{xmlelement, _, _, ContentOld1} = OldEl,
|
510 |
Content2 = [{xmlelement, D2, [], [{xmlcdata,Content}]}],
|
511 |
ContentOld2 = lists:keysort(2, ContentOld1),
|
512 |
ContentOld3 = lists:keydelete(D2, 2, ContentOld2),
|
513 |
ContentNew = lists:keymerge(2, Content2, ContentOld3),
|
514 |
{xmlelement, Data1, [], ContentNew}
|
515 |
end,
|
516 |
Els3 = lists:keydelete(Data1, 2, Els2),
|
517 |
lists:keymerge(2, [NewEl], Els3).
|
518 |
|
519 |
-record(last_activity, {us, timestamp, status}).
|
520 |
|
521 |
delete_older_users(Days) ->
|
522 |
%% Convert older time
|
523 |
SecOlder = Days*24*60*60,
|
524 |
|
525 |
%% Get current time
|
526 |
{MegaSecs, Secs, _MicroSecs} = now(),
|
527 |
TimeStamp_now = MegaSecs * 1000000 + Secs,
|
528 |
|
529 |
%% Get the list of registered users
|
530 |
Users = ejabberd_auth:dirty_get_registered_users(),
|
531 |
|
532 |
%% For a user, remove if required and answer true
|
533 |
F = fun({LUser, LServer}) ->
|
534 |
%% Check if the user is logged
|
535 |
case ejabberd_sm:get_user_resources(LUser, LServer) of
|
536 |
%% If it isnt
|
537 |
[] ->
|
538 |
%% Look for his last_activity
|
539 |
case mnesia:dirty_read(last_activity, {LUser, LServer}) of
|
540 |
%% If it is
|
541 |
%% existent:
|
542 |
[#last_activity{timestamp = TimeStamp}] ->
|
543 |
%% get his age
|
544 |
Sec = TimeStamp_now - TimeStamp,
|
545 |
%% If he is
|
546 |
if
|
547 |
%% younger than SecOlder:
|
548 |
Sec < SecOlder ->
|
549 |
%% do nothing
|
550 |
false;
|
551 |
%% older:
|
552 |
true ->
|
553 |
%% remove the user
|
554 |
ejabberd_auth:remove_user(LUser, LServer),
|
555 |
true
|
556 |
end;
|
557 |
%% nonexistent:
|
558 |
[] ->
|
559 |
%% remove the user
|
560 |
ejabberd_auth:remove_user(LUser, LServer),
|
561 |
true
|
562 |
end;
|
563 |
%% Else
|
564 |
_ ->
|
565 |
%% do nothing
|
566 |
false
|
567 |
end
|
568 |
end,
|
569 |
%% Apply the function to every user in the list
|
570 |
Users_removed = lists:filter(F, Users),
|
571 |
{removed, length(Users_removed), Users_removed}.
|
572 |
|
573 |
num_active_users(Host, Days) ->
|
574 |
list_last_activity(Host, true, Days).
|
575 |
|
576 |
%% Code based on ejabberd/src/web/ejabberd_web_admin.erl
|
577 |
list_last_activity(Host, Integral, Days) ->
|
578 |
{MegaSecs, Secs, _MicroSecs} = now(),
|
579 |
TimeStamp = MegaSecs * 1000000 + Secs,
|
580 |
TS = TimeStamp - Days * 86400,
|
581 |
case catch mnesia:dirty_select(
|
582 |
last_activity, [{{last_activity, {'_', Host}, '$1', '_'},
|
583 |
[{'>', '$1', TS}],
|
584 |
[{'trunc', {'/',
|
585 |
{'-', TimeStamp, '$1'},
|
586 |
86400}}]}]) of
|
587 |
{'EXIT', _Reason} ->
|
588 |
[];
|
589 |
Vals ->
|
590 |
Hist = histogram(Vals, Integral),
|
591 |
if
|
592 |
Hist == [] ->
|
593 |
0;
|
594 |
true ->
|
595 |
Left = if
|
596 |
Days == infinity ->
|
597 |
0;
|
598 |
true ->
|
599 |
Days - length(Hist)
|
600 |
end,
|
601 |
Tail = if
|
602 |
Integral ->
|
603 |
lists:duplicate(Left, lists:last(Hist));
|
604 |
true ->
|
605 |
lists:duplicate(Left, 0)
|
606 |
end,
|
607 |
lists:nth(Days, Hist ++ Tail)
|
608 |
end
|
609 |
end.
|
610 |
histogram(Values, Integral) ->
|
611 |
histogram(lists:sort(Values), Integral, 0, 0, []).
|
612 |
histogram([H | T], Integral, Current, Count, Hist) when Current == H ->
|
613 |
histogram(T, Integral, Current, Count + 1, Hist);
|
614 |
histogram([H | _] = Values, Integral, Current, Count, Hist) when Current < H ->
|
615 |
if
|
616 |
Integral ->
|
617 |
histogram(Values, Integral, Current + 1, Count, [Count | Hist]);
|
618 |
true ->
|
619 |
histogram(Values, Integral, Current + 1, 0, [Count | Hist])
|
620 |
end;
|
621 |
histogram([], _Integral, _Current, Count, Hist) ->
|
622 |
if
|
623 |
Count > 0 ->
|
624 |
lists:reverse([Count | Hist]);
|
625 |
true ->
|
626 |
lists:reverse(Hist)
|
627 |
end.
|
628 |
|
629 |
group_parameters(Ps, [Char]) ->
|
630 |
{none, Grouped_Ps} = lists:foldl(
|
631 |
fun(P, {State, Res}) ->
|
632 |
case State of
|
633 |
none ->
|
634 |
case P of
|
635 |
[Char | PTail]->
|
636 |
{building, [PTail | Res]};
|
637 |
_ ->
|
638 |
{none, [P | Res]}
|
639 |
end;
|
640 |
building ->
|
641 |
[ResHead | ResTail] = Res,
|
642 |
case lists:last(P) of
|
643 |
Char ->
|
644 |
P2 = lists:sublist(P, length(P)-1),
|
645 |
{none, [ResHead ++ " " ++ P2 | ResTail]};
|
646 |
_ ->
|
647 |
{building, [ResHead ++ " " ++ P | ResTail]}
|
648 |
end
|
649 |
end
|
650 |
end,
|
651 |
{none, []},
|
652 |
Ps),
|
653 |
lists:reverse(Grouped_Ps).
|