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