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