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