This commit is contained in:
Scott Lewis 2025-02-05 16:55:45 -08:00
parent 38067bb725
commit 02e58f0010
9 changed files with 172 additions and 44 deletions

View file

@ -37,7 +37,7 @@ graalvmNative {
if (System.getenv("GRAALVM_HOME") == null) {
toolchainDetection.set(true)
javaLauncher.set(javaToolchains.launcherFor {
languageVersion.set(JavaLanguageVersion.of(21))
languageVersion.set(JavaLanguageVersion.of(22))
})
} else {
toolchainDetection.set(false)

View file

@ -8,7 +8,7 @@ java {
targetCompatibility = JavaVersion.VERSION_21
toolchain {
languageVersion.set(JavaLanguageVersion.of(21))
languageVersion.set(JavaLanguageVersion.of(22))
}
}

View file

@ -197,10 +197,6 @@ public interface Manager extends Closeable {
boolean notifySelf
) throws IOException, AttachmentInvalidException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException, UnregisteredRecipientException, InvalidStickerException;
SendMessageResults sendStoryMessage(
Message message, Set<RecipientIdentifier> recipients, boolean notifySelf
) throws IOException, AttachmentInvalidException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException, UnregisteredRecipientException, InvalidStickerException;
SendMessageResults sendEditMessage(
Message message,
Set<RecipientIdentifier> recipients,
@ -372,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;
}

View file

@ -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;
}

View file

@ -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;
@ -95,9 +124,10 @@ import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.SignalSessionLock;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage.StoryContext;
import org.whispersystems.signalservice.api.messages.SignalServiceGroupV2;
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;
@ -110,34 +140,10 @@ 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;
@ -752,18 +758,31 @@ public class ManagerImpl implements Manager {
}
@Override
public SendMessageResults sendStoryMessage(
Message message, Set<RecipientIdentifier> recipients, boolean notifySelf
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 messageBuilder = SignalServiceDataMessage.newBuilder();
applyMessage(messageBuilder, message);
messageBuilder.withStoryContext(new StoryContext(account.getSelfAddress().getServiceId(), System.currentTimeMillis()));
return sendMessage(messageBuilder, recipients, notifySelf);
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) {
SignalServiceAttachment.Builder attachmentBuilder = new SignalServiceAttachment.Builder().withFileName(attachments.get(0));
storyMessage = SignalServiceStoryMessage.forFileAttachment(profileKey, null, attachmentBuilder.build(), 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
@ -1625,4 +1644,5 @@ public class ManagerImpl implements Manager {
account = null;
}
}

View file

@ -754,4 +754,8 @@ public interface Signal extends DBusInterface {
}
}
}
long sendStoryMessage(String messageText, List<String> attachments, byte[] groupId);
//long sendStoryMessage(String messageText, List<String> attachments, byte[] groupId);
}

View file

@ -233,6 +233,8 @@ public class SendCommand implements JsonRpcLocalCommand {
final var editTimestamp = ns.getLong("edit-timestamp");
final boolean story = false;
try {
final var message = new Message(messageText,
attachments,
@ -244,7 +246,8 @@ public class SendCommand implements JsonRpcLocalCommand {
textStyles);
var results = editTimestamp != null
? m.sendEditMessage(message, recipientIdentifiers, editTimestamp)
: m.sendMessage(message, recipientIdentifiers, notifySelf);
//: story ? // m.sendStoryMessage(message, recipientIdentifiers, notifySelf)
: m.sendMessage(message, recipientIdentifiers, notifySelf);
outputResult(outputWriter, results);
} catch (AttachmentInvalidException | IOException e) {
throw new UnexpectedErrorException("Failed to send message: " + e.getMessage() + " (" + e.getClass()

View file

@ -429,9 +429,9 @@ public class DbusManagerImpl implements Manager {
@Override
public SendMessageResults sendStoryMessage(
final Message message, final Set<RecipientIdentifier> recipients, final boolean notifySelf
final Message message, final RecipientIdentifier.Group recipient
) throws IOException, AttachmentInvalidException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException {
return handleMessage(recipients,
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));
@ -1160,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();
}
}

View file

@ -475,7 +475,7 @@ public class DbusSignalImpl implements Signal, AutoCloseable {
List.of(),
Optional.empty(),
List.of());
var results = m.sendStoryMessage(message, Set.of(getGroupRecipientIdentifier(groupId)), false);
var results = m.sendStoryMessage(message,getGroupRecipientIdentifier(groupId));
checkSendMessageResults(results);
return results.timestamp();
} catch (IOException | InvalidStickerException e) {