1 |
|
%%%------------------------------------------------------------------- |
2 |
|
%%% File : service_admin_extra_vcard.erl |
3 |
|
%%% Author : Badlop <badlop@process-one.net>, Piotr Nosek <piotr.nosek@erlang-solutions.com> |
4 |
|
%%% Purpose : Contributed administrative functions and commands |
5 |
|
%%% Created : 10 Aug 2008 by Badlop <badlop@process-one.net> |
6 |
|
%%% |
7 |
|
%%% |
8 |
|
%%% ejabberd, Copyright (C) 2002-2008 ProcessOne |
9 |
|
%%% |
10 |
|
%%% This program is free software; you can redistribute it and/or |
11 |
|
%%% modify it under the terms of the GNU General Public License as |
12 |
|
%%% published by the Free Software Foundation; either version 2 of the |
13 |
|
%%% License, or (at your option) any later version. |
14 |
|
%%% |
15 |
|
%%% This program is distributed in the hope that it will be useful, |
16 |
|
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of |
17 |
|
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
18 |
|
%%% General Public License for more details. |
19 |
|
%%% |
20 |
|
%%% You should have received a copy of the GNU General Public License |
21 |
|
%%% along with this program; if not, write to the Free Software |
22 |
|
%%% Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
23 |
|
%%% |
24 |
|
%%%------------------------------------------------------------------- |
25 |
|
|
26 |
|
-module(service_admin_extra_vcard). |
27 |
|
-author('badlop@process-one.net'). |
28 |
|
|
29 |
|
-export([ |
30 |
|
commands/0, |
31 |
|
|
32 |
|
get_vcard/3, |
33 |
|
get_vcard/4, |
34 |
|
set_vcard/4, |
35 |
|
set_vcard/5 |
36 |
|
]). |
37 |
|
|
38 |
|
-ignore_xref([commands/0, get_vcard/3, get_vcard/4, set_vcard/5, set_vcard/4, set_vcard/5]). |
39 |
|
|
40 |
|
-include("mongoose.hrl"). |
41 |
|
-include("ejabberd_commands.hrl"). |
42 |
|
-include("mod_roster.hrl"). |
43 |
|
-include("jlib.hrl"). |
44 |
|
-include_lib("exml/include/exml.hrl"). |
45 |
|
|
46 |
|
%%% |
47 |
|
%%% Register commands |
48 |
|
%%% |
49 |
|
|
50 |
|
-spec commands() -> [ejabberd_commands:cmd(), ...]. |
51 |
|
commands() -> |
52 |
166 |
Vcard1FieldsString = "Some vcard field names in get/set_vcard are:\n" |
53 |
|
" FN - Full Name\n" |
54 |
|
" NICKNAME - Nickname\n" |
55 |
|
" BDAY - Birthday\n" |
56 |
|
" TITLE - Work: Position\n" |
57 |
|
" ROLE - Work: Role", |
58 |
|
|
59 |
166 |
Vcard2FieldsString = "Some vcard field names and subnames in get/set_vcard2 are:\n" |
60 |
|
" N FAMILY - Family name\n" |
61 |
|
" N GIVEN - Given name\n" |
62 |
|
" N MIDDLE - Middle name\n" |
63 |
|
" ADR CTRY - Address: Country\n" |
64 |
|
" ADR LOCALITY - Address: City\n" |
65 |
|
" EMAIL USERID - E-Mail Address\n" |
66 |
|
" ORG ORGNAME - Work: Company\n" |
67 |
|
" ORG ORGUNIT - Work: Department", |
68 |
|
|
69 |
166 |
VcardXEP = "For a full list of vCard fields check XEP-0054: vcard-temp at " |
70 |
|
"http://www.xmpp.org/extensions/xep-0054.html", |
71 |
|
|
72 |
166 |
[ |
73 |
|
#ejabberd_commands{name = get_vcard, tags = [vcard], |
74 |
|
desc = "Get content from a vCard field", |
75 |
|
longdesc = Vcard1FieldsString ++ "\n" ++ Vcard2FieldsString ++ "\n\n" |
76 |
|
++ VcardXEP, |
77 |
|
module = ?MODULE, function = get_vcard, |
78 |
|
args = [{user, binary}, {host, binary}, {name, binary}], |
79 |
|
result = {content, binary}}, |
80 |
|
#ejabberd_commands{name = get_vcard2, tags = [vcard], |
81 |
|
desc = "Get content from a vCard field", |
82 |
|
longdesc = Vcard2FieldsString ++ "\n\n" ++ Vcard1FieldsString ++ "\n" |
83 |
|
++ VcardXEP, |
84 |
|
module = ?MODULE, function = get_vcard, |
85 |
|
args = [{user, binary}, {host, binary}, |
86 |
|
{name, binary}, {subname, binary}], |
87 |
|
result = {content, binary}}, |
88 |
|
#ejabberd_commands{name = get_vcard2_multi, tags = [vcard], |
89 |
|
desc = "Get multiple contents from a vCard field", |
90 |
|
longdesc = Vcard2FieldsString ++ "\n\n" ++ Vcard1FieldsString ++ "\n" |
91 |
|
++ VcardXEP, |
92 |
|
module = ?MODULE, function = get_vcard, |
93 |
|
args = [{user, binary}, {host, binary}, |
94 |
|
{name, binary}, {subname, binary}], |
95 |
|
result = {contents, {list, {value, binary}}}}, |
96 |
|
#ejabberd_commands{name = set_vcard, tags = [vcard], |
97 |
|
desc = "Set content in a vCard field", |
98 |
|
longdesc = Vcard1FieldsString ++ "\n" ++ Vcard2FieldsString ++ "\n\n" |
99 |
|
++ VcardXEP, |
100 |
|
module = ?MODULE, function = set_vcard, |
101 |
|
args = [{user, binary}, {host, binary}, |
102 |
|
{name, binary}, {content, binary}], |
103 |
|
result = {res, restuple}}, |
104 |
|
#ejabberd_commands{name = set_vcard2, tags = [vcard], |
105 |
|
desc = "Set content in a vCard subfield", |
106 |
|
longdesc = Vcard2FieldsString ++ "\n\n" ++ Vcard1FieldsString ++ "\n" |
107 |
|
++ VcardXEP, |
108 |
|
module = ?MODULE, function = set_vcard, |
109 |
|
args = [{user, binary}, {host, binary}, {name, binary}, |
110 |
|
{subname, binary}, {content, binary}], |
111 |
|
result = {res, restuple}}, |
112 |
|
#ejabberd_commands{name = set_vcard2_multi, tags = [vcard], |
113 |
|
desc = "Set multiple contents in a vCard subfield", |
114 |
|
longdesc = Vcard2FieldsString ++ "\n\n" ++ Vcard1FieldsString ++ "\n" |
115 |
|
++ VcardXEP, |
116 |
|
module = ?MODULE, function = set_vcard, |
117 |
|
args = [{user, binary}, {host, binary}, {name, binary}, |
118 |
|
{subname, binary}, {contents, {list, binary}}], |
119 |
|
result = {res, restuple}} |
120 |
|
]. |
121 |
|
|
122 |
|
%%% |
123 |
|
%%% Vcard |
124 |
|
%%% |
125 |
|
-spec get_vcard(jid:user(), jid:server(), any()) |
126 |
|
-> {error, string()} | [binary()]. |
127 |
|
get_vcard(User, Host, Name) -> |
128 |
2 |
JID = jid:make(User, Host, <<>>), |
129 |
2 |
case ejabberd_auth:does_user_exist(JID) of |
130 |
|
true -> |
131 |
2 |
get_vcard_content(JID, [Name]); |
132 |
|
false -> |
133 |
:-( |
{error, io_lib:format("User ~s@~s does not exist", [User, Host])} |
134 |
|
end. |
135 |
|
|
136 |
|
-spec get_vcard(jid:user(), jid:server(), any(), any()) |
137 |
|
-> {error, string()} | [binary()]. |
138 |
|
get_vcard(User, Host, Name, Subname) -> |
139 |
4 |
JID = jid:make(User, Host, <<>>), |
140 |
4 |
case ejabberd_auth:does_user_exist(JID) of |
141 |
|
true -> |
142 |
4 |
get_vcard_content(JID, [Name, Subname]); |
143 |
|
false -> |
144 |
:-( |
{error, io_lib:format("User ~s@~s does not exist", [User, Host])} |
145 |
|
end. |
146 |
|
|
147 |
|
-spec set_vcard(jid:user(), jid:server(), [binary()], |
148 |
|
binary() | [binary()]) -> {ok, string()} | {user_does_not_exist, string()}. |
149 |
|
set_vcard(User, Host, Name, SomeContent) -> |
150 |
1 |
JID = jid:make(User, Host, <<>>), |
151 |
1 |
case ejabberd_auth:does_user_exist(JID) of |
152 |
|
true -> |
153 |
1 |
set_vcard_content(JID, [Name], SomeContent); |
154 |
|
false -> |
155 |
:-( |
{user_does_not_exist, io_lib:format("User ~s@~s does not exist", [User, Host])} |
156 |
|
end. |
157 |
|
|
158 |
|
-spec set_vcard(jid:user(), jid:server(), [binary()], [binary()], |
159 |
|
binary() | [binary()]) -> {ok, string()} | {user_does_not_exist, string()}. |
160 |
|
set_vcard(User, Host, Name, Subname, SomeContent) -> |
161 |
2 |
JID = jid:make(User, Host, <<>>), |
162 |
2 |
case ejabberd_auth:does_user_exist(JID) of |
163 |
|
true -> |
164 |
2 |
set_vcard_content(JID, [Name, Subname], SomeContent); |
165 |
|
false -> |
166 |
:-( |
{user_does_not_exist, io_lib:format("User ~s@~s does not exist", [User, Host])} |
167 |
|
end. |
168 |
|
|
169 |
|
|
170 |
|
%% |
171 |
|
%% Internal vcard |
172 |
|
|
173 |
|
-spec get_vcard_content(jid:jid(), any()) -> |
174 |
|
{error, string()} | list(binary()). |
175 |
|
get_vcard_content(#jid{lserver = LServer} = NoResJID, Data) -> |
176 |
6 |
JID = jid:replace_resource(NoResJID, atom_to_binary(?MODULE)), |
177 |
6 |
IQ = #iq{type = get, xmlns = ?NS_VCARD, sub_el = []}, |
178 |
6 |
{ok, HostType} = mongoose_domain_api:get_domain_host_type(LServer), |
179 |
6 |
Acc = mongoose_acc:new(#{ location => ?LOCATION, |
180 |
|
from_jid => JID, |
181 |
|
to_jid => JID, |
182 |
|
lserver => JID#jid.lserver, |
183 |
|
host_type => HostType, |
184 |
|
element => jlib:iq_to_xml(IQ) }), |
185 |
6 |
Extra = #{}, |
186 |
6 |
{_, IQr} = mod_vcard:process_sm_iq(Acc, JID, JID, IQ, Extra), |
187 |
6 |
case IQr#iq.sub_el of |
188 |
|
[#xmlel{} = A1] -> |
189 |
5 |
case get_vcard(Data, A1) of |
190 |
|
[] -> |
191 |
2 |
{error, "Value not found in vcard"}; |
192 |
|
ElemList -> |
193 |
3 |
[exml_query:cdata(Elem) || Elem <- ElemList] |
194 |
|
end; |
195 |
|
_ -> |
196 |
1 |
{error, "Vcard not found"} |
197 |
|
end. |
198 |
|
|
199 |
|
|
200 |
|
-spec get_vcard([binary()], exml:element()) -> [exml:element()]. |
201 |
|
get_vcard([Data1, Data2], A1) -> |
202 |
4 |
A2List = exml_query:subelements(A1, Data1), |
203 |
4 |
lists:flatten([get_vcard([Data2], A2) || A2 <- A2List]); |
204 |
|
get_vcard([Data], A1) -> |
205 |
4 |
exml_query:subelements(A1, Data). |
206 |
|
|
207 |
|
-spec set_vcard_content(jid:jid(), Data :: [binary()], |
208 |
|
ContentList :: binary() | [binary()]) -> {ok, string()}. |
209 |
|
set_vcard_content(JID, D, SomeContent) when is_binary(SomeContent) -> |
210 |
2 |
set_vcard_content(JID, D, [SomeContent]); |
211 |
|
set_vcard_content(JID, Data, ContentList) -> |
212 |
3 |
IQ = #iq{type = get, xmlns = ?NS_VCARD, sub_el = []}, |
213 |
3 |
{ok, HostType} = mongoose_domain_api:get_domain_host_type(JID#jid.lserver), |
214 |
3 |
Acc = mongoose_acc:new(#{ location => ?LOCATION, |
215 |
|
from_jid => JID, |
216 |
|
to_jid => JID, |
217 |
|
lserver => JID#jid.lserver, |
218 |
|
host_type => HostType, |
219 |
|
element => jlib:iq_to_xml(IQ) }), |
220 |
|
|
221 |
3 |
Extra = #{}, |
222 |
3 |
{Acc1, IQr} = mod_vcard:process_sm_iq(Acc, JID, JID, IQ, Extra), |
223 |
|
|
224 |
|
%% Get old vcard |
225 |
3 |
A4 = case IQr#iq.sub_el of |
226 |
|
[A1] -> |
227 |
2 |
{_, _, _, A2} = A1, |
228 |
2 |
update_vcard_els(Data, ContentList, A2); |
229 |
|
_ -> |
230 |
1 |
update_vcard_els(Data, ContentList, []) |
231 |
|
end, |
232 |
|
|
233 |
|
%% Build new vcard |
234 |
3 |
SubEl = #xmlel{name = <<"vCard">>, attrs = [{<<"xmlns">>, <<"vcard-temp">>}], children = A4}, |
235 |
3 |
IQ2 = #iq{type = set, sub_el = SubEl}, |
236 |
3 |
Extra = #{}, |
237 |
3 |
mod_vcard:process_sm_iq(Acc1, JID, JID, IQ2, Extra), |
238 |
3 |
{ok, ""}. |
239 |
|
|
240 |
|
-spec update_vcard_els(Data :: [binary(), ...], |
241 |
|
ContentList :: [binary() | string()], |
242 |
|
Els :: [jlib:xmlcdata() | exml:element()] |
243 |
|
) -> [jlib:xmlcdata() | exml:element()]. |
244 |
|
update_vcard_els(Data, ContentList, Els1) -> |
245 |
3 |
Els2 = lists:keysort(2, Els1), |
246 |
3 |
[Data1 | Data2] = Data, |
247 |
3 |
NewEls = case Data2 of |
248 |
|
[] -> |
249 |
1 |
[#xmlel{ name = Data1, children = [#xmlcdata{content = Content}] } |
250 |
1 |
|| Content <- ContentList]; |
251 |
|
[D2] -> |
252 |
2 |
OldEl = case lists:keysearch(Data1, 2, Els2) of |
253 |
1 |
{value, A} -> A; |
254 |
1 |
false -> #xmlel{ name = Data1 } |
255 |
|
end, |
256 |
2 |
ContentOld1 = OldEl#xmlel.children, |
257 |
2 |
Content2 = [#xmlel{ name = D2, children = [#xmlcdata{content=Content}]} |
258 |
2 |
|| Content <- ContentList], |
259 |
2 |
ContentOld2 = [A || {_, X, _, _} = A <- ContentOld1, X/=D2], |
260 |
2 |
ContentOld3 = lists:keysort(2, ContentOld2), |
261 |
2 |
ContentNew = lists:keymerge(2, Content2, ContentOld3), |
262 |
2 |
[#xmlel{ name = Data1, children = ContentNew}] |
263 |
|
end, |
264 |
3 |
Els3 = lists:keydelete(Data1, 2, Els2), |
265 |
3 |
lists:keymerge(2, NewEls, Els3). |