Skip to content

Commit

Permalink
More work for API stability
Browse files Browse the repository at this point in the history
  • Loading branch information
Auties00 committed Oct 24, 2023
1 parent e6b56c8 commit 6f78f0b
Show file tree
Hide file tree
Showing 7 changed files with 108 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,6 @@ public final class ChatMessageInfo implements MessageInfo, MessageStatusInfo<Cha
private final Jid originalSender;
@ProtobufProperty(index = 52, type = ProtobufType.UINT64)
private long revokeTimestampSeconds;

@JsonBackReference
private Chat chat;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

public final class NewsletterMessageInfo implements MessageInfo, MessageStatusInfo<NewsletterMessageInfo> {
@JsonBackReference
private final Newsletter newsletter;
private Newsletter newsletter;
private final String id;
private final int serverId;
private final Long timestampSeconds;
Expand All @@ -33,6 +33,11 @@ public NewsletterMessageInfo(Newsletter newsletter, String id, int serverId, Lon
this.status = status;
}

public NewsletterMessageInfo setNewsletter(Newsletter newsletter) {
this.newsletter = newsletter;
return this;
}

public Jid newsletterJid() {
return newsletter.jid();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ public final class StickerMessage extends LocalMediaMessage<StickerMessage> impl
private final boolean avatar;

@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
public StickerMessage(String mediaUrl, byte[] mediaSha256, byte[] mediaEncryptedSha256, byte[] mediaKey, String mimetype, Integer height, Integer width, String mediaDirectPath, Long mediaSize, Long mediaKeyTimestampSeconds, Integer firstFrameLength, byte[] firstFrameSidecar, boolean animated, byte[] thumbnail, ContextInfo contextInfo, long stickerSentTimestamp, boolean avatar) {
public StickerMessage(String mediaUrl, byte[] mediaSha256, byte[] mediaEncryptedSha256, byte[] mediaKey, String mimetype, Integer height, Integer width, String mediaDirectPath, Long mediaSize, Long mediaKeyTimestampSeconds, Integer firstFrameLength, byte[] firstFrameSidecar, boolean animated, byte[] thumbnail, ContextInfo contextInfo, Long stickerSentTimestamp, boolean avatar) {
this.mediaUrl = mediaUrl;
this.mediaSha256 = mediaSha256;
this.mediaEncryptedSha256 = mediaEncryptedSha256;
Expand Down
72 changes: 43 additions & 29 deletions src/main/java/it/auties/whatsapp/socket/MessageHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
import it.auties.whatsapp.model.sync.PushName;
import it.auties.whatsapp.util.*;

import java.io.ByteArrayOutputStream;
import java.lang.System.Logger;
import java.lang.System.Logger.Level;
import java.nio.charset.StandardCharsets;
Expand Down Expand Up @@ -365,6 +366,15 @@ private Node getPlainMessageNode(MessageContainer message) {
return Node.of("reaction", Map.of("code", reactionMessage.content()));
}

if(message.content() instanceof TextMessage textMessage && textMessage.thumbnail().isEmpty()) {
var byteArrayOutputStream = new ByteArrayOutputStream();
byteArrayOutputStream.write(10);
var encoded = textMessage.text().getBytes(StandardCharsets.UTF_8);
byteArrayOutputStream.writeBytes(BytesHelper.intToVarInt(encoded.length));
byteArrayOutputStream.writeBytes(encoded);
return Node.of("plaintext", byteArrayOutputStream.toByteArray());
}

var messageAttributes = Attributes.of()
.put("mediatype", getMediaType(message), Objects::nonNull)
.toMap();
Expand Down Expand Up @@ -1278,17 +1288,24 @@ private void handlePastParticipants(GroupPastParticipants pastParticipants) {

@SafeVarargs
private <T> List<T> toSingleList(List<T>... all) {
return Stream.of(all)
.filter(Objects::nonNull)
.flatMap(Collection::stream)
.toList();
return switch (all.length) {
case 0 -> List.of();
case 1 -> all[0];
default -> Stream.of(all)
.filter(Objects::nonNull)
.flatMap(Collection::stream)
.toList();
};
}

private ChatMessageKey attributeSender(ChatMessageInfo info, Jid senderJid) {
private void attributeSender(ChatMessageInfo info, Jid senderJid) {
if(senderJid.server() != JidServer.WHATSAPP && senderJid.server() != JidServer.USER) {
return;
}

var contact = socketHandler.store().findContactByJid(senderJid)
.orElseGet(() -> socketHandler.store().addContact(new Contact(senderJid)));
info.setSender(contact);
return info.key();
}

private void attributeContext(ContextInfo contextInfo) {
Expand All @@ -1308,13 +1325,30 @@ private void attributeContextSender(ContextInfo contextInfo, Jid senderJid) {
contextInfo.setQuotedMessageSender(contact);
}

private void processMessage(ChatMessageInfo info) {
protected ChatMessageInfo attributeChatMessage(ChatMessageInfo info) {
var chat = socketHandler.store().findChatByJid(info.chatJid())
.orElseGet(() -> socketHandler.store().addNewChat(info.chatJid()));
info.setChat(chat);
var me = socketHandler.store().jid().orElse(null);
if (info.fromMe() && me != null) {
info.key().setSenderJid(me.withoutDevice());
}

attributeSender(info, info.senderJid());
info.message()
.contentWithContext()
.flatMap(ContextualMessage::contextInfo)
.ifPresent(this::attributeContext);
processMessageWithSecret(info);
return info;
}

private void processMessageWithSecret(ChatMessageInfo info) {
switch (info.message().content()) {
case PollCreationMessage pollCreationMessage -> handlePollCreation(info, pollCreationMessage);
case PollUpdateMessage pollUpdateMessage -> handlePollUpdate(info, pollUpdateMessage);
case ReactionMessage reactionMessage -> handleReactionMessage(info, reactionMessage);
default -> {
}
default -> {}
}
}

Expand Down Expand Up @@ -1384,26 +1418,6 @@ private void handleReactionMessage(ChatMessageInfo info, ReactionMessage reactio
.ifPresent(message -> message.reactions().add(reactionMessage));
}

protected ChatMessageInfo attributeChatMessage(ChatMessageInfo info) {
var chat = socketHandler.store().findChatByJid(info.chatJid())
.orElseGet(() -> socketHandler.store().addNewChat(info.chatJid()));
info.setChat(chat);
var me = socketHandler.store().jid().orElse(null);
if (info.fromMe() && me != null) {
info.key().setSenderJid(me.withoutDevice());
}

info.key()
.senderJid()
.ifPresent(senderJid -> attributeSender(info, senderJid));
info.message()
.contentWithContext()
.flatMap(ContextualMessage::contextInfo)
.ifPresent(this::attributeContext);
processMessage(info);
return info;
}

protected void dispose() {
historyCache.clear();
historySyncTask = null;
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/it/auties/whatsapp/util/BytesHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -169,4 +169,14 @@ public static String bytesToCrockford(byte[] bytes) {

return crockford.toString();
}

public static byte[] intToVarInt(int value) {
var out = new ByteArrayOutputStream();
while ((value & 0xFFFFFF80) != 0L) {
out.write((byte) ((value & 0x7F) | 0x80));
value >>>= 7;
}
out.write((byte) (value & 0x7F));
return out.toByteArray();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@
import it.auties.whatsapp.controller.Store;
import it.auties.whatsapp.model.chat.Chat;
import it.auties.whatsapp.model.chat.ChatBuilder;
import it.auties.whatsapp.model.info.ContextInfo;
import it.auties.whatsapp.model.jid.Jid;
import it.auties.whatsapp.model.message.model.ContextualMessage;
import it.auties.whatsapp.model.mobile.PhoneNumber;
import it.auties.whatsapp.model.newsletter.Newsletter;
import it.auties.whatsapp.model.sync.HistorySyncMessage;

import java.io.IOException;
import java.io.UncheckedIOException;
Expand Down Expand Up @@ -331,19 +334,42 @@ public CompletableFuture<Void> attributeStore(Store store) {
var futures = walker.map(entry -> handleStoreFile(store, entry))
.filter(Objects::nonNull)
.toArray(CompletableFuture[]::new);
var result = CompletableFuture.allOf(futures);
var result = CompletableFuture.allOf(futures)
.thenRun(() -> attributeStoreContextualMessages(store));
attributeStoreSerializers.put(store.uuid(), result);
return result;
} catch (IOException exception) {
return CompletableFuture.failedFuture(exception);
}
}

// Do this after we have all the chats, or it won't work for obvious reasons
private void attributeStoreContextualMessages(Store store) {
store.chats()
.stream()
.flatMap(chat -> chat.messages().stream())
.forEach(message -> attributeStoreContextualMessage(store, message));
}

private void attributeStoreContextualMessage(Store store, HistorySyncMessage message) {
message.messageInfo()
.message()
.contentWithContext()
.flatMap(ContextualMessage::contextInfo)
.ifPresent(contextInfo -> attributeStoreContextInfo(store, contextInfo));
}

private void attributeStoreContextInfo(Store store, ContextInfo contextInfo) {
contextInfo.quotedMessageChatJid()
.flatMap(store::findChatByJid)
.ifPresent(contextInfo::setQuotedMessageChat);
}

private CompletableFuture<Void> handleStoreFile(Store store, Path entry) {
return switch (FileType.of(entry)) {
case UNKNOWN -> null;
case NEWSLETTER -> CompletableFuture.runAsync(() -> deserializeNewsletter(store, entry));
case CHAT -> CompletableFuture.runAsync(() -> deserializeChat(store, entry));
case UNKNOWN -> null;
};
}

Expand Down Expand Up @@ -421,7 +447,11 @@ private void linkToUuid(ClientType type, UUID uuid, String string) {

private void deserializeChat(Store store, Path chatFile) {
try (var input = new GZIPInputStream(Files.newInputStream(chatFile))) {
store.addChatDirect(Smile.readValue(input, Chat.class));
var chat = Smile.readValue(input, Chat.class);
for (var message : chat.messages()) {
message.messageInfo().setChat(chat);
}
store.addChatDirect(chat);
} catch (IOException exception) {
store.addChatDirect(rescueChat(chatFile));
}
Expand All @@ -444,7 +474,11 @@ private Chat rescueChat(Path entry) {

private void deserializeNewsletter(Store store, Path newsletterFile) {
try (var input = new GZIPInputStream(Files.newInputStream(newsletterFile))) {
store.addNewsletter(Smile.readValue(input, Newsletter.class));
var newsletter = Smile.readValue(input, Newsletter.class);
for (var message : newsletter.messages()) {
message.setNewsletter(newsletter);
}
store.addNewsletter(newsletter);
} catch (IOException exception) {
store.addNewsletter(rescueNewsletter(newsletterFile));
}
Expand Down
11 changes: 10 additions & 1 deletion src/test/java/it/auties/whatsapp/local/WebRunner.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import it.auties.whatsapp.api.WebHistoryLength;
import it.auties.whatsapp.api.Whatsapp;
import it.auties.whatsapp.model.info.ChatMessageInfo;
import it.auties.whatsapp.model.newsletter.NewsletterViewerRole;

// Just used for testing locally
public class WebRunner {
Expand All @@ -17,7 +18,15 @@ public static void main(String[] args) {
.addNewChatMessageListener((api, message) -> System.out.println(message.toJson()))
.addContactsListener((api, contacts) -> System.out.printf("Contacts: %s%n", contacts.size()))
.addChatsListener(chats -> System.out.printf("Chats: %s%n", chats.size()))
.addNewslettersListener((newsletters) -> System.out.printf("Newsletters: %s%n", newsletters.size()))
.addNewslettersListener((api, newsletters) -> {
System.out.printf("Newsletters: %s%n", newsletters.size());
var jid = newsletters.stream()
.filter(entry -> entry.viewerMetadata().isPresent() && entry.viewerMetadata().get().role() == NewsletterViewerRole.OWNER)
.findFirst()
.orElseThrow()
.jid();
api.sendMessage(jid, "Hello").join();
})
.addNodeReceivedListener(incoming -> System.out.printf("Received node %s%n", incoming))
.addNodeSentListener(outgoing -> System.out.printf("Sent node %s%n", outgoing))
.addActionListener ((action, info) -> System.out.printf("New action: %s, info: %s%n", action, info))
Expand Down

0 comments on commit 6f78f0b

Please sign in to comment.