wazzapi 0.2.0__tar.gz → 0.4.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.
- {wazzapi-0.2.0 → wazzapi-0.4.0}/PKG-INFO +20 -4
- {wazzapi-0.2.0 → wazzapi-0.4.0}/README.md +17 -1
- wazzapi-0.4.0/examples/list_devices.py +15 -0
- {wazzapi-0.2.0 → wazzapi-0.4.0}/pyproject.toml +5 -5
- {wazzapi-0.2.0 → wazzapi-0.4.0}/tests/smoke_test.py +1 -1
- {wazzapi-0.2.0 → wazzapi-0.4.0}/tests/test_client.py +1 -0
- {wazzapi-0.2.0 → wazzapi-0.4.0}/tests/test_resources.py +123 -0
- {wazzapi-0.2.0 → wazzapi-0.4.0}/wazzapi/__init__.py +1 -1
- {wazzapi-0.2.0 → wazzapi-0.4.0}/wazzapi/client.py +33 -1
- {wazzapi-0.2.0 → wazzapi-0.4.0}/wazzapi/models/__init__.py +12 -1
- {wazzapi-0.2.0 → wazzapi-0.4.0}/wazzapi/models/contacts.py +23 -0
- wazzapi-0.4.0/wazzapi/models/devices.py +42 -0
- {wazzapi-0.2.0 → wazzapi-0.4.0}/wazzapi/models/messages.py +3 -0
- {wazzapi-0.2.0 → wazzapi-0.4.0}/wazzapi/resources/__init__.py +8 -1
- {wazzapi-0.2.0 → wazzapi-0.4.0}/wazzapi/resources/contacts.py +36 -0
- wazzapi-0.4.0/wazzapi/resources/devices.py +40 -0
- {wazzapi-0.2.0 → wazzapi-0.4.0}/.gitignore +0 -0
- {wazzapi-0.2.0 → wazzapi-0.4.0}/examples/create_template.py +0 -0
- {wazzapi-0.2.0 → wazzapi-0.4.0}/examples/list_contacts.py +0 -0
- {wazzapi-0.2.0 → wazzapi-0.4.0}/examples/preview_template.py +0 -0
- {wazzapi-0.2.0 → wazzapi-0.4.0}/examples/send_message.py +0 -0
- {wazzapi-0.2.0 → wazzapi-0.4.0}/examples/verify_webhook.py +0 -0
- {wazzapi-0.2.0 → wazzapi-0.4.0}/tests/test_groups.py +0 -0
- {wazzapi-0.2.0 → wazzapi-0.4.0}/tests/test_media.py +0 -0
- {wazzapi-0.2.0 → wazzapi-0.4.0}/tests/test_webhooks.py +0 -0
- {wazzapi-0.2.0 → wazzapi-0.4.0}/wazzapi/errors.py +0 -0
- {wazzapi-0.2.0 → wazzapi-0.4.0}/wazzapi/media.py +0 -0
- {wazzapi-0.2.0 → wazzapi-0.4.0}/wazzapi/models/base.py +0 -0
- {wazzapi-0.2.0 → wazzapi-0.4.0}/wazzapi/models/groups.py +0 -0
- {wazzapi-0.2.0 → wazzapi-0.4.0}/wazzapi/models/media.py +0 -0
- {wazzapi-0.2.0 → wazzapi-0.4.0}/wazzapi/models/templates.py +0 -0
- {wazzapi-0.2.0 → wazzapi-0.4.0}/wazzapi/models/webhooks.py +0 -0
- {wazzapi-0.2.0 → wazzapi-0.4.0}/wazzapi/py.typed +0 -0
- {wazzapi-0.2.0 → wazzapi-0.4.0}/wazzapi/resources/base.py +0 -0
- {wazzapi-0.2.0 → wazzapi-0.4.0}/wazzapi/resources/groups.py +0 -0
- {wazzapi-0.2.0 → wazzapi-0.4.0}/wazzapi/resources/messages.py +0 -0
- {wazzapi-0.2.0 → wazzapi-0.4.0}/wazzapi/resources/templates.py +0 -0
- {wazzapi-0.2.0 → wazzapi-0.4.0}/wazzapi/webhooks.py +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wazzapi
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Typed Python SDK for WazzAPI
|
|
5
|
-
Project-URL: Homepage, https://github.com/wazzapihq/wazzapi-py
|
|
6
5
|
Project-URL: API, https://api.wazzapi.com
|
|
6
|
+
Project-URL: Homepage, https://github.com/wazzapihq/wazzapi-py
|
|
7
7
|
Keywords: api,sdk,wazzapi,webhook,whatsapp
|
|
8
8
|
Classifier: Development Status :: 4 - Beta
|
|
9
9
|
Classifier: Intended Audience :: Developers
|
|
@@ -17,7 +17,7 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
17
17
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
18
18
|
Classifier: Typing :: Typed
|
|
19
19
|
Requires-Python: >=3.10
|
|
20
|
-
Requires-Dist: cryptography<
|
|
20
|
+
Requires-Dist: cryptography<50.0,>=46.0
|
|
21
21
|
Requires-Dist: httpx<1.0,>=0.28
|
|
22
22
|
Requires-Dist: pydantic<3.0,>=2.11
|
|
23
23
|
Description-Content-Type: text/markdown
|
|
@@ -26,11 +26,12 @@ Description-Content-Type: text/markdown
|
|
|
26
26
|
|
|
27
27
|
Official Python SDK for WazzAPI.
|
|
28
28
|
|
|
29
|
-
Use it to send messages, manage contacts and templates, and verify incoming webhooks with a simple typed client.
|
|
29
|
+
Use it to send messages, inspect WhatsApp devices, manage contacts and templates, and verify incoming webhooks with a simple typed client.
|
|
30
30
|
|
|
31
31
|
## What you can do with this SDK
|
|
32
32
|
|
|
33
33
|
- send direct WhatsApp messages
|
|
34
|
+
- list and inspect WhatsApp devices
|
|
34
35
|
- list, create, update, and delete contacts
|
|
35
36
|
- manage message templates and preview rendered content
|
|
36
37
|
- verify and parse incoming WazzAPI webhooks
|
|
@@ -118,6 +119,19 @@ with WazzapiClient(api_key="your-api-key") as client:
|
|
|
118
119
|
|
|
119
120
|
for contact in response.contacts:
|
|
120
121
|
print(contact.model_dump())
|
|
122
|
+
|
|
123
|
+
### List devices
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
from wazzapi import WazzapiClient
|
|
127
|
+
|
|
128
|
+
with WazzapiClient(api_key="your-api-key") as client:
|
|
129
|
+
response = client.devices.list(limit=20, status="connected")
|
|
130
|
+
|
|
131
|
+
for device in response.devices:
|
|
132
|
+
print(device.name, device.session_name, device.status)
|
|
133
|
+
```
|
|
134
|
+
|
|
121
135
|
```
|
|
122
136
|
|
|
123
137
|
### Create a template
|
|
@@ -182,6 +196,7 @@ Supported webhook event families:
|
|
|
182
196
|
|
|
183
197
|
Ready-to-run examples live in `examples/`:
|
|
184
198
|
|
|
199
|
+
- `examples/list_devices.py`
|
|
185
200
|
- `examples/list_contacts.py`
|
|
186
201
|
- `examples/send_message.py`
|
|
187
202
|
- `examples/create_template.py`
|
|
@@ -192,6 +207,7 @@ Run them with:
|
|
|
192
207
|
|
|
193
208
|
```bash
|
|
194
209
|
uv run python examples/list_contacts.py
|
|
210
|
+
uv run python examples/list_devices.py
|
|
195
211
|
uv run python examples/send_message.py
|
|
196
212
|
uv run python examples/create_template.py
|
|
197
213
|
uv run python examples/preview_template.py
|
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
Official Python SDK for WazzAPI.
|
|
4
4
|
|
|
5
|
-
Use it to send messages, manage contacts and templates, and verify incoming webhooks with a simple typed client.
|
|
5
|
+
Use it to send messages, inspect WhatsApp devices, manage contacts and templates, and verify incoming webhooks with a simple typed client.
|
|
6
6
|
|
|
7
7
|
## What you can do with this SDK
|
|
8
8
|
|
|
9
9
|
- send direct WhatsApp messages
|
|
10
|
+
- list and inspect WhatsApp devices
|
|
10
11
|
- list, create, update, and delete contacts
|
|
11
12
|
- manage message templates and preview rendered content
|
|
12
13
|
- verify and parse incoming WazzAPI webhooks
|
|
@@ -94,6 +95,19 @@ with WazzapiClient(api_key="your-api-key") as client:
|
|
|
94
95
|
|
|
95
96
|
for contact in response.contacts:
|
|
96
97
|
print(contact.model_dump())
|
|
98
|
+
|
|
99
|
+
### List devices
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
from wazzapi import WazzapiClient
|
|
103
|
+
|
|
104
|
+
with WazzapiClient(api_key="your-api-key") as client:
|
|
105
|
+
response = client.devices.list(limit=20, status="connected")
|
|
106
|
+
|
|
107
|
+
for device in response.devices:
|
|
108
|
+
print(device.name, device.session_name, device.status)
|
|
109
|
+
```
|
|
110
|
+
|
|
97
111
|
```
|
|
98
112
|
|
|
99
113
|
### Create a template
|
|
@@ -158,6 +172,7 @@ Supported webhook event families:
|
|
|
158
172
|
|
|
159
173
|
Ready-to-run examples live in `examples/`:
|
|
160
174
|
|
|
175
|
+
- `examples/list_devices.py`
|
|
161
176
|
- `examples/list_contacts.py`
|
|
162
177
|
- `examples/send_message.py`
|
|
163
178
|
- `examples/create_template.py`
|
|
@@ -168,6 +183,7 @@ Run them with:
|
|
|
168
183
|
|
|
169
184
|
```bash
|
|
170
185
|
uv run python examples/list_contacts.py
|
|
186
|
+
uv run python examples/list_devices.py
|
|
171
187
|
uv run python examples/send_message.py
|
|
172
188
|
uv run python examples/create_template.py
|
|
173
189
|
uv run python examples/preview_template.py
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
from wazzapi import WazzapiClient
|
|
6
|
+
|
|
7
|
+
api_key = os.environ["WAZZAPI_API_KEY"]
|
|
8
|
+
|
|
9
|
+
with WazzapiClient(api_key=api_key) as client:
|
|
10
|
+
response = client.devices.list(limit=20, status="connected")
|
|
11
|
+
|
|
12
|
+
for device in response.devices:
|
|
13
|
+
print(device.model_dump())
|
|
14
|
+
|
|
15
|
+
print({"total": response.total, "limit": response.limit, "offset": response.offset})
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "wazzapi"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.4.0"
|
|
4
4
|
description = "Typed Python SDK for WazzAPI"
|
|
5
5
|
readme = { file = "README.md", content-type = "text/markdown" }
|
|
6
6
|
requires-python = ">=3.10"
|
|
@@ -19,19 +19,19 @@ classifiers = [
|
|
|
19
19
|
"Typing :: Typed",
|
|
20
20
|
]
|
|
21
21
|
dependencies = [
|
|
22
|
-
"cryptography>=
|
|
22
|
+
"cryptography>=46.0,<50.0",
|
|
23
23
|
"httpx>=0.28,<1.0",
|
|
24
24
|
"pydantic>=2.11,<3.0",
|
|
25
25
|
]
|
|
26
26
|
|
|
27
27
|
[project.urls]
|
|
28
|
-
Homepage = "https://github.com/wazzapihq/wazzapi-py"
|
|
29
28
|
API = "https://api.wazzapi.com"
|
|
29
|
+
Homepage = "https://github.com/wazzapihq/wazzapi-py"
|
|
30
30
|
|
|
31
31
|
[dependency-groups]
|
|
32
32
|
dev = [
|
|
33
|
-
"pytest>=
|
|
34
|
-
"pytest-asyncio>=0
|
|
33
|
+
"pytest>=9.0,<10.0",
|
|
34
|
+
"pytest-asyncio>=1.0,<2.0",
|
|
35
35
|
"twine>=6.1,<7.0",
|
|
36
36
|
]
|
|
37
37
|
|
|
@@ -15,6 +15,7 @@ def test_client_formats_bearer_auth_header() -> None:
|
|
|
15
15
|
assert client.http.headers["Authorization"] == "Bearer plain-token"
|
|
16
16
|
assert client.http.headers["Accept"] == "application/json"
|
|
17
17
|
assert client.http.headers["Content-Type"] == "application/json"
|
|
18
|
+
assert client.devices is not None
|
|
18
19
|
finally:
|
|
19
20
|
client.close()
|
|
20
21
|
|
|
@@ -12,6 +12,18 @@ from wazzapi import WazzapiClient, models
|
|
|
12
12
|
@pytest.mark.parametrize(
|
|
13
13
|
("call_factory", "expected_method", "expected_path", "expected_response_type", "response_json"),
|
|
14
14
|
[
|
|
15
|
+
(
|
|
16
|
+
lambda client: client.devices.list(limit=25, status="connected", search="main"),
|
|
17
|
+
"GET",
|
|
18
|
+
"/api/v1/devices",
|
|
19
|
+
models.DeviceListResponse,
|
|
20
|
+
{
|
|
21
|
+
"devices": [],
|
|
22
|
+
"total": 0,
|
|
23
|
+
"limit": 25,
|
|
24
|
+
"offset": 0,
|
|
25
|
+
},
|
|
26
|
+
),
|
|
15
27
|
(
|
|
16
28
|
lambda client: client.contacts.list(limit=25, search="alice"),
|
|
17
29
|
"GET",
|
|
@@ -134,6 +146,50 @@ def test_delete_endpoints_return_none() -> None:
|
|
|
134
146
|
assert seen_paths == ["/api/v1/contacts/contact_1", "/api/v1/templates/template_1"]
|
|
135
147
|
|
|
136
148
|
|
|
149
|
+
def test_get_device_returns_typed_response() -> None:
|
|
150
|
+
seen: dict[str, object] = {}
|
|
151
|
+
|
|
152
|
+
def handler(request: httpx.Request) -> httpx.Response:
|
|
153
|
+
seen["method"] = request.method
|
|
154
|
+
seen["path"] = request.url.path
|
|
155
|
+
return httpx.Response(
|
|
156
|
+
200,
|
|
157
|
+
json={
|
|
158
|
+
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
|
159
|
+
"name": "Primary Device",
|
|
160
|
+
"session_name": "main",
|
|
161
|
+
"status": "connected",
|
|
162
|
+
"is_primary": True,
|
|
163
|
+
"auto_warmer_enabled": True,
|
|
164
|
+
"total_messages_sent": 123,
|
|
165
|
+
"total_messages_received": 45,
|
|
166
|
+
"contacts_synced_count": 900,
|
|
167
|
+
"created_at": "2026-05-01T00:00:00Z",
|
|
168
|
+
"updated_at": "2026-05-02T00:00:00Z",
|
|
169
|
+
"timezone": "Asia/Jakarta",
|
|
170
|
+
"connection_attempts": 2,
|
|
171
|
+
"daily_message_limit": 1000,
|
|
172
|
+
"message_delay_ms": 500,
|
|
173
|
+
"phone_number": "6281234567890",
|
|
174
|
+
"last_connected_at": "2026-05-02T00:00:00Z",
|
|
175
|
+
"last_health_check_at": "2026-05-02T01:00:00Z",
|
|
176
|
+
"last_contact_sync_at": "2026-05-02T02:00:00Z",
|
|
177
|
+
"last_contact_sync_status": "completed",
|
|
178
|
+
"backend_id": "7a5d6343-1dd1-429a-8d83-25c9bf06c878",
|
|
179
|
+
},
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
transport = httpx.MockTransport(handler)
|
|
183
|
+
with WazzapiClient(base_url="https://api.example.com", transport=transport) as client:
|
|
184
|
+
result = client.devices.get("3c90c3cc-0d44-4b50-8888-8dd25736052a")
|
|
185
|
+
|
|
186
|
+
assert seen["method"] == "GET"
|
|
187
|
+
assert seen["path"] == "/api/v1/devices/3c90c3cc-0d44-4b50-8888-8dd25736052a"
|
|
188
|
+
assert isinstance(result, models.DeviceResponse)
|
|
189
|
+
assert result.session_name == "main"
|
|
190
|
+
assert result.timezone == "Asia/Jakarta"
|
|
191
|
+
|
|
192
|
+
|
|
137
193
|
def test_sync_status_returns_typed_list() -> None:
|
|
138
194
|
def handler(_: httpx.Request) -> httpx.Response:
|
|
139
195
|
return httpx.Response(
|
|
@@ -157,3 +213,70 @@ def test_sync_status_returns_typed_list() -> None:
|
|
|
157
213
|
assert len(result) == 1
|
|
158
214
|
assert isinstance(result[0], models.ContactSyncStatusResponse)
|
|
159
215
|
assert result[0].account_id == "wa_123"
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def test_get_profile_picture_returns_metadata() -> None:
|
|
219
|
+
seen: dict[str, object] = {}
|
|
220
|
+
|
|
221
|
+
def handler(request: httpx.Request) -> httpx.Response:
|
|
222
|
+
seen["method"] = request.method
|
|
223
|
+
seen["path"] = request.url.path
|
|
224
|
+
seen["query"] = dict(request.url.params)
|
|
225
|
+
return httpx.Response(
|
|
226
|
+
200,
|
|
227
|
+
json={
|
|
228
|
+
"contact_id": "contact_1",
|
|
229
|
+
"phone_number": "+6281234567890",
|
|
230
|
+
"url": "https://cdn.example.com/pic.jpg",
|
|
231
|
+
"picture_id": "pic_1",
|
|
232
|
+
"picture_type": "image",
|
|
233
|
+
"preview": True,
|
|
234
|
+
"whatsapp_account_id": "wa_1",
|
|
235
|
+
"cached": False,
|
|
236
|
+
},
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
transport = httpx.MockTransport(handler)
|
|
240
|
+
with WazzapiClient(base_url="https://api.example.com", transport=transport) as client:
|
|
241
|
+
result = client.contacts.get_profile_picture(
|
|
242
|
+
"contact_1",
|
|
243
|
+
whatsapp_account_id="wa_1",
|
|
244
|
+
preview=True,
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
assert seen["method"] == "GET"
|
|
248
|
+
assert seen["path"] == "/api/v1/contacts/contact_1/profile-picture"
|
|
249
|
+
assert seen["query"] == {"whatsapp_account_id": "wa_1", "preview": "true"}
|
|
250
|
+
assert isinstance(result, models.ContactProfilePictureResponse)
|
|
251
|
+
assert result.url == "https://cdn.example.com/pic.jpg"
|
|
252
|
+
assert result.preview is True
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def test_get_profile_picture_image_returns_bytes() -> None:
|
|
256
|
+
seen: dict[str, object] = {}
|
|
257
|
+
image_bytes = b"\xff\xd8\xff\xe0fake-jpeg"
|
|
258
|
+
|
|
259
|
+
def handler(request: httpx.Request) -> httpx.Response:
|
|
260
|
+
seen["method"] = request.method
|
|
261
|
+
seen["path"] = request.url.path
|
|
262
|
+
seen["query"] = dict(request.url.params)
|
|
263
|
+
return httpx.Response(
|
|
264
|
+
200,
|
|
265
|
+
content=image_bytes,
|
|
266
|
+
headers={"content-type": "image/jpeg"},
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
transport = httpx.MockTransport(handler)
|
|
270
|
+
with WazzapiClient(base_url="https://api.example.com", transport=transport) as client:
|
|
271
|
+
result = client.contacts.get_profile_picture_image(
|
|
272
|
+
"contact_1",
|
|
273
|
+
proxy=True,
|
|
274
|
+
preview=False,
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
assert seen["method"] == "GET"
|
|
278
|
+
assert seen["path"] == "/api/v1/contacts/contact_1/profile-picture/image"
|
|
279
|
+
assert seen["query"] == {"proxy": "true", "preview": "false"}
|
|
280
|
+
assert isinstance(result, models.ContactProfilePictureImageResult)
|
|
281
|
+
assert result.content == image_bytes
|
|
282
|
+
assert result.content_type == "image/jpeg"
|
|
@@ -7,7 +7,7 @@ import httpx
|
|
|
7
7
|
from pydantic import BaseModel, TypeAdapter
|
|
8
8
|
|
|
9
9
|
from .errors import WazzapiAPIError
|
|
10
|
-
from .resources import ContactsResource, GroupsResource, MessagesResource, TemplatesResource
|
|
10
|
+
from .resources import ContactsResource, DevicesResource, GroupsResource, MessagesResource, TemplatesResource
|
|
11
11
|
|
|
12
12
|
DEFAULT_BASE_URL = "https://api.wazzapi.com"
|
|
13
13
|
|
|
@@ -39,6 +39,7 @@ class WazzapiClient:
|
|
|
39
39
|
)
|
|
40
40
|
|
|
41
41
|
self.contacts = ContactsResource(self)
|
|
42
|
+
self.devices = DevicesResource(self)
|
|
42
43
|
self.groups = GroupsResource(self)
|
|
43
44
|
self.messages = MessagesResource(self)
|
|
44
45
|
self.templates = TemplatesResource(self)
|
|
@@ -106,6 +107,37 @@ class WazzapiClient:
|
|
|
106
107
|
payload = response.json()
|
|
107
108
|
return TypeAdapter(response_model).validate_python(payload)
|
|
108
109
|
|
|
110
|
+
def _request_bytes(
|
|
111
|
+
self,
|
|
112
|
+
method: str,
|
|
113
|
+
path: str,
|
|
114
|
+
*,
|
|
115
|
+
params: Mapping[str, Any] | None = None,
|
|
116
|
+
response_model: Any | None = None,
|
|
117
|
+
) -> Any:
|
|
118
|
+
"""Issue a request and return binary content (follows redirects)."""
|
|
119
|
+
|
|
120
|
+
response = self._client.request(
|
|
121
|
+
method,
|
|
122
|
+
path,
|
|
123
|
+
params=self._filter_none(params),
|
|
124
|
+
follow_redirects=True,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
if response.status_code >= 400:
|
|
128
|
+
raise WazzapiAPIError.from_response(response)
|
|
129
|
+
|
|
130
|
+
payload = {
|
|
131
|
+
"content": response.content,
|
|
132
|
+
"content_type": response.headers.get("content-type"),
|
|
133
|
+
"final_url": str(response.url) if response.url else None,
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if response_model is None:
|
|
137
|
+
return payload
|
|
138
|
+
|
|
139
|
+
return TypeAdapter(response_model).validate_python(payload)
|
|
140
|
+
|
|
109
141
|
@staticmethod
|
|
110
142
|
def _filter_none(data: Mapping[str, Any] | None) -> dict[str, Any] | None:
|
|
111
143
|
if data is None:
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
from .base import WazzapiModel
|
|
2
2
|
from .contacts import *
|
|
3
3
|
from .contacts import __all__ as contacts_all
|
|
4
|
+
from .devices import *
|
|
5
|
+
from .devices import __all__ as devices_all
|
|
4
6
|
from .groups import *
|
|
5
7
|
from .groups import __all__ as groups_all
|
|
6
8
|
from .media import *
|
|
@@ -12,4 +14,13 @@ from .templates import __all__ as templates_all
|
|
|
12
14
|
from .webhooks import *
|
|
13
15
|
from .webhooks import __all__ as webhooks_all
|
|
14
16
|
|
|
15
|
-
__all__ = [
|
|
17
|
+
__all__ = [
|
|
18
|
+
"WazzapiModel",
|
|
19
|
+
*contacts_all,
|
|
20
|
+
*devices_all,
|
|
21
|
+
*groups_all,
|
|
22
|
+
*media_all,
|
|
23
|
+
*messages_all,
|
|
24
|
+
*templates_all,
|
|
25
|
+
*webhooks_all,
|
|
26
|
+
]
|
|
@@ -173,6 +173,27 @@ class ContactUpdateRequest(WazzapiModel):
|
|
|
173
173
|
is_opted_out: bool | None = None
|
|
174
174
|
|
|
175
175
|
|
|
176
|
+
class ContactProfilePictureResponse(WazzapiModel):
|
|
177
|
+
"""Ephemeral WhatsApp profile picture metadata for a contact."""
|
|
178
|
+
|
|
179
|
+
contact_id: str
|
|
180
|
+
phone_number: str
|
|
181
|
+
url: str
|
|
182
|
+
picture_id: str | None = None
|
|
183
|
+
picture_type: str | None = None
|
|
184
|
+
preview: bool
|
|
185
|
+
whatsapp_account_id: str
|
|
186
|
+
cached: bool = False
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
class ContactProfilePictureImageResult(WazzapiModel):
|
|
190
|
+
"""Raw image bytes returned by the profile-picture image endpoint."""
|
|
191
|
+
|
|
192
|
+
content: bytes
|
|
193
|
+
content_type: str | None = None
|
|
194
|
+
final_url: str | None = None
|
|
195
|
+
|
|
196
|
+
|
|
176
197
|
class RemoveFromGroupRequest(WazzapiModel):
|
|
177
198
|
contact_ids: list[str]
|
|
178
199
|
|
|
@@ -193,6 +214,8 @@ __all__ = [
|
|
|
193
214
|
"ContactGroupUpdateRequest",
|
|
194
215
|
"ContactItem",
|
|
195
216
|
"ContactListResponse",
|
|
217
|
+
"ContactProfilePictureImageResult",
|
|
218
|
+
"ContactProfilePictureResponse",
|
|
196
219
|
"ContactResponse",
|
|
197
220
|
"ContactSyncHistoryItem",
|
|
198
221
|
"ContactSyncHistoryResponse",
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
from .base import WazzapiModel
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class DeviceItem(WazzapiModel):
|
|
9
|
+
id: str
|
|
10
|
+
name: str
|
|
11
|
+
session_name: str
|
|
12
|
+
status: str
|
|
13
|
+
is_primary: bool
|
|
14
|
+
auto_warmer_enabled: bool
|
|
15
|
+
total_messages_sent: int
|
|
16
|
+
total_messages_received: int
|
|
17
|
+
contacts_synced_count: int
|
|
18
|
+
created_at: datetime
|
|
19
|
+
updated_at: datetime
|
|
20
|
+
phone_number: str | None = None
|
|
21
|
+
last_connected_at: datetime | None = None
|
|
22
|
+
last_health_check_at: datetime | None = None
|
|
23
|
+
last_contact_sync_at: datetime | None = None
|
|
24
|
+
last_contact_sync_status: str | None = None
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class DeviceListResponse(WazzapiModel):
|
|
28
|
+
devices: list[DeviceItem]
|
|
29
|
+
total: int
|
|
30
|
+
limit: int
|
|
31
|
+
offset: int
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class DeviceResponse(DeviceItem):
|
|
35
|
+
timezone: str
|
|
36
|
+
connection_attempts: int
|
|
37
|
+
daily_message_limit: int
|
|
38
|
+
message_delay_ms: int
|
|
39
|
+
backend_id: str | None = None
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
__all__ = ["DeviceItem", "DeviceListResponse", "DeviceResponse"]
|
|
@@ -136,6 +136,9 @@ class SendMessageRequest(WazzapiModel):
|
|
|
136
136
|
message_type: str = "text"
|
|
137
137
|
media_type: str | None = None
|
|
138
138
|
media_url: str | None = None
|
|
139
|
+
media_base64: str | None = None
|
|
140
|
+
filename: str | None = None
|
|
141
|
+
mimetype: str | None = None
|
|
139
142
|
caption: str | None = None
|
|
140
143
|
latitude: float | None = None
|
|
141
144
|
longitude: float | None = None
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
from .contacts import ContactsResource
|
|
2
|
+
from .devices import DevicesResource
|
|
2
3
|
from .groups import GroupsResource
|
|
3
4
|
from .messages import MessagesResource
|
|
4
5
|
from .templates import TemplatesResource
|
|
5
6
|
|
|
6
|
-
__all__ = [
|
|
7
|
+
__all__ = [
|
|
8
|
+
"ContactsResource",
|
|
9
|
+
"DevicesResource",
|
|
10
|
+
"GroupsResource",
|
|
11
|
+
"MessagesResource",
|
|
12
|
+
"TemplatesResource",
|
|
13
|
+
]
|
|
@@ -218,5 +218,41 @@ class ContactsResource(BaseResource):
|
|
|
218
218
|
self._client._request("DELETE", f"/api/v1/contacts/{contact_id}")
|
|
219
219
|
return None
|
|
220
220
|
|
|
221
|
+
def get_profile_picture(
|
|
222
|
+
self,
|
|
223
|
+
contact_id: str,
|
|
224
|
+
*,
|
|
225
|
+
whatsapp_account_id: str | None = None,
|
|
226
|
+
preview: bool | None = None,
|
|
227
|
+
) -> models.ContactProfilePictureResponse:
|
|
228
|
+
return self._client._request(
|
|
229
|
+
"GET",
|
|
230
|
+
f"/api/v1/contacts/{contact_id}/profile-picture",
|
|
231
|
+
params={
|
|
232
|
+
"whatsapp_account_id": whatsapp_account_id,
|
|
233
|
+
"preview": preview,
|
|
234
|
+
},
|
|
235
|
+
response_model=models.ContactProfilePictureResponse,
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
def get_profile_picture_image(
|
|
239
|
+
self,
|
|
240
|
+
contact_id: str,
|
|
241
|
+
*,
|
|
242
|
+
whatsapp_account_id: str | None = None,
|
|
243
|
+
preview: bool | None = None,
|
|
244
|
+
proxy: bool | None = None,
|
|
245
|
+
) -> models.ContactProfilePictureImageResult:
|
|
246
|
+
return self._client._request_bytes(
|
|
247
|
+
"GET",
|
|
248
|
+
f"/api/v1/contacts/{contact_id}/profile-picture/image",
|
|
249
|
+
params={
|
|
250
|
+
"whatsapp_account_id": whatsapp_account_id,
|
|
251
|
+
"preview": preview,
|
|
252
|
+
"proxy": proxy,
|
|
253
|
+
},
|
|
254
|
+
response_model=models.ContactProfilePictureImageResult,
|
|
255
|
+
)
|
|
256
|
+
|
|
221
257
|
|
|
222
258
|
__all__ = ["ContactsResource"]
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from .. import models
|
|
4
|
+
from .base import BaseResource
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class DevicesResource(BaseResource):
|
|
8
|
+
def list(
|
|
9
|
+
self,
|
|
10
|
+
*,
|
|
11
|
+
limit: int | None = None,
|
|
12
|
+
offset: int | None = None,
|
|
13
|
+
status: str | None = None,
|
|
14
|
+
search: str | None = None,
|
|
15
|
+
sort_by: str | None = None,
|
|
16
|
+
sort_order: str | None = None,
|
|
17
|
+
) -> models.DeviceListResponse:
|
|
18
|
+
return self._client._request(
|
|
19
|
+
"GET",
|
|
20
|
+
"/api/v1/devices",
|
|
21
|
+
params={
|
|
22
|
+
"limit": limit,
|
|
23
|
+
"offset": offset,
|
|
24
|
+
"status": status,
|
|
25
|
+
"search": search,
|
|
26
|
+
"sort_by": sort_by,
|
|
27
|
+
"sort_order": sort_order,
|
|
28
|
+
},
|
|
29
|
+
response_model=models.DeviceListResponse,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
def get(self, device_id: str) -> models.DeviceResponse:
|
|
33
|
+
return self._client._request(
|
|
34
|
+
"GET",
|
|
35
|
+
f"/api/v1/devices/{device_id}",
|
|
36
|
+
response_model=models.DeviceResponse,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
__all__ = ["DevicesResource"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|