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 |
Acc0 = mongoose_acc:new(#{ location => ?LOCATION, |
50 |
|
host_type => HostType, |
51 |
|
lserver => LServer, |
52 |
|
element => undefined }), |
53 |
93 |
Acc1 = mongoose_acc:set(roster, show_full_roster, true, Acc0), |
54 |
93 |
Acc2 = mongoose_hooks:roster_get(Acc1, CallerJID), |
55 |
93 |
{ok, mongoose_acc:get(roster, items, Acc2)}; |
56 |
|
false -> |
57 |
3 |
{user_not_exist, io_lib:format("The user ~s does not exist", |
58 |
|
[jid:to_binary(CallerJID)])} |
59 |
|
end; |
60 |
|
{error, not_found} -> |
61 |
3 |
?UNKNOWN_DOMAIN_RESULT |
62 |
|
end. |
63 |
|
|
64 |
|
-spec get_contact(jid:jid(), jid:jid()) -> |
65 |
|
{ok, mod_roster:roster()} | |
66 |
|
{contact_not_found | internal | unknown_domain | user_not_exist, iolist()}. |
67 |
|
get_contact(#jid{lserver = LServer} = UserJID, ContactJID) -> |
68 |
13 |
case mongoose_domain_api:get_domain_host_type(LServer) of |
69 |
|
{ok, HostType} -> |
70 |
11 |
ContactL = jid:to_lower(ContactJID), |
71 |
11 |
case mod_roster:get_roster_entry(HostType, UserJID, ContactL, full) of |
72 |
|
#roster{} = R -> |
73 |
6 |
{ok, R}; |
74 |
|
does_not_exist -> |
75 |
5 |
{contact_not_found, "Given contact does not exist"}; |
76 |
|
error -> |
77 |
:-( |
?INTERNAL_ERROR_RESULT(error, get) |
78 |
|
end; |
79 |
|
{error, not_found} -> |
80 |
2 |
?UNKNOWN_DOMAIN_RESULT |
81 |
|
end. |
82 |
|
|
83 |
|
-spec delete_contact(jid:jid(), jid:jid()) -> |
84 |
|
{ok | contact_not_found | internal | unknown_domain, iolist()}. |
85 |
|
delete_contact(#jid{lserver = LServer} = CallerJID, ContactJID) -> |
86 |
37 |
case mongoose_domain_api:get_domain_host_type(LServer) of |
87 |
|
{ok, HostType} -> |
88 |
33 |
case mod_roster:remove_from_roster(HostType, CallerJID, ContactJID) of |
89 |
|
ok -> |
90 |
22 |
{ok, io_lib:format("Contact ~s deleted successfully", |
91 |
|
[jid:to_binary(ContactJID)])}; |
92 |
|
{error, does_not_exist} -> |
93 |
11 |
ErrMsg = io_lib:format("Cannot remove ~s contact that does not exist", |
94 |
|
[jid:to_binary(ContactJID)]), |
95 |
11 |
{contact_not_found, ErrMsg}; |
96 |
|
{error, Error} -> |
97 |
:-( |
?INTERNAL_ERROR_RESULT(Error, delete) |
98 |
|
end; |
99 |
|
{error, not_found} -> |
100 |
4 |
?UNKNOWN_DOMAIN_RESULT |
101 |
|
end. |
102 |
|
|
103 |
|
-spec subscription(jid:jid(), jid:jid(), mod_roster:sub_presence()) -> |
104 |
|
{ok | unknown_domain, iolist()}. |
105 |
|
subscription(#jid{lserver = LServer} = CallerJID, ContactJID, Type) -> |
106 |
179 |
StanzaType = atom_to_binary(Type, latin1), |
107 |
179 |
El = #xmlel{name = <<"presence">>, attrs = [{<<"type">>, StanzaType}]}, |
108 |
179 |
case mongoose_domain_api:get_domain_host_type(LServer) of |
109 |
|
{ok, HostType} -> |
110 |
177 |
Acc1 = mongoose_acc:new(#{ location => ?LOCATION, |
111 |
|
from_jid => CallerJID, |
112 |
|
to_jid => ContactJID, |
113 |
|
host_type => HostType, |
114 |
|
lserver => LServer, |
115 |
|
element => El }), |
116 |
177 |
Acc2 = mongoose_hooks:roster_out_subscription(Acc1, CallerJID, ContactJID, Type), |
117 |
177 |
ejabberd_router:route(CallerJID, jid:to_bare(ContactJID), Acc2), |
118 |
177 |
{ok, io_lib:format("Subscription stanza with type ~s sent successfully", [StanzaType])}; |
119 |
|
{error, not_found} -> |
120 |
2 |
?UNKNOWN_DOMAIN_RESULT |
121 |
|
end. |
122 |
|
|
123 |
|
-spec set_mutual_subscription(jid:jid(), jid:jid(), sub_mutual_action()) -> |
124 |
|
{ok | contact_not_found | internal | unknown_domain | user_not_exist, iolist()}. |
125 |
|
set_mutual_subscription(UserA, UserB, connect) -> |
126 |
8 |
subscribe_both({UserA, <<>>, []}, {UserB, <<>>, []}); |
127 |
|
set_mutual_subscription(UserA, UserB, disconnect) -> |
128 |
7 |
Seq = [fun() -> delete_contact(UserA, UserB) end, |
129 |
4 |
fun() -> delete_contact(UserB, UserA) end], |
130 |
7 |
case run_seq(Seq, ok) of |
131 |
|
ok -> |
132 |
4 |
{ok, "Mutual subscription removed successfully"}; |
133 |
|
Error -> |
134 |
3 |
Error |
135 |
|
end. |
136 |
|
|
137 |
|
-spec subscribe_both({jid:jid(), binary(), [binary()]}, {jid:jid(), binary(), [binary()]}) -> |
138 |
|
{ok | internal | unknown_domain | user_not_exist, iolist()}. |
139 |
|
subscribe_both({UserA, NameA, GroupsA}, {UserB, NameB, GroupsB}) -> |
140 |
50 |
Seq = [fun() -> add_contact(UserA, UserB, NameB, GroupsB) end, |
141 |
39 |
fun() -> add_contact(UserB, UserA, NameA, GroupsA) end, |
142 |
39 |
fun() -> subscription(UserA, UserB, subscribe) end, |
143 |
39 |
fun() -> subscription(UserB, UserA, subscribe) end, |
144 |
39 |
fun() -> subscription(UserA, UserB, subscribed) end, |
145 |
39 |
fun() -> subscription(UserB, UserA, subscribed) end], |
146 |
50 |
case run_seq(Seq, ok) of |
147 |
|
ok -> |
148 |
39 |
{ok, io_lib:format("Subscription between users ~s and ~s created successfully", |
149 |
|
[jid:to_binary(UserA), jid:to_binary(UserB)])}; |
150 |
|
Error -> |
151 |
11 |
Error |
152 |
|
end. |
153 |
|
|
154 |
|
%% Internal |
155 |
|
|
156 |
|
-spec does_users_exist(jid:jid(), jid:jid()) -> ok | {user_not_exist, iolist()}. |
157 |
|
does_users_exist(User, Contact) -> |
158 |
141 |
case lists:filter(fun(U) -> not ejabberd_auth:does_user_exist(U) end, [User, Contact]) of |
159 |
|
[] -> |
160 |
120 |
ok; |
161 |
|
[NotExist | _]-> |
162 |
21 |
{user_not_exist, io_lib:format("The user ~s does not exist", |
163 |
|
[jid:to_binary(NotExist)])} |
164 |
|
end. |
165 |
|
|
166 |
|
-spec run_seq([fun(() -> any())], term()) -> ok | {atom(), iolist()}. |
167 |
|
run_seq([Cmd | Seq], ok) -> |
168 |
57 |
run_seq(Seq, Cmd()); |
169 |
|
run_seq([Cmd | Seq], {ok, _}) -> |
170 |
199 |
run_seq(Seq, Cmd()); |
171 |
43 |
run_seq([], _) -> ok; |
172 |
|
run_seq(_, {_, _} = Error) -> |
173 |
14 |
Error. |