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