mirror of
https://github.com/AsamK/signal-cli.git
synced 2026-09-23 06:39:04 +00:00
Compare commits
12 Commits
e30083b9a8
...
b40bfd5a63
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b40bfd5a63 | ||
|
|
f9cbfa6d6c | ||
|
|
d4b3816c5d | ||
|
|
4a35d47515 | ||
|
|
52d4d61e2b | ||
|
|
5bff902394 | ||
|
|
f33eb86335 | ||
|
|
2ea26b9d1b | ||
|
|
10fa3e1619 | ||
|
|
956e17c81c | ||
|
|
6f749352d8 | ||
|
|
e54df4367f |
@ -79,7 +79,10 @@ of all country codes.)
|
||||
```
|
||||
|
||||
Registering may require solving a CAPTCHA
|
||||
challenge: [Registration with captcha](https://github.com/AsamK/signal-cli/wiki/Registration-with-captcha)
|
||||
challenge: [Registration with captcha](https://github.com/AsamK/signal-cli/wiki/Registration-with-captcha).
|
||||
In this case, provide the captcha token with the `--captcha` flag:
|
||||
|
||||
signal-cli -a ACCOUNT register --captcha CAPTCHA_TOKEN
|
||||
|
||||
* Verify the number using the code received via SMS or voice, optionally add `--pin PIN_CODE` if you've added a pin code
|
||||
to your account
|
||||
|
||||
@ -1,10 +1,12 @@
|
||||
package org.asamk.signal.manager;
|
||||
|
||||
import org.asamk.signal.manager.internal.LibSignalLogger;
|
||||
import org.asamk.signal.manager.internal.SignalLogger;
|
||||
|
||||
public class ManagerLogger {
|
||||
|
||||
public static void initLogger() {
|
||||
LibSignalLogger.initLogger();
|
||||
SignalLogger.initLogger();
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,7 +12,8 @@ public record Message(
|
||||
Optional<Sticker> sticker,
|
||||
List<Preview> previews,
|
||||
Optional<StoryReply> storyReply,
|
||||
List<TextStyle> textStyles
|
||||
List<TextStyle> textStyles,
|
||||
boolean urgent
|
||||
) {
|
||||
|
||||
public record Mention(RecipientIdentifier.Single recipient, int start, int length) {}
|
||||
|
||||
@ -682,7 +682,7 @@ public class GroupHelper {
|
||||
|
||||
private void sendExpirationTimerUpdate(GroupIdV1 groupId) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException {
|
||||
final var messageBuilder = SignalServiceDataMessage.newBuilder().asExpirationUpdate();
|
||||
context.getSendHelper().sendAsGroupMessage(messageBuilder, groupId, false, Optional.empty());
|
||||
context.getSendHelper().sendAsGroupMessage(messageBuilder, groupId, false, Optional.empty(), true);
|
||||
}
|
||||
|
||||
private SendGroupMessageResults updateGroupV2(
|
||||
|
||||
@ -84,7 +84,8 @@ public class SendHelper {
|
||||
public SendMessageResult sendMessage(
|
||||
final SignalServiceDataMessage.Builder messageBuilder,
|
||||
final RecipientId recipientId,
|
||||
Optional<Long> editTargetTimestamp
|
||||
Optional<Long> editTargetTimestamp,
|
||||
boolean urgent
|
||||
) {
|
||||
var contact = account.getContactStore().getContact(recipientId);
|
||||
if (contact == null || !contact.isProfileSharingEnabled() || contact.isHidden()) {
|
||||
@ -102,7 +103,7 @@ public class SendHelper {
|
||||
}
|
||||
|
||||
final var message = messageBuilder.build();
|
||||
return sendMessage(message, recipientId, editTargetTimestamp);
|
||||
return sendMessage(message, recipientId, editTargetTimestamp, urgent);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -113,10 +114,11 @@ public class SendHelper {
|
||||
final SignalServiceDataMessage.Builder messageBuilder,
|
||||
final GroupId groupId,
|
||||
final boolean includeSelf,
|
||||
final Optional<Long> editTargetTimestamp
|
||||
final Optional<Long> editTargetTimestamp,
|
||||
boolean urgent
|
||||
) throws IOException, GroupNotFoundException, NotAGroupMemberException, GroupSendingNotAllowedException {
|
||||
final var g = getGroupForSending(groupId);
|
||||
return sendAsGroupMessage(messageBuilder, g, includeSelf, editTargetTimestamp);
|
||||
return sendAsGroupMessage(messageBuilder, g, includeSelf, editTargetTimestamp, urgent);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -128,7 +130,7 @@ public class SendHelper {
|
||||
final Set<RecipientId> recipientIds,
|
||||
final GroupInfo groupInfo
|
||||
) throws IOException {
|
||||
return sendGroupMessage(message, recipientIds, groupInfo, ContentHint.IMPLICIT, Optional.empty());
|
||||
return sendGroupMessage(message, recipientIds, groupInfo, ContentHint.IMPLICIT, Optional.empty(), true);
|
||||
}
|
||||
|
||||
public SendMessageResult sendReceiptMessage(
|
||||
@ -311,7 +313,8 @@ public class SendHelper {
|
||||
final SignalServiceDataMessage.Builder messageBuilder,
|
||||
final GroupInfo g,
|
||||
final boolean includeSelf,
|
||||
final Optional<Long> editTargetTimestamp
|
||||
final Optional<Long> editTargetTimestamp,
|
||||
boolean urgent
|
||||
) throws IOException, GroupSendingNotAllowedException {
|
||||
GroupUtils.setGroupContext(messageBuilder, g);
|
||||
messageBuilder.withExpiration(g.getMessageExpirationTimer());
|
||||
@ -330,7 +333,7 @@ public class SendHelper {
|
||||
}
|
||||
}
|
||||
|
||||
return sendGroupMessage(message, recipients, g, ContentHint.RESENDABLE, editTargetTimestamp);
|
||||
return sendGroupMessage(message, recipients, g, ContentHint.RESENDABLE, editTargetTimestamp, urgent);
|
||||
}
|
||||
|
||||
private List<SendMessageResult> sendGroupMessage(
|
||||
@ -338,13 +341,13 @@ public class SendHelper {
|
||||
final Set<RecipientId> recipientIds,
|
||||
final GroupInfo groupInfo,
|
||||
final ContentHint contentHint,
|
||||
final Optional<Long> editTargetTimestamp
|
||||
final Optional<Long> editTargetTimestamp,
|
||||
boolean urgent
|
||||
) throws IOException {
|
||||
final var messageSender = dependencies.getMessageSender();
|
||||
final var messageSendLogStore = account.getMessageSendLogStore();
|
||||
final AtomicLong entryId = new AtomicLong(-1);
|
||||
|
||||
final var urgent = true;
|
||||
final PartialSendCompleteListener partialSendCompleteListener = sendResult -> {
|
||||
logger.trace("Partial message send result: {}", sendResult.isSuccess());
|
||||
synchronized (entryId) {
|
||||
@ -712,10 +715,10 @@ public class SendHelper {
|
||||
private SendMessageResult sendMessage(
|
||||
SignalServiceDataMessage message,
|
||||
RecipientId recipientId,
|
||||
Optional<Long> editTargetTimestamp
|
||||
Optional<Long> editTargetTimestamp,
|
||||
boolean urgent
|
||||
) {
|
||||
final var messageSendLogStore = account.getMessageSendLogStore();
|
||||
final var urgent = true;
|
||||
final var result = handleSendMessage(recipientId,
|
||||
editTargetTimestamp.isEmpty()
|
||||
? (messageSender, address, unidentifiedAccess, includePniSignature) -> messageSender.sendDataMessage(
|
||||
|
||||
@ -211,18 +211,19 @@ public class StorageHelper {
|
||||
remoteOnlyRecords.size());
|
||||
}
|
||||
|
||||
if (!idDifference.localOnlyIds().isEmpty()) {
|
||||
final var updated = account.getRecipientStore()
|
||||
.removeStorageIdsFromLocalOnlyUnregisteredRecipients(connection,
|
||||
idDifference.localOnlyIds());
|
||||
|
||||
if (updated > 0) {
|
||||
logger.warn(
|
||||
"Found {} records that were deleted remotely but only marked unregistered locally. Removed those from local store.",
|
||||
updated);
|
||||
}
|
||||
}
|
||||
|
||||
// This logic is wrong, records should only be deleted if they're deleted remotely, not if the remote record is updated
|
||||
// if (!idDifference.localOnlyIds().isEmpty()) {
|
||||
// final var updated = account.getRecipientStore()
|
||||
// .removeStorageIdsFromLocalOnlyUnregisteredRecipients(connection,
|
||||
// idDifference.localOnlyIds());
|
||||
//
|
||||
// if (updated > 0) {
|
||||
// logger.warn(
|
||||
// "Found {} records that were deleted remotely but only marked unregistered locally. Removed those from local store.",
|
||||
// updated);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
final var unknownInserts = processKnownRecords(connection, remoteOnlyRecords);
|
||||
final var unknownDeletes = idDifference.localOnlyIds()
|
||||
.stream()
|
||||
|
||||
@ -666,14 +666,15 @@ public class ManagerImpl implements Manager {
|
||||
Set<RecipientIdentifier> recipients,
|
||||
boolean notifySelf
|
||||
) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException {
|
||||
return sendMessage(messageBuilder, recipients, notifySelf, Optional.empty());
|
||||
return sendMessage(messageBuilder, recipients, notifySelf, Optional.empty(), true);
|
||||
}
|
||||
|
||||
private SendMessageResults sendMessage(
|
||||
SignalServiceDataMessage.Builder messageBuilder,
|
||||
Set<RecipientIdentifier> recipients,
|
||||
boolean notifySelf,
|
||||
Optional<Long> editTargetTimestamp
|
||||
Optional<Long> editTargetTimestamp,
|
||||
boolean urgent
|
||||
) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException {
|
||||
var results = new HashMap<RecipientIdentifier, List<SendMessageResult>>();
|
||||
long timestamp = getNextMessageTimestamp();
|
||||
@ -685,14 +686,14 @@ public class ManagerImpl implements Manager {
|
||||
)) {
|
||||
final var result = notifySelf
|
||||
? context.getSendHelper()
|
||||
.sendMessage(messageBuilder, account.getSelfRecipientId(), editTargetTimestamp)
|
||||
.sendMessage(messageBuilder, account.getSelfRecipientId(), editTargetTimestamp, urgent)
|
||||
: context.getSendHelper().sendSelfMessage(messageBuilder, editTargetTimestamp);
|
||||
results.put(recipient, List.of(toSendMessageResult(result)));
|
||||
} else if (recipient instanceof RecipientIdentifier.Single single) {
|
||||
try {
|
||||
final var recipientId = context.getRecipientHelper().resolveRecipient(single);
|
||||
final var result = context.getSendHelper()
|
||||
.sendMessage(messageBuilder, recipientId, editTargetTimestamp);
|
||||
.sendMessage(messageBuilder, recipientId, editTargetTimestamp, urgent);
|
||||
results.put(recipient, List.of(toSendMessageResult(result)));
|
||||
} catch (UnregisteredRecipientException e) {
|
||||
results.put(recipient,
|
||||
@ -700,7 +701,7 @@ public class ManagerImpl implements Manager {
|
||||
}
|
||||
} else if (recipient instanceof RecipientIdentifier.Group group) {
|
||||
final var result = context.getSendHelper()
|
||||
.sendAsGroupMessage(messageBuilder, group.groupId(), notifySelf, editTargetTimestamp);
|
||||
.sendAsGroupMessage(messageBuilder, group.groupId(), notifySelf, editTargetTimestamp, urgent);
|
||||
results.put(recipient, result.stream().map(this::toSendMessageResult).toList());
|
||||
}
|
||||
}
|
||||
@ -799,7 +800,7 @@ public class ManagerImpl implements Manager {
|
||||
}
|
||||
final var messageBuilder = SignalServiceDataMessage.newBuilder();
|
||||
applyMessage(messageBuilder, message);
|
||||
return sendMessage(messageBuilder, recipients, notifySelf);
|
||||
return sendMessage(messageBuilder, recipients, notifySelf, Optional.empty(), message.urgent());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -810,7 +811,7 @@ public class ManagerImpl implements Manager {
|
||||
) throws IOException, AttachmentInvalidException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException, UnregisteredRecipientException, InvalidStickerException {
|
||||
final var messageBuilder = SignalServiceDataMessage.newBuilder();
|
||||
applyMessage(messageBuilder, message);
|
||||
return sendMessage(messageBuilder, recipients, false, Optional.of(editTargetTimestamp));
|
||||
return sendMessage(messageBuilder, recipients, false, Optional.of(editTargetTimestamp), message.urgent());
|
||||
}
|
||||
|
||||
private void applyMessage(
|
||||
@ -1479,7 +1480,21 @@ public class ManagerImpl implements Manager {
|
||||
return List.of();
|
||||
}
|
||||
// refresh profiles of explicitly given recipients
|
||||
context.getProfileHelper().refreshRecipientProfiles(recipientIds);
|
||||
if (recipientIds.isEmpty()) {
|
||||
final var rIds = account.getRecipientStore()
|
||||
.getRecipients(onlyContacts, blocked, recipientIds, name)
|
||||
.stream()
|
||||
.filter(r -> r.isRegistered())
|
||||
.map(r -> r.getRecipientId())
|
||||
.toList();
|
||||
try {
|
||||
context.getProfileHelper().getRecipientProfiles(rIds);
|
||||
} catch (Exception e) {
|
||||
logger.warn("Failed to refresh profiles for recipients", e);
|
||||
}
|
||||
} else {
|
||||
context.getProfileHelper().refreshRecipientProfiles(recipientIds);
|
||||
}
|
||||
return account.getRecipientStore()
|
||||
.getRecipients(onlyContacts, blocked, recipientIds, name)
|
||||
.stream()
|
||||
|
||||
@ -0,0 +1,46 @@
|
||||
package org.asamk.signal.manager.internal;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class SignalLogger extends Log.Logger {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger("LibSignalService");
|
||||
|
||||
public static void initLogger() {
|
||||
Log.initialize(() -> true, new SignalLogger());
|
||||
}
|
||||
|
||||
private SignalLogger() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void v(final String tag, final String message, final Throwable throwable, final boolean b) {
|
||||
logger.trace("[{}]: {}", tag, message, throwable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void d(final String tag, final String message, final Throwable throwable, final boolean b) {
|
||||
logger.debug("[{}]: {}", tag, message, throwable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void i(final String tag, final String message, final Throwable throwable, final boolean b) {
|
||||
logger.info("[{}]: {}", tag, message, throwable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void w(final String tag, final String message, final Throwable throwable, final boolean b) {
|
||||
logger.warn("[{}]: {}", tag, message, throwable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void e(final String tag, final String message, final Throwable throwable, final boolean b) {
|
||||
logger.error("[{}]: {}", tag, message, throwable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() {
|
||||
}
|
||||
}
|
||||
@ -99,6 +99,7 @@ public class LegacyRecipientStore2 {
|
||||
expiringProfileKeyCredential,
|
||||
profile,
|
||||
null,
|
||||
null,
|
||||
null);
|
||||
}).collect(Collectors.toMap(Recipient::getRecipientId, r -> r));
|
||||
|
||||
|
||||
@ -23,6 +23,8 @@ public class Recipient {
|
||||
|
||||
private final Boolean discoverable;
|
||||
|
||||
private final Long unregisteredTimestamp;
|
||||
|
||||
private final byte[] storageRecord;
|
||||
|
||||
public Recipient(
|
||||
@ -33,6 +35,7 @@ public class Recipient {
|
||||
final ExpiringProfileKeyCredential expiringProfileKeyCredential,
|
||||
final Profile profile,
|
||||
final Boolean discoverable,
|
||||
final Long unregisteredTimestamp,
|
||||
final byte[] storageRecord
|
||||
) {
|
||||
this.recipientId = recipientId;
|
||||
@ -42,6 +45,7 @@ public class Recipient {
|
||||
this.expiringProfileKeyCredential = expiringProfileKeyCredential;
|
||||
this.profile = profile;
|
||||
this.discoverable = discoverable;
|
||||
this.unregisteredTimestamp = unregisteredTimestamp;
|
||||
this.storageRecord = storageRecord;
|
||||
}
|
||||
|
||||
@ -53,6 +57,7 @@ public class Recipient {
|
||||
expiringProfileKeyCredential = builder.expiringProfileKeyCredential;
|
||||
profile = builder.profile;
|
||||
discoverable = builder.discoverable;
|
||||
unregisteredTimestamp = builder.unregisteredTimestamp;
|
||||
storageRecord = builder.storageRecord;
|
||||
}
|
||||
|
||||
@ -100,6 +105,14 @@ public class Recipient {
|
||||
return discoverable;
|
||||
}
|
||||
|
||||
public Long getUnregisteredTimestamp() {
|
||||
return unregisteredTimestamp;
|
||||
}
|
||||
|
||||
public boolean isRegistered() {
|
||||
return unregisteredTimestamp == null;
|
||||
}
|
||||
|
||||
public byte[] getStorageRecord() {
|
||||
return storageRecord;
|
||||
}
|
||||
@ -131,6 +144,7 @@ public class Recipient {
|
||||
private ExpiringProfileKeyCredential expiringProfileKeyCredential;
|
||||
private Profile profile;
|
||||
private Boolean discoverable;
|
||||
private Long unregisteredTimestamp;
|
||||
private byte[] storageRecord;
|
||||
|
||||
private Builder() {
|
||||
@ -171,6 +185,11 @@ public class Recipient {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withUnregisteredTimestamp(final Long val) {
|
||||
unregisteredTimestamp = val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withStorageRecord(final byte[] val) {
|
||||
storageRecord = val;
|
||||
return this;
|
||||
|
||||
@ -981,7 +981,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
|
||||
if (registered) {
|
||||
markRegistered(connection, recipientId);
|
||||
} else {
|
||||
markUnregistered(connection, recipientId);
|
||||
markUnregisteredAndSplitIfNecessary(connection, recipientId);
|
||||
}
|
||||
connection.commit();
|
||||
} catch (SQLException e) {
|
||||
@ -1549,6 +1549,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
|
||||
getExpiringProfileKeyCredentialFromResultSet(resultSet),
|
||||
getProfileFromResultSet(resultSet),
|
||||
getDiscoverableFromResultSet(resultSet),
|
||||
getUnregisteredTimestampFromResultSet(resultSet),
|
||||
getStorageRecordFromResultSet(resultSet));
|
||||
}
|
||||
|
||||
@ -1580,6 +1581,14 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
|
||||
return discoverable;
|
||||
}
|
||||
|
||||
private static Long getUnregisteredTimestampFromResultSet(final ResultSet resultSet) throws SQLException {
|
||||
final var discoverable = resultSet.getLong("unregistered_timestamp");
|
||||
if (resultSet.wasNull()) {
|
||||
return null;
|
||||
}
|
||||
return discoverable;
|
||||
}
|
||||
|
||||
private Profile getProfileFromResultSet(ResultSet resultSet) throws SQLException {
|
||||
final var profileCapabilities = resultSet.getString("profile_capabilities");
|
||||
final var profileUnidentifiedAccessMode = resultSet.getString("profile_unidentified_access_mode");
|
||||
|
||||
@ -31,6 +31,7 @@ import java.util.regex.Pattern;
|
||||
import okio.ByteString;
|
||||
|
||||
import static org.asamk.signal.manager.util.Utils.firstNonEmpty;
|
||||
import static org.asamk.signal.manager.util.Utils.firstNonNull;
|
||||
import static org.asamk.signal.manager.util.Utils.nullIfEmpty;
|
||||
|
||||
public class ContactRecordProcessor extends DefaultStorageRecordProcessor<SignalContactRecord> {
|
||||
@ -129,27 +130,32 @@ public class ContactRecordProcessor extends DefaultStorageRecordProcessor<Signal
|
||||
identityKey = local.identityKey.size() > 0 ? local.identityKey : ByteString.EMPTY;
|
||||
}
|
||||
|
||||
if ((!local.aci.isEmpty() || local.aciBinary.size() > 0)
|
||||
final var localAci = ACI.parseOrNull(local.aci, local.aciBinary);
|
||||
final var localPni = PNI.parseOrNull(local.pni, local.pniBinary);
|
||||
final var remoteAci = ACI.parseOrNull(remote.aci, remote.aciBinary);
|
||||
final var remotePni = PNI.parseOrNull(remote.pni, remote.pniBinary);
|
||||
|
||||
if (localAci != null
|
||||
&& local.identityKey.size() > 0
|
||||
&& remote.identityKey.size() > 0
|
||||
&& !local.identityKey.equals(remote.identityKey)) {
|
||||
logger.debug("The local and remote identity keys do not match for {}. Enqueueing a profile fetch.",
|
||||
local.aci);
|
||||
localAci);
|
||||
final var address = getRecipientAddress(local);
|
||||
jobExecutor.enqueueJob(new DownloadProfileJob(address));
|
||||
}
|
||||
|
||||
String pni;
|
||||
PNI pni;
|
||||
String e164;
|
||||
if (account.isPrimaryDevice()) {
|
||||
final var e164sMatchButPnisDont = !local.e164.isEmpty()
|
||||
&& local.e164.equals(remote.e164)
|
||||
&& !local.pni.isEmpty()
|
||||
&& !remote.pni.isEmpty()
|
||||
&& !local.pni.equals(remote.pni);
|
||||
&& localPni != null
|
||||
&& remotePni != null
|
||||
&& !localPni.equals(remotePni);
|
||||
|
||||
final var pnisMatchButE164sDont = !local.pni.isEmpty()
|
||||
&& local.pni.equals(remote.pni)
|
||||
final var pnisMatchButE164sDont = localPni != null
|
||||
&& localPni.equals(remotePni)
|
||||
&& !local.e164.isEmpty()
|
||||
&& !remote.e164.isEmpty()
|
||||
&& !local.e164.equals(remote.e164);
|
||||
@ -161,14 +167,14 @@ public class ContactRecordProcessor extends DefaultStorageRecordProcessor<Signal
|
||||
logger.debug("Matching PNIs, but the E164s differ! Trusting our local pair.");
|
||||
}
|
||||
jobExecutor.enqueueJob(new RefreshRecipientsJob());
|
||||
pni = local.pni;
|
||||
pni = localPni;
|
||||
e164 = local.e164;
|
||||
} else {
|
||||
pni = firstNonEmpty(remote.pni, local.pni);
|
||||
pni = firstNonNull(remotePni, localPni);
|
||||
e164 = firstNonEmpty(remote.e164, local.e164);
|
||||
}
|
||||
} else {
|
||||
pni = firstNonEmpty(remote.pni, local.pni);
|
||||
pni = firstNonNull(remotePni, localPni);
|
||||
e164 = firstNonEmpty(remote.e164, local.e164);
|
||||
}
|
||||
|
||||
@ -177,11 +183,7 @@ public class ContactRecordProcessor extends DefaultStorageRecordProcessor<Signal
|
||||
? ByteString.EMPTY
|
||||
: remote.profileKey;
|
||||
final var mergedBuilder = remote.newBuilder()
|
||||
.aci(local.aci.isEmpty() ? remote.aci : local.aci)
|
||||
.aciBinary(firstNonEmpty(local.aciBinary, remote.aciBinary))
|
||||
.e164(e164)
|
||||
.pni(pni)
|
||||
.pniBinary(pni.isEmpty() ? ByteString.EMPTY : PNI.parseOrThrow(pni).toByteStringWithoutPrefix())
|
||||
.givenName(profileGivenName)
|
||||
.familyName(profileFamilyName)
|
||||
.systemGivenName(account.isPrimaryDevice() ? local.systemGivenName : remote.systemGivenName)
|
||||
@ -203,6 +205,28 @@ public class ContactRecordProcessor extends DefaultStorageRecordProcessor<Signal
|
||||
.nickname(remote.nickname)
|
||||
.note(remote.note)
|
||||
.avatarColor(remote.avatarColor);
|
||||
if (remote.aci.isEmpty() && remote.aciBinary.size() == 0) {
|
||||
mergedBuilder.aci(localAci == null ? remote.aci : localAci.toString())
|
||||
.aciBinary(localAci == null ? remote.aciBinary : localAci.toByteString());
|
||||
} else {
|
||||
if (!remote.aci.isEmpty()) {
|
||||
mergedBuilder.aci(localAci == null ? remote.aci : localAci.toString());
|
||||
}
|
||||
if (remote.aciBinary.size() > 0) {
|
||||
mergedBuilder.aciBinary(localAci == null ? remote.aciBinary : localAci.toByteString());
|
||||
}
|
||||
}
|
||||
if (remote.pni.isEmpty() && remote.pniBinary.size() == 0) {
|
||||
mergedBuilder.pni(pni == null ? "" : pni.toStringWithoutPrefix())
|
||||
.pniBinary(pni == null ? ByteString.EMPTY : pni.toByteStringWithoutPrefix());
|
||||
} else {
|
||||
if (!remote.pni.isEmpty()) {
|
||||
mergedBuilder.pni(pni == null ? "" : pni.toStringWithoutPrefix());
|
||||
}
|
||||
if (remote.pniBinary.size() > 0) {
|
||||
mergedBuilder.pniBinary(pni == null ? ByteString.EMPTY : pni.toByteStringWithoutPrefix());
|
||||
}
|
||||
}
|
||||
final var merged = mergedBuilder.build();
|
||||
|
||||
final var matchesRemote = doProtosMatch(merged, remote);
|
||||
@ -337,15 +361,16 @@ public class ContactRecordProcessor extends DefaultStorageRecordProcessor<Signal
|
||||
public int compare(SignalContactRecord lhsRecord, SignalContactRecord rhsRecord) {
|
||||
final var lhs = lhsRecord.getProto();
|
||||
final var rhs = rhsRecord.getProto();
|
||||
final var lhsAci = ACI.parseOrNull(lhs.aci, lhs.aciBinary);
|
||||
final var rhsAci = ACI.parseOrNull(rhs.aci, rhs.aciBinary);
|
||||
final var lhsPni = PNI.parseOrNull(lhs.pni, lhs.pniBinary);
|
||||
final var rhsPni = PNI.parseOrNull(rhs.pni, rhs.pniBinary);
|
||||
if ((
|
||||
(!lhs.aci.isEmpty() && Objects.equals(lhs.aci, rhs.aci)) || (
|
||||
lhs.aciBinary.size() != 0 && Objects.equals(lhs.aciBinary, rhs.aciBinary)
|
||||
)
|
||||
(lhsAci != null && Objects.equals(lhsAci, rhsAci))
|
||||
) || (
|
||||
!lhs.e164.isEmpty() && Objects.equals(lhs.e164, rhs.e164)
|
||||
) || (
|
||||
(!lhs.pni.isEmpty() && Objects.equals(lhs.pni, rhs.pni) && lhs.pniBinary == rhs.pniBinary)
|
||||
|| (lhs.pniBinary.size() != 0 && Objects.equals(lhs.pniBinary, rhs.pniBinary))
|
||||
(lhsPni != null && Objects.equals(lhsPni, rhsPni))
|
||||
)) {
|
||||
return 0;
|
||||
} else {
|
||||
|
||||
@ -33,7 +33,7 @@ import static org.signal.core.util.StringExtensionsKt.emptyIfNull;
|
||||
|
||||
public final class StorageSyncModels {
|
||||
|
||||
private final static boolean useBinaryId = false;
|
||||
private final static boolean useBinaryId = true;
|
||||
private final static boolean useStringId = true;
|
||||
|
||||
private StorageSyncModels() {
|
||||
|
||||
@ -186,8 +186,8 @@ public final class StorageSyncValidations {
|
||||
|
||||
if (insert.getProto().contact != null) {
|
||||
final var contact = insert.getProto().contact;
|
||||
final var aci = ACI.parseOrNull(contact.aci);
|
||||
final var pni = PNI.parseOrNull(contact.pni);
|
||||
final var aci = ACI.parseOrNull(contact.aci, contact.aciBinary);
|
||||
final var pni = PNI.parseOrNull(contact.pni, contact.pniBinary);
|
||||
final var number = contact.e164.isEmpty() ? null : contact.e164;
|
||||
final var username = contact.username.isEmpty() ? null : contact.username;
|
||||
final var address = new RecipientAddress(aci, pni, number, username);
|
||||
|
||||
@ -52,7 +52,7 @@ public class LinkCommand implements ProvisioningCommand {
|
||||
try {
|
||||
final URI deviceLinkUri = m.getDeviceLinkUri();
|
||||
if (System.console() != null) {
|
||||
printQrCode(writer, deviceLinkUri);
|
||||
printQrCode(writer, deviceLinkUri.toString());
|
||||
}
|
||||
writer.println("{}", deviceLinkUri);
|
||||
var number = m.finishDeviceLink(deviceName);
|
||||
@ -70,12 +70,14 @@ public class LinkCommand implements ProvisioningCommand {
|
||||
}
|
||||
}
|
||||
|
||||
private void printQrCode(final PlainTextWriter writer, final URI deviceLinkUri) {
|
||||
private void printQrCode(final PlainTextWriter writer, final String contents) {
|
||||
try {
|
||||
var bitMatrix = new QRCodeWriter().encode(deviceLinkUri.toString(), BarcodeFormat.QR_CODE, 0, 0);
|
||||
var bitMatrix = new QRCodeWriter().encode(contents, BarcodeFormat.QR_CODE, 0, 0);
|
||||
writer.println("\033[37;40m");
|
||||
for (int y = 0; y < bitMatrix.getHeight(); y += 2) {
|
||||
writer.println(formatQRCodeLinePair(bitMatrix, y));
|
||||
}
|
||||
writer.println("\033[39;49m");
|
||||
} catch (WriterException e) {
|
||||
logger.error("Failed to generate QR code", e);
|
||||
}
|
||||
@ -86,7 +88,7 @@ public class LinkCommand implements ProvisioningCommand {
|
||||
for (int x = 0; x < bitMatrix.getWidth(); x++) {
|
||||
boolean upper = bitMatrix.get(x, y);
|
||||
boolean lower = y + 1 < bitMatrix.getHeight() && bitMatrix.get(x, y + 1);
|
||||
line.append((upper && lower) ? "█" : (upper ? "▀" : (lower ? "▄" : " ")));
|
||||
line.append((upper && lower) ? " " : (upper ? "▄" : (lower ? "▀" : "█")));
|
||||
}
|
||||
return line.toString();
|
||||
}
|
||||
|
||||
@ -105,6 +105,10 @@ public class SendCommand implements JsonRpcLocalCommand {
|
||||
subparser.addArgument("--edit-timestamp")
|
||||
.type(long.class)
|
||||
.help("Specify the timestamp of a previous message with the recipient or group to send an edited message.");
|
||||
subparser.addArgument("--no-urgent")
|
||||
.action(Arguments.storeTrue())
|
||||
.help("Send the message without the urgent flag, so no push notification is triggered for the recipient. "
|
||||
+ "The message will still be delivered in real-time if the recipient's app is active.");
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -115,6 +119,7 @@ public class SendCommand implements JsonRpcLocalCommand {
|
||||
) throws CommandException {
|
||||
final var notifySelf = Boolean.TRUE.equals(ns.getBoolean("notify-self"));
|
||||
final var isNoteToSelf = Boolean.TRUE.equals(ns.getBoolean("note-to-self"));
|
||||
final var noUrgent = Boolean.TRUE.equals(ns.getBoolean("no-urgent"));
|
||||
final var recipientStrings = ns.<String>getList("recipient");
|
||||
final var groupIdStrings = ns.<String>getList("group-id");
|
||||
final var usernameStrings = ns.<String>getList("username");
|
||||
@ -247,7 +252,8 @@ public class SendCommand implements JsonRpcLocalCommand {
|
||||
Optional.ofNullable(sticker),
|
||||
previews,
|
||||
Optional.ofNullable((storyReply)),
|
||||
textStyles);
|
||||
textStyles,
|
||||
!noUrgent);
|
||||
var results = editTimestamp != null
|
||||
? m.sendEditMessage(message, recipientIdentifiers, editTimestamp)
|
||||
: m.sendMessage(message, recipientIdentifiers, notifySelf);
|
||||
|
||||
@ -242,7 +242,8 @@ public class DbusSignalImpl implements Signal, AutoCloseable {
|
||||
Optional.empty(),
|
||||
List.of(),
|
||||
Optional.empty(),
|
||||
List.of());
|
||||
List.of(),
|
||||
true);
|
||||
final var recipientIdentifiers = getSingleRecipientIdentifiers(recipients, m.getSelfNumber()).stream()
|
||||
.map(RecipientIdentifier.class::cast)
|
||||
.collect(Collectors.toSet());
|
||||
@ -407,7 +408,8 @@ public class DbusSignalImpl implements Signal, AutoCloseable {
|
||||
Optional.empty(),
|
||||
List.of(),
|
||||
Optional.empty(),
|
||||
List.of());
|
||||
List.of(),
|
||||
true);
|
||||
final var results = m.sendMessage(message, Set.of(RecipientIdentifier.NoteToSelf.INSTANCE), false);
|
||||
checkSendMessageResults(results);
|
||||
return results.timestamp();
|
||||
@ -453,7 +455,8 @@ public class DbusSignalImpl implements Signal, AutoCloseable {
|
||||
Optional.empty(),
|
||||
List.of(),
|
||||
Optional.empty(),
|
||||
List.of());
|
||||
List.of(),
|
||||
true);
|
||||
var results = m.sendMessage(message, Set.of(getGroupRecipientIdentifier(groupId)), false);
|
||||
checkSendMessageResults(results);
|
||||
return results.timestamp();
|
||||
|
||||
@ -64,7 +64,9 @@ public class LogConfigurator extends ContextAwareBase implements Configurator {
|
||||
consoleAppender.addFilter(new Filter<>() {
|
||||
@Override
|
||||
public FilterReply decide(final ILoggingEvent event) {
|
||||
return !"LibSignal".equals(event.getLoggerName()) && (
|
||||
return !"LibSignal".equals(event.getLoggerName())
|
||||
&& !"LibSignalService".equals(event.getLoggerName())
|
||||
&& (
|
||||
event.getLevel().isGreaterOrEqual(Level.WARN) || (
|
||||
event.getLevel().isGreaterOrEqual(Level.INFO) && event.getLoggerName()
|
||||
.startsWith("org.asamk")
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user