kappelas-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.
- kappelas_sdk-0.1.0/.gitignore +12 -0
- kappelas_sdk-0.1.0/PKG-INFO +490 -0
- kappelas_sdk-0.1.0/README.md +467 -0
- kappelas_sdk-0.1.0/pyproject.toml +35 -0
- kappelas_sdk-0.1.0/src/kappelas/__init__.py +111 -0
- kappelas_sdk-0.1.0/src/kappelas/_emitter.py +136 -0
- kappelas_sdk-0.1.0/src/kappelas/_http.py +170 -0
- kappelas_sdk-0.1.0/src/kappelas/_parsers.py +266 -0
- kappelas_sdk-0.1.0/src/kappelas/_ws.py +179 -0
- kappelas_sdk-0.1.0/src/kappelas/bot.py +219 -0
- kappelas_sdk-0.1.0/src/kappelas/errors.py +142 -0
- kappelas_sdk-0.1.0/src/kappelas/resources/__init__.py +11 -0
- kappelas_sdk-0.1.0/src/kappelas/resources/chats.py +55 -0
- kappelas_sdk-0.1.0/src/kappelas/resources/messages.py +237 -0
- kappelas_sdk-0.1.0/src/kappelas/resources/profile.py +23 -0
- kappelas_sdk-0.1.0/src/kappelas/resources/webhooks.py +46 -0
- kappelas_sdk-0.1.0/src/kappelas/types.py +259 -0
- kappelas_sdk-0.1.0/src/kappelas/user.py +181 -0
- kappelas_sdk-0.1.0/test_all.py +196 -0
- kappelas_sdk-0.1.0/test_live.py +41 -0
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: kappelas-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official Kappela SDK for Python — build bots and personal automations
|
|
5
|
+
Project-URL: Homepage, https://github.com/Arnel7/kappelas-sdk-python
|
|
6
|
+
Project-URL: Repository, https://github.com/Arnel7/kappelas-sdk-python
|
|
7
|
+
Project-URL: Bug Tracker, https://github.com/Arnel7/kappelas-sdk-python/issues
|
|
8
|
+
Author-email: Arnel LAWSON <arnellawson7@gmail.com>
|
|
9
|
+
License: MIT
|
|
10
|
+
Keywords: api,bot,kappela,messaging,sdk
|
|
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.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Topic :: Communications :: Chat
|
|
18
|
+
Classifier: Typing :: Typed
|
|
19
|
+
Requires-Python: >=3.11
|
|
20
|
+
Requires-Dist: httpx>=0.27
|
|
21
|
+
Requires-Dist: websockets>=12
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# kappelas-sdk
|
|
25
|
+
|
|
26
|
+
[](https://pypi.org/project/kappelas-sdk/)
|
|
27
|
+
[](https://pypi.org/project/kappelas-sdk/)
|
|
28
|
+
[](LICENSE)
|
|
29
|
+
|
|
30
|
+
**Official Python SDK for the [Kappela](https://kappelas.com) messaging platform.**
|
|
31
|
+
Build bots and personal automations — send messages, handle events, manage chats.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Table of contents
|
|
36
|
+
|
|
37
|
+
- [Prerequisites](#prerequisites)
|
|
38
|
+
- [Install](#install)
|
|
39
|
+
- [Quick start](#quick-start)
|
|
40
|
+
- [Async-first design](#async-first-design)
|
|
41
|
+
- [Events — WebSocket vs Webhook](#events--websocket-vs-webhook)
|
|
42
|
+
- [API reference](#api-reference)
|
|
43
|
+
- [messages](#messages)
|
|
44
|
+
- [chats](#chats)
|
|
45
|
+
- [webhooks](#webhooks)
|
|
46
|
+
- [profile](#profile)
|
|
47
|
+
- [Keyboards](#keyboards)
|
|
48
|
+
- [Error handling](#error-handling)
|
|
49
|
+
- [File input](#file-input)
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Prerequisites
|
|
54
|
+
|
|
55
|
+
You need a bot token from **BotMother**, the official Kappela bot manager.
|
|
56
|
+
|
|
57
|
+
1. Open Kappela and start a conversation with **BotMother** at `kappelas.com/bot/botmother_bot`
|
|
58
|
+
2. Follow the instructions to create a bot
|
|
59
|
+
3. BotMother gives you a token — keep it secret, it gives full control over your bot
|
|
60
|
+
|
|
61
|
+
For personal automation (sending messages as yourself), generate an API key from your Kappela account settings (`sk_...`).
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Install
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
pip install kappelas-sdk
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Requires **Python 3.11+**.
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Quick start
|
|
76
|
+
|
|
77
|
+
### Bot
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
import asyncio
|
|
81
|
+
from kappelas import KappelaBot
|
|
82
|
+
|
|
83
|
+
bot = KappelaBot('YOUR_BOT_TOKEN')
|
|
84
|
+
|
|
85
|
+
@bot.on('message')
|
|
86
|
+
async def on_message(msg):
|
|
87
|
+
await bot.messages.send(msg.chat_id, f'Echo: {msg.text}')
|
|
88
|
+
|
|
89
|
+
@bot.on('callback_query')
|
|
90
|
+
async def on_callback(cb):
|
|
91
|
+
await bot.messages.send(cb.chat_id, f'You clicked: {cb.callback_data}')
|
|
92
|
+
|
|
93
|
+
asyncio.run(bot.run())
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Personal automation
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
import asyncio
|
|
100
|
+
from kappelas import KappelaUser
|
|
101
|
+
|
|
102
|
+
me = KappelaUser('sk_your_api_key')
|
|
103
|
+
|
|
104
|
+
@me.on('message')
|
|
105
|
+
async def on_message(msg):
|
|
106
|
+
print(f'[{msg.chat_id}] {msg.sender_name}: {msg.text}')
|
|
107
|
+
|
|
108
|
+
asyncio.run(me.run())
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Async-first design
|
|
114
|
+
|
|
115
|
+
Every method that touches the network is a coroutine — `await` it:
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
result = await bot.messages.send(chat_id, 'Hello!')
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Use `asyncio.run()` as the entry point for standalone scripts, or integrate into any async framework (FastAPI, aiohttp, etc.).
|
|
122
|
+
|
|
123
|
+
Both `KappelaBot` and `KappelaUser` support `async with`, which automatically closes the WebSocket and HTTP client on exit:
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
async def main():
|
|
127
|
+
async with KappelaBot('YOUR_BOT_TOKEN') as bot:
|
|
128
|
+
@bot.on('message')
|
|
129
|
+
async def on_message(msg):
|
|
130
|
+
await bot.messages.send(msg.chat_id, f'Echo: {msg.text}')
|
|
131
|
+
|
|
132
|
+
await bot.run()
|
|
133
|
+
|
|
134
|
+
asyncio.run(main())
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Events — WebSocket vs Webhook
|
|
140
|
+
|
|
141
|
+
| Mode | Method | Best for |
|
|
142
|
+
|------|--------|----------|
|
|
143
|
+
| **WebSocket** | `await bot.run()` | Development, local scripts |
|
|
144
|
+
| **Webhook** | `await bot.webhooks.set()` + `bot.handle_webhook()` | Production servers |
|
|
145
|
+
|
|
146
|
+
The same `on('message')` and `on('callback_query')` handlers work in both modes — no code change needed when switching.
|
|
147
|
+
|
|
148
|
+
### WebSocket (development)
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
bot = KappelaBot('YOUR_BOT_TOKEN')
|
|
152
|
+
|
|
153
|
+
@bot.on('message')
|
|
154
|
+
async def on_message(msg): ...
|
|
155
|
+
|
|
156
|
+
@bot.on('callback_query')
|
|
157
|
+
async def on_callback(cb): ...
|
|
158
|
+
|
|
159
|
+
asyncio.run(bot.run()) # blocks, auto-reconnects on disconnect
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
`run()` blocks until `stop()` is called. Use `start()` if you need to connect in the background inside an already-running event loop.
|
|
163
|
+
|
|
164
|
+
### Webhook (production)
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
from fastapi import FastAPI, Request
|
|
168
|
+
from kappelas import KappelaBot
|
|
169
|
+
|
|
170
|
+
app = FastAPI()
|
|
171
|
+
bot = KappelaBot('YOUR_BOT_TOKEN')
|
|
172
|
+
|
|
173
|
+
@bot.on('message')
|
|
174
|
+
async def on_message(msg):
|
|
175
|
+
await bot.messages.send(msg.chat_id, f'Echo: {msg.text}')
|
|
176
|
+
|
|
177
|
+
@bot.on('callback_query')
|
|
178
|
+
async def on_callback(cb):
|
|
179
|
+
await bot.messages.send(cb.chat_id, f'Clicked: {cb.callback_data}')
|
|
180
|
+
|
|
181
|
+
@app.on_event('startup')
|
|
182
|
+
async def register_webhook():
|
|
183
|
+
await bot.webhooks.set('https://your-server.com/kappela-webhook')
|
|
184
|
+
|
|
185
|
+
@app.post('/kappela-webhook')
|
|
186
|
+
async def webhook(request: Request):
|
|
187
|
+
bot.handle_webhook(await request.json())
|
|
188
|
+
return {'ok': True}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
> Do **not** call `bot.run()` in webhook mode.
|
|
192
|
+
|
|
193
|
+
### Event reference
|
|
194
|
+
|
|
195
|
+
| Event | Handler signature | Description |
|
|
196
|
+
|-------|-------------------|-------------|
|
|
197
|
+
| `message` | `async def handler(msg: Message)` | Incoming message of any type |
|
|
198
|
+
| `callback_query` | `async def handler(cb: CallbackQuery)` | Inline button clicked by a user |
|
|
199
|
+
| `connected` | `async def handler()` | WebSocket connected or reconnected |
|
|
200
|
+
| `disconnected` | `async def handler(code, reason)` | WebSocket disconnected |
|
|
201
|
+
| `error` | `async def handler(exc: Exception)` | Connection or handler error |
|
|
202
|
+
| `raw` | `async def handler(event: dict)` | Raw `{ type, data }` wire event |
|
|
203
|
+
|
|
204
|
+
### `CallbackQuery` fields
|
|
205
|
+
|
|
206
|
+
```python
|
|
207
|
+
@bot.on('callback_query')
|
|
208
|
+
async def on_callback(cb):
|
|
209
|
+
cb.chat_id # int — chat where the button was clicked
|
|
210
|
+
cb.sender_id # str — UUID of the user who clicked
|
|
211
|
+
cb.sender_nom # str | None — display name (e.g. "Arnel LAWSON")
|
|
212
|
+
cb.sender_username # str | None — username (e.g. "arnell")
|
|
213
|
+
cb.callback_data # str — value set on the button
|
|
214
|
+
cb.sent_at # int — Unix timestamp (seconds)
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
> Clicks are deduplicated server-side — your handler fires exactly once per click.
|
|
218
|
+
|
|
219
|
+
### Decorator vs method usage
|
|
220
|
+
|
|
221
|
+
```python
|
|
222
|
+
# Decorator (persistent)
|
|
223
|
+
@bot.on('message')
|
|
224
|
+
async def on_message(msg): ...
|
|
225
|
+
|
|
226
|
+
# Method call (persistent)
|
|
227
|
+
async def on_message(msg): ...
|
|
228
|
+
bot.on('message', on_message)
|
|
229
|
+
bot.off('message', on_message) # remove handler
|
|
230
|
+
|
|
231
|
+
# fires once then auto-removes
|
|
232
|
+
@bot.once('connected')
|
|
233
|
+
async def on_first_connect(): ...
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## API reference
|
|
239
|
+
|
|
240
|
+
### Constructor
|
|
241
|
+
|
|
242
|
+
#### `KappelaBot(token, *, base_url, max_retries, timeout, ws_max_retries)`
|
|
243
|
+
|
|
244
|
+
| Parameter | Type | Default | Description |
|
|
245
|
+
|-----------|------|---------|-------------|
|
|
246
|
+
| `token` | `str` | — | Bot token from BotMother (required) |
|
|
247
|
+
| `base_url` | `str` | `'https://api.kappelas.com'` | Override API base URL |
|
|
248
|
+
| `max_retries` | `int` | `2` | HTTP retry count on 429 / 5xx |
|
|
249
|
+
| `timeout` | `float` | `30.0` | Per-request timeout (seconds) |
|
|
250
|
+
| `ws_max_retries` | `int` | `12` | Max WebSocket reconnect attempts |
|
|
251
|
+
|
|
252
|
+
#### `KappelaUser(api_key, *, base_url, max_retries, timeout, ws_max_retries)`
|
|
253
|
+
|
|
254
|
+
| Parameter | Type | Default | Description |
|
|
255
|
+
|-----------|------|---------|-------------|
|
|
256
|
+
| `api_key` | `str` | — | Personal API key `sk_...` (required) |
|
|
257
|
+
| `base_url` | `str` | `'https://api.kappelas.com'` | Override API base URL |
|
|
258
|
+
| `max_retries` | `int` | `2` | HTTP retry count on 429 / 5xx |
|
|
259
|
+
| `timeout` | `float` | `30.0` | Per-request timeout (seconds) |
|
|
260
|
+
| `ws_max_retries` | `int` | `12` | Max WebSocket reconnect attempts |
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
### `messages`
|
|
265
|
+
|
|
266
|
+
#### `messages.send(chat_id, text, *, reply_markup, reply_to_id, delete_previous)` → `SendResult`
|
|
267
|
+
|
|
268
|
+
```python
|
|
269
|
+
result = await bot.messages.send(
|
|
270
|
+
chat_id = 42,
|
|
271
|
+
text = 'Hello!',
|
|
272
|
+
reply_to_id = 123, # optional — reply to a message
|
|
273
|
+
delete_previous = False, # optional
|
|
274
|
+
reply_markup = InlineKeyboard(inline_keyboard=[[
|
|
275
|
+
InlineKeyboardButton(text='Yes', callback_data='yes'),
|
|
276
|
+
InlineKeyboardButton(text='No', callback_data='no'),
|
|
277
|
+
]]),
|
|
278
|
+
)
|
|
279
|
+
# → SendResult(message_id=..., created_at=...)
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
#### `messages.send_photo(chat_id, photo, *, caption, reply_to_id, delete_previous, reply_markup)` → `SendMediaResult`
|
|
283
|
+
|
|
284
|
+
```python
|
|
285
|
+
with open('banner.png', 'rb') as f:
|
|
286
|
+
await bot.messages.send_photo(chat_id, f, caption='Check this out!')
|
|
287
|
+
# → SendMediaResult(message_id=..., created_at=..., media_id=...)
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
#### `messages.send_video` / `send_document` / `send_audio` → `SendMediaResult`
|
|
291
|
+
|
|
292
|
+
Same signature — replace the file parameter (`video`, `document`, `audio`) with your file.
|
|
293
|
+
|
|
294
|
+
#### `messages.send_carousel(chat_id, carousel, *, text, quick_reply_buttons)` → `SendCarouselResult`
|
|
295
|
+
|
|
296
|
+
```python
|
|
297
|
+
from kappelas import CarouselCard
|
|
298
|
+
|
|
299
|
+
await bot.messages.send_carousel(
|
|
300
|
+
chat_id = 42,
|
|
301
|
+
text = 'Pick a product:',
|
|
302
|
+
carousel = [
|
|
303
|
+
CarouselCard(id='p1', title='Widget A', subtitle='$9.99', button_text='Buy'),
|
|
304
|
+
CarouselCard(id='p2', title='Widget B', subtitle='$19.99', button_text='Buy'),
|
|
305
|
+
],
|
|
306
|
+
quick_reply_buttons=['See more', 'Cancel'],
|
|
307
|
+
)
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
#### `messages.edit(chat_id, message_id, *, new_text, new_extra_data)` → `EditMessageResult`
|
|
311
|
+
|
|
312
|
+
```python
|
|
313
|
+
# Edit text
|
|
314
|
+
await bot.messages.edit(42, 123, new_text='Updated!')
|
|
315
|
+
|
|
316
|
+
# Edit inline keyboard only (no text change)
|
|
317
|
+
await bot.messages.edit(42, 123, new_extra_data={
|
|
318
|
+
'inline_keyboard': [[{'text': 'Done ✅', 'callback_data': 'done'}]]
|
|
319
|
+
})
|
|
320
|
+
# → EditMessageResult(edited=True, message_id=...)
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
#### `messages.send_typing(chat_id, *, is_typing)` → `TypingResult`
|
|
324
|
+
|
|
325
|
+
```python
|
|
326
|
+
await bot.messages.send_typing(42) # show
|
|
327
|
+
await bot.messages.send_typing(42, is_typing=False) # hide
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
#### `messages.delete(chat_id, message_id)` → `DeleteResult`
|
|
331
|
+
|
|
332
|
+
```python
|
|
333
|
+
await bot.messages.delete(42, 123)
|
|
334
|
+
# → DeleteResult(deleted=True)
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
---
|
|
338
|
+
|
|
339
|
+
### `chats`
|
|
340
|
+
|
|
341
|
+
#### `chats.list(*, limit, offset)` → `ChatsResult`
|
|
342
|
+
|
|
343
|
+
```python
|
|
344
|
+
page = await bot.chats.list(limit=20, offset=0)
|
|
345
|
+
print(page.chats, page.has_more)
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
#### `chats.iterate(page_size?)` → `AsyncGenerator[Chat]`
|
|
349
|
+
|
|
350
|
+
```python
|
|
351
|
+
async for chat in bot.chats.iterate():
|
|
352
|
+
print(chat.chat_id, chat.title, chat.type)
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
---
|
|
356
|
+
|
|
357
|
+
### `webhooks`
|
|
358
|
+
|
|
359
|
+
#### `webhooks.set(url, *, secret)` → `WebhookSetResult`
|
|
360
|
+
|
|
361
|
+
```python
|
|
362
|
+
await bot.webhooks.set('https://your-server.com/kappela-webhook')
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
#### `webhooks.get_info()` → `WebhookInfo`
|
|
366
|
+
|
|
367
|
+
```python
|
|
368
|
+
info = await bot.webhooks.get_info()
|
|
369
|
+
# → WebhookInfo(active=True, url='https://...', created_at=...)
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
#### `webhooks.delete()` → `WebhookDeleteResult`
|
|
373
|
+
|
|
374
|
+
```python
|
|
375
|
+
await bot.webhooks.delete()
|
|
376
|
+
# → WebhookDeleteResult(active=False)
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
---
|
|
380
|
+
|
|
381
|
+
### `profile`
|
|
382
|
+
|
|
383
|
+
#### `profile.get()` → `BotProfile | UserProfile`
|
|
384
|
+
|
|
385
|
+
```python
|
|
386
|
+
profile = await bot.profile.get()
|
|
387
|
+
# BotProfile → user_id, username, is_bot=True, about, description, avatar_url
|
|
388
|
+
# UserProfile → id, username, nom, is_bot=False, is_premium, avatar_url, ...
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
---
|
|
392
|
+
|
|
393
|
+
## Keyboards
|
|
394
|
+
|
|
395
|
+
Three types of keyboard can be passed as `reply_markup` on any `send*` call:
|
|
396
|
+
|
|
397
|
+
```python
|
|
398
|
+
from kappelas import InlineKeyboard, InlineKeyboardButton, ReplyKeyboard, ScrollKeyboard
|
|
399
|
+
|
|
400
|
+
# Inline buttons — attached to the message
|
|
401
|
+
inline = InlineKeyboard(inline_keyboard=[
|
|
402
|
+
[
|
|
403
|
+
InlineKeyboardButton(text='Yes', callback_data='yes'),
|
|
404
|
+
InlineKeyboardButton(text='No', callback_data='no'),
|
|
405
|
+
],
|
|
406
|
+
[
|
|
407
|
+
InlineKeyboardButton(text='Open website', url='https://kappelas.com'),
|
|
408
|
+
],
|
|
409
|
+
])
|
|
410
|
+
|
|
411
|
+
# Reply keyboard — shown below the input bar
|
|
412
|
+
reply = ReplyKeyboard(keyboard=[
|
|
413
|
+
['Option A', 'Option B'],
|
|
414
|
+
['Cancel'],
|
|
415
|
+
])
|
|
416
|
+
|
|
417
|
+
# Scroll keyboard — horizontal scrollable chips
|
|
418
|
+
scroll = ScrollKeyboard(scroll_keyboard=['Small', 'Medium', 'Large'])
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
---
|
|
422
|
+
|
|
423
|
+
## Error handling
|
|
424
|
+
|
|
425
|
+
All API errors raise `KappelaError` with structured fields:
|
|
426
|
+
|
|
427
|
+
```python
|
|
428
|
+
from kappelas import KappelaError
|
|
429
|
+
|
|
430
|
+
try:
|
|
431
|
+
await bot.messages.send(999, 'Hi')
|
|
432
|
+
except KappelaError as e:
|
|
433
|
+
e.error_code # 'NOT_FOUND'
|
|
434
|
+
e.status # 404
|
|
435
|
+
e.hint # 'The requested resource does not exist.'
|
|
436
|
+
e.solutions # ['Check the ID is correct', ...]
|
|
437
|
+
e.request_id # include when contacting support
|
|
438
|
+
print(e) # full formatted block
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
### Error codes
|
|
442
|
+
|
|
443
|
+
| Code | HTTP | Meaning |
|
|
444
|
+
|------|------|---------|
|
|
445
|
+
| `UNAUTHORIZED` | 401 | Token or API key invalid / expired |
|
|
446
|
+
| `FORBIDDEN` | 403 | Missing permission or role |
|
|
447
|
+
| `NOT_FOUND` | 404 | Resource does not exist |
|
|
448
|
+
| `MISSING_FIELD` | 400 | Required parameter missing |
|
|
449
|
+
| `INVALID_FIELD` | 400 | Parameter has wrong type or format |
|
|
450
|
+
| `CONFLICT` | 409 | Resource already exists |
|
|
451
|
+
| `METHOD_NOT_ALLOWED` | 405 | Wrong HTTP method |
|
|
452
|
+
| `INVALID_PATH` | 404 | API path does not exist |
|
|
453
|
+
| `INTERNAL_ERROR` | 500 | Unexpected server error |
|
|
454
|
+
| `SERVICE_UNAVAILABLE` | 503 | Service temporarily down |
|
|
455
|
+
| `UPSTREAM_ERROR` | 502 | Upstream service error |
|
|
456
|
+
|
|
457
|
+
---
|
|
458
|
+
|
|
459
|
+
## File input
|
|
460
|
+
|
|
461
|
+
Media methods accept files in several forms:
|
|
462
|
+
|
|
463
|
+
| Type | Example |
|
|
464
|
+
|------|---------|
|
|
465
|
+
| `bytes` | `open('img.jpg', 'rb').read()` |
|
|
466
|
+
| `IO[bytes]` | `open('img.jpg', 'rb')` |
|
|
467
|
+
| `FileData` | `FileData(data=b'...', filename='img.jpg', content_type='image/jpeg')` |
|
|
468
|
+
|
|
469
|
+
```python
|
|
470
|
+
from kappelas import FileData
|
|
471
|
+
|
|
472
|
+
# bytes
|
|
473
|
+
await bot.messages.send_photo(chat_id, open('photo.jpg', 'rb').read())
|
|
474
|
+
|
|
475
|
+
# file object
|
|
476
|
+
with open('photo.jpg', 'rb') as f:
|
|
477
|
+
await bot.messages.send_photo(chat_id, f)
|
|
478
|
+
|
|
479
|
+
# explicit metadata
|
|
480
|
+
await bot.messages.send_document(
|
|
481
|
+
chat_id,
|
|
482
|
+
FileData(data=pdf_bytes, filename='report.pdf', content_type='application/pdf'),
|
|
483
|
+
)
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
---
|
|
487
|
+
|
|
488
|
+
## License
|
|
489
|
+
|
|
490
|
+
MIT © Arnel LAWSON
|