telekit 2.3.0b3__tar.gz → 2.4.0b1__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.0b3 → telekit-2.4.0b1}/PKG-INFO +246 -6
- telekit-2.3.0b3/telekit.egg-info/PKG-INFO → telekit-2.4.0b1/README.md +46 -46
- {telekit-2.3.0b3 → telekit-2.4.0b1}/setup.py +4 -3
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/__init__.py +9 -1
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/_callback_query_handler.py +15 -3
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/_chain.py +2 -2
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/_chain_base.py +1 -2
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/_chain_inline_keyboards_logic.py +71 -2
- telekit-2.4.0b1/telekit/_inline_keyboard.py +545 -0
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/_input_handler.py +3 -2
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/_logger.py +46 -41
- telekit-2.4.0b1/telekit/_reply_keyboard.py +463 -0
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/_telekit_dsl/mixin.py +478 -4
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/_version.py +1 -1
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/debug.py +1 -0
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/example/example_handlers/calendar.py +11 -79
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/inline_buttons.py +4 -0
- telekit-2.4.0b1/telekit/reply_buttons.py +254 -0
- telekit-2.4.0b1/telekit/scheduler.py +167 -0
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/senders.py +24 -6
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/server.py +4 -6
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/types.py +31 -1
- telekit-2.4.0b1/telekit.egg-info/PKG-INFO +624 -0
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit.egg-info/SOURCES.txt +4 -0
- telekit-2.3.0b3/README.md +0 -342
- {telekit-2.3.0b3 → telekit-2.4.0b1}/LICENSE +0 -0
- {telekit-2.3.0b3 → telekit-2.4.0b1}/setup.cfg +0 -0
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/_buildtext/__init__.py +0 -0
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/_buildtext/formatter.py +0 -0
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/_buildtext/styles.py +0 -0
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/_chain_entry_logic.py +0 -0
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/_chapters/__init__.py +0 -0
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/_chapters/chapters.py +0 -0
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/_handler.py +0 -0
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/_init.py +0 -0
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/_inline_buttons.py +0 -0
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/_on.py +0 -0
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/_snapvault/__init__.py +0 -0
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/_snapvault/snapcode.py +0 -0
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/_snapvault/snapvault.py +0 -0
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/_state.py +0 -0
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/_telekit_dsl/__init__.py +0 -0
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/_telekit_dsl/parser/__init__.py +0 -0
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/_telekit_dsl/parser/builder.py +0 -0
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/_telekit_dsl/parser/canvas_parser.py +0 -0
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/_telekit_dsl/parser/lexer.py +0 -0
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/_telekit_dsl/parser/nodes.py +0 -0
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/_telekit_dsl/parser/parser.py +0 -0
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/_telekit_dsl/parser/token.py +0 -0
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/_telekit_dsl/telekit_dsl.py +0 -0
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/_telekit_dsl/telekit_orm.py +0 -0
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/_timeout.py +0 -0
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/_trait.py +0 -0
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/_user.py +0 -0
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/dices.py +0 -0
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/example/__init__.py +0 -0
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/example/example_handlers/__init__.py +0 -0
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/example/example_handlers/complete_hotel.py +0 -0
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/example/example_handlers/counter.py +0 -0
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/example/example_handlers/dsl.py +0 -0
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/example/example_handlers/entry.py +0 -0
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/example/example_handlers/faq.py +0 -0
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/example/example_handlers/hotel.py +0 -0
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/example/example_handlers/on_text.py +0 -0
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/example/example_handlers/pages.py +0 -0
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/example/example_handlers/pyapi.py +0 -0
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/example/example_handlers/qr.py +0 -0
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/example/example_handlers/quiz.py +0 -0
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/example/example_handlers/spells.py +0 -0
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/example/example_handlers/start.py +0 -0
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/example/example_handlers/text_document.py +0 -0
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/example/example_server.py +0 -0
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/parameters.py +0 -0
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/styles.py +0 -0
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/traits/__init__.py +0 -0
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/traits/calendar_pick.py +0 -0
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/traits/paginated_choice.py +0 -0
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/traits/track_handoff_origin.py +0 -0
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit/utils.py +0 -0
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit.egg-info/dependency_links.txt +0 -0
- {telekit-2.3.0b3 → telekit-2.4.0b1}/telekit.egg-info/requires.txt +0 -0
- {telekit-2.3.0b3 → telekit-2.4.0b1}/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.0b1
|
|
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,6 +421,204 @@ It tries to make Telegram bot development easier.
|
|
|
379
421
|
|
|
380
422
|
---
|
|
381
423
|
|
|
382
|
-
# Changes in version 2.
|
|
424
|
+
# Changes in version 2.4.0b1
|
|
425
|
+
|
|
426
|
+
## Inline and Reply Keyboards
|
|
427
|
+
|
|
428
|
+
### `chain.set_keyboard()`
|
|
429
|
+
|
|
430
|
+
Universal method for attaching a keyboard to the current chain message.
|
|
431
|
+
Accepts either an `InlineKeyboard` or a `ReplyKeyboard` instance.
|
|
432
|
+
|
|
433
|
+
```python
|
|
434
|
+
# Inline keyboard
|
|
435
|
+
self.chain.set_keyboard(
|
|
436
|
+
InlineKeyboard()
|
|
437
|
+
.add_callback("Click Me!", self.handle)
|
|
438
|
+
.row()
|
|
439
|
+
.add_link("YouTube", "https://youtube.com")
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
# Reply keyboard
|
|
443
|
+
self.chain.set_keyboard(
|
|
444
|
+
ReplyKeyboard(one_time_keyboard=True)
|
|
445
|
+
.add_text("Hello!")
|
|
446
|
+
.add_text("Hi")
|
|
447
|
+
.row()
|
|
448
|
+
.add_contact("📱 Share phone")
|
|
449
|
+
)
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
> [!NOTE]
|
|
453
|
+
> Inline and reply keyboards are mutually exclusive in Telegram.
|
|
454
|
+
> Only one keyboard can be displayed at a time per message.
|
|
455
|
+
> Passing an `InlineKeyboard` attaches buttons directly to the message;
|
|
456
|
+
> passing a `ReplyKeyboard` replaces the user's system keyboard below the input field.
|
|
457
|
+
|
|
458
|
+
### `InlineKeyboard`
|
|
459
|
+
|
|
460
|
+
Fluent builder for Telegram inline keyboards. Supports all standard inline button types, conditional rendering via `when=`, and row/column layout helpers.
|
|
461
|
+
|
|
462
|
+
**Layout helpers**
|
|
463
|
+
|
|
464
|
+
| **Method** | **Description** |
|
|
465
|
+
|------------------|----------------------------------------------------------------|
|
|
466
|
+
| `row(when=)` | Finalize the current row and start a new one. |
|
|
467
|
+
| `column_start()` | Enable column mode — every subsequent button gets its own row. |
|
|
468
|
+
| `column_end()` | Disable column mode and flush the current row. |
|
|
469
|
+
|
|
470
|
+
**Button methods**
|
|
471
|
+
|
|
472
|
+
| **Method** | **Description** |
|
|
473
|
+
|------------|-----------------|
|
|
474
|
+
| `add(text, button, when=)` | Attach any `InlineButton` instance. |
|
|
475
|
+
| `add_callback(text, callback, pass_args, pass_kwargs, answer_text, answer_as_alert, style, when=)` | Button that fires a callback function. |
|
|
476
|
+
| `add_link(text, url, style, when=)` | Button that opens a URL or `tg://` link. |
|
|
477
|
+
| `add_webapp(text, url, style, when=)` | Button that opens a Telegram Mini App. |
|
|
478
|
+
| `add_suggest(text, suggestion, style, strict, when=)` | Button that simulates the user sending a message. |
|
|
479
|
+
| `add_copy(text, copy_text, style, strict, when=)` | Button that copies text to the clipboard. |
|
|
480
|
+
| `add_static(text, style, when=)` | Decorative button with no action. |
|
|
481
|
+
| `add_alert(text, alert_text, persistent, style, when=)` | Button that shows a popup alert dialog. |
|
|
482
|
+
| `add_notification(text, notification_text, persistent, style, when=)` | Button that shows a brief top-of-chat notification. |
|
|
483
|
+
| `add_invoke(text, obj, invoke, pass_args, pass_kwargs, answer_text, answer_as_alert, style, when=)` | Button that calls a named method on an arbitrary object. |
|
|
484
|
+
|
|
485
|
+
**Bulk helpers**
|
|
486
|
+
|
|
487
|
+
| **Method** | **Description** |
|
|
488
|
+
|------------|-----------------|
|
|
489
|
+
| `extend(buttons, column=, when=)` | Add multiple buttons from a `dict[str, InlineButton \| None]` or `list[str]`. |
|
|
490
|
+
| `extend_rows(*rows, when=)` | Append one or more pre-built `list[tuple[str, InlineButton]]` rows. |
|
|
491
|
+
|
|
492
|
+
All button methods accept a `style` parameter: `"danger"` (red), `"success"` (green), or `"primary"` (blue).
|
|
493
|
+
|
|
494
|
+
```python
|
|
495
|
+
InlineKeyboard()
|
|
496
|
+
.add_link("YouTube", "https://youtube.com")
|
|
497
|
+
.add_copy("Copy Me", "copied!")
|
|
498
|
+
.add_alert("Info", "This is an alert")
|
|
499
|
+
.row()
|
|
500
|
+
.add_callback("Click Me!", self.handle)
|
|
501
|
+
.column_start()
|
|
502
|
+
.add_link("A", "https://example.com")
|
|
503
|
+
.add_callback("B", self.handle_b)
|
|
504
|
+
.column_end()
|
|
505
|
+
.extend({"Alert": AlertButton("Hi!"), "Notify": NotificationButton("Hey")}, column=True)
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
### `ReplyKeyboard`
|
|
509
|
+
|
|
510
|
+
Fluent builder for Telegram reply keyboards. Mirrors the `InlineKeyboard` layout API (`row`, `column_start`, `column_end`, `extend`, `extend_rows`).
|
|
511
|
+
|
|
512
|
+
**Constructor parameters**
|
|
513
|
+
|
|
514
|
+
| **Parameter** | **Default** | **Description** |
|
|
515
|
+
|---|---|---|
|
|
516
|
+
| `resize_keyboard` | `True` | Shrink the keyboard to fit its buttons. |
|
|
517
|
+
| `one_time_keyboard` | `False` | Hide the keyboard after the first press. |
|
|
518
|
+
| `input_field_placeholder` | `None` | Placeholder text shown while the keyboard is active (max 64 chars). |
|
|
519
|
+
| `selective` | `False` | Show only to mentioned users or the original sender. |
|
|
520
|
+
| `is_persistent` | `False` | Always show the keyboard; do not collapse it to an icon. |
|
|
521
|
+
|
|
522
|
+
**Button methods**
|
|
523
|
+
|
|
524
|
+
| **Method** | **Description** |
|
|
525
|
+
|---|---|
|
|
526
|
+
| `add(text, button, when=)` | Attach any `ReplyButton` instance. |
|
|
527
|
+
| `add_text(text, when=)` | Plain text button — sends the label as a message. |
|
|
528
|
+
| `add_contact(text, when=)` | Prompts the user to share their phone number (private chats only). |
|
|
529
|
+
| `add_location(text, when=)` | Prompts the user to share their geolocation (private chats only). |
|
|
530
|
+
| `add_poll(text, poll_type, when=)` | Opens the poll creation dialog; `poll_type` can be `"quiz"`, `"regular"`, or `None`. |
|
|
531
|
+
| `add_webapp(text, url, when=)` | Opens a Telegram Mini App. |
|
|
532
|
+
| `add_request_user(text, request_id, user_is_bot, user_is_premium, when=)` | Lets the user pick a Telegram user; result returned as a service message. |
|
|
533
|
+
| `add_request_chat(text, request_id, chat_is_channel, chat_is_forum, chat_has_username, chat_is_created, user_administrator_rights, bot_administrator_rights, bot_is_member, when=)` | Lets the user pick a chat; result returned as a service message. |
|
|
534
|
+
|
|
535
|
+
**Layout helpers** — identical to `InlineKeyboard`
|
|
536
|
+
|
|
537
|
+
| **Method** | **Description** |
|
|
538
|
+
|---|---|
|
|
539
|
+
| `row(when=)` | Finalize the current row and start a new one. |
|
|
540
|
+
| `column_start()` | Enable column mode — every subsequent button gets its own row. |
|
|
541
|
+
| `column_end()` | Disable column mode and flush the current row. |
|
|
542
|
+
| `extend(buttons, column=, when=)` | Add multiple buttons from a `dict[str, ReplyButton \| None]` or `list[str]`. |
|
|
543
|
+
| `extend_rows(*rows, when=)` | Append one or more pre-built `list[tuple[str, ReplyButton]]` rows. |
|
|
544
|
+
|
|
545
|
+
```python
|
|
546
|
+
ReplyKeyboard(input_field_placeholder="Choose:", one_time_keyboard=True)
|
|
547
|
+
.add_text("Hello!")
|
|
548
|
+
.add_text("Hi")
|
|
549
|
+
.row()
|
|
550
|
+
.add_contact("📱 Phone")
|
|
551
|
+
.add_location("📍 Location")
|
|
552
|
+
.add_poll("📊 Poll")
|
|
553
|
+
.row()
|
|
554
|
+
.add_request_user("Pick user", request_id=1)
|
|
555
|
+
.add_request_chat("Pick chat", request_id=2)
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
## Telekit DSL
|
|
559
|
+
|
|
560
|
+
### `InstanceDSLHandler`
|
|
561
|
+
|
|
562
|
+
Instance-oriented variant of `DSLHandler` where each instance carries its own
|
|
563
|
+
`executable_model`, `_script_data_factory`, and `_jinja_env` — allowing multiple
|
|
564
|
+
instances to run completely independent scripts simultaneously.
|
|
565
|
+
|
|
566
|
+
Use the `*_locally` instance methods instead of the class-level ones:
|
|
567
|
+
|
|
568
|
+
```python
|
|
569
|
+
class MyHandler(telekit.InstanceDSLHandler):
|
|
570
|
+
@classmethod
|
|
571
|
+
def init_handler(cls) -> None:
|
|
572
|
+
cls.on.message().invoke(cls.handle)
|
|
573
|
+
|
|
574
|
+
def handle(self):
|
|
575
|
+
script = fetch_script_from_db(self.user.id) # per-user DSL
|
|
576
|
+
self.analyze_string_locally(script)
|
|
577
|
+
self.start_script()
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
| **Method** | **Description** |
|
|
581
|
+
| --------------------------------------- | ---------------------------------------------------- |
|
|
582
|
+
| `analyze_file_locally(path, encoding)` | Analyse a script file on this instance. |
|
|
583
|
+
| `analyze_string_locally(script)` | Analyse a DSL string on this instance. |
|
|
584
|
+
| `analyze_canvas_locally(file_path)` | Analyse an Obsidian `.canvas` file on this instance. |
|
|
585
|
+
| `analyze_executable_model_locally(model)` | Load a pre-built model dict on this instance. |
|
|
586
|
+
|
|
587
|
+
**Security**
|
|
588
|
+
|
|
589
|
+
When accepting scripts from untrusted users, restrict dangerous features via the
|
|
590
|
+
`RESTRICTED` class attribute. Set `DEFAULT_TIMEOUT` to control the fallback timeout,
|
|
591
|
+
and `DEFAULT_CONFIG` to provide a safe base config.
|
|
592
|
+
|
|
593
|
+
```python
|
|
594
|
+
class SafeDSL(telekit.InstanceDSLHandler):
|
|
595
|
+
RESTRICTED: list[RestrictedToken] = ["hook", "jinja", "redirect", "handoff", "config"]
|
|
596
|
+
DEFAULT_TIMEOUT = 120
|
|
597
|
+
DEFAULT_CONFIG = {"template": "vars"}
|
|
598
|
+
```
|
|
383
599
|
|
|
384
|
-
|
|
600
|
+
| **Token** | **Effect** |
|
|
601
|
+
| -------------- | ----------------------------------------------------------------------------------------------- |
|
|
602
|
+
| `"handoff"` | Disables `handoff` button type (cross-handler transitions). |
|
|
603
|
+
| `"redirect"` | Disables `redirect` button type (simulated user messages). |
|
|
604
|
+
| `"hook"` | Removes all `on_enter`, `on_enter_once`, `on_exit`, `on_timeout` hooks. |
|
|
605
|
+
| `"jinja"` | Forces template engine to `"vars"`; Jinja is never executed. |
|
|
606
|
+
| `"timeout"` | Ignores per-script `timeout_time`; uses `DEFAULT_TIMEOUT` only. |
|
|
607
|
+
| `"config"` | Replaces script config with `DEFAULT_CONFIG`; `vars_*` keys are preserved unless `"vars"` is also set. |
|
|
608
|
+
| `"vars"` | Removes all `vars_*` keys and disables `{{variable}}` substitution. |
|
|
609
|
+
| `"images"` | Strips `image` field from every scene. |
|
|
610
|
+
| `"links"` | Disables `link` button type (external URLs). |
|
|
611
|
+
| `"suggest"` | Disables `suggest` button type (pre-filled entry suggestions). |
|
|
612
|
+
| `"entry"` | Disables entry handlers (free-text input routing). |
|
|
613
|
+
| `"next"` | Disables `next` magic scene navigation. |
|
|
614
|
+
| `"back"` | Disables `back` magic scene navigation. |
|
|
615
|
+
|
|
616
|
+
## Scheduler
|
|
617
|
+
|
|
618
|
+
- Added `every` decorator for scheduling functions in a background daemon thread.
|
|
619
|
+
- Implemented `PeriodicTask` class to manage periodic execution and error handling.
|
|
620
|
+
|
|
621
|
+
## Others
|
|
622
|
+
|
|
623
|
+
- Enhanced `Debug` class with callback query tracing functionality.
|
|
624
|
+
- Refactored `BaseSender` to use `send_or_handle_error`.
|
|
@@ -1,39 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: telekit
|
|
3
|
-
Version: 2.3.0b3
|
|
4
|
-
Summary: Declarative, developer-friendly library for building Telegram bots
|
|
5
|
-
Home-page: https://github.com/Romashkaa/telekit
|
|
6
|
-
Author: romashka
|
|
7
|
-
Author-email: notromashka@gmail.com
|
|
8
|
-
License: GPLv3
|
|
9
|
-
Project-URL: GitHub, https://github.com/Romashkaa/telekit
|
|
10
|
-
Project-URL: Telegram, https://t.me/TelekitLib
|
|
11
|
-
Keywords: telegram bot api declarative tools bot-api
|
|
12
|
-
Classifier: Programming Language :: Python
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.14
|
|
16
|
-
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
|
|
17
|
-
Classifier: Operating System :: OS Independent
|
|
18
|
-
Requires-Python: >=3.12
|
|
19
|
-
Description-Content-Type: text/markdown
|
|
20
|
-
License-File: LICENSE
|
|
21
|
-
Requires-Dist: charset_normalizer==3.4.2
|
|
22
|
-
Requires-Dist: Jinja2==3.1.6
|
|
23
|
-
Requires-Dist: pyTelegramBotAPI==4.31.0
|
|
24
|
-
Dynamic: author
|
|
25
|
-
Dynamic: author-email
|
|
26
|
-
Dynamic: classifier
|
|
27
|
-
Dynamic: description
|
|
28
|
-
Dynamic: description-content-type
|
|
29
|
-
Dynamic: home-page
|
|
30
|
-
Dynamic: keywords
|
|
31
|
-
Dynamic: license-file
|
|
32
|
-
Dynamic: project-url
|
|
33
|
-
Dynamic: requires-dist
|
|
34
|
-
Dynamic: requires-python
|
|
35
|
-
Dynamic: summary
|
|
36
|
-
|
|
37
1
|

|
|
38
2
|
|
|
39
3
|
[](https://pypi.org/project/telekit/)
|
|
@@ -77,7 +41,7 @@ Telekit comes with a [built-in DSL](https://github.com/Romashkaa/telekit/blob/ma
|
|
|
77
41
|
|
|
78
42
|
> See the [full example](https://github.com/Romashkaa/telekit/blob/main/docs/examples/quiz.md)
|
|
79
43
|
|
|
80
|
-
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.
|
|
81
45
|
|
|
82
46
|
**Key features:**
|
|
83
47
|
- Declarative bot logic with **chains** for effortless handling of complex conversations
|
|
@@ -85,6 +49,7 @@ Even in its beta stage, Telekit accelerates bot development, offering typed comm
|
|
|
85
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**
|
|
86
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
|
|
87
51
|
- Built-in **Permission** and **Logging** system for user management
|
|
52
|
+
- Reusable **Traits** system for pluggable, self-contained behavior modules
|
|
88
53
|
- Seamless integration with [pyTelegramBotAPI](https://github.com/eternnoir/pyTelegramBotAPI)
|
|
89
54
|
- Fast to develop and easy-to-extend code
|
|
90
55
|
|
|
@@ -340,14 +305,54 @@ telekit.Server(BOT_TOKEN).polling()
|
|
|
340
305
|
<summary>Click to see what you can do with the DSL</summary>
|
|
341
306
|
<table>
|
|
342
307
|
<tr>
|
|
343
|
-
<td><img src="
|
|
344
|
-
<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>
|
|
345
314
|
</tr>
|
|
346
315
|
</table>
|
|
347
316
|
</details>
|
|
348
317
|
|
|
349
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.
|
|
350
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
|
+
|
|
351
356
|
### Example Bot
|
|
352
357
|
|
|
353
358
|
You can launch an example bot by **running the following code**:
|
|
@@ -368,6 +373,7 @@ It includes example commands, dialogs, keyboards, and style usage.
|
|
|
368
373
|
- **Styles API** for rich text (`Bold`, `Italic`, `Links`) with **automatic escaping**.
|
|
369
374
|
- Deep linking and **typed command parameters**.
|
|
370
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).
|
|
371
377
|
- **Zero-code** [Obsidian Canvas](https://github.com/Romashkaa/telekit/blob/main/docs/examples/canvas_faq.md) mode.
|
|
372
378
|
- Seamless integration with **pyTelegramBotAPI**.
|
|
373
379
|
|
|
@@ -375,10 +381,4 @@ Telekit doesn't try to be everything.
|
|
|
375
381
|
It tries to make Telegram bot development easier.
|
|
376
382
|
|
|
377
383
|
> [!TIP]
|
|
378
|
-
> If you're interested and want to learn more, check out the [Tutorial](https://github.com/Romashkaa/telekit/blob/main/docs/tutorial2/0_tutorial.md)
|
|
379
|
-
|
|
380
|
-
---
|
|
381
|
-
|
|
382
|
-
# Changes in version 2.3.0b3
|
|
383
|
-
|
|
384
|
-
[View changelog on GitHub](https://github.com/Romashkaa/telekit/blob/main/CHANGELOG.md)
|
|
384
|
+
> If you're interested and want to learn more, check out the [Tutorial](https://github.com/Romashkaa/telekit/blob/main/docs/tutorial2/0_tutorial.md)
|
|
@@ -26,10 +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
|
-
|
|
32
|
-
|
|
31
|
+
with open('CHANGELOG.md', 'r') as f:
|
|
32
|
+
return f.read()
|
|
33
|
+
|
|
33
34
|
def install_requires():
|
|
34
35
|
with open('telekit/requirements.txt', 'r') as f:
|
|
35
36
|
return f.read().split("\n")
|
|
@@ -22,11 +22,13 @@ from ._trait import Trait
|
|
|
22
22
|
from ._chain import Chain
|
|
23
23
|
from ._callback_query_handler import CallbackQueryHandler
|
|
24
24
|
from .server import Server, example
|
|
25
|
+
from ._inline_keyboard import InlineKeyboard
|
|
26
|
+
from ._reply_keyboard import ReplyKeyboard
|
|
25
27
|
from ._snapvault import Vault
|
|
26
28
|
from ._chapters import chapters
|
|
27
29
|
from ._user import User
|
|
28
30
|
from ._telekit_dsl.telekit_dsl import TelekitDSL
|
|
29
|
-
from ._telekit_dsl.mixin import DSLHandler
|
|
31
|
+
from ._telekit_dsl.mixin import DSLHandler, InstanceDSLHandler
|
|
30
32
|
from ._logger import enable_file_logging
|
|
31
33
|
|
|
32
34
|
from . import senders
|
|
@@ -38,6 +40,7 @@ from . import dices
|
|
|
38
40
|
from . import utils
|
|
39
41
|
from . import traits
|
|
40
42
|
from . import debug
|
|
43
|
+
from . import scheduler
|
|
41
44
|
|
|
42
45
|
Styles = styles.Styles
|
|
43
46
|
|
|
@@ -52,6 +55,7 @@ __all__ = [
|
|
|
52
55
|
"parameters",
|
|
53
56
|
"inline_buttons",
|
|
54
57
|
"dices",
|
|
58
|
+
"scheduler",
|
|
55
59
|
|
|
56
60
|
"Styles",
|
|
57
61
|
"User",
|
|
@@ -62,8 +66,12 @@ __all__ = [
|
|
|
62
66
|
"Handler",
|
|
63
67
|
"CallbackQueryHandler",
|
|
64
68
|
|
|
69
|
+
"InlineKeyboard",
|
|
70
|
+
"ReplyKeyboard",
|
|
71
|
+
|
|
65
72
|
"TelekitDSL",
|
|
66
73
|
"DSLHandler",
|
|
74
|
+
"InstanceDSLHandler",
|
|
67
75
|
|
|
68
76
|
"Vault",
|
|
69
77
|
"enable_file_logging",
|
|
@@ -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
|
|
|
@@ -30,7 +30,7 @@ from .debug import Debug
|
|
|
30
30
|
from .styles import TextEntity
|
|
31
31
|
|
|
32
32
|
# Chain modules
|
|
33
|
-
from ._chain_base import
|
|
33
|
+
from ._chain_base import _library
|
|
34
34
|
from ._chain_inline_keyboards_logic import ChainInlineKeyboardLogic
|
|
35
35
|
from ._chain_entry_logic import ChainEntryLogic, TextDocument
|
|
36
36
|
|
|
@@ -156,7 +156,7 @@ class Chain(ChainInlineKeyboardLogic, ChainEntryLogic):
|
|
|
156
156
|
self._previous_message = message
|
|
157
157
|
|
|
158
158
|
if Debug.timeout_warnings and _handler and not _timeout:
|
|
159
|
-
|
|
159
|
+
_library.warning(
|
|
160
160
|
"Next-message handler is active, but no timeout was set for the chain. "
|
|
161
161
|
"This may cause the bot to wait indefinitely."
|
|
162
162
|
)
|
|
@@ -31,8 +31,10 @@ from telebot.types import (
|
|
|
31
31
|
)
|
|
32
32
|
|
|
33
33
|
from ._callback_query_handler import CallbackQueryHandler
|
|
34
|
+
from ._inline_keyboard import InlineKeyboard
|
|
35
|
+
from ._reply_keyboard import ReplyKeyboard
|
|
34
36
|
from ._inline_buttons import InlineButton, CallbackButton
|
|
35
|
-
from ._chain_base import ChainBase,
|
|
37
|
+
from ._chain_base import ChainBase, _library
|
|
36
38
|
|
|
37
39
|
if typing.TYPE_CHECKING:
|
|
38
40
|
from ._chain import Chain # only for type hints
|
|
@@ -249,7 +251,7 @@ class ChainInlineKeyboardLogic(ChainBase):
|
|
|
249
251
|
if not isinstance(choices, dict):
|
|
250
252
|
for c in choices:
|
|
251
253
|
if type(c).__str__ is object.__str__:
|
|
252
|
-
|
|
254
|
+
_library.warning(
|
|
253
255
|
f"{type(c).__name__} does not implement __str__. "
|
|
254
256
|
f"Consider passing a dict with explicit labels.",
|
|
255
257
|
stacklevel=3
|
|
@@ -335,6 +337,73 @@ class ChainInlineKeyboardLogic(ChainBase):
|
|
|
335
337
|
|
|
336
338
|
self.sender.set_reply_markup(markup)
|
|
337
339
|
|
|
340
|
+
#
|
|
341
|
+
|
|
342
|
+
def set_keyboard(self, keyboard: InlineKeyboard | ReplyKeyboard) -> None:
|
|
343
|
+
"""
|
|
344
|
+
Sets a keyboard for the chain from an `InlineKeyboard` or `ReplyKeyboard` object.
|
|
345
|
+
|
|
346
|
+
Example::
|
|
347
|
+
|
|
348
|
+
from telekit.types import InlineKeyboard, ReplyKeyboard
|
|
349
|
+
|
|
350
|
+
# Inline keyboard
|
|
351
|
+
self.chain.set_keyboard(
|
|
352
|
+
InlineKeyboard()
|
|
353
|
+
.add_callback("Click Me!", self.handle)
|
|
354
|
+
.row()
|
|
355
|
+
.add_link("YouTube", "https://youtube.com")
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
# Reply keyboard
|
|
359
|
+
self.chain.set_keyboard(
|
|
360
|
+
ReplyKeyboard(one_time_keyboard=True)
|
|
361
|
+
.add_text("Hello!")
|
|
362
|
+
.add_text("Hi")
|
|
363
|
+
.row()
|
|
364
|
+
.add_contact("📱 Share phone")
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
:param keyboard: A constructed `InlineKeyboard` instance with buttons and rows.
|
|
368
|
+
:type keyboard: `InlineKeyboard`
|
|
369
|
+
"""
|
|
370
|
+
# --- ReplyKeyboard ---
|
|
371
|
+
if isinstance(keyboard, ReplyKeyboard):
|
|
372
|
+
markup = keyboard._compile()
|
|
373
|
+
self.sender.set_reply_markup(markup)
|
|
374
|
+
self._handler.set_button_callbacks(None)
|
|
375
|
+
return
|
|
376
|
+
|
|
377
|
+
# --- InlineKeyboard ---
|
|
378
|
+
button_callbacks: dict[str, Callable[[CallbackQuery], None]] = {}
|
|
379
|
+
markup = InlineKeyboardMarkup()
|
|
380
|
+
|
|
381
|
+
button_index = 0
|
|
382
|
+
|
|
383
|
+
for row in keyboard._compile():
|
|
384
|
+
compiled_row: list[InlineKeyboardButton] = []
|
|
385
|
+
for caption, inline_button in row:
|
|
386
|
+
if isinstance(inline_button, CallbackButton):
|
|
387
|
+
invoker = inline_button.build_invoker(self._cancel_timeout_and_handlers)
|
|
388
|
+
callback_data = CallbackQueryHandler.inline_button(f"{button_index}:{random.randint(1000, 9999)}")
|
|
389
|
+
button_callbacks[callback_data] = invoker
|
|
390
|
+
compiled_row.append(
|
|
391
|
+
InlineKeyboardButton(
|
|
392
|
+
text=caption,
|
|
393
|
+
callback_data=callback_data,
|
|
394
|
+
**invoker._kwargs # pyright: ignore[reportArgumentType]
|
|
395
|
+
)
|
|
396
|
+
)
|
|
397
|
+
else:
|
|
398
|
+
compiled_row.append(inline_button._compile(caption))
|
|
399
|
+
button_index += 1
|
|
400
|
+
markup.keyboard.append(compiled_row)
|
|
401
|
+
|
|
402
|
+
self.sender.set_reply_markup(markup)
|
|
403
|
+
self._handler.set_button_callbacks(button_callbacks)
|
|
404
|
+
|
|
405
|
+
# Utils
|
|
406
|
+
|
|
338
407
|
def _get_invoker_with_argument(self, callback: Callable, argument: Any, query_answer: tuple[str, bool] | None = None) -> Callable[[CallbackQuery], None]:
|
|
339
408
|
def invoker(call: CallbackQuery) -> None:
|
|
340
409
|
self._cancel_timeout_and_handlers()
|