Packages
ejabberd
21.7.0
26.4.0
26.3.0
26.2.0
26.1.0
25.10.0
25.8.0
25.7.0
25.4.0
25.3.0
24.12.0
24.10.0
24.7.0
24.6.0
24.2.6
23.10.0
23.4.0
23.1.0
22.10.0
22.5.0
21.12.0
21.7.0
21.4.0
21.1.0
20.12.0
20.7.0
20.4.0
20.3.0
20.2.0
20.1.0
19.9.1
19.9.0
19.8.0
19.5.0
19.2.0
18.12.1
18.12.0
18.6.0
18.4.0
18.3.0
18.1.0
17.11.0
17.9.0
17.6.0
17.3.0
17.1.0
16.12.0-beta1
16.9.0
16.8.0
16.6.2
16.6.1
16.6.0
16.4.1
16.4.0
16.3.0
16.2.0
16.1.0-beta1
Robust, Ubiquitous and Massively Scalable Messaging Platform (XMPP, MQTT, SIP Server)
Current section
Files
Jump to
Current section
Files
src/mod_mix.erl
%%%-------------------------------------------------------------------
%%% File : mod_mix.erl
%%% Author : Evgeny Khramtsov <ekhramtsov@process-one.net>
%%% Created : 2 Mar 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2018 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(mod_mix).
-behaviour(gen_mod).
-behaviour(gen_server).
-protocol({xep, 369, '0.14.1'}).
%% API
-export([route/1]).
%% gen_mod callbacks
-export([start/2, stop/1, reload/3, depends/2, mod_opt_type/1, 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, format_status/2]).
%% Hooks
-export([process_disco_info/1,
process_disco_items/1,
process_mix_core/1,
process_mam_query/1,
process_pubsub_query/1]).
-include_lib("xmpp/include/xmpp.hrl").
-include("logger.hrl").
-include("translate.hrl").
-include("ejabberd_stacktrace.hrl").
-callback init(binary(), gen_mod:opts()) -> ok | {error, db_failure}.
-callback set_channel(binary(), binary(), binary(),
jid:jid(), boolean(), binary()) ->
ok | {error, db_failure}.
-callback get_channels(binary(), binary()) ->
{ok, [binary()]} | {error, db_failure}.
-callback get_channel(binary(), binary(), binary()) ->
{ok, {jid(), boolean(), binary()}} |
{error, notfound | db_failure}.
-callback set_participant(binary(), binary(), binary(), jid(), binary(), binary()) ->
ok | {error, db_failure}.
-callback get_participant(binary(), binary(), binary(), jid()) ->
{ok, {binary(), binary()}} | {error, notfound | db_failure}.
-record(state, {hosts :: [binary()],
server_host :: binary()}).
%%%===================================================================
%%% API
%%%===================================================================
start(Host, Opts) ->
gen_mod:start_child(?MODULE, Host, Opts).
stop(Host) ->
gen_mod:stop_child(?MODULE, Host).
reload(Host, NewOpts, OldOpts) ->
Proc = gen_mod:get_module_proc(Host, ?MODULE),
gen_server:cast(Proc, {reload, Host, NewOpts, OldOpts}).
depends(_Host, _Opts) ->
[{mod_mam, hard}].
mod_opt_type(access_create) ->
econf:acl();
mod_opt_type(name) ->
econf:binary();
mod_opt_type(host) ->
econf:host();
mod_opt_type(hosts) ->
econf:hosts();
mod_opt_type(db_type) ->
econf:db_type(?MODULE).
mod_options(Host) ->
[{access_create, all},
{host, <<"mix.", Host/binary>>},
{hosts, []},
{name, ?T("Channels")},
{db_type, ejabberd_config:default_db(Host, ?MODULE)}].
mod_doc() ->
#{desc =>
[?T("This module is an experimental implementation of "
"https://xmpp.org/extensions/xep-0369.html"
"[XEP-0369: Mediated Information eXchange (MIX)]. "
"MIX support was added in ejabberd 16.03 as an "
"experimental feature, updated in 19.02, and is not "
"yet ready to use in production. It's asserted that "
"the MIX protocol is going to replace the MUC protocol "
"in the future (see 'mod_muc')."), "",
?T("To learn more about how to use that feature, you can refer to "
"our tutorial: https://docs.ejabberd.im/tutorials/mix-010/"
"[Getting started with XEP-0369: Mediated Information "
"eXchange (MIX) v0.1]."), "",
?T("The module depends on 'mod_mam'.")],
opts =>
[{access_create,
#{value => ?T("AccessName"),
desc =>
?T("An access rule to control MIX channels creations. "
"The default value is 'all'.")}},
{host,
#{desc => ?T("Deprecated. Use 'hosts' instead.")}},
{hosts,
#{value => ?T("[Host, ...]"),
desc =>
?T("This option defines the Jabber IDs of the service. "
"If the 'hosts' option is not specified, the only Jabber ID will "
"be the hostname of the virtual host with the prefix \"mix.\". "
"The keyword '@HOST@' is replaced with the real virtual host name.")}},
{name,
#{value => ?T("Name"),
desc =>
?T("A name of the service in the Service Discovery. "
"This will only be displayed by special XMPP clients. "
"The default value is 'Channels'.")}},
{db_type,
#{value => "mnesia | sql",
desc =>
?T("Same as top-level 'default_db' option, but applied to this module only.")}}]}.
-spec route(stanza()) -> ok.
route(#iq{} = IQ) ->
ejabberd_router:process_iq(IQ);
route(#message{type = groupchat, id = ID, lang = Lang,
to = #jid{luser = <<_, _/binary>>}} = Msg) ->
case ID of
<<>> ->
Txt = ?T("Attribute 'id' is mandatory for MIX messages"),
Err = xmpp:err_bad_request(Txt, Lang),
ejabberd_router:route_error(Msg, Err);
_ ->
process_mix_message(Msg)
end;
route(Pkt) ->
?DEBUG("Dropping packet:~n~ts", [xmpp:pp(Pkt)]).
-spec process_disco_info(iq()) -> iq().
process_disco_info(#iq{type = set, lang = Lang} = IQ) ->
Txt = ?T("Value 'set' of 'type' attribute is not allowed"),
xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
process_disco_info(#iq{type = get, to = #jid{luser = <<>>} = To,
from = _From, lang = Lang,
sub_els = [#disco_info{node = <<>>}]} = IQ) ->
ServerHost = ejabberd_router:host_of_route(To#jid.lserver),
X = ejabberd_hooks:run_fold(disco_info, ServerHost, [],
[ServerHost, ?MODULE, <<"">>, Lang]),
Name = mod_mix_opt:name(ServerHost),
Identity = #identity{category = <<"conference">>,
type = <<"mix">>,
name = translate:translate(Lang, Name)},
Features = [?NS_DISCO_INFO, ?NS_DISCO_ITEMS,
?NS_MIX_CORE_0, ?NS_MIX_CORE_SEARCHABLE_0,
?NS_MIX_CORE_CREATE_CHANNEL_0],
xmpp:make_iq_result(
IQ, #disco_info{features = Features,
identities = [Identity],
xdata = X});
process_disco_info(#iq{type = get, to = #jid{luser = <<_, _/binary>>} = To,
sub_els = [#disco_info{node = Node}]} = IQ)
when Node == <<"mix">>; Node == <<>> ->
{Chan, Host, _} = jid:tolower(To),
ServerHost = ejabberd_router:host_of_route(Host),
Mod = gen_mod:db_mod(ServerHost, ?MODULE),
case Mod:get_channel(ServerHost, Chan, Host) of
{ok, _} ->
Identity = #identity{category = <<"conference">>,
type = <<"mix">>},
Features = [?NS_DISCO_INFO, ?NS_DISCO_ITEMS,
?NS_MIX_CORE_0, ?NS_MAM_2],
xmpp:make_iq_result(
IQ, #disco_info{node = Node,
features = Features,
identities = [Identity]});
{error, notfound} ->
xmpp:make_error(IQ, no_channel_error(IQ));
{error, db_failure} ->
xmpp:make_error(IQ, db_error(IQ))
end;
process_disco_info(#iq{type = get, sub_els = [#disco_info{node = Node}]} = IQ) ->
xmpp:make_iq_result(IQ, #disco_info{node = Node, features = [?NS_DISCO_INFO]});
process_disco_info(IQ) ->
xmpp:make_error(IQ, unsupported_error(IQ)).
-spec process_disco_items(iq()) -> iq().
process_disco_items(#iq{type = set, lang = Lang} = IQ) ->
Txt = ?T("Value 'set' of 'type' attribute is not allowed"),
xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
process_disco_items(#iq{type = get, to = #jid{luser = <<>>} = To,
sub_els = [#disco_items{node = <<>>}]} = IQ) ->
Host = To#jid.lserver,
ServerHost = ejabberd_router:host_of_route(Host),
Mod = gen_mod:db_mod(ServerHost, ?MODULE),
case Mod:get_channels(ServerHost, Host) of
{ok, Channels} ->
Items = [#disco_item{jid = jid:make(Channel, Host)}
|| Channel <- Channels],
xmpp:make_iq_result(IQ, #disco_items{items = Items});
{error, db_failure} ->
xmpp:make_error(IQ, db_error(IQ))
end;
process_disco_items(#iq{type = get, to = #jid{luser = <<_, _/binary>>} = To,
sub_els = [#disco_items{node = Node}]} = IQ)
when Node == <<"mix">>; Node == <<>> ->
{Chan, Host, _} = jid:tolower(To),
ServerHost = ejabberd_router:host_of_route(Host),
Mod = gen_mod:db_mod(ServerHost, ?MODULE),
case Mod:get_channel(ServerHost, Chan, Host) of
{ok, _} ->
BTo = jid:remove_resource(To),
Items = [#disco_item{jid = BTo, node = N} || N <- known_nodes()],
xmpp:make_iq_result(IQ, #disco_items{node = Node, items = Items});
{error, notfound} ->
xmpp:make_error(IQ, no_channel_error(IQ));
{error, db_failure} ->
xmpp:make_error(IQ, db_error(IQ))
end;
process_disco_items(#iq{type = get, sub_els = [#disco_items{node = Node}]} = IQ) ->
xmpp:make_iq_result(IQ, #disco_items{node = Node});
process_disco_items(IQ) ->
xmpp:make_error(IQ, unsupported_error(IQ)).
-spec process_mix_core(iq()) -> iq().
process_mix_core(#iq{type = set, to = #jid{luser = <<>>},
sub_els = [#mix_create{}]} = IQ) ->
process_mix_create(IQ);
process_mix_core(#iq{type = set, to = #jid{luser = <<>>},
sub_els = [#mix_destroy{}]} = IQ) ->
process_mix_destroy(IQ);
process_mix_core(#iq{type = set, to = #jid{luser = <<_, _/binary>>},
sub_els = [#mix_join{}]} = IQ) ->
process_mix_join(IQ);
process_mix_core(#iq{type = set, to = #jid{luser = <<_, _/binary>>},
sub_els = [#mix_leave{}]} = IQ) ->
process_mix_leave(IQ);
process_mix_core(#iq{type = set, to = #jid{luser = <<_, _/binary>>},
sub_els = [#mix_setnick{}]} = IQ) ->
process_mix_setnick(IQ);
process_mix_core(IQ) ->
xmpp:make_error(IQ, unsupported_error(IQ)).
process_pubsub_query(#iq{type = get,
sub_els = [#pubsub{items = #ps_items{node = Node}}]} = IQ)
when Node == ?NS_MIX_NODES_PARTICIPANTS ->
process_participants_list(IQ);
process_pubsub_query(IQ) ->
xmpp:make_error(IQ, unsupported_error(IQ)).
process_mam_query(#iq{from = From, to = To, type = T,
sub_els = [#mam_query{}]} = IQ)
when T == get; T == set ->
{Chan, Host, _} = jid:tolower(To),
ServerHost = ejabberd_router:host_of_route(Host),
Mod = gen_mod:db_mod(ServerHost, ?MODULE),
case Mod:get_channel(ServerHost, Chan, Host) of
{ok, _} ->
BFrom = jid:remove_resource(From),
case Mod:get_participant(ServerHost, Chan, Host, BFrom) of
{ok, _} ->
mod_mam:process_iq(ServerHost, IQ, mix);
{error, notfound} ->
xmpp:make_error(IQ, not_joined_error(IQ));
{error, db_failure} ->
xmpp:make_error(IQ, db_error(IQ))
end;
{error, notfound} ->
xmpp:make_error(IQ, no_channel_error(IQ));
{error, db_failure} ->
xmpp:make_error(IQ, db_error(IQ))
end;
process_mam_query(IQ) ->
xmpp:make_error(IQ, unsupported_error(IQ)).
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
init([Host|_]) ->
process_flag(trap_exit, true),
Opts = gen_mod:get_module_opts(Host, ?MODULE),
Mod = gen_mod:db_mod(Opts, ?MODULE),
MyHosts = gen_mod:get_opt_hosts(Opts),
case Mod:init(Host, gen_mod:set_opt(hosts, MyHosts, Opts)) of
ok ->
lists:foreach(
fun(MyHost) ->
ejabberd_router:register_route(
MyHost, Host, {apply, ?MODULE, route}),
register_iq_handlers(MyHost)
end, MyHosts),
{ok, #state{hosts = MyHosts, server_host = Host}};
{error, db_failure} ->
{stop, db_failure}
end.
handle_call(Request, From, State) ->
?WARNING_MSG("Unexpected call from ~p: ~p", [From, Request]),
{noreply, State}.
handle_cast(Request, State) ->
?WARNING_MSG("Unexpected cast: ~p", [Request]),
{noreply, State}.
handle_info({route, Packet}, State) ->
try route(Packet)
catch ?EX_RULE(Class, Reason, St) ->
StackTrace = ?EX_STACK(St),
?ERROR_MSG("Failed to route packet:~n~ts~n** ~ts",
[xmpp:pp(Packet),
misc:format_exception(2, Class, Reason, StackTrace)])
end,
{noreply, State};
handle_info(Info, State) ->
?WARNING_MSG("Unexpected info: ~p", [Info]),
{noreply, State}.
terminate(_Reason, State) ->
lists:foreach(
fun(MyHost) ->
unregister_iq_handlers(MyHost),
ejabberd_router:unregister_route(MyHost)
end, State#state.hosts).
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
format_status(_Opt, Status) ->
Status.
%%%===================================================================
%%% Internal functions
%%%===================================================================
-spec process_mix_create(iq()) -> iq().
process_mix_create(#iq{to = To, from = From,
sub_els = [#mix_create{channel = Chan}]} = IQ) ->
Host = To#jid.lserver,
ServerHost = ejabberd_router:host_of_route(Host),
Mod = gen_mod:db_mod(ServerHost, ?MODULE),
Creator = jid:remove_resource(From),
Chan1 = case Chan of
<<>> -> p1_rand:get_string();
_ -> Chan
end,
Ret = case Mod:get_channel(ServerHost, Chan1, Host) of
{ok, {#jid{luser = U, lserver = S}, _, _}} ->
case {From#jid.luser, From#jid.lserver} of
{U, S} -> ok;
_ -> {error, conflict}
end;
{error, notfound} ->
Key = xmpp_util:hex(p1_rand:bytes(20)),
Mod:set_channel(ServerHost, Chan1, Host,
Creator, Chan == <<>>, Key);
{error, db_failure} = Err ->
Err
end,
case Ret of
ok ->
xmpp:make_iq_result(IQ, #mix_create{channel = Chan1});
{error, conflict} ->
xmpp:make_error(IQ, channel_exists_error(IQ));
{error, db_failure} ->
xmpp:make_error(IQ, db_error(IQ))
end.
-spec process_mix_destroy(iq()) -> iq().
process_mix_destroy(#iq{to = To,
from = #jid{luser = U, lserver = S},
sub_els = [#mix_destroy{channel = Chan}]} = IQ) ->
Host = To#jid.lserver,
ServerHost = ejabberd_router:host_of_route(Host),
Mod = gen_mod:db_mod(ServerHost, ?MODULE),
case Mod:get_channel(ServerHost, Chan, Host) of
{ok, {#jid{luser = U, lserver = S}, _, _}} ->
case Mod:del_channel(ServerHost, Chan, Host) of
ok ->
xmpp:make_iq_result(IQ, #mix_destroy{channel = Chan});
{error, db_failure} ->
xmpp:make_error(IQ, db_error(IQ))
end;
{ok, _} ->
xmpp:make_error(IQ, ownership_error(IQ));
{error, notfound} ->
xmpp:make_error(IQ, no_channel_error(IQ));
{error, db_failure} ->
xmpp:make_error(IQ, db_error(IQ))
end.
-spec process_mix_join(iq()) -> iq().
process_mix_join(#iq{to = To, from = From,
sub_els = [#mix_join{} = JoinReq]} = IQ) ->
Chan = To#jid.luser,
Host = To#jid.lserver,
ServerHost = ejabberd_router:host_of_route(Host),
Mod = gen_mod:db_mod(ServerHost, ?MODULE),
case Mod:get_channel(ServerHost, Chan, Host) of
{ok, {_, _, Key}} ->
ID = make_id(From, Key),
Nick = JoinReq#mix_join.nick,
BFrom = jid:remove_resource(From),
Nodes = filter_nodes(JoinReq#mix_join.subscribe),
try
ok = Mod:set_participant(ServerHost, Chan, Host, BFrom, ID, Nick),
ok = Mod:subscribe(ServerHost, Chan, Host, BFrom, Nodes),
notify_participant_joined(Mod, ServerHost, To, From, ID, Nick),
xmpp:make_iq_result(IQ, #mix_join{id = ID,
subscribe = Nodes,
nick = Nick})
catch _:{badmatch, {error, db_failure}} ->
xmpp:make_error(IQ, db_error(IQ))
end;
{error, notfound} ->
xmpp:make_error(IQ, no_channel_error(IQ));
{error, db_failure} ->
xmpp:make_error(IQ, db_error(IQ))
end.
-spec process_mix_leave(iq()) -> iq().
process_mix_leave(#iq{to = To, from = From,
sub_els = [#mix_leave{}]} = IQ) ->
{Chan, Host, _} = jid:tolower(To),
ServerHost = ejabberd_router:host_of_route(Host),
Mod = gen_mod:db_mod(ServerHost, ?MODULE),
BFrom = jid:remove_resource(From),
case Mod:get_channel(ServerHost, Chan, Host) of
{ok, _} ->
case Mod:get_participant(ServerHost, Chan, Host, BFrom) of
{ok, {ID, _}} ->
try
ok = Mod:unsubscribe(ServerHost, Chan, Host, BFrom),
ok = Mod:del_participant(ServerHost, Chan, Host, BFrom),
notify_participant_left(Mod, ServerHost, To, ID),
xmpp:make_iq_result(IQ, #mix_leave{})
catch _:{badmatch, {error, db_failure}} ->
xmpp:make_error(IQ, db_error(IQ))
end;
{error, notfound} ->
xmpp:make_iq_result(IQ, #mix_leave{});
{error, db_failure} ->
xmpp:make_error(IQ, db_error(IQ))
end;
{error, notfound} ->
xmpp:make_iq_result(IQ, #mix_leave{});
{error, db_failure} ->
xmpp:make_error(IQ, db_error(IQ))
end.
-spec process_mix_setnick(iq()) -> iq().
process_mix_setnick(#iq{to = To, from = From,
sub_els = [#mix_setnick{nick = Nick}]} = IQ) ->
{Chan, Host, _} = jid:tolower(To),
ServerHost = ejabberd_router:host_of_route(Host),
Mod = gen_mod:db_mod(ServerHost, ?MODULE),
BFrom = jid:remove_resource(From),
case Mod:get_channel(ServerHost, Chan, Host) of
{ok, _} ->
case Mod:get_participant(ServerHost, Chan, Host, BFrom) of
{ok, {_, Nick}} ->
xmpp:make_iq_result(IQ, #mix_setnick{nick = Nick});
{ok, {ID, _}} ->
case Mod:set_participant(ServerHost, Chan, Host, BFrom, ID, Nick) of
ok ->
notify_participant_joined(Mod, ServerHost, To, From, ID, Nick),
xmpp:make_iq_result(IQ, #mix_setnick{nick = Nick});
{error, db_failure} ->
xmpp:make_error(IQ, db_error(IQ))
end;
{error, notfound} ->
xmpp:make_error(IQ, not_joined_error(IQ));
{error, db_failure} ->
xmpp:make_error(IQ, db_error(IQ))
end;
{error, notfound} ->
xmpp:make_error(IQ, no_channel_error(IQ));
{error, db_failure} ->
xmpp:make_error(IQ, db_error(IQ))
end.
-spec process_mix_message(message()) -> ok.
process_mix_message(#message{from = From, to = To,
id = SubmissionID} = Msg) ->
{Chan, Host, _} = jid:tolower(To),
{FUser, FServer, _} = jid:tolower(From),
ServerHost = ejabberd_router:host_of_route(Host),
Mod = gen_mod:db_mod(ServerHost, ?MODULE),
case Mod:get_channel(ServerHost, Chan, Host) of
{ok, _} ->
BFrom = jid:remove_resource(From),
case Mod:get_participant(ServerHost, Chan, Host, BFrom) of
{ok, {StableID, Nick}} ->
MamID = mod_mam:make_id(),
Msg1 = xmpp:set_subtag(
Msg#message{from = jid:replace_resource(To, StableID),
to = undefined,
id = integer_to_binary(MamID)},
#mix{jid = BFrom, nick = Nick}),
Msg2 = xmpp:put_meta(Msg1, stanza_id, MamID),
case ejabberd_hooks:run_fold(
store_mam_message, ServerHost, Msg2,
[Chan, Host, BFrom, Nick, groupchat, recv]) of
#message{} ->
multicast(Mod, ServerHost, Chan, Host,
?NS_MIX_NODES_MESSAGES,
fun(#jid{luser = U, lserver = S})
when U == FUser, S == FServer ->
xmpp:set_subtag(
Msg1, #mix{jid = BFrom,
nick = Nick,
submission_id = SubmissionID});
(_) ->
Msg1
end);
_ ->
ok
end;
{error, notfound} ->
ejabberd_router:route_error(Msg, not_joined_error(Msg));
{error, db_failure} ->
ejabberd_router:route_error(Msg, db_error(Msg))
end;
{error, notfound} ->
ejabberd_router:route_error(Msg, no_channel_error(Msg));
{error, db_failure} ->
ejabberd_router:route_error(Msg, db_error(Msg))
end.
-spec process_participants_list(iq()) -> iq().
process_participants_list(#iq{from = From, to = To} = IQ) ->
{Chan, Host, _} = jid:tolower(To),
ServerHost = ejabberd_router:host_of_route(Host),
Mod = gen_mod:db_mod(ServerHost, ?MODULE),
case Mod:get_channel(ServerHost, Chan, Host) of
{ok, _} ->
BFrom = jid:remove_resource(From),
case Mod:get_participant(ServerHost, Chan, Host, BFrom) of
{ok, _} ->
case Mod:get_participants(ServerHost, Chan, Host) of
{ok, Participants} ->
Items = items_of_participants(Participants),
Pubsub = #pubsub{
items = #ps_items{
node = ?NS_MIX_NODES_PARTICIPANTS,
items = Items}},
xmpp:make_iq_result(IQ, Pubsub);
{error, db_failure} ->
xmpp:make_error(IQ, db_error(IQ))
end;
{error, notfound} ->
xmpp:make_error(IQ, not_joined_error(IQ));
{error, db_failure} ->
xmpp:make_error(IQ, db_error(IQ))
end;
{error, notfound} ->
xmpp:make_error(IQ, no_channel_error(IQ));
{error, db_failure} ->
xmpp:make_error(IQ, db_error(IQ))
end.
-spec items_of_participants([{jid(), binary(), binary()}]) -> [ps_item()].
items_of_participants(Participants) ->
lists:map(
fun({JID, ID, Nick}) ->
Participant = #mix_participant{jid = JID, nick = Nick},
#ps_item{id = ID,
sub_els = [xmpp:encode(Participant)]}
end, Participants).
-spec known_nodes() -> [binary()].
known_nodes() ->
[?NS_MIX_NODES_MESSAGES,
?NS_MIX_NODES_PARTICIPANTS].
-spec filter_nodes([binary()]) -> [binary()].
filter_nodes(Nodes) ->
lists:filter(
fun(Node) ->
lists:member(Node, Nodes)
end, known_nodes()).
-spec multicast(module(), binary(), binary(),
binary(), binary(), fun((jid()) -> message())) -> ok.
multicast(Mod, LServer, Chan, Service, Node, F) ->
case Mod:get_subscribed(LServer, Chan, Service, Node) of
{ok, Subscribers} ->
lists:foreach(
fun(To) ->
Msg = xmpp:set_to(F(To), To),
ejabberd_router:route(Msg)
end, Subscribers);
{error, db_failure} ->
ok
end.
-spec notify_participant_joined(module(), binary(),
jid(), jid(), binary(), binary()) -> ok.
notify_participant_joined(Mod, LServer, To, From, ID, Nick) ->
{Chan, Host, _} = jid:tolower(To),
Participant = #mix_participant{jid = jid:remove_resource(From),
nick = Nick},
Item = #ps_item{id = ID,
sub_els = [xmpp:encode(Participant)]},
Items = #ps_items{node = ?NS_MIX_NODES_PARTICIPANTS,
items = [Item]},
Event = #ps_event{items = Items},
Msg = #message{from = jid:remove_resource(To),
id = p1_rand:get_string(),
sub_els = [Event]},
multicast(Mod, LServer, Chan, Host,
?NS_MIX_NODES_PARTICIPANTS,
fun(_) -> Msg end).
-spec notify_participant_left(module(), binary(), jid(), binary()) -> ok.
notify_participant_left(Mod, LServer, To, ID) ->
{Chan, Host, _} = jid:tolower(To),
Items = #ps_items{node = ?NS_MIX_NODES_PARTICIPANTS,
retract = ID},
Event = #ps_event{items = Items},
Msg = #message{from = jid:remove_resource(To),
id = p1_rand:get_string(),
sub_els = [Event]},
multicast(Mod, LServer, Chan, Host, ?NS_MIX_NODES_PARTICIPANTS,
fun(_) -> Msg end).
-spec make_id(jid(), binary()) -> binary().
make_id(JID, Key) ->
Data = jid:encode(jid:tolower(jid:remove_resource(JID))),
xmpp_util:hex(misc:crypto_hmac(sha256, Data, Key, 10)).
%%%===================================================================
%%% Error generators
%%%===================================================================
-spec db_error(stanza()) -> stanza_error().
db_error(Pkt) ->
Txt = ?T("Database failure"),
xmpp:err_internal_server_error(Txt, xmpp:get_lang(Pkt)).
-spec channel_exists_error(stanza()) -> stanza_error().
channel_exists_error(Pkt) ->
Txt = ?T("Channel already exists"),
xmpp:err_conflict(Txt, xmpp:get_lang(Pkt)).
-spec no_channel_error(stanza()) -> stanza_error().
no_channel_error(Pkt) ->
Txt = ?T("Channel does not exist"),
xmpp:err_item_not_found(Txt, xmpp:get_lang(Pkt)).
-spec not_joined_error(stanza()) -> stanza_error().
not_joined_error(Pkt) ->
Txt = ?T("You are not joined to the channel"),
xmpp:err_forbidden(Txt, xmpp:get_lang(Pkt)).
-spec unsupported_error(stanza()) -> stanza_error().
unsupported_error(Pkt) ->
Txt = ?T("No module is handling this query"),
xmpp:err_service_unavailable(Txt, xmpp:get_lang(Pkt)).
-spec ownership_error(stanza()) -> stanza_error().
ownership_error(Pkt) ->
Txt = ?T("Owner privileges required"),
xmpp:err_forbidden(Txt, xmpp:get_lang(Pkt)).
%%%===================================================================
%%% IQ handlers
%%%===================================================================
-spec register_iq_handlers(binary()) -> ok.
register_iq_handlers(Host) ->
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO,
?MODULE, process_disco_info),
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS,
?MODULE, process_disco_items),
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_MIX_CORE_0,
?MODULE, process_mix_core),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_DISCO_INFO,
?MODULE, process_disco_info),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_DISCO_ITEMS,
?MODULE, process_disco_items),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_MIX_CORE_0,
?MODULE, process_mix_core),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_PUBSUB,
?MODULE, process_pubsub_query),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_MAM_2,
?MODULE, process_mam_query).
-spec unregister_iq_handlers(binary()) -> ok.
unregister_iq_handlers(Host) ->
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO),
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS),
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_MIX_CORE_0),
gen_iq_handler:remove_iq_handler(ejabberd_sm, 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_MIX_CORE_0),
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_PUBSUB),
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_MAM_2).