1 package org
.asamk
.signal
.manager
.helper
;
3 import org
.asamk
.signal
.manager
.SignalDependencies
;
4 import org
.asamk
.signal
.manager
.groups
.GroupId
;
5 import org
.asamk
.signal
.manager
.groups
.GroupNotFoundException
;
6 import org
.asamk
.signal
.manager
.groups
.GroupUtils
;
7 import org
.asamk
.signal
.manager
.groups
.NotAGroupMemberException
;
8 import org
.asamk
.signal
.manager
.storage
.SignalAccount
;
9 import org
.asamk
.signal
.manager
.storage
.groups
.GroupInfo
;
10 import org
.asamk
.signal
.manager
.storage
.recipients
.RecipientId
;
11 import org
.asamk
.signal
.manager
.storage
.recipients
.RecipientResolver
;
12 import org
.slf4j
.Logger
;
13 import org
.slf4j
.LoggerFactory
;
14 import org
.whispersystems
.libsignal
.util
.Pair
;
15 import org
.whispersystems
.libsignal
.util
.guava
.Optional
;
16 import org
.whispersystems
.signalservice
.api
.crypto
.ContentHint
;
17 import org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException
;
18 import org
.whispersystems
.signalservice
.api
.messages
.SendMessageResult
;
19 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceDataMessage
;
20 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceReceiptMessage
;
21 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceTypingMessage
;
22 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SentTranscriptMessage
;
23 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SignalServiceSyncMessage
;
24 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.UnregisteredUserException
;
26 import java
.io
.IOException
;
27 import java
.util
.ArrayList
;
28 import java
.util
.List
;
31 import java
.util
.stream
.Collectors
;
33 public class SendHelper
{
35 private final static Logger logger
= LoggerFactory
.getLogger(SendHelper
.class);
37 private final SignalAccount account
;
38 private final SignalDependencies dependencies
;
39 private final UnidentifiedAccessHelper unidentifiedAccessHelper
;
40 private final SignalServiceAddressResolver addressResolver
;
41 private final RecipientResolver recipientResolver
;
42 private final IdentityFailureHandler identityFailureHandler
;
43 private final GroupProvider groupProvider
;
44 private final RecipientRegistrationRefresher recipientRegistrationRefresher
;
47 final SignalAccount account
,
48 final SignalDependencies dependencies
,
49 final UnidentifiedAccessHelper unidentifiedAccessHelper
,
50 final SignalServiceAddressResolver addressResolver
,
51 final RecipientResolver recipientResolver
,
52 final IdentityFailureHandler identityFailureHandler
,
53 final GroupProvider groupProvider
,
54 final RecipientRegistrationRefresher recipientRegistrationRefresher
56 this.account
= account
;
57 this.dependencies
= dependencies
;
58 this.unidentifiedAccessHelper
= unidentifiedAccessHelper
;
59 this.addressResolver
= addressResolver
;
60 this.recipientResolver
= recipientResolver
;
61 this.identityFailureHandler
= identityFailureHandler
;
62 this.groupProvider
= groupProvider
;
63 this.recipientRegistrationRefresher
= recipientRegistrationRefresher
;
67 * Send a single message to one or multiple recipients.
68 * The message is extended with the current expiration timer for each recipient.
70 public Pair
<Long
, List
<SendMessageResult
>> sendMessage(
71 SignalServiceDataMessage
.Builder messageBuilder
, Set
<RecipientId
> recipientIds
72 ) throws IOException
{
73 // Send to all individually, so sync messages are sent correctly
74 messageBuilder
.withProfileKey(account
.getProfileKey().serialize());
75 var results
= new ArrayList
<SendMessageResult
>(recipientIds
.size());
77 for (var recipientId
: recipientIds
) {
78 final var contact
= account
.getContactStore().getContact(recipientId
);
79 final var expirationTime
= contact
!= null ? contact
.getMessageExpirationTime() : 0;
80 messageBuilder
.withExpiration(expirationTime
);
82 final var singleMessage
= messageBuilder
.build();
83 timestamp
= singleMessage
.getTimestamp();
84 final var result
= sendMessage(singleMessage
, recipientId
);
85 handlePossibleIdentityFailure(result
);
89 return new Pair
<>(timestamp
, results
);
93 * Send a group message to the given group
94 * The message is extended with the current expiration timer for the group and the group context.
96 public Pair
<Long
, List
<SendMessageResult
>> sendAsGroupMessage(
97 SignalServiceDataMessage
.Builder messageBuilder
, GroupId groupId
98 ) throws IOException
, GroupNotFoundException
, NotAGroupMemberException
{
99 final var g
= getGroupForSending(groupId
);
100 GroupUtils
.setGroupContext(messageBuilder
, g
);
101 messageBuilder
.withExpiration(g
.getMessageExpirationTime());
103 final var recipients
= g
.getMembersWithout(account
.getSelfRecipientId());
104 return sendGroupMessage(messageBuilder
.build(), recipients
);
108 * Send a complete group message to the given recipients (should be current/old/new members)
109 * This method should only be used for create/update/quit group messages.
111 public Pair
<Long
, List
<SendMessageResult
>> sendGroupMessage(
112 final SignalServiceDataMessage message
, final Set
<RecipientId
> recipientIds
113 ) throws IOException
{
114 List
<SendMessageResult
> result
= sendGroupMessageInternal(message
, recipientIds
);
116 for (var r
: result
) {
117 handlePossibleIdentityFailure(r
);
120 return new Pair
<>(message
.getTimestamp(), result
);
123 public void sendReceiptMessage(
124 final SignalServiceReceiptMessage receiptMessage
, final RecipientId recipientId
125 ) throws IOException
, UntrustedIdentityException
{
126 final var messageSender
= dependencies
.getMessageSender();
127 messageSender
.sendReceipt(addressResolver
.resolveSignalServiceAddress(recipientId
),
128 unidentifiedAccessHelper
.getAccessFor(recipientId
),
132 public SendMessageResult
sendNullMessage(RecipientId recipientId
) throws IOException
{
133 var messageSender
= dependencies
.getMessageSender();
135 final var address
= addressResolver
.resolveSignalServiceAddress(recipientId
);
138 return messageSender
.sendNullMessage(address
, unidentifiedAccessHelper
.getAccessFor(recipientId
));
139 } catch (UnregisteredUserException e
) {
140 final var newRecipientId
= recipientRegistrationRefresher
.refreshRecipientRegistration(recipientId
);
141 final var newAddress
= addressResolver
.resolveSignalServiceAddress(newRecipientId
);
142 return messageSender
.sendNullMessage(newAddress
, unidentifiedAccessHelper
.getAccessFor(newRecipientId
));
144 } catch (UntrustedIdentityException e
) {
145 return SendMessageResult
.identityFailure(address
, e
.getIdentityKey());
149 public Pair
<Long
, SendMessageResult
> sendSelfMessage(
150 SignalServiceDataMessage
.Builder messageBuilder
151 ) throws IOException
{
152 final var recipientId
= account
.getSelfRecipientId();
153 final var contact
= account
.getContactStore().getContact(recipientId
);
154 final var expirationTime
= contact
!= null ? contact
.getMessageExpirationTime() : 0;
155 messageBuilder
.withExpiration(expirationTime
);
157 var message
= messageBuilder
.build();
158 final var result
= sendSelfMessage(message
);
159 return new Pair
<>(message
.getTimestamp(), result
);
162 public SendMessageResult
sendSyncMessage(SignalServiceSyncMessage message
) throws IOException
{
163 var messageSender
= dependencies
.getMessageSender();
165 return messageSender
.sendSyncMessage(message
, unidentifiedAccessHelper
.getAccessForSync());
166 } catch (UntrustedIdentityException e
) {
167 var address
= addressResolver
.resolveSignalServiceAddress(account
.getSelfRecipientId());
168 return SendMessageResult
.identityFailure(address
, e
.getIdentityKey());
172 public void sendTypingMessage(
173 SignalServiceTypingMessage message
, Set
<RecipientId
> recipientIds
174 ) throws IOException
, UntrustedIdentityException
{
175 var messageSender
= dependencies
.getMessageSender();
176 for (var recipientId
: recipientIds
) {
177 final var address
= addressResolver
.resolveSignalServiceAddress(recipientId
);
179 messageSender
.sendTyping(address
, unidentifiedAccessHelper
.getAccessFor(recipientId
), message
);
180 } catch (UnregisteredUserException e
) {
181 final var newRecipientId
= recipientRegistrationRefresher
.refreshRecipientRegistration(recipientId
);
182 final var newAddress
= addressResolver
.resolveSignalServiceAddress(newRecipientId
);
183 messageSender
.sendTyping(newAddress
, unidentifiedAccessHelper
.getAccessFor(newRecipientId
), message
);
188 public void sendGroupTypingMessage(
189 SignalServiceTypingMessage message
, GroupId groupId
190 ) throws IOException
, NotAGroupMemberException
, GroupNotFoundException
{
191 final var g
= getGroupForSending(groupId
);
192 final var messageSender
= dependencies
.getMessageSender();
193 final var recipientIdList
= new ArrayList
<>(g
.getMembersWithout(account
.getSelfRecipientId()));
194 final var addresses
= recipientIdList
.stream()
195 .map(addressResolver
::resolveSignalServiceAddress
)
196 .collect(Collectors
.toList());
197 messageSender
.sendTyping(addresses
, unidentifiedAccessHelper
.getAccessFor(recipientIdList
), message
, null);
200 private GroupInfo
getGroupForSending(GroupId groupId
) throws GroupNotFoundException
, NotAGroupMemberException
{
201 var g
= groupProvider
.getGroup(groupId
);
203 throw new GroupNotFoundException(groupId
);
205 if (!g
.isMember(account
.getSelfRecipientId())) {
206 throw new NotAGroupMemberException(groupId
, g
.getTitle());
211 private List
<SendMessageResult
> sendGroupMessageInternal(
212 final SignalServiceDataMessage message
, final Set
<RecipientId
> recipientIds
213 ) throws IOException
{
215 var messageSender
= dependencies
.getMessageSender();
216 // isRecipientUpdate is true if we've already sent this message to some recipients in the past, otherwise false.
217 final var isRecipientUpdate
= false;
218 final var recipientIdList
= new ArrayList
<>(recipientIds
);
219 final var addresses
= recipientIdList
.stream()
220 .map(addressResolver
::resolveSignalServiceAddress
)
221 .collect(Collectors
.toList());
222 return messageSender
.sendDataMessage(addresses
,
223 unidentifiedAccessHelper
.getAccessFor(recipientIdList
),
227 sendResult
-> logger
.trace("Partial message send result: {}", sendResult
.isSuccess()),
229 } catch (UntrustedIdentityException e
) {
234 private SendMessageResult
sendMessage(
235 SignalServiceDataMessage message
, RecipientId recipientId
236 ) throws IOException
{
237 var messageSender
= dependencies
.getMessageSender();
239 final var address
= addressResolver
.resolveSignalServiceAddress(recipientId
);
242 return messageSender
.sendDataMessage(address
,
243 unidentifiedAccessHelper
.getAccessFor(recipientId
),
246 } catch (UnregisteredUserException e
) {
247 final var newRecipientId
= recipientRegistrationRefresher
.refreshRecipientRegistration(recipientId
);
248 return messageSender
.sendDataMessage(addressResolver
.resolveSignalServiceAddress(newRecipientId
),
249 unidentifiedAccessHelper
.getAccessFor(newRecipientId
),
253 } catch (UntrustedIdentityException e
) {
254 return SendMessageResult
.identityFailure(address
, e
.getIdentityKey());
258 private SendMessageResult
sendSelfMessage(SignalServiceDataMessage message
) throws IOException
{
259 var address
= account
.getSelfAddress();
260 var transcript
= new SentTranscriptMessage(Optional
.of(address
),
261 message
.getTimestamp(),
263 message
.getExpiresInSeconds(),
264 Map
.of(address
, true),
266 var syncMessage
= SignalServiceSyncMessage
.forSentTranscript(transcript
);
268 return sendSyncMessage(syncMessage
);
271 private void handlePossibleIdentityFailure(final SendMessageResult r
) {
272 if (r
.getIdentityFailure() != null) {
273 final var recipientId
= recipientResolver
.resolveRecipient(r
.getAddress());
274 identityFailureHandler
.handleIdentityFailure(recipientId
, r
.getIdentityFailure());