25
1
mirror of https://github.com/processone/ejabberd.git synced 2024-10-13 15:16:49 +02:00

Generate ejabberd.yml.5 man page from source code directly

Several documentation callbacks (doc/0 and mod_doc/0) are implemented
and `ejabberdctl man` command is added to generate a man page. Note
that the command requires a2x to be installed (which is a part of
asciidoc package).
This commit is contained in:
Evgeny Khramtsov 2020-01-08 12:24:51 +03:00
parent c40d8fe11b
commit 97da380acd
62 changed files with 4850 additions and 60 deletions

View File

@ -388,7 +388,11 @@ get_commands_spec() ->
#ejabberd_commands{name = gc, tags = [server],
desc = "Force full garbage collection",
module = ?MODULE, function = gc,
args = [], result = {res, rescode}}
args = [], result = {res, rescode}},
#ejabberd_commands{name = man, tags = [documentation],
desc = "Generate Unix manpage for current ejabberd version",
module = ejabberd_doc, function = man,
args = [], result = {res, restuple}}
].

View File

@ -36,6 +36,7 @@
-export([default_db/2, default_db/3, default_ram_db/2, default_ram_db/3]).
-export([beams/1, validators/1, globals/0, may_hide_data/1]).
-export([dump/0, dump/1, convert_to_yaml/1, convert_to_yaml/2]).
-export([callback_modules/1]).
%% Deprecated functions
-export([get_option/2, set_option/2]).
@ -63,6 +64,7 @@
-callback opt_type(atom()) -> econf:validator().
-callback options() -> [atom() | {atom(), term()}].
-callback globals() -> [atom()].
-callback doc() -> any().
-optional_callbacks([globals/0]).

459
src/ejabberd_doc.erl Normal file
View File

@ -0,0 +1,459 @@
%%%----------------------------------------------------------------------
%%% File : ejabberd_doc.erl
%%% Purpose : Options documentation generator
%%%
%%% ejabberd, Copyright (C) 2002-2019 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
%%% published by the Free Software Foundation; either version 2 of the
%%% License, or (at your option) any later version.
%%%
%%% This program is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License along
%%% with this program; if not, write to the Free Software Foundation, Inc.,
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%%
%%%----------------------------------------------------------------------
-module(ejabberd_doc).
%% API
-export([man/0, man/1, have_a2x/0]).
-include("translate.hrl").
%%%===================================================================
%%% API
%%%===================================================================
man() ->
man(<<"en">>).
man(Lang) when is_list(Lang) ->
man(list_to_binary(Lang));
man(Lang) ->
{ModDoc, SubModDoc} =
lists:foldl(
fun(M, {Mods, SubMods} = Acc) ->
case lists:prefix("mod_", atom_to_list(M)) orelse
lists:prefix("Elixir.Mod", atom_to_list(M)) of
true ->
try M:mod_doc() of
#{desc := Descr} = Map ->
DocOpts = maps:get(opts, Map, []),
Example = maps:get(example, Map, []),
{[{M, Descr, DocOpts, #{example => Example}}|Mods], SubMods};
#{opts := DocOpts} ->
{ParentMod, Backend} = strip_backend_suffix(M),
{Mods, dict:append(ParentMod, {M, Backend, DocOpts}, SubMods)}
catch _:undef ->
case erlang:function_exported(
M, mod_options, 1) of
true ->
warn("module ~s is not documented", [M]);
false ->
ok
end,
Acc
end;
false ->
Acc
end
end, {[], dict:new()}, ejabberd_config:beams(all)),
Doc = lists:flatmap(
fun(M) ->
try M:doc()
catch _:undef -> []
end
end, ejabberd_config:callback_modules(all)),
Options =
["TOP LEVEL OPTIONS",
"-----------------",
tr(Lang, ?T("This section describes top level options of ejabberd.")),
io_lib:nl()] ++
lists:flatmap(
fun(Opt) ->
opt_to_man(Lang, Opt, 1)
end, lists:keysort(1, Doc)),
ModDoc1 = lists:map(
fun({M, Descr, DocOpts, Ex}) ->
case dict:find(M, SubModDoc) of
{ok, Backends} ->
{M, Descr, DocOpts, Backends, Ex};
error ->
{M, Descr, DocOpts, [], Ex}
end
end, ModDoc),
ModOptions =
[io_lib:nl(),
"MODULES",
"-------",
"[[modules]]",
tr(Lang, ?T("This section describes options of all ejabberd modules.")),
io_lib:nl()] ++
lists:flatmap(
fun({M, Descr, DocOpts, Backends, Example}) ->
ModName = atom_to_list(M),
[io_lib:nl(),
ModName,
lists:duplicate(length(atom_to_list(M)), $~),
"[[" ++ ModName ++ "]]",
io_lib:nl()] ++
tr_multi(Lang, Descr) ++ [io_lib:nl()] ++
opts_to_man(Lang, [{M, '', DocOpts}|Backends]) ++
format_example(0, Lang, Example)
end, lists:keysort(1, ModDoc1)),
ListenOptions =
[io_lib:nl(),
"LISTENERS",
"-------",
"[[listeners]]",
tr(Lang, ?T("This section describes options of all ejabberd listeners.")),
io_lib:nl(),
"TODO"],
AsciiData =
[[unicode:characters_to_binary(Line), io_lib:nl()]
|| Line <- man_header(Lang) ++ Options ++ [io_lib:nl()]
++ ModOptions ++ ListenOptions ++ man_footer(Lang)],
warn_undocumented_modules(ModDoc1),
warn_undocumented_options(Doc),
write_man(AsciiData).
%%%===================================================================
%%% Internal functions
%%%===================================================================
opts_to_man(Lang, [{_, _, []}]) ->
Text = tr(Lang, ?T("The module has no options.")),
[Text, io_lib:nl()];
opts_to_man(Lang, Backends) ->
lists:flatmap(
fun({_, Backend, DocOpts}) when DocOpts /= [] ->
Text = if Backend == '' ->
tr(Lang, ?T("Available options"));
true ->
lists:flatten(
io_lib:format(
tr(Lang, ?T("Available options for '~s' backend")),
[Backend]))
end,
[Text ++ ":", lists:duplicate(length(Text)+1, $^)|
lists:flatmap(
fun(Opt) -> opt_to_man(Lang, Opt, 1) end,
lists:keysort(1, DocOpts))] ++ [io_lib:nl()];
(_) ->
[]
end, Backends).
opt_to_man(Lang, {Option, Options}, Level) ->
[format_option(Lang, Option, Options)|format_desc(Lang, Options)] ++
format_example(Level, Lang, Options);
opt_to_man(Lang, {Option, Options, Children}, Level) ->
[format_option(Lang, Option, Options)|format_desc(Lang, Options)] ++
lists:append(
[[H ++ ":"|T]
|| [H|T] <- lists:map(
fun(Opt) -> opt_to_man(Lang, Opt, Level+1) end,
lists:keysort(1, Children))]) ++
[io_lib:nl()|format_example(Level, Lang, Options)].
format_option(Lang, Option, #{value := Val}) ->
"*" ++ atom_to_list(Option) ++ "*: 'pass:[" ++
tr(Lang, Val) ++ "]'::";
format_option(_Lang, Option, #{}) ->
"*" ++ atom_to_list(Option) ++ "*::".
format_desc(Lang, #{desc := Desc}) ->
tr_multi(Lang, Desc).
format_example(Level, Lang, #{example := [_|_] = Example}) ->
case lists:all(fun is_list/1, Example) of
true ->
if Level == 0 ->
["*Example*:",
"^^^^^^^^^^"];
true ->
["+", "*Example*:", "+"]
end ++ format_yaml(Example);
false when Level == 0 ->
["Examples:",
"^^^^^^^^^"] ++
lists:flatmap(
fun({Text, Lines}) ->
[tr(Lang, Text)] ++ format_yaml(Lines)
end, Example);
false ->
lists:flatmap(
fun(Block) ->
["+", "''''", "+"|Block]
end,
lists:map(
fun({Text, Lines}) ->
[tr(Lang, Text), "+"] ++ format_yaml(Lines)
end, Example))
end;
format_example(_, _, _) ->
[].
format_yaml(Lines) ->
["==========================",
"[source,yaml]",
"----"|Lines] ++
["----",
"=========================="].
man_header(Lang) ->
["ejabberd.yml(5)",
"===============",
":doctype: manpage",
":version: " ++ binary_to_list(ejabberd_config:version()),
io_lib:nl(),
"NAME",
"----",
"ejabberd.yml - " ++ tr(Lang, ?T("main configuration file for ejabberd.")),
io_lib:nl(),
"SYNOPSIS",
"--------",
"ejabberd.yml",
io_lib:nl(),
"DESCRIPTION",
"-----------",
tr(Lang, ?T("The configuration file is written in "
"https://en.wikipedia.org/wiki/YAML[YAML] language.")),
io_lib:nl(),
tr(Lang, ?T("WARNING: YAML is indentation sensitive, so make sure you respect "
"indentation, or otherwise you will get pretty cryptic "
"configuration errors.")),
io_lib:nl(),
tr(Lang, ?T("Logically, configuration options are splitted into 3 main categories: "
"'Modules', 'Listeners' and everything else called 'Top Level' options. "
"Thus this document is splitted into 3 main chapters describing each "
"category separately. So, the contents of ejabberd.yml will typically "
"look like this:")),
io_lib:nl(),
"==========================",
"[source,yaml]",
"----",
"hosts:",
" - example.com",
" - domain.tld",
"loglevel: info",
"...",
"listen:",
" -",
" port: 5222",
" module: ejabberd_c2s",
" ...",
"modules:",
" mod_roster: {}",
" ...",
"----",
"==========================",
io_lib:nl(),
tr(Lang, ?T("Any configuration error (such as syntax error, unknown option "
"or invalid option value) is fatal in the sense that ejabberd will "
"refuse to load the whole configuration file and will not start or will "
"abort configuration reload.")),
io_lib:nl(),
tr(Lang, ?T("All options can be changed in runtime by running 'ejabberdctl "
"reload-config' command. Configuration reload is atomic: either all options "
"are accepted and applied simultaneously or the new configuration is "
"refused without any impact on currently running configuration.")),
io_lib:nl(),
tr(Lang, ?T("Some options can be specified for particular virtual host(s) only "
"using 'host_config' or 'append_host_config' options. Such options "
"are called 'local'. Examples are 'modules', 'auth_method' and 'default_db'. "
"The options that cannot be defined per virtual host are called 'global'. "
"Examples are 'loglevel', 'certfiles' and 'listen'. It is a configuration "
"mistake to put 'global' options under 'host_config' or 'append_host_config' "
"section - ejabberd will refuse to load such configuration.")),
io_lib:nl(),
str:format(
tr(Lang, ?T("It is not recommended to write ejabberd.yml from scratch. Instead it is "
"better to start from \"default\" configuration file available at ~s. "
"Once you get ejabberd running you can start changing configuration "
"options to meet your requirements.")),
[default_config_url()]),
io_lib:nl(),
str:format(
tr(Lang, ?T("Note that this document is intended to provide comprehensive description of "
"all configuration options that can be consulted to understand the meaning "
"of a particular option, its format and possible values. It will be quite "
"hard to understand how to configure ejabberd by reading this document only "
"- for this purpose the reader is recommended to read online Configuration "
"Guide available at ~s.")),
[configuration_guide_url()]),
io_lib:nl()].
man_footer(Lang) ->
{Year, _, _} = date(),
[io_lib:nl(),
"AUTHOR",
"------",
"https://www.process-one.net[ProcessOne].",
io_lib:nl(),
"VERSION",
"-------",
str:format(
tr(Lang, ?T("This document describes the configuration file of ejabberd ~ts. "
"Configuration options of other ejabberd versions "
"may differ significantly.")),
[ejabberd_config:version()]),
io_lib:nl(),
"REPORTING BUGS",
"--------------",
tr(Lang, ?T("Report bugs to <https://github.com/processone/ejabberd/issues>")),
io_lib:nl(),
"SEE ALSO",
"---------",
tr(Lang, ?T("Default configuration file")) ++ ": " ++ default_config_url(),
io_lib:nl(),
tr(Lang, ?T("Main site")) ++ ": <https://ejabberd.im>",
io_lib:nl(),
tr(Lang, ?T("Documentation")) ++ ": <https://docs.ejabberd.im>",
io_lib:nl(),
tr(Lang, ?T("Configuration Guide")) ++ ": " ++ configuration_guide_url(),
io_lib:nl(),
tr(Lang, ?T("Source code")) ++ ": <https://github.com/processone/ejabberd>",
io_lib:nl(),
"COPYING",
"-------",
"Copyright (c) 2002-" ++ integer_to_list(Year) ++
" https://www.process-one.net[ProcessOne]."].
tr(Lang, {Format, Args}) ->
unicode:characters_to_list(
str:format(
translate:translate(Lang, iolist_to_binary(Format)),
Args));
tr(Lang, Txt) ->
unicode:characters_to_list(translate:translate(Lang, iolist_to_binary(Txt))).
tr_multi(Lang, Txt) when is_binary(Txt) ->
tr_multi(Lang, [Txt]);
tr_multi(Lang, {Format, Args}) ->
tr_multi(Lang, [{Format, Args}]);
tr_multi(Lang, Lines) when is_list(Lines) ->
[tr(Lang, Txt) || Txt <- Lines].
write_man(AsciiData) ->
case file:get_cwd() of
{ok, Cwd} ->
AsciiDocFile = filename:join(Cwd, "ejabberd.yml.5.txt"),
ManPage = filename:join(Cwd, "ejabberd.yml.5"),
case file:write_file(AsciiDocFile, AsciiData) of
ok ->
Ret = run_a2x(Cwd, AsciiDocFile),
%%file:delete(AsciiDocFile),
case Ret of
ok ->
{ok, lists:flatten(
io_lib:format(
"The manpage saved as ~ts", [ManPage]))};
{error, Error} ->
{error, lists:flatten(
io_lib:format(
"Failed to generate manpage: ~ts", [Error]))}
end;
{error, Reason} ->
{error, lists:flatten(
io_lib:format(
"Failed to write to ~ts: ~s",
[AsciiDocFile, file:format_error(Reason)]))}
end;
{error, Reason} ->
{error, lists:flatten(
io_lib:format("Failed to get current directory: ~s",
[file:format_error(Reason)]))}
end.
have_a2x() ->
case os:find_executable("a2x") of
false -> false;
Path -> {true, Path}
end.
run_a2x(Cwd, AsciiDocFile) ->
case have_a2x() of
false ->
{error, "a2x was not found: do you have 'asciidoc' installed?"};
{true, Path} ->
Cmd = lists:flatten(
io_lib:format("~ts -f manpage ~ts -D ~ts",
[Path, AsciiDocFile, Cwd])),
case os:cmd(Cmd) of
"" -> ok;
Ret -> {error, Ret}
end
end.
warn_undocumented_modules(Docs) ->
lists:foreach(
fun({M, _, DocOpts, Backends, _}) ->
warn_undocumented_module(M, DocOpts),
lists:foreach(
fun({SubM, _, SubOpts}) ->
warn_undocumented_module(SubM, SubOpts)
end, Backends)
end, Docs).
warn_undocumented_module(M, DocOpts) ->
try M:mod_options(ejabberd_config:get_myname()) of
Defaults ->
lists:foreach(
fun(OptDefault) ->
Opt = case OptDefault of
O when is_atom(O) -> O;
{O, _} -> O
end,
case lists:keymember(Opt, 1, DocOpts) of
false ->
warn("~s: option ~s is not documented",
[M, Opt]);
true ->
ok
end
end, Defaults)
catch _:undef ->
ok
end.
warn_undocumented_options(Docs) ->
Opts = lists:flatmap(
fun(M) ->
try M:options() of
Defaults ->
lists:map(
fun({O, _}) -> O;
(O) when is_atom(O) -> O
end, Defaults)
catch _:undef ->
[]
end
end, ejabberd_config:callback_modules(all)),
lists:foreach(
fun(Opt) ->
case lists:keymember(Opt, 1, Docs) of
false ->
warn("option ~s is not documented", [Opt]);
true ->
ok
end
end, Opts).
warn(Format, Args) ->
io:format(standard_error, "Warning: " ++ Format ++ "~n", Args).
strip_backend_suffix(M) ->
[H|T] = lists:reverse(string:tokens(atom_to_list(M), "_")),
{list_to_atom(string:join(lists:reverse(T), "_")), list_to_atom(H)}.
default_config_url() ->
"<https://github.com/processone/ejabberd/blob/" ++
binary_to_list(binary:part(ejabberd_config:version(), {0,5})) ++
"/ejabberd.yml.example>".
configuration_guide_url() ->
"<https://docs.ejabberd.im/admin/configuration>".

View File

@ -19,7 +19,7 @@
-module(ejabberd_options).
-behaviour(ejabberd_config).
-export([opt_type/1, options/0, globals/0]).
-export([opt_type/1, options/0, globals/0, doc/0]).
-ifdef(NEW_SQL_SCHEMA).
-define(USE_NEW_SQL_SCHEMA_DEFAULT, true).
@ -730,6 +730,9 @@ globals() ->
websocket_ping_interval,
websocket_timeout].
doc() ->
ejabberd_options_doc:doc().
%%%===================================================================
%%% Internal functions
%%%===================================================================

1256
src/ejabberd_options_doc.erl Normal file

File diff suppressed because it is too large Load Diff

View File

@ -53,12 +53,18 @@
-type opts() :: #{atom() => term()}.
-type db_type() :: atom().
-type opt_desc() :: #{desc => binary() | [binary()],
value => string() | binary()}.
-type opt_doc() :: {atom(), opt_desc()} | {atom(), opt_desc(), [opt_doc()]}.
-callback start(binary(), opts()) -> ok | {ok, pid()} | {error, term()}.
-callback stop(binary()) -> any().
-callback reload(binary(), opts(), opts()) -> ok | {ok, pid()} | {error, term()}.
-callback mod_opt_type(atom()) -> econf:validator().
-callback mod_options(binary()) -> [{atom(), term()} | atom()].
-callback mod_doc() -> #{desc => binary() | [binary()],
opts => [opt_doc()],
example => [string()] | [{binary(), [string()]}]}.
-callback depends(binary(), opts()) -> [{module(), hard | soft}].
-optional_callbacks([mod_opt_type/1, reload/3]).

View File

@ -36,7 +36,7 @@
get_local_identity/5, get_local_features/5,
get_sm_commands/5, get_sm_identity/5, get_sm_features/5,
ping_item/4, ping_command/4, mod_opt_type/1, depends/2,
mod_options/1]).
mod_options/1, mod_doc/0]).
-include("logger.hrl").
-include("xmpp.hrl").
@ -278,3 +278,15 @@ mod_opt_type(report_commands_node) ->
mod_options(_Host) ->
[{report_commands_node, false}].
mod_doc() ->
#{desc =>
?T("This module implements https://xmpp.org/extensions/xep-0050.html"
"[XEP-0050: Ad-Hoc Commands]. It's an auxiliary module and is "
"only needed by some of the other modules."),
opts =>
[{report_commands_node,
#{value => "true | false",
desc =>
?T("Provide the Commands item in the Service Disvocery. "
"Default value: 'false'.")}}]}.

View File

@ -29,9 +29,10 @@
-behaviour(gen_mod).
-include("logger.hrl").
-include("translate.hrl").
-export([start/2, stop/1, reload/3, mod_options/1,
get_commands_spec/0, depends/2]).
get_commands_spec/0, depends/2, mod_doc/0]).
% Commands API
-export([
@ -1589,3 +1590,59 @@ num_prio(_) ->
-1.
mod_options(_) -> [].
mod_doc() ->
#{desc =>
[?T("This module provides additional administrative commands."), "",
?T("Details for some commands:"), "",
?T("- 'ban-acount':"),
?T("This command kicks all the connected sessions of the
account from the server. It also changes their password to
a randomly generated one, so they can't login anymore
unless a server administrator changes their password
again. It is possible to define the reason of the ban. The
new password also includes the reason and the date and time
of the ban. For example, if this command is called:
'ejabberdctl vhost example.org ban-account boby \"Spammed
rooms\"', then the sessions of the local account which JID
is boby@example.org will be kicked, and its password will
be set to something like this:
'BANNED_ACCOUNT--20080425T21:45:07--2176635--Spammed_rooms'"),
?T("- 'pushroster' (and 'pushroster-all'):"),
?T("The roster file must be placed, if using Windows, on
the directory where you installed ejabberd: C:/Program
Files/ejabberd or similar. If you use other Operating
System, place the file on the same directory where the
.beam files are installed. See below an example roster
file."),
?T("- 'srg-create':"),
?T("If you want to put a group Name with blankspaces, use
the characters \"\' and \\'\" to define when the Name
starts and ends. For example: 'ejabberdctl srg-create g1
example.org \"\'Group number 1\\'\" this_is_g1 g1'")],
opts =>
[{module_resource,
#{value => ?T("Resource"),
desc =>
?T("Indicate the resource that the XMPP stanzas must
use in the FROM or TO JIDs. This is only useful in
the 'get_vcard*' and 'set_vcard*' commands. The
default value is 'mod_admin_extra'.")}}],
example =>
[{?T("With this configuration, vCards can only be modified
with mod_admin_extra commands:"),
["acl:",
" adminextraresource:",
" - resource: \"modadminextraf8x,31ad\"",
"access_rules:",
" vcard_set:",
" - allow: adminextraresource",
"modules:",
" mod_admin_extra:",
" module_resource: \"modadminextraf8x,31ad\"",
" mod_vcard:",
" access_set: vcard_set"]},
{?T("Content of roster file for 'pushroster' command:"),
["[{<<\"bob\">>, <<\"example.org\">>, <<\"workers\">>, <<\"Bob\">>},",
"{<<\"mart\">>, <<\"example.org\">>, <<\"workers\">>, <<\"Mart\">>},",
"{<<\"Rich\">>, <<\"example.org\">>, <<\"bosses\">>, <<\"Rich\">>}]."]}]}.

View File

@ -29,7 +29,7 @@
-behaviour(gen_mod).
-export([start/2, stop/1, reload/3, mod_options/1,
get_commands_spec/0, depends/2]).
get_commands_spec/0, depends/2, mod_doc/0]).
% Commands API
-export([update_sql/0]).
@ -39,6 +39,7 @@
-include("ejabberd_commands.hrl").
-include("xmpp.hrl").
-include("ejabberd_sql_pt.hrl").
-include("translate.hrl").
%%%
%%% gen_mod
@ -358,3 +359,9 @@ sql_query(Host, Query) ->
end.
mod_options(_) -> [].
mod_doc() ->
#{desc =>
?T("This module can be used to update existing SQL database "
"from 'old' to 'new' schema. When the module is loaded "
"use 'update_sql' ejabberdctl command.")}.

View File

@ -35,7 +35,7 @@
-export([start/2, stop/1, reload/3, export/1, import_info/0,
import_start/2, import/5, announce/1, send_motd/1, disco_identity/5,
disco_features/5, disco_items/5, depends/2,
send_announcement_to_all/3, announce_commands/4,
send_announcement_to_all/3, announce_commands/4, mod_doc/0,
announce_items/4, mod_opt_type/1, mod_options/1, clean_cache/1]).
-export([init/1, handle_call/3, handle_cast/2,
handle_info/2, terminate/2, code_change/3]).
@ -917,3 +917,64 @@ mod_options(Host) ->
{cache_size, ejabberd_option:cache_size(Host)},
{cache_missed, ejabberd_option:cache_missed(Host)},
{cache_life_time, ejabberd_option:cache_life_time(Host)}].
mod_doc() ->
#{desc =>
[?T("This module enables configured users to broadcast "
"announcements and to set the message of the day (MOTD). "
"Configured users can perform these actions with an XMPP "
"client either using Ad-hoc Commands or sending messages "
"to specific JIDs."), "",
?T("The Ad-hoc Commands are listed in the Server Discovery. "
"For this feature to work, 'mod_adhoc' must be enabled."), "",
?T("The specific JIDs where messages can be sent are listed below. "
"The first JID in each entry will apply only to the specified "
"virtual host example.org, while the JID between brackets "
"will apply to all virtual hosts in ejabberd:"), "",
"example.org/announce/all (example.org/announce/all-hosts/all)::",
?T("The message is sent to all registered users. If the user is "
"online and connected to several resources, only the resource "
"with the highest priority will receive the message. "
"If the registered user is not connected, the message will be "
"stored offline in assumption that offline storage (see 'mod_offline') "
"is enabled."),
"example.org/announce/online (example.org/announce/all-hosts/online)::",
?T("The message is sent to all connected users. If the user is "
"online and connected to several resources, all resources will "
"receive the message."),
"example.org/announce/motd (example.org/announce/all-hosts/motd)::",
?T("The message is set as the message of the day (MOTD) and is sent "
"to users when they login. In addition the message is sent to all "
"connected users (similar to announce/online)."),
"example.org/announce/motd/update (example.org/announce/all-hosts/motd/update)::",
?T("The message is set as message of the day (MOTD) and is sent to users "
"when they login. The message is not sent to any currently connected user."),
"example.org/announce/motd/delete (example.org/announce/all-hosts/motd/delete)::",
?T("Any message sent to this JID removes the existing message of the day (MOTD).")],
opts =>
[{access,
#{value => ?T("AccessName"),
desc =>
?T("This option specifies who is allowed to send announcements "
"and to set the message of the day. The default value is 'none' "
"(i.e. nobody is able to send such messages).")}},
{db_type,
#{value => "mnesia | sql",
desc =>
?T("Same as top-level 'default_db' option, but applied to this module only.")}},
{use_cache,
#{value => "true | false",
desc =>
?T("Same as top-level 'use_cache' option, but applied to this module only.")}},
{cache_size,
#{value => "pos_integer() | infinity",
desc =>
?T("Same as top-level 'cache_size' option, but applied to this module only.")}},
{cache_missed,
#{value => "true | false",
desc =>
?T("Same as top-level 'cache_missed' option, but applied to this module only.")}},
{cache_life_time,
#{value => "timeout()",
desc =>
?T("Same as top-level 'cache_life_time' option, but applied to this module only.")}}]}.

View File

@ -26,6 +26,7 @@
%% gen_mod API
-export([start/2, stop/1, reload/3, depends/2, mod_opt_type/1, mod_options/1]).
-export([mod_doc/0]).
%% Hooks
-export([pubsub_publish_item/6, vcard_iq_convert/1, vcard_iq_publish/1,
get_sm_features/5]).
@ -33,6 +34,7 @@
-include("xmpp.hrl").
-include("logger.hrl").
-include("pubsub.hrl").
-include("translate.hrl").
-type avatar_id_meta() :: #{avatar_meta => {binary(), avatar_meta()}}.
-opaque convert_rule() :: {default | eimp:img_type(), eimp:img_type()}.
@ -458,3 +460,36 @@ mod_opt_type(rate_limit) ->
mod_options(_) ->
[{rate_limit, 10},
{convert, []}].
mod_doc() ->
#{desc =>
[?T("The purpose of the module is to cope with legacy and modern "
"XMPP clients posting avatars. The process is described in "
"https://xmpp.org/extensions/xep-0398.html"
"[XEP-0398: User Avatar to vCard-Based Avatars Conversion]."), "",
?T("Also, the module supports conversion between avatar "
"image formats on the fly."), "",
?T("The module depends on 'mod_vcard', 'mod_vcard_xupdate' and "
"'mod_pubsub'.")],
opts =>
[{convert,
#{value => "{From: To}",
desc =>
?T("Defines image convertion rules: the format in 'From' "
"will be converted to format in 'To'. The value of 'From' "
"can also be 'default', which is match-all rule. NOTE: "
"the list of supported formats is detected at compile time "
"depending on the image libraries installed in the system."),
example =>
[{?T("In this example avatars in WebP format are "
"converted to JPEG, all other formats are "
"converted to PNG:"),
["convert:",
" webp: jpg",
" default: png"]}]}},
{rate_limit,
#{value => ?T("Number"),
desc =>
?T("Limit any given JID by the number of avatars it is able "
"to convert per minute. This is to protect the server from "
"image convertion DoS. The default value is '10'.")}}]}.

View File

@ -29,7 +29,7 @@
-behaviour(gen_mod).
%% API
-export([start/2, stop/1, reload/3,
-export([start/2, stop/1, reload/3, mod_doc/0,
depends/2, mod_opt_type/1, mod_options/1]).
-export([filter_packet/1, filter_offline_msg/1, filter_subscription/2]).
@ -260,3 +260,54 @@ mod_options(_) ->
{captcha, false},
{allow_local_users, true},
{allow_transports, true}].
mod_doc() ->
#{desc =>
?T("This module allows to block/log messages coming from an "
"unknown entity. If a writing entity is not in your roster, "
"you can let this module drop and/or log the message. "
"By default you'll just not receive message from that entity. "
"Enable this module if you want to drop SPAM messages."),
opts =>
[{access,
#{value => ?T("AccessName"),
desc =>
?T("The option is supposed to be used when 'allow_local_users' "
"and 'allow_transports' are not enough. It's an ACL where "
"'deny' means the message will be rejected (or a CAPTCHA "
"would be generated for a presence, if configured), and "
"'allow' means the sender is whitelisted and the stanza "
"will pass through. The default value is 'none', which "
"means nothing is whitelisted.")}},
{drop,
#{value => "true | false",
desc =>
?T("This option specifies if strangers messages should "
"be dropped or not. The default value is 'true'.")}},
{log,
#{value => "true | false",
desc =>
?T("This option specifies if strangers' messages should "
"be logged (as info message) in ejabberd.log. "
"The default value is 'false'.")}},
{allow_local_users,
#{value => "true | false",
desc =>
?T("This option specifies if strangers from the same "
"local host should be accepted or not. "
"The default value is 'true'.")}},
{allow_transports,
#{value => "true | false",
desc =>
?T("If set to 'true' and some server's JID is in user's "
"roster, then messages from any user of this server "
"are accepted even if no subscription present. "
"The default value is 'true'.")}},
{captcha,
#{value => "true | false",
desc =>
?T("Whether to generate CAPTCHA or not in response to "
"messages from strangers. See also section "
"https://docs.ejabberd.im/admin/configuration/#captcha"
"[CAPTCHA] of the Configuration Guide. "
"The default value is 'false'.")}}]}.

View File

@ -30,7 +30,7 @@
-protocol({xep, 191, '1.2'}).
-export([start/2, stop/1, reload/3, process_iq/1, depends/2,
disco_features/5, mod_options/1]).
disco_features/5, mod_options/1, mod_doc/0]).
-include("logger.hrl").
-include("xmpp.hrl").
@ -265,3 +265,11 @@ err_db_failure(#iq{lang = Lang} = IQ) ->
mod_options(_Host) ->
[].
mod_doc() ->
#{desc =>
[?T("The module implements "
"https://xmpp.org/extensions/xep-0191.html"
"[XEP-0191: Blocking Command]."), "",
?T("This module depends on 'mod_privacy' where "
"all the configuration is performed.")]}.

View File

@ -36,13 +36,14 @@
-export([start/2, stop/1, reload/3, process/2, open_session/2,
close_session/1, find_session/1, clean_cache/1]).
-export([depends/2, mod_opt_type/1, mod_options/1]).
-export([depends/2, mod_opt_type/1, mod_options/1, mod_doc/0]).
-include("logger.hrl").
-include_lib("stdlib/include/ms_transform.hrl").
-include("xmpp.hrl").
-include("ejabberd_http.hrl").
-include("bosh.hrl").
-include("translate.hrl").
-callback init() -> any().
-callback open_session(binary(), pid()) -> ok | {error, any()}.
@ -197,6 +198,51 @@ mod_options(Host) ->
{cache_missed, ejabberd_option:cache_missed(Host)},
{cache_life_time, ejabberd_option:cache_life_time(Host)}].
mod_doc() ->
#{desc =>
?T("This module implements XMPP over BOSH as defined in "
"https://xmpp.org/extensions/xep-0124.html[XEP-0124] and "
"https://xmpp.org/extensions/xep-0206.html[XEP-0206]. BOSH "
"stands for Bidirectional-streams Over Synchronous HTTP. "
"It makes it possible to simulate long lived connections "
"required by XMPP over the HTTP protocol. In practice, "
"this module makes it possible to use XMPP in a browser without "
"Websocket support and more generally to have a way to use "
"XMPP while having to get through an HTTP proxy."),
opts =>
[{json,
#{value => "true | false",
desc => ?T("This option has no effect.")}},
{max_inactivity,
#{value => "timeout()",
desc =>
?T("The option defines the maximum inactivity period. "
"The default value is '30' seconds.")}},
{queue_type,
#{value => "ram | file",
desc =>
?T("Same as top-level 'queue_type' option, but applied to this module only.")}},
{ram_db_type,
#{value => "mnesia | sql | redis",
desc =>
?T("Same as 'default_ram_db' but applied to this module only.")}},
{use_cache,
#{value => "true | false",
desc =>
?T("Same as top-level 'use_cache' option, but applied to this module only.")}},
{cache_size,
#{value => "pos_integer() | infinity",
desc =>
?T("Same as top-level 'cache_size' option, but applied to this module only.")}},
{cache_missed,
#{value => "true | false",
desc =>
?T("Same as top-level 'cache_missed' option, but applied to this module only.")}},
{cache_life_time,
#{value => "timeout()",
desc =>
?T("Same as top-level 'cache_life_time' option, but applied to this module only.")}}]}.
%%%----------------------------------------------------------------------
%%% Cache stuff
%%%----------------------------------------------------------------------

View File

@ -49,12 +49,13 @@
handle_cast/2, terminate/2, code_change/3]).
-export([user_send_packet/1, user_receive_packet/1,
c2s_presence_in/2, mod_opt_type/1, mod_options/1]).
c2s_presence_in/2, mod_opt_type/1, mod_options/1, mod_doc/0]).
-include("logger.hrl").
-include("xmpp.hrl").
-include("mod_caps.hrl").
-include("translate.hrl").
-define(BAD_HASH_LIFETIME, 600).
@ -563,3 +564,32 @@ mod_options(Host) ->
{cache_size, ejabberd_option:cache_size(Host)},
{cache_missed, ejabberd_option:cache_missed(Host)},
{cache_life_time, ejabberd_option:cache_life_time(Host)}].
mod_doc() ->
#{desc =>
[?T("This module implements "
"https://xmpp.org/extensions/xep-0115.html"
"[XEP-0115: Entity Capabilities]."),
?T("The main purpose of the module is to provide "
"PEP functionality (see 'mod_pubsub').")],
opts =>
[{db_type,
#{value => "mnesia | sql",
desc =>
?T("Same as top-level 'default_db' option, but applied to this module only.")}},
{use_cache,
#{value => "true | false",
desc =>
?T("Same as top-level 'use_cache' option, but applied to this module only.")}},
{cache_size,
#{value => "pos_integer() | infinity",
desc =>
?T("Same as top-level 'cache_size' option, but applied to this module only.")}},
{cache_missed,
#{value => "true | false",
desc =>
?T("Same as top-level 'cache_missed' option, but applied to this module only.")}},
{cache_life_time,
#{value => "timeout()",
desc =>
?T("Same as top-level 'cache_life_time' option, but applied to this module only.")}}]}.

View File

@ -37,7 +37,7 @@
-export([user_send_packet/1, user_receive_packet/1,
iq_handler/1, disco_features/5,
is_carbon_copy/1, depends/2,
mod_options/1]).
mod_options/1, mod_doc/0]).
-export([c2s_copy_session/2, c2s_session_opened/1, c2s_session_resumed/1]).
%% For debugging purposes
-export([list/2]).
@ -301,3 +301,10 @@ depends(_Host, _Opts) ->
mod_options(_) ->
[].
mod_doc() ->
#{desc =>
?T("The module implements https://xmpp.org/extensions/xep-0280.html"
"[XEP-0280: Message Carbons]. "
"The module broadcasts messages on all connected "
"user resources (devices).")}.

View File

@ -32,6 +32,7 @@
%% gen_mod callbacks.
-export([start/2, stop/1, reload/3, mod_opt_type/1, depends/2, mod_options/1]).
-export([mod_doc/0]).
%% ejabberd_hooks callbacks.
-export([filter_presence/1, filter_chat_states/1,
@ -42,6 +43,7 @@
-include("logger.hrl").
-include("xmpp.hrl").
-include("translate.hrl").
-define(CSI_QUEUE_MAX, 100).
@ -151,6 +153,38 @@ mod_options(_) ->
{queue_chat_states, true},
{queue_pep, true}].
mod_doc() ->
#{desc =>
[?T("This module allows for queueing certain types of stanzas "
"when a client indicates that the user is not actively using "
"the client right now (see https://xmpp.org/extensions/xep-0352.html"
"[XEP-0352: Client State Indication]). This can save bandwidth and "
"resources."), "",
?T("A stanza is dropped from the queue if it's effectively obsoleted "
"by a new one (e.g., a new presence stanza would replace an old "
"one from the same client). The queue is flushed if a stanza arrives "
"that won't be queued, or if the queue size reaches a certain limit "
"(currently 100 stanzas), or if the client becomes active again.")],
opts =>
[{queue_presence,
#{value => "true | false",
desc =>
?T("While a client is inactive, queue presence stanzas "
"that indicate (un)availability. The default value is 'true'.")}},
{queue_chat_states,
#{value => "true | false",
desc =>
?T("Queue \"standalone\" chat state notifications (as defined in "
"https://xmpp.org/extensions/xep-0085.html"
"[XEP-0085: Chat State Notifications]) while a client "
"indicates inactivity. The default value is 'true'.")}},
{queue_pep,
#{value => "true | false",
desc =>
?T("Queue PEP notifications while a client is inactive. "
"When the queue is flushed, only the most recent notification "
"of a given PEP node is delivered. The default value is 'true'.")}}]}.
-spec depends(binary(), gen_mod:opts()) -> [{module(), hard | soft}].
depends(_Host, _Opts) ->
[].

View File

@ -36,7 +36,7 @@
adhoc_local_items/4, adhoc_local_commands/4,
get_sm_identity/5, get_sm_features/5, get_sm_items/5,
adhoc_sm_items/4, adhoc_sm_commands/4, mod_options/1,
depends/2]).
depends/2, mod_doc/0]).
-include("logger.hrl").
-include("xmpp.hrl").
@ -1558,3 +1558,10 @@ tr(Lang, Text) ->
translate:translate(Lang, Text).
mod_options(_) -> [].
mod_doc() ->
#{desc =>
?T("The module provides server configuration functionality via "
"https://xmpp.org/extensions/xep-0050.html"
"[XEP-0050: Ad-Hoc Commands]. This module requires "
"'mod_adhoc' to be loaded.")}.

View File

@ -32,6 +32,7 @@
%% API
-export([start/2, stop/1, reload/3, mod_opt_type/1, depends/2, mod_options/1]).
-export([mod_doc/0]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
@ -82,6 +83,51 @@ mod_opt_type(namespaces) ->
mod_options(_Host) ->
[{namespaces, []}].
mod_doc() ->
#{desc =>
?T("This module is an implementation of "
"https://xmpp.org/extensions/xep-0355.html"
"[XEP-0355: Namespace Delegation]. "
"Only admin mode has been implemented by now. "
"Namespace delegation allows external services to "
"handle IQ using specific namespace. This may be applied "
"for external PEP service."),
opts =>
[{namespaces,
#{value => "{Namespace: Options}",
desc =>
?T("If you want to delegate namespaces to a component, "
"specify them in this option, and associate them "
"to an access rule. The 'Options' are:")},
[{filtering,
#{value => ?T("Attributes"),
desc =>
?T("The list of attributes. Currently not used.")}},
{access,
#{value => ?T("AccessName"),
desc =>
?T("The option defines which components are allowed "
"for namespace delegation. The default value is 'none'.")}}]}],
example =>
["access_rules:",
" external_pubsub:",
" allow: external_component",
" external_mam:",
" allow: external_component",
"",
"acl:",
" external_component:",
" server: sat-pubsub.example.org",
"",
"modules:",
" ...",
" mod_delegation:",
" namespaces:",
" urn:xmpp:mam:1:",
" access: external_mam",
" http://jabber.org/protocol/pubsub:",
" access: external_pubsub"]}.
depends(_, _) ->
[].

View File

@ -37,7 +37,8 @@
get_local_features/5, get_local_services/5,
process_sm_iq_items/1, process_sm_iq_info/1,
get_sm_identity/5, get_sm_features/5, get_sm_items/5,
get_info/5, mod_opt_type/1, mod_options/1, depends/2]).
get_info/5, mod_opt_type/1, mod_options/1, depends/2,
mod_doc/0]).
-include("logger.hrl").
-include("translate.hrl").
@ -436,3 +437,70 @@ mod_options(_Host) ->
[{extra_domains, []},
{server_info, []},
{name, ?T("ejabberd")}].
mod_doc() ->
#{desc =>
?T("This module adds support for "
"https://xmpp.org/extensions/xep-0030.html"
"[XEP-0030: Service Discovery]. With this module enabled, "
"services on your server can be discovered by XMPP clients."),
opts =>
[{extra_domains,
#{value => "[Domain, ...]",
desc =>
?T("With this option, you can specify a list of extra "
"domains that are added to the Service Discovery item list. "
"The default value is an empty list.")}},
{name,
#{value => ?T("Name"),
desc =>
?T("A name of the server in the Service Discovery. "
"This will only be displayed by special XMPP clients. "
"The default value is 'ejabberd'.")}},
{server_info,
#{value => "[Info, ...]",
example =>
["server_info:",
" -",
" modules: all",
" name: abuse-addresses",
" urls: [mailto:abuse@shakespeare.lit]",
" -",
" modules: [mod_muc]",
" name: \"Web chatroom logs\"",
" urls: [http://www.example.org/muc-logs]",
" -",
" modules: [mod_disco]",
" name: feedback-addresses",
" urls:",
" - http://shakespeare.lit/feedback.php",
" - mailto:feedback@shakespeare.lit",
" - xmpp:feedback@shakespeare.lit",
" -",
" modules:",
" - mod_disco",
" - mod_vcard",
" name: admin-addresses",
" urls:",
" - mailto:xmpp@shakespeare.lit",
" - xmpp:admins@shakespeare.lit"],
desc =>
?T("Specify additional information about the server, "
"as described in https://xmpp.org/extensions/xep-0157.html"
"[XEP-0157: Contact Addresses for XMPP Services]. Every 'Info' "
"element in the list is constructed from the following options:")},
[{modules,
#{value => "all | [Module, ...]",
desc =>
?T("The value can be the keyword 'all', in which case the "
"information is reported in all the services, "
"or a list of ejabberd modules, in which case the "
"information is only specified for the services provided "
"by those modules.")}},
{name,
#{value => ?T("Name"),
desc => ?T("Any arbitrary name of the contact.")}},
{urls,
#{value => "[URI, ...]",
desc => ?T("A list of contact URIs, such as "
"HTTP URLs, XMPP URIs and so on.")}}]}]}.

View File

@ -34,7 +34,7 @@
-export([init/1, handle_call/3, handle_cast/2,
handle_info/2, terminate/2, code_change/3,
mod_opt_type/1, mod_options/1, depends/2]).
mod_opt_type/1, mod_options/1, depends/2, mod_doc/0]).