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
.NotAGroupMemberException
;
12 import org
.asamk
.signal
.manager
.storage
.identities
.IdentityInfo
;
13 import org
.asamk
.signal
.util
.ErrorUtils
;
14 import org
.freedesktop
.dbus
.exceptions
.DBusExecutionException
;
15 import org
.whispersystems
.libsignal
.util
.Pair
;
16 import org
.whispersystems
.libsignal
.util
.guava
.Optional
;
17 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupLinkNotActiveException
;
18 import org
.whispersystems
.signalservice
.api
.messages
.SendMessageResult
;
19 import org
.whispersystems
.signalservice
.api
.push
.SignalServiceAddress
;
20 import org
.whispersystems
.signalservice
.api
.util
.InvalidNumberException
;
23 import java
.io
.IOException
;
24 import java
.util
.ArrayList
;
25 import java
.util
.List
;
26 import java
.util
.Objects
;
27 import java
.util
.stream
.Collectors
;
28 import java
.util
.stream
.Stream
;
30 public class DbusSignalImpl
implements Signal
{
32 private final Manager m
;
34 public DbusSignalImpl(final Manager m
) {
39 public boolean isRemote() {
44 public String
getObjectPath() {
49 public long sendMessage(final String message
, final List
<String
> attachments
, final String recipient
) {
50 var recipients
= new ArrayList
<String
>(1);
51 recipients
.add(recipient
);
52 return sendMessage(message
, attachments
, recipients
);
55 private static void checkSendMessageResult(long timestamp
, SendMessageResult result
) throws DBusExecutionException
{
56 var error
= ErrorUtils
.getErrorMessageFromSendMessageResult(result
);
62 final var message
= timestamp
+ "\nFailed to send message:\n" + error
+ '\n';
64 if (result
.getIdentityFailure() != null) {
65 throw new Error
.UntrustedIdentity(message
);
67 throw new Error
.Failure(message
);
71 private static void checkSendMessageResults(
72 long timestamp
, List
<SendMessageResult
> results
73 ) throws DBusExecutionException
{
74 if (results
.size() == 1) {
75 checkSendMessageResult(timestamp
, results
.get(0));
79 var errors
= ErrorUtils
.getErrorMessagesFromSendMessageResults(results
);
80 if (errors
.size() == 0) {
84 var message
= new StringBuilder();
85 message
.append(timestamp
).append('\n');
86 message
.append("Failed to send (some) messages:\n");
87 for (var error
: errors
) {
88 message
.append(error
).append('\n');
91 throw new Error
.Failure(message
.toString());
95 public long sendMessage(final String message
, final List
<String
> attachments
, final List
<String
> recipients
) {
97 final var results
= m
.sendMessage(message
, attachments
, recipients
);
98 checkSendMessageResults(results
.first(), results
.second());
99 return results
.first();
100 } catch (InvalidNumberException e
) {
101 throw new Error
.InvalidNumber(e
.getMessage());
102 } catch (AttachmentInvalidException e
) {
103 throw new Error
.AttachmentInvalid(e
.getMessage());
104 } catch (IOException e
) {
105 throw new Error
.Failure(e
.getMessage());
110 public long sendRemoteDeleteMessage(
111 final long targetSentTimestamp
, final String recipient
113 var recipients
= new ArrayList
<String
>(1);
114 recipients
.add(recipient
);
115 return sendRemoteDeleteMessage(targetSentTimestamp
, recipients
);
119 public long sendRemoteDeleteMessage(
120 final long targetSentTimestamp
, final List
<String
> recipients
123 final var results
= m
.sendRemoteDeleteMessage(targetSentTimestamp
, recipients
);
124 checkSendMessageResults(results
.first(), results
.second());
125 return results
.first();
126 } catch (IOException e
) {
127 throw new Error
.Failure(e
.getMessage());
128 } catch (InvalidNumberException e
) {
129 throw new Error
.InvalidNumber(e
.getMessage());
134 public long sendGroupRemoteDeleteMessage(
135 final long targetSentTimestamp
, final byte[] groupId
138 final var results
= m
.sendGroupRemoteDeleteMessage(targetSentTimestamp
, GroupId
.unknownVersion(groupId
));
139 checkSendMessageResults(results
.first(), results
.second());
140 return results
.first();
141 } catch (IOException e
) {
142 throw new Error
.Failure(e
.getMessage());
143 } catch (GroupNotFoundException
| NotAGroupMemberException e
) {
144 throw new Error
.GroupNotFound(e
.getMessage());
149 public long sendMessageReaction(
151 final boolean remove
,
152 final String targetAuthor
,
153 final long targetSentTimestamp
,
154 final String recipient
156 var recipients
= new ArrayList
<String
>(1);
157 recipients
.add(recipient
);
158 return sendMessageReaction(emoji
, remove
, targetAuthor
, targetSentTimestamp
, recipients
);
162 public long sendMessageReaction(
164 final boolean remove
,
165 final String targetAuthor
,
166 final long targetSentTimestamp
,
167 final List
<String
> recipients
170 final var results
= m
.sendMessageReaction(emoji
, remove
, targetAuthor
, targetSentTimestamp
, recipients
);
171 checkSendMessageResults(results
.first(), results
.second());
172 return results
.first();
173 } catch (InvalidNumberException e
) {
174 throw new Error
.InvalidNumber(e
.getMessage());
175 } catch (IOException e
) {
176 throw new Error
.Failure(e
.getMessage());
181 public long sendNoteToSelfMessage(
182 final String message
, final List
<String
> attachments
183 ) throws Error
.AttachmentInvalid
, Error
.Failure
, Error
.UntrustedIdentity
{
185 final var results
= m
.sendSelfMessage(message
, attachments
);
186 checkSendMessageResult(results
.first(), results
.second());
187 return results
.first();
188 } catch (AttachmentInvalidException e
) {
189 throw new Error
.AttachmentInvalid(e
.getMessage());
190 } catch (IOException e
) {
191 throw new Error
.Failure(e
.getMessage());
196 public void sendEndSessionMessage(final List
<String
> recipients
) {
198 final var results
= m
.sendEndSessionMessage(recipients
);
199 checkSendMessageResults(results
.first(), results
.second());
200 } catch (IOException e
) {
201 throw new Error
.Failure(e
.getMessage());
202 } catch (InvalidNumberException e
) {
203 throw new Error
.InvalidNumber(e
.getMessage());
208 public long sendGroupMessage(final String message
, final List
<String
> attachments
, final byte[] groupId
) {
210 var results
= m
.sendGroupMessage(message
, attachments
, GroupId
.unknownVersion(groupId
));
211 checkSendMessageResults(results
.first(), results
.second());
212 return results
.first();
213 } catch (IOException e
) {
214 throw new Error
.Failure(e
.getMessage());
215 } catch (GroupNotFoundException
| NotAGroupMemberException e
) {
216 throw new Error
.GroupNotFound(e
.getMessage());
217 } catch (AttachmentInvalidException e
) {
218 throw new Error
.AttachmentInvalid(e
.getMessage());
223 public long sendGroupMessageReaction(
225 final boolean remove
,
226 final String targetAuthor
,
227 final long targetSentTimestamp
,
231 final var results
= m
.sendGroupMessageReaction(emoji
,
235 GroupId
.unknownVersion(groupId
));
236 checkSendMessageResults(results
.first(), results
.second());
237 return results
.first();
238 } catch (IOException e
) {
239 throw new Error
.Failure(e
.getMessage());
240 } catch (InvalidNumberException e
) {
241 throw new Error
.InvalidNumber(e
.getMessage());
242 } catch (GroupNotFoundException
| NotAGroupMemberException e
) {
243 throw new Error
.GroupNotFound(e
.getMessage());
247 // Since contact names might be empty if not defined, also potentially return
250 public String
getContactName(final String number
) {
252 return m
.getContactOrProfileName(number
);
253 } catch (InvalidNumberException e
) {
254 throw new Error
.InvalidNumber(e
.getMessage());
259 public void setContactName(final String number
, final String name
) {
261 m
.setContactName(number
, name
);
262 } catch (InvalidNumberException e
) {
263 throw new Error
.InvalidNumber(e
.getMessage());
264 } catch (NotMasterDeviceException e
) {
265 throw new Error
.Failure("This command doesn't work on linked devices.");
270 public void setContactBlocked(final String number
, final boolean blocked
) {
272 m
.setContactBlocked(number
, blocked
);
273 } catch (InvalidNumberException e
) {
274 throw new Error
.InvalidNumber(e
.getMessage());
275 } catch (NotMasterDeviceException e
) {
276 throw new Error
.Failure("This command doesn't work on linked devices.");
281 public void setGroupBlocked(final byte[] groupId
, final boolean blocked
) {
283 m
.setGroupBlocked(GroupId
.unknownVersion(groupId
), blocked
);
284 } catch (GroupNotFoundException e
) {
285 throw new Error
.GroupNotFound(e
.getMessage());
290 public List
<byte[]> getGroupIds() {
291 var groups
= m
.getGroups();
292 var ids
= new ArrayList
<byte[]>(groups
.size());
293 for (var group
: groups
) {
294 ids
.add(group
.getGroupId().serialize());
300 public String
getGroupName(final byte[] groupId
) {
301 var group
= m
.getGroup(GroupId
.unknownVersion(groupId
));
305 return group
.getTitle();
310 public List
<String
> getGroupMembers(final byte[] groupId
) {
311 var group
= m
.getGroup(GroupId
.unknownVersion(groupId
));
315 return group
.getMembers()
317 .map(m
::resolveSignalServiceAddress
)
318 .map(SignalServiceAddress
::getLegacyIdentifier
)
319 .collect(Collectors
.toList());
324 public byte[] updateGroup(byte[] groupId
, String name
, List
<String
> members
, String avatar
) {
326 if (groupId
.length
== 0) {
329 if (name
.isEmpty()) {
332 if (members
.isEmpty()) {
335 if (avatar
.isEmpty()) {
338 if (groupId
== null) {
339 final var results
= m
.createGroup(name
, members
, avatar
== null ?
null : new File(avatar
));
340 checkSendMessageResults(0, results
.second());
341 return results
.first().serialize();
343 final var results
= m
.updateGroup(GroupId
.unknownVersion(groupId
),
348 avatar
== null ?
null : new File(avatar
));
349 checkSendMessageResults(results
.first(), results
.second());
352 } catch (IOException e
) {
353 throw new Error
.Failure(e
.getMessage());
354 } catch (GroupNotFoundException
| NotAGroupMemberException e
) {
355 throw new Error
.GroupNotFound(e
.getMessage());
356 } catch (InvalidNumberException e
) {
357 throw new Error
.InvalidNumber(e
.getMessage());
358 } catch (AttachmentInvalidException e
) {
359 throw new Error
.AttachmentInvalid(e
.getMessage());
364 public boolean isRegistered() {
369 public void updateProfile(
372 final String aboutEmoji
,
374 final boolean removeAvatar
377 if (avatarPath
.isEmpty()) {
380 Optional
<File
> avatarFile
= removeAvatar
382 : avatarPath
== null ?
null : Optional
.of(new File(avatarPath
));
383 m
.setProfile(name
, null, about
, aboutEmoji
, avatarFile
);
384 } catch (IOException e
) {
385 throw new Error
.Failure(e
.getMessage());
389 // Provide option to query a version string in order to react on potential
390 // future interface changes
392 public String
version() {
393 return BaseConfig
.PROJECT_VERSION
;
396 // Create a unique list of Numbers from Identities and Contacts to really get
397 // all numbers the system knows
399 public List
<String
> listNumbers() {
400 return Stream
.concat(m
.getIdentities().stream().map(IdentityInfo
::getRecipientId
),
401 m
.getContacts().stream().map(Pair
::first
))
402 .map(m
::resolveSignalServiceAddress
)
403 .map(a
-> a
.getNumber().orNull())
404 .filter(Objects
::nonNull
)
406 .collect(Collectors
.toList());
410 public List
<String
> getContactNumber(final String name
) {
411 // Contact names have precedence.
412 var numbers
= new ArrayList
<String
>();
413 var contacts
= m
.getContacts();
414 for (var c
: contacts
) {
415 if (name
.equals(c
.second().getName())) {
416 numbers
.add(m
.resolveSignalServiceAddress(c
.first()).getLegacyIdentifier());
419 // Try profiles if no contact name was found
420 for (var identity
: m
.getIdentities()) {
421 final var recipientId
= identity
.getRecipientId();
422 final var address
= m
.resolveSignalServiceAddress(recipientId
);
423 var number
= address
.getNumber().orNull();
424 if (number
!= null) {
425 var profile
= m
.getRecipientProfile(recipientId
);
426 if (profile
!= null && profile
.getDisplayName().equals(name
)) {
435 public void quitGroup(final byte[] groupId
) {
436 var group
= GroupId
.unknownVersion(groupId
);
438 m
.sendQuitGroupMessage(group
);
439 } catch (GroupNotFoundException
| NotAGroupMemberException e
) {
440 throw new Error
.GroupNotFound(e
.getMessage());
441 } catch (IOException e
) {
442 throw new Error
.Failure(e
.getMessage());
447 public void joinGroup(final String groupLink
) {
449 final var linkUrl
= GroupInviteLinkUrl
.fromUri(groupLink
);
450 if (linkUrl
== null) {
451 throw new Error
.Failure("Group link is invalid:");
453 m
.joinGroup(linkUrl
);
454 } catch (GroupInviteLinkUrl
.InvalidGroupLinkException
| GroupLinkNotActiveException e
) {
455 throw new Error
.Failure("Group link is invalid: " + e
.getMessage());
456 } catch (GroupInviteLinkUrl
.UnknownGroupLinkVersionException e
) {
457 throw new Error
.Failure("Group link was created with an incompatible version: " + e
.getMessage());
458 } catch (IOException e
) {
459 throw new Error
.Failure(e
.getMessage());
464 public boolean isContactBlocked(final String number
) {
466 return m
.isContactBlocked(number
);
467 } catch (InvalidNumberException e
) {
468 throw new Error
.InvalidNumber(e
.getMessage());
473 public boolean isGroupBlocked(final byte[] groupId
) {
474 var group
= m
.getGroup(GroupId
.unknownVersion(groupId
));
478 return group
.isBlocked();
483 public boolean isMember(final byte[] groupId
) {
484 var group
= m
.getGroup(GroupId
.unknownVersion(groupId
));
488 return group
.isMember(m
.getSelfRecipientId());