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.
@@ -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 }}
@@ -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
@@ -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"