hiws 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hiws-0.1.0/LICENSE +21 -0
- hiws-0.1.0/PKG-INFO +95 -0
- hiws-0.1.0/README.md +58 -0
- hiws-0.1.0/hiws/WhatsappMessenger.py +436 -0
- hiws-0.1.0/hiws/__init__.py +4 -0
- hiws-0.1.0/hiws/types/__init__.py +5 -0
- hiws-0.1.0/hiws/types/exceptions/WhatsappApiException.py +91 -0
- hiws-0.1.0/hiws/types/exceptions/__init__.py +3 -0
- hiws-0.1.0/hiws/types/message/__init__.py +81 -0
- hiws-0.1.0/hiws/types/message/contact.py +53 -0
- hiws-0.1.0/hiws/types/status/__init__.py +54 -0
- hiws-0.1.0/hiws/types/update.py +41 -0
- hiws-0.1.0/hiws.egg-info/PKG-INFO +95 -0
- hiws-0.1.0/hiws.egg-info/SOURCES.txt +18 -0
- hiws-0.1.0/hiws.egg-info/dependency_links.txt +1 -0
- hiws-0.1.0/hiws.egg-info/requires.txt +9 -0
- hiws-0.1.0/hiws.egg-info/top_level.txt +1 -0
- hiws-0.1.0/pyproject.toml +54 -0
- hiws-0.1.0/setup.cfg +4 -0
- hiws-0.1.0/setup.py +41 -0
hiws-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Tomas Santana
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
hiws-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: hiws
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A wrapper for WhatsApp's Cloud API
|
|
5
|
+
Home-page: https://github.com/cervant-ai/hiws
|
|
6
|
+
Author: Tomas Santana
|
|
7
|
+
Author-email: Tomas Santana <tomas@cervant.chat>
|
|
8
|
+
License: MIT
|
|
9
|
+
Project-URL: Homepage, https://github.com/cervant-ai/hiws
|
|
10
|
+
Project-URL: Repository, https://github.com/cervant-ai/hiws.git
|
|
11
|
+
Project-URL: Issues, https://github.com/cervant-ai/hiws/issues
|
|
12
|
+
Keywords: hiws,python,package
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Requires-Python: >=3.7
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Requires-Dist: pydantic>=2.11.7
|
|
26
|
+
Requires-Dist: httpx>=0.28.1
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: pytest>=6.0; extra == "dev"
|
|
29
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
30
|
+
Requires-Dist: black>=21.0; extra == "dev"
|
|
31
|
+
Requires-Dist: flake8>=3.8.0; extra == "dev"
|
|
32
|
+
Requires-Dist: mypy>=0.800; extra == "dev"
|
|
33
|
+
Dynamic: author
|
|
34
|
+
Dynamic: home-page
|
|
35
|
+
Dynamic: license-file
|
|
36
|
+
Dynamic: requires-python
|
|
37
|
+
|
|
38
|
+
# hiws
|
|
39
|
+
|
|
40
|
+
A Python package for interacting with WhatsApp's Cloud API.
|
|
41
|
+
|
|
42
|
+
## Installation
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install hiws
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Usage
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from hiws import WhatsAppMessenger
|
|
52
|
+
|
|
53
|
+
messenger = WhatsAppMessenger(access_token="YOUR_ACCESS_TOKEN", phone_number_id="YOUR_PHONE_NUMBER_ID")
|
|
54
|
+
|
|
55
|
+
# Example usage
|
|
56
|
+
message_id = await messenger.send_text("Hello, World!", recipient_id="RECIPIENT_ID")
|
|
57
|
+
|
|
58
|
+
print(message_id)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Development
|
|
62
|
+
|
|
63
|
+
To install in development mode:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
git clone https://github.com/cervant-ai/hiws.git
|
|
67
|
+
cd hiws
|
|
68
|
+
pip install -e .
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
To install development dependencies:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
pip install -e ".[dev]"
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Testing
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
pytest
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
pip install -e ".[dev]"
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Testing
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
pytest
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## License
|
|
94
|
+
|
|
95
|
+
This project is licensed under the MIT License - see the LICENSE file for details.
|
hiws-0.1.0/README.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# hiws
|
|
2
|
+
|
|
3
|
+
A Python package for interacting with WhatsApp's Cloud API.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install hiws
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from hiws import WhatsAppMessenger
|
|
15
|
+
|
|
16
|
+
messenger = WhatsAppMessenger(access_token="YOUR_ACCESS_TOKEN", phone_number_id="YOUR_PHONE_NUMBER_ID")
|
|
17
|
+
|
|
18
|
+
# Example usage
|
|
19
|
+
message_id = await messenger.send_text("Hello, World!", recipient_id="RECIPIENT_ID")
|
|
20
|
+
|
|
21
|
+
print(message_id)
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Development
|
|
25
|
+
|
|
26
|
+
To install in development mode:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
git clone https://github.com/cervant-ai/hiws.git
|
|
30
|
+
cd hiws
|
|
31
|
+
pip install -e .
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
To install development dependencies:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install -e ".[dev]"
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Testing
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pytest
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install -e ".[dev]"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Testing
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pytest
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## License
|
|
57
|
+
|
|
58
|
+
This project is licensed under the MIT License - see the LICENSE file for details.
|
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
import httpx
|
|
2
|
+
from hiws.types.exceptions import WhatsappApiException
|
|
3
|
+
from hiws.types import Contact
|
|
4
|
+
from typing import Optional, Any, Dict
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
BASE_URL = "https://graph.facebook.com/{API_VERSION}"
|
|
8
|
+
MESSAGES_ENDPOINT = "/{phone_number_id}/messages"
|
|
9
|
+
DEFAULT_TIMEOUT = 15.0
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class WhatsappMessenger:
|
|
13
|
+
"""
|
|
14
|
+
A class to interact with the WhatsApp Cloud API.
|
|
15
|
+
|
|
16
|
+
Attributes:
|
|
17
|
+
access_token (str): The access token for authentication.
|
|
18
|
+
phone_number_id (str): The phone number ID associated with the WhatsApp Business account.
|
|
19
|
+
api_version (str): The version of the WhatsApp API to use.
|
|
20
|
+
request_timeout (float): Timeout for HTTP requests in seconds. Default is 15.0 seconds.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
access_token: str
|
|
24
|
+
phone_number_id: str
|
|
25
|
+
api_version: str
|
|
26
|
+
request_timeout: float = DEFAULT_TIMEOUT
|
|
27
|
+
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
access_token: str,
|
|
31
|
+
phone_number_id: str,
|
|
32
|
+
api_version: str = "v23.0",
|
|
33
|
+
request_timeout: float = DEFAULT_TIMEOUT,
|
|
34
|
+
):
|
|
35
|
+
self.access_token = access_token
|
|
36
|
+
self.phone_number_id = phone_number_id
|
|
37
|
+
self.api_version = api_version
|
|
38
|
+
self.base_url = BASE_URL.format(API_VERSION=self.api_version)
|
|
39
|
+
self.messages_endpoint = MESSAGES_ENDPOINT.format(phone_number_id=self.phone_number_id)
|
|
40
|
+
self.request_timeout = request_timeout
|
|
41
|
+
|
|
42
|
+
async def send_text(
|
|
43
|
+
self,
|
|
44
|
+
recipient_phone_number: str,
|
|
45
|
+
text: str,
|
|
46
|
+
enable_link_preview: bool = True,
|
|
47
|
+
reply_to: Optional[str] = None
|
|
48
|
+
) -> str:
|
|
49
|
+
"""
|
|
50
|
+
Send a text message.
|
|
51
|
+
Args:
|
|
52
|
+
recipient_phone_number (str): The recipient's phone number in international format (without leading '+').
|
|
53
|
+
text (str): The text message to send.
|
|
54
|
+
enable_link_preview (bool): Whether to enable link preview. Default is True.
|
|
55
|
+
reply_to (Optional[str]): Message ID to reply to. Default is None.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
str: The message ID of the sent message.
|
|
59
|
+
|
|
60
|
+
Raises:
|
|
61
|
+
WhatsappApiException: If there is an error sending the message.
|
|
62
|
+
|
|
63
|
+
## Note
|
|
64
|
+
WhatsApp Cloud API has a limit of 4096 characters for text messages.
|
|
65
|
+
Messages longer than this will not be sent.
|
|
66
|
+
|
|
67
|
+
## Documentation
|
|
68
|
+
https://developers.facebook.com/docs/whatsapp/cloud-api/messages/text-messages
|
|
69
|
+
"""
|
|
70
|
+
payload = {
|
|
71
|
+
"messaging_product": "whatsapp",
|
|
72
|
+
"recipient_type": "individual",
|
|
73
|
+
"to": recipient_phone_number,
|
|
74
|
+
"type": "text",
|
|
75
|
+
"text": {"preview_url": enable_link_preview, "body": text},
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if reply_to:
|
|
79
|
+
payload["context"] = {"message_id": reply_to}
|
|
80
|
+
|
|
81
|
+
return await self._send_message_payload(payload)
|
|
82
|
+
|
|
83
|
+
async def send_image(
|
|
84
|
+
self,
|
|
85
|
+
recipient_phone_number: str,
|
|
86
|
+
image_link: Optional[str] = None,
|
|
87
|
+
media_id: Optional[str] = None,
|
|
88
|
+
caption: Optional[str] = None,
|
|
89
|
+
reply_to: Optional[str] = None
|
|
90
|
+
) -> str:
|
|
91
|
+
"""
|
|
92
|
+
Send an image.
|
|
93
|
+
Args:
|
|
94
|
+
recipient_phone_number (str): The recipient's phone number in international format (without leading '+').
|
|
95
|
+
image_link (Optional[str]): The URL of the image to send. Default is None.
|
|
96
|
+
media_id (Optional[str]): The media ID of a previously uploaded image. Default is None.
|
|
97
|
+
caption (Optional[str]): Caption for the image. Default is None.
|
|
98
|
+
reply_to (Optional[str]): Message ID to reply to. Default is None.
|
|
99
|
+
Returns:
|
|
100
|
+
str: The message ID of the sent message.
|
|
101
|
+
Raises:
|
|
102
|
+
ValueError: If neither image_link nor media_id is provided.
|
|
103
|
+
WhatsappApiException: If there is an error sending the message.
|
|
104
|
+
## Note
|
|
105
|
+
Supported formats: JPEG, PNG.
|
|
106
|
+
Max file size: 5 MB.
|
|
107
|
+
## Documentation
|
|
108
|
+
https://developers.facebook.com/docs/whatsapp/cloud-api/messages/image-messages
|
|
109
|
+
"""
|
|
110
|
+
return await self._send_media(
|
|
111
|
+
media_type="image",
|
|
112
|
+
media_link=image_link,
|
|
113
|
+
media_id=media_id,
|
|
114
|
+
caption=caption,
|
|
115
|
+
recipient_phone_number=recipient_phone_number,
|
|
116
|
+
reply_to=reply_to
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
async def send_document(
|
|
120
|
+
self,
|
|
121
|
+
recipient_phone_number: str,
|
|
122
|
+
document_link: Optional[str] = None,
|
|
123
|
+
media_id: Optional[str] = None,
|
|
124
|
+
caption: Optional[str] = None,
|
|
125
|
+
filename: Optional[str] = None,
|
|
126
|
+
reply_to: Optional[str] = None
|
|
127
|
+
) -> str:
|
|
128
|
+
"""
|
|
129
|
+
Send a document.
|
|
130
|
+
Args:
|
|
131
|
+
recipient_phone_number (str): The recipient's phone number in international format (without leading '+').
|
|
132
|
+
document_link (Optional[str]): The URL of the document to send. Default is None.
|
|
133
|
+
media_id (Optional[str]): The media ID of a previously uploaded document. Default is None.
|
|
134
|
+
caption (Optional[str]): Caption for the document. Default is None.
|
|
135
|
+
filename (Optional[str]): Filename for the document. Default is None.
|
|
136
|
+
reply_to (Optional[str]): Message ID to reply to. Default is None.
|
|
137
|
+
Returns:
|
|
138
|
+
str: The message ID of the sent message.
|
|
139
|
+
Raises:
|
|
140
|
+
ValueError: If neither document_link nor media_id is provided.
|
|
141
|
+
WhatsappApiException: If there is an error sending the message.
|
|
142
|
+
## Note
|
|
143
|
+
Supported formats: PDF, XLS, XLSX, DOC, DOCX, PPT, PPTX, TXT.
|
|
144
|
+
Max file size: 100 MB.
|
|
145
|
+
## Documentation
|
|
146
|
+
https://developers.facebook.com/docs/whatsapp/cloud-api/messages/document-messages
|
|
147
|
+
"""
|
|
148
|
+
return await self._send_media(
|
|
149
|
+
media_type="document",
|
|
150
|
+
media_link=document_link,
|
|
151
|
+
media_id=media_id,
|
|
152
|
+
caption=caption,
|
|
153
|
+
filename=filename,
|
|
154
|
+
recipient_phone_number=recipient_phone_number,
|
|
155
|
+
reply_to=reply_to
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
async def send_audio(
|
|
159
|
+
self,
|
|
160
|
+
recipient_phone_number: str,
|
|
161
|
+
audio_link: Optional[str] = None,
|
|
162
|
+
media_id: Optional[str] = None,
|
|
163
|
+
caption: Optional[str] = None,
|
|
164
|
+
reply_to: Optional[str] = None
|
|
165
|
+
) -> str:
|
|
166
|
+
"""
|
|
167
|
+
Send an audio message.
|
|
168
|
+
Args:
|
|
169
|
+
recipient_phone_number (str): The recipient's phone number in international format (without leading '+').
|
|
170
|
+
audio_link (Optional[str]): The URL of the audio to send. Default is None.
|
|
171
|
+
media_id (Optional[str]): The media ID of a previously uploaded audio. Default is None.
|
|
172
|
+
caption (Optional[str]): Caption for the audio. Default is None.
|
|
173
|
+
reply_to (Optional[str]): Message ID to reply to. Default is None.
|
|
174
|
+
Returns:
|
|
175
|
+
str: The message ID of the sent message.
|
|
176
|
+
Raises:
|
|
177
|
+
ValueError: If neither audio_link nor media_id is provided.
|
|
178
|
+
WhatsappApiException: If there is an error sending the message.
|
|
179
|
+
## Note
|
|
180
|
+
Supported formats: AAC, AMR, MP3, M4A, OGG (only OPUS codec).
|
|
181
|
+
Max file size: 16 MB.
|
|
182
|
+
"""
|
|
183
|
+
return await self._send_media(
|
|
184
|
+
media_type="audio",
|
|
185
|
+
media_link=audio_link,
|
|
186
|
+
media_id=media_id,
|
|
187
|
+
caption=caption,
|
|
188
|
+
recipient_phone_number=recipient_phone_number,
|
|
189
|
+
reply_to=reply_to
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
async def send_video(
|
|
193
|
+
self,
|
|
194
|
+
recipient_phone_number: str,
|
|
195
|
+
video_link: Optional[str] = None,
|
|
196
|
+
media_id: Optional[str] = None,
|
|
197
|
+
caption: Optional[str] = None,
|
|
198
|
+
reply_to: Optional[str] = None
|
|
199
|
+
) -> str:
|
|
200
|
+
"""
|
|
201
|
+
Send a video message.
|
|
202
|
+
Args:
|
|
203
|
+
recipient_phone_number (str): The recipient's phone number in international format (without leading '+').
|
|
204
|
+
video_link (Optional[str]): The URL of the video to send. Default is None.
|
|
205
|
+
media_id (Optional[str]): The media ID of a previously uploaded video. Default is None.
|
|
206
|
+
caption (Optional[str]): Caption for the video. Default is None.
|
|
207
|
+
reply_to (Optional[str]): Message ID to reply to. Default is None.
|
|
208
|
+
Returns:
|
|
209
|
+
str: The message ID of the sent message.
|
|
210
|
+
Raises:
|
|
211
|
+
ValueError: If neither video_link nor media_id is provided.
|
|
212
|
+
WhatsappApiException: If there is an error sending the message.
|
|
213
|
+
## Note
|
|
214
|
+
Supported formats: MP4, 3GPP.
|
|
215
|
+
Max file size: 16 MB.
|
|
216
|
+
## Documentation
|
|
217
|
+
https://developers.facebook.com/docs/whatsapp/cloud-api/messages/video-messages
|
|
218
|
+
"""
|
|
219
|
+
return await self._send_media(
|
|
220
|
+
media_type="video",
|
|
221
|
+
media_link=video_link,
|
|
222
|
+
media_id=media_id,
|
|
223
|
+
caption=caption,
|
|
224
|
+
recipient_phone_number=recipient_phone_number,
|
|
225
|
+
reply_to=reply_to
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
async def send_contact(
|
|
229
|
+
self,
|
|
230
|
+
recipient_phone_number: str,
|
|
231
|
+
contact: Contact | Dict[str, Any],
|
|
232
|
+
) -> str:
|
|
233
|
+
"""
|
|
234
|
+
Send a contact message.
|
|
235
|
+
Args:
|
|
236
|
+
recipient_phone_number (str): The recipient's phone number in international format (without leading '+').
|
|
237
|
+
contact (Contact | Dict[str, Any]): The contact information to send.
|
|
238
|
+
Returns:
|
|
239
|
+
str: The message ID of the sent message.
|
|
240
|
+
Raises:
|
|
241
|
+
ValueError: If contact is invalid.
|
|
242
|
+
WhatsappApiException: If there is an error sending the message.
|
|
243
|
+
"""
|
|
244
|
+
if isinstance(contact, Contact):
|
|
245
|
+
contact = contact.model_dump(mode="json", exclude_none=True)
|
|
246
|
+
|
|
247
|
+
payload = {
|
|
248
|
+
"messaging_product": "whatsapp",
|
|
249
|
+
"recipient_type": "individual",
|
|
250
|
+
"to": recipient_phone_number,
|
|
251
|
+
"type": "contact",
|
|
252
|
+
"contact": contact,
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return await self._send_message_payload(payload)
|
|
256
|
+
|
|
257
|
+
async def mark_as_read(
|
|
258
|
+
self,
|
|
259
|
+
message_id: str
|
|
260
|
+
) -> None:
|
|
261
|
+
"""
|
|
262
|
+
Mark a message as read.
|
|
263
|
+
Args:
|
|
264
|
+
message_id (str): The ID of the message to mark as read.
|
|
265
|
+
Returns:
|
|
266
|
+
str: The message ID of the read receipt.
|
|
267
|
+
Raises:
|
|
268
|
+
WhatsappApiException: If there is an error sending the read receipt.
|
|
269
|
+
## Documentation
|
|
270
|
+
https://developers.facebook.com/docs/whatsapp/cloud-api/guides/mark-message-as-read
|
|
271
|
+
"""
|
|
272
|
+
payload = {
|
|
273
|
+
"messaging_product": "whatsapp",
|
|
274
|
+
"status": "read",
|
|
275
|
+
"message_id": message_id
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
await self._send_mark_as_read_payload(payload)
|
|
279
|
+
|
|
280
|
+
async def send_typing_indicator(
|
|
281
|
+
self,
|
|
282
|
+
message_id: str
|
|
283
|
+
) -> None:
|
|
284
|
+
"""
|
|
285
|
+
Send a typing indicator.
|
|
286
|
+
Args:
|
|
287
|
+
message_id (str): The ID of the message to which the typing indicator relates. This message will be marked as read.
|
|
288
|
+
Returns:
|
|
289
|
+
str: The message ID of the typing indicator.
|
|
290
|
+
"""
|
|
291
|
+
payload = {
|
|
292
|
+
"messaging_product": "whatsapp",
|
|
293
|
+
"status": "read",
|
|
294
|
+
"message_id": message_id,
|
|
295
|
+
"typing_indicator": {
|
|
296
|
+
"type": "text",
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
await self._send_mark_as_read_payload(payload)
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
async def _send_media(
|
|
307
|
+
self,
|
|
308
|
+
recipient_phone_number: str,
|
|
309
|
+
media_type: str,
|
|
310
|
+
media_link: Optional[str] = None,
|
|
311
|
+
media_id: Optional[str] = None,
|
|
312
|
+
caption: Optional[str] = None,
|
|
313
|
+
filename: Optional[str] = None,
|
|
314
|
+
reply_to: Optional[str] = None
|
|
315
|
+
) -> str:
|
|
316
|
+
if not media_link and not media_id:
|
|
317
|
+
raise ValueError("Either media_link or media_id must be provided.")
|
|
318
|
+
|
|
319
|
+
media_payload = {}
|
|
320
|
+
if media_link:
|
|
321
|
+
media_payload["link"] = media_link
|
|
322
|
+
if media_id:
|
|
323
|
+
media_payload["id"] = media_id
|
|
324
|
+
if caption:
|
|
325
|
+
media_payload["caption"] = caption
|
|
326
|
+
if filename and media_type == "document":
|
|
327
|
+
media_payload["filename"] = filename
|
|
328
|
+
|
|
329
|
+
payload = {
|
|
330
|
+
"messaging_product": "whatsapp",
|
|
331
|
+
"recipient_type": "individual",
|
|
332
|
+
"to": recipient_phone_number,
|
|
333
|
+
"type": media_type,
|
|
334
|
+
media_type: media_payload,
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if reply_to:
|
|
338
|
+
payload["context"] = {"message_id": reply_to}
|
|
339
|
+
|
|
340
|
+
return await self._send_message_payload(payload)
|
|
341
|
+
|
|
342
|
+
async def _send_message_payload(self, payload: dict) -> str:
|
|
343
|
+
"""
|
|
344
|
+
Send a custom payload to WhatsApp's API messages endpoint with robust error handling.
|
|
345
|
+
|
|
346
|
+
Args:
|
|
347
|
+
payload (dict): The payload to send.
|
|
348
|
+
timeout (Optional[float]): Request timeout in seconds. Defaults to DEFAULT_TIMEOUT.
|
|
349
|
+
|
|
350
|
+
Returns:
|
|
351
|
+
SendResponse: A mapping with at least the "message_id" and, when available, "recipient_id".
|
|
352
|
+
|
|
353
|
+
Raises:
|
|
354
|
+
WhatsappApiException: For network errors, non-2xx responses, or malformed responses.
|
|
355
|
+
"""
|
|
356
|
+
url = f"{self.base_url}{self.messages_endpoint}"
|
|
357
|
+
response = await self._send_payload(self.messages_endpoint, payload)
|
|
358
|
+
|
|
359
|
+
try:
|
|
360
|
+
data = response.json()
|
|
361
|
+
except ValueError as e:
|
|
362
|
+
raise WhatsappApiException(
|
|
363
|
+
message="Failed to parse JSON response",
|
|
364
|
+
endpoint=self.messages_endpoint,
|
|
365
|
+
method="POST",
|
|
366
|
+
payload=payload,
|
|
367
|
+
status_code=response.status_code,
|
|
368
|
+
details=str(e),
|
|
369
|
+
) from e
|
|
370
|
+
|
|
371
|
+
message_id = data.get("message_id") or data.get("id")
|
|
372
|
+
if not message_id:
|
|
373
|
+
raise WhatsappApiException(
|
|
374
|
+
message="Missing message_id in successful response",
|
|
375
|
+
endpoint=url,
|
|
376
|
+
method="POST",
|
|
377
|
+
payload=payload,
|
|
378
|
+
status_code=response.status_code,
|
|
379
|
+
response_json=data,
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
return message_id
|
|
383
|
+
|
|
384
|
+
async def _send_mark_as_read_payload(self, payload: dict) -> None:
|
|
385
|
+
response = await self._send_payload(self.messages_endpoint, payload)
|
|
386
|
+
try:
|
|
387
|
+
data = response.json()
|
|
388
|
+
except ValueError as e:
|
|
389
|
+
raise WhatsappApiException(
|
|
390
|
+
message="Failed to parse JSON response",
|
|
391
|
+
endpoint=self.messages_endpoint,
|
|
392
|
+
method="POST",
|
|
393
|
+
payload=payload,
|
|
394
|
+
status_code=response.status_code,
|
|
395
|
+
details=str(e),
|
|
396
|
+
) from e
|
|
397
|
+
success = data.get("success")
|
|
398
|
+
if not success:
|
|
399
|
+
raise WhatsappApiException(
|
|
400
|
+
message="Failed to mark message as read",
|
|
401
|
+
endpoint=self.messages_endpoint,
|
|
402
|
+
method="POST",
|
|
403
|
+
payload=payload,
|
|
404
|
+
status_code=response.status_code,
|
|
405
|
+
response_json=data,
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
async def _send_payload(self, endpoint: str, payload: dict) -> httpx.Response:
|
|
409
|
+
url = f"{self.base_url}{endpoint}"
|
|
410
|
+
headers = {
|
|
411
|
+
"Authorization": f"Bearer {self.access_token}",
|
|
412
|
+
"Content-Type": "application/json",
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
try:
|
|
416
|
+
async with httpx.AsyncClient(timeout=self.request_timeout) as client:
|
|
417
|
+
response = await client.post(url, json=payload, headers=headers)
|
|
418
|
+
except httpx.RequestError as e:
|
|
419
|
+
raise WhatsappApiException(
|
|
420
|
+
message="Network error while sending payload",
|
|
421
|
+
endpoint=url,
|
|
422
|
+
method="POST",
|
|
423
|
+
payload=payload,
|
|
424
|
+
details=str(e),
|
|
425
|
+
) from e
|
|
426
|
+
|
|
427
|
+
# Non-2xx -> raise a structured exception with parsed error body when possible
|
|
428
|
+
if response.status_code < 200 or response.status_code >= 300:
|
|
429
|
+
raise WhatsappApiException.from_httpx_response(
|
|
430
|
+
response,
|
|
431
|
+
endpoint=url,
|
|
432
|
+
method="POST",
|
|
433
|
+
payload=payload,
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
return response
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, Optional
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class WhatsappApiException(Exception):
|
|
7
|
+
"""Exception raised for Instagram API errors.
|
|
8
|
+
|
|
9
|
+
Captures HTTP details, endpoint, method, payload, and parsed error body when available.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
def __init__(
|
|
13
|
+
self,
|
|
14
|
+
message: str,
|
|
15
|
+
*,
|
|
16
|
+
status_code: Optional[int] = None,
|
|
17
|
+
endpoint: Optional[str] = None,
|
|
18
|
+
method: Optional[str] = None,
|
|
19
|
+
payload: Optional[Dict[str, Any]] = None,
|
|
20
|
+
response_text: Optional[str] = None,
|
|
21
|
+
response_json: Optional[Dict[str, Any]] = None,
|
|
22
|
+
details: Optional[str] = None,
|
|
23
|
+
error_code: Optional[str] = None,
|
|
24
|
+
) -> None:
|
|
25
|
+
super().__init__(message)
|
|
26
|
+
self.message = message
|
|
27
|
+
self.status_code = status_code
|
|
28
|
+
self.endpoint = endpoint
|
|
29
|
+
self.method = method
|
|
30
|
+
self.payload = payload
|
|
31
|
+
self.response_text = response_text
|
|
32
|
+
self.response_json = response_json
|
|
33
|
+
self.details = details
|
|
34
|
+
self.error_code = error_code
|
|
35
|
+
|
|
36
|
+
def __str__(self) -> str:
|
|
37
|
+
base = self.message
|
|
38
|
+
parts = []
|
|
39
|
+
if self.status_code is not None:
|
|
40
|
+
parts.append(f"status={self.status_code}")
|
|
41
|
+
if self.endpoint:
|
|
42
|
+
parts.append(f"endpoint={self.endpoint}")
|
|
43
|
+
if self.method:
|
|
44
|
+
parts.append(f"method={self.method}")
|
|
45
|
+
if self.error_code:
|
|
46
|
+
parts.append(f"error_code={self.error_code}")
|
|
47
|
+
if self.details:
|
|
48
|
+
parts.append(f"details={self.details}")
|
|
49
|
+
if parts:
|
|
50
|
+
base += " (" + ", ".join(parts) + ")"
|
|
51
|
+
return base
|
|
52
|
+
|
|
53
|
+
@classmethod
|
|
54
|
+
def from_httpx_response(
|
|
55
|
+
cls,
|
|
56
|
+
response: Any,
|
|
57
|
+
*,
|
|
58
|
+
endpoint: Optional[str] = None,
|
|
59
|
+
method: Optional[str] = None,
|
|
60
|
+
payload: Optional[Dict[str, Any]] = None,
|
|
61
|
+
) -> "WhatsappApiException":
|
|
62
|
+
"""
|
|
63
|
+
Build an exception from an httpx.Response, attempting to parse JSON error details.
|
|
64
|
+
"""
|
|
65
|
+
status = getattr(response, "status_code", None)
|
|
66
|
+
text = None
|
|
67
|
+
data: Optional[Dict[str, Any]] = None
|
|
68
|
+
code: Optional[str] = None
|
|
69
|
+
message: str = "Instagram API request failed"
|
|
70
|
+
|
|
71
|
+
try:
|
|
72
|
+
data = response.json()
|
|
73
|
+
# Graph API errors are often under { "error": { "message": "...", "code": 190, ... } }
|
|
74
|
+
err = data.get("error") if isinstance(data, dict) else None
|
|
75
|
+
if isinstance(err, dict):
|
|
76
|
+
message = err.get("message") or message
|
|
77
|
+
code_value = err.get("code")
|
|
78
|
+
code = str(code_value) if code_value is not None else None
|
|
79
|
+
except Exception:
|
|
80
|
+
text = getattr(response, "text", None)
|
|
81
|
+
|
|
82
|
+
return cls(
|
|
83
|
+
message=message,
|
|
84
|
+
status_code=status,
|
|
85
|
+
endpoint=endpoint,
|
|
86
|
+
method=method,
|
|
87
|
+
payload=payload,
|
|
88
|
+
response_text=text,
|
|
89
|
+
response_json=data,
|
|
90
|
+
error_code=code,
|
|
91
|
+
)
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
from pydantic import BaseModel, Field
|
|
2
|
+
from typing import List, Optional
|
|
3
|
+
from hiws.types.message.contact import Contact
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class BaseMessage(BaseModel):
|
|
7
|
+
from_phone_number: str = Field(alias="from")
|
|
8
|
+
id: str
|
|
9
|
+
timestamp: str
|
|
10
|
+
|
|
11
|
+
class Text(BaseModel):
|
|
12
|
+
body: str
|
|
13
|
+
|
|
14
|
+
class TextMessage(BaseMessage):
|
|
15
|
+
text: Text
|
|
16
|
+
type: str = "text"
|
|
17
|
+
|
|
18
|
+
class Reaction(BaseModel):
|
|
19
|
+
message_id: str
|
|
20
|
+
emoji: str
|
|
21
|
+
|
|
22
|
+
class ReactionMessage(BaseMessage):
|
|
23
|
+
reaction: Reaction
|
|
24
|
+
type: str = "reaction"
|
|
25
|
+
|
|
26
|
+
class Media(BaseModel):
|
|
27
|
+
id: str
|
|
28
|
+
mime_type: str
|
|
29
|
+
caption: str
|
|
30
|
+
sha256: str
|
|
31
|
+
|
|
32
|
+
class ImageMessage(BaseMessage):
|
|
33
|
+
image: Media
|
|
34
|
+
type: str = "image"
|
|
35
|
+
|
|
36
|
+
class StickerMessage(BaseMessage):
|
|
37
|
+
sticker: Media
|
|
38
|
+
type: str = "sticker"
|
|
39
|
+
|
|
40
|
+
class MessageError(BaseModel):
|
|
41
|
+
code: int
|
|
42
|
+
details: str
|
|
43
|
+
title: str
|
|
44
|
+
|
|
45
|
+
class UnknownMessage(BaseModel):
|
|
46
|
+
errors: List[MessageError]
|
|
47
|
+
type: str = "unknown"
|
|
48
|
+
|
|
49
|
+
class Location(BaseModel):
|
|
50
|
+
latitude: float
|
|
51
|
+
longitude: float
|
|
52
|
+
name: Optional[str]
|
|
53
|
+
address: Optional[str]
|
|
54
|
+
|
|
55
|
+
class LocationMessage(BaseMessage):
|
|
56
|
+
location: Location
|
|
57
|
+
type: str = "location"
|
|
58
|
+
|
|
59
|
+
class ContactMessage(BaseMessage):
|
|
60
|
+
contacts: List[Contact]
|
|
61
|
+
type: str = "contacts"
|
|
62
|
+
|
|
63
|
+
class Button(BaseModel):
|
|
64
|
+
text: str
|
|
65
|
+
payload: Optional[str]
|
|
66
|
+
|
|
67
|
+
class QuickReplyButtonMessage(BaseMessage):
|
|
68
|
+
button: Button
|
|
69
|
+
type: str = "button"
|
|
70
|
+
|
|
71
|
+
class SystemUpdate(BaseModel):
|
|
72
|
+
body: str
|
|
73
|
+
type: str = "system"
|
|
74
|
+
new_wa_id: Optional[str]
|
|
75
|
+
|
|
76
|
+
class SystemMessage(BaseMessage):
|
|
77
|
+
system: SystemUpdate
|
|
78
|
+
type: str = "system"
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
type Message = TextMessage | ReactionMessage | ImageMessage | StickerMessage | LocationMessage | QuickReplyButtonMessage | SystemMessage | UnknownMessage
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from pydantic import BaseModel, Field
|
|
2
|
+
from typing import List, Literal, Optional
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Address(BaseModel):
|
|
6
|
+
city: Optional[str]
|
|
7
|
+
country: Optional[str]
|
|
8
|
+
country_code: Optional[str]
|
|
9
|
+
state: Optional[str]
|
|
10
|
+
street: Optional[str]
|
|
11
|
+
type: Literal["HOME", "WORK"] = "HOME"
|
|
12
|
+
zip: Optional[str]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Email(BaseModel):
|
|
16
|
+
email: str
|
|
17
|
+
type: Literal["HOME", "WORK"] = "HOME"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Name(BaseModel):
|
|
21
|
+
formatted_name: Optional[str]
|
|
22
|
+
first_name: Optional[str]
|
|
23
|
+
last_name: Optional[str]
|
|
24
|
+
middle_name: Optional[str]
|
|
25
|
+
prefix: Optional[str]
|
|
26
|
+
suffix: Optional[str]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class Org(BaseModel):
|
|
30
|
+
company: Optional[str]
|
|
31
|
+
department: Optional[str]
|
|
32
|
+
title: Optional[str]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class Phone(BaseModel):
|
|
36
|
+
phone: str
|
|
37
|
+
wa_id: Optional[str]
|
|
38
|
+
type: Literal["HOME", "WORK", "MOBILE"] = "HOME"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class Url(BaseModel):
|
|
42
|
+
url: str
|
|
43
|
+
type: Literal["HOME", "WORK"] = "HOME"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class Contact(BaseModel):
|
|
47
|
+
addresses: Optional[List[Address]]
|
|
48
|
+
birthday: Optional[str]
|
|
49
|
+
emails: Optional[List[Email]]
|
|
50
|
+
name: Optional[Name]
|
|
51
|
+
org: Optional[Org]
|
|
52
|
+
phones: List[Phone]
|
|
53
|
+
urls: Optional[List[Url]]
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from pydantic import BaseModel
|
|
2
|
+
from typing import List, Literal, Optional
|
|
3
|
+
|
|
4
|
+
class StatusPricing(BaseModel):
|
|
5
|
+
pricing_model: str
|
|
6
|
+
billable: bool
|
|
7
|
+
category: Optional[str]
|
|
8
|
+
|
|
9
|
+
class StatusConversationOrigin(BaseModel):
|
|
10
|
+
type: str
|
|
11
|
+
|
|
12
|
+
class StatusConversation(BaseModel):
|
|
13
|
+
id: str
|
|
14
|
+
expiration_timestamp: Optional[str]
|
|
15
|
+
origin: Optional[StatusConversationOrigin]
|
|
16
|
+
|
|
17
|
+
class StatusErrorData(BaseModel):
|
|
18
|
+
details: str
|
|
19
|
+
|
|
20
|
+
class StatusError(BaseModel):
|
|
21
|
+
code: int
|
|
22
|
+
title: str
|
|
23
|
+
message: Optional[str]
|
|
24
|
+
error_data: Optional[StatusErrorData]
|
|
25
|
+
href: Optional[str]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class BaseStatus(BaseModel):
|
|
29
|
+
id: str
|
|
30
|
+
status: Literal["delivered", "read", "failed", "sent"]
|
|
31
|
+
timestamp: str
|
|
32
|
+
recipient_id: str
|
|
33
|
+
|
|
34
|
+
class SentStatus(BaseStatus):
|
|
35
|
+
status: Literal["sent"]
|
|
36
|
+
conversation: StatusConversation
|
|
37
|
+
pricing: StatusPricing
|
|
38
|
+
|
|
39
|
+
class DeliveredStatus(BaseStatus):
|
|
40
|
+
status: Literal["delivered"]
|
|
41
|
+
conversation: StatusConversation
|
|
42
|
+
pricing: StatusPricing
|
|
43
|
+
|
|
44
|
+
class ReadStatus(BaseStatus):
|
|
45
|
+
status: Literal["read"]
|
|
46
|
+
|
|
47
|
+
class FailedStatus(BaseStatus):
|
|
48
|
+
status: Literal["failed"]
|
|
49
|
+
errors: List[StatusError]
|
|
50
|
+
|
|
51
|
+
type Status = SentStatus | DeliveredStatus | ReadStatus | FailedStatus
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from typing import List, Optional
|
|
2
|
+
from pydantic import BaseModel
|
|
3
|
+
from hiws.types.message import Message
|
|
4
|
+
from hiws.types.status import Status
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Profile(BaseModel):
|
|
8
|
+
name: str
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class RequestContact(BaseModel):
|
|
12
|
+
profile: Profile
|
|
13
|
+
wa_id: str
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Metadata(BaseModel):
|
|
17
|
+
display_phone_number: str
|
|
18
|
+
phone_number_id: str
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Value(BaseModel):
|
|
22
|
+
messaging_product: str
|
|
23
|
+
metadata: Metadata
|
|
24
|
+
contacts: List[RequestContact]
|
|
25
|
+
messages: Optional[List[Message]] = None
|
|
26
|
+
statuses: Optional[List[Status]] = None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class Change(BaseModel):
|
|
30
|
+
value: Value
|
|
31
|
+
field: str
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class Entry(BaseModel):
|
|
35
|
+
id: str
|
|
36
|
+
changes: List[Change]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class Update(BaseModel):
|
|
40
|
+
object: str
|
|
41
|
+
entry: List[Entry]
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: hiws
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A wrapper for WhatsApp's Cloud API
|
|
5
|
+
Home-page: https://github.com/cervant-ai/hiws
|
|
6
|
+
Author: Tomas Santana
|
|
7
|
+
Author-email: Tomas Santana <tomas@cervant.chat>
|
|
8
|
+
License: MIT
|
|
9
|
+
Project-URL: Homepage, https://github.com/cervant-ai/hiws
|
|
10
|
+
Project-URL: Repository, https://github.com/cervant-ai/hiws.git
|
|
11
|
+
Project-URL: Issues, https://github.com/cervant-ai/hiws/issues
|
|
12
|
+
Keywords: hiws,python,package
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Requires-Python: >=3.7
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Requires-Dist: pydantic>=2.11.7
|
|
26
|
+
Requires-Dist: httpx>=0.28.1
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: pytest>=6.0; extra == "dev"
|
|
29
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
30
|
+
Requires-Dist: black>=21.0; extra == "dev"
|
|
31
|
+
Requires-Dist: flake8>=3.8.0; extra == "dev"
|
|
32
|
+
Requires-Dist: mypy>=0.800; extra == "dev"
|
|
33
|
+
Dynamic: author
|
|
34
|
+
Dynamic: home-page
|
|
35
|
+
Dynamic: license-file
|
|
36
|
+
Dynamic: requires-python
|
|
37
|
+
|
|
38
|
+
# hiws
|
|
39
|
+
|
|
40
|
+
A Python package for interacting with WhatsApp's Cloud API.
|
|
41
|
+
|
|
42
|
+
## Installation
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install hiws
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Usage
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from hiws import WhatsAppMessenger
|
|
52
|
+
|
|
53
|
+
messenger = WhatsAppMessenger(access_token="YOUR_ACCESS_TOKEN", phone_number_id="YOUR_PHONE_NUMBER_ID")
|
|
54
|
+
|
|
55
|
+
# Example usage
|
|
56
|
+
message_id = await messenger.send_text("Hello, World!", recipient_id="RECIPIENT_ID")
|
|
57
|
+
|
|
58
|
+
print(message_id)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Development
|
|
62
|
+
|
|
63
|
+
To install in development mode:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
git clone https://github.com/cervant-ai/hiws.git
|
|
67
|
+
cd hiws
|
|
68
|
+
pip install -e .
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
To install development dependencies:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
pip install -e ".[dev]"
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Testing
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
pytest
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
pip install -e ".[dev]"
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Testing
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
pytest
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## License
|
|
94
|
+
|
|
95
|
+
This project is licensed under the MIT License - see the LICENSE file for details.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
setup.py
|
|
5
|
+
hiws/WhatsappMessenger.py
|
|
6
|
+
hiws/__init__.py
|
|
7
|
+
hiws.egg-info/PKG-INFO
|
|
8
|
+
hiws.egg-info/SOURCES.txt
|
|
9
|
+
hiws.egg-info/dependency_links.txt
|
|
10
|
+
hiws.egg-info/requires.txt
|
|
11
|
+
hiws.egg-info/top_level.txt
|
|
12
|
+
hiws/types/__init__.py
|
|
13
|
+
hiws/types/update.py
|
|
14
|
+
hiws/types/exceptions/WhatsappApiException.py
|
|
15
|
+
hiws/types/exceptions/__init__.py
|
|
16
|
+
hiws/types/message/__init__.py
|
|
17
|
+
hiws/types/message/contact.py
|
|
18
|
+
hiws/types/status/__init__.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
hiws
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=45", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "hiws"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "A wrapper for WhatsApp's Cloud API"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.7"
|
|
11
|
+
license = {text = "MIT"}
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "Tomas Santana", email = "tomas@cervant.chat"},
|
|
14
|
+
]
|
|
15
|
+
keywords = ["hiws", "python", "package"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 3 - Alpha",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"Operating System :: OS Independent",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.7",
|
|
22
|
+
"Programming Language :: Python :: 3.8",
|
|
23
|
+
"Programming Language :: Python :: 3.9",
|
|
24
|
+
"Programming Language :: Python :: 3.10",
|
|
25
|
+
"Programming Language :: Python :: 3.11",
|
|
26
|
+
]
|
|
27
|
+
dependencies = [
|
|
28
|
+
"pydantic>=2.11.7",
|
|
29
|
+
"httpx>=0.28.1",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[project.optional-dependencies]
|
|
33
|
+
dev = [
|
|
34
|
+
"pytest>=6.0",
|
|
35
|
+
"pytest-asyncio>=0.23",
|
|
36
|
+
"black>=21.0",
|
|
37
|
+
"flake8>=3.8.0",
|
|
38
|
+
"mypy>=0.800",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
[project.urls]
|
|
42
|
+
Homepage = "https://github.com/cervant-ai/hiws"
|
|
43
|
+
Repository = "https://github.com/cervant-ai/hiws.git"
|
|
44
|
+
Issues = "https://github.com/cervant-ai/hiws/issues"
|
|
45
|
+
|
|
46
|
+
[tool.black]
|
|
47
|
+
line-length = 88
|
|
48
|
+
target-version = ['py37']
|
|
49
|
+
|
|
50
|
+
[tool.mypy]
|
|
51
|
+
python_version = "3.7"
|
|
52
|
+
warn_return_any = true
|
|
53
|
+
warn_unused_configs = true
|
|
54
|
+
disallow_untyped_defs = true
|
hiws-0.1.0/setup.cfg
ADDED
hiws-0.1.0/setup.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
with open("README.md", "r", encoding="utf-8") as fh:
|
|
4
|
+
long_description = fh.read()
|
|
5
|
+
|
|
6
|
+
setup(
|
|
7
|
+
name="hiws",
|
|
8
|
+
version="0.1.0",
|
|
9
|
+
author="Tomas Santana",
|
|
10
|
+
author_email="tomas@cervant.chat",
|
|
11
|
+
description="A simple Python wrapper for WhatsApp's Cloud API",
|
|
12
|
+
long_description=long_description,
|
|
13
|
+
long_description_content_type="text/markdown",
|
|
14
|
+
url="https://github.com/cervant-ai/hiws",
|
|
15
|
+
packages=find_packages(),
|
|
16
|
+
classifiers=[
|
|
17
|
+
"Development Status :: 3 - Alpha",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"Operating System :: OS Independent",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.7",
|
|
22
|
+
"Programming Language :: Python :: 3.8",
|
|
23
|
+
"Programming Language :: Python :: 3.9",
|
|
24
|
+
"Programming Language :: Python :: 3.10",
|
|
25
|
+
"Programming Language :: Python :: 3.11",
|
|
26
|
+
],
|
|
27
|
+
python_requires=">=3.7",
|
|
28
|
+
install_requires=[
|
|
29
|
+
"pydantic>=2.11.7",
|
|
30
|
+
"httpx>=0.28.1",
|
|
31
|
+
],
|
|
32
|
+
extras_require={
|
|
33
|
+
"dev": [
|
|
34
|
+
"pytest>=6.0",
|
|
35
|
+
"pytest-asyncio>=0.23",
|
|
36
|
+
"black>=21.0",
|
|
37
|
+
"flake8>=3.8.0",
|
|
38
|
+
"mypy>=0.800",
|
|
39
|
+
],
|
|
40
|
+
},
|
|
41
|
+
)
|