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
.freedesktop
.dbus
.exceptions
.DBusExecutionException
;
16 import org
.whispersystems
.libsignal
.util
.Pair
;
17 import org
.whispersystems
.libsignal
.util
.guava
.Optional
;
18 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupLinkNotActiveException
;
19 import org
.whispersystems
.signalservice
.api
.messages
.SendMessageResult
;
20 import org
.whispersystems
.signalservice
.api
.push
.SignalServiceAddress
;
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 public class DbusSignalImpl
implements Signal
{
34 private final Manager m
;
36 public DbusSignalImpl(final Manager m
) {
41 public boolean isRemote() {
46 public String
getObjectPath() {
51 public long sendMessage(final String message
, final List
<String
> attachments
, final String recipient
) {
52 var recipients
= new ArrayList
<String
>(1);
53 recipients
.add(recipient
);
54 return sendMessage(message
, attachments
, recipients
);
57 private static void checkSendMessageResult(long timestamp
, SendMessageResult result
) throws DBusExecutionException
{
58 var error
= ErrorUtils
.getErrorMessageFromSendMessageResult(result
);
64 final var message
= timestamp
+ "\nFailed to send message:\n" + error
+ '\n';
66 if (result
.getIdentityFailure() != null) {
67 throw new Error
.UntrustedIdentity(message
);
69 throw new Error
.Failure(message
);
73 private static void checkSendMessageResults(
74 long timestamp
, List
<SendMessageResult
> results
75 ) throws DBusExecutionException
{
76 if (results
.size() == 1) {
77 checkSendMessageResult(timestamp
, results
.get(0));
81 var errors
= ErrorUtils
.getErrorMessagesFromSendMessageResults(results
);
82 if (errors
.size() == 0) {
86 var message
= new StringBuilder();
87 message
.append(timestamp
).append('\n');
88 message
.append("Failed to send (some) messages:\n");
89 for (var error
: errors
) {
90 message
.append(error
).append('\n');
93 throw new Error
.Failure(message
.toString());
97 public long sendMessage(final String message
, final List
<String
> attachments
, final List
<String
> recipients
) {
99 final var results
= m
.sendMessage(message
, attachments
, recipients
);
100 checkSendMessageResults(results
.first(), results
.second());
101 return results
.first();
102 } catch (InvalidNumberException e
) {
103 throw new Error
.InvalidNumber(e
.getMessage());
104 } catch (AttachmentInvalidException e
) {
105 throw new Error
.AttachmentInvalid(e
.getMessage());
106 } catch (IOException e
) {
107 throw new Error
.Failure(e
.getMessage());
112 public long sendRemoteDeleteMessage(
113 final long targetSentTimestamp
, final String recipient
115 var recipients
= new ArrayList
<String
>(1);
116 recipients
.add(recipient
);
117 return sendRemoteDeleteMessage(targetSentTimestamp
, recipients
);
121 public long sendRemoteDeleteMessage(
122 final long targetSentTimestamp
, final List
<String
> recipients
125 final var results
= m
.sendRemoteDeleteMessage(targetSentTimestamp
, recipients
);
126 checkSendMessageResults(results
.first(), results
.second());
127 return results
.first();
128 } catch (IOException e
) {
129 throw new Error
.Failure(e
.getMessage());
130 } catch (InvalidNumberException e
) {
131 throw new Error
.InvalidNumber(e
.getMessage());
136 public long sendGroupRemoteDeleteMessage(
137 final long targetSentTimestamp
, final byte[] groupId
140 final var results
= m
.sendGroupRemoteDeleteMessage(targetSentTimestamp
, GroupId
.unknownVersion(groupId
));
141 checkSendMessageResults(results
.first(), results
.second());
142 return results
.first();
143 } catch (IOException e
) {
144 throw new Error
.Failure(e
.getMessage());
145 } catch (GroupNotFoundException
| NotAGroupMemberException e
) {
146 throw new Error
.GroupNotFound(e
.getMessage());
151 public long sendMessageReaction(
153 final boolean remove
,
154 final String targetAuthor
,
155 final long targetSentTimestamp
,
156 final String recipient
158 var recipients
= new ArrayList
<String
>(1);
159 recipients
.add(recipient
);
160 return sendMessageReaction(emoji
, remove
, targetAuthor
, targetSentTimestamp
, recipients
);
164 public long sendMessageReaction(
166 final boolean remove
,
167 final String targetAuthor
,
168 final long targetSentTimestamp
,
169 final List
<String
> recipients
172 final var results
= m
.sendMessageReaction(emoji
, remove
, targetAuthor
, targetSentTimestamp
, recipients
);
173 checkSendMessageResults(results
.first(), results
.second());
174 return results
.first();
175 } catch (InvalidNumberException e
) {
176 throw new Error
.InvalidNumber(e
.getMessage());
177 } catch (IOException e
) {
178 throw new Error
.Failure(e
.getMessage());
183 public long sendNoteToSelfMessage(
184 final String message
, final List
<String
> attachments
185 ) throws Error
.AttachmentInvalid
, Error
.Failure
, Error
.UntrustedIdentity
{
187 final var results
= m
.sendSelfMessage(message
, attachments
);
188 checkSendMessageResult(results
.first(), results
.second());
189 return results
.first();
190 } catch (AttachmentInvalidException e
) {
191 throw new Error
.AttachmentInvalid(e
.getMessage());
192 } catch (IOException e
) {
193 throw new Error
.Failure(e
.getMessage());
198 public void sendEndSessionMessage(final List
<String
> recipients
) {
200 final var results
= m
.sendEndSessionMessage(recipients
);
201 checkSendMessageResults(results
.first(), results
.second());
202 } catch (IOException e
) {
203 throw new Error
.Failure(e
.getMessage());
204 } catch (InvalidNumberException e
) {
205 throw new Error
.InvalidNumber(e
.getMessage());
210 public long sendGroupMessage(final String message
, final List
<String
> attachments
, final byte[] groupId
) {
212 var results
= m
.sendGroupMessage(message
, attachments
, GroupId
.unknownVersion(groupId
));
213 checkSendMessageResults(results
.first(), results
.second());
214 return results
.first();
215 } catch (IOException e
) {
216 throw new Error
.Failure(e
.getMessage());
217 } catch (GroupNotFoundException
| NotAGroupMemberException e
) {
218 throw new Error
.GroupNotFound(e
.getMessage());
219 } catch (AttachmentInvalidException e
) {
220 throw new Error
.AttachmentInvalid(e
.getMessage());
225 public long sendGroupMessageReaction(
227 final boolean remove
,
228 final String targetAuthor
,
229 final long targetSentTimestamp
,
233 final var results
= m
.sendGroupMessageReaction(emoji
,
237 GroupId
.unknownVersion(groupId
));
238 checkSendMessageResults(results
.first(), results
.second());
239 return results
.first();
240 } catch (IOException e
) {
241 throw new Error
.Failure(e
.getMessage());
242 } catch (InvalidNumberException e
) {
243 throw new Error
.InvalidNumber(e
.getMessage());
244 } catch (GroupNotFoundException
| NotAGroupMemberException e
) {
245 throw new Error
.GroupNotFound(e
.getMessage());
249 // Since contact names might be empty if not defined, also potentially return
252 public String
getContactName(final String number
) {
254 return m
.getContactOrProfileName(number
);
255 } catch (InvalidNumberException e
) {
256 throw new Error
.InvalidNumber(e
.getMessage());
261 public void setContactName(final String number
, final String name
) {
263 m
.setContactName(number
, name
);
264 } catch (InvalidNumberException e
) {
265 throw new Error
.InvalidNumber(e
.getMessage());
266 } catch (NotMasterDeviceException e
) {
267 throw new Error
.Failure("This command doesn't work on linked devices.");
272 public void setContactBlocked(final String number
, final boolean blocked
) {
274 m
.setContactBlocked(number
, blocked
);
275 } catch (InvalidNumberException e
) {
276 throw new Error
.InvalidNumber(e
.getMessage());
277 } catch (NotMasterDeviceException e
) {
278 throw new Error
.Failure("This command doesn't work on linked devices.");
283 public void setGroupBlocked(final byte[] groupId
, final boolean blocked
) {
285 m
.setGroupBlocked(GroupId
.unknownVersion(groupId
), blocked
);
286 } catch (GroupNotFoundException e
) {
287 throw new Error
.GroupNotFound(e
.getMessage());
292 public List
<byte[]> getGroupIds() {
293 var groups
= m
.getGroups();
294 var ids
= new ArrayList
<byte[]>(groups
.size());
295 for (var group
: groups
) {
296 ids
.add(group
.getGroupId().serialize());
302 public String
getGroupName(final byte[] groupId
) {
303 var group
= m
.getGroup(GroupId
.unknownVersion(groupId
));
307 return group
.getTitle();
312 public List
<String
> getGroupMembers(final byte[] groupId
) {
313 var group
= m
.getGroup(GroupId
.unknownVersion(groupId
));
317 return group
.getMembers()
319 .map(m
::resolveSignalServiceAddress
)
320 .map(SignalServiceAddress
::getLegacyIdentifier
)
321 .collect(Collectors
.toList());
326 public byte[] updateGroup(byte[] groupId
, String name
, List
<String
> members
, String avatar
) {
328 if (groupId
.length
== 0) {
331 if (name
.isEmpty()) {
334 if (members
.isEmpty()) {
337 if (avatar
.isEmpty()) {
340 if (groupId
== null) {
341 final var results
= m
.createGroup(name
, members
, avatar
== null ?
null : new File(avatar
));
342 checkSendMessageResults(0, results
.second());
343 return results
.first().serialize();
345 final var results
= m
.updateGroup(GroupId
.unknownVersion(groupId
),
356 avatar
== null ?
null : new File(avatar
),
358 if (results
!= null) {
359 checkSendMessageResults(results
.first(), results
.second());
363 } catch (IOException e
) {
364 throw new Error
.Failure(e
.getMessage());
365 } catch (GroupNotFoundException
| NotAGroupMemberException e
) {
366 throw new Error
.GroupNotFound(e
.getMessage());
367 } catch (InvalidNumberException e
) {
368 throw new Error
.InvalidNumber(e
.getMessage());
369 } catch (AttachmentInvalidException e
) {
370 throw new Error
.AttachmentInvalid(e
.getMessage());
375 public boolean isRegistered() {
380 public void updateProfile(
383 final String aboutEmoji
,
385 final boolean removeAvatar
388 if (avatarPath
.isEmpty()) {
391 Optional
<File
> avatarFile
= removeAvatar
393 : avatarPath
== null ?
null : Optional
.of(new File(avatarPath
));
394 m
.setProfile(name
, null, about
, aboutEmoji
, avatarFile
);
395 } catch (IOException e
) {
396 throw new Error
.Failure(e
.getMessage());
400 // Provide option to query a version string in order to react on potential
401 // future interface changes
403 public String
version() {
404 return BaseConfig
.PROJECT_VERSION
;
407 // Create a unique list of Numbers from Identities and Contacts to really get
408 // all numbers the system knows
410 public List
<String
> listNumbers() {
411 return Stream
.concat(m
.getIdentities().stream().map(IdentityInfo
::getRecipientId
),
412 m
.getContacts().stream().map(Pair
::first
))
413 .map(m
::resolveSignalServiceAddress
)
414 .map(a
-> a
.getNumber().orNull())
415 .filter(Objects
::nonNull
)
417 .collect(Collectors
.toList());
421 public List
<String
> getContactNumber(final String name
) {
422 // Contact names have precedence.
423 var numbers
= new ArrayList
<String
>();
424 var contacts
= m
.getContacts();
425 for (var c
: contacts
) {
426 if (name
.equals(c
.second().getName())) {
427 numbers
.add(m
.resolveSignalServiceAddress(c
.first()).getLegacyIdentifier());
430 // Try profiles if no contact name was found
431 for (var identity
: m
.getIdentities()) {
432 final var recipientId
= identity
.getRecipientId();
433 final var address
= m
.resolveSignalServiceAddress(recipientId
);
434 var number
= address
.getNumber().orNull();
435 if (number
!= null) {
436 var profile
= m
.getRecipientProfile(recipientId
);
437 if (profile
!= null && profile
.getDisplayName().equals(name
)) {
446 public void quitGroup(final byte[] groupId
) {
447 var group
= GroupId
.unknownVersion(groupId
);
449 m
.sendQuitGroupMessage(group
, Set
.of());
450 } catch (GroupNotFoundException
| NotAGroupMemberException e
) {
451 throw new Error
.GroupNotFound(e
.getMessage());
452 } catch (IOException
| LastGroupAdminException e
) {
453 throw new Error
.Failure(e
.getMessage());
454 } catch (InvalidNumberException e
) {
455 throw new Error
.InvalidNumber(e
.getMessage());
460 public void joinGroup(final String groupLink
) {
462 final var linkUrl
= GroupInviteLinkUrl
.fromUri(groupLink
);
463 if (linkUrl
== null) {
464 throw new Error
.Failure("Group link is invalid:");
466 m
.joinGroup(linkUrl
);
467 } catch (GroupInviteLinkUrl
.InvalidGroupLinkException
| GroupLinkNotActiveException e
) {
468 throw new Error
.Failure("Group link is invalid: " + e
.getMessage());
469 } catch (GroupInviteLinkUrl
.UnknownGroupLinkVersionException e
) {
470 throw new Error
.Failure("Group link was created with an incompatible version: " + e
.getMessage());
471 } catch (IOException e
) {
472 throw new Error
.Failure(e
.getMessage());
477 public boolean isContactBlocked(final String number
) {
479 return m
.isContactBlocked(number
);
480 } catch (InvalidNumberException e
) {
481 throw new Error
.InvalidNumber(e
.getMessage());
486 public boolean isGroupBlocked(final byte[] groupId
) {
487 var group
= m
.getGroup(GroupId
.unknownVersion(groupId
));
491 return group
.isBlocked();
496 public boolean isMember(final byte[] groupId
) {
497 var group
= m
.getGroup(GroupId
.unknownVersion(groupId
));
501 return group
.isMember(m
.getSelfRecipientId());