mirror of
https://github.com/AsamK/signal-cli
synced 2025-08-28 18:10:38 +00:00
Merge 66562822d2
into 2b150112ff
This commit is contained in:
commit
fa50d3188b
7 changed files with 213 additions and 31 deletions
|
@ -368,4 +368,7 @@ public interface Manager extends Closeable {
|
|||
|
||||
void handleMessage(MessageEnvelope envelope, Throwable e);
|
||||
}
|
||||
|
||||
SendMessageResults sendStoryMessage(Message message, RecipientIdentifier.Group recipient)
|
||||
throws IOException, AttachmentInvalidException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException, UnregisteredRecipientException, InvalidStickerException;
|
||||
}
|
||||
|
|
|
@ -31,6 +31,8 @@ import org.whispersystems.signalservice.api.messages.SendMessageResult;
|
|||
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceEditMessage;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceStoryMessage;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceStoryMessageRecipient;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceTypingMessage;
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage;
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
|
||||
|
@ -53,6 +55,7 @@ import java.util.Optional;
|
|||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import okio.ByteString;
|
||||
|
||||
|
@ -331,6 +334,66 @@ public class SendHelper {
|
|||
editTargetTimestamp);
|
||||
}
|
||||
|
||||
public List<SendMessageResult> sendGroupStoryMessage(
|
||||
final SignalServiceStoryMessage message,
|
||||
final GroupInfo g
|
||||
) throws IOException {
|
||||
final var messageSender = dependencies.getMessageSender();
|
||||
final var messageSendLogStore = account.getMessageSendLogStore();
|
||||
final AtomicLong entryId = new AtomicLong(-1);
|
||||
final boolean urgent = true;
|
||||
final long timestamp = System.currentTimeMillis();
|
||||
// remove sender/self
|
||||
final Set<RecipientId> recipientIds = g.getMembersWithout(account.getSelfRecipientId());
|
||||
final List<String> distributionListIds = List.of(g.getGroupId().toBase64());
|
||||
final Set<SignalServiceStoryMessageRecipient> messageRecipients = recipientIds.stream().map(i -> {
|
||||
SignalServiceAddress ssa = context.getRecipientHelper().resolveSignalServiceAddress(i);
|
||||
return new SignalServiceStoryMessageRecipient(ssa, distributionListIds, true);
|
||||
}).collect(Collectors.toSet());
|
||||
final SenderKeySenderHandler senderKeySender = (distId, recipients, unidentifiedAccess, groupSendEndorsements, isRecipientUpdate) -> messageSender.sendGroupStory(
|
||||
g.getDistributionId(),
|
||||
Optional.of(g.getGroupId().serialize()),
|
||||
recipients,
|
||||
unidentifiedAccess,
|
||||
groupSendEndorsements,
|
||||
true,
|
||||
message,
|
||||
timestamp,
|
||||
messageRecipients,
|
||||
sendResult -> {
|
||||
logger.trace("Partial message send results: {}", sendResult.size());
|
||||
synchronized (entryId) {
|
||||
if (entryId.get() == -1) {
|
||||
final var newId = messageSendLogStore.insertIfPossible(timestamp,
|
||||
sendResult,
|
||||
ContentHint.RESENDABLE,
|
||||
urgent);
|
||||
entryId.set(newId);
|
||||
} else {
|
||||
messageSendLogStore.addRecipientToExistingEntryIfPossible(entryId.get(), sendResult);
|
||||
}
|
||||
}
|
||||
synchronized (entryId) {
|
||||
if (entryId.get() == -1) {
|
||||
final var newId = messageSendLogStore.insertIfPossible(timestamp,
|
||||
sendResult,
|
||||
ContentHint.RESENDABLE,
|
||||
urgent);
|
||||
entryId.set(newId);
|
||||
} else {
|
||||
messageSendLogStore.addRecipientToExistingEntryIfPossible(entryId.get(), sendResult);
|
||||
}
|
||||
}
|
||||
});
|
||||
final var results = sendStoryMessageInternal(senderKeySender, recipientIds, g.getDistributionId());
|
||||
|
||||
for (var r : results) {
|
||||
handleSendMessageResult(r);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private List<SendMessageResult> sendGroupMessage(
|
||||
final SignalServiceDataMessage message,
|
||||
final Set<RecipientId> recipientIds,
|
||||
|
@ -466,6 +529,44 @@ public class SendHelper {
|
|||
return g;
|
||||
}
|
||||
|
||||
private List<SendMessageResult> sendStoryMessageInternal(
|
||||
final SenderKeySenderHandler senderKeySender,
|
||||
final Set<RecipientId> recipientIds,
|
||||
final DistributionId distributionId
|
||||
) throws IOException {
|
||||
long startTime = System.currentTimeMillis();
|
||||
Set<RecipientId> senderKeyTargets = distributionId == null
|
||||
? Set.of()
|
||||
: getSenderKeyCapableRecipientIds(recipientIds);
|
||||
final var allResults = new ArrayList<SendMessageResult>();
|
||||
|
||||
if (!senderKeyTargets.isEmpty()) {
|
||||
final var results = sendGroupMessageInternalWithSenderKey(senderKeySender,
|
||||
senderKeyTargets,
|
||||
distributionId,
|
||||
false);
|
||||
|
||||
if (results == null) {
|
||||
senderKeyTargets = Set.of();
|
||||
} else {
|
||||
results.stream().filter(SendMessageResult::isSuccess).forEach(allResults::add);
|
||||
final var recipientResolver = account.getRecipientResolver();
|
||||
final var failedTargets = results.stream()
|
||||
.filter(r -> !r.isSuccess())
|
||||
.map(r -> recipientResolver.resolveRecipient(r.getAddress()))
|
||||
.toList();
|
||||
if (!failedTargets.isEmpty()) {
|
||||
senderKeyTargets = new HashSet<>(senderKeyTargets);
|
||||
failedTargets.forEach(senderKeyTargets::remove);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final var duration = Duration.ofMillis(System.currentTimeMillis() - startTime);
|
||||
logger.debug("Sending took {}", duration.toString());
|
||||
return allResults;
|
||||
}
|
||||
|
||||
private List<SendMessageResult> sendGroupMessageInternal(
|
||||
final LegacySenderHandler legacySender,
|
||||
final SenderKeySenderHandler senderKeySender,
|
||||
|
@ -546,12 +647,12 @@ public class SendHelper {
|
|||
|
||||
senderKeyTargets.add(recipientId);
|
||||
}
|
||||
|
||||
/*
|
||||
if (senderKeyTargets.size() < 2) {
|
||||
logger.debug("Too few sender-key-capable users ({}). Doing all legacy sends.", senderKeyTargets.size());
|
||||
return Set.of();
|
||||
}
|
||||
|
||||
*/
|
||||
logger.debug("Can use sender key for {}/{} recipients.", senderKeyTargets.size(), recipientIds.size());
|
||||
return senderKeyTargets;
|
||||
}
|
||||
|
|
|
@ -16,6 +16,34 @@
|
|||
*/
|
||||
package org.asamk.signal.manager.internal;
|
||||
|
||||
import static org.asamk.signal.manager.config.ServiceConfig.MAX_MESSAGE_SIZE_BYTES;
|
||||
import static org.signal.core.util.StringExtensionsKt.splitByByteLength;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.asamk.signal.manager.Manager;
|
||||
import org.asamk.signal.manager.api.AlreadyReceivingException;
|
||||
import org.asamk.signal.manager.api.AttachmentInvalidException;
|
||||
|
@ -78,6 +106,7 @@ import org.asamk.signal.manager.storage.AttachmentStore;
|
|||
import org.asamk.signal.manager.storage.AvatarStore;
|
||||
import org.asamk.signal.manager.storage.SignalAccount;
|
||||
import org.asamk.signal.manager.storage.groups.GroupInfo;
|
||||
import org.asamk.signal.manager.storage.groups.GroupInfoV2;
|
||||
import org.asamk.signal.manager.storage.identities.IdentityInfo;
|
||||
import org.asamk.signal.manager.storage.recipients.RecipientAddress;
|
||||
import org.asamk.signal.manager.storage.recipients.RecipientId;
|
||||
|
@ -97,6 +126,7 @@ import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
|
|||
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServicePreview;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceStoryMessage;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceTypingMessage;
|
||||
import org.whispersystems.signalservice.api.push.ServiceId;
|
||||
import org.whispersystems.signalservice.api.push.ServiceId.ACI;
|
||||
|
@ -109,41 +139,14 @@ import org.whispersystems.signalservice.api.util.DeviceNameUtil;
|
|||
import org.whispersystems.signalservice.api.util.InvalidNumberException;
|
||||
import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
|
||||
import org.whispersystems.signalservice.api.util.StreamDetails;
|
||||
import org.whispersystems.signalservice.internal.push.BodyRange;
|
||||
import org.whispersystems.signalservice.internal.util.Hex;
|
||||
import org.whispersystems.signalservice.internal.util.Util;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable;
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
import okio.Utf8;
|
||||
|
||||
import static org.asamk.signal.manager.config.ServiceConfig.MAX_MESSAGE_SIZE_BYTES;
|
||||
import static org.signal.core.util.StringExtensionsKt.splitByByteLength;
|
||||
|
||||
public class ManagerImpl implements Manager {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ManagerImpl.class);
|
||||
|
@ -750,6 +753,35 @@ public class ManagerImpl implements Manager {
|
|||
return sendMessage(messageBuilder, recipients, notifySelf);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public SendMessageResults sendStoryMessage(Message message, RecipientIdentifier.Group idGroup
|
||||
) throws IOException, AttachmentInvalidException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException, UnregisteredRecipientException, InvalidStickerException {
|
||||
final var selfProfile = context.getProfileHelper().getSelfProfile();
|
||||
if (selfProfile == null || selfProfile.getDisplayName().isEmpty()) {
|
||||
logger.warn(
|
||||
"No profile name set. When sending a message it's recommended to set a profile name with the updateProfile command. This may become mandatory in the future.");
|
||||
}
|
||||
final var profileKey = account.getProfileKey().serialize();
|
||||
GroupInfoV2 groupInfo = (GroupInfoV2) context.getGroupHelper().getGroup(idGroup.groupId());
|
||||
List<String> attachments = message.attachments();
|
||||
List<BodyRange> bodyRanges = message.textStyles().stream().map(t -> t.toBodyRange()).toList();
|
||||
|
||||
SignalServiceStoryMessage storyMessage = null;
|
||||
if (attachments != null && attachments.size() > 0) {
|
||||
var attachment = context.getAttachmentHelper().uploadAttachment(attachments.get(0));
|
||||
storyMessage = SignalServiceStoryMessage.forFileAttachment(profileKey, null, attachment, true, bodyRanges);
|
||||
} else {
|
||||
//SignalServiceTextAttachment textBuilder = new SignalServiceTextAttachment.
|
||||
//storyMessage = SignalServiceStoryMessage.forTextAttachment(profileKey, ssgroup, textBuilder.build(), true, bodyRanges);
|
||||
}
|
||||
var results = new HashMap<RecipientIdentifier, List<SendMessageResult>>();
|
||||
long timestamp = System.currentTimeMillis();
|
||||
final var result = context.getSendHelper().sendGroupStoryMessage(storyMessage, groupInfo);
|
||||
results.put(idGroup, result.stream().map(this::toSendMessageResult).toList());
|
||||
return new SendMessageResults(timestamp, results);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SendMessageResults sendEditMessage(
|
||||
Message message,
|
||||
|
|
|
@ -754,4 +754,6 @@ public interface Signal extends DBusInterface {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
long sendStoryMessage(String messageText, List<String> attachments, byte[] groupId);
|
||||
}
|
||||
|
|
|
@ -124,6 +124,13 @@ public class SendCommand implements JsonRpcLocalCommand {
|
|||
groupIdStrings,
|
||||
usernameStrings);
|
||||
|
||||
boolean isStory = false;
|
||||
|
||||
if (recipientIdentifiers.size() > 0
|
||||
&& recipientIdentifiers.iterator().next() instanceof RecipientIdentifier.Group) {
|
||||
isStory = Boolean.TRUE.equals(ns.getBoolean("story"));
|
||||
}
|
||||
|
||||
final var isEndSession = Boolean.TRUE.equals(ns.getBoolean("end-session"));
|
||||
if (isEndSession) {
|
||||
final var singleRecipients = recipientIdentifiers.stream()
|
||||
|
@ -247,7 +254,8 @@ public class SendCommand implements JsonRpcLocalCommand {
|
|||
textStyles);
|
||||
var results = editTimestamp != null
|
||||
? m.sendEditMessage(message, recipientIdentifiers, editTimestamp)
|
||||
: m.sendMessage(message, recipientIdentifiers, notifySelf);
|
||||
: isStory ? m.sendStoryMessage(message, (RecipientIdentifier.Group) recipientIdentifiers.iterator().next())
|
||||
: m.sendMessage(message, recipientIdentifiers, notifySelf);
|
||||
outputResult(outputWriter, results);
|
||||
} catch (AttachmentInvalidException | IOException e) {
|
||||
throw new UnexpectedErrorException("Failed to send message: " + e.getMessage() + " (" + e.getClass()
|
||||
|
|
|
@ -427,6 +427,16 @@ public class DbusManagerImpl implements Manager {
|
|||
groupId -> signal.sendGroupMessage(message.messageText(), message.attachments(), groupId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public SendMessageResults sendStoryMessage(
|
||||
final Message message, final RecipientIdentifier.Group recipient
|
||||
) throws IOException, AttachmentInvalidException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException {
|
||||
return handleMessage(Set.of(recipient),
|
||||
numbers -> signal.sendMessage(message.messageText(), message.attachments(), numbers),
|
||||
() -> signal.sendNoteToSelfMessage(message.messageText(), message.attachments()),
|
||||
groupId -> signal.sendStoryMessage(message.messageText(), message.attachments(), groupId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public SendMessageResults sendEditMessage(
|
||||
final Message message,
|
||||
|
@ -1150,4 +1160,5 @@ public class DbusManagerImpl implements Manager {
|
|||
private <T> T getValue(final Map<String, Variant<?>> stringVariantMap, final String field) {
|
||||
return (T) stringVariantMap.get(field).getValue();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -464,6 +464,31 @@ public class DbusSignalImpl implements Signal, AutoCloseable {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long sendStoryMessage(final String messageText, final List<String> attachments, final byte[] groupId) {
|
||||
try {
|
||||
final var message = new Message(messageText,
|
||||
attachments,
|
||||
List.of(),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
List.of(),
|
||||
Optional.empty(),
|
||||
List.of());
|
||||
var results = m.sendStoryMessage(message,getGroupRecipientIdentifier(groupId));
|
||||
checkSendMessageResults(results);
|
||||
return results.timestamp();
|
||||
} catch (IOException | InvalidStickerException e) {
|
||||
throw new Error.Failure(e.getMessage());
|
||||
} catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) {
|
||||
throw new Error.GroupNotFound(e.getMessage());
|
||||
} catch (AttachmentInvalidException e) {
|
||||
throw new Error.AttachmentInvalid(e.getMessage());
|
||||
} catch (UnregisteredRecipientException e) {
|
||||
throw new Error.UntrustedIdentity(e.getSender().getIdentifier() + " is not registered.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendGroupTyping(
|
||||
final byte[] groupId,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue