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