]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/helper/SendHelper.java
Update libsignal-service-java
[signal-cli] / lib / src / main / java / org / asamk / signal / manager / helper / SendHelper.java
1 package org.asamk.signal.manager.helper;
2
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;
26
27 import java.io.IOException;
28 import java.util.ArrayList;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.Set;
32 import java.util.stream.Collectors;
33
34 public class SendHelper {
35
36 private final static Logger logger = LoggerFactory.getLogger(SendHelper.class);
37
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;
46
47 private final SignalServiceMessageSender.IndividualSendEvents sendEvents = new SignalServiceMessageSender.IndividualSendEvents() {
48 @Override
49 public void onMessageEncrypted() {
50 }
51
52 @Override
53 public void onMessageSent() {
54 }
55
56 @Override
57 public void onSyncMessageSent() {
58 }
59 };
60
61 private final SignalServiceMessageSender.LegacyGroupEvents legacyGroupEvents = new SignalServiceMessageSender.LegacyGroupEvents() {
62 @Override
63 public void onMessageSent() {
64 }
65
66 @Override
67 public void onSyncMessageSent() {
68 }
69 };
70
71 public SendHelper(
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
80 ) {
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;
89 }
90
91 /**
92 * Send a single message to one or multiple recipients.
93 * The message is extended with the current expiration timer for each recipient.
94 */
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());
102
103 final var message = messageBuilder.build();
104 final var result = sendMessage(message, recipientId);
105 handlePossibleIdentityFailure(result);
106 return result;
107 }
108
109 /**
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.
112 */
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);
118 }
119
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());
125
126 final var message = messageBuilder.build();
127 final var recipients = g.getMembersWithout(account.getSelfRecipientId());
128
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());
137 }
138 }
139
140 return sendGroupMessage(message, recipients);
141 }
142
143 /**
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.
146 */
147 public List<SendMessageResult> sendGroupMessage(
148 final SignalServiceDataMessage message, final Set<RecipientId> recipientIds
149 ) throws IOException {
150 List<SendMessageResult> result = sendGroupMessageInternal(message, recipientIds);
151
152 for (var r : result) {
153 handlePossibleIdentityFailure(r);
154 }
155
156 return result;
157 }
158
159 public void sendDeliveryReceipt(
160 RecipientId recipientId, List<Long> messageIds
161 ) throws IOException, UntrustedIdentityException {
162 var receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.DELIVERY,
163 messageIds,
164 System.currentTimeMillis());
165
166 sendReceiptMessage(receiptMessage, recipientId);
167 }
168
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);
174 try {
175 messageSender.sendReceipt(address, unidentifiedAccessHelper.getAccessFor(recipientId), receiptMessage);
176 } catch (org.whispersystems.signalservice.api.crypto.UntrustedIdentityException e) {
177 throw new UntrustedIdentityException(address);
178 }
179 }
180
181 public SendMessageResult sendNullMessage(RecipientId recipientId) throws IOException {
182 var messageSender = dependencies.getMessageSender();
183
184 final var address = addressResolver.resolveSignalServiceAddress(recipientId);
185 try {
186 try {
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));
192 }
193 } catch (org.whispersystems.signalservice.api.crypto.UntrustedIdentityException e) {
194 return SendMessageResult.identityFailure(address, e.getIdentityKey());
195 }
196 }
197
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);
205
206 var message = messageBuilder.build();
207 return sendSelfMessage(message);
208 }
209
210 public SendMessageResult sendSyncMessage(SignalServiceSyncMessage message) throws IOException {
211 var messageSender = dependencies.getMessageSender();
212 try {
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());
217 }
218 }
219
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);
225 try {
226 try {
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);
232 }
233 } catch (org.whispersystems.signalservice.api.crypto.UntrustedIdentityException e) {
234 throw new UntrustedIdentityException(address);
235 }
236 }
237
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());
244 }
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);
251 }
252
253 private GroupInfo getGroupForSending(GroupId groupId) throws GroupNotFoundException, NotAGroupMemberException {
254 var g = groupProvider.getGroup(groupId);
255 if (g == null) {
256 throw new GroupNotFoundException(groupId);
257 }
258 if (!g.isMember(account.getSelfRecipientId())) {
259 throw new NotAGroupMemberException(groupId, g.getTitle());
260 }
261 return g;
262 }
263
264 private List<SendMessageResult> sendGroupMessageInternal(
265 final SignalServiceDataMessage message, final Set<RecipientId> recipientIds
266 ) throws IOException {
267 try {
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),
277 isRecipientUpdate,
278 ContentHint.DEFAULT,
279 message,
280 legacyGroupEvents,
281 sendResult -> logger.trace("Partial message send result: {}", sendResult.isSuccess()),
282 () -> false);
283 } catch (org.whispersystems.signalservice.api.crypto.UntrustedIdentityException e) {
284 return List.of();
285 }
286 }
287
288 private SendMessageResult sendMessage(
289 SignalServiceDataMessage message, RecipientId recipientId
290 ) throws IOException {
291 var messageSender = dependencies.getMessageSender();
292
293 final var address = addressResolver.resolveSignalServiceAddress(recipientId);
294 try {
295 try {
296 return messageSender.sendDataMessage(address,
297 unidentifiedAccessHelper.getAccessFor(recipientId),
298 ContentHint.DEFAULT,
299 message,
300 sendEvents);
301 } catch (UnregisteredUserException e) {
302 final var newRecipientId = recipientRegistrationRefresher.refreshRecipientRegistration(recipientId);
303 return messageSender.sendDataMessage(addressResolver.resolveSignalServiceAddress(newRecipientId),
304 unidentifiedAccessHelper.getAccessFor(newRecipientId),
305 ContentHint.DEFAULT,
306 message,
307 sendEvents);
308 }
309 } catch (org.whispersystems.signalservice.api.crypto.UntrustedIdentityException e) {
310 return SendMessageResult.identityFailure(address, e.getIdentityKey());
311 }
312 }
313
314 private SendMessageResult sendSelfMessage(SignalServiceDataMessage message) throws IOException {
315 var address = account.getSelfAddress();
316 var transcript = new SentTranscriptMessage(Optional.of(address),
317 message.getTimestamp(),
318 message,
319 message.getExpiresInSeconds(),
320 Map.of(address, true),
321 false);
322 var syncMessage = SignalServiceSyncMessage.forSentTranscript(transcript);
323
324 return sendSyncMessage(syncMessage);
325 }
326
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());
331 }
332 }
333 }