xmpp.chapril.org-ejabberd/src/mod_mix.erl

315 lines
11 KiB
Erlang
Raw Normal View History

2016-03-08 18:04:29 +01:00
%%%-------------------------------------------------------------------
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
%%% @copyright (C) 2016, Evgeny Khramtsov
%%% @doc
%%%
%%% @end
%%% Created : 2 Mar 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
%%%-------------------------------------------------------------------
-module(mod_mix).
-behaviour(gen_server).
-behaviour(gen_mod).
%% API
2016-07-31 07:51:47 +02:00
-export([start_link/2, start/2, stop/1, process_iq/1,
2016-03-08 18:04:29 +01:00
disco_items/5, disco_identity/5, disco_info/5,
disco_features/5, mod_opt_type/1, depends/2]).
2016-03-08 18:04:29 +01:00
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-include("logger.hrl").
2016-07-31 07:51:47 +02:00
-include("xmpp.hrl").
2016-03-08 18:04:29 +01:00
-define(PROCNAME, ejabberd_mod_mix).
-define(NODES, [?NS_MIX_NODES_MESSAGES,
?NS_MIX_NODES_PRESENCE,
?NS_MIX_NODES_PARTICIPANTS,
?NS_MIX_NODES_SUBJECT,
?NS_MIX_NODES_CONFIG]).
-record(state, {server_host :: binary(),
host :: binary()}).
%%%===================================================================
%%% API
%%%===================================================================
start_link(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
start(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
ChildSpec = {Proc, {?MODULE, start_link, [Host, Opts]},
2016-08-01 14:09:16 +02:00
transient, 5000, worker, [?MODULE]},
2016-03-08 18:04:29 +01:00
supervisor:start_child(ejabberd_sup, ChildSpec).
stop(Host) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
supervisor:terminate_child(ejabberd_sup, Proc),
supervisor:delete_child(ejabberd_sup, Proc),
ok.
-spec disco_features({error, stanza_error()} | {result, [binary()]} | empty,
2016-08-09 09:56:32 +02:00
jid(), jid(), binary(), binary()) -> {result, [binary()]}.
2016-03-08 18:04:29 +01:00
disco_features(_Acc, _From, _To, _Node, _Lang) ->
{result, [?NS_MIX_0]}.
disco_items(_Acc, _From, To, _Node, _Lang) when To#jid.luser /= <<"">> ->
2016-07-31 07:51:47 +02:00
BareTo = jid:remove_resource(To),
{result, [#disco_item{jid = BareTo, node = Node} || Node <- ?NODES]};
2016-03-08 18:04:29 +01:00
disco_items(_Acc, _From, _To, _Node, _Lang) ->
{result, []}.
disco_identity(Acc, _From, To, _Node, _Lang) when To#jid.luser == <<"">> ->
2016-07-31 07:51:47 +02:00
Acc ++ [#identity{category = <<"conference">>,
name = <<"MIX service">>,
type = <<"text">>}];
2016-03-08 18:04:29 +01:00
disco_identity(Acc, _From, _To, _Node, _Lang) ->
2016-07-31 07:51:47 +02:00
Acc ++ [#identity{category = <<"conference">>,
type = <<"mix">>}].
2016-03-08 18:04:29 +01:00
2016-08-09 09:56:32 +02:00
-spec disco_info([xdata()], binary(), module(), binary(), binary()) -> [xdata()];
([xdata()], jid(), jid(), binary(), binary()) -> [xdata()].
2016-03-08 18:04:29 +01:00
disco_info(_Acc, _From, To, _Node, _Lang) when is_atom(To) ->
2016-07-31 07:51:47 +02:00
[#xdata{type = result,
fields = [#xdata_field{var = <<"FORM_TYPE">>,
type = hidden,
values = [?NS_MIX_SERVICEINFO_0]}]}];
2016-03-08 18:04:29 +01:00
disco_info(Acc, _From, _To, _Node, _Lang) ->
Acc.
2016-07-31 07:51:47 +02:00
process_iq(#iq{type = set, from = From, to = To,
sub_els = [#mix_join{subscribe = SubNodes}]} = IQ) ->
Nodes = [Node || Node <- SubNodes, lists:member(Node, ?NODES)],
2016-03-08 18:04:29 +01:00
case subscribe_nodes(From, To, Nodes) of
{result, _} ->
case publish_participant(From, To) of
{result, _} ->
2016-07-31 07:51:47 +02:00
BareFrom = jid:remove_resource(From),
xmpp:make_iq_result(
IQ, #mix_join{jid = BareFrom, subscribe = Nodes});
2016-03-08 18:04:29 +01:00
{error, Err} ->
2016-07-31 07:51:47 +02:00
xmpp:make_error(IQ, Err)
2016-03-08 18:04:29 +01:00
end;
{error, Err} ->
2016-07-31 07:51:47 +02:00
xmpp:make_error(IQ, Err)
2016-03-08 18:04:29 +01:00
end;
2016-07-31 07:51:47 +02:00
process_iq(#iq{type = set, from = From, to = To,
sub_els = [#mix_leave{}]} = IQ) ->
2016-03-08 18:04:29 +01:00
case delete_participant(From, To) of
{result, _} ->
case unsubscribe_nodes(From, To, ?NODES) of
{result, _} ->
2016-07-31 07:51:47 +02:00
xmpp:make_iq_result(IQ);
2016-03-08 18:04:29 +01:00
{error, Err} ->
2016-07-31 07:51:47 +02:00
xmpp:make_error(IQ, Err)
2016-03-08 18:04:29 +01:00
end;
{error, Err} ->
2016-07-31 07:51:47 +02:00
xmpp:make_error(IQ, Err)
2016-03-08 18:04:29 +01:00
end;
2016-07-31 07:51:47 +02:00
process_iq(#iq{lang = Lang} = IQ) ->
Txt = <<"Unsupported MIX query">>,
2016-07-31 07:51:47 +02:00
xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang)).
2016-03-08 18:04:29 +01:00
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
init([ServerHost, Opts]) ->
Host = gen_mod:get_opt_host(ServerHost, Opts, <<"mix.@HOST@">>),
IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
one_queue),
ConfigTab = gen_mod:get_module_proc(Host, config),
ets:new(ConfigTab, [named_table]),
ets:insert(ConfigTab, {plugins, [<<"mix">>]}),
ejabberd_hooks:add(disco_local_items, Host, ?MODULE, disco_items, 100),
ejabberd_hooks:add(disco_local_features, Host, ?MODULE, disco_features, 100),
ejabberd_hooks:add(disco_local_identity, Host, ?MODULE, disco_identity, 100),
ejabberd_hooks:add(disco_sm_items, Host, ?MODULE, disco_items, 100),
ejabberd_hooks:add(disco_sm_features, Host, ?MODULE, disco_features, 100),
ejabberd_hooks:add(disco_sm_identity, Host, ?MODULE, disco_identity, 100),
ejabberd_hooks:add(disco_info, Host, ?MODULE, disco_info, 100),
gen_iq_handler:add_iq_handler(ejabberd_local, Host,
?NS_DISCO_ITEMS, mod_disco,
process_local_iq_items, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_local, Host,
?NS_DISCO_INFO, mod_disco,
process_local_iq_info, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
?NS_DISCO_ITEMS, mod_disco,
process_local_iq_items, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
?NS_DISCO_INFO, mod_disco,
process_local_iq_info, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
?NS_PUBSUB, mod_pubsub, iq_sm, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
?NS_MIX_0, ?MODULE, process_iq, IQDisc),
ejabberd_router:register_route(Host, ServerHost),
2016-03-08 18:04:29 +01:00
{ok, #state{server_host = ServerHost, host = Host}}.
handle_call(_Request, _From, State) ->
Reply = ok,
{reply, Reply, State}.
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info({route, From, To, Packet}, State) ->
case catch do_route(State, From, To, Packet) of
{'EXIT', _} = Err ->
try
?ERROR_MSG("failed to route packet ~p from '~s' to '~s': ~p",
[Packet, jid:to_string(From), jid:to_string(To), Err]),
2016-07-31 07:51:47 +02:00
Error = xmpp:err_internal_server_error(),
ejabberd_router:route_error(To, From, Packet, Error)
2016-03-08 18:04:29 +01:00
catch _:_ ->
ok
end;
_ ->
ok
end,
{noreply, State};
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, #state{host = Host}) ->
ejabberd_hooks:delete(disco_local_items, Host, ?MODULE, disco_items, 100),
ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, disco_features, 100),
ejabberd_hooks:delete(disco_local_identity, Host, ?MODULE, disco_identity, 100),
ejabberd_hooks:delete(disco_sm_items, Host, ?MODULE, disco_items, 100),
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, disco_features, 100),
ejabberd_hooks:delete(disco_sm_identity, Host, ?MODULE, disco_identity, 100),
ejabberd_hooks:delete(disco_info, Host, ?MODULE, disco_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_INFO),
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_DISCO_ITEMS),
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_DISCO_INFO),
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_PUBSUB),
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_MIX_0),
ejabberd_router:unregister_route(Host),
2016-03-08 18:04:29 +01:00
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%%===================================================================
%%% Internal functions
%%%===================================================================
2016-07-31 07:51:47 +02:00
do_route(_State, From, To, #iq{} = Packet) ->
ejabberd_router:process_iq(From, To, Packet);
do_route(_State, From, To, #presence{type = unavailable})
2016-03-08 18:04:29 +01:00
when To#jid.luser /= <<"">> ->
2016-07-31 07:51:47 +02:00
delete_presence(From, To);
2016-03-08 18:04:29 +01:00
do_route(_State, _From, _To, _Packet) ->
ok.
subscribe_nodes(From, To, Nodes) ->
LTo = jid:tolower(jid:remove_resource(To)),
LFrom = jid:tolower(jid:remove_resource(From)),
lists:foldl(
fun(_Node, {error, _} = Err) ->
Err;
(Node, {result, _}) ->
2016-09-13 11:30:05 +02:00
case mod_pubsub:subscribe_node(LTo, Node, From, From, []) of
2016-03-08 18:04:29 +01:00
{error, _} = Err ->
case is_item_not_found(Err) of
true ->
case mod_pubsub:create_node(
LTo, To#jid.lserver, Node, LFrom, <<"mix">>) of
{result, _} ->
2016-09-13 11:30:05 +02:00
mod_pubsub:subscribe_node(LTo, Node, From, From, []);
2016-03-08 18:04:29 +01:00
Error ->
Error
end;
false ->
Err
end;
{result, _} = Result ->
Result
end
end, {result, []}, Nodes).
unsubscribe_nodes(From, To, Nodes) ->
LTo = jid:tolower(jid:remove_resource(To)),
2016-09-13 11:30:05 +02:00
BareFrom = jid:remove_resource(From),
2016-03-08 18:04:29 +01:00
lists:foldl(
fun(_Node, {error, _} = Err) ->
Err;
(Node, {result, _} = Result) ->
2016-09-13 11:30:05 +02:00
case mod_pubsub:unsubscribe_node(LTo, Node, From, BareFrom, <<"">>) of
2016-03-08 18:04:29 +01:00
{error, _} = Err ->
case is_not_subscribed(Err) of
true -> Result;
_ -> Err
end;
{result, _} = Res ->
Res
end
end, {result, []}, Nodes).
publish_participant(From, To) ->
2016-07-31 07:51:47 +02:00
BareFrom = jid:remove_resource(From),
LFrom = jid:tolower(BareFrom),
2016-03-08 18:04:29 +01:00
LTo = jid:tolower(jid:remove_resource(To)),
2016-07-31 07:51:47 +02:00
Participant = #mix_participant{jid = BareFrom},
2016-03-08 18:04:29 +01:00
ItemID = p1_sha:sha(jid:to_string(LFrom)),
mod_pubsub:publish_item(
LTo, To#jid.lserver, ?NS_MIX_NODES_PARTICIPANTS,
2016-07-31 07:51:47 +02:00
From, ItemID, [xmpp:encode(Participant)]).
2016-03-08 18:04:29 +01:00
delete_presence(From, To) ->
LFrom = jid:tolower(From),
LTo = jid:tolower(jid:remove_resource(To)),
case mod_pubsub:get_items(LTo, ?NS_MIX_NODES_PRESENCE) of
Items when is_list(Items) ->
lists:foreach(
2016-07-31 07:51:47 +02:00
fun({pubsub_item, {ItemID, _}, _, {_, LJID}, _})
when LJID == LFrom ->
2016-03-08 18:04:29 +01:00
delete_item(From, To, ?NS_MIX_NODES_PRESENCE, ItemID);
(_) ->
ok
end, Items);
_ ->
ok
end.
delete_participant(From, To) ->
LFrom = jid:tolower(jid:remove_resource(From)),
ItemID = p1_sha:sha(jid:to_string(LFrom)),
delete_presence(From, To),
delete_item(From, To, ?NS_MIX_NODES_PARTICIPANTS, ItemID).
delete_item(From, To, Node, ItemID) ->
LTo = jid:tolower(jid:remove_resource(To)),
case mod_pubsub:delete_item(
LTo, Node, From, ItemID, true) of
{result, _} = Res ->
Res;
{error, _} = Err ->
case is_item_not_found(Err) of
true -> {result, []};
false -> Err
end
end.
2016-09-13 11:30:05 +02:00
-spec is_item_not_found({error, stanza_error()}) -> boolean().
is_item_not_found({error, #stanza_error{reason = 'item-not-found'}}) -> true;
is_item_not_found({error, _}) -> false.
2016-03-08 18:04:29 +01:00
2016-09-13 11:30:05 +02:00
-spec is_not_subscribed({error, stanza_error()}) -> boolean().
is_not_subscribed({error, #stanza_error{sub_els = Els}}) ->
%% TODO: make xmpp:get_els function working for any XMPP element
%% with sub_els field
xmpp:has_subtag(#message{sub_els = Els},
#ps_error{type = 'not-subscribed'}).
2016-03-09 09:19:15 +01:00
depends(_Host, _Opts) ->
[{mod_pubsub, hard}].
2016-03-09 09:19:15 +01:00
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
mod_opt_type(host) -> fun iolist_to_binary/1;
mod_opt_type(_) -> [host, iqdisc].