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
.util
.ErrorUtils
;
12 import org
.freedesktop
.dbus
.exceptions
.DBusExecutionException
;
13 import org
.whispersystems
.libsignal
.util
.guava
.Optional
;
14 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupLinkNotActiveException
;
15 import org
.whispersystems
.signalservice
.api
.messages
.SendMessageResult
;
16 import org
.whispersystems
.signalservice
.api
.push
.SignalServiceAddress
;
17 import org
.whispersystems
.signalservice
.api
.util
.InvalidNumberException
;
20 import java
.io
.IOException
;
21 import java
.util
.ArrayList
;
22 import java
.util
.List
;
23 import java
.util
.Objects
;
24 import java
.util
.stream
.Collectors
;
25 import java
.util
.stream
.Stream
;
27 public class DbusSignalImpl
implements Signal
{
29 private final Manager m
;
31 public DbusSignalImpl(final Manager m
) {
36 public boolean isRemote() {
41 public String
getObjectPath() {
46 public long sendMessage(final String message
, final List
<String
> attachments
, final String recipient
) {
47 var recipients
= new ArrayList
<String
>(1);
48 recipients
.add(recipient
);
49 return sendMessage(message
, attachments
, recipients
);
52 private static void checkSendMessageResult(long timestamp
, SendMessageResult result
) throws DBusExecutionException
{
53 var error
= ErrorUtils
.getErrorMessageFromSendMessageResult(result
);
59 final var message
= timestamp
+ "\nFailed to send message:\n" + error
+ '\n';
61 if (result
.getIdentityFailure() != null) {
62 throw new Error
.UntrustedIdentity(message
);
64 throw new Error
.Failure(message
);
68 private static void checkSendMessageResults(
69 long timestamp
, List
<SendMessageResult
> results
70 ) throws DBusExecutionException
{
71 if (results
.size() == 1) {
72 checkSendMessageResult(timestamp
, results
.get(0));
76 var errors
= ErrorUtils
.getErrorMessagesFromSendMessageResults(results
);
77 if (errors
.size() == 0) {
81 var message
= new StringBuilder();
82 message
.append(timestamp
).append('\n');
83 message
.append("Failed to send (some) messages:\n");
84 for (var error
: errors
) {
85 message
.append(error
).append('\n');
88 throw new Error
.Failure(message
.toString());
92 public long sendMessage(final String message
, final List
<String
> attachments
, final List
<String
> recipients
) {
94 final var results
= m
.sendMessage(message
, attachments
, recipients
);
95 checkSendMessageResults(results
.first(), results
.second());
96 return results
.first();
97 } catch (InvalidNumberException e
) {
98 throw new Error
.InvalidNumber(e
.getMessage());
99 } catch (AttachmentInvalidException e
) {
100 throw new Error
.AttachmentInvalid(e
.getMessage());
101 } catch (IOException e
) {
102 throw new Error
.Failure(e
.getMessage());
107 public long sendRemoteDeleteMessage(
108 final long targetSentTimestamp
, final String recipient
110 var recipients
= new ArrayList
<String
>(1);
111 recipients
.add(recipient
);
112 return sendRemoteDeleteMessage(targetSentTimestamp
, recipients
);
116 public long sendRemoteDeleteMessage(
117 final long targetSentTimestamp
, final List
<String
> recipients
120 final var results
= m
.sendRemoteDeleteMessage(targetSentTimestamp
, recipients
);
121 checkSendMessageResults(results
.first(), results
.second());
122 return results
.first();
123 } catch (IOException e
) {
124 throw new Error
.Failure(e
.getMessage());
125 } catch (InvalidNumberException e
) {
126 throw new Error
.InvalidNumber(e
.getMessage());
131 public long sendGroupRemoteDeleteMessage(
132 final long targetSentTimestamp
, final byte[] groupId
135 final var results
= m
.sendGroupRemoteDeleteMessage(targetSentTimestamp
, GroupId
.unknownVersion(groupId
));
136 checkSendMessageResults(results
.first(), results
.second());
137 return results
.first();
138 } catch (IOException e
) {
139 throw new Error
.Failure(e
.getMessage());
140 } catch (GroupNotFoundException
| NotAGroupMemberException e
) {
141 throw new Error
.GroupNotFound(e
.getMessage());
146 public long sendMessageReaction(
147 final String emoji
, final boolean remove
, final String targetAuthor
, final long targetSentTimestamp
, final String recipient
149 var recipients
= new ArrayList
<String
>(1);
150 recipients
.add(recipient
);
151 return sendMessageReaction(emoji
, remove
, targetAuthor
, targetSentTimestamp
, recipients
);
155 public long sendMessageReaction(
156 final String emoji
, final boolean remove
, final String targetAuthor
, final long targetSentTimestamp
, final List
<String
> recipients
159 final var results
= m
.sendMessageReaction(emoji
, remove
, targetAuthor
, targetSentTimestamp
, recipients
);
160 checkSendMessageResults(results
.first(), results
.second());
161 return results
.first();
162 } catch (InvalidNumberException e
) {
163 throw new Error
.InvalidNumber(e
.getMessage());
164 } catch (IOException e
) {
165 throw new Error
.Failure(e
.getMessage());
170 public long sendNoteToSelfMessage(
171 final String message
, final List
<String
> attachments
172 ) throws Error
.AttachmentInvalid
, Error
.Failure
, Error
.UntrustedIdentity
{
174 final var results
= m
.sendSelfMessage(message
, attachments
);
175 checkSendMessageResult(results
.first(), results
.second());
176 return results
.first();
177 } catch (AttachmentInvalidException e
) {
178 throw new Error
.AttachmentInvalid(e
.getMessage());
179 } catch (IOException e
) {
180 throw new Error
.Failure(e
.getMessage());
185 public void sendEndSessionMessage(final List
<String
> recipients
) {
187 final var results
= m
.sendEndSessionMessage(recipients
);
188 checkSendMessageResults(results
.first(), results
.second());
189 } catch (IOException e
) {
190 throw new Error
.Failure(e
.getMessage());
191 } catch (InvalidNumberException e
) {
192 throw new Error
.InvalidNumber(e
.getMessage());
197 public long sendGroupMessage(final String message
, final List
<String
> attachments
, final byte[] groupId
) {
199 var results
= m
.sendGroupMessage(message
, attachments
, GroupId
.unknownVersion(groupId
));
200 checkSendMessageResults(results
.first(), results
.second());
201 return results
.first();
202 } catch (IOException e
) {
203 throw new Error
.Failure(e
.getMessage());
204 } catch (GroupNotFoundException
| NotAGroupMemberException e
) {
205 throw new Error
.GroupNotFound(e
.getMessage());
206 } catch (AttachmentInvalidException e
) {
207 throw new Error
.AttachmentInvalid(e
.getMessage());
212 public long sendGroupMessageReaction(
213 final String emoji
, final boolean remove
, final String targetAuthor
, final long targetSentTimestamp
, final byte[] groupId
216 final var results
= m
.sendGroupMessageReaction(emoji
, remove
, targetAuthor
, targetSentTimestamp
, 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 (InvalidNumberException e
) {
222 throw new Error
.InvalidNumber(e
.getMessage());
223 } catch (GroupNotFoundException
| NotAGroupMemberException e
) {
224 throw new Error
.GroupNotFound(e
.getMessage());
228 // Since contact names might be empty if not defined, also potentially return
231 public String
getContactName(final String number
) {
233 return m
.getContactOrProfileName(number
);
234 } catch (Exception e
) {
235 throw new Error
.InvalidNumber(e
.getMessage());
240 public void setContactName(final String number
, final String name
) {
242 m
.setContactName(number
, name
);
243 } catch (InvalidNumberException e
) {
244 throw new Error
.InvalidNumber(e
.getMessage());
249 public void setContactBlocked(final String number
, final boolean blocked
) {
251 m
.setContactBlocked(number
, blocked
);
252 } catch (InvalidNumberException e
) {
253 throw new Error
.InvalidNumber(e
.getMessage());
258 public void setGroupBlocked(final byte[] groupId
, final boolean blocked
) {
260 m
.setGroupBlocked(GroupId
.unknownVersion(groupId
), blocked
);
261 } catch (GroupNotFoundException e
) {
262 throw new Error
.GroupNotFound(e
.getMessage());
267 public List
<byte[]> getGroupIds() {
268 var groups
= m
.getGroups();
269 var ids
= new ArrayList
<byte[]>(groups
.size());
270 for (var group
: groups
) {
271 ids
.add(group
.getGroupId().serialize());
277 public String
getGroupName(final byte[] groupId
) {
278 var group
= m
.getGroup(GroupId
.unknownVersion(groupId
));
282 return group
.getTitle();
287 public List
<String
> getGroupMembers(final byte[] groupId
) {
288 var group
= m
.getGroup(GroupId
.unknownVersion(groupId
));
292 return group
.getMembers()
294 .map(m
::resolveSignalServiceAddress
)
295 .map(SignalServiceAddress
::getLegacyIdentifier
)
296 .collect(Collectors
.toList());
301 public byte[] updateGroup(byte[] groupId
, String name
, List
<String
> members
, String avatar
) {
303 if (groupId
.length
== 0) {
306 if (name
.isEmpty()) {
309 if (members
.isEmpty()) {
312 if (avatar
.isEmpty()) {
315 final var results
= m
.updateGroup(groupId
== null ?
null : GroupId
.unknownVersion(groupId
),
318 avatar
== null ?
null : new File(avatar
));
319 checkSendMessageResults(0, results
.second());
320 return results
.first().serialize();
321 } catch (IOException e
) {
322 throw new Error
.Failure(e
.getMessage());
323 } catch (GroupNotFoundException
| NotAGroupMemberException e
) {
324 throw new Error
.GroupNotFound(e
.getMessage());
325 } catch (InvalidNumberException e
) {
326 throw new Error
.InvalidNumber(e
.getMessage());
327 } catch (AttachmentInvalidException e
) {
328 throw new Error
.AttachmentInvalid(e
.getMessage());
333 public boolean isRegistered() {
338 public void updateProfile(
341 final String aboutEmoji
,
343 final boolean removeAvatar
346 if (avatarPath
.isEmpty()) {
349 Optional
<File
> avatarFile
= removeAvatar
351 : avatarPath
== null ?
null : Optional
.of(new File(avatarPath
));
352 m
.setProfile(name
, about
, aboutEmoji
, avatarFile
);
353 } catch (IOException e
) {
354 throw new Error
.Failure(e
.getMessage());
358 // Provide option to query a version string in order to react on potential
359 // future interface changes
361 public String
version() {
362 return BaseConfig
.PROJECT_VERSION
;
365 // Create a unique list of Numbers from Identities and Contacts to really get
366 // all numbers the system knows
368 public List
<String
> listNumbers() {
369 return Stream
.concat(m
.getIdentities().stream().map(i
-> i
.getAddress().getNumber().orNull()),
370 m
.getContacts().stream().map(c
-> c
.number
))
371 .filter(Objects
::nonNull
)
373 .collect(Collectors
.toList());
377 public List
<String
> getContactNumber(final String name
) {
378 // Contact names have precedence.
379 var numbers
= new ArrayList
<String
>();
380 var contacts
= m
.getContacts();
381 for (var c
: contacts
) {
382 if (c
.name
!= null && c
.name
.equals(name
)) {
383 numbers
.add(c
.number
);
386 // Try profiles if no contact name was found
387 for (var identity
: m
.getIdentities()) {
388 final var address
= identity
.getAddress();
389 var number
= address
.getNumber().orNull();
390 if (number
!= null) {
391 var profile
= m
.getRecipientProfile(address
);
392 if (profile
!= null && profile
.getDisplayName().equals(name
)) {
401 public void quitGroup(final byte[] groupId
) {
402 var group
= GroupId
.unknownVersion(groupId
);
404 m
.sendQuitGroupMessage(group
);
405 } catch (GroupNotFoundException
| NotAGroupMemberException e
) {
406 throw new Error
.GroupNotFound(e
.getMessage());
407 } catch (IOException e
) {
408 throw new Error
.Failure(e
.getMessage());
413 public void joinGroup(final String groupLink
) {
415 final var linkUrl
= GroupInviteLinkUrl
.fromUri(groupLink
);
416 if (linkUrl
== null) {
417 throw new Error
.Failure("Group link is invalid:");
419 m
.joinGroup(linkUrl
);
420 } catch (GroupInviteLinkUrl
.InvalidGroupLinkException
| GroupLinkNotActiveException e
) {
421 throw new Error
.Failure("Group link is invalid: " + e
.getMessage());
422 } catch (GroupInviteLinkUrl
.UnknownGroupLinkVersionException e
) {
423 throw new Error
.Failure("Group link was created with an incompatible version: " + e
.getMessage());
424 } catch (IOException e
) {
425 throw new Error
.Failure(e
.getMessage());
430 public boolean isContactBlocked(final String number
) {
431 var contacts
= m
.getContacts();
432 for (var c
: contacts
) {
433 if (c
.number
.equals(number
)) {
441 public boolean isGroupBlocked(final byte[] groupId
) {
442 var group
= m
.getGroup(GroupId
.unknownVersion(groupId
));
446 return group
.isBlocked();
451 public boolean isMember(final byte[] groupId
) {
452 var group
= m
.getGroup(GroupId
.unknownVersion(groupId
));
456 return group
.isMember(m
.getSelfAddress());