1 |
|
%% @doc Provide an interface for frontends (like graphql or ctl) to manage roster. |
2 |
|
-module(mod_roster_api). |
3 |
|
|
4 |
|
-export([list_contacts/1, |
5 |
|
get_contact/2, |
6 |
|
add_contact/4, |
7 |
|
delete_contact/2, |
8 |
|
subscription/3, |
9 |
|
subscribe_both/2, |
10 |
|
set_mutual_subscription/3]). |
11 |
|
|
12 |
|
-include("mongoose.hrl"). |
13 |
|
-include("jlib.hrl"). |
14 |
|
-include("mod_roster.hrl"). |
15 |
|
|
16 |
|
-type sub_mutual_action() :: connect | disconnect. |
17 |
|
|
18 |
|
-define(UNKNOWN_DOMAIN_RESULT, {unknown_domain, "Domain not found"}). |
19 |
|
-define(INTERNAL_ERROR_RESULT(Error, Operation), |
20 |
|
{internal, io_lib:format("Cannot ~p a contact because: ~p", [Operation, Error])}). |
21 |
|
|
22 |
|
-spec add_contact(jid:jid(), jid:jid(), binary(), [binary()]) -> |
23 |
|
{ok | internal | user_not_exist | unknown_domain, iolist()}. |
24 |
|
add_contact(#jid{lserver = LServer} = CallerJID, ContactJID, Name, Groups) -> |
25 |
147 |
case mongoose_domain_api:get_domain_host_type(LServer) of |
26 |
|
{ok, HostType} -> |
27 |
141 |
case does_users_exist(CallerJID, ContactJID) of |
28 |
|
ok -> |
29 |
120 |
case mod_roster:set_roster_entry(HostType, CallerJID, ContactJID, |
30 |
|
Name, Groups) of |
31 |
|
ok -> |
32 |
120 |
{ok, "Contact added successfully"}; |
33 |
|
{error, Error} -> |
34 |
:-( |
?INTERNAL_ERROR_RESULT(Error, create) |
35 |
|
end; |
36 |
|
Error -> |
37 |
21 |
Error |
38 |
|
end; |
39 |
|
{error, not_found} -> |
40 |
6 |
?UNKNOWN_DOMAIN_RESULT |
41 |
|
end. |
42 |
|
|
43 |
|
-spec list_contacts(jid:jid()) -> {ok, [mod_roster:roster()]} | {unknown_domain, iolist()}. |
44 |
|
list_contacts(#jid{lserver = LServer} = CallerJID) -> |
45 |
99 |
case mongoose_domain_api:get_domain_host_type(LServer) of |
46 |
|
{ok, HostType} -> |
47 |
96 |
case ejabberd_auth:does_user_exist(CallerJID) of |
48 |
|
true -> |
49 |
93 |
mongoose_instrument:execute(mod_roster_get, #{host_type => HostType}, |
50 |
|
#{count => 1, jid => CallerJID}), |
51 |
93 |
Acc0 = mongoose_acc:new(#{ location => ?LOCATION, |
52 |
|
host_type => HostType, |
53 |
|
lserver => LServer, |
54 |
|
element => undefined }), |
55 |
93 |
Roster = mongoose_hooks:roster_get(Acc0, CallerJID, true), |
56 |
93 |
{ok, Roster}; |
57 |
|
false -> |
58 |
3 |
{user_not_exist, io_lib:format("The user ~s does not exist", |
59 |
|
[jid:to_binary(CallerJID)])} |
60 |
|
end; |
61 |
|
{error, not_found} -> |
62 |
3 |
?UNKNOWN_DOMAIN_RESULT |
63 |
|
end. |
64 |
|
|
65 |
|
-spec get_contact(jid:jid(), jid:jid()) -> |
66 |
|
{ok, mod_roster:roster()} | |
67 |
|
{contact_not_found | internal | unknown_domain | user_not_exist, iolist()}. |
68 |
|
get_contact(#jid{lserver = LServer} = UserJID, ContactJID) -> |
69 |
13 |
case mongoose_domain_api:get_domain_host_type(LServer) of |
70 |
|
{ok, HostType} -> |
71 |
11 |
ContactL = jid:to_lower(ContactJID), |
72 |
11 |
case mod_roster:get_roster_entry(HostType, UserJID, ContactL, full) of |
73 |
|
#roster{} = R -> |
74 |
6 |
{ok, R}; |
75 |
|
does_not_exist -> |
76 |
5 |
{contact_not_found, "Given contact does not exist"}; |
77 |
|
error -> |
78 |
:-( |
?INTERNAL_ERROR_RESULT(error, get) |
79 |
|
end; |
80 |
|
{error, not_found} -> |
81 |
2 |
?UNKNOWN_DOMAIN_RESULT |
82 |
|
end. |
83 |
|
|
84 |
|
-spec delete_contact(jid:jid(), jid:jid()) -> |
85 |
|
{ok | contact_not_found | internal | unknown_domain, iolist()}. |
86 |
|
delete_contact(#jid{lserver = LServer} = CallerJID, ContactJID) -> |
87 |
37 |
case mongoose_domain_api:get_domain_host_type(LServer) of |
88 |
|
{ok, HostType} -> |
89 |
33 |
case mod_roster:remove_from_roster(HostType, CallerJID, ContactJID) of |
90 |
|
ok -> |
91 |
22 |
{ok, io_lib:format("Contact ~s deleted successfully", |
92 |
|
[jid:to_binary(ContactJID)])}; |
93 |
|
{error, does_not_exist} -> |
94 |
11 |
ErrMsg = io_lib:format("Cannot remove ~s contact that does not exist", |
95 |
|
[jid:to_binary(ContactJID)]), |
96 |
11 |
{contact_not_found, ErrMsg}; |
97 |
|
{error, Error} -> |
98 |
:-( |
?INTERNAL_ERROR_RESULT(Error, delete) |
99 |
|
end; |
100 |
|
{error, not_found} -> |
101 |
4 |
?UNKNOWN_DOMAIN_RESULT |
102 |
|
end. |
103 |
|
|
104 |
|
-spec subscription(jid:jid(), jid:jid(), mod_roster:sub_presence()) -> |
105 |
|
{ok | unknown_domain, iolist()}. |
106 |
|
subscription(#jid{lserver = LServer} = CallerJID, ContactJID, Type) -> |
107 |
179 |
StanzaType = atom_to_binary(Type, latin1), |
108 |
179 |
El = #xmlel{name = <<"presence">>, attrs = [{<<"type">>, StanzaType}]}, |
109 |
179 |
case mongoose_domain_api:get_domain_host_type(LServer) of |
110 |
|
{ok, HostType} -> |
111 |
177 |
Acc1 = mongoose_acc:new(#{ location => ?LOCATION, |
112 |
|
from_jid => CallerJID, |
113 |
|
to_jid => ContactJID, |
114 |
|
host_type => HostType, |
115 |
|
lserver => LServer, |
116 |
|
element => El }), |
117 |
177 |
Acc2 = mongoose_hooks:roster_out_subscription(Acc1, CallerJID, ContactJID, Type), |
118 |
177 |
ejabberd_router:route(CallerJID, jid:to_bare(ContactJID), Acc2), |
119 |
177 |
{ok, io_lib:format("Subscription stanza with type ~s sent successfully", [StanzaType])}; |
120 |
|
{error, not_found} -> |
121 |
2 |
?UNKNOWN_DOMAIN_RESULT |
122 |
|
end. |
123 |
|
|
124 |
|
-spec set_mutual_subscription(jid:jid(), jid:jid(), sub_mutual_action()) -> |
125 |
|
{ok | contact_not_found | internal | unknown_domain | user_not_exist, iolist()}. |
126 |
|
set_mutual_subscription(UserA, UserB, connect) -> |
127 |
8 |
subscribe_both({UserA, <<>>, []}, {UserB, <<>>, []}); |
128 |
|
set_mutual_subscription(UserA, UserB, disconnect) -> |
129 |
7 |
Seq = [fun() -> delete_contact(UserA, UserB) end, |
130 |
4 |
fun() -> delete_contact(UserB, UserA) end], |
131 |
7 |
case run_seq(Seq, ok) of |
132 |
|
ok -> |
133 |
4 |
{ok, "Mutual subscription removed successfully"}; |
134 |
|
Error -> |
135 |
3 |
Error |
136 |
|
end. |
137 |
|
|
138 |
|
-spec subscribe_both({jid:jid(), binary(), [binary()]}, {jid:jid(), binary(), [binary()]}) -> |
139 |
|
{ok | internal | unknown_domain | user_not_exist, iolist()}. |
140 |
|
subscribe_both({UserA, NameA, GroupsA}, {UserB, NameB, GroupsB}) -> |
141 |
50 |
Seq = [fun() -> add_contact(UserA, UserB, NameB, GroupsB) end, |
142 |
39 |
fun() -> add_contact(UserB, UserA, NameA, GroupsA) end, |
143 |
39 |
fun() -> subscription(UserA, UserB, subscribe) end, |
144 |
39 |
fun() -> subscription(UserB, UserA, subscribe) end, |
145 |
39 |
fun() -> subscription(UserA, UserB, subscribed) end, |
146 |
39 |
fun() -> subscription(UserB, UserA, subscribed) end], |
147 |
50 |
case run_seq(Seq, ok) of |
148 |
|
ok -> |
149 |
39 |
{ok, io_lib:format("Subscription between users ~s and ~s created successfully", |
150 |
|
[jid:to_binary(UserA), jid:to_binary(UserB)])}; |
151 |
|
Error -> |
152 |
11 |
Error |
153 |
|
end. |
154 |
|
|
155 |
|
%% Internal |
156 |
|
|
157 |
|
-spec does_users_exist(jid:jid(), jid:jid()) -> ok | {user_not_exist, iolist()}. |
158 |
|
does_users_exist(User, Contact) -> |
159 |
141 |
case lists:filter(fun(U) -> not ejabberd_auth:does_user_exist(U) end, [User, Contact]) of |
160 |
|
[] -> |
161 |
120 |
ok; |
162 |
|
[NotExist | _]-> |
163 |
21 |
{user_not_exist, io_lib:format("The user ~s does not exist", |
164 |
|
[jid:to_binary(NotExist)])} |
165 |
|
end. |
166 |
|
|
167 |
|
-spec run_seq([fun(() -> any())], term()) -> ok | {atom(), iolist()}. |
168 |
|
run_seq([Cmd | Seq], ok) -> |
169 |
57 |
run_seq(Seq, Cmd()); |
170 |
|
run_seq([Cmd | Seq], {ok, _}) -> |
171 |
199 |
run_seq(Seq, Cmd()); |
172 |
43 |
run_seq([], _) -> ok; |
173 |
|
run_seq(_, {_, _} = Error) -> |
174 |
14 |
Error. |