1 |
|
%%% @doc This module is responsible for running disco_* hooks that collect information |
2 |
|
%%% for service discovery. It also contains helpers for direct construction of XML elements |
3 |
|
%%% representing the advertised services. |
4 |
|
|
5 |
|
-module(mongoose_disco). |
6 |
|
|
7 |
|
%% Hooks wrappers |
8 |
|
-export([get_local_identity/5, |
9 |
|
get_sm_identity/5, |
10 |
|
get_local_items/5, |
11 |
|
get_sm_items/5, |
12 |
|
get_local_features/5, |
13 |
|
get_sm_features/5, |
14 |
|
get_muc_features/6, |
15 |
|
get_info/4]). |
16 |
|
|
17 |
|
%% Helpers used by hook handlers for Acc manipulation |
18 |
|
-export([add_identities/2, |
19 |
|
add_items/2, |
20 |
|
add_features/2, |
21 |
|
add_info/2]). |
22 |
|
|
23 |
|
%% XML construction helpers |
24 |
|
-export([identities_to_xml/1, |
25 |
|
items_to_xml/1, |
26 |
|
features_to_xml/1, |
27 |
|
info_list_to_xml/1]). |
28 |
|
|
29 |
|
-ignore_xref([items_to_xml/1]). |
30 |
|
|
31 |
|
-include("jlib.hrl"). |
32 |
|
|
33 |
|
-type feature_acc() :: acc(feature()). |
34 |
|
-type feature() :: binary(). |
35 |
|
|
36 |
|
-type item_acc() :: acc(item()). |
37 |
|
-type item() :: #{jid := jid:lserver(), name => binary(), node => binary()}. |
38 |
|
|
39 |
|
-type identity_acc() :: acc(identity()). |
40 |
|
-type identity() :: #{category := binary(), type := binary(), name => binary()}. |
41 |
|
|
42 |
|
-type info_acc() :: #{host_type := mongooseim:host_type(), |
43 |
|
module := module(), |
44 |
|
node := binary(), |
45 |
|
lang := ejabberd:lang(), |
46 |
|
result := empty | [info()]}. |
47 |
|
-type info() :: #{xmlns := binary(), fields := [info_field()]}. |
48 |
|
-type info_field() :: #{var := binary(), values := [binary()], label => binary()}. |
49 |
|
|
50 |
|
-type acc(Elem) :: #{host_type := mongooseim:host_type(), |
51 |
|
from_jid := jid:jid(), |
52 |
|
to_jid := jid:jid(), |
53 |
|
node := binary(), |
54 |
|
lang := ejabberd:lang(), |
55 |
|
result := empty | [Elem]}. |
56 |
|
|
57 |
|
-export_type([item_acc/0, feature_acc/0, identity_acc/0, |
58 |
|
item/0, feature/0, identity/0, |
59 |
|
info_field/0, info/0, info_acc/0]). |
60 |
|
|
61 |
|
%% Hook wrapper API |
62 |
|
|
63 |
|
-spec get_local_identity(mongooseim:host_type(), jid:jid(), jid:jid(), binary(), ejabberd:lang()) -> |
64 |
|
[exml:element()]. |
65 |
|
get_local_identity(HostType, From, To, Node, Lang) -> |
66 |
227 |
InitialAcc = new_acc(HostType, From, To, Node, Lang), |
67 |
227 |
FinalAcc = mongoose_hooks:disco_local_identity(InitialAcc), |
68 |
227 |
Identities = extract_result(FinalAcc), |
69 |
227 |
identities_to_xml(Identities). |
70 |
|
|
71 |
|
-spec get_sm_identity(mongooseim:host_type(), jid:jid(), jid:jid(), binary(), ejabberd:lang()) -> |
72 |
|
[exml:element()]. |
73 |
|
get_sm_identity(HostType, From, To, Node, Lang) -> |
74 |
12 |
InitialAcc = new_acc(HostType, From, To, Node, Lang), |
75 |
12 |
IdentityAcc = mongoose_hooks:disco_sm_identity(InitialAcc), |
76 |
12 |
Identities = extract_result(IdentityAcc), |
77 |
12 |
identities_to_xml(Identities). |
78 |
|
|
79 |
|
-spec get_local_items(mongooseim:host_type(), jid:jid(), jid:jid(), binary(), ejabberd:lang()) -> |
80 |
|
{result, [exml:element()]} | empty. |
81 |
|
get_local_items(HostType, From, To, Node, Lang) -> |
82 |
45 |
Acc = new_acc(HostType, From, To, Node, Lang), |
83 |
45 |
case mongoose_hooks:disco_local_items(Acc) of |
84 |
:-( |
#{result := empty} -> empty; |
85 |
45 |
#{result := Items} -> {result, items_to_xml(Items)} |
86 |
|
end. |
87 |
|
|
88 |
|
-spec get_sm_items(mongooseim:host_type(), jid:jid(), jid:jid(), binary(), ejabberd:lang()) -> |
89 |
|
{result, [exml:element()]} | empty. |
90 |
|
get_sm_items(HostType, From, To, Node, Lang) -> |
91 |
13 |
Acc = new_acc(HostType, From, To, Node, Lang), |
92 |
13 |
case mongoose_hooks:disco_sm_items(Acc) of |
93 |
6 |
#{result := empty} -> empty; |
94 |
7 |
#{result := Items} -> {result, items_to_xml(Items)} |
95 |
|
end. |
96 |
|
|
97 |
|
-spec get_local_features(mongooseim:host_type(), jid:jid(), jid:jid(), binary(), ejabberd:lang()) -> |
98 |
|
{result, [exml:element()]} | empty. |
99 |
|
get_local_features(HostType, From, To, Node, Lang) -> |
100 |
227 |
Acc = new_acc(HostType, From, To, Node, Lang), |
101 |
227 |
case mongoose_hooks:disco_local_features(Acc) of |
102 |
:-( |
#{result := empty} -> empty; |
103 |
227 |
#{result := Features} -> {result, features_to_xml(Features)} |
104 |
|
end. |
105 |
|
|
106 |
|
-spec get_sm_features(mongooseim:host_type(), jid:jid(), jid:jid(), binary(), ejabberd:lang()) -> |
107 |
|
{result, [exml:element()]} | empty. |
108 |
|
get_sm_features(HostType, From, To, Node, Lang) -> |
109 |
12 |
Acc = new_acc(HostType, From, To, Node, Lang), |
110 |
12 |
case mongoose_hooks:disco_sm_features(Acc) of |
111 |
:-( |
#{result := empty} -> empty; |
112 |
12 |
#{result := Features} -> {result, features_to_xml(Features)} |
113 |
|
end. |
114 |
|
|
115 |
|
-spec get_muc_features(mongooseim:host_type(), jid:jid(), jid:jid(), binary(), ejabberd:lang(), |
116 |
|
[mongoose_disco:feature()]) -> |
117 |
|
[exml:element()]. |
118 |
|
get_muc_features(HostType, From, To, Node, Lang, ExtraFeatures) -> |
119 |
29 |
InitialAcc = new_acc(HostType, From, To, Node, Lang), |
120 |
29 |
FinalAcc = mongoose_hooks:disco_muc_features(InitialAcc), |
121 |
29 |
Features = ExtraFeatures ++ extract_result(FinalAcc), |
122 |
29 |
features_to_xml(Features). |
123 |
|
|
124 |
|
-spec get_info(mongooseim:host_type(), module() , binary(), ejabberd:lang()) -> [exml:element()]. |
125 |
|
get_info(HostType, Module, Node, Lang) -> |
126 |
248 |
InitialAcc = new_info_acc(HostType, Module, Node, Lang), |
127 |
248 |
FinalAcc = mongoose_hooks:disco_info(InitialAcc), |
128 |
248 |
InfoList = extract_result(FinalAcc), |
129 |
248 |
info_list_to_xml(InfoList). |
130 |
|
|
131 |
|
%% Helper API |
132 |
|
|
133 |
|
-spec add_identities([identity()], identity_acc()) -> identity_acc(). |
134 |
|
add_identities(Identities, Acc) -> |
135 |
241 |
add(Identities, Acc). |
136 |
|
|
137 |
|
-spec add_items([item()], item_acc()) -> item_acc(). |
138 |
|
add_items(Items, Acc) -> |
139 |
126 |
add(Items, Acc). |
140 |
|
|
141 |
|
-spec add_features([feature()], feature_acc()) -> feature_acc(). |
142 |
|
add_features(Features, Acc) -> |
143 |
1399 |
add(Features, Acc). |
144 |
|
|
145 |
|
-spec add_info([info()], info_acc()) -> info_acc(). |
146 |
|
add_info(InfoList, Acc) -> |
147 |
243 |
add(InfoList, Acc). |
148 |
|
|
149 |
|
%% XML construction API |
150 |
|
|
151 |
|
-spec identities_to_xml([identity()]) -> [exml:element()]. |
152 |
|
identities_to_xml(Identities) -> |
153 |
275 |
lists:map(fun identity_to_xml/1, Identities). |
154 |
|
|
155 |
|
-spec items_to_xml([item()]) -> [exml:element()]. |
156 |
|
items_to_xml(Items) -> |
157 |
|
%% For each JID, leave only the rightmost item with that JID (the one which was added first). |
158 |
|
%% This is needed as extension modules might add more detailed information about an item |
159 |
|
%% than the default which is obtained from the registered routes and contains only the JID. |
160 |
52 |
maps:values(maps:from_list([{JID, item_to_xml(Item)} || #{jid := JID} = Item <- Items])). |
161 |
|
|
162 |
|
-spec features_to_xml([feature()]) -> [exml:element()]. |
163 |
|
features_to_xml(Features) -> |
164 |
|
%% Avoid duplicating features |
165 |
275 |
[feature_to_xml(Feature) || Feature <- lists:usort(Features)]. |
166 |
|
|
167 |
|
-spec info_list_to_xml([info()]) -> [exml:element()]. |
168 |
|
info_list_to_xml(InfoList) -> |
169 |
253 |
[info_to_xml(Info) || Info <- InfoList]. |
170 |
|
|
171 |
|
%% Acc manipulation |
172 |
|
|
173 |
|
-spec new_acc(mongooseim:host_type(), jid:jid(), jid:jid(), binary(), ejabberd:lang()) -> |
174 |
|
acc(any()). |
175 |
|
new_acc(HostType, From, To, Node, Lang) -> |
176 |
565 |
#{host_type => HostType, |
177 |
|
from_jid => From, |
178 |
|
to_jid => To, |
179 |
|
node => Node, |
180 |
|
lang => Lang, |
181 |
|
result => empty}. |
182 |
|
|
183 |
|
-spec new_info_acc(mongooseim:host_type(), module(), binary(), ejabberd:lang()) -> info_acc(). |
184 |
|
new_info_acc(HostType, Module, Node, Lang) -> |
185 |
248 |
#{host_type => HostType, |
186 |
|
module => Module, |
187 |
|
node => Node, |
188 |
|
lang => Lang, |
189 |
|
result => empty}. |
190 |
|
|
191 |
|
-spec add([Elem], acc(Elem)) -> acc(Elem); |
192 |
|
([info()], info_acc()) -> info_acc(). |
193 |
|
add(Elements, Acc = #{result := empty}) -> |
194 |
786 |
Acc#{result := Elements}; |
195 |
|
add(Elements, Acc = #{result := InitialElements}) -> |
196 |
1223 |
Acc#{result := Elements ++ InitialElements}. |
197 |
|
|
198 |
|
-spec extract_result(acc(Elem)) -> [Elem]; |
199 |
|
(info_acc()) -> [info()]. |
200 |
15 |
extract_result(#{result := empty}) -> []; |
201 |
501 |
extract_result(#{result := Elements}) -> Elements. |
202 |
|
|
203 |
|
%% Conversion to XML |
204 |
|
|
205 |
|
feature_to_xml(Feature) when is_binary(Feature) -> |
206 |
8256 |
#xmlel{name = <<"feature">>, attrs = [{<<"var">>, Feature}]}. |
207 |
|
|
208 |
|
item_to_xml(Item) -> |
209 |
128 |
#xmlel{name = <<"item">>, |
210 |
163 |
attrs = lists:map(fun({Key, Value}) -> {atom_to_binary(Key, utf8), Value} end, |
211 |
|
maps:to_list(Item))}. |
212 |
|
|
213 |
|
identity_to_xml(Identity) -> |
214 |
279 |
#xmlel{name = <<"identity">>, |
215 |
822 |
attrs = lists:map(fun({Key, Value}) -> {atom_to_binary(Key, utf8), Value} end, |
216 |
|
maps:to_list(Identity))}. |
217 |
|
|
218 |
|
-spec info_to_xml(info()) -> exml:element(). |
219 |
|
info_to_xml(#{xmlns := NS, fields := Fields}) -> |
220 |
248 |
mongoose_data_forms:form(#{type => <<"result">>, ns => NS, fields => Fields}). |