Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: pin messages #505

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 39 additions & 4 deletions src/main/java/it/auties/whatsapp/api/Whatsapp.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,7 @@
import it.auties.whatsapp.model.message.model.reserved.ExtendedMediaMessage;
import it.auties.whatsapp.model.message.server.ProtocolMessage;
import it.auties.whatsapp.model.message.server.ProtocolMessageBuilder;
import it.auties.whatsapp.model.message.standard.CallMessageBuilder;
import it.auties.whatsapp.model.message.standard.NewsletterAdminInviteMessageBuilder;
import it.auties.whatsapp.model.message.standard.ReactionMessageBuilder;
import it.auties.whatsapp.model.message.standard.TextMessage;
import it.auties.whatsapp.model.message.standard.*;
import it.auties.whatsapp.model.newsletter.*;
import it.auties.whatsapp.model.node.Attributes;
import it.auties.whatsapp.model.node.Node;
Expand Down Expand Up @@ -674,6 +671,44 @@ public <T extends MessageInfo> CompletableFuture<T> editMessage(T oldMessage, Me
};
}

/**
* Pin a message
*
* @param messageKey non-null message key to pin
* @param pinTimer the default timer that message will be pinned
* @return a CompletableFuture
*/
public CompletableFuture<Void> pinMessage(ChatMessageKey messageKey, ChatMessagePinTimer pinTimer) {
if (messageKey.fromMe()) {
messageKey.setSenderJid(null);
}
var message = new PinInChatMessageBuilder()
.key(messageKey)
.pinType(PinInChatMessage.Type.PIN_FOR_ALL)
.senderTimestampMilliseconds(Clock.nowMilliseconds())
.build();
var deviceInfo = new DeviceContextInfoBuilder()
.messageAddOnDurationInSecs(pinTimer.periodSeconds())
.build();
var sender = messageKey.chatJid().hasServer(JidServer.GROUP) ? jidOrThrowError() : null;
var key = new ChatMessageKeyBuilder()
.id(ChatMessageKey.randomIdV2(sender, store().clientType()))
.chatJid(messageKey.chatJid())
.fromMe(true)
.senderJid(sender)
.build();
var pinInfo = new ChatMessageInfoBuilder()
.status(MessageStatus.PENDING)
.senderJid(sender)
.key(key)
.message(MessageContainer.of(message).withDeviceInfo(deviceInfo))
.timestampSeconds(Clock.nowSeconds())
.build();
var attrs = Map.of("edit", 2);
var request = new MessageSendRequest.Chat(pinInfo, null, false, false, attrs);
return socketHandler.sendMessage(request);
}

public CompletableFuture<ChatMessageInfo> sendStatus(String message) {
return sendStatus(MessageContainer.of(message));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package it.auties.whatsapp.model.chat;

import it.auties.protobuf.annotation.ProtobufConverter;
import it.auties.protobuf.annotation.ProtobufEnumIndex;
import it.auties.protobuf.model.ProtobufEnum;

import java.time.Duration;
import java.util.Arrays;

/**
* Enum representing the ChatMessagePinTimer period. Each constant is associated with a specific
* duration period.
*/
public enum ChatMessagePinTimer implements ProtobufEnum {
/**
* ChatMessagePinTimer with duration of 0 days.
*/
OFF(0, Duration.ofDays(0)),

/**
* ChatMessagePinTimer with duration of 1 day.
*/
ONE_DAY(1, Duration.ofDays(1)),

/**
* ChatMessagePinTimer with duration of 7 days.
*/
ONE_WEEK(2, Duration.ofDays(7)),

/**
* ChatMessagePinTimer with duration of 30 days.
*/
ONE_MONTH(3, Duration.ofDays(30));

private final Duration period;
final int index;

ChatMessagePinTimer(@ProtobufEnumIndex int index, Duration period) {
this.index = index;
this.period = period;
}

public int index() {
return index;
}

public Duration period() {
return period;
}

@ProtobufConverter
public static ChatMessagePinTimer of(int value) {
return Arrays.stream(values())
.filter(entry -> entry.period().toSeconds() == value || entry.period().toDays() == value)
.findFirst()
.orElse(OFF);
}

@ProtobufConverter
public int periodSeconds() {
return (int) period.toSeconds();
}
}
12 changes: 10 additions & 2 deletions src/main/java/it/auties/whatsapp/model/info/DeviceContextInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,16 @@ public final class DeviceContextInfo implements Info, ProtobufMessage {
@ProtobufProperty(index = 4, type = ProtobufType.BYTES)
private final byte[] paddingBytes;

@ProtobufProperty(index = 5, type = ProtobufType.UINT32)
private final int messageAddOnDurationInSecs;

@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
public DeviceContextInfo(DeviceListMetadata deviceListMetadata, int deviceListMetadataVersion, byte[] messageSecret, byte[] paddingBytes) {
public DeviceContextInfo(DeviceListMetadata deviceListMetadata, int deviceListMetadataVersion, byte[] messageSecret, byte[] paddingBytes, int messageAddOnDurationInSecs) {
this.deviceListMetadata = deviceListMetadata;
this.deviceListMetadataVersion = deviceListMetadataVersion;
this.messageSecret = messageSecret;
this.paddingBytes = paddingBytes;
this.messageAddOnDurationInSecs = messageAddOnDurationInSecs;
}

public Optional<DeviceListMetadata> deviceListMetadata() {
Expand All @@ -50,4 +54,8 @@ public void setMessageSecret(byte[] messageSecret) {
public Optional<byte[]> paddingBytes() {
return Optional.ofNullable(paddingBytes);
}
}

public int messageAddOnDurationInSecs() {
return messageAddOnDurationInSecs;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
/**
* A model interface that represents a message sent by a contact or by Whatsapp.
*/
public sealed interface Message extends ProtobufMessage permits ButtonMessage, ContextualMessage, PaymentMessage, ServerMessage, CallMessage, EmptyMessage, KeepInChatMessage, NewsletterAdminInviteMessage, PollUpdateMessage, ReactionMessage {
public sealed interface Message extends ProtobufMessage permits ButtonMessage, ContextualMessage, PaymentMessage, ServerMessage, CallMessage, EmptyMessage, KeepInChatMessage, NewsletterAdminInviteMessage, PinInChatMessage, PollUpdateMessage, ReactionMessage {
/**
* Return message type
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@ public record MessageContainer(
Optional<FutureMessageContainer> editedMessage,
@ProtobufProperty(index = 59, type = ProtobufType.OBJECT)
Optional<FutureMessageContainer> viewOnceV2ExtensionMessage,
@ProtobufProperty(index = 63, type = ProtobufType.OBJECT)
Optional<PinInChatMessage> pinInChatMessage,
@ProtobufProperty(index = 78, type = ProtobufType.OBJECT)
Optional<NewsletterAdminInviteMessage> newsletterAdminInviteMessage,
@ProtobufProperty(index = 35, type = ProtobufType.OBJECT)
Expand Down Expand Up @@ -210,6 +212,7 @@ public static <T extends Message> MessageContainerBuilder ofBuilder(T message) {
builder.pollCreationMessage(Optional.of(pollCreationMessage));
case PollUpdateMessage pollUpdateMessage -> builder.pollUpdateMessage(Optional.of(pollUpdateMessage));
case KeepInChatMessage keepInChatMessage -> builder.keepInChatMessage(Optional.of(keepInChatMessage));
case PinInChatMessage pinInChatMessage -> builder.pinInChatMessage(Optional.of(pinInChatMessage));
case RequestPhoneNumberMessage requestPhoneNumberMessage ->
builder.requestPhoneNumberMessage(Optional.of(requestPhoneNumberMessage));
case EncryptedReactionMessage encReactionMessage ->
Expand Down Expand Up @@ -420,6 +423,9 @@ public Message content() {
if (keepInChatMessage.isPresent()) {
return keepInChatMessage.get();
}
if (pinInChatMessage.isPresent()) {
return pinInChatMessage.get();
}
if (documentWithCaptionMessage.isPresent()) {
return documentWithCaptionMessage.get().unbox();
}
Expand Down Expand Up @@ -668,4 +674,4 @@ public boolean isEmpty() {
public String toString() {
return Objects.toString(content());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,10 @@ public enum MessageType {
* Text edit
*/
EDITED,
/**
* Pin in chat
*/
PIN_IN_CHAT,
/**
* Newsletter admin invite
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package it.auties.whatsapp.model.message.model;

import it.auties.protobuf.annotation.ProtobufEnumIndex;
import it.auties.protobuf.annotation.ProtobufMessageName;
import it.auties.protobuf.annotation.ProtobufProperty;
import it.auties.protobuf.model.ProtobufEnum;
import it.auties.protobuf.model.ProtobufMessage;
import it.auties.protobuf.model.ProtobufType;
import it.auties.whatsapp.model.info.DeviceContextInfo;
import it.auties.whatsapp.util.Clock;

import java.time.ZonedDateTime;
import java.util.Optional;


/**
* A model class that represents an ephemeral message that was saved manually by the user in a chat
*/
@ProtobufMessageName("PinInChat")
public record PinInChat(
@ProtobufProperty(index = 1, type = ProtobufType.OBJECT)
Type pinType,
@ProtobufProperty(index = 2, type = ProtobufType.OBJECT)
ChatMessageKey key,
@ProtobufProperty(index = 3, type = ProtobufType.INT64)
long clientTimestampInMilliseconds,
@ProtobufProperty(index = 4, type = ProtobufType.INT64)
long serverTimestampMilliseconds,
@ProtobufProperty(index = 5, type = ProtobufType.OBJECT)
DeviceContextInfo messageAddOnContextInfo
) implements ProtobufMessage {
public Optional<ZonedDateTime> serverTimestamp() { return Clock.parseMilliseconds(serverTimestampMilliseconds); }

public Optional<ZonedDateTime> clientTimestamp() { return Clock.parseMilliseconds(clientTimestampInMilliseconds); }

public enum Type implements ProtobufEnum {
UNKNOWN_TYPE(0),
PIN_FOR_ALL(1),
UNDO_PIN_FOR_ALL(2);

final int index;

Type(@ProtobufEnumIndex int index) { this.index = index; }

public int index() { return index; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package it.auties.whatsapp.model.message.standard;

import it.auties.protobuf.annotation.ProtobufMessageName;
import it.auties.protobuf.annotation.ProtobufProperty;
import it.auties.protobuf.model.ProtobufEnum;
import it.auties.protobuf.model.ProtobufType;
import it.auties.whatsapp.model.message.model.*;


@ProtobufMessageName("Message.PinInChatMessage")
public record PinInChatMessage(
@ProtobufProperty(index = 1, type = ProtobufType.OBJECT)
ChatMessageKey key,
@ProtobufProperty(index = 2, type = ProtobufType.OBJECT)
Type pinType,
@ProtobufProperty(index = 3, type = ProtobufType.INT64)
long senderTimestampMilliseconds
) implements Message {
@Override
public MessageType type() { return MessageType.PIN_IN_CHAT; }

@Override
public MessageCategory category() { return MessageCategory.STANDARD; }

@ProtobufMessageName("Message.PinInChatMessage.Type")
public enum Type implements ProtobufEnum {
UNKNOWN_TYPE(0),
PIN_FOR_ALL(1),
UNPIN_FOR_ALL(2);

final int index;

Type(int index) { this.index = index; }

public int index() { return index; }
}
}
Loading