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
.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
.manager
.storage
.identities
.IdentityInfo
;
12 import org
.asamk
.signal
.util
.ErrorUtils
;
13 import org
.freedesktop
.dbus
.exceptions
.DBusExecutionException
;
14 import org
.whispersystems
.libsignal
.util
.guava
.Optional
;
15 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupLinkNotActiveException
;
16 import org
.whispersystems
.signalservice
.api
.messages
.SendMessageResult
;
17 import org
.whispersystems
.signalservice
.api
.push
.SignalServiceAddress
;
18 import org
.whispersystems
.signalservice
.api
.util
.InvalidNumberException
;
21 import java
.io
.IOException
;
22 import java
.util
.ArrayList
;
23 import java
.util
.List
;
24 import java
.util
.Objects
;
25 import java
.util
.stream
.Collectors
;
26 import java
.util
.stream
.Stream
;
28 public class DbusSignalImpl
implements Signal
{
30 private final Manager m
;
32 public DbusSignalImpl(final Manager m
) {
37 public boolean isRemote() {
42 public String
getObjectPath() {
47 public long sendMessage(final String message
, final List
<String
> attachments
, final String recipient
) {
48 var recipients
= new ArrayList
<String
>(1);
49 recipients
.add(recipient
);
50 return sendMessage(message
, attachments
, recipients
);
53 private static void checkSendMessageResult(long timestamp
, SendMessageResult result
) throws DBusExecutionException
{
54 var error
= ErrorUtils
.getErrorMessageFromSendMessageResult(result
);
60 final var message
= timestamp
+ "\nFailed to send message:\n" + error
+ '\n';
62 if (result
.getIdentityFailure() != null) {
63 throw new Error
.UntrustedIdentity(message
);
65 throw new Error
.Failure(message
);
69 private static void checkSendMessageResults(
70 long timestamp
, List
<SendMessageResult
> results
71 ) throws DBusExecutionException
{
72 if (results
.size() == 1) {
73 checkSendMessageResult(timestamp
, results
.get(0));
77 var errors
= ErrorUtils
.getErrorMessagesFromSendMessageResults(results
);
78 if (errors
.size() == 0) {
82 var message
= new StringBuilder();
83 message
.append(timestamp
).append('\n');
84 message
.append("Failed to send (some) messages:\n");
85 for (var error
: errors
) {
86 message
.append(error
).append('\n');
89 throw new Error
.Failure(message
.toString());
93 public long sendMessage(final String message
, final List
<String
> attachments
, final List
<String
> recipients
) {
95 final var results
= m
.sendMessage(message
, attachments
, recipients
);
96 checkSendMessageResults(results
.first(), results
.second());
97 return results
.first();
98 } catch (InvalidNumberException e
) {
99 throw new Error
.InvalidNumber(e
.getMessage());
100 } catch (AttachmentInvalidException e
) {
101 throw new Error
.AttachmentInvalid(e
.getMessage());
102 } catch (IOException e
) {
103 throw new Error
.Failure(e
.getMessage());
108 public long sendRemoteDeleteMessage(
109 final long targetSentTimestamp
, final String recipient
111 var recipients
= new ArrayList
<String
>(1);
112 recipients
.add(recipient
);
113 return sendRemoteDeleteMessage(targetSentTimestamp
, recipients
);
117 public long sendRemoteDeleteMessage(
118 final long targetSentTimestamp
, final List
<String
> recipients
121 final var results
= m
.sendRemoteDeleteMessage(targetSentTimestamp
, recipients
);
122 checkSendMessageResults(results
.first(), results
.second());
123 return results
.first();
124 } catch (IOException e
) {
125 throw new Error
.Failure(e
.getMessage());
126 } catch (InvalidNumberException e
) {
127 throw new Error
.InvalidNumber(e
.getMessage());
132 public long sendGroupRemoteDeleteMessage(
133 final long targetSentTimestamp
, final byte[] groupId
136 final var results
= m
.sendGroupRemoteDeleteMessage(targetSentTimestamp
, GroupId
.unknownVersion(groupId
));
137 checkSendMessageResults(results
.first(), results
.second());
138 return results
.first();
139 } catch (IOException e
) {
140 throw new Error
.Failure(e
.getMessage());
141 } catch (GroupNotFoundException
| NotAGroupMemberException e
) {
142 throw new Error
.GroupNotFound(e
.getMessage());
147 public long sendMessageReaction(
149 final boolean remove
,
150 final String targetAuthor
,
151 final long targetSentTimestamp
,
152 final String recipient
154 var recipients
= new ArrayList
<String
>(1);
155 recipients
.add(recipient
);
156 return sendMessageReaction(emoji
, remove
, targetAuthor
, targetSentTimestamp
, recipients
);
160 public long sendMessageReaction(
162 final boolean remove
,
163 final String targetAuthor
,
164 final long targetSentTimestamp
,
165 final List
<String
> recipients
168 final var results
= m
.sendMessageReaction(emoji
, remove
, targetAuthor
, targetSentTimestamp
, recipients
);
169 checkSendMessageResults(results
.first(), results
.second());
170 return results
.first();
171 } catch (InvalidNumberException e
) {
172 throw new Error
.InvalidNumber(e
.getMessage());
173 } catch (IOException e
) {
174 throw new Error
.Failure(e
.getMessage());
179 public long sendNoteToSelfMessage(
180 final String message
, final List
<String
> attachments
181 ) throws Error
.AttachmentInvalid
, Error
.Failure
, Error
.UntrustedIdentity
{
183 final var results
= m
.sendSelfMessage(message
, attachments
);
184 checkSendMessageResult(results
.first(), results
.second());
185 return results
.first();
186 } catch (AttachmentInvalidException e
) {
187 throw new Error
.AttachmentInvalid(e
.getMessage());
188 } catch (IOException e
) {
189 throw new Error
.Failure(e
.getMessage());
194 public void sendEndSessionMessage(final List
<String
> recipients
) {
196 final var results
= m
.sendEndSessionMessage(recipients
);
197 checkSendMessageResults(results
.first(), results
.second());
198 } catch (IOException e
) {
199 throw new Error
.Failure(e
.getMessage());
200 } catch (InvalidNumberException e
) {
201 throw new Error
.InvalidNumber(e
.getMessage());
206 public long sendGroupMessage(final String message
, final List
<String
> attachments
, final byte[] groupId
) {
208 var results
= m
.sendGroupMessage(message
, attachments
, GroupId
.unknownVersion(groupId
));
209 checkSendMessageResults(results
.first(), results
.second());
210 return results
.first();
211 } catch (IOException e
) {
212 throw new Error
.Failure(e
.getMessage());
213 } catch (GroupNotFoundException
| NotAGroupMemberException e
) {
214 throw new Error
.GroupNotFound(e
.getMessage());
215 } catch (AttachmentInvalidException e
) {
216 throw new Error
.AttachmentInvalid(e
.getMessage());
221 public long sendGroupMessageReaction(
223 final boolean remove
,
224 final String targetAuthor
,
225 final long targetSentTimestamp
,
229 final var results
= m
.sendGroupMessageReaction(emoji
,
233 GroupId
.unknownVersion(groupId
));
234 checkSendMessageResults(results
.first(), results
.second());
235 return results
.first();
236 } catch (IOException e
) {
237 throw new Error
.Failure(e
.getMessage());
238 } catch (InvalidNumberException e
) {
239 throw new Error
.InvalidNumber(e
.getMessage());
240 } catch (GroupNotFoundException
| NotAGroupMemberException e
) {
241 throw new Error
.GroupNotFound(e
.getMessage());
245 // Since contact names might be empty if not defined, also potentially return
248 public String
getContactName(final String number
) {
250 return m
.getContactOrProfileName(number
);
251 } catch (Exception e
) {
252 throw new Error
.InvalidNumber(e
.getMessage());
257 public void setContactName(final String number
, final String name
) {
259 m
.setContactName(number
, name
);
260 } catch (InvalidNumberException e
) {
261 throw new Error
.InvalidNumber(e
.getMessage());
266 public void setContactBlocked(final String number
, final boolean blocked
) {
268 m
.setContactBlocked(number
, blocked
);
269 } catch (InvalidNumberException e
) {
270 throw new Error
.InvalidNumber(e
.getMessage());
275 public void setGroupBlocked(final byte[] groupId
, final boolean blocked
) {
277 m
.setGroupBlocked(GroupId
.unknownVersion(groupId
), blocked
);
278 } catch (GroupNotFoundException e
) {
279 throw new Error
.GroupNotFound(e
.getMessage());
284 public List
<byte[]> getGroupIds() {
285 var groups
= m
.getGroups();
286 var ids
= new ArrayList
<byte[]>(groups
.size());
287 for (var group
: groups
) {
288 ids
.add(group
.getGroupId().serialize());
294 public String
getGroupName(final byte[] groupId
) {
295 var group
= m
.getGroup(GroupId
.unknownVersion(groupId
));
299 return group
.getTitle();
304 public List
<String
> getGroupMembers(final byte[] groupId
) {
305 var group
= m
.getGroup(GroupId
.unknownVersion(groupId
));
309 return group
.getMembers()
311 .map(m
::resolveSignalServiceAddress
)
312 .map(SignalServiceAddress
::getLegacyIdentifier
)
313 .collect(Collectors
.toList());
318 public byte[] updateGroup(byte[] groupId
, String name
, List
<String
> members
, String avatar
) {
320 if (groupId
.length
== 0) {
323 if (name
.isEmpty()) {
326 if (members
.isEmpty()) {
329 if (avatar
.isEmpty()) {
332 final var results
= m
.updateGroup(groupId
== null ?
null : GroupId
.unknownVersion(groupId
),
335 avatar
== null ?
null : new File(avatar
));
336 checkSendMessageResults(0, results
.second());
337 return results
.first().serialize();
338 } catch (IOException e
) {
339 throw new Error
.Failure(e
.getMessage());
340 } catch (GroupNotFoundException
| NotAGroupMemberException e
) {
341 throw new Error
.GroupNotFound(e
.getMessage());
342 } catch (InvalidNumberException e
) {
343 throw new Error
.InvalidNumber(e
.getMessage());
344 } catch (AttachmentInvalidException e
) {
345 throw new Error
.AttachmentInvalid(e
.getMessage());
350 public boolean isRegistered() {
355 public void updateProfile(
358 final String aboutEmoji
,
360 final boolean removeAvatar
363 if (avatarPath
.isEmpty()) {
366 Optional
<File
> avatarFile
= removeAvatar
368 : avatarPath
== null ?
null : Optional
.of(new File(avatarPath
));
369 m
.setProfile(name
, about
, aboutEmoji
, avatarFile
);
370 } catch (IOException e
) {
371 throw new Error
.Failure(e
.getMessage());
375 // Provide option to query a version string in order to react on potential
376 // future interface changes
378 public String
version() {
379 return BaseConfig
.PROJECT_VERSION
;
382 // Create a unique list of Numbers from Identities and Contacts to really get
383 // all numbers the system knows
385 public List
<String
> listNumbers() {
386 return Stream
.concat(m
.getIdentities()
388 .map(IdentityInfo
::getRecipientId
)
389 .map(m
::resolveSignalServiceAddress
)
390 .map(a
-> a
.getNumber().orNull()), m
.getContacts().stream().map(c
-> c
.number
))
391 .filter(Objects
::nonNull
)
393 .collect(Collectors
.toList());
397 public List
<String
> getContactNumber(final String name
) {
398 // Contact names have precedence.
399 var numbers
= new ArrayList
<String
>();
400 var contacts
= m
.getContacts();
401 for (var c
: contacts
) {
402 if (c
.name
!= null && c
.name
.equals(name
)) {
403 numbers
.add(c
.number
);
406 // Try profiles if no contact name was found
407 for (var identity
: m
.getIdentities()) {
408 final var recipientId
= identity
.getRecipientId();
409 final var address
= m
.resolveSignalServiceAddress(recipientId
);
410 var number
= address
.getNumber().orNull();
411 if (number
!= null) {
412 var profile
= m
.getRecipientProfile(address
);
413 if (profile
!= null && profile
.getDisplayName().equals(name
)) {
422 public void quitGroup(final byte[] groupId
) {
423 var group
= GroupId
.unknownVersion(groupId
);
425 m
.sendQuitGroupMessage(group
);
426 } catch (GroupNotFoundException
| NotAGroupMemberException e
) {
427 throw new Error
.GroupNotFound(e
.getMessage());
428 } catch (IOException e
) {
429 throw new Error
.Failure(e
.getMessage());
434 public void joinGroup(final String groupLink
) {
436 final var linkUrl
= GroupInviteLinkUrl
.fromUri(groupLink
);
437 if (linkUrl
== null) {
438 throw new Error
.Failure("Group link is invalid:");
440 m
.joinGroup(linkUrl
);
441 } catch (GroupInviteLinkUrl
.InvalidGroupLinkException
| GroupLinkNotActiveException e
) {
442 throw new Error
.Failure("Group link is invalid: " + e
.getMessage());
443 } catch (GroupInviteLinkUrl
.UnknownGroupLinkVersionException e
) {
444 throw new Error
.Failure("Group link was created with an incompatible version: " + e
.getMessage());
445 } catch (IOException e
) {
446 throw new Error
.Failure(e
.getMessage());
451 public boolean isContactBlocked(final String number
) {
452 var contacts
= m
.getContacts();
453 for (var c
: contacts
) {
454 if (c
.number
.equals(number
)) {
462 public boolean isGroupBlocked(final byte[] groupId
) {
463 var group
= m
.getGroup(GroupId
.unknownVersion(groupId
));
467 return group
.isBlocked();
472 public boolean isMember(final byte[] groupId
) {
473 var group
= m
.getGroup(GroupId
.unknownVersion(groupId
));
477 return group
.isMember(m
.getSelfAddress());