bzapper 0.2.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.
- bzapper-0.2.0/.github/workflows/publish.yml +21 -0
- bzapper-0.2.0/.gitignore +46 -0
- bzapper-0.2.0/LICENSE +21 -0
- bzapper-0.2.0/PKG-INFO +243 -0
- bzapper-0.2.0/README.md +218 -0
- bzapper-0.2.0/bzapper/__init__.py +13 -0
- bzapper-0.2.0/bzapper/client.py +948 -0
- bzapper-0.2.0/bzapper/errors.py +39 -0
- bzapper-0.2.0/bzapper/py.typed +0 -0
- bzapper-0.2.0/examples/quickstart.py +109 -0
- bzapper-0.2.0/pyproject.toml +35 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Publica o pacote `bzapper` no PyPI ao dar push de uma tag vX.Y.Z no repo público
|
|
2
|
+
# bernisoftware/bzapper-python. Requer secret PYPI_TOKEN (token de projeto do PyPI).
|
|
3
|
+
name: publish
|
|
4
|
+
on:
|
|
5
|
+
push:
|
|
6
|
+
tags: ['v*']
|
|
7
|
+
jobs:
|
|
8
|
+
pypi:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
steps:
|
|
11
|
+
- uses: actions/checkout@v4
|
|
12
|
+
- uses: actions/setup-python@v5
|
|
13
|
+
with:
|
|
14
|
+
python-version: '3.x'
|
|
15
|
+
- run: pip install build twine
|
|
16
|
+
- run: python -m build
|
|
17
|
+
- run: twine check dist/*
|
|
18
|
+
- run: twine upload dist/*
|
|
19
|
+
env:
|
|
20
|
+
TWINE_USERNAME: __token__
|
|
21
|
+
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
|
bzapper-0.2.0/.gitignore
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# dependencies
|
|
2
|
+
node_modules/
|
|
3
|
+
.pnpm-store/
|
|
4
|
+
|
|
5
|
+
# build outputs
|
|
6
|
+
dist/
|
|
7
|
+
build/
|
|
8
|
+
.output/
|
|
9
|
+
.turbo/
|
|
10
|
+
**/.docusaurus/
|
|
11
|
+
**/.cache/
|
|
12
|
+
|
|
13
|
+
# go
|
|
14
|
+
apps/api/bin/
|
|
15
|
+
apps/api/tmp/
|
|
16
|
+
|
|
17
|
+
# env / secrets (NUNCA commitar segredos)
|
|
18
|
+
.env
|
|
19
|
+
.env.*
|
|
20
|
+
!.env.example
|
|
21
|
+
|
|
22
|
+
# editor / os
|
|
23
|
+
.DS_Store
|
|
24
|
+
.idea/
|
|
25
|
+
.vscode/*
|
|
26
|
+
!.vscode/extensions.json
|
|
27
|
+
*.log
|
|
28
|
+
|
|
29
|
+
# generated SDK (regenerado do OpenAPI no build)
|
|
30
|
+
packages/sdk/src/generated/
|
|
31
|
+
|
|
32
|
+
# generated brand tokens (regenerado de tokens/source no build)
|
|
33
|
+
packages/brand/dist/
|
|
34
|
+
|
|
35
|
+
# assets de marca sincronizados nos apps (fonte única em packages/brand)
|
|
36
|
+
apps/*/public/logo/
|
|
37
|
+
apps/*/public/icons/
|
|
38
|
+
apps/*/public/site.webmanifest
|
|
39
|
+
apps/*/static/logo/
|
|
40
|
+
apps/*/static/icons/
|
|
41
|
+
apps/*/static/site.webmanifest
|
|
42
|
+
|
|
43
|
+
# test
|
|
44
|
+
coverage/
|
|
45
|
+
.do/.secrets-prod.local
|
|
46
|
+
*.local
|
bzapper-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Berni Software
|
|
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.
|
bzapper-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: bzapper
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Official Python SDK for the bZapper WhatsApp gateway API.
|
|
5
|
+
Project-URL: Homepage, https://bzapper.com.br
|
|
6
|
+
Project-URL: Repository, https://github.com/bernisoftware/bzapper
|
|
7
|
+
Author-email: Berni Software <vinicius@berni.com.br>
|
|
8
|
+
License: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: api,bzapper,gateway,messaging,sdk,whatsapp
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Communications :: Chat
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
|
+
Requires-Python: >=3.9
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
# bzapper
|
|
27
|
+
|
|
28
|
+
Official **Python SDK** for the [bZapper](https://bzapper.com.br) WhatsApp gateway API — a multi-tenant WhatsApp gateway: connect numbers, send and receive messages, rotate numbers (anti-ban) and track usage.
|
|
29
|
+
|
|
30
|
+
Zero runtime dependencies (pure standard library). Python 3.9+.
|
|
31
|
+
|
|
32
|
+
## Install
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install bzapper
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Hello world
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from bzapper import Client
|
|
42
|
+
|
|
43
|
+
client = Client("http://localhost:8080", "bz_live_...")
|
|
44
|
+
client.send_text("+5511999999999", "Hello from bZapper!")
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Client configuration
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from bzapper import Client
|
|
51
|
+
|
|
52
|
+
client = Client(
|
|
53
|
+
base_url="https://api.bzapper.com.br", # http://localhost:8080 in dev
|
|
54
|
+
api_key="bz_live_...", # tenant API key
|
|
55
|
+
locale="pt-BR", # optional, sets Accept-Language
|
|
56
|
+
timeout=30, # optional, seconds
|
|
57
|
+
)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Every request sends `Authorization: Bearer <api_key>`, `Content-Type: application/json` and, when `locale` is set, `Accept-Language: <locale>`.
|
|
61
|
+
|
|
62
|
+
## Messages
|
|
63
|
+
|
|
64
|
+
Every message method accepts the common **SendBase** options as keyword
|
|
65
|
+
arguments: `instance_id`, `pool_id`, `quoted_message_id`, `client_reference`
|
|
66
|
+
and `mentions`. Each returns the queued-message object
|
|
67
|
+
(`message_id`, `status`, optional `client_reference`).
|
|
68
|
+
|
|
69
|
+
`to` is a phone in E.164 (`+5511999999999`) or a JID.
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
# Text
|
|
73
|
+
client.send_text("+5511999999999", "Hello!")
|
|
74
|
+
|
|
75
|
+
# Image (use url OR base64, never both)
|
|
76
|
+
client.send_image("+5511999999999", {"url": "https://picsum.photos/600", "caption": "Hi"})
|
|
77
|
+
|
|
78
|
+
# Video
|
|
79
|
+
client.send_video("+5511999999999", {"url": "https://example.com/clip.mp4"})
|
|
80
|
+
|
|
81
|
+
# Document
|
|
82
|
+
client.send_document("+5511999999999", {"url": "https://example.com/file.pdf", "filename": "file.pdf"})
|
|
83
|
+
|
|
84
|
+
# Audio — set ptt=True for a voice note
|
|
85
|
+
client.send_audio("+5511999999999", {"url": "https://example.com/note.ogg", "ptt": True})
|
|
86
|
+
|
|
87
|
+
# Sticker
|
|
88
|
+
client.send_sticker("+5511999999999", {"url": "https://example.com/sticker.webp"})
|
|
89
|
+
|
|
90
|
+
# Location
|
|
91
|
+
client.send_location("+5511999999999", -23.5613, -46.6565, name="Av. Paulista", address="São Paulo")
|
|
92
|
+
|
|
93
|
+
# Contact
|
|
94
|
+
client.send_contact("+5511999999999", contact_name="Berni Software")
|
|
95
|
+
|
|
96
|
+
# Poll
|
|
97
|
+
client.send_poll("+5511999999999", "Pizza or sushi?", ["Pizza", "Sushi"], selectable_count=1)
|
|
98
|
+
|
|
99
|
+
# Reaction (quoted_message_id is the wa_message_id; empty emoji removes it)
|
|
100
|
+
client.send_reaction("+5511999999999", quoted_message_id="ABCD1234", emoji="👍")
|
|
101
|
+
|
|
102
|
+
# Buttons
|
|
103
|
+
client.send_buttons(
|
|
104
|
+
"+5511999999999",
|
|
105
|
+
"Choose an option:",
|
|
106
|
+
[{"id": "a", "title": "Option A"}, {"id": "b", "title": "Option B"}],
|
|
107
|
+
footer="Powered by bZapper",
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
# List
|
|
111
|
+
client.send_list(
|
|
112
|
+
"+5511999999999",
|
|
113
|
+
"Pick from the menu:",
|
|
114
|
+
[{
|
|
115
|
+
"title": "Drinks",
|
|
116
|
+
"rows": [
|
|
117
|
+
{"id": "1", "title": "Coffee", "description": "Hot"},
|
|
118
|
+
{"id": "2", "title": "Tea"},
|
|
119
|
+
],
|
|
120
|
+
}],
|
|
121
|
+
button_text="Open menu",
|
|
122
|
+
)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### MediaInput
|
|
126
|
+
|
|
127
|
+
The `media` argument is a dict: `{"url"?, "base64"?, "caption"?, "filename"?, "mimetype"?, "ptt"?}`. Use **`url` OR `base64`, never both**.
|
|
128
|
+
|
|
129
|
+
### Caveat: buttons & lists
|
|
130
|
+
|
|
131
|
+
Buttons and lists are **not reliable** on WhatsApp (worse in groups). The API
|
|
132
|
+
**always** also sends an equivalent **numbered text menu** as a fallback. Design
|
|
133
|
+
your flows so the numbered menu alone is enough.
|
|
134
|
+
|
|
135
|
+
## Instances (numbers)
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
client.list_instances()
|
|
139
|
+
inst = client.create_instance("+5511999999999", nickname="Support", proxy_url=None)
|
|
140
|
+
client.get_instance(inst["id"])
|
|
141
|
+
|
|
142
|
+
# Connect via QR (default) or pairing code
|
|
143
|
+
res = client.connect_instance(inst["id"], method="qr") # -> {"status", "qr_code"?}
|
|
144
|
+
res = client.connect_instance(inst["id"], method="code") # -> {"status", "pair_code"?}
|
|
145
|
+
|
|
146
|
+
client.disconnect_instance(inst["id"])
|
|
147
|
+
|
|
148
|
+
# Update the WhatsApp profile (display name / about / picture)
|
|
149
|
+
client.set_profile(inst["id"], display_name="Support", status_message="We reply fast")
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Groups, presence and conversations
|
|
153
|
+
|
|
154
|
+
For these advanced calls `instance_id` is **required**. It travels in the query
|
|
155
|
+
string for groups/conversations and in the body for presence/chats/contacts —
|
|
156
|
+
the SDK handles that for you, you just pass it as an argument. `jid` is the
|
|
157
|
+
group/chat JID.
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
inst_id = "01J..." # an instance id
|
|
161
|
+
|
|
162
|
+
# Presence — works in groups too! Use the group JID as `to`.
|
|
163
|
+
client.presence_chat(inst_id, "+5511999999999", "typing")
|
|
164
|
+
client.presence_chat(inst_id, "12036304@g.us", "typing") # group presence
|
|
165
|
+
client.presence_chat(inst_id, "12036304@g.us", "paused")
|
|
166
|
+
|
|
167
|
+
# Conversations
|
|
168
|
+
client.list_conversations(inst_id)
|
|
169
|
+
client.conversation_history(
|
|
170
|
+
"12036304@g.us", inst_id, before="2026-06-01T00:00:00Z", limit=50 # limit ≤ 200
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
# Chats — archive / pin / mark read (on=True) or undo (on=False)
|
|
174
|
+
client.archive_chat("12036304@g.us", inst_id, on=True)
|
|
175
|
+
client.pin_chat("12036304@g.us", inst_id, on=True)
|
|
176
|
+
client.mark_chat("12036304@g.us", inst_id, on=True)
|
|
177
|
+
|
|
178
|
+
# Groups
|
|
179
|
+
client.list_groups(inst_id)
|
|
180
|
+
group = client.create_group(inst_id, "My group", ["+5511999999999", "+5511888888888"])
|
|
181
|
+
client.get_group(group["jid"], inst_id)
|
|
182
|
+
client.update_group_participants(
|
|
183
|
+
group["jid"], inst_id, "add", ["+5511777777777"] # add|remove|promote|demote
|
|
184
|
+
)
|
|
185
|
+
client.group_invite(group["jid"], inst_id) # -> invite link/code
|
|
186
|
+
client.join_group(inst_id, "Cabc123InviteCode") # join via invite code
|
|
187
|
+
client.leave_group(group["jid"], inst_id)
|
|
188
|
+
|
|
189
|
+
# Contacts — which numbers are on WhatsApp?
|
|
190
|
+
client.contacts_check(inst_id, ["+5511999999999", "+5511888888888"])
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Realtime (SSE)
|
|
194
|
+
|
|
195
|
+
The API exposes a server-sent-events stream at `GET /stream` for inbound
|
|
196
|
+
messages and status updates. It is not wrapped by this SDK — connect with any
|
|
197
|
+
SSE client, sending the same `Authorization: Bearer <api_key>` header.
|
|
198
|
+
|
|
199
|
+
## API keys
|
|
200
|
+
|
|
201
|
+
```python
|
|
202
|
+
client.list_keys()
|
|
203
|
+
created = client.create_key("CI key", role="agent") # role: "admin" | "agent"
|
|
204
|
+
print(created["api_key"]) # raw key — shown only once, store it now
|
|
205
|
+
client.revoke_key(created["key"]["id"])
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
## Usage
|
|
209
|
+
|
|
210
|
+
```python
|
|
211
|
+
client.get_usage() # whole period
|
|
212
|
+
client.get_usage(from_="2026-06-01T00:00:00Z", to="2026-06-30T23:59:59Z") # RFC3339
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## Error handling
|
|
216
|
+
|
|
217
|
+
Non-2xx responses raise `BzapperError` with a **stable `code`**, a localized
|
|
218
|
+
`message` and the `status_code`. Always branch on `code` — never parse the
|
|
219
|
+
human-readable `message`.
|
|
220
|
+
|
|
221
|
+
```python
|
|
222
|
+
from bzapper import BzapperError
|
|
223
|
+
|
|
224
|
+
try:
|
|
225
|
+
client.send_text("+5511999999999", "Hi")
|
|
226
|
+
except BzapperError as err:
|
|
227
|
+
if err.code == "instance_not_connected":
|
|
228
|
+
# reconnect flow...
|
|
229
|
+
...
|
|
230
|
+
elif err.code == "rate_limited":
|
|
231
|
+
# back off...
|
|
232
|
+
...
|
|
233
|
+
else:
|
|
234
|
+
print(err.code, err.status_code, err.message)
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## Example
|
|
238
|
+
|
|
239
|
+
A runnable script is in [`examples/quickstart.py`](examples/quickstart.py).
|
|
240
|
+
|
|
241
|
+
## License
|
|
242
|
+
|
|
243
|
+
MIT © Berni Software
|
bzapper-0.2.0/README.md
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
# bzapper
|
|
2
|
+
|
|
3
|
+
Official **Python SDK** for the [bZapper](https://bzapper.com.br) WhatsApp gateway API — a multi-tenant WhatsApp gateway: connect numbers, send and receive messages, rotate numbers (anti-ban) and track usage.
|
|
4
|
+
|
|
5
|
+
Zero runtime dependencies (pure standard library). Python 3.9+.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install bzapper
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Hello world
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
from bzapper import Client
|
|
17
|
+
|
|
18
|
+
client = Client("http://localhost:8080", "bz_live_...")
|
|
19
|
+
client.send_text("+5511999999999", "Hello from bZapper!")
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Client configuration
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
from bzapper import Client
|
|
26
|
+
|
|
27
|
+
client = Client(
|
|
28
|
+
base_url="https://api.bzapper.com.br", # http://localhost:8080 in dev
|
|
29
|
+
api_key="bz_live_...", # tenant API key
|
|
30
|
+
locale="pt-BR", # optional, sets Accept-Language
|
|
31
|
+
timeout=30, # optional, seconds
|
|
32
|
+
)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Every request sends `Authorization: Bearer <api_key>`, `Content-Type: application/json` and, when `locale` is set, `Accept-Language: <locale>`.
|
|
36
|
+
|
|
37
|
+
## Messages
|
|
38
|
+
|
|
39
|
+
Every message method accepts the common **SendBase** options as keyword
|
|
40
|
+
arguments: `instance_id`, `pool_id`, `quoted_message_id`, `client_reference`
|
|
41
|
+
and `mentions`. Each returns the queued-message object
|
|
42
|
+
(`message_id`, `status`, optional `client_reference`).
|
|
43
|
+
|
|
44
|
+
`to` is a phone in E.164 (`+5511999999999`) or a JID.
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
# Text
|
|
48
|
+
client.send_text("+5511999999999", "Hello!")
|
|
49
|
+
|
|
50
|
+
# Image (use url OR base64, never both)
|
|
51
|
+
client.send_image("+5511999999999", {"url": "https://picsum.photos/600", "caption": "Hi"})
|
|
52
|
+
|
|
53
|
+
# Video
|
|
54
|
+
client.send_video("+5511999999999", {"url": "https://example.com/clip.mp4"})
|
|
55
|
+
|
|
56
|
+
# Document
|
|
57
|
+
client.send_document("+5511999999999", {"url": "https://example.com/file.pdf", "filename": "file.pdf"})
|
|
58
|
+
|
|
59
|
+
# Audio — set ptt=True for a voice note
|
|
60
|
+
client.send_audio("+5511999999999", {"url": "https://example.com/note.ogg", "ptt": True})
|
|
61
|
+
|
|
62
|
+
# Sticker
|
|
63
|
+
client.send_sticker("+5511999999999", {"url": "https://example.com/sticker.webp"})
|
|
64
|
+
|
|
65
|
+
# Location
|
|
66
|
+
client.send_location("+5511999999999", -23.5613, -46.6565, name="Av. Paulista", address="São Paulo")
|
|
67
|
+
|
|
68
|
+
# Contact
|
|
69
|
+
client.send_contact("+5511999999999", contact_name="Berni Software")
|
|
70
|
+
|
|
71
|
+
# Poll
|
|
72
|
+
client.send_poll("+5511999999999", "Pizza or sushi?", ["Pizza", "Sushi"], selectable_count=1)
|
|
73
|
+
|
|
74
|
+
# Reaction (quoted_message_id is the wa_message_id; empty emoji removes it)
|
|
75
|
+
client.send_reaction("+5511999999999", quoted_message_id="ABCD1234", emoji="👍")
|
|
76
|
+
|
|
77
|
+
# Buttons
|
|
78
|
+
client.send_buttons(
|
|
79
|
+
"+5511999999999",
|
|
80
|
+
"Choose an option:",
|
|
81
|
+
[{"id": "a", "title": "Option A"}, {"id": "b", "title": "Option B"}],
|
|
82
|
+
footer="Powered by bZapper",
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
# List
|
|
86
|
+
client.send_list(
|
|
87
|
+
"+5511999999999",
|
|
88
|
+
"Pick from the menu:",
|
|
89
|
+
[{
|
|
90
|
+
"title": "Drinks",
|
|
91
|
+
"rows": [
|
|
92
|
+
{"id": "1", "title": "Coffee", "description": "Hot"},
|
|
93
|
+
{"id": "2", "title": "Tea"},
|
|
94
|
+
],
|
|
95
|
+
}],
|
|
96
|
+
button_text="Open menu",
|
|
97
|
+
)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### MediaInput
|
|
101
|
+
|
|
102
|
+
The `media` argument is a dict: `{"url"?, "base64"?, "caption"?, "filename"?, "mimetype"?, "ptt"?}`. Use **`url` OR `base64`, never both**.
|
|
103
|
+
|
|
104
|
+
### Caveat: buttons & lists
|
|
105
|
+
|
|
106
|
+
Buttons and lists are **not reliable** on WhatsApp (worse in groups). The API
|
|
107
|
+
**always** also sends an equivalent **numbered text menu** as a fallback. Design
|
|
108
|
+
your flows so the numbered menu alone is enough.
|
|
109
|
+
|
|
110
|
+
## Instances (numbers)
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
client.list_instances()
|
|
114
|
+
inst = client.create_instance("+5511999999999", nickname="Support", proxy_url=None)
|
|
115
|
+
client.get_instance(inst["id"])
|
|
116
|
+
|
|
117
|
+
# Connect via QR (default) or pairing code
|
|
118
|
+
res = client.connect_instance(inst["id"], method="qr") # -> {"status", "qr_code"?}
|
|
119
|
+
res = client.connect_instance(inst["id"], method="code") # -> {"status", "pair_code"?}
|
|
120
|
+
|
|
121
|
+
client.disconnect_instance(inst["id"])
|
|
122
|
+
|
|
123
|
+
# Update the WhatsApp profile (display name / about / picture)
|
|
124
|
+
client.set_profile(inst["id"], display_name="Support", status_message="We reply fast")
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Groups, presence and conversations
|
|
128
|
+
|
|
129
|
+
For these advanced calls `instance_id` is **required**. It travels in the query
|
|
130
|
+
string for groups/conversations and in the body for presence/chats/contacts —
|
|
131
|
+
the SDK handles that for you, you just pass it as an argument. `jid` is the
|
|
132
|
+
group/chat JID.
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
inst_id = "01J..." # an instance id
|
|
136
|
+
|
|
137
|
+
# Presence — works in groups too! Use the group JID as `to`.
|
|
138
|
+
client.presence_chat(inst_id, "+5511999999999", "typing")
|
|
139
|
+
client.presence_chat(inst_id, "12036304@g.us", "typing") # group presence
|
|
140
|
+
client.presence_chat(inst_id, "12036304@g.us", "paused")
|
|
141
|
+
|
|
142
|
+
# Conversations
|
|
143
|
+
client.list_conversations(inst_id)
|
|
144
|
+
client.conversation_history(
|
|
145
|
+
"12036304@g.us", inst_id, before="2026-06-01T00:00:00Z", limit=50 # limit ≤ 200
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
# Chats — archive / pin / mark read (on=True) or undo (on=False)
|
|
149
|
+
client.archive_chat("12036304@g.us", inst_id, on=True)
|
|
150
|
+
client.pin_chat("12036304@g.us", inst_id, on=True)
|
|
151
|
+
client.mark_chat("12036304@g.us", inst_id, on=True)
|
|
152
|
+
|
|
153
|
+
# Groups
|
|
154
|
+
client.list_groups(inst_id)
|
|
155
|
+
group = client.create_group(inst_id, "My group", ["+5511999999999", "+5511888888888"])
|
|
156
|
+
client.get_group(group["jid"], inst_id)
|
|
157
|
+
client.update_group_participants(
|
|
158
|
+
group["jid"], inst_id, "add", ["+5511777777777"] # add|remove|promote|demote
|
|
159
|
+
)
|
|
160
|
+
client.group_invite(group["jid"], inst_id) # -> invite link/code
|
|
161
|
+
client.join_group(inst_id, "Cabc123InviteCode") # join via invite code
|
|
162
|
+
client.leave_group(group["jid"], inst_id)
|
|
163
|
+
|
|
164
|
+
# Contacts — which numbers are on WhatsApp?
|
|
165
|
+
client.contacts_check(inst_id, ["+5511999999999", "+5511888888888"])
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Realtime (SSE)
|
|
169
|
+
|
|
170
|
+
The API exposes a server-sent-events stream at `GET /stream` for inbound
|
|
171
|
+
messages and status updates. It is not wrapped by this SDK — connect with any
|
|
172
|
+
SSE client, sending the same `Authorization: Bearer <api_key>` header.
|
|
173
|
+
|
|
174
|
+
## API keys
|
|
175
|
+
|
|
176
|
+
```python
|
|
177
|
+
client.list_keys()
|
|
178
|
+
created = client.create_key("CI key", role="agent") # role: "admin" | "agent"
|
|
179
|
+
print(created["api_key"]) # raw key — shown only once, store it now
|
|
180
|
+
client.revoke_key(created["key"]["id"])
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Usage
|
|
184
|
+
|
|
185
|
+
```python
|
|
186
|
+
client.get_usage() # whole period
|
|
187
|
+
client.get_usage(from_="2026-06-01T00:00:00Z", to="2026-06-30T23:59:59Z") # RFC3339
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Error handling
|
|
191
|
+
|
|
192
|
+
Non-2xx responses raise `BzapperError` with a **stable `code`**, a localized
|
|
193
|
+
`message` and the `status_code`. Always branch on `code` — never parse the
|
|
194
|
+
human-readable `message`.
|
|
195
|
+
|
|
196
|
+
```python
|
|
197
|
+
from bzapper import BzapperError
|
|
198
|
+
|
|
199
|
+
try:
|
|
200
|
+
client.send_text("+5511999999999", "Hi")
|
|
201
|
+
except BzapperError as err:
|
|
202
|
+
if err.code == "instance_not_connected":
|
|
203
|
+
# reconnect flow...
|
|
204
|
+
...
|
|
205
|
+
elif err.code == "rate_limited":
|
|
206
|
+
# back off...
|
|
207
|
+
...
|
|
208
|
+
else:
|
|
209
|
+
print(err.code, err.status_code, err.message)
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## Example
|
|
213
|
+
|
|
214
|
+
A runnable script is in [`examples/quickstart.py`](examples/quickstart.py).
|
|
215
|
+
|
|
216
|
+
## License
|
|
217
|
+
|
|
218
|
+
MIT © Berni Software
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""bZapper — official Python SDK for the bZapper WhatsApp gateway API.
|
|
2
|
+
|
|
3
|
+
Quickstart:
|
|
4
|
+
>>> from bzapper import Client
|
|
5
|
+
>>> client = Client("http://localhost:8080", "bz_live_...")
|
|
6
|
+
>>> client.send_text("+5511999999999", "Hello from bZapper!")
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from .client import Client
|
|
10
|
+
from .errors import BzapperError
|
|
11
|
+
|
|
12
|
+
__all__ = ["Client", "BzapperError"]
|
|
13
|
+
__version__ = "0.2.0"
|