diff --git a/README.md b/README.md index 813e892..6fda20e 100644 --- a/README.md +++ b/README.md @@ -58,9 +58,7 @@ greenAPI = API.GreenAPI( ### Sending a text message to a WhatsApp number -Link to example: [sendTextMessage.py]( -https://github.com/green-api/whatsapp-api-client-python/blob/master/examples/sendTextMessage.py -). +Link to example: [sendTextMessage.py](./examples/sync/sendTextMessage.py). ``` response = greenAPI.sending.sendMessage("11001234567@c.us", "Message text") @@ -68,11 +66,23 @@ response = greenAPI.sending.sendMessage("11001234567@c.us", "Message text") print(response.data) ``` +### Sending a text message asynchronously + +Link to example: [sendMessageAsync.py](./examples/async/sending/sendMessageAsync.py). + +``` +import asyncio + +async def main(): + response = await greenAPI.sending.sendMessageAsync("11001234567@c.us", "Message text") + print(response.data) + +asyncio.run(main()) +``` + ### Sending an image via URL -Link to example: [sendPictureByLink.py]( -https://github.com/green-api/whatsapp-api-client-python/blob/master/examples/sendPictureByLink.py -). +Link to example: [sendPictureByLink.py](./examples/sync/sendPictureByLink.py). ``` response = greenAPI.sending.sendFileByUrl( @@ -87,29 +97,44 @@ print(response.data) ### Sending an image by uploading from the disk -Link to example: [sendPictureByUpload.py]( -https://github.com/green-api/whatsapp-api-client-python/blob/master/examples/sendPictureByUpload.py -). +Link to example: [sendPictureByUpload.py](./examples/sync/sendPictureByUpload.py). ``` response = greenAPI.sending.sendFileByUpload( "11001234567@c.us", - "data/rates.png", - "rates.png", + "data/logo.jpg", + "logo.jpg", "Available rates" ) print(response.data) ``` +### Sending an image asynchronously by uploading from the disk + +Link to example: [sendFileByUploadAsync.py](./examples/async/sending/sendFileByUploadAsync.py). + +``` +import asyncio + +async def main(): + response = await greenAPI.sending.sendFileByUploadAsync( + "11001234567@c.us", + "data/logo.jpg", + "logo.jpg", + "Available rates" + ) + print(response.data) + +asyncio.run(main()) +``` + ### Group creation and sending a message to the group **Attention**. If one tries to create a group with a non-existent number, WhatsApp may block the sender's number. The number in the example is non-existent. -Link to example: [createGroupAndSendMessage.py]( -https://github.com/green-api/whatsapp-api-client-python/blob/master/examples/createGroupAndSendMessage.py -). +Link to example: [createGroupAndSendMessage.py](./examples/sync/createGroupAndSendMessage.py). ``` create_group_response = greenAPI.groups.createGroup( @@ -123,9 +148,7 @@ if create_group_response.code == 200: ### Receive incoming messages by HTTP API -Link to example: [receiveNotification.py]( -https://github.com/green-api/whatsapp-api-client-python/blob/master/examples/receiveNotification.py -). +Link to example: [receiveNotification.py](./examples/sync/receiveNotification.py). The general concept of receiving data in the GREEN API is described [here]( https://green-api.com/en/docs/api/receiving/ @@ -142,18 +165,27 @@ onEvent - your function which should contain parameters: | typeWebhook | received notification type (str) | | body | notification body (dict) | -Notification body types and formats can be found [here]( -https://green-api.com/en/docs/api/receiving/notifications-format/ -). +Notification body types and formats can be found [here](https://green-api.com/en/docs/api/receiving/notifications-format/). This method will be called when an incoming notification is received. Next, process notifications according to the business logic of your system. +### Receive incoming messages asynchronously by HTTP API + +Link to example: [receiveNotificationAsync.py](./examples/async/receiveNotificationAsync.py). + +``` +import asyncio + +async def main(): + await greenAPI.webhooks.startReceivingNotificationsAsync(onEvent) + +asyncio.run(main()) +``` + ### Sending a polling message -Link to example: [sendPoll.py]( -https://github.com/green-api/whatsapp-api-client-python/blob/master/examples/sendPoll.py -). +Link to example: [sendPoll.py](./examples/sync/sendPoll.py). ``` response = greenAPI.sending.sendPoll( @@ -171,7 +203,7 @@ print(response.data) ### Sending a text status -Link to example: [sendTextStatus.py](https://github.com/green-api/whatsapp-api-client-python/blob/master/examples/statusesMethods/sendTextStatus.py). +Link to example: [sendTextStatus.py](./examples/sync/statusesMethods/sendTextStatus.py). ``` response = greenAPI.statuses.sendTextStatus( @@ -185,16 +217,24 @@ print(response.data) ## Examples list -| Description | Module | -|----------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------| -| Example of sending text | [sendTextMessage.py](https://github.com/green-api/whatsapp-api-client-python/blob/master/examples/sendTextMessage.py) | -| Example of sending a picture by URL | [sendPictureByLink.py](https://github.com/green-api/whatsapp-api-client-python/blob/master/examples/sendPictureByLink.py) | -| Example of sending a picture by uploading from the disk | [sendPictureByUpload.py](https://github.com/green-api/whatsapp-api-client-python/blob/master/examples/sendPictureByUpload.py) | -| Example of a group creation and sending a message to the group | [createGroupAndSendMessage.py](https://github.com/green-api/whatsapp-api-client-python/blob/master/examples/createGroupAndSendMessage.py) | -| Example of incoming webhooks receiving | [receiveNotification.py](https://github.com/green-api/whatsapp-api-client-python/blob/master/examples/receiveNotification.py) | -| Example of sending a polling message | [sendPoll.py](https://github.com/green-api/whatsapp-api-client-python/blob/master/examples/sendPoll.py) | -| Example of sending a text status | [sendTextStatus.py](https://github.com/green-api/whatsapp-api-client-python/blob/master/examples/statusesMethods/sendTextStatus.py) | -| Example of creating instance | [CreateInstance.py](https://github.com/green-api/whatsapp-api-client-python/blob/master/examples/partherMethods/CreateInstance.py) | +| Description | Module | +|----------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------| +| Example of sending text | [sendTextMessage.py](./examples/sync/sendTextMessage.pyy) | +| Example of sending text asynchronously | [sendTextMessageAsync.py](./examples/async/sendMessageAsync.py) | +| Example of sending a picture by URL | [sendPictureByLink.py](./examples/sync/sendPictureByLink.py) | +| Example of sending a file by URL asynchronously | [sendFileByUrlAsync.py](./examples/async/sending/sendFileByUrlAsync.py) | +| Example of sending a picture by uploading from the disk | [sendPictureByUpload.py](./examples/sync/sendPictureByUpload.py) | +| Example of sending file by uploading from the disk asynchronously | [sendFileByUploadAsync.py](./examples/async/sending/sendFileByUploadAsync.py) | +| Example of a group creation and sending a message to the group | [createGroupAndSendMessage.py](./examples/sync/createGroupAndSendMessage.py) | +| Example of a group creation and sending a message to the group asynchronously | [createGroupAndSendMessageAsync.py](./examples/async/createGroupAndSendMessageAsync.py) | +| Example of incoming webhooks receiving | [receiveNotification.py](./examples/sync/receiveNotification.py) | +| Example of incoming webhooks receiving asynchronously | [receiveNotificationAsync.py](./examples/async/receiveNotificationAsync.py) | +| Example of sending a polling message | [sendPoll.py](./examples/sync/sendPoll.py) | +| Example of sending a polling message asynchronously | [sendPollAsync.py](./examples/async/sending/sendPollasync.py) | +| Example of sending a text status | [sendTextStatus.py](./examples/sync/statusesMethods/sendTextStatus.py) | +| Example of sending a text status asynchronously | [sendTextStatusAsync.py](./examples/async/statusesMethods/sendTextStatusAsync.py) | +| Example of creating instance | [CreateInstance.py](./examples/sync/partherMethods/CreateInstance.py) | +| Example of creating instance asynchronously | [CreateInstanceAsync.py](./examples/async/partherMethods/CreateInstanceAsync.py) | ## The full list of the library methods @@ -272,10 +312,9 @@ print(response.data) ## External products - [requests](https://requests.readthedocs.io/en/latest/) - for HTTP requests. +- [aiohttp](https://docs.aiohttp.org/) - for async HTTP requests. ## License -Licensed under [ -Creative Commons Attribution-NoDerivatives 4.0 International (CC BY-ND 4.0) -](https://creativecommons.org/licenses/by-nd/4.0/) terms. +Licensed under [Creative Commons Attribution-NoDerivatives 4.0 International (CC BY-ND 4.0)](https://creativecommons.org/licenses/by-nd/4.0/) terms. Please see file [LICENSE](https://github.com/green-api/whatsapp-api-client-python/blob/master/LICENSE). diff --git a/docs/README.md b/docs/README.md index 4b74f0d..827ad4c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -57,7 +57,7 @@ greenAPI = API.GreenAPI( ### Отправка текстового сообщения на номер WhatsApp -Ссылка на пример: [sendTextMessage.py](../examples/sendTextMessage.py). +Ссылка на пример: [sendTextMessage.py](../examples/sync/sendTextMessage.py). ``` response = greenAPI.sending.sendMessage("11001234567@c.us", "Message text") @@ -65,9 +65,24 @@ response = greenAPI.sending.sendMessage("11001234567@c.us", "Message text") print(response.data) ``` + +### Отправка текстового сообщения асинхронно + +Ссылка на пример: [sendMessageAsync.py](https://github.com/green-api/whatsapp-api-client-python/blob/master/examples/async/sending/sendMessageAsync.py). + +``` +import asyncio + +async def main(): + response = await greenAPI.sending.sendMessageAsync("11001234567@c.us", "Message text") + print(response.data) + +asyncio.run(main()) +``` + ### Отправка картинки по URL -Ссылка на пример: [sendPictureByLink.py](../examples/sendPictureByLink.py). +Ссылка на пример: [sendPictureByLink.py](../examples/sync/sendPictureByLink.py). ``` response = greenAPI.sending.sendFileByUrl( @@ -82,25 +97,45 @@ print(response.data) ### Отправка картинки загрузкой с диска -Ссылка на пример: [sendPictureByUpload.py](../examples/sendPictureByUpload.py). +Ссылка на пример: [sendPictureByUpload.py](../examples/sync/sendPictureByUpload.py). ``` response = greenAPI.sending.sendFileByUpload( "11001234567@c.us", - "data/rates.png", - "rates.png", + "data/logo.jpg", + "logo.jpg", "Available rates" ) print(response.data) ``` + +### Отправка картинки асинхронно загрузкой с диска + +Ссылка на пример: [sendFileByUploadAsync.py](../examples/async/sending/sendFileByUploadAsync.py). + +``` +import asyncio + +async def main(): + response = await greenAPI.sending.sendFileByUploadAsync( + "11001234567@c.us", + "data/logo.jpg", + "logo.jpg", + "Available rates" + ) + print(response.data) + +asyncio.run(main()) +``` + ### Создание группы и отправка сообщения в эту группу **Важно**. Если попытаться создать группу с несуществующим номером WhatsApp, то может заблокировать номер отправителя. Номер в примере не существует. -Ссылка на пример: [createGroupAndSendMessage.py](../examples/createGroupAndSendMessage.py). +Ссылка на пример: [createGroupAndSendMessage.py](../examples/sync/createGroupAndSendMessage.py). ``` create_group_response = greenAPI.groups.createGroup( @@ -114,7 +149,7 @@ if create_group_response.code == 200: ### Получение входящих уведомлений через HTTP API -Ссылка на пример: [receiveNotification.py](../examples/receiveNotification.py). +Ссылка на пример: [receiveNotification.py](../examples/sync/receiveNotification.py). Общая концепция получения данных в GREEN API описана [здесь](https://green-api.com/docs/api/receiving/). Для старта получения уведомлений через HTTP API требуется выполнить метод библиотеки: @@ -135,9 +170,23 @@ onEvent - ваша функция, которая должен содержат Эта функция будет вызываться при получении входящего уведомления. Далее обрабатываете уведомления согласно бизнес-логике вашей системы. + +### Асинхронное получение входящих уведомлений через HTTP API + +Ссылка на пример: [receiveNotificationAsync.py](../examples/async/receiveNotificationAsync.py). + +``` +import asyncio + +async def main(): + await greenAPI.webhooks.startReceivingNotificationsAsync(onEvent) + +asyncio.run(main()) +``` + ### Отправка сообщения с опросом -Ссылка на пример: [sendPoll.py](../examples/sendPoll.py). +Ссылка на пример: [sendPoll.py](../examples/sync/sendPoll.py). ``` response = greenAPI.sending.sendPoll( @@ -155,7 +204,7 @@ print(response.data) ## Отправка текстового статуса -Ссылка на пример: [sendPoll.py](https://github.com/green-api/whatsapp-api-client-python/blob/master/examples/statusesMethods/sendTextStatus.py). +Ссылка на пример: [sendPoll.py](../examples/sync/statusesMethods/sendTextStatus.py). ``` response = greenAPI.statuses.sendTextStatus( @@ -169,19 +218,28 @@ print(response.data) ## Список примеров -| Описание | Модуль | -|------------------------------------------------------|--------------------------------------------------------------------------| -| Пример отправки текста | [sendTextMessage.py](../examples/sendTextMessage.py) | -| Пример отправки картинки по URL | [sendPictureByLink.py](../examples/sendPictureByLink.py) | -| Пример отправки картинки загрузкой с диска | [sendPictureByUpload.py](../examples/sendPictureByUpload.py) | -| Пример создание группы и отправка сообщения в группу | [createGroupAndSendMessage.py](../examples/createGroupAndSendMessage.py) | -| Пример получения входящих уведомлений | [receiveNotification.py](../examples/receiveNotification.py) | -| Пример отправки сообщения с опросом | [sendPoll.py](../examples/sendPoll.py) | -| Пример отправки текстового статуса | [sendTextStatus.py](https://github.com/green-api/whatsapp-api-client-python/blob/master/examples/statusesMethods/sendTextStatus.py) | -| Пример создания инстанса | [CreateInstance.py](https://github.com/green-api/whatsapp-api-client-python/blob/master/examples/partherMethods/CreateInstance.py) | +| Описание | Модуль | +|--------------------------------------------------------|-----------------------------------------------------------------------------------| +| Пример отправки текста | [sendTextMessage.py](../examples/sync/sendTextMessage.py) | +| Пример асинхронной отправки текста | [sendTextMessageAsync.py](../examples/async/sending/sendMessageAsync.py) | +| Пример отправки картинки по URL | [sendPictureByLink.py](../examples/sync/sendPictureByLink.py) | +| Пример асинхронной отправки файла по URL | [sendFileByUrlAsync.py](../examples/async/sending/sendFileByUrlAsync.py) | +| Пример отправки картинки загрузкой с диска | [sendPictureByUpload.py](../examples/sync/sendPictureByUpload.py) | +| Пример асинхронной отправки картинки загрузкой с диска | [sendFileByUploadAsync.py](../examples/async/sending/sendFileByUploadAsync.py) | +| Пример создания группы и отправки сообщения в группу | [createGroupAndSendMessage.py](../examples/sync/createGroupAndSendMessage.py) | +| Пример асинхронных создания группы и отправки сообщения в группу | [createGroupAndSendMessageAsync.py](../examples/async/createGroupAndSendMessageAsync.py) | +| Пример получения входящих уведомлений | [receiveNotification.py](../examples/sync/receiveNotification.py) | +| Пример асинхронного получения входящих уведомлений | [receiveNotificationФынтс.py](../examples/async/receiveNotificationAsync.py) | +| Пример отправки сообщения с опросом | [sendPoll.py](../examples/sync/sendPoll.py) | +| Пример асинхронной отправки сообщения с опросом | [sendPollAsync.py](../examples/async/sending/sendPollAsync.py) | +| Пример отправки текстового статуса | [sendTextStatus.py](../examples/sync/statusesMethods/sendTextStatus.py) | +| Пример асинхронной отправки текстового статуса | [sendTextStatusAsync.py](../examples/async/statusesMethods/sendTextStatusAsync.py) | +| Пример создания инстанса | [CreateInstance.py](../examples/sync/partherMethods/CreateInstance.py) | +| Пример асинхронного создания инстанса | [CreateInstanceAsync.py](../examples/async/partnerMethods/CreateInstanceAsync.py) | + ## Полный список методов библиотеки -| Метод API | Описание | Documentation link | +| Метод API | Описание | Ссылка на документацию | |----------------------------------------|---------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------| | `account.getSettings` | Метод предназначен для получения текущих настроек аккаунта | [GetSettings](https://green-api.com/docs/api/account/GetSettings/) | | `account.getWaSettings` | Метод предназначен для получения информации о аккаунте WhatsApp | [GetWaSettings](https://green-api.com/docs/api/account/GetWaSettings/) | @@ -247,10 +305,9 @@ print(response.data) ## Сторонние продукты - [requests](https://requests.readthedocs.io/en/latest/) - для HTTP запросов. +- [aiohttp](https://docs.aiohttp.org/) - для асинхронных HTTP запросов. ## Лицензия -Лицензировано на условиях [ -Creative Commons Attribution-NoDerivatives 4.0 International (CC BY-ND 4.0) -](https://creativecommons.org/licenses/by-nd/4.0/). +Лицензировано на условиях [Creative Commons Attribution-NoDerivatives 4.0 International (CC BY-ND 4.0)](https://creativecommons.org/licenses/by-nd/4.0/). [LICENSE](../LICENSE). diff --git a/examples/async/accountMethodsAsync.py b/examples/async/accountMethodsAsync.py new file mode 100644 index 0000000..132301c --- /dev/null +++ b/examples/async/accountMethodsAsync.py @@ -0,0 +1,32 @@ +import asyncio +from whatsapp_api_client_python import API + +greenAPI = API.GreenAPI( + "1101000001", "d75b3a66374942c5b3c019c698abc2067e151558acbd412345" +) + +async def main(): + response = await greenAPI.account.getSettingsAsync() + print(response.data) if response.code == 200 else print(response.error) + + response = await greenAPI.account.getWaSettingsAsync() + print(response.data) if response.code == 200 else print(response.error) + + settings = {"outgoingWebhook": "yes", "incomingWebhook": "yes"} + response = await greenAPI.account.setSettingsAsync(settings) + print(response.data) if response.code == 200 else print(response.error) + + response = await greenAPI.account.getStateInstanceAsync() + print(response.data) if response.code == 200 else print(response.error) + + response = await greenAPI.account.rebootAsync() + print(response.data) if response.code == 200 else print(response.error) + + response = await greenAPI.account.qrAsync() + print(response.data) if response.code == 200 else print(response.error) + + response = await greenAPI.account.getAuthorizationCodeAsync(79876543210) + print(response.data) if response.code == 200 else print(response.error) + +if __name__ == '__main__': + asyncio.run(main()) \ No newline at end of file diff --git a/examples/async/createGroupAndSendMessageAsync.py b/examples/async/createGroupAndSendMessageAsync.py new file mode 100644 index 0000000..03ca892 --- /dev/null +++ b/examples/async/createGroupAndSendMessageAsync.py @@ -0,0 +1,25 @@ +import asyncio +from whatsapp_api_client_python import API + +greenAPI = API.GreenAPI( + "1101000001", "d75b3a66374942c5b3c019c698abc2067e151558acbd412345" +) + +async def main(): + create_group_response = await greenAPI.groups.createGroupAsync( + "Async group", ["11001234567@c.us", "11001234568@c.us"] + ) + + if create_group_response.code == 200: + print(create_group_response.data) + + chat_id = create_group_response.data["chatId"] + send_message_response = await greenAPI.sending.sendMessageAsync( + chat_id, "Message text" + ) + print(send_message_response.data) if send_message_response.code == 200 else print(send_message_response.error) + else: + print(create_group_response.error) + +if __name__ == '__main__': + asyncio.run(main()) \ No newline at end of file diff --git a/examples/async/groupsMethodsAsync.py b/examples/async/groupsMethodsAsync.py new file mode 100644 index 0000000..3aa946d --- /dev/null +++ b/examples/async/groupsMethodsAsync.py @@ -0,0 +1,25 @@ +import asyncio +from whatsapp_api_client_python import API + +greenAPI = API.GreenAPI( + "1101000001", "d75b3a66374942c5b3c019c698abc2067e151558acbd412345" +) + +async def main(): + response = await greenAPI.groups.createGroupAsync( + "SDK Python", + ["11001234567@c.us", "11001234568@c.us"] + ) + print(response.data) if response.code == 200 else print(response.error) + + response = await greenAPI.groups.addGroupParticipantAsync( + "1234567890@g.us", + "11001234567@c.us" + ) + print(response.data) if response.code == 200 else print(response.error) + + response = await greenAPI.groups.getGroupDataAsync("1234567890@g.us") + print(response.data) if response.code == 200 else print(response.error) + +if __name__ == '__main__': + asyncio.run(main()) \ No newline at end of file diff --git a/examples/async/lastMessagesAsync.py b/examples/async/lastMessagesAsync.py new file mode 100644 index 0000000..25aa77d --- /dev/null +++ b/examples/async/lastMessagesAsync.py @@ -0,0 +1,16 @@ +import asyncio +from whatsapp_api_client_python import API + +greenAPI = API.GreenAPI( + "1101000001", "d75b3a66374942c5b3c019c698abc2067e151558acbd412345" +) + +async def main(): + response = await greenAPI.journals.lastIncomingMessagesAsync(4320) + print(response.data) if response.code == 200 else print(response.error) + + response = await greenAPI.journals.lastOutgoingMessagesAsync(4320) + print(response.data) if response.code == 200 else print(response.error) + +if __name__ == '__main__': + asyncio.run(main()) \ No newline at end of file diff --git a/examples/async/partnerMethods/CreateInstanceAsync.py b/examples/async/partnerMethods/CreateInstanceAsync.py new file mode 100644 index 0000000..190e102 --- /dev/null +++ b/examples/async/partnerMethods/CreateInstanceAsync.py @@ -0,0 +1,32 @@ +import asyncio +from whatsapp_api_client_python import API + +greenAPI = API.GreenApiPartner( + "gac.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrst" +) + +async def main(): + settings = { + "name": "Created by Python SDK", + "webhookUrl": "https://webhook.url", + "webhookUrlToken": "auth_token", + "delaySendMessagesMilliseconds": 5000, + "markIncomingMessagesReaded": "yes", + "markIncomingMessagesReadedOnReply": "yes", + "outgoingWebhook": "yes", + "outgoingMessageWebhook": "yes", + "outgoingAPIMessageWebhook": "yes", + "stateWebhook": "yes", + "incomingWebhook": "yes", + "keepOnlineStatus": "yes", + "pollMessageWebhook": "yes", + "incomingCallWebhook": "yes", + "editedMessageWebhook": "yes", + "deletedMessageWebhook": "yes" + } + + response = await greenAPI.partner.createInstanceAsync(settings) + print(response.data) if response.code == 200 else print(response.error) + +if __name__ == '__main__': + asyncio.run(main()) \ No newline at end of file diff --git a/examples/async/partnerMethods/DeleteInstanceAccountAsync.py b/examples/async/partnerMethods/DeleteInstanceAccountAsync.py new file mode 100644 index 0000000..6d1c022 --- /dev/null +++ b/examples/async/partnerMethods/DeleteInstanceAccountAsync.py @@ -0,0 +1,13 @@ +import asyncio +from whatsapp_api_client_python import API + +greenAPI = API.GreenApiPartner( + "gac.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrst" +) + +async def main(): + response = await greenAPI.partner.deleteInstanceAccountAsync(0) + print(response.data) if response.code == 200 else print(response.error) + +if __name__ == '__main__': + asyncio.run(main()) diff --git a/examples/async/partnerMethods/GetInstancesAsync.py b/examples/async/partnerMethods/GetInstancesAsync.py new file mode 100644 index 0000000..f7f796b --- /dev/null +++ b/examples/async/partnerMethods/GetInstancesAsync.py @@ -0,0 +1,13 @@ +import asyncio +from whatsapp_api_client_python import API + +greenAPI = API.GreenApiPartner( + "gac.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrst" +) + +async def main(): + response = await greenAPI.partner.getInstancesAsync() + print(response.data) if response.code == 200 else print(response.error) + +if __name__ == '__main__': + asyncio.run(main()) \ No newline at end of file diff --git a/examples/async/payload.py b/examples/async/payload.py new file mode 100644 index 0000000..775177f --- /dev/null +++ b/examples/async/payload.py @@ -0,0 +1,127 @@ +import asyncio +from whatsapp_api_client_python import API + +class GreenAPIDemo: + def __init__(self): + self.greenAPI = API.GreenAPI("1101000001", "d75b3a66374942c5b3c019c698abc2067e151558acbd412345") + self.test_chat = "11001234567@c.us" + + async def run_demo(self): + await self.demo_account_management() + await self.demo_contacts() + await self.demo_sending_messages() + await self.demo_journals() + await self.demo_queues() + + async def demo_account_management(self): + response = await self.greenAPI.account.getStateInstanceAsync() + print(f"getStateInstanceAsync: {response.data.get('stateInstance') if response.code == 200 else response.error}") + + response = await self.greenAPI.account.getSettingsAsync() + if response.code == 200: + settings = response.data + print(f"getSettingsAsync:") + print(f" - delaySendMessagesMilliseconds: {settings.get('delaySendMessagesMilliseconds', 'N/A')}ms") + print(f" - incomingWebhook: {settings.get('incomingWebhook', 'N/A')}") + print(f" - outgoingWebhook: {settings.get('outgoingWebhook', 'N/A')}") + + new_settings = {"delaySendMessagesMilliseconds": 1000, "outgoingWebhook": "yes", "incomingWebhook": "yes"} + response = await self.greenAPI.account.setSettingsAsync(new_settings) + print(f"setSettingsAsync: {'Success' if response.code == 200 else 'Error'}") + + response = await self.greenAPI.account.qrAsync() + if response.code == 200: + print(f"qrAsync received (data size: {len(response.data)} bytes)") + + async def demo_contacts(self): + response = await self.greenAPI.serviceMethods.getContactsAsync() + if response.code == 200: + contacts = response.data + print(f"getContactsAsync: {len(contacts)} contacts were received") + + for i, contact in enumerate(contacts[:3]): + print(f" {i+1}. {contact.get('name', 'No name')} - {contact.get('id')}") + + test_numbers = [79001234567, 79001234568] + for number in test_numbers: + response = await self.greenAPI.serviceMethods.checkWhatsappAsync(number) + if response.code == 200: + exists = response.data.get('existsWhatsapp', False) + status = "Whatsapp exists" if exists else "WhatsApp don't exist" + print(f"Phone: {number}: {status}") + + async def demo_sending_messages(self): + response = await self.greenAPI.sending.sendMessageAsync( + self.test_chat, + "This message was sent from Green-API SDK Python" + ) + print(f"Text message {'sent' if response.code == 200 else 'error'}") + + response = await self.greenAPI.sending.sendMessageAsync( + self.test_chat, + "Checking link preview: https://green-api.com", + linkPreview=True + ) + print(f"Message with preview {'sent' if response.code == 200 else 'error'}") + + response = await self.greenAPI.sending.sendPollAsync( + self.test_chat, + "Wake me up", + [ + {"optionName": "Wake me up inside"}, + {"optionName": "Before you go go"}, + {"optionName": "When september ends"} + ], + multipleAnswers=False + ) + print(f"Poll message {'sent' if response.code == 200 else 'error'}") + + contact = { + "phoneContact": 79001234567, + "firstName": "Jane", + "lastName": "Doe" + } + response = await self.greenAPI.sending.sendContactAsync( + self.test_chat, + contact + ) + print(f"Contact message {'sent' if response.code == 200 else 'error'}") + + response = await self.greenAPI.sending.sendLocationAsync( + self.test_chat, + 55.755826, + 37.617300, + "Red Square," + "Moscow, Russia" + ) + print(f"Location message {'sent' if response.code == 200 else 'error'}") + + async def demo_journals(self): + response = await self.greenAPI.journals.lastIncomingMessagesAsync(minutes=1440) + if response.code == 200: + messages = response.data + print(f"lastIncomingMessages: {len(messages)}") + for msg in messages[:2]: + print(f" - From: {msg.get('senderId')}") + print(f" Text: {msg.get('textMessage', 'Media/File')}") + + async def demo_queues(self): + response = await self.greenAPI.queues.showMessagesQueueAsync() + if response.code == 200: + queue = response.data + print(f"MessagesQueue: {len(queue)}") + + print(f"Waiting 5 seconds... (for all messages to send)") + await asyncio.sleep(5) + + response = await self.greenAPI.queues.clearMessagesQueueAsync() + print(f"Queue cleared: {'success' if response.code == 200 else 'error'}") + +async def main(): + demo = GreenAPIDemo() + try: + await demo.run_demo() + except Exception as e: + print(f"error: {e}") +if __name__ == '__main__': + asyncio.run(main()) \ No newline at end of file diff --git a/examples/async/receiveNotificationAsync.py b/examples/async/receiveNotificationAsync.py new file mode 100644 index 0000000..ac518a0 --- /dev/null +++ b/examples/async/receiveNotificationAsync.py @@ -0,0 +1,104 @@ +import asyncio +from datetime import datetime +from json import dumps +from whatsapp_api_client_python import API + +greenAPI = API.GreenAPI( + "1101000001", "d75b3a66374942c5b3c019c698abc2067e151558acbd412345" +) + +async def main(): + stop_event = asyncio.Event() + + try: + await greenAPI.webhooks.startReceivingNotificationsAsync(handler) + await stop_event.wait() + await greenAPI.webhooks.stopReceivingNotificationsAsync() + + except Exception as e: + print(e) + +def handler(type_webhook: str, body: dict) -> None: + if type_webhook == "incomingMessageReceived": + incoming_message_received(body) + elif type_webhook == "outgoingMessageReceived": + outgoing_message_received(body) + elif type_webhook == "outgoingAPIMessageReceived": + outgoing_api_message_received(body) + elif type_webhook == "outgoingMessageStatus": + outgoing_message_status(body) + elif type_webhook == "stateInstanceChanged": + state_instance_changed(body) + elif type_webhook == "deviceInfo": + device_info(body) + elif type_webhook == "incomingCall": + incoming_call(body) + elif type_webhook == "statusInstanceChanged": + status_instance_changed(body) + +def get_notification_time(timestamp: int) -> str: + return str(datetime.fromtimestamp(timestamp)) + +def incoming_message_received(body: dict) -> None: + timestamp = body["timestamp"] + time = get_notification_time(timestamp) + data = dumps(body, ensure_ascii=False, indent=4) + + print(f"New incoming message at {time} with data: {data}", end="\n\n") + +def outgoing_message_received(body: dict) -> None: + timestamp = body["timestamp"] + time = get_notification_time(timestamp) + data = dumps(body, ensure_ascii=False, indent=4) + + print(f"New outgoing message at {time} with data: {data}", end="\n\n") + +def outgoing_api_message_received(body: dict) -> None: + timestamp = body["timestamp"] + time = get_notification_time(timestamp) + data = dumps(body, ensure_ascii=False, indent=4) + + print(f"New outgoing API message at {time} with data: {data}", end="\n\n") + +def outgoing_message_status(body: dict) -> None: + timestamp = body["timestamp"] + time = get_notification_time(timestamp) + data = dumps(body, ensure_ascii=False, indent=4) + + response = f"Status of sent message has been updated at {time} with data: {data}" + print(response, end="\n\n") + +def state_instance_changed(body: dict) -> None: + timestamp = body["timestamp"] + time = get_notification_time(timestamp) + data = dumps(body, ensure_ascii=False, indent=4) + + print(f"Current instance state at {time} with data: {data}", end="\n\n") + +def device_info(body: dict) -> None: + timestamp = body["timestamp"] + time = get_notification_time(timestamp) + data = dumps(body, ensure_ascii=False, indent=4) + + response = f"Current device information at {time} with data: {data}" + print(response, end="\n\n") + +def incoming_call(body: dict) -> None: + timestamp = body["timestamp"] + time = get_notification_time(timestamp) + data = dumps(body, ensure_ascii=False, indent=4) + + print(f"New incoming call at {time} with data: {data}", end="\n\n") + +def status_instance_changed(body: dict) -> None: + timestamp = body["timestamp"] + time = get_notification_time(timestamp) + data = dumps(body, ensure_ascii=False, indent=4) + + print(f"Current instance status at {time} with data: {data}", end="\n\n") + +if __name__ == '__main__': + try: + asyncio.run(main()) + except KeyboardInterrupt: + print("\n Application Stopped") \ No newline at end of file diff --git a/examples/async/sending/sendFileByUploadAsync.py b/examples/async/sending/sendFileByUploadAsync.py new file mode 100644 index 0000000..eb7f79e --- /dev/null +++ b/examples/async/sending/sendFileByUploadAsync.py @@ -0,0 +1,18 @@ +import asyncio +from whatsapp_api_client_python import API + +greenAPI = API.GreenAPI( + "1101000001", "d75b3a66374942c5b3c019c698abc2067e151558acbd412345" +) + +async def main(): + response = await greenAPI.sending.sendFileByUploadAsync( + "11001234567@c.us", + "data/logo.jpg", + "logo.jpg", + "Available rates" + ) + print(response.data) if response.code == 200 else print(response.error) + +if __name__ == '__main__': + asyncio.run(main()) \ No newline at end of file diff --git a/examples/async/sending/sendFileByUrlAsync.py b/examples/async/sending/sendFileByUrlAsync.py new file mode 100644 index 0000000..53e0d3d --- /dev/null +++ b/examples/async/sending/sendFileByUrlAsync.py @@ -0,0 +1,18 @@ +import asyncio +from whatsapp_api_client_python import API + +greenAPI = API.GreenAPI( + "1101000001", "d75b3a66374942c5b3c019c698abc2067e151558acbd412345" +) + +async def main(): + response = await greenAPI.sending.sendFileByUrlAsync( + "11001234567@c.us", + "https://download.samplelib.com/png/sample-clouds2-400x300.png", + "sample-clouds2-400x300.png", + "Sample PNG" + ) + print(response.data) if response.code == 200 else print(response.error) + +if __name__ == '__main__': + asyncio.run(main()) \ No newline at end of file diff --git a/examples/async/sending/sendLocationAsync.py b/examples/async/sending/sendLocationAsync.py new file mode 100644 index 0000000..672117a --- /dev/null +++ b/examples/async/sending/sendLocationAsync.py @@ -0,0 +1,19 @@ +import asyncio +from whatsapp_api_client_python import API + +greenAPI = API.GreenAPI( + "1101000001", "d75b3a66374942c5b3c019c698abc2067e151558acbd412345" +) + +async def main(): + response = await greenAPI.sending.sendLocationAsync( + "11001234567@c.us", + 0.0, + 0.0, + "Restaurant", + "123456, Best Place", + ) + print(response.data) if response.code == 200 else print(response.error) + +if __name__ == '__main__': + asyncio.run(main()) \ No newline at end of file diff --git a/examples/async/sending/sendMessageAsync.py b/examples/async/sending/sendMessageAsync.py new file mode 100644 index 0000000..9c24758 --- /dev/null +++ b/examples/async/sending/sendMessageAsync.py @@ -0,0 +1,13 @@ +import asyncio +from whatsapp_api_client_python import API + +greenAPI = API.GreenAPI( + "1101000001", "d75b3a66374942c5b3c019c698abc2067e151558acbd412345" +) + +async def main(): + response = await greenAPI.sending.sendMessageAsync("11001234567@c.us", "Message Text (async)") + print(response.data) if response.code == 200 else print(response.error) + +if __name__ == '__main__': + asyncio.run(main()) \ No newline at end of file diff --git a/examples/async/sending/sendPollAsync.py b/examples/async/sending/sendPollAsync.py new file mode 100644 index 0000000..a1f31f6 --- /dev/null +++ b/examples/async/sending/sendPollAsync.py @@ -0,0 +1,21 @@ +import asyncio +from whatsapp_api_client_python import API + +greenAPI = API.GreenAPI( + "1101000001", "d75b3a66374942c5b3c019c698abc2067e151558acbd412345" +) + +async def main(): + response = await greenAPI.sending.sendPollAsync( + "11001234567@c.us", + "Please choose a color:", + [ + {"optionName": "Red"}, + {"optionName": "Green"}, + {"optionName": "Blue"} + ] + ) + print(response.data) if response.code == 200 else print(response.error) + +if __name__ == '__main__': + asyncio.run(main()) \ No newline at end of file diff --git a/examples/async/sending/uploadFileAndSendFileByUrlAsync.py b/examples/async/sending/uploadFileAndSendFileByUrlAsync.py new file mode 100644 index 0000000..1ea0a5a --- /dev/null +++ b/examples/async/sending/uploadFileAndSendFileByUrlAsync.py @@ -0,0 +1,28 @@ +import asyncio +from os.path import basename +from urllib.parse import urlparse +from whatsapp_api_client_python import API + +greenAPI = API.GreenAPI( + "1101000001", "d75b3a66374942c5b3c019c698abc2067e151558acbd412345" +) + +async def main(): + upload_file_response = await greenAPI.sending.uploadFileAsync("data/logo.jpg") + + if upload_file_response.code == 200: + print(upload_file_response.data) + + url_file = upload_file_response.data["urlFile"] + url = urlparse(url_file) + file_name = basename(url.path) + + send_file_response = await greenAPI.sending.sendFileByUrlAsync( + "11001234567@c.us", url_file, file_name + ) + print(send_file_response.data) if send_file_response.code == 200 else print(send_file_response.error) + else: + print(upload_file_response.error) + +if __name__ == '__main__': + asyncio.run(main()) \ No newline at end of file diff --git a/examples/async/serviceMethodsAsync..py b/examples/async/serviceMethodsAsync..py new file mode 100644 index 0000000..6d3a73b --- /dev/null +++ b/examples/async/serviceMethodsAsync..py @@ -0,0 +1,32 @@ +import asyncio +from whatsapp_api_client_python import API + +greenAPI = API.GreenAPI( + "1101000001", "d75b3a66374942c5b3c019c698abc2067e151558acbd412345" +) + +async def main(): + + response = await greenAPI.serviceMethods.checkWhatsappAsync(79001234567) + print(response.data) if response.code == 200 else print(response.error) + + response = await greenAPI.serviceMethods.getContactsAsync() + print(response.data) if response.code == 200 else print(response.error) + + response = await greenAPI.serviceMethods.deleteMessageAsync( + "11001234567@c.us", "BAE52A7F04F452F9", True + ) + print(response.data) if response.code == 200 else print(response.error) + + response = await greenAPI.serviceMethods.deleteMessageAsync( + "11001234567@c.us", "BAE52A7F04F452F9" + ) + print(response.data) if response.code == 200 else print(response.error) + + response = await greenAPI.serviceMethods.editMessageAsync( + "11001234567@c.us", "BAE5F793F61411D0", "New text (async)" + ) + print(response.data) if response.code == 200 else print(response.error) + +if __name__ == '__main__': + asyncio.run(main()) \ No newline at end of file diff --git a/examples/async/statusesMethods/deleteStatusAsync.py b/examples/async/statusesMethods/deleteStatusAsync.py new file mode 100644 index 0000000..860deb7 --- /dev/null +++ b/examples/async/statusesMethods/deleteStatusAsync.py @@ -0,0 +1,13 @@ +import asyncio +from whatsapp_api_client_python import API + +greenAPI = API.GreenAPI( + "1101000001", "d75b3a66374942c5b3c019c698abc2067e151558acbd412345" +) + +async def main(): + response = await greenAPI.statuses.deleteStatusAsync('BAE54F518532FCB1') + print(response.data) if response.code == 200 else print(response.error) + +if __name__ == '__main__': + asyncio.run(main()) \ No newline at end of file diff --git a/examples/async/statusesMethods/getStatusesAsync.py b/examples/async/statusesMethods/getStatusesAsync.py new file mode 100644 index 0000000..1c61063 --- /dev/null +++ b/examples/async/statusesMethods/getStatusesAsync.py @@ -0,0 +1,19 @@ +import asyncio +from whatsapp_api_client_python import API, response + +greenAPI = API.GreenAPI( + "1101000001", "d75b3a66374942c5b3c019c698abc2067e151558acbd412345" +) + +async def main(): + response1 = await greenAPI.statuses.getIncomingStatusesAsync(1400) + print(response1.data) if response.code == 200 else print(response1.error) + + response2 = await greenAPI.statuses.getOutgoingStatusesAsync(1400) + print(response2.data) if response.code == 200 else print(response2.error) + + response3 = await greenAPI.statuses.getStatusStatisticAsync('BAE54F518532FCB1') + print(response3.data) if response.code == 200 else print(response3.error) + +if __name__ == '__main__': + asyncio.run(main()) \ No newline at end of file diff --git a/examples/async/statusesMethods/sendMediaStatusAsync.py b/examples/async/statusesMethods/sendMediaStatusAsync.py new file mode 100644 index 0000000..c2068f3 --- /dev/null +++ b/examples/async/statusesMethods/sendMediaStatusAsync.py @@ -0,0 +1,17 @@ +import asyncio +from whatsapp_api_client_python import API + +greenAPI = API.GreenAPI( + "1101000001", "d75b3a66374942c5b3c019c698abc2067e151558acbd412345" +) + +async def main(): + response = await greenAPI.statuses.sendMediaStatusAsync( + "https://example.com/file.mp4", + "test.mp4", + "#54c774" + ) + print(response.data) if response.code == 200 else print(response.error) + +if __name__ == '__main__': + asyncio.run(main()) \ No newline at end of file diff --git a/examples/async/statusesMethods/sendTextStatusAsync.py b/examples/async/statusesMethods/sendTextStatusAsync.py new file mode 100644 index 0000000..e355544 --- /dev/null +++ b/examples/async/statusesMethods/sendTextStatusAsync.py @@ -0,0 +1,17 @@ +import asyncio +from whatsapp_api_client_python import API + +greenAPI = API.GreenAPI( + "1101000001", "d75b3a66374942c5b3c019c698abc2067e151558acbd412345" +) + +async def main(): + response = await greenAPI.statuses.sendTextStatusAsync( + "I sent this status using Green Api Python SDK!", + "#54c774", + "NORICAN_REGULAR" + ) + print(response.data) if response.code == 200 else print(response.error) + +if __name__ == '__main__': + asyncio.run(main()) \ No newline at end of file diff --git a/examples/async/statusesMethods/sendVoiceStatusAsync.py b/examples/async/statusesMethods/sendVoiceStatusAsync.py new file mode 100644 index 0000000..6551077 --- /dev/null +++ b/examples/async/statusesMethods/sendVoiceStatusAsync.py @@ -0,0 +1,17 @@ +import asyncio +from whatsapp_api_client_python import API + +greenAPI = API.GreenAPI( + "1101000001", "d75b3a66374942c5b3c019c698abc2067e151558acbd412345" +) + +async def main(): + response = await greenAPI.statuses.sendVoiceStatusAsync( + "https://example.com/file.mp3", + "test.mp3" + "#54c774" + ) + print(response.data) if response.code == 200 else print(response.error) + +if __name__ == '__main__': + asyncio.run(main()) diff --git a/examples/data/logo.jpg b/examples/data/logo.jpg new file mode 100644 index 0000000..4fc572b Binary files /dev/null and b/examples/data/logo.jpg differ diff --git a/examples/data/rates.png b/examples/data/rates.png deleted file mode 100644 index 31a5016..0000000 Binary files a/examples/data/rates.png and /dev/null differ diff --git a/examples/createGroupAndSendMessage.py b/examples/sync/createGroupAndSendMessage.py similarity index 100% rename from examples/createGroupAndSendMessage.py rename to examples/sync/createGroupAndSendMessage.py diff --git a/examples/lastMessages.py b/examples/sync/lastMessages.py similarity index 100% rename from examples/lastMessages.py rename to examples/sync/lastMessages.py diff --git a/examples/partherMethods/CreateInstance.py b/examples/sync/partnerMethods/CreateInstance.py similarity index 100% rename from examples/partherMethods/CreateInstance.py rename to examples/sync/partnerMethods/CreateInstance.py diff --git a/examples/partherMethods/DeleteInstanceAccount.py b/examples/sync/partnerMethods/DeleteInstanceAccount.py similarity index 100% rename from examples/partherMethods/DeleteInstanceAccount.py rename to examples/sync/partnerMethods/DeleteInstanceAccount.py diff --git a/examples/partherMethods/GetInstances.py b/examples/sync/partnerMethods/GetInstances.py similarity index 100% rename from examples/partherMethods/GetInstances.py rename to examples/sync/partnerMethods/GetInstances.py diff --git a/examples/receiveNotification.py b/examples/sync/receiveNotification.py similarity index 100% rename from examples/receiveNotification.py rename to examples/sync/receiveNotification.py diff --git a/examples/sendPictureByLink.py b/examples/sync/sendPictureByLink.py similarity index 100% rename from examples/sendPictureByLink.py rename to examples/sync/sendPictureByLink.py diff --git a/examples/sendPictureByUpload.py b/examples/sync/sendPictureByUpload.py similarity index 87% rename from examples/sendPictureByUpload.py rename to examples/sync/sendPictureByUpload.py index d3e960c..00532f5 100644 --- a/examples/sendPictureByUpload.py +++ b/examples/sync/sendPictureByUpload.py @@ -8,8 +8,8 @@ def main(): response = greenAPI.sending.sendFileByUpload( "11001234567@c.us", - "data/rates.png", - "rates.png", + "data/logo.jpg", + "logo.jpg", "Available rates" ) diff --git a/examples/sendPoll.py b/examples/sync/sendPoll.py similarity index 100% rename from examples/sendPoll.py rename to examples/sync/sendPoll.py diff --git a/examples/sendTextMessage.py b/examples/sync/sendTextMessage.py similarity index 100% rename from examples/sendTextMessage.py rename to examples/sync/sendTextMessage.py diff --git a/examples/serviceMethods.py b/examples/sync/serviceMethods.py similarity index 100% rename from examples/serviceMethods.py rename to examples/sync/serviceMethods.py diff --git a/examples/setSettings.py b/examples/sync/setSettings.py similarity index 100% rename from examples/setSettings.py rename to examples/sync/setSettings.py diff --git a/examples/statusesMethods/deleteStatus.py b/examples/sync/statusesMethods/deleteStatus.py similarity index 100% rename from examples/statusesMethods/deleteStatus.py rename to examples/sync/statusesMethods/deleteStatus.py diff --git a/examples/statusesMethods/getStatuses.py b/examples/sync/statusesMethods/getStatuses.py similarity index 100% rename from examples/statusesMethods/getStatuses.py rename to examples/sync/statusesMethods/getStatuses.py diff --git a/examples/statusesMethods/sendMediaStatus.py b/examples/sync/statusesMethods/sendMediaStatus.py similarity index 100% rename from examples/statusesMethods/sendMediaStatus.py rename to examples/sync/statusesMethods/sendMediaStatus.py diff --git a/examples/statusesMethods/sendTextStatus.py b/examples/sync/statusesMethods/sendTextStatus.py similarity index 100% rename from examples/statusesMethods/sendTextStatus.py rename to examples/sync/statusesMethods/sendTextStatus.py diff --git a/examples/statusesMethods/sendVoiceStatus.py b/examples/sync/statusesMethods/sendVoiceStatus.py similarity index 100% rename from examples/statusesMethods/sendVoiceStatus.py rename to examples/sync/statusesMethods/sendVoiceStatus.py diff --git a/examples/uploadFileAndSendFileByUrl.py b/examples/sync/uploadFileAndSendFileByUrl.py similarity index 97% rename from examples/uploadFileAndSendFileByUrl.py rename to examples/sync/uploadFileAndSendFileByUrl.py index 26ca8a2..abfd096 100644 --- a/examples/uploadFileAndSendFileByUrl.py +++ b/examples/sync/uploadFileAndSendFileByUrl.py @@ -10,7 +10,7 @@ def main(): upload_file_response = greenAPI.sending.uploadFile( - "data/rates.png" + "data/logo.jpg" ) if upload_file_response.code == 200: print(upload_file_response.data) diff --git a/requirements.txt b/requirements.txt index 0eb8cae..cb60b50 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,4 @@ requests>=2.31.0 +aiofiles>=24.1.0 +aiogram>=3.21.0 +aiohttp>=3.9.0 \ No newline at end of file diff --git a/setup.py b/setup.py index 59235f6..2624f07 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setup( name="whatsapp-api-client-python", - version="0.0.50", + version="0.0.51", description=( "This library helps you easily create" " a Python application with WhatsApp API." @@ -43,6 +43,6 @@ "Creative Commons Attribution-NoDerivatives 4.0 International" " (CC BY-ND 4.0)" ), - install_requires=["requests>=2.31.0"], + install_requires=["requests>=2.31.0", "aiofiles>=24.1.0", "aiogram>=3.21.0", "aiohttp>=3.9.0"], python_requires=">=3.7" -) +) \ No newline at end of file diff --git a/tests/test_async_methods.py b/tests/test_async_methods.py new file mode 100644 index 0000000..536449c --- /dev/null +++ b/tests/test_async_methods.py @@ -0,0 +1,127 @@ +import typing +import pytest +from unittest.mock import Mock, patch + +from whatsapp_api_client_python.API import GreenAPI + +api = GreenAPI("", "") +path = "examples/data/logo.jpg" + +class TestAsyncMethods: + + @pytest.mark.asyncio + @patch("whatsapp_api_client_python.API.Session.request") + async def test_async_methods(self, mock_raw_request): + mock_response = Mock() + mock_response.code = 200 + mock_response.data = {"example": {"key": "value"}} + mock_raw_request.return_value = mock_response + + methods_coroutines = [] + methods_coroutines.extend(self.account_methods()) + methods_coroutines.extend(self.group_methods()) + methods_coroutines.extend(self.status_methods()) + methods_coroutines.extend(self.log_methods()) + methods_coroutines.extend(self.queue_methods()) + methods_coroutines.extend(self.read_mark_methods()) + methods_coroutines.extend(self.receiving_methods()) + methods_coroutines.extend(self.sending_methods()) + methods_coroutines.extend(self.service_methods()) + + responses = [] + for coro in methods_coroutines: + response = await coro + responses.append(response) + + for response in responses: + assert response.code == 200 + assert response.data == {"example": {"key": "value"}} + + assert mock_raw_request.call_count == len(responses) + + def account_methods(self) -> typing.List: + return [ + api.account.getSettingsAsync(), + api.account.getWaSettingsAsync(), + api.account.setSettingsAsync({}), + api.account.getStateInstanceAsync(), + api.account.rebootAsync(), + api.account.logoutAsync(), + api.account.qrAsync(), + api.account.setProfilePictureAsync(path), + api.account.getAuthorizationCodeAsync(0) + ] + + def group_methods(self) -> typing.List: + return [ + api.groups.createGroupAsync("", []), + api.groups.updateGroupNameAsync(""), + api.groups.getGroupDataAsync(""), + api.groups.removeGroupParticipantAsync("", ""), + api.groups.addGroupParticipantAsync("", ""), + api.groups.setGroupAdminAsync("", ""), + api.groups.removeAdminAsync("", ""), + api.groups.setGroupPictureAsync("", ""), + api.groups.leaveGroupAsync("") + ] + + def status_methods(self) -> typing.List: + return [ + api.statuses.sendTextStatusAsync(""), + api.statuses.sendVoiceStatusAsync("", ""), + api.statuses.sendMediaStatusAsync("", ""), + api.statuses.deleteStatusAsync(""), + api.statuses.getStatusStatisticAsync(""), + api.statuses.getIncomingStatusesAsync(), + api.statuses.getOutgoingStatusesAsync() + ] + + def log_methods(self) -> typing.List: + return [ + api.journals.getChatHistoryAsync(""), + api.journals.getMessageAsync("", ""), + api.journals.lastIncomingMessagesAsync(), + api.journals.lastOutgoingMessagesAsync() + ] + + def queue_methods(self) -> typing.List: + return [ + api.queues.showMessagesQueueAsync(), + api.queues.clearMessagesQueueAsync() + ] + + def read_mark_methods(self) -> typing.List: + return [api.marking.readChatAsync("")] + + def receiving_methods(self) -> typing.List: + return [ + api.receiving.receiveNotificationAsync(), + api.receiving.deleteNotificationAsync(0), + api.receiving.downloadFileAsync("", "") + ] + + def sending_methods(self) -> typing.List: + return [ + api.sending.sendMessageAsync("", ""), + api.sending.sendFileByUploadAsync("", ""), + api.sending.sendFileByUrlAsync("", "", ""), + api.sending.uploadFileAsync("image_path"), + api.sending.sendLocationAsync("", 0.0, 0.0), + api.sending.sendContactAsync("", {}), + api.sending.sendPollAsync("", "", []) + ] + + def service_methods(self) -> typing.List: + return [ + api.serviceMethods.checkWhatsappAsync(0), + api.serviceMethods.getAvatarAsync(""), + api.serviceMethods.getContactsAsync(), + api.serviceMethods.getContactInfoAsync(""), + api.serviceMethods.deleteMessageAsync("", ""), + api.serviceMethods.archiveChatAsync(""), + api.serviceMethods.unarchiveChatAsync(""), + api.serviceMethods.setDisappearingChatAsync("") + ] + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) \ No newline at end of file diff --git a/tests/test_methods.py b/tests/test_methods.py index 5861b7b..0590bfb 100644 --- a/tests/test_methods.py +++ b/tests/test_methods.py @@ -7,7 +7,7 @@ api = GreenAPI("", "") -path = "examples/data/rates.png" +path = "examples/data/logo.jpg" class MethodsTestCase(unittest.TestCase): diff --git a/whatsapp_api_client_python/API.py b/whatsapp_api_client_python/API.py index 7cfe050..e99be99 100644 --- a/whatsapp_api_client_python/API.py +++ b/whatsapp_api_client_python/API.py @@ -2,6 +2,7 @@ import logging from typing import Any, NoReturn, Optional +import aiohttp from requests import Response, Session from requests.adapters import HTTPAdapter, Retry @@ -107,6 +108,59 @@ def request( return GreenAPIResponse(response.status_code, response.text) + async def requestAsync( + self, + method: str, + url: str, + payload: Optional[dict] = None, + files: Optional[dict] = None + ) -> GreenAPIResponse: + url = url.replace("{{host}}", self.host) + url = url.replace("{{media}}", self.media) + url = url.replace("{{idInstance}}", self.idInstance) + url = url.replace("{{apiTokenInstance}}", self.apiTokenInstance) + + headers = { + 'User-Agent': 'GREEN-API_SDK_PY/1.0' + } + + try: + timeout = aiohttp.ClientTimeout(total=self.host_timeout if not files else self.media_timeout) + + async with aiohttp.ClientSession(timeout=timeout, headers=headers) as session: + if not files: + async with session.request(method=method, url=url, json=payload) as response: + text = await response.text() + status_code = response.status + else: + data = aiohttp.FormData() + for key, value in (payload or {}).items(): + if isinstance(value, (dict, list)): + import json + data.add_field(key, json.dumps(value), content_type='application/json') + else: + data.add_field(key, str(value)) + + for field_name, file_data in files.items(): + filename, file_obj, content_type = file_data + data.add_field(field_name, file_obj, filename=filename, content_type=content_type) + + async with session.request(method=method, url=url, data=data) as response: + text = await response.text() + status_code = response.status + + except Exception as error: + error_message = f"Async request was failed with error: {error}." + + if self.raise_errors: + raise GreenAPIError(error_message) + self.logger.log(logging.CRITICAL, error_message) + + return GreenAPIResponse(None, error_message) + + self.__handle_response_async(status_code, text) + return GreenAPIResponse(status_code, text) + def raw_request(self, **arguments: Any) -> GreenAPIResponse: try: response = self.session.request(**arguments) @@ -123,6 +177,29 @@ def raw_request(self, **arguments: Any) -> GreenAPIResponse: return GreenAPIResponse(response.status_code, response.text) + async def raw_request_async(self, **arguments: Any) -> GreenAPIResponse: + try: + timeout = aiohttp.ClientTimeout(total=arguments.pop('timeout', self.host_timeout)) + headers = arguments.pop('headers', {}) + headers['User-Agent'] = 'GREEN-API_SDK_PY/1.0' + + async with aiohttp.ClientSession(timeout=timeout, headers=headers) as session: + async with session.request(**arguments) as response: + text = await response.text() + status_code = response.status + + except Exception as error: + error_message = f"Async raw request was failed with error: {error}." + + if self.raise_errors: + raise GreenAPIError(error_message) + self.logger.log(logging.CRITICAL, error_message) + + return GreenAPIResponse(None, error_message) + + self.__handle_response_async(status_code, text) + return GreenAPIResponse(status_code, text) + def __handle_response(self, response: Response) -> Optional[NoReturn]: status_code = response.status_code if status_code != 200 or self.debug_mode: @@ -149,6 +226,31 @@ def __handle_response(self, response: Response) -> Optional[NoReturn]: logging.DEBUG, f"Request was successful with data: {data}" ) + def __handle_response_async(self, status_code: int, text: str) -> Optional[NoReturn]: + if status_code != 200 or self.debug_mode: + try: + data = json.dumps( + json.loads(text), ensure_ascii=False, indent=4 + ) + except json.JSONDecodeError: + data = text + + if status_code != 200: + error_message = ( + f"Async request was failed with status code: {status_code}." + f" Data: {data}" + ) + + if self.raise_errors: + raise GreenAPIError(error_message) + self.logger.log(logging.ERROR, error_message) + + return None + + self.logger.log( + logging.DEBUG, f"Async request was successful with data: {data}" + ) + def __prepare_logger(self) -> None: handler = logging.StreamHandler() handler.setFormatter(logging.Formatter( @@ -213,4 +315,14 @@ def request( url = url.replace("{{partnerToken}}", self.partnerToken) - return super().request(method, url, payload, files) \ No newline at end of file + return super().request(method, url, payload, files) + + async def requestAsync( + self, + method: str, + url: str, + payload: Optional[dict] = None, + files: Optional[dict] = None + ) -> GreenAPIResponse: + url = url.replace("{{partnerToken}}", self.partnerToken) + return await super().requestAsync(method, url, payload, files) \ No newline at end of file diff --git a/whatsapp_api_client_python/tools/account.py b/whatsapp_api_client_python/tools/account.py index b0013c4..bd8948b 100644 --- a/whatsapp_api_client_python/tools/account.py +++ b/whatsapp_api_client_python/tools/account.py @@ -1,6 +1,8 @@ from pathlib import Path from typing import Dict, TYPE_CHECKING, Union +import aiofiles + from ..response import Response if TYPE_CHECKING: @@ -25,6 +27,11 @@ def getSettings(self) -> Response: ) ) + async def getSettingsAsync(self) -> Response: + return await self.api.requestAsync( + "GET", "{{host}}/waInstance{{idInstance}}/getSettings/{{apiTokenInstance}}" + ) + def getWaSettings(self) -> Response: """ The method is aimed to get information about the WhatsApp @@ -40,6 +47,11 @@ def getWaSettings(self) -> Response: ) ) + async def getWaSettingsAsync(self) -> Response: + return await self.api.requestAsync( + "GET", "{{host}}/waInstance{{idInstance}}/getWaSettings/{{apiTokenInstance}}" + ) + def setSettings(self, requestBody: Dict[str, Union[int, str]]) -> Response: """ The method is aimed for setting account settings. @@ -54,6 +66,14 @@ def setSettings(self, requestBody: Dict[str, Union[int, str]]) -> Response: ), requestBody ) + async def setSettingsAsync(self, requestBody: Dict[str, Union[int, str]]) -> Response: + return await self.api.requestAsync( + "POST", + "{{host}}/waInstance{{idInstance}}/setSettings/{{apiTokenInstance}}", + requestBody + ) + + def getStateInstance(self) -> Response: """ The method is aimed for getting the account state. @@ -68,6 +88,11 @@ def getStateInstance(self) -> Response: ) ) + async def getStateInstanceAsync(self) -> Response: + return await self.api.requestAsync( + "GET", "{{host}}/waInstance{{idInstance}}/getStateInstance/{{apiTokenInstance}}" + ) + def getStatusInstance(self) -> Response: """ The method is aimed for getting the status of the account @@ -82,7 +107,7 @@ def getStatusInstance(self) -> Response: "getStatusInstance/{{apiTokenInstance}}" ) ) - + def reboot(self) -> Response: """ The method is aimed for rebooting an account. @@ -96,6 +121,11 @@ def reboot(self) -> Response: ) ) + async def rebootAsync(self) -> Response: + return await self.api.requestAsync( + "GET", "{{host}}/waInstance{{idInstance}}/reboot/{{apiTokenInstance}}" + ) + def logout(self) -> Response: """ The method is aimed for logging out an account. @@ -109,6 +139,11 @@ def logout(self) -> Response: ) ) + async def logoutAsync(self) -> Response: + return await self.api.requestAsync( + "GET", "{{host}}/waInstance{{idInstance}}/logout/{{apiTokenInstance}}" + ) + def qr(self) -> Response: """ The method is aimed for getting QR code. @@ -120,6 +155,11 @@ def qr(self) -> Response: "GET", "{{host}}/waInstance{{idInstance}}/qr/{{apiTokenInstance}}" ) + async def qrAsync(self) -> Response: + return await self.api.requestAsync( + "GET", "{{host}}/waInstance{{idInstance}}/qr/{{apiTokenInstance}}" + ) + def setProfilePicture(self, path: str) -> Response: """ The method is aimed for setting an account picture. @@ -137,6 +177,18 @@ def setProfilePicture(self, path: str) -> Response: ), files=files ) + async def setProfilePictureAsync(self, path: str) -> Response: + file_name = Path(path).name + async with aiofiles.open(path, "rb") as file: + file_data = await file.read() + files = {"file": (file_name, file_data, "image/jpeg")} + + return await self.api.requestAsync( + "POST", + "{{host}}/waInstance{{idInstance}}/setProfilePicture/{{apiTokenInstance}}", + files=files + ) + def getAuthorizationCode(self, phoneNumber: int) -> Response: """ The method is designed to authorize an instance by phone number. @@ -153,3 +205,13 @@ def getAuthorizationCode(self, phoneNumber: int) -> Response: "getAuthorizationCode/{{apiTokenInstance}}" ), request_body ) + + async def getAuthorizationCodeAsync(self, phoneNumber: int) -> Response: + request_body = locals() + request_body.pop("self") + + return await self.api.requestAsync( + "POST", + "{{host}}/waInstance{{idInstance}}/getAuthorizationCode/{{apiTokenInstance}}", + request_body + ) \ No newline at end of file diff --git a/whatsapp_api_client_python/tools/device.py b/whatsapp_api_client_python/tools/device.py index 04a42b8..8c18426 100644 --- a/whatsapp_api_client_python/tools/device.py +++ b/whatsapp_api_client_python/tools/device.py @@ -4,14 +4,14 @@ if TYPE_CHECKING: from ..API import GreenApi - - class Device: def __init__(self, api: "GreenApi"): self.api = api def getDeviceInfo(self) -> Response: """ + The method is deprecated. + The method is aimed for getting information about the device (phone) running WhatsApp Business application. @@ -24,3 +24,4 @@ def getDeviceInfo(self) -> Response: "getDeviceInfo/{{apiTokenInstance}}" ) ) + diff --git a/whatsapp_api_client_python/tools/groups.py b/whatsapp_api_client_python/tools/groups.py index ca3aba4..981ef00 100644 --- a/whatsapp_api_client_python/tools/groups.py +++ b/whatsapp_api_client_python/tools/groups.py @@ -1,6 +1,8 @@ from pathlib import Path from typing import List, TYPE_CHECKING +import aiofiles + from ..response import Response if TYPE_CHECKING: @@ -27,6 +29,16 @@ def createGroup(self, groupName: str, chatIds: List[str]) -> Response: ), request_body ) + + async def createGroupAsync(self, groupName: str, chatIds: List[str]) -> Response: + request_body = self.__handle_parameters(locals()) + + return await self.api.requestAsync( + "POST", + "{{host}}/waInstance{{idInstance}}/createGroup/{{apiTokenInstance}}", + request_body + ) + def updateGroupName(self, groupId: str, groupName: str) -> Response: """ The method changes a group chat name. @@ -43,6 +55,15 @@ def updateGroupName(self, groupId: str, groupName: str) -> Response: ), request_body ) + async def updateGroupNameAsync(self, groupId: str, groupName: str) -> Response: + request_body = self.__handle_parameters(locals()) + + return await self.api.requestAsync( + "POST", + "{{host}}/waInstance{{idInstance}}/updateGroupName/{{apiTokenInstance}}", + request_body + ) + def getGroupData(self, groupId: str) -> Response: """ The method gets group chat data. @@ -59,6 +80,15 @@ def getGroupData(self, groupId: str) -> Response: ), request_body ) + async def getGroupDataAsync(self, groupId: str) -> Response: + request_body = self.__handle_parameters(locals()) + + return await self.api.requestAsync( + "POST", + "{{host}}/waInstance{{idInstance}}/getGroupData/{{apiTokenInstance}}", + request_body + ) + def addGroupParticipant( self, groupId: str, participantChatId: str ) -> Response: @@ -77,6 +107,17 @@ def addGroupParticipant( ), request_body ) + async def addGroupParticipantAsync( + self, groupId: str, participantChatId: str + ) -> Response: + request_body = self.__handle_parameters(locals()) + + return await self.api.requestAsync( + "POST", + "{{host}}/waInstance{{idInstance}}/addGroupParticipant/{{apiTokenInstance}}", + request_body + ) + def removeGroupParticipant( self, groupId: str, participantChatId: str ) -> Response: @@ -95,6 +136,17 @@ def removeGroupParticipant( ), request_body ) + async def removeGroupParticipantAsync( + self, groupId: str, participantChatId: str + ) -> Response: + request_body = self.__handle_parameters(locals()) + + return await self.api.requestAsync( + "POST", + "{{host}}/waInstance{{idInstance}}/removeGroupParticipant/{{apiTokenInstance}}", + request_body + ) + def setGroupAdmin(self, groupId: str, participantChatId: str) -> Response: """ The method sets a group chat participant as an administrator. @@ -111,6 +163,15 @@ def setGroupAdmin(self, groupId: str, participantChatId: str) -> Response: ), request_body ) + async def setGroupAdminAsync(self, groupId: str, participantChatId: str) -> Response: + request_body = self.__handle_parameters(locals()) + + return await self.api.requestAsync( + "POST", + "{{host}}/waInstance{{idInstance}}/setGroupAdmin/{{apiTokenInstance}}", + request_body + ) + def removeAdmin(self, groupId: str, participantChatId: str) -> Response: """ The method removes a participant from group chat administration @@ -128,6 +189,15 @@ def removeAdmin(self, groupId: str, participantChatId: str) -> Response: ), request_body ) + async def removeAdminAsync(self, groupId: str, participantChatId: str) -> Response: + request_body = self.__handle_parameters(locals()) + + return await self.api.requestAsync( + "POST", + "{{host}}/waInstance{{idInstance}}/removeAdmin/{{apiTokenInstance}}", + request_body + ) + def setGroupPicture(self, groupId: str, path: str) -> Response: """ The method sets a group picture. @@ -149,6 +219,23 @@ def setGroupPicture(self, groupId: str, path: str) -> Response: ), request_body, files ) + async def setGroupPictureAsync(self, groupId: str, path: str) -> Response: + request_body = self.__handle_parameters(locals()) + + request_body.pop("path") + file_name = Path(path).name + + async with aiofiles.open(path, "rb") as file: + file_data = await file.read() + files = {"file": (file_name, file_data, "image/jpeg")} + + return await self.api.requestAsync( + "POST", + "{{host}}/waInstance{{idInstance}}/setGroupPicture/{{apiTokenInstance}}", + request_body, + files=files + ) + def leaveGroup(self, groupId: str) -> Response: """ The method makes the current account user leave the group chat. @@ -165,6 +252,15 @@ def leaveGroup(self, groupId: str) -> Response: ), request_body ) + async def leaveGroupAsync(self, groupId: str) -> Response: + request_body = self.__handle_parameters(locals()) + + return await self.api.requestAsync( + "POST", + "{{host}}/waInstance{{idInstance}}/leaveGroup/{{apiTokenInstance}}", + request_body + ) + @classmethod def __handle_parameters(cls, parameters: dict) -> dict: handled_parameters = parameters.copy() @@ -175,4 +271,4 @@ def __handle_parameters(cls, parameters: dict) -> dict: if value is None: handled_parameters.pop(key) - return handled_parameters + return handled_parameters \ No newline at end of file diff --git a/whatsapp_api_client_python/tools/journals.py b/whatsapp_api_client_python/tools/journals.py index 6d12bb2..8168a03 100644 --- a/whatsapp_api_client_python/tools/journals.py +++ b/whatsapp_api_client_python/tools/journals.py @@ -5,7 +5,6 @@ if TYPE_CHECKING: from ..API import GreenApi - class Journals: def __init__(self, api: "GreenApi"): self.api = api @@ -31,6 +30,20 @@ def getChatHistory( ), request_body ) + async def getChatHistoryAsync( + self, chatId: str, count: Optional[int] = None + ) -> Response: + request_body = locals() + if count is None: + request_body.pop("count") + request_body.pop("self") + + return await self.api.requestAsync( + "POST", + "{{host}}/waInstance{{idInstance}}/getChatHistory/{{apiTokenInstance}}", + request_body + ) + def getMessage(self, chatId: str, idMessage: str) -> Response: """ The method returns the chat message. @@ -48,6 +61,16 @@ def getMessage(self, chatId: str, idMessage: str) -> Response: ), request_body ) + async def getMessageAsync(self, chatId: str, idMessage: str) -> Response: + request_body = locals() + request_body.pop("self") + + return await self.api.requestAsync( + "POST", + "{{host}}/waInstance{{idInstance}}/getMessage/{{apiTokenInstance}}", + request_body + ) + def lastIncomingMessages(self, minutes: Optional[int] = None) -> Response: """ The method returns the last incoming messages of the account. @@ -66,6 +89,15 @@ def lastIncomingMessages(self, minutes: Optional[int] = None) -> Response: ) ) + async def lastIncomingMessagesAsync(self, minutes: Optional[int] = None) -> Response: + params = {"minutes": minutes} if minutes else {} + + return await self.api.requestAsync( + "GET", + "{{host}}/waInstance{{idInstance}}/lastIncomingMessages/{{apiTokenInstance}}", + params + ) + def lastOutgoingMessages(self, minutes: Optional[int] = None) -> Response: """ The method returns the last outgoing messages of the account. @@ -83,3 +115,12 @@ def lastOutgoingMessages(self, minutes: Optional[int] = None) -> Response: "lastOutgoingMessages/{{apiTokenInstance}}"+append_minutes ) ) + + async def lastOutgoingMessagesAsync(self, minutes: Optional[int] = None) -> Response: + params = {"minutes": minutes} if minutes else {} + + return await self.api.requestAsync( + "GET", + "{{host}}/waInstance{{idInstance}}/lastOutgoingMessages/{{apiTokenInstance}}", + params + ) \ No newline at end of file diff --git a/whatsapp_api_client_python/tools/marking.py b/whatsapp_api_client_python/tools/marking.py index 70f2193..52cfc81 100644 --- a/whatsapp_api_client_python/tools/marking.py +++ b/whatsapp_api_client_python/tools/marking.py @@ -30,3 +30,17 @@ def readChat( "readChat/{{apiTokenInstance}}" ), request_body ) + + async def readChatAsync( + self, chatId: str, idMessage: Optional[str] = None + ) -> Response: + request_body = locals() + if idMessage is None: + request_body.pop("idMessage") + request_body.pop("self") + + return await self.api.requestAsync( + "POST", + "{{host}}/waInstance{{idInstance}}/readChat/{{apiTokenInstance}}", + request_body + ) \ No newline at end of file diff --git a/whatsapp_api_client_python/tools/partner.py b/whatsapp_api_client_python/tools/partner.py index a003213..26c8676 100644 --- a/whatsapp_api_client_python/tools/partner.py +++ b/whatsapp_api_client_python/tools/partner.py @@ -22,7 +22,12 @@ def getInstances(self) -> Response: "getInstances/{{partnerToken}}" ) ) - + + async def getInstancesAsync(self) -> Response: + return await self.api.requestAsync( + "GET", "{{host}}/partner/getInstances/{{partnerToken}}" + ) + def createInstance(self, requestBody: Dict[str, Union[int, str]]) -> Response: """ The method is aimed for creating a messenger account instance on the partner's part. @@ -36,7 +41,14 @@ def createInstance(self, requestBody: Dict[str, Union[int, str]]) -> Response: "createInstance/{{partnerToken}}" ), requestBody ) - + + async def createInstanceAsync(self, requestBody: Dict[str, Union[int, str]]) -> Response: + return await self.api.requestAsync( + "POST", + "{{host}}/partner/createInstance/{{partnerToken}}", + requestBody + ) + def deleteInstanceAccount(self, idInstance: int) -> Response: """ The method is aimed for deleting an instance of the partners's account. @@ -52,7 +64,16 @@ def deleteInstanceAccount(self, idInstance: int) -> Response: "deleteInstanceAccount/{{partnerToken}}" ), request_body ) - + + async def deleteInstanceAccountAsync(self, idInstance: int) -> Response: + request_body = self.__handle_parameters(locals()) + + return await self.api.requestAsync( + "POST", + "{{host}}/partner/deleteInstanceAccount/{{partnerToken}}", + request_body + ) + @classmethod def __handle_parameters(cls, parameters: dict) -> dict: handled_parameters = parameters.copy() diff --git a/whatsapp_api_client_python/tools/queues.py b/whatsapp_api_client_python/tools/queues.py index 60684ba..197bc01 100644 --- a/whatsapp_api_client_python/tools/queues.py +++ b/whatsapp_api_client_python/tools/queues.py @@ -25,6 +25,11 @@ def showMessagesQueue(self) -> Response: ) ) + async def showMessagesQueueAsync(self) -> Response: + return await self.api.requestAsync( + "GET", "{{host}}/waInstance{{idInstance}}/showMessagesQueue/{{apiTokenInstance}}" + ) + def clearMessagesQueue(self) -> Response: """ The method is aimed for clearing the queue of messages to be @@ -39,3 +44,8 @@ def clearMessagesQueue(self) -> Response: "clearMessagesQueue/{{apiTokenInstance}}" ) ) + + async def clearMessagesQueueAsync(self) -> Response: + return await self.api.requestAsync( + "GET", "{{host}}/waInstance{{idInstance}}/clearMessagesQueue/{{apiTokenInstance}}" + ) \ No newline at end of file diff --git a/whatsapp_api_client_python/tools/receiving.py b/whatsapp_api_client_python/tools/receiving.py index 8c3964a..84e04e9 100644 --- a/whatsapp_api_client_python/tools/receiving.py +++ b/whatsapp_api_client_python/tools/receiving.py @@ -25,6 +25,11 @@ def receiveNotification(self) -> Response: ) ) + async def receiveNotificationAsync(self) -> Response: + return await self.api.requestAsync( + "GET", "{{host}}/waInstance{{idInstance}}/receiveNotification/{{apiTokenInstance}}" + ) + def deleteNotification(self, receiptId: int) -> Response: """ The method is aimed for deleting an incoming notification from @@ -40,6 +45,13 @@ def deleteNotification(self, receiptId: int) -> Response: return self.api.request("DELETE", f"{url}/{receiptId}") + async def deleteNotificationAsync(self, receiptId: int) -> Response: + return await self.api.requestAsync( + "DELETE", + "{{host}}/waInstance{{idInstance}}/deleteNotification/{{apiTokenInstance}}/", + receiptId + ) + def downloadFile(self, chatId: str, idMessage: str) -> Response: """ The method is aimed for downloading incoming and outgoing files. @@ -56,3 +68,13 @@ def downloadFile(self, chatId: str, idMessage: str) -> Response: "downloadFile/{{apiTokenInstance}}" ), request_body ) + + async def downloadFileAsync(self, chatId: str, idMessage: str) -> Response: + request_body = locals() + request_body.pop("self") + + return await self.api.requestAsync( + "POST", + "{{host}}/waInstance{{idInstance}}/downloadFile/{{apiTokenInstance}}", + request_body + ) diff --git a/whatsapp_api_client_python/tools/sending.py b/whatsapp_api_client_python/tools/sending.py index 9b7857b..49e7922 100644 --- a/whatsapp_api_client_python/tools/sending.py +++ b/whatsapp_api_client_python/tools/sending.py @@ -2,6 +2,8 @@ import pathlib from typing import Dict, List, Optional, TYPE_CHECKING, Union +import aiofiles + from ..response import Response if TYPE_CHECKING: @@ -36,6 +38,22 @@ def sendMessage( ), request_body ) + async def sendMessageAsync( + self, + chatId: str, + message: str, + quotedMessageId: Optional[str] = None, + archiveChat: Optional[bool] = None, + linkPreview: Optional[bool] = None + ) -> Response: + request_body = self.__handle_parameters(locals()) + + return await self.api.requestAsync( + "POST", + "{{host}}/waInstance{{idInstance}}/sendMessage/{{apiTokenInstance}}", + request_body + ) + def sendButtons( self, chatId: str, @@ -46,6 +64,8 @@ def sendButtons( archiveChat: Optional[bool] = None ) -> Response: """ + The method is deprecated. + The method is aimed for sending a button message to a personal or a group chat. @@ -144,6 +164,32 @@ def sendFileByUpload( ), request_body, files ) + async def sendFileByUploadAsync( + self, + chatId: str, + path: str, + fileName: Optional[str] = None, + caption: Optional[str] = None, + quotedMessageId: Optional[str] = None + ) -> Response: + request_body = self.__handle_parameters(locals()) + + file_name = pathlib.Path(path).name + content_type = mimetypes.guess_type(file_name)[0] + + async with aiofiles.open(path, "rb") as file: + file_data = await file.read() + files = {"file": (file_name, file_data, content_type)} + + request_body.pop("path") + + return await self.api.requestAsync( + "POST", + "{{media}}/waInstance{{idInstance}}/sendFileByUpload/{{apiTokenInstance}}", + request_body, + files=files + ) + def sendFileByUrl( self, chatId: str, @@ -168,6 +214,23 @@ def sendFileByUrl( ), request_body ) + async def sendFileByUrlAsync( + self, + chatId: str, + urlFile: str, + fileName: str, + caption: Optional[str] = None, + quotedMessageId: Optional[str] = None, + archiveChat: Optional[bool] = None + ) -> Response: + request_body = self.__handle_parameters(locals()) + + return await self.api.requestAsync( + "POST", + "{{host}}/waInstance{{idInstance}}/sendFileByUrl/{{apiTokenInstance}}", + request_body + ) + def uploadFile(self, path: str) -> Response: """ The method is designed to upload a file to the cloud storage, @@ -191,6 +254,22 @@ def uploadFile(self, path: str) -> Response: "GA-Filename": file_name} ) + async def uploadFileAsync(self, path: str) -> Response: + file_name = pathlib.Path(path).name + content_type = mimetypes.guess_type(file_name)[0] + + async with aiofiles.open(path, "rb") as file: + return await self.api.raw_request_async( + method="POST", + url=( + f"{self.api.media}/waInstance{self.api.idInstance}/" + f"uploadFile/{self.api.apiTokenInstance}" + ), + data=file.read(), + headers={"Content-Type": content_type, + "GA-Filename": file_name} + ) + def sendLocation( self, chatId: str, @@ -215,6 +294,23 @@ def sendLocation( ), request_body ) + async def sendLocationAsync( + self, + chatId: str, + latitude: float, + longitude: float, + nameLocation: Optional[str] = None, + address: Optional[str] = None, + quotedMessageId: Optional[str] = None + ) -> Response: + request_body = self.__handle_parameters(locals()) + + return await self.api.requestAsync( + "POST", + "{{host}}/waInstance{{idInstance}}/sendLocation/{{apiTokenInstance}}", + request_body + ) + def sendContact( self, chatId: str, @@ -236,6 +332,20 @@ def sendContact( ), request_body ) + async def sendContactAsync( + self, + chatId: str, + contact: Dict[str, Union[int, str]], + quotedMessageId: Optional[str] = None + ) -> Response: + request_body = self.__handle_parameters(locals()) + + return await self.api.requestAsync( + "POST", + "{{host}}/waInstance{{idInstance}}/sendContact/{{apiTokenInstance}}", + request_body + ) + def sendLink( self, chatId: str, @@ -282,6 +392,20 @@ def forwardMessages( ), request_body ) + async def forwardMessagesAsync( + self, + chatId: str, + chatIdFrom: str, + messages: List[str] + ) -> Response: + request_body = self.__handle_parameters(locals()) + + return await self.api.requestAsync( + "POST", + "{{host}}/waInstance{{idInstance}}/forwardMessages/{{apiTokenInstance}}", + request_body + ) + def sendPoll( self, chatId: str, @@ -306,14 +430,29 @@ def sendPoll( ), request_body ) + async def sendPollAsync( + self, + chatId: str, + message: str, + options: List[Dict[str, str]], + multipleAnswers: Optional[bool] = None, + quotedMessageId: Optional[str] = None + ) -> Response: + request_body = self.__handle_parameters(locals()) + + return await self.api.requestAsync( + "POST", + "{{host}}/waInstance{{idInstance}}/sendPoll/{{apiTokenInstance}}", + request_body + ) + @classmethod def __handle_parameters(cls, parameters: dict) -> dict: handled_parameters = parameters.copy() - handled_parameters.pop("self") for key, value in parameters.items(): if value is None: handled_parameters.pop(key) - return handled_parameters + return handled_parameters \ No newline at end of file diff --git a/whatsapp_api_client_python/tools/serviceMethods.py b/whatsapp_api_client_python/tools/serviceMethods.py index 1e2ee4a..43b7b7e 100644 --- a/whatsapp_api_client_python/tools/serviceMethods.py +++ b/whatsapp_api_client_python/tools/serviceMethods.py @@ -5,7 +5,6 @@ if TYPE_CHECKING: from ..API import GreenApi - class ServiceMethods: def __init__(self, api: "GreenApi"): self.api = api @@ -28,6 +27,16 @@ def checkWhatsapp(self, phoneNumber: int) -> Response: ), request_body ) + async def checkWhatsappAsync(self, phoneNumber: int) -> Response: + request_body = locals() + request_body.pop("self") + + return await self.api.requestAsync( + "POST", + "{{host}}/waInstance{{idInstance}}/checkWhatsapp/{{apiTokenInstance}}", + request_body + ) + def getAvatar(self, chatId: str) -> Response: """ The method returns a user or a group chat avatar. @@ -45,6 +54,16 @@ def getAvatar(self, chatId: str) -> Response: ), request_body ) + async def getAvatarAsync(self, chatId: str) -> Response: + request_body = locals() + request_body.pop("self") + + return await self.api.requestAsync( + "POST", + "{{host}}/waInstance{{idInstance}}/getAvatar/{{apiTokenInstance}}", + request_body + ) + def getContacts(self) -> Response: """ The method is aimed for getting a list of the current account @@ -60,6 +79,11 @@ def getContacts(self) -> Response: ) ) + async def getContactsAsync(self) -> Response: + return await self.api.requestAsync( + "GET", "{{host}}/waInstance{{idInstance}}/getContacts/{{apiTokenInstance}}" + ) + def getContactInfo(self, chatId: str) -> Response: """ The method is aimed for getting information on a contact. @@ -77,6 +101,16 @@ def getContactInfo(self, chatId: str) -> Response: ), request_body ) + async def getContactInfoAsync(self, chatId: str) -> Response: + request_body = locals() + request_body.pop("self") + + return await self.api.requestAsync( + "POST", + "{{host}}/waInstance{{idInstance}}/getContactInfo/{{apiTokenInstance}}", + request_body + ) + def deleteMessage(self, chatId: str, idMessage: str, onlySenderDelete: Optional[bool] = None) -> Response: """ The method deletes a message from a chat. @@ -97,6 +131,18 @@ def deleteMessage(self, chatId: str, idMessage: str, onlySenderDelete: Optional[ ), request_body ) + async def deleteMessageAsync(self, chatId: str, idMessage: str, onlySenderDelete: Optional[bool] = None) -> Response: + request_body = locals() + if onlySenderDelete is None: + request_body.pop("onlySenderDelete") + request_body.pop("self") + + return await self.api.requestAsync( + "POST", + "{{host}}/waInstance{{idInstance}}/deleteMessage/{{apiTokenInstance}}", + request_body + ) + def editMessage(self, chatId: str, idMessage: str, message: str) -> Response: """ The method edits a message in chat. @@ -114,6 +160,16 @@ def editMessage(self, chatId: str, idMessage: str, message: str) -> Response: ), request_body ) + async def editMessageAsync(self, chatId: str, idMessage: str, message: str) -> Response: + request_body = locals() + request_body.pop("self") + + return await self.api.requestAsync( + "POST", + "{{host}}/waInstance{{idInstance}}/editMessage/{{apiTokenInstance}}", + request_body + ) + def archiveChat(self, chatId: str) -> Response: """ The method archives a chat. @@ -131,6 +187,16 @@ def archiveChat(self, chatId: str) -> Response: ), request_body ) + async def archiveChatAsync(self, chatId: str) -> Response: + request_body = locals() + request_body.pop("self") + + return await self.api.requestAsync( + "POST", + "{{host}}/waInstance{{idInstance}}/archiveChat/{{apiTokenInstance}}", + request_body + ) + def unarchiveChat(self, chatId: str) -> Response: """ The method unarchives a chat. @@ -148,6 +214,16 @@ def unarchiveChat(self, chatId: str) -> Response: ), request_body ) + async def unarchiveChatAsync(self, chatId: str) -> Response: + request_body = locals() + request_body.pop("self") + + return await self.api.requestAsync( + "POST", + "{{host}}/waInstance{{idInstance}}/unarchiveChat/{{apiTokenInstance}}", + request_body + ) + def setDisappearingChat( self, chatId: str, ephemeralExpiration: Optional[int] = None ) -> Response: @@ -169,3 +245,17 @@ def setDisappearingChat( "setDisappearingChat/{{apiTokenInstance}}" ), request_body ) + + async def setDisappearingChatAsync( + self, chatId: str, ephemeralExpiration: Optional[int] = None + ) -> Response: + request_body = locals() + if ephemeralExpiration is None: + request_body.pop("ephemeralExpiration") + request_body.pop("self") + + return await self.api.requestAsync( + "POST", + "{{host}}/waInstance{{idInstance}}/setDisappearingChat/{{apiTokenInstance}}", + request_body + ) \ No newline at end of file diff --git a/whatsapp_api_client_python/tools/statuses.py b/whatsapp_api_client_python/tools/statuses.py index 1c1c883..1b22139 100644 --- a/whatsapp_api_client_python/tools/statuses.py +++ b/whatsapp_api_client_python/tools/statuses.py @@ -32,6 +32,21 @@ def sendTextStatus( ), request_body ) + async def sendTextStatusAsync( + self, + message: str, + backgroundColor: Optional[str] = None, + font: Optional[str] = None, + participants: Optional[List[str]] = None + ) -> Response: + request_body = self.__handle_parameters(locals()) + + return await self.api.requestAsync( + "POST", + "{{host}}/waInstance{{idInstance}}/sendTextStatus/{{apiTokenInstance}}", + request_body + ) + def sendVoiceStatus( self, urlFile: str, @@ -54,6 +69,21 @@ def sendVoiceStatus( ), request_body ) + async def sendVoiceStatusAsync( + self, + urlFile: str, + fileName: str, + backgroundColor: Optional[str] = None, + participants: Optional[List[str]] = None + ) -> Response: + request_body = self.__handle_parameters(locals()) + + return await self.api.requestAsync( + "POST", + "{{host}}/waInstance{{idInstance}}/sendVoiceStatus/{{apiTokenInstance}}", + request_body + ) + def sendMediaStatus( self, urlFile: str, @@ -76,6 +106,21 @@ def sendMediaStatus( ), request_body ) + async def sendMediaStatusAsync( + self, + urlFile: str, + fileName: str, + caption: Optional[str] = None, + participants: Optional[List[str]] = None + ) -> Response: + request_body = self.__handle_parameters(locals()) + + return await self.api.requestAsync( + "POST", + "{{host}}/waInstance{{idInstance}}/sendMediaStatus/{{apiTokenInstance}}", + request_body + ) + def deleteStatus( self, idMessage: str @@ -95,6 +140,18 @@ def deleteStatus( ), request_body ) + async def deleteStatusAsync( + self, + idMessage: str + ) -> Response: + request_body = self.__handle_parameters(locals()) + + return await self.api.requestAsync( + "POST", + "{{host}}/waInstance{{idInstance}}/deleteStatus/{{apiTokenInstance}}", + request_body + ) + def getStatusStatistic( self, idMessage: str @@ -111,6 +168,14 @@ def getStatusStatistic( return self.api.request("GET", f"{url}?idMessage={idMessage}") + async def getStatusStatisticAsync( + self, + idMessage: str + ) -> Response: + return await self.api.requestAsync( + "GET", + f"{{host}}/waInstance{{idInstance}}/getStatusStatistic/{{apiTokenInstance}}?idMessage={idMessage}") + def getIncomingStatuses( self, minutes: Optional[int] = None @@ -131,6 +196,18 @@ def getIncomingStatuses( else: return self.api.request("GET", f"{url}") + async def getIncomingStatusesAsync( + self, + minutes: Optional[int] = None + ) -> Response: + params = {"minutes": minutes} if minutes else {} + + return await self.api.requestAsync( + "GET", + "{{host}}/waInstance{{idInstance}}/getIncomingStatuses/{{apiTokenInstance}}", + params + ) + def getOutgoingStatuses( self, minutes: Optional[int] = None @@ -151,6 +228,17 @@ def getOutgoingStatuses( else: return self.api.request("GET", f"{url}") + async def getOutgoingStatusesAsync( + self, + minutes: Optional[int] = None + ) -> Response: + + params = {"minutes": minutes} if minutes else {} + return await self.api.requestAsync( + "GET", + "{{host}}/waInstance{{idInstance}}/getOutgoingStatuses/{{apiTokenInstance}}", + params) + @classmethod def __handle_parameters(cls, parameters: dict) -> dict: handled_parameters = parameters.copy() diff --git a/whatsapp_api_client_python/tools/webhooks.py b/whatsapp_api_client_python/tools/webhooks.py index eba2635..5fab98d 100644 --- a/whatsapp_api_client_python/tools/webhooks.py +++ b/whatsapp_api_client_python/tools/webhooks.py @@ -1,10 +1,10 @@ +import asyncio import logging from typing import Any, Callable, Optional, TYPE_CHECKING if TYPE_CHECKING: from ..API import GreenApi - class Webhooks: _running: Optional[bool] = None @@ -34,9 +34,19 @@ def startReceivingNotifications( self._start_polling(onEvent) + async def startReceivingNotificationsAsync( + self, onEvent: Callable[[str, dict], Any] + ) -> None: + self._running = True + + await self._start_pollingAsync(onEvent) + def stopReceivingNotifications(self) -> None: self._running = False + async def stopReceivingNotificationsAsync(self) -> None: + self._running = False + def job(self, onEvent: Callable[[str, dict], Any]) -> None: """Deprecated""" @@ -99,3 +109,36 @@ def _start_polling(self, handler: Callable[[str, dict], Any]) -> None: self.api.logger.log( logging.INFO, "Stopped receiving incoming notifications." ) + + async def _start_pollingAsync(self, handler: Callable[[str, dict], Any]) -> None: + self.api.session.headers["Connection"] = "keep-alive" + + self.api.logger.log( + logging.INFO, "Started receiving incoming notifications asynchronously." + ) + + while self._running: + try: + response = await self.api.receiving.receiveNotificationAsync() + if response.code == 200: + if not response.data: + await asyncio.sleep(0.1) + continue + response = response.data + + body = response["body"] + type_webhook = body["typeWebhook"] + + handler(type_webhook, body) + + await self.api.receiving.deleteNotificationAsync( + response["receiptId"] + ) + except KeyboardInterrupt: + break + + self.api.session.headers["Connection"] = "close" + + self.api.logger.log( + logging.INFO, "Stopped receiving incoming notifications asynchronously." + )