waappa-sdk 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- waappa_sdk-0.1.0/LICENSE +21 -0
- waappa_sdk-0.1.0/PKG-INFO +161 -0
- waappa_sdk-0.1.0/README.md +141 -0
- waappa_sdk-0.1.0/pyproject.toml +3 -0
- waappa_sdk-0.1.0/setup.cfg +36 -0
- waappa_sdk-0.1.0/waappa/__init__.py +3 -0
- waappa_sdk-0.1.0/waappa/client.py +247 -0
- waappa_sdk-0.1.0/waappa/py.typed +1 -0
- waappa_sdk-0.1.0/waappa_sdk.egg-info/PKG-INFO +161 -0
- waappa_sdk-0.1.0/waappa_sdk.egg-info/SOURCES.txt +11 -0
- waappa_sdk-0.1.0/waappa_sdk.egg-info/dependency_links.txt +1 -0
- waappa_sdk-0.1.0/waappa_sdk.egg-info/top_level.txt +1 -0
waappa_sdk-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Waappa
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: waappa-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official Python SDK for the Waappa API.
|
|
5
|
+
Author: Waappa
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: waappa,whatsapp,waha,sdk
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Requires-Python: >=3.9
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Dynamic: license-file
|
|
20
|
+
|
|
21
|
+
# Waappa Python SDK
|
|
22
|
+
|
|
23
|
+
Official Python SDK for the Waappa API.
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pip install waappa-sdk
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Requires Python 3.9 or newer.
|
|
32
|
+
|
|
33
|
+
## Quick Start
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
from waappa import WaappaClient
|
|
37
|
+
|
|
38
|
+
waappa = WaappaClient(
|
|
39
|
+
api_key="waappa_session_api_key",
|
|
40
|
+
session="default",
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
waappa.send_text(
|
|
44
|
+
chatId="919876543210@c.us",
|
|
45
|
+
text="Hello from Waappa",
|
|
46
|
+
)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The SDK sends the API key as the `Authorization` header. By default it uses:
|
|
50
|
+
|
|
51
|
+
```text
|
|
52
|
+
https://api.waappa.com
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Use `base_url` for local or private deployments:
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
waappa = WaappaClient(
|
|
59
|
+
api_key="waappa_session_api_key",
|
|
60
|
+
session="default",
|
|
61
|
+
base_url="http://localhost:3001",
|
|
62
|
+
)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Messages
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
waappa.send_image(
|
|
69
|
+
chatId="919876543210@c.us",
|
|
70
|
+
caption="Invoice",
|
|
71
|
+
file={
|
|
72
|
+
"mimetype": "image/jpeg",
|
|
73
|
+
"filename": "invoice.jpg",
|
|
74
|
+
"url": "https://example.com/invoice.jpg",
|
|
75
|
+
},
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
waappa.send_poll(
|
|
79
|
+
chatId="919876543210@c.us",
|
|
80
|
+
poll={
|
|
81
|
+
"name": "Choose one",
|
|
82
|
+
"options": ["A", "B"],
|
|
83
|
+
"multipleAnswers": False,
|
|
84
|
+
},
|
|
85
|
+
)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Supported message helpers:
|
|
89
|
+
|
|
90
|
+
- `send_text`
|
|
91
|
+
- `send_image`
|
|
92
|
+
- `send_voice`
|
|
93
|
+
- `send_video`
|
|
94
|
+
- `send_file`
|
|
95
|
+
- `send_poll`
|
|
96
|
+
- `send_list`
|
|
97
|
+
- `send_contact_vcard`
|
|
98
|
+
- `send_location`
|
|
99
|
+
- `send_event`
|
|
100
|
+
- `edit_message`
|
|
101
|
+
- `delete_message`
|
|
102
|
+
- `get_messages`
|
|
103
|
+
|
|
104
|
+
## Media Upload
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
uploaded = waappa.upload("invoice.jpg")
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Use the returned URL in `send_image`, `send_video`, `send_voice`, or `send_file`.
|
|
111
|
+
|
|
112
|
+
## Groups, Contacts, Labels, LIDs, Channels
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
waappa.create_group("Support", ["919876543210@c.us"])
|
|
116
|
+
|
|
117
|
+
contacts = waappa.list_contacts()
|
|
118
|
+
labels = waappa.list_labels()
|
|
119
|
+
channels = waappa.list_channels()
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
The client includes helpers for the selected Waappa API surface: groups, group participants, contacts, labels, LID/phone resolution, and channels.
|
|
123
|
+
|
|
124
|
+
## Admin / Session Management
|
|
125
|
+
|
|
126
|
+
Session management methods require a master key:
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
admin = WaappaClient(api_key="mk_live_...")
|
|
130
|
+
sessions = admin.list_sessions()
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Errors
|
|
134
|
+
|
|
135
|
+
Failed API responses raise `WaappaError`:
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
from waappa import WaappaError
|
|
139
|
+
|
|
140
|
+
try:
|
|
141
|
+
waappa.send_text(chatId="bad", text="Hello")
|
|
142
|
+
except WaappaError as error:
|
|
143
|
+
print(error.status)
|
|
144
|
+
print(error.data)
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Development
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
python -m compileall waappa
|
|
151
|
+
python -m pip wheel . --no-deps -w dist
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Publishing
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
python -m build
|
|
158
|
+
python -m twine upload dist/*
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Before publishing, run a smoke test against a real Waappa API key and session.
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# Waappa Python SDK
|
|
2
|
+
|
|
3
|
+
Official Python SDK for the Waappa API.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install waappa-sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Requires Python 3.9 or newer.
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
from waappa import WaappaClient
|
|
17
|
+
|
|
18
|
+
waappa = WaappaClient(
|
|
19
|
+
api_key="waappa_session_api_key",
|
|
20
|
+
session="default",
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
waappa.send_text(
|
|
24
|
+
chatId="919876543210@c.us",
|
|
25
|
+
text="Hello from Waappa",
|
|
26
|
+
)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The SDK sends the API key as the `Authorization` header. By default it uses:
|
|
30
|
+
|
|
31
|
+
```text
|
|
32
|
+
https://api.waappa.com
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Use `base_url` for local or private deployments:
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
waappa = WaappaClient(
|
|
39
|
+
api_key="waappa_session_api_key",
|
|
40
|
+
session="default",
|
|
41
|
+
base_url="http://localhost:3001",
|
|
42
|
+
)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Messages
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
waappa.send_image(
|
|
49
|
+
chatId="919876543210@c.us",
|
|
50
|
+
caption="Invoice",
|
|
51
|
+
file={
|
|
52
|
+
"mimetype": "image/jpeg",
|
|
53
|
+
"filename": "invoice.jpg",
|
|
54
|
+
"url": "https://example.com/invoice.jpg",
|
|
55
|
+
},
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
waappa.send_poll(
|
|
59
|
+
chatId="919876543210@c.us",
|
|
60
|
+
poll={
|
|
61
|
+
"name": "Choose one",
|
|
62
|
+
"options": ["A", "B"],
|
|
63
|
+
"multipleAnswers": False,
|
|
64
|
+
},
|
|
65
|
+
)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Supported message helpers:
|
|
69
|
+
|
|
70
|
+
- `send_text`
|
|
71
|
+
- `send_image`
|
|
72
|
+
- `send_voice`
|
|
73
|
+
- `send_video`
|
|
74
|
+
- `send_file`
|
|
75
|
+
- `send_poll`
|
|
76
|
+
- `send_list`
|
|
77
|
+
- `send_contact_vcard`
|
|
78
|
+
- `send_location`
|
|
79
|
+
- `send_event`
|
|
80
|
+
- `edit_message`
|
|
81
|
+
- `delete_message`
|
|
82
|
+
- `get_messages`
|
|
83
|
+
|
|
84
|
+
## Media Upload
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
uploaded = waappa.upload("invoice.jpg")
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Use the returned URL in `send_image`, `send_video`, `send_voice`, or `send_file`.
|
|
91
|
+
|
|
92
|
+
## Groups, Contacts, Labels, LIDs, Channels
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
waappa.create_group("Support", ["919876543210@c.us"])
|
|
96
|
+
|
|
97
|
+
contacts = waappa.list_contacts()
|
|
98
|
+
labels = waappa.list_labels()
|
|
99
|
+
channels = waappa.list_channels()
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
The client includes helpers for the selected Waappa API surface: groups, group participants, contacts, labels, LID/phone resolution, and channels.
|
|
103
|
+
|
|
104
|
+
## Admin / Session Management
|
|
105
|
+
|
|
106
|
+
Session management methods require a master key:
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
admin = WaappaClient(api_key="mk_live_...")
|
|
110
|
+
sessions = admin.list_sessions()
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Errors
|
|
114
|
+
|
|
115
|
+
Failed API responses raise `WaappaError`:
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
from waappa import WaappaError
|
|
119
|
+
|
|
120
|
+
try:
|
|
121
|
+
waappa.send_text(chatId="bad", text="Hello")
|
|
122
|
+
except WaappaError as error:
|
|
123
|
+
print(error.status)
|
|
124
|
+
print(error.data)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Development
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
python -m compileall waappa
|
|
131
|
+
python -m pip wheel . --no-deps -w dist
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Publishing
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
python -m build
|
|
138
|
+
python -m twine upload dist/*
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Before publishing, run a smoke test against a real Waappa API key and session.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
[metadata]
|
|
2
|
+
name = waappa-sdk
|
|
3
|
+
version = 0.1.0
|
|
4
|
+
description = Official Python SDK for the Waappa API.
|
|
5
|
+
long_description = file: README.md
|
|
6
|
+
long_description_content_type = text/markdown
|
|
7
|
+
author = Waappa
|
|
8
|
+
license = MIT
|
|
9
|
+
license_files = LICENSE
|
|
10
|
+
keywords = waappa, whatsapp, waha, sdk
|
|
11
|
+
classifiers =
|
|
12
|
+
Development Status :: 3 - Alpha
|
|
13
|
+
Intended Audience :: Developers
|
|
14
|
+
License :: OSI Approved :: MIT License
|
|
15
|
+
Programming Language :: Python :: 3
|
|
16
|
+
Programming Language :: Python :: 3.9
|
|
17
|
+
Programming Language :: Python :: 3.10
|
|
18
|
+
Programming Language :: Python :: 3.11
|
|
19
|
+
Programming Language :: Python :: 3.12
|
|
20
|
+
|
|
21
|
+
[options]
|
|
22
|
+
packages = find:
|
|
23
|
+
python_requires = >=3.9
|
|
24
|
+
include_package_data = True
|
|
25
|
+
|
|
26
|
+
[options.packages.find]
|
|
27
|
+
where = .
|
|
28
|
+
include = waappa*
|
|
29
|
+
|
|
30
|
+
[options.package_data]
|
|
31
|
+
waappa = py.typed
|
|
32
|
+
|
|
33
|
+
[egg_info]
|
|
34
|
+
tag_build =
|
|
35
|
+
tag_date = 0
|
|
36
|
+
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import mimetypes
|
|
5
|
+
import os
|
|
6
|
+
import uuid
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any, BinaryIO, Mapping
|
|
9
|
+
from urllib.parse import urlencode, quote
|
|
10
|
+
from urllib.request import Request, urlopen
|
|
11
|
+
from urllib.error import HTTPError
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class WaappaError(Exception):
|
|
15
|
+
def __init__(self, status: int, data: Any):
|
|
16
|
+
super().__init__(f"Waappa API request failed with status {status}")
|
|
17
|
+
self.status = status
|
|
18
|
+
self.data = data
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class WaappaClient:
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
api_key: str,
|
|
25
|
+
*,
|
|
26
|
+
base_url: str = "https://api.waappa.com",
|
|
27
|
+
session: str | None = None,
|
|
28
|
+
timeout: float = 30.0,
|
|
29
|
+
) -> None:
|
|
30
|
+
if not api_key:
|
|
31
|
+
raise ValueError("WaappaClient requires an api_key.")
|
|
32
|
+
self.api_key = api_key
|
|
33
|
+
self.base_url = base_url.rstrip("/")
|
|
34
|
+
self.session = session
|
|
35
|
+
self.timeout = timeout
|
|
36
|
+
|
|
37
|
+
def request(
|
|
38
|
+
self,
|
|
39
|
+
method: str,
|
|
40
|
+
path: str,
|
|
41
|
+
*,
|
|
42
|
+
query: Mapping[str, Any] | None = None,
|
|
43
|
+
body: Any = None,
|
|
44
|
+
headers: Mapping[str, str] | None = None,
|
|
45
|
+
) -> Any:
|
|
46
|
+
url = f"{self.base_url}{path}"
|
|
47
|
+
clean_query = {k: v for k, v in (query or {}).items() if v is not None}
|
|
48
|
+
if clean_query:
|
|
49
|
+
url = f"{url}?{urlencode(clean_query)}"
|
|
50
|
+
|
|
51
|
+
request_headers = {"Authorization": self.api_key, **(headers or {})}
|
|
52
|
+
data = None
|
|
53
|
+
if body is not None:
|
|
54
|
+
if isinstance(body, bytes):
|
|
55
|
+
data = body
|
|
56
|
+
else:
|
|
57
|
+
request_headers.setdefault("Content-Type", "application/json")
|
|
58
|
+
data = json.dumps(body).encode("utf-8")
|
|
59
|
+
|
|
60
|
+
req = Request(url, data=data, headers=request_headers, method=method.upper())
|
|
61
|
+
try:
|
|
62
|
+
with urlopen(req, timeout=self.timeout) as response:
|
|
63
|
+
return _decode_response(response.read(), response.headers.get("Content-Type", ""))
|
|
64
|
+
except HTTPError as exc:
|
|
65
|
+
raise WaappaError(exc.code, _decode_response(exc.read(), exc.headers.get("Content-Type", ""))) from exc
|
|
66
|
+
|
|
67
|
+
def health(self) -> Any:
|
|
68
|
+
return self.request("GET", "/health")
|
|
69
|
+
|
|
70
|
+
def upload(self, file: str | os.PathLike[str] | BinaryIO, *, filename: str | None = None, mimetype: str | None = None) -> Any:
|
|
71
|
+
file_name, file_bytes = _read_upload_file(file, filename)
|
|
72
|
+
content_type = mimetype or mimetypes.guess_type(file_name)[0] or "application/octet-stream"
|
|
73
|
+
boundary = f"waappa-{uuid.uuid4().hex}"
|
|
74
|
+
body = _multipart_body(boundary, "file", file_name, content_type, file_bytes)
|
|
75
|
+
return self.request(
|
|
76
|
+
"POST",
|
|
77
|
+
"/api/upload",
|
|
78
|
+
body=body,
|
|
79
|
+
headers={"Content-Type": f"multipart/form-data; boundary={boundary}"},
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
def send_text(self, **body: Any) -> Any:
|
|
83
|
+
return self.request("POST", "/api/sendText", body=body)
|
|
84
|
+
|
|
85
|
+
def send_poll(self, **body: Any) -> Any:
|
|
86
|
+
return self.request("POST", "/api/sendPoll", body=body)
|
|
87
|
+
|
|
88
|
+
def send_list(self, **body: Any) -> Any:
|
|
89
|
+
return self.request("POST", "/api/sendList", body=body)
|
|
90
|
+
|
|
91
|
+
def send_contact_vcard(self, **body: Any) -> Any:
|
|
92
|
+
return self.request("POST", "/api/sendContactVcard", body=body)
|
|
93
|
+
|
|
94
|
+
def send_location(self, **body: Any) -> Any:
|
|
95
|
+
return self.request("POST", "/api/sendLocation", body=body)
|
|
96
|
+
|
|
97
|
+
def send_image(self, **body: Any) -> Any:
|
|
98
|
+
return self.request("POST", "/api/sendImage", body=body)
|
|
99
|
+
|
|
100
|
+
def send_voice(self, **body: Any) -> Any:
|
|
101
|
+
return self.request("POST", "/api/sendVoice", body=body)
|
|
102
|
+
|
|
103
|
+
def send_video(self, **body: Any) -> Any:
|
|
104
|
+
return self.request("POST", "/api/sendVideo", body=body)
|
|
105
|
+
|
|
106
|
+
def send_file(self, **body: Any) -> Any:
|
|
107
|
+
return self.request("POST", "/api/sendFile", body=body)
|
|
108
|
+
|
|
109
|
+
def send_event(self, body: Mapping[str, Any], session: str | None = None) -> Any:
|
|
110
|
+
return self.request("POST", f"/api/{_q(self._session(session))}/events", body=dict(body))
|
|
111
|
+
|
|
112
|
+
def get_messages(self, chat_id: str, *, session: str | None = None, **query: Any) -> Any:
|
|
113
|
+
return self.request("GET", f"/api/{_q(self._session(session))}/chats/{_q(chat_id)}/messages", query=query)
|
|
114
|
+
|
|
115
|
+
def edit_message(self, chat_id: str, message_id: str, body: Mapping[str, Any], *, session: str | None = None) -> Any:
|
|
116
|
+
return self.request("PUT", f"/api/{_q(self._session(session))}/chats/{_q(chat_id)}/messages/{_q(message_id)}", body=dict(body))
|
|
117
|
+
|
|
118
|
+
def delete_message(self, chat_id: str, message_id: str, *, session: str | None = None) -> Any:
|
|
119
|
+
return self.request("DELETE", f"/api/{_q(self._session(session))}/chats/{_q(chat_id)}/messages/{_q(message_id)}")
|
|
120
|
+
|
|
121
|
+
def list_groups(self, *, session: str | None = None) -> Any:
|
|
122
|
+
return self.request("GET", f"/api/{_q(self._session(session))}/groups")
|
|
123
|
+
|
|
124
|
+
def create_group(self, name: str, participants: list[str], *, session: str | None = None) -> Any:
|
|
125
|
+
return self.request("POST", f"/api/{_q(self._session(session))}/groups", body={"name": name, "participants": participants})
|
|
126
|
+
|
|
127
|
+
def list_group_participants(self, group_id: str, *, session: str | None = None) -> Any:
|
|
128
|
+
return self.request("GET", f"/api/{_q(self._session(session))}/groups/{_q(group_id)}/participants/v2")
|
|
129
|
+
|
|
130
|
+
def add_group_participants(self, group_id: str, participants: list[str], *, session: str | None = None) -> Any:
|
|
131
|
+
return self.request("POST", f"/api/{_q(self._session(session))}/groups/{_q(group_id)}/participants/add", body={"participants": participants})
|
|
132
|
+
|
|
133
|
+
def remove_group_participants(self, group_id: str, participants: list[str], *, session: str | None = None) -> Any:
|
|
134
|
+
return self.request("POST", f"/api/{_q(self._session(session))}/groups/{_q(group_id)}/participants/remove", body={"participants": participants})
|
|
135
|
+
|
|
136
|
+
def list_contacts(self, **query: Any) -> Any:
|
|
137
|
+
return self.request("GET", "/api/contacts/all", query=query)
|
|
138
|
+
|
|
139
|
+
def get_contact(self, **query: Any) -> Any:
|
|
140
|
+
return self.request("GET", "/api/contacts", query=query)
|
|
141
|
+
|
|
142
|
+
def update_contact(self, chat_id: str, body: Mapping[str, Any], *, session: str | None = None) -> Any:
|
|
143
|
+
return self.request("PUT", f"/api/{_q(self._session(session))}/contacts/{_q(chat_id)}", body=dict(body))
|
|
144
|
+
|
|
145
|
+
def check_contact_exists(self, **query: Any) -> Any:
|
|
146
|
+
return self.request("GET", "/api/contacts/check-exists", query=query)
|
|
147
|
+
|
|
148
|
+
def get_contact_about(self, **query: Any) -> Any:
|
|
149
|
+
return self.request("GET", "/api/contacts/about", query=query)
|
|
150
|
+
|
|
151
|
+
def get_contact_profile_picture(self, **query: Any) -> Any:
|
|
152
|
+
return self.request("GET", "/api/contacts/profile-picture", query=query)
|
|
153
|
+
|
|
154
|
+
def block_contact(self, contact_id: str) -> Any:
|
|
155
|
+
return self.request("POST", "/api/contacts/block", body={"contactId": contact_id})
|
|
156
|
+
|
|
157
|
+
def unblock_contact(self, contact_id: str) -> Any:
|
|
158
|
+
return self.request("POST", "/api/contacts/unblock", body={"contactId": contact_id})
|
|
159
|
+
|
|
160
|
+
def list_labels(self, *, session: str | None = None) -> Any:
|
|
161
|
+
return self.request("GET", f"/api/{_q(self._session(session))}/labels")
|
|
162
|
+
|
|
163
|
+
def create_label(self, name: str, color: int | None = None, *, session: str | None = None) -> Any:
|
|
164
|
+
return self.request("POST", f"/api/{_q(self._session(session))}/labels", body={"name": name, "color": color})
|
|
165
|
+
|
|
166
|
+
def update_label(self, label_id: str, body: Mapping[str, Any], *, session: str | None = None) -> Any:
|
|
167
|
+
return self.request("PUT", f"/api/{_q(self._session(session))}/labels/{_q(label_id)}", body=dict(body))
|
|
168
|
+
|
|
169
|
+
def delete_label(self, label_id: str, *, session: str | None = None) -> Any:
|
|
170
|
+
return self.request("DELETE", f"/api/{_q(self._session(session))}/labels/{_q(label_id)}")
|
|
171
|
+
|
|
172
|
+
def list_lids(self, *, session: str | None = None) -> Any:
|
|
173
|
+
return self.request("GET", f"/api/{_q(self._session(session))}/lids")
|
|
174
|
+
|
|
175
|
+
def count_lids(self, *, session: str | None = None) -> Any:
|
|
176
|
+
return self.request("GET", f"/api/{_q(self._session(session))}/lids/count")
|
|
177
|
+
|
|
178
|
+
def resolve_lid(self, lid: str, *, session: str | None = None) -> Any:
|
|
179
|
+
return self.request("GET", f"/api/{_q(self._session(session))}/lids/{_q(lid)}")
|
|
180
|
+
|
|
181
|
+
def resolve_phone_number_to_lid(self, phone_number: str, *, session: str | None = None) -> Any:
|
|
182
|
+
return self.request("GET", f"/api/{_q(self._session(session))}/lids/pn/{_q(phone_number)}")
|
|
183
|
+
|
|
184
|
+
def list_channels(self, *, session: str | None = None) -> Any:
|
|
185
|
+
return self.request("GET", f"/api/{_q(self._session(session))}/channels")
|
|
186
|
+
|
|
187
|
+
def create_channel(self, body: Mapping[str, Any], *, session: str | None = None) -> Any:
|
|
188
|
+
return self.request("POST", f"/api/{_q(self._session(session))}/channels", body=dict(body))
|
|
189
|
+
|
|
190
|
+
def get_channel(self, channel_id: str, *, session: str | None = None) -> Any:
|
|
191
|
+
return self.request("GET", f"/api/{_q(self._session(session))}/channels/{_q(channel_id)}")
|
|
192
|
+
|
|
193
|
+
def delete_channel(self, channel_id: str, *, session: str | None = None) -> Any:
|
|
194
|
+
return self.request("DELETE", f"/api/{_q(self._session(session))}/channels/{_q(channel_id)}")
|
|
195
|
+
|
|
196
|
+
def list_sessions(self, **query: Any) -> Any:
|
|
197
|
+
return self.request("GET", "/admin/sessions", query=query)
|
|
198
|
+
|
|
199
|
+
def create_session(self, body: Mapping[str, Any]) -> Any:
|
|
200
|
+
return self.request("POST", "/admin/sessions", body=dict(body))
|
|
201
|
+
|
|
202
|
+
def start_session(self, name: str) -> Any:
|
|
203
|
+
return self.request("POST", f"/admin/sessions/{_q(name)}/start")
|
|
204
|
+
|
|
205
|
+
def stop_session(self, name: str) -> Any:
|
|
206
|
+
return self.request("POST", f"/admin/sessions/{_q(name)}/stop")
|
|
207
|
+
|
|
208
|
+
def restart_session(self, name: str) -> Any:
|
|
209
|
+
return self.request("POST", f"/admin/sessions/{_q(name)}/restart")
|
|
210
|
+
|
|
211
|
+
def _session(self, session: str | None) -> str:
|
|
212
|
+
selected = session or self.session
|
|
213
|
+
if not selected:
|
|
214
|
+
raise ValueError("This method requires a session. Pass session to the client or method.")
|
|
215
|
+
return selected
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def _decode_response(data: bytes, content_type: str) -> Any:
|
|
219
|
+
if not data:
|
|
220
|
+
return None
|
|
221
|
+
text = data.decode("utf-8")
|
|
222
|
+
if "application/json" in content_type:
|
|
223
|
+
return json.loads(text)
|
|
224
|
+
return text
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def _q(value: str) -> str:
|
|
228
|
+
return quote(value, safe="")
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def _read_upload_file(file: str | os.PathLike[str] | BinaryIO, filename: str | None) -> tuple[str, bytes]:
|
|
232
|
+
if isinstance(file, (str, os.PathLike)):
|
|
233
|
+
path = Path(file)
|
|
234
|
+
return filename or path.name, path.read_bytes()
|
|
235
|
+
return filename or getattr(file, "name", "upload.bin"), file.read()
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def _multipart_body(boundary: str, field: str, filename: str, content_type: str, data: bytes) -> bytes:
|
|
239
|
+
return b"".join(
|
|
240
|
+
[
|
|
241
|
+
f"--{boundary}\r\n".encode(),
|
|
242
|
+
f'Content-Disposition: form-data; name="{field}"; filename="{filename}"\r\n'.encode(),
|
|
243
|
+
f"Content-Type: {content_type}\r\n\r\n".encode(),
|
|
244
|
+
data,
|
|
245
|
+
f"\r\n--{boundary}--\r\n".encode(),
|
|
246
|
+
]
|
|
247
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: waappa-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official Python SDK for the Waappa API.
|
|
5
|
+
Author: Waappa
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: waappa,whatsapp,waha,sdk
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Requires-Python: >=3.9
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Dynamic: license-file
|
|
20
|
+
|
|
21
|
+
# Waappa Python SDK
|
|
22
|
+
|
|
23
|
+
Official Python SDK for the Waappa API.
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pip install waappa-sdk
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Requires Python 3.9 or newer.
|
|
32
|
+
|
|
33
|
+
## Quick Start
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
from waappa import WaappaClient
|
|
37
|
+
|
|
38
|
+
waappa = WaappaClient(
|
|
39
|
+
api_key="waappa_session_api_key",
|
|
40
|
+
session="default",
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
waappa.send_text(
|
|
44
|
+
chatId="919876543210@c.us",
|
|
45
|
+
text="Hello from Waappa",
|
|
46
|
+
)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The SDK sends the API key as the `Authorization` header. By default it uses:
|
|
50
|
+
|
|
51
|
+
```text
|
|
52
|
+
https://api.waappa.com
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Use `base_url` for local or private deployments:
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
waappa = WaappaClient(
|
|
59
|
+
api_key="waappa_session_api_key",
|
|
60
|
+
session="default",
|
|
61
|
+
base_url="http://localhost:3001",
|
|
62
|
+
)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Messages
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
waappa.send_image(
|
|
69
|
+
chatId="919876543210@c.us",
|
|
70
|
+
caption="Invoice",
|
|
71
|
+
file={
|
|
72
|
+
"mimetype": "image/jpeg",
|
|
73
|
+
"filename": "invoice.jpg",
|
|
74
|
+
"url": "https://example.com/invoice.jpg",
|
|
75
|
+
},
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
waappa.send_poll(
|
|
79
|
+
chatId="919876543210@c.us",
|
|
80
|
+
poll={
|
|
81
|
+
"name": "Choose one",
|
|
82
|
+
"options": ["A", "B"],
|
|
83
|
+
"multipleAnswers": False,
|
|
84
|
+
},
|
|
85
|
+
)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Supported message helpers:
|
|
89
|
+
|
|
90
|
+
- `send_text`
|
|
91
|
+
- `send_image`
|
|
92
|
+
- `send_voice`
|
|
93
|
+
- `send_video`
|
|
94
|
+
- `send_file`
|
|
95
|
+
- `send_poll`
|
|
96
|
+
- `send_list`
|
|
97
|
+
- `send_contact_vcard`
|
|
98
|
+
- `send_location`
|
|
99
|
+
- `send_event`
|
|
100
|
+
- `edit_message`
|
|
101
|
+
- `delete_message`
|
|
102
|
+
- `get_messages`
|
|
103
|
+
|
|
104
|
+
## Media Upload
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
uploaded = waappa.upload("invoice.jpg")
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Use the returned URL in `send_image`, `send_video`, `send_voice`, or `send_file`.
|
|
111
|
+
|
|
112
|
+
## Groups, Contacts, Labels, LIDs, Channels
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
waappa.create_group("Support", ["919876543210@c.us"])
|
|
116
|
+
|
|
117
|
+
contacts = waappa.list_contacts()
|
|
118
|
+
labels = waappa.list_labels()
|
|
119
|
+
channels = waappa.list_channels()
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
The client includes helpers for the selected Waappa API surface: groups, group participants, contacts, labels, LID/phone resolution, and channels.
|
|
123
|
+
|
|
124
|
+
## Admin / Session Management
|
|
125
|
+
|
|
126
|
+
Session management methods require a master key:
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
admin = WaappaClient(api_key="mk_live_...")
|
|
130
|
+
sessions = admin.list_sessions()
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Errors
|
|
134
|
+
|
|
135
|
+
Failed API responses raise `WaappaError`:
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
from waappa import WaappaError
|
|
139
|
+
|
|
140
|
+
try:
|
|
141
|
+
waappa.send_text(chatId="bad", text="Hello")
|
|
142
|
+
except WaappaError as error:
|
|
143
|
+
print(error.status)
|
|
144
|
+
print(error.data)
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Development
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
python -m compileall waappa
|
|
151
|
+
python -m pip wheel . --no-deps -w dist
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Publishing
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
python -m build
|
|
158
|
+
python -m twine upload dist/*
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Before publishing, run a smoke test against a real Waappa API key and session.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
waappa
|