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
.api
.Message
;
9 import org
.asamk
.signal
.manager
.groups
.GroupId
;
10 import org
.asamk
.signal
.manager
.groups
.GroupInviteLinkUrl
;
11 import org
.asamk
.signal
.manager
.groups
.GroupNotFoundException
;
12 import org
.asamk
.signal
.manager
.groups
.LastGroupAdminException
;
13 import org
.asamk
.signal
.manager
.groups
.NotAGroupMemberException
;
14 import org
.asamk
.signal
.manager
.storage
.identities
.IdentityInfo
;
15 import org
.asamk
.signal
.util
.ErrorUtils
;
16 import org
.asamk
.signal
.util
.Util
;
17 import org
.freedesktop
.dbus
.exceptions
.DBusExecutionException
;
18 import org
.whispersystems
.libsignal
.util
.Pair
;
19 import org
.whispersystems
.libsignal
.util
.guava
.Optional
;
20 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupLinkNotActiveException
;
21 import org
.whispersystems
.signalservice
.api
.messages
.SendMessageResult
;
22 import org
.whispersystems
.signalservice
.api
.util
.InvalidNumberException
;
25 import java
.io
.IOException
;
26 import java
.util
.ArrayList
;
27 import java
.util
.List
;
28 import java
.util
.Objects
;
30 import java
.util
.stream
.Collectors
;
31 import java
.util
.stream
.Stream
;
33 import static org
.asamk
.signal
.util
.Util
.getLegacyIdentifier
;
35 public class DbusSignalImpl
implements Signal
{
37 private final Manager m
;
38 private final String objectPath
;
40 public DbusSignalImpl(final Manager m
, final String objectPath
) {
42 this.objectPath
= objectPath
;
46 public boolean isRemote() {
51 public String
getObjectPath() {
56 public long sendMessage(final String message
, final List
<String
> attachments
, final String recipient
) {
57 var recipients
= new ArrayList
<String
>(1);
58 recipients
.add(recipient
);
59 return sendMessage(message
, attachments
, recipients
);
62 private static void checkSendMessageResult(long timestamp
, SendMessageResult result
) throws DBusExecutionException
{
63 var error
= ErrorUtils
.getErrorMessageFromSendMessageResult(result
);
69 final var message
= timestamp
+ "\nFailed to send message:\n" + error
+ '\n';
71 if (result
.getIdentityFailure() != null) {
72 throw new Error
.UntrustedIdentity(message
);
74 throw new Error
.Failure(message
);
78 private static void checkSendMessageResults(
79 long timestamp
, List
<SendMessageResult
> results
80 ) throws DBusExecutionException
{
81 if (results
.size() == 1) {
82 checkSendMessageResult(timestamp
, results
.get(0));
86 var errors
= ErrorUtils
.getErrorMessagesFromSendMessageResults(results
);
87 if (errors
.size() == 0) {
91 var message
= new StringBuilder();
92 message
.append(timestamp
).append('\n');
93 message
.append("Failed to send (some) messages:\n");
94 for (var error
: errors
) {
95 message
.append(error
).append('\n');
98 throw new Error
.Failure(message
.toString());
102 public long sendMessage(final String message
, final List
<String
> attachments
, final List
<String
> recipients
) {
104 final var results
= m
.sendMessage(new Message(message
, attachments
), recipients
);
105 checkSendMessageResults(results
.first(), results
.second());
106 return results
.first();
107 } catch (InvalidNumberException e
) {
108 throw new Error
.InvalidNumber(e
.getMessage());
109 } catch (AttachmentInvalidException e
) {
110 throw new Error
.AttachmentInvalid(e
.getMessage());
111 } catch (IOException e
) {
112 throw new Error
.Failure(e
.getMessage());
117 public long sendRemoteDeleteMessage(
118 final long targetSentTimestamp
, final String recipient
120 var recipients
= new ArrayList
<String
>(1);
121 recipients
.add(recipient
);
122 return sendRemoteDeleteMessage(targetSentTimestamp
, recipients
);
126 public long sendRemoteDeleteMessage(
127 final long targetSentTimestamp
, final List
<String
> recipients
130 final var results
= m
.sendRemoteDeleteMessage(targetSentTimestamp
, recipients
);
131 checkSendMessageResults(results
.first(), results
.second());
132 return results
.first();
133 } catch (IOException e
) {
134 throw new Error
.Failure(e
.getMessage());
135 } catch (InvalidNumberException e
) {
136 throw new Error
.InvalidNumber(e
.getMessage());
141 public long sendGroupRemoteDeleteMessage(
142 final long targetSentTimestamp
, final byte[] groupId
145 final var results
= m
.sendGroupRemoteDeleteMessage(targetSentTimestamp
, GroupId
.unknownVersion(groupId
));
146 checkSendMessageResults(results
.first(), results
.second());
147 return results
.first();
148 } catch (IOException e
) {
149 throw new Error
.Failure(e
.getMessage());
150 } catch (GroupNotFoundException
| NotAGroupMemberException e
) {
151 throw new Error
.GroupNotFound(e
.getMessage());
156 public long sendMessageReaction(
158 final boolean remove
,
159 final String targetAuthor
,
160 final long targetSentTimestamp
,
161 final String recipient
163 var recipients
= new ArrayList
<String
>(1);
164 recipients
.add(recipient
);
165 return sendMessageReaction(emoji
, remove
, targetAuthor
, targetSentTimestamp
, recipients
);
169 public long sendMessageReaction(
171 final boolean remove
,
172 final String targetAuthor
,
173 final long targetSentTimestamp
,
174 final List
<String
> recipients
177 final var results
= m
.sendMessageReaction(emoji
, remove
, targetAuthor
, targetSentTimestamp
, recipients
);
178 checkSendMessageResults(results
.first(), results
.second());
179 return results
.first();
180 } catch (InvalidNumberException e
) {
181 throw new Error
.InvalidNumber(e
.getMessage());
182 } catch (IOException e
) {
183 throw new Error
.Failure(e
.getMessage());
188 public long sendNoteToSelfMessage(
189 final String message
, final List
<String
> attachments
190 ) throws Error
.AttachmentInvalid
, Error
.Failure
, Error
.UntrustedIdentity
{
192 final var results
= m
.sendSelfMessage(new Message(message
, attachments
));
193 checkSendMessageResult(results
.first(), results
.second());
194 return results
.first();
195 } catch (AttachmentInvalidException e
) {
196 throw new Error
.AttachmentInvalid(e
.getMessage());
197 } catch (IOException e
) {
198 throw new Error
.Failure(e
.getMessage());
203 public void sendEndSessionMessage(final List
<String
> recipients
) {
205 final var results
= m
.sendEndSessionMessage(recipients
);
206 checkSendMessageResults(results
.first(), results
.second());
207 } catch (IOException e
) {
208 throw new Error
.Failure(e
.getMessage());
209 } catch (InvalidNumberException e
) {
210 throw new Error
.InvalidNumber(e
.getMessage());
215 public long sendGroupMessage(final String message
, final List
<String
> attachments
, final byte[] groupId
) {
217 var results
= m
.sendGroupMessage(new Message(message
, attachments
), GroupId
.unknownVersion(groupId
));
218 checkSendMessageResults(results
.first(), results
.second());
219 return results
.first();
220 } catch (IOException e
) {
221 throw new Error
.Failure(e
.getMessage());
222 } catch (GroupNotFoundException
| NotAGroupMemberException e
) {
223 throw new Error
.GroupNotFound(e
.getMessage());
224 } catch (AttachmentInvalidException e
) {
225 throw new Error
.AttachmentInvalid(e
.getMessage());
230 public long sendGroupMessageReaction(
232 final boolean remove
,
233 final String targetAuthor
,
234 final long targetSentTimestamp
,
238 final var results
= m
.sendGroupMessageReaction(emoji
,
242 GroupId
.unknownVersion(groupId
));
243 checkSendMessageResults(results
.first(), results
.second());
244 return results
.first();
245 } catch (IOException e
) {
246 throw new Error
.Failure(e
.getMessage());
247 } catch (InvalidNumberException e
) {
248 throw new Error
.InvalidNumber(e
.getMessage());
249 } catch (GroupNotFoundException
| NotAGroupMemberException e
) {
250 throw new Error
.GroupNotFound(e
.getMessage());
254 // Since contact names might be empty if not defined, also potentially return
257 public String
getContactName(final String number
) {
259 return m
.getContactOrProfileName(number
);
260 } catch (InvalidNumberException e
) {
261 throw new Error
.InvalidNumber(e
.getMessage());
266 public void setContactName(final String number
, final String name
) {
268 m
.setContactName(number
, name
);
269 } catch (InvalidNumberException e
) {
270 throw new Error
.InvalidNumber(e
.getMessage());
271 } catch (NotMasterDeviceException e
) {
272 throw new Error
.Failure("This command doesn't work on linked devices.");
277 public void setContactBlocked(final String number
, final boolean blocked
) {
279 m
.setContactBlocked(number
, blocked
);
280 } catch (InvalidNumberException e
) {
281 throw new Error
.InvalidNumber(e
.getMessage());
282 } catch (NotMasterDeviceException e
) {
283 throw new Error
.Failure("This command doesn't work on linked devices.");
288 public void setGroupBlocked(final byte[] groupId
, final boolean blocked
) {
290 m
.setGroupBlocked(GroupId
.unknownVersion(groupId
), blocked
);
291 } catch (GroupNotFoundException e
) {
292 throw new Error
.GroupNotFound(e
.getMessage());
297 public List
<byte[]> getGroupIds() {
298 var groups
= m
.getGroups();
299 var ids
= new ArrayList
<byte[]>(groups
.size());
300 for (var group
: groups
) {
301 ids
.add(group
.getGroupId().serialize());
307 public String
getGroupName(final byte[] groupId
) {
308 var group
= m
.getGroup(GroupId
.unknownVersion(groupId
));
312 return group
.getTitle();
317 public List
<String
> getGroupMembers(final byte[] groupId
) {
318 var group
= m
.getGroup(GroupId
.unknownVersion(groupId
));
322 return group
.getMembers()
324 .map(m
::resolveSignalServiceAddress
)
325 .map(Util
::getLegacyIdentifier
)
326 .collect(Collectors
.toList());
331 public byte[] updateGroup(byte[] groupId
, String name
, List
<String
> members
, String avatar
) {
333 if (groupId
.length
== 0) {
336 if (name
.isEmpty()) {
339 if (members
.isEmpty()) {
342 if (avatar
.isEmpty()) {
345 if (groupId
== null) {
346 final var results
= m
.createGroup(name
, members
, avatar
== null ?
null : new File(avatar
));
347 checkSendMessageResults(0, results
.second());
348 return results
.first().serialize();
350 final var results
= m
.updateGroup(GroupId
.unknownVersion(groupId
),
361 avatar
== null ?
null : new File(avatar
),
364 if (results
!= null) {
365 checkSendMessageResults(results
.first(), results
.second());
369 } catch (IOException e
) {
370 throw new Error
.Failure(e
.getMessage());
371 } catch (GroupNotFoundException
| NotAGroupMemberException e
) {
372 throw new Error
.GroupNotFound(e
.getMessage());
373 } catch (InvalidNumberException e
) {
374 throw new Error
.InvalidNumber(e
.getMessage());
375 } catch (AttachmentInvalidException e
) {
376 throw new Error
.AttachmentInvalid(e
.getMessage());
381 public boolean isRegistered() {
386 public void updateProfile(
389 final String aboutEmoji
,
391 final boolean removeAvatar
394 if (avatarPath
.isEmpty()) {
397 Optional
<File
> avatarFile
= removeAvatar
399 : avatarPath
== null ?
null : Optional
.of(new File(avatarPath
));
400 m
.setProfile(name
, null, about
, aboutEmoji
, avatarFile
);
401 } catch (IOException e
) {
402 throw new Error
.Failure(e
.getMessage());
406 // Provide option to query a version string in order to react on potential
407 // future interface changes
409 public String
version() {
410 return BaseConfig
.PROJECT_VERSION
;
413 // Create a unique list of Numbers from Identities and Contacts to really get
414 // all numbers the system knows
416 public List
<String
> listNumbers() {
417 return Stream
.concat(m
.getIdentities().stream().map(IdentityInfo
::getRecipientId
),
418 m
.getContacts().stream().map(Pair
::first
))
419 .map(m
::resolveSignalServiceAddress
)
420 .map(a
-> a
.getNumber().orNull())
421 .filter(Objects
::nonNull
)
423 .collect(Collectors
.toList());
427 public List
<String
> getContactNumber(final String name
) {
428 // Contact names have precedence.
429 var numbers
= new ArrayList
<String
>();
430 var contacts
= m
.getContacts();
431 for (var c
: contacts
) {
432 if (name
.equals(c
.second().getName())) {
433 numbers
.add(getLegacyIdentifier(m
.resolveSignalServiceAddress(c
.first())));
436 // Try profiles if no contact name was found
437 for (var identity
: m
.getIdentities()) {
438 final var recipientId
= identity
.getRecipientId();
439 final var address
= m
.resolveSignalServiceAddress(recipientId
);
440 var number
= address
.getNumber().orNull();
441 if (number
!= null) {
442 var profile
= m
.getRecipientProfile(recipientId
);
443 if (profile
!= null && profile
.getDisplayName().equals(name
)) {
452 public void quitGroup(final byte[] groupId
) {
453 var group
= GroupId
.unknownVersion(groupId
);
455 m
.sendQuitGroupMessage(group
, Set
.of());
456 } catch (GroupNotFoundException
| NotAGroupMemberException e
) {
457 throw new Error
.GroupNotFound(e
.getMessage());
458 } catch (IOException
| LastGroupAdminException e
) {
459 throw new Error
.Failure(e
.getMessage());
460 } catch (InvalidNumberException e
) {
461 throw new Error
.InvalidNumber(e
.getMessage());
466 public void joinGroup(final String groupLink
) {
468 final var linkUrl
= GroupInviteLinkUrl
.fromUri(groupLink
);
469 if (linkUrl
== null) {
470 throw new Error
.Failure("Group link is invalid:");
472 m
.joinGroup(linkUrl
);
473 } catch (GroupInviteLinkUrl
.InvalidGroupLinkException
| GroupLinkNotActiveException e
) {
474 throw new Error
.Failure("Group link is invalid: " + e
.getMessage());
475 } catch (GroupInviteLinkUrl
.UnknownGroupLinkVersionException e
) {
476 throw new Error
.Failure("Group link was created with an incompatible version: " + e
.getMessage());
477 } catch (IOException e
) {
478 throw new Error
.Failure(e
.getMessage());
483 public boolean isContactBlocked(final String number
) {
485 return m
.isContactBlocked(number
);
486 } catch (InvalidNumberException e
) {
487 throw new Error
.InvalidNumber(e
.getMessage());
492 public boolean isGroupBlocked(final byte[] groupId
) {
493 var group
= m
.getGroup(GroupId
.unknownVersion(groupId
));
497 return group
.isBlocked();
502 public boolean isMember(final byte[] groupId
) {
503 var group
= m
.getGroup(GroupId
.unknownVersion(groupId
));
507 return group
.isMember(m
.getSelfRecipientId());