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
.protocol
.IdentityInfo
;
12 import org
.asamk
.signal
.manager
.util
.Utils
;
13 import org
.asamk
.signal
.util
.ErrorUtils
;
14 import org
.freedesktop
.dbus
.exceptions
.DBusExecutionException
;
15 import org
.whispersystems
.libsignal
.util
.guava
.Optional
;
16 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupLinkNotActiveException
;
17 import org
.whispersystems
.signalservice
.api
.messages
.SendMessageResult
;
18 import org
.whispersystems
.signalservice
.api
.push
.SignalServiceAddress
;
19 import org
.whispersystems
.signalservice
.api
.util
.InvalidNumberException
;
22 import java
.io
.IOException
;
23 import java
.util
.ArrayList
;
24 import java
.util
.List
;
25 import java
.util
.Objects
;
26 import java
.util
.stream
.Collectors
;
27 import java
.util
.stream
.Stream
;
29 public class DbusSignalImpl
implements Signal
{
31 private final Manager m
;
33 public DbusSignalImpl(final Manager m
) {
38 public boolean isRemote() {
43 public String
getObjectPath() {
48 public long sendMessage(final String message
, final List
<String
> attachments
, final String recipient
) {
49 var recipients
= new ArrayList
<String
>(1);
50 recipients
.add(recipient
);
51 return sendMessage(message
, attachments
, recipients
);
54 private static void checkSendMessageResult(long timestamp
, SendMessageResult result
) throws DBusExecutionException
{
55 var error
= ErrorUtils
.getErrorMessageFromSendMessageResult(result
);
61 final var message
= timestamp
+ "\nFailed to send message:\n" + error
+ '\n';
63 if (result
.getIdentityFailure() != null) {
64 throw new Error
.UntrustedIdentity(message
);
66 throw new Error
.Failure(message
);
70 private static void checkSendMessageResults(
71 long timestamp
, List
<SendMessageResult
> results
72 ) throws DBusExecutionException
{
73 if (results
.size() == 1) {
74 checkSendMessageResult(timestamp
, results
.get(0));
78 var errors
= ErrorUtils
.getErrorMessagesFromSendMessageResults(results
);
79 if (errors
.size() == 0) {
83 var message
= new StringBuilder();
84 message
.append(timestamp
).append('\n');
85 message
.append("Failed to send (some) messages:\n");
86 for (var error
: errors
) {
87 message
.append(error
).append('\n');
90 throw new Error
.Failure(message
.toString());
94 public long sendMessage(final String message
, final List
<String
> attachments
, final List
<String
> recipients
) {
96 final var results
= m
.sendMessage(message
, attachments
, recipients
);
97 checkSendMessageResults(results
.first(), results
.second());
98 return results
.first();
99 } catch (InvalidNumberException e
) {
100 throw new Error
.InvalidNumber(e
.getMessage());
101 } catch (AttachmentInvalidException e
) {
102 throw new Error
.AttachmentInvalid(e
.getMessage());
103 } catch (IOException e
) {
104 throw new Error
.Failure(e
.getMessage());
109 public long sendNoteToSelfMessage(
110 final String message
, final List
<String
> attachments
111 ) throws Error
.AttachmentInvalid
, Error
.Failure
, Error
.UntrustedIdentity
{
113 final var results
= m
.sendSelfMessage(message
, attachments
);
114 checkSendMessageResult(results
.first(), results
.second());
115 return results
.first();
116 } catch (AttachmentInvalidException e
) {
117 throw new Error
.AttachmentInvalid(e
.getMessage());
118 } catch (IOException e
) {
119 throw new Error
.Failure(e
.getMessage());
124 public void sendEndSessionMessage(final List
<String
> recipients
) {
126 final var results
= m
.sendEndSessionMessage(recipients
);
127 checkSendMessageResults(results
.first(), results
.second());
128 } catch (IOException e
) {
129 throw new Error
.Failure(e
.getMessage());
130 } catch (InvalidNumberException e
) {
131 throw new Error
.InvalidNumber(e
.getMessage());
136 public long sendGroupMessage(final String message
, final List
<String
> attachments
, final byte[] groupId
) {
138 var results
= m
.sendGroupMessage(message
, attachments
, 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());
145 } catch (AttachmentInvalidException e
) {
146 throw new Error
.AttachmentInvalid(e
.getMessage());
150 // Since contact names might be empty if not defined, also potentially return
153 public String
getContactName(final String number
) {
156 name
= m
.getContactOrProfileName(number
);
157 } catch (Exception e
) {
158 throw new Error
.InvalidNumber(e
.getMessage());
164 public void setContactName(final String number
, final String name
) {
166 m
.setContactName(number
, name
);
167 } catch (InvalidNumberException e
) {
168 throw new Error
.InvalidNumber(e
.getMessage());
173 public void setContactBlocked(final String number
, final boolean blocked
) {
175 m
.setContactBlocked(number
, blocked
);
176 } catch (InvalidNumberException e
) {
177 throw new Error
.InvalidNumber(e
.getMessage());
182 public void setGroupBlocked(final byte[] groupId
, final boolean blocked
) {
184 m
.setGroupBlocked(GroupId
.unknownVersion(groupId
), blocked
);
185 } catch (GroupNotFoundException e
) {
186 throw new Error
.GroupNotFound(e
.getMessage());
191 public List
<byte[]> getGroupIds() {
192 var groups
= m
.getGroups();
193 var ids
= new ArrayList
<byte[]>(groups
.size());
194 for (var group
: groups
) {
195 ids
.add(group
.getGroupId().serialize());
201 public String
getGroupName(final byte[] groupId
) {
202 var group
= m
.getGroup(GroupId
.unknownVersion(groupId
));
206 return group
.getTitle();
211 public List
<String
> getGroupMembers(final byte[] groupId
) {
212 var group
= m
.getGroup(GroupId
.unknownVersion(groupId
));
216 return group
.getMembers()
218 .map(m
::resolveSignalServiceAddress
)
219 .map(SignalServiceAddress
::getLegacyIdentifier
)
220 .collect(Collectors
.toList());
225 public byte[] updateGroup(byte[] groupId
, String name
, List
<String
> members
, String avatar
) {
227 if (groupId
.length
== 0) {
230 if (name
.isEmpty()) {
233 if (members
.isEmpty()) {
236 if (avatar
.isEmpty()) {
239 final var results
= m
.updateGroup(groupId
== null ?
null : GroupId
.unknownVersion(groupId
),
242 avatar
== null ?
null : new File(avatar
));
243 checkSendMessageResults(0, results
.second());
244 return results
.first().serialize();
245 } catch (IOException e
) {
246 throw new Error
.Failure(e
.getMessage());
247 } catch (GroupNotFoundException
| NotAGroupMemberException e
) {
248 throw new Error
.GroupNotFound(e
.getMessage());
249 } catch (InvalidNumberException e
) {
250 throw new Error
.InvalidNumber(e
.getMessage());
251 } catch (AttachmentInvalidException e
) {
252 throw new Error
.AttachmentInvalid(e
.getMessage());
257 public boolean isRegistered() {
262 public void updateProfile(
265 final String aboutEmoji
,
267 final boolean removeAvatar
270 if (avatarPath
.isEmpty()) {
273 Optional
<File
> avatarFile
= removeAvatar
275 : avatarPath
== null ?
null : Optional
.of(new File(avatarPath
));
276 m
.setProfile(name
, about
, aboutEmoji
, avatarFile
);
277 } catch (IOException e
) {
278 throw new Error
.Failure(e
.getMessage());
282 // Provide option to query a version string in order to react on potential
283 // future interface changes
285 public String
version() {
286 return BaseConfig
.PROJECT_VERSION
;
289 // Create a unique list of Numbers from Identities and Contacts to really get
290 // all numbers the system knows
292 public List
<String
> listNumbers() {
293 return Stream
.concat(m
.getIdentities().stream().map(i
-> i
.getAddress().getNumber().orNull()),
294 m
.getContacts().stream().map(c
-> c
.number
))
295 .filter(Objects
::nonNull
)
297 .collect(Collectors
.toList());
301 public List
<String
> getContactNumber(final String name
) {
302 // Contact names have precendence.
303 List
<String
> numbers
= new ArrayList
<>();
304 var contacts
= m
.getContacts();
305 for (var c
: contacts
) {
306 if (c
.name
!= null && c
.name
.equals(name
)) {
307 numbers
.add(c
.number
);
310 // Try profiles if no contact name was found
311 for (IdentityInfo identity
: m
.getIdentities()) {
312 String number
= identity
.getAddress().getNumber().orNull();
313 if (number
!= null) {
314 var address
= Utils
.getSignalServiceAddressFromIdentifier(number
);
315 var profile
= m
.getRecipientProfile(address
);
316 String profileName
= profile
.getDisplayName();
317 if (profileName
.equals(name
)) {
322 if (numbers
.size() == 0) {
323 throw new Error
.Failure("Contact name not found");
329 public void quitGroup(final byte[] groupId
) {
330 var group
= GroupId
.unknownVersion(groupId
);
332 m
.sendQuitGroupMessage(group
);
333 } catch (GroupNotFoundException
| NotAGroupMemberException e
) {
334 throw new Error
.GroupNotFound(e
.getMessage());
335 } catch (IOException e
) {
336 throw new Error
.Failure(e
.getMessage());
341 public void joinGroup(final String groupLink
) {
342 final GroupInviteLinkUrl linkUrl
;
344 linkUrl
= GroupInviteLinkUrl
.fromUri(groupLink
);
345 m
.joinGroup(linkUrl
);
346 } catch (GroupInviteLinkUrl
.InvalidGroupLinkException
| GroupLinkNotActiveException e
) {
347 throw new Error
.Failure("Group link is invalid: " + e
.getMessage());
348 } catch (GroupInviteLinkUrl
.UnknownGroupLinkVersionException e
) {
349 throw new Error
.Failure("Group link was created with an incompatible version: " + e
.getMessage());
350 } catch (IOException e
) {
351 throw new Error
.Failure(e
.getMessage());
356 public boolean isContactBlocked(final String number
) {
357 var contacts
= m
.getContacts();
358 for (var c
: contacts
) {
359 if (c
.number
.equals(number
)) {
367 public boolean isGroupBlocked(final byte[] groupId
) {
368 var group
= m
.getGroup(GroupId
.unknownVersion(groupId
));
372 return group
.isBlocked();
377 public boolean isMember(final byte[] groupId
) {
378 var group
= m
.getGroup(GroupId
.unknownVersion(groupId
));
382 return group
.isMember(m
.getSelfAddress());