]> nmode's Git Repositories - signal-cli/blob - src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java
some small dbus return code fixes (#556)
[signal-cli] / src / main / java / org / asamk / signal / dbus / DbusSignalImpl.java
1 package org.asamk.signal.dbus;
2
3 import org.asamk.Signal;
4 import org.asamk.signal.BaseConfig;
5 import org.asamk.signal.manager.AttachmentInvalidException;
6 import org.asamk.signal.manager.Manager;
7 import org.asamk.signal.manager.groups.GroupId;
8 import org.asamk.signal.manager.groups.GroupInviteLinkUrl;
9 import org.asamk.signal.manager.groups.GroupNotFoundException;
10 import org.asamk.signal.manager.groups.NotAGroupMemberException;
11 import org.asamk.signal.util.ErrorUtils;
12 import org.freedesktop.dbus.exceptions.DBusExecutionException;
13 import org.whispersystems.libsignal.util.guava.Optional;
14 import org.whispersystems.signalservice.api.groupsv2.GroupLinkNotActiveException;
15 import org.whispersystems.signalservice.api.messages.SendMessageResult;
16 import org.whispersystems.signalservice.api.push.SignalServiceAddress;
17 import org.whispersystems.signalservice.api.util.InvalidNumberException;
18
19 import java.io.File;
20 import java.io.IOException;
21 import java.util.ArrayList;
22 import java.util.List;
23 import java.util.Objects;
24 import java.util.stream.Collectors;
25 import java.util.stream.Stream;
26
27 public class DbusSignalImpl implements Signal {
28
29 private final Manager m;
30
31 public DbusSignalImpl(final Manager m) {
32 this.m = m;
33 }
34
35 @Override
36 public boolean isRemote() {
37 return false;
38 }
39
40 @Override
41 public String getObjectPath() {
42 return null;
43 }
44
45 @Override
46 public long sendMessage(final String message, final List<String> attachments, final String recipient) {
47 var recipients = new ArrayList<String>(1);
48 recipients.add(recipient);
49 return sendMessage(message, attachments, recipients);
50 }
51
52 private static void checkSendMessageResult(long timestamp, SendMessageResult result) throws DBusExecutionException {
53 var error = ErrorUtils.getErrorMessageFromSendMessageResult(result);
54
55 if (error == null) {
56 return;
57 }
58
59 final var message = timestamp + "\nFailed to send message:\n" + error + '\n';
60
61 if (result.getIdentityFailure() != null) {
62 throw new Error.UntrustedIdentity(message);
63 } else {
64 throw new Error.Failure(message);
65 }
66 }
67
68 private static void checkSendMessageResults(
69 long timestamp, List<SendMessageResult> results
70 ) throws DBusExecutionException {
71 if (results.size() == 1) {
72 checkSendMessageResult(timestamp, results.get(0));
73 return;
74 }
75
76 var errors = ErrorUtils.getErrorMessagesFromSendMessageResults(results);
77 if (errors.size() == 0) {
78 return;
79 }
80
81 var message = new StringBuilder();
82 message.append(timestamp).append('\n');
83 message.append("Failed to send (some) messages:\n");
84 for (var error : errors) {
85 message.append(error).append('\n');
86 }
87
88 throw new Error.Failure(message.toString());
89 }
90
91 @Override
92 public long sendMessage(final String message, final List<String> attachments, final List<String> recipients) {
93 try {
94 final var results = m.sendMessage(message, attachments, recipients);
95 checkSendMessageResults(results.first(), results.second());
96 return results.first();
97 } catch (InvalidNumberException e) {
98 throw new Error.InvalidNumber(e.getMessage());
99 } catch (AttachmentInvalidException e) {
100 throw new Error.AttachmentInvalid(e.getMessage());
101 } catch (IOException e) {
102 throw new Error.Failure(e.getMessage());
103 }
104 }
105
106 @Override
107 public long sendNoteToSelfMessage(
108 final String message, final List<String> attachments
109 ) throws Error.AttachmentInvalid, Error.Failure, Error.UntrustedIdentity {
110 try {
111 final var results = m.sendSelfMessage(message, attachments);
112 checkSendMessageResult(results.first(), results.second());
113 return results.first();
114 } catch (AttachmentInvalidException e) {
115 throw new Error.AttachmentInvalid(e.getMessage());
116 } catch (IOException e) {
117 throw new Error.Failure(e.getMessage());
118 }
119 }
120
121 @Override
122 public void sendEndSessionMessage(final List<String> recipients) {
123 try {
124 final var results = m.sendEndSessionMessage(recipients);
125 checkSendMessageResults(results.first(), results.second());
126 } catch (IOException e) {
127 throw new Error.Failure(e.getMessage());
128 } catch (InvalidNumberException e) {
129 throw new Error.InvalidNumber(e.getMessage());
130 }
131 }
132
133 @Override
134 public long sendGroupMessage(final String message, final List<String> attachments, final byte[] groupId) {
135 try {
136 var results = m.sendGroupMessage(message, attachments, GroupId.unknownVersion(groupId));
137 checkSendMessageResults(results.first(), results.second());
138 return results.first();
139 } catch (IOException e) {
140 throw new Error.Failure(e.getMessage());
141 } catch (GroupNotFoundException | NotAGroupMemberException e) {
142 throw new Error.GroupNotFound(e.getMessage());
143 } catch (AttachmentInvalidException e) {
144 throw new Error.AttachmentInvalid(e.getMessage());
145 }
146 }
147
148 // Since contact names might be empty if not defined, also potentially return
149 // the profile name
150 @Override
151 public String getContactName(final String number) {
152 try {
153 return m.getContactOrProfileName(number);
154 } catch (Exception e) {
155 throw new Error.InvalidNumber(e.getMessage());
156 }
157 }
158
159 @Override
160 public void setContactName(final String number, final String name) {
161 try {
162 m.setContactName(number, name);
163 } catch (InvalidNumberException e) {
164 throw new Error.InvalidNumber(e.getMessage());
165 }
166 }
167
168 @Override
169 public void setContactBlocked(final String number, final boolean blocked) {
170 try {
171 m.setContactBlocked(number, blocked);
172 } catch (InvalidNumberException e) {
173 throw new Error.InvalidNumber(e.getMessage());
174 }
175 }
176
177 @Override
178 public void setGroupBlocked(final byte[] groupId, final boolean blocked) {
179 try {
180 m.setGroupBlocked(GroupId.unknownVersion(groupId), blocked);
181 } catch (GroupNotFoundException e) {
182 throw new Error.GroupNotFound(e.getMessage());
183 }
184 }
185
186 @Override
187 public List<byte[]> getGroupIds() {
188 var groups = m.getGroups();
189 var ids = new ArrayList<byte[]>(groups.size());
190 for (var group : groups) {
191 ids.add(group.getGroupId().serialize());
192 }
193 return ids;
194 }
195
196 @Override
197 public String getGroupName(final byte[] groupId) {
198 var group = m.getGroup(GroupId.unknownVersion(groupId));
199 if (group == null) {
200 return "";
201 } else {
202 return group.getTitle();
203 }
204 }
205
206 @Override
207 public List<String> getGroupMembers(final byte[] groupId) {
208 var group = m.getGroup(GroupId.unknownVersion(groupId));
209 if (group == null) {
210 return List.of();
211 } else {
212 return group.getMembers()
213 .stream()
214 .map(m::resolveSignalServiceAddress)
215 .map(SignalServiceAddress::getLegacyIdentifier)
216 .collect(Collectors.toList());
217 }
218 }
219
220 @Override
221 public byte[] updateGroup(byte[] groupId, String name, List<String> members, String avatar) {
222 try {
223 if (groupId.length == 0) {
224 groupId = null;
225 }
226 if (name.isEmpty()) {
227 name = null;
228 }
229 if (members.isEmpty()) {
230 members = null;
231 }
232 if (avatar.isEmpty()) {
233 avatar = null;
234 }
235 final var results = m.updateGroup(groupId == null ? null : GroupId.unknownVersion(groupId),
236 name,
237 members,
238 avatar == null ? null : new File(avatar));
239 checkSendMessageResults(0, results.second());
240 return results.first().serialize();
241 } catch (IOException e) {
242 throw new Error.Failure(e.getMessage());
243 } catch (GroupNotFoundException | NotAGroupMemberException e) {
244 throw new Error.GroupNotFound(e.getMessage());
245 } catch (InvalidNumberException e) {
246 throw new Error.InvalidNumber(e.getMessage());
247 } catch (AttachmentInvalidException e) {
248 throw new Error.AttachmentInvalid(e.getMessage());
249 }
250 }
251
252 @Override
253 public boolean isRegistered() {
254 return true;
255 }
256
257 @Override
258 public void updateProfile(
259 final String name,
260 final String about,
261 final String aboutEmoji,
262 String avatarPath,
263 final boolean removeAvatar
264 ) {
265 try {
266 if (avatarPath.isEmpty()) {
267 avatarPath = null;
268 }
269 Optional<File> avatarFile = removeAvatar
270 ? Optional.absent()
271 : avatarPath == null ? null : Optional.of(new File(avatarPath));
272 m.setProfile(name, about, aboutEmoji, avatarFile);
273 } catch (IOException e) {
274 throw new Error.Failure(e.getMessage());
275 }
276 }
277
278 // Provide option to query a version string in order to react on potential
279 // future interface changes
280 @Override
281 public String version() {
282 return BaseConfig.PROJECT_VERSION;
283 }
284
285 // Create a unique list of Numbers from Identities and Contacts to really get
286 // all numbers the system knows
287 @Override
288 public List<String> listNumbers() {
289 return Stream.concat(m.getIdentities().stream().map(i -> i.getAddress().getNumber().orNull()),
290 m.getContacts().stream().map(c -> c.number))
291 .filter(Objects::nonNull)
292 .distinct()
293 .collect(Collectors.toList());
294 }
295
296 @Override
297 public List<String> getContactNumber(final String name) {
298 // Contact names have precedence.
299 var numbers = new ArrayList<String>();
300 var contacts = m.getContacts();
301 for (var c : contacts) {
302 if (c.name != null && c.name.equals(name)) {
303 numbers.add(c.number);
304 }
305 }
306 // Try profiles if no contact name was found
307 for (var identity : m.getIdentities()) {
308 final var address = identity.getAddress();
309 var number = address.getNumber().orNull();
310 if (number != null) {
311 var profile = m.getRecipientProfile(address);
312 if (profile != null && profile.getDisplayName().equals(name)) {
313 numbers.add(number);
314 }
315 }
316 }
317 return numbers;
318 }
319
320 @Override
321 public void quitGroup(final byte[] groupId) {
322 var group = GroupId.unknownVersion(groupId);
323 try {
324 m.sendQuitGroupMessage(group);
325 } catch (GroupNotFoundException | NotAGroupMemberException e) {
326 throw new Error.GroupNotFound(e.getMessage());
327 } catch (IOException e) {
328 throw new Error.Failure(e.getMessage());
329 }
330 }
331
332 @Override
333 public void joinGroup(final String groupLink) {
334 try {
335 final var linkUrl = GroupInviteLinkUrl.fromUri(groupLink);
336 if (linkUrl == null) {
337 throw new Error.Failure("Group link is invalid:");
338 }
339 m.joinGroup(linkUrl);
340 } catch (GroupInviteLinkUrl.InvalidGroupLinkException | GroupLinkNotActiveException e) {
341 throw new Error.Failure("Group link is invalid: " + e.getMessage());
342 } catch (GroupInviteLinkUrl.UnknownGroupLinkVersionException e) {
343 throw new Error.Failure("Group link was created with an incompatible version: " + e.getMessage());
344 } catch (IOException e) {
345 throw new Error.Failure(e.getMessage());
346 }
347 }
348
349 @Override
350 public boolean isContactBlocked(final String number) {
351 var contacts = m.getContacts();
352 for (var c : contacts) {
353 if (c.number.equals(number)) {
354 return c.blocked;
355 }
356 }
357 return false;
358 }
359
360 @Override
361 public boolean isGroupBlocked(final byte[] groupId) {
362 var group = m.getGroup(GroupId.unknownVersion(groupId));
363 if (group == null) {
364 return false;
365 } else {
366 return group.isBlocked();
367 }
368 }
369
370 @Override
371 public boolean isMember(final byte[] groupId) {
372 var group = m.getGroup(GroupId.unknownVersion(groupId));
373 if (group == null) {
374 return false;
375 } else {
376 return group.isMember(m.getSelfAddress());
377 }
378 }
379 }