nestra-py 1.2.2__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,326 @@
1
+ Metadata-Version: 2.4
2
+ Name: nestra-py
3
+ Version: 1.2.2
4
+ Summary: SDK Pythona dla API i Gateway Nestra (bot API 1.2.2: moderacja, role, reakcje, kanaly, zaproszenia, webhooki, DM, voice, komendy)
5
+ Author: Nestra
6
+ License: MIT
7
+ Project-URL: Homepage, https://nestra.gg
8
+ Project-URL: Documentation, https://nestra.gg
9
+ Keywords: nestra,bot,chat,sdk,asyncio
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Framework :: AsyncIO
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: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3.14
19
+ Classifier: Topic :: Communications :: Chat
20
+ Requires-Python: >=3.11
21
+ Description-Content-Type: text/markdown
22
+ Provides-Extra: dev
23
+ Requires-Dist: pytest>=8.0; extra == "dev"
24
+
25
+ # Nestra-PY
26
+
27
+ Oficjalne SDK Pythona do API i Gatewaya Nestra. Klient REST, klient WebSocket,
28
+ embedy, komendy prefiksowe i reconnect w jednym pakiecie.
29
+
30
+ ## Cechy
31
+
32
+ - REST: gildie, kanały, wiadomości, członkowie, moderacja (kick/ban/timeout/role), reakcje, zaproszenia, webhooki, DM, voice, komendy, presence i uploady.
33
+ - Gateway: połączenie asyncio, IDENTIFY z `botToken`, heartbeat i reconnect z backoffem.
34
+ - Asynchroniczne handlery eventów: `READY`, `MESSAGE_CREATE`, `MESSAGE_UPDATE`, `MESSAGE_DELETE`, `GUILD_CREATE` i dowolne inne eventy.
35
+ - `EmbedBuilder` z limitami zgodnymi z API Nestra.
36
+ - `CommandHandler` dla komend prefiksowych z aliasami, pomocą i obsługą błędów.
37
+ - Rate limit queue czytająca nagłówki `X-RateLimit-*` i `Retry-After`.
38
+ - Zero zależności zewnętrznych w runtime. Wymagany jest tylko Python 3.11+.
39
+
40
+ ## Instalacja
41
+
42
+ Z katalogu repozytorium:
43
+
44
+ ```powershell
45
+ python -m pip install -e integrations/nestra-py
46
+ ```
47
+
48
+ Albo bez instalacji, przez `PYTHONPATH`:
49
+
50
+ ```powershell
51
+ $env:PYTHONPATH = "integrations/nestra-py"
52
+ python examples/nestra-py-bot/bot.py
53
+ ```
54
+
55
+ Wymagania: Python 3.11 lub nowszy. SDK nie wymaga `websockets`, `aiohttp` ani
56
+ `requests`. Testy deweloperskie można uruchomić przez `pytest` albo wbudowany
57
+ `unittest`.
58
+
59
+ ## Szybki start (REST)
60
+
61
+ ```python
62
+ import asyncio
63
+
64
+ from nestra import EmbedBuilder, NestraREST
65
+
66
+
67
+ async def main() -> None:
68
+ rest = NestraREST.from_env()
69
+ me = await rest.get_current_user()
70
+ print(f"Zalogowany bot: {me.username}")
71
+
72
+ guilds = await rest.get_guilds()
73
+ if not guilds:
74
+ print("Bot nie jest członkiem żadnego serwera.")
75
+ return
76
+
77
+ detail = await rest.get_guild(guilds[0].id)
78
+ channel = detail.channel_by_name("ogolny")
79
+ if channel is None:
80
+ print("Brak kanału ogolny.")
81
+ return
82
+
83
+ embed = (
84
+ EmbedBuilder()
85
+ .set_title("Nestra-PY")
86
+ .set_description("Wiadomość wysłana przez SDK")
87
+ .set_color("blurple")
88
+ .set_footer("nestra.gg")
89
+ )
90
+ await rest.send_message(channel.id, embeds=embed)
91
+
92
+
93
+ asyncio.run(main())
94
+ ```
95
+
96
+ `NestraREST.from_env()` czyta `BOT_TOKEN` oraz opcjonalny `NESTRA_API_URL`
97
+ (domyślnie `http://localhost:4000`). Token bota ma prefiks `nestra_bot_` i jest
98
+ pokazywany w panelu Developer tylko raz. Bot musi być członkiem serwera: dodaje
99
+ go właściciel lub administrator przez panel albo bot dołącza kodem zaproszenia
100
+ (`join_guild`).
101
+
102
+ ## Szybki start (Gateway)
103
+
104
+ ```python
105
+ import asyncio
106
+
107
+ from nestra import GatewayClient, NestraREST
108
+
109
+
110
+ async def main() -> None:
111
+ rest = NestraREST.from_env()
112
+ bot = GatewayClient.from_env(rest=rest)
113
+
114
+ @bot.event("READY")
115
+ async def on_ready(data):
116
+ print(f"Połączono jako {bot.user.name if bot.user else '?'}")
117
+
118
+ @bot.event("MESSAGE_CREATE")
119
+ async def on_message(data):
120
+ message = data["message"]
121
+ if message["author"] and message["content"] == "!ping":
122
+ await rest.send_message(message["channelId"], "pong")
123
+
124
+ await bot.start()
125
+ await bot.wait_until_ready(timeout=30)
126
+ await bot.wait_until_closed()
127
+
128
+
129
+ asyncio.run(main())
130
+ ```
131
+
132
+ `GatewayClient.from_env()` czyta `BOT_TOKEN`, opcjonalny `NESTRA_GATEWAY_URL`
133
+ (domyślnie `ws://localhost:4001/gateway`) oraz `NESTRA_GATEWAY_ORIGIN`
134
+ (domyślnie `http://localhost:5174`). Gateway Nestra wymaga nagłówka `Origin`
135
+ z listy CORS, dlatego bot nie jest przeglądarką, ale nadal wysyła ten nagłówek.
136
+
137
+ ### Eventy
138
+
139
+ Handlery rejestruje się dekoratorem `@bot.event("NAZWA")`. Handler może być
140
+ funkcją synchroniczną albo `async`. SDK wywołuje je po kolei, więc wiadomości
141
+ zachowują kolejność. Błąd handlera trafia do `@bot.on_error` i nie zamyka
142
+ połączenia.
143
+
144
+ ```python
145
+ @bot.on_error
146
+ async def on_error(error, event_name, data):
147
+ print(f"Błąd {event_name}: {error}")
148
+ ```
149
+
150
+ `GUILD_CREATE` jest obsługiwany jak każdy inny event. Obecny Gateway wysyła
151
+ gildie w `READY` i eventy `GUILD_UPDATE`/`GUILD_DELETE`; rejestracja
152
+ `GUILD_CREATE` pozwala odebrać ten event, gdy pojawi się w protokole.
153
+
154
+ ### Reconnect
155
+
156
+ Klient sam wysyła heartbeat na podstawie `HELLO`. Po zerwaniu połączenia bot
157
+ wysyła ponownie IDENTIFY, bo Gateway nie obsługuje RESUME dla tokenów botów.
158
+ Sesje użytkownika (`token_type="Bearer"`) mogą użyć RESUME. Opóźnienia rosną
159
+ wykładniczo (`ReconnectBackoff`) i mają jitter. Zamknięcia `4001` (błędny
160
+ token) oraz `4002` (błędny payload) są traktowane jako trwałe, bez reconnectu.
161
+
162
+ ## Komendy prefiksowe
163
+
164
+ ```python
165
+ from nestra import CommandContext, CommandHandler
166
+
167
+ commands = CommandHandler(prefix="!", rest=rest)
168
+
169
+
170
+ @commands.command("ping", description="Odpowiada pongiem")
171
+ async def ping(ctx: CommandContext) -> None:
172
+ await ctx.reply("pong")
173
+
174
+
175
+ @bot.event("MESSAGE_CREATE")
176
+ async def on_message(data):
177
+ from nestra import Message
178
+
179
+ await commands.handle_message(Message.from_dict(data["message"]))
180
+ ```
181
+
182
+ `parse_command("!ping teraz", "!")` zwraca `ParsedCommand(name="ping",
183
+ args="teraz", argv=["teraz"])`. Parser obsługuje cudzysłowy przez `shlex` i
184
+ nie wywala się na niezamkniętym cudzysłowie.
185
+
186
+ ## Embedy
187
+
188
+ `EmbedBuilder` waliduje limity API: 256 znaków tytułu, 4096 opisu, 25 pól,
189
+ 6000 znaków łącznie. Kolory można podawać jako `int`, hex (`#5865F2`) albo
190
+ nazwę (`blurple`, `green`, `red`).
191
+
192
+ ```python
193
+ embed = (
194
+ EmbedBuilder()
195
+ .set_title("Status")
196
+ .set_description("Wszystkie systemy działają")
197
+ .set_color("#57F287")
198
+ .add_field("API", "ok", inline=True)
199
+ .add_field("Gateway", "ok", inline=True)
200
+ .set_timestamp()
201
+ )
202
+ ```
203
+
204
+ Do API można przekazać pojedynczy builder, listę builderów albo zwykłe dict-y.
205
+ SDK wysyła wyłącznie adresy `http`/`https` w polach URL.
206
+
207
+ ## API klienta
208
+
209
+ | Metoda | Opis |
210
+ |---|---|
211
+ | `get_guilds()` | lista gildii bota |
212
+ | `get_guild(guild_id)` | szczegóły gildii, kanały i role |
213
+ | `get_guild_channels(guild_id)` | kanały gildii |
214
+ | `get_members(guild_id, limit=50)` | członkowie gildii (do 1000) |
215
+ | `edit_guild(guild_id, ...)` | edycja nazwy/opisu/koloru gildii |
216
+ | `kick_member(guild_id, user_id, reason=...)` | wyrzucenie członka |
217
+ | `ban_member(...)` / `unban_member(...)` | ban i unban |
218
+ | `get_bans(guild_id)` | lista banów |
219
+ | `timeout_member(...)` | timeout członka |
220
+ | `add_role(...)` / `remove_role(...)` | role członka |
221
+ | `join_guild(invite_code)` | dodanie bota przez zaproszenie, zwraca ID gildii |
222
+ | `leave_guild(guild_id)` | opuszczenie gildii |
223
+ | `create_invite(...)` | zaproszenie do kanału |
224
+ | `get_invites(guild_id)` / `delete_invite(code)` | lista i usuwanie zaproszeń |
225
+ | `create_channel(guild_id, name, ...)` | nowy kanał |
226
+ | `update_channel(guild_id, channel_id, ...)` | edycja kanału |
227
+ | `delete_channel(guild_id, channel_id)` | usunięcie kanału |
228
+ | `get_messages(channel_id, limit=50, before=None)` | historia wiadomości |
229
+ | `send_message(channel_id, content, embeds=..., reply_to_id=...)` | wysyłka wiadomości |
230
+ | `edit_message(channel_id, message_id, content=..., embeds=...)` | edycja treści i/lub embedów |
231
+ | `edit_embed_message(channel_id, message_id, embeds)` | edycja z samym embedem |
232
+ | `delete_message(channel_id, message_id)` | usunięcie wiadomości bota |
233
+ | `add_reaction(...)` / `remove_reaction(...)` | reakcje bota |
234
+ | `get_current_user()` | konto bota (`/api/v1/bot/me`) |
235
+ | `get_user(user_id)` | profil użytkownika |
236
+ | `set_presence(status, ...)` | presence bota |
237
+ | `open_dm(user_id)` / `send_dm(user_id, ...)` | kanał DM i wiadomość |
238
+ | `join_voice(guild_id, channel_id)` / `leave_voice(guild_id)` | token LiveKit i wyjście |
239
+ | `get_commands()` / `register_commands(...)` / `delete_command(id)` | komendy aplikacji |
240
+ | `get_webhooks(guild_id)` | webhooki gildii |
241
+ | `create_webhook(...)` | nowy webhook |
242
+ | `update_webhook(...)` / `delete_webhook(guild_id, webhook_id)` | edycja i usunięcie |
243
+ | `execute_webhook(webhook_id, token, ...)` | wysyłka przez webhook, bez auth |
244
+ | `upload_attachment(channel_id, filename, data)` | upload pliku |
245
+ | `send_file(channel_id, filename, data, content=None)` | upload i wysyłka wiadomości |
246
+
247
+ Wszystkie metody bota korzystają z namespace `/api/v1/bot/...` (1.2.2). Kontrakt
248
+ rozszerza subagent bot-api równolegle; jeśli API zwróci 404 dla nowej trasy,
249
+ SDK zgłosi czytelny `NestraAPIError`, a pozostałe metody działają.
250
+
251
+ Wszystkie metody REST są asynchroniczne. `NestraREST.request()` pozwala wywołać
252
+ dowolny endpoint API bez oczekiwania na dodanie metody do SDK.
253
+
254
+ ## Rate limity i błędy
255
+
256
+ Każde żądanie przechodzi przez `RateLimitQueue`. Po odpowiedzi `429` klient
257
+ czeka `Retry-After` (albo wartość z JSON) i ponawia żądanie, maksymalnie
258
+ `max_retries` razy. Błędy `5xx` i problemy sieciowe mają krótki backoff.
259
+
260
+ Błędy API są zgłaszane jako `NestraAPIError`:
261
+
262
+ ```python
263
+ from nestra import NestraAPIError
264
+
265
+ try:
266
+ await rest.send_message(channel_id, "test")
267
+ except NestraAPIError as error:
268
+ print(error.status, error.code, error.message)
269
+ ```
270
+
271
+ `error.status == 0` oznacza błąd sieci albo niepoprawną odpowiedź JSON.
272
+
273
+ ## Bezpieczeństwo tokenu
274
+
275
+ - Token bota to sekret. Trzymaj go w zmiennej `BOT_TOKEN`, nie w kodzie ani w repozytorium.
276
+ - SDK nie loguje tokenu. `repr(NestraREST(...))` nie zawiera sekretu.
277
+ - Nie wysyłaj tokenu do przeglądarki ani do kodu klienta WWW.
278
+ - W produkcji używaj `https://` i `wss://`; połączenie z tokenem przez `http`/`ws` jest dopuszczalne tylko lokalnie.
279
+ - Przy wycieku tokenu unieważnij klucz w panelu deweloperskim i wygeneruj nowy.
280
+ - Bot powinien mieć tylko uprawnienia potrzebne do działania. Token nie zastępuje kontroli uprawnień po stronie API.
281
+
282
+ ## Zgodność z bot auth
283
+
284
+ Bot auth jest wdrożony w API i Gatewayu:
285
+
286
+ - REST: `Authorization: Bot <token>` i trasy `/api/v1/bot/...`,
287
+ - Gateway: IDENTIFY przyjmuje dokładnie jedno pole, `botToken` albo `token`;
288
+ SDK wysyła `botToken` dla tokenu bota i `token` dla sesji użytkownika,
289
+ - READY bota zawiera `bot: true`, pustą obecność i listę gildii bota.
290
+
291
+ Nie mieszaj tokenu bota i JWT użytkownika w jednym kliencie. `token_type="Bearer"`
292
+ służy do sesji użytkownika, nie do kluczy `nstr_` ani tokenów `nestra_bot_`.
293
+
294
+ ## Testy
295
+
296
+ Z katalogu pakietu:
297
+
298
+ ```powershell
299
+ python -m unittest discover -s tests -v
300
+ python -m compileall -q nestra
301
+ ```
302
+
303
+ Testy jednostkowe nie wymagają uruchomionego API. Test dymny uruchamia się
304
+ tylko z `NESTRA_SMOKE=1` i prawidłowym `BOT_TOKEN`:
305
+
306
+ ```powershell
307
+ $env:NESTRA_SMOKE = "1"
308
+ python -m unittest tests.test_smoke -v
309
+ ```
310
+
311
+ ## Struktura
312
+
313
+ ```text
314
+ integrations/nestra-py/
315
+ nestra/
316
+ __init__.py
317
+ rest.py # REST, rate limit, błędy
318
+ gateway.py # Gateway, heartbeat, reconnect, eventy
319
+ embed.py # EmbedBuilder
320
+ commands.py # CommandHandler
321
+ types.py # modele DTO
322
+ _ws.py # minimalny klient WebSocket
323
+ ```
324
+
325
+ Przykładowy bot znajduje się w `examples/nestra-py-bot` w repozytorium Nestra.
326
+
@@ -0,0 +1,302 @@
1
+ # Nestra-PY
2
+
3
+ Oficjalne SDK Pythona do API i Gatewaya Nestra. Klient REST, klient WebSocket,
4
+ embedy, komendy prefiksowe i reconnect w jednym pakiecie.
5
+
6
+ ## Cechy
7
+
8
+ - REST: gildie, kanały, wiadomości, członkowie, moderacja (kick/ban/timeout/role), reakcje, zaproszenia, webhooki, DM, voice, komendy, presence i uploady.
9
+ - Gateway: połączenie asyncio, IDENTIFY z `botToken`, heartbeat i reconnect z backoffem.
10
+ - Asynchroniczne handlery eventów: `READY`, `MESSAGE_CREATE`, `MESSAGE_UPDATE`, `MESSAGE_DELETE`, `GUILD_CREATE` i dowolne inne eventy.
11
+ - `EmbedBuilder` z limitami zgodnymi z API Nestra.
12
+ - `CommandHandler` dla komend prefiksowych z aliasami, pomocą i obsługą błędów.
13
+ - Rate limit queue czytająca nagłówki `X-RateLimit-*` i `Retry-After`.
14
+ - Zero zależności zewnętrznych w runtime. Wymagany jest tylko Python 3.11+.
15
+
16
+ ## Instalacja
17
+
18
+ Z katalogu repozytorium:
19
+
20
+ ```powershell
21
+ python -m pip install -e integrations/nestra-py
22
+ ```
23
+
24
+ Albo bez instalacji, przez `PYTHONPATH`:
25
+
26
+ ```powershell
27
+ $env:PYTHONPATH = "integrations/nestra-py"
28
+ python examples/nestra-py-bot/bot.py
29
+ ```
30
+
31
+ Wymagania: Python 3.11 lub nowszy. SDK nie wymaga `websockets`, `aiohttp` ani
32
+ `requests`. Testy deweloperskie można uruchomić przez `pytest` albo wbudowany
33
+ `unittest`.
34
+
35
+ ## Szybki start (REST)
36
+
37
+ ```python
38
+ import asyncio
39
+
40
+ from nestra import EmbedBuilder, NestraREST
41
+
42
+
43
+ async def main() -> None:
44
+ rest = NestraREST.from_env()
45
+ me = await rest.get_current_user()
46
+ print(f"Zalogowany bot: {me.username}")
47
+
48
+ guilds = await rest.get_guilds()
49
+ if not guilds:
50
+ print("Bot nie jest członkiem żadnego serwera.")
51
+ return
52
+
53
+ detail = await rest.get_guild(guilds[0].id)
54
+ channel = detail.channel_by_name("ogolny")
55
+ if channel is None:
56
+ print("Brak kanału ogolny.")
57
+ return
58
+
59
+ embed = (
60
+ EmbedBuilder()
61
+ .set_title("Nestra-PY")
62
+ .set_description("Wiadomość wysłana przez SDK")
63
+ .set_color("blurple")
64
+ .set_footer("nestra.gg")
65
+ )
66
+ await rest.send_message(channel.id, embeds=embed)
67
+
68
+
69
+ asyncio.run(main())
70
+ ```
71
+
72
+ `NestraREST.from_env()` czyta `BOT_TOKEN` oraz opcjonalny `NESTRA_API_URL`
73
+ (domyślnie `http://localhost:4000`). Token bota ma prefiks `nestra_bot_` i jest
74
+ pokazywany w panelu Developer tylko raz. Bot musi być członkiem serwera: dodaje
75
+ go właściciel lub administrator przez panel albo bot dołącza kodem zaproszenia
76
+ (`join_guild`).
77
+
78
+ ## Szybki start (Gateway)
79
+
80
+ ```python
81
+ import asyncio
82
+
83
+ from nestra import GatewayClient, NestraREST
84
+
85
+
86
+ async def main() -> None:
87
+ rest = NestraREST.from_env()
88
+ bot = GatewayClient.from_env(rest=rest)
89
+
90
+ @bot.event("READY")
91
+ async def on_ready(data):
92
+ print(f"Połączono jako {bot.user.name if bot.user else '?'}")
93
+
94
+ @bot.event("MESSAGE_CREATE")
95
+ async def on_message(data):
96
+ message = data["message"]
97
+ if message["author"] and message["content"] == "!ping":
98
+ await rest.send_message(message["channelId"], "pong")
99
+
100
+ await bot.start()
101
+ await bot.wait_until_ready(timeout=30)
102
+ await bot.wait_until_closed()
103
+
104
+
105
+ asyncio.run(main())
106
+ ```
107
+
108
+ `GatewayClient.from_env()` czyta `BOT_TOKEN`, opcjonalny `NESTRA_GATEWAY_URL`
109
+ (domyślnie `ws://localhost:4001/gateway`) oraz `NESTRA_GATEWAY_ORIGIN`
110
+ (domyślnie `http://localhost:5174`). Gateway Nestra wymaga nagłówka `Origin`
111
+ z listy CORS, dlatego bot nie jest przeglądarką, ale nadal wysyła ten nagłówek.
112
+
113
+ ### Eventy
114
+
115
+ Handlery rejestruje się dekoratorem `@bot.event("NAZWA")`. Handler może być
116
+ funkcją synchroniczną albo `async`. SDK wywołuje je po kolei, więc wiadomości
117
+ zachowują kolejność. Błąd handlera trafia do `@bot.on_error` i nie zamyka
118
+ połączenia.
119
+
120
+ ```python
121
+ @bot.on_error
122
+ async def on_error(error, event_name, data):
123
+ print(f"Błąd {event_name}: {error}")
124
+ ```
125
+
126
+ `GUILD_CREATE` jest obsługiwany jak każdy inny event. Obecny Gateway wysyła
127
+ gildie w `READY` i eventy `GUILD_UPDATE`/`GUILD_DELETE`; rejestracja
128
+ `GUILD_CREATE` pozwala odebrać ten event, gdy pojawi się w protokole.
129
+
130
+ ### Reconnect
131
+
132
+ Klient sam wysyła heartbeat na podstawie `HELLO`. Po zerwaniu połączenia bot
133
+ wysyła ponownie IDENTIFY, bo Gateway nie obsługuje RESUME dla tokenów botów.
134
+ Sesje użytkownika (`token_type="Bearer"`) mogą użyć RESUME. Opóźnienia rosną
135
+ wykładniczo (`ReconnectBackoff`) i mają jitter. Zamknięcia `4001` (błędny
136
+ token) oraz `4002` (błędny payload) są traktowane jako trwałe, bez reconnectu.
137
+
138
+ ## Komendy prefiksowe
139
+
140
+ ```python
141
+ from nestra import CommandContext, CommandHandler
142
+
143
+ commands = CommandHandler(prefix="!", rest=rest)
144
+
145
+
146
+ @commands.command("ping", description="Odpowiada pongiem")
147
+ async def ping(ctx: CommandContext) -> None:
148
+ await ctx.reply("pong")
149
+
150
+
151
+ @bot.event("MESSAGE_CREATE")
152
+ async def on_message(data):
153
+ from nestra import Message
154
+
155
+ await commands.handle_message(Message.from_dict(data["message"]))
156
+ ```
157
+
158
+ `parse_command("!ping teraz", "!")` zwraca `ParsedCommand(name="ping",
159
+ args="teraz", argv=["teraz"])`. Parser obsługuje cudzysłowy przez `shlex` i
160
+ nie wywala się na niezamkniętym cudzysłowie.
161
+
162
+ ## Embedy
163
+
164
+ `EmbedBuilder` waliduje limity API: 256 znaków tytułu, 4096 opisu, 25 pól,
165
+ 6000 znaków łącznie. Kolory można podawać jako `int`, hex (`#5865F2`) albo
166
+ nazwę (`blurple`, `green`, `red`).
167
+
168
+ ```python
169
+ embed = (
170
+ EmbedBuilder()
171
+ .set_title("Status")
172
+ .set_description("Wszystkie systemy działają")
173
+ .set_color("#57F287")
174
+ .add_field("API", "ok", inline=True)
175
+ .add_field("Gateway", "ok", inline=True)
176
+ .set_timestamp()
177
+ )
178
+ ```
179
+
180
+ Do API można przekazać pojedynczy builder, listę builderów albo zwykłe dict-y.
181
+ SDK wysyła wyłącznie adresy `http`/`https` w polach URL.
182
+
183
+ ## API klienta
184
+
185
+ | Metoda | Opis |
186
+ |---|---|
187
+ | `get_guilds()` | lista gildii bota |
188
+ | `get_guild(guild_id)` | szczegóły gildii, kanały i role |
189
+ | `get_guild_channels(guild_id)` | kanały gildii |
190
+ | `get_members(guild_id, limit=50)` | członkowie gildii (do 1000) |
191
+ | `edit_guild(guild_id, ...)` | edycja nazwy/opisu/koloru gildii |
192
+ | `kick_member(guild_id, user_id, reason=...)` | wyrzucenie członka |
193
+ | `ban_member(...)` / `unban_member(...)` | ban i unban |
194
+ | `get_bans(guild_id)` | lista banów |
195
+ | `timeout_member(...)` | timeout członka |
196
+ | `add_role(...)` / `remove_role(...)` | role członka |
197
+ | `join_guild(invite_code)` | dodanie bota przez zaproszenie, zwraca ID gildii |
198
+ | `leave_guild(guild_id)` | opuszczenie gildii |
199
+ | `create_invite(...)` | zaproszenie do kanału |
200
+ | `get_invites(guild_id)` / `delete_invite(code)` | lista i usuwanie zaproszeń |
201
+ | `create_channel(guild_id, name, ...)` | nowy kanał |
202
+ | `update_channel(guild_id, channel_id, ...)` | edycja kanału |
203
+ | `delete_channel(guild_id, channel_id)` | usunięcie kanału |
204
+ | `get_messages(channel_id, limit=50, before=None)` | historia wiadomości |
205
+ | `send_message(channel_id, content, embeds=..., reply_to_id=...)` | wysyłka wiadomości |
206
+ | `edit_message(channel_id, message_id, content=..., embeds=...)` | edycja treści i/lub embedów |
207
+ | `edit_embed_message(channel_id, message_id, embeds)` | edycja z samym embedem |
208
+ | `delete_message(channel_id, message_id)` | usunięcie wiadomości bota |
209
+ | `add_reaction(...)` / `remove_reaction(...)` | reakcje bota |
210
+ | `get_current_user()` | konto bota (`/api/v1/bot/me`) |
211
+ | `get_user(user_id)` | profil użytkownika |
212
+ | `set_presence(status, ...)` | presence bota |
213
+ | `open_dm(user_id)` / `send_dm(user_id, ...)` | kanał DM i wiadomość |
214
+ | `join_voice(guild_id, channel_id)` / `leave_voice(guild_id)` | token LiveKit i wyjście |
215
+ | `get_commands()` / `register_commands(...)` / `delete_command(id)` | komendy aplikacji |
216
+ | `get_webhooks(guild_id)` | webhooki gildii |
217
+ | `create_webhook(...)` | nowy webhook |
218
+ | `update_webhook(...)` / `delete_webhook(guild_id, webhook_id)` | edycja i usunięcie |
219
+ | `execute_webhook(webhook_id, token, ...)` | wysyłka przez webhook, bez auth |
220
+ | `upload_attachment(channel_id, filename, data)` | upload pliku |
221
+ | `send_file(channel_id, filename, data, content=None)` | upload i wysyłka wiadomości |
222
+
223
+ Wszystkie metody bota korzystają z namespace `/api/v1/bot/...` (1.2.2). Kontrakt
224
+ rozszerza subagent bot-api równolegle; jeśli API zwróci 404 dla nowej trasy,
225
+ SDK zgłosi czytelny `NestraAPIError`, a pozostałe metody działają.
226
+
227
+ Wszystkie metody REST są asynchroniczne. `NestraREST.request()` pozwala wywołać
228
+ dowolny endpoint API bez oczekiwania na dodanie metody do SDK.
229
+
230
+ ## Rate limity i błędy
231
+
232
+ Każde żądanie przechodzi przez `RateLimitQueue`. Po odpowiedzi `429` klient
233
+ czeka `Retry-After` (albo wartość z JSON) i ponawia żądanie, maksymalnie
234
+ `max_retries` razy. Błędy `5xx` i problemy sieciowe mają krótki backoff.
235
+
236
+ Błędy API są zgłaszane jako `NestraAPIError`:
237
+
238
+ ```python
239
+ from nestra import NestraAPIError
240
+
241
+ try:
242
+ await rest.send_message(channel_id, "test")
243
+ except NestraAPIError as error:
244
+ print(error.status, error.code, error.message)
245
+ ```
246
+
247
+ `error.status == 0` oznacza błąd sieci albo niepoprawną odpowiedź JSON.
248
+
249
+ ## Bezpieczeństwo tokenu
250
+
251
+ - Token bota to sekret. Trzymaj go w zmiennej `BOT_TOKEN`, nie w kodzie ani w repozytorium.
252
+ - SDK nie loguje tokenu. `repr(NestraREST(...))` nie zawiera sekretu.
253
+ - Nie wysyłaj tokenu do przeglądarki ani do kodu klienta WWW.
254
+ - W produkcji używaj `https://` i `wss://`; połączenie z tokenem przez `http`/`ws` jest dopuszczalne tylko lokalnie.
255
+ - Przy wycieku tokenu unieważnij klucz w panelu deweloperskim i wygeneruj nowy.
256
+ - Bot powinien mieć tylko uprawnienia potrzebne do działania. Token nie zastępuje kontroli uprawnień po stronie API.
257
+
258
+ ## Zgodność z bot auth
259
+
260
+ Bot auth jest wdrożony w API i Gatewayu:
261
+
262
+ - REST: `Authorization: Bot <token>` i trasy `/api/v1/bot/...`,
263
+ - Gateway: IDENTIFY przyjmuje dokładnie jedno pole, `botToken` albo `token`;
264
+ SDK wysyła `botToken` dla tokenu bota i `token` dla sesji użytkownika,
265
+ - READY bota zawiera `bot: true`, pustą obecność i listę gildii bota.
266
+
267
+ Nie mieszaj tokenu bota i JWT użytkownika w jednym kliencie. `token_type="Bearer"`
268
+ służy do sesji użytkownika, nie do kluczy `nstr_` ani tokenów `nestra_bot_`.
269
+
270
+ ## Testy
271
+
272
+ Z katalogu pakietu:
273
+
274
+ ```powershell
275
+ python -m unittest discover -s tests -v
276
+ python -m compileall -q nestra
277
+ ```
278
+
279
+ Testy jednostkowe nie wymagają uruchomionego API. Test dymny uruchamia się
280
+ tylko z `NESTRA_SMOKE=1` i prawidłowym `BOT_TOKEN`:
281
+
282
+ ```powershell
283
+ $env:NESTRA_SMOKE = "1"
284
+ python -m unittest tests.test_smoke -v
285
+ ```
286
+
287
+ ## Struktura
288
+
289
+ ```text
290
+ integrations/nestra-py/
291
+ nestra/
292
+ __init__.py
293
+ rest.py # REST, rate limit, błędy
294
+ gateway.py # Gateway, heartbeat, reconnect, eventy
295
+ embed.py # EmbedBuilder
296
+ commands.py # CommandHandler
297
+ types.py # modele DTO
298
+ _ws.py # minimalny klient WebSocket
299
+ ```
300
+
301
+ Przykładowy bot znajduje się w `examples/nestra-py-bot` w repozytorium Nestra.
302
+
@@ -0,0 +1,77 @@
1
+ """Nestra-PY - oficjalne SDK Pythona dla API i Gateway Nestra.
2
+
3
+ Pakiet nie ma zaleznosci zewnetrznych: REST korzysta z urllib, Gateway z
4
+ asyncio streams i wlasnej implementacji WebSocket.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from .commands import Command, CommandContext, CommandHandler, ParsedCommand, parse_command
10
+ from .embed import (
11
+ COLORS,
12
+ EMBED_MAX_COUNT,
13
+ EMBED_MAX_TOTAL_CHARS,
14
+ Embed,
15
+ EmbedBuilder,
16
+ parse_color,
17
+ total_characters,
18
+ validate_embeds,
19
+ )
20
+ from .gateway import GatewayClient, GatewayError, GatewayFatalError, ReconnectBackoff
21
+ from .rest import (
22
+ DEFAULT_API_URL,
23
+ NestraAPIError,
24
+ NestraREST,
25
+ RateLimitBucket,
26
+ RateLimitQueue,
27
+ )
28
+ from .types import (
29
+ Attachment,
30
+ Channel,
31
+ Guild,
32
+ GuildDetail,
33
+ Member,
34
+ Message,
35
+ MessagePage,
36
+ Role,
37
+ User,
38
+ Webhook,
39
+ )
40
+
41
+ __version__ = "1.2.2"
42
+
43
+ __all__ = [
44
+ "Attachment",
45
+ "COLORS",
46
+ "Channel",
47
+ "Command",
48
+ "CommandContext",
49
+ "CommandHandler",
50
+ "DEFAULT_API_URL",
51
+ "EMBED_MAX_COUNT",
52
+ "EMBED_MAX_TOTAL_CHARS",
53
+ "Embed",
54
+ "EmbedBuilder",
55
+ "GatewayClient",
56
+ "GatewayError",
57
+ "GatewayFatalError",
58
+ "Guild",
59
+ "GuildDetail",
60
+ "Member",
61
+ "Message",
62
+ "MessagePage",
63
+ "NestraAPIError",
64
+ "NestraREST",
65
+ "ParsedCommand",
66
+ "RateLimitBucket",
67
+ "RateLimitQueue",
68
+ "ReconnectBackoff",
69
+ "Role",
70
+ "User",
71
+ "Webhook",
72
+ "__version__",
73
+ "parse_color",
74
+ "parse_command",
75
+ "total_characters",
76
+ "validate_embeds",
77
+ ]