Merge branch 'AsamK:master' into dev-http

This commit is contained in:
ced-b 2022-10-31 11:38:03 -04:00 committed by GitHub
commit 48b4fd5b6f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 250 additions and 91 deletions

View file

@ -17,6 +17,7 @@ jobs:
with: with:
distribution: 'adopt' distribution: 'adopt'
java-version: ${{ matrix.java }} java-version: ${{ matrix.java }}
cache: 'gradle'
- name: Build with Gradle - name: Build with Gradle
run: ./gradlew --no-daemon build run: ./gradlew --no-daemon build
- name: Compress archive - name: Compress archive
@ -37,6 +38,7 @@ jobs:
with: with:
version: 'latest' version: 'latest'
java-version: '17' java-version: '17'
cache: 'gradle'
components: 'native-image' components: 'native-image'
github-token: ${{ secrets.GITHUB_TOKEN }} github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Build with Gradle - name: Build with Gradle

View file

@ -179,6 +179,7 @@ jobs:
with: with:
distribution: 'adopt' distribution: 'adopt'
java-version: ${{ env.JAVA_VERSION }} java-version: ${{ env.JAVA_VERSION }}
java-package: 'jre"
- name: Run signal-cli - name: Run signal-cli
run: | run: |

View file

@ -162,6 +162,12 @@ pub enum CliCommands {
#[arg(long)] #[arg(long)]
sticker: Option<String>, sticker: Option<String>,
#[arg(long)]
story_timestamp: Option<u64>,
#[arg(long)]
story_author: Option<String>,
}, },
SendContacts, SendContacts,
SendPaymentNotification { SendPaymentNotification {
@ -193,6 +199,9 @@ pub enum CliCommands {
#[arg(short = 'r', long)] #[arg(short = 'r', long)]
remove: bool, remove: bool,
#[arg(long)]
story: bool,
}, },
SendReceipt { SendReceipt {
recipient: String, recipient: String,

View file

@ -130,6 +130,8 @@ pub trait Rpc {
#[allow(non_snake_case)] quoteMessage: Option<String>, #[allow(non_snake_case)] quoteMessage: Option<String>,
#[allow(non_snake_case)] quoteMention: Vec<String>, #[allow(non_snake_case)] quoteMention: Vec<String>,
sticker: Option<String>, sticker: Option<String>,
#[allow(non_snake_case)] storyTimestamp: Option<u64>,
#[allow(non_snake_case)] storyAuthor: Option<String>,
) -> Result<Value>; ) -> Result<Value>;
#[rpc(name = "sendContacts", params = "named")] #[rpc(name = "sendContacts", params = "named")]
@ -155,6 +157,7 @@ pub trait Rpc {
#[allow(non_snake_case)] targetAuthor: String, #[allow(non_snake_case)] targetAuthor: String,
#[allow(non_snake_case)] targetTimestamp: u64, #[allow(non_snake_case)] targetTimestamp: u64,
remove: bool, remove: bool,
story: bool,
) -> Result<Value>; ) -> Result<Value>;
#[rpc(name = "sendReceipt", params = "named")] #[rpc(name = "sendReceipt", params = "named")]

View file

@ -127,6 +127,8 @@ async fn main() -> Result<(), anyhow::Error> {
quote_message, quote_message,
quote_mention, quote_mention,
sticker, sticker,
story_timestamp,
story_author,
} => { } => {
client client
.send( .send(
@ -143,6 +145,8 @@ async fn main() -> Result<(), anyhow::Error> {
quote_message, quote_message,
quote_mention, quote_mention,
sticker, sticker,
story_timestamp,
story_author,
) )
.await .await
} }
@ -164,6 +168,7 @@ async fn main() -> Result<(), anyhow::Error> {
target_author, target_author,
target_timestamp, target_timestamp,
remove, remove,
story,
} => { } => {
client client
.send_reaction( .send_reaction(
@ -175,6 +180,7 @@ async fn main() -> Result<(), anyhow::Error> {
target_author, target_author,
target_timestamp, target_timestamp,
remove, remove,
story,
) )
.await .await
} }

View file

@ -605,6 +605,17 @@
{"name":"id","parameterTypes":[] } {"name":"id","parameterTypes":[] }
] ]
}, },
{
"name":"org.asamk.signal.commands.ReceiveCommand$ReceiveParams",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"queryAllDeclaredConstructors":true,
"methods":[
{"name":"<init>","parameterTypes":["java.lang.Double","java.lang.Integer"] },
{"name":"maxMessages","parameterTypes":[] },
{"name":"timeout","parameterTypes":[] }
]
},
{ {
"name":"org.asamk.signal.commands.RegisterCommand$RegistrationParams", "name":"org.asamk.signal.commands.RegisterCommand$RegistrationParams",
"allDeclaredFields":true, "allDeclaredFields":true,

View file

@ -136,7 +136,8 @@ public interface Manager extends Closeable {
boolean remove, boolean remove,
RecipientIdentifier.Single targetAuthor, RecipientIdentifier.Single targetAuthor,
long targetSentTimestamp, long targetSentTimestamp,
Set<RecipientIdentifier> recipients Set<RecipientIdentifier> recipients,
final boolean isStory
) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException, UnregisteredRecipientException; ) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException, UnregisteredRecipientException;
SendMessageResults sendPaymentNotificationMessage( SendMessageResults sendPaymentNotificationMessage(
@ -201,12 +202,9 @@ public interface Manager extends Closeable {
/** /**
* Receive new messages from server, returns if no new message arrive in a timespan of timeout. * Receive new messages from server, returns if no new message arrive in a timespan of timeout.
*/ */
void receiveMessages(Duration timeout, ReceiveMessageHandler handler) throws IOException; public void receiveMessages(
Optional<Duration> timeout, Optional<Integer> maxMessages, ReceiveMessageHandler handler
/** ) throws IOException;
* Receive new messages from server, returns only if the thread is interrupted.
*/
void receiveMessages(ReceiveMessageHandler handler) throws IOException;
void setReceiveConfig(ReceiveConfig receiveConfig); void setReceiveConfig(ReceiveConfig receiveConfig);

View file

@ -624,6 +624,14 @@ class ManagerImpl implements Manager {
} }
messageBuilder.withPreviews(previews); messageBuilder.withPreviews(previews);
} }
if (message.storyReply().isPresent()) {
final var storyReply = message.storyReply().get();
final var authorServiceId = context.getRecipientHelper()
.resolveSignalServiceAddress(context.getRecipientHelper().resolveRecipient(storyReply.author()))
.getServiceId();
messageBuilder.withStoryContext(new SignalServiceDataMessage.StoryContext(authorServiceId,
storyReply.timestamp()));
}
} }
private ArrayList<SignalServiceDataMessage.Mention> resolveMentions(final List<Message.Mention> mentionList) throws UnregisteredRecipientException { private ArrayList<SignalServiceDataMessage.Mention> resolveMentions(final List<Message.Mention> mentionList) throws UnregisteredRecipientException {
@ -667,14 +675,19 @@ class ManagerImpl implements Manager {
boolean remove, boolean remove,
RecipientIdentifier.Single targetAuthor, RecipientIdentifier.Single targetAuthor,
long targetSentTimestamp, long targetSentTimestamp,
Set<RecipientIdentifier> recipients Set<RecipientIdentifier> recipients,
final boolean isStory
) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException, UnregisteredRecipientException { ) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException, UnregisteredRecipientException {
var targetAuthorRecipientId = context.getRecipientHelper().resolveRecipient(targetAuthor); var targetAuthorRecipientId = context.getRecipientHelper().resolveRecipient(targetAuthor);
var reaction = new SignalServiceDataMessage.Reaction(emoji, final var authorServiceId = context.getRecipientHelper()
remove, .resolveSignalServiceAddress(targetAuthorRecipientId)
context.getRecipientHelper().resolveSignalServiceAddress(targetAuthorRecipientId).getServiceId(), .getServiceId();
targetSentTimestamp); var reaction = new SignalServiceDataMessage.Reaction(emoji, remove, authorServiceId, targetSentTimestamp);
final var messageBuilder = SignalServiceDataMessage.newBuilder().withReaction(reaction); final var messageBuilder = SignalServiceDataMessage.newBuilder().withReaction(reaction);
if (isStory) {
messageBuilder.withStoryContext(new SignalServiceDataMessage.StoryContext(authorServiceId,
targetSentTimestamp));
}
return sendMessage(messageBuilder, recipients); return sendMessage(messageBuilder, recipients);
} }
@ -948,17 +961,16 @@ class ManagerImpl implements Manager {
} }
@Override @Override
public void receiveMessages(Duration timeout, ReceiveMessageHandler handler) throws IOException { public void receiveMessages(
receiveMessages(timeout, true, handler); Optional<Duration> timeout,
} Optional<Integer> maxMessages,
ReceiveMessageHandler handler
@Override ) throws IOException {
public void receiveMessages(ReceiveMessageHandler handler) throws IOException { receiveMessages(timeout.orElse(Duration.ofMinutes(1)), timeout.isPresent(), maxMessages.orElse(null), handler);
receiveMessages(Duration.ofMinutes(1), false, handler);
} }
private void receiveMessages( private void receiveMessages(
Duration timeout, boolean returnOnTimeout, ReceiveMessageHandler handler Duration timeout, boolean returnOnTimeout, Integer maxMessages, ReceiveMessageHandler handler
) throws IOException { ) throws IOException {
if (isReceiving()) { if (isReceiving()) {
throw new IllegalStateException("Already receiving message."); throw new IllegalStateException("Already receiving message.");
@ -966,7 +978,7 @@ class ManagerImpl implements Manager {
isReceivingSynchronous = true; isReceivingSynchronous = true;
receiveThread = Thread.currentThread(); receiveThread = Thread.currentThread();
try { try {
context.getReceiveHelper().receiveMessages(timeout, returnOnTimeout, handler); context.getReceiveHelper().receiveMessages(timeout, returnOnTimeout, maxMessages, handler);
} finally { } finally {
receiveThread = null; receiveThread = null;
isReceivingSynchronous = false; isReceivingSynchronous = false;

View file

@ -21,7 +21,6 @@ import org.asamk.signal.manager.config.ServiceConfig;
import org.asamk.signal.manager.config.ServiceEnvironmentConfig; import org.asamk.signal.manager.config.ServiceEnvironmentConfig;
import org.asamk.signal.manager.storage.SignalAccount; import org.asamk.signal.manager.storage.SignalAccount;
import org.asamk.signal.manager.storage.accounts.AccountsStore; import org.asamk.signal.manager.storage.accounts.AccountsStore;
import org.asamk.signal.manager.storage.identities.TrustNewIdentity;
import org.asamk.signal.manager.util.KeyUtils; import org.asamk.signal.manager.util.KeyUtils;
import org.signal.libsignal.protocol.IdentityKeyPair; import org.signal.libsignal.protocol.IdentityKeyPair;
import org.signal.libsignal.protocol.util.KeyHelper; import org.signal.libsignal.protocol.util.KeyHelper;
@ -147,7 +146,7 @@ class ProvisioningManagerImpl implements ProvisioningManager {
registrationId, registrationId,
pniRegistrationId, pniRegistrationId,
profileKey, profileKey,
TrustNewIdentity.ON_FIRST_USE); Settings.DEFAULT);
ManagerImpl m = null; ManagerImpl m = null;
try { try {
@ -194,10 +193,7 @@ class ProvisioningManagerImpl implements ProvisioningManager {
private boolean canRelinkExistingAccount(final String accountPath) throws IOException { private boolean canRelinkExistingAccount(final String accountPath) throws IOException {
final SignalAccount signalAccount; final SignalAccount signalAccount;
try { try {
signalAccount = SignalAccount.load(pathConfig.dataPath(), signalAccount = SignalAccount.load(pathConfig.dataPath(), accountPath, false, Settings.DEFAULT);
accountPath,
false,
TrustNewIdentity.ON_FIRST_USE);
} catch (IOException e) { } catch (IOException e) {
logger.debug("Account in use or failed to load.", e); logger.debug("Account in use or failed to load.", e);
return false; return false;

View file

@ -0,0 +1,8 @@
package org.asamk.signal.manager;
import org.asamk.signal.manager.storage.identities.TrustNewIdentity;
public record Settings(TrustNewIdentity trustNewIdentity, boolean disableMessageSendLog) {
public static Settings DEFAULT = new Settings(TrustNewIdentity.ON_FIRST_USE, false);
}

View file

@ -7,7 +7,6 @@ import org.asamk.signal.manager.config.ServiceEnvironment;
import org.asamk.signal.manager.config.ServiceEnvironmentConfig; import org.asamk.signal.manager.config.ServiceEnvironmentConfig;
import org.asamk.signal.manager.storage.SignalAccount; import org.asamk.signal.manager.storage.SignalAccount;
import org.asamk.signal.manager.storage.accounts.AccountsStore; import org.asamk.signal.manager.storage.accounts.AccountsStore;
import org.asamk.signal.manager.storage.identities.TrustNewIdentity;
import org.asamk.signal.manager.util.KeyUtils; import org.asamk.signal.manager.util.KeyUtils;
import org.signal.libsignal.protocol.util.KeyHelper; import org.signal.libsignal.protocol.util.KeyHelper;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -28,27 +27,27 @@ public class SignalAccountFiles {
private final ServiceEnvironment serviceEnvironment; private final ServiceEnvironment serviceEnvironment;
private final ServiceEnvironmentConfig serviceEnvironmentConfig; private final ServiceEnvironmentConfig serviceEnvironmentConfig;
private final String userAgent; private final String userAgent;
private final TrustNewIdentity trustNewIdentity; private final Settings settings;
private final AccountsStore accountsStore; private final AccountsStore accountsStore;
public SignalAccountFiles( public SignalAccountFiles(
final File settingsPath, final File settingsPath,
final ServiceEnvironment serviceEnvironment, final ServiceEnvironment serviceEnvironment,
final String userAgent, final String userAgent,
final TrustNewIdentity trustNewIdentity final Settings settings
) throws IOException { ) throws IOException {
this.pathConfig = PathConfig.createDefault(settingsPath); this.pathConfig = PathConfig.createDefault(settingsPath);
this.serviceEnvironment = serviceEnvironment; this.serviceEnvironment = serviceEnvironment;
this.serviceEnvironmentConfig = ServiceConfig.getServiceEnvironmentConfig(this.serviceEnvironment, userAgent); this.serviceEnvironmentConfig = ServiceConfig.getServiceEnvironmentConfig(this.serviceEnvironment, userAgent);
this.userAgent = userAgent; this.userAgent = userAgent;
this.trustNewIdentity = trustNewIdentity; this.settings = settings;
this.accountsStore = new AccountsStore(pathConfig.dataPath(), serviceEnvironment, accountPath -> { this.accountsStore = new AccountsStore(pathConfig.dataPath(), serviceEnvironment, accountPath -> {
if (accountPath == null || !SignalAccount.accountFileExists(pathConfig.dataPath(), accountPath)) { if (accountPath == null || !SignalAccount.accountFileExists(pathConfig.dataPath(), accountPath)) {
return null; return null;
} }
try { try {
return SignalAccount.load(pathConfig.dataPath(), accountPath, false, trustNewIdentity); return SignalAccount.load(pathConfig.dataPath(), accountPath, false, settings);
} catch (Exception e) { } catch (Exception e) {
return null; return null;
} }
@ -90,7 +89,7 @@ public class SignalAccountFiles {
throw new NotRegisteredException(); throw new NotRegisteredException();
} }
var account = SignalAccount.load(pathConfig.dataPath(), accountPath, true, trustNewIdentity); var account = SignalAccount.load(pathConfig.dataPath(), accountPath, true, settings);
if (!number.equals(account.getNumber())) { if (!number.equals(account.getNumber())) {
account.close(); account.close();
throw new IOException("Number in account file doesn't match expected number: " + account.getNumber()); throw new IOException("Number in account file doesn't match expected number: " + account.getNumber());
@ -168,7 +167,7 @@ public class SignalAccountFiles {
registrationId, registrationId,
pniRegistrationId, pniRegistrationId,
profileKey, profileKey,
trustNewIdentity); settings);
return new RegistrationManagerImpl(account, return new RegistrationManagerImpl(account,
pathConfig, pathConfig,
@ -178,7 +177,7 @@ public class SignalAccountFiles {
new AccountFileUpdaterImpl(accountsStore, newAccountPath)); new AccountFileUpdaterImpl(accountsStore, newAccountPath));
} }
var account = SignalAccount.load(pathConfig.dataPath(), accountPath, true, trustNewIdentity); var account = SignalAccount.load(pathConfig.dataPath(), accountPath, true, settings);
if (!number.equals(account.getNumber())) { if (!number.equals(account.getNumber())) {
account.close(); account.close();
throw new IOException("Number in account file doesn't match expected number: " + account.getNumber()); throw new IOException("Number in account file doesn't match expected number: " + account.getNumber());

View file

@ -9,7 +9,8 @@ public record Message(
List<Mention> mentions, List<Mention> mentions,
Optional<Quote> quote, Optional<Quote> quote,
Optional<Sticker> sticker, Optional<Sticker> sticker,
List<Preview> previews List<Preview> previews,
Optional<StoryReply> storyReply
) { ) {
public record Mention(RecipientIdentifier.Single recipient, int start, int length) {} public record Mention(RecipientIdentifier.Single recipient, int start, int length) {}
@ -19,4 +20,6 @@ public record Message(
public record Sticker(byte[] packId, int stickerId) {} public record Sticker(byte[] packId, int stickerId) {}
public record Preview(String url, String title, String description, Optional<String> image) {} public record Preview(String url, String title, String description, Optional<String> image) {}
public record StoryReply(long timestamp, RecipientIdentifier.Single author) {}
} }

View file

@ -80,7 +80,7 @@ public class ReceiveHelper {
public void receiveMessagesContinuously(Manager.ReceiveMessageHandler handler) { public void receiveMessagesContinuously(Manager.ReceiveMessageHandler handler) {
while (!shouldStop) { while (!shouldStop) {
try { try {
receiveMessages(Duration.ofMinutes(1), false, handler); receiveMessages(Duration.ofMinutes(1), false, null, handler);
break; break;
} catch (IOException e) { } catch (IOException e) {
logger.warn("Receiving messages failed, retrying", e); logger.warn("Receiving messages failed, retrying", e);
@ -89,7 +89,7 @@ public class ReceiveHelper {
} }
public void receiveMessages( public void receiveMessages(
Duration timeout, boolean returnOnTimeout, Manager.ReceiveMessageHandler handler Duration timeout, boolean returnOnTimeout, Integer maxMessages, Manager.ReceiveMessageHandler handler
) throws IOException { ) throws IOException {
needsToRetryFailedMessages = true; needsToRetryFailedMessages = true;
hasCaughtUpWithOldMessages = false; hasCaughtUpWithOldMessages = false;
@ -107,7 +107,7 @@ public class ReceiveHelper {
signalWebSocket.connect(); signalWebSocket.connect();
try { try {
receiveMessagesInternal(signalWebSocket, timeout, returnOnTimeout, handler, queuedActions); receiveMessagesInternal(signalWebSocket, timeout, returnOnTimeout, maxMessages, handler, queuedActions);
} finally { } finally {
hasCaughtUpWithOldMessages = false; hasCaughtUpWithOldMessages = false;
handleQueuedActions(queuedActions.keySet()); handleQueuedActions(queuedActions.keySet());
@ -122,13 +122,15 @@ public class ReceiveHelper {
final SignalWebSocket signalWebSocket, final SignalWebSocket signalWebSocket,
Duration timeout, Duration timeout,
boolean returnOnTimeout, boolean returnOnTimeout,
Integer maxMessages,
Manager.ReceiveMessageHandler handler, Manager.ReceiveMessageHandler handler,
final Map<HandleAction, HandleAction> queuedActions final Map<HandleAction, HandleAction> queuedActions
) throws IOException { ) throws IOException {
int remainingMessages = maxMessages == null ? -1 : maxMessages;
var backOffCounter = 0; var backOffCounter = 0;
isWaitingForMessage = false; isWaitingForMessage = false;
while (!shouldStop) { while (!shouldStop && remainingMessages != 0) {
if (needsToRetryFailedMessages) { if (needsToRetryFailedMessages) {
retryFailedReceivedMessages(handler); retryFailedReceivedMessages(handler);
needsToRetryFailedMessages = false; needsToRetryFailedMessages = false;
@ -154,6 +156,9 @@ public class ReceiveHelper {
backOffCounter = 0; backOffCounter = 0;
if (result.isPresent()) { if (result.isPresent()) {
if (remainingMessages > 0) {
remainingMessages -= 1;
}
envelope = result.get(); envelope = result.get();
logger.debug("New message received from server"); logger.debug("New message received from server");
} else { } else {

View file

@ -3,6 +3,7 @@ package org.asamk.signal.manager.storage;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import org.asamk.signal.manager.Settings;
import org.asamk.signal.manager.api.Pair; import org.asamk.signal.manager.api.Pair;
import org.asamk.signal.manager.api.TrustLevel; import org.asamk.signal.manager.api.TrustLevel;
import org.asamk.signal.manager.config.ServiceEnvironment; import org.asamk.signal.manager.config.ServiceEnvironment;
@ -17,7 +18,6 @@ import org.asamk.signal.manager.storage.groups.LegacyGroupStore;
import org.asamk.signal.manager.storage.identities.IdentityKeyStore; import org.asamk.signal.manager.storage.identities.IdentityKeyStore;
import org.asamk.signal.manager.storage.identities.LegacyIdentityKeyStore; import org.asamk.signal.manager.storage.identities.LegacyIdentityKeyStore;
import org.asamk.signal.manager.storage.identities.SignalIdentityKeyStore; import org.asamk.signal.manager.storage.identities.SignalIdentityKeyStore;
import org.asamk.signal.manager.storage.identities.TrustNewIdentity;
import org.asamk.signal.manager.storage.messageCache.MessageCache; import org.asamk.signal.manager.storage.messageCache.MessageCache;
import org.asamk.signal.manager.storage.prekeys.LegacyPreKeyStore; import org.asamk.signal.manager.storage.prekeys.LegacyPreKeyStore;
import org.asamk.signal.manager.storage.prekeys.LegacySignedPreKeyStore; import org.asamk.signal.manager.storage.prekeys.LegacySignedPreKeyStore;
@ -136,7 +136,7 @@ public class SignalAccount implements Closeable {
private IdentityKeyPair pniIdentityKeyPair; private IdentityKeyPair pniIdentityKeyPair;
private int localRegistrationId; private int localRegistrationId;
private int localPniRegistrationId; private int localPniRegistrationId;
private TrustNewIdentity trustNewIdentity; private Settings settings;
private long lastReceiveTimestamp = 0; private long lastReceiveTimestamp = 0;
private boolean registered = false; private boolean registered = false;
@ -170,7 +170,7 @@ public class SignalAccount implements Closeable {
} }
public static SignalAccount load( public static SignalAccount load(
File dataPath, String accountPath, boolean waitForLock, final TrustNewIdentity trustNewIdentity File dataPath, String accountPath, boolean waitForLock, final Settings settings
) throws IOException { ) throws IOException {
logger.trace("Opening account file"); logger.trace("Opening account file");
final var fileName = getFileName(dataPath, accountPath); final var fileName = getFileName(dataPath, accountPath);
@ -178,7 +178,7 @@ public class SignalAccount implements Closeable {
try { try {
var signalAccount = new SignalAccount(pair.first(), pair.second()); var signalAccount = new SignalAccount(pair.first(), pair.second());
logger.trace("Loading account file"); logger.trace("Loading account file");
signalAccount.load(dataPath, accountPath, trustNewIdentity); signalAccount.load(dataPath, accountPath, settings);
logger.trace("Migrating legacy parts of account file"); logger.trace("Migrating legacy parts of account file");
signalAccount.migrateLegacyConfigs(); signalAccount.migrateLegacyConfigs();
@ -200,7 +200,7 @@ public class SignalAccount implements Closeable {
int registrationId, int registrationId,
int pniRegistrationId, int pniRegistrationId,
ProfileKey profileKey, ProfileKey profileKey,
final TrustNewIdentity trustNewIdentity final Settings settings
) throws IOException { ) throws IOException {
IOUtils.createPrivateDirectories(dataPath); IOUtils.createPrivateDirectories(dataPath);
var fileName = getFileName(dataPath, accountPath); var fileName = getFileName(dataPath, accountPath);
@ -221,7 +221,7 @@ public class SignalAccount implements Closeable {
signalAccount.pniIdentityKeyPair = pniIdentityKey; signalAccount.pniIdentityKeyPair = pniIdentityKey;
signalAccount.localRegistrationId = registrationId; signalAccount.localRegistrationId = registrationId;
signalAccount.localPniRegistrationId = pniRegistrationId; signalAccount.localPniRegistrationId = pniRegistrationId;
signalAccount.trustNewIdentity = trustNewIdentity; signalAccount.settings = settings;
signalAccount.configurationStore = new ConfigurationStore(signalAccount::saveConfigurationStore); signalAccount.configurationStore = new ConfigurationStore(signalAccount::saveConfigurationStore);
signalAccount.registered = false; signalAccount.registered = false;
@ -248,7 +248,7 @@ public class SignalAccount implements Closeable {
int registrationId, int registrationId,
int pniRegistrationId, int pniRegistrationId,
ProfileKey profileKey, ProfileKey profileKey,
final TrustNewIdentity trustNewIdentity final Settings settings
) throws IOException { ) throws IOException {
IOUtils.createPrivateDirectories(dataPath); IOUtils.createPrivateDirectories(dataPath);
var fileName = getFileName(dataPath, accountPath); var fileName = getFileName(dataPath, accountPath);
@ -267,10 +267,10 @@ public class SignalAccount implements Closeable {
registrationId, registrationId,
pniRegistrationId, pniRegistrationId,
profileKey, profileKey,
trustNewIdentity); settings);
} }
final var signalAccount = load(dataPath, accountPath, true, trustNewIdentity); final var signalAccount = load(dataPath, accountPath, true, settings);
signalAccount.setProvisioningData(number, signalAccount.setProvisioningData(number,
aci, aci,
pni, pni,
@ -318,7 +318,7 @@ public class SignalAccount implements Closeable {
int registrationId, int registrationId,
int pniRegistrationId, int pniRegistrationId,
ProfileKey profileKey, ProfileKey profileKey,
final TrustNewIdentity trustNewIdentity final Settings settings
) throws IOException { ) throws IOException {
var fileName = getFileName(dataPath, accountPath); var fileName = getFileName(dataPath, accountPath);
IOUtils.createPrivateFile(fileName); IOUtils.createPrivateFile(fileName);
@ -331,7 +331,7 @@ public class SignalAccount implements Closeable {
signalAccount.serviceEnvironment = serviceEnvironment; signalAccount.serviceEnvironment = serviceEnvironment;
signalAccount.localRegistrationId = registrationId; signalAccount.localRegistrationId = registrationId;
signalAccount.localPniRegistrationId = pniRegistrationId; signalAccount.localPniRegistrationId = pniRegistrationId;
signalAccount.trustNewIdentity = trustNewIdentity; signalAccount.settings = settings;
signalAccount.setProvisioningData(number, signalAccount.setProvisioningData(number,
aci, aci,
pni, pni,
@ -502,7 +502,7 @@ public class SignalAccount implements Closeable {
} }
private void load( private void load(
File dataPath, String accountPath, final TrustNewIdentity trustNewIdentity File dataPath, String accountPath, final Settings settings
) throws IOException { ) throws IOException {
this.dataPath = dataPath; this.dataPath = dataPath;
this.accountPath = accountPath; this.accountPath = accountPath;
@ -685,7 +685,7 @@ public class SignalAccount implements Closeable {
this.aciIdentityKeyPair = aciIdentityKeyPair; this.aciIdentityKeyPair = aciIdentityKeyPair;
this.localRegistrationId = registrationId; this.localRegistrationId = registrationId;
this.trustNewIdentity = trustNewIdentity; this.settings = settings;
migratedLegacyConfig = loadLegacyStores(rootNode, legacySignalProtocolStore) || migratedLegacyConfig; migratedLegacyConfig = loadLegacyStores(rootNode, legacySignalProtocolStore) || migratedLegacyConfig;
@ -1156,7 +1156,7 @@ public class SignalAccount implements Closeable {
public IdentityKeyStore getIdentityKeyStore() { public IdentityKeyStore getIdentityKeyStore() {
return getOrCreate(() -> identityKeyStore, return getOrCreate(() -> identityKeyStore,
() -> identityKeyStore = new IdentityKeyStore(getAccountDatabase(), trustNewIdentity)); () -> identityKeyStore = new IdentityKeyStore(getAccountDatabase(), settings.trustNewIdentity()));
} }
public SignalIdentityKeyStore getAciIdentityKeyStore() { public SignalIdentityKeyStore getAciIdentityKeyStore() {
@ -1242,7 +1242,8 @@ public class SignalAccount implements Closeable {
public MessageSendLogStore getMessageSendLogStore() { public MessageSendLogStore getMessageSendLogStore() {
return getOrCreate(() -> messageSendLogStore, return getOrCreate(() -> messageSendLogStore,
() -> messageSendLogStore = new MessageSendLogStore(getAccountDatabase())); () -> messageSendLogStore = new MessageSendLogStore(getAccountDatabase(),
settings.disableMessageSendLog()));
} }
public CredentialsProvider getCredentialsProvider() { public CredentialsProvider getCredentialsProvider() {

View file

@ -33,9 +33,11 @@ public class MessageSendLogStore implements AutoCloseable {
private final Database database; private final Database database;
private final Thread cleanupThread; private final Thread cleanupThread;
private final boolean sendLogDisabled;
public MessageSendLogStore(final Database database) { public MessageSendLogStore(final Database database, final boolean disableMessageSendLog) {
this.database = database; this.database = database;
this.sendLogDisabled = disableMessageSendLog;
this.cleanupThread = new Thread(() -> { this.cleanupThread = new Thread(() -> {
try { try {
final var interval = Duration.ofHours(1).toMillis(); final var interval = Duration.ofHours(1).toMillis();
@ -43,6 +45,7 @@ public class MessageSendLogStore implements AutoCloseable {
try (final var connection = database.getConnection()) { try (final var connection = database.getConnection()) {
deleteOutdatedEntries(connection); deleteOutdatedEntries(connection);
} catch (SQLException e) { } catch (SQLException e) {
logger.debug("MSL", e);
logger.warn("Deleting outdated entries failed"); logger.warn("Deleting outdated entries failed");
break; break;
} }
@ -113,6 +116,9 @@ public class MessageSendLogStore implements AutoCloseable {
public long insertIfPossible( public long insertIfPossible(
long sentTimestamp, SendMessageResult sendMessageResult, ContentHint contentHint, boolean urgent long sentTimestamp, SendMessageResult sendMessageResult, ContentHint contentHint, boolean urgent
) { ) {
if (sendLogDisabled) {
return -1;
}
final RecipientDevices recipientDevice = getRecipientDevices(sendMessageResult); final RecipientDevices recipientDevice = getRecipientDevices(sendMessageResult);
if (recipientDevice == null) { if (recipientDevice == null) {
return -1; return -1;
@ -128,6 +134,9 @@ public class MessageSendLogStore implements AutoCloseable {
public long insertIfPossible( public long insertIfPossible(
long sentTimestamp, List<SendMessageResult> sendMessageResults, ContentHint contentHint, boolean urgent long sentTimestamp, List<SendMessageResult> sendMessageResults, ContentHint contentHint, boolean urgent
) { ) {
if (sendLogDisabled) {
return -1;
}
final var recipientDevices = sendMessageResults.stream() final var recipientDevices = sendMessageResults.stream()
.map(this::getRecipientDevices) .map(this::getRecipientDevices)
.filter(Objects::nonNull) .filter(Objects::nonNull)
@ -146,6 +155,9 @@ public class MessageSendLogStore implements AutoCloseable {
} }
public void addRecipientToExistingEntryIfPossible(final long contentId, final SendMessageResult sendMessageResult) { public void addRecipientToExistingEntryIfPossible(final long contentId, final SendMessageResult sendMessageResult) {
if (sendLogDisabled) {
return;
}
final RecipientDevices recipientDevice = getRecipientDevices(sendMessageResult); final RecipientDevices recipientDevice = getRecipientDevices(sendMessageResult);
if (recipientDevice == null) { if (recipientDevice == null) {
return; return;
@ -157,6 +169,9 @@ public class MessageSendLogStore implements AutoCloseable {
public void addRecipientToExistingEntryIfPossible( public void addRecipientToExistingEntryIfPossible(
final long contentId, final List<SendMessageResult> sendMessageResults final long contentId, final List<SendMessageResult> sendMessageResults
) { ) {
if (sendLogDisabled) {
return;
}
final var recipientDevices = sendMessageResults.stream() final var recipientDevices = sendMessageResults.stream()
.map(this::getRecipientDevices) .map(this::getRecipientDevices)
.filter(Objects::nonNull) .filter(Objects::nonNull)

View file

@ -79,6 +79,9 @@ Choose when to trust new identities:
- `always`: Trust any new identity key without verification - `always`: Trust any new identity key without verification
- `never`: Don't trust any unknown identity key, every key must be verified manually - `never`: Don't trust any unknown identity key, every key must be verified manually
*--disable-send-log*::
Disable message send log (for resending messages that recipient couldn't decrypt).
== Commands == Commands
=== register === register
@ -272,6 +275,12 @@ Specify the description for the link preview (optional).
*--preview-image*:: *--preview-image*::
Specify the image file for the link preview (optional). Specify the image file for the link preview (optional).
*--story-timestamp*::
Specify the timestamp of a story to reply to.
*--story-author*::
Specify the number of the author of the story.
*-e*, *--end-session*:: *-e*, *--end-session*::
Clear session state and send end session message. Clear session state and send end session message.
@ -310,6 +319,9 @@ Specify the timestamp of the message to which to react.
*-r*, *--remove*:: *-r*, *--remove*::
Remove a reaction. Remove a reaction.
*--story*::
React to a story instead of a normal message
=== sendReceipt === sendReceipt
Send a read or viewed receipt to a previously received message. Send a read or viewed receipt to a previously received message.
@ -360,6 +372,9 @@ In json mode this is outputted as one json object per line.
Number of seconds to wait for new messages (negative values disable timeout). Number of seconds to wait for new messages (negative values disable timeout).
Default is 5 seconds. Default is 5 seconds.
*--max-messages*::
Maximum number of messages to receive, before returning.
*--ignore-attachments*:: *--ignore-attachments*::
Dont download attachments of received messages. Dont download attachments of received messages.

View file

@ -23,6 +23,7 @@ import org.asamk.signal.dbus.DbusProvisioningManagerImpl;
import org.asamk.signal.dbus.DbusRegistrationManagerImpl; import org.asamk.signal.dbus.DbusRegistrationManagerImpl;
import org.asamk.signal.manager.Manager; import org.asamk.signal.manager.Manager;
import org.asamk.signal.manager.RegistrationManager; import org.asamk.signal.manager.RegistrationManager;
import org.asamk.signal.manager.Settings;
import org.asamk.signal.manager.SignalAccountFiles; import org.asamk.signal.manager.SignalAccountFiles;
import org.asamk.signal.manager.api.AccountCheckException; import org.asamk.signal.manager.api.AccountCheckException;
import org.asamk.signal.manager.api.NotRegisteredException; import org.asamk.signal.manager.api.NotRegisteredException;
@ -101,6 +102,10 @@ public class App {
.type(Arguments.enumStringType(TrustNewIdentityCli.class)) .type(Arguments.enumStringType(TrustNewIdentityCli.class))
.setDefault(TrustNewIdentityCli.ON_FIRST_USE); .setDefault(TrustNewIdentityCli.ON_FIRST_USE);
parser.addArgument("--disable-send-log")
.help("Disable message send log (for resending messages that recipient couldn't decrypt)")
.action(Arguments.storeTrue());
var subparsers = parser.addSubparsers().title("subcommands").dest("command"); var subparsers = parser.addSubparsers().title("subcommands").dest("command");
Commands.getCommandSubparserAttachers().forEach((key, value) -> { Commands.getCommandSubparserAttachers().forEach((key, value) -> {
@ -167,12 +172,14 @@ public class App {
? TrustNewIdentity.ON_FIRST_USE ? TrustNewIdentity.ON_FIRST_USE
: trustNewIdentityCli == TrustNewIdentityCli.ALWAYS ? TrustNewIdentity.ALWAYS : TrustNewIdentity.NEVER; : trustNewIdentityCli == TrustNewIdentityCli.ALWAYS ? TrustNewIdentity.ALWAYS : TrustNewIdentity.NEVER;
final var disableSendLog = Boolean.TRUE.equals(ns.getBoolean("disable-send-log"));
final SignalAccountFiles signalAccountFiles; final SignalAccountFiles signalAccountFiles;
try { try {
signalAccountFiles = new SignalAccountFiles(configPath, signalAccountFiles = new SignalAccountFiles(configPath,
serviceEnvironment, serviceEnvironment,
BaseConfig.USER_AGENT, BaseConfig.USER_AGENT,
trustNewIdentity); new Settings(trustNewIdentity, disableSendLog));
} catch (IOException e) { } catch (IOException e) {
throw new IOErrorException("Failed to read local accounts list", e); throw new IOErrorException("Failed to read local accounts list", e);
} }

View file

@ -1,5 +1,7 @@
package org.asamk.signal.commands; package org.asamk.signal.commands;
import com.fasterxml.jackson.core.type.TypeReference;
import net.sourceforge.argparse4j.impl.Arguments; import net.sourceforge.argparse4j.impl.Arguments;
import net.sourceforge.argparse4j.inf.Namespace; import net.sourceforge.argparse4j.inf.Namespace;
import net.sourceforge.argparse4j.inf.Subparser; import net.sourceforge.argparse4j.inf.Subparser;
@ -19,9 +21,11 @@ import org.slf4j.LoggerFactory;
import java.io.IOException; import java.io.IOException;
import java.time.Duration; import java.time.Duration;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Optional;
public class ReceiveCommand implements LocalCommand { public class ReceiveCommand implements LocalCommand, JsonRpcSingleCommand<ReceiveCommand.ReceiveParams> {
private final static Logger logger = LoggerFactory.getLogger(ReceiveCommand.class); private final static Logger logger = LoggerFactory.getLogger(ReceiveCommand.class);
@ -37,6 +41,10 @@ public class ReceiveCommand implements LocalCommand {
.type(double.class) .type(double.class)
.setDefault(3.0) .setDefault(3.0)
.help("Number of seconds to wait for new messages (negative values disable timeout)"); .help("Number of seconds to wait for new messages (negative values disable timeout)");
subparser.addArgument("--max-messages")
.type(int.class)
.setDefault(-1)
.help("Maximum number of messages to receive, before returning.");
subparser.addArgument("--ignore-attachments") subparser.addArgument("--ignore-attachments")
.help("Dont download attachments of received messages.") .help("Dont download attachments of received messages.")
.action(Arguments.storeTrue()); .action(Arguments.storeTrue());
@ -58,6 +66,7 @@ public class ReceiveCommand implements LocalCommand {
final Namespace ns, final Manager m, final OutputWriter outputWriter final Namespace ns, final Manager m, final OutputWriter outputWriter
) throws CommandException { ) throws CommandException {
final var timeout = ns.getDouble("timeout"); final var timeout = ns.getDouble("timeout");
final var maxMessagesRaw = ns.getInt("max-messages");
final var ignoreAttachments = Boolean.TRUE.equals(ns.getBoolean("ignore-attachments")); final var ignoreAttachments = Boolean.TRUE.equals(ns.getBoolean("ignore-attachments"));
final var ignoreStories = Boolean.TRUE.equals(ns.getBoolean("ignore-stories")); final var ignoreStories = Boolean.TRUE.equals(ns.getBoolean("ignore-stories"));
final var sendReadReceipts = Boolean.TRUE.equals(ns.getBoolean("send-read-receipts")); final var sendReadReceipts = Boolean.TRUE.equals(ns.getBoolean("send-read-receipts"));
@ -65,13 +74,37 @@ public class ReceiveCommand implements LocalCommand {
try { try {
final var handler = outputWriter instanceof JsonWriter ? new JsonReceiveMessageHandler(m, final var handler = outputWriter instanceof JsonWriter ? new JsonReceiveMessageHandler(m,
(JsonWriter) outputWriter) : new ReceiveMessageHandler(m, (PlainTextWriter) outputWriter); (JsonWriter) outputWriter) : new ReceiveMessageHandler(m, (PlainTextWriter) outputWriter);
if (timeout < 0) { final var duration = timeout < 0 ? null : Duration.ofMillis((long) (timeout * 1000));
m.receiveMessages(handler); final var maxMessages = maxMessagesRaw < 0 ? null : maxMessagesRaw;
} else { m.receiveMessages(Optional.ofNullable(duration), Optional.ofNullable(maxMessages), handler);
m.receiveMessages(Duration.ofMillis((long) (timeout * 1000)), handler);
}
} catch (IOException e) { } catch (IOException e) {
throw new IOErrorException("Error while receiving messages: " + e.getMessage(), e); throw new IOErrorException("Error while receiving messages: " + e.getMessage(), e);
} }
} }
@Override
public TypeReference<ReceiveParams> getRequestType() {
return new TypeReference<>() {};
}
@Override
public void handleCommand(
final ReceiveParams request, final Manager m, final JsonWriter jsonWriter
) throws CommandException {
final var timeout = request.timeout() == null ? 3.0 : request.timeout();
final var maxMessagesRaw = request.maxMessages() == null ? -1 : request.maxMessages();
try {
final var messages = new ArrayList<>();
final var handler = new JsonReceiveMessageHandler(m, messages::add);
final var duration = timeout < 0 ? null : Duration.ofMillis((long) (timeout * 1000));
final var maxMessages = maxMessagesRaw < 0 ? null : maxMessagesRaw;
m.receiveMessages(Optional.ofNullable(duration), Optional.ofNullable(maxMessages), handler);
jsonWriter.write(messages);
} catch (IOException e) {
throw new IOErrorException("Error while receiving messages: " + e.getMessage(), e);
}
}
record ReceiveParams(Double timeout, Integer maxMessages) {}
} }

View file

@ -80,6 +80,10 @@ public class SendCommand implements JsonRpcLocalCommand {
subparser.addArgument("--preview-title").help("Specify the title for the link preview (mandatory)."); subparser.addArgument("--preview-title").help("Specify the title for the link preview (mandatory).");
subparser.addArgument("--preview-description").help("Specify the description for the link preview (optional)."); subparser.addArgument("--preview-description").help("Specify the description for the link preview (optional).");
subparser.addArgument("--preview-image").help("Specify the image file for the link preview (optional)."); subparser.addArgument("--preview-image").help("Specify the image file for the link preview (optional).");
subparser.addArgument("--story-timestamp")
.type(long.class)
.help("Specify the timestamp of a story to reply to.");
subparser.addArgument("--story-author").help("Specify the number of the author of the story.");
} }
@Override @Override
@ -170,18 +174,30 @@ public class SendCommand implements JsonRpcLocalCommand {
previews = List.of(); previews = List.of();
} }
final Message.StoryReply storyReply;
final var storyReplyTimestamp = ns.getLong("story-timestamp");
if (storyReplyTimestamp != null) {
final var storyAuthor = ns.getString("story-author");
storyReply = new Message.StoryReply(storyReplyTimestamp,
CommandUtil.getSingleRecipientIdentifier(storyAuthor, m.getSelfNumber()));
} else {
storyReply = null;
}
if (messageText.isEmpty() && attachments.isEmpty() && sticker == null && quote == null) { if (messageText.isEmpty() && attachments.isEmpty() && sticker == null && quote == null) {
throw new UserErrorException( throw new UserErrorException(
"Sending empty message is not allowed, either a message, attachment or sticker must be given."); "Sending empty message is not allowed, either a message, attachment or sticker must be given.");
} }
try { try {
var results = m.sendMessage(new Message(messageText, final var message = new Message(messageText,
attachments, attachments,
mentions, mentions,
Optional.ofNullable(quote), Optional.ofNullable(quote),
Optional.ofNullable(sticker), Optional.ofNullable(sticker),
previews), recipientIdentifiers); previews,
Optional.ofNullable((storyReply)));
var results = m.sendMessage(message, recipientIdentifiers);
outputResult(outputWriter, results); outputResult(outputWriter, results);
} catch (AttachmentInvalidException | IOException e) { } catch (AttachmentInvalidException | IOException e) {
throw new UnexpectedErrorException("Failed to send message: " + e.getMessage() + " (" + e.getClass() throw new UnexpectedErrorException("Failed to send message: " + e.getMessage() + " (" + e.getClass()

View file

@ -45,6 +45,9 @@ public class SendReactionCommand implements JsonRpcLocalCommand {
.type(long.class) .type(long.class)
.help("Specify the timestamp of the message to which to react."); .help("Specify the timestamp of the message to which to react.");
subparser.addArgument("-r", "--remove").help("Remove a reaction.").action(Arguments.storeTrue()); subparser.addArgument("-r", "--remove").help("Remove a reaction.").action(Arguments.storeTrue());
subparser.addArgument("--story")
.help("React to a story instead of a normal message")
.action(Arguments.storeTrue());
} }
@Override @Override
@ -64,13 +67,15 @@ public class SendReactionCommand implements JsonRpcLocalCommand {
final var isRemove = Boolean.TRUE.equals(ns.getBoolean("remove")); final var isRemove = Boolean.TRUE.equals(ns.getBoolean("remove"));
final var targetAuthor = ns.getString("target-author"); final var targetAuthor = ns.getString("target-author");
final var targetTimestamp = ns.getLong("target-timestamp"); final var targetTimestamp = ns.getLong("target-timestamp");
final var isStory = Boolean.TRUE.equals(ns.getBoolean("story"));
try { try {
final var results = m.sendMessageReaction(emoji, final var results = m.sendMessageReaction(emoji,
isRemove, isRemove,
CommandUtil.getSingleRecipientIdentifier(targetAuthor, m.getSelfNumber()), CommandUtil.getSingleRecipientIdentifier(targetAuthor, m.getSelfNumber()),
targetTimestamp, targetTimestamp,
recipientIdentifiers); recipientIdentifiers,
isStory);
outputResult(outputWriter, results); outputResult(outputWriter, results);
} catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) { } catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) {
throw new UserErrorException(e.getMessage()); throw new UserErrorException(e.getMessage());

View file

@ -58,6 +58,7 @@ import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Supplier; import java.util.function.Supplier;
@ -364,7 +365,8 @@ public class DbusManagerImpl implements Manager {
final boolean remove, final boolean remove,
final RecipientIdentifier.Single targetAuthor, final RecipientIdentifier.Single targetAuthor,
final long targetSentTimestamp, final long targetSentTimestamp,
final Set<RecipientIdentifier> recipients final Set<RecipientIdentifier> recipients,
final boolean isStory
) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException { ) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException {
return handleMessage(recipients, return handleMessage(recipients,
numbers -> signal.sendMessageReaction(emoji, numbers -> signal.sendMessageReaction(emoji,
@ -496,39 +498,46 @@ public class DbusManagerImpl implements Manager {
} }
} }
@Override
public void receiveMessages(final ReceiveMessageHandler handler) throws IOException {
addReceiveHandler(handler);
try {
synchronized (this) {
this.wait();
}
} catch (InterruptedException ignored) {
}
removeReceiveHandler(handler);
}
@Override @Override
public void receiveMessages( public void receiveMessages(
final Duration timeout, final ReceiveMessageHandler handler Optional<Duration> timeout, Optional<Integer> maxMessages, ReceiveMessageHandler handler
) throws IOException { ) throws IOException {
final var remainingMessages = new AtomicInteger(maxMessages.orElse(-1));
final var lastMessage = new AtomicLong(System.currentTimeMillis()); final var lastMessage = new AtomicLong(System.currentTimeMillis());
final var thread = Thread.currentThread();
final ReceiveMessageHandler receiveHandler = (envelope, e) -> { final ReceiveMessageHandler receiveHandler = (envelope, e) -> {
lastMessage.set(System.currentTimeMillis()); lastMessage.set(System.currentTimeMillis());
handler.handleMessage(envelope, e); handler.handleMessage(envelope, e);
if (remainingMessages.get() > 0) {
if (remainingMessages.decrementAndGet() <= 0) {
remainingMessages.set(0);
thread.interrupt();
}
}
}; };
addReceiveHandler(receiveHandler); addReceiveHandler(receiveHandler);
while (true) { if (timeout.isPresent()) {
try { while (remainingMessages.get() != 0) {
final var sleepTimeRemaining = timeout.toMillis() - (System.currentTimeMillis() - lastMessage.get()); try {
if (sleepTimeRemaining < 0) { final var passedTime = System.currentTimeMillis() - lastMessage.get();
break; final var sleepTimeRemaining = timeout.get().toMillis() - passedTime;
if (sleepTimeRemaining < 0) {
break;
}
Thread.sleep(sleepTimeRemaining);
} catch (InterruptedException ignored) {
}
}
} else {
try {
synchronized (this) {
this.wait();
} }
Thread.sleep(sleepTimeRemaining);
} catch (InterruptedException ignored) { } catch (InterruptedException ignored) {
} }
} }
removeReceiveHandler(receiveHandler); removeReceiveHandler(receiveHandler);
} }

View file

@ -218,7 +218,8 @@ public class DbusSignalImpl implements Signal {
List.of(), List.of(),
Optional.empty(), Optional.empty(),
Optional.empty(), Optional.empty(),
List.of()), List.of(),
Optional.empty()),
getSingleRecipientIdentifiers(recipients, m.getSelfNumber()).stream() getSingleRecipientIdentifiers(recipients, m.getSelfNumber()).stream()
.map(RecipientIdentifier.class::cast) .map(RecipientIdentifier.class::cast)
.collect(Collectors.toSet())); .collect(Collectors.toSet()));
@ -287,7 +288,8 @@ public class DbusSignalImpl implements Signal {
targetSentTimestamp, targetSentTimestamp,
getSingleRecipientIdentifiers(recipients, m.getSelfNumber()).stream() getSingleRecipientIdentifiers(recipients, m.getSelfNumber()).stream()
.map(RecipientIdentifier.class::cast) .map(RecipientIdentifier.class::cast)
.collect(Collectors.toSet())); .collect(Collectors.toSet()),
false);
checkSendMessageResults(results); checkSendMessageResults(results);
return results.timestamp(); return results.timestamp();
} catch (IOException e) { } catch (IOException e) {
@ -385,7 +387,8 @@ public class DbusSignalImpl implements Signal {
List.of(), List.of(),
Optional.empty(), Optional.empty(),
Optional.empty(), Optional.empty(),
List.of()), Set.of(RecipientIdentifier.NoteToSelf.INSTANCE)); List.of(),
Optional.empty()), Set.of(RecipientIdentifier.NoteToSelf.INSTANCE));
checkSendMessageResults(results); checkSendMessageResults(results);
return results.timestamp(); return results.timestamp();
} catch (AttachmentInvalidException e) { } catch (AttachmentInvalidException e) {
@ -427,7 +430,8 @@ public class DbusSignalImpl implements Signal {
List.of(), List.of(),
Optional.empty(), Optional.empty(),
Optional.empty(), Optional.empty(),
List.of()), Set.of(getGroupRecipientIdentifier(groupId))); List.of(),
Optional.empty()), Set.of(getGroupRecipientIdentifier(groupId)));
checkSendMessageResults(results); checkSendMessageResults(results);
return results.timestamp(); return results.timestamp();
} catch (IOException | InvalidStickerException e) { } catch (IOException | InvalidStickerException e) {
@ -485,7 +489,8 @@ public class DbusSignalImpl implements Signal {
remove, remove,
getSingleRecipientIdentifier(targetAuthor, m.getSelfNumber()), getSingleRecipientIdentifier(targetAuthor, m.getSelfNumber()),
targetSentTimestamp, targetSentTimestamp,
Set.of(getGroupRecipientIdentifier(groupId))); Set.of(getGroupRecipientIdentifier(groupId)),
false);
checkSendMessageResults(results); checkSendMessageResults(results);
return results.timestamp(); return results.timestamp();
} catch (IOException e) { } catch (IOException e) {