]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/helper/SendHelper.java
Create libsignal dependencies only when required
[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 public SendHelper(
62 final SignalAccount account,
63 final SignalDependencies dependencies,
64 final UnidentifiedAccessHelper unidentifiedAccessHelper,
65 final SignalServiceAddressResolver addressResolver,
66 final RecipientResolver recipientResolver,
67 final IdentityFailureHandler identityFailureHandler,
68 final GroupProvider groupProvider,
69 final RecipientRegistrationRefresher recipientRegistrationRefresher
70 ) {
71 this.account = account;
72 this.dependencies = dependencies;
73 this.unidentifiedAccessHelper = unidentifiedAccessHelper;
74 this.addressResolver = addressResolver;
75 this.recipientResolver = recipientResolver;
76 this.identityFailureHandler = identityFailureHandler;
77 this.groupProvider = groupProvider;
78 this.recipientRegistrationRefresher = recipientRegistrationRefresher;
79 }
80
81 /**
82 * Send a single message to one or multiple recipients.
83 * The message is extended with the current expiration timer for each recipient.
84 */
85 public SendMessageResult sendMessage(
86 final SignalServiceDataMessage.Builder messageBuilder, final RecipientId recipientId
87 ) throws IOException {
88 final var contact = account.getContactStore().getContact(recipientId);
89 final var expirationTime = contact != null ? contact.getMessageExpirationTime() : 0;
90 messageBuilder.withExpiration(expirationTime);
91 messageBuilder.withProfileKey(account.getProfileKey().serialize());
92
93 final var message = messageBuilder.build();
94 final var result = sendMessage(message, recipientId);
95 handlePossibleIdentityFailure(result);
96 return result;
97 }
98
99 /**
100 * Send a group message to the given group
101 * The message is extended with the current expiration timer for the group and the group context.
102 */
103 public List<SendMessageResult> sendAsGroupMessage(
104 SignalServiceDataMessage.Builder messageBuilder, GroupId groupId
105 ) throws IOException, GroupNotFoundException, NotAGroupMemberException, GroupSendingNotAllowedException {
106 final var g = getGroupForSending(groupId);
107 return sendAsGroupMessage(messageBuilder, g);
108 }
109
110 private List<SendMessageResult> sendAsGroupMessage(
111 final SignalServiceDataMessage.Builder messageBuilder, final GroupInfo g
112 ) throws IOException, GroupSendingNotAllowedException {
113 GroupUtils.setGroupContext(messageBuilder, g);
114 messageBuilder.withExpiration(g.getMessageExpirationTime());
115
116 final var message = messageBuilder.build();
117 final var recipients = g.getMembersWithout(account.getSelfRecipientId());
118
119 if (g.isAnnouncementGroup() && !g.isAdmin(account.getSelfRecipientId())) {
120 if (message.getBody().isPresent()
121 || message.getAttachments().isPresent()
122 || message.getQuote().isPresent()
123 || message.getPreviews().isPresent()
124 || message.getMentions().isPresent()
125 || message.getSticker().isPresent()) {
126 throw new GroupSendingNotAllowedException(g.getGroupId(), g.getTitle());
127 }
128 }
129
130 return sendGroupMessage(message, recipients);
131 }
132
133 /**
134 * Send a complete group message to the given recipients (should be current/old/new members)
135 * This method should only be used for create/update/quit group messages.
136 */
137 public List<SendMessageResult> sendGroupMessage(
138 final SignalServiceDataMessage message, final Set<RecipientId> recipientIds
139 ) throws IOException {
140 List<SendMessageResult> result = sendGroupMessageInternal(message, recipientIds);
141
142 for (var r : result) {
143 handlePossibleIdentityFailure(r);
144 }
145
146 return result;
147 }
148
149 public void sendDeliveryReceipt(
150 RecipientId recipientId, List<Long> messageIds
151 ) throws IOException, UntrustedIdentityException {
152 var receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.DELIVERY,
153 messageIds,
154 System.currentTimeMillis());
155
156 sendReceiptMessage(receiptMessage, recipientId);
157 }
158
159 public void sendReceiptMessage(
160 final SignalServiceReceiptMessage receiptMessage, final RecipientId recipientId
161 ) throws IOException, UntrustedIdentityException {
162 final var messageSender = dependencies.getMessageSender();
163 final var address = addressResolver.resolveSignalServiceAddress(recipientId);
164 try {
165 messageSender.sendReceipt(address, unidentifiedAccessHelper.getAccessFor(recipientId), receiptMessage);
166 } catch (org.whispersystems.signalservice.api.crypto.UntrustedIdentityException e) {
167 throw new UntrustedIdentityException(address);
168 }
169 }
170
171 public SendMessageResult sendNullMessage(RecipientId recipientId) throws IOException {
172 var messageSender = dependencies.getMessageSender();
173
174 final var address = addressResolver.resolveSignalServiceAddress(recipientId);
175 try {
176 try {
177 return messageSender.sendNullMessage(address, unidentifiedAccessHelper.getAccessFor(recipientId));
178 } catch (UnregisteredUserException e) {
179 final var newRecipientId = recipientRegistrationRefresher.refreshRecipientRegistration(recipientId);
180 final var newAddress = addressResolver.resolveSignalServiceAddress(newRecipientId);
181 return messageSender.sendNullMessage(newAddress, unidentifiedAccessHelper.getAccessFor(newRecipientId));
182 }
183 } catch (org.whispersystems.signalservice.api.crypto.UntrustedIdentityException e) {
184 return SendMessageResult.identityFailure(address, e.getIdentityKey());
185 }
186 }
187
188 public SendMessageResult sendSelfMessage(
189 SignalServiceDataMessage.Builder messageBuilder
190 ) throws IOException {
191 final var recipientId = account.getSelfRecipientId();
192 final var contact = account.getContactStore().getContact(recipientId);
193 final var expirationTime = contact != null ? contact.getMessageExpirationTime() : 0;
194 messageBuilder.withExpiration(expirationTime);
195
196 var message = messageBuilder.build();
197 return sendSelfMessage(message);
198 }
199
200 public SendMessageResult sendSyncMessage(SignalServiceSyncMessage message) throws IOException {
201 var messageSender = dependencies.getMessageSender();
202 try {
203 return messageSender.sendSyncMessage(message, unidentifiedAccessHelper.getAccessForSync());
204 } catch (org.whispersystems.signalservice.api.crypto.UntrustedIdentityException e) {
205 var address = addressResolver.resolveSignalServiceAddress(account.getSelfRecipientId());
206 return SendMessageResult.identityFailure(address, e.getIdentityKey());
207 }
208 }
209
210 public void sendTypingMessage(
211 SignalServiceTypingMessage message, RecipientId recipientId
212 ) throws IOException, UntrustedIdentityException {
213 var messageSender = dependencies.getMessageSender();
214 final var address = addressResolver.resolveSignalServiceAddress(recipientId);
215 try {
216 try {
217 messageSender.sendTyping(address, unidentifiedAccessHelper.getAccessFor(recipientId), message);
218 } catch (UnregisteredUserException e) {
219 final var newRecipientId = recipientRegistrationRefresher.refreshRecipientRegistration(recipientId);
220 final var newAddress = addressResolver.resolveSignalServiceAddress(newRecipientId);
221 messageSender.sendTyping(newAddress, unidentifiedAccessHelper.getAccessFor(newRecipientId), message);
222 }
223 } catch (org.whispersystems.signalservice.api.crypto.UntrustedIdentityException e) {
224 throw new UntrustedIdentityException(address);
225 }
226 }
227
228 public void sendGroupTypingMessage(
229 SignalServiceTypingMessage message, GroupId groupId
230 ) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException {
231 final var g = getGroupForSending(groupId);
232 if (g.isAnnouncementGroup() && !g.isAdmin(account.getSelfRecipientId())) {
233 throw new GroupSendingNotAllowedException(groupId, g.getTitle());
234 }
235 final var messageSender = dependencies.getMessageSender();
236 final var recipientIdList = new ArrayList<>(g.getMembersWithout(account.getSelfRecipientId()));
237 final var addresses = recipientIdList.stream()
238 .map(addressResolver::resolveSignalServiceAddress)
239 .collect(Collectors.toList());
240 messageSender.sendTyping(addresses, unidentifiedAccessHelper.getAccessFor(recipientIdList), message, null);
241 }
242
243 private GroupInfo getGroupForSending(GroupId groupId) throws GroupNotFoundException, NotAGroupMemberException {
244 var g = groupProvider.getGroup(groupId);
245 if (g == null) {
246 throw new GroupNotFoundException(groupId);
247 }
248 if (!g.isMember(account.getSelfRecipientId())) {
249 throw new NotAGroupMemberException(groupId, g.getTitle());
250 }
251 return g;
252 }
253
254 private List<SendMessageResult> sendGroupMessageInternal(
255 final SignalServiceDataMessage message, final Set<RecipientId> recipientIds
256 ) throws IOException {
257 try {
258 var messageSender = dependencies.getMessageSender();
259 // isRecipientUpdate is true if we've already sent this message to some recipients in the past, otherwise false.
260 final var isRecipientUpdate = false;
261 final var recipientIdList = new ArrayList<>(recipientIds);
262 final var addresses = recipientIdList.stream()
263 .map(addressResolver::resolveSignalServiceAddress)
264 .collect(Collectors.toList());
265 return messageSender.sendDataMessage(addresses,
266 unidentifiedAccessHelper.getAccessFor(recipientIdList),
267 isRecipientUpdate,
268 ContentHint.DEFAULT,
269 message,
270 sendResult -> logger.trace("Partial message send result: {}", sendResult.isSuccess()),
271 () -> false);
272 } catch (org.whispersystems.signalservice.api.crypto.UntrustedIdentityException e) {
273 return List.of();
274 }
275 }
276
277 private SendMessageResult sendMessage(
278 SignalServiceDataMessage message, RecipientId recipientId
279 ) throws IOException {
280 var messageSender = dependencies.getMessageSender();
281
282 final var address = addressResolver.resolveSignalServiceAddress(recipientId);
283 try {
284 try {
285 return messageSender.sendDataMessage(address,
286 unidentifiedAccessHelper.getAccessFor(recipientId),
287 ContentHint.DEFAULT,
288 message,
289 sendEvents);
290 } catch (UnregisteredUserException e) {
291 final var newRecipientId = recipientRegistrationRefresher.refreshRecipientRegistration(recipientId);
292 return messageSender.sendDataMessage(addressResolver.resolveSignalServiceAddress(newRecipientId),
293 unidentifiedAccessHelper.getAccessFor(newRecipientId),
294 ContentHint.DEFAULT,
295 message,
296 sendEvents);
297 }
298 } catch (org.whispersystems.signalservice.api.crypto.UntrustedIdentityException e) {
299 return SendMessageResult.identityFailure(address, e.getIdentityKey());
300 }
301 }
302
303 private SendMessageResult sendSelfMessage(SignalServiceDataMessage message) throws IOException {
304 var address = account.getSelfAddress();
305 var transcript = new SentTranscriptMessage(Optional.of(address),
306 message.getTimestamp(),
307 message,
308 message.getExpiresInSeconds(),
309 Map.of(address, true),
310 false);
311 var syncMessage = SignalServiceSyncMessage.forSentTranscript(transcript);
312
313 return sendSyncMessage(syncMessage);
314 }
315
316 private void handlePossibleIdentityFailure(final SendMessageResult r) {
317 if (r.getIdentityFailure() != null) {
318 final var recipientId = recipientResolver.resolveRecipient(r.getAddress());
319 identityFailureHandler.handleIdentityFailure(recipientId, r.getIdentityFailure());
320 }
321 }
322 }