Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions lib/src/main/java/org/asamk/signal/manager/Manager.java
Original file line number Diff line number Diff line change
Expand Up @@ -368,4 +368,7 @@ interface ReceiveMessageHandler {

void handleMessage(MessageEnvelope envelope, Throwable e);
}

SendMessageResults sendStoryMessage(Message message, RecipientIdentifier.Group recipient)
throws IOException, AttachmentInvalidException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException, UnregisteredRecipientException, InvalidStickerException;
}
105 changes: 103 additions & 2 deletions lib/src/main/java/org/asamk/signal/manager/helper/SendHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
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;
Expand All @@ -53,6 +55,7 @@
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;

import okio.ByteString;

Expand Down Expand Up @@ -331,6 +334,66 @@ private List<SendMessageResult> sendAsGroupMessage(
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,
Expand Down Expand Up @@ -466,6 +529,44 @@ private GroupInfo getGroupForSending(GroupId groupId) throws GroupNotFoundExcept
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,
Expand Down Expand Up @@ -546,12 +647,12 @@ private Set<RecipientId> getSenderKeyCapableRecipientIds(final Set<RecipientId>

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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -78,6 +106,7 @@
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;
Expand All @@ -97,6 +126,7 @@
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;
Expand All @@ -109,41 +139,14 @@
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);
Expand Down Expand Up @@ -750,6 +753,35 @@ public SendMessageResults sendMessage(
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,
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/org/asamk/Signal.java
Original file line number Diff line number Diff line change
Expand Up @@ -754,4 +754,6 @@ public UnregisteredRecipient(final String message) {
}
}
}

long sendStoryMessage(String messageText, List<String> attachments, byte[] groupId);
}
10 changes: 9 additions & 1 deletion src/main/java/org/asamk/signal/commands/SendCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,13 @@ public void handleCommand(
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()
Expand Down Expand Up @@ -244,7 +251,8 @@ public void handleCommand(
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()
Expand Down
11 changes: 11 additions & 0 deletions src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,16 @@ public SendMessageResults sendMessage(
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,
Expand Down Expand Up @@ -1150,4 +1160,5 @@ public InputStream retrieveSticker(final StickerPackId stickerPackId, final int
private <T> T getValue(final Map<String, Variant<?>> stringVariantMap, final String field) {
return (T) stringVariantMap.get(field).getValue();
}

}
25 changes: 25 additions & 0 deletions src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,31 @@ public long sendGroupMessage(final String messageText, final List<String> attach
}
}

@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,
Expand Down
Loading