1 package org
.asamk
.signal
.dbus
;
3 import org
.asamk
.Signal
;
4 import org
.asamk
.signal
.manager
.AttachmentInvalidException
;
5 import org
.asamk
.signal
.manager
.Manager
;
6 import org
.asamk
.signal
.manager
.groups
.GroupId
;
7 import org
.asamk
.signal
.manager
.groups
.GroupNotFoundException
;
8 import org
.asamk
.signal
.manager
.groups
.NotAGroupMemberException
;
9 import org
.asamk
.signal
.manager
.groups
.GroupInviteLinkUrl
;
10 import org
.asamk
.signal
.manager
.storage
.protocol
.IdentityInfo
;
11 import org
.asamk
.signal
.util
.ErrorUtils
;
12 import org
.asamk
.signal
.manager
.util
.Utils
;
13 import org
.asamk
.signal
.BaseConfig
;
14 import org
.freedesktop
.dbus
.exceptions
.DBusExecutionException
;
15 import org
.whispersystems
.libsignal
.util
.guava
.Optional
;
16 import org
.whispersystems
.signalservice
.api
.messages
.SendMessageResult
;
17 import org
.whispersystems
.signalservice
.api
.push
.SignalServiceAddress
;
18 import org
.whispersystems
.signalservice
.api
.util
.InvalidNumberException
;
19 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupLinkNotActiveException
;
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
.Stream
;
27 import java
.util
.stream
.Collectors
;
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(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 sendNoteToSelfMessage(final String message
, final List
<String
> attachments
)
109 throws Error
.AttachmentInvalid
, Error
.Failure
, Error
.UntrustedIdentity
{
111 final var results
= m
.sendSelfMessage(message
, attachments
);
112 checkSendMessageResult(results
.first(), results
.second());
113 return results
.first();
114 } catch (AttachmentInvalidException e
) {
115 throw new Error
.AttachmentInvalid(e
.getMessage());
116 } catch (IOException e
) {
117 throw new Error
.Failure(e
.getMessage());
122 public void sendEndSessionMessage(final List
<String
> recipients
) {
124 final var results
= m
.sendEndSessionMessage(recipients
);
125 checkSendMessageResults(results
.first(), results
.second());
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 sendGroupMessage(final String message
, final List
<String
> attachments
, final byte[] groupId
) {
136 var results
= m
.sendGroupMessage(message
, attachments
, 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());
143 } catch (AttachmentInvalidException e
) {
144 throw new Error
.AttachmentInvalid(e
.getMessage());
148 // Since contact names might be empty if not defined, also potentially return
151 public String
getContactName(final String number
) {
154 name
= m
.getContactOrProfileName(number
);
155 } catch (Exception e
) {
156 throw new Error
.InvalidNumber(e
.getMessage());
162 public void setContactName(final String number
, final String name
) {
164 m
.setContactName(number
, name
);
165 } catch (InvalidNumberException e
) {
166 throw new Error
.InvalidNumber(e
.getMessage());
171 public void setContactBlocked(final String number
, final boolean blocked
) {
173 m
.setContactBlocked(number
, blocked
);
174 } catch (InvalidNumberException e
) {
175 throw new Error
.InvalidNumber(e
.getMessage());
180 public void setGroupBlocked(final byte[] groupId
, final boolean blocked
) {
182 m
.setGroupBlocked(GroupId
.unknownVersion(groupId
), blocked
);
183 } catch (GroupNotFoundException e
) {
184 throw new Error
.GroupNotFound(e
.getMessage());
189 public List
<byte[]> getGroupIds() {
190 var groups
= m
.getGroups();
191 var ids
= new ArrayList
<byte[]>(groups
.size());
192 for (var group
: groups
) {
193 ids
.add(group
.getGroupId().serialize());
199 public String
getGroupName(final byte[] groupId
) {
200 var group
= m
.getGroup(GroupId
.unknownVersion(groupId
));
204 return group
.getTitle();
209 public List
<String
> getGroupMembers(final byte[] groupId
) {
210 var group
= m
.getGroup(GroupId
.unknownVersion(groupId
));
214 return group
.getMembers().stream().map(m
::resolveSignalServiceAddress
)
215 .map(SignalServiceAddress
::getLegacyIdentifier
).collect(Collectors
.toList());
220 public byte[] updateGroup(byte[] groupId
, String name
, List
<String
> members
, String avatar
) {
222 if (groupId
.length
== 0) {
225 if (name
.isEmpty()) {
228 if (members
.isEmpty()) {
231 if (avatar
.isEmpty()) {
234 final var results
= m
.updateGroup(groupId
== null ?
null : GroupId
.unknownVersion(groupId
), name
, members
,
235 avatar
== null ?
null : new File(avatar
));
236 checkSendMessageResults(0, results
.second());
237 return results
.first().serialize();
238 } catch (IOException e
) {
239 throw new Error
.Failure(e
.getMessage());
240 } catch (GroupNotFoundException
| NotAGroupMemberException e
) {
241 throw new Error
.GroupNotFound(e
.getMessage());
242 } catch (InvalidNumberException e
) {
243 throw new Error
.InvalidNumber(e
.getMessage());
244 } catch (AttachmentInvalidException e
) {
245 throw new Error
.AttachmentInvalid(e
.getMessage());
250 public boolean isRegistered() {
255 public void updateProfile(final String name
, final String about
, final String aboutEmoji
, String avatarPath
,
256 final boolean removeAvatar
) {
258 if (avatarPath
.isEmpty()) {
261 Optional
<File
> avatarFile
= removeAvatar ? Optional
.absent()
262 : avatarPath
== null ?
null : Optional
.of(new File(avatarPath
));
263 m
.setProfile(name
, about
, aboutEmoji
, avatarFile
);
264 } catch (IOException e
) {
265 throw new Error
.Failure(e
.getMessage());
269 // Provide option to query a version string in order to react on potential
270 // future interface changes
272 public String
version() {
273 return BaseConfig
.PROJECT_VERSION
;
276 // Create a unique list of Numbers from Identities and Contacts to really get
277 // all numbers the system knows
279 public List
<String
> listNumbers() {
280 return Stream
.concat(m
.getIdentities().stream().map(i
-> i
.getAddress().getNumber().orNull()),
281 m
.getContacts().stream().map(c
-> c
.number
))
282 .filter(Objects
::nonNull
)
284 .collect(Collectors
.toList());
288 public List
<String
> getContactNumber(final String name
) {
289 // Contact names have precendence.
290 List
<String
> numbers
=new ArrayList
<>();
291 var contacts
= m
.getContacts();
292 for (var c
: contacts
) {
293 if (c
.name
!=null && c
.name
.equals(name
)) {
294 numbers
.add(c
.number
);
297 // Try profiles if no contact name was found
298 for (IdentityInfo identity
: m
.getIdentities()) {
299 String number
= identity
.getAddress().getNumber().orNull();
300 if (number
!= null) {
301 var address
= Utils
.getSignalServiceAddressFromIdentifier(number
);
302 var profile
= m
.getRecipientProfile(address
);
303 String profileName
= profile
.getDisplayName();
304 if (profileName
.equals(name
)) {
309 if (numbers
.size()==0) {
310 throw new Error
.Failure("Contact name not found");
316 public void quitGroup(final byte[] groupId
) {
317 var group
= GroupId
.unknownVersion(groupId
);
319 m
.sendQuitGroupMessage(group
);
320 } catch (GroupNotFoundException
| NotAGroupMemberException e
) {
321 throw new Error
.GroupNotFound(e
.getMessage());
322 } catch (IOException e
) {
323 throw new Error
.Failure(e
.getMessage());
328 public void joinGroup(final String groupLink
) {
329 final GroupInviteLinkUrl linkUrl
;
331 linkUrl
= GroupInviteLinkUrl
.fromUri(groupLink
);
332 m
.joinGroup(linkUrl
);
333 } catch (GroupInviteLinkUrl
.InvalidGroupLinkException
| GroupLinkNotActiveException e
) {
334 throw new Error
.Failure("Group link is invalid: " + e
.getMessage());
335 } catch (GroupInviteLinkUrl
.UnknownGroupLinkVersionException e
) {
336 throw new Error
.Failure("Group link was created with an incompatible version: " + e
.getMessage());
337 } catch (IOException e
) {
338 throw new Error
.Failure(e
.getMessage());
343 public boolean isContactBlocked(final String number
) {
344 var contacts
= m
.getContacts();
345 for (var c
: contacts
) {
346 if (c
.number
.equals(number
)) {
354 public boolean isGroupBlocked(final byte[] groupId
) {
355 var group
= m
.getGroup(GroupId
.unknownVersion(groupId
));
359 return group
.isBlocked();
364 public boolean isMember(final byte[] groupId
) {
365 var group
= m
.getGroup(GroupId
.unknownVersion(groupId
));
369 return group
.isMember(m
.getSelfAddress());