{"id":14986952,"url":"https://github.com/auties00/cobalt","last_synced_at":"2025-05-15T05:08:12.025Z","repository":{"id":37255178,"uuid":"325345603","full_name":"Auties00/Cobalt","owner":"Auties00","description":"Standalone unofficial fully-featured Whatsapp Web and Mobile API for Java and Kotlin","archived":false,"fork":false,"pushed_at":"2025-05-01T21:43:56.000Z","size":8440,"stargazers_count":733,"open_issues_count":13,"forks_count":214,"subscribers_count":39,"default_branch":"master","last_synced_at":"2025-05-12T01:35:39.816Z","etag":null,"topics":["java","whatsapp","whatsapp-api","whatsapp-automation","whatsapp-bot","whatsapp-chat","whatsapp-web","whatsapp-web-api","whatsappweb"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Auties00.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2020-12-29T17:19:18.000Z","updated_at":"2025-05-11T15:44:05.000Z","dependencies_parsed_at":"2024-11-21T09:01:56.281Z","dependency_job_id":"82685141-7a2a-4528-ac83-a0828e53a673","html_url":"https://github.com/Auties00/Cobalt","commit_stats":{"total_commits":9,"total_committers":2,"mean_commits":4.5,"dds":0.2222222222222222,"last_synced_commit":"73d4d6153e739fc867a62d0506d53dc0619e4cea"},"previous_names":["auties00/cobalt","auties00/whatsappweb4j"],"tags_count":30,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Auties00%2FCobalt","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Auties00%2FCobalt/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Auties00%2FCobalt/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Auties00%2FCobalt/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Auties00","download_url":"https://codeload.github.com/Auties00/Cobalt/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254276447,"owners_count":22043867,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["java","whatsapp","whatsapp-api","whatsapp-automation","whatsapp-bot","whatsapp-chat","whatsapp-web","whatsapp-web-api","whatsappweb"],"created_at":"2024-09-24T14:13:51.807Z","updated_at":"2025-05-15T05:08:07.018Z","avatar_url":"https://github.com/Auties00.png","language":"Java","funding_links":["https://www.paypal.me/AutiesDevelopment"],"categories":[],"sub_categories":[],"readme":"# Cobalt\n\nWhatsapp4j has been renamed to Cobalt to comply with an official request coming from Whatsapp.\nThe repository's history was cleared to comply with this request, but keep in mind that the project has been actively developed for over two years.\nTo be clear, this library is not affiliated with Whatsapp LLC in any way.\nThis is a personal project that I maintain in my free time\n\n### What is Cobalt\n\nCobalt is a library built to interact with Whatsapp.\nIt can be used with:\n1. Whatsapp Web (Companion)\n2. Whatsapp Mobile (Personal and Business)\n\n### Donations\n\nIf you like my work, you can become a sponsor here on GitHub or tip me through:\n- [Paypal](https://www.paypal.me/AutiesDevelopment).\n\nI can also work on sponsored features and/or projects!\n\n### Java version\n\nThis library was built for [Java 21](https://openjdk.java.net/projects/jdk/21/), the latest LTS.\n\n### Breaking changes policy\n\nUntil the library doesn't reach release 1.0, there will be major breaking changes between each release.\nThis is needed to finalize the design of the API.\nAfter this milestone, breaking changes will be present only in major releases.\n\n### Optimizing memory usage\n\nIf the machine you are hosting this library on has memory constraints, please look into how to tune a JVM.\nThe easiest thing you can do is use the -Xmx argument to specify the maximum size, in bytes, of the memory allocation pool.\nI have written this disclaimer because many new devs tend to get confused by Java's opportunistic memory allocation.\n\n### Can this library get my device banned?\n\nWhile there is no risk in using this library with your main account, keep in mind that Whatsapp has anti-spam measures for their web client.\nIf you add a participant from a brand-new number to a group, it will most likely get you banned.\nIf you compile the library yourself, don't run the CI on a brand-new number, or it will get banned for spamming too many requests(the CI has to test that all the library works).\nIn short, if you use this library without a malicious intent, you will never get banned.\n\n### How to install\n\n#### Maven\n\n - Dependency\n    ```xml\n    \u003cdependency\u003e\n        \u003cgroupId\u003ecom.github.auties00\u003c/groupId\u003e\n        \u003cartifactId\u003ecobalt\u003c/artifactId\u003e\n        \u003cversion\u003e0.0.9\u003c/version\u003e\n    \u003c/dependency\u003e\n    ```\n   \n - Annotation processor (required for @RegisterListener)\n    ```xml\n    \u003cplugin\u003e\n        \u003cgroupId\u003eorg.apache.maven.plugins\u003c/groupId\u003e\n        \u003cartifactId\u003emaven-compiler-plugin\u003c/artifactId\u003e\n        \u003cconfiguration\u003e\n            \u003cannotationProcessorPaths\u003e\n                \u003cannotationProcessorPath\u003e\n                    \u003cgroupId\u003ecom.github.auties00\u003c/groupId\u003e\n                    \u003cartifactId\u003ecobalt\u003c/artifactId\u003e\n                    \u003cversion\u003e0.0.9\u003c/version\u003e\n                \u003cannotationProcessorPath\u003e\n            \u003cannotationProcessorPaths\u003e\n        \u003cconfiguration\u003e\n    \u003cplugin\u003e\n    ```\n\n#### Gradle\n\n- Groovy DSL\n    - Dependency\n    ```groovy\n    implementation 'com.github.auties00:cobalt:0.0.9'\n    ```\n  \n    - Annotation processor (required for @RegisterListener)\n    ```groovy\n    annotationProcessor 'com.github.auties00:cobalt:0.0.9'\n    ```\n\n- Kotlin DSL\n    - Dependency\n    ```groovy\n    implementation(\"com.github.auties00:cobalt:0.0.9\")\n    ```\n  \n    - Annotation processor (required for @RegisterListener)\n    ```groovy\n    annotationProcessor(\"com.github.auties00:cobalt:0.0.9\")\n    ```\n\n### Javadocs \u0026 Documentation\n\nJavadocs for Cobalt are available [here](https://www.javadoc.io/doc/com.github.auties00/cobalt/0.0.8).\nThe documentation for this project reaches most of the publicly available APIs(i.e. public members in exported packages), but sometimes the Javadoc may be incomplete \nor some methods could be absent from the project's README. If you find any of the latter, know that even small contributions are welcomed!\n\n### How to contribute\n\nAs of today, no additional configuration or artifact building is needed to edit this project.\nI recommend using the latest version of IntelliJ, though any other IDE should work.\nIf you are not familiar with git, follow these short tutorials in order:\n\n1. [Fork this project](https://docs.github.com/en/github/getting-started-with-github/fork-a-repo)\n2. [Clone the new repo](https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/cloning-a-repository)\n3. [Create a new branch](https://docs.github.com/en/desktop/contributing-and-collaborating-using-github-desktop/managing-branches#creating-a-branch)\n4. Once you have implemented the new\n   feature, [create a new merge request](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request)\n\nIf you are trying to implement a feature that is present on WhatsappWeb's WebClient, for example audio or video calls,\nconsider using [CobaltAnalyzer](https://github.com/Auties00/CobaltAnalyzer), a tool I built for this exact purpose.\n\n### Disclaimer about async operations \nThis library heavily depends on async operations using the CompletableFuture construct.\nRemember to handle them as your application will terminate without doing anything if the main thread is not executing any task.\nPlease do not open redundant issues on GitHub because of this.\n\n### How to create a connection\n\u003cdetails\u003e\n  \u003csummary\u003eDetailed Walkthrough\u003c/summary\u003e\n\n\nTo create a new connection, start by creating a builder with the api you need:\n- Web\n    ```java\n    Whatsapp.webBuilder()\n    ```\n- Mobile\n  ```java\n    Whatsapp.mobileBuilder()\n  ```\nIf you want to use a custom serializer, specify it:\n  ```java\n  .serializer(new CustomControllerSerializer())\n  ```  \nNow select the type of connection that you need:\n- Create a fresh connection\n  ```java\n  .newConnection(someUuid)\n  ```   \n- Retrieve a connection by id if available, otherwise create a new one\n  ```java\n  .newConnection(someUuid)\n  ```\n- Retrieve a connection by phone number if available, otherwise create a new one\n  ```java\n  .newConnection(phoneNumber)\n  ```\n- Retrieve a connection by an alias if available, otherwise create a new one\n  ```java\n  .newConnection(alias)\n  ```\n- Retrieve a connection by id if available, otherwise returns an empty Optional\n  ```java\n  .newOptionalConnection(someUuid)\n  ```\n- Retrieve the first connection that was serialized if available, otherwise create a new one\n  ```java\n  .firstConnection()\n  ```\n- Retrieve the first connection that was serialized if available, otherwise returns an empty Optional\n  ```java\n  .firstOptionalConnection()\n  ```\n- Retrieve the last connection that was serialized if available, otherwise create a new one\n  ```java\n  .lastConnection()\n  ```\n- Retrieve the last connection that was serialized if available, otherwise returns an empty Optional\n  ```java\n  .lastOptionalConnection()\n  ```\nYou can now customize the API with these options:\n- name - The device's name for Whatsapp Web, the push name for Whatsapp's Mobile\n  ```java\n  .name(\"Some Custom Name :)\")\n  ```\n- version - The version of Whatsapp to use\n  ```java\n  .version(new Version(\"x.xx.xx\"))\n  ```\n- autodetectListeners - Whether listeners annotated with `@RegisterListener` should automatically be registered\n  ```java\n  .autodetectListeners(true)\n  ```\n- textPreviewSetting - Whether a media preview should be generated for text messages containing links\n  ```java\n  .textPreviewSetting(TextPreviewSetting.ENABLED_WITH_INFERENCE)\n  ```\n- checkPatchMacs - Whether patch macs coming from app state pulls should be validated\n  ```java\n  .checkPatchMacs(checkPatchMacs)\n  ```\n- proxy - The proxy to use for the socket connection\n  ```java\n  .proxy(someProxy)\n  ```\n\nThere are also platform specific options:\n1. Web\n    - historyLength: The amount of messages to sync from the companion device\n      ```java\n      .historyLength(WebHistoryLength.THREE_MONTHS)\n      ```\n2. Mobile\n    - device: the device you want to fake:\n      ```java\n      .device(CompanionDevice.android(false)) // Standard Android\n      .device(CompanionDevice.android(true)) //Business android\n      .device(CompanionDevice.ios(false)) // Standard iOS\n      .device(CompanionDevice.ios(true)) // Business iOS\n      .device(CompanionDevice.kaiOs()) // Standard KaiOS\n       ```\n    - businessCategory: the category of your business account\n      ```java\n      .businessCategory(new BusinessCategory(id, name))\n       ```\n    - businessEmail: the email of your business account\n      ```java\n      .businessEmail(\"email@domanin.com\")\n       ```\n    - businessWebsite: the website of your business account\n      ```java\n      .businessWebsite(\"https://google.com\")\n       ```\n    - businessDescription: the description of your business account\n      ```java\n      .businessDescription(\"A nice description\")\n       ```\n    - businessLatitude: the latitude of your business account\n      ```java\n      .businessLatitude(37.386051)\n       ```\n    - businessLongitude: the longitude of your business account\n      ```java\n      .businessLongitude(-122.083855)\n       ```\n    - businessAddress: the address of your business account\n      ```java\n      .businessAddress(\"1600 Amphitheatre Pkwy, Mountain View\")\n       ```\n\n\u003e **_IMPORTANT:_** All options are serialized: there is no need to specify them again when deserializing an existing session\n\nFinally select the registration status of your session:\n- Creates a new registered session: this means that the QR code was already scanned / the OTP was already sent to Whatsapp\n  ```java\n  .registered()\n  ```\n- Creates a new unregistered session: this means that the QR code wasn't scanned / the OTP wasn't sent to the companion's phone via SMS/Call/OTP\n\n  If you are using the Web API, you can either register via QR code:\n  ```java\n  .unregistered(QrHandler.toTerminal())\n  ```  \n  or with a pairing code(new feature):\n  ```java\n  .unregistered(yourPhoneNumberWithCountryCode, PairingCodeHandler.toTerminal())\n  ```  \n  Otherwise, if you are using the mobile API, you can decide if you want to receive an SMS, a call or an OTP:\n  ```java\n  .verificationCodeMethod(VerificationCodeMethod.SMS)\n  ```  \n  Then provide a supplier for that verification method:\n  ```java\n  .verificationCodeSupplier(() -\u003e yourAsyncOrSyncLogic())\n  ```\n  Finally, register:\n  ```java\n  .register(yourPhoneNumberWithCountryCode)\n  ```\n\nNow you can connect to your session:\n  ```java\n  .connect()\n  ```\nto connect to Whatsapp.\nRemember to handle the result using, for example, `join` to await the connection's result.\nFinally, if you want to pause the current thread until the connection is closed, use:\n  ```java\n  .awaitDisconnection()\n  ```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eWeb QR Pairing Example\u003c/summary\u003e\n\n  ```java\n  Whatsapp.webBuilder() // Use the Web api\n        .lastConnection() // Deserialize the last connection, or create a new one if it doesn't exist\n        .unregistered(QrHandler.toTerminal()) // Print the QR to the terminal\n        .addLoggedInListener(api -\u003e System.out.printf(\"Connected: %s%n\", api.store().privacySettings())) // Print a message when connected\n        .addDisconnectedListener(reason -\u003e System.out.printf(\"Disconnected: %s%n\", reason)) // Print a message when disconnected\n        .addNewChatMessageListener(message -\u003e System.out.printf(\"New message: %s%n\", message.toJson())) // Print a message when a new chat message arrives\n        .connect() // Connect to Whatsapp asynchronously\n        .join() // Await the result\n        .awaitDisconnection(); // Wait \n  ```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eWeb Pairing Code Example\u003c/summary\u003e\n\n  ```java\n  System.out.println(\"Enter the phone number(include the country code prefix, but no +, spaces or parenthesis):\")\n  var scanner = new Scanner(System.in);\n  var phoneNumber = scanner.nextLong();\n  Whatsapp.webBuilder() // Use the Web api\n        .lastConnection() // Deserialize the last connection, or create a new one if it doesn't exist\n        .unregistered(phoneNumber, PairingCodeHandler.toTerminal()) // Print the pairing code to the terminal\n        .addLoggedInListener(api -\u003e System.out.printf(\"Connected: %s%n\", api.store().privacySettings())) // Print a message when connected\n        .addDisconnectedListener(reason -\u003e System.out.printf(\"Disconnected: %s%n\", reason)) // Print a message when disconnected\n        .addNewChatMessageListener(message -\u003e System.out.printf(\"New message: %s%n\", message.toJson())) // Print a message when a new chat message arrives\n        .connect() // Connect to Whatsapp asynchronously\n        .join() // Await the result\n        .awaitDisconnection(); // Wait \n  ```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eMobile Example\u003c/summary\u003e\n\n  ```java\n  System.out.println(\"Enter the phone number(include the country code prefix, but no +, spaces or parenthesis):\")\n  var scanner = new Scanner(System.in);\n  var phoneNumber = scanner.nextLong();\n  Whatsapp.mobileBuilder() // Use the Mobile api\n        .lastConnection() // Deserialize the last connection, or create a new one if it doesn't exist\n        .device(CompanionDevice.ios(false)) // Use a non-business iOS account\n        .unregistered() // If the connection was just created, it needs to be registered\n        .verificationCodeMethod(VerificationCodeMethod.SMS) // If the connection was just created, send an SMS OTP\n        .verificationCodeSupplier(() -\u003e { // Called when the OTP needs to be sent to Whatsapp\n            System.out.println(\"Enter OTP: \"); \n            var scanner = new Scanner(System.in);\n            return scanner.nextLine();\n        })\n        .register(phoneNumber) // Register the phone number asynchronously, if necessary\n        .join() // Await the result\n        .addLoggedInListener(api -\u003e System.out.printf(\"Connected: %s%n\", api.store().privacySettings())) // Print a message when connected\n        .addDisconnectedListener(reason -\u003e System.out.printf(\"Disconnected: %s%n\", reason)) // Print a message when disconnected\n        .addNewChatMessageListener(message -\u003e System.out.printf(\"New message: %s%n\", message.toJson())) // Print a message when a new chat message arrives\n        .connect() // Connect to Whatsapp asynchronously\n        .join() // Await the result\n        .awaitDisconnection(); // Wait \n  ```\n\u003c/details\u003e\n\n### How to close a connection\n\nThere are three ways to close a connection:\n\n1. Disconnect\n   \n   ```java\n   api.disconnect();\n   ```\n   \u003e **_IMPORTANT:_** The session remains valid for future uses\n\n2. Reconnect\n\n   ```java\n   api.reconnect();\n   ```\n   \u003e **_IMPORTANT:_** The session remains valid for future uses\n\n3. Log out\n\n   ```java\n   api.logout();\n   ```\n   \u003e **_IMPORTANT:_** The session doesn't remain valid for future uses\n\n### What is a listener and how to register it\n\nListeners are crucial to handle events related to Whatsapp and implement logic for your application.\nListeners can be used either as:\n\n1. Standalone concrete implementation\n   \n   If your application is complex enough, \n   it's preferable to divide your listeners' logic across multiple specialized classes.\n   To create a new concrete listener, declare a class or record that implements the Listener interface:\n\n   ```java\n   import it.auties.whatsapp.listener.Listener;\n\n   public class MyListener implements Listener {\n    @Override\n    public void onLoggedIn() {\n        System.out.println(\"Hello :)\");\n    }\n   }\n   ```\n\n   Remember to manually register this listener:\n\n   ```java\n   api.addListener(new MyListener());\n   ```\n\n   Or to register it automatically using the `@RegisterListener` annotation:\n\n   ```java\n   import it.auties.whatsapp.listener.RegisterListener;\n   import it.auties.whatsapp.listener.Listener;\n\n   @RegisterListener // Automatically registers this listener\n   public class MyListener implements Listener {\n    @Override\n    public void onLoggedIn() {\n        System.out.println(\"Hello :)\");\n    }\n   }\n   ```\n   \n   Listeners often need access to the Whatsapp instance that registered them to, for example, send messages. \n   If your listener is marked with @RegisterListener and a single argument constructor that takes a Whatsapp instance as a parameter exists,\n   the latter can be injected automatically, regardless of if your implementation uses a class or a record.\n   Records, though, are usually more elegant:\n\n   ```java\n   import it.auties.whatsapp.listener.RegisterListener;\n   import it.auties.whatsapp.api.Whatsapp;\n   import it.auties.whatsapp.listener.Listener;\n\n   @RegisterListener // Automatically registers this listener\n   public record MyListener(Whatsapp api) implements Listener { // A non-null whatsapp instance is injected\n    @Override\n    public void onLoggedIn() {\n        System.out.println(\"Hello :)\");\n    }\n   }\n   ```\n   \n   \u003e **_IMPORTANT:_** @RegisterListener will only work if you register the annotation processor provided by Cobalt\n\n2. Functional interface\n   \n   If your application is very simple or only requires this library in small operations, \n   it's preferable to add a listener using a lambda instead of using full-fledged classes.\n   To declare a new functional listener, call the method add followed by the name of the listener that you want to implement without the on suffix:\n   ```java\n   api.addDisconnectedListener(reason -\u003e System.out.println(\"Goodbye: \" + reason));\n   ```\n\n   All lambda listeners can access the instance of `Whatsapp` that called them: \n   ```java\n   api.addDisconnectedListener((whatsapp, reason) -\u003e System.out.println(\"Goodbye: \" + reason));\n   ```\n\n   This is extremely useful if you want to implement a functionality for your application in a compact manner:\n   ```java\n    Whatsapp.newConnection()\n                .addLoggedInListener(() -\u003e System.out.println(\"Connected\"))\n                .addNewMessageListener((whatsapp, info) -\u003e whatsapp.sendMessage(info.chatJid(), \"Automatic answer\", info))\n                .connect()\n                .join();\n   ```\n\n### How to handle serialization\n\nIn the original version of WhatsappWeb, chats, contacts and messages could be queried at any from Whatsapp's servers.\nThe multi-device implementation, instead, sends all of this information progressively when the connection is initialized for the first time and doesn't allow any subsequent queries to access the latter.\nIn practice, this means that this data needs to be serialized somewhere.\nThe same is true for the mobile api.\n\nBy default, this library serializes data regarding a session at `$HOME/.whatsapp4j/[web|mobile]/\u003csession_id\u003e`.\nThe data is stored in gzipped .smile files to reduce disk usage. \n\nIf your application needs to serialize data in a different way, for example in a database create a custom implementation of ControllerSerializer.\nThen make sure to specify your implementation in the `Whatsapp` builder.\nThis is explained in the \"How to create a connection\" section.\n\n### How to handle session disconnects\n\nWhen the session is closed, the onDisconnect method in any listener is invoked.\nThese are the three reasons that can cause a disconnect:\n\n1. DISCONNECTED\n\n    A normal disconnection.\n    This doesn't indicate any error being thrown.\n\n2. RECONNECT\n\n    The client is being disconnected but only to reopen the connection.\n    This always happens when the QR is first scanned for example.\n\n3. LOGGED_OUT\n\n    The client was logged out by itself or by its companion.\n    By default, no error is thrown if this happens, though this behaviour can be changed easily:\n    ```java\n    import it.auties.whatsapp.api.DisconnectReason;\n    import it.auties.whatsapp.listener.Listener;\n\n    class ThrowOnLogOut implements Listener {\n        @Override\n        public void onDisconnected(DisconnectReason reason) {\n            if (reason != SocketEvent.LOGGED_OUT) {\n                return;\n            }\n\n            throw new RuntimeException(\"Hey, I was logged off :/\");\n        }\n    }\n    ```\n\n### How to query chats, contacts, messages and status\n\nAccess the store associated with a connection by calling the store method:\n```java\nvar store = api.store();\n```\n\n\u003e **_IMPORTANT:_** When your program first starts up, these fields will be empty. For each type of data, an event is\n\u003e fired and listenable using a WhatsappListener\n\nYou can access all the chats that are in memory:\n\n```java\nvar chats = store.chats();\n```\n\nOr the contacts:\n\n```java\nvar contacts = store.contacts();\n```\n\nOr even the status:\n\n```java\nvar status = store.status();\n```\n\nData can also be easily queried by using these methods:\n\n- Chats\n   - Query a chat by its jid\n     ```java\n     var chat = store.findChatByJid(jid);\n     ```\n  - Query a chat by its name\n    ```java\n    var chat = store.findChatByName(name);\n    ```  \n  - Query a chat by a message inside it\n    ```java\n    var chat = store.findChatByMessage(message);\n    ```   \n  - Query all chats that match a name\n    ```java\n    var chats = store.findChatsByName(name);\n    ```  \n- Contacts\n   - Query a contact by its jid\n     ```java\n     var chat = store.findContactByJid(jid);\n     ```  \n  - Query a contact by its name\n    ```java\n    var contact = store.findContactByName(name);\n    ```\n   - Query all contacts that match a name\n     ```java\n     var contacts = store.findContactsByName(name);\n     ```     \n- Media status\n  - Query status by sender\n    ```java\n    var chat = store.findStatusBySender(contact);\n    ```  \n\n### How to query other data\n\nTo access information about the companion device:\n```java\nvar companion = store.jid();\n```\nThis object is a jid like any other, but it has the device field filled to distinguish it from the main one.\nInstead, if you only need the phone number:\n```java\nvar phoneNumber = store.jid().toPhoneNumber();\n```\nAll the settings and metadata about the companion is available inside the Store class\n```java\nvar store = api.store();\n```\nExplore of the available methods!\n\n### How to query cryptographic data\n\nAccess keys store associated with a connection by calling the keys method:\n```java\nvar keys = api.keys();\n```\nThere are several methods to access and query cryptographic data, but as it's only necessary for advanced users, \nplease check the javadocs if this is what you need.\n\n### How to send messages\n\nTo send a message, start by finding the chat where the message should be sent. Here is an example:\n\n```java\nvar chat = api.store()\n        .findChatByName(\"My Awesome Friend\")\n        .orElseThrow(() -\u003e new NoSuchElementException(\"Hey, you don't exist\"));\n``` \n\nAll types of messages supported by Whatsapp are supported by this library:\n\u003e **_IMPORTANT:_** Buttons are not documented here because they are unstable.\n\u003e If you are interested you can try to use them, but they are not guaranteed to work.\n\u003e There are some examples in the tests directory.\n\n- Text\n\n    ```java\n    api.sendMessage(chat,  \"This is a text message!\");\n    ```\n\n- Complex text\n\n    ```java\n    var message = new TextMessageBuilder() // Create a new text message\n            .text(\"Check this video out: https://www.youtube.com/watch?v=dQw4w9WgXcQ\") // Set the text of the message\n            .canonicalUrl(\"https://www.youtube.com/watch?v=dQw4w9WgXcQ\") // Set the url of the message\n            .matchedText(\"https://www.youtube.com/watch?v=dQw4w9WgXcQ\") // Set the matched text for the url in the message\n            .title(\"A nice suprise\") // Set the title of the url\n            .description(\"Check me out\") // Set the description of the url\n            .build(); // Create the message\n    api.sendMessage(chat,  message); \n    ```\n\n- Location\n\n    ```java\n    var location = new LocationMessageBuilder() // Create a new location message\n            .caption(\"Look at this!\") // Set the caption of the message, that is the text below the file\n            .latitude(38.9193) // Set the longitude of the location to share\n            .longitude(1183.1389) // Set the latitude of the location to share\n            .build(); // Create the message\n    api.sendMessage(chat, location);\n    ```\n\n- Live location\n\n    ```java\n    var location = new LiveLocationMessageBuilder() // Create a new live location message\n            .caption(\"Look at this!\") // Set the caption of the message, that is the text below the file. Not available if this message is live\n            .latitude(38.9193) // Set the longitude of the location to share\n            .longitude(1183.1389) // Set the latitude of the location to share\n            .accuracy(10) // Set the accuracy of the location in meters\n            .speed(12) // Set the speed of the device sharing the location in meter per endTimeStamp\n            .build(); // Create the message\n    api.sendMessage(chat, location);\n    ```\n  \u003e **_IMPORTANT:_** Live location updates are not supported by Whatsapp multi-device. No ETA has been given for a fix.\n\n- Group invite\n    ```java\n    var group = api.store()\n            .findChatByName(\"Programmers\")\n            .filter(Chat::isGroup)\n            .orElseThrow(() -\u003e new NoSuchElementException(\"Hey, you don't exist\"));\n    var inviteCode = api.queryGroupInviteCode(group).join();\n    var groupInvite = new GroupInviteMessageBuilder() // Create a new group invite message\n            .caption(\"Come join my group of fellow programmers\") // Set the caption of this message\n            .name(group.name()) // Set the name of the group\n            .groupJid(group.jid())) // Set the jid of the group\n            .inviteExpiration(ZonedDateTime.now().plusDays(3).toEpochSecond()) // Set the expiration of this invite\n            .inviteCode(inviteCode) // Set the code of the group\n            .build(); // Create the message\n    api.sendMessage(chat, groupInvite); \n    ```\n\n- Contact\n    ```java\n     var vcard = new ContactCardBuilder() // Create a new vcard\n            .name(\"A nice friend\") // Set the name of the contact\n            .phoneNumber(contact) // Set the phone number of the contact\n            .build(); // Create the vcard\n    var contactMessage = new ContactMessageBuilder()  // Create a new contact message\n            .name(\"A nice friend\") // Set the display name of the contact\n            .vcard(vcard) // Set the vcard(https://en.wikipedia.org/wiki/VCard) of the contact\n            .build(); // Create the message\n    api.sendMessage(chat, contactMessage);\n    ```\n\n- Contact array\n\n    ```java\n    var contactsMessage = new ContactsArrayMessageBuilder()  // Create a new contacts array message\n            .name(\"A nice friend\") // Set the display name of the first contact that this message contains\n            .contacts(List.of(jack,lucy,jeff)) // Set a list of contact messages that this message wraps\n            .build(); // Create the message\n    api.sendMessage(chat, contactsMessage);\n    ```\n\n- Media\n\n    \u003e **_IMPORTANT:_**\n    \u003e \n    \u003e The thumbnail for videos and gifs is generated automatically only if ffmpeg is installed on the host machine.\n    \u003e \n    \u003e The length of videos, gifs and audios in seconds is computed automatically only if ffprobe is installed on the host machine.\n\n    To send a media, start by reading the content inside a byte array.\n    You might want to read it from a file:\n\n    ```java\n    var media = Files.readAllBytes(Path.of(\"somewhere\"));\n    ```\n\n    Or from a URL:\n\n    ```java\n    var media = new URL(url).openStream().readAllBytes();\n    ```\n   \n   All medias supported by Whatsapp are supported by this library:\n\n   - Image\n  \n     ```java\n     var image = new ImageMessageSimpleBuilder() // Create a new image message builder\n           .media(media) // Set the image of this message\n           .caption(\"A nice image\") // Set the caption of this message\n           .build(); // Create the message\n     api.sendMessage(chat,  image);\n     ```\n\n  - Audio or voice\n\n    ```java\n     var audio = new AudioMessageSimpleBuilder() // Create a new audio message builder\n           .media(urlMedia) // Set the audio of this message\n           .voiceMessage(false) // Set whether this message is a voice message\n           .build(); // Create the message\n     api.sendMessage(chat,  audio);\n    ```\n\n  -  Video\n\n     ```java\n     var video = new VideoMessageSimpleBuilder() // Create a new video message builder\n           .media(urlMedia) // Set the video of this message\n           .caption(\"A nice video\") // Set the caption of this message\n           .width(100) // Set the width of the video\n           .height(100) // Set the height of the video\n           .build(); // Create the message\n     api.sendMessage(chat,  video); \n     ```\n     \n  -  GIF(Video)\n\n     ```java\n     var gif = new GifMessageSimpleBuilder() // Create a new gif message builder\n           .media(urlMedia) // Set the gif of this message\n           .caption(\"A nice gif\") // Set the caption of this message\n           .gifAttribution(VideoMessageAttribution.TENOR) // Set the source of the gif\n           .build(); // Create the message\n     api.sendMessage(chat,  gif);\n     ```\n     \u003e **_IMPORTANT:_** Whatsapp doesn't support conventional gifs. Instead, videos can be played as gifs if particular attributes are set. Sending a conventional gif will result in an exception if detected or in undefined behaviour.\n\n  -  Document\n\n     ```java\n     var document = new DocumentMessageSimpleBuilder() // Create a new document message builder\n           .media(urlMedia) // Set the document of this message\n           .title(\"A nice pdf\") // Set the title of the document\n           .fileName(\"pdf-test.pdf\") // Set the name of the document\n           .pageCount(1) // Set the number of pages of the document\n           .build(); // Create the message\n     api.sendMessage(chat,  document);\n     ```\n- Reaction\n\n    - Send a reaction\n\n    ```java\n    var someMessage = ...; // The message to react to\n    api.sendReaction(someMessage, Emoji.RED_HEART); // Use the Emoji class for a list of all Emojis\n    ```\n\n    - Remove a reaction\n\n    ```java\n    var someMessage = ...; // The message to react to\n    api.removeReaction(someMessage); // Use the Emoji class for a list of all Emojis\n    ```\n\n### How to wait for replies\n\nIf you want to wait for a single reply, use:\n``` java\nvar response = api.awaitReply(info).join(); \n```\n\nYou can also register a listener, but in many cases the async/await paradigm is easier to use then callback based listeners.\n\n### How to delete messages\n\n``` java\nvar result = api.delete(someMessage, everyone); // Deletes a message for yourself or everyone\n```\n\n### How to change your status\n\nTo change the status of the client:\n\n``` java\napi.changePresence(true); // online\napi.changePresence(false); // offline\n```\n\nIf you want to change the status of your companion, start by choosing the right presence:\nThese are the allowed values:\n\n- AVAILABLE\n- UNAVAILABLE\n- COMPOSING\n- RECORDING\n\nThen, execute this method:\n\n``` java\napi.changePresence(chat,  presence);\n```\n\n\u003e **_IMPORTANT:_** The changePresence method returns a CompletableFuture: remember to handle this async construct if\n\u003e needed\n\n### How to query the last known presence for a contact\n\nTo query the last known status of a Contact, use the following snippet:\n\n``` java\nvar lastKnownPresenceOptional = contact.lastKnownPresence();\n```\n\nIf the returned value is an empty Optional, the last status of the contact is unknown.\n\nWhatsapp starts sending updates regarding the presence of a contact only when:\n\n- A message was recently exchanged between you and said contact\n- A new message arrives from said contact\n- You send a message to said contact\n\nTo force Whatsapp to send these updates use:\n\n``` java\napi.subscribeToPresence(contact);\n```\n\nThen, after the subscribeToUserPresence's future is completed, query again the presence of that contact.\n\n### Query data about a group, or a contact\n\n##### About\n\n``` java\nvar status = api.queryAbout(contact) // A completable future\n      .join() // Wait for the future to complete\n      .flatMap(ContactAboutResponse::about) // Map the response to its status\n      .orElse(null); // If no status is available yield null\n```\n\n##### Profile picture or chat picture\n\n``` java\nvar picture = api.queryPicture(contact) // A completable future\n      .join() // Wait for the future to complete\n      .orElse(null); // If no picture is available yield null\n```\n\n##### Group's Metadata\n\n``` java\nvar metadata = api.queryGroupMetadata(group); // A completable future\n      .join(); // Wait for the future to complete\n```\n\n### Search messages\n\n``` java\nvar messages = chat.messages(); // All the messages in a chat\nvar firstMessage = chat.firstMessage(); // First message in a chat chronologically\nvar lastMessage = chat.lastMessage(); // Last message in a chat chronologically \nvar starredMessages = chat.starredMessages(); // All the starred messages in a chat\n```\n\n### Change the state of a chat\n\n##### Mute a chat\n\n``` java\nvar future = api.muteChat(chat);\n```\n\n##### Unmute a chat\n\n``` java\nvar future = api.unmuteChat(chat);\n```\n\n##### Archive a chat\n\n``` java\nvar future = api.archiveChat(chat);\n```\n\n##### Unarchive a chat\n\n``` java\nvar future = api.unarchiveChat(chat);\n```\n\n##### Change ephemeral message status in a chat\n\n``` java\nvar future = api.changeEphemeralTimer(chat,  ChatEphemeralTimer.ONE_WEEK);\n```   \n\n##### Mark a chat as read\n\n``` java\nvar future = api.markChatRead(chat);\n```   \n\n##### Mark a chat as unread\n\n``` java\nvar future = api.markChatUnread(chat);\n```   \n\n##### Pin a chat\n\n``` java\nvar future = api.pinChat(chat);\n``` \n\n##### Unpin a chat\n\n``` java\nvar future = api.unpinChat(chat);\n```\n\n##### Clear a chat\n\n``` java\nvar future = api.clearChat(chat, false);\n```\n\n##### Delete a chat\n\n``` java\nvar future = api.deleteChat(chat);\n```\n\n### Change the state of a participant of a group\n\n##### Add a contact to a group\n\n``` java\nvar future = api.addGroupParticipant(group, contact);\n```\n\n##### Remove a contact from a group\n\n``` java\nvar future = api.removeGroupParticipant(group, contact);\n```\n\n##### Promote a contact to admin in a group\n\n``` java\nvar future = api.promoteGroupParticipant(group, contact);\n```\n\n##### Demote a contact to user in a group\n\n``` java\nvar future = api.demoteGroupParticipant(group, contact);\n```\n\n### Change the metadata or settings of a group\n\n##### Change group's name/subject\n\n``` java\nvar future = api.changeGroupSubject(group, newName);\n```\n\n##### Change or remove group's description\n\n``` java\nvar future = api.changeGroupDescription(group, newDescription);\n```\n\n##### Change a setting in a group\n\n``` java\nvar future = api.changeGroupSetting(group, GroupSetting.EDIT_GROUP_INFO, GroupPolicy.ANYONE);\n```\n\n##### Change or remove the picture of a group\n\n``` java\nvar future = api.changeGroupPicture(group, img);\n```\n\n### Other group related methods\n\n##### Create a group\n\n``` java\nvar future = api.createGroup(\"A nice name :)\", friend, friend2);\n```\n\n##### Leave a group\n\n``` java\nvar future = api.leaveGroup(group);\n```\n\n##### Query a group's invite code\n\n``` java\nvar future = api.queryGroupInviteCode(group);\n```\n\n##### Revoke a group's invite code\n\n``` java\nvar future = api.revokeGroupInvite(group);\n```\n\n##### Accept a group invite\n\n``` java\nvar future = api.acceptGroupInvite(inviteCode);\n```\n\n### Companions (Mobile api only)\n\n##### Link a companion\n\n``` java\nvar future = api.linkCompanion(qrCode);\n```\n\n##### Unlink a companion\n\n``` java\nvar future = api.unlinkCompanion(companionJid);\n```\n\n##### Unlink all companions\n\n``` java\nvar future = api.unlinkCompanions();\n```\n\n### 2FA (Mobile api only)\n\n##### Enable 2FA\n\n``` java\nvar future = api.enable2fa(\"000000\", \"mail@domain.com\");\n```\n\n##### Disable 2FA\n\n``` java\nvar future = api.disable2fa();\n```\n\n### Calls (Mobile api only)\n\n##### Start a call\n\n``` java\nvar future = api.startCall(contact);\n```\n\n\u003e **_IMPORTANT:_** Currently there is no audio/video support\n\n##### Stop or reject a call\n\n``` java\nvar future = api.stopCall(contact);\n```\n\n### Communities\n\n\u003e **_IMPORTANT:_** Fully supported, but not documented here. Check the Javadocs.\n\n### Newsletters / Channels\n\n\u003e **_IMPORTANT:_** Fully supported, but not documented here. Check the Javadocs.\n\nSome methods may not be listed here, all contributions are welcomed to this documentation!\nSome methods may not be supported on the mobile api, please report them, so I can fix them.\nIdeally I'd like all of them to work.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fauties00%2Fcobalt","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fauties00%2Fcobalt","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fauties00%2Fcobalt/lists"}