telekit 2.3.0b2__tar.gz → 2.4.0b0__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.
- {telekit-2.3.0b2 → telekit-2.4.0b0}/PKG-INFO +97 -131
- {telekit-2.3.0b2 → telekit-2.4.0b0}/README.md +45 -3
- {telekit-2.3.0b2 → telekit-2.4.0b0}/setup.py +2 -2
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit/__init__.py +8 -1
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit/_callback_query_handler.py +15 -3
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit/_chain.py +4 -3
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit/_chain_base.py +4 -5
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit/_chain_inline_keyboards_logic.py +2 -2
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit/_init.py +1 -1
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit/_inline_buttons.py +11 -0
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit/_logger.py +46 -41
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit/_state.py +15 -2
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit/_telekit_dsl/mixin.py +478 -4
- telekit-2.4.0b0/telekit/_trait.py +10 -0
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit/_version.py +1 -1
- telekit-2.4.0b0/telekit/debug.py +9 -0
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit/example/example_handlers/__init__.py +2 -1
- telekit-2.4.0b0/telekit/example/example_handlers/calendar.py +155 -0
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit/example/example_handlers/start.py +2 -1
- telekit-2.4.0b0/telekit/scheduler.py +167 -0
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit/senders.py +13 -4
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit/server.py +7 -7
- telekit-2.4.0b0/telekit/traits/__init__.py +3 -0
- telekit-2.4.0b0/telekit/traits/calendar_pick.py +807 -0
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit/traits/paginated_choice.py +1 -1
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit/traits/track_handoff_origin.py +1 -1
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit.egg-info/PKG-INFO +97 -131
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit.egg-info/SOURCES.txt +5 -0
- telekit-2.3.0b2/telekit/traits/__init__.py +0 -2
- {telekit-2.3.0b2 → telekit-2.4.0b0}/LICENSE +0 -0
- {telekit-2.3.0b2 → telekit-2.4.0b0}/setup.cfg +0 -0
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit/_buildtext/__init__.py +0 -0
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit/_buildtext/formatter.py +0 -0
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit/_buildtext/styles.py +0 -0
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit/_chain_entry_logic.py +0 -0
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit/_chapters/__init__.py +0 -0
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit/_chapters/chapters.py +0 -0
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit/_handler.py +0 -0
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit/_input_handler.py +0 -0
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit/_on.py +0 -0
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit/_snapvault/__init__.py +0 -0
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit/_snapvault/snapcode.py +0 -0
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit/_snapvault/snapvault.py +0 -0
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit/_telekit_dsl/__init__.py +0 -0
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit/_telekit_dsl/parser/__init__.py +0 -0
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit/_telekit_dsl/parser/builder.py +0 -0
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit/_telekit_dsl/parser/canvas_parser.py +0 -0
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit/_telekit_dsl/parser/lexer.py +0 -0
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit/_telekit_dsl/parser/nodes.py +0 -0
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit/_telekit_dsl/parser/parser.py +0 -0
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit/_telekit_dsl/parser/token.py +0 -0
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit/_telekit_dsl/telekit_dsl.py +0 -0
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit/_telekit_dsl/telekit_orm.py +0 -0
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit/_timeout.py +0 -0
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit/_user.py +0 -0
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit/dices.py +0 -0
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit/example/__init__.py +0 -0
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit/example/example_handlers/complete_hotel.py +0 -0
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit/example/example_handlers/counter.py +0 -0
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit/example/example_handlers/dsl.py +0 -0
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit/example/example_handlers/entry.py +0 -0
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit/example/example_handlers/faq.py +0 -0
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit/example/example_handlers/hotel.py +0 -0
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit/example/example_handlers/on_text.py +0 -0
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit/example/example_handlers/pages.py +0 -0
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit/example/example_handlers/pyapi.py +0 -0
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit/example/example_handlers/qr.py +0 -0
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit/example/example_handlers/quiz.py +0 -0
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit/example/example_handlers/spells.py +0 -0
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit/example/example_handlers/text_document.py +0 -0
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit/example/example_server.py +0 -0
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit/inline_buttons.py +0 -0
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit/parameters.py +0 -0
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit/styles.py +0 -0
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit/types.py +0 -0
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit/utils.py +0 -0
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit.egg-info/dependency_links.txt +0 -0
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit.egg-info/requires.txt +0 -0
- {telekit-2.3.0b2 → telekit-2.4.0b0}/telekit.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: telekit
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4.0b0
|
|
4
4
|
Summary: Declarative, developer-friendly library for building Telegram bots
|
|
5
5
|
Home-page: https://github.com/Romashkaa/telekit
|
|
6
6
|
Author: romashka
|
|
@@ -77,7 +77,7 @@ Telekit comes with a [built-in DSL](https://github.com/Romashkaa/telekit/blob/ma
|
|
|
77
77
|
|
|
78
78
|
> See the [full example](https://github.com/Romashkaa/telekit/blob/main/docs/examples/quiz.md)
|
|
79
79
|
|
|
80
|
-
Even in its beta stage, Telekit accelerates bot development, offering typed command parameters
|
|
80
|
+
Even in its beta stage, Telekit accelerates bot development, offering typed **command parameters**, **text styling** via `Bold()`, `Italic()`, a built-in declarative **calendar picker**, emoji **game results** for `🎲 🎯 🏀 ⚽ 🎳 🎰`, and much more out of the box. Its declarative design makes bots easier to read, maintain, and extend.
|
|
81
81
|
|
|
82
82
|
**Key features:**
|
|
83
83
|
- Declarative bot logic with **chains** for effortless handling of complex conversations
|
|
@@ -85,6 +85,7 @@ Even in its beta stage, Telekit accelerates bot development, offering typed comm
|
|
|
85
85
|
- Automatic handling of [message formatting](https://github.com/Romashkaa/telekit/blob/main/docs/tutorial2/6_styles.md) via [Sender](https://github.com/Romashkaa/telekit/blob/main/docs/tutorial2/5_senders.md) and **callback routing**
|
|
86
86
|
- **Deep Linking** support with type-checked [Command Parameters](https://github.com/Romashkaa/telekit/blob/main/docs/tutorial2/command_trigger_parameters.md) for flexible user input
|
|
87
87
|
- Built-in **Permission** and **Logging** system for user management
|
|
88
|
+
- Reusable **Traits** system for pluggable, self-contained behavior modules
|
|
88
89
|
- Seamless integration with [pyTelegramBotAPI](https://github.com/eternnoir/pyTelegramBotAPI)
|
|
89
90
|
- Fast to develop and easy-to-extend code
|
|
90
91
|
|
|
@@ -340,14 +341,54 @@ telekit.Server(BOT_TOKEN).polling()
|
|
|
340
341
|
<summary>Click to see what you can do with the DSL</summary>
|
|
341
342
|
<table>
|
|
342
343
|
<tr>
|
|
343
|
-
<td><img src="
|
|
344
|
-
<td><img src="
|
|
344
|
+
<td><img src="./docs/images/telekit_example_7.jpg" alt="Telekit Example 7" width="300"></td>
|
|
345
|
+
<td><img src="./docs/images/telekit_example_8.jpg" alt="Telekit Example 8" width="300"></td>
|
|
346
|
+
</tr>
|
|
347
|
+
<tr>
|
|
348
|
+
<td><img src="./docs/images/telekit_example_6.jpg" alt="Telekit Example 7" width="300"></td>
|
|
349
|
+
<td><img src="./docs/images/telekit_example_1.jpg" alt="Telekit Example 8" width="300"></td>
|
|
345
350
|
</tr>
|
|
346
351
|
</table>
|
|
347
352
|
</details>
|
|
348
353
|
|
|
349
354
|
You can find a [full quiz example](https://github.com/Romashkaa/telekit/blob/main/docs/examples/complete_hotel.md) and [DSL reference](https://github.com/Romashkaa/telekit/blob/main/docs/tutorial/11_telekit_dsl.md) in the repository.
|
|
350
355
|
|
|
356
|
+
### Traits
|
|
357
|
+
|
|
358
|
+
Traits are reusable behavior modules you can mix into any handler.
|
|
359
|
+
|
|
360
|
+
This example demonstrates the simplest way to use the built-in CalendarPick trait. It allows a user to pick a date from an inline calendar and handles the result via a callback.
|
|
361
|
+
|
|
362
|
+
```py
|
|
363
|
+
from telekit.traits import CalendarPick
|
|
364
|
+
|
|
365
|
+
class CalendarHandler(CalendarPick, telekit.Handler):
|
|
366
|
+
|
|
367
|
+
@classmethod
|
|
368
|
+
def init_handler(cls) -> None:
|
|
369
|
+
cls.on.command("calendar").invoke(cls.handle)
|
|
370
|
+
|
|
371
|
+
def handle(self) -> None:
|
|
372
|
+
self.chain.sender.set_title("📅 Choose a date")
|
|
373
|
+
self.chain.sender.set_message("Select any date — past or future:")
|
|
374
|
+
self.chain.sender.set_remove_text(False)
|
|
375
|
+
|
|
376
|
+
self.calendar_pick(self.handle_date) # HERE
|
|
377
|
+
|
|
378
|
+
def handle_date(self, date: datetime.date) -> None:
|
|
379
|
+
self.chain.sender.set_text(f"You picked: {date}")
|
|
380
|
+
self.chain.send()
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
<details>
|
|
384
|
+
<summary>Result</summary>
|
|
385
|
+
<table>
|
|
386
|
+
<tr>
|
|
387
|
+
<td><img src="./docs/images/calendar.png" alt="Telekit Calendar Example" width="500"></td>
|
|
388
|
+
</tr>
|
|
389
|
+
</table>
|
|
390
|
+
</details>
|
|
391
|
+
|
|
351
392
|
### Example Bot
|
|
352
393
|
|
|
353
394
|
You can launch an example bot by **running the following code**:
|
|
@@ -368,6 +409,7 @@ It includes example commands, dialogs, keyboards, and style usage.
|
|
|
368
409
|
- **Styles API** for rich text (`Bold`, `Italic`, `Links`) with **automatic escaping**.
|
|
369
410
|
- Deep linking and **typed command parameters**.
|
|
370
411
|
- **Built-in DSL** for menus, FAQs, and simple bots.
|
|
412
|
+
- Reusable **Traits** for composable, plug-and-play behavior (for example, a built-in declarative calendar picker).
|
|
371
413
|
- **Zero-code** [Obsidian Canvas](https://github.com/Romashkaa/telekit/blob/main/docs/examples/canvas_faq.md) mode.
|
|
372
414
|
- Seamless integration with **pyTelegramBotAPI**.
|
|
373
415
|
|
|
@@ -379,148 +421,72 @@ It tries to make Telegram bot development easier.
|
|
|
379
421
|
|
|
380
422
|
---
|
|
381
423
|
|
|
382
|
-
# Changes in version 2.
|
|
383
|
-
|
|
384
|
-
## Inline Buttons
|
|
385
|
-
|
|
386
|
-
| **Name** | **Description** |
|
|
387
|
-
| ---------------- | ------------------------------------------------------------------------------- |
|
|
388
|
-
| `StaticButton` | A non-interactive inline button that performs no action when pressed. |
|
|
389
|
-
| `AnswerButton` | A button that responds to a callback query with a notification or alert without executing custom logic. |
|
|
390
|
-
|
|
391
|
-
- `AnswerButton` and its subclasses (`AlertButton`, `NotificationButton`) now support the `persistent` parameter (Defauts to `True`):
|
|
392
|
-
- `True` — non-blocking hint (does not affect further interactions)
|
|
393
|
-
- `False` — terminates interaction after click
|
|
424
|
+
# Changes in version 2.4.0b0
|
|
394
425
|
|
|
395
|
-
##
|
|
426
|
+
## Scheduler
|
|
396
427
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
| `TrackHandoffOrigin` | Tracks which handler transferred control to this one via `handoff()`. |
|
|
400
|
-
| `PaginatedChoice` | Adds a paginated inline keyboard for choosing from a list of items. |
|
|
428
|
+
- Added `every` decorator for scheduling functions in a background daemon thread.
|
|
429
|
+
- Implemented `PeriodicTask` class to manage periodic execution and error handling.
|
|
401
430
|
|
|
402
|
-
|
|
431
|
+
## Telekit DSL
|
|
403
432
|
|
|
404
|
-
`
|
|
433
|
+
### `InstanceDSLHandler`
|
|
405
434
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
| `is_handed_off` | `True` if this handler was reached via `handoff()`, `False` otherwise. |
|
|
410
|
-
| `handoff_back()` | Transfer control back to the origin handler via `self.handoff_origin.handle()` |
|
|
411
|
-
| `handoff_back_or(handler)` | Like `handoff_back()`, but falls back to `handler` on fail. |
|
|
435
|
+
Instance-oriented variant of `DSLHandler` where each instance carries its own
|
|
436
|
+
`executable_model`, `_script_data_factory`, and `_jinja_env` — allowing multiple
|
|
437
|
+
instances to run completely independent scripts simultaneously.
|
|
412
438
|
|
|
413
|
-
|
|
439
|
+
Use the `*_locally` instance methods instead of the class-level ones:
|
|
414
440
|
|
|
415
441
|
```python
|
|
416
|
-
class MyHandler(
|
|
442
|
+
class MyHandler(telekit.InstanceDSLHandler):
|
|
443
|
+
@classmethod
|
|
444
|
+
def init_handler(cls) -> None:
|
|
445
|
+
cls.on.message().invoke(cls.handle)
|
|
417
446
|
|
|
418
447
|
def handle(self):
|
|
419
|
-
|
|
420
|
-
self.
|
|
421
|
-
|
|
422
|
-
})
|
|
423
|
-
self.chain.edit()
|
|
424
|
-
```
|
|
425
|
-
|
|
426
|
-
### PaginatedChoice
|
|
427
|
-
|
|
428
|
-
Renders a paginated inline keyboard from any dict or iterable.
|
|
429
|
-
Navigation buttons (`« Back`, `Next »`) are added automatically.
|
|
430
|
-
|
|
431
|
-
| **Member** | **Description** |
|
|
432
|
-
| -------------------- | ---------------------------------------------------------------------------- |
|
|
433
|
-
| `paginated_choice(choices, on_choice, ...)` | Display a paginated choice keyboard. |
|
|
434
|
-
| `PAGINATED_CHOICE_BACK_LABEL` | Label for the back navigation button. Defaults to `« Back`. |
|
|
435
|
-
| `PAGINATED_CHOICE_NEXT_LABEL` | Label for the next navigation button. Defaults to `Next »`. |
|
|
436
|
-
| `PAGINATED_CHOICE_PAGE_LABEL` | Label template for the page indicator button. Supports `{page}` and `{pages}` placeholders. Set to `None` to hide. Defaults to `{page} / {pages}`. |
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
```python
|
|
440
|
-
self.chain.sender.set_title("🔤 What is your initial?")
|
|
441
|
-
self.chain.sender.set_message("Pick the first letter of your name")
|
|
442
|
-
self.chain.sender.set_remove_text(False)
|
|
443
|
-
|
|
444
|
-
self.paginated_choice(
|
|
445
|
-
choices="ABCDEFGHIJKLMNOPQRSTUVWXYZ",
|
|
446
|
-
on_choice=self.handle_letter,
|
|
447
|
-
row_width=5
|
|
448
|
-
)
|
|
448
|
+
script = fetch_script_from_db(self.user.id) # per-user DSL
|
|
449
|
+
self.analyze_string_locally(script)
|
|
450
|
+
self.start_script()
|
|
449
451
|
```
|
|
450
452
|
|
|
451
|
-
|
|
453
|
+
| **Method** | **Description** |
|
|
454
|
+
| --------------------------------------- | ---------------------------------------------------- |
|
|
455
|
+
| `analyze_file_locally(path, encoding)` | Analyse a script file on this instance. |
|
|
456
|
+
| `analyze_string_locally(script)` | Analyse a DSL string on this instance. |
|
|
457
|
+
| `analyze_canvas_locally(file_path)` | Analyse an Obsidian `.canvas` file on this instance. |
|
|
458
|
+
| `analyze_executable_model_locally(model)` | Load a pre-built model dict on this instance. |
|
|
452
459
|
|
|
453
|
-
|
|
454
|
-
class MyHandler(PaginatedChoice, telekit.Handler):
|
|
455
|
-
PAGINATED_CHOICE_BACK_LABEL = "⬅️ Назад"
|
|
456
|
-
PAGINATED_CHOICE_NEXT_LABEL = "Далі ➡️"
|
|
457
|
-
PAGINATED_CHOICE_PAGE_LABEL = None # hide page indicator
|
|
458
|
-
```
|
|
460
|
+
**Security**
|
|
459
461
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
<tr>
|
|
464
|
-
<td><img src="https://github.com/Romashkaa/telekit/blob/main/docs/images/paginated_choice.png?raw=true" alt="Example" width="300"></td>
|
|
465
|
-
</tr>
|
|
466
|
-
</table>
|
|
467
|
-
</details>
|
|
468
|
-
|
|
469
|
-
`choices` accepts a `dict[str, T]`, or any `Iterable[T]`.
|
|
470
|
-
If only one item is present, `on_choice` is called immediately without rendering a keyboard.
|
|
471
|
-
|
|
472
|
-
## Utils
|
|
473
|
-
|
|
474
|
-
| **Name** | **Description** |
|
|
475
|
-
| ------------------ | ------------------------------------------------------------------ |
|
|
476
|
-
| `load_env` | Load all key-value pairs from a `.env` file into a dictionary. |
|
|
477
|
-
| `read_env_var` | Read a single variable by name from a `.env` file. |
|
|
478
|
-
| `compose_keyboard` | Merge multiple button groups into a single inline keyboard with computed `row_width`. |
|
|
479
|
-
|
|
480
|
-
### Environment Utils
|
|
481
|
-
|
|
482
|
-
- `read_token` and `read_canvas_path` now support reading from `.env` files.
|
|
483
|
-
Pass `".env"` to use the default key, or `".env:KEY"` to specify a custom one:
|
|
462
|
+
When accepting scripts from untrusted users, restrict dangerous features via the
|
|
463
|
+
`RESTRICTED` class attribute. Set `DEFAULT_TIMEOUT` to control the fallback timeout,
|
|
464
|
+
and `DEFAULT_CONFIG` to provide a safe base config.
|
|
484
465
|
|
|
485
466
|
```python
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
read_canvas_path(".env:MY_CANVAS") # reads MY_CANVAS
|
|
467
|
+
class SafeDSL(telekit.InstanceDSLHandler):
|
|
468
|
+
RESTRICTED: list[RestrictedToken] = ["hook", "jinja", "redirect", "handoff", "config"]
|
|
469
|
+
DEFAULT_TIMEOUT = 120
|
|
470
|
+
DEFAULT_CONFIG = {"template": "vars"}
|
|
491
471
|
```
|
|
492
472
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
`
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
|
501
|
-
|
|
502
|
-
| `
|
|
503
|
-
| `
|
|
504
|
-
|
|
505
|
-
|
|
|
506
|
-
|
|
507
|
-
| `
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
# | 🆕 Create |
|
|
514
|
-
# | 1 | 2 | 3 |
|
|
515
|
-
# | 4 | 5 | 6 |
|
|
516
|
-
# | 7 | 8 | 9 |
|
|
517
|
-
# | « Back | Next » |
|
|
518
|
-
|
|
519
|
-
keyboard, row_width = compose_keyboard(
|
|
520
|
-
{"🆕 Create": "create"},
|
|
521
|
-
{str(n): str(n) for n in range(1, 10)},
|
|
522
|
-
{"« Back": "back", "Next »": "next"},
|
|
523
|
-
widths=(-1, 3, -1), # or (1, 3, 2)
|
|
524
|
-
)
|
|
525
|
-
self.chain.set_inline_choice(keyboard, row_width)
|
|
526
|
-
```
|
|
473
|
+
| **Token** | **Effect** |
|
|
474
|
+
| -------------- | ----------------------------------------------------------------------------------------------- |
|
|
475
|
+
| `"handoff"` | Disables `handoff` button type (cross-handler transitions). |
|
|
476
|
+
| `"redirect"` | Disables `redirect` button type (simulated user messages). |
|
|
477
|
+
| `"hook"` | Removes all `on_enter`, `on_enter_once`, `on_exit`, `on_timeout` hooks. |
|
|
478
|
+
| `"jinja"` | Forces template engine to `"vars"`; Jinja is never executed. |
|
|
479
|
+
| `"timeout"` | Ignores per-script `timeout_time`; uses `DEFAULT_TIMEOUT` only. |
|
|
480
|
+
| `"config"` | Replaces script config with `DEFAULT_CONFIG`; `vars_*` keys are preserved unless `"vars"` is also set. |
|
|
481
|
+
| `"vars"` | Removes all `vars_*` keys and disables `{{variable}}` substitution. |
|
|
482
|
+
| `"images"` | Strips `image` field from every scene. |
|
|
483
|
+
| `"links"` | Disables `link` button type (external URLs). |
|
|
484
|
+
| `"suggest"` | Disables `suggest` button type (pre-filled entry suggestions). |
|
|
485
|
+
| `"entry"` | Disables entry handlers (free-text input routing). |
|
|
486
|
+
| `"next"` | Disables `next` magic scene navigation. |
|
|
487
|
+
| `"back"` | Disables `back` magic scene navigation. |
|
|
488
|
+
|
|
489
|
+
## Others
|
|
490
|
+
|
|
491
|
+
- Enhanced `Debug` class with callback query tracing functionality.
|
|
492
|
+
- Refactored `BaseSender` to use `send_or_handle_error`.
|
|
@@ -41,7 +41,7 @@ Telekit comes with a [built-in DSL](https://github.com/Romashkaa/telekit/blob/ma
|
|
|
41
41
|
|
|
42
42
|
> See the [full example](https://github.com/Romashkaa/telekit/blob/main/docs/examples/quiz.md)
|
|
43
43
|
|
|
44
|
-
Even in its beta stage, Telekit accelerates bot development, offering typed command parameters
|
|
44
|
+
Even in its beta stage, Telekit accelerates bot development, offering typed **command parameters**, **text styling** via `Bold()`, `Italic()`, a built-in declarative **calendar picker**, emoji **game results** for `🎲 🎯 🏀 ⚽ 🎳 🎰`, and much more out of the box. Its declarative design makes bots easier to read, maintain, and extend.
|
|
45
45
|
|
|
46
46
|
**Key features:**
|
|
47
47
|
- Declarative bot logic with **chains** for effortless handling of complex conversations
|
|
@@ -49,6 +49,7 @@ Even in its beta stage, Telekit accelerates bot development, offering typed comm
|
|
|
49
49
|
- Automatic handling of [message formatting](https://github.com/Romashkaa/telekit/blob/main/docs/tutorial2/6_styles.md) via [Sender](https://github.com/Romashkaa/telekit/blob/main/docs/tutorial2/5_senders.md) and **callback routing**
|
|
50
50
|
- **Deep Linking** support with type-checked [Command Parameters](https://github.com/Romashkaa/telekit/blob/main/docs/tutorial2/command_trigger_parameters.md) for flexible user input
|
|
51
51
|
- Built-in **Permission** and **Logging** system for user management
|
|
52
|
+
- Reusable **Traits** system for pluggable, self-contained behavior modules
|
|
52
53
|
- Seamless integration with [pyTelegramBotAPI](https://github.com/eternnoir/pyTelegramBotAPI)
|
|
53
54
|
- Fast to develop and easy-to-extend code
|
|
54
55
|
|
|
@@ -304,14 +305,54 @@ telekit.Server(BOT_TOKEN).polling()
|
|
|
304
305
|
<summary>Click to see what you can do with the DSL</summary>
|
|
305
306
|
<table>
|
|
306
307
|
<tr>
|
|
307
|
-
<td><img src="
|
|
308
|
-
<td><img src="
|
|
308
|
+
<td><img src="./docs/images/telekit_example_7.jpg" alt="Telekit Example 7" width="300"></td>
|
|
309
|
+
<td><img src="./docs/images/telekit_example_8.jpg" alt="Telekit Example 8" width="300"></td>
|
|
310
|
+
</tr>
|
|
311
|
+
<tr>
|
|
312
|
+
<td><img src="./docs/images/telekit_example_6.jpg" alt="Telekit Example 7" width="300"></td>
|
|
313
|
+
<td><img src="./docs/images/telekit_example_1.jpg" alt="Telekit Example 8" width="300"></td>
|
|
309
314
|
</tr>
|
|
310
315
|
</table>
|
|
311
316
|
</details>
|
|
312
317
|
|
|
313
318
|
You can find a [full quiz example](https://github.com/Romashkaa/telekit/blob/main/docs/examples/complete_hotel.md) and [DSL reference](https://github.com/Romashkaa/telekit/blob/main/docs/tutorial/11_telekit_dsl.md) in the repository.
|
|
314
319
|
|
|
320
|
+
### Traits
|
|
321
|
+
|
|
322
|
+
Traits are reusable behavior modules you can mix into any handler.
|
|
323
|
+
|
|
324
|
+
This example demonstrates the simplest way to use the built-in CalendarPick trait. It allows a user to pick a date from an inline calendar and handles the result via a callback.
|
|
325
|
+
|
|
326
|
+
```py
|
|
327
|
+
from telekit.traits import CalendarPick
|
|
328
|
+
|
|
329
|
+
class CalendarHandler(CalendarPick, telekit.Handler):
|
|
330
|
+
|
|
331
|
+
@classmethod
|
|
332
|
+
def init_handler(cls) -> None:
|
|
333
|
+
cls.on.command("calendar").invoke(cls.handle)
|
|
334
|
+
|
|
335
|
+
def handle(self) -> None:
|
|
336
|
+
self.chain.sender.set_title("📅 Choose a date")
|
|
337
|
+
self.chain.sender.set_message("Select any date — past or future:")
|
|
338
|
+
self.chain.sender.set_remove_text(False)
|
|
339
|
+
|
|
340
|
+
self.calendar_pick(self.handle_date) # HERE
|
|
341
|
+
|
|
342
|
+
def handle_date(self, date: datetime.date) -> None:
|
|
343
|
+
self.chain.sender.set_text(f"You picked: {date}")
|
|
344
|
+
self.chain.send()
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
<details>
|
|
348
|
+
<summary>Result</summary>
|
|
349
|
+
<table>
|
|
350
|
+
<tr>
|
|
351
|
+
<td><img src="./docs/images/calendar.png" alt="Telekit Calendar Example" width="500"></td>
|
|
352
|
+
</tr>
|
|
353
|
+
</table>
|
|
354
|
+
</details>
|
|
355
|
+
|
|
315
356
|
### Example Bot
|
|
316
357
|
|
|
317
358
|
You can launch an example bot by **running the following code**:
|
|
@@ -332,6 +373,7 @@ It includes example commands, dialogs, keyboards, and style usage.
|
|
|
332
373
|
- **Styles API** for rich text (`Bold`, `Italic`, `Links`) with **automatic escaping**.
|
|
333
374
|
- Deep linking and **typed command parameters**.
|
|
334
375
|
- **Built-in DSL** for menus, FAQs, and simple bots.
|
|
376
|
+
- Reusable **Traits** for composable, plug-and-play behavior (for example, a built-in declarative calendar picker).
|
|
335
377
|
- **Zero-code** [Obsidian Canvas](https://github.com/Romashkaa/telekit/blob/main/docs/examples/canvas_faq.md) mode.
|
|
336
378
|
- Seamless integration with **pyTelegramBotAPI**.
|
|
337
379
|
|
|
@@ -26,11 +26,11 @@ remove_cache()
|
|
|
26
26
|
def readme():
|
|
27
27
|
with open('README.md', 'r') as f:
|
|
28
28
|
return f.read()
|
|
29
|
-
|
|
29
|
+
|
|
30
30
|
def changelog():
|
|
31
31
|
with open('CHANGELOG.md', 'r') as f:
|
|
32
32
|
return f.read()
|
|
33
|
-
|
|
33
|
+
|
|
34
34
|
def install_requires():
|
|
35
35
|
with open('telekit/requirements.txt', 'r') as f:
|
|
36
36
|
return f.read().split("\n")
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
#
|
|
19
19
|
|
|
20
20
|
from ._handler import Handler
|
|
21
|
+
from ._trait import Trait
|
|
21
22
|
from ._chain import Chain
|
|
22
23
|
from ._callback_query_handler import CallbackQueryHandler
|
|
23
24
|
from .server import Server, example
|
|
@@ -25,7 +26,7 @@ from ._snapvault import Vault
|
|
|
25
26
|
from ._chapters import chapters
|
|
26
27
|
from ._user import User
|
|
27
28
|
from ._telekit_dsl.telekit_dsl import TelekitDSL
|
|
28
|
-
from ._telekit_dsl.mixin import DSLHandler
|
|
29
|
+
from ._telekit_dsl.mixin import DSLHandler, InstanceDSLHandler
|
|
29
30
|
from ._logger import enable_file_logging
|
|
30
31
|
|
|
31
32
|
from . import senders
|
|
@@ -36,6 +37,8 @@ from . import inline_buttons
|
|
|
36
37
|
from . import dices
|
|
37
38
|
from . import utils
|
|
38
39
|
from . import traits
|
|
40
|
+
from . import debug
|
|
41
|
+
from . import scheduler
|
|
39
42
|
|
|
40
43
|
Styles = styles.Styles
|
|
41
44
|
|
|
@@ -50,20 +53,24 @@ __all__ = [
|
|
|
50
53
|
"parameters",
|
|
51
54
|
"inline_buttons",
|
|
52
55
|
"dices",
|
|
56
|
+
"scheduler",
|
|
53
57
|
|
|
54
58
|
"Styles",
|
|
55
59
|
"User",
|
|
56
60
|
|
|
57
61
|
"Server",
|
|
58
62
|
"Chain",
|
|
63
|
+
"Trait",
|
|
59
64
|
"Handler",
|
|
60
65
|
"CallbackQueryHandler",
|
|
61
66
|
|
|
62
67
|
"TelekitDSL",
|
|
63
68
|
"DSLHandler",
|
|
69
|
+
"InstanceDSLHandler",
|
|
64
70
|
|
|
65
71
|
"Vault",
|
|
66
72
|
"enable_file_logging",
|
|
67
73
|
"chapters",
|
|
68
74
|
"example",
|
|
75
|
+
"debug",
|
|
69
76
|
]
|
|
@@ -26,6 +26,9 @@ from telebot.types import (
|
|
|
26
26
|
CallbackQuery
|
|
27
27
|
)
|
|
28
28
|
|
|
29
|
+
from .debug import Debug
|
|
30
|
+
from ._logger import _library
|
|
31
|
+
|
|
29
32
|
|
|
30
33
|
class CallbackQueryHandler:
|
|
31
34
|
|
|
@@ -44,9 +47,18 @@ class CallbackQueryHandler:
|
|
|
44
47
|
|
|
45
48
|
@bot.callback_query_handler(func=lambda call: True)
|
|
46
49
|
def handle(call: CallbackQuery) -> None:
|
|
50
|
+
if Debug.callback_query_tracing:
|
|
51
|
+
_library.info(
|
|
52
|
+
"CallbackQuery("
|
|
53
|
+
f"user_id={call.from_user.id}, "
|
|
54
|
+
f"data={call.data!r},\n"
|
|
55
|
+
f"message_text={call.message.text if call.message else None!r}"
|
|
56
|
+
f") - {Debug.callback_query_tracing=}"
|
|
57
|
+
)
|
|
58
|
+
|
|
47
59
|
if not call.data:
|
|
48
60
|
return
|
|
49
|
-
|
|
61
|
+
|
|
50
62
|
if call.data.startswith(cls.INLINE_BUTTON):
|
|
51
63
|
cls._handle_inline_button(call)
|
|
52
64
|
elif call.data.startswith(cls.STATIC_BUTTON):
|
|
@@ -180,11 +192,11 @@ class CallbackQueryHandler:
|
|
|
180
192
|
|
|
181
193
|
# (text: str, is_alert: bool)
|
|
182
194
|
|
|
183
|
-
_invalid_data_answer: tuple[str, bool] = ("Invalid Call Data
|
|
195
|
+
_invalid_data_answer: tuple[str, bool] = ("Invalid Call Data", True)
|
|
184
196
|
_button_is_no_active_answer: tuple[str, bool] = ("Button is no longer active", True)
|
|
185
197
|
|
|
186
198
|
@classmethod
|
|
187
|
-
def set_invalid_data_answer(cls, answer: str="Invalid Call Data
|
|
199
|
+
def set_invalid_data_answer(cls, answer: str="Invalid Call Data", is_alert: bool=True):
|
|
188
200
|
"""
|
|
189
201
|
Sets the response message and type for cases when the received callback data is invalid or empty.
|
|
190
202
|
|
|
@@ -26,10 +26,11 @@ from telebot.types import Message
|
|
|
26
26
|
|
|
27
27
|
# Local modules
|
|
28
28
|
from . import senders
|
|
29
|
+
from .debug import Debug
|
|
29
30
|
from .styles import TextEntity
|
|
30
31
|
|
|
31
32
|
# Chain modules
|
|
32
|
-
from ._chain_base import
|
|
33
|
+
from ._chain_base import _library
|
|
33
34
|
from ._chain_inline_keyboards_logic import ChainInlineKeyboardLogic
|
|
34
35
|
from ._chain_entry_logic import ChainEntryLogic, TextDocument
|
|
35
36
|
|
|
@@ -154,8 +155,8 @@ class Chain(ChainInlineKeyboardLogic, ChainEntryLogic):
|
|
|
154
155
|
self.sender.set_edit_message(None)
|
|
155
156
|
self._previous_message = message
|
|
156
157
|
|
|
157
|
-
if
|
|
158
|
-
|
|
158
|
+
if Debug.timeout_warnings and _handler and not _timeout:
|
|
159
|
+
_library.warning(
|
|
159
160
|
"Next-message handler is active, but no timeout was set for the chain. "
|
|
160
161
|
"This may cause the bot to wait indefinitely."
|
|
161
162
|
)
|
|
@@ -31,14 +31,12 @@ from . import _input_handler
|
|
|
31
31
|
from . import _timeout
|
|
32
32
|
|
|
33
33
|
# Logging
|
|
34
|
-
from .
|
|
35
|
-
|
|
34
|
+
from .debug import Debug
|
|
35
|
+
from ._logger import _library as _library
|
|
36
36
|
|
|
37
37
|
class ChainBase:
|
|
38
38
|
|
|
39
39
|
bot: telebot.TeleBot
|
|
40
|
-
|
|
41
|
-
_timeout_warnings_enabled: bool = True
|
|
42
40
|
|
|
43
41
|
@classmethod
|
|
44
42
|
def _init(cls, bot: telebot.TeleBot):
|
|
@@ -206,4 +204,5 @@ class ChainBase:
|
|
|
206
204
|
# Timeout API
|
|
207
205
|
|
|
208
206
|
def disable_timeout_warnings(self, value: bool = True) -> None:
|
|
209
|
-
|
|
207
|
+
""".. deprecated:: Set ``Debug.timeout_warnings`` directly."""
|
|
208
|
+
Debug.timeout_warnings = not value
|
|
@@ -32,7 +32,7 @@ from telebot.types import (
|
|
|
32
32
|
|
|
33
33
|
from ._callback_query_handler import CallbackQueryHandler
|
|
34
34
|
from ._inline_buttons import InlineButton, CallbackButton
|
|
35
|
-
from ._chain_base import ChainBase,
|
|
35
|
+
from ._chain_base import ChainBase, _library
|
|
36
36
|
|
|
37
37
|
if typing.TYPE_CHECKING:
|
|
38
38
|
from ._chain import Chain # only for type hints
|
|
@@ -249,7 +249,7 @@ class ChainInlineKeyboardLogic(ChainBase):
|
|
|
249
249
|
if not isinstance(choices, dict):
|
|
250
250
|
for c in choices:
|
|
251
251
|
if type(c).__str__ is object.__str__:
|
|
252
|
-
|
|
252
|
+
_library.warning(
|
|
253
253
|
f"{type(c).__name__} does not implement __str__. "
|
|
254
254
|
f"Consider passing a dict with explicit labels.",
|
|
255
255
|
stacklevel=3
|
|
@@ -26,6 +26,8 @@ from telebot.types import InlineKeyboardButton, CallbackQuery
|
|
|
26
26
|
from telebot import TeleBot
|
|
27
27
|
from ._callback_query_handler import CallbackQueryHandler
|
|
28
28
|
|
|
29
|
+
from ._state import TelekitState
|
|
30
|
+
|
|
29
31
|
__all__ = [
|
|
30
32
|
"InlineButton",
|
|
31
33
|
|
|
@@ -122,6 +124,15 @@ class InlineButton:
|
|
|
122
124
|
raise ValueError(f"Unknown style: {style!r}. Must be one of {_BUTTON_STYLES_LIST}")
|
|
123
125
|
raise TypeError(f"Style must be str, ButtonStyle, or None, got {type(style)}")
|
|
124
126
|
|
|
127
|
+
def _normalize_custom_emoji_id(self, custom_emoji_id: int | str | None) -> str | None:
|
|
128
|
+
if not custom_emoji_id:
|
|
129
|
+
return None
|
|
130
|
+
|
|
131
|
+
if not TelekitState.is_premium():
|
|
132
|
+
return None
|
|
133
|
+
|
|
134
|
+
return str(custom_emoji_id)
|
|
135
|
+
|
|
125
136
|
class StaticButton(InlineButton):
|
|
126
137
|
|
|
127
138
|
def __init__(self, *, style: str | None | ButtonStyle = None, **kwargs):
|