1 package org
.asamk
.signal
.manager
.helper
;
3 import org
.asamk
.signal
.manager
.SignalDependencies
;
4 import org
.asamk
.signal
.manager
.UntrustedIdentityException
;
5 import org
.asamk
.signal
.manager
.groups
.GroupId
;
6 import org
.asamk
.signal
.manager
.groups
.GroupNotFoundException
;
7 import org
.asamk
.signal
.manager
.groups
.GroupSendingNotAllowedException
;
8 import org
.asamk
.signal
.manager
.groups
.GroupUtils
;
9 import org
.asamk
.signal
.manager
.groups
.NotAGroupMemberException
;
10 import org
.asamk
.signal
.manager
.storage
.SignalAccount
;
11 import org
.asamk
.signal
.manager
.storage
.groups
.GroupInfo
;
12 import org
.asamk
.signal
.manager
.storage
.recipients
.RecipientId
;
13 import org
.asamk
.signal
.manager
.storage
.recipients
.RecipientResolver
;
14 import org
.slf4j
.Logger
;
15 import org
.slf4j
.LoggerFactory
;
16 import org
.whispersystems
.libsignal
.util
.guava
.Optional
;
17 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageSender
;
18 import org
.whispersystems
.signalservice
.api
.crypto
.ContentHint
;
19 import org
.whispersystems
.signalservice
.api
.messages
.SendMessageResult
;
20 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceDataMessage
;
21 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceReceiptMessage
;
22 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceTypingMessage
;
23 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SentTranscriptMessage
;
24 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SignalServiceSyncMessage
;
25 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.UnregisteredUserException
;
27 import java
.io
.IOException
;
28 import java
.util
.ArrayList
;
29 import java
.util
.List
;
32 import java
.util
.stream
.Collectors
;
34 public class SendHelper
{
36 private final static Logger logger
= LoggerFactory
.getLogger(SendHelper
.class);
38 private final SignalAccount account
;
39 private final SignalDependencies dependencies
;
40 private final UnidentifiedAccessHelper unidentifiedAccessHelper
;
41 private final SignalServiceAddressResolver addressResolver
;
42 private final RecipientResolver recipientResolver
;
43 private final IdentityFailureHandler identityFailureHandler
;
44 private final GroupProvider groupProvider
;
45 private final RecipientRegistrationRefresher recipientRegistrationRefresher
;
47 private final SignalServiceMessageSender
.IndividualSendEvents sendEvents
= new SignalServiceMessageSender
.IndividualSendEvents() {
49 public void onMessageEncrypted() {
53 public void onMessageSent() {
57 public void onSyncMessageSent() {
61 private final SignalServiceMessageSender
.LegacyGroupEvents legacyGroupEvents
= new SignalServiceMessageSender
.LegacyGroupEvents() {
63 public void onMessageSent() {
67 public void onSyncMessageSent() {
72 final SignalAccount account
,
73 final SignalDependencies dependencies
,
74 final UnidentifiedAccessHelper unidentifiedAccessHelper
,
75 final SignalServiceAddressResolver addressResolver
,
76 final RecipientResolver recipientResolver
,
77 final IdentityFailureHandler identityFailureHandler
,
78 final GroupProvider groupProvider
,
79 final RecipientRegistrationRefresher recipientRegistrationRefresher
81 this.account
= account
;
82 this.dependencies
= dependencies
;
83 this.unidentifiedAccessHelper
= unidentifiedAccessHelper
;
84 this.addressResolver
= addressResolver
;
85 this.recipientResolver
= recipientResolver
;
86 this.identityFailureHandler
= identityFailureHandler
;
87 this.groupProvider
= groupProvider
;
88 this.recipientRegistrationRefresher
= recipientRegistrationRefresher
;
92 * Send a single message to one or multiple recipients.
93 * The message is extended with the current expiration timer for each recipient.
95 public SendMessageResult
sendMessage(
96 final SignalServiceDataMessage
.Builder messageBuilder
, final RecipientId recipientId
97 ) throws IOException
{
98 final var contact
= account
.getContactStore().getContact(recipientId
);
99 final var expirationTime
= contact
!= null ? contact
.getMessageExpirationTime() : 0;
100 messageBuilder
.withExpiration(expirationTime
);
101 messageBuilder
.withProfileKey(account
.getProfileKey().serialize());
103 final var message
= messageBuilder
.build();
104 final var result
= sendMessage(message
, recipientId
);
105 handlePossibleIdentityFailure(result
);
110 * Send a group message to the given group
111 * The message is extended with the current expiration timer for the group and the group context.
113 public List
<SendMessageResult
> sendAsGroupMessage(
114 SignalServiceDataMessage
.Builder messageBuilder
, GroupId groupId
115 ) throws IOException
, GroupNotFoundException
, NotAGroupMemberException
, GroupSendingNotAllowedException
{
116 final var g
= getGroupForSending(groupId
);
117 return sendAsGroupMessage(messageBuilder
, g
);
120 private List
<SendMessageResult
> sendAsGroupMessage(
121 final SignalServiceDataMessage
.Builder messageBuilder
, final GroupInfo g
122 ) throws IOException
, GroupSendingNotAllowedException
{
123 GroupUtils
.setGroupContext(messageBuilder
, g
);
124 messageBuilder
.withExpiration(g
.getMessageExpirationTime());
126 final var message
= messageBuilder
.build();
127 final var recipients
= g
.getMembersWithout(account
.getSelfRecipientId());
129 if (g
.isAnnouncementGroup() && !g
.isAdmin(account
.getSelfRecipientId())) {
130 if (message
.getBody().isPresent()
131 || message
.getAttachments().isPresent()
132 || message
.getQuote().isPresent()
133 || message
.getPreviews().isPresent()
134 || message
.getMentions().isPresent()
135 || message
.getSticker().isPresent()) {
136 throw new GroupSendingNotAllowedException(g
.getGroupId(), g
.getTitle());
140 return sendGroupMessage(message
, recipients
);
144 * Send a complete group message to the given recipients (should be current/old/new members)
145 * This method should only be used for create/update/quit group messages.
147 public List
<SendMessageResult
> sendGroupMessage(
148 final SignalServiceDataMessage message
, final Set
<RecipientId
> recipientIds
149 ) throws IOException
{
150 List
<SendMessageResult
> result
= sendGroupMessageInternal(message
, recipientIds
);
152 for (var r
: result
) {
153 handlePossibleIdentityFailure(r
);
159 public void sendDeliveryReceipt(
160 RecipientId recipientId
, List
<Long
> messageIds
161 ) throws IOException
, UntrustedIdentityException
{
162 var receiptMessage
= new SignalServiceReceiptMessage(SignalServiceReceiptMessage
.Type
.DELIVERY
,
164 System
.currentTimeMillis());
166 sendReceiptMessage(receiptMessage
, recipientId
);
169 public void sendReceiptMessage(
170 final SignalServiceReceiptMessage receiptMessage
, final RecipientId recipientId
171 ) throws IOException
, UntrustedIdentityException
{
172 final var messageSender
= dependencies
.getMessageSender();
173 final var address
= addressResolver
.resolveSignalServiceAddress(recipientId
);
175 messageSender
.sendReceipt(address
, unidentifiedAccessHelper
.getAccessFor(recipientId
), receiptMessage
);
176 } catch (org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException e
) {
177 throw new UntrustedIdentityException(address
);
181 public SendMessageResult
sendNullMessage(RecipientId recipientId
) throws IOException
{
182 var messageSender
= dependencies
.getMessageSender();
184 final var address
= addressResolver
.resolveSignalServiceAddress(recipientId
);
187 return messageSender
.sendNullMessage(address
, unidentifiedAccessHelper
.getAccessFor(recipientId
));
188 } catch (UnregisteredUserException e
) {
189 final var newRecipientId
= recipientRegistrationRefresher
.refreshRecipientRegistration(recipientId
);
190 final var newAddress
= addressResolver
.resolveSignalServiceAddress(newRecipientId
);
191 return messageSender
.sendNullMessage(newAddress
, unidentifiedAccessHelper
.getAccessFor(newRecipientId
));
193 } catch (org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException e
) {
194 return SendMessageResult
.identityFailure(address
, e
.getIdentityKey());
198 public SendMessageResult
sendSelfMessage(
199 SignalServiceDataMessage
.Builder messageBuilder
200 ) throws IOException
{
201 final var recipientId
= account
.getSelfRecipientId();
202 final var contact
= account
.getContactStore().getContact(recipientId
);
203 final var expirationTime
= contact
!= null ? contact
.getMessageExpirationTime() : 0;
204 messageBuilder
.withExpiration(expirationTime
);
206 var message
= messageBuilder
.build();
207 return sendSelfMessage(message
);
210 public SendMessageResult
sendSyncMessage(SignalServiceSyncMessage message
) throws IOException
{
211 var messageSender
= dependencies
.getMessageSender();
213 return messageSender
.sendSyncMessage(message
, unidentifiedAccessHelper
.getAccessForSync());
214 } catch (org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException e
) {
215 var address
= addressResolver
.resolveSignalServiceAddress(account
.getSelfRecipientId());
216 return SendMessageResult
.identityFailure(address
, e
.getIdentityKey());
220 public void sendTypingMessage(
221 SignalServiceTypingMessage message
, RecipientId recipientId
222 ) throws IOException
, UntrustedIdentityException
{
223 var messageSender
= dependencies
.getMessageSender();
224 final var address
= addressResolver
.resolveSignalServiceAddress(recipientId
);
227 messageSender
.sendTyping(address
, unidentifiedAccessHelper
.getAccessFor(recipientId
), message
);
228 } catch (UnregisteredUserException e
) {
229 final var newRecipientId
= recipientRegistrationRefresher
.refreshRecipientRegistration(recipientId
);
230 final var newAddress
= addressResolver
.resolveSignalServiceAddress(newRecipientId
);
231 messageSender
.sendTyping(newAddress
, unidentifiedAccessHelper
.getAccessFor(newRecipientId
), message
);
233 } catch (org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException e
) {
234 throw new UntrustedIdentityException(address
);
238 public void sendGroupTypingMessage(
239 SignalServiceTypingMessage message
, GroupId groupId
240 ) throws IOException
, NotAGroupMemberException
, GroupNotFoundException
, GroupSendingNotAllowedException
{
241 final var g
= getGroupForSending(groupId
);
242 if (g
.isAnnouncementGroup() && !g
.isAdmin(account
.getSelfRecipientId())) {
243 throw new GroupSendingNotAllowedException(groupId
, g
.getTitle());
245 final var messageSender
= dependencies
.getMessageSender();
246 final var recipientIdList
= new ArrayList
<>(g
.getMembersWithout(account
.getSelfRecipientId()));
247 final var addresses
= recipientIdList
.stream()
248 .map(addressResolver
::resolveSignalServiceAddress
)
249 .collect(Collectors
.toList());
250 messageSender
.sendTyping(addresses
, unidentifiedAccessHelper
.getAccessFor(recipientIdList
), message
, null);
253 private GroupInfo
getGroupForSending(GroupId groupId
) throws GroupNotFoundException
, NotAGroupMemberException
{
254 var g
= groupProvider
.getGroup(groupId
);
256 throw new GroupNotFoundException(groupId
);
258 if (!g
.isMember(account
.getSelfRecipientId())) {
259 throw new NotAGroupMemberException(groupId
, g
.getTitle());
264 private List
<SendMessageResult
> sendGroupMessageInternal(
265 final SignalServiceDataMessage message
, final Set
<RecipientId
> recipientIds
266 ) throws IOException
{
268 var messageSender
= dependencies
.getMessageSender();
269 // isRecipientUpdate is true if we've already sent this message to some recipients in the past, otherwise false.
270 final var isRecipientUpdate
= false;
271 final var recipientIdList
= new ArrayList
<>(recipientIds
);
272 final var addresses
= recipientIdList
.stream()
273 .map(addressResolver
::resolveSignalServiceAddress
)
274 .collect(Collectors
.toList());
275 return messageSender
.sendDataMessage(addresses
,
276 unidentifiedAccessHelper
.getAccessFor(recipientIdList
),
281 sendResult
-> logger
.trace("Partial message send result: {}", sendResult
.isSuccess()),
283 } catch (org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException e
) {
288 private SendMessageResult
sendMessage(
289 SignalServiceDataMessage message
, RecipientId recipientId
290 ) throws IOException
{
291 var messageSender
= dependencies
.getMessageSender();
293 final var address
= addressResolver
.resolveSignalServiceAddress(recipientId
);
296 return messageSender
.sendDataMessage(address
,
297 unidentifiedAccessHelper
.getAccessFor(recipientId
),
301 } catch (UnregisteredUserException e
) {
302 final var newRecipientId
= recipientRegistrationRefresher
.refreshRecipientRegistration(recipientId
);
303 return messageSender
.sendDataMessage(addressResolver
.resolveSignalServiceAddress(newRecipientId
),
304 unidentifiedAccessHelper
.getAccessFor(newRecipientId
),
309 } catch (org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException e
) {
310 return SendMessageResult
.identityFailure(address
, e
.getIdentityKey());
314 private SendMessageResult
sendSelfMessage(SignalServiceDataMessage message
) throws IOException
{
315 var address
= account
.getSelfAddress();
316 var transcript
= new SentTranscriptMessage(Optional
.of(address
),
317 message
.getTimestamp(),
319 message
.getExpiresInSeconds(),
320 Map
.of(address
, true),
322 var syncMessage
= SignalServiceSyncMessage
.forSentTranscript(transcript
);
324 return sendSyncMessage(syncMessage
);
327 private void handlePossibleIdentityFailure(final SendMessageResult r
) {
328 if (r
.getIdentityFailure() != null) {
329 final var recipientId
= recipientResolver
.resolveRecipient(r
.getAddress());
330 identityFailureHandler
.handleIdentityFailure(recipientId
, r
.getIdentityFailure());