1 package org
.asamk
.signal
.dbus
;
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
.NotMasterDeviceException
;
8 import org
.asamk
.signal
.manager
.groups
.GroupId
;
9 import org
.asamk
.signal
.manager
.groups
.GroupInviteLinkUrl
;
10 import org
.asamk
.signal
.manager
.groups
.GroupNotFoundException
;
11 import org
.asamk
.signal
.manager
.groups
.LastGroupAdminException
;
12 import org
.asamk
.signal
.manager
.groups
.NotAGroupMemberException
;
13 import org
.asamk
.signal
.manager
.storage
.identities
.IdentityInfo
;
14 import org
.asamk
.signal
.util
.ErrorUtils
;
15 import org
.asamk
.signal
.util
.Util
;
16 import org
.freedesktop
.dbus
.exceptions
.DBusExecutionException
;
17 import org
.whispersystems
.libsignal
.util
.Pair
;
18 import org
.whispersystems
.libsignal
.util
.guava
.Optional
;
19 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupLinkNotActiveException
;
20 import org
.whispersystems
.signalservice
.api
.messages
.SendMessageResult
;
21 import org
.whispersystems
.signalservice
.api
.util
.InvalidNumberException
;
24 import java
.io
.IOException
;
25 import java
.util
.ArrayList
;
26 import java
.util
.List
;
27 import java
.util
.Objects
;
29 import java
.util
.stream
.Collectors
;
30 import java
.util
.stream
.Stream
;
32 import static org
.asamk
.signal
.util
.Util
.getLegacyIdentifier
;
34 public class DbusSignalImpl
implements Signal
{
36 private final Manager m
;
38 public DbusSignalImpl(final Manager m
) {
43 public boolean isRemote() {
48 public String
getObjectPath() {
53 public long sendMessage(final String message
, final List
<String
> attachments
, final String recipient
) {
54 var recipients
= new ArrayList
<String
>(1);
55 recipients
.add(recipient
);
56 return sendMessage(message
, attachments
, recipients
);
59 private static void checkSendMessageResult(long timestamp
, SendMessageResult result
) throws DBusExecutionException
{
60 var error
= ErrorUtils
.getErrorMessageFromSendMessageResult(result
);
66 final var message
= timestamp
+ "\nFailed to send message:\n" + error
+ '\n';
68 if (result
.getIdentityFailure() != null) {
69 throw new Error
.UntrustedIdentity(message
);
71 throw new Error
.Failure(message
);
75 private static void checkSendMessageResults(
76 long timestamp
, List
<SendMessageResult
> results
77 ) throws DBusExecutionException
{
78 if (results
.size() == 1) {
79 checkSendMessageResult(timestamp
, results
.get(0));
83 var errors
= ErrorUtils
.getErrorMessagesFromSendMessageResults(results
);
84 if (errors
.size() == 0) {
88 var message
= new StringBuilder();
89 message
.append(timestamp
).append('\n');
90 message
.append("Failed to send (some) messages:\n");
91 for (var error
: errors
) {
92 message
.append(error
).append('\n');
95 throw new Error
.Failure(message
.toString());
99 public long sendMessage(final String message
, final List
<String
> attachments
, final List
<String
> recipients
) {
101 final var results
= m
.sendMessage(message
, attachments
, recipients
);
102 checkSendMessageResults(results
.first(), results
.second());
103 return results
.first();
104 } catch (InvalidNumberException e
) {
105 throw new Error
.InvalidNumber(e
.getMessage());
106 } catch (AttachmentInvalidException e
) {
107 throw new Error
.AttachmentInvalid(e
.getMessage());
108 } catch (IOException e
) {
109 throw new Error
.Failure(e
.getMessage());
114 public long sendRemoteDeleteMessage(
115 final long targetSentTimestamp
, final String recipient
117 var recipients
= new ArrayList
<String
>(1);
118 recipients
.add(recipient
);
119 return sendRemoteDeleteMessage(targetSentTimestamp
, recipients
);
123 public long sendRemoteDeleteMessage(
124 final long targetSentTimestamp
, final List
<String
> recipients
127 final var results
= m
.sendRemoteDeleteMessage(targetSentTimestamp
, recipients
);
128 checkSendMessageResults(results
.first(), results
.second());
129 return results
.first();
130 } catch (IOException e
) {
131 throw new Error
.Failure(e
.getMessage());
132 } catch (InvalidNumberException e
) {
133 throw new Error
.InvalidNumber(e
.getMessage());
138 public long sendGroupRemoteDeleteMessage(
139 final long targetSentTimestamp
, final byte[] groupId
142 final var results
= m
.sendGroupRemoteDeleteMessage(targetSentTimestamp
, GroupId
.unknownVersion(groupId
));
143 checkSendMessageResults(results
.first(), results
.second());
144 return results
.first();
145 } catch (IOException e
) {
146 throw new Error
.Failure(e
.getMessage());
147 } catch (GroupNotFoundException
| NotAGroupMemberException e
) {
148 throw new Error
.GroupNotFound(e
.getMessage());
153 public long sendMessageReaction(
155 final boolean remove
,
156 final String targetAuthor
,
157 final long targetSentTimestamp
,
158 final String recipient
160 var recipients
= new ArrayList
<String
>(1);
161 recipients
.add(recipient
);
162 return sendMessageReaction(emoji
, remove
, targetAuthor
, targetSentTimestamp
, recipients
);
166 public long sendMessageReaction(
168 final boolean remove
,
169 final String targetAuthor
,
170 final long targetSentTimestamp
,
171 final List
<String
> recipients
174 final var results
= m
.sendMessageReaction(emoji
, remove
, targetAuthor
, targetSentTimestamp
, recipients
);
175 checkSendMessageResults(results
.first(), results
.second());
176 return results
.first();
177 } catch (InvalidNumberException e
) {
178 throw new Error
.InvalidNumber(e
.getMessage());
179 } catch (IOException e
) {
180 throw new Error
.Failure(e
.getMessage());
185 public long sendNoteToSelfMessage(
186 final String message
, final List
<String
> attachments
187 ) throws Error
.AttachmentInvalid
, Error
.Failure
, Error
.UntrustedIdentity
{
189 final var results
= m
.sendSelfMessage(message
, attachments
);
190 checkSendMessageResult(results
.first(), results
.second());
191 return results
.first();
192 } catch (AttachmentInvalidException e
) {
193 throw new Error
.AttachmentInvalid(e
.getMessage());
194 } catch (IOException e
) {
195 throw new Error
.Failure(e
.getMessage());
200 public void sendEndSessionMessage(final List
<String
> recipients
) {
202 final var results
= m
.sendEndSessionMessage(recipients
);
203 checkSendMessageResults(results
.first(), results
.second());
204 } catch (IOException e
) {
205 throw new Error
.Failure(e
.getMessage());
206 } catch (InvalidNumberException e
) {
207 throw new Error
.InvalidNumber(e
.getMessage());
212 public long sendGroupMessage(final String message
, final List
<String
> attachments
, final byte[] groupId
) {
214 var results
= m
.sendGroupMessage(message
, attachments
, GroupId
.unknownVersion(groupId
));
215 checkSendMessageResults(results
.first(), results
.second());
216 return results
.first();
217 } catch (IOException e
) {
218 throw new Error
.Failure(e
.getMessage());
219 } catch (GroupNotFoundException
| NotAGroupMemberException e
) {
220 throw new Error
.GroupNotFound(e
.getMessage());
221 } catch (AttachmentInvalidException e
) {
222 throw new Error
.AttachmentInvalid(e
.getMessage());
227 public long sendGroupMessageReaction(
229 final boolean remove
,
230 final String targetAuthor
,
231 final long targetSentTimestamp
,
235 final var results
= m
.sendGroupMessageReaction(emoji
,
239 GroupId
.unknownVersion(groupId
));
240 checkSendMessageResults(results
.first(), results
.second());
241 return results
.first();
242 } catch (IOException e
) {
243 throw new Error
.Failure(e
.getMessage());
244 } catch (InvalidNumberException e
) {
245 throw new Error
.InvalidNumber(e
.getMessage());
246 } catch (GroupNotFoundException
| NotAGroupMemberException e
) {
247 throw new Error
.GroupNotFound(e
.getMessage());
251 // Since contact names might be empty if not defined, also potentially return
254 public String
getContactName(final String number
) {
256 return m
.getContactOrProfileName(number
);
257 } catch (InvalidNumberException e
) {
258 throw new Error
.InvalidNumber(e
.getMessage());
263 public void setContactName(final String number
, final String name
) {
265 m
.setContactName(number
, name
);
266 } catch (InvalidNumberException e
) {
267 throw new Error
.InvalidNumber(e
.getMessage());
268 } catch (NotMasterDeviceException e
) {
269 throw new Error
.Failure("This command doesn't work on linked devices.");
274 public void setContactBlocked(final String number
, final boolean blocked
) {
276 m
.setContactBlocked(number
, blocked
);
277 } catch (InvalidNumberException e
) {
278 throw new Error
.InvalidNumber(e
.getMessage());
279 } catch (NotMasterDeviceException e
) {
280 throw new Error
.Failure("This command doesn't work on linked devices.");
285 public void setGroupBlocked(final byte[] groupId
, final boolean blocked
) {
287 m
.setGroupBlocked(GroupId
.unknownVersion(groupId
), blocked
);
288 } catch (GroupNotFoundException e
) {
289 throw new Error
.GroupNotFound(e
.getMessage());
294 public List
<byte[]> getGroupIds() {
295 var groups
= m
.getGroups();
296 var ids
= new ArrayList
<byte[]>(groups
.size());
297 for (var group
: groups
) {
298 ids
.add(group
.getGroupId().serialize());
304 public String
getGroupName(final byte[] groupId
) {
305 var group
= m
.getGroup(GroupId
.unknownVersion(groupId
));
309 return group
.getTitle();
314 public List
<String
> getGroupMembers(final byte[] groupId
) {
315 var group
= m
.getGroup(GroupId
.unknownVersion(groupId
));
319 return group
.getMembers()
321 .map(m
::resolveSignalServiceAddress
)
322 .map(Util
::getLegacyIdentifier
)
323 .collect(Collectors
.toList());
328 public byte[] updateGroup(byte[] groupId
, String name
, List
<String
> members
, String avatar
) {
330 if (groupId
.length
== 0) {
333 if (name
.isEmpty()) {
336 if (members
.isEmpty()) {
339 if (avatar
.isEmpty()) {
342 if (groupId
== null) {
343 final var results
= m
.createGroup(name
, members
, avatar
== null ?
null : new File(avatar
));
344 checkSendMessageResults(0, results
.second());
345 return results
.first().serialize();
347 final var results
= m
.updateGroup(GroupId
.unknownVersion(groupId
),
358 avatar
== null ?
null : new File(avatar
),
360 if (results
!= null) {
361 checkSendMessageResults(results
.first(), results
.second());
365 } catch (IOException e
) {
366 throw new Error
.Failure(e
.getMessage());
367 } catch (GroupNotFoundException
| NotAGroupMemberException e
) {
368 throw new Error
.GroupNotFound(e
.getMessage());
369 } catch (InvalidNumberException e
) {
370 throw new Error
.InvalidNumber(e
.getMessage());
371 } catch (AttachmentInvalidException e
) {
372 throw new Error
.AttachmentInvalid(e
.getMessage());
377 public boolean isRegistered() {
382 public void updateProfile(
385 final String aboutEmoji
,
387 final boolean removeAvatar
390 if (avatarPath
.isEmpty()) {
393 Optional
<File
> avatarFile
= removeAvatar
395 : avatarPath
== null ?
null : Optional
.of(new File(avatarPath
));
396 m
.setProfile(name
, null, about
, aboutEmoji
, avatarFile
);
397 } catch (IOException e
) {
398 throw new Error
.Failure(e
.getMessage());
402 // Provide option to query a version string in order to react on potential
403 // future interface changes
405 public String
version() {
406 return BaseConfig
.PROJECT_VERSION
;
409 // Create a unique list of Numbers from Identities and Contacts to really get
410 // all numbers the system knows
412 public List
<String
> listNumbers() {
413 return Stream
.concat(m
.getIdentities().stream().map(IdentityInfo
::getRecipientId
),
414 m
.getContacts().stream().map(Pair
::first
))
415 .map(m
::resolveSignalServiceAddress
)
416 .map(a
-> a
.getNumber().orNull())
417 .filter(Objects
::nonNull
)
419 .collect(Collectors
.toList());
423 public List
<String
> getContactNumber(final String name
) {
424 // Contact names have precedence.
425 var numbers
= new ArrayList
<String
>();
426 var contacts
= m
.getContacts();
427 for (var c
: contacts
) {
428 if (name
.equals(c
.second().getName())) {
429 numbers
.add(getLegacyIdentifier(m
.resolveSignalServiceAddress(c
.first())));
432 // Try profiles if no contact name was found
433 for (var identity
: m
.getIdentities()) {
434 final var recipientId
= identity
.getRecipientId();
435 final var address
= m
.resolveSignalServiceAddress(recipientId
);
436 var number
= address
.getNumber().orNull();
437 if (number
!= null) {
438 var profile
= m
.getRecipientProfile(recipientId
);
439 if (profile
!= null && profile
.getDisplayName().equals(name
)) {
448 public void quitGroup(final byte[] groupId
) {
449 var group
= GroupId
.unknownVersion(groupId
);
451 m
.sendQuitGroupMessage(group
, Set
.of());
452 } catch (GroupNotFoundException
| NotAGroupMemberException e
) {
453 throw new Error
.GroupNotFound(e
.getMessage());
454 } catch (IOException
| LastGroupAdminException e
) {
455 throw new Error
.Failure(e
.getMessage());
456 } catch (InvalidNumberException e
) {
457 throw new Error
.InvalidNumber(e
.getMessage());
462 public void joinGroup(final String groupLink
) {
464 final var linkUrl
= GroupInviteLinkUrl
.fromUri(groupLink
);
465 if (linkUrl
== null) {
466 throw new Error
.Failure("Group link is invalid:");
468 m
.joinGroup(linkUrl
);
469 } catch (GroupInviteLinkUrl
.InvalidGroupLinkException
| GroupLinkNotActiveException e
) {
470 throw new Error
.Failure("Group link is invalid: " + e
.getMessage());
471 } catch (GroupInviteLinkUrl
.UnknownGroupLinkVersionException e
) {
472 throw new Error
.Failure("Group link was created with an incompatible version: " + e
.getMessage());
473 } catch (IOException e
) {
474 throw new Error
.Failure(e
.getMessage());
479 public boolean isContactBlocked(final String number
) {
481 return m
.isContactBlocked(number
);
482 } catch (InvalidNumberException e
) {
483 throw new Error
.InvalidNumber(e
.getMessage());
488 public boolean isGroupBlocked(final byte[] groupId
) {
489 var group
= m
.getGroup(GroupId
.unknownVersion(groupId
));
493 return group
.isBlocked();
498 public boolean isMember(final byte[] groupId
) {
499 var group
= m
.getGroup(GroupId
.unknownVersion(groupId
));
503 return group
.isMember(m
.getSelfRecipientId());