openwa-sdk 1.0.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.
- openwa_sdk-1.0.0/PKG-INFO +72 -0
- openwa_sdk-1.0.0/README.md +58 -0
- openwa_sdk-1.0.0/openwa/__init__.py +473 -0
- openwa_sdk-1.0.0/openwa_sdk.egg-info/PKG-INFO +72 -0
- openwa_sdk-1.0.0/openwa_sdk.egg-info/SOURCES.txt +8 -0
- openwa_sdk-1.0.0/openwa_sdk.egg-info/dependency_links.txt +1 -0
- openwa_sdk-1.0.0/openwa_sdk.egg-info/requires.txt +5 -0
- openwa_sdk-1.0.0/openwa_sdk.egg-info/top_level.txt +1 -0
- openwa_sdk-1.0.0/pyproject.toml +25 -0
- openwa_sdk-1.0.0/setup.cfg +4 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: openwa-sdk
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Official Python SDK for OpenWA WhatsApp API Gateway
|
|
5
|
+
Author: OpenWA Contributors
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: whatsapp,api,sdk,openwa,gateway
|
|
8
|
+
Requires-Python: >=3.9
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
Requires-Dist: httpx>=0.25.0
|
|
11
|
+
Provides-Extra: dev
|
|
12
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
13
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
14
|
+
|
|
15
|
+
# OpenWA Python SDK
|
|
16
|
+
|
|
17
|
+
The official Python client library for the [OpenWA](https://github.com/bonheurNE07/OpenWA-Python) REST API Gateway.
|
|
18
|
+
|
|
19
|
+
This SDK allows you to easily manage WhatsApp sessions, send text/media messages, and interact with WhatsApp groups natively in Python. It supports both **Synchronous** and **Asynchronous** workflows out of the box!
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install openwa-sdk
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
from openwa import OpenWAClient
|
|
31
|
+
import asyncio
|
|
32
|
+
|
|
33
|
+
# Initialize the client (points to your self-hosted OpenWA Gateway)
|
|
34
|
+
client = OpenWAClient(
|
|
35
|
+
base_url="http://localhost:2785",
|
|
36
|
+
api_key="secret-key"
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
# --- Synchronous Example ---
|
|
40
|
+
# 1. Create a session
|
|
41
|
+
session = client.sessions.create("my-session")
|
|
42
|
+
print(f"Session Created: {session}")
|
|
43
|
+
|
|
44
|
+
# 2. Start the session (returns QR code in the terminal)
|
|
45
|
+
client.sessions.start("my-session")
|
|
46
|
+
|
|
47
|
+
# 3. Send a message
|
|
48
|
+
client.messages.send_text("my-session", {
|
|
49
|
+
"chatId": "1234567890@c.us",
|
|
50
|
+
"text": "Hello from Python!"
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
# --- Asynchronous Example ---
|
|
54
|
+
async def main():
|
|
55
|
+
async_client = client.get_async_client()
|
|
56
|
+
|
|
57
|
+
# Send a message asynchronously
|
|
58
|
+
await async_client.messages.send_text("my-session", {
|
|
59
|
+
"chatId": "1234567890@c.us",
|
|
60
|
+
"text": "Hello async world!"
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
asyncio.run(main())
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Features
|
|
67
|
+
* **Fully Typed**: Written with type hints for excellent IDE autocomplete.
|
|
68
|
+
* **Sync & Async**: Backed by `httpx`, giving you both blocking and non-blocking clients.
|
|
69
|
+
* **Zero Overhead**: Directly maps to the OpenWA OpenAPI specification.
|
|
70
|
+
|
|
71
|
+
## License
|
|
72
|
+
MIT License
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# OpenWA Python SDK
|
|
2
|
+
|
|
3
|
+
The official Python client library for the [OpenWA](https://github.com/bonheurNE07/OpenWA-Python) REST API Gateway.
|
|
4
|
+
|
|
5
|
+
This SDK allows you to easily manage WhatsApp sessions, send text/media messages, and interact with WhatsApp groups natively in Python. It supports both **Synchronous** and **Asynchronous** workflows out of the box!
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install openwa-sdk
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
from openwa import OpenWAClient
|
|
17
|
+
import asyncio
|
|
18
|
+
|
|
19
|
+
# Initialize the client (points to your self-hosted OpenWA Gateway)
|
|
20
|
+
client = OpenWAClient(
|
|
21
|
+
base_url="http://localhost:2785",
|
|
22
|
+
api_key="secret-key"
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
# --- Synchronous Example ---
|
|
26
|
+
# 1. Create a session
|
|
27
|
+
session = client.sessions.create("my-session")
|
|
28
|
+
print(f"Session Created: {session}")
|
|
29
|
+
|
|
30
|
+
# 2. Start the session (returns QR code in the terminal)
|
|
31
|
+
client.sessions.start("my-session")
|
|
32
|
+
|
|
33
|
+
# 3. Send a message
|
|
34
|
+
client.messages.send_text("my-session", {
|
|
35
|
+
"chatId": "1234567890@c.us",
|
|
36
|
+
"text": "Hello from Python!"
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
# --- Asynchronous Example ---
|
|
40
|
+
async def main():
|
|
41
|
+
async_client = client.get_async_client()
|
|
42
|
+
|
|
43
|
+
# Send a message asynchronously
|
|
44
|
+
await async_client.messages.send_text("my-session", {
|
|
45
|
+
"chatId": "1234567890@c.us",
|
|
46
|
+
"text": "Hello async world!"
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
asyncio.run(main())
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Features
|
|
53
|
+
* **Fully Typed**: Written with type hints for excellent IDE autocomplete.
|
|
54
|
+
* **Sync & Async**: Backed by `httpx`, giving you both blocking and non-blocking clients.
|
|
55
|
+
* **Zero Overhead**: Directly maps to the OpenWA OpenAPI specification.
|
|
56
|
+
|
|
57
|
+
## License
|
|
58
|
+
MIT License
|
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
"""
|
|
2
|
+
OpenWA Python SDK
|
|
3
|
+
|
|
4
|
+
Official client library for the OpenWA WhatsApp API Gateway.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from typing import Any, Optional
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class OpenWAError(Exception):
|
|
15
|
+
"""Base exception for OpenWA SDK."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class OpenWAAPIError(OpenWAError):
|
|
19
|
+
"""API returned an error response."""
|
|
20
|
+
|
|
21
|
+
def __init__(self, status_code: int, message: str) -> None:
|
|
22
|
+
self.status_code = status_code
|
|
23
|
+
self.message = message
|
|
24
|
+
super().__init__(f"API Error {status_code}: {message}")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class OpenWAClientConfig:
|
|
29
|
+
"""Configuration for the OpenWA client."""
|
|
30
|
+
|
|
31
|
+
base_url: str
|
|
32
|
+
api_key: str
|
|
33
|
+
timeout: float = 30.0
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class MessageResponse:
|
|
38
|
+
message_id: str
|
|
39
|
+
timestamp: int
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class _BaseClient:
|
|
43
|
+
def __init__(
|
|
44
|
+
self,
|
|
45
|
+
base_url: Optional[str] = None,
|
|
46
|
+
api_key: Optional[str] = None,
|
|
47
|
+
timeout: float = 30.0,
|
|
48
|
+
) -> None:
|
|
49
|
+
url = base_url or os.getenv("OPENWA_BASE_URL")
|
|
50
|
+
key = api_key or os.getenv("OPENWA_API_KEY")
|
|
51
|
+
if not url:
|
|
52
|
+
raise ValueError(
|
|
53
|
+
"base_url must be provided or set via OPENWA_BASE_URL environment variable."
|
|
54
|
+
)
|
|
55
|
+
if not key:
|
|
56
|
+
raise ValueError(
|
|
57
|
+
"api_key must be provided or set via OPENWA_API_KEY environment variable."
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
self.config = OpenWAClientConfig(
|
|
61
|
+
base_url=url.rstrip("/"),
|
|
62
|
+
api_key=key,
|
|
63
|
+
timeout=timeout,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
def _handle_response(self, response: Any) -> Any:
|
|
67
|
+
if 400 <= response.status_code < 600:
|
|
68
|
+
try:
|
|
69
|
+
error_data = response.json()
|
|
70
|
+
msg = (
|
|
71
|
+
error_data.get("detail")
|
|
72
|
+
or error_data.get("message")
|
|
73
|
+
or response.text
|
|
74
|
+
)
|
|
75
|
+
except Exception:
|
|
76
|
+
msg = response.text
|
|
77
|
+
raise OpenWAAPIError(response.status_code, msg)
|
|
78
|
+
|
|
79
|
+
if response.status_code == 204:
|
|
80
|
+
return None
|
|
81
|
+
return response.json()
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class OpenWAClient(_BaseClient):
|
|
85
|
+
"""Synchronous OpenWA API client."""
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def sessions(self) -> "_SessionsResource":
|
|
89
|
+
return _SessionsResource(self)
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def messages(self) -> "_MessagesResource":
|
|
93
|
+
return _MessagesResource(self)
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def webhooks(self) -> "_WebhooksResource":
|
|
97
|
+
return _WebhooksResource(self)
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def api_keys(self) -> "_ApiKeysResource":
|
|
101
|
+
return _ApiKeysResource(self)
|
|
102
|
+
|
|
103
|
+
@property
|
|
104
|
+
def contacts(self) -> "_ContactsResource":
|
|
105
|
+
return _ContactsResource(self)
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def groups(self) -> "_GroupsResource":
|
|
109
|
+
return _GroupsResource(self)
|
|
110
|
+
|
|
111
|
+
def _request(self, method: str, path: str, json: Any = None) -> Any:
|
|
112
|
+
try:
|
|
113
|
+
import httpx
|
|
114
|
+
except ImportError:
|
|
115
|
+
raise ImportError("httpx is required. Install with: pip install httpx")
|
|
116
|
+
|
|
117
|
+
with httpx.Client(timeout=self.config.timeout) as client:
|
|
118
|
+
request_kwargs = {"json": json} if json is not None else {}
|
|
119
|
+
if method == "DELETE" and json is not None:
|
|
120
|
+
request_kwargs = {
|
|
121
|
+
"request": client.build_request(
|
|
122
|
+
"DELETE",
|
|
123
|
+
f"{self.config.base_url}{path}",
|
|
124
|
+
json=json,
|
|
125
|
+
headers={
|
|
126
|
+
"Content-Type": "application/json",
|
|
127
|
+
"X-API-Key": self.config.api_key,
|
|
128
|
+
},
|
|
129
|
+
)
|
|
130
|
+
}
|
|
131
|
+
response = client.send(request_kwargs["request"])
|
|
132
|
+
else:
|
|
133
|
+
response = client.request(
|
|
134
|
+
method,
|
|
135
|
+
f"{self.config.base_url}{path}",
|
|
136
|
+
headers={
|
|
137
|
+
"Content-Type": "application/json",
|
|
138
|
+
"X-API-Key": self.config.api_key,
|
|
139
|
+
},
|
|
140
|
+
**request_kwargs,
|
|
141
|
+
)
|
|
142
|
+
return self._handle_response(response)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class AsyncOpenWAClient(_BaseClient):
|
|
146
|
+
"""Asynchronous OpenWA API client."""
|
|
147
|
+
|
|
148
|
+
@property
|
|
149
|
+
def sessions(self) -> "_AsyncSessionsResource":
|
|
150
|
+
return _AsyncSessionsResource(self)
|
|
151
|
+
|
|
152
|
+
@property
|
|
153
|
+
def messages(self) -> "_AsyncMessagesResource":
|
|
154
|
+
return _AsyncMessagesResource(self)
|
|
155
|
+
|
|
156
|
+
@property
|
|
157
|
+
def webhooks(self) -> "_AsyncWebhooksResource":
|
|
158
|
+
return _AsyncWebhooksResource(self)
|
|
159
|
+
|
|
160
|
+
@property
|
|
161
|
+
def api_keys(self) -> "_AsyncApiKeysResource":
|
|
162
|
+
return _AsyncApiKeysResource(self)
|
|
163
|
+
|
|
164
|
+
@property
|
|
165
|
+
def contacts(self) -> "_AsyncContactsResource":
|
|
166
|
+
return _AsyncContactsResource(self)
|
|
167
|
+
|
|
168
|
+
@property
|
|
169
|
+
def groups(self) -> "_AsyncGroupsResource":
|
|
170
|
+
return _AsyncGroupsResource(self)
|
|
171
|
+
|
|
172
|
+
async def _request(self, method: str, path: str, json: Any = None) -> Any:
|
|
173
|
+
try:
|
|
174
|
+
import httpx
|
|
175
|
+
except ImportError:
|
|
176
|
+
raise ImportError("httpx is required. Install with: pip install httpx")
|
|
177
|
+
|
|
178
|
+
async with httpx.AsyncClient(timeout=self.config.timeout) as client:
|
|
179
|
+
request_kwargs = {"json": json} if json is not None else {}
|
|
180
|
+
if method == "DELETE" and json is not None:
|
|
181
|
+
req = client.build_request(
|
|
182
|
+
"DELETE",
|
|
183
|
+
f"{self.config.base_url}{path}",
|
|
184
|
+
json=json,
|
|
185
|
+
headers={
|
|
186
|
+
"Content-Type": "application/json",
|
|
187
|
+
"X-API-Key": self.config.api_key,
|
|
188
|
+
},
|
|
189
|
+
)
|
|
190
|
+
response = await client.send(req)
|
|
191
|
+
else:
|
|
192
|
+
response = await client.request(
|
|
193
|
+
method,
|
|
194
|
+
f"{self.config.base_url}{path}",
|
|
195
|
+
headers={
|
|
196
|
+
"Content-Type": "application/json",
|
|
197
|
+
"X-API-Key": self.config.api_key,
|
|
198
|
+
},
|
|
199
|
+
**request_kwargs,
|
|
200
|
+
)
|
|
201
|
+
return self._handle_response(response)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
class _SessionsResource:
|
|
205
|
+
def __init__(self, client: OpenWAClient) -> None:
|
|
206
|
+
self._client = client
|
|
207
|
+
|
|
208
|
+
def list(self) -> list[dict]:
|
|
209
|
+
return self._client._request("GET", "/api/sessions")
|
|
210
|
+
|
|
211
|
+
def get(self, session_id: str) -> dict:
|
|
212
|
+
return self._client._request("GET", f"/api/sessions/{session_id}")
|
|
213
|
+
|
|
214
|
+
def create(self, name: str) -> dict:
|
|
215
|
+
return self._client._request("POST", "/api/sessions", {"name": name})
|
|
216
|
+
|
|
217
|
+
def start(self, session_id: str) -> dict:
|
|
218
|
+
return self._client._request("POST", f"/api/sessions/{session_id}/start")
|
|
219
|
+
|
|
220
|
+
def stop(self, session_id: str) -> dict:
|
|
221
|
+
return self._client._request("POST", f"/api/sessions/{session_id}/stop")
|
|
222
|
+
|
|
223
|
+
def delete(self, session_id: str) -> None:
|
|
224
|
+
self._client._request("DELETE", f"/api/sessions/{session_id}")
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
class _AsyncSessionsResource:
|
|
228
|
+
def __init__(self, client: AsyncOpenWAClient) -> None:
|
|
229
|
+
self._client = client
|
|
230
|
+
|
|
231
|
+
async def list(self) -> list[dict]:
|
|
232
|
+
return await self._client._request("GET", "/api/sessions")
|
|
233
|
+
|
|
234
|
+
async def get(self, session_id: str) -> dict:
|
|
235
|
+
return await self._client._request("GET", f"/api/sessions/{session_id}")
|
|
236
|
+
|
|
237
|
+
async def create(self, name: str) -> dict:
|
|
238
|
+
return await self._client._request("POST", "/api/sessions", {"name": name})
|
|
239
|
+
|
|
240
|
+
async def start(self, session_id: str) -> dict:
|
|
241
|
+
return await self._client._request("POST", f"/api/sessions/{session_id}/start")
|
|
242
|
+
|
|
243
|
+
async def stop(self, session_id: str) -> dict:
|
|
244
|
+
return await self._client._request("POST", f"/api/sessions/{session_id}/stop")
|
|
245
|
+
|
|
246
|
+
async def delete(self, session_id: str) -> None:
|
|
247
|
+
await self._client._request("DELETE", f"/api/sessions/{session_id}")
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
class _MessagesResource:
|
|
251
|
+
def __init__(self, client: OpenWAClient) -> None:
|
|
252
|
+
self._client = client
|
|
253
|
+
|
|
254
|
+
def list(self, session_id: str) -> list[dict]:
|
|
255
|
+
return self._client._request("GET", f"/api/sessions/{session_id}/messages")
|
|
256
|
+
|
|
257
|
+
def send_text(self, session_id: str, data: dict[str, str]) -> MessageResponse:
|
|
258
|
+
res = self._client._request(
|
|
259
|
+
"POST", f"/api/sessions/{session_id}/messages/send-text", data
|
|
260
|
+
)
|
|
261
|
+
return MessageResponse(res["messageId"], res["timestamp"])
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
class _AsyncMessagesResource:
|
|
265
|
+
def __init__(self, client: AsyncOpenWAClient) -> None:
|
|
266
|
+
self._client = client
|
|
267
|
+
|
|
268
|
+
async def list(self, session_id: str) -> list[dict]:
|
|
269
|
+
return await self._client._request(
|
|
270
|
+
"GET", f"/api/sessions/{session_id}/messages"
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
async def send_text(self, session_id: str, data: dict[str, str]) -> MessageResponse:
|
|
274
|
+
res = await self._client._request(
|
|
275
|
+
"POST", f"/api/sessions/{session_id}/messages/send-text", data
|
|
276
|
+
)
|
|
277
|
+
return MessageResponse(res["messageId"], res["timestamp"])
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
class _WebhooksResource:
|
|
281
|
+
def __init__(self, client: OpenWAClient) -> None:
|
|
282
|
+
self._client = client
|
|
283
|
+
|
|
284
|
+
def list_all(self) -> list[dict]:
|
|
285
|
+
return self._client._request("GET", "/api/webhooks")
|
|
286
|
+
|
|
287
|
+
def create(self, session_id: str, data: dict) -> dict:
|
|
288
|
+
return self._client._request(
|
|
289
|
+
"POST", f"/api/sessions/{session_id}/webhooks", data
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
def update(self, session_id: str, webhook_id: str, data: dict) -> dict:
|
|
293
|
+
return self._client._request(
|
|
294
|
+
"PUT", f"/api/sessions/{session_id}/webhooks/{webhook_id}", data
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
def delete(self, session_id: str, webhook_id: str) -> None:
|
|
298
|
+
self._client._request(
|
|
299
|
+
"DELETE", f"/api/sessions/{session_id}/webhooks/{webhook_id}"
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
class _AsyncWebhooksResource:
|
|
304
|
+
def __init__(self, client: AsyncOpenWAClient) -> None:
|
|
305
|
+
self._client = client
|
|
306
|
+
|
|
307
|
+
async def list_all(self) -> list[dict]:
|
|
308
|
+
return await self._client._request("GET", "/api/webhooks")
|
|
309
|
+
|
|
310
|
+
async def create(self, session_id: str, data: dict) -> dict:
|
|
311
|
+
return await self._client._request(
|
|
312
|
+
"POST", f"/api/sessions/{session_id}/webhooks", data
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
async def update(self, session_id: str, webhook_id: str, data: dict) -> dict:
|
|
316
|
+
return await self._client._request(
|
|
317
|
+
"PUT", f"/api/sessions/{session_id}/webhooks/{webhook_id}", data
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
async def delete(self, session_id: str, webhook_id: str) -> None:
|
|
321
|
+
await self._client._request(
|
|
322
|
+
"DELETE", f"/api/sessions/{session_id}/webhooks/{webhook_id}"
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
class _ApiKeysResource:
|
|
327
|
+
def __init__(self, client: OpenWAClient) -> None:
|
|
328
|
+
self._client = client
|
|
329
|
+
|
|
330
|
+
def list(self) -> list[dict]:
|
|
331
|
+
return self._client._request("GET", "/api/auth/api-keys")
|
|
332
|
+
|
|
333
|
+
def create(self, data: dict) -> dict:
|
|
334
|
+
return self._client._request("POST", "/api/auth/api-keys", data)
|
|
335
|
+
|
|
336
|
+
def revoke(self, key_id: str) -> dict:
|
|
337
|
+
return self._client._request("POST", f"/api/auth/api-keys/{key_id}/revoke")
|
|
338
|
+
|
|
339
|
+
def rotate(self, key_id: str) -> dict:
|
|
340
|
+
return self._client._request("POST", f"/api/auth/api-keys/{key_id}/rotate")
|
|
341
|
+
|
|
342
|
+
def delete(self, key_id: str) -> None:
|
|
343
|
+
self._client._request("DELETE", f"/api/auth/api-keys/{key_id}")
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
class _AsyncApiKeysResource:
|
|
347
|
+
def __init__(self, client: AsyncOpenWAClient) -> None:
|
|
348
|
+
self._client = client
|
|
349
|
+
|
|
350
|
+
async def list(self) -> list[dict]:
|
|
351
|
+
return await self._client._request("GET", "/api/auth/api-keys")
|
|
352
|
+
|
|
353
|
+
async def create(self, data: dict) -> dict:
|
|
354
|
+
return await self._client._request("POST", "/api/auth/api-keys", data)
|
|
355
|
+
|
|
356
|
+
async def revoke(self, key_id: str) -> dict:
|
|
357
|
+
return await self._client._request(
|
|
358
|
+
"POST", f"/api/auth/api-keys/{key_id}/revoke"
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
async def rotate(self, key_id: str) -> dict:
|
|
362
|
+
return await self._client._request(
|
|
363
|
+
"POST", f"/api/auth/api-keys/{key_id}/rotate"
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
async def delete(self, key_id: str) -> None:
|
|
367
|
+
await self._client._request("DELETE", f"/api/auth/api-keys/{key_id}")
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
class _ContactsResource:
|
|
371
|
+
def __init__(self, client: OpenWAClient) -> None:
|
|
372
|
+
self._client = client
|
|
373
|
+
|
|
374
|
+
def list(self, session_id: str) -> list[dict]:
|
|
375
|
+
return self._client._request("GET", f"/api/sessions/{session_id}/contacts")
|
|
376
|
+
|
|
377
|
+
def get(self, session_id: str, contact_id: str) -> dict:
|
|
378
|
+
return self._client._request(
|
|
379
|
+
"GET", f"/api/sessions/{session_id}/contacts/{contact_id}"
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
def check_number(self, session_id: str, number: str) -> dict:
|
|
383
|
+
return self._client._request(
|
|
384
|
+
"GET", f"/api/sessions/{session_id}/contacts/check/{number}"
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
def block(self, session_id: str, contact_id: str) -> dict:
|
|
388
|
+
return self._client._request(
|
|
389
|
+
"POST", f"/api/sessions/{session_id}/contacts/{contact_id}/block"
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
class _AsyncContactsResource:
|
|
394
|
+
def __init__(self, client: AsyncOpenWAClient) -> None:
|
|
395
|
+
self._client = client
|
|
396
|
+
|
|
397
|
+
async def list(self, session_id: str) -> list[dict]:
|
|
398
|
+
return await self._client._request(
|
|
399
|
+
"GET", f"/api/sessions/{session_id}/contacts"
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
async def get(self, session_id: str, contact_id: str) -> dict:
|
|
403
|
+
return await self._client._request(
|
|
404
|
+
"GET", f"/api/sessions/{session_id}/contacts/{contact_id}"
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
async def check_number(self, session_id: str, number: str) -> dict:
|
|
408
|
+
return await self._client._request(
|
|
409
|
+
"GET", f"/api/sessions/{session_id}/contacts/check/{number}"
|
|
410
|
+
)
|
|
411
|
+
|
|
412
|
+
async def block(self, session_id: str, contact_id: str) -> dict:
|
|
413
|
+
return await self._client._request(
|
|
414
|
+
"POST", f"/api/sessions/{session_id}/contacts/{contact_id}/block"
|
|
415
|
+
)
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
class _GroupsResource:
|
|
419
|
+
def __init__(self, client: OpenWAClient) -> None:
|
|
420
|
+
self._client = client
|
|
421
|
+
|
|
422
|
+
def list(self, session_id: str) -> list[dict]:
|
|
423
|
+
return self._client._request("GET", f"/api/sessions/{session_id}/groups")
|
|
424
|
+
|
|
425
|
+
def create(self, session_id: str, data: dict) -> dict:
|
|
426
|
+
return self._client._request("POST", f"/api/sessions/{session_id}/groups", data)
|
|
427
|
+
|
|
428
|
+
def add_participants(self, session_id: str, group_id: str, data: dict) -> dict:
|
|
429
|
+
return self._client._request(
|
|
430
|
+
"POST", f"/api/sessions/{session_id}/groups/{group_id}/participants", data
|
|
431
|
+
)
|
|
432
|
+
|
|
433
|
+
def remove_participants(self, session_id: str, group_id: str, data: dict) -> dict:
|
|
434
|
+
return self._client._request(
|
|
435
|
+
"DELETE", f"/api/sessions/{session_id}/groups/{group_id}/participants", data
|
|
436
|
+
)
|
|
437
|
+
|
|
438
|
+
def set_subject(self, session_id: str, group_id: str, data: dict) -> dict:
|
|
439
|
+
return self._client._request(
|
|
440
|
+
"PUT", f"/api/sessions/{session_id}/groups/{group_id}/subject", data
|
|
441
|
+
)
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
class _AsyncGroupsResource:
|
|
445
|
+
def __init__(self, client: AsyncOpenWAClient) -> None:
|
|
446
|
+
self._client = client
|
|
447
|
+
|
|
448
|
+
async def list(self, session_id: str) -> list[dict]:
|
|
449
|
+
return await self._client._request("GET", f"/api/sessions/{session_id}/groups")
|
|
450
|
+
|
|
451
|
+
async def create(self, session_id: str, data: dict) -> dict:
|
|
452
|
+
return await self._client._request(
|
|
453
|
+
"POST", f"/api/sessions/{session_id}/groups", data
|
|
454
|
+
)
|
|
455
|
+
|
|
456
|
+
async def add_participants(
|
|
457
|
+
self, session_id: str, group_id: str, data: dict
|
|
458
|
+
) -> dict:
|
|
459
|
+
return await self._client._request(
|
|
460
|
+
"POST", f"/api/sessions/{session_id}/groups/{group_id}/participants", data
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
async def remove_participants(
|
|
464
|
+
self, session_id: str, group_id: str, data: dict
|
|
465
|
+
) -> dict:
|
|
466
|
+
return await self._client._request(
|
|
467
|
+
"DELETE", f"/api/sessions/{session_id}/groups/{group_id}/participants", data
|
|
468
|
+
)
|
|
469
|
+
|
|
470
|
+
async def set_subject(self, session_id: str, group_id: str, data: dict) -> dict:
|
|
471
|
+
return await self._client._request(
|
|
472
|
+
"PUT", f"/api/sessions/{session_id}/groups/{group_id}/subject", data
|
|
473
|
+
)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: openwa-sdk
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Official Python SDK for OpenWA WhatsApp API Gateway
|
|
5
|
+
Author: OpenWA Contributors
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: whatsapp,api,sdk,openwa,gateway
|
|
8
|
+
Requires-Python: >=3.9
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
Requires-Dist: httpx>=0.25.0
|
|
11
|
+
Provides-Extra: dev
|
|
12
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
13
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
14
|
+
|
|
15
|
+
# OpenWA Python SDK
|
|
16
|
+
|
|
17
|
+
The official Python client library for the [OpenWA](https://github.com/bonheurNE07/OpenWA-Python) REST API Gateway.
|
|
18
|
+
|
|
19
|
+
This SDK allows you to easily manage WhatsApp sessions, send text/media messages, and interact with WhatsApp groups natively in Python. It supports both **Synchronous** and **Asynchronous** workflows out of the box!
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install openwa-sdk
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
from openwa import OpenWAClient
|
|
31
|
+
import asyncio
|
|
32
|
+
|
|
33
|
+
# Initialize the client (points to your self-hosted OpenWA Gateway)
|
|
34
|
+
client = OpenWAClient(
|
|
35
|
+
base_url="http://localhost:2785",
|
|
36
|
+
api_key="secret-key"
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
# --- Synchronous Example ---
|
|
40
|
+
# 1. Create a session
|
|
41
|
+
session = client.sessions.create("my-session")
|
|
42
|
+
print(f"Session Created: {session}")
|
|
43
|
+
|
|
44
|
+
# 2. Start the session (returns QR code in the terminal)
|
|
45
|
+
client.sessions.start("my-session")
|
|
46
|
+
|
|
47
|
+
# 3. Send a message
|
|
48
|
+
client.messages.send_text("my-session", {
|
|
49
|
+
"chatId": "1234567890@c.us",
|
|
50
|
+
"text": "Hello from Python!"
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
# --- Asynchronous Example ---
|
|
54
|
+
async def main():
|
|
55
|
+
async_client = client.get_async_client()
|
|
56
|
+
|
|
57
|
+
# Send a message asynchronously
|
|
58
|
+
await async_client.messages.send_text("my-session", {
|
|
59
|
+
"chatId": "1234567890@c.us",
|
|
60
|
+
"text": "Hello async world!"
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
asyncio.run(main())
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Features
|
|
67
|
+
* **Fully Typed**: Written with type hints for excellent IDE autocomplete.
|
|
68
|
+
* **Sync & Async**: Backed by `httpx`, giving you both blocking and non-blocking clients.
|
|
69
|
+
* **Zero Overhead**: Directly maps to the OpenWA OpenAPI specification.
|
|
70
|
+
|
|
71
|
+
## License
|
|
72
|
+
MIT License
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
openwa
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "openwa-sdk"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Official Python SDK for OpenWA WhatsApp API Gateway"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
requires-python = ">=3.9"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "OpenWA Contributors" }
|
|
14
|
+
]
|
|
15
|
+
keywords = ["whatsapp", "api", "sdk", "openwa", "gateway"]
|
|
16
|
+
|
|
17
|
+
dependencies = [
|
|
18
|
+
"httpx>=0.25.0",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[project.optional-dependencies]
|
|
22
|
+
dev = [
|
|
23
|
+
"pytest>=7.0",
|
|
24
|
+
"pytest-asyncio>=0.23",
|
|
25
|
+
]
|