diff --git a/src/main/java/it/auties/whatsapp/api/Whatsapp.java b/src/main/java/it/auties/whatsapp/api/Whatsapp.java index cd7f3fc4..fef357b5 100644 --- a/src/main/java/it/auties/whatsapp/api/Whatsapp.java +++ b/src/main/java/it/auties/whatsapp/api/Whatsapp.java @@ -92,11 +92,11 @@ public class Whatsapp { // This is to make sure that the instances remain in memory only as long as it's needed private static final Map instances = new ConcurrentHashMap<>(); - protected static Optional getInstanceByUuid(UUID uuid) { + static Optional getInstanceByUuid(UUID uuid) { return Optional.ofNullable(instances.get(uuid)); } - protected static void removeInstanceByUuid(UUID uuid) { + static void removeInstanceByUuid(UUID uuid) { instances.remove(uuid); } diff --git a/src/main/java/it/auties/whatsapp/crypto/Sha1.java b/src/main/java/it/auties/whatsapp/crypto/Sha1.java deleted file mode 100644 index ca206001..00000000 --- a/src/main/java/it/auties/whatsapp/crypto/Sha1.java +++ /dev/null @@ -1,24 +0,0 @@ -package it.auties.whatsapp.crypto; - - -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; - -public final class Sha1 { - private static final String SHA_1 = "SHA-1"; - - public static byte[] calculate(String data) { - return calculate(data.getBytes(StandardCharsets.UTF_8)); - } - - public static byte[] calculate(byte[] data) { - try { - var digest = MessageDigest.getInstance(SHA_1); - digest.update(data); - return digest.digest(); - } catch (NoSuchAlgorithmException exception) { - throw new UnsupportedOperationException("Missing sha1 implementation"); - } - } -} diff --git a/src/main/java/it/auties/whatsapp/listener/Listener.java b/src/main/java/it/auties/whatsapp/listener/Listener.java index ccb74839..f4ff1dac 100644 --- a/src/main/java/it/auties/whatsapp/listener/Listener.java +++ b/src/main/java/it/auties/whatsapp/listener/Listener.java @@ -333,7 +333,7 @@ default void onNewMessage(MessageInfo info) { * @param everyone whether this message was deleted by you only for yourself or whether the * message was permanently removed */ - default void onMessageDeleted(Whatsapp whatsapp, ChatMessageInfo info, boolean everyone) { + default void onMessageDeleted(Whatsapp whatsapp, MessageInfo info, boolean everyone) { } /** @@ -343,7 +343,7 @@ default void onMessageDeleted(Whatsapp whatsapp, ChatMessageInfo info, boolean e * @param everyone whether this message was deleted by you only for yourself or whether the * message was permanently removed */ - default void onMessageDeleted(ChatMessageInfo info, boolean everyone) { + default void onMessageDeleted(MessageInfo info, boolean everyone) { } /** diff --git a/src/main/java/it/auties/whatsapp/listener/OnMessageDeleted.java b/src/main/java/it/auties/whatsapp/listener/OnMessageDeleted.java index 7b2967c0..e8abf25f 100644 --- a/src/main/java/it/auties/whatsapp/listener/OnMessageDeleted.java +++ b/src/main/java/it/auties/whatsapp/listener/OnMessageDeleted.java @@ -1,6 +1,6 @@ package it.auties.whatsapp.listener; -import it.auties.whatsapp.model.info.ChatMessageInfo; +import it.auties.whatsapp.model.info.MessageInfo; public interface OnMessageDeleted extends Listener { /** @@ -11,5 +11,5 @@ public interface OnMessageDeleted extends Listener { * message was permanently removed */ @Override - void onMessageDeleted(ChatMessageInfo info, boolean everyone); + void onMessageDeleted(MessageInfo info, boolean everyone); } \ No newline at end of file diff --git a/src/main/java/it/auties/whatsapp/listener/OnWhatsappMessageDeleted.java b/src/main/java/it/auties/whatsapp/listener/OnWhatsappMessageDeleted.java index 18b1765d..cae83c61 100644 --- a/src/main/java/it/auties/whatsapp/listener/OnWhatsappMessageDeleted.java +++ b/src/main/java/it/auties/whatsapp/listener/OnWhatsappMessageDeleted.java @@ -1,7 +1,7 @@ package it.auties.whatsapp.listener; import it.auties.whatsapp.api.Whatsapp; -import it.auties.whatsapp.model.info.ChatMessageInfo; +import it.auties.whatsapp.model.info.MessageInfo; public interface OnWhatsappMessageDeleted extends Listener { /** @@ -13,5 +13,5 @@ public interface OnWhatsappMessageDeleted extends Listener { * message was permanently removed */ @Override - void onMessageDeleted(Whatsapp whatsapp, ChatMessageInfo info, boolean everyone); + void onMessageDeleted(Whatsapp whatsapp, MessageInfo info, boolean everyone); } \ No newline at end of file diff --git a/src/main/java/it/auties/whatsapp/socket/AppStateHandler.java b/src/main/java/it/auties/whatsapp/socket/AppStateHandler.java index ce7c7d63..7e938c8c 100644 --- a/src/main/java/it/auties/whatsapp/socket/AppStateHandler.java +++ b/src/main/java/it/auties/whatsapp/socket/AppStateHandler.java @@ -442,9 +442,15 @@ private void onAction(ActionDataSync mutation, Action action) { var chat = targetChat.orElseGet(() -> createChat(messageIndex)); updateName(contact, chat, contactAction); } - case DeleteMessageForMeAction deleteMessageForMeAction -> { - targetChatMessage.ifPresent(message -> targetChat.ifPresent(chat -> chat.removeMessage(message))); - targetNewsletterMessage.ifPresent(message -> targetNewsletter.ifPresent(newsletter -> newsletter.removeMessage(message))); + case DeleteMessageForMeAction ignored -> { + targetChatMessage.ifPresent(message -> { + targetChat.ifPresent(chat -> chat.removeMessage(message)); + socketHandler.onMessageDeleted(message, false); + }); + targetNewsletterMessage.ifPresent(message -> { + targetNewsletter.ifPresent(newsletter -> newsletter.removeMessage(message)); + socketHandler.onMessageDeleted(message, false); + }); } case MarkChatAsReadAction markAction -> targetChat.ifPresent(chat -> { var read = markAction.read() ? 0 : -1; diff --git a/src/main/java/it/auties/whatsapp/socket/MessageHandler.java b/src/main/java/it/auties/whatsapp/socket/MessageHandler.java index 06601577..894aea8f 100644 --- a/src/main/java/it/auties/whatsapp/socket/MessageHandler.java +++ b/src/main/java/it/auties/whatsapp/socket/MessageHandler.java @@ -239,13 +239,12 @@ private AttachmentType getAttachmentType(Jid chatJid, ExtendedMediaMessage me }; } - - private MutableAttachmentProvider attributeMediaMessage(MutableAttachmentProvider mediaMessage, MediaFile upload) { + private void attributeMediaMessage(MutableAttachmentProvider mediaMessage, MediaFile upload) { if (mediaMessage instanceof ExtendedMediaMessage extendedMediaMessage) { extendedMediaMessage.setHandle(upload.handle()); } - return mediaMessage.setMediaSha256(upload.fileSha256()) + mediaMessage.setMediaSha256(upload.fileSha256()) .setMediaEncryptedSha256(upload.fileEncSha256()) .setMediaKey(upload.mediaKey()) .setMediaUrl(upload.url()) @@ -686,13 +685,13 @@ public void decode(Node node, JidProvider chatOverride, boolean notify) { var plainText = node.findNode("plaintext"); if (plainText.isPresent()) { - decodeNewsletterMessage(node, plainText.get(), businessName, chatOverride, notify); + decodeNewsletterMessage(node, plainText.get(), chatOverride, notify); return; } var reaction = node.findNode("reaction"); if (reaction.isPresent()) { - decodeNewsletterReaction(node, reaction.get(), businessName, chatOverride, notify); + decodeNewsletterReaction(node, reaction.get(), chatOverride, notify); return; } @@ -728,27 +727,27 @@ private Node createMessageNode(MessageSendRequest.Chat request, CipheredMessageR private String getMediaType(MessageContainer container) { var content = container.content(); return switch (content) { - case ImageMessage imageMessage -> "image"; + case ImageMessage ignored -> "image"; case VideoOrGifMessage videoMessage -> videoMessage.gifPlayback() ? "gif" : "video"; case AudioMessage audioMessage -> audioMessage.voiceMessage() ? "ptt" : "audio"; - case ContactMessage contactMessage -> "vcard"; - case DocumentMessage documentMessage -> "document"; - case ContactsMessage contactsMessage -> "contact_array"; - case LiveLocationMessage liveLocationMessage -> "livelocation"; - case StickerMessage stickerMessage -> "sticker"; - case ListMessage listMessage -> "list"; - case ListResponseMessage listResponseMessage -> "list_response"; - case ButtonsResponseMessage buttonsResponseMessage -> "buttons_response"; - case PaymentOrderMessage paymentOrderMessage -> "order"; - case ProductMessage productMessage -> "product"; - case NativeFlowResponseMessage nativeFlowResponseMessage -> "native_flow_response"; + case ContactMessage ignored -> "vcard"; + case DocumentMessage ignored -> "document"; + case ContactsMessage ignored -> "contact_array"; + case LiveLocationMessage ignored -> "livelocation"; + case StickerMessage ignored -> "sticker"; + case ListMessage ignored -> "list"; + case ListResponseMessage ignored -> "list_response"; + case ButtonsResponseMessage ignored -> "buttons_response"; + case PaymentOrderMessage ignored -> "order"; + case ProductMessage ignored -> "product"; + case NativeFlowResponseMessage ignored -> "native_flow_response"; case ButtonsMessage buttonsMessage -> buttonsMessage.headerType().hasMedia() ? buttonsMessage.headerType().name().toLowerCase() : null; case null, default -> null; }; } - private void decodeNewsletterMessage(Node messageNode, Node plainTextNode, String businessName, JidProvider chatOverride, boolean notify) { + private void decodeNewsletterMessage(Node messageNode, Node plainTextNode, JidProvider chatOverride, boolean notify) { try { var newsletterJid = messageNode.attributes() .getOptionalJid("from") @@ -816,7 +815,7 @@ private void decodeNewsletterMessage(Node messageNode, Node plainTextNode, Strin } } - private void decodeNewsletterReaction(Node messageNode, Node reactionNode, String businessName, JidProvider chatOverride, boolean notify) { + private void decodeNewsletterReaction(Node messageNode, Node reactionNode, JidProvider chatOverride, boolean notify) { try { var messageId = messageNode.attributes() .getRequiredString("id"); @@ -945,11 +944,11 @@ private void decodeChatMessage(Node infoNode, Node messageNode, String businessN } } - private CompletableFuture sendEncMessageReceipt(Node infoNode, String id, Jid chatJid, Jid senderJid, boolean fromMe) { + private void sendEncMessageReceipt(Node infoNode, String id, Jid chatJid, Jid senderJid, boolean fromMe) { var participant = fromMe && senderJid == null ? chatJid : senderJid; var category = infoNode.attributes().getString("category"); var receiptType = getReceiptType(category, fromMe); - return socketHandler.sendMessageAck(chatJid, infoNode) + socketHandler.sendMessageAck(chatJid, infoNode) .thenComposeAsync(ignored -> socketHandler.sendReceipt(chatJid, participant, List.of(id), receiptType)); } @@ -1106,7 +1105,7 @@ private void onHistorySyncNotification(ChatMessageInfo info, ProtocolMessage pro } downloadHistorySync(protocolMessage) - .thenAcceptAsync(history -> onHistoryNotification(info, history)) + .thenAcceptAsync(this::onHistoryNotification) .exceptionallyAsync(throwable -> socketHandler.handleFailure(HISTORY_SYNC, throwable)) .thenRunAsync(() -> socketHandler.sendReceipt(info.chatJid(), null, List.of(info.id()), "hist_sync")); } @@ -1139,7 +1138,7 @@ private CompletableFuture downloadHistorySyncNotification(HistorySy .thenApplyAsync(result -> HistorySyncSpec.decode(BytesHelper.decompress(result)))); } - private void onHistoryNotification(ChatMessageInfo info, HistorySync history) { + private void onHistoryNotification(HistorySync history) { handleHistorySync(history); if (history.progress() == null) { return; @@ -1169,7 +1168,6 @@ private void handleHistorySync(HistorySync history) { } private void handleInitialStatus(HistorySync history) { - var store = socketHandler.store(); for (var messageInfo : history.statusV3Messages()) { socketHandler.store().addStatus(messageInfo); } @@ -1271,7 +1269,6 @@ private void onForcedHistorySyncCompletion() { private void handleConversations(HistorySync history) { - var store = socketHandler.store(); for (var chat : history.conversations()) { for (var message : chat.messages()) { attributeChatMessage(message.messageInfo()); @@ -1338,7 +1335,7 @@ private void attributeContextSender(ContextInfo contextInfo, Jid senderJid) { contextInfo.setQuotedMessageSender(contact); } - protected ChatMessageInfo attributeChatMessage(ChatMessageInfo info) { + protected void attributeChatMessage(ChatMessageInfo info) { var chat = socketHandler.store().findChatByJid(info.chatJid()) .orElseGet(() -> socketHandler.store().addNewChat(info.chatJid())); info.setChat(chat); @@ -1353,7 +1350,6 @@ protected ChatMessageInfo attributeChatMessage(ChatMessageInfo info) { .flatMap(ContextualMessage::contextInfo) .ifPresent(this::attributeContext); processMessageWithSecret(info); - return info; } private void processMessageWithSecret(ChatMessageInfo info) { diff --git a/src/main/java/it/auties/whatsapp/socket/SocketHandler.java b/src/main/java/it/auties/whatsapp/socket/SocketHandler.java index 0e25f5cb..61a0ab70 100644 --- a/src/main/java/it/auties/whatsapp/socket/SocketHandler.java +++ b/src/main/java/it/auties/whatsapp/socket/SocketHandler.java @@ -710,7 +710,7 @@ protected void onSetting(Setting setting) { }); } - protected void onMessageDeleted(ChatMessageInfo message, boolean everyone) { + protected void onMessageDeleted(MessageInfo message, boolean everyone) { callListenersAsync(listener -> { listener.onMessageDeleted(whatsapp, message, everyone); listener.onMessageDeleted(message, everyone); diff --git a/src/main/java/it/auties/whatsapp/socket/SocketRequest.java b/src/main/java/it/auties/whatsapp/socket/SocketRequest.java index 13f850cf..392b17cc 100644 --- a/src/main/java/it/auties/whatsapp/socket/SocketRequest.java +++ b/src/main/java/it/auties/whatsapp/socket/SocketRequest.java @@ -21,20 +21,9 @@ import static java.util.concurrent.CompletableFuture.delayedExecutor; import static java.util.concurrent.TimeUnit.SECONDS; -/** - * An abstract model class that represents a request made from the client to the server. - */ -@SuppressWarnings("UnusedReturnValue") public record SocketRequest(String id, Object body, CompletableFuture future, Function filter, Throwable caller) { - /** - * The timeout in seconds before a Request wrapping a Node fails - */ private static final int TIMEOUT = 60; - - /** - * The delayed executor used to cancel futures - */ private static final Executor EXECUTOR = delayedExecutor(TIMEOUT, SECONDS); private SocketRequest(String id, Function filter, Object body) { @@ -58,39 +47,22 @@ private void cancelTimedFuture() { future.completeExceptionally(caller); } - /** - * Constructs a new request with the provided body expecting a newsletters - */ public static SocketRequest of(Node body, Function filter) { return new SocketRequest(body.id(), filter, body); } - /** - * Constructs a new request with the provided body expecting a newsletters - */ public static SocketRequest of(byte[] body) { return new SocketRequest(null, null, body); } - /** - * Sends a request to the WebSocket linked to {@code session}. - * - * @param session the WhatsappWeb's WebSocket session - * @param store the store - */ public CompletableFuture sendWithPrologue(SocketSession session, Keys keys, Store store) { return send(session, keys, store, true, false); } - /** - * Sends a request to the WebSocket linked to {@code session}. - * - * @param store the store - * @param session the WhatsappWeb's WebSocket session - * @param prologue whether the prologue should be prepended to the request - * @param response whether the request expects a newsletters - * @return this request - */ + public CompletableFuture send(SocketSession session, Keys keys, Store store) { + return send(session, keys, store, false, true); + } + public CompletableFuture send(SocketSession session, Keys keys, Store store, boolean prologue, boolean response) { var ciphered = encryptMessage(keys); var byteArrayOutputStream = new ByteArrayOutputStream(); @@ -110,6 +82,12 @@ public CompletableFuture send(SocketSession session, Keys keys, Store stor } } + public CompletableFuture sendWithNoResponse(SocketSession session, Keys keys, Store store) { + return send(session, keys, store, false, false) + .thenRun(() -> {}); + } + + private byte[] getPrologueData(Store store) { return switch (store.clientType()) { case WEB -> Specification.Whatsapp.WEB_PROLOGUE; @@ -155,35 +133,6 @@ private Void onSendError(Throwable throwable) { return null; } - /** - * Sends a request to the WebSocket linked to {@code session}. - * - * @param store the store - * @param session the WhatsappWeb's WebSocket session - * @return this request - */ - public CompletableFuture send(SocketSession session, Keys keys, Store store) { - return send(session, keys, store, false, true); - } - - /** - * Sends a request to the WebSocket linked to {@code session}. - * - * @param store the store - * @param session the WhatsappWeb's WebSocket session - * @return this request - */ - public CompletableFuture sendWithNoResponse(SocketSession session, Keys keys, Store store) { - return send(session, keys, store, false, false) - .thenRunAsync(() -> { - }); - } - - /** - * Completes this request using {@code newsletters} - * - * @param response the newsletters used to complete {@link SocketRequest#future} - */ public boolean complete(Node response, boolean exceptionally) { if (response == null) { future.complete(null); diff --git a/src/main/java/it/auties/whatsapp/socket/SocketSession.java b/src/main/java/it/auties/whatsapp/socket/SocketSession.java index c0eaee11..49bca82a 100644 --- a/src/main/java/it/auties/whatsapp/socket/SocketSession.java +++ b/src/main/java/it/auties/whatsapp/socket/SocketSession.java @@ -45,8 +45,6 @@ private SocketSession(URI proxy, ExecutorService executor) { public abstract CompletableFuture sendBinary(byte[] bytes); - abstract boolean isOpen(); - static SocketSession of(URI proxy, ExecutorService executor, boolean webSocket) { if (webSocket) { return new WebSocketSession(proxy, executor); @@ -102,8 +100,7 @@ public CompletableFuture sendBinary(byte[] bytes) { }); } - @Override - boolean isOpen() { + private boolean isOpen() { return session != null && !session.isInputClosed() && !session.isOutputClosed(); } @@ -233,8 +230,7 @@ void disconnect() { } } - @Override - public boolean isOpen() { + private boolean isOpen() { return socket != null && socket.isOpen(); } diff --git a/src/main/java/it/auties/whatsapp/util/DefaultControllerSerializer.java b/src/main/java/it/auties/whatsapp/util/DefaultControllerSerializer.java index b894a06a..68bd71f1 100644 --- a/src/main/java/it/auties/whatsapp/util/DefaultControllerSerializer.java +++ b/src/main/java/it/auties/whatsapp/util/DefaultControllerSerializer.java @@ -556,8 +556,8 @@ public void deleteSession(Controller controller) { } } - private Path delete(Path path) throws IOException { - return Files.walkFileTree(path, new SimpleFileVisitor<>() { + private void delete(Path path) throws IOException { + Files.walkFileTree(path, new SimpleFileVisitor<>() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { Files.delete(file); diff --git a/src/main/java/it/auties/whatsapp/util/ListenerScanner.java b/src/main/java/it/auties/whatsapp/util/ListenerScanner.java index 4b5d48b4..3888cf6e 100644 --- a/src/main/java/it/auties/whatsapp/util/ListenerScanner.java +++ b/src/main/java/it/auties/whatsapp/util/ListenerScanner.java @@ -25,7 +25,7 @@ private static List> loadListeners() { public static List scan(Whatsapp whatsapp, boolean useCache) { var listeners = useCache ? cache : loadListeners(); - return cache.stream() + return listeners.stream() .map(listener -> initialize(listener, whatsapp)) .toList(); } diff --git a/src/main/java/it/auties/whatsapp/util/Specification.java b/src/main/java/it/auties/whatsapp/util/Specification.java index f6316f91..84f27a40 100644 --- a/src/main/java/it/auties/whatsapp/util/Specification.java +++ b/src/main/java/it/auties/whatsapp/util/Specification.java @@ -13,25 +13,15 @@ public final static class Whatsapp { public static final String DEFAULT_NAME = "Cobalt"; public static final byte[] NOISE_PROTOCOL = "Noise_XX_25519_AESGCM_SHA256\0\0\0\0".getBytes(StandardCharsets.UTF_8); public static final String WEB_ORIGIN = "https://web.whatsapp.com"; - public static final String WEB_HOST = "web.whatsapp.com"; public static final URI WEB_SOCKET_ENDPOINT = URI.create("wss://web.whatsapp.com/ws/chat"); public static final String SOCKET_ENDPOINT = "g.whatsapp.net"; public static final int SOCKET_PORT = 443; public static final String WEB_UPDATE_URL = "https://web.whatsapp.com/check-update?version=2.2245.9&platform=web"; - public static final String MOBILE_IOS_ = "https://web.whatsapp.com/check-update?version=2.2245.9&platform=web"; public static final String MOBILE_IOS_URL = "https://itunes.apple.com/lookup?bundleId=net.whatsapp.WhatsApp"; public static final String MOBILE_BUSINESS_IOS_URL = "https://itunes.apple.com/lookup?bundleId=net.whatsapp.WhatsAppSMB"; public static final String MOBILE_REGISTRATION_ENDPOINT = "https://v.whatsapp.net/v2"; public static final String MOBILE_KAIOS_REGISTRATION_ENDPOINT = "https://v-k.whatsapp.net/v2"; public static final Version DEFAULT_MOBILE_KAIOS_VERSION = Version.of("2.2329.8"); - public static final String APNS_WHATSAPP_BUSINESS_NAME = "net.whatsapp.WhatsAppSMB"; - public static final String APNS_WHATSAPP_NAME = "net.whatsapp.WhatsApp"; - public static final String[] DEFAULT_APNS_FILTERS = new String[]{ - "net.whatsapp.WhatsApp", - "net.whatsapp.WhatsApp.voip", - "net.whatsapp.WhatsAppSMB", - "net.whatsapp.WhatsAppSMB.voip" - }; private static final byte[] WHATSAPP_VERSION_HEADER = "WA".getBytes(StandardCharsets.UTF_8); private static final byte[] WEB_VERSION = new byte[]{6, BinaryTokens.DICTIONARY_VERSION}; public static final byte[] WEB_PROLOGUE = BytesHelper.concat(WHATSAPP_VERSION_HEADER, WEB_VERSION);