Merge pull request #4118 from badlop/api-version-1
Commands API version 1
This commit is contained in:
commit
e26c547afc
|
@ -67,42 +67,24 @@
|
|||
args_example = none :: none | [any()] | '_',
|
||||
result_example = none :: any()}).
|
||||
|
||||
%% TODO Fix me: Type is not up to date
|
||||
-type ejabberd_commands() :: #ejabberd_commands{name :: atom(),
|
||||
tags :: [atom()],
|
||||
desc :: string(),
|
||||
longdesc :: string(),
|
||||
version :: integer(),
|
||||
module :: atom(),
|
||||
function :: atom(),
|
||||
args :: [aterm()],
|
||||
policy :: open | restricted | admin | user,
|
||||
access :: [{atom(),atom(),atom()}|atom()],
|
||||
result :: rterm()}.
|
||||
-type ejabberd_commands() :: #ejabberd_commands{name :: atom(),
|
||||
tags :: [atom()],
|
||||
desc :: string(),
|
||||
longdesc :: string(),
|
||||
version :: integer(),
|
||||
note :: string(),
|
||||
weight :: integer(),
|
||||
module :: atom(),
|
||||
function :: atom(),
|
||||
args :: [aterm()],
|
||||
policy :: open | restricted | admin | user,
|
||||
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.
|
||||
|
|
|
@ -450,11 +450,11 @@ delete_obsolete_data() ->
|
|||
%%%===================================================================
|
||||
get_commands_spec() ->
|
||||
[#ejabberd_commands{name = request_certificate, tags = [acme],
|
||||
desc = "Requests certificates for all or the specified "
|
||||
"domains: all | domain1,domain2,...",
|
||||
desc = "Requests certificates for all or some domains",
|
||||
longdesc = "Domains can be `all`, or a list of domains separared with comma characters",
|
||||
module = ?MODULE, function = request_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}],
|
||||
result = {res, restuple}},
|
||||
#ejabberd_commands{name = list_certificates, tags = [acme],
|
||||
|
|
|
@ -129,7 +129,7 @@ get_commands_spec() ->
|
|||
desc = "Reopen the log files after being renamed",
|
||||
longdesc = "This can be useful when an external tool is "
|
||||
"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,
|
||||
module = ?MODULE, function = reopen_log,
|
||||
args = [], result = {res, rescode}},
|
||||
|
@ -157,9 +157,10 @@ get_commands_spec() ->
|
|||
result = {levelatom, atom}},
|
||||
#ejabberd_commands{name = set_loglevel, tags = [logs],
|
||||
desc = "Set the loglevel",
|
||||
longdesc = "Possible loglevels: `none`, `emergency`, `alert`, `critical`,
|
||||
`error`, `warning`, `notice`, `info`, `debug`.",
|
||||
module = ?MODULE, function = set_loglevel,
|
||||
args_desc = ["Desired logging level: none | emergency | alert | critical "
|
||||
"| error | warning | notice | info | debug"],
|
||||
args_desc = ["Desired logging level"],
|
||||
args_example = ["debug"],
|
||||
args = [{loglevel, string}],
|
||||
result = {res, rescode}},
|
||||
|
@ -171,7 +172,8 @@ get_commands_spec() ->
|
|||
result_example = ["mod_configure", "mod_vcard"],
|
||||
result = {modules, {list, {module, string}}}},
|
||||
#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,
|
||||
args_example = ["mod_vcard"],
|
||||
args = [{module, string}],
|
||||
|
@ -373,7 +375,7 @@ get_commands_spec() ->
|
|||
result = {res, rescode}},
|
||||
#ejabberd_commands{name = set_master, tags = [cluster],
|
||||
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.",
|
||||
module = ?MODULE, function = set_master,
|
||||
args_desc = ["Name of the erlang node that will be considered master of this node"],
|
||||
|
|
|
@ -86,7 +86,8 @@ get_commands_spec() ->
|
|||
args_desc = ["Path to file where generated "
|
||||
"documentation should be stored",
|
||||
"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`) "
|
||||
"that will have example invocation include in markdown document"],
|
||||
result_desc = "0 if command failed, 1 when succeeded",
|
||||
|
@ -147,13 +148,25 @@ register_commands(Definer, Commands) ->
|
|||
lists:foreach(
|
||||
fun(Command) ->
|
||||
%% 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])
|
||||
end,
|
||||
Commands),
|
||||
ejabberd_access_permissions:invalidate(),
|
||||
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.
|
||||
|
||||
unregister_commands(Commands) ->
|
||||
|
|
|
@ -386,7 +386,7 @@ gen_doc(#ejabberd_commands{name=Name, tags=Tags, desc=Desc, longdesc=LongDesc,
|
|||
ResultText = case Result of
|
||||
{res,rescode} ->
|
||||
[?TAG(dl, [gen_param(res, integer,
|
||||
"Status code (0 on success, 1 otherwise)",
|
||||
"Status code (`0` on success, `1` otherwise)",
|
||||
HTMLOutput)])];
|
||||
{res,restuple} ->
|
||||
[?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)])]
|
||||
end
|
||||
end,
|
||||
TagsText = [?RAW("*`"++atom_to_list(Tag)++"`* ") || Tag <- Tags],
|
||||
TagsText = ?RAW(string:join(["*`"++atom_to_list(Tag)++"`*" || Tag <- Tags], ", ")),
|
||||
IsDefinerMod = case Definer of
|
||||
unknown -> true;
|
||||
unknown -> false;
|
||||
_ -> lists:member(gen_mod, proplists:get_value(behaviour, Definer:module_info(attributes)))
|
||||
end,
|
||||
ModuleText = case IsDefinerMod of
|
||||
|
@ -477,8 +477,16 @@ maybe_add_policy_arguments(#ejabberd_commands{args=Args1, policy=user}=Cmd) ->
|
|||
maybe_add_policy_arguments(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) ->
|
||||
Cmds = find_commands_definitions(),
|
||||
generate_md_output(File, RegExp, Languages, Cmds).
|
||||
|
||||
generate_md_output(File, RegExp, Languages, Cmds) ->
|
||||
{ok, RE} = re:compile(RegExp),
|
||||
Cmds2 = lists:filter(fun(#ejabberd_commands{name=Name, module=Module}) ->
|
||||
re:run(atom_to_list(Name), RE, [{capture, none}]) == match orelse
|
||||
|
|
|
@ -23,8 +23,6 @@
|
|||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
%%% Does not support commands that have arguments with ctypes: list, tuple
|
||||
|
||||
-module(ejabberd_ctl).
|
||||
|
||||
-behaviour(gen_server).
|
||||
|
@ -335,14 +333,14 @@ call_command([CmdString | Args], Auth, _AccessCommands, Version) ->
|
|||
ArgsFormatted,
|
||||
CI2,
|
||||
Version),
|
||||
format_result_preliminary(Result, ResultFormat);
|
||||
format_result_preliminary(Result, ResultFormat, Version);
|
||||
{'EXIT', {function_clause,[{lists,zip,[A1,A2|_], _} | _]}} ->
|
||||
{NumCompa, TextCompa} =
|
||||
case {length(A1), length(A2)} of
|
||||
{L1, L2} when L1 < L2 -> {L2-L1, "less argument"};
|
||||
{L1, L2} when L1 > L2 -> {L1-L2, "more argument"}
|
||||
end,
|
||||
process(["help" | [CmdString]]),
|
||||
process(["help" | [CmdString]], Version),
|
||||
{io_lib:format("Error: the command '~ts' requires ~p ~ts.",
|
||||
[CmdString, NumCompa, TextCompa]),
|
||||
wrong_command_arguments}
|
||||
|
@ -372,6 +370,13 @@ format_arg(Arg, string) ->
|
|||
NumChars = integer_to_list(length(Arg)),
|
||||
Parse = "~" ++ NumChars ++ "c",
|
||||
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) ->
|
||||
S = unicode:characters_to_binary(Arg, utf8),
|
||||
JSON = jiffy:decode(S),
|
||||
|
@ -385,67 +390,71 @@ format_arg2(Arg, Parse)->
|
|||
%% Format result
|
||||
%%-----------------------------
|
||||
|
||||
format_result_preliminary(Result, {A, {list, B}}) ->
|
||||
format_result(Result, {A, {top_result_list, B}});
|
||||
format_result_preliminary(Result, ResultFormat) ->
|
||||
format_result(Result, ResultFormat).
|
||||
format_result_preliminary(Result, {A, {list, B}}, Version) ->
|
||||
format_result(Result, {A, {top_result_list, B}}, Version);
|
||||
format_result_preliminary(Result, ResultFormat, Version) ->
|
||||
format_result(Result, ResultFormat, Version).
|
||||
|
||||
format_result({error, ErrorAtom}, _) ->
|
||||
format_result({error, ErrorAtom}, _, _Version) ->
|
||||
{io_lib:format("Error: ~p", [ErrorAtom]), make_status(error)};
|
||||
|
||||
%% An error should always be allowed to return extended error to help with API.
|
||||
%% Extended error is of the form:
|
||||
%% {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)};
|
||||
|
||||
format_result(Atom, {_Name, atom}) ->
|
||||
format_result(Atom, {_Name, atom}, _Version) ->
|
||||
io_lib:format("~p", [Atom]);
|
||||
|
||||
format_result(Int, {_Name, integer}) ->
|
||||
format_result(Int, {_Name, integer}, _Version) ->
|
||||
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]);
|
||||
|
||||
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)]);
|
||||
|
||||
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)]);
|
||||
|
||||
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)]);
|
||||
|
||||
format_result(Other, {_Name, string}) ->
|
||||
format_result(Other, {_Name, string}, _Version) ->
|
||||
io_lib:format("~p", [Other]);
|
||||
|
||||
format_result(Code, {_Name, rescode}) ->
|
||||
format_result(Code, {_Name, rescode}, _Version) ->
|
||||
make_status(Code);
|
||||
|
||||
format_result({Code, Text}, {_Name, restuple}) ->
|
||||
format_result({Code, Text}, {_Name, restuple}, _Version) ->
|
||||
{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, ElementsDef) |
|
||||
format_result([FirstElement | Elements], {_Name, {top_result_list, ElementsDef}}, Version) ->
|
||||
[format_result(FirstElement, ElementsDef, Version) |
|
||||
lists:map(
|
||||
fun(Element) ->
|
||||
["\n" | format_result(Element, ElementsDef)]
|
||||
["\n" | format_result(Element, ElementsDef, Version)]
|
||||
end,
|
||||
Elements)];
|
||||
|
||||
%% 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
|
||||
[format_result(FirstElement, ElementsDef) |
|
||||
[format_result(FirstElement, ElementsDef, Version) |
|
||||
%% If there are more elements, put always first a newline character
|
||||
lists:map(
|
||||
fun(Element) ->
|
||||
[";" | format_result(Element, ElementsDef)]
|
||||
[Separator | format_result(Element, ElementsDef, Version)]
|
||||
end,
|
||||
Elements)];
|
||||
|
||||
|
@ -453,17 +462,17 @@ format_result([FirstElement | Elements], {_Name, {list, ElementsDef}}) ->
|
|||
%% 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,
|
||||
%% 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),
|
||||
[{FirstE, FirstD} | ElementsAndDef] = lists:zip(ElementsList, ElementsDef),
|
||||
[format_result(FirstE, FirstD) |
|
||||
[format_result(FirstE, FirstD, Version) |
|
||||
lists:map(
|
||||
fun({Element, ElementDef}) ->
|
||||
["\t" | format_result(Element, ElementDef)]
|
||||
["\t" | format_result(Element, ElementDef, Version)]
|
||||
end,
|
||||
ElementsAndDef)];
|
||||
|
||||
format_result(404, {_Name, _}) ->
|
||||
format_result(404, {_Name, _}, _Version) ->
|
||||
make_status(not_found).
|
||||
|
||||
make_status(ok) -> ?STATUS_SUCCESS;
|
||||
|
@ -491,19 +500,24 @@ get_list_commands(Version) ->
|
|||
tuple_command_help({Name, _Args, Desc}) ->
|
||||
{Args, _, _} = ejabberd_commands:get_command_format(Name, admin),
|
||||
Arguments = [atom_to_list(ArgN) || {ArgN, _ArgF} <- Args],
|
||||
Prepend = case is_supported_args(Args) of
|
||||
true -> "";
|
||||
false -> "*"
|
||||
end,
|
||||
CallString = atom_to_list(Name),
|
||||
{CallString, Arguments, Prepend ++ Desc}.
|
||||
{CallString, Arguments, Desc}.
|
||||
|
||||
is_supported_args(Args) ->
|
||||
lists:all(
|
||||
fun({_Name, Format}) ->
|
||||
(Format == integer)
|
||||
or (Format == string)
|
||||
or (Format == binary)
|
||||
has_tuple_args(Args) ->
|
||||
lists:any(
|
||||
fun({_Name, tuple}) -> true;
|
||||
({_Name, {tuple, _}}) -> true;
|
||||
({_Name, {list, SubArg}}) ->
|
||||
has_tuple_args([SubArg]);
|
||||
(_) -> false
|
||||
end,
|
||||
Args).
|
||||
|
||||
has_list_args(Args) ->
|
||||
lists:any(
|
||||
fun({_Name, list}) -> true;
|
||||
({_Name, {list, _}}) -> true;
|
||||
(_) -> false
|
||||
end,
|
||||
Args).
|
||||
|
||||
|
@ -768,12 +782,13 @@ print_usage_help(MaxC, ShCode) ->
|
|||
" ejabberdctl ", ?C("help"), " ", ?C("register"), "\n",
|
||||
" ejabberdctl ", ?C("help"), " ", ?C("regist*"), "\n",
|
||||
"\n",
|
||||
"Please note that 'ejabberdctl' shows all ejabberd commands,\n",
|
||||
"even those that cannot be used in the shell with ejabberdctl.\n",
|
||||
"Those commands can be identified because their description starts with: *\n",
|
||||
"Some command arguments are lists or tuples, like add_rosteritem and create_room_with_opts.\n",
|
||||
"Separate the elements in a list with the , character.\n",
|
||||
"Separate the elements in a tuple with the : character.\n",
|
||||
"\n",
|
||||
"Some commands return lists, like get_roster and get_user_subscriptions.\n",
|
||||
"In those commands, the elements in the list are separated with: ;\n"],
|
||||
"Some commands results are lists or tuples, like get_roster and get_user_subscriptions.\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 = [],
|
||||
C = #ejabberd_commands{
|
||||
name = help,
|
||||
|
@ -893,9 +908,13 @@ print_usage_command2(Cmd, C, MaxC, ShCode) ->
|
|||
_ -> ["", prepare_description(0, MaxC, LongDesc), "\n\n"]
|
||||
end,
|
||||
|
||||
NoteEjabberdctl = case is_supported_args(ArgsDef) of
|
||||
true -> "";
|
||||
false -> [" ", ?B("Note:"), " This command cannot be executed using ejabberdctl. Try ejabberd_xmlrpc.\n\n"]
|
||||
NoteEjabberdctlList = case has_list_args(ArgsDef) of
|
||||
true -> [" ", ?B("Note:"), " In a list argument, separate the elements using the , character for example: one,two,three\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,
|
||||
|
||||
case Cmd of
|
||||
|
@ -903,7 +922,7 @@ print_usage_command2(Cmd, C, MaxC, ShCode) ->
|
|||
_ -> print([NameFmt, "\n", ArgsFmt, "\n", ReturnsFmt,
|
||||
"\n\n", XmlrpcFmt, TagsFmt, "\n\n", ModuleFmt, DescFmt, "\n\n"], [])
|
||||
end,
|
||||
print([LongDescFmt, NoteEjabberdctl], []).
|
||||
print([LongDescFmt, NoteEjabberdctlList, NoteEjabberdctlTuple], []).
|
||||
|
||||
format_usage_ctype(Type, _Indentation)
|
||||
when (Type==atom) or (Type==integer) or (Type==string) or (Type==binary) or (Type==rescode) or (Type==restuple)->
|
||||
|
|
|
@ -81,7 +81,7 @@
|
|||
get_commands_spec() ->
|
||||
[
|
||||
#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,
|
||||
args = [{jid, string},{ttl, integer}, {scopes, string}],
|
||||
policy = restricted,
|
||||
|
@ -91,16 +91,28 @@ get_commands_spec() ->
|
|||
"List of scopes to allow, separated by ';'"],
|
||||
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],
|
||||
desc = "List 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",
|
||||
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",
|
||||
module = ?MODULE, function = oauth_list_tokens,
|
||||
args = [],
|
||||
policy = restricted,
|
||||
result = {tokens, {list, {token, {tuple, [{token, string}, {user, string}, {scope, string}, {expires_in, string}]}}}}
|
||||
},
|
||||
#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",
|
||||
module = ?MODULE, function = oauth_revoke_token,
|
||||
args = [{token, binary}],
|
||||
|
@ -109,7 +121,7 @@ get_commands_spec() ->
|
|||
result_desc = "Result code"
|
||||
},
|
||||
#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,
|
||||
args = [{client_id, binary},
|
||||
{client_name, binary},
|
||||
|
@ -118,7 +130,7 @@ get_commands_spec() ->
|
|||
result = {res, restuple}
|
||||
},
|
||||
#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,
|
||||
args = [{client_id, binary},
|
||||
{client_name, binary},
|
||||
|
@ -127,7 +139,7 @@ get_commands_spec() ->
|
|||
result = {res, restuple}
|
||||
},
|
||||
#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,
|
||||
args = [{client_id, binary}],
|
||||
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, ";")],
|
||||
oauth_issue_token(Jid, TTLSeconds, Scopes);
|
||||
oauth_issue_token(Jid, TTLSeconds, Scopes) ->
|
||||
try jid:decode(list_to_binary(Jid)) of
|
||||
#jid{luser =Username, lserver = Server} ->
|
||||
Ctx1 = #oauth_ctx{password = admin_generated},
|
||||
|
|
|
@ -238,7 +238,7 @@ do_command(Auth, Command, AttrL, ArgsF, ArgsR,
|
|||
ArgsFormatted = format_args(rename_old_args(AttrL, ArgsR), ArgsF),
|
||||
Result = ejabberd_commands:execute_command2(Command, ArgsFormatted, Auth),
|
||||
ResultFormatted = format_result(Result, ResultF),
|
||||
{command_result, ResultFormatted}.
|
||||
{command_result, {struct, [ResultFormatted]}}.
|
||||
|
||||
rename_old_args(Args, []) ->
|
||||
Args;
|
||||
|
@ -291,6 +291,14 @@ format_args(Args, ArgsFormat) ->
|
|||
L when is_list(L) -> exit({additional_unused_args, L})
|
||||
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},
|
||||
{list, {ElementDefName, ElementDefFormat}})
|
||||
when is_list(Elements) ->
|
||||
|
@ -307,11 +315,18 @@ format_arg({array, [{struct, Elements}]},
|
|||
format_arg(ElementValue, ElementDefFormat)
|
||||
end,
|
||||
Elements);
|
||||
%% Old ejabberd 23.10
|
||||
format_arg({array, [{struct, Elements}]},
|
||||
{tuple, ElementsDef})
|
||||
when is_list(Elements) ->
|
||||
FormattedList = format_args(Elements, ElementsDef),
|
||||
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})
|
||||
when is_list(Elements) and is_atom(ElementsDef) ->
|
||||
[format_arg(Element, ElementsDef)
|
||||
|
@ -336,6 +351,10 @@ process_unicode_codepoints(Str) ->
|
|||
%% 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) ->
|
||||
throw({error, lists:flatten(Error)});
|
||||
format_result({error, Error}, _) ->
|
||||
|
@ -346,45 +365,36 @@ format_result({error, _Type, _Code, Error}, _) ->
|
|||
throw({error, Error});
|
||||
format_result(String, string) -> lists:flatten(String);
|
||||
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}) ->
|
||||
{struct, [{Name, Int}]};
|
||||
{Name, Int};
|
||||
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) ->
|
||||
{struct, [{Name, binary_to_list(Binary)}]};
|
||||
{Name, binary_to_list(Binary)};
|
||||
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) ->
|
||||
{struct, [{Name, integer_to_list(Integer)}]};
|
||||
{Name, integer_to_list(Integer)};
|
||||
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) ->
|
||||
{struct, [{Name, lists:flatten(String)}]};
|
||||
{Name, lists:flatten(String)};
|
||||
format_result(Binary, {Name, binary}) when is_binary(Binary) ->
|
||||
{struct, [{Name, binary_to_list(Binary)}]};
|
||||
format_result(Code, {Name, rescode}) ->
|
||||
{struct, [{Name, make_status(Code)}]};
|
||||
format_result({Code, Text}, {Name, restuple}) ->
|
||||
{struct,
|
||||
[{Name, make_status(Code)},
|
||||
{text, io_lib:format("~s", [Text])}]};
|
||||
format_result(Elements, {Name, {list, ElementsDef}}) ->
|
||||
FormattedList = lists:map(fun (Element) ->
|
||||
format_result(Element, ElementsDef)
|
||||
end,
|
||||
Elements),
|
||||
{struct, [{Name, {array, 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}}]};
|
||||
{Name, binary_to_list(Binary)};
|
||||
|
||||
|
||||
format_result(Els, {Name, {list, Def}}) ->
|
||||
FormattedList = [element(2, format_result(El, Def)) || El <- Els],
|
||||
{Name, {array, FormattedList}};
|
||||
|
||||
|
||||
format_result(Tuple,
|
||||
{Name, {tuple, Def}}) ->
|
||||
Els = lists:zip(tuple_to_list(Tuple), Def),
|
||||
FormattedList = [format_result(El, ElDef) || {El, ElDef} <- Els],
|
||||
{Name, {struct, FormattedList}};
|
||||
|
||||
format_result(404, {Name, _}) ->
|
||||
{struct, [{Name, make_status(not_found)}]}.
|
||||
|
||||
|
|
|
@ -113,14 +113,14 @@ depends(_Host, _Opts) ->
|
|||
%%%
|
||||
|
||||
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"
|
||||
"* NICKNAME - Nickname\n"
|
||||
"* BDAY - Birthday\n"
|
||||
"* TITLE - Work: Position\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 GIVEN - Given name\n"
|
||||
"* N MIDDLE - Middle name\n"
|
||||
|
@ -134,8 +134,8 @@ get_commands_spec() ->
|
|||
"* ORG ORGNAME - Work: Company\n"
|
||||
"* ORG ORGUNIT - Work: Department\n",
|
||||
|
||||
VcardXEP = "For a full list of vCard fields check XEP-0054: vcard-temp at "
|
||||
"https://xmpp.org/extensions/xep-0054.html",
|
||||
VcardXEP = "For a full list of vCard fields check [XEP-0054: vcard-temp]"
|
||||
"(https://xmpp.org/extensions/xep-0054.html)",
|
||||
|
||||
[
|
||||
#ejabberd_commands{name = compile, tags = [erlang],
|
||||
|
@ -145,8 +145,7 @@ get_commands_spec() ->
|
|||
args_example = ["/home/me/srcs/ejabberd/mod_example.erl"],
|
||||
args_desc = ["Filename of erlang source file to compile"],
|
||||
result = {res, rescode},
|
||||
result_example = ok,
|
||||
result_desc = "Status code: 0 on success, 1 otherwise"},
|
||||
result_example = ok},
|
||||
#ejabberd_commands{name = get_cookie, tags = [erlang],
|
||||
desc = "Get the Erlang cookie of this node",
|
||||
module = ?MODULE, function = get_cookie,
|
||||
|
@ -163,9 +162,9 @@ get_commands_spec() ->
|
|||
result = {res, integer},
|
||||
result_example = 0,
|
||||
result_desc = "Returns integer code:\n"
|
||||
" - 0: code reloaded, module restarted\n"
|
||||
" - 1: error: module not loaded\n"
|
||||
" - 2: code not reloaded, but module restarted"},
|
||||
" - `0`: code reloaded, module restarted\n"
|
||||
" - `1`: error: module not loaded\n"
|
||||
" - `2`: code not reloaded, but module restarted"},
|
||||
#ejabberd_commands{name = delete_old_users, tags = [accounts, purge],
|
||||
desc = "Delete users that didn't log in last days, or that never logged",
|
||||
longdesc = "To protect admin accounts, configure this for example:\n"
|
||||
|
@ -206,8 +205,7 @@ get_commands_spec() ->
|
|||
args_example = [<<"peter">>, <<"myserver.com">>],
|
||||
args_desc = ["User name to check", "Server to check"],
|
||||
result = {res, rescode},
|
||||
result_example = ok,
|
||||
result_desc = "Status code: 0 on success, 1 otherwise"},
|
||||
result_example = ok},
|
||||
#ejabberd_commands{name = check_password, tags = [accounts],
|
||||
desc = "Check if a password is correct",
|
||||
module = ?MODULE, function = check_password,
|
||||
|
@ -215,8 +213,7 @@ get_commands_spec() ->
|
|||
args_example = [<<"peter">>, <<"myserver.com">>, <<"secret">>],
|
||||
args_desc = ["User name to check", "Server to check", "Password to check"],
|
||||
result = {res, rescode},
|
||||
result_example = ok,
|
||||
result_desc = "Status code: 0 on success, 1 otherwise"},
|
||||
result_example = ok},
|
||||
#ejabberd_commands{name = check_password_hash, tags = [accounts],
|
||||
desc = "Check if the password hash is correct",
|
||||
longdesc = "Allows hash methods from the Erlang/OTP "
|
||||
|
@ -229,8 +226,7 @@ get_commands_spec() ->
|
|||
args_desc = ["User name to check", "Server to check",
|
||||
"Password's hash value", "Name of hash method"],
|
||||
result = {res, rescode},
|
||||
result_example = ok,
|
||||
result_desc = "Status code: 0 on success, 1 otherwise"},
|
||||
result_example = ok},
|
||||
#ejabberd_commands{name = change_password, tags = [accounts],
|
||||
desc = "Change the password of an account",
|
||||
module = ?MODULE, function = set_password,
|
||||
|
@ -239,8 +235,7 @@ get_commands_spec() ->
|
|||
args_desc = ["User name", "Server name",
|
||||
"New password for user"],
|
||||
result = {res, rescode},
|
||||
result_example = ok,
|
||||
result_desc = "Status code: 0 on success, 1 otherwise"},
|
||||
result_example = ok},
|
||||
#ejabberd_commands{name = ban_account, tags = [accounts],
|
||||
desc = "Ban an account: kick sessions and set random password",
|
||||
module = ?MODULE, function = ban_account,
|
||||
|
@ -249,8 +244,7 @@ get_commands_spec() ->
|
|||
args_desc = ["User name to ban", "Server name",
|
||||
"Reason for banning user"],
|
||||
result = {res, rescode},
|
||||
result_example = ok,
|
||||
result_desc = "Status code: 0 on success, 1 otherwise"},
|
||||
result_example = ok},
|
||||
#ejabberd_commands{name = num_resources, tags = [session],
|
||||
desc = "Get the number of resources of a user",
|
||||
module = ?MODULE, function = num_resources,
|
||||
|
@ -278,8 +272,7 @@ get_commands_spec() ->
|
|||
args_desc = ["User name", "Server name", "User's resource",
|
||||
"Reason for closing session"],
|
||||
result = {res, rescode},
|
||||
result_example = ok,
|
||||
result_desc = "Status code: 0 on success, 1 otherwise"},
|
||||
result_example = ok},
|
||||
#ejabberd_commands{name = status_num_host, tags = [session, statistics],
|
||||
desc = "Number of logged users with this status in host",
|
||||
policy = admin,
|
||||
|
@ -430,6 +423,22 @@ get_commands_spec() ->
|
|||
"Show: `away`, `chat`, `dnd`, `xa`.", "Status text",
|
||||
"Priority, provide this value as an integer"],
|
||||
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],
|
||||
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",
|
||||
"Nickname", "Group", "Subscription"],
|
||||
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"},
|
||||
%%{"", "example: add-roster peter localhost mike server.com MiKe Employees both"},
|
||||
%%{"", "will add mike@server.com to peter@localhost roster"},
|
||||
|
@ -516,52 +539,56 @@ get_commands_spec() ->
|
|||
result = {res, rescode}},
|
||||
#ejabberd_commands{name = process_rosteritems, tags = [roster],
|
||||
desc = "List/delete rosteritems that match filter",
|
||||
longdesc = "Explanation of each argument:\n"
|
||||
" - action: what to do with each rosteritem that "
|
||||
longdesc = "Explanation of each argument:\n\n"
|
||||
"* `action`: what to do with each rosteritem that "
|
||||
"matches all the filtering options\n"
|
||||
" - subs: subscription type\n"
|
||||
" - asks: pending subscription\n"
|
||||
" - users: the JIDs of the local user\n"
|
||||
" - contacts: the JIDs of the contact in the roster\n"
|
||||
"* `subs`: subscription type\n"
|
||||
"* `asks`: pending subscription\n"
|
||||
"* `users`: the JIDs of the local user\n"
|
||||
"* `contacts`: the JIDs of the contact in the roster\n"
|
||||
"\n"
|
||||
" *** Mnesia: \n"
|
||||
"**Mnesia backend:**\n"
|
||||
"\n"
|
||||
"Allowed values in the arguments:\n"
|
||||
" ACTION = list | delete\n"
|
||||
" SUBS = SUB[:SUB]* | any\n"
|
||||
" SUB = none | from | to | both\n"
|
||||
" ASKS = ASK[:ASK]* | any\n"
|
||||
" ASK = none | out | in\n"
|
||||
" USERS = JID[:JID]* | any\n"
|
||||
" CONTACTS = JID[:JID]* | any\n"
|
||||
" JID = characters valid in a JID, and can use the "
|
||||
"globs: *, ?, ! and [...]\n"
|
||||
"Allowed values in the arguments:\n\n"
|
||||
"* `action` = `list` | `delete`\n"
|
||||
"* `subs` = `any` | SUB[:SUB]*\n"
|
||||
"* `asks` = `any` | ASK[:ASK]*\n"
|
||||
"* `users` = `any` | JID[:JID]*\n"
|
||||
"* `contacts` = `any` | JID[:JID]*\n"
|
||||
"\nwhere\n\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"
|
||||
"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 "
|
||||
"'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 "
|
||||
"user part and the server part contains the word 'icq'"
|
||||
":\n list none:from:to any *@example.org *:*@*icq*"
|
||||
"user part and the server part contains the word `icq`"
|
||||
":\n `list none:from:to any *@example.org *:*@*icq*`"
|
||||
"\n\n"
|
||||
" *** SQL:\n"
|
||||
"**SQL backend:**\n"
|
||||
"\n"
|
||||
"Allowed values in the arguments:\n"
|
||||
" ACTION = list | delete\n"
|
||||
" SUBS = any | none | from | to | both\n"
|
||||
" ASKS = any | none | out | in\n"
|
||||
" USERS = JID\n"
|
||||
" CONTACTS = JID\n"
|
||||
" JID = characters valid in a JID, and can use the "
|
||||
"globs: _ and %\n"
|
||||
"Allowed values in the arguments:\n\n"
|
||||
"* `action` = `list` | `delete`\n"
|
||||
"* `subs` = `any` | SUB\n"
|
||||
"* `asks` = `any` | ASK\n"
|
||||
"* `users` = JID\n"
|
||||
"* `contacts` = JID\n"
|
||||
"\nwhere\n\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"
|
||||
"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 "
|
||||
"'example.org' and that the contact JID's "
|
||||
"server part contains the word 'icq'"
|
||||
":\n list to any %@example.org %@%icq%",
|
||||
"`example.org` and that the contact JID's "
|
||||
"server part contains the word `icq`"
|
||||
":\n `list to any %@example.org %@%icq%`",
|
||||
module = mod_roster, function = process_rosteritems,
|
||||
args = [{action, string}, {subs, string},
|
||||
{asks, string}, {users, string},
|
||||
|
@ -576,8 +603,8 @@ get_commands_spec() ->
|
|||
#ejabberd_commands{name = get_roster, tags = [roster],
|
||||
desc = "Get list of contacts in a local user roster",
|
||||
longdesc =
|
||||
"Subscription can be: \"none\", \"from\", \"to\", \"both\". "
|
||||
"Pending can be: \"in\", \"out\", \"none\".",
|
||||
"`subscription` can be: `none`, `from`, `to`, `both`.\n\n"
|
||||
"`pending` can be: `in`, `out`, `none`.",
|
||||
note = "improved in 23.10",
|
||||
policy = user,
|
||||
module = ?MODULE, function = get_roster,
|
||||
|
@ -593,11 +620,12 @@ get_commands_spec() ->
|
|||
#ejabberd_commands{name = push_roster, tags = [roster],
|
||||
desc = "Push template roster from file to a user",
|
||||
longdesc = "The text file must contain an erlang term: a list "
|
||||
"of tuples with username, servername, group and nick. Example:\n"
|
||||
"[{<<\"user1\">>, <<\"localhost\">>, <<\"Workers\">>, <<\"User 1\">>},\n"
|
||||
" {<<\"user2\">>, <<\"localhost\">>, <<\"Workers\">>, <<\"User 2\">>}].\n"
|
||||
"When using UTF8 character encoding add /utf8 to certain string. Example:\n"
|
||||
"[{<<\"user2\">>, <<\"localhost\">>, <<\"Workers\"/utf8>>, <<\"User 2\"/utf8>>}].",
|
||||
"of tuples with username, servername, group and nick. For example:\n"
|
||||
"`[{\"user1\", \"localhost\", \"Workers\", \"User 1\"},\n"
|
||||
" {\"user2\", \"localhost\", \"Workers\", \"User 2\"}].`\n\n"
|
||||
"If there are problems parsing UTF8 character encoding, "
|
||||
"provide the corresponding string with the `<<\"STRING\"/utf8>>` syntax, for example:\n"
|
||||
"`[{\"user2\", \"localhost\", \"Workers\", <<\"User 2\"/utf8>>}]`.",
|
||||
module = ?MODULE, function = push_roster,
|
||||
args = [{file, binary}, {user, binary}, {host, binary}],
|
||||
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",
|
||||
longdesc = "The text file must contain an erlang term: a list "
|
||||
"of tuples with username, servername, group and nick. Example:\n"
|
||||
"[{\"user1\", \"localhost\", \"Workers\", \"User 1\"},\n"
|
||||
" {\"user2\", \"localhost\", \"Workers\", \"User 2\"}].",
|
||||
"`[{\"user1\", \"localhost\", \"Workers\", \"User 1\"},\n"
|
||||
" {\"user2\", \"localhost\", \"Workers\", \"User 2\"}].`",
|
||||
module = ?MODULE, function = push_roster_all,
|
||||
args = [{file, binary}],
|
||||
args_example = [<<"/home/ejabberd/roster.txt">>],
|
||||
|
@ -624,7 +652,9 @@ get_commands_spec() ->
|
|||
|
||||
#ejabberd_commands{name = get_last, tags = [last],
|
||||
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`",
|
||||
module = ?MODULE, function = get_last,
|
||||
args = [{user, binary}, {host, binary}],
|
||||
|
@ -681,6 +711,18 @@ get_commands_spec() ->
|
|||
args_desc = ["Group identifier", "Group server name", "Group name",
|
||||
"Group description", "Groups to display"],
|
||||
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],
|
||||
desc = "Delete a Shared Roster Group",
|
||||
module = ?MODULE, function = srg_delete,
|
||||
|
@ -782,7 +824,9 @@ get_commands_spec() ->
|
|||
result = {res, rescode}},
|
||||
|
||||
#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,
|
||||
module = ?MODULE, function = stats,
|
||||
args = [{name, binary}],
|
||||
|
@ -792,7 +836,8 @@ get_commands_spec() ->
|
|||
result_desc = "Integer statistic value",
|
||||
result = {stat, integer}},
|
||||
#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,
|
||||
module = ?MODULE, function = stats,
|
||||
args = [{name, binary}, {host, binary}],
|
||||
|
@ -1081,14 +1126,10 @@ get_presence(U, S) ->
|
|||
{FullJID, Show, Status}
|
||||
end.
|
||||
|
||||
set_presence(User, Host, Resource, Type, Show, Status, Priority)
|
||||
when is_integer(Priority) ->
|
||||
BPriority = integer_to_binary(Priority),
|
||||
set_presence(User, Host, Resource, Type, Show, Status, BPriority);
|
||||
set_presence(User, Host, Resource, Type, Show, Status, Priority0) ->
|
||||
Priority = if is_integer(Priority0) -> Priority0;
|
||||
true -> binary_to_integer(Priority0)
|
||||
end,
|
||||
set_presence(User, Host, Resource, Type, Show, Status, Priority) when is_binary(Priority) ->
|
||||
set_presence(User, Host, Resource, Type, Show, Status, binary_to_integer(Priority));
|
||||
|
||||
set_presence(User, Host, Resource, Type, Show, Status, Priority) ->
|
||||
Pres = #presence{
|
||||
from = jid:make(User, Host, Resource),
|
||||
to = jid:make(User, Host),
|
||||
|
@ -1286,14 +1327,16 @@ update_vcard_els(Data, ContentList, Els1) ->
|
|||
%%% 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
|
||||
{error, _} ->
|
||||
throw({error, "Invalid 'localuser'/'localserver'"});
|
||||
{_, error} ->
|
||||
throw({error, "Invalid 'user'/'server'"});
|
||||
{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
|
||||
ok -> ok;
|
||||
_ -> error
|
||||
|
@ -1408,6 +1451,11 @@ push_roster_item(LU, LS, R, U, S, Action) ->
|
|||
ejabberd_router:route(
|
||||
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}) ->
|
||||
Groups = binary:split(Group,<<";">>, [global, trim]),
|
||||
#roster_item{jid = jid:make(U, S),
|
||||
|
@ -1488,11 +1536,14 @@ private_set2(Username, Host, Xml) ->
|
|||
%%% 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
|
||||
<<>> -> [];
|
||||
_ -> ejabberd_regexp:split(Display, <<"\\\\n">>)
|
||||
<<>> -> [];
|
||||
_ -> ejabberd_regexp:split(Display, <<"\\\\n">>)
|
||||
end,
|
||||
srg_create(Group, Host, Label, Description, DisplayList);
|
||||
|
||||
srg_create(Group, Host, Label, Description, DisplayList) ->
|
||||
Opts = [{label, Label},
|
||||
{displayed_groups, DisplayList},
|
||||
{description, Description}],
|
||||
|
|
|
@ -72,8 +72,7 @@ get_commands_spec() ->
|
|||
args_example = [],
|
||||
args_desc = [],
|
||||
result = {res, rescode},
|
||||
result_example = ok,
|
||||
result_desc = "Status code: 0 on success, 1 otherwise"}
|
||||
result_example = ok}
|
||||
].
|
||||
|
||||
update_sql() ->
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
-include("ejabberd_stacktrace.hrl").
|
||||
-include("translate.hrl").
|
||||
|
||||
-define(DEFAULT_API_VERSION, 0).
|
||||
-define(DEFAULT_API_VERSION, 1000000).
|
||||
|
||||
-define(CT_PLAIN,
|
||||
{<<"Content-Type">>, <<"text/plain">>}).
|
||||
|
@ -135,7 +135,7 @@ extract_auth(#request{auth = HTTPAuth, ip = {IP, _}, opts = Opts}) ->
|
|||
process(_, #request{method = 'POST', data = <<>>}) ->
|
||||
?DEBUG("Bad Request: no 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),
|
||||
try
|
||||
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]),
|
||||
badrequest_response()
|
||||
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),
|
||||
try
|
||||
Args = case Data of
|
||||
|
@ -412,7 +412,15 @@ format_command_result(Cmd, Auth, Result, Version) ->
|
|||
{_, T} = format_result(Result, ResultFormat),
|
||||
{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.
|
||||
|
||||
format_result(Atom, {Name, atom}) ->
|
||||
|
|
|
@ -93,10 +93,11 @@ depends(_Host, _Opts) ->
|
|||
get_commands_spec() ->
|
||||
[
|
||||
#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,
|
||||
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"],
|
||||
result_desc = "List of rooms",
|
||||
result_example = ["room1@muc.example.com", "room2@muc.example.com"],
|
||||
|
@ -104,10 +105,11 @@ get_commands_spec() ->
|
|||
args_rename = [{host, service}],
|
||||
result = {rooms, {list, {room, string}}}},
|
||||
#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,
|
||||
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"],
|
||||
args_example = ["muc.example.com", "^prefix"],
|
||||
result_desc = "List of rooms with summary",
|
||||
|
@ -160,7 +162,7 @@ get_commands_spec() ->
|
|||
args_example = ["/home/ejabberd/rooms.txt"],
|
||||
args = [{file, string}],
|
||||
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",
|
||||
longdesc =
|
||||
"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"],
|
||||
args = [{user, binary}, {host, binary}],
|
||||
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",
|
||||
note = "added in 21.04",
|
||||
module = ?MODULE, function = get_user_subscriptions,
|
||||
|
@ -306,6 +308,22 @@ get_commands_spec() ->
|
|||
args = [{name, binary}, {service, binary}, {password, binary},
|
||||
{reason, binary}, {users, binary}],
|
||||
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],
|
||||
desc = "Change an option in a MUC room",
|
||||
|
@ -329,7 +347,7 @@ get_commands_spec() ->
|
|||
{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",
|
||||
module = ?MODULE, function = subscribe_room,
|
||||
args_desc = ["User JID", "a user's nick",
|
||||
|
@ -342,7 +360,21 @@ get_commands_spec() ->
|
|||
args = [{user, binary}, {nick, binary}, {room, binary},
|
||||
{nodes, binary}],
|
||||
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",
|
||||
note = "added in 22.05",
|
||||
longdesc = "This command accepts up to 50 users at once "
|
||||
|
@ -365,14 +397,38 @@ get_commands_spec() ->
|
|||
{room, binary},
|
||||
{nodes, binary}],
|
||||
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",
|
||||
module = ?MODULE, function = unsubscribe_room,
|
||||
args_desc = ["User JID", "the room to subscribe"],
|
||||
args_example = ["tom@localhost", "room1@conference.localhost"],
|
||||
args = [{user, binary}, {room, binary}],
|
||||
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",
|
||||
module = ?MODULE, function = get_subscribers,
|
||||
args_desc = ["Room name", "MUC service"],
|
||||
|
@ -1072,20 +1128,22 @@ get_room_occupants_number(Room, Host) ->
|
|||
%%----------------------------
|
||||
%% 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
|
||||
error ->
|
||||
throw({error, "Invalid 'roomname' or 'service'"});
|
||||
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)
|
||||
|| UserJid <- Users],
|
||||
ok
|
||||
end.
|
||||
|
||||
get_users_to_invite(RoomJid, UsersString) ->
|
||||
UsersStrings = binary:split(UsersString, <<":">>, [global]),
|
||||
get_users_to_invite(RoomJid, UsersStrings) ->
|
||||
OccupantsTuples = get_room_occupants(RoomJid#jid.luser,
|
||||
RoomJid#jid.lserver),
|
||||
OccupantsJids = [jid:decode(JidString)
|
||||
|
@ -1437,8 +1495,10 @@ set_room_affiliation(Name, Service, JID, AffiliationString) ->
|
|||
|
||||
subscribe_room(_User, Nick, _Room, _Nodes) when Nick == <<"">> ->
|
||||
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*"),
|
||||
subscribe_room(User, Nick, Room, NodeList);
|
||||
subscribe_room(User, Nick, Room, NodeList) ->
|
||||
try jid:decode(Room) of
|
||||
#jid{luser = Name, lserver = Host} when Name /= <<"">> ->
|
||||
try jid:decode(User) of
|
||||
|
|
Loading…
Reference in New Issue