telekit 2.2.0a2__tar.gz → 2.3.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.2.0a2/telekit.egg-info → telekit-2.3.0b1}/PKG-INFO +81 -23
- {telekit-2.2.0a2 → telekit-2.3.0b1}/README.md +2 -1
- {telekit-2.2.0a2 → telekit-2.3.0b1}/telekit/__init__.py +2 -0
- {telekit-2.2.0a2 → telekit-2.3.0b1}/telekit/_chain.py +1 -4
- {telekit-2.2.0a2 → telekit-2.3.0b1}/telekit/_chain_base.py +6 -0
- {telekit-2.2.0a2 → telekit-2.3.0b1}/telekit/_chain_entry_logic.py +12 -0
- {telekit-2.2.0a2 → telekit-2.3.0b1}/telekit/_chain_inline_keyboards_logic.py +41 -20
- {telekit-2.2.0a2 → telekit-2.3.0b1}/telekit/_handler.py +7 -0
- {telekit-2.2.0a2 → telekit-2.3.0b1}/telekit/_inline_buttons.py +133 -5
- {telekit-2.2.0a2 → telekit-2.3.0b1}/telekit/_telekit_dsl/mixin.py +2 -2
- {telekit-2.2.0a2 → telekit-2.3.0b1}/telekit/_version.py +1 -1
- {telekit-2.2.0a2 → telekit-2.3.0b1}/telekit/example/example_handlers/entry.py +5 -1
- {telekit-2.2.0a2 → telekit-2.3.0b1}/telekit/example/example_handlers/pages.py +12 -5
- {telekit-2.2.0a2 → telekit-2.3.0b1}/telekit/example/example_handlers/text_document.py +6 -2
- {telekit-2.2.0a2 → telekit-2.3.0b1}/telekit/inline_buttons.py +2 -0
- {telekit-2.2.0a2 → telekit-2.3.0b1}/telekit/senders.py +402 -100
- telekit-2.3.0b1/telekit/traits/__init__.py +2 -0
- telekit-2.3.0b1/telekit/traits/paginated_choice.py +109 -0
- telekit-2.3.0b1/telekit/traits/track_handoff_origin.py +99 -0
- {telekit-2.2.0a2 → telekit-2.3.0b1}/telekit/types.py +43 -30
- {telekit-2.2.0a2 → telekit-2.3.0b1}/telekit/utils.py +141 -14
- {telekit-2.2.0a2 → telekit-2.3.0b1/telekit.egg-info}/PKG-INFO +81 -23
- {telekit-2.2.0a2 → telekit-2.3.0b1}/telekit.egg-info/SOURCES.txt +4 -1
- {telekit-2.2.0a2 → telekit-2.3.0b1}/LICENSE +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0b1}/setup.cfg +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0b1}/setup.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0b1}/telekit/_buildtext/__init__.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0b1}/telekit/_buildtext/formatter.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0b1}/telekit/_buildtext/styles.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0b1}/telekit/_callback_query_handler.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0b1}/telekit/_chapters/__init__.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0b1}/telekit/_chapters/chapters.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0b1}/telekit/_init.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0b1}/telekit/_input_handler.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0b1}/telekit/_logger.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0b1}/telekit/_on.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0b1}/telekit/_snapvault/__init__.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0b1}/telekit/_snapvault/snapcode.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0b1}/telekit/_snapvault/snapvault.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0b1}/telekit/_state.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0b1}/telekit/_telekit_dsl/__init__.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0b1}/telekit/_telekit_dsl/parser/__init__.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0b1}/telekit/_telekit_dsl/parser/builder.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0b1}/telekit/_telekit_dsl/parser/canvas_parser.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0b1}/telekit/_telekit_dsl/parser/lexer.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0b1}/telekit/_telekit_dsl/parser/nodes.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0b1}/telekit/_telekit_dsl/parser/parser.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0b1}/telekit/_telekit_dsl/parser/token.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0b1}/telekit/_telekit_dsl/telekit_dsl.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0b1}/telekit/_telekit_dsl/telekit_orm.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0b1}/telekit/_timeout.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0b1}/telekit/_user.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0b1}/telekit/dices.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0b1}/telekit/example/__init__.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0b1}/telekit/example/example_handlers/__init__.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0b1}/telekit/example/example_handlers/complete_hotel.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0b1}/telekit/example/example_handlers/counter.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0b1}/telekit/example/example_handlers/dsl.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0b1}/telekit/example/example_handlers/faq.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0b1}/telekit/example/example_handlers/hotel.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0b1}/telekit/example/example_handlers/on_text.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0b1}/telekit/example/example_handlers/pyapi.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0b1}/telekit/example/example_handlers/qr.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0b1}/telekit/example/example_handlers/quiz.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0b1}/telekit/example/example_handlers/spells.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0b1}/telekit/example/example_handlers/start.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0b1}/telekit/example/example_server.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0b1}/telekit/parameters.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0b1}/telekit/server.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0b1}/telekit/styles.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0b1}/telekit.egg-info/dependency_links.txt +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0b1}/telekit.egg-info/requires.txt +0 -0
- {telekit-2.2.0a2 → telekit-2.3.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.3.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
|
|
@@ -101,7 +101,8 @@ Even in its beta stage, Telekit accelerates bot development, offering typed comm
|
|
|
101
101
|
- [Dialogue](https://github.com/Romashkaa/telekit/blob/main/docs/examples/dialogue.md)
|
|
102
102
|
- [Risk Game](https://github.com/Romashkaa/telekit/blob/main/docs/examples/risk_game.md)
|
|
103
103
|
- [Counter](https://github.com/Romashkaa/telekit/blob/main/docs/examples/counter.md)
|
|
104
|
-
- [Quiz (
|
|
104
|
+
- [Quiz (DSL)](https://github.com/Romashkaa/telekit/blob/main/docs/examples/quiz.md)
|
|
105
|
+
- [Hotel (DSL)](https://github.com/Romashkaa/telekit/blob/main/docs/examples/complete_hotel.md)
|
|
105
106
|
- [More...](https://github.com/Romashkaa/telekit/blob/main/docs/examples/examples.md)
|
|
106
107
|
|
|
107
108
|
## Overview
|
|
@@ -378,29 +379,86 @@ It tries to make Telegram bot development easier.
|
|
|
378
379
|
|
|
379
380
|
---
|
|
380
381
|
|
|
381
|
-
# Changes in version 2.
|
|
382
|
+
# Changes in version 2.3.0b1
|
|
382
383
|
|
|
383
|
-
##
|
|
384
|
+
## Traits
|
|
384
385
|
|
|
385
|
-
| **Name**
|
|
386
|
-
|
|
387
|
-
| `
|
|
388
|
-
| `
|
|
386
|
+
| **Name** | **Description** |
|
|
387
|
+
| -------------------------- | --------------------------------------------------------------------- |
|
|
388
|
+
| `TrackHandoffOrigin` | Tracks which handler transferred control to this one via `handoff()`. |
|
|
389
|
+
| `PaginatedChoice` | Adds a paginated inline keyboard for choosing from a list of items. |
|
|
389
390
|
|
|
390
|
-
|
|
391
|
+
### TrackHandoffOrigin
|
|
391
392
|
|
|
392
|
-
|
|
393
|
-
| ---------------------- | --------------------------------------------------------- |
|
|
394
|
-
| `bio` | Bio of the user or description of the chat. |
|
|
395
|
-
| `birthdate` | Birthdate of the user, if set and visible. |
|
|
396
|
-
| `description` | Description of the group or channel. |
|
|
397
|
-
| `mention` | `tg://user?id=` deep link, works even without a username. |
|
|
398
|
-
| `is_private` | Whether the message was sent in a private chat. |
|
|
399
|
-
| `is_group` | Whether the message was sent in a group. |
|
|
400
|
-
| `is_supergroup` | Whether the message was sent in a supergroup. |
|
|
401
|
-
| `is_channel` | Whether the message was sent in a channel. |
|
|
402
|
-
| `avatar` | File ID of the user's most recent profile photo. |
|
|
403
|
-
| `profile_photos_count` | Total number of profile photos the user has set. |
|
|
393
|
+
`TrackHandoffOrigin` adds three members to any handler that inherits it:
|
|
404
394
|
|
|
405
|
-
|
|
406
|
-
|
|
395
|
+
| **Member** | **Description** |
|
|
396
|
+
| ------------------- | -------------------------------------------------------------------------------- |
|
|
397
|
+
| `handoff_origin` | The handler instance that handed off to this one, or `None` if invoked directly. |
|
|
398
|
+
| `is_handed_off` | `True` if this handler was reached via `handoff()`, `False` otherwise. |
|
|
399
|
+
| `handoff_back()` | Transfer control back to the origin handler via `self.handoff_origin.handle()` |
|
|
400
|
+
| `handoff_back_or(handler)` | Like `handoff_back()`, but falls back to `handler` on fail. |
|
|
401
|
+
|
|
402
|
+
Set `TRACK_HANDOFF_ORIGIN = False` on any subclass to opt out of tracking.
|
|
403
|
+
|
|
404
|
+
```python
|
|
405
|
+
class MyHandler(TrackHandoffOrigin, telekit.Handler):
|
|
406
|
+
|
|
407
|
+
def handle(self):
|
|
408
|
+
...
|
|
409
|
+
self.chain.set_inline_keyboard({
|
|
410
|
+
"« Back": self.handoff_back_or(StartHandler)
|
|
411
|
+
})
|
|
412
|
+
self.chain.edit()
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
### PaginatedChoice
|
|
416
|
+
|
|
417
|
+
Renders a paginated inline keyboard from any dict or iterable.
|
|
418
|
+
Navigation buttons (`« Back`, `Next »`) are added automatically.
|
|
419
|
+
|
|
420
|
+
| **Member** | **Description** |
|
|
421
|
+
| -------------------- | ---------------------------------------------------------------------------- |
|
|
422
|
+
| `paginated_choice(choices, on_choice, on_update, row_width)` | Display a paginated choice keyboard. |
|
|
423
|
+
|
|
424
|
+
```python
|
|
425
|
+
self.chain.sender.set_title("🔤 What is your initial?")
|
|
426
|
+
self.chain.sender.set_message("Pick the first letter of your name")
|
|
427
|
+
self.chain.sender.set_remove_text(False)
|
|
428
|
+
|
|
429
|
+
self.paginated_choice(
|
|
430
|
+
choices="ABCDEFGHIJKLMNOPQRSTUVWXYZ",
|
|
431
|
+
on_choice=self.handle_letter,
|
|
432
|
+
row_width=5
|
|
433
|
+
)
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
<details>
|
|
437
|
+
<summary>(Click to see the result)</summary>
|
|
438
|
+
<table>
|
|
439
|
+
<tr>
|
|
440
|
+
<td><img src="https://github.com/Romashkaa/telekit/blob/main/docs/images/paginated_choice.png?raw=true" alt="Example" width="300"></td>
|
|
441
|
+
</tr>
|
|
442
|
+
</table>
|
|
443
|
+
</details>
|
|
444
|
+
|
|
445
|
+
`choices` accepts a `dict[str, T]`, or any `Iterable[T]`.
|
|
446
|
+
If only one item is present, `on_choice` is called immediately without rendering a keyboard.
|
|
447
|
+
|
|
448
|
+
## Utils
|
|
449
|
+
|
|
450
|
+
| **Name** | **Description** |
|
|
451
|
+
| ---------------- | ------------------------------------------------------------------ |
|
|
452
|
+
| `load_env` | Load all key-value pairs from a `.env` file into a dictionary. |
|
|
453
|
+
| `read_env_var` | Read a single variable by name from a `.env` file. |
|
|
454
|
+
|
|
455
|
+
- `read_token` and `read_canvas_path` now support reading from `.env` files.
|
|
456
|
+
Pass `".env"` to use the default key, or `".env:KEY"` to specify a custom one:
|
|
457
|
+
|
|
458
|
+
```python
|
|
459
|
+
read_token(".env") # reads TOKEN
|
|
460
|
+
read_token(".env:BOT_TOKEN") # reads BOT_TOKEN
|
|
461
|
+
|
|
462
|
+
read_canvas_path(".env") # reads CANVAS_PATH
|
|
463
|
+
read_canvas_path(".env:MY_CANVAS") # reads MY_CANVAS
|
|
464
|
+
```
|
|
@@ -65,7 +65,8 @@ Even in its beta stage, Telekit accelerates bot development, offering typed comm
|
|
|
65
65
|
- [Dialogue](https://github.com/Romashkaa/telekit/blob/main/docs/examples/dialogue.md)
|
|
66
66
|
- [Risk Game](https://github.com/Romashkaa/telekit/blob/main/docs/examples/risk_game.md)
|
|
67
67
|
- [Counter](https://github.com/Romashkaa/telekit/blob/main/docs/examples/counter.md)
|
|
68
|
-
- [Quiz (
|
|
68
|
+
- [Quiz (DSL)](https://github.com/Romashkaa/telekit/blob/main/docs/examples/quiz.md)
|
|
69
|
+
- [Hotel (DSL)](https://github.com/Romashkaa/telekit/blob/main/docs/examples/complete_hotel.md)
|
|
69
70
|
- [More...](https://github.com/Romashkaa/telekit/blob/main/docs/examples/examples.md)
|
|
70
71
|
|
|
71
72
|
## Overview
|
|
@@ -35,12 +35,14 @@ from . import parameters
|
|
|
35
35
|
from . import inline_buttons
|
|
36
36
|
from . import dices
|
|
37
37
|
from . import utils
|
|
38
|
+
from . import traits
|
|
38
39
|
|
|
39
40
|
Styles = styles.Styles
|
|
40
41
|
|
|
41
42
|
from ._version import __version__
|
|
42
43
|
|
|
43
44
|
__all__ = [
|
|
45
|
+
"traits",
|
|
44
46
|
"utils",
|
|
45
47
|
"senders",
|
|
46
48
|
"types",
|
|
@@ -28,11 +28,8 @@ from telebot.types import Message
|
|
|
28
28
|
from . import senders
|
|
29
29
|
from .styles import TextEntity
|
|
30
30
|
|
|
31
|
-
# Logging
|
|
32
|
-
from ._logger import logger
|
|
33
|
-
library = logger.library
|
|
34
|
-
|
|
35
31
|
# Chain modules
|
|
32
|
+
from ._chain_base import library
|
|
36
33
|
from ._chain_inline_keyboards_logic import ChainInlineKeyboardLogic
|
|
37
34
|
from ._chain_entry_logic import ChainEntryLogic, TextDocument
|
|
38
35
|
|
|
@@ -30,6 +30,10 @@ from . import senders
|
|
|
30
30
|
from . import _input_handler
|
|
31
31
|
from . import _timeout
|
|
32
32
|
|
|
33
|
+
# Logging
|
|
34
|
+
from ._logger import logger
|
|
35
|
+
library = logger.library
|
|
36
|
+
|
|
33
37
|
class ChainBase:
|
|
34
38
|
|
|
35
39
|
bot: telebot.TeleBot
|
|
@@ -49,6 +53,8 @@ class ChainBase:
|
|
|
49
53
|
def __init__(self, chat_id: int, *, previous_message: Message | None = None):
|
|
50
54
|
self.chat_id = chat_id
|
|
51
55
|
self.sender = senders.Sender(chat_id)
|
|
56
|
+
self.received = None
|
|
57
|
+
|
|
52
58
|
self._handler = _input_handler.InputHandler(chat_id)
|
|
53
59
|
self._previous_message = previous_message
|
|
54
60
|
self._timeout_handler = _timeout.TimeoutHandler()
|
|
@@ -68,6 +68,8 @@ class ChainEntryLogic(ChainBase):
|
|
|
68
68
|
"""
|
|
69
69
|
|
|
70
70
|
def callback(message: Message) -> bool:
|
|
71
|
+
self.received = message
|
|
72
|
+
|
|
71
73
|
if delete_user_response:
|
|
72
74
|
self.sender.delete_message(message, True)
|
|
73
75
|
|
|
@@ -154,6 +156,8 @@ class ChainEntryLogic(ChainBase):
|
|
|
154
156
|
"""
|
|
155
157
|
|
|
156
158
|
def callback(message: Message) -> bool:
|
|
159
|
+
self.received = message
|
|
160
|
+
|
|
157
161
|
if delete_user_response:
|
|
158
162
|
self.sender.delete_message(message, True)
|
|
159
163
|
|
|
@@ -246,6 +250,8 @@ class ChainEntryLogic(ChainBase):
|
|
|
246
250
|
"""
|
|
247
251
|
|
|
248
252
|
def callback(message: Message) -> bool:
|
|
253
|
+
self.received = message
|
|
254
|
+
|
|
249
255
|
if delete_user_response:
|
|
250
256
|
self.sender.delete_message(message, True)
|
|
251
257
|
|
|
@@ -340,6 +346,8 @@ class ChainEntryLogic(ChainBase):
|
|
|
340
346
|
"""
|
|
341
347
|
|
|
342
348
|
def callback(message: Message) -> bool:
|
|
349
|
+
self.received = message
|
|
350
|
+
|
|
343
351
|
if delete_user_response:
|
|
344
352
|
self.sender.delete_message(message, True)
|
|
345
353
|
|
|
@@ -453,6 +461,8 @@ class ChainEntryLogic(ChainBase):
|
|
|
453
461
|
"""
|
|
454
462
|
|
|
455
463
|
def callback(message: Message) -> bool:
|
|
464
|
+
self.received = message
|
|
465
|
+
|
|
456
466
|
if delete_user_response:
|
|
457
467
|
self.sender.delete_message(message, True)
|
|
458
468
|
|
|
@@ -592,6 +602,8 @@ class ChainEntryLogic(ChainBase):
|
|
|
592
602
|
"""
|
|
593
603
|
|
|
594
604
|
def callback(message: Message) -> bool:
|
|
605
|
+
self.received = message
|
|
606
|
+
|
|
595
607
|
if delete_user_response:
|
|
596
608
|
self.sender.delete_message(message, True)
|
|
597
609
|
|
|
@@ -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
|
|
@@ -151,15 +151,24 @@ class ChainInlineKeyboardLogic(ChainBase):
|
|
|
151
151
|
buttons: list[InlineKeyboardButton] = []
|
|
152
152
|
|
|
153
153
|
for index, (caption, value) in enumerate(keyboard.items()):
|
|
154
|
-
if enable_special_buttons and isinstance(value, InlineButton):
|
|
154
|
+
if enable_special_buttons and isinstance(value, InlineButton) and not isinstance(value, CallbackButton):
|
|
155
155
|
buttons.append(value._compile(caption))
|
|
156
156
|
else:
|
|
157
|
+
if isinstance(value, CallbackButton):
|
|
158
|
+
invoker = value.build_invoker(self._cancel_timeout_and_handlers)
|
|
159
|
+
else:
|
|
160
|
+
invoker = self._get_invoker_with_argument(func, value)
|
|
161
|
+
|
|
157
162
|
callback_data = CallbackQueryHandler.inline_button(f"{index}:{random.randint(1000, 9999)}")
|
|
158
|
-
callback_functions[callback_data] =
|
|
163
|
+
callback_functions[callback_data] = invoker
|
|
164
|
+
|
|
165
|
+
kwargs: dict[str, Any] = getattr(invoker, "_kwargs", None) or {}
|
|
166
|
+
|
|
159
167
|
buttons.append(
|
|
160
168
|
InlineKeyboardButton(
|
|
161
169
|
text=caption,
|
|
162
|
-
callback_data=callback_data
|
|
170
|
+
callback_data=callback_data,
|
|
171
|
+
**kwargs
|
|
163
172
|
)
|
|
164
173
|
)
|
|
165
174
|
|
|
@@ -242,18 +251,34 @@ class ChainInlineKeyboardLogic(ChainBase):
|
|
|
242
251
|
buttons: list[InlineKeyboardButton] = []
|
|
243
252
|
|
|
244
253
|
if not isinstance(choices, dict):
|
|
254
|
+
for c in choices:
|
|
255
|
+
if type(c).__str__ is object.__str__:
|
|
256
|
+
library.warning(
|
|
257
|
+
f"{type(c).__name__} does not implement __str__. "
|
|
258
|
+
f"Consider passing a dict with explicit labels.",
|
|
259
|
+
stacklevel=3
|
|
260
|
+
)
|
|
245
261
|
choices = {str(c): c for c in choices}
|
|
246
262
|
|
|
247
263
|
for index, (caption, value) in enumerate(choices.items()):
|
|
248
|
-
if enable_special_buttons and isinstance(value, InlineButton):
|
|
264
|
+
if enable_special_buttons and isinstance(value, InlineButton) and not isinstance(value, CallbackButton):
|
|
249
265
|
buttons.append(value._compile(caption))
|
|
250
266
|
else:
|
|
267
|
+
if isinstance(value, CallbackButton):
|
|
268
|
+
invoker = value.build_invoker(self._cancel_timeout_and_handlers)
|
|
269
|
+
else:
|
|
270
|
+
invoker = self._get_invoker_with_argument(func, value)
|
|
271
|
+
|
|
251
272
|
callback_data = CallbackQueryHandler.inline_button(f"{index}:{random.randint(1000, 9999)}")
|
|
252
|
-
callback_functions[callback_data] =
|
|
273
|
+
callback_functions[callback_data] = invoker
|
|
274
|
+
|
|
275
|
+
kwargs: dict[str, Any] = getattr(invoker, "_kwargs", None) or {}
|
|
276
|
+
|
|
253
277
|
buttons.append(
|
|
254
278
|
InlineKeyboardButton(
|
|
255
279
|
text=caption,
|
|
256
|
-
callback_data=callback_data
|
|
280
|
+
callback_data=callback_data,
|
|
281
|
+
**kwargs
|
|
257
282
|
)
|
|
258
283
|
)
|
|
259
284
|
|
|
@@ -314,21 +339,21 @@ class ChainInlineKeyboardLogic(ChainBase):
|
|
|
314
339
|
|
|
315
340
|
self.sender.set_reply_markup(markup)
|
|
316
341
|
|
|
317
|
-
def
|
|
318
|
-
def
|
|
342
|
+
def _get_invoker_with_argument(self, callback: Callable, argument: Any, query_answer: tuple[str, bool] | None = None) -> Callable[[CallbackQuery], None]:
|
|
343
|
+
def invoker(call: CallbackQuery) -> None:
|
|
319
344
|
self._cancel_timeout_and_handlers()
|
|
320
|
-
|
|
345
|
+
callback(argument)
|
|
321
346
|
self._answer_callback_query(call, query_answer)
|
|
322
347
|
|
|
323
|
-
return
|
|
348
|
+
return invoker
|
|
324
349
|
|
|
325
|
-
def
|
|
326
|
-
def
|
|
350
|
+
def _get_invoker(self, callback: Callable[..., None], query_answer: tuple[str, bool] | None = None) -> Callable[[CallbackQuery], None]:
|
|
351
|
+
def invoker(call: CallbackQuery):
|
|
327
352
|
self._cancel_timeout_and_handlers()
|
|
328
|
-
|
|
353
|
+
callback()
|
|
329
354
|
self._answer_callback_query(call, query_answer)
|
|
330
355
|
|
|
331
|
-
return
|
|
356
|
+
return invoker
|
|
332
357
|
|
|
333
358
|
def _answer_callback_query(self, call: CallbackQuery, query_answer: tuple[str, bool] | None = None):
|
|
334
359
|
if query_answer is None:
|
|
@@ -369,8 +394,4 @@ class ChainInlineKeyboardLogic(ChainBase):
|
|
|
369
394
|
rows.append(buttons[index:index + last_width])
|
|
370
395
|
index += last_width
|
|
371
396
|
|
|
372
|
-
return rows
|
|
373
|
-
|
|
374
|
-
def _is_valid_telegram_callback(self, data: str) -> bool:
|
|
375
|
-
byte_size = len(data.encode('utf-8'))
|
|
376
|
-
return 1 <= byte_size <= 64
|
|
397
|
+
return rows
|
|
@@ -225,9 +225,16 @@ class Handler:
|
|
|
225
225
|
|
|
226
226
|
handler_instance = handler(self.message)
|
|
227
227
|
handler_instance.chain._set_previous_message(self.chain.get_previous_message())
|
|
228
|
+
handler_instance._on_handoff(self)
|
|
228
229
|
|
|
229
230
|
return handler_instance
|
|
230
231
|
|
|
232
|
+
|
|
233
|
+
def _on_handoff(self, origin: "Handler") -> None:
|
|
234
|
+
"""Called when this handler is reached via handoff(). Override to customize."""
|
|
235
|
+
pass
|
|
236
|
+
|
|
237
|
+
|
|
231
238
|
def freeze(self, func, *args):
|
|
232
239
|
"""
|
|
233
240
|
Return a zero-argument callback that invokes the given function
|
|
@@ -36,6 +36,7 @@ __all__ = [
|
|
|
36
36
|
"CallbackButton",
|
|
37
37
|
"AlertButton",
|
|
38
38
|
"NotificationButton",
|
|
39
|
+
"InvokeButton",
|
|
39
40
|
|
|
40
41
|
"ButtonStyle"
|
|
41
42
|
]
|
|
@@ -56,8 +57,9 @@ class InlineButton:
|
|
|
56
57
|
- `SuggestButton`
|
|
57
58
|
- `CopyTextButton`
|
|
58
59
|
- `CallbackButton`
|
|
59
|
-
- `AlertButton
|
|
60
|
-
- `NotificationButton
|
|
60
|
+
- `AlertButton`
|
|
61
|
+
- `NotificationButton`
|
|
62
|
+
- `InvokeButton`
|
|
61
63
|
"""
|
|
62
64
|
|
|
63
65
|
_bot: TeleBot
|
|
@@ -76,6 +78,7 @@ class InlineButton:
|
|
|
76
78
|
Callback: type["CallbackButton"]
|
|
77
79
|
Alert: type["AlertButton"]
|
|
78
80
|
Notification: type["NotificationButton"]
|
|
81
|
+
Invoke: type["InvokeButton"]
|
|
79
82
|
|
|
80
83
|
Styles: type[ButtonStyle] = ButtonStyle
|
|
81
84
|
|
|
@@ -336,11 +339,11 @@ class CallbackButton(InlineButton):
|
|
|
336
339
|
|
|
337
340
|
def __call__(self, call: CallbackQuery):
|
|
338
341
|
self._invoke_chain_callback()
|
|
339
|
-
self._invoke_callback()
|
|
340
342
|
self._answer_callback_query(call)
|
|
343
|
+
self._invoke_callback()
|
|
341
344
|
|
|
342
345
|
def __init__(
|
|
343
|
-
self,
|
|
346
|
+
self,
|
|
344
347
|
callback: Callable[..., Any] | None,
|
|
345
348
|
|
|
346
349
|
pass_args: tuple | list | None = None,
|
|
@@ -482,6 +485,130 @@ class NotificationButton(CallbackButton):
|
|
|
482
485
|
style=style,
|
|
483
486
|
**kwargs
|
|
484
487
|
)
|
|
488
|
+
|
|
489
|
+
class InvokeButton(CallbackButton):
|
|
490
|
+
"""
|
|
491
|
+
An inline keyboard button that calls a named method on a given object when pressed.
|
|
492
|
+
|
|
493
|
+
Unlike `CallbackButton`, which takes a callable directly, `InvokeButton` resolves
|
|
494
|
+
the method at invocation time via `getattr(obj, invoke)`.
|
|
495
|
+
|
|
496
|
+
:param obj: The object on which the method will be called.
|
|
497
|
+
:type obj: `Any`
|
|
498
|
+
|
|
499
|
+
:param invoke: Name of the method to call on `obj`.
|
|
500
|
+
:type invoke: `str`
|
|
501
|
+
|
|
502
|
+
:param pass_args: Positional arguments to pass into the method.
|
|
503
|
+
:type pass_args: `tuple | list | None`
|
|
504
|
+
|
|
505
|
+
:param pass_kwargs: Keyword arguments to pass into the method.
|
|
506
|
+
:type pass_kwargs: `dict[str, Any] | None`
|
|
507
|
+
|
|
508
|
+
:param answer_text: Optional text to send as an answer to the callback query.
|
|
509
|
+
:type answer_text: `str | None`
|
|
510
|
+
|
|
511
|
+
:param answer_as_alert: If `True`, the answer is shown as a popup alert.
|
|
512
|
+
If `False`, it appears as a notification at the top of the chat.
|
|
513
|
+
:type answer_as_alert: `bool`
|
|
514
|
+
|
|
515
|
+
:param style: Style of the button. Must be one of `ButtonStyle.DANGER` (red),
|
|
516
|
+
`*.SUCCESS` (green) or `*.PRIMARY` (blue).
|
|
517
|
+
You can also pass these as string values: "danger", "success", "primary".
|
|
518
|
+
If omitted, an app-specific default style is used.
|
|
519
|
+
:type style: `str | ButtonStyle | None`
|
|
520
|
+
|
|
521
|
+
:param kwargs: Additional keyword arguments passed directly to `InlineKeyboardButton`.
|
|
522
|
+
:type kwargs: `Any`
|
|
523
|
+
|
|
524
|
+
Example::
|
|
525
|
+
|
|
526
|
+
self.chain.set_inline_keyboard({
|
|
527
|
+
"📖 My Deck": InvokeButton(self.handoff(DeckHandler), "handle"),
|
|
528
|
+
})
|
|
529
|
+
"""
|
|
530
|
+
class _CallbackInvoker(CallbackButton._CallbackInvoker):
|
|
531
|
+
def __init__(
|
|
532
|
+
self,
|
|
533
|
+
chain_callback: Callable[[], None],
|
|
534
|
+
|
|
535
|
+
obj: Any,
|
|
536
|
+
invoke: str,
|
|
537
|
+
|
|
538
|
+
pass_args: tuple | list | None = None,
|
|
539
|
+
pass_kwargs: dict[str, Any] | None = None,
|
|
540
|
+
|
|
541
|
+
answer_text: str | None = None,
|
|
542
|
+
answer_as_alert: bool = True,
|
|
543
|
+
|
|
544
|
+
style: str | None | ButtonStyle = None,
|
|
545
|
+
|
|
546
|
+
kwargs: dict[str, Any] = {}
|
|
547
|
+
):
|
|
548
|
+
self._obj = obj
|
|
549
|
+
self._invoke = invoke
|
|
550
|
+
|
|
551
|
+
self._pass_args = pass_args
|
|
552
|
+
self._pass_kwargs = pass_kwargs
|
|
553
|
+
|
|
554
|
+
self._answer_text = answer_text
|
|
555
|
+
self._answer_as_alert = answer_as_alert
|
|
556
|
+
|
|
557
|
+
self._style = style
|
|
558
|
+
|
|
559
|
+
self._kwargs = {"style": style} | kwargs
|
|
560
|
+
|
|
561
|
+
self._chain_callback: Callable[[], None] = chain_callback
|
|
562
|
+
|
|
563
|
+
def _invoke_callback(self):
|
|
564
|
+
obj: Any = self.__dict__["_obj"]
|
|
565
|
+
callback: Any = getattr(obj, self._invoke)
|
|
566
|
+
|
|
567
|
+
args = self._pass_args or ()
|
|
568
|
+
kwargs = self._pass_kwargs or {}
|
|
569
|
+
|
|
570
|
+
callback(*args, **kwargs)
|
|
571
|
+
|
|
572
|
+
def __init__(
|
|
573
|
+
self,
|
|
574
|
+
obj: Any,
|
|
575
|
+
invoke: str,
|
|
576
|
+
|
|
577
|
+
pass_args: tuple | list | None = None,
|
|
578
|
+
pass_kwargs: dict[str, Any] | None = None,
|
|
579
|
+
*,
|
|
580
|
+
answer_text: str | None = None,
|
|
581
|
+
answer_as_alert: bool = True,
|
|
582
|
+
|
|
583
|
+
style: str | None | ButtonStyle = None,
|
|
584
|
+
|
|
585
|
+
**kwargs
|
|
586
|
+
):
|
|
587
|
+
self._obj = obj
|
|
588
|
+
self._invoke = invoke
|
|
589
|
+
|
|
590
|
+
self._pass_args = pass_args
|
|
591
|
+
self._pass_kwargs = pass_kwargs
|
|
592
|
+
|
|
593
|
+
self._answer_text = answer_text
|
|
594
|
+
self._answer_as_alert = answer_as_alert
|
|
595
|
+
|
|
596
|
+
self._style = self._normalize_style(style)
|
|
597
|
+
|
|
598
|
+
self._kwargs = kwargs
|
|
599
|
+
|
|
600
|
+
def build_invoker(self, chain_callback: Callable[[], None]) -> _CallbackInvoker:
|
|
601
|
+
return self._CallbackInvoker(
|
|
602
|
+
chain_callback=chain_callback,
|
|
603
|
+
obj=self._obj,
|
|
604
|
+
invoke=self._invoke,
|
|
605
|
+
pass_args=self._pass_args,
|
|
606
|
+
pass_kwargs=self._pass_kwargs,
|
|
607
|
+
answer_text=self._answer_text,
|
|
608
|
+
answer_as_alert=self._answer_as_alert,
|
|
609
|
+
style=self._style,
|
|
610
|
+
kwargs=self._kwargs
|
|
611
|
+
)
|
|
485
612
|
|
|
486
613
|
InlineButton.Link = LinkButton
|
|
487
614
|
InlineButton.WebApp = WebAppButton
|
|
@@ -489,4 +616,5 @@ InlineButton.Suggest = SuggestButton
|
|
|
489
616
|
InlineButton.CopyText = CopyTextButton
|
|
490
617
|
InlineButton.Callback = CallbackButton
|
|
491
618
|
InlineButton.Alert = AlertButton
|
|
492
|
-
InlineButton.Notification = NotificationButton
|
|
619
|
+
InlineButton.Notification = NotificationButton
|
|
620
|
+
InlineButton.Invoke = InvokeButton
|
|
@@ -368,7 +368,7 @@ class DSLHandler(telekit.Handler):
|
|
|
368
368
|
if not hasattr(self, "_script_data_factory"):
|
|
369
369
|
message: str = f"{type(self).__name__}().start_script(): Script is not analyzed yet. Call cls.analyze_file() or cls.analyze_string() before starting it."
|
|
370
370
|
library.error(message)
|
|
371
|
-
self.chain.sender.
|
|
371
|
+
self.chain.sender.send_error("DSLError", message)
|
|
372
372
|
return
|
|
373
373
|
|
|
374
374
|
self.script_data = self._script_data_factory()
|
|
@@ -799,7 +799,7 @@ class DSLHandler(telekit.Handler):
|
|
|
799
799
|
# ----------------------------------------------------------------------------
|
|
800
800
|
|
|
801
801
|
def _fail(self, message: str, exception: type[Exception]=Exception):
|
|
802
|
-
self.chain.sender.
|
|
802
|
+
self.chain.sender.send_error("🤷 Something went wrong...", message)
|
|
803
803
|
library.error(message)
|
|
804
804
|
return exception(message)
|
|
805
805
|
|
|
@@ -44,8 +44,12 @@ class EntryHandler(telekit.Handler):
|
|
|
44
44
|
# Handling Logic
|
|
45
45
|
# ------------------------------------------
|
|
46
46
|
|
|
47
|
+
def __init__(self, *args, **kwargs):
|
|
48
|
+
super().__init__(*args, **kwargs)
|
|
49
|
+
|
|
50
|
+
self._user_data = UserData(self.user.id)
|
|
51
|
+
|
|
47
52
|
def handle(self) -> None:
|
|
48
|
-
self._user_data = UserData(self.message.chat.id)
|
|
49
53
|
self.chain.disable_timeout_warnings()
|
|
50
54
|
self.entry_name()
|
|
51
55
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import telebot.types
|
|
2
1
|
import telekit
|
|
3
2
|
|
|
3
|
+
from telekit.inline_buttons import CallbackButton
|
|
4
|
+
|
|
4
5
|
source = """
|
|
5
6
|
# Page Title
|
|
6
7
|
|
|
@@ -46,9 +47,12 @@ class PagesHandler(telekit.Handler):
|
|
|
46
47
|
self.chain.sender.set_title("Simple Pages Example")
|
|
47
48
|
self.chain.sender.set_message("Here are some common questions and answers to help you get started:")
|
|
48
49
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
self.chain.set_inline_choice(
|
|
51
|
+
self.display_page,
|
|
52
|
+
pages | {
|
|
53
|
+
"« Back": CallbackButton(self.handoff_start)
|
|
54
|
+
}
|
|
55
|
+
)
|
|
52
56
|
|
|
53
57
|
self.chain.disable_timeout_warnings()
|
|
54
58
|
self.chain.edit()
|
|
@@ -60,4 +64,7 @@ class PagesHandler(telekit.Handler):
|
|
|
60
64
|
self.chain.sender.set_message(page[1])
|
|
61
65
|
|
|
62
66
|
self.chain.set_inline_keyboard({"« Back": self.display_home_page})
|
|
63
|
-
self.chain.edit()
|
|
67
|
+
self.chain.edit()
|
|
68
|
+
|
|
69
|
+
def handoff_start(self):
|
|
70
|
+
self.handoff("StartHandler").handle()
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
from telebot.types import Message
|
|
2
1
|
from telekit.types import (
|
|
3
2
|
TextDocument, CopyTextButton, ParseMode
|
|
4
3
|
)
|
|
5
4
|
from telekit.styles import Quote, Escape
|
|
6
5
|
import telekit
|
|
7
6
|
|
|
8
|
-
class TextDocumentHandler(telekit.Handler):
|
|
7
|
+
class TextDocumentHandler(telekit.traits.TrackHandoffOrigin, telekit.Handler):
|
|
9
8
|
|
|
10
9
|
@classmethod
|
|
11
10
|
def init_handler(cls) -> None:
|
|
@@ -26,6 +25,11 @@ class TextDocumentHandler(telekit.Handler):
|
|
|
26
25
|
self.handle_text_document,
|
|
27
26
|
allowed_extensions=(".txt", ".py", ".md", ".html")
|
|
28
27
|
)
|
|
28
|
+
self.chain.set_inline_keyboard(
|
|
29
|
+
{
|
|
30
|
+
"« Back": self.handoff_back_or("StartHandler")
|
|
31
|
+
}
|
|
32
|
+
)
|
|
29
33
|
|
|
30
34
|
self.chain.disable_timeout_warnings()
|
|
31
35
|
self.chain.edit()
|