wazzapi 0.3.0__tar.gz → 0.5.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.
Files changed (45) hide show
  1. {wazzapi-0.3.0 → wazzapi-0.5.0}/PKG-INFO +2 -2
  2. {wazzapi-0.3.0 → wazzapi-0.5.0}/pyproject.toml +4 -4
  3. {wazzapi-0.3.0 → wazzapi-0.5.0}/tests/smoke_test.py +1 -1
  4. {wazzapi-0.3.0 → wazzapi-0.5.0}/tests/test_resources.py +67 -0
  5. {wazzapi-0.3.0 → wazzapi-0.5.0}/wazzapi/__init__.py +1 -1
  6. {wazzapi-0.3.0 → wazzapi-0.5.0}/wazzapi/client.py +44 -1
  7. {wazzapi-0.3.0 → wazzapi-0.5.0}/wazzapi/models/__init__.py +9 -0
  8. wazzapi-0.5.0/wazzapi/models/campaigns.py +103 -0
  9. {wazzapi-0.3.0 → wazzapi-0.5.0}/wazzapi/models/contacts.py +23 -0
  10. wazzapi-0.5.0/wazzapi/models/conversations.py +80 -0
  11. {wazzapi-0.3.0 → wazzapi-0.5.0}/wazzapi/models/messages.py +3 -0
  12. wazzapi-0.5.0/wazzapi/models/webhook_subscriptions.py +87 -0
  13. {wazzapi-0.3.0 → wazzapi-0.5.0}/wazzapi/resources/__init__.py +6 -0
  14. wazzapi-0.5.0/wazzapi/resources/campaigns.py +87 -0
  15. {wazzapi-0.3.0 → wazzapi-0.5.0}/wazzapi/resources/contacts.py +36 -0
  16. wazzapi-0.5.0/wazzapi/resources/conversations.py +80 -0
  17. wazzapi-0.5.0/wazzapi/resources/devices.py +99 -0
  18. wazzapi-0.5.0/wazzapi/resources/webhook_subscriptions.py +80 -0
  19. wazzapi-0.3.0/wazzapi/resources/devices.py +0 -40
  20. {wazzapi-0.3.0 → wazzapi-0.5.0}/.gitignore +0 -0
  21. {wazzapi-0.3.0 → wazzapi-0.5.0}/README.md +0 -0
  22. {wazzapi-0.3.0 → wazzapi-0.5.0}/examples/create_template.py +0 -0
  23. {wazzapi-0.3.0 → wazzapi-0.5.0}/examples/list_contacts.py +0 -0
  24. {wazzapi-0.3.0 → wazzapi-0.5.0}/examples/list_devices.py +0 -0
  25. {wazzapi-0.3.0 → wazzapi-0.5.0}/examples/preview_template.py +0 -0
  26. {wazzapi-0.3.0 → wazzapi-0.5.0}/examples/send_message.py +0 -0
  27. {wazzapi-0.3.0 → wazzapi-0.5.0}/examples/verify_webhook.py +0 -0
  28. {wazzapi-0.3.0 → wazzapi-0.5.0}/tests/test_client.py +0 -0
  29. {wazzapi-0.3.0 → wazzapi-0.5.0}/tests/test_groups.py +0 -0
  30. {wazzapi-0.3.0 → wazzapi-0.5.0}/tests/test_media.py +0 -0
  31. {wazzapi-0.3.0 → wazzapi-0.5.0}/tests/test_webhooks.py +0 -0
  32. {wazzapi-0.3.0 → wazzapi-0.5.0}/wazzapi/errors.py +0 -0
  33. {wazzapi-0.3.0 → wazzapi-0.5.0}/wazzapi/media.py +0 -0
  34. {wazzapi-0.3.0 → wazzapi-0.5.0}/wazzapi/models/base.py +0 -0
  35. {wazzapi-0.3.0 → wazzapi-0.5.0}/wazzapi/models/devices.py +0 -0
  36. {wazzapi-0.3.0 → wazzapi-0.5.0}/wazzapi/models/groups.py +0 -0
  37. {wazzapi-0.3.0 → wazzapi-0.5.0}/wazzapi/models/media.py +0 -0
  38. {wazzapi-0.3.0 → wazzapi-0.5.0}/wazzapi/models/templates.py +0 -0
  39. {wazzapi-0.3.0 → wazzapi-0.5.0}/wazzapi/models/webhooks.py +0 -0
  40. {wazzapi-0.3.0 → wazzapi-0.5.0}/wazzapi/py.typed +0 -0
  41. {wazzapi-0.3.0 → wazzapi-0.5.0}/wazzapi/resources/base.py +0 -0
  42. {wazzapi-0.3.0 → wazzapi-0.5.0}/wazzapi/resources/groups.py +0 -0
  43. {wazzapi-0.3.0 → wazzapi-0.5.0}/wazzapi/resources/messages.py +0 -0
  44. {wazzapi-0.3.0 → wazzapi-0.5.0}/wazzapi/resources/templates.py +0 -0
  45. {wazzapi-0.3.0 → wazzapi-0.5.0}/wazzapi/webhooks.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wazzapi
3
- Version: 0.3.0
3
+ Version: 0.5.0
4
4
  Summary: Typed Python SDK for WazzAPI
5
5
  Project-URL: API, https://api.wazzapi.com
6
6
  Project-URL: Homepage, https://github.com/wazzapihq/wazzapi-py
@@ -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<45.0,>=44.0
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "wazzapi"
3
- version = "0.3.0"
3
+ version = "0.5.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,7 +19,7 @@ classifiers = [
19
19
  "Typing :: Typed",
20
20
  ]
21
21
  dependencies = [
22
- "cryptography>=44.0,<45.0",
22
+ "cryptography>=46.0,<50.0",
23
23
  "httpx>=0.28,<1.0",
24
24
  "pydantic>=2.11,<3.0",
25
25
  ]
@@ -30,8 +30,8 @@ Homepage = "https://github.com/wazzapihq/wazzapi-py"
30
30
 
31
31
  [dependency-groups]
32
32
  dev = [
33
- "pytest>=8.3,<9.0",
34
- "pytest-asyncio>=0.25,<1.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
 
@@ -10,7 +10,7 @@ from wazzapi import (
10
10
 
11
11
 
12
12
  def main() -> None:
13
- assert __version__ == "0.3.0"
13
+ assert __version__ == "0.5.0"
14
14
 
15
15
  client = WazzapiClient(api_key="smoke-test-token")
16
16
  try:
@@ -213,3 +213,70 @@ def test_sync_status_returns_typed_list() -> None:
213
213
  assert len(result) == 1
214
214
  assert isinstance(result[0], models.ContactSyncStatusResponse)
215
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"
@@ -16,7 +16,7 @@ from .webhooks import (
16
16
  verify_webhook_signature,
17
17
  )
18
18
 
19
- __version__ = "0.3.0"
19
+ __version__ = "0.5.0"
20
20
 
21
21
  __all__ = [
22
22
  "EVENT_HEADER",
@@ -7,7 +7,16 @@ import httpx
7
7
  from pydantic import BaseModel, TypeAdapter
8
8
 
9
9
  from .errors import WazzapiAPIError
10
- from .resources import ContactsResource, DevicesResource, GroupsResource, MessagesResource, TemplatesResource
10
+ from .resources import (
11
+ CampaignsResource,
12
+ ContactsResource,
13
+ ConversationsResource,
14
+ DevicesResource,
15
+ GroupsResource,
16
+ MessagesResource,
17
+ TemplatesResource,
18
+ WebhookSubscriptionsResource,
19
+ )
11
20
 
12
21
  DEFAULT_BASE_URL = "https://api.wazzapi.com"
13
22
 
@@ -38,11 +47,14 @@ class WazzapiClient:
38
47
  transport=transport,
39
48
  )
40
49
 
50
+ self.campaigns = CampaignsResource(self)
41
51
  self.contacts = ContactsResource(self)
52
+ self.conversations = ConversationsResource(self)
42
53
  self.devices = DevicesResource(self)
43
54
  self.groups = GroupsResource(self)
44
55
  self.messages = MessagesResource(self)
45
56
  self.templates = TemplatesResource(self)
57
+ self.webhook_subscriptions = WebhookSubscriptionsResource(self)
46
58
 
47
59
  @staticmethod
48
60
  def _build_headers(
@@ -107,6 +119,37 @@ class WazzapiClient:
107
119
  payload = response.json()
108
120
  return TypeAdapter(response_model).validate_python(payload)
109
121
 
122
+ def _request_bytes(
123
+ self,
124
+ method: str,
125
+ path: str,
126
+ *,
127
+ params: Mapping[str, Any] | None = None,
128
+ response_model: Any | None = None,
129
+ ) -> Any:
130
+ """Issue a request and return binary content (follows redirects)."""
131
+
132
+ response = self._client.request(
133
+ method,
134
+ path,
135
+ params=self._filter_none(params),
136
+ follow_redirects=True,
137
+ )
138
+
139
+ if response.status_code >= 400:
140
+ raise WazzapiAPIError.from_response(response)
141
+
142
+ payload = {
143
+ "content": response.content,
144
+ "content_type": response.headers.get("content-type"),
145
+ "final_url": str(response.url) if response.url else None,
146
+ }
147
+
148
+ if response_model is None:
149
+ return payload
150
+
151
+ return TypeAdapter(response_model).validate_python(payload)
152
+
110
153
  @staticmethod
111
154
  def _filter_none(data: Mapping[str, Any] | None) -> dict[str, Any] | None:
112
155
  if data is None:
@@ -1,6 +1,10 @@
1
1
  from .base import WazzapiModel
2
+ from .campaigns import *
3
+ from .campaigns import __all__ as campaigns_all
2
4
  from .contacts import *
3
5
  from .contacts import __all__ as contacts_all
6
+ from .conversations import *
7
+ from .conversations import __all__ as conversations_all
4
8
  from .devices import *
5
9
  from .devices import __all__ as devices_all
6
10
  from .groups import *
@@ -11,16 +15,21 @@ from .messages import *
11
15
  from .messages import __all__ as messages_all
12
16
  from .templates import *
13
17
  from .templates import __all__ as templates_all
18
+ from .webhook_subscriptions import *
19
+ from .webhook_subscriptions import __all__ as webhook_subscriptions_all
14
20
  from .webhooks import *
15
21
  from .webhooks import __all__ as webhooks_all
16
22
 
17
23
  __all__ = [
18
24
  "WazzapiModel",
25
+ *campaigns_all,
19
26
  *contacts_all,
27
+ *conversations_all,
20
28
  *devices_all,
21
29
  *groups_all,
22
30
  *media_all,
23
31
  *messages_all,
24
32
  *templates_all,
33
+ *webhook_subscriptions_all,
25
34
  *webhooks_all,
26
35
  ]
@@ -0,0 +1,103 @@
1
+ from __future__ import annotations
2
+
3
+ from datetime import datetime
4
+ from typing import Any
5
+
6
+ from .base import WazzapiModel
7
+
8
+
9
+ class CampaignCreateRequest(WazzapiModel):
10
+ name: str
11
+ whatsapp_account_id: str
12
+ recipient_type: str
13
+ description: str | None = None
14
+ template_id: str | None = None
15
+ message_type: str = "text"
16
+ custom_message: str | None = None
17
+ message_payload: dict[str, Any] | None = None
18
+ recipient_group_ids: list[str] | None = None
19
+ recipient_contact_ids: list[str] | None = None
20
+ scheduled_for: datetime | None = None
21
+
22
+
23
+ class CampaignUpdateRequest(WazzapiModel):
24
+ name: str | None = None
25
+ description: str | None = None
26
+ template_id: str | None = None
27
+ message_type: str | None = None
28
+ custom_message: str | None = None
29
+ message_payload: dict[str, Any] | None = None
30
+ recipient_type: str | None = None
31
+ recipient_group_ids: list[str] | None = None
32
+ recipient_contact_ids: list[str] | None = None
33
+ scheduled_for: datetime | None = None
34
+
35
+
36
+ class CampaignItem(WazzapiModel):
37
+ id: str
38
+ name: str
39
+ status: str
40
+ recipient_type: str
41
+ total_recipients: int
42
+ messages_sent: int
43
+ messages_delivered: int
44
+ messages_failed: int
45
+ whatsapp_account_id: str
46
+ message_type: str = "text"
47
+ recipient_contact_ids: list[str] | None = None
48
+ scheduled_for: str | None = None
49
+ created_at: str
50
+
51
+
52
+ class CampaignListResponse(WazzapiModel):
53
+ items: list[CampaignItem]
54
+ total: int
55
+ limit: int
56
+ offset: int
57
+
58
+
59
+ class CampaignResponse(WazzapiModel):
60
+ id: str
61
+ name: str
62
+ status: str
63
+ description: str | None = None
64
+ whatsapp_account_id: str
65
+ template_id: str | None = None
66
+ message_type: str = "text"
67
+ custom_message: str | None = None
68
+ message_payload: dict[str, Any] | None = None
69
+ recipient_type: str
70
+ recipient_group_ids: list[str] | None = None
71
+ recipient_contact_ids: list[str] | None = None
72
+ total_recipients: int = 0
73
+ messages_sent: int = 0
74
+ messages_delivered: int = 0
75
+ messages_failed: int = 0
76
+ scheduled_for: str | None = None
77
+ created_at: str | None = None
78
+ updated_at: str | None = None
79
+
80
+
81
+ class CampaignActionResponse(WazzapiModel):
82
+ success: bool
83
+ campaign_id: str
84
+ status: str
85
+
86
+
87
+ class CampaignExecuteResponse(WazzapiModel):
88
+ success: bool
89
+ campaign_id: str
90
+ status: str
91
+ total_recipients: int
92
+ job_id: str | None = None
93
+
94
+
95
+ __all__ = [
96
+ "CampaignActionResponse",
97
+ "CampaignCreateRequest",
98
+ "CampaignExecuteResponse",
99
+ "CampaignItem",
100
+ "CampaignListResponse",
101
+ "CampaignResponse",
102
+ "CampaignUpdateRequest",
103
+ ]
@@ -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,80 @@
1
+ from __future__ import annotations
2
+
3
+ from datetime import datetime
4
+
5
+ from .base import WazzapiModel
6
+
7
+
8
+ class ConversationItem(WazzapiModel):
9
+ id: str
10
+ whatsapp_account_id: str
11
+ contact_id: str | None = None
12
+ phone_number: str
13
+ status: str
14
+ assignee_user_id: str | None = None
15
+ subject: str | None = None
16
+ last_message_preview: str | None = None
17
+ last_message_at: datetime | None = None
18
+ last_inbound_at: datetime | None = None
19
+ last_outbound_at: datetime | None = None
20
+ unread_count: int
21
+ created_at: datetime
22
+ updated_at: datetime
23
+
24
+
25
+ class ConversationListResponse(WazzapiModel):
26
+ conversations: list[ConversationItem]
27
+ total: int
28
+ limit: int
29
+ offset: int
30
+
31
+
32
+ class ConversationUpdateRequest(WazzapiModel):
33
+ status: str | None = None
34
+ assignee_user_id: str | None = None
35
+ clear_assignee: bool = False
36
+ mark_read: bool = False
37
+
38
+
39
+ class ConversationMessageItem(WazzapiModel):
40
+ id: str
41
+ direction: str
42
+ content: str
43
+ status: str
44
+ message_type: str
45
+ phone_number: str
46
+ whatsapp_message_id: str | None = None
47
+ created_at: datetime
48
+ sent_at: datetime | None = None
49
+ delivered_at: datetime | None = None
50
+ read_at: datetime | None = None
51
+
52
+
53
+ class ConversationMessageListResponse(WazzapiModel):
54
+ messages: list[ConversationMessageItem]
55
+ total: int
56
+ limit: int
57
+ offset: int
58
+
59
+
60
+ class ConversationReplyRequest(WazzapiModel):
61
+ content: str
62
+ whatsapp_account_id: str | None = None
63
+
64
+
65
+ class ConversationReplyResponse(WazzapiModel):
66
+ success: bool
67
+ message_id: str
68
+ conversation_id: str
69
+ status: str
70
+
71
+
72
+ __all__ = [
73
+ "ConversationItem",
74
+ "ConversationListResponse",
75
+ "ConversationMessageItem",
76
+ "ConversationMessageListResponse",
77
+ "ConversationReplyRequest",
78
+ "ConversationReplyResponse",
79
+ "ConversationUpdateRequest",
80
+ ]
@@ -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
@@ -0,0 +1,87 @@
1
+ from __future__ import annotations
2
+
3
+ from datetime import datetime
4
+
5
+ from .base import WazzapiModel
6
+
7
+
8
+ class WebhookCreateRequest(WazzapiModel):
9
+ url: str
10
+ subscribed_events: list[str]
11
+ secret: str | None = None
12
+ description: str | None = None
13
+
14
+
15
+ class WebhookUpdateRequest(WazzapiModel):
16
+ url: str | None = None
17
+ subscribed_events: list[str] | None = None
18
+ is_active: bool | None = None
19
+ description: str | None = None
20
+
21
+
22
+ class WebhookResponse(WazzapiModel):
23
+ id: str
24
+ url: str
25
+ secret_prefix: str
26
+ subscribed_events: list[str]
27
+ is_active: bool
28
+ status: str
29
+ failure_count: int
30
+ last_success_at: datetime | None = None
31
+ last_failure_at: datetime | None = None
32
+ description: str | None = None
33
+ created_at: datetime
34
+ updated_at: datetime
35
+
36
+
37
+ class WebhookListItem(WazzapiModel):
38
+ id: str
39
+ url: str
40
+ subscribed_events: list[str]
41
+ is_active: bool
42
+ status: str
43
+ failure_count: int
44
+ last_success_at: datetime | None = None
45
+ last_failure_at: datetime | None = None
46
+ description: str | None = None
47
+ created_at: datetime
48
+
49
+
50
+ class WebhookListResponse(WazzapiModel):
51
+ webhooks: list[WebhookListItem]
52
+ total: int
53
+
54
+
55
+ class WebhookEventListItem(WazzapiModel):
56
+ id: str
57
+ event_type: str
58
+ status: str
59
+ retry_count: int
60
+ response_code: int | None = None
61
+ error_message: str | None = None
62
+ created_at: datetime
63
+ delivered_at: datetime | None = None
64
+
65
+
66
+ class WebhookEventListResponse(WazzapiModel):
67
+ events: list[WebhookEventListItem]
68
+ total: int
69
+
70
+
71
+ class PingWebhookResponse(WazzapiModel):
72
+ success: bool
73
+ status_code: int | None = None
74
+ response_time_ms: int | None = None
75
+ error: str | None = None
76
+
77
+
78
+ __all__ = [
79
+ "PingWebhookResponse",
80
+ "WebhookCreateRequest",
81
+ "WebhookEventListItem",
82
+ "WebhookEventListResponse",
83
+ "WebhookListItem",
84
+ "WebhookListResponse",
85
+ "WebhookResponse",
86
+ "WebhookUpdateRequest",
87
+ ]
@@ -1,13 +1,19 @@
1
+ from .campaigns import CampaignsResource
1
2
  from .contacts import ContactsResource
3
+ from .conversations import ConversationsResource
2
4
  from .devices import DevicesResource
3
5
  from .groups import GroupsResource
4
6
  from .messages import MessagesResource
5
7
  from .templates import TemplatesResource
8
+ from .webhook_subscriptions import WebhookSubscriptionsResource
6
9
 
7
10
  __all__ = [
11
+ "CampaignsResource",
8
12
  "ContactsResource",
13
+ "ConversationsResource",
9
14
  "DevicesResource",
10
15
  "GroupsResource",
11
16
  "MessagesResource",
12
17
  "TemplatesResource",
18
+ "WebhookSubscriptionsResource",
13
19
  ]
@@ -0,0 +1,87 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from .. import models
6
+ from .base import BaseResource
7
+
8
+
9
+ class CampaignsResource(BaseResource):
10
+ def list(
11
+ self,
12
+ *,
13
+ limit: int | None = None,
14
+ offset: int | None = None,
15
+ status: str | None = None,
16
+ ) -> models.CampaignListResponse:
17
+ return self._client._request(
18
+ "GET",
19
+ "/api/v1/campaigns",
20
+ params={"limit": limit, "offset": offset, "status": status},
21
+ response_model=models.CampaignListResponse,
22
+ )
23
+
24
+ def create(
25
+ self,
26
+ payload: models.CampaignCreateRequest | dict[str, Any],
27
+ ) -> models.CampaignResponse:
28
+ return self._client._request(
29
+ "POST",
30
+ "/api/v1/campaigns",
31
+ json_body=payload,
32
+ response_model=models.CampaignResponse,
33
+ )
34
+
35
+ def get(self, campaign_id: str) -> models.CampaignResponse:
36
+ return self._client._request(
37
+ "GET",
38
+ f"/api/v1/campaigns/{campaign_id}",
39
+ response_model=models.CampaignResponse,
40
+ )
41
+
42
+ def update(
43
+ self,
44
+ campaign_id: str,
45
+ payload: models.CampaignUpdateRequest | dict[str, Any],
46
+ ) -> models.CampaignResponse:
47
+ return self._client._request(
48
+ "PATCH",
49
+ f"/api/v1/campaigns/{campaign_id}",
50
+ json_body=payload,
51
+ response_model=models.CampaignResponse,
52
+ )
53
+
54
+ def delete(self, campaign_id: str) -> None:
55
+ self._client._request("DELETE", f"/api/v1/campaigns/{campaign_id}")
56
+ return None
57
+
58
+ def execute(self, campaign_id: str) -> models.CampaignExecuteResponse:
59
+ return self._client._request(
60
+ "POST",
61
+ f"/api/v1/campaigns/{campaign_id}/execute",
62
+ response_model=models.CampaignExecuteResponse,
63
+ )
64
+
65
+ def pause(self, campaign_id: str) -> models.CampaignActionResponse:
66
+ return self._client._request(
67
+ "POST",
68
+ f"/api/v1/campaigns/{campaign_id}/pause",
69
+ response_model=models.CampaignActionResponse,
70
+ )
71
+
72
+ def resume(self, campaign_id: str) -> models.CampaignActionResponse:
73
+ return self._client._request(
74
+ "POST",
75
+ f"/api/v1/campaigns/{campaign_id}/resume",
76
+ response_model=models.CampaignActionResponse,
77
+ )
78
+
79
+ def cancel(self, campaign_id: str) -> models.CampaignActionResponse:
80
+ return self._client._request(
81
+ "POST",
82
+ f"/api/v1/campaigns/{campaign_id}/cancel",
83
+ response_model=models.CampaignActionResponse,
84
+ )
85
+
86
+
87
+ __all__ = ["CampaignsResource"]
@@ -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,80 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from .. import models
6
+ from .base import BaseResource
7
+
8
+
9
+ class ConversationsResource(BaseResource):
10
+ def list(
11
+ self,
12
+ *,
13
+ limit: int | None = None,
14
+ offset: int | None = None,
15
+ status: str | None = None,
16
+ whatsapp_account_id: str | None = None,
17
+ assignee_user_id: str | None = None,
18
+ search: str | None = None,
19
+ ) -> models.ConversationListResponse:
20
+ return self._client._request(
21
+ "GET",
22
+ "/api/v1/conversations",
23
+ params={
24
+ "limit": limit,
25
+ "offset": offset,
26
+ "status": status,
27
+ "whatsapp_account_id": whatsapp_account_id,
28
+ "assignee_user_id": assignee_user_id,
29
+ "search": search,
30
+ },
31
+ response_model=models.ConversationListResponse,
32
+ )
33
+
34
+ def get(self, conversation_id: str) -> models.ConversationItem:
35
+ return self._client._request(
36
+ "GET",
37
+ f"/api/v1/conversations/{conversation_id}",
38
+ response_model=models.ConversationItem,
39
+ )
40
+
41
+ def update(
42
+ self,
43
+ conversation_id: str,
44
+ payload: models.ConversationUpdateRequest | dict[str, Any],
45
+ ) -> models.ConversationItem:
46
+ return self._client._request(
47
+ "PATCH",
48
+ f"/api/v1/conversations/{conversation_id}",
49
+ json_body=payload,
50
+ response_model=models.ConversationItem,
51
+ )
52
+
53
+ def list_messages(
54
+ self,
55
+ conversation_id: str,
56
+ *,
57
+ limit: int | None = None,
58
+ offset: int | None = None,
59
+ ) -> models.ConversationMessageListResponse:
60
+ return self._client._request(
61
+ "GET",
62
+ f"/api/v1/conversations/{conversation_id}/messages",
63
+ params={"limit": limit, "offset": offset},
64
+ response_model=models.ConversationMessageListResponse,
65
+ )
66
+
67
+ def reply(
68
+ self,
69
+ conversation_id: str,
70
+ payload: models.ConversationReplyRequest | dict[str, Any],
71
+ ) -> models.ConversationReplyResponse:
72
+ return self._client._request(
73
+ "POST",
74
+ f"/api/v1/conversations/{conversation_id}/reply",
75
+ json_body=payload,
76
+ response_model=models.ConversationReplyResponse,
77
+ )
78
+
79
+
80
+ __all__ = ["ConversationsResource"]
@@ -0,0 +1,99 @@
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
+ def link(self, payload: dict) -> dict:
40
+ return self._client._request(
41
+ "POST",
42
+ "/api/v1/devices",
43
+ json_body=payload,
44
+ response_model=dict,
45
+ )
46
+
47
+ def get_qr(self, device_id: str) -> dict:
48
+ return self._client._request(
49
+ "GET",
50
+ f"/api/v1/devices/{device_id}/qr",
51
+ response_model=dict,
52
+ )
53
+
54
+ def get_status(self, device_id: str) -> dict:
55
+ return self._client._request(
56
+ "GET",
57
+ f"/api/v1/devices/{device_id}/status",
58
+ response_model=dict,
59
+ )
60
+
61
+ def disconnect(self, device_id: str) -> dict:
62
+ return self._client._request(
63
+ "POST",
64
+ f"/api/v1/devices/{device_id}/disconnect",
65
+ response_model=dict,
66
+ )
67
+
68
+ def reconnect(self, device_id: str) -> dict:
69
+ return self._client._request(
70
+ "POST",
71
+ f"/api/v1/devices/{device_id}/reconnect",
72
+ response_model=dict,
73
+ )
74
+
75
+ def get_proxy(self, device_id: str) -> dict:
76
+ return self._client._request(
77
+ "GET",
78
+ f"/api/v1/devices/{device_id}/proxy",
79
+ response_model=dict,
80
+ )
81
+
82
+ def update_proxy(self, device_id: str, payload: dict) -> dict:
83
+ return self._client._request(
84
+ "PATCH",
85
+ f"/api/v1/devices/{device_id}/proxy",
86
+ json_body=payload,
87
+ response_model=dict,
88
+ )
89
+
90
+ def test_proxy(self, payload: dict) -> dict:
91
+ return self._client._request(
92
+ "POST",
93
+ "/api/v1/devices/proxy/test",
94
+ json_body=payload,
95
+ response_model=dict,
96
+ )
97
+
98
+
99
+ __all__ = ["DevicesResource"]
@@ -0,0 +1,80 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from .. import models
6
+ from .base import BaseResource
7
+
8
+
9
+ class WebhookSubscriptionsResource(BaseResource):
10
+ def list(
11
+ self,
12
+ *,
13
+ limit: int | None = None,
14
+ offset: int | None = None,
15
+ active_only: bool | None = None,
16
+ ) -> models.WebhookListResponse:
17
+ return self._client._request(
18
+ "GET",
19
+ "/api/v1/webhooks/subscriptions",
20
+ params={"limit": limit, "offset": offset, "active_only": active_only},
21
+ response_model=models.WebhookListResponse,
22
+ )
23
+
24
+ def create(
25
+ self,
26
+ payload: models.WebhookCreateRequest | dict[str, Any],
27
+ ) -> models.WebhookResponse:
28
+ return self._client._request(
29
+ "POST",
30
+ "/api/v1/webhooks/subscriptions",
31
+ json_body=payload,
32
+ response_model=models.WebhookResponse,
33
+ )
34
+
35
+ def get(self, webhook_id: str) -> models.WebhookResponse:
36
+ return self._client._request(
37
+ "GET",
38
+ f"/api/v1/webhooks/subscriptions/{webhook_id}",
39
+ response_model=models.WebhookResponse,
40
+ )
41
+
42
+ def update(
43
+ self,
44
+ webhook_id: str,
45
+ payload: models.WebhookUpdateRequest | dict[str, Any],
46
+ ) -> models.WebhookResponse:
47
+ return self._client._request(
48
+ "PATCH",
49
+ f"/api/v1/webhooks/subscriptions/{webhook_id}",
50
+ json_body=payload,
51
+ response_model=models.WebhookResponse,
52
+ )
53
+
54
+ def delete(self, webhook_id: str) -> None:
55
+ self._client._request("DELETE", f"/api/v1/webhooks/subscriptions/{webhook_id}")
56
+ return None
57
+
58
+ def list_events(
59
+ self,
60
+ webhook_id: str,
61
+ *,
62
+ limit: int | None = None,
63
+ offset: int | None = None,
64
+ ) -> models.WebhookEventListResponse:
65
+ return self._client._request(
66
+ "GET",
67
+ f"/api/v1/webhooks/subscriptions/{webhook_id}/events",
68
+ params={"limit": limit, "offset": offset},
69
+ response_model=models.WebhookEventListResponse,
70
+ )
71
+
72
+ def ping(self, webhook_id: str) -> models.PingWebhookResponse:
73
+ return self._client._request(
74
+ "POST",
75
+ f"/api/v1/webhooks/subscriptions/{webhook_id}/ping",
76
+ response_model=models.PingWebhookResponse,
77
+ )
78
+
79
+
80
+ __all__ = ["WebhookSubscriptionsResource"]
@@ -1,40 +0,0 @@
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