Implement editing of previous messages

This commit is contained in:
AsamK 2023-05-11 19:10:29 +02:00
parent 72390e595d
commit 8a31b7f2c1
13 changed files with 179 additions and 42 deletions

View File

@ -288,7 +288,8 @@
"name":"java.lang.Long", "name":"java.lang.Long",
"allDeclaredFields":true, "allDeclaredFields":true,
"allDeclaredMethods":true, "allDeclaredMethods":true,
"allDeclaredConstructors":true "allDeclaredConstructors":true,
"methods":[{"name":"valueOf","parameterTypes":["java.lang.String"] }]
}, },
{ {
"name":"java.lang.Number", "name":"java.lang.Number",
@ -838,11 +839,25 @@
{"name":"viewOnce","parameterTypes":[] } {"name":"viewOnce","parameterTypes":[] }
] ]
}, },
{
"name":"org.asamk.signal.json.JsonEditMessage",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"queryAllDeclaredConstructors":true,
"methods":[
{"name":"dataMessage","parameterTypes":[] },
{"name":"targetSentTimestamp","parameterTypes":[] }
]
},
{ {
"name":"org.asamk.signal.json.JsonError", "name":"org.asamk.signal.json.JsonError",
"allDeclaredFields":true, "allDeclaredFields":true,
"allDeclaredMethods":true, "allDeclaredMethods":true,
"allDeclaredConstructors":true "allDeclaredConstructors":true,
"methods":[
{"name":"message","parameterTypes":[] },
{"name":"type","parameterTypes":[] }
]
}, },
{ {
"name":"org.asamk.signal.json.JsonGroupInfo", "name":"org.asamk.signal.json.JsonGroupInfo",
@ -875,6 +890,7 @@
"methods":[ "methods":[
{"name":"callMessage","parameterTypes":[] }, {"name":"callMessage","parameterTypes":[] },
{"name":"dataMessage","parameterTypes":[] }, {"name":"dataMessage","parameterTypes":[] },
{"name":"editMessage","parameterTypes":[] },
{"name":"receiptMessage","parameterTypes":[] }, {"name":"receiptMessage","parameterTypes":[] },
{"name":"source","parameterTypes":[] }, {"name":"source","parameterTypes":[] },
{"name":"sourceDevice","parameterTypes":[] }, {"name":"sourceDevice","parameterTypes":[] },
@ -1007,7 +1023,11 @@
"name":"org.asamk.signal.json.JsonSticker", "name":"org.asamk.signal.json.JsonSticker",
"allDeclaredFields":true, "allDeclaredFields":true,
"allDeclaredMethods":true, "allDeclaredMethods":true,
"allDeclaredConstructors":true "allDeclaredConstructors":true,
"methods":[
{"name":"packId","parameterTypes":[] },
{"name":"stickerId","parameterTypes":[] }
]
}, },
{ {
"name":"org.asamk.signal.json.JsonStoryContext", "name":"org.asamk.signal.json.JsonStoryContext",
@ -1069,7 +1089,8 @@
{"name":"dataMessage","parameterTypes":[] }, {"name":"dataMessage","parameterTypes":[] },
{"name":"destination","parameterTypes":[] }, {"name":"destination","parameterTypes":[] },
{"name":"destinationNumber","parameterTypes":[] }, {"name":"destinationNumber","parameterTypes":[] },
{"name":"destinationUuid","parameterTypes":[] } {"name":"destinationUuid","parameterTypes":[] },
{"name":"editMessage","parameterTypes":[] }
] ]
}, },
{ {
@ -2417,13 +2438,15 @@
"name":"org.whispersystems.signalservice.api.groupsv2.CredentialResponse", "name":"org.whispersystems.signalservice.api.groupsv2.CredentialResponse",
"allDeclaredFields":true, "allDeclaredFields":true,
"allDeclaredMethods":true, "allDeclaredMethods":true,
"allDeclaredConstructors":true "allDeclaredConstructors":true,
"methods":[{"name":"<init>","parameterTypes":[] }]
}, },
{ {
"name":"org.whispersystems.signalservice.api.groupsv2.TemporalCredential", "name":"org.whispersystems.signalservice.api.groupsv2.TemporalCredential",
"allDeclaredFields":true, "allDeclaredFields":true,
"allDeclaredMethods":true, "allDeclaredMethods":true,
"allDeclaredConstructors":true "allDeclaredConstructors":true,
"methods":[{"name":"<init>","parameterTypes":[] }]
}, },
{ {
"name":"org.whispersystems.signalservice.api.groupsv2.TemporalCredential[]" "name":"org.whispersystems.signalservice.api.groupsv2.TemporalCredential[]"
@ -3236,6 +3259,14 @@
{"name":"sentTimestamp_"} {"name":"sentTimestamp_"}
] ]
}, },
{
"name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$EditMessage",
"fields":[
{"name":"bitField0_"},
{"name":"dataMessage_"},
{"name":"targetSentTimestamp_"}
]
},
{ {
"name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$Envelope", "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$Envelope",
"fields":[ "fields":[

View File

@ -142,6 +142,10 @@ public interface Manager extends Closeable {
Message message, Set<RecipientIdentifier> recipients Message message, Set<RecipientIdentifier> recipients
) throws IOException, AttachmentInvalidException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException, UnregisteredRecipientException, InvalidStickerException; ) throws IOException, AttachmentInvalidException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException, UnregisteredRecipientException, InvalidStickerException;
SendMessageResults sendEditMessage(
Message message, Set<RecipientIdentifier> recipients, long editTargetTimestamp
) throws IOException, AttachmentInvalidException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException, UnregisteredRecipientException, InvalidStickerException;
SendMessageResults sendRemoteDeleteMessage( SendMessageResults sendRemoteDeleteMessage(
long targetSentTimestamp, Set<RecipientIdentifier> recipients long targetSentTimestamp, Set<RecipientIdentifier> recipients
) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException; ) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException;

View File

@ -466,6 +466,14 @@ class ManagerImpl implements Manager {
private SendMessageResults sendMessage( private SendMessageResults sendMessage(
SignalServiceDataMessage.Builder messageBuilder, Set<RecipientIdentifier> recipients SignalServiceDataMessage.Builder messageBuilder, Set<RecipientIdentifier> recipients
) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException {
return sendMessage(messageBuilder, recipients, Optional.empty());
}
private SendMessageResults sendMessage(
SignalServiceDataMessage.Builder messageBuilder,
Set<RecipientIdentifier> recipients,
Optional<Long> editTargetTimestamp
) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException { ) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException {
var results = new HashMap<RecipientIdentifier, List<SendMessageResult>>(); var results = new HashMap<RecipientIdentifier, List<SendMessageResult>>();
long timestamp = System.currentTimeMillis(); long timestamp = System.currentTimeMillis();
@ -474,17 +482,19 @@ class ManagerImpl implements Manager {
if (recipient instanceof RecipientIdentifier.Single single) { if (recipient instanceof RecipientIdentifier.Single single) {
try { try {
final var recipientId = context.getRecipientHelper().resolveRecipient(single); final var recipientId = context.getRecipientHelper().resolveRecipient(single);
final var result = context.getSendHelper().sendMessage(messageBuilder, recipientId); final var result = context.getSendHelper()
.sendMessage(messageBuilder, recipientId, editTargetTimestamp);
results.put(recipient, List.of(toSendMessageResult(result))); results.put(recipient, List.of(toSendMessageResult(result)));
} catch (UnregisteredRecipientException e) { } catch (UnregisteredRecipientException e) {
results.put(recipient, results.put(recipient,
List.of(SendMessageResult.unregisteredFailure(single.toPartialRecipientAddress()))); List.of(SendMessageResult.unregisteredFailure(single.toPartialRecipientAddress())));
} }
} else if (recipient instanceof RecipientIdentifier.NoteToSelf) { } else if (recipient instanceof RecipientIdentifier.NoteToSelf) {
final var result = context.getSendHelper().sendSelfMessage(messageBuilder); final var result = context.getSendHelper().sendSelfMessage(messageBuilder, editTargetTimestamp);
results.put(recipient, List.of(toSendMessageResult(result))); results.put(recipient, List.of(toSendMessageResult(result)));
} else if (recipient instanceof RecipientIdentifier.Group group) { } else if (recipient instanceof RecipientIdentifier.Group group) {
final var result = context.getSendHelper().sendAsGroupMessage(messageBuilder, group.groupId()); final var result = context.getSendHelper()
.sendAsGroupMessage(messageBuilder, group.groupId(), editTargetTimestamp);
results.put(recipient, result.stream().map(this::toSendMessageResult).toList()); results.put(recipient, result.stream().map(this::toSendMessageResult).toList());
} }
} }
@ -581,6 +591,15 @@ class ManagerImpl implements Manager {
return sendMessage(messageBuilder, recipients); return sendMessage(messageBuilder, recipients);
} }
@Override
public SendMessageResults sendEditMessage(
Message message, Set<RecipientIdentifier> recipients, long editTargetTimestamp
) throws IOException, AttachmentInvalidException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException, UnregisteredRecipientException, InvalidStickerException {
final var messageBuilder = SignalServiceDataMessage.newBuilder();
applyMessage(messageBuilder, message);
return sendMessage(messageBuilder, recipients, Optional.of(editTargetTimestamp));
}
private void applyMessage( private void applyMessage(
final SignalServiceDataMessage.Builder messageBuilder, final Message message final SignalServiceDataMessage.Builder messageBuilder, final Message message
) throws AttachmentInvalidException, IOException, UnregisteredRecipientException, InvalidStickerException { ) throws AttachmentInvalidException, IOException, UnregisteredRecipientException, InvalidStickerException {

View File

@ -9,6 +9,7 @@ import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer; import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer;
import org.whispersystems.signalservice.api.messages.SignalServiceContent; import org.whispersystems.signalservice.api.messages.SignalServiceContent;
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
import org.whispersystems.signalservice.api.messages.SignalServiceEditMessage;
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope; import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
import org.whispersystems.signalservice.api.messages.SignalServiceGroup; import org.whispersystems.signalservice.api.messages.SignalServiceGroup;
import org.whispersystems.signalservice.api.messages.SignalServiceGroupContext; import org.whispersystems.signalservice.api.messages.SignalServiceGroupContext;
@ -51,6 +52,7 @@ public record MessageEnvelope(
Optional<Receipt> receipt, Optional<Receipt> receipt,
Optional<Typing> typing, Optional<Typing> typing,
Optional<Data> data, Optional<Data> data,
Optional<Edit> edit,
Optional<Sync> sync, Optional<Sync> sync,
Optional<Call> call, Optional<Call> call,
Optional<Story> story Optional<Story> story
@ -541,6 +543,19 @@ public record MessageEnvelope(
} }
} }
public record Edit(long targetSentTimestamp, Data dataMessage) {
public static Edit from(
final SignalServiceEditMessage editMessage,
RecipientResolver recipientResolver,
RecipientAddressResolver addressResolver,
final AttachmentFileProvider fileProvider
) {
return new Edit(editMessage.getTargetSentTimestamp(),
Data.from(editMessage.getDataMessage(), recipientResolver, addressResolver, fileProvider));
}
}
public record Sync( public record Sync(
Optional<Sent> sent, Optional<Sent> sent,
Optional<Blocked> blocked, Optional<Blocked> blocked,
@ -582,6 +597,7 @@ public record MessageEnvelope(
Optional<RecipientAddress> destination, Optional<RecipientAddress> destination,
Set<RecipientAddress> recipients, Set<RecipientAddress> recipients,
Optional<Data> message, Optional<Data> message,
Optional<Edit> editMessage,
Optional<Story> story Optional<Story> story
) { ) {
@ -603,6 +619,8 @@ public record MessageEnvelope(
.collect(Collectors.toSet()), .collect(Collectors.toSet()),
sentMessage.getDataMessage() sentMessage.getDataMessage()
.map(message -> Data.from(message, recipientResolver, addressResolver, fileProvider)), .map(message -> Data.from(message, recipientResolver, addressResolver, fileProvider)),
sentMessage.getEditMessage()
.map(message -> Edit.from(message, recipientResolver, addressResolver, fileProvider)),
sentMessage.getStoryMessage().map(s -> Story.from(s, fileProvider))); sentMessage.getStoryMessage().map(s -> Story.from(s, fileProvider)));
} }
} }
@ -920,6 +938,7 @@ public record MessageEnvelope(
Optional<Receipt> receipt; Optional<Receipt> receipt;
Optional<Typing> typing; Optional<Typing> typing;
Optional<Data> data; Optional<Data> data;
Optional<Edit> edit;
Optional<Sync> sync; Optional<Sync> sync;
Optional<Call> call; Optional<Call> call;
Optional<Story> story; Optional<Story> story;
@ -928,6 +947,7 @@ public record MessageEnvelope(
typing = content.getTypingMessage().map(Typing::from); typing = content.getTypingMessage().map(Typing::from);
data = content.getDataMessage() data = content.getDataMessage()
.map(dataMessage -> Data.from(dataMessage, recipientResolver, addressResolver, fileProvider)); .map(dataMessage -> Data.from(dataMessage, recipientResolver, addressResolver, fileProvider));
edit = content.getEditMessage().map(s -> Edit.from(s, recipientResolver, addressResolver, fileProvider));
sync = content.getSyncMessage().map(s -> Sync.from(s, recipientResolver, addressResolver, fileProvider)); sync = content.getSyncMessage().map(s -> Sync.from(s, recipientResolver, addressResolver, fileProvider));
call = content.getCallMessage().map(Call::from); call = content.getCallMessage().map(Call::from);
story = content.getStoryMessage().map(s -> Story.from(s, fileProvider)); story = content.getStoryMessage().map(s -> Story.from(s, fileProvider));
@ -937,6 +957,7 @@ public record MessageEnvelope(
List.of(envelope.getTimestamp()))) : Optional.empty(); List.of(envelope.getTimestamp()))) : Optional.empty();
typing = Optional.empty(); typing = Optional.empty();
data = Optional.empty(); data = Optional.empty();
edit = Optional.empty();
sync = Optional.empty(); sync = Optional.empty();
call = Optional.empty(); call = Optional.empty();
story = Optional.empty(); story = Optional.empty();
@ -953,6 +974,7 @@ public record MessageEnvelope(
receipt, receipt,
typing, typing,
data, data,
edit,
sync, sync,
call, call,
story); story);

View File

@ -563,7 +563,7 @@ public class GroupHelper {
private void sendExpirationTimerUpdate(GroupIdV1 groupId) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException { private void sendExpirationTimerUpdate(GroupIdV1 groupId) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException {
final var messageBuilder = SignalServiceDataMessage.newBuilder().asExpirationUpdate(); final var messageBuilder = SignalServiceDataMessage.newBuilder().asExpirationUpdate();
context.getSendHelper().sendAsGroupMessage(messageBuilder, groupId); context.getSendHelper().sendAsGroupMessage(messageBuilder, groupId, Optional.empty());
} }
private SendGroupMessageResults updateGroupV2( private SendGroupMessageResults updateGroupV2(

View File

@ -29,6 +29,7 @@ import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
import org.whispersystems.signalservice.api.messages.SendMessageResult; import org.whispersystems.signalservice.api.messages.SendMessageResult;
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; 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.SignalServiceReceiptMessage;
import org.whispersystems.signalservice.api.messages.SignalServiceTypingMessage; import org.whispersystems.signalservice.api.messages.SignalServiceTypingMessage;
import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage; import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage;
@ -71,8 +72,10 @@ public class SendHelper {
* The message is extended with the current expiration timer. * The message is extended with the current expiration timer.
*/ */
public SendMessageResult sendMessage( public SendMessageResult sendMessage(
final SignalServiceDataMessage.Builder messageBuilder, final RecipientId recipientId final SignalServiceDataMessage.Builder messageBuilder,
) throws IOException { final RecipientId recipientId,
Optional<Long> editTargetTimestamp
) {
var contact = account.getContactStore().getContact(recipientId); var contact = account.getContactStore().getContact(recipientId);
if (contact == null || !contact.isProfileSharingEnabled()) { if (contact == null || !contact.isProfileSharingEnabled()) {
final var contactBuilder = contact == null ? Contact.newBuilder() : Contact.newBuilder(contact); final var contactBuilder = contact == null ? Contact.newBuilder() : Contact.newBuilder(contact);
@ -89,7 +92,7 @@ public class SendHelper {
} }
final var message = messageBuilder.build(); final var message = messageBuilder.build();
return sendMessage(message, recipientId); return sendMessage(message, recipientId, editTargetTimestamp);
} }
/** /**
@ -97,10 +100,10 @@ public class SendHelper {
* The message is extended with the current expiration timer for the group and the group context. * The message is extended with the current expiration timer for the group and the group context.
*/ */
public List<SendMessageResult> sendAsGroupMessage( public List<SendMessageResult> sendAsGroupMessage(
SignalServiceDataMessage.Builder messageBuilder, GroupId groupId SignalServiceDataMessage.Builder messageBuilder, GroupId groupId, Optional<Long> editTargetTimestamp
) throws IOException, GroupNotFoundException, NotAGroupMemberException, GroupSendingNotAllowedException { ) throws IOException, GroupNotFoundException, NotAGroupMemberException, GroupSendingNotAllowedException {
final var g = getGroupForSending(groupId); final var g = getGroupForSending(groupId);
return sendAsGroupMessage(messageBuilder, g); return sendAsGroupMessage(messageBuilder, g, editTargetTimestamp);
} }
/** /**
@ -112,7 +115,7 @@ public class SendHelper {
final Set<RecipientId> recipientIds, final Set<RecipientId> recipientIds,
final DistributionId distributionId final DistributionId distributionId
) throws IOException { ) throws IOException {
return sendGroupMessage(message, recipientIds, distributionId, ContentHint.IMPLICIT); return sendGroupMessage(message, recipientIds, distributionId, ContentHint.IMPLICIT, Optional.empty());
} }
public SendMessageResult sendReceiptMessage( public SendMessageResult sendReceiptMessage(
@ -169,7 +172,7 @@ public class SendHelper {
} }
public SendMessageResult sendSelfMessage( public SendMessageResult sendSelfMessage(
SignalServiceDataMessage.Builder messageBuilder SignalServiceDataMessage.Builder messageBuilder, Optional<Long> editTargetTimestamp
) { ) {
final var recipientId = account.getSelfRecipientId(); final var recipientId = account.getSelfRecipientId();
final var contact = account.getContactStore().getContact(recipientId); final var contact = account.getContactStore().getContact(recipientId);
@ -177,7 +180,7 @@ public class SendHelper {
messageBuilder.withExpiration(expirationTime); messageBuilder.withExpiration(expirationTime);
var message = messageBuilder.build(); var message = messageBuilder.build();
return sendSelfMessage(message); return sendSelfMessage(message, editTargetTimestamp);
} }
public SendMessageResult sendSyncMessage(SignalServiceSyncMessage message) { public SendMessageResult sendSyncMessage(SignalServiceSyncMessage message) {
@ -289,7 +292,7 @@ public class SendHelper {
} }
private List<SendMessageResult> sendAsGroupMessage( private List<SendMessageResult> sendAsGroupMessage(
final SignalServiceDataMessage.Builder messageBuilder, final GroupInfo g final SignalServiceDataMessage.Builder messageBuilder, final GroupInfo g, Optional<Long> editTargetTimestamp
) throws IOException, GroupSendingNotAllowedException { ) throws IOException, GroupSendingNotAllowedException {
GroupUtils.setGroupContext(messageBuilder, g); GroupUtils.setGroupContext(messageBuilder, g);
messageBuilder.withExpiration(g.getMessageExpirationTimer()); messageBuilder.withExpiration(g.getMessageExpirationTimer());
@ -308,14 +311,19 @@ public class SendHelper {
} }
} }
return sendGroupMessage(message, recipients, g.getDistributionId(), ContentHint.RESENDABLE); return sendGroupMessage(message,
recipients,
g.getDistributionId(),
ContentHint.RESENDABLE,
editTargetTimestamp);
} }
private List<SendMessageResult> sendGroupMessage( private List<SendMessageResult> sendGroupMessage(
final SignalServiceDataMessage message, final SignalServiceDataMessage message,
final Set<RecipientId> recipientIds, final Set<RecipientId> recipientIds,
final DistributionId distributionId, final DistributionId distributionId,
final ContentHint contentHint final ContentHint contentHint,
final Optional<Long> editTargetTimestamp
) throws IOException { ) throws IOException {
final var messageSender = dependencies.getMessageSender(); final var messageSender = dependencies.getMessageSender();
final var messageSendLogStore = account.getMessageSendLogStore(); final var messageSendLogStore = account.getMessageSendLogStore();
@ -355,7 +363,7 @@ public class SendHelper {
SignalServiceMessageSender.SenderKeyGroupEvents.EMPTY, SignalServiceMessageSender.SenderKeyGroupEvents.EMPTY,
urgent, urgent,
false, false,
null, editTargetTimestamp.map(timestamp -> new SignalServiceEditMessage(timestamp, message)).orElse(null),
sendResult -> { sendResult -> {
logger.trace("Partial message send results: {}", sendResult.size()); logger.trace("Partial message send results: {}", sendResult.size());
synchronized (entryId) { synchronized (entryId) {
@ -613,18 +621,27 @@ public class SendHelper {
} }
private SendMessageResult sendMessage( private SendMessageResult sendMessage(
SignalServiceDataMessage message, RecipientId recipientId SignalServiceDataMessage message, RecipientId recipientId, Optional<Long> editTargetTimestamp
) { ) {
final var messageSendLogStore = account.getMessageSendLogStore(); final var messageSendLogStore = account.getMessageSendLogStore();
final var urgent = true; final var urgent = true;
final var includePniSignature = false;
final var result = handleSendMessage(recipientId, final var result = handleSendMessage(recipientId,
(messageSender, address, unidentifiedAccess) -> messageSender.sendDataMessage(address, editTargetTimestamp.isEmpty()
? (messageSender, address, unidentifiedAccess) -> messageSender.sendDataMessage(address,
unidentifiedAccess, unidentifiedAccess,
ContentHint.RESENDABLE, ContentHint.RESENDABLE,
message, message,
SignalServiceMessageSender.IndividualSendEvents.EMPTY, SignalServiceMessageSender.IndividualSendEvents.EMPTY,
urgent, urgent,
false)); includePniSignature)
: (messageSender, address, unidentifiedAccess) -> messageSender.sendEditMessage(address,
unidentifiedAccess,
ContentHint.RESENDABLE,
message,
SignalServiceMessageSender.IndividualSendEvents.EMPTY,
urgent,
editTargetTimestamp.get()));
messageSendLogStore.insertIfPossible(message.getTimestamp(), result, ContentHint.RESENDABLE, urgent); messageSendLogStore.insertIfPossible(message.getTimestamp(), result, ContentHint.RESENDABLE, urgent);
handleSendMessageResult(result); handleSendMessageResult(result);
return result; return result;
@ -665,17 +682,17 @@ public class SendHelper {
} }
} }
private SendMessageResult sendSelfMessage(SignalServiceDataMessage message) { private SendMessageResult sendSelfMessage(SignalServiceDataMessage message, Optional<Long> editTargetTimestamp) {
var address = account.getSelfAddress(); var address = account.getSelfAddress();
var transcript = new SentTranscriptMessage(Optional.of(address), var transcript = new SentTranscriptMessage(Optional.of(address),
message.getTimestamp(), message.getTimestamp(),
Optional.of(message), editTargetTimestamp.isEmpty() ? Optional.of(message) : Optional.empty(),
message.getExpiresInSeconds(), message.getExpiresInSeconds(),
Map.of(address.getServiceId(), true), Map.of(address.getServiceId(), true),
false, false,
Optional.empty(), Optional.empty(),
Set.of(), Set.of(),
Optional.empty()); editTargetTimestamp.map((timestamp) -> new SignalServiceEditMessage(timestamp, message)));
var syncMessage = SignalServiceSyncMessage.forSentTranscript(transcript); var syncMessage = SignalServiceSyncMessage.forSentTranscript(transcript);
return sendSyncMessage(syncMessage); return sendSyncMessage(syncMessage);

View File

@ -285,6 +285,9 @@ 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.
*--edit-timestamp*::
Specify the timestamp of a previous message with the recipient or group to send an edited message.
=== sendPaymentNotification === sendPaymentNotification
Send a payment notification. Send a payment notification.

View File

@ -69,6 +69,10 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler {
var message = envelope.data().get(); var message = envelope.data().get();
printDataMessage(writer, message); printDataMessage(writer, message);
} }
if (envelope.edit().isPresent()) {
var message = envelope.edit().get();
printEditMessage(writer, message);
}
if (envelope.story().isPresent()) { if (envelope.story().isPresent()) {
var message = envelope.story().get(); var message = envelope.story().get();
printStoryMessage(writer.indentedWriter(), message); printStoryMessage(writer.indentedWriter(), message);
@ -192,6 +196,13 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler {
} }
} }
private void printEditMessage(
PlainTextWriter writer, MessageEnvelope.Edit message
) {
writer.println("Edit: Target message timestamp: {}", DateUtils.formatTimestamp(message.targetSentTimestamp()));
printDataMessage(writer.indentedWriter(), message.dataMessage());
}
private void printStoryMessage( private void printStoryMessage(
PlainTextWriter writer, MessageEnvelope.Story message PlainTextWriter writer, MessageEnvelope.Story message
) { ) {

View File

@ -84,6 +84,9 @@ public class SendCommand implements JsonRpcLocalCommand {
.type(long.class) .type(long.class)
.help("Specify the timestamp of a story to reply to."); .help("Specify the timestamp of a story to reply to.");
subparser.addArgument("--story-author").help("Specify the number of the author of the story."); subparser.addArgument("--story-author").help("Specify the number of the author of the story.");
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.");
} }
@Override @Override
@ -189,6 +192,8 @@ public class SendCommand implements JsonRpcLocalCommand {
"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.");
} }
final var editTimestamp = ns.getLong("edit-timestamp");
try { try {
final var message = new Message(messageText, final var message = new Message(messageText,
attachments, attachments,
@ -197,7 +202,9 @@ public class SendCommand implements JsonRpcLocalCommand {
Optional.ofNullable(sticker), Optional.ofNullable(sticker),
previews, previews,
Optional.ofNullable((storyReply))); Optional.ofNullable((storyReply)));
var results = m.sendMessage(message, recipientIdentifiers); var results = editTimestamp != null
? m.sendEditMessage(message, recipientIdentifiers, editTimestamp)
: 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

@ -10,6 +10,7 @@ import org.asamk.signal.manager.api.Group;
import org.asamk.signal.manager.api.Identity; import org.asamk.signal.manager.api.Identity;
import org.asamk.signal.manager.api.InactiveGroupLinkException; import org.asamk.signal.manager.api.InactiveGroupLinkException;
import org.asamk.signal.manager.api.InvalidDeviceLinkException; import org.asamk.signal.manager.api.InvalidDeviceLinkException;
import org.asamk.signal.manager.api.InvalidStickerException;
import org.asamk.signal.manager.api.InvalidUsernameException; import org.asamk.signal.manager.api.InvalidUsernameException;
import org.asamk.signal.manager.api.Message; import org.asamk.signal.manager.api.Message;
import org.asamk.signal.manager.api.MessageEnvelope; import org.asamk.signal.manager.api.MessageEnvelope;
@ -25,6 +26,7 @@ import org.asamk.signal.manager.api.StickerPack;
import org.asamk.signal.manager.api.StickerPackInvalidException; import org.asamk.signal.manager.api.StickerPackInvalidException;
import org.asamk.signal.manager.api.StickerPackUrl; import org.asamk.signal.manager.api.StickerPackUrl;
import org.asamk.signal.manager.api.TypingAction; import org.asamk.signal.manager.api.TypingAction;
import org.asamk.signal.manager.api.UnregisteredRecipientException;
import org.asamk.signal.manager.api.UpdateGroup; import org.asamk.signal.manager.api.UpdateGroup;
import org.asamk.signal.manager.api.UpdateProfile; import org.asamk.signal.manager.api.UpdateProfile;
import org.asamk.signal.manager.api.UserStatus; import org.asamk.signal.manager.api.UserStatus;
@ -369,6 +371,13 @@ public class DbusManagerImpl implements Manager {
groupId -> signal.sendGroupMessage(message.messageText(), message.attachments(), groupId)); groupId -> signal.sendGroupMessage(message.messageText(), message.attachments(), groupId));
} }
@Override
public SendMessageResults sendEditMessage(
final Message message, final Set<RecipientIdentifier> recipients, final long editTargetTimestamp
) throws IOException, AttachmentInvalidException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException, UnregisteredRecipientException, InvalidStickerException {
throw new UnsupportedOperationException();
}
@Override @Override
public SendMessageResults sendRemoteDeleteMessage( public SendMessageResults sendRemoteDeleteMessage(
final long targetSentTimestamp, final Set<RecipientIdentifier> recipients final long targetSentTimestamp, final Set<RecipientIdentifier> recipients
@ -801,6 +810,7 @@ public class DbusManagerImpl implements Manager {
List.of())), List.of())),
Optional.empty(), Optional.empty(),
Optional.empty(), Optional.empty(),
Optional.empty(),
Optional.empty()); Optional.empty());
notifyMessageHandlers(envelope); notifyMessageHandlers(envelope);
}; };
@ -827,6 +837,7 @@ public class DbusManagerImpl implements Manager {
Optional.empty(), Optional.empty(),
Optional.empty(), Optional.empty(),
Optional.empty(), Optional.empty(),
Optional.empty(),
Optional.empty()); Optional.empty());
notifyMessageHandlers(envelope); notifyMessageHandlers(envelope);
}; };
@ -844,6 +855,7 @@ public class DbusManagerImpl implements Manager {
Optional.empty(), Optional.empty(),
Optional.empty(), Optional.empty(),
Optional.empty(), Optional.empty(),
Optional.empty(),
Optional.of(new MessageEnvelope.Sync(Optional.of(new MessageEnvelope.Sync.Sent(syncReceived.getTimestamp(), Optional.of(new MessageEnvelope.Sync(Optional.of(new MessageEnvelope.Sync.Sent(syncReceived.getTimestamp(),
syncReceived.getTimestamp(), syncReceived.getTimestamp(),
syncReceived.getDestination().isEmpty() syncReceived.getDestination().isEmpty()
@ -874,6 +886,7 @@ public class DbusManagerImpl implements Manager {
List.of(), List.of(),
List.of(), List.of(),
List.of())), List.of())),
Optional.empty(),
Optional.empty())), Optional.empty())),
Optional.empty(), Optional.empty(),
List.of(), List.of(),

View File

@ -0,0 +1,10 @@
package org.asamk.signal.json;
import org.asamk.signal.manager.api.MessageEnvelope;
record JsonEditMessage(long targetSentTimestamp, JsonDataMessage dataMessage) {
static JsonEditMessage from(MessageEnvelope.Edit editMessage) {
return new JsonEditMessage(editMessage.targetSentTimestamp(), JsonDataMessage.from(editMessage.dataMessage()));
}
}

View File

@ -18,6 +18,7 @@ public record JsonMessageEnvelope(
Integer sourceDevice, Integer sourceDevice,
long timestamp, long timestamp,
@JsonInclude(JsonInclude.Include.NON_NULL) JsonDataMessage dataMessage, @JsonInclude(JsonInclude.Include.NON_NULL) JsonDataMessage dataMessage,
@JsonInclude(JsonInclude.Include.NON_NULL) JsonEditMessage editMessage,
@JsonInclude(JsonInclude.Include.NON_NULL) JsonStoryMessage storyMessage, @JsonInclude(JsonInclude.Include.NON_NULL) JsonStoryMessage storyMessage,
@JsonInclude(JsonInclude.Include.NON_NULL) JsonSyncMessage syncMessage, @JsonInclude(JsonInclude.Include.NON_NULL) JsonSyncMessage syncMessage,
@JsonInclude(JsonInclude.Include.NON_NULL) JsonCallMessage callMessage, @JsonInclude(JsonInclude.Include.NON_NULL) JsonCallMessage callMessage,
@ -61,6 +62,7 @@ public record JsonMessageEnvelope(
final var typingMessage = envelope.typing().map(JsonTypingMessage::from).orElse(null); final var typingMessage = envelope.typing().map(JsonTypingMessage::from).orElse(null);
final var dataMessage = envelope.data().map(JsonDataMessage::from).orElse(null); final var dataMessage = envelope.data().map(JsonDataMessage::from).orElse(null);
final var editMessage = envelope.edit().map(JsonEditMessage::from).orElse(null);
final var storyMessage = envelope.story().map(JsonStoryMessage::from).orElse(null); final var storyMessage = envelope.story().map(JsonStoryMessage::from).orElse(null);
final var syncMessage = envelope.sync().map(JsonSyncMessage::from).orElse(null); final var syncMessage = envelope.sync().map(JsonSyncMessage::from).orElse(null);
final var callMessage = envelope.call().map(JsonCallMessage::from).orElse(null); final var callMessage = envelope.call().map(JsonCallMessage::from).orElse(null);
@ -72,6 +74,7 @@ public record JsonMessageEnvelope(
sourceDevice, sourceDevice,
timestamp, timestamp,
dataMessage, dataMessage,
editMessage,
storyMessage, storyMessage,
syncMessage, syncMessage,
callMessage, callMessage,

View File

@ -1,8 +1,10 @@
package org.asamk.signal.json; package org.asamk.signal.json;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonUnwrapped; import com.fasterxml.jackson.annotation.JsonUnwrapped;
import org.asamk.signal.manager.api.MessageEnvelope; import org.asamk.signal.manager.api.MessageEnvelope;
import org.asamk.signal.manager.api.RecipientAddress;
import java.util.UUID; import java.util.UUID;
@ -10,22 +12,17 @@ record JsonSyncDataMessage(
@Deprecated String destination, @Deprecated String destination,
String destinationNumber, String destinationNumber,
String destinationUuid, String destinationUuid,
@JsonInclude(JsonInclude.Include.NON_NULL) JsonEditMessage editMessage,
@JsonUnwrapped JsonDataMessage dataMessage @JsonUnwrapped JsonDataMessage dataMessage
) { ) {
static JsonSyncDataMessage from(MessageEnvelope.Sync.Sent transcriptMessage) { static JsonSyncDataMessage from(MessageEnvelope.Sync.Sent transcriptMessage) {
if (transcriptMessage.destination().isPresent()) { return new JsonSyncDataMessage(transcriptMessage.destination()
final var address = transcriptMessage.destination().get(); .map(RecipientAddress::getLegacyIdentifier)
return new JsonSyncDataMessage(address.getLegacyIdentifier(), .orElse(null),
address.number().orElse(null), transcriptMessage.destination().flatMap(RecipientAddress::number).orElse(null),
address.uuid().map(UUID::toString).orElse(null), transcriptMessage.destination().flatMap(address -> address.uuid().map(UUID::toString)).orElse(null),
transcriptMessage.editMessage().map(JsonEditMessage::from).orElse(null),
transcriptMessage.message().map(JsonDataMessage::from).orElse(null)); transcriptMessage.message().map(JsonDataMessage::from).orElse(null));
} else {
return new JsonSyncDataMessage(null,
null,
null,
transcriptMessage.message().map(JsonDataMessage::from).orElse(null));
}
} }
} }