25
1
mirror of https://github.com/processone/ejabberd.git synced 2024-12-26 17:38:45 +01:00

Support XEP-0157: Contact Addresses for XMPP Services (EJAB-235)

SVN Revision: 2368
This commit is contained in:
Badlop 2009-07-17 20:45:44 +00:00
parent 2729285977
commit 9df5639974
11 changed files with 169 additions and 14 deletions

View File

@ -1958,6 +1958,7 @@ disabled for instances of <TT>ejabberd</TT> with hundreds of thousands users.</P
</P><P>This module adds support for Service Discovery (<A HREF="http://www.xmpp.org/extensions/xep-0030.html">XEP-0030</A>). With </P><P>This module adds support for Service Discovery (<A HREF="http://www.xmpp.org/extensions/xep-0030.html">XEP-0030</A>). With
this module enabled, services on your server can be discovered by this module enabled, services on your server can be discovered by
Jabber clients. Note that <TT>ejabberd</TT> has no modules with support Jabber clients. Note that <TT>ejabberd</TT> has no modules with support
@ -1969,8 +1970,16 @@ the services you offer.</P><P>Options:
<B><TT>iqdisc</TT></B></DT><DD CLASS="dd-description"> This specifies <B><TT>iqdisc</TT></B></DT><DD CLASS="dd-description"> This specifies
the processing discipline for Service Discovery (<TT>http://jabber.org/protocol/disco#items</TT> and the processing discipline for Service Discovery (<TT>http://jabber.org/protocol/disco#items</TT> and
<TT>http://jabber.org/protocol/disco#info</TT>) IQ queries (see section&#XA0;<A HREF="#modiqdiscoption">3.3.2</A>). <TT>http://jabber.org/protocol/disco#info</TT>) IQ queries (see section&#XA0;<A HREF="#modiqdiscoption">3.3.2</A>).
</DD><DT CLASS="dt-description"><B><TT>extra_domains</TT></B></DT><DD CLASS="dd-description"> With this option, </DD><DT CLASS="dt-description"><B><TT>{extra_domains, [ Domain ]}</TT></B></DT><DD CLASS="dd-description"> With this option,
extra domains can be added to the Service Discovery item list. you can specify a list of extra domains that are added to the Service Discovery item list.
</DD><DT CLASS="dt-description"><B><TT>{server_info, [ {Modules, Field, [Value]} ]}</TT></B></DT><DD CLASS="dd-description">
Specify additional information about the server,
as described in Contact Addresses for XMPP Services (<A HREF="http://www.xmpp.org/extensions/xep-0157.html">XEP-0157</A>).
<TT>Modules</TT> can be the keyword &#X2018;all&#X2019;,
in which case the information is reported in all the services;
or a list of <TT>ejabberd</TT> modules,
in which case the information is only specified for the services provided by those modules.
Any arbitrary <TT>Field</TT> and <TT>Value</TT> can be specified, not only contact addresses.
</DD></DL><P>Examples: </DD></DL><P>Examples:
</P><UL CLASS="itemize"><LI CLASS="li-itemize"> </P><UL CLASS="itemize"><LI CLASS="li-itemize">
To serve a link to the Jabber User Directory on <TT>jabber.org</TT>: To serve a link to the Jabber User Directory on <TT>jabber.org</TT>:
@ -1996,6 +2005,28 @@ To serve a link to the Jabber User Directory on <TT>jabber.org</TT>:
"example.com"]}]}, "example.com"]}]},
... ...
]}. ]}.
</PRE></LI><LI CLASS="li-itemize">With this configuration, all services show abuse addresses,
feedback address on the main server,
and admin addresses for both the main server and the vJUD service:
<PRE CLASS="verbatim">{modules,
[
...
{mod_disco, [{server_info, [
{all,
"abuse-addresses",
["mailto:abuse@shakespeare.lit"]},
{[mod_muc],
"Web chatroom logs",
["http://www.example.org/muc-logs"]},
{[mod_disco],
"feedback-addresses",
["http://shakespeare.lit/feedback.php", "mailto:feedback@shakespeare.lit", "xmpp:feedback@shakespeare.lit"]},
{[mod_disco, mod_vcard],
"admin-addresses",
["mailto:xmpp@shakespeare.lit", "xmpp:admins@shakespeare.lit"]}
]}]},
...
]}.
</PRE></LI></UL><P> <A NAME="modecho"></A> </P><!--TOC subsection <TT>mod_echo</TT>--> </PRE></LI></UL><P> <A NAME="modecho"></A> </P><!--TOC subsection <TT>mod_echo</TT>-->
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc42">3.3.5</A>&#XA0;&#XA0;<A HREF="#modecho"><TT>mod_echo</TT></A></H3><!--SEC END --><P> <A NAME="modecho"></A> <H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc42">3.3.5</A>&#XA0;&#XA0;<A HREF="#modecho"><TT>mod_echo</TT></A></H3><!--SEC END --><P> <A NAME="modecho"></A>
</P><P>This module simply echoes any Jabber </P><P>This module simply echoes any Jabber
@ -3275,6 +3306,10 @@ Starts the Erlang system detached from the system console.
Maximum number of Erlang processes. Maximum number of Erlang processes.
</DD><DT CLASS="dt-description"><B><TT>-remsh ejabberd@localhost</TT></B></DT><DD CLASS="dd-description"> </DD><DT CLASS="dt-description"><B><TT>-remsh ejabberd@localhost</TT></B></DT><DD CLASS="dd-description">
Open an Erlang shell in a remote Erlang node. Open an Erlang shell in a remote Erlang node.
</DD><DT CLASS="dt-description"><B><TT>-hidden</TT></B></DT><DD CLASS="dd-description">
The connections to other nodes are hidden (not published).
The result is that this node is not considered part of the cluster.
This is important when starting a temporary <TT>ctl</TT> or <TT>debug</TT> node.
</DD></DL><P> </DD></DL><P>
Note that some characters need to be escaped when used in shell scripts, for instance <CODE>"</CODE> and <CODE>{}</CODE>. Note that some characters need to be escaped when used in shell scripts, for instance <CODE>"</CODE> and <CODE>{}</CODE>.
You can find other options in the Erlang manual page (<TT>erl -man erl</TT>).</P><P> <A NAME="eja-commands"></A> </P><!--TOC section <TT>ejabberd</TT> Commands--> You can find other options in the Erlang manual page (<TT>erl -man erl</TT>).</P><P> <A NAME="eja-commands"></A> </P><!--TOC section <TT>ejabberd</TT> Commands-->

View File

@ -2600,6 +2600,7 @@ disabled for instances of \ejabberd{} with hundreds of thousands users.
\ind{protocols!XEP-0030: Service Discovery} \ind{protocols!XEP-0030: Service Discovery}
\ind{protocols!XEP-0011: Jabber Browsing} \ind{protocols!XEP-0011: Jabber Browsing}
\ind{protocols!XEP-0094: Agent Information} \ind{protocols!XEP-0094: Agent Information}
\ind{protocols!XEP-0157: Contact Addresses for XMPP Services}
This module adds support for Service Discovery (\xepref{0030}). With This module adds support for Service Discovery (\xepref{0030}). With
this module enabled, services on your server can be discovered by this module enabled, services on your server can be discovered by
@ -2613,8 +2614,16 @@ Options:
\begin{description} \begin{description}
\iqdiscitem{Service Discovery (\ns{http://jabber.org/protocol/disco\#items} and \iqdiscitem{Service Discovery (\ns{http://jabber.org/protocol/disco\#items} and
\ns{http://jabber.org/protocol/disco\#info})} \ns{http://jabber.org/protocol/disco\#info})}
\titem{extra\_domains} \ind{options!extra\_domains}With this option, \titem{\{extra\_domains, [ Domain ]\}} \ind{options!extra\_domains}With this option,
extra domains can be added to the Service Discovery item list. you can specify a list of extra domains that are added to the Service Discovery item list.
\titem{\{server\_info, [ \{Modules, Field, [Value]\} ]\}} \ind{options!server\_info}
Specify additional information about the server,
as described in Contact Addresses for XMPP Services (\xepref{0157}).
\term{Modules} 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.
Any arbitrary \term{Field} and \term{Value} can be specified, not only contact addresses.
\end{description} \end{description}
Examples: Examples:
@ -2648,9 +2657,32 @@ Examples:
... ...
]}. ]}.
\end{verbatim} \end{verbatim}
\item With this configuration, all services show abuse addresses,
feedback address on the main server,
and admin addresses for both the main server and the vJUD service:
\begin{verbatim}
{modules,
[
...
{mod_disco, [{server_info, [
{all,
"abuse-addresses",
["mailto:abuse@shakespeare.lit"]},
{[mod_muc],
"Web chatroom logs",
["http://www.example.org/muc-logs"]},
{[mod_disco],
"feedback-addresses",
["http://shakespeare.lit/feedback.php", "mailto:feedback@shakespeare.lit", "xmpp:feedback@shakespeare.lit"]},
{[mod_disco, mod_vcard],
"admin-addresses",
["mailto:xmpp@shakespeare.lit", "xmpp:admins@shakespeare.lit"]}
]}]},
...
]}.
\end{verbatim}
\end{itemize} \end{itemize}
\makesubsection{modecho}{\modecho{}} \makesubsection{modecho}{\modecho{}}
\ind{modules!\modecho{}}\ind{debugging} \ind{modules!\modecho{}}\ind{debugging}

View File

@ -57,6 +57,7 @@
-define(NS_COMMANDS, "http://jabber.org/protocol/commands"). -define(NS_COMMANDS, "http://jabber.org/protocol/commands").
-define(NS_BYTESTREAMS, "http://jabber.org/protocol/bytestreams"). -define(NS_BYTESTREAMS, "http://jabber.org/protocol/bytestreams").
-define(NS_ADMIN, "http://jabber.org/protocol/admin"). -define(NS_ADMIN, "http://jabber.org/protocol/admin").
-define(NS_SERVERINFO, "http://jabber.org/network/serverinfo").
-define(NS_RSM, "http://jabber.org/protocol/rsm"). -define(NS_RSM, "http://jabber.org/protocol/rsm").
-define(NS_EJABBERD_CONFIG, "ejabberd:config"). -define(NS_EJABBERD_CONFIG, "ejabberd:config").

View File

@ -1,7 +1,7 @@
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
%%% File : mod_disco.erl %%% File : mod_disco.erl
%%% Author : Alexey Shchepin <alexey@process-one.net> %%% Author : Alexey Shchepin <alexey@process-one.net>
%%% Purpose : Service Discovery (JEP-0030) support %%% Purpose : Service Discovery (XEP-0030) support
%%% Created : 1 Jan 2003 by Alexey Shchepin <alexey@process-one.net> %%% Created : 1 Jan 2003 by Alexey Shchepin <alexey@process-one.net>
%%% %%%
%%% %%%
@ -41,6 +41,7 @@
get_sm_identity/5, get_sm_identity/5,
get_sm_features/5, get_sm_features/5,
get_sm_items/5, get_sm_items/5,
get_info/5,
register_feature/2, register_feature/2,
unregister_feature/2, unregister_feature/2,
register_extra_domain/2, register_extra_domain/2,
@ -79,6 +80,7 @@ start(Host, Opts) ->
ejabberd_hooks:add(disco_sm_items, Host, ?MODULE, get_sm_items, 100), ejabberd_hooks:add(disco_sm_items, Host, ?MODULE, get_sm_items, 100),
ejabberd_hooks:add(disco_sm_features, Host, ?MODULE, get_sm_features, 100), ejabberd_hooks:add(disco_sm_features, Host, ?MODULE, get_sm_features, 100),
ejabberd_hooks:add(disco_sm_identity, Host, ?MODULE, get_sm_identity, 100), ejabberd_hooks:add(disco_sm_identity, Host, ?MODULE, get_sm_identity, 100),
ejabberd_hooks:add(disco_info, Host, ?MODULE, get_info, 100),
ok. ok.
stop(Host) -> stop(Host) ->
@ -88,6 +90,7 @@ stop(Host) ->
ejabberd_hooks:delete(disco_local_identity, Host, ?MODULE, get_local_identity, 100), ejabberd_hooks:delete(disco_local_identity, Host, ?MODULE, get_local_identity, 100),
ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, get_local_features, 100), ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, get_local_features, 100),
ejabberd_hooks:delete(disco_local_items, Host, ?MODULE, get_local_services, 100), ejabberd_hooks:delete(disco_local_items, Host, ?MODULE, get_local_services, 100),
ejabberd_hooks:delete(disco_info, Host, ?MODULE, get_info, 100),
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS), gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS),
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO), gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO),
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_DISCO_ITEMS), gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_DISCO_ITEMS),
@ -153,6 +156,8 @@ process_local_iq_info(From, To, #iq{type = Type, lang = Lang,
Host, Host,
[], [],
[From, To, Node, Lang]), [From, To, Node, Lang]),
Info = ejabberd_hooks:run_fold(disco_info, Host, [],
[Host, ?MODULE, Node, Lang]),
case ejabberd_hooks:run_fold(disco_local_features, case ejabberd_hooks:run_fold(disco_local_features,
Host, Host,
empty, empty,
@ -166,6 +171,7 @@ process_local_iq_info(From, To, #iq{type = Type, lang = Lang,
sub_el = [{xmlelement, "query", sub_el = [{xmlelement, "query",
[{"xmlns", ?NS_DISCO_INFO} | ANode], [{"xmlns", ?NS_DISCO_INFO} | ANode],
Identity ++ Identity ++
Info ++
lists:map(fun feature_to_xml/1, Features) lists:map(fun feature_to_xml/1, Features)
}]}; }]};
{error, Error} -> {error, Error} ->
@ -362,3 +368,60 @@ get_user_resources(User, Server) ->
[{"jid", User ++ "@" ++ Server ++ "/" ++ R}, [{"jid", User ++ "@" ++ Server ++ "/" ++ R},
{"name", User}], []} {"name", User}], []}
end, lists:sort(Rs)). end, lists:sort(Rs)).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Support for: XEP-0157 Contact Addresses for XMPP Services
get_info(_A, Host, Module, Node, _Lang) when Node == [] ->
Serverinfo_fields = get_fields_xml(Host, Module),
[{xmlelement, "x",
[{"xmlns", ?NS_XDATA}, {"type", "result"}],
[{xmlelement, "field",
[{"var", "FORM_TYPE"}, {"type", "hidden"}],
[{xmlelement, "value",
[],
[{xmlcdata, ?NS_SERVERINFO}]
}]
}]
++ Serverinfo_fields
}];
get_info(_, _, _, _Node, _) ->
[].
get_fields_xml(Host, Module) ->
Fields = gen_mod:get_module_opt(Host, ?MODULE, server_info, []),
%% filter, and get only the ones allowed for this module
Fields_good = lists:filter(
fun({Modules, _, _}) ->
case Modules of
all -> true;
Modules -> lists:member(Module, Modules)
end
end,
Fields),
fields_to_xml(Fields_good).
fields_to_xml(Fields) ->
[ field_to_xml(Field) || Field <- Fields].
field_to_xml({_, Var, Values}) ->
Values_xml = values_to_xml(Values),
{xmlelement, "field",
[{"var", Var}],
Values_xml
}.
values_to_xml(Values) ->
lists:map(
fun(Value) ->
{xmlelement, "value",
[],
[{xmlcdata, Value}]
}
end,
Values
).

View File

@ -214,6 +214,9 @@ do_route1(Host, ServerHost, From, To, Packet) ->
#iq{type = get, xmlns = ?NS_DISCO_INFO = XMLNS, #iq{type = get, xmlns = ?NS_DISCO_INFO = XMLNS,
sub_el = SubEl, lang = Lang} = IQ -> sub_el = SubEl, lang = Lang} = IQ ->
Node = xml:get_tag_attr_s("node", SubEl), Node = xml:get_tag_attr_s("node", SubEl),
Info = ejabberd_hooks:run_fold(
disco_info, ServerHost, [],
[ServerHost, ?MODULE, "", ""]),
case iq_disco(Node, Lang) of case iq_disco(Node, Lang) of
[] -> [] ->
Res = IQ#iq{type = result, Res = IQ#iq{type = result,
@ -227,7 +230,7 @@ do_route1(Host, ServerHost, From, To, Packet) ->
Res = IQ#iq{type = result, Res = IQ#iq{type = result,
sub_el = [{xmlelement, "query", sub_el = [{xmlelement, "query",
[{"xmlns", XMLNS}], [{"xmlns", XMLNS}],
DiscoInfo}]}, DiscoInfo ++ Info}]},
ejabberd_router:route(To, ejabberd_router:route(To,
From, From,
jlib:iq_to_xml(Res)) jlib:iq_to_xml(Res))

View File

@ -353,10 +353,14 @@ do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
case jlib:iq_query_info(Packet) of case jlib:iq_query_info(Packet) of
#iq{type = get, xmlns = ?NS_DISCO_INFO = XMLNS, #iq{type = get, xmlns = ?NS_DISCO_INFO = XMLNS,
sub_el = _SubEl, lang = Lang} = IQ -> sub_el = _SubEl, lang = Lang} = IQ ->
Info = ejabberd_hooks:run_fold(
disco_info, ServerHost, [],
[ServerHost, ?MODULE, "", ""]),
Res = IQ#iq{type = result, Res = IQ#iq{type = result,
sub_el = [{xmlelement, "query", sub_el = [{xmlelement, "query",
[{"xmlns", XMLNS}], [{"xmlns", XMLNS}],
iq_disco_info(Lang)}]}, iq_disco_info(Lang)
++Info}]},
ejabberd_router:route(To, ejabberd_router:route(To,
From, From,
jlib:iq_to_xml(Res)); jlib:iq_to_xml(Res));

View File

@ -120,9 +120,13 @@ delete_listener(Host) ->
%%%------------------------ %%%------------------------
%% disco#info request %% disco#info request
process_iq(_, #iq{type = get, xmlns = ?NS_DISCO_INFO, lang = Lang} = IQ, #state{name=Name}) -> process_iq(_, #iq{type = get, xmlns = ?NS_DISCO_INFO, lang = Lang} = IQ,
#state{name=Name, serverhost=ServerHost}) ->
Info = ejabberd_hooks:run_fold(
disco_info, ServerHost, [], [ServerHost, ?MODULE, "", ""]),
IQ#iq{type = result, sub_el = IQ#iq{type = result, sub_el =
[{xmlelement, "query", [{"xmlns", ?NS_DISCO_INFO}], iq_disco_info(Lang, Name)}]}; [{xmlelement, "query", [{"xmlns", ?NS_DISCO_INFO}],
iq_disco_info(Name, Lang) ++ Info}]};
%% disco#items request %% disco#items request
process_iq(_, #iq{type = get, xmlns = ?NS_DISCO_ITEMS} = IQ, _) -> process_iq(_, #iq{type = get, xmlns = ?NS_DISCO_ITEMS} = IQ, _) ->

View File

@ -888,12 +888,15 @@ do_route(ServerHost, Access, Plugins, Host, From, To, Packet) ->
sub_el = SubEl, lang = Lang} = IQ -> sub_el = SubEl, lang = Lang} = IQ ->
{xmlelement, _, QAttrs, _} = SubEl, {xmlelement, _, QAttrs, _} = SubEl,
Node = xml:get_attr_s("node", QAttrs), Node = xml:get_attr_s("node", QAttrs),
Info = ejabberd_hooks:run_fold(
disco_info, ServerHost, [],
[ServerHost, ?MODULE, "", ""]),
Res = case iq_disco_info(Host, Node, From, Lang) of Res = case iq_disco_info(Host, Node, From, Lang) of
{result, IQRes} -> {result, IQRes} ->
jlib:iq_to_xml( jlib:iq_to_xml(
IQ#iq{type = result, IQ#iq{type = result,
sub_el = [{xmlelement, "query", sub_el = [{xmlelement, "query",
QAttrs, IQRes}]}); QAttrs, IQRes++Info}]});
{error, Error} -> {error, Error} ->
jlib:make_error_reply(Packet, Error) jlib:make_error_reply(Packet, Error)
end, end,

View File

@ -367,6 +367,9 @@ do_route(ServerHost, From, To, Packet) ->
Packet, ?ERR_NOT_ALLOWED), Packet, ?ERR_NOT_ALLOWED),
ejabberd_router:route(To, From, Err); ejabberd_router:route(To, From, Err);
get -> get ->
Info = ejabberd_hooks:run_fold(
disco_info, ServerHost, [],
[ServerHost, ?MODULE, "", ""]),
ResIQ = ResIQ =
IQ#iq{type = result, IQ#iq{type = result,
sub_el = [{xmlelement, sub_el = [{xmlelement,
@ -384,7 +387,7 @@ do_route(ServerHost, From, To, Packet) ->
[{"var", ?NS_SEARCH}], []}, [{"var", ?NS_SEARCH}], []},
{xmlelement, "feature", {xmlelement, "feature",
[{"var", ?NS_VCARD}], []} [{"var", ?NS_VCARD}], []}
] ] ++ Info
}]}, }]},
ejabberd_router:route(To, ejabberd_router:route(To,
From, From,

View File

@ -406,6 +406,7 @@ do_route(State, From, To, Packet) ->
route(State, From, To, Packet) -> route(State, From, To, Packet) ->
#jid{user = User, resource = Resource} = To, #jid{user = User, resource = Resource} = To,
ServerHost = State#state.serverhost,
if if
(User /= "") or (Resource /= "") -> (User /= "") or (Resource /= "") ->
Err = jlib:make_error_reply(Packet, ?ERR_SERVICE_UNAVAILABLE), Err = jlib:make_error_reply(Packet, ?ERR_SERVICE_UNAVAILABLE),
@ -467,6 +468,9 @@ route(State, From, To, Packet) ->
Packet, ?ERR_NOT_ALLOWED), Packet, ?ERR_NOT_ALLOWED),
ejabberd_router:route(To, From, Err); ejabberd_router:route(To, From, Err);
get -> get ->
Info = ejabberd_hooks:run_fold(
disco_info, ServerHost, [],
[ServerHost, ?MODULE, "", ""]),
ResIQ = ResIQ =
IQ#iq{type = result, IQ#iq{type = result,
sub_el = [{xmlelement, sub_el = [{xmlelement,
@ -482,7 +486,7 @@ route(State, From, To, Packet) ->
[{"var", ?NS_SEARCH}], []}, [{"var", ?NS_SEARCH}], []},
{xmlelement, "feature", {xmlelement, "feature",
[{"var", ?NS_VCARD}], []} [{"var", ?NS_VCARD}], []}
] ] ++ Info
}]}, }]},
ejabberd_router:route(To, ejabberd_router:route(To,
From, From,

View File

@ -344,6 +344,9 @@ do_route(ServerHost, From, To, Packet) ->
Packet, ?ERR_NOT_ALLOWED), Packet, ?ERR_NOT_ALLOWED),
ejabberd_router:route(To, From, Err); ejabberd_router:route(To, From, Err);
get -> get ->
Info = ejabberd_hooks:run_fold(
disco_info, ServerHost, [],
[ServerHost, ?MODULE, "", ""]),
ResIQ = ResIQ =
IQ#iq{type = result, IQ#iq{type = result,
sub_el = [{xmlelement, sub_el = [{xmlelement,
@ -359,7 +362,7 @@ do_route(ServerHost, From, To, Packet) ->
[{"var", ?NS_SEARCH}], []}, [{"var", ?NS_SEARCH}], []},
{xmlelement, "feature", {xmlelement, "feature",
[{"var", ?NS_VCARD}], []} [{"var", ?NS_VCARD}], []}
] ] ++ Info
}]}, }]},
ejabberd_router:route(To, ejabberd_router:route(To,
From, From,