Compare commits

..

1 Commits

22 changed files with 32 additions and 358 deletions

View File

@ -75,16 +75,6 @@ jobs:
asset_name: signal-cli-${{ steps.version.outputs.version }}-Linux-client.tar.gz
asset_content_type: application/x-compressed-tar # .tar.gz
- name: Upload JSON schemas archive
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: signal-cli-${{ steps.version.outputs.version }}-json-schemas.tar.gz
asset_name: signal-cli-${{ steps.version.outputs.version }}-json-schemas.tar.gz
asset_content_type: application/x-compressed-tar # .tar.gz
build-container:
needs: release
runs-on: ubuntu-latest

View File

@ -2,10 +2,6 @@
## [Unreleased]
### Added
- New `sendStory` command to post file attachment stories to "My Story"
### Fixed
- Sending to large groups is no longer slowed down by members that are already known to be unregistered; they are skipped instead of being retried via the legacy 1:1 send path on every send.

View File

@ -54,7 +54,6 @@ import org.asamk.signal.manager.api.UserStatus;
import org.asamk.signal.manager.api.UsernameLinkUrl;
import org.asamk.signal.manager.api.UsernameStatus;
import org.asamk.signal.manager.api.VerificationMethodNotAvailableException;
import org.signal.core.util.UuidUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -75,10 +74,6 @@ public interface Manager extends Closeable {
return PhoneNumberUtil.getInstance().isPossibleNumber(e164Number, countryCode);
}
static boolean isValidAci(final String aci) {
return UuidUtil.INSTANCE.isUuid(aci);
}
static boolean isSignalClientAvailable() {
final Logger logger = LoggerFactory.getLogger(Manager.class);
try {
@ -96,8 +91,6 @@ public interface Manager extends Closeable {
String getSelfNumber();
String getSelfACI();
/**
* This is used for checking a set of phone numbers for registration on Signal
*
@ -220,14 +213,6 @@ public interface Manager extends Closeable {
long editTargetTimestamp
) throws IOException, AttachmentInvalidException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException, UnregisteredRecipientException, InvalidStickerException;
/**
* Post a file attachment story to "My Story".
*
* @param attachment path to the file to upload and post as a story
* @param allowsReplies whether other users are allowed to reply to this story
*/
SendMessageResults sendStory(String attachment, boolean allowsReplies) throws IOException, AttachmentInvalidException;
SendMessageResults sendRemoteDeleteMessage(
long targetSentTimestamp,
Set<RecipientIdentifier> recipients

View File

@ -15,7 +15,6 @@ import org.asamk.signal.manager.internal.RegistrationManagerImpl;
import org.asamk.signal.manager.storage.SignalAccount;
import org.asamk.signal.manager.storage.accounts.AccountsStore;
import org.asamk.signal.manager.util.KeyUtils;
import org.signal.core.models.ServiceId.ACI;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.push.exceptions.DeprecatedVersionException;
@ -68,7 +67,7 @@ public class SignalAccountFiles {
public MultiAccountManager initMultiAccountManager() throws IOException {
final var managerPairs = accountsStore.getAllAccounts().parallelStream().map(a -> {
try {
return new Pair<Manager, Throwable>(initManagerByNumber(a.number(), a.path()), null);
return new Pair<Manager, Throwable>(initManager(a.number(), a.path()), null);
} catch (NotRegisteredException e) {
logger.warn("Ignoring {}: {} ({})", a.number(), e.getMessage(), e.getClass().getSimpleName());
return null;
@ -91,31 +90,15 @@ public class SignalAccountFiles {
return new MultiAccountManagerImpl(managers, this);
}
public Manager initManagerByNumber(String number) throws IOException, NotRegisteredException, AccountCheckException {
public Manager initManager(String number) throws IOException, NotRegisteredException, AccountCheckException {
final var accountPath = accountsStore.getPathByNumber(number);
return this.initManagerByNumber(number, accountPath);
return this.initManager(number, accountPath);
}
public Manager initManagerByAci(String aciStr) throws IOException, NotRegisteredException, AccountCheckException {
final var aci = ACI.parseOrThrow(aciStr);
final var accountPath = accountsStore.getPathByAci(aci);
return this.initManagerByAci(aci, accountPath);
}
private Manager initManagerByNumber(
private Manager initManager(
String number,
String accountPath
) throws IOException, NotRegisteredException, AccountCheckException {
final var account = loadAccount(accountPath);
if (!number.equals(account.getNumber())) {
account.close();
throw new IOException("Number in account file doesn't match expected number: " + account.getNumber());
}
return initManagerFromAccount(number, accountPath, account);
}
private SignalAccount loadAccount(final String accountPath) throws NotRegisteredException, IOException {
if (accountPath == null) {
throw new NotRegisteredException();
}
@ -123,27 +106,12 @@ public class SignalAccountFiles {
throw new NotRegisteredException();
}
return SignalAccount.load(pathConfig.dataPath(), accountPath, true, settings);
}
private Manager initManagerByAci(
ACI aci,
String accountPath
) throws IOException, NotRegisteredException, AccountCheckException {
final var account = loadAccount(accountPath);
if (!aci.equals(account.getAci())) {
var account = SignalAccount.load(pathConfig.dataPath(), accountPath, true, settings);
if (!number.equals(account.getNumber())) {
account.close();
throw new IOException("ACI in account file doesn't match expected ACI: " + account.getAci());
throw new IOException("Number in account file doesn't match expected number: " + account.getNumber());
}
return initManagerFromAccount(aci.toString(), accountPath, account);
}
private ManagerImpl initManagerFromAccount(
final String identifier,
final String accountPath,
final SignalAccount account
) throws NotRegisteredException, IOException, AccountCheckException {
if (!account.isRegistered()) {
account.close();
throw new NotRegisteredException();
@ -168,7 +136,7 @@ public class SignalAccountFiles {
throw new IOException("signal-cli version is too old for the Signal-Server, please update.");
} catch (IOException e) {
manager.close();
throw new AccountCheckException("Error while checking account " + identifier + ": " + e.getMessage(), e);
throw new AccountCheckException("Error while checking account " + number + ": " + e.getMessage(), e);
}
if (account.getServiceEnvironment() == null) {

View File

@ -35,8 +35,6 @@ 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.calls.SignalServiceCallMessage;
import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage;
@ -332,53 +330,6 @@ public class SendHelper {
return result;
}
/**
* Send a story message (file attachment) to "My Story".
*/
public List<SendMessageResult> sendStoryMessage(
SignalServiceStoryMessage storyMessage,
long timestamp,
Set<RecipientId> recipientIds,
boolean allowsReplies
) throws IOException {
final var messageSender = dependencies.getMessageSender();
final var recipientIdList = List.copyOf(recipientIds);
final var addressesMap = recipientIdList.stream()
.collect(Collectors.toMap(id -> id, context.getRecipientHelper()::resolveSignalServiceAddress));
final var unidentifiedAccessesMap = context.getUnidentifiedAccessHelper().getAccessFor(recipientIds);
final var addresses = recipientIdList.stream().map(addressesMap::get).toList();
final var unidentifiedAccesses = recipientIdList.stream().map(unidentifiedAccessesMap::get).toList();
final var storyMessageRecipients = recipientIdList.stream()
.map(id -> new SignalServiceStoryMessageRecipient(addressesMap.get(id),
List.of(DistributionId.MY_STORY.asUuid().toString()),
allowsReplies))
.collect(Collectors.toSet());
final List<SendMessageResult> results;
try {
results = messageSender.sendGroupStory(DistributionId.MY_STORY,
Optional.empty(),
addresses,
unidentifiedAccesses,
null,
false,
storyMessage,
timestamp,
storyMessageRecipients,
null);
} catch (UntrustedIdentityException | InvalidKeyException | NoSessionException | InvalidRegistrationIdException e) {
throw new IOException(e);
}
for (var r : results) {
handleSendMessageResult(r);
}
return results;
}
private List<SendMessageResult> sendAsGroupMessage(
final SignalServiceDataMessage.Builder messageBuilder,
final GroupInfo g,

View File

@ -105,12 +105,10 @@ import org.signal.libsignal.usernames.BaseUsernameException;
import org.signal.network.exceptions.NonSuccessfulResponseCodeException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
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.messages.calls.AnswerMessage;
import org.whispersystems.signalservice.api.messages.calls.BusyMessage;
@ -247,11 +245,6 @@ public class ManagerImpl implements Manager {
return account.getNumber();
}
@Override
public String getSelfACI() {
return account.getAci().toString();
}
public void checkAccountState() throws IOException {
context.getAccountHelper().checkAccountState();
final var lastRecipientsRefresh = account.getLastRecipientsRefresh();
@ -834,50 +827,6 @@ public class ManagerImpl implements Manager {
return sendMessage(messageBuilder, recipients, false, Optional.of(editTargetTimestamp), message.urgent());
}
@Override
public SendMessageResults sendStory(
String attachment,
boolean allowsReplies
) throws IOException, AttachmentInvalidException {
final var file = new File(attachment);
final var mimeType = MimeUtils.getFileMimeType(file);
if (mimeType.isEmpty() || (!mimeType.get().startsWith("image/") && !mimeType.get().startsWith("video/"))) {
throw new AttachmentInvalidException(attachment,
new IOException("Stories only support image and video attachments"));
}
final var recipients = account.getRecipientStore()
.getRecipients(true, Optional.of(false), Set.of(), Optional.empty());
final var recipientIds = recipients.stream()
.filter(r -> !r.getRecipientId().equals(account.getSelfRecipientId()))
.filter(r -> r.getContact() != null && !r.getContact().hideStory())
.map(r -> r.getRecipientId())
.collect(Collectors.toSet());
if (recipientIds.isEmpty()) {
throw new IOException("No eligible contacts found for story delivery");
}
final var uploadedAttachment = context.getAttachmentHelper().uploadAttachment(attachment);
final var storyMessage = SignalServiceStoryMessage.forFileAttachment(account.getProfileKey().serialize(),
null,
uploadedAttachment,
allowsReplies,
List.of());
final var timestamp = getNextMessageTimestamp();
final var sendResults = context.getSendHelper()
.sendStoryMessage(storyMessage, timestamp, recipientIds, allowsReplies);
final var results = new HashMap<RecipientIdentifier, List<SendMessageResult>>();
for (final var sendResult : sendResults) {
final var result = toSendMessageResult(sendResult);
results.put(RecipientIdentifier.Single.fromAddress(result.address()), List.of(result));
}
return new SendMessageResults(timestamp, results);
}
private void applyMessage(
final SignalServiceDataMessage.Builder messageBuilder,
final Message message

View File

@ -7,7 +7,6 @@ import org.asamk.signal.manager.RegistrationManager;
import org.asamk.signal.manager.SignalAccountFiles;
import org.asamk.signal.manager.api.AccountCheckException;
import org.asamk.signal.manager.api.NotRegisteredException;
import org.signal.core.util.UuidUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -96,47 +95,23 @@ public class MultiAccountManagerImpl implements MultiAccountManager {
}
@Override
public Manager getManager(final String identifier) {
public Manager getManager(final String number) {
synchronized (managers) {
if (UuidUtil.INSTANCE.isUuid(identifier)) {
// Check if UUID corresponds to an already-loaded manager
final var existing = managers.stream()
.filter(m -> m.getSelfACI().equals(identifier))
.findFirst()
.orElse(null);
if (existing != null) {
logger.debug("Found already loaded manager for ACI: {}", identifier);
return existing;
}
// Load by ACI
try {
final var newManager = signalAccountFiles.initManagerByAci(identifier);
managers.add(newManager);
return newManager;
} catch (NotRegisteredException e) {
logger.debug("Manager not found by ACI: {}", identifier);
} catch (IOException | IllegalArgumentException | AccountCheckException e) {
logger.warn("Failed to load new manager by ACI: {}", identifier, e);
}
} else {
// Phone number check already loaded managers
var existing = managers.stream()
.filter(m -> m.getSelfNumber().equals(identifier))
.findFirst()
.orElse(null);
if (existing != null) {
return existing;
}
// Load by phone number
try {
final var newManager = signalAccountFiles.initManagerByNumber(identifier);
managers.add(newManager);
return newManager;
} catch (NotRegisteredException | IOException | IllegalArgumentException | AccountCheckException e) {
logger.warn("Failed to load manager by number: {}", identifier, e);
}
final var manager = managers.stream()
.filter(m -> m.getSelfNumber().equals(number))
.findFirst()
.orElse(null);
if (manager != null) {
return manager;
}
try {
final var newManager = signalAccountFiles.initManager(number);
managers.add(newManager);
return newManager;
} catch (IOException | NotRegisteredException | AccountCheckException e) {
logger.warn("Failed to load new manager", e);
return null;
}
return null;
}
}

View File

@ -30,7 +30,6 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@ -52,16 +51,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
private final Map<Long, Long> recipientsMerged = new HashMap<>();
private static final int MAX_RECIPIENT_CACHE_SIZE = 2000;
private final Map<ServiceId, RecipientWithAddress> recipientAddressCache = Collections.synchronizedMap(
new LinkedHashMap<>(16, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<ServiceId, RecipientWithAddress> eldest) {
return size() > MAX_RECIPIENT_CACHE_SIZE;
}
});
private final Map<ServiceId, RecipientWithAddress> recipientAddressCache = Collections.synchronizedMap(new HashMap<>());
public static void createSql(Connection connection) throws SQLException {
// When modifying the CREATE statement here, also add a migration in AccountDatabase.java

View File

@ -18,7 +18,7 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@ -28,15 +28,8 @@ public class SessionStore implements SignalServiceSessionStore {
private static final String TABLE_SESSION = "session";
private static final Logger logger = LoggerFactory.getLogger(SessionStore.class);
private static final int MAX_CACHE_SIZE = 1000;
private final Map<Key, SessionRecord> cachedSessions = new LinkedHashMap<>(16, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<Key, SessionRecord> eldest) {
return size() > MAX_CACHE_SIZE;
}
};
private final Map<Key, SessionRecord> cachedSessions = new HashMap<>();
private final Database database;
private final int accountIdType;

View File

@ -632,16 +632,6 @@ Specify the timestamp of the message to which to react.
*--type* TYPE::
Specify the receipt type, either `read` (the default) or `viewed`.
=== sendStory
Post a file attachment story to your Story, visible to all contacts.
*-a* ATTACHMENT, *--attachment* ATTACHMENT::
Specify the file path to the image or video to post as a story.
*--no-replies*::
Disable replies on this story. By default, replies are allowed.
=== sendTyping
Send typing message to trigger a typing indicator for the recipient.

View File

@ -18,13 +18,12 @@ fi
VERSION=$(sed -n 's/\s*version\s*=\s*"\(.*\)".*/\1/p' build.gradle.kts | tail -n1)
echo "$VERSION" >dist/VERSION
# Build jar and schemas
# Build jar
$ENGINE build -t signal-cli:build ${OVERRIDE_JAVA_VERSION:+--build-arg ZULU_TAG=$OVERRIDE_JAVA_VERSION} -f reproducible-builds/build.Containerfile .
git clean -Xfd -e '!/dist/' -e '!/dist/**' -e '!/github/' -e '!/github/**'
# shellcheck disable=SC2086
$ENGINE run --pull=never --rm -v "$(pwd)":/signal-cli:Z -e VERSION="$VERSION" $USER signal-cli:build
mv build/distributions/signal-cli-*.tar.gz dist/
mv build/signal-cli-*-json-schemas.tar.gz dist/
if [ -n "${OVERRIDE_JAVA_VERSION:-}" ]; then
echo -e "\e[33mBuild was performed with overridden Java version $OVERRIDE_JAVA_VERSION, native-image and client will not be built.\e[0m"

View File

@ -12,7 +12,7 @@ reset_file_dates
if [ "$1" == "build" ]; then
./gradlew build jsonSchemas \
./gradlew build \
--no-daemon \
--max-workers=1 \
-Dkotlin.compiler.execution.strategy=in-process \
@ -20,12 +20,6 @@ if [ "$1" == "build" ]; then
-Dorg.gradle.caching=false \
-Porg.gradle.java.installations.auto-download=false \
-Porg.gradle.java.installations.auto-detect=false
schemas_tar="build/signal-cli-${VERSION}-json-schemas.tar"
reset_file_dates
tar --sort=name --mtime="@$SOURCE_DATE_EPOCH" --owner=0 --group=0 --numeric-owner -cf "$schemas_tar" -C build/generated/META-INF/schemas .
gzip -n -9 "$schemas_tar"
cd man
make install
cd ..

View File

@ -22,8 +22,6 @@ public interface Signal extends DBusInterface {
String getSelfNumber();
String getSelfACI();
void subscribeReceive();
void unsubscribeReceive();

View File

@ -184,20 +184,16 @@ public class App {
}
account = getAccountIfOnlyOne(signalAccountFiles);
} else if (!Manager.isValidNumber(account, null)) {
throw new UserErrorException("Invalid account (phone number), make sure you include the country code.");
}
if (command instanceof RegistrationCommand registrationCommand) {
if (!Manager.isValidNumber(account, null)) {
throw new UserErrorException("Invalid account (phone number), make sure you include the country code.");
}
handleRegistrationCommand(registrationCommand, account, signalAccountFiles, commandHandler);
return;
}
if (command instanceof LocalCommand localCommand) {
if (!Manager.isValidNumber(account, null) && !Manager.isValidAci(account)) {
throw new UserErrorException("Invalid account (phone number), make sure you include the country code.");
}
handleLocalCommand(localCommand, account, signalAccountFiles, commandHandler);
return;
}
@ -334,11 +330,7 @@ public class App {
) throws CommandException {
logger.trace("Loading account file for {}", account);
try {
if (Manager.isValidAci(account)) {
return signalAccountFiles.initManagerByAci(account);
} else {
return signalAccountFiles.initManagerByNumber(account);
}
return signalAccountFiles.initManager(account);
} catch (NotRegisteredException e) {
throw new UserErrorException("User " + account + " is not registered.");
} catch (AccountCheckException ace) {

View File

@ -52,7 +52,6 @@ public class Commands {
addCommand(new SendPollTerminateCommand());
addCommand(new SendReactionCommand());
addCommand(new SendReceiptCommand());
addCommand(new SendStoryCommand());
addCommand(new SendSyncRequestCommand());
addCommand(new SendTypingCommand());
addCommand(new SendUnpinMessageCommand());

View File

@ -1,57 +0,0 @@
package org.asamk.signal.commands;
import net.sourceforge.argparse4j.impl.Arguments;
import net.sourceforge.argparse4j.inf.Namespace;
import net.sourceforge.argparse4j.inf.Subparser;
import org.asamk.signal.commands.exceptions.CommandException;
import org.asamk.signal.commands.exceptions.UnexpectedErrorException;
import org.asamk.signal.commands.exceptions.UserErrorException;
import org.asamk.signal.manager.Manager;
import org.asamk.signal.manager.api.AttachmentInvalidException;
import org.asamk.signal.output.OutputWriter;
import java.io.IOException;
import static org.asamk.signal.util.SendMessageResultUtils.outputResult;
public class SendStoryCommand implements JsonRpcLocalCommand {
@Override
public String getName() {
return "sendStory";
}
@Override
public void attachToSubparser(final Subparser subparser) {
subparser.help("Post a story to your Story.");
subparser.addArgument("-a", "--attachment")
.required(true)
.help("Specify the file path to the image or video to post as a story.");
subparser.addArgument("--no-replies")
.action(Arguments.storeTrue())
.help("Disable replies on this story.");
}
@Override
public void handleCommand(
final Namespace ns,
final Manager m,
final OutputWriter outputWriter
) throws CommandException {
final var attachment = ns.getString("attachment");
if (attachment == null || attachment.isEmpty()) {
throw new UserErrorException("An attachment is required for sending a story.");
}
final var noReplies = Boolean.TRUE.equals(ns.getBoolean("no-replies"));
try {
final var results = m.sendStory(attachment, !noReplies);
outputResult(outputWriter, results);
} catch (AttachmentInvalidException | IOException e) {
throw new UnexpectedErrorException("Failed to send story: " + e.getMessage() + " (" + e.getClass()
.getSimpleName() + ")", e);
}
}
}

View File

@ -113,11 +113,6 @@ public class DbusManagerImpl implements Manager {
return signal.getSelfNumber();
}
@Override
public String getSelfACI() {
return signal.getSelfACI();
}
@Override
public Map<String, UserStatus> getUserStatus(final Set<String> numbers) throws IOException {
final var numbersList = new ArrayList<>(numbers);
@ -547,11 +542,6 @@ public class DbusManagerImpl implements Manager {
return new SendMessageResults(timestamp, Map.of());
}
@Override
public SendMessageResults sendStory(String attachment, boolean allowsReplies) {
throw new UnsupportedOperationException();
}
@Override
public void sendEndSessionMessage(final Set<RecipientIdentifier.Single> recipients) throws IOException {
signal.sendEndSessionMessage(recipients.stream().map(RecipientIdentifier.Single::getIdentifier).toList());

View File

@ -141,11 +141,6 @@ public class DbusSignalImpl implements Signal, AutoCloseable {
return m.getSelfNumber();
}
@Override
public String getSelfACI() {
return m.getSelfACI();
}
@Override
public void subscribeReceive() {
if (dbusMessageHandler == null) {

View File

@ -262,9 +262,6 @@ public class HttpServerHandler implements AutoCloseable {
} else {
final var manager = c.getManager(account);
if (manager == null) {
// Account not found by the given identifier (number or ACI/UUID)
// Log the available accounts to help debug
logger.warn("Account not found for identifier: {}", account);
return null;
}
return List.of(manager);

View File

@ -79,7 +79,7 @@ public class Util {
for (var param : params) {
final var paramParts = param.split("=", 2);
var name = URLDecoder.decode(paramParts[0], StandardCharsets.UTF_8);
var value = paramParts.length == 1 ? null : URLDecoder.decode(paramParts[1].replace("+", "%2B"), StandardCharsets.UTF_8);
var value = paramParts.length == 1 ? null : URLDecoder.decode(paramParts[1], StandardCharsets.UTF_8);
map.put(name, value);
}
return map;

View File

@ -123,11 +123,6 @@ class SseInitialFlushTest {
return "+10000000000";
}
@Override
public String getSelfACI() {
return "00000000-0000-0000-0000-000000000000";
}
@Override
public void addReceiveHandler(ReceiveMessageHandler handler, boolean isWeakListener) {
// no-op
@ -354,11 +349,6 @@ class SseInitialFlushTest {
return null;
}
@Override
public SendMessageResults sendStory(String attachment, boolean allowsReplies) {
return new SendMessageResults(0, Map.of());
}
@Override
public void hideRecipient(RecipientIdentifier.Single recipient) {
}

View File

@ -120,11 +120,6 @@ class SubscribeCallEventsTest {
return selfNumber;
}
@Override
public String getSelfACI() {
return "00000000-0000-0000-0000-000000000000";
}
// --- Stubs for remaining Manager interface methods ---
@Override
public Map<String, UserStatus> getUserStatus(Set<String> n) {
@ -372,11 +367,6 @@ class SubscribeCallEventsTest {
return null;
}
@Override
public SendMessageResults sendStory(String attachment, boolean allowsReplies) {
return new SendMessageResults(0, Map.of());
}
@Override
public void hideRecipient(RecipientIdentifier.Single r) {
}