diff --git a/src/main/java/it/auties/whatsapp/api/Whatsapp.java b/src/main/java/it/auties/whatsapp/api/Whatsapp.java index b8c05869..c7e1f849 100644 --- a/src/main/java/it/auties/whatsapp/api/Whatsapp.java +++ b/src/main/java/it/auties/whatsapp/api/Whatsapp.java @@ -411,7 +411,7 @@ public CompletableFuture sendReaction(MessageInfo message */ public CompletableFuture sendReaction(MessageInfo message, String reaction) { var key = new ChatMessageKeyBuilder() - .id(ChatMessageKey.randomId()) + .id(ChatMessageKey.randomIdV2(message.senderJid(), store().clientType())) .chatJid(message.parentJid()) .senderJid(message.senderJid()) .fromMe(Objects.equals(message.senderJid().toSimpleJid(), jidOrThrowError().toSimpleJid())) @@ -574,7 +574,7 @@ public CompletableFuture sendChatMessage(JidProvider recipient, .deviceListMetadataVersion(2) .build(); var key = new ChatMessageKeyBuilder() - .id(ChatMessageKey.randomId()) + .id(ChatMessageKey.randomIdV2(jidOrThrowError(), store().clientType())) .chatJid(recipient.toJid()) .fromMe(true) .senderJid(jidOrThrowError()) @@ -688,7 +688,7 @@ public CompletableFuture sendStatus(MessageContainer message) { .deviceListMetadataVersion(2) .build(); var key = new ChatMessageKeyBuilder() - .id(ChatMessageKey.randomId()) + .id(ChatMessageKey.randomIdV2(jidOrThrowError(), store().clientType())) .chatJid(Jid.of("status@broadcast")) .fromMe(true) .senderJid(jidOrThrowError()) @@ -1731,7 +1731,7 @@ public CompletableFuture deleteMessage(ChatMessageInfo messageInfo, boolea .build(); var sender = messageInfo.chatJid().hasServer(JidServer.GROUP) ? jidOrThrowError() : null; var key = new ChatMessageKeyBuilder() - .id(ChatMessageKey.randomId()) + .id(ChatMessageKey.randomIdV2(sender, store().clientType())) .chatJid(messageInfo.chatJid()) .fromMe(true) .senderJid(sender) diff --git a/src/main/java/it/auties/whatsapp/model/message/model/ChatMessageKey.java b/src/main/java/it/auties/whatsapp/model/message/model/ChatMessageKey.java index 85bb5334..a9bb5249 100644 --- a/src/main/java/it/auties/whatsapp/model/message/model/ChatMessageKey.java +++ b/src/main/java/it/auties/whatsapp/model/message/model/ChatMessageKey.java @@ -5,14 +5,17 @@ import it.auties.protobuf.annotation.ProtobufProperty; import it.auties.protobuf.model.ProtobufMessage; import it.auties.protobuf.model.ProtobufType; +import it.auties.whatsapp.api.ClientType; import it.auties.whatsapp.model.info.ChatMessageInfo; import it.auties.whatsapp.model.jid.Jid; +import it.auties.whatsapp.model.jid.JidServer; import it.auties.whatsapp.util.Bytes; -import java.util.HexFormat; -import java.util.Locale; -import java.util.Objects; -import java.util.Optional; +import java.nio.ByteBuffer; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.time.Instant; +import java.util.*; /** * A container for unique identifiers and metadata linked to a {@link Message} and contained in @@ -36,7 +39,7 @@ public final class ChatMessageKey implements ProtobufMessage { public ChatMessageKey(Jid chatJid, boolean fromMe, String id, Jid senderJid) { this.chatJid = chatJid; this.fromMe = fromMe; - this.id = Objects.requireNonNullElseGet(id, ChatMessageKey::randomId); + this.id = Objects.requireNonNullElse(id, randomIdV2(senderJid)); this.senderJid = senderJid; } @@ -45,7 +48,7 @@ public ChatMessageKey(Jid chatJid, boolean fromMe) { } public ChatMessageKey(Jid chatJid, boolean fromMe, Jid senderJid) { - this(chatJid, fromMe, randomId(), senderJid); + this(chatJid, fromMe, randomIdV2(senderJid), senderJid); } /** @@ -59,6 +62,77 @@ public static String randomId() { .toUpperCase(Locale.ROOT); } + /** + * Generates a random message id based on the Client Type. Generation methods are taken from WEB and Android code + * @param jid senderJid + * @param clientType clientType (Mobile or Web) + * @return a non-null String + */ + + public static String randomIdV2(Jid jid, ClientType... clientType) { + var type = clientType.length == 0 ? ClientType.WEB : clientType[0]; + return switch (type) { + case ClientType.WEB -> randomWebKeyId(jid); + case ClientType.MOBILE -> randomMobileKeyId(jid); + }; + } + + private static String randomWebKeyId(Jid jid) { + try { + var random = new Random(); + var meJid = Objects.requireNonNullElse(jid, Jid.ofServer(JidServer.WHATSAPP)); + var meUser = "%s@%s".formatted(meJid.user(), "@c.us"); + long timeSeconds = Instant.now().getEpochSecond(); + byte[] randomBytes = new byte[16]; + random.nextBytes(randomBytes); + var buffer = ByteBuffer.allocate(Long.BYTES + meUser.length() + randomBytes.length); + buffer.putLong(timeSeconds); + buffer.put(meUser.getBytes()); + buffer.put(randomBytes); + var digest = MessageDigest.getInstance("SHA-256"); + byte[] hash = digest.digest(buffer.array()); + byte[] truncatedHash = new byte[9]; + System.arraycopy(hash, 0, truncatedHash, 0, 9); + return "3EB0" + HexFormat.of().formatHex(truncatedHash).toUpperCase(); + } catch (NoSuchAlgorithmException e) { + return randomId(); + } + } + + private static String randomMobileKeyId(Jid jid) { + try { + var random = new Random(); + var meJid = Objects.requireNonNullElse(jid, Jid.ofServer(JidServer.WHATSAPP)); + var meUser = meJid.toSimpleJid().toString().getBytes(); + var messageDigest = MessageDigest.getInstance("MD5"); + long timeMillis = System.currentTimeMillis(); + byte[] bArr = new byte[8]; + for (int i = 7; i >= 0; i--) { + bArr[i] = (byte) timeMillis; + timeMillis >>= 8; + } + messageDigest.update(bArr); + messageDigest.update(meUser); + byte[] bArr2 = new byte[16]; + random.nextBytes(bArr2); + messageDigest.update(bArr2); + var digested = messageDigest.digest(); + char[] cArr = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; + char[] cArr2 = new char[digested.length * 2]; + int i = 0; + for (byte b : digested) { + int i2 = b & 255; + int i3 = i + 1; + cArr2[i] = cArr[i2 >>> 4]; + i = i3 + 1; + cArr2[i3] = cArr[i2 & 15]; + } + return new String(cArr2); + } catch (NoSuchAlgorithmException e) { + return randomId(); + } + } + public Jid chatJid() { return chatJid; } diff --git a/src/main/java/it/auties/whatsapp/socket/SocketHandler.java b/src/main/java/it/auties/whatsapp/socket/SocketHandler.java index 8759ba18..a57704a4 100644 --- a/src/main/java/it/auties/whatsapp/socket/SocketHandler.java +++ b/src/main/java/it/auties/whatsapp/socket/SocketHandler.java @@ -455,7 +455,7 @@ public CompletableFuture sendPeerMessage(Jid companion, ProtocolMessage me var jid = store.jid() .orElseThrow(() -> new IllegalStateException("The session isn't connected")); var key = new ChatMessageKeyBuilder() - .id(ChatMessageKey.randomId()) + .id(ChatMessageKey.randomIdV2(jid, store.clientType())) .chatJid(companion) .fromMe(true) .senderJid(jid) diff --git a/src/test/java/it/auties/whatsapp/TestLibrary.java b/src/test/java/it/auties/whatsapp/TestLibrary.java index 8423d433..53a6bf72 100644 --- a/src/test/java/it/auties/whatsapp/TestLibrary.java +++ b/src/test/java/it/auties/whatsapp/TestLibrary.java @@ -756,7 +756,7 @@ public void testListMessage() { .jid() .orElseThrow(); var keyInfo = new ChatMessageKeyBuilder() - .id(ChatMessageKey.randomId()) + .id(ChatMessageKey.randomIdV2(jid, api.store().clientType())) .chatJid(contact) .senderJid(jid) .fromMe(true)