From 811dc0dcf392ae0a6ba260f64743f72328363ad0 Mon Sep 17 00:00:00 2001 From: Scott Lewis Date: Mon, 27 Jan 2025 16:45:20 -0800 Subject: [PATCH 1/4] Added sendStoryMessage --- .../org/asamk/signal/manager/Manager.java | 4 + .../signal/manager/internal/ManagerImpl.java | 16 + src/main/java/org/asamk/Signal.java | 1153 ++++++++--------- .../asamk/signal/dbus/DbusManagerImpl.java | 10 + .../org/asamk/signal/dbus/DbusSignalImpl.java | 25 + 5 files changed, 601 insertions(+), 607 deletions(-) diff --git a/lib/src/main/java/org/asamk/signal/manager/Manager.java b/lib/src/main/java/org/asamk/signal/manager/Manager.java index 4b0869944e..7fcaa69099 100644 --- a/lib/src/main/java/org/asamk/signal/manager/Manager.java +++ b/lib/src/main/java/org/asamk/signal/manager/Manager.java @@ -189,6 +189,10 @@ SendMessageResults sendMessage( Message message, Set recipients, boolean notifySelf ) throws IOException, AttachmentInvalidException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException, UnregisteredRecipientException, InvalidStickerException; + SendMessageResults sendStoryMessage( + Message message, Set recipients, boolean notifySelf + ) throws IOException, AttachmentInvalidException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException, UnregisteredRecipientException, InvalidStickerException; + SendMessageResults sendEditMessage( Message message, Set recipients, long editTargetTimestamp ) throws IOException, AttachmentInvalidException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException, UnregisteredRecipientException, InvalidStickerException; diff --git a/lib/src/main/java/org/asamk/signal/manager/internal/ManagerImpl.java b/lib/src/main/java/org/asamk/signal/manager/internal/ManagerImpl.java index 27bda58326..b04023a8a4 100644 --- a/lib/src/main/java/org/asamk/signal/manager/internal/ManagerImpl.java +++ b/lib/src/main/java/org/asamk/signal/manager/internal/ManagerImpl.java @@ -96,6 +96,7 @@ import org.whispersystems.signalservice.api.SignalSessionLock; import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; +import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage.StoryContext; import org.whispersystems.signalservice.api.messages.SignalServicePreview; import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage; import org.whispersystems.signalservice.api.messages.SignalServiceTypingMessage; @@ -736,6 +737,21 @@ public SendMessageResults sendMessage( return sendMessage(messageBuilder, recipients, notifySelf); } + @Override + public SendMessageResults sendStoryMessage( + Message message, Set recipients, boolean notifySelf + ) throws IOException, AttachmentInvalidException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException, UnregisteredRecipientException, InvalidStickerException { + final var selfProfile = context.getProfileHelper().getSelfProfile(); + if (selfProfile == null || selfProfile.getDisplayName().isEmpty()) { + logger.warn( + "No profile name set. When sending a message it's recommended to set a profile name with the updateProfile command. This may become mandatory in the future."); + } + final var messageBuilder = SignalServiceDataMessage.newBuilder(); + applyMessage(messageBuilder, message); + messageBuilder.withStoryContext(new StoryContext(account.getSelfAddress().getServiceId(), System.currentTimeMillis())); + return sendMessage(messageBuilder, recipients, notifySelf); + } + @Override public SendMessageResults sendEditMessage( Message message, Set recipients, long editTargetTimestamp diff --git a/src/main/java/org/asamk/Signal.java b/src/main/java/org/asamk/Signal.java index 1d71565969..f825b5ec1c 100644 --- a/src/main/java/org/asamk/Signal.java +++ b/src/main/java/org/asamk/Signal.java @@ -15,723 +15,662 @@ import java.util.Map; /** - * DBus interface for the org.asamk.Signal service. - * Including emitted Signals and returned Errors. + * DBus interface for the org.asamk.Signal service. Including emitted Signals + * and returned Errors. */ public interface Signal extends DBusInterface { - String getSelfNumber(); + String getSelfNumber(); - void subscribeReceive(); + void subscribeReceive(); - void unsubscribeReceive(); + void unsubscribeReceive(); - long sendMessage( - String message, List attachments, String recipient - ) throws Error.AttachmentInvalid, Error.Failure, Error.InvalidNumber, Error.UntrustedIdentity; + long sendMessage(String message, List attachments, String recipient) + throws Error.AttachmentInvalid, Error.Failure, Error.InvalidNumber, Error.UntrustedIdentity; - long sendMessage( - String message, List attachments, List recipients - ) throws Error.AttachmentInvalid, Error.Failure, Error.InvalidNumber, Error.UntrustedIdentity; + long sendMessage(String message, List attachments, List recipients) + throws Error.AttachmentInvalid, Error.Failure, Error.InvalidNumber, Error.UntrustedIdentity; - void sendTyping( - String recipient, boolean stop - ) throws Error.Failure, Error.UntrustedIdentity; + void sendTyping(String recipient, boolean stop) throws Error.Failure, Error.UntrustedIdentity; - void sendReadReceipt( - String recipient, List messageIds - ) throws Error.Failure, Error.UntrustedIdentity; + void sendReadReceipt(String recipient, List messageIds) throws Error.Failure, Error.UntrustedIdentity; - void sendViewedReceipt( - String recipient, List messageIds - ) throws Error.Failure, Error.UntrustedIdentity; + void sendViewedReceipt(String recipient, List messageIds) throws Error.Failure, Error.UntrustedIdentity; - long sendRemoteDeleteMessage( - long targetSentTimestamp, String recipient - ) throws Error.Failure, Error.InvalidNumber; + long sendRemoteDeleteMessage(long targetSentTimestamp, String recipient) throws Error.Failure, Error.InvalidNumber; - long sendRemoteDeleteMessage( - long targetSentTimestamp, List recipients - ) throws Error.Failure, Error.InvalidNumber; + long sendRemoteDeleteMessage(long targetSentTimestamp, List recipients) + throws Error.Failure, Error.InvalidNumber; - long sendMessageReaction( - String emoji, boolean remove, String targetAuthor, long targetSentTimestamp, String recipient - ) throws Error.InvalidNumber, Error.Failure; + long sendMessageReaction(String emoji, boolean remove, String targetAuthor, long targetSentTimestamp, + String recipient) throws Error.InvalidNumber, Error.Failure; - long sendMessageReaction( - String emoji, boolean remove, String targetAuthor, long targetSentTimestamp, List recipients - ) throws Error.InvalidNumber, Error.Failure; + long sendMessageReaction(String emoji, boolean remove, String targetAuthor, long targetSentTimestamp, + List recipients) throws Error.InvalidNumber, Error.Failure; - long sendPaymentNotification(byte[] receipt, String note, String recipient) throws Error.Failure; + long sendPaymentNotification(byte[] receipt, String note, String recipient) throws Error.Failure; - void sendContacts() throws Error.Failure; + void sendContacts() throws Error.Failure; - void sendSyncRequest() throws Error.Failure; + void sendSyncRequest() throws Error.Failure; - long sendNoteToSelfMessage( - String message, List attachments - ) throws Error.AttachmentInvalid, Error.Failure; + long sendNoteToSelfMessage(String message, List attachments) throws Error.AttachmentInvalid, Error.Failure; - void sendEndSessionMessage(List recipients) throws Error.Failure, Error.InvalidNumber, Error.UntrustedIdentity; + void sendEndSessionMessage(List recipients) + throws Error.Failure, Error.InvalidNumber, Error.UntrustedIdentity; - void deleteRecipient(final String recipient) throws Error.Failure; + void deleteRecipient(final String recipient) throws Error.Failure; - void deleteContact(final String recipient) throws Error.Failure; + void deleteContact(final String recipient) throws Error.Failure; - long sendGroupMessage( - String message, List attachments, byte[] groupId - ) throws Error.GroupNotFound, Error.Failure, Error.AttachmentInvalid, Error.InvalidGroupId; + long sendGroupMessage(String message, List attachments, byte[] groupId) + throws Error.GroupNotFound, Error.Failure, Error.AttachmentInvalid, Error.InvalidGroupId; - void sendGroupTyping( - final byte[] groupId, final boolean stop - ) throws Error.Failure, Error.GroupNotFound, Error.UntrustedIdentity; + long sendStoryMessage(String messageText, List attachments, byte[] groupId); - long sendGroupRemoteDeleteMessage( - long targetSentTimestamp, byte[] groupId - ) throws Error.Failure, Error.GroupNotFound, Error.InvalidGroupId; + void sendGroupTyping(final byte[] groupId, final boolean stop) + throws Error.Failure, Error.GroupNotFound, Error.UntrustedIdentity; - long sendGroupMessageReaction( - String emoji, boolean remove, String targetAuthor, long targetSentTimestamp, byte[] groupId - ) throws Error.GroupNotFound, Error.Failure, Error.InvalidNumber, Error.InvalidGroupId; + long sendGroupRemoteDeleteMessage(long targetSentTimestamp, byte[] groupId) + throws Error.Failure, Error.GroupNotFound, Error.InvalidGroupId; - String getContactName(String number) throws Error.InvalidNumber; + long sendGroupMessageReaction(String emoji, boolean remove, String targetAuthor, long targetSentTimestamp, + byte[] groupId) throws Error.GroupNotFound, Error.Failure, Error.InvalidNumber, Error.InvalidGroupId; - void setContactName(String number, String name) throws Error.InvalidNumber; + String getContactName(String number) throws Error.InvalidNumber; - void setExpirationTimer(final String number, final int expiration) throws Error.Failure; + void setContactName(String number, String name) throws Error.InvalidNumber; - void setContactBlocked(String number, boolean blocked) throws Error.InvalidNumber; + void setExpirationTimer(final String number, final int expiration) throws Error.Failure; - @Deprecated - void setGroupBlocked(byte[] groupId, boolean blocked) throws Error.GroupNotFound, Error.InvalidGroupId; + void setContactBlocked(String number, boolean blocked) throws Error.InvalidNumber; - @Deprecated - List getGroupIds(); + @Deprecated + void setGroupBlocked(byte[] groupId, boolean blocked) throws Error.GroupNotFound, Error.InvalidGroupId; - DBusPath getGroup(byte[] groupId); + @Deprecated + List getGroupIds(); - List listGroups(); + DBusPath getGroup(byte[] groupId); - @Deprecated - String getGroupName(byte[] groupId) throws Error.InvalidGroupId; + List listGroups(); - @Deprecated - List getGroupMembers(byte[] groupId) throws Error.InvalidGroupId; + @Deprecated + String getGroupName(byte[] groupId) throws Error.InvalidGroupId; - byte[] createGroup( - String name, List members, String avatar - ) throws Error.AttachmentInvalid, Error.Failure, Error.InvalidNumber; + @Deprecated + List getGroupMembers(byte[] groupId) throws Error.InvalidGroupId; - @Deprecated - byte[] updateGroup( - byte[] groupId, String name, List members, String avatar - ) throws Error.AttachmentInvalid, Error.Failure, Error.InvalidNumber, Error.GroupNotFound, Error.InvalidGroupId; + byte[] createGroup(String name, List members, String avatar) + throws Error.AttachmentInvalid, Error.Failure, Error.InvalidNumber; - @Deprecated - boolean isRegistered() throws Error.Failure, Error.InvalidNumber; + @Deprecated + byte[] updateGroup(byte[] groupId, String name, List members, String avatar) throws Error.AttachmentInvalid, + Error.Failure, Error.InvalidNumber, Error.GroupNotFound, Error.InvalidGroupId; - boolean isRegistered(String number) throws Error.Failure, Error.InvalidNumber; + @Deprecated + boolean isRegistered() throws Error.Failure, Error.InvalidNumber; - List isRegistered(List numbers) throws Error.Failure, Error.InvalidNumber; + boolean isRegistered(String number) throws Error.Failure, Error.InvalidNumber; - void addDevice(String uri) throws Error.InvalidUri; + List isRegistered(List numbers) throws Error.Failure, Error.InvalidNumber; - DBusPath getDevice(long deviceId); + void addDevice(String uri) throws Error.InvalidUri; - DBusPath getIdentity(String number); + DBusPath getDevice(long deviceId); - List listIdentities(); + DBusPath getIdentity(String number); - List listDevices() throws Error.Failure; + List listIdentities(); - DBusPath getThisDevice(); + List listDevices() throws Error.Failure; - void updateProfile( - String givenName, - String familyName, - String about, - String aboutEmoji, - String avatarPath, - boolean removeAvatar - ) throws Error.Failure; + DBusPath getThisDevice(); - void updateProfile( - String name, String about, String aboutEmoji, String avatarPath, boolean removeAvatar - ) throws Error.Failure; + void updateProfile(String givenName, String familyName, String about, String aboutEmoji, String avatarPath, + boolean removeAvatar) throws Error.Failure; - void removePin(); + void updateProfile(String name, String about, String aboutEmoji, String avatarPath, boolean removeAvatar) + throws Error.Failure; - void setPin(String registrationLockPin); + void removePin(); - String version(); + void setPin(String registrationLockPin); - List listNumbers(); + String version(); - List getContactNumber(final String name) throws Error.Failure; + List listNumbers(); - @Deprecated - void quitGroup(final byte[] groupId) throws Error.GroupNotFound, Error.Failure, Error.InvalidGroupId; + List getContactNumber(final String name) throws Error.Failure; - boolean isContactBlocked(final String number) throws Error.InvalidNumber; + @Deprecated + void quitGroup(final byte[] groupId) throws Error.GroupNotFound, Error.Failure, Error.InvalidGroupId; - @Deprecated - boolean isGroupBlocked(final byte[] groupId) throws Error.InvalidGroupId; + boolean isContactBlocked(final String number) throws Error.InvalidNumber; - @Deprecated - boolean isMember(final byte[] groupId) throws Error.InvalidGroupId; + @Deprecated + boolean isGroupBlocked(final byte[] groupId) throws Error.InvalidGroupId; - byte[] joinGroup(final String groupLink) throws Error.Failure; + @Deprecated + boolean isMember(final byte[] groupId) throws Error.InvalidGroupId; - String uploadStickerPack(String stickerPackPath) throws Error.Failure; + byte[] joinGroup(final String groupLink) throws Error.Failure; - void submitRateLimitChallenge(String challenge, String captchaString) throws Error.Failure; + String uploadStickerPack(String stickerPackPath) throws Error.Failure; - void unregister() throws Error.Failure; + void submitRateLimitChallenge(String challenge, String captchaString) throws Error.Failure; - void deleteAccount() throws Error.Failure; + void unregister() throws Error.Failure; - class MessageReceivedV2 extends DBusSignal { + void deleteAccount() throws Error.Failure; - private final long timestamp; - private final String sender; - private final byte[] groupId; - private final String message; - private final Map> extras; + class MessageReceivedV2 extends DBusSignal { - public MessageReceivedV2( - String objectpath, - long timestamp, - String sender, - byte[] groupId, - String message, - final Map> extras - ) throws DBusException { - super(objectpath, timestamp, sender, groupId, message, extras); - this.timestamp = timestamp; - this.sender = sender; - this.groupId = groupId; - this.message = message; - this.extras = extras; - } - - public long getTimestamp() { - return timestamp; - } - - public String getSender() { - return sender; - } - - public byte[] getGroupId() { - return groupId; - } - - public String getMessage() { - return message; - } - - public Map> getExtras() { - return extras; - } - } - - class EditMessageReceived extends DBusSignal { - - private final long timestamp; - private final long targetSentTimestamp; - private final String sender; - private final byte[] groupId; - private final String message; - private final Map> extras; - - public EditMessageReceived( - String objectpath, - long timestamp, - final long targetSentTimestamp, - String sender, - byte[] groupId, - String message, - final Map> extras - ) throws DBusException { - super(objectpath, timestamp, targetSentTimestamp, sender, groupId, message, extras); - this.timestamp = timestamp; - this.targetSentTimestamp = targetSentTimestamp; - this.sender = sender; - this.groupId = groupId; - this.message = message; - this.extras = extras; - } - - public long getTimestamp() { - return timestamp; - } - - public long getTargetSentTimestamp() { - return targetSentTimestamp; - } - - public String getSender() { - return sender; - } - - public byte[] getGroupId() { - return groupId; - } - - public String getMessage() { - return message; - } - - public Map> getExtras() { - return extras; - } - } - - class MessageReceived extends DBusSignal { - - private final long timestamp; - private final String sender; - private final byte[] groupId; - private final String message; - private final List attachments; - - public MessageReceived( - String objectpath, - long timestamp, - String sender, - byte[] groupId, - String message, - List attachments - ) throws DBusException { - super(objectpath, timestamp, sender, groupId, message, attachments); - this.timestamp = timestamp; - this.sender = sender; - this.groupId = groupId; - this.message = message; - this.attachments = attachments; - } - - public long getTimestamp() { - return timestamp; - } - - public String getSender() { - return sender; - } - - public byte[] getGroupId() { - return groupId; - } - - public String getMessage() { - return message; - } - - public List getAttachments() { - return attachments; - } - } - - class ReceiptReceived extends DBusSignal { - - private final long timestamp; - private final String sender; - - public ReceiptReceived(String objectpath, long timestamp, String sender) throws DBusException { - super(objectpath, timestamp, sender); - this.timestamp = timestamp; - this.sender = sender; - } - - public long getTimestamp() { - return timestamp; - } - - public String getSender() { - return sender; - } - } - - class ReceiptReceivedV2 extends DBusSignal { - - private final long timestamp; - private final String sender; - private final String type; - private final Map> extras; - - public ReceiptReceivedV2( - String objectpath, - long timestamp, - String sender, - final String type, - final Map> extras - ) throws DBusException { - super(objectpath, timestamp, sender, type, extras); - this.timestamp = timestamp; - this.sender = sender; - this.type = type; - this.extras = extras; - } - - public long getTimestamp() { - return timestamp; - } - - public String getSender() { - return sender; - } - - public String getReceiptType() { - return type; - } - - public Map> getExtras() { - return extras; - } - } - - class SyncMessageReceived extends DBusSignal { - - private final long timestamp; - private final String source; - private final String destination; - private final byte[] groupId; - private final String message; - private final List attachments; - - public SyncMessageReceived( - String objectpath, - long timestamp, - String source, - String destination, - byte[] groupId, - String message, - List attachments - ) throws DBusException { - super(objectpath, timestamp, source, destination, groupId, message, attachments); - this.timestamp = timestamp; - this.source = source; - this.destination = destination; - this.groupId = groupId; - this.message = message; - this.attachments = attachments; - } - - public long getTimestamp() { - return timestamp; - } - - public String getSource() { - return source; - } - - public String getDestination() { - return destination; - } - - public byte[] getGroupId() { - return groupId; - } - - public String getMessage() { - return message; - } - - public List getAttachments() { - return attachments; - } - } - - class SyncMessageReceivedV2 extends DBusSignal { - - private final long timestamp; - private final String source; - private final String destination; - private final byte[] groupId; - private final String message; - private final Map> extras; - - public SyncMessageReceivedV2( - String objectpath, - long timestamp, - String source, - String destination, - byte[] groupId, - String message, - final Map> extras - ) throws DBusException { - super(objectpath, timestamp, source, destination, groupId, message, extras); - this.timestamp = timestamp; - this.source = source; - this.destination = destination; - this.groupId = groupId; - this.message = message; - this.extras = extras; - } - - public long getTimestamp() { - return timestamp; - } - - public String getSource() { - return source; - } - - public String getDestination() { - return destination; - } - - public byte[] getGroupId() { - return groupId; - } - - public String getMessage() { - return message; - } - - public Map> getExtras() { - return extras; - } - } - - class StructDevice extends Struct { - - @Position(0) - final DBusPath objectPath; - - @Position(1) - final Long id; - - @Position(2) - final String name; - - public StructDevice(final DBusPath objectPath, final Long id, final String name) { - this.objectPath = objectPath; - this.id = id; - this.name = name; - } - - public DBusPath getObjectPath() { - return objectPath; - } - - public Long getId() { - return id; - } - - public String getName() { - return name; - } - } - - @DBusProperty(name = "Id", type = Integer.class, access = DBusProperty.Access.READ) - @DBusProperty(name = "Name", type = String.class) - @DBusProperty(name = "Created", type = String.class, access = DBusProperty.Access.READ) - @DBusProperty(name = "LastSeen", type = String.class, access = DBusProperty.Access.READ) - interface Device extends DBusInterface, Properties { - - void removeDevice() throws Error.Failure; - } - - @DBusProperty(name = "ReadReceipts", type = Boolean.class) - @DBusProperty(name = "UnidentifiedDeliveryIndicators", type = Boolean.class) - @DBusProperty(name = "TypingIndicators", type = Boolean.class) - @DBusProperty(name = "LinkPreviews", type = Boolean.class) - interface Configuration extends DBusInterface, Properties {} - - class StructGroup extends Struct { - - @Position(0) - final DBusPath objectPath; - - @Position(1) - final byte[] id; - - @Position(2) - final String name; - - public StructGroup(final DBusPath objectPath, final byte[] id, final String name) { - this.objectPath = objectPath; - this.id = id; - this.name = name; - } - - public DBusPath getObjectPath() { - return objectPath; - } + private final long timestamp; + private final String sender; + private final byte[] groupId; + private final String message; + private final Map> extras; - public byte[] getId() { - return id; - } + public MessageReceivedV2(String objectpath, long timestamp, String sender, byte[] groupId, String message, + final Map> extras) throws DBusException { + super(objectpath, timestamp, sender, groupId, message, extras); + this.timestamp = timestamp; + this.sender = sender; + this.groupId = groupId; + this.message = message; + this.extras = extras; + } - public String getName() { - return name; - } - } + public long getTimestamp() { + return timestamp; + } - @DBusProperty(name = "Id", type = Byte[].class, access = DBusProperty.Access.READ) - @DBusProperty(name = "Name", type = String.class) - @DBusProperty(name = "Description", type = String.class) - @DBusProperty(name = "Avatar", type = String.class, access = DBusProperty.Access.WRITE) - @DBusProperty(name = "IsBlocked", type = Boolean.class) - @DBusProperty(name = "IsMember", type = Boolean.class, access = DBusProperty.Access.READ) - @DBusProperty(name = "IsAdmin", type = Boolean.class, access = DBusProperty.Access.READ) - @DBusProperty(name = "MessageExpirationTimer", type = Integer.class) - @DBusProperty(name = "Members", type = String[].class, access = DBusProperty.Access.READ) - @DBusProperty(name = "PendingMembers", type = String[].class, access = DBusProperty.Access.READ) - @DBusProperty(name = "RequestingMembers", type = String[].class, access = DBusProperty.Access.READ) - @DBusProperty(name = "Admins", type = String[].class, access = DBusProperty.Access.READ) - @DBusProperty(name = "Banned", type = String[].class, access = DBusProperty.Access.READ) - @DBusProperty(name = "PermissionAddMember", type = String.class) - @DBusProperty(name = "PermissionEditDetails", type = String.class) - @DBusProperty(name = "PermissionSendMessage", type = String.class) - @DBusProperty(name = "GroupInviteLink", type = String.class, access = DBusProperty.Access.READ) - interface Group extends DBusInterface, Properties { + public String getSender() { + return sender; + } - void quitGroup() throws Error.Failure, Error.LastGroupAdmin; + public byte[] getGroupId() { + return groupId; + } - void deleteGroup() throws Error.Failure; + public String getMessage() { + return message; + } - void addMembers(List recipients) throws Error.Failure; + public Map> getExtras() { + return extras; + } + } - void removeMembers(List recipients) throws Error.Failure; + class EditMessageReceived extends DBusSignal { - void addAdmins(List recipients) throws Error.Failure; + private final long timestamp; + private final long targetSentTimestamp; + private final String sender; + private final byte[] groupId; + private final String message; + private final Map> extras; - void removeAdmins(List recipients) throws Error.Failure; + public EditMessageReceived(String objectpath, long timestamp, final long targetSentTimestamp, String sender, + byte[] groupId, String message, final Map> extras) throws DBusException { + super(objectpath, timestamp, targetSentTimestamp, sender, groupId, message, extras); + this.timestamp = timestamp; + this.targetSentTimestamp = targetSentTimestamp; + this.sender = sender; + this.groupId = groupId; + this.message = message; + this.extras = extras; + } - void resetLink() throws Error.Failure; + public long getTimestamp() { + return timestamp; + } - void disableLink() throws Error.Failure; + public long getTargetSentTimestamp() { + return targetSentTimestamp; + } - void enableLink(boolean requiresApproval) throws Error.Failure; - } + public String getSender() { + return sender; + } - class StructIdentity extends Struct { + public byte[] getGroupId() { + return groupId; + } + + public String getMessage() { + return message; + } + + public Map> getExtras() { + return extras; + } + } + + class MessageReceived extends DBusSignal { + + private final long timestamp; + private final String sender; + private final byte[] groupId; + private final String message; + private final List attachments; + + public MessageReceived(String objectpath, long timestamp, String sender, byte[] groupId, String message, + List attachments) throws DBusException { + super(objectpath, timestamp, sender, groupId, message, attachments); + this.timestamp = timestamp; + this.sender = sender; + this.groupId = groupId; + this.message = message; + this.attachments = attachments; + } + + public long getTimestamp() { + return timestamp; + } + + public String getSender() { + return sender; + } + + public byte[] getGroupId() { + return groupId; + } + + public String getMessage() { + return message; + } + + public List getAttachments() { + return attachments; + } + } + + class ReceiptReceived extends DBusSignal { + + private final long timestamp; + private final String sender; + + public ReceiptReceived(String objectpath, long timestamp, String sender) throws DBusException { + super(objectpath, timestamp, sender); + this.timestamp = timestamp; + this.sender = sender; + } + + public long getTimestamp() { + return timestamp; + } + + public String getSender() { + return sender; + } + } + + class ReceiptReceivedV2 extends DBusSignal { + + private final long timestamp; + private final String sender; + private final String type; + private final Map> extras; + + public ReceiptReceivedV2(String objectpath, long timestamp, String sender, final String type, + final Map> extras) throws DBusException { + super(objectpath, timestamp, sender, type, extras); + this.timestamp = timestamp; + this.sender = sender; + this.type = type; + this.extras = extras; + } + + public long getTimestamp() { + return timestamp; + } + + public String getSender() { + return sender; + } + + public String getReceiptType() { + return type; + } + + public Map> getExtras() { + return extras; + } + } + + class SyncMessageReceived extends DBusSignal { + + private final long timestamp; + private final String source; + private final String destination; + private final byte[] groupId; + private final String message; + private final List attachments; + + public SyncMessageReceived(String objectpath, long timestamp, String source, String destination, byte[] groupId, + String message, List attachments) throws DBusException { + super(objectpath, timestamp, source, destination, groupId, message, attachments); + this.timestamp = timestamp; + this.source = source; + this.destination = destination; + this.groupId = groupId; + this.message = message; + this.attachments = attachments; + } + + public long getTimestamp() { + return timestamp; + } + + public String getSource() { + return source; + } + + public String getDestination() { + return destination; + } + + public byte[] getGroupId() { + return groupId; + } + + public String getMessage() { + return message; + } + + public List getAttachments() { + return attachments; + } + } + + class SyncMessageReceivedV2 extends DBusSignal { + + private final long timestamp; + private final String source; + private final String destination; + private final byte[] groupId; + private final String message; + private final Map> extras; + + public SyncMessageReceivedV2(String objectpath, long timestamp, String source, String destination, + byte[] groupId, String message, final Map> extras) throws DBusException { + super(objectpath, timestamp, source, destination, groupId, message, extras); + this.timestamp = timestamp; + this.source = source; + this.destination = destination; + this.groupId = groupId; + this.message = message; + this.extras = extras; + } + + public long getTimestamp() { + return timestamp; + } + + public String getSource() { + return source; + } + + public String getDestination() { + return destination; + } + + public byte[] getGroupId() { + return groupId; + } + + public String getMessage() { + return message; + } + + public Map> getExtras() { + return extras; + } + } + + class StructDevice extends Struct { + + @Position(0) + final DBusPath objectPath; + + @Position(1) + final Long id; + + @Position(2) + final String name; + + public StructDevice(final DBusPath objectPath, final Long id, final String name) { + this.objectPath = objectPath; + this.id = id; + this.name = name; + } + + public DBusPath getObjectPath() { + return objectPath; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + } + + @DBusProperty(name = "Id", type = Integer.class, access = DBusProperty.Access.READ) + @DBusProperty(name = "Name", type = String.class) + @DBusProperty(name = "Created", type = String.class, access = DBusProperty.Access.READ) + @DBusProperty(name = "LastSeen", type = String.class, access = DBusProperty.Access.READ) + interface Device extends DBusInterface, Properties { + + void removeDevice() throws Error.Failure; + } + + @DBusProperty(name = "ReadReceipts", type = Boolean.class) + @DBusProperty(name = "UnidentifiedDeliveryIndicators", type = Boolean.class) + @DBusProperty(name = "TypingIndicators", type = Boolean.class) + @DBusProperty(name = "LinkPreviews", type = Boolean.class) + interface Configuration extends DBusInterface, Properties { + } + + class StructGroup extends Struct { + + @Position(0) + final DBusPath objectPath; + + @Position(1) + final byte[] id; + + @Position(2) + final String name; + + public StructGroup(final DBusPath objectPath, final byte[] id, final String name) { + this.objectPath = objectPath; + this.id = id; + this.name = name; + } - @Position(0) - final DBusPath objectPath; + public DBusPath getObjectPath() { + return objectPath; + } - @Position(1) - final String uuid; + public byte[] getId() { + return id; + } - @Position(2) - final String number; + public String getName() { + return name; + } + } - public StructIdentity(final DBusPath objectPath, final String uuid, final String number) { - this.objectPath = objectPath; - this.uuid = uuid; - this.number = number; - } + @DBusProperty(name = "Id", type = Byte[].class, access = DBusProperty.Access.READ) + @DBusProperty(name = "Name", type = String.class) + @DBusProperty(name = "Description", type = String.class) + @DBusProperty(name = "Avatar", type = String.class, access = DBusProperty.Access.WRITE) + @DBusProperty(name = "IsBlocked", type = Boolean.class) + @DBusProperty(name = "IsMember", type = Boolean.class, access = DBusProperty.Access.READ) + @DBusProperty(name = "IsAdmin", type = Boolean.class, access = DBusProperty.Access.READ) + @DBusProperty(name = "MessageExpirationTimer", type = Integer.class) + @DBusProperty(name = "Members", type = String[].class, access = DBusProperty.Access.READ) + @DBusProperty(name = "PendingMembers", type = String[].class, access = DBusProperty.Access.READ) + @DBusProperty(name = "RequestingMembers", type = String[].class, access = DBusProperty.Access.READ) + @DBusProperty(name = "Admins", type = String[].class, access = DBusProperty.Access.READ) + @DBusProperty(name = "Banned", type = String[].class, access = DBusProperty.Access.READ) + @DBusProperty(name = "PermissionAddMember", type = String.class) + @DBusProperty(name = "PermissionEditDetails", type = String.class) + @DBusProperty(name = "PermissionSendMessage", type = String.class) + @DBusProperty(name = "GroupInviteLink", type = String.class, access = DBusProperty.Access.READ) + interface Group extends DBusInterface, Properties { - public DBusPath getObjectPath() { - return objectPath; - } + void quitGroup() throws Error.Failure, Error.LastGroupAdmin; - public String getUuid() { - return uuid; - } + void deleteGroup() throws Error.Failure; - public String getNumber() { - return number; - } - } + void addMembers(List recipients) throws Error.Failure; - @DBusProperty(name = "Number", type = String.class, access = DBusProperty.Access.READ) - @DBusProperty(name = "Uuid", type = String.class, access = DBusProperty.Access.READ) - @DBusProperty(name = "Fingerprint", type = Byte[].class, access = DBusProperty.Access.READ) - @DBusProperty(name = "SafetyNumber", type = String.class, access = DBusProperty.Access.READ) - @DBusProperty(name = "TrustLevel", type = String.class, access = DBusProperty.Access.READ) - @DBusProperty(name = "AddedDate", type = Long.class, access = DBusProperty.Access.READ) - @DBusProperty(name = "ScannableSafetyNumber", type = Byte[].class, access = DBusProperty.Access.READ) - interface Identity extends DBusInterface, Properties { + void removeMembers(List recipients) throws Error.Failure; - void trust() throws Error.Failure; + void addAdmins(List recipients) throws Error.Failure; - void trustVerified(String safetyNumber) throws Error.Failure; - } + void removeAdmins(List recipients) throws Error.Failure; - interface Error { + void resetLink() throws Error.Failure; - class AttachmentInvalid extends DBusExecutionException { + void disableLink() throws Error.Failure; - public AttachmentInvalid(final String message) { - super("Invalid attachment: " + message); - } - } + void enableLink(boolean requiresApproval) throws Error.Failure; + } - class InvalidUri extends DBusExecutionException { + class StructIdentity extends Struct { - public InvalidUri(final String message) { - super("Invalid uri: " + message); - } - } + @Position(0) + final DBusPath objectPath; - class Failure extends DBusExecutionException { + @Position(1) + final String uuid; - public Failure(final Exception e) { - super("Failure: " + e.getMessage() + " (" + e.getClass().getSimpleName() + ")"); - } + @Position(2) + final String number; - public Failure(final String message) { - super("Failure: " + message); - } - } + public StructIdentity(final DBusPath objectPath, final String uuid, final String number) { + this.objectPath = objectPath; + this.uuid = uuid; + this.number = number; + } - class DeviceNotFound extends DBusExecutionException { + public DBusPath getObjectPath() { + return objectPath; + } - public DeviceNotFound(final String message) { - super("Device not found: " + message); - } - } + public String getUuid() { + return uuid; + } - class GroupNotFound extends DBusExecutionException { + public String getNumber() { + return number; + } + } - public GroupNotFound(final String message) { - super("Group not found: " + message); - } - } + @DBusProperty(name = "Number", type = String.class, access = DBusProperty.Access.READ) + @DBusProperty(name = "Uuid", type = String.class, access = DBusProperty.Access.READ) + @DBusProperty(name = "Fingerprint", type = Byte[].class, access = DBusProperty.Access.READ) + @DBusProperty(name = "SafetyNumber", type = String.class, access = DBusProperty.Access.READ) + @DBusProperty(name = "TrustLevel", type = String.class, access = DBusProperty.Access.READ) + @DBusProperty(name = "AddedDate", type = Long.class, access = DBusProperty.Access.READ) + @DBusProperty(name = "ScannableSafetyNumber", type = Byte[].class, access = DBusProperty.Access.READ) + interface Identity extends DBusInterface, Properties { - class NotAGroupMember extends DBusExecutionException { + void trust() throws Error.Failure; - public NotAGroupMember(final String message) { - super("Not a group member: " + message); - } - } + void trustVerified(String safetyNumber) throws Error.Failure; + } - class InvalidGroupId extends DBusExecutionException { + interface Error { - public InvalidGroupId(final String message) { - super("Invalid group id: " + message); - } - } + class AttachmentInvalid extends DBusExecutionException { - class LastGroupAdmin extends DBusExecutionException { + public AttachmentInvalid(final String message) { + super("Invalid attachment: " + message); + } + } - public LastGroupAdmin(final String message) { - super("Last group admin: " + message); - } - } + class InvalidUri extends DBusExecutionException { - class InvalidNumber extends DBusExecutionException { + public InvalidUri(final String message) { + super("Invalid uri: " + message); + } + } - public InvalidNumber(final String message) { - super("Invalid number: " + message); - } - } + class Failure extends DBusExecutionException { - class UntrustedIdentity extends DBusExecutionException { + public Failure(final Exception e) { + super("Failure: " + e.getMessage() + " (" + e.getClass().getSimpleName() + ")"); + } - public UntrustedIdentity(final String message) { - super("Untrusted identity: " + message); - } - } + public Failure(final String message) { + super("Failure: " + message); + } + } - class UnregisteredRecipient extends DBusExecutionException { + class DeviceNotFound extends DBusExecutionException { + + public DeviceNotFound(final String message) { + super("Device not found: " + message); + } + } + + class GroupNotFound extends DBusExecutionException { + + public GroupNotFound(final String message) { + super("Group not found: " + message); + } + } + + class NotAGroupMember extends DBusExecutionException { + + public NotAGroupMember(final String message) { + super("Not a group member: " + message); + } + } + + class InvalidGroupId extends DBusExecutionException { + + public InvalidGroupId(final String message) { + super("Invalid group id: " + message); + } + } + + class LastGroupAdmin extends DBusExecutionException { + + public LastGroupAdmin(final String message) { + super("Last group admin: " + message); + } + } + + class InvalidNumber extends DBusExecutionException { + + public InvalidNumber(final String message) { + super("Invalid number: " + message); + } + } + + class UntrustedIdentity extends DBusExecutionException { + + public UntrustedIdentity(final String message) { + super("Untrusted identity: " + message); + } + } + + class UnregisteredRecipient extends DBusExecutionException { + + public UnregisteredRecipient(final String message) { + super("Unregistered recipient: " + message); + } + } + } - public UnregisteredRecipient(final String message) { - super("Unregistered recipient: " + message); - } - } - } } diff --git a/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java b/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java index 9ae3001a6c..0397659d2e 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java +++ b/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java @@ -420,6 +420,16 @@ public SendMessageResults sendMessage( groupId -> signal.sendGroupMessage(message.messageText(), message.attachments(), groupId)); } + @Override + public SendMessageResults sendStoryMessage( + final Message message, final Set recipients, final boolean notifySelf + ) throws IOException, AttachmentInvalidException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException { + return handleMessage(recipients, + numbers -> signal.sendMessage(message.messageText(), message.attachments(), numbers), + () -> signal.sendNoteToSelfMessage(message.messageText(), message.attachments()), + groupId -> signal.sendStoryMessage(message.messageText(), message.attachments(), groupId)); + } + @Override public SendMessageResults sendEditMessage( final Message message, final Set recipients, final long editTargetTimestamp diff --git a/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java b/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java index 1b8b782555..13e8550890 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java +++ b/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java @@ -459,6 +459,31 @@ public long sendGroupMessage(final String messageText, final List attach } } + @Override + public long sendStoryMessage(final String messageText, final List attachments, final byte[] groupId) { + try { + final var message = new Message(messageText, + attachments, + List.of(), + Optional.empty(), + Optional.empty(), + List.of(), + Optional.empty(), + List.of()); + var results = m.sendStoryMessage(message, Set.of(getGroupRecipientIdentifier(groupId)), false); + checkSendMessageResults(results); + return results.timestamp(); + } catch (IOException | InvalidStickerException e) { + throw new Error.Failure(e.getMessage()); + } catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) { + throw new Error.GroupNotFound(e.getMessage()); + } catch (AttachmentInvalidException e) { + throw new Error.AttachmentInvalid(e.getMessage()); + } catch (UnregisteredRecipientException e) { + throw new Error.UntrustedIdentity(e.getSender().getIdentifier() + " is not registered."); + } + } + @Override public void sendGroupTyping( final byte[] groupId, final boolean stop From 02e58f00105d92f7382afeeacd7194a41aa25350 Mon Sep 17 00:00:00 2001 From: Scott Lewis Date: Wed, 5 Feb 2025 16:55:45 -0800 Subject: [PATCH 2/4] Merge --- build.gradle.kts | 2 +- lib/build.gradle.kts | 2 +- .../org/asamk/signal/manager/Manager.java | 7 +- .../signal/manager/helper/SendHelper.java | 105 +++++++++++++++++- .../signal/manager/internal/ManagerImpl.java | 84 ++++++++------ src/main/java/org/asamk/Signal.java | 4 + .../asamk/signal/commands/SendCommand.java | 5 +- .../asamk/signal/dbus/DbusManagerImpl.java | 5 +- .../org/asamk/signal/dbus/DbusSignalImpl.java | 2 +- 9 files changed, 172 insertions(+), 44 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index f51d9f1ce8..8878b9c20a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -37,7 +37,7 @@ graalvmNative { if (System.getenv("GRAALVM_HOME") == null) { toolchainDetection.set(true) javaLauncher.set(javaToolchains.launcherFor { - languageVersion.set(JavaLanguageVersion.of(21)) + languageVersion.set(JavaLanguageVersion.of(22)) }) } else { toolchainDetection.set(false) diff --git a/lib/build.gradle.kts b/lib/build.gradle.kts index c8f22f7cee..080912018e 100644 --- a/lib/build.gradle.kts +++ b/lib/build.gradle.kts @@ -8,7 +8,7 @@ java { targetCompatibility = JavaVersion.VERSION_21 toolchain { - languageVersion.set(JavaLanguageVersion.of(21)) + languageVersion.set(JavaLanguageVersion.of(22)) } } diff --git a/lib/src/main/java/org/asamk/signal/manager/Manager.java b/lib/src/main/java/org/asamk/signal/manager/Manager.java index 1f1b790051..7d390bf067 100644 --- a/lib/src/main/java/org/asamk/signal/manager/Manager.java +++ b/lib/src/main/java/org/asamk/signal/manager/Manager.java @@ -197,10 +197,6 @@ SendMessageResults sendMessage( boolean notifySelf ) throws IOException, AttachmentInvalidException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException, UnregisteredRecipientException, InvalidStickerException; - SendMessageResults sendStoryMessage( - Message message, Set recipients, boolean notifySelf - ) throws IOException, AttachmentInvalidException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException, UnregisteredRecipientException, InvalidStickerException; - SendMessageResults sendEditMessage( Message message, Set recipients, @@ -372,4 +368,7 @@ interface ReceiveMessageHandler { void handleMessage(MessageEnvelope envelope, Throwable e); } + + SendMessageResults sendStoryMessage(Message message, RecipientIdentifier.Group recipient) + throws IOException, AttachmentInvalidException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException, UnregisteredRecipientException, InvalidStickerException; } diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/SendHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/SendHelper.java index 2fb550032d..a42975897c 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/SendHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/SendHelper.java @@ -31,6 +31,8 @@ import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; import org.whispersystems.signalservice.api.messages.SignalServiceEditMessage; import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage; +import org.whispersystems.signalservice.api.messages.SignalServiceStoryMessage; +import org.whispersystems.signalservice.api.messages.SignalServiceStoryMessageRecipient; import org.whispersystems.signalservice.api.messages.SignalServiceTypingMessage; import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage; import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage; @@ -53,6 +55,7 @@ import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Collectors; import okio.ByteString; @@ -331,6 +334,66 @@ private List sendAsGroupMessage( editTargetTimestamp); } + public List sendGroupStoryMessage( + final SignalServiceStoryMessage message, + final GroupInfo g + ) throws IOException { + final var messageSender = dependencies.getMessageSender(); + final var messageSendLogStore = account.getMessageSendLogStore(); + final AtomicLong entryId = new AtomicLong(-1); + final boolean urgent = true; + final long timestamp = System.currentTimeMillis(); + // remove sender/self + final Set recipientIds = g.getMembersWithout(account.getSelfRecipientId()); + final List distributionListIds = List.of(g.getGroupId().toBase64()); + final Set messageRecipients = recipientIds.stream().map(i -> { + SignalServiceAddress ssa = context.getRecipientHelper().resolveSignalServiceAddress(i); + return new SignalServiceStoryMessageRecipient(ssa, distributionListIds, true); + }).collect(Collectors.toSet()); + final SenderKeySenderHandler senderKeySender = (distId, recipients, unidentifiedAccess, groupSendEndorsements, isRecipientUpdate) -> messageSender.sendGroupStory( + g.getDistributionId(), + Optional.of(g.getGroupId().serialize()), + recipients, + unidentifiedAccess, + groupSendEndorsements, + true, + message, + timestamp, + messageRecipients, + sendResult -> { + logger.trace("Partial message send results: {}", sendResult.size()); + synchronized (entryId) { + if (entryId.get() == -1) { + final var newId = messageSendLogStore.insertIfPossible(timestamp, + sendResult, + ContentHint.RESENDABLE, + urgent); + entryId.set(newId); + } else { + messageSendLogStore.addRecipientToExistingEntryIfPossible(entryId.get(), sendResult); + } + } + synchronized (entryId) { + if (entryId.get() == -1) { + final var newId = messageSendLogStore.insertIfPossible(timestamp, + sendResult, + ContentHint.RESENDABLE, + urgent); + entryId.set(newId); + } else { + messageSendLogStore.addRecipientToExistingEntryIfPossible(entryId.get(), sendResult); + } + } + }); + final var results = sendStoryMessageInternal(senderKeySender, recipientIds, g.getDistributionId()); + + for (var r : results) { + handleSendMessageResult(r); + } + + return results; + } + private List sendGroupMessage( final SignalServiceDataMessage message, final Set recipientIds, @@ -466,6 +529,44 @@ private GroupInfo getGroupForSending(GroupId groupId) throws GroupNotFoundExcept return g; } + private List sendStoryMessageInternal( + final SenderKeySenderHandler senderKeySender, + final Set recipientIds, + final DistributionId distributionId + ) throws IOException { + long startTime = System.currentTimeMillis(); + Set senderKeyTargets = distributionId == null + ? Set.of() + : getSenderKeyCapableRecipientIds(recipientIds); + final var allResults = new ArrayList(); + + if (!senderKeyTargets.isEmpty()) { + final var results = sendGroupMessageInternalWithSenderKey(senderKeySender, + senderKeyTargets, + distributionId, + false); + + if (results == null) { + senderKeyTargets = Set.of(); + } else { + results.stream().filter(SendMessageResult::isSuccess).forEach(allResults::add); + final var recipientResolver = account.getRecipientResolver(); + final var failedTargets = results.stream() + .filter(r -> !r.isSuccess()) + .map(r -> recipientResolver.resolveRecipient(r.getAddress())) + .toList(); + if (!failedTargets.isEmpty()) { + senderKeyTargets = new HashSet<>(senderKeyTargets); + failedTargets.forEach(senderKeyTargets::remove); + } + } + } + + final var duration = Duration.ofMillis(System.currentTimeMillis() - startTime); + logger.debug("Sending took {}", duration.toString()); + return allResults; + } + private List sendGroupMessageInternal( final LegacySenderHandler legacySender, final SenderKeySenderHandler senderKeySender, @@ -546,12 +647,12 @@ private Set getSenderKeyCapableRecipientIds(final Set senderKeyTargets.add(recipientId); } - +/* if (senderKeyTargets.size() < 2) { logger.debug("Too few sender-key-capable users ({}). Doing all legacy sends.", senderKeyTargets.size()); return Set.of(); } - +*/ logger.debug("Can use sender key for {}/{} recipients.", senderKeyTargets.size(), recipientIds.size()); return senderKeyTargets; } diff --git a/lib/src/main/java/org/asamk/signal/manager/internal/ManagerImpl.java b/lib/src/main/java/org/asamk/signal/manager/internal/ManagerImpl.java index 06eb0e4c9c..f178b3a854 100644 --- a/lib/src/main/java/org/asamk/signal/manager/internal/ManagerImpl.java +++ b/lib/src/main/java/org/asamk/signal/manager/internal/ManagerImpl.java @@ -16,6 +16,34 @@ */ package org.asamk.signal.manager.internal; +import static org.asamk.signal.manager.config.ServiceConfig.MAX_MESSAGE_SIZE_BYTES; +import static org.signal.core.util.StringExtensionsKt.splitByByteLength; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + import org.asamk.signal.manager.Manager; import org.asamk.signal.manager.api.AlreadyReceivingException; import org.asamk.signal.manager.api.AttachmentInvalidException; @@ -78,6 +106,7 @@ import org.asamk.signal.manager.storage.AvatarStore; import org.asamk.signal.manager.storage.SignalAccount; import org.asamk.signal.manager.storage.groups.GroupInfo; +import org.asamk.signal.manager.storage.groups.GroupInfoV2; import org.asamk.signal.manager.storage.identities.IdentityInfo; import org.asamk.signal.manager.storage.recipients.RecipientAddress; import org.asamk.signal.manager.storage.recipients.RecipientId; @@ -95,9 +124,10 @@ import org.whispersystems.signalservice.api.SignalSessionLock; import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; -import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage.StoryContext; +import org.whispersystems.signalservice.api.messages.SignalServiceGroupV2; import org.whispersystems.signalservice.api.messages.SignalServicePreview; import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage; +import org.whispersystems.signalservice.api.messages.SignalServiceStoryMessage; import org.whispersystems.signalservice.api.messages.SignalServiceTypingMessage; import org.whispersystems.signalservice.api.push.ServiceId; import org.whispersystems.signalservice.api.push.ServiceId.ACI; @@ -110,34 +140,10 @@ import org.whispersystems.signalservice.api.util.InvalidNumberException; import org.whispersystems.signalservice.api.util.PhoneNumberFormatter; import org.whispersystems.signalservice.api.util.StreamDetails; +import org.whispersystems.signalservice.internal.push.BodyRange; import org.whispersystems.signalservice.internal.util.Hex; import org.whispersystems.signalservice.internal.util.Util; -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; -import java.time.Duration; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.locks.ReentrantLock; -import java.util.function.Function; -import java.util.stream.Collectors; -import java.util.stream.Stream; - import io.reactivex.rxjava3.disposables.CompositeDisposable; import io.reactivex.rxjava3.schedulers.Schedulers; import okio.Utf8; @@ -752,18 +758,31 @@ public SendMessageResults sendMessage( } @Override - public SendMessageResults sendStoryMessage( - Message message, Set recipients, boolean notifySelf + public SendMessageResults sendStoryMessage(Message message, RecipientIdentifier.Group idGroup ) throws IOException, AttachmentInvalidException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException, UnregisteredRecipientException, InvalidStickerException { final var selfProfile = context.getProfileHelper().getSelfProfile(); if (selfProfile == null || selfProfile.getDisplayName().isEmpty()) { logger.warn( "No profile name set. When sending a message it's recommended to set a profile name with the updateProfile command. This may become mandatory in the future."); } - final var messageBuilder = SignalServiceDataMessage.newBuilder(); - applyMessage(messageBuilder, message); - messageBuilder.withStoryContext(new StoryContext(account.getSelfAddress().getServiceId(), System.currentTimeMillis())); - return sendMessage(messageBuilder, recipients, notifySelf); + final var profileKey = account.getProfileKey().serialize(); + GroupInfoV2 groupInfo = (GroupInfoV2) context.getGroupHelper().getGroup(idGroup.groupId()); + List attachments = message.attachments(); + List bodyRanges = message.textStyles().stream().map(t -> t.toBodyRange()).toList(); + + SignalServiceStoryMessage storyMessage = null; + if (attachments != null && attachments.size() > 0) { + SignalServiceAttachment.Builder attachmentBuilder = new SignalServiceAttachment.Builder().withFileName(attachments.get(0)); + storyMessage = SignalServiceStoryMessage.forFileAttachment(profileKey, null, attachmentBuilder.build(), true, bodyRanges); + } else { + //SignalServiceTextAttachment textBuilder = new SignalServiceTextAttachment. + //storyMessage = SignalServiceStoryMessage.forTextAttachment(profileKey, ssgroup, textBuilder.build(), true, bodyRanges); + } + var results = new HashMap>(); + long timestamp = System.currentTimeMillis(); + final var result = context.getSendHelper().sendGroupStoryMessage(storyMessage, groupInfo); + results.put(idGroup, result.stream().map(this::toSendMessageResult).toList()); + return new SendMessageResults(timestamp, results); } @Override @@ -1625,4 +1644,5 @@ public void close() { account = null; } + } diff --git a/src/main/java/org/asamk/Signal.java b/src/main/java/org/asamk/Signal.java index ac1f6d509b..b74b8383f7 100644 --- a/src/main/java/org/asamk/Signal.java +++ b/src/main/java/org/asamk/Signal.java @@ -754,4 +754,8 @@ public UnregisteredRecipient(final String message) { } } } + + long sendStoryMessage(String messageText, List attachments, byte[] groupId); + + //long sendStoryMessage(String messageText, List attachments, byte[] groupId); } diff --git a/src/main/java/org/asamk/signal/commands/SendCommand.java b/src/main/java/org/asamk/signal/commands/SendCommand.java index 9784171aef..a669596ed8 100644 --- a/src/main/java/org/asamk/signal/commands/SendCommand.java +++ b/src/main/java/org/asamk/signal/commands/SendCommand.java @@ -233,6 +233,8 @@ public void handleCommand( final var editTimestamp = ns.getLong("edit-timestamp"); + final boolean story = false; + try { final var message = new Message(messageText, attachments, @@ -244,7 +246,8 @@ public void handleCommand( textStyles); var results = editTimestamp != null ? m.sendEditMessage(message, recipientIdentifiers, editTimestamp) - : m.sendMessage(message, recipientIdentifiers, notifySelf); + //: story ? // m.sendStoryMessage(message, recipientIdentifiers, notifySelf) + : m.sendMessage(message, recipientIdentifiers, notifySelf); outputResult(outputWriter, results); } catch (AttachmentInvalidException | IOException e) { throw new UnexpectedErrorException("Failed to send message: " + e.getMessage() + " (" + e.getClass() diff --git a/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java b/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java index a2f34a3cb9..9c24f8025f 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java +++ b/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java @@ -429,9 +429,9 @@ public SendMessageResults sendMessage( @Override public SendMessageResults sendStoryMessage( - final Message message, final Set recipients, final boolean notifySelf + final Message message, final RecipientIdentifier.Group recipient ) throws IOException, AttachmentInvalidException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException { - return handleMessage(recipients, + return handleMessage(Set.of(recipient), numbers -> signal.sendMessage(message.messageText(), message.attachments(), numbers), () -> signal.sendNoteToSelfMessage(message.messageText(), message.attachments()), groupId -> signal.sendStoryMessage(message.messageText(), message.attachments(), groupId)); @@ -1160,4 +1160,5 @@ public InputStream retrieveSticker(final StickerPackId stickerPackId, final int private T getValue(final Map> stringVariantMap, final String field) { return (T) stringVariantMap.get(field).getValue(); } + } diff --git a/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java b/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java index fd3bd72830..0b289f019a 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java +++ b/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java @@ -475,7 +475,7 @@ public long sendStoryMessage(final String messageText, final List attach List.of(), Optional.empty(), List.of()); - var results = m.sendStoryMessage(message, Set.of(getGroupRecipientIdentifier(groupId)), false); + var results = m.sendStoryMessage(message,getGroupRecipientIdentifier(groupId)); checkSendMessageResults(results); return results.timestamp(); } catch (IOException | InvalidStickerException e) { From 2803976ee590f2f709b58f4937f3759647103e78 Mon Sep 17 00:00:00 2001 From: Scott Lewis Date: Mon, 17 Feb 2025 16:15:45 -0800 Subject: [PATCH 3/4] Impl of sendStoryMessage Signed-off-by: Scott Lewis --- .../asamk/signal/manager/internal/ManagerImpl.java | 10 +++------- src/main/java/org/asamk/Signal.java | 2 -- .../java/org/asamk/signal/commands/SendCommand.java | 11 ++++++++--- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/lib/src/main/java/org/asamk/signal/manager/internal/ManagerImpl.java b/lib/src/main/java/org/asamk/signal/manager/internal/ManagerImpl.java index f178b3a854..e7874fad95 100644 --- a/lib/src/main/java/org/asamk/signal/manager/internal/ManagerImpl.java +++ b/lib/src/main/java/org/asamk/signal/manager/internal/ManagerImpl.java @@ -124,7 +124,6 @@ import org.whispersystems.signalservice.api.SignalSessionLock; import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; -import org.whispersystems.signalservice.api.messages.SignalServiceGroupV2; import org.whispersystems.signalservice.api.messages.SignalServicePreview; import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage; import org.whispersystems.signalservice.api.messages.SignalServiceStoryMessage; @@ -148,9 +147,6 @@ import io.reactivex.rxjava3.schedulers.Schedulers; import okio.Utf8; -import static org.asamk.signal.manager.config.ServiceConfig.MAX_MESSAGE_SIZE_BYTES; -import static org.signal.core.util.StringExtensionsKt.splitByByteLength; - public class ManagerImpl implements Manager { private static final Logger logger = LoggerFactory.getLogger(ManagerImpl.class); @@ -757,6 +753,7 @@ public SendMessageResults sendMessage( return sendMessage(messageBuilder, recipients, notifySelf); } + @Override public SendMessageResults sendStoryMessage(Message message, RecipientIdentifier.Group idGroup ) throws IOException, AttachmentInvalidException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException, UnregisteredRecipientException, InvalidStickerException { @@ -772,8 +769,8 @@ public SendMessageResults sendStoryMessage(Message message, RecipientIdentifier. SignalServiceStoryMessage storyMessage = null; if (attachments != null && attachments.size() > 0) { - SignalServiceAttachment.Builder attachmentBuilder = new SignalServiceAttachment.Builder().withFileName(attachments.get(0)); - storyMessage = SignalServiceStoryMessage.forFileAttachment(profileKey, null, attachmentBuilder.build(), true, bodyRanges); + var attachment = context.getAttachmentHelper().uploadAttachment(attachments.get(0)); + storyMessage = SignalServiceStoryMessage.forFileAttachment(profileKey, null, attachment, true, bodyRanges); } else { //SignalServiceTextAttachment textBuilder = new SignalServiceTextAttachment. //storyMessage = SignalServiceStoryMessage.forTextAttachment(profileKey, ssgroup, textBuilder.build(), true, bodyRanges); @@ -1644,5 +1641,4 @@ public void close() { account = null; } - } diff --git a/src/main/java/org/asamk/Signal.java b/src/main/java/org/asamk/Signal.java index b74b8383f7..b896f90b0c 100644 --- a/src/main/java/org/asamk/Signal.java +++ b/src/main/java/org/asamk/Signal.java @@ -756,6 +756,4 @@ public UnregisteredRecipient(final String message) { } long sendStoryMessage(String messageText, List attachments, byte[] groupId); - - //long sendStoryMessage(String messageText, List attachments, byte[] groupId); } diff --git a/src/main/java/org/asamk/signal/commands/SendCommand.java b/src/main/java/org/asamk/signal/commands/SendCommand.java index a669596ed8..13ffcead1e 100644 --- a/src/main/java/org/asamk/signal/commands/SendCommand.java +++ b/src/main/java/org/asamk/signal/commands/SendCommand.java @@ -124,6 +124,13 @@ public void handleCommand( groupIdStrings, usernameStrings); + boolean isStory = false; + + if (recipientIdentifiers.size() > 0 + && recipientIdentifiers.iterator().next() instanceof RecipientIdentifier.Group) { + isStory = Boolean.TRUE.equals(ns.getBoolean("story")); + } + final var isEndSession = Boolean.TRUE.equals(ns.getBoolean("end-session")); if (isEndSession) { final var singleRecipients = recipientIdentifiers.stream() @@ -233,8 +240,6 @@ public void handleCommand( final var editTimestamp = ns.getLong("edit-timestamp"); - final boolean story = false; - try { final var message = new Message(messageText, attachments, @@ -246,7 +251,7 @@ public void handleCommand( textStyles); var results = editTimestamp != null ? m.sendEditMessage(message, recipientIdentifiers, editTimestamp) - //: story ? // m.sendStoryMessage(message, recipientIdentifiers, notifySelf) + : isStory ? m.sendStoryMessage(message, (RecipientIdentifier.Group) recipientIdentifiers.iterator().next()) : m.sendMessage(message, recipientIdentifiers, notifySelf); outputResult(outputWriter, results); } catch (AttachmentInvalidException | IOException e) { From 66562822d22e532c697f6365eb7210aaffba64b8 Mon Sep 17 00:00:00 2001 From: Scott Lewis Date: Mon, 17 Feb 2025 16:45:12 -0800 Subject: [PATCH 4/4] Changed back to java 21 --- build.gradle.kts | 2 +- lib/build.gradle.kts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index eb6d232016..3f728938d9 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -37,7 +37,7 @@ graalvmNative { if (System.getenv("GRAALVM_HOME") == null) { toolchainDetection.set(true) javaLauncher.set(javaToolchains.launcherFor { - languageVersion.set(JavaLanguageVersion.of(22)) + languageVersion.set(JavaLanguageVersion.of(21)) }) } else { toolchainDetection.set(false) diff --git a/lib/build.gradle.kts b/lib/build.gradle.kts index 080912018e..c8f22f7cee 100644 --- a/lib/build.gradle.kts +++ b/lib/build.gradle.kts @@ -8,7 +8,7 @@ java { targetCompatibility = JavaVersion.VERSION_21 toolchain { - languageVersion.set(JavaLanguageVersion.of(22)) + languageVersion.set(JavaLanguageVersion.of(21)) } }