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
;
37 private final String objectPath
;
39 public DbusSignalImpl(final Manager m
, final String objectPath
) {
41 this.objectPath
= objectPath
;
45 public boolean isRemote() {
50 public String
getObjectPath() {
55 public long sendMessage(final String message
, final List
<String
> attachments
, final String recipient
) {
56 var recipients
= new ArrayList
<String
>(1);
57 recipients
.add(recipient
);
58 return sendMessage(message
, attachments
, recipients
);
61 private static void checkSendMessageResult(long timestamp
, SendMessageResult result
) throws DBusExecutionException
{
62 var error
= ErrorUtils
.getErrorMessageFromSendMessageResult(result
);
68 final var message
= timestamp
+ "\nFailed to send message:\n" + error
+ '\n';
70 if (result
.getIdentityFailure() != null) {
71 throw new Error
.UntrustedIdentity(message
);
73 throw new Error
.Failure(message
);
77 private static void checkSendMessageResults(
78 long timestamp
, List
<SendMessageResult
> results
79 ) throws DBusExecutionException
{
80 if (results
.size() == 1) {
81 checkSendMessageResult(timestamp
, results
.get(0));
85 var errors
= ErrorUtils
.getErrorMessagesFromSendMessageResults(results
);
86 if (errors
.size() == 0) {
90 var message
= new StringBuilder();
91 message
.append(timestamp
).append('\n');
92 message
.append("Failed to send (some) messages:\n");
93 for (var error
: errors
) {
94 message
.append(error
).append('\n');
97 throw new Error
.Failure(message
.toString());
101 public long sendMessage(final String message
, final List
<String
> attachments
, final List
<String
> recipients
) {
103 final var results
= m
.sendMessage(message
, attachments
, recipients
);
104 checkSendMessageResults(results
.first(), results
.second());
105 return results
.first();
106 } catch (InvalidNumberException e
) {
107 throw new Error
.InvalidNumber(e
.getMessage());
108 } catch (AttachmentInvalidException e
) {
109 throw new Error
.AttachmentInvalid(e
.getMessage());
110 } catch (IOException e
) {
111 throw new Error
.Failure(e
.getMessage());
116 public long sendRemoteDeleteMessage(
117 final long targetSentTimestamp
, final String recipient
119 var recipients
= new ArrayList
<String
>(1);
120 recipients
.add(recipient
);
121 return sendRemoteDeleteMessage(targetSentTimestamp
, recipients
);
125 public long sendRemoteDeleteMessage(
126 final long targetSentTimestamp
, final List
<String
> recipients
129 final var results
= m
.sendRemoteDeleteMessage(targetSentTimestamp
, recipients
);
130 checkSendMessageResults(results
.first(), results
.second());
131 return results
.first();
132 } catch (IOException e
) {
133 throw new Error
.Failure(e
.getMessage());
134 } catch (InvalidNumberException e
) {
135 throw new Error
.InvalidNumber(e
.getMessage());
140 public long sendGroupRemoteDeleteMessage(
141 final long targetSentTimestamp
, final byte[] groupId
144 final var results
= m
.sendGroupRemoteDeleteMessage(targetSentTimestamp
, GroupId
.unknownVersion(groupId
));
145 checkSendMessageResults(results
.first(), results
.second());
146 return results
.first();
147 } catch (IOException e
) {
148 throw new Error
.Failure(e
.getMessage());
149 } catch (GroupNotFoundException
| NotAGroupMemberException e
) {
150 throw new Error
.GroupNotFound(e
.getMessage());
155 public long sendMessageReaction(
157 final boolean remove
,
158 final String targetAuthor
,
159 final long targetSentTimestamp
,
160 final String recipient
162 var recipients
= new ArrayList
<String
>(1);
163 recipients
.add(recipient
);
164 return sendMessageReaction(emoji
, remove
, targetAuthor
, targetSentTimestamp
, recipients
);
168 public long sendMessageReaction(
170 final boolean remove
,
171 final String targetAuthor
,
172 final long targetSentTimestamp
,
173 final List
<String
> recipients
176 final var results
= m
.sendMessageReaction(emoji
, remove
, targetAuthor
, targetSentTimestamp
, recipients
);
177 checkSendMessageResults(results
.first(), results
.second());
178 return results
.first();
179 } catch (InvalidNumberException e
) {
180 throw new Error
.InvalidNumber(e
.getMessage());
181 } catch (IOException e
) {
182 throw new Error
.Failure(e
.getMessage());
187 public long sendNoteToSelfMessage(
188 final String message
, final List
<String
> attachments
189 ) throws Error
.AttachmentInvalid
, Error
.Failure
, Error
.UntrustedIdentity
{
191 final var results
= m
.sendSelfMessage(message
, attachments
);
192 checkSendMessageResult(results
.first(), results
.second());
193 return results
.first();
194 } catch (AttachmentInvalidException e
) {
195 throw new Error
.AttachmentInvalid(e
.getMessage());
196 } catch (IOException e
) {
197 throw new Error
.Failure(e
.getMessage());
202 public void sendEndSessionMessage(final List
<String
> recipients
) {
204 final var results
= m
.sendEndSessionMessage(recipients
);
205 checkSendMessageResults(results
.first(), results
.second());
206 } catch (IOException e
) {
207 throw new Error
.Failure(e
.getMessage());
208 } catch (InvalidNumberException e
) {
209 throw new Error
.InvalidNumber(e
.getMessage());
214 public long sendGroupMessage(final String message
, final List
<String
> attachments
, final byte[] groupId
) {
216 var results
= m
.sendGroupMessage(message
, attachments
, 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 (GroupNotFoundException
| NotAGroupMemberException e
) {
222 throw new Error
.GroupNotFound(e
.getMessage());
223 } catch (AttachmentInvalidException e
) {
224 throw new Error
.AttachmentInvalid(e
.getMessage());
229 public long sendGroupMessageReaction(
231 final boolean remove
,
232 final String targetAuthor
,
233 final long targetSentTimestamp
,
237 final var results
= m
.sendGroupMessageReaction(emoji
,
241 GroupId
.unknownVersion(groupId
));
242 checkSendMessageResults(results
.first(), results
.second());
243 return results
.first();
244 } catch (IOException e
) {
245 throw new Error
.Failure(e
.getMessage());
246 } catch (InvalidNumberException e
) {
247 throw new Error
.InvalidNumber(e
.getMessage());
248 } catch (GroupNotFoundException
| NotAGroupMemberException e
) {
249 throw new Error
.GroupNotFound(e
.getMessage());
253 // Since contact names might be empty if not defined, also potentially return
256 public String
getContactName(final String number
) {
258 return m
.getContactOrProfileName(number
);
259 } catch (InvalidNumberException e
) {
260 throw new Error
.InvalidNumber(e
.getMessage());
265 public void setContactName(final String number
, final String name
) {
267 m
.setContactName(number
, name
);
268 } catch (InvalidNumberException e
) {
269 throw new Error
.InvalidNumber(e
.getMessage());
270 } catch (NotMasterDeviceException e
) {
271 throw new Error
.Failure("This command doesn't work on linked devices.");
276 public void setContactBlocked(final String number
, final boolean blocked
) {
278 m
.setContactBlocked(number
, blocked
);
279 } catch (InvalidNumberException e
) {
280 throw new Error
.InvalidNumber(e
.getMessage());
281 } catch (NotMasterDeviceException e
) {
282 throw new Error
.Failure("This command doesn't work on linked devices.");
287 public void setGroupBlocked(final byte[] groupId
, final boolean blocked
) {
289 m
.setGroupBlocked(GroupId
.unknownVersion(groupId
), blocked
);
290 } catch (GroupNotFoundException e
) {
291 throw new Error
.GroupNotFound(e
.getMessage());
296 public List
<byte[]> getGroupIds() {
297 var groups
= m
.getGroups();
298 var ids
= new ArrayList
<byte[]>(groups
.size());
299 for (var group
: groups
) {
300 ids
.add(group
.getGroupId().serialize());
306 public String
getGroupName(final byte[] groupId
) {
307 var group
= m
.getGroup(GroupId
.unknownVersion(groupId
));
311 return group
.getTitle();
316 public List
<String
> getGroupMembers(final byte[] groupId
) {
317 var group
= m
.getGroup(GroupId
.unknownVersion(groupId
));
321 return group
.getMembers()
323 .map(m
::resolveSignalServiceAddress
)
324 .map(Util
::getLegacyIdentifier
)
325 .collect(Collectors
.toList());
330 public byte[] updateGroup(byte[] groupId
, String name
, List
<String
> members
, String avatar
) {
332 if (groupId
.length
== 0) {
335 if (name
.isEmpty()) {
338 if (members
.isEmpty()) {
341 if (avatar
.isEmpty()) {
344 if (groupId
== null) {
345 final var results
= m
.createGroup(name
, members
, avatar
== null ?
null : new File(avatar
));
346 checkSendMessageResults(0, results
.second());
347 return results
.first().serialize();
349 final var results
= m
.updateGroup(GroupId
.unknownVersion(groupId
),
360 avatar
== null ?
null : new File(avatar
),
363 if (results
!= null) {
364 checkSendMessageResults(results
.first(), results
.second());
368 } catch (IOException e
) {
369 throw new Error
.Failure(e
.getMessage());
370 } catch (GroupNotFoundException
| NotAGroupMemberException e
) {
371 throw new Error
.GroupNotFound(e
.getMessage());
372 } catch (InvalidNumberException e
) {
373 throw new Error
.InvalidNumber(e
.getMessage());
374 } catch (AttachmentInvalidException e
) {
375 throw new Error
.AttachmentInvalid(e
.getMessage());
380 public boolean isRegistered() {
385 public void updateProfile(
388 final String aboutEmoji
,
390 final boolean removeAvatar
393 if (avatarPath
.isEmpty()) {
396 Optional
<File
> avatarFile
= removeAvatar
398 : avatarPath
== null ?
null : Optional
.of(new File(avatarPath
));
399 m
.setProfile(name
, null, about
, aboutEmoji
, avatarFile
);
400 } catch (IOException e
) {
401 throw new Error
.Failure(e
.getMessage());
405 // Provide option to query a version string in order to react on potential
406 // future interface changes
408 public String
version() {
409 return BaseConfig
.PROJECT_VERSION
;
412 // Create a unique list of Numbers from Identities and Contacts to really get
413 // all numbers the system knows
415 public List
<String
> listNumbers() {
416 return Stream
.concat(m
.getIdentities().stream().map(IdentityInfo
::getRecipientId
),
417 m
.getContacts().stream().map(Pair
::first
))
418 .map(m
::resolveSignalServiceAddress
)
419 .map(a
-> a
.getNumber().orNull())
420 .filter(Objects
::nonNull
)
422 .collect(Collectors
.toList());
426 public List
<String
> getContactNumber(final String name
) {
427 // Contact names have precedence.
428 var numbers
= new ArrayList
<String
>();
429 var contacts
= m
.getContacts();
430 for (var c
: contacts
) {
431 if (name
.equals(c
.second().getName())) {
432 numbers
.add(getLegacyIdentifier(m
.resolveSignalServiceAddress(c
.first())));
435 // Try profiles if no contact name was found
436 for (var identity
: m
.getIdentities()) {
437 final var recipientId
= identity
.getRecipientId();
438 final var address
= m
.resolveSignalServiceAddress(recipientId
);
439 var number
= address
.getNumber().orNull();
440 if (number
!= null) {
441 var profile
= m
.getRecipientProfile(recipientId
);
442 if (profile
!= null && profile
.getDisplayName().equals(name
)) {
451 public void quitGroup(final byte[] groupId
) {
452 var group
= GroupId
.unknownVersion(groupId
);
454 m
.sendQuitGroupMessage(group
, Set
.of());
455 } catch (GroupNotFoundException
| NotAGroupMemberException e
) {
456 throw new Error
.GroupNotFound(e
.getMessage());
457 } catch (IOException
| LastGroupAdminException e
) {
458 throw new Error
.Failure(e
.getMessage());
459 } catch (InvalidNumberException e
) {
460 throw new Error
.InvalidNumber(e
.getMessage());
465 public void joinGroup(final String groupLink
) {
467 final var linkUrl
= GroupInviteLinkUrl
.fromUri(groupLink
);
468 if (linkUrl
== null) {
469 throw new Error
.Failure("Group link is invalid:");
471 m
.joinGroup(linkUrl
);
472 } catch (GroupInviteLinkUrl
.InvalidGroupLinkException
| GroupLinkNotActiveException e
) {
473 throw new Error
.Failure("Group link is invalid: " + e
.getMessage());
474 } catch (GroupInviteLinkUrl
.UnknownGroupLinkVersionException e
) {
475 throw new Error
.Failure("Group link was created with an incompatible version: " + e
.getMessage());
476 } catch (IOException e
) {
477 throw new Error
.Failure(e
.getMessage());
482 public boolean isContactBlocked(final String number
) {
484 return m
.isContactBlocked(number
);
485 } catch (InvalidNumberException e
) {
486 throw new Error
.InvalidNumber(e
.getMessage());
491 public boolean isGroupBlocked(final byte[] groupId
) {
492 var group
= m
.getGroup(GroupId
.unknownVersion(groupId
));
496 return group
.isBlocked();
501 public boolean isMember(final byte[] groupId
) {
502 var group
= m
.getGroup(GroupId
.unknownVersion(groupId
));
506 return group
.isMember(m
.getSelfRecipientId());