24
1
mirror of https://github.com/processone/ejabberd.git synced 2024-06-02 21:17:12 +02:00

Merge pull request #4118 from badlop/api-version-1

Commands API version 1
This commit is contained in:
badlop 2024-01-05 13:10:06 +01:00 committed by GitHub
commit e26c547afc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 409 additions and 243 deletions

View File

@ -67,42 +67,24 @@
args_example = none :: none | [any()] | '_', args_example = none :: none | [any()] | '_',
result_example = none :: any()}). result_example = none :: any()}).
%% TODO Fix me: Type is not up to date -type ejabberd_commands() :: #ejabberd_commands{name :: atom(),
-type ejabberd_commands() :: #ejabberd_commands{name :: atom(), tags :: [atom()],
tags :: [atom()], desc :: string(),
desc :: string(), longdesc :: string(),
longdesc :: string(), version :: integer(),
version :: integer(), note :: string(),
module :: atom(), weight :: integer(),
function :: atom(), module :: atom(),
args :: [aterm()], function :: atom(),
policy :: open | restricted | admin | user, args :: [aterm()],
access :: [{atom(),atom(),atom()}|atom()], policy :: open | restricted | admin | user,
result :: rterm()}. access :: [{atom(),atom(),atom()}|atom()],
definer :: atom(),
result :: rterm(),
args_rename :: [{atom(),atom()}],
args_desc :: none | [string()] | '_',
result_desc :: none | string() | '_',
args_example :: none | [any()] | '_',
result_example :: any()
}.
%% @type ejabberd_commands() = #ejabberd_commands{
%% name = atom(),
%% tags = [atom()],
%% desc = string(),
%% longdesc = string(),
%% module = atom(),
%% function = atom(),
%% args = [aterm()],
%% result = rterm()
%% }.
%% desc: Description of the command
%% args: Describe the accepted arguments.
%% This way the function that calls the command can format the
%% arguments before calling.
%% @type atype() = integer | string | {tuple, [aterm()]} | {list, aterm()}.
%% Allowed types for arguments are integer, string, tuple and list.
%% @type rtype() = integer | string | atom | {tuple, [rterm()]} | {list, rterm()} | rescode | restuple.
%% A rtype is either an atom or a tuple with two elements.
%% @type aterm() = {Name::atom(), Type::atype()}.
%% An argument term is a tuple with the term name and the term type.
%% @type rterm() = {Name::atom(), Type::rtype()}.
%% A result term is a tuple with the term name and the term type.

View File

@ -450,11 +450,11 @@ delete_obsolete_data() ->
%%%=================================================================== %%%===================================================================
get_commands_spec() -> get_commands_spec() ->
[#ejabberd_commands{name = request_certificate, tags = [acme], [#ejabberd_commands{name = request_certificate, tags = [acme],
desc = "Requests certificates for all or the specified " desc = "Requests certificates for all or some domains",
"domains: all | domain1,domain2,...", longdesc = "Domains can be `all`, or a list of domains separared with comma characters",
module = ?MODULE, function = request_certificate, module = ?MODULE, function = request_certificate,
args_desc = ["Domains for which to acquire a certificate"], args_desc = ["Domains for which to acquire a certificate"],
args_example = ["all | domain.tld,conference.domain.tld,..."], args_example = ["example.com,domain.tld,conference.domain.tld"],
args = [{domains, string}], args = [{domains, string}],
result = {res, restuple}}, result = {res, restuple}},
#ejabberd_commands{name = list_certificates, tags = [acme], #ejabberd_commands{name = list_certificates, tags = [acme],

View File

@ -129,7 +129,7 @@ get_commands_spec() ->
desc = "Reopen the log files after being renamed", desc = "Reopen the log files after being renamed",
longdesc = "This can be useful when an external tool is " longdesc = "This can be useful when an external tool is "
"used for log rotation. See " "used for log rotation. See "
"https://docs.ejabberd.im/admin/guide/troubleshooting/#log-files", "[Log Files](https://docs.ejabberd.im/admin/guide/troubleshooting/#log-files).",
policy = admin, policy = admin,
module = ?MODULE, function = reopen_log, module = ?MODULE, function = reopen_log,
args = [], result = {res, rescode}}, args = [], result = {res, rescode}},
@ -157,9 +157,10 @@ get_commands_spec() ->
result = {levelatom, atom}}, result = {levelatom, atom}},
#ejabberd_commands{name = set_loglevel, tags = [logs], #ejabberd_commands{name = set_loglevel, tags = [logs],
desc = "Set the loglevel", desc = "Set the loglevel",
longdesc = "Possible loglevels: `none`, `emergency`, `alert`, `critical`,
`error`, `warning`, `notice`, `info`, `debug`.",
module = ?MODULE, function = set_loglevel, module = ?MODULE, function = set_loglevel,
args_desc = ["Desired logging level: none | emergency | alert | critical " args_desc = ["Desired logging level"],
"| error | warning | notice | info | debug"],
args_example = ["debug"], args_example = ["debug"],
args = [{loglevel, string}], args = [{loglevel, string}],
result = {res, rescode}}, result = {res, rescode}},
@ -171,7 +172,8 @@ get_commands_spec() ->
result_example = ["mod_configure", "mod_vcard"], result_example = ["mod_configure", "mod_vcard"],
result = {modules, {list, {module, string}}}}, result = {modules, {list, {module, string}}}},
#ejabberd_commands{name = update, tags = [server], #ejabberd_commands{name = update, tags = [server],
desc = "Update the given module, or use the keyword: all", desc = "Update the given module",
longdesc = "To update all the possible modules, use `all`.",
module = ?MODULE, function = update, module = ?MODULE, function = update,
args_example = ["mod_vcard"], args_example = ["mod_vcard"],
args = [{module, string}], args = [{module, string}],
@ -373,7 +375,7 @@ get_commands_spec() ->
result = {res, rescode}}, result = {res, rescode}},
#ejabberd_commands{name = set_master, tags = [cluster], #ejabberd_commands{name = set_master, tags = [cluster],
desc = "Set master node of the clustered Mnesia tables", desc = "Set master node of the clustered Mnesia tables",
longdesc = "If you provide as nodename `self`, this " longdesc = "If `nodename` is set to `self`, then this "
"node will be set as its own master.", "node will be set as its own master.",
module = ?MODULE, function = set_master, module = ?MODULE, function = set_master,
args_desc = ["Name of the erlang node that will be considered master of this node"], args_desc = ["Name of the erlang node that will be considered master of this node"],

View File

@ -86,7 +86,8 @@ get_commands_spec() ->
args_desc = ["Path to file where generated " args_desc = ["Path to file where generated "
"documentation should be stored", "documentation should be stored",
"Regexp matching names of commands or modules " "Regexp matching names of commands or modules "
"that will be included inside generated document", "that will be included inside generated document, "
"or `runtime` to get commands registered at runtime",
"Comma separated list of languages (chosen from `java`, `perl`, `xmlrpc`, `json`) " "Comma separated list of languages (chosen from `java`, `perl`, `xmlrpc`, `json`) "
"that will have example invocation include in markdown document"], "that will have example invocation include in markdown document"],
result_desc = "0 if command failed, 1 when succeeded", result_desc = "0 if command failed, 1 when succeeded",
@ -147,13 +148,25 @@ register_commands(Definer, Commands) ->
lists:foreach( lists:foreach(
fun(Command) -> fun(Command) ->
%% XXX check if command exists %% XXX check if command exists
mnesia:dirty_write(Command#ejabberd_commands{definer = Definer}) mnesia:dirty_write(register_command_prepare(Command, Definer))
%% ?DEBUG("This command is already defined:~n~p", [Command]) %% ?DEBUG("This command is already defined:~n~p", [Command])
end, end,
Commands), Commands),
ejabberd_access_permissions:invalidate(), ejabberd_access_permissions:invalidate(),
ok. ok.
register_command_prepare(Command, Definer) ->
Tags1 = Command#ejabberd_commands.tags,
Tags2 = case Command#ejabberd_commands.version of
0 -> Tags1;
Version -> Tags1 ++ [list_to_atom("v"++integer_to_list(Version))]
end,
Command#ejabberd_commands{definer = Definer, tags = Tags2}.
-spec unregister_commands([ejabberd_commands()]) -> ok. -spec unregister_commands([ejabberd_commands()]) -> ok.
unregister_commands(Commands) -> unregister_commands(Commands) ->

View File

@ -386,7 +386,7 @@ gen_doc(#ejabberd_commands{name=Name, tags=Tags, desc=Desc, longdesc=LongDesc,
ResultText = case Result of ResultText = case Result of
{res,rescode} -> {res,rescode} ->
[?TAG(dl, [gen_param(res, integer, [?TAG(dl, [gen_param(res, integer,
"Status code (0 on success, 1 otherwise)", "Status code (`0` on success, `1` otherwise)",
HTMLOutput)])]; HTMLOutput)])];
{res,restuple} -> {res,restuple} ->
[?TAG(dl, [gen_param(res, string, [?TAG(dl, [gen_param(res, string,
@ -400,9 +400,9 @@ gen_doc(#ejabberd_commands{name=Name, tags=Tags, desc=Desc, longdesc=LongDesc,
[?TAG(dl, [gen_param(RName, Type, ResultDesc, HTMLOutput)])] [?TAG(dl, [gen_param(RName, Type, ResultDesc, HTMLOutput)])]
end end
end, end,
TagsText = [?RAW("*`"++atom_to_list(Tag)++"`* ") || Tag <- Tags], TagsText = ?RAW(string:join(["*`"++atom_to_list(Tag)++"`*" || Tag <- Tags], ", ")),
IsDefinerMod = case Definer of IsDefinerMod = case Definer of
unknown -> true; unknown -> false;
_ -> lists:member(gen_mod, proplists:get_value(behaviour, Definer:module_info(attributes))) _ -> lists:member(gen_mod, proplists:get_value(behaviour, Definer:module_info(attributes)))
end, end,
ModuleText = case IsDefinerMod of ModuleText = case IsDefinerMod of
@ -477,8 +477,16 @@ maybe_add_policy_arguments(#ejabberd_commands{args=Args1, policy=user}=Cmd) ->
maybe_add_policy_arguments(Cmd) -> maybe_add_policy_arguments(Cmd) ->
Cmd. Cmd.
generate_md_output(File, <<"runtime">>, Languages) ->
Cmds = lists:map(fun({N, _, _}) ->
ejabberd_commands:get_command_definition(N)
end, ejabberd_commands:list_commands()),
generate_md_output(File, <<".">>, Languages, Cmds);
generate_md_output(File, RegExp, Languages) -> generate_md_output(File, RegExp, Languages) ->
Cmds = find_commands_definitions(), Cmds = find_commands_definitions(),
generate_md_output(File, RegExp, Languages, Cmds).
generate_md_output(File, RegExp, Languages, Cmds) ->
{ok, RE} = re:compile(RegExp), {ok, RE} = re:compile(RegExp),
Cmds2 = lists:filter(fun(#ejabberd_commands{name=Name, module=Module}) -> Cmds2 = lists:filter(fun(#ejabberd_commands{name=Name, module=Module}) ->
re:run(atom_to_list(Name), RE, [{capture, none}]) == match orelse re:run(atom_to_list(Name), RE, [{capture, none}]) == match orelse

View File

@ -23,8 +23,6 @@
%%% %%%
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
%%% Does not support commands that have arguments with ctypes: list, tuple
-module(ejabberd_ctl). -module(ejabberd_ctl).
-behaviour(gen_server). -behaviour(gen_server).
@ -335,14 +333,14 @@ call_command([CmdString | Args], Auth, _AccessCommands, Version) ->
ArgsFormatted, ArgsFormatted,
CI2, CI2,
Version), Version),
format_result_preliminary(Result, ResultFormat); format_result_preliminary(Result, ResultFormat, Version);
{'EXIT', {function_clause,[{lists,zip,[A1,A2|_], _} | _]}} -> {'EXIT', {function_clause,[{lists,zip,[A1,A2|_], _} | _]}} ->
{NumCompa, TextCompa} = {NumCompa, TextCompa} =
case {length(A1), length(A2)} of case {length(A1), length(A2)} of
{L1, L2} when L1 < L2 -> {L2-L1, "less argument"}; {L1, L2} when L1 < L2 -> {L2-L1, "less argument"};
{L1, L2} when L1 > L2 -> {L1-L2, "more argument"} {L1, L2} when L1 > L2 -> {L1-L2, "more argument"}
end, end,
process(["help" | [CmdString]]), process(["help" | [CmdString]], Version),
{io_lib:format("Error: the command '~ts' requires ~p ~ts.", {io_lib:format("Error: the command '~ts' requires ~p ~ts.",
[CmdString, NumCompa, TextCompa]), [CmdString, NumCompa, TextCompa]),
wrong_command_arguments} wrong_command_arguments}
@ -372,6 +370,13 @@ format_arg(Arg, string) ->
NumChars = integer_to_list(length(Arg)), NumChars = integer_to_list(length(Arg)),
Parse = "~" ++ NumChars ++ "c", Parse = "~" ++ NumChars ++ "c",
format_arg2(Arg, Parse); format_arg2(Arg, Parse);
format_arg(Arg, {list, {_ArgName, ArgFormat}}) ->
[format_arg(Element, ArgFormat) || Element <- string:tokens(Arg, ",")];
format_arg(Arg, {list, ArgFormat}) ->
[format_arg(Element, ArgFormat) || Element <- string:tokens(Arg, ",")];
format_arg(Arg, {tuple, Elements}) ->
Args = string:tokens(Arg, ":"),
list_to_tuple(format_args(Args, Elements));
format_arg(Arg, Format) -> format_arg(Arg, Format) ->
S = unicode:characters_to_binary(Arg, utf8), S = unicode:characters_to_binary(Arg, utf8),
JSON = jiffy:decode(S), JSON = jiffy:decode(S),
@ -385,67 +390,71 @@ format_arg2(Arg, Parse)->
%% Format result %% Format result
%%----------------------------- %%-----------------------------
format_result_preliminary(Result, {A, {list, B}}) -> format_result_preliminary(Result, {A, {list, B}}, Version) ->
format_result(Result, {A, {top_result_list, B}}); format_result(Result, {A, {top_result_list, B}}, Version);
format_result_preliminary(Result, ResultFormat) -> format_result_preliminary(Result, ResultFormat, Version) ->
format_result(Result, ResultFormat). format_result(Result, ResultFormat, Version).
format_result({error, ErrorAtom}, _) -> format_result({error, ErrorAtom}, _, _Version) ->
{io_lib:format("Error: ~p", [ErrorAtom]), make_status(error)}; {io_lib:format("Error: ~p", [ErrorAtom]), make_status(error)};
%% An error should always be allowed to return extended error to help with API. %% An error should always be allowed to return extended error to help with API.
%% Extended error is of the form: %% Extended error is of the form:
%% {error, type :: atom(), code :: int(), Desc :: string()} %% {error, type :: atom(), code :: int(), Desc :: string()}
format_result({error, ErrorAtom, Code, Msg}, _) -> format_result({error, ErrorAtom, Code, Msg}, _, _Version) ->
{io_lib:format("Error: ~p: ~s", [ErrorAtom, Msg]), make_status(Code)}; {io_lib:format("Error: ~p: ~s", [ErrorAtom, Msg]), make_status(Code)};
format_result(Atom, {_Name, atom}) -> format_result(Atom, {_Name, atom}, _Version) ->
io_lib:format("~p", [Atom]); io_lib:format("~p", [Atom]);
format_result(Int, {_Name, integer}) -> format_result(Int, {_Name, integer}, _Version) ->
io_lib:format("~p", [Int]); io_lib:format("~p", [Int]);
format_result([A|_]=String, {_Name, string}) when is_list(String) and is_integer(A) -> format_result([A|_]=String, {_Name, string}, _Version) when is_list(String) and is_integer(A) ->
io_lib:format("~ts", [String]); io_lib:format("~ts", [String]);
format_result(Binary, {_Name, string}) when is_binary(Binary) -> format_result(Binary, {_Name, string}, _Version) when is_binary(Binary) ->
io_lib:format("~ts", [binary_to_list(Binary)]); io_lib:format("~ts", [binary_to_list(Binary)]);
format_result(Atom, {_Name, string}) when is_atom(Atom) -> format_result(Atom, {_Name, string}, _Version) when is_atom(Atom) ->
io_lib:format("~ts", [atom_to_list(Atom)]); io_lib:format("~ts", [atom_to_list(Atom)]);
format_result(Integer, {_Name, string}) when is_integer(Integer) -> format_result(Integer, {_Name, string}, _Version) when is_integer(Integer) ->
io_lib:format("~ts", [integer_to_list(Integer)]); io_lib:format("~ts", [integer_to_list(Integer)]);
format_result(Other, {_Name, string}) -> format_result(Other, {_Name, string}, _Version) ->
io_lib:format("~p", [Other]); io_lib:format("~p", [Other]);
format_result(Code, {_Name, rescode}) -> format_result(Code, {_Name, rescode}, _Version) ->
make_status(Code); make_status(Code);
format_result({Code, Text}, {_Name, restuple}) -> format_result({Code, Text}, {_Name, restuple}, _Version) ->
{io_lib:format("~ts", [Text]), make_status(Code)}; {io_lib:format("~ts", [Text]), make_status(Code)};
format_result([], {_Name, {top_result_list, _ElementsDef}}) -> format_result([], {_Name, {top_result_list, _ElementsDef}}, _Version) ->
""; "";
format_result([FirstElement | Elements], {_Name, {top_result_list, ElementsDef}}) -> format_result([FirstElement | Elements], {_Name, {top_result_list, ElementsDef}}, Version) ->
[format_result(FirstElement, ElementsDef) | [format_result(FirstElement, ElementsDef, Version) |
lists:map( lists:map(
fun(Element) -> fun(Element) ->
["\n" | format_result(Element, ElementsDef)] ["\n" | format_result(Element, ElementsDef, Version)]
end, end,
Elements)]; Elements)];
%% The result is a list of something: [something()] %% The result is a list of something: [something()]
format_result([], {_Name, {list, _ElementsDef}}) -> format_result([], {_Name, {list, _ElementsDef}}, _Version) ->
""; "";
format_result([FirstElement | Elements], {_Name, {list, ElementsDef}}) -> format_result([FirstElement | Elements], {_Name, {list, ElementsDef}}, Version) ->
Separator = case Version of
0 -> ";";
_ -> ","
end,
%% Start formatting the first element %% Start formatting the first element
[format_result(FirstElement, ElementsDef) | [format_result(FirstElement, ElementsDef, Version) |
%% If there are more elements, put always first a newline character %% If there are more elements, put always first a newline character
lists:map( lists:map(
fun(Element) -> fun(Element) ->
[";" | format_result(Element, ElementsDef)] [Separator | format_result(Element, ElementsDef, Version)]
end, end,
Elements)]; Elements)];
@ -453,17 +462,17 @@ format_result([FirstElement | Elements], {_Name, {list, ElementsDef}}) ->
%% NOTE: the elements in the tuple are separated with tabular characters, %% NOTE: the elements in the tuple are separated with tabular characters,
%% if a string is empty, it will be difficult to notice in the shell, %% if a string is empty, it will be difficult to notice in the shell,
%% maybe a different separation character should be used, like ;;? %% maybe a different separation character should be used, like ;;?
format_result(ElementsTuple, {_Name, {tuple, ElementsDef}}) -> format_result(ElementsTuple, {_Name, {tuple, ElementsDef}}, Version) ->
ElementsList = tuple_to_list(ElementsTuple), ElementsList = tuple_to_list(ElementsTuple),
[{FirstE, FirstD} | ElementsAndDef] = lists:zip(ElementsList, ElementsDef), [{FirstE, FirstD} | ElementsAndDef] = lists:zip(ElementsList, ElementsDef),
[format_result(FirstE, FirstD) | [format_result(FirstE, FirstD, Version) |
lists:map( lists:map(
fun({Element, ElementDef}) -> fun({Element, ElementDef}) ->
["\t" | format_result(Element, ElementDef)] ["\t" | format_result(Element, ElementDef, Version)]
end, end,
ElementsAndDef)]; ElementsAndDef)];
format_result(404, {_Name, _}) -> format_result(404, {_Name, _}, _Version) ->
make_status(not_found). make_status(not_found).
make_status(ok) -> ?STATUS_SUCCESS; make_status(ok) -> ?STATUS_SUCCESS;
@ -491,19 +500,24 @@ get_list_commands(Version) ->
tuple_command_help({Name, _Args, Desc}) -> tuple_command_help({Name, _Args, Desc}) ->
{Args, _, _} = ejabberd_commands:get_command_format(Name, admin), {Args, _, _} = ejabberd_commands:get_command_format(Name, admin),
Arguments = [atom_to_list(ArgN) || {ArgN, _ArgF} <- Args], Arguments = [atom_to_list(ArgN) || {ArgN, _ArgF} <- Args],
Prepend = case is_supported_args(Args) of
true -> "";
false -> "*"
end,
CallString = atom_to_list(Name), CallString = atom_to_list(Name),
{CallString, Arguments, Prepend ++ Desc}. {CallString, Arguments, Desc}.
is_supported_args(Args) -> has_tuple_args(Args) ->
lists:all( lists:any(
fun({_Name, Format}) -> fun({_Name, tuple}) -> true;
(Format == integer) ({_Name, {tuple, _}}) -> true;
or (Format == string) ({_Name, {list, SubArg}}) ->
or (Format == binary) has_tuple_args([SubArg]);
(_) -> false
end,
Args).
has_list_args(Args) ->
lists:any(
fun({_Name, list}) -> true;
({_Name, {list, _}}) -> true;
(_) -> false
end, end,
Args). Args).
@ -768,12 +782,13 @@ print_usage_help(MaxC, ShCode) ->
" ejabberdctl ", ?C("help"), " ", ?C("register"), "\n", " ejabberdctl ", ?C("help"), " ", ?C("register"), "\n",
" ejabberdctl ", ?C("help"), " ", ?C("regist*"), "\n", " ejabberdctl ", ?C("help"), " ", ?C("regist*"), "\n",
"\n", "\n",
"Please note that 'ejabberdctl' shows all ejabberd commands,\n", "Some command arguments are lists or tuples, like add_rosteritem and create_room_with_opts.\n",
"even those that cannot be used in the shell with ejabberdctl.\n", "Separate the elements in a list with the , character.\n",
"Those commands can be identified because their description starts with: *\n", "Separate the elements in a tuple with the : character.\n",
"\n", "\n",
"Some commands return lists, like get_roster and get_user_subscriptions.\n", "Some commands results are lists or tuples, like get_roster and get_user_subscriptions.\n",
"In those commands, the elements in the list are separated with: ;\n"], "The elements in a list are separated with a , character.\n",
"The elements in a tuple are separated with a tabular character.\n"],
ArgsDef = [], ArgsDef = [],
C = #ejabberd_commands{ C = #ejabberd_commands{
name = help, name = help,
@ -893,9 +908,13 @@ print_usage_command2(Cmd, C, MaxC, ShCode) ->
_ -> ["", prepare_description(0, MaxC, LongDesc), "\n\n"] _ -> ["", prepare_description(0, MaxC, LongDesc), "\n\n"]
end, end,
NoteEjabberdctl = case is_supported_args(ArgsDef) of NoteEjabberdctlList = case has_list_args(ArgsDef) of
true -> ""; true -> [" ", ?B("Note:"), " In a list argument, separate the elements using the , character for example: one,two,three\n\n"];
false -> [" ", ?B("Note:"), " This command cannot be executed using ejabberdctl. Try ejabberd_xmlrpc.\n\n"] false -> ""
end,
NoteEjabberdctlTuple = case has_tuple_args(ArgsDef) of
true -> [" ", ?B("Note:"), " In a tuple argument, separate the elements using the : character for example: members_only:true\n\n"];
false -> ""
end, end,
case Cmd of case Cmd of
@ -903,7 +922,7 @@ print_usage_command2(Cmd, C, MaxC, ShCode) ->
_ -> print([NameFmt, "\n", ArgsFmt, "\n", ReturnsFmt, _ -> print([NameFmt, "\n", ArgsFmt, "\n", ReturnsFmt,
"\n\n", XmlrpcFmt, TagsFmt, "\n\n", ModuleFmt, DescFmt, "\n\n"], []) "\n\n", XmlrpcFmt, TagsFmt, "\n\n", ModuleFmt, DescFmt, "\n\n"], [])
end, end,
print([LongDescFmt, NoteEjabberdctl], []). print([LongDescFmt, NoteEjabberdctlList, NoteEjabberdctlTuple], []).
format_usage_ctype(Type, _Indentation) format_usage_ctype(Type, _Indentation)
when (Type==atom) or (Type==integer) or (Type==string) or (Type==binary) or (Type==rescode) or (Type==restuple)-> when (Type==atom) or (Type==integer) or (Type==string) or (Type==binary) or (Type==rescode) or (Type==restuple)->

View File

@ -81,7 +81,7 @@
get_commands_spec() -> get_commands_spec() ->
[ [
#ejabberd_commands{name = oauth_issue_token, tags = [oauth], #ejabberd_commands{name = oauth_issue_token, tags = [oauth],
desc = "Issue an oauth token for the given jid", desc = "Issue an [OAuth](https://docs.ejabberd.im/developer/ejabberd-api/oauth/) token for the given jid",
module = ?MODULE, function = oauth_issue_token, module = ?MODULE, function = oauth_issue_token,
args = [{jid, string},{ttl, integer}, {scopes, string}], args = [{jid, string},{ttl, integer}, {scopes, string}],
policy = restricted, policy = restricted,
@ -91,16 +91,28 @@ get_commands_spec() ->
"List of scopes to allow, separated by ';'"], "List of scopes to allow, separated by ';'"],
result = {result, {tuple, [{token, string}, {scopes, string}, {expires_in, string}]}} result = {result, {tuple, [{token, string}, {scopes, string}, {expires_in, string}]}}
}, },
#ejabberd_commands{name = oauth_issue_token, tags = [oauth],
desc = "Issue an [OAuth](https://docs.ejabberd.im/developer/ejabberd-api/oauth/) token for the given jid",
module = ?MODULE, function = oauth_issue_token,
version = 1,
args = [{jid, string}, {ttl, integer}, {scopes, {list, {scope, binary}}}],
policy = restricted,
args_example = ["user@server.com", 3600, ["connected_users_number", "muc_online_rooms"]],
args_desc = ["Jid for which issue token",
"Time to live of generated token in seconds",
"List of scopes to allow"],
result = {result, {tuple, [{token, string}, {scopes, {list, {scope, string}}}, {expires_in, string}]}}
},
#ejabberd_commands{name = oauth_list_tokens, tags = [oauth], #ejabberd_commands{name = oauth_list_tokens, tags = [oauth],
desc = "List oauth tokens, user, scope, and seconds to expire (only Mnesia)", desc = "List [OAuth](https://docs.ejabberd.im/developer/ejabberd-api/oauth/) tokens, user, scope, and seconds to expire (only Mnesia)",
longdesc = "List oauth tokens, their user and scope, and how many seconds remain until expirity", longdesc = "List OAuth tokens, their user and scope, and how many seconds remain until expirity",
module = ?MODULE, function = oauth_list_tokens, module = ?MODULE, function = oauth_list_tokens,
args = [], args = [],
policy = restricted, policy = restricted,
result = {tokens, {list, {token, {tuple, [{token, string}, {user, string}, {scope, string}, {expires_in, string}]}}}} result = {tokens, {list, {token, {tuple, [{token, string}, {user, string}, {scope, string}, {expires_in, string}]}}}}
}, },
#ejabberd_commands{name = oauth_revoke_token, tags = [oauth], #ejabberd_commands{name = oauth_revoke_token, tags = [oauth],
desc = "Revoke authorization for a token", desc = "Revoke authorization for an [OAuth](https://docs.ejabberd.im/developer/ejabberd-api/oauth/) token",
note = "changed in 22.05", note = "changed in 22.05",
module = ?MODULE, function = oauth_revoke_token, module = ?MODULE, function = oauth_revoke_token,
args = [{token, binary}], args = [{token, binary}],
@ -109,7 +121,7 @@ get_commands_spec() ->
result_desc = "Result code" result_desc = "Result code"
}, },
#ejabberd_commands{name = oauth_add_client_password, tags = [oauth], #ejabberd_commands{name = oauth_add_client_password, tags = [oauth],
desc = "Add OAUTH client_id with password grant type", desc = "Add [OAuth](https://docs.ejabberd.im/developer/ejabberd-api/oauth/) client_id with password grant type",
module = ?MODULE, function = oauth_add_client_password, module = ?MODULE, function = oauth_add_client_password,
args = [{client_id, binary}, args = [{client_id, binary},
{client_name, binary}, {client_name, binary},
@ -118,7 +130,7 @@ get_commands_spec() ->
result = {res, restuple} result = {res, restuple}
}, },
#ejabberd_commands{name = oauth_add_client_implicit, tags = [oauth], #ejabberd_commands{name = oauth_add_client_implicit, tags = [oauth],
desc = "Add OAUTH client_id with implicit grant type", desc = "Add [OAuth](https://docs.ejabberd.im/developer/ejabberd-api/oauth/) client_id with implicit grant type",
module = ?MODULE, function = oauth_add_client_implicit, module = ?MODULE, function = oauth_add_client_implicit,
args = [{client_id, binary}, args = [{client_id, binary},
{client_name, binary}, {client_name, binary},
@ -127,7 +139,7 @@ get_commands_spec() ->
result = {res, restuple} result = {res, restuple}
}, },
#ejabberd_commands{name = oauth_remove_client, tags = [oauth], #ejabberd_commands{name = oauth_remove_client, tags = [oauth],
desc = "Remove OAUTH client_id", desc = "Remove [OAuth](https://docs.ejabberd.im/developer/ejabberd-api/oauth/) client_id",
module = ?MODULE, function = oauth_remove_client, module = ?MODULE, function = oauth_remove_client,
args = [{client_id, binary}], args = [{client_id, binary}],
policy = restricted, policy = restricted,
@ -135,8 +147,10 @@ get_commands_spec() ->
} }
]. ].
oauth_issue_token(Jid, TTLSeconds, ScopesString) -> oauth_issue_token(Jid, TTLSeconds, [Head|_] = ScopesString) when is_integer(Head) ->
Scopes = [list_to_binary(Scope) || Scope <- string:tokens(ScopesString, ";")], Scopes = [list_to_binary(Scope) || Scope <- string:tokens(ScopesString, ";")],
oauth_issue_token(Jid, TTLSeconds, Scopes);
oauth_issue_token(Jid, TTLSeconds, Scopes) ->
try jid:decode(list_to_binary(Jid)) of try jid:decode(list_to_binary(Jid)) of
#jid{luser =Username, lserver = Server} -> #jid{luser =Username, lserver = Server} ->
Ctx1 = #oauth_ctx{password = admin_generated}, Ctx1 = #oauth_ctx{password = admin_generated},

View File

@ -238,7 +238,7 @@ do_command(Auth, Command, AttrL, ArgsF, ArgsR,
ArgsFormatted = format_args(rename_old_args(AttrL, ArgsR), ArgsF), ArgsFormatted = format_args(rename_old_args(AttrL, ArgsR), ArgsF),
Result = ejabberd_commands:execute_command2(Command, ArgsFormatted, Auth), Result = ejabberd_commands:execute_command2(Command, ArgsFormatted, Auth),
ResultFormatted = format_result(Result, ResultF), ResultFormatted = format_result(Result, ResultF),
{command_result, ResultFormatted}. {command_result, {struct, [ResultFormatted]}}.
rename_old_args(Args, []) -> rename_old_args(Args, []) ->
Args; Args;
@ -291,6 +291,14 @@ format_args(Args, ArgsFormat) ->
L when is_list(L) -> exit({additional_unused_args, L}) L when is_list(L) -> exit({additional_unused_args, L})
end. end.
format_arg({array, Elements},
{list, {_ElementDefName, ElementDefFormat}})
when is_list(Elements) ->
lists:map(fun (ElementValue) ->
format_arg(ElementValue, ElementDefFormat)
end,
Elements);
format_arg({array, Elements}, format_arg({array, Elements},
{list, {ElementDefName, ElementDefFormat}}) {list, {ElementDefName, ElementDefFormat}})
when is_list(Elements) -> when is_list(Elements) ->
@ -307,11 +315,18 @@ format_arg({array, [{struct, Elements}]},
format_arg(ElementValue, ElementDefFormat) format_arg(ElementValue, ElementDefFormat)
end, end,
Elements); Elements);
%% Old ejabberd 23.10
format_arg({array, [{struct, Elements}]}, format_arg({array, [{struct, Elements}]},
{tuple, ElementsDef}) {tuple, ElementsDef})
when is_list(Elements) -> when is_list(Elements) ->
FormattedList = format_args(Elements, ElementsDef), FormattedList = format_args(Elements, ElementsDef),
list_to_tuple(FormattedList); list_to_tuple(FormattedList);
%% New ejabberd 24.xx
format_arg({struct, Elements},
{tuple, ElementsDef})
when is_list(Elements) ->
FormattedList = format_args(Elements, ElementsDef),
list_to_tuple(FormattedList);
format_arg({array, Elements}, {list, ElementsDef}) format_arg({array, Elements}, {list, ElementsDef})
when is_list(Elements) and is_atom(ElementsDef) -> when is_list(Elements) and is_atom(ElementsDef) ->
[format_arg(Element, ElementsDef) [format_arg(Element, ElementsDef)
@ -336,6 +351,10 @@ process_unicode_codepoints(Str) ->
%% Result %% Result
%% ----------------------------- %% -----------------------------
format_result(Code, {Name, rescode}) ->
{Name, make_status(Code)};
format_result({_Code, Text}, {_Name, restuple}) ->
{text, io_lib:format("~s", [Text])};
format_result({error, Error}, _) when is_list(Error) -> format_result({error, Error}, _) when is_list(Error) ->
throw({error, lists:flatten(Error)}); throw({error, lists:flatten(Error)});
format_result({error, Error}, _) -> format_result({error, Error}, _) ->
@ -346,45 +365,36 @@ format_result({error, _Type, _Code, Error}, _) ->
throw({error, Error}); throw({error, Error});
format_result(String, string) -> lists:flatten(String); format_result(String, string) -> lists:flatten(String);
format_result(Atom, {Name, atom}) -> format_result(Atom, {Name, atom}) ->
{struct, {Name, iolist_to_binary(atom_to_list(Atom))};
[{Name, iolist_to_binary(atom_to_list(Atom))}]};
format_result(Int, {Name, integer}) -> format_result(Int, {Name, integer}) ->
{struct, [{Name, Int}]}; {Name, Int};
format_result([A|_]=String, {Name, string}) when is_list(String) and is_integer(A) -> format_result([A|_]=String, {Name, string}) when is_list(String) and is_integer(A) ->
{struct, [{Name, lists:flatten(String)}]}; {Name, lists:flatten(String)};
format_result(Binary, {Name, string}) when is_binary(Binary) -> format_result(Binary, {Name, string}) when is_binary(Binary) ->
{struct, [{Name, binary_to_list(Binary)}]}; {Name, binary_to_list(Binary)};
format_result(Atom, {Name, string}) when is_atom(Atom) -> format_result(Atom, {Name, string}) when is_atom(Atom) ->
{struct, [{Name, atom_to_list(Atom)}]}; {Name, atom_to_list(Atom)};
format_result(Integer, {Name, string}) when is_integer(Integer) -> format_result(Integer, {Name, string}) when is_integer(Integer) ->
{struct, [{Name, integer_to_list(Integer)}]}; {Name, integer_to_list(Integer)};
format_result(Other, {Name, string}) -> format_result(Other, {Name, string}) ->
{struct, [{Name, io_lib:format("~p", [Other])}]}; {Name, io_lib:format("~p", [Other])};
format_result(String, {Name, binary}) when is_list(String) -> format_result(String, {Name, binary}) when is_list(String) ->
{struct, [{Name, lists:flatten(String)}]}; {Name, lists:flatten(String)};
format_result(Binary, {Name, binary}) when is_binary(Binary) -> format_result(Binary, {Name, binary}) when is_binary(Binary) ->
{struct, [{Name, binary_to_list(Binary)}]}; {Name, binary_to_list(Binary)};
format_result(Code, {Name, rescode}) ->
{struct, [{Name, make_status(Code)}]};
format_result({Code, Text}, {Name, restuple}) -> format_result(Els, {Name, {list, Def}}) ->
{struct, FormattedList = [element(2, format_result(El, Def)) || El <- Els],
[{Name, make_status(Code)}, {Name, {array, FormattedList}};
{text, io_lib:format("~s", [Text])}]};
format_result(Elements, {Name, {list, ElementsDef}}) ->
FormattedList = lists:map(fun (Element) -> format_result(Tuple,
format_result(Element, ElementsDef) {Name, {tuple, Def}}) ->
end, Els = lists:zip(tuple_to_list(Tuple), Def),
Elements), FormattedList = [format_result(El, ElDef) || {El, ElDef} <- Els],
{struct, [{Name, {array, FormattedList}}]}; {Name, {struct, FormattedList}};
format_result(ElementsTuple,
{Name, {tuple, ElementsDef}}) ->
ElementsList = tuple_to_list(ElementsTuple),
ElementsAndDef = lists:zip(ElementsList, ElementsDef),
FormattedList = lists:map(fun ({Element, ElementDef}) ->
format_result(Element, ElementDef)
end,
ElementsAndDef),
{struct, [{Name, {array, FormattedList}}]};
format_result(404, {Name, _}) -> format_result(404, {Name, _}) ->
{struct, [{Name, make_status(not_found)}]}. {struct, [{Name, make_status(not_found)}]}.

View File

@ -113,14 +113,14 @@ depends(_Host, _Opts) ->
%%% %%%
get_commands_spec() -> get_commands_spec() ->
Vcard1FieldsString = "Some vcard field names in get/set_vcard are:\n\n" Vcard1FieldsString = "Some vcard field names in `get`/`set_vcard` are:\n\n"
"* FN - Full Name\n" "* FN - Full Name\n"
"* NICKNAME - Nickname\n" "* NICKNAME - Nickname\n"
"* BDAY - Birthday\n" "* BDAY - Birthday\n"
"* TITLE - Work: Position\n" "* TITLE - Work: Position\n"
"* ROLE - Work: Role\n", "* ROLE - Work: Role\n",
Vcard2FieldsString = "Some vcard field names and subnames in get/set_vcard2 are:\n\n" Vcard2FieldsString = "Some vcard field names and subnames in `get`/`set_vcard2` are:\n\n"
"* N FAMILY - Family name\n" "* N FAMILY - Family name\n"
"* N GIVEN - Given name\n" "* N GIVEN - Given name\n"
"* N MIDDLE - Middle name\n" "* N MIDDLE - Middle name\n"
@ -134,8 +134,8 @@ get_commands_spec() ->
"* ORG ORGNAME - Work: Company\n" "* ORG ORGNAME - Work: Company\n"
"* ORG ORGUNIT - Work: Department\n", "* ORG ORGUNIT - Work: Department\n",
VcardXEP = "For a full list of vCard fields check XEP-0054: vcard-temp at " VcardXEP = "For a full list of vCard fields check [XEP-0054: vcard-temp]"
"https://xmpp.org/extensions/xep-0054.html", "(https://xmpp.org/extensions/xep-0054.html)",
[ [
#ejabberd_commands{name = compile, tags = [erlang], #ejabberd_commands{name = compile, tags = [erlang],
@ -145,8 +145,7 @@ get_commands_spec() ->
args_example = ["/home/me/srcs/ejabberd/mod_example.erl"], args_example = ["/home/me/srcs/ejabberd/mod_example.erl"],
args_desc = ["Filename of erlang source file to compile"], args_desc = ["Filename of erlang source file to compile"],
result = {res, rescode}, result = {res, rescode},
result_example = ok, result_example = ok},
result_desc = "Status code: 0 on success, 1 otherwise"},
#ejabberd_commands{name = get_cookie, tags = [erlang], #ejabberd_commands{name = get_cookie, tags = [erlang],
desc = "Get the Erlang cookie of this node", desc = "Get the Erlang cookie of this node",
module = ?MODULE, function = get_cookie, module = ?MODULE, function = get_cookie,
@ -163,9 +162,9 @@ get_commands_spec() ->
result = {res, integer}, result = {res, integer},
result_example = 0, result_example = 0,
result_desc = "Returns integer code:\n" result_desc = "Returns integer code:\n"
" - 0: code reloaded, module restarted\n" " - `0`: code reloaded, module restarted\n"
" - 1: error: module not loaded\n" " - `1`: error: module not loaded\n"
" - 2: code not reloaded, but module restarted"}, " - `2`: code not reloaded, but module restarted"},
#ejabberd_commands{name = delete_old_users, tags = [accounts, purge], #ejabberd_commands{name = delete_old_users, tags = [accounts, purge],
desc = "Delete users that didn't log in last days, or that never logged", desc = "Delete users that didn't log in last days, or that never logged",
longdesc = "To protect admin accounts, configure this for example:\n" longdesc = "To protect admin accounts, configure this for example:\n"
@ -206,8 +205,7 @@ get_commands_spec() ->
args_example = [<<"peter">>, <<"myserver.com">>], args_example = [<<"peter">>, <<"myserver.com">>],
args_desc = ["User name to check", "Server to check"], args_desc = ["User name to check", "Server to check"],
result = {res, rescode}, result = {res, rescode},
result_example = ok, result_example = ok},
result_desc = "Status code: 0 on success, 1 otherwise"},
#ejabberd_commands{name = check_password, tags = [accounts], #ejabberd_commands{name = check_password, tags = [accounts],
desc = "Check if a password is correct", desc = "Check if a password is correct",
module = ?MODULE, function = check_password, module = ?MODULE, function = check_password,
@ -215,8 +213,7 @@ get_commands_spec() ->
args_example = [<<"peter">>, <<"myserver.com">>, <<"secret">>], args_example = [<<"peter">>, <<"myserver.com">>, <<"secret">>],
args_desc = ["User name to check", "Server to check", "Password to check"], args_desc = ["User name to check", "Server to check", "Password to check"],
result = {res, rescode}, result = {res, rescode},
result_example = ok, result_example = ok},
result_desc = "Status code: 0 on success, 1 otherwise"},
#ejabberd_commands{name = check_password_hash, tags = [accounts], #ejabberd_commands{name = check_password_hash, tags = [accounts],
desc = "Check if the password hash is correct", desc = "Check if the password hash is correct",
longdesc = "Allows hash methods from the Erlang/OTP " longdesc = "Allows hash methods from the Erlang/OTP "
@ -229,8 +226,7 @@ get_commands_spec() ->
args_desc = ["User name to check", "Server to check", args_desc = ["User name to check", "Server to check",
"Password's hash value", "Name of hash method"], "Password's hash value", "Name of hash method"],
result = {res, rescode}, result = {res, rescode},
result_example = ok, result_example = ok},
result_desc = "Status code: 0 on success, 1 otherwise"},
#ejabberd_commands{name = change_password, tags = [accounts], #ejabberd_commands{name = change_password, tags = [accounts],
desc = "Change the password of an account", desc = "Change the password of an account",
module = ?MODULE, function = set_password, module = ?MODULE, function = set_password,
@ -239,8 +235,7 @@ get_commands_spec() ->
args_desc = ["User name", "Server name", args_desc = ["User name", "Server name",
"New password for user"], "New password for user"],
result = {res, rescode}, result = {res, rescode},
result_example = ok, result_example = ok},
result_desc = "Status code: 0 on success, 1 otherwise"},
#ejabberd_commands{name = ban_account, tags = [accounts], #ejabberd_commands{name = ban_account, tags = [accounts],
desc = "Ban an account: kick sessions and set random password", desc = "Ban an account: kick sessions and set random password",
module = ?MODULE, function = ban_account, module = ?MODULE, function = ban_account,
@ -249,8 +244,7 @@ get_commands_spec() ->
args_desc = ["User name to ban", "Server name", args_desc = ["User name to ban", "Server name",
"Reason for banning user"], "Reason for banning user"],
result = {res, rescode}, result = {res, rescode},
result_example = ok, result_example = ok},
result_desc = "Status code: 0 on success, 1 otherwise"},
#ejabberd_commands{name = num_resources, tags = [session], #ejabberd_commands{name = num_resources, tags = [session],
desc = "Get the number of resources of a user", desc = "Get the number of resources of a user",
module = ?MODULE, function = num_resources, module = ?MODULE, function = num_resources,
@ -278,8 +272,7 @@ get_commands_spec() ->
args_desc = ["User name", "Server name", "User's resource", args_desc = ["User name", "Server name", "User's resource",
"Reason for closing session"], "Reason for closing session"],
result = {res, rescode}, result = {res, rescode},
result_example = ok, result_example = ok},
result_desc = "Status code: 0 on success, 1 otherwise"},
#ejabberd_commands{name = status_num_host, tags = [session, statistics], #ejabberd_commands{name = status_num_host, tags = [session, statistics],
desc = "Number of logged users with this status in host", desc = "Number of logged users with this status in host",
policy = admin, policy = admin,
@ -430,6 +423,22 @@ get_commands_spec() ->
"Show: `away`, `chat`, `dnd`, `xa`.", "Status text", "Show: `away`, `chat`, `dnd`, `xa`.", "Status text",
"Priority, provide this value as an integer"], "Priority, provide this value as an integer"],
result = {res, rescode}}, result = {res, rescode}},
#ejabberd_commands{name = set_presence,
tags = [session],
desc = "Set presence of a session",
module = ?MODULE, function = set_presence,
version = 1,
args = [{user, binary}, {host, binary},
{resource, binary}, {type, binary},
{show, binary}, {status, binary},
{priority, integer}],
args_example = [<<"user1">>,<<"myserver.com">>,<<"tka1">>,
<<"available">>,<<"away">>,<<"BB">>, 7],
args_desc = ["User name", "Server name", "Resource",
"Type: `available`, `error`, `probe`...",
"Show: `away`, `chat`, `dnd`, `xa`.", "Status text",
"Priority, provide this value as an integer"],
result = {res, rescode}},
#ejabberd_commands{name = set_nickname, tags = [vcard], #ejabberd_commands{name = set_nickname, tags = [vcard],
desc = "Set nickname in a user's vCard", desc = "Set nickname in a user's vCard",
@ -502,6 +511,20 @@ get_commands_spec() ->
args_desc = ["User name", "Server name", "Contact user name", "Contact server name", args_desc = ["User name", "Server name", "Contact user name", "Contact server name",
"Nickname", "Group", "Subscription"], "Nickname", "Group", "Subscription"],
result = {res, rescode}}, result = {res, rescode}},
#ejabberd_commands{name = add_rosteritem, tags = [roster],
desc = "Add an item to a user's roster (supports ODBC)",
module = ?MODULE, function = add_rosteritem,
version = 1,
args = [{localuser, binary}, {localhost, binary},
{user, binary}, {host, binary},
{nick, binary}, {groups, {list, {group, binary}}},
{subs, binary}],
args_rename = [{localserver, localhost}, {server, host}],
args_example = [<<"user1">>,<<"myserver.com">>,<<"user2">>, <<"myserver.com">>,
<<"User 2">>, [<<"Friends">>, <<"Team 1">>], <<"both">>],
args_desc = ["User name", "Server name", "Contact user name", "Contact server name",
"Nickname", "Groups", "Subscription"],
result = {res, rescode}},
%%{"", "subs= none, from, to or both"}, %%{"", "subs= none, from, to or both"},
%%{"", "example: add-roster peter localhost mike server.com MiKe Employees both"}, %%{"", "example: add-roster peter localhost mike server.com MiKe Employees both"},
%%{"", "will add mike@server.com to peter@localhost roster"}, %%{"", "will add mike@server.com to peter@localhost roster"},
@ -516,52 +539,56 @@ get_commands_spec() ->
result = {res, rescode}}, result = {res, rescode}},
#ejabberd_commands{name = process_rosteritems, tags = [roster], #ejabberd_commands{name = process_rosteritems, tags = [roster],
desc = "List/delete rosteritems that match filter", desc = "List/delete rosteritems that match filter",
longdesc = "Explanation of each argument:\n" longdesc = "Explanation of each argument:\n\n"
" - action: what to do with each rosteritem that " "* `action`: what to do with each rosteritem that "
"matches all the filtering options\n" "matches all the filtering options\n"
" - subs: subscription type\n" "* `subs`: subscription type\n"
" - asks: pending subscription\n" "* `asks`: pending subscription\n"
" - users: the JIDs of the local user\n" "* `users`: the JIDs of the local user\n"
" - contacts: the JIDs of the contact in the roster\n" "* `contacts`: the JIDs of the contact in the roster\n"
"\n" "\n"
" *** Mnesia: \n" "**Mnesia backend:**\n"
"\n" "\n"
"Allowed values in the arguments:\n" "Allowed values in the arguments:\n\n"
" ACTION = list | delete\n" "* `action` = `list` | `delete`\n"
" SUBS = SUB[:SUB]* | any\n" "* `subs` = `any` | SUB[:SUB]*\n"
" SUB = none | from | to | both\n" "* `asks` = `any` | ASK[:ASK]*\n"
" ASKS = ASK[:ASK]* | any\n" "* `users` = `any` | JID[:JID]*\n"
" ASK = none | out | in\n" "* `contacts` = `any` | JID[:JID]*\n"
" USERS = JID[:JID]* | any\n" "\nwhere\n\n"
" CONTACTS = JID[:JID]* | any\n" "* SUB = `none` | `from `| `to` | `both`\n"
" JID = characters valid in a JID, and can use the " "* ASK = `none` | `out` | `in`\n"
"globs: *, ?, ! and [...]\n" "* JID = characters valid in a JID, and can use the "
"globs: `*`, `?`, `!` and `[...]`\n"
"\n" "\n"
"This example will list roster items with subscription " "This example will list roster items with subscription "
"'none', 'from' or 'to' that have any ask property, of " "`none`, `from` or `to` that have any ask property, of "
"local users which JID is in the virtual host " "local users which JID is in the virtual host "
"'example.org' and that the contact JID is either a " "`example.org` and that the contact JID is either a "
"bare server name (without user part) or that has a " "bare server name (without user part) or that has a "
"user part and the server part contains the word 'icq'" "user part and the server part contains the word `icq`"
":\n list none:from:to any *@example.org *:*@*icq*" ":\n `list none:from:to any *@example.org *:*@*icq*`"
"\n\n" "\n\n"
" *** SQL:\n" "**SQL backend:**\n"
"\n" "\n"
"Allowed values in the arguments:\n" "Allowed values in the arguments:\n\n"
" ACTION = list | delete\n" "* `action` = `list` | `delete`\n"
" SUBS = any | none | from | to | both\n" "* `subs` = `any` | SUB\n"
" ASKS = any | none | out | in\n" "* `asks` = `any` | ASK\n"
" USERS = JID\n" "* `users` = JID\n"
" CONTACTS = JID\n" "* `contacts` = JID\n"
" JID = characters valid in a JID, and can use the " "\nwhere\n\n"
"globs: _ and %\n" "* SUB = `none` | `from` | `to` | `both`\n"
"* ASK = `none` | `out` | `in`\n"
"* JID = characters valid in a JID, and can use the "
"globs: `_` and `%`\n"
"\n" "\n"
"This example will list roster items with subscription " "This example will list roster items with subscription "
"'to' that have any ask property, of " "`to` that have any ask property, of "
"local users which JID is in the virtual host " "local users which JID is in the virtual host "
"'example.org' and that the contact JID's " "`example.org` and that the contact JID's "
"server part contains the word 'icq'" "server part contains the word `icq`"
":\n list to any %@example.org %@%icq%", ":\n `list to any %@example.org %@%icq%`",
module = mod_roster, function = process_rosteritems, module = mod_roster, function = process_rosteritems,
args = [{action, string}, {subs, string}, args = [{action, string}, {subs, string},
{asks, string}, {users, string}, {asks, string}, {users, string},
@ -576,8 +603,8 @@ get_commands_spec() ->
#ejabberd_commands{name = get_roster, tags = [roster], #ejabberd_commands{name = get_roster, tags = [roster],
desc = "Get list of contacts in a local user roster", desc = "Get list of contacts in a local user roster",
longdesc = longdesc =
"Subscription can be: \"none\", \"from\", \"to\", \"both\". " "`subscription` can be: `none`, `from`, `to`, `both`.\n\n"
"Pending can be: \"in\", \"out\", \"none\".", "`pending` can be: `in`, `out`, `none`.",
note = "improved in 23.10", note = "improved in 23.10",
policy = user, policy = user,
module = ?MODULE, function = get_roster, module = ?MODULE, function = get_roster,
@ -593,11 +620,12 @@ get_commands_spec() ->
#ejabberd_commands{name = push_roster, tags = [roster], #ejabberd_commands{name = push_roster, tags = [roster],
desc = "Push template roster from file to a user", desc = "Push template roster from file to a user",
longdesc = "The text file must contain an erlang term: a list " longdesc = "The text file must contain an erlang term: a list "
"of tuples with username, servername, group and nick. Example:\n" "of tuples with username, servername, group and nick. For example:\n"
"[{<<\"user1\">>, <<\"localhost\">>, <<\"Workers\">>, <<\"User 1\">>},\n" "`[{\"user1\", \"localhost\", \"Workers\", \"User 1\"},\n"
" {<<\"user2\">>, <<\"localhost\">>, <<\"Workers\">>, <<\"User 2\">>}].\n" " {\"user2\", \"localhost\", \"Workers\", \"User 2\"}].`\n\n"
"When using UTF8 character encoding add /utf8 to certain string. Example:\n" "If there are problems parsing UTF8 character encoding, "
"[{<<\"user2\">>, <<\"localhost\">>, <<\"Workers\"/utf8>>, <<\"User 2\"/utf8>>}].", "provide the corresponding string with the `<<\"STRING\"/utf8>>` syntax, for example:\n"
"`[{\"user2\", \"localhost\", \"Workers\", <<\"User 2\"/utf8>>}]`.",
module = ?MODULE, function = push_roster, module = ?MODULE, function = push_roster,
args = [{file, binary}, {user, binary}, {host, binary}], args = [{file, binary}, {user, binary}, {host, binary}],
args_example = [<<"/home/ejabberd/roster.txt">>, <<"user1">>, <<"localhost">>], args_example = [<<"/home/ejabberd/roster.txt">>, <<"user1">>, <<"localhost">>],
@ -607,8 +635,8 @@ get_commands_spec() ->
desc = "Push template roster from file to all those users", desc = "Push template roster from file to all those users",
longdesc = "The text file must contain an erlang term: a list " longdesc = "The text file must contain an erlang term: a list "
"of tuples with username, servername, group and nick. Example:\n" "of tuples with username, servername, group and nick. Example:\n"
"[{\"user1\", \"localhost\", \"Workers\", \"User 1\"},\n" "`[{\"user1\", \"localhost\", \"Workers\", \"User 1\"},\n"
" {\"user2\", \"localhost\", \"Workers\", \"User 2\"}].", " {\"user2\", \"localhost\", \"Workers\", \"User 2\"}].`",
module = ?MODULE, function = push_roster_all, module = ?MODULE, function = push_roster_all,
args = [{file, binary}], args = [{file, binary}],
args_example = [<<"/home/ejabberd/roster.txt">>], args_example = [<<"/home/ejabberd/roster.txt">>],
@ -624,7 +652,9 @@ get_commands_spec() ->
#ejabberd_commands{name = get_last, tags = [last], #ejabberd_commands{name = get_last, tags = [last],
desc = "Get last activity information", desc = "Get last activity information",
longdesc = "Timestamp is UTC and XEP-0082 format, for example: " longdesc = "Timestamp is UTC and "
"[XEP-0082](https://xmpp.org/extensions/xep-0082.html)"
" format, for example: "
"`2017-02-23T22:25:28.063062Z ONLINE`", "`2017-02-23T22:25:28.063062Z ONLINE`",
module = ?MODULE, function = get_last, module = ?MODULE, function = get_last,
args = [{user, binary}, {host, binary}], args = [{user, binary}, {host, binary}],
@ -681,6 +711,18 @@ get_commands_spec() ->
args_desc = ["Group identifier", "Group server name", "Group name", args_desc = ["Group identifier", "Group server name", "Group name",
"Group description", "Groups to display"], "Group description", "Groups to display"],
result = {res, rescode}}, result = {res, rescode}},
#ejabberd_commands{name = srg_create, tags = [shared_roster_group],
desc = "Create a Shared Roster Group",
module = ?MODULE, function = srg_create,
version = 1,
args = [{group, binary}, {host, binary},
{label, binary}, {description, binary}, {display, {list, {group, binary}}}],
args_rename = [{name, label}],
args_example = [<<"group3">>, <<"myserver.com">>, <<"Group3">>,
<<"Third group">>, [<<"group1">>, <<"group2">>]],
args_desc = ["Group identifier", "Group server name", "Group name",
"Group description", "List of groups to display"],
result = {res, rescode}},
#ejabberd_commands{name = srg_delete, tags = [shared_roster_group], #ejabberd_commands{name = srg_delete, tags = [shared_roster_group],
desc = "Delete a Shared Roster Group", desc = "Delete a Shared Roster Group",
module = ?MODULE, function = srg_delete, module = ?MODULE, function = srg_delete,
@ -782,7 +824,9 @@ get_commands_spec() ->
result = {res, rescode}}, result = {res, rescode}},
#ejabberd_commands{name = stats, tags = [statistics], #ejabberd_commands{name = stats, tags = [statistics],
desc = "Get statistical value: registeredusers onlineusers onlineusersnode uptimeseconds processes", desc = "Get some statistical value for the whole ejabberd server",
longdesc = "Allowed statistics `name` are: `registeredusers`, "
"`onlineusers`, `onlineusersnode`, `uptimeseconds`, `processes`.",
policy = admin, policy = admin,
module = ?MODULE, function = stats, module = ?MODULE, function = stats,
args = [{name, binary}], args = [{name, binary}],
@ -792,7 +836,8 @@ get_commands_spec() ->
result_desc = "Integer statistic value", result_desc = "Integer statistic value",
result = {stat, integer}}, result = {stat, integer}},
#ejabberd_commands{name = stats_host, tags = [statistics], #ejabberd_commands{name = stats_host, tags = [statistics],
desc = "Get statistical value for this host: registeredusers onlineusers", desc = "Get some statistical value for this host",
longdesc = "Allowed statistics `name` are: `registeredusers`, `onlineusers`.",
policy = admin, policy = admin,
module = ?MODULE, function = stats, module = ?MODULE, function = stats,
args = [{name, binary}, {host, binary}], args = [{name, binary}, {host, binary}],
@ -1081,14 +1126,10 @@ get_presence(U, S) ->
{FullJID, Show, Status} {FullJID, Show, Status}
end. end.
set_presence(User, Host, Resource, Type, Show, Status, Priority) set_presence(User, Host, Resource, Type, Show, Status, Priority) when is_binary(Priority) ->
when is_integer(Priority) -> set_presence(User, Host, Resource, Type, Show, Status, binary_to_integer(Priority));
BPriority = integer_to_binary(Priority),
set_presence(User, Host, Resource, Type, Show, Status, BPriority); set_presence(User, Host, Resource, Type, Show, Status, Priority) ->
set_presence(User, Host, Resource, Type, Show, Status, Priority0) ->
Priority = if is_integer(Priority0) -> Priority0;
true -> binary_to_integer(Priority0)
end,
Pres = #presence{ Pres = #presence{
from = jid:make(User, Host, Resource), from = jid:make(User, Host, Resource),
to = jid:make(User, Host), to = jid:make(User, Host),
@ -1286,14 +1327,16 @@ update_vcard_els(Data, ContentList, Els1) ->
%%% Roster %%% Roster
%%% %%%
add_rosteritem(LocalUser, LocalServer, User, Server, Nick, Group, Subs) -> add_rosteritem(LocalUser, LocalServer, User, Server, Nick, Group, Subs) when is_binary(Group) ->
add_rosteritem(LocalUser, LocalServer, User, Server, Nick, [Group], Subs);
add_rosteritem(LocalUser, LocalServer, User, Server, Nick, Groups, Subs) ->
case {jid:make(LocalUser, LocalServer), jid:make(User, Server)} of case {jid:make(LocalUser, LocalServer), jid:make(User, Server)} of
{error, _} -> {error, _} ->
throw({error, "Invalid 'localuser'/'localserver'"}); throw({error, "Invalid 'localuser'/'localserver'"});
{_, error} -> {_, error} ->
throw({error, "Invalid 'user'/'server'"}); throw({error, "Invalid 'user'/'server'"});
{Jid, _Jid2} -> {Jid, _Jid2} ->
RosterItem = build_roster_item(User, Server, {add, Nick, Subs, Group}), RosterItem = build_roster_item(User, Server, {add, Nick, Subs, Groups}),
case mod_roster:set_item_and_notify_clients(Jid, RosterItem, true) of case mod_roster:set_item_and_notify_clients(Jid, RosterItem, true) of
ok -> ok; ok -> ok;
_ -> error _ -> error
@ -1408,6 +1451,11 @@ push_roster_item(LU, LS, R, U, S, Action) ->
ejabberd_router:route( ejabberd_router:route(
xmpp:set_from_to(ResIQ, jid:remove_resource(LJID), LJID)). xmpp:set_from_to(ResIQ, jid:remove_resource(LJID), LJID)).
build_roster_item(U, S, {add, Nick, Subs, Groups}) when is_list(Groups) ->
#roster_item{jid = jid:make(U, S),
name = Nick,
subscription = misc:binary_to_atom(Subs),
groups = Groups};
build_roster_item(U, S, {add, Nick, Subs, Group}) -> build_roster_item(U, S, {add, Nick, Subs, Group}) ->
Groups = binary:split(Group,<<";">>, [global, trim]), Groups = binary:split(Group,<<";">>, [global, trim]),
#roster_item{jid = jid:make(U, S), #roster_item{jid = jid:make(U, S),
@ -1488,11 +1536,14 @@ private_set2(Username, Host, Xml) ->
%%% Shared Roster Groups %%% Shared Roster Groups
%%% %%%
srg_create(Group, Host, Label, Description, Display) -> srg_create(Group, Host, Label, Description, Display) when is_binary(Display) ->
DisplayList = case Display of DisplayList = case Display of
<<>> -> []; <<>> -> [];
_ -> ejabberd_regexp:split(Display, <<"\\\\n">>) _ -> ejabberd_regexp:split(Display, <<"\\\\n">>)
end, end,
srg_create(Group, Host, Label, Description, DisplayList);
srg_create(Group, Host, Label, Description, DisplayList) ->
Opts = [{label, Label}, Opts = [{label, Label},
{displayed_groups, DisplayList}, {displayed_groups, DisplayList},
{description, Description}], {description, Description}],

View File

@ -72,8 +72,7 @@ get_commands_spec() ->
args_example = [], args_example = [],
args_desc = [], args_desc = [],
result = {res, rescode}, result = {res, rescode},
result_example = ok, result_example = ok}
result_desc = "Status code: 0 on success, 1 otherwise"}
]. ].
update_sql() -> update_sql() ->

View File

@ -39,7 +39,7 @@
-include("ejabberd_stacktrace.hrl"). -include("ejabberd_stacktrace.hrl").
-include("translate.hrl"). -include("translate.hrl").
-define(DEFAULT_API_VERSION, 0). -define(DEFAULT_API_VERSION, 1000000).
-define(CT_PLAIN, -define(CT_PLAIN,
{<<"Content-Type">>, <<"text/plain">>}). {<<"Content-Type">>, <<"text/plain">>}).
@ -135,7 +135,7 @@ extract_auth(#request{auth = HTTPAuth, ip = {IP, _}, opts = Opts}) ->
process(_, #request{method = 'POST', data = <<>>}) -> process(_, #request{method = 'POST', data = <<>>}) ->
?DEBUG("Bad Request: no data", []), ?DEBUG("Bad Request: no data", []),
badrequest_response(<<"Missing POST data">>); badrequest_response(<<"Missing POST data">>);
process([Call], #request{method = 'POST', data = Data, ip = IPPort} = Req) -> process([Call | _], #request{method = 'POST', data = Data, ip = IPPort} = Req) ->
Version = get_api_version(Req), Version = get_api_version(Req),
try try
Args = extract_args(Data), Args = extract_args(Data),
@ -153,7 +153,7 @@ process([Call], #request{method = 'POST', data = Data, ip = IPPort} = Req) ->
?DEBUG("Bad Request: ~p ~p", [_Error, StackTrace]), ?DEBUG("Bad Request: ~p ~p", [_Error, StackTrace]),
badrequest_response() badrequest_response()
end; end;
process([Call], #request{method = 'GET', q = Data, ip = {IP, _}} = Req) -> process([Call | _], #request{method = 'GET', q = Data, ip = {IP, _}} = Req) ->
Version = get_api_version(Req), Version = get_api_version(Req),
try try
Args = case Data of Args = case Data of
@ -412,7 +412,15 @@ format_command_result(Cmd, Auth, Result, Version) ->
{_, T} = format_result(Result, ResultFormat), {_, T} = format_result(Result, ResultFormat),
{200, T}; {200, T};
_ -> _ ->
{200, {[format_result(Result, ResultFormat)]}} OtherResult1 = format_result(Result, ResultFormat),
OtherResult2 = case Version of
0 ->
{[OtherResult1]};
_ ->
{_, Other3} = OtherResult1,
Other3
end,
{200, OtherResult2}
end. end.
format_result(Atom, {Name, atom}) -> format_result(Atom, {Name, atom}) ->

View File

@ -93,10 +93,11 @@ depends(_Host, _Opts) ->
get_commands_spec() -> get_commands_spec() ->
[ [
#ejabberd_commands{name = muc_online_rooms, tags = [muc], #ejabberd_commands{name = muc_online_rooms, tags = [muc],
desc = "List existing rooms ('global' to get all vhosts)", desc = "List existing rooms",
longdesc = "Ask for a specific host, or `global` to use all vhosts.",
policy = admin, policy = admin,
module = ?MODULE, function = muc_online_rooms, module = ?MODULE, function = muc_online_rooms,
args_desc = ["MUC service, or 'global' for all"], args_desc = ["MUC service, or `global` for all"],
args_example = ["muc.example.com"], args_example = ["muc.example.com"],
result_desc = "List of rooms", result_desc = "List of rooms",
result_example = ["room1@muc.example.com", "room2@muc.example.com"], result_example = ["room1@muc.example.com", "room2@muc.example.com"],
@ -104,10 +105,11 @@ get_commands_spec() ->
args_rename = [{host, service}], args_rename = [{host, service}],
result = {rooms, {list, {room, string}}}}, result = {rooms, {list, {room, string}}}},
#ejabberd_commands{name = muc_online_rooms_by_regex, tags = [muc], #ejabberd_commands{name = muc_online_rooms_by_regex, tags = [muc],
desc = "List existing rooms ('global' to get all vhosts) by regex", desc = "List existing rooms filtered by regexp",
longdesc = "Ask for a specific host, or `global` to use all vhosts.",
policy = admin, policy = admin,
module = ?MODULE, function = muc_online_rooms_by_regex, module = ?MODULE, function = muc_online_rooms_by_regex,
args_desc = ["MUC service, or 'global' for all", args_desc = ["MUC service, or `global` for all",
"Regex pattern for room name"], "Regex pattern for room name"],
args_example = ["muc.example.com", "^prefix"], args_example = ["muc.example.com", "^prefix"],
result_desc = "List of rooms with summary", result_desc = "List of rooms with summary",
@ -160,7 +162,7 @@ get_commands_spec() ->
args_example = ["/home/ejabberd/rooms.txt"], args_example = ["/home/ejabberd/rooms.txt"],
args = [{file, string}], args = [{file, string}],
result = {res, rescode}}, result = {res, rescode}},
#ejabberd_commands{name = create_room_with_opts, tags = [muc_room], #ejabberd_commands{name = create_room_with_opts, tags = [muc_room, muc_sub],
desc = "Create a MUC room name@service in host with given options", desc = "Create a MUC room name@service in host with given options",
longdesc = longdesc =
"The syntax of `affiliations` is: `Type:JID,Type:JID`. " "The syntax of `affiliations` is: `Type:JID,Type:JID`. "
@ -246,7 +248,7 @@ get_commands_spec() ->
result_example = ["room1@muc.example.com", "room2@muc.example.com"], result_example = ["room1@muc.example.com", "room2@muc.example.com"],
args = [{user, binary}, {host, binary}], args = [{user, binary}, {host, binary}],
result = {rooms, {list, {room, string}}}}, result = {rooms, {list, {room, string}}}},
#ejabberd_commands{name = get_user_subscriptions, tags = [muc], #ejabberd_commands{name = get_user_subscriptions, tags = [muc, muc_sub],
desc = "Get the list of rooms where this user is subscribed", desc = "Get the list of rooms where this user is subscribed",
note = "added in 21.04", note = "added in 21.04",
module = ?MODULE, function = get_user_subscriptions, module = ?MODULE, function = get_user_subscriptions,
@ -306,6 +308,22 @@ get_commands_spec() ->
args = [{name, binary}, {service, binary}, {password, binary}, args = [{name, binary}, {service, binary}, {password, binary},
{reason, binary}, {users, binary}], {reason, binary}, {users, binary}],
result = {res, rescode}}, result = {res, rescode}},
#ejabberd_commands{name = send_direct_invitation, tags = [muc_room],
desc = "Send a direct invitation to several destinations",
longdesc = "Since ejabberd 20.12, this command is "
"asynchronous: the API call may return before the "
"server has send all the invitations.\n\n"
"`password` and `message` can be set to `none`.",
module = ?MODULE, function = send_direct_invitation,
version = 1,
args_desc = ["Room name", "MUC service", "Password, or `none`",
"Reason text, or `none`", "List of users JIDs"],
args_example = [<<"room1">>, <<"muc.example.com">>,
<<>>, <<"Check this out!">>,
["user2@localhost", "user3@example.com"]],
args = [{name, binary}, {service, binary}, {password, binary},
{reason, binary}, {users, {list, {jid, binary}}}],
result = {res, rescode}},
#ejabberd_commands{name = change_room_option, tags = [muc_room], #ejabberd_commands{name = change_room_option, tags = [muc_room],
desc = "Change an option in a MUC room", desc = "Change an option in a MUC room",
@ -329,7 +347,7 @@ get_commands_spec() ->
{value, string} {value, string}
]}} ]}}
}}}, }}},
#ejabberd_commands{name = subscribe_room, tags = [muc_room], #ejabberd_commands{name = subscribe_room, tags = [muc_room, muc_sub],
desc = "Subscribe to a MUC conference", desc = "Subscribe to a MUC conference",
module = ?MODULE, function = subscribe_room, module = ?MODULE, function = subscribe_room,
args_desc = ["User JID", "a user's nick", args_desc = ["User JID", "a user's nick",
@ -342,7 +360,21 @@ get_commands_spec() ->
args = [{user, binary}, {nick, binary}, {room, binary}, args = [{user, binary}, {nick, binary}, {room, binary},
{nodes, binary}], {nodes, binary}],
result = {nodes, {list, {node, string}}}}, result = {nodes, {list, {node, string}}}},
#ejabberd_commands{name = subscribe_room_many, tags = [muc_room], #ejabberd_commands{name = subscribe_room, tags = [muc_room, muc_sub],
desc = "Subscribe to a MUC conference",
module = ?MODULE, function = subscribe_room,
version = 1,
args_desc = ["User JID", "a user's nick",
"the room to subscribe", "list of nodes"],
args_example = ["tom@localhost", "Tom", "room1@conference.localhost",
["urn:xmpp:mucsub:nodes:messages", "urn:xmpp:mucsub:nodes:affiliations"]],
result_desc = "The list of nodes that has subscribed",
result_example = ["urn:xmpp:mucsub:nodes:messages",
"urn:xmpp:mucsub:nodes:affiliations"],
args = [{user, binary}, {nick, binary}, {room, binary},
{nodes, {list, {node, binary}}}],
result = {nodes, {list, {node, string}}}},
#ejabberd_commands{name = subscribe_room_many, tags = [muc_room, muc_sub],
desc = "Subscribe several users to a MUC conference", desc = "Subscribe several users to a MUC conference",
note = "added in 22.05", note = "added in 22.05",
longdesc = "This command accepts up to 50 users at once " longdesc = "This command accepts up to 50 users at once "
@ -365,14 +397,38 @@ get_commands_spec() ->
{room, binary}, {room, binary},
{nodes, binary}], {nodes, binary}],
result = {res, rescode}}, result = {res, rescode}},
#ejabberd_commands{name = unsubscribe_room, tags = [muc_room], #ejabberd_commands{name = subscribe_room_many, tags = [muc_room, muc_sub],
desc = "Subscribe several users to a MUC conference",
note = "added in 22.05",
longdesc = "This command accepts up to 50 users at once "
"(this is configurable with the *`mod_muc_admin`* option "
"`subscribe_room_many_max_users`)",
module = ?MODULE, function = subscribe_room_many,
version = 1,
args_desc = ["Users JIDs and nicks",
"the room to subscribe",
"nodes separated by commas: `,`"],
args_example = [[{"tom@localhost", "Tom"},
{"jerry@localhost", "Jerry"}],
"room1@conference.localhost",
["urn:xmpp:mucsub:nodes:messages", "urn:xmpp:mucsub:nodes:affiliations"]],
args = [{users, {list,
{user, {tuple,
[{jid, binary},
{nick, binary}
]}}
}},
{room, binary},
{nodes, {list, {node, binary}}}],
result = {res, rescode}},
#ejabberd_commands{name = unsubscribe_room, tags = [muc_room, muc_sub],
desc = "Unsubscribe from a MUC conference", desc = "Unsubscribe from a MUC conference",
module = ?MODULE, function = unsubscribe_room, module = ?MODULE, function = unsubscribe_room,
args_desc = ["User JID", "the room to subscribe"], args_desc = ["User JID", "the room to subscribe"],
args_example = ["tom@localhost", "room1@conference.localhost"], args_example = ["tom@localhost", "room1@conference.localhost"],
args = [{user, binary}, {room, binary}], args = [{user, binary}, {room, binary}],
result = {res, rescode}}, result = {res, rescode}},
#ejabberd_commands{name = get_subscribers, tags = [muc_room], #ejabberd_commands{name = get_subscribers, tags = [muc_room, muc_sub],
desc = "List subscribers of a MUC conference", desc = "List subscribers of a MUC conference",
module = ?MODULE, function = get_subscribers, module = ?MODULE, function = get_subscribers,
args_desc = ["Room name", "MUC service"], args_desc = ["Room name", "MUC service"],
@ -1072,20 +1128,22 @@ get_room_occupants_number(Room, Host) ->
%%---------------------------- %%----------------------------
%% http://xmpp.org/extensions/xep-0249.html %% http://xmpp.org/extensions/xep-0249.html
send_direct_invitation(RoomName, RoomService, Password, Reason, UsersString) -> send_direct_invitation(RoomName, RoomService, Password, Reason, UsersString) when is_binary(UsersString) ->
UsersStrings = binary:split(UsersString, <<":">>, [global]),
send_direct_invitation(RoomName, RoomService, Password, Reason, UsersStrings);
send_direct_invitation(RoomName, RoomService, Password, Reason, UsersStrings) ->
case jid:make(RoomName, RoomService) of case jid:make(RoomName, RoomService) of
error -> error ->
throw({error, "Invalid 'roomname' or 'service'"}); throw({error, "Invalid 'roomname' or 'service'"});
RoomJid -> RoomJid ->
XmlEl = build_invitation(Password, Reason, RoomJid), XmlEl = build_invitation(Password, Reason, RoomJid),
Users = get_users_to_invite(RoomJid, UsersString), Users = get_users_to_invite(RoomJid, UsersStrings),
[send_direct_invitation(RoomJid, UserJid, XmlEl) [send_direct_invitation(RoomJid, UserJid, XmlEl)
|| UserJid <- Users], || UserJid <- Users],
ok ok
end. end.
get_users_to_invite(RoomJid, UsersString) -> get_users_to_invite(RoomJid, UsersStrings) ->
UsersStrings = binary:split(UsersString, <<":">>, [global]),
OccupantsTuples = get_room_occupants(RoomJid#jid.luser, OccupantsTuples = get_room_occupants(RoomJid#jid.luser,
RoomJid#jid.lserver), RoomJid#jid.lserver),
OccupantsJids = [jid:decode(JidString) OccupantsJids = [jid:decode(JidString)
@ -1437,8 +1495,10 @@ set_room_affiliation(Name, Service, JID, AffiliationString) ->
subscribe_room(_User, Nick, _Room, _Nodes) when Nick == <<"">> -> subscribe_room(_User, Nick, _Room, _Nodes) when Nick == <<"">> ->
throw({error, "Nickname must be set"}); throw({error, "Nickname must be set"});
subscribe_room(User, Nick, Room, Nodes) -> subscribe_room(User, Nick, Room, Nodes) when is_binary(Nodes) ->
NodeList = re:split(Nodes, "\\h*,\\h*"), NodeList = re:split(Nodes, "\\h*,\\h*"),
subscribe_room(User, Nick, Room, NodeList);
subscribe_room(User, Nick, Room, NodeList) ->
try jid:decode(Room) of try jid:decode(Room) of
#jid{luser = Name, lserver = Host} when Name /= <<"">> -> #jid{luser = Name, lserver = Host} when Name /= <<"">> ->
try jid:decode(User) of try jid:decode(User) of