wazzapi 0.1.0__py3-none-any.whl
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/__init__.py +36 -0
- wazzapi/client.py +120 -0
- wazzapi/errors.py +60 -0
- wazzapi/models/__init__.py +11 -0
- wazzapi/models/base.py +10 -0
- wazzapi/models/contacts.py +204 -0
- wazzapi/models/messages.py +173 -0
- wazzapi/models/templates.py +100 -0
- wazzapi/models/webhooks.py +87 -0
- wazzapi/py.typed +0 -0
- wazzapi/resources/__init__.py +5 -0
- wazzapi/resources/base.py +14 -0
- wazzapi/resources/contacts.py +222 -0
- wazzapi/resources/messages.py +174 -0
- wazzapi/resources/templates.py +87 -0
- wazzapi/webhooks.py +131 -0
- wazzapi-0.1.0.dist-info/METADATA +201 -0
- wazzapi-0.1.0.dist-info/RECORD +19 -0
- wazzapi-0.1.0.dist-info/WHEEL +4 -0
wazzapi/__init__.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from . import models
|
|
2
|
+
from .client import WazzapiClient
|
|
3
|
+
from .errors import WazzapiAPIError, WazzapiError
|
|
4
|
+
from .models import *
|
|
5
|
+
from .webhooks import (
|
|
6
|
+
EVENT_HEADER,
|
|
7
|
+
EVENT_ID_HEADER,
|
|
8
|
+
SIGNATURE_HEADER,
|
|
9
|
+
WebhookHandler,
|
|
10
|
+
WazzapiWebhookError,
|
|
11
|
+
WazzapiWebhookParseError,
|
|
12
|
+
WazzapiWebhookVerificationError,
|
|
13
|
+
generate_webhook_signature,
|
|
14
|
+
parse_webhook,
|
|
15
|
+
verify_webhook_signature,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
__version__ = "0.1.0"
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"EVENT_HEADER",
|
|
22
|
+
"EVENT_ID_HEADER",
|
|
23
|
+
"SIGNATURE_HEADER",
|
|
24
|
+
"__version__",
|
|
25
|
+
"generate_webhook_signature",
|
|
26
|
+
"parse_webhook",
|
|
27
|
+
"WazzapiAPIError",
|
|
28
|
+
"WazzapiClient",
|
|
29
|
+
"WazzapiError",
|
|
30
|
+
"WazzapiWebhookError",
|
|
31
|
+
"WazzapiWebhookParseError",
|
|
32
|
+
"WazzapiWebhookVerificationError",
|
|
33
|
+
"verify_webhook_signature",
|
|
34
|
+
"WebhookHandler",
|
|
35
|
+
"models",
|
|
36
|
+
] + models.__all__
|
wazzapi/client.py
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Mapping
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
import httpx
|
|
7
|
+
from pydantic import BaseModel, TypeAdapter
|
|
8
|
+
|
|
9
|
+
from .errors import WazzapiAPIError
|
|
10
|
+
from .resources import ContactsResource, MessagesResource, TemplatesResource
|
|
11
|
+
|
|
12
|
+
DEFAULT_BASE_URL = "https://api.wazzapi.com"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class WazzapiClient:
|
|
16
|
+
"""Synchronous client for the WazzAPI API."""
|
|
17
|
+
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
*,
|
|
21
|
+
base_url: str = DEFAULT_BASE_URL,
|
|
22
|
+
api_key: str | None = None,
|
|
23
|
+
timeout: float = 30.0,
|
|
24
|
+
headers: Mapping[str, str] | None = None,
|
|
25
|
+
client: httpx.Client | None = None,
|
|
26
|
+
transport: httpx.BaseTransport | None = None,
|
|
27
|
+
) -> None:
|
|
28
|
+
base_headers = self._build_headers(
|
|
29
|
+
api_key=api_key,
|
|
30
|
+
headers=headers,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
self._owns_client = client is None
|
|
34
|
+
self._client = client or httpx.Client(
|
|
35
|
+
base_url=base_url.rstrip("/"),
|
|
36
|
+
timeout=timeout,
|
|
37
|
+
headers=base_headers,
|
|
38
|
+
transport=transport,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
self.contacts = ContactsResource(self)
|
|
42
|
+
self.messages = MessagesResource(self)
|
|
43
|
+
self.templates = TemplatesResource(self)
|
|
44
|
+
|
|
45
|
+
@staticmethod
|
|
46
|
+
def _build_headers(
|
|
47
|
+
*,
|
|
48
|
+
api_key: str | None,
|
|
49
|
+
headers: Mapping[str, str] | None,
|
|
50
|
+
) -> dict[str, str]:
|
|
51
|
+
merged_headers: dict[str, str] = {
|
|
52
|
+
"Accept": "application/json",
|
|
53
|
+
"Content-Type": "application/json",
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if headers:
|
|
57
|
+
merged_headers.update(dict(headers))
|
|
58
|
+
|
|
59
|
+
if api_key:
|
|
60
|
+
merged_headers["Authorization"] = (
|
|
61
|
+
api_key if api_key.lower().startswith("bearer ") else f"Bearer {api_key}"
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
return merged_headers
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def http(self) -> httpx.Client:
|
|
68
|
+
return self._client
|
|
69
|
+
|
|
70
|
+
def close(self) -> None:
|
|
71
|
+
if self._owns_client:
|
|
72
|
+
self._client.close()
|
|
73
|
+
|
|
74
|
+
def __enter__(self) -> "WazzapiClient":
|
|
75
|
+
return self
|
|
76
|
+
|
|
77
|
+
def __exit__(self, exc_type: Any, exc: Any, tb: Any) -> None:
|
|
78
|
+
self.close()
|
|
79
|
+
|
|
80
|
+
def _request(
|
|
81
|
+
self,
|
|
82
|
+
method: str,
|
|
83
|
+
path: str,
|
|
84
|
+
*,
|
|
85
|
+
params: Mapping[str, Any] | None = None,
|
|
86
|
+
json_body: BaseModel | Mapping[str, Any] | None = None,
|
|
87
|
+
response_model: Any | None = None,
|
|
88
|
+
) -> Any:
|
|
89
|
+
response = self._client.request(
|
|
90
|
+
method,
|
|
91
|
+
path,
|
|
92
|
+
params=self._filter_none(params),
|
|
93
|
+
json=self._normalize_body(json_body),
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
if response.status_code >= 400:
|
|
97
|
+
raise WazzapiAPIError.from_response(response)
|
|
98
|
+
|
|
99
|
+
if response.status_code == 204 or not response.content:
|
|
100
|
+
return None
|
|
101
|
+
|
|
102
|
+
if response_model is None:
|
|
103
|
+
return response.json()
|
|
104
|
+
|
|
105
|
+
payload = response.json()
|
|
106
|
+
return TypeAdapter(response_model).validate_python(payload)
|
|
107
|
+
|
|
108
|
+
@staticmethod
|
|
109
|
+
def _filter_none(data: Mapping[str, Any] | None) -> dict[str, Any] | None:
|
|
110
|
+
if data is None:
|
|
111
|
+
return None
|
|
112
|
+
return {key: value for key, value in data.items() if value is not None}
|
|
113
|
+
|
|
114
|
+
@staticmethod
|
|
115
|
+
def _normalize_body(data: BaseModel | Mapping[str, Any] | None) -> dict[str, Any] | None:
|
|
116
|
+
if data is None:
|
|
117
|
+
return None
|
|
118
|
+
if isinstance(data, BaseModel):
|
|
119
|
+
return data.model_dump(mode="json", exclude_none=True)
|
|
120
|
+
return {key: value for key, value in data.items() if value is not None}
|
wazzapi/errors.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class WazzapiError(Exception):
|
|
9
|
+
"""Base exception for the WazzAPI SDK."""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class WazzapiAPIError(WazzapiError):
|
|
13
|
+
"""Raised when the WazzAPI API returns a non-success response."""
|
|
14
|
+
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
status_code: int,
|
|
18
|
+
message: str,
|
|
19
|
+
*,
|
|
20
|
+
details: Any | None = None,
|
|
21
|
+
response_text: str | None = None,
|
|
22
|
+
) -> None:
|
|
23
|
+
super().__init__(f"WazzAPI API error {status_code}: {message}")
|
|
24
|
+
self.status_code = status_code
|
|
25
|
+
self.message = message
|
|
26
|
+
self.details = details
|
|
27
|
+
self.response_text = response_text
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def from_response(cls, response: httpx.Response) -> "WazzapiAPIError":
|
|
31
|
+
details: Any | None = None
|
|
32
|
+
message = response.reason_phrase or "Request failed"
|
|
33
|
+
|
|
34
|
+
try:
|
|
35
|
+
payload = response.json()
|
|
36
|
+
except ValueError:
|
|
37
|
+
payload = None
|
|
38
|
+
|
|
39
|
+
if isinstance(payload, dict):
|
|
40
|
+
details = payload.get("detail", payload)
|
|
41
|
+
if isinstance(details, str):
|
|
42
|
+
message = details
|
|
43
|
+
elif isinstance(details, list) and details:
|
|
44
|
+
first_item = details[0]
|
|
45
|
+
if isinstance(first_item, dict):
|
|
46
|
+
message = first_item.get("msg") or message
|
|
47
|
+
else:
|
|
48
|
+
message = str(first_item)
|
|
49
|
+
elif payload.get("message"):
|
|
50
|
+
message = str(payload["message"])
|
|
51
|
+
elif payload is not None:
|
|
52
|
+
details = payload
|
|
53
|
+
message = str(payload)
|
|
54
|
+
|
|
55
|
+
return cls(
|
|
56
|
+
response.status_code,
|
|
57
|
+
message,
|
|
58
|
+
details=details,
|
|
59
|
+
response_text=response.text,
|
|
60
|
+
)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from .base import WazzapiModel
|
|
2
|
+
from .contacts import *
|
|
3
|
+
from .contacts import __all__ as contacts_all
|
|
4
|
+
from .messages import *
|
|
5
|
+
from .messages import __all__ as messages_all
|
|
6
|
+
from .templates import *
|
|
7
|
+
from .templates import __all__ as templates_all
|
|
8
|
+
from .webhooks import *
|
|
9
|
+
from .webhooks import __all__ as webhooks_all
|
|
10
|
+
|
|
11
|
+
__all__ = ["WazzapiModel", *contacts_all, *messages_all, *templates_all, *webhooks_all]
|
wazzapi/models/base.py
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from pydantic import Field
|
|
7
|
+
|
|
8
|
+
from .base import WazzapiModel
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class AddToGroupRequest(WazzapiModel):
|
|
12
|
+
contact_ids: list[str]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class AddToGroupResponse(WazzapiModel):
|
|
16
|
+
success: bool
|
|
17
|
+
added: int
|
|
18
|
+
member_count: int
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class BulkDeleteRequest(WazzapiModel):
|
|
22
|
+
contact_ids: list[str]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class BulkDeleteResponse(WazzapiModel):
|
|
26
|
+
success: bool
|
|
27
|
+
deleted: int
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class CSVExportResponse(WazzapiModel):
|
|
31
|
+
csv_data: str
|
|
32
|
+
count: int
|
|
33
|
+
filename: str
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class CSVImportRequest(WazzapiModel):
|
|
37
|
+
csv_content: str
|
|
38
|
+
skip_duplicates: bool | None = None
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class CSVImportResponse(WazzapiModel):
|
|
42
|
+
success: bool
|
|
43
|
+
imported: int
|
|
44
|
+
updated: int
|
|
45
|
+
errors: list[str] = Field(default_factory=list)
|
|
46
|
+
rows_processed: int
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class ContactCreateRequest(WazzapiModel):
|
|
50
|
+
phone_number: str
|
|
51
|
+
name: str | None = None
|
|
52
|
+
email: str | None = None
|
|
53
|
+
tags: list[str] | None = None
|
|
54
|
+
custom_fields: dict[str, Any] | None = None
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class ContactGroupCreateRequest(WazzapiModel):
|
|
58
|
+
name: str
|
|
59
|
+
description: str | None = None
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class ContactGroupItem(WazzapiModel):
|
|
63
|
+
id: str
|
|
64
|
+
name: str
|
|
65
|
+
description: str | None = None
|
|
66
|
+
member_count: int
|
|
67
|
+
created_at: datetime
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class ContactGroupListResponse(WazzapiModel):
|
|
71
|
+
groups: list[ContactGroupItem]
|
|
72
|
+
total: int
|
|
73
|
+
limit: int
|
|
74
|
+
offset: int
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class ContactItem(WazzapiModel):
|
|
78
|
+
id: str
|
|
79
|
+
phone_number: str
|
|
80
|
+
name: str | None = None
|
|
81
|
+
email: str | None = None
|
|
82
|
+
whatsapp_name: str | None = None
|
|
83
|
+
profile_picture_url: str | None = None
|
|
84
|
+
source: str
|
|
85
|
+
source_details: dict[str, Any] | None = None
|
|
86
|
+
is_opted_out: bool
|
|
87
|
+
tags: list[str] = Field(default_factory=list)
|
|
88
|
+
last_message_at: datetime | None = None
|
|
89
|
+
created_at: datetime
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class ContactGroupMembersResponse(WazzapiModel):
|
|
93
|
+
group: ContactGroupItem
|
|
94
|
+
contacts: list[ContactItem]
|
|
95
|
+
total: int
|
|
96
|
+
limit: int
|
|
97
|
+
offset: int
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class ContactGroupUpdateRequest(WazzapiModel):
|
|
101
|
+
name: str | None = None
|
|
102
|
+
description: str | None = None
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class ContactListResponse(WazzapiModel):
|
|
106
|
+
contacts: list[ContactItem]
|
|
107
|
+
total: int
|
|
108
|
+
limit: int
|
|
109
|
+
offset: int
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class ContactResponse(WazzapiModel):
|
|
113
|
+
id: str
|
|
114
|
+
phone_number: str
|
|
115
|
+
name: str | None = None
|
|
116
|
+
email: str | None = None
|
|
117
|
+
whatsapp_name: str | None = None
|
|
118
|
+
profile_picture_url: str | None = None
|
|
119
|
+
custom_fields: dict[str, Any] | None = None
|
|
120
|
+
source: str
|
|
121
|
+
source_details: dict[str, Any] | None = None
|
|
122
|
+
is_opted_out: bool
|
|
123
|
+
opted_out_at: datetime | None = None
|
|
124
|
+
tags: list[str] = Field(default_factory=list)
|
|
125
|
+
last_message_at: datetime | None = None
|
|
126
|
+
created_at: datetime
|
|
127
|
+
updated_at: datetime
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class ContactSyncHistoryItem(WazzapiModel):
|
|
131
|
+
id: str
|
|
132
|
+
account_id: str
|
|
133
|
+
account_name: str
|
|
134
|
+
sync_type: str
|
|
135
|
+
status: str
|
|
136
|
+
contacts_count: int
|
|
137
|
+
started_at: datetime
|
|
138
|
+
completed_at: datetime | None = None
|
|
139
|
+
error_message: str | None = None
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class ContactSyncHistoryResponse(WazzapiModel):
|
|
143
|
+
history: list[ContactSyncHistoryItem]
|
|
144
|
+
total: int
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class ContactSyncRequest(WazzapiModel):
|
|
148
|
+
whatsapp_account_id: str
|
|
149
|
+
sync_type: str | None = None
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
class ContactSyncResponse(WazzapiModel):
|
|
153
|
+
success: bool
|
|
154
|
+
job_id: str | None = None
|
|
155
|
+
message: str
|
|
156
|
+
status: str | None = None
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
class ContactSyncStatusResponse(WazzapiModel):
|
|
160
|
+
account_id: str
|
|
161
|
+
account_name: str
|
|
162
|
+
last_sync_at: datetime | None = None
|
|
163
|
+
last_sync_status: str | None = None
|
|
164
|
+
contacts_synced_count: int
|
|
165
|
+
can_sync: bool
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class ContactUpdateRequest(WazzapiModel):
|
|
169
|
+
name: str | None = None
|
|
170
|
+
email: str | None = None
|
|
171
|
+
tags: list[str] | None = None
|
|
172
|
+
custom_fields: dict[str, Any] | None = None
|
|
173
|
+
is_opted_out: bool | None = None
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
class RemoveFromGroupRequest(WazzapiModel):
|
|
177
|
+
contact_ids: list[str]
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
__all__ = [
|
|
181
|
+
"AddToGroupRequest",
|
|
182
|
+
"AddToGroupResponse",
|
|
183
|
+
"BulkDeleteRequest",
|
|
184
|
+
"BulkDeleteResponse",
|
|
185
|
+
"CSVExportResponse",
|
|
186
|
+
"CSVImportRequest",
|
|
187
|
+
"CSVImportResponse",
|
|
188
|
+
"ContactCreateRequest",
|
|
189
|
+
"ContactGroupCreateRequest",
|
|
190
|
+
"ContactGroupItem",
|
|
191
|
+
"ContactGroupListResponse",
|
|
192
|
+
"ContactGroupMembersResponse",
|
|
193
|
+
"ContactGroupUpdateRequest",
|
|
194
|
+
"ContactItem",
|
|
195
|
+
"ContactListResponse",
|
|
196
|
+
"ContactResponse",
|
|
197
|
+
"ContactSyncHistoryItem",
|
|
198
|
+
"ContactSyncHistoryResponse",
|
|
199
|
+
"ContactSyncRequest",
|
|
200
|
+
"ContactSyncResponse",
|
|
201
|
+
"ContactSyncStatusResponse",
|
|
202
|
+
"ContactUpdateRequest",
|
|
203
|
+
"RemoveFromGroupRequest",
|
|
204
|
+
]
|
|
@@ -0,0 +1,173 @@
|
|
|
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 InteractiveButton(WazzapiModel):
|
|
10
|
+
id: str
|
|
11
|
+
title: str
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ButtonReplyRequest(WazzapiModel):
|
|
15
|
+
phone_number: str
|
|
16
|
+
body: str
|
|
17
|
+
buttons: list[InteractiveButton]
|
|
18
|
+
whatsapp_account_id: str
|
|
19
|
+
footer: str | None = None
|
|
20
|
+
contact_id: str | None = None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class CancelMessageResponse(WazzapiModel):
|
|
24
|
+
success: bool
|
|
25
|
+
message_id: str
|
|
26
|
+
status: str
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class InteractiveMessageResponse(WazzapiModel):
|
|
30
|
+
success: bool
|
|
31
|
+
message_id: str
|
|
32
|
+
status: str
|
|
33
|
+
run_id: str | None = None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class InteractiveRow(WazzapiModel):
|
|
37
|
+
id: str
|
|
38
|
+
title: str
|
|
39
|
+
description: str | None = None
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class InteractiveSection(WazzapiModel):
|
|
43
|
+
title: str
|
|
44
|
+
rows: list[InteractiveRow]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class ListReplyRequest(WazzapiModel):
|
|
48
|
+
phone_number: str
|
|
49
|
+
body: str
|
|
50
|
+
button_text: str
|
|
51
|
+
sections: list[InteractiveSection]
|
|
52
|
+
whatsapp_account_id: str
|
|
53
|
+
footer: str | None = None
|
|
54
|
+
contact_id: str | None = None
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class MessageItem(WazzapiModel):
|
|
58
|
+
id: str
|
|
59
|
+
phone_number: str
|
|
60
|
+
content: str
|
|
61
|
+
message_type: str = "text"
|
|
62
|
+
media_type: str | None = None
|
|
63
|
+
media_url: str | None = None
|
|
64
|
+
status: str
|
|
65
|
+
direction: str
|
|
66
|
+
retry_count: int
|
|
67
|
+
contact_id: str | None = None
|
|
68
|
+
contact_name: str | None = None
|
|
69
|
+
whatsapp_account_id: str
|
|
70
|
+
campaign_id: str | None = None
|
|
71
|
+
batch_id: str | None = None
|
|
72
|
+
scheduled_for: datetime | None = None
|
|
73
|
+
created_at: datetime
|
|
74
|
+
sent_at: datetime | None = None
|
|
75
|
+
delivered_at: datetime | None = None
|
|
76
|
+
read_at: datetime | None = None
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class MessageListResponse(WazzapiModel):
|
|
80
|
+
messages: list[MessageItem]
|
|
81
|
+
total_count: int
|
|
82
|
+
has_more: bool
|
|
83
|
+
current_page: int
|
|
84
|
+
total_pages: int
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class MessageResponse(WazzapiModel):
|
|
88
|
+
id: str
|
|
89
|
+
phone_number: str
|
|
90
|
+
content: str
|
|
91
|
+
message_type: str = "text"
|
|
92
|
+
media_type: str | None = None
|
|
93
|
+
media_url: str | None = None
|
|
94
|
+
status: str
|
|
95
|
+
direction: str
|
|
96
|
+
failure_reason: str | None = None
|
|
97
|
+
retry_count: int
|
|
98
|
+
whatsapp_message_id: str | None = None
|
|
99
|
+
variable_values: dict[str, Any] | None = None
|
|
100
|
+
contact_id: str | None = None
|
|
101
|
+
contact_name: str | None = None
|
|
102
|
+
whatsapp_account_id: str
|
|
103
|
+
campaign_id: str | None = None
|
|
104
|
+
batch_id: str | None = None
|
|
105
|
+
scheduled_for: datetime | None = None
|
|
106
|
+
queued_at: datetime | None = None
|
|
107
|
+
sent_at: datetime | None = None
|
|
108
|
+
delivered_at: datetime | None = None
|
|
109
|
+
read_at: datetime | None = None
|
|
110
|
+
failed_at: datetime | None = None
|
|
111
|
+
created_at: datetime
|
|
112
|
+
updated_at: datetime
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class MessageStatsResponse(WazzapiModel):
|
|
116
|
+
total: int
|
|
117
|
+
by_status: dict[str, int]
|
|
118
|
+
by_direction: dict[str, int]
|
|
119
|
+
last_7_days: int
|
|
120
|
+
last_30_days: int
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class RetryMessageResponse(WazzapiModel):
|
|
124
|
+
success: bool
|
|
125
|
+
message_id: str
|
|
126
|
+
run_id: str | None = None
|
|
127
|
+
status: str
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class SendMessageRequest(WazzapiModel):
|
|
131
|
+
phone_number: str
|
|
132
|
+
whatsapp_account_id: str
|
|
133
|
+
content: str | None = None
|
|
134
|
+
template_id: str | None = None
|
|
135
|
+
custom_variables: dict[str, Any] | None = None
|
|
136
|
+
message_type: str = "text"
|
|
137
|
+
media_type: str | None = None
|
|
138
|
+
media_url: str | None = None
|
|
139
|
+
caption: str | None = None
|
|
140
|
+
latitude: float | None = None
|
|
141
|
+
longitude: float | None = None
|
|
142
|
+
location_title: str | None = None
|
|
143
|
+
location_address: str | None = None
|
|
144
|
+
contacts: list[dict[str, Any]] | None = None
|
|
145
|
+
contact_id: str | None = None
|
|
146
|
+
scheduled_for: datetime | None = None
|
|
147
|
+
validate_phone: bool = False
|
|
148
|
+
status_callback_url: str | None = None
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
class SendMessageResponse(WazzapiModel):
|
|
152
|
+
success: bool
|
|
153
|
+
message_id: str
|
|
154
|
+
status: str
|
|
155
|
+
run_id: str | None = None
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
__all__ = [
|
|
159
|
+
"ButtonReplyRequest",
|
|
160
|
+
"CancelMessageResponse",
|
|
161
|
+
"InteractiveButton",
|
|
162
|
+
"InteractiveMessageResponse",
|
|
163
|
+
"InteractiveRow",
|
|
164
|
+
"InteractiveSection",
|
|
165
|
+
"ListReplyRequest",
|
|
166
|
+
"MessageItem",
|
|
167
|
+
"MessageListResponse",
|
|
168
|
+
"MessageResponse",
|
|
169
|
+
"MessageStatsResponse",
|
|
170
|
+
"RetryMessageResponse",
|
|
171
|
+
"SendMessageRequest",
|
|
172
|
+
"SendMessageResponse",
|
|
173
|
+
]
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from pydantic import Field
|
|
7
|
+
|
|
8
|
+
from .base import WazzapiModel
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class BuiltinVariableInfo(WazzapiModel):
|
|
12
|
+
name: str
|
|
13
|
+
description: str
|
|
14
|
+
example: str
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class BuiltinVariablesResponse(WazzapiModel):
|
|
18
|
+
variables: list[BuiltinVariableInfo]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class TemplateCreateRequest(WazzapiModel):
|
|
22
|
+
name: str
|
|
23
|
+
content: str
|
|
24
|
+
category: str = "marketing"
|
|
25
|
+
media_type: str | None = None
|
|
26
|
+
media_url: str | None = None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class TemplateItem(WazzapiModel):
|
|
30
|
+
id: str
|
|
31
|
+
name: str
|
|
32
|
+
category: str
|
|
33
|
+
content: str
|
|
34
|
+
variables: list[str] = Field(default_factory=list)
|
|
35
|
+
builtin_variables: list[str] = Field(default_factory=list)
|
|
36
|
+
custom_variables: list[str] = Field(default_factory=list)
|
|
37
|
+
media_type: str | None = None
|
|
38
|
+
media_url: str | None = None
|
|
39
|
+
times_used: int
|
|
40
|
+
last_used_at: datetime | None = None
|
|
41
|
+
created_at: datetime
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class TemplateListResponse(WazzapiModel):
|
|
45
|
+
data: list[TemplateItem]
|
|
46
|
+
pagination: dict[str, Any]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class TemplatePreviewRequest(WazzapiModel):
|
|
50
|
+
content: str | None = None
|
|
51
|
+
template_id: str | None = None
|
|
52
|
+
custom_variables: dict[str, Any] | None = None
|
|
53
|
+
contact_id: str | None = None
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class TemplatePreviewResponse(WazzapiModel):
|
|
57
|
+
preview: str
|
|
58
|
+
all_variables: list[str] = Field(default_factory=list)
|
|
59
|
+
builtin_variables: list[str] = Field(default_factory=list)
|
|
60
|
+
custom_variables: list[str] = Field(default_factory=list)
|
|
61
|
+
missing_variables: list[str] = Field(default_factory=list)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class TemplateResponse(WazzapiModel):
|
|
65
|
+
id: str
|
|
66
|
+
name: str
|
|
67
|
+
category: str
|
|
68
|
+
content: str
|
|
69
|
+
variables: list[str] = Field(default_factory=list)
|
|
70
|
+
builtin_variables: list[str] = Field(default_factory=list)
|
|
71
|
+
custom_variables: list[str] = Field(default_factory=list)
|
|
72
|
+
media_type: str | None = None
|
|
73
|
+
media_url: str | None = None
|
|
74
|
+
is_active: bool
|
|
75
|
+
times_used: int
|
|
76
|
+
last_used_at: datetime | None = None
|
|
77
|
+
created_at: datetime
|
|
78
|
+
updated_at: datetime
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class TemplateUpdateRequest(WazzapiModel):
|
|
82
|
+
name: str | None = None
|
|
83
|
+
content: str | None = None
|
|
84
|
+
category: str | None = None
|
|
85
|
+
is_active: bool | None = None
|
|
86
|
+
media_type: str | None = None
|
|
87
|
+
media_url: str | None = None
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
__all__ = [
|
|
91
|
+
"BuiltinVariableInfo",
|
|
92
|
+
"BuiltinVariablesResponse",
|
|
93
|
+
"TemplateCreateRequest",
|
|
94
|
+
"TemplateItem",
|
|
95
|
+
"TemplateListResponse",
|
|
96
|
+
"TemplatePreviewRequest",
|
|
97
|
+
"TemplatePreviewResponse",
|
|
98
|
+
"TemplateResponse",
|
|
99
|
+
"TemplateUpdateRequest",
|
|
100
|
+
]
|