]> nmode's Git Repositories - signal-cli/blob - src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java
Send remote delete (#593)
[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 sendRemoteDeleteMessage(
108 final long targetSentTimestamp, final String recipient
109 ) {
110 var recipients = new ArrayList<String>(1);
111 recipients.add(recipient);
112 return sendRemoteDeleteMessage(targetSentTimestamp, recipients);
113 }
114
115 @Override
116 public long sendRemoteDeleteMessage(
117 final long targetSentTimestamp, final List<String> recipients
118 ) {
119 try {
120 final var results = m.sendRemoteDeleteMessage(targetSentTimestamp, recipients);
121 checkSendMessageResults(results.first(), results.second());
122 return results.first();
123 } catch (IOException e) {
124 throw new Error.Failure(e.getMessage());
125 } catch (InvalidNumberException e) {
126 throw new Error.InvalidNumber(e.getMessage());
127 }
128 }
129
130 @Override
131 public long sendGroupRemoteDeleteMessage(
132 final long targetSentTimestamp, final byte[] groupId
133 ) {
134 try {
135 final var results = m.sendGroupRemoteDeleteMessage(targetSentTimestamp, GroupId.unknownVersion(groupId));
136 checkSendMessageResults(results.first(), results.second());
137 return results.first();
138 } catch (IOException e) {
139 throw new Error.Failure(e.getMessage());
140 } catch (GroupNotFoundException | NotAGroupMemberException e) {
141 throw new Error.GroupNotFound(e.getMessage());
142 }
143 }
144
145 @Override
146 public long sendMessageReaction(
147 final String emoji, final boolean remove, final String targetAuthor, final long targetSentTimestamp, final String recipient
148 ) {
149 var recipients = new ArrayList<String>(1);
150 recipients.add(recipient);
151 return sendMessageReaction(emoji, remove, targetAuthor, targetSentTimestamp, recipients);
152 }
153
154 @Override
155 public long sendMessageReaction(
156 final String emoji, final boolean remove, final String targetAuthor, final long targetSentTimestamp, final List<String> recipients
157 ) {
158 try {
159 final var results = m.sendMessageReaction(emoji, remove, targetAuthor, targetSentTimestamp, recipients);
160 checkSendMessageResults(results.first(), results.second());
161 return results.first();
162 } catch (InvalidNumberException e) {
163 throw new Error.InvalidNumber(e.getMessage());
164 } catch (IOException e) {
165 throw new Error.Failure(e.getMessage());
166 }
167 }
168
169 @Override
170 public long sendNoteToSelfMessage(
171 final String message, final List<String> attachments
172 ) throws Error.AttachmentInvalid, Error.Failure, Error.UntrustedIdentity {
173 try {
174 final var results = m.sendSelfMessage(message, attachments);
175 checkSendMessageResult(results.first(), results.second());
176 return results.first();
177 } catch (AttachmentInvalidException e) {
178 throw new Error.AttachmentInvalid(e.getMessage());
179 } catch (IOException e) {
180 throw new Error.Failure(e.getMessage());
181 }
182 }
183
184 @Override
185 public void sendEndSessionMessage(final List<String> recipients) {
186 try {
187 final var results = m.sendEndSessionMessage(recipients);
188 checkSendMessageResults(results.first(), results.second());
189 } catch (IOException e) {
190 throw new Error.Failure(e.getMessage());
191 } catch (InvalidNumberException e) {
192 throw new Error.InvalidNumber(e.getMessage());
193 }
194 }
195
196 @Override
197 public long sendGroupMessage(final String message, final List<String> attachments, final byte[] groupId) {
198 try {
199 var results = m.sendGroupMessage(message, attachments, GroupId.unknownVersion(groupId));
200 checkSendMessageResults(results.first(), results.second());
201 return results.first();
202 } catch (IOException e) {
203 throw new Error.Failure(e.getMessage());
204 } catch (GroupNotFoundException | NotAGroupMemberException e) {
205 throw new Error.GroupNotFound(e.getMessage());
206 } catch (AttachmentInvalidException e) {
207 throw new Error.AttachmentInvalid(e.getMessage());
208 }
209 }
210
211 @Override
212 public long sendGroupMessageReaction(
213 final String emoji, final boolean remove, final String targetAuthor, final long targetSentTimestamp, final byte[] groupId
214 ) {
215 try {
216 final var results = m.sendGroupMessageReaction(emoji, remove, targetAuthor, targetSentTimestamp, GroupId.unknownVersion(groupId));
217 checkSendMessageResults(results.first(), results.second());
218 return results.first();
219 } catch (IOException e) {
220 throw new Error.Failure(e.getMessage());
221 } catch (InvalidNumberException e) {
222 throw new Error.InvalidNumber(e.getMessage());
223 } catch (GroupNotFoundException | NotAGroupMemberException e) {
224 throw new Error.GroupNotFound(e.getMessage());
225 }
226 }
227
228 // Since contact names might be empty if not defined, also potentially return
229 // the profile name
230 @Override
231 public String getContactName(final String number) {
232 try {
233 return m.getContactOrProfileName(number);
234 } catch (Exception e) {
235 throw new Error.InvalidNumber(e.getMessage());
236 }
237 }
238
239 @Override
240 public void setContactName(final String number, final String name) {
241 try {
242 m.setContactName(number, name);
243 } catch (InvalidNumberException e) {
244 throw new Error.InvalidNumber(e.getMessage());
245 }
246 }
247
248 @Override
249 public void setContactBlocked(final String number, final boolean blocked) {
250 try {
251 m.setContactBlocked(number, blocked);
252 } catch (InvalidNumberException e) {
253 throw new Error.InvalidNumber(e.getMessage());
254 }
255 }
256
257 @Override
258 public void setGroupBlocked(final byte[] groupId, final boolean blocked) {
259 try {
260 m.setGroupBlocked(GroupId.unknownVersion(groupId), blocked);
261 } catch (GroupNotFoundException e) {
262 throw new Error.GroupNotFound(e.getMessage());
263 }
264 }
265
266 @Override
267 public List<byte[]> getGroupIds() {
268 var groups = m.getGroups();
269 var ids = new ArrayList<byte[]>(groups.size());
270 for (var group : groups) {
271 ids.add(group.getGroupId().serialize());
272 }
273 return ids;
274 }
275
276 @Override
277 public String getGroupName(final byte[] groupId) {
278 var group = m.getGroup(GroupId.unknownVersion(groupId));
279 if (group == null) {
280 return "";
281 } else {
282 return group.getTitle();
283 }
284 }
285
286 @Override
287 public List<String> getGroupMembers(final byte[] groupId) {
288 var group = m.getGroup(GroupId.unknownVersion(groupId));
289 if (group == null) {
290 return List.of();
291 } else {
292 return group.getMembers()
293 .stream()
294 .map(m::resolveSignalServiceAddress)
295 .map(SignalServiceAddress::getLegacyIdentifier)
296 .collect(Collectors.toList());
297 }
298 }
299
300 @Override
301 public byte[] updateGroup(byte[] groupId, String name, List<String> members, String avatar) {
302 try {
303 if (groupId.length == 0) {
304 groupId = null;
305 }
306 if (name.isEmpty()) {
307 name = null;
308 }
309 if (members.isEmpty()) {
310 members = null;
311 }
312 if (avatar.isEmpty()) {
313 avatar = null;
314 }
315 final var results = m.updateGroup(groupId == null ? null : GroupId.unknownVersion(groupId),
316 name,
317 members,
318 avatar == null ? null : new File(avatar));
319 checkSendMessageResults(0, results.second());
320 return results.first().serialize();
321 } catch (IOException e) {
322 throw new Error.Failure(e.getMessage());
323 } catch (GroupNotFoundException | NotAGroupMemberException e) {
324 throw new Error.GroupNotFound(e.getMessage());
325 } catch (InvalidNumberException e) {
326 throw new Error.InvalidNumber(e.getMessage());
327 } catch (AttachmentInvalidException e) {
328 throw new Error.AttachmentInvalid(e.getMessage());
329 }
330 }
331
332 @Override
333 public boolean isRegistered() {
334 return true;
335 }
336
337 @Override
338 public void updateProfile(
339 final String name,
340 final String about,
341 final String aboutEmoji,
342 String avatarPath,
343 final boolean removeAvatar
344 ) {
345 try {
346 if (avatarPath.isEmpty()) {
347 avatarPath = null;
348 }
349 Optional<File> avatarFile = removeAvatar
350 ? Optional.absent()
351 : avatarPath == null ? null : Optional.of(new File(avatarPath));
352 m.setProfile(name, about, aboutEmoji, avatarFile);
353 } catch (IOException e) {
354 throw new Error.Failure(e.getMessage());
355 }
356 }
357
358 // Provide option to query a version string in order to react on potential
359 // future interface changes
360 @Override
361 public String version() {
362 return BaseConfig.PROJECT_VERSION;
363 }
364
365 // Create a unique list of Numbers from Identities and Contacts to really get
366 // all numbers the system knows
367 @Override
368 public List<String> listNumbers() {
369 return Stream.concat(m.getIdentities().stream().map(i -> i.getAddress().getNumber().orNull()),
370 m.getContacts().stream().map(c -> c.number))
371 .filter(Objects::nonNull)
372 .distinct()
373 .collect(Collectors.toList());
374 }
375
376 @Override
377 public List<String> getContactNumber(final String name) {
378 // Contact names have precedence.
379 var numbers = new ArrayList<String>();
380 var contacts = m.getContacts();
381 for (var c : contacts) {
382 if (c.name != null && c.name.equals(name)) {
383 numbers.add(c.number);
384 }
385 }
386 // Try profiles if no contact name was found
387 for (var identity : m.getIdentities()) {
388 final var address = identity.getAddress();
389 var number = address.getNumber().orNull();
390 if (number != null) {
391 var profile = m.getRecipientProfile(address);
392 if (profile != null && profile.getDisplayName().equals(name)) {
393 numbers.add(number);
394 }
395 }
396 }
397 return numbers;
398 }
399
400 @Override
401 public void quitGroup(final byte[] groupId) {
402 var group = GroupId.unknownVersion(groupId);
403 try {
404 m.sendQuitGroupMessage(group);
405 } catch (GroupNotFoundException | NotAGroupMemberException e) {
406 throw new Error.GroupNotFound(e.getMessage());
407 } catch (IOException e) {
408 throw new Error.Failure(e.getMessage());
409 }
410 }
411
412 @Override
413 public void joinGroup(final String groupLink) {
414 try {
415 final var linkUrl = GroupInviteLinkUrl.fromUri(groupLink);
416 if (linkUrl == null) {
417 throw new Error.Failure("Group link is invalid:");
418 }
419 m.joinGroup(linkUrl);
420 } catch (GroupInviteLinkUrl.InvalidGroupLinkException | GroupLinkNotActiveException e) {
421 throw new Error.Failure("Group link is invalid: " + e.getMessage());
422 } catch (GroupInviteLinkUrl.UnknownGroupLinkVersionException e) {
423 throw new Error.Failure("Group link was created with an incompatible version: " + e.getMessage());
424 } catch (IOException e) {
425 throw new Error.Failure(e.getMessage());
426 }
427 }
428
429 @Override
430 public boolean isContactBlocked(final String number) {
431 var contacts = m.getContacts();
432 for (var c : contacts) {
433 if (c.number.equals(number)) {
434 return c.blocked;
435 }
436 }
437 return false;
438 }
439
440 @Override
441 public boolean isGroupBlocked(final byte[] groupId) {
442 var group = m.getGroup(GroupId.unknownVersion(groupId));
443 if (group == null) {
444 return false;
445 } else {
446 return group.isBlocked();
447 }
448 }
449
450 @Override
451 public boolean isMember(final byte[] groupId) {
452 var group = m.getGroup(GroupId.unknownVersion(groupId));
453 if (group == null) {
454 return false;
455 } else {
456 return group.isMember(m.getSelfAddress());
457 }
458 }
459 }