telekit 2.2.0a2__tar.gz → 2.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {telekit-2.2.0a2/telekit.egg-info → telekit-2.3.0}/PKG-INFO +44 -28
- {telekit-2.2.0a2 → telekit-2.3.0}/README.md +41 -2
- {telekit-2.2.0a2 → telekit-2.3.0}/setup.py +1 -2
- {telekit-2.2.0a2 → telekit-2.3.0}/telekit/__init__.py +6 -0
- {telekit-2.2.0a2 → telekit-2.3.0}/telekit/_callback_query_handler.py +18 -6
- {telekit-2.2.0a2 → telekit-2.3.0}/telekit/_chain.py +3 -5
- {telekit-2.2.0a2 → telekit-2.3.0}/telekit/_chain_base.py +9 -3
- {telekit-2.2.0a2 → telekit-2.3.0}/telekit/_chain_entry_logic.py +12 -0
- {telekit-2.2.0a2 → telekit-2.3.0}/telekit/_chain_inline_keyboards_logic.py +42 -25
- {telekit-2.2.0a2 → telekit-2.3.0}/telekit/_handler.py +7 -0
- {telekit-2.2.0a2 → telekit-2.3.0}/telekit/_init.py +1 -1
- {telekit-2.2.0a2 → telekit-2.3.0}/telekit/_inline_buttons.py +243 -12
- {telekit-2.2.0a2 → telekit-2.3.0}/telekit/_logger.py +42 -41
- {telekit-2.2.0a2 → telekit-2.3.0}/telekit/_state.py +15 -2
- {telekit-2.2.0a2 → telekit-2.3.0}/telekit/_telekit_dsl/mixin.py +2 -2
- telekit-2.3.0/telekit/_trait.py +10 -0
- {telekit-2.2.0a2 → telekit-2.3.0}/telekit/_version.py +1 -1
- telekit-2.3.0/telekit/debug.py +8 -0
- {telekit-2.2.0a2 → telekit-2.3.0}/telekit/example/example_handlers/__init__.py +2 -1
- telekit-2.3.0/telekit/example/example_handlers/calendar.py +155 -0
- {telekit-2.2.0a2 → telekit-2.3.0}/telekit/example/example_handlers/entry.py +5 -1
- {telekit-2.2.0a2 → telekit-2.3.0}/telekit/example/example_handlers/pages.py +12 -5
- {telekit-2.2.0a2 → telekit-2.3.0}/telekit/example/example_handlers/spells.py +12 -13
- {telekit-2.2.0a2 → telekit-2.3.0}/telekit/example/example_handlers/start.py +2 -1
- {telekit-2.2.0a2 → telekit-2.3.0}/telekit/example/example_handlers/text_document.py +6 -2
- {telekit-2.2.0a2 → telekit-2.3.0}/telekit/inline_buttons.py +8 -0
- {telekit-2.2.0a2 → telekit-2.3.0}/telekit/senders.py +413 -102
- {telekit-2.2.0a2 → telekit-2.3.0}/telekit/server.py +7 -5
- telekit-2.3.0/telekit/traits/__init__.py +3 -0
- telekit-2.3.0/telekit/traits/calendar_pick.py +807 -0
- telekit-2.3.0/telekit/traits/paginated_choice.py +145 -0
- telekit-2.3.0/telekit/traits/track_handoff_origin.py +99 -0
- {telekit-2.2.0a2 → telekit-2.3.0}/telekit/types.py +43 -30
- {telekit-2.2.0a2 → telekit-2.3.0}/telekit/utils.py +215 -15
- {telekit-2.2.0a2 → telekit-2.3.0/telekit.egg-info}/PKG-INFO +44 -28
- {telekit-2.2.0a2 → telekit-2.3.0}/telekit.egg-info/SOURCES.txt +8 -1
- {telekit-2.2.0a2 → telekit-2.3.0}/LICENSE +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0}/setup.cfg +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0}/telekit/_buildtext/__init__.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0}/telekit/_buildtext/formatter.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0}/telekit/_buildtext/styles.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0}/telekit/_chapters/__init__.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0}/telekit/_chapters/chapters.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0}/telekit/_input_handler.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0}/telekit/_on.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0}/telekit/_snapvault/__init__.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0}/telekit/_snapvault/snapcode.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0}/telekit/_snapvault/snapvault.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0}/telekit/_telekit_dsl/__init__.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0}/telekit/_telekit_dsl/parser/__init__.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0}/telekit/_telekit_dsl/parser/builder.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0}/telekit/_telekit_dsl/parser/canvas_parser.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0}/telekit/_telekit_dsl/parser/lexer.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0}/telekit/_telekit_dsl/parser/nodes.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0}/telekit/_telekit_dsl/parser/parser.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0}/telekit/_telekit_dsl/parser/token.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0}/telekit/_telekit_dsl/telekit_dsl.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0}/telekit/_telekit_dsl/telekit_orm.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0}/telekit/_timeout.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0}/telekit/_user.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0}/telekit/dices.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0}/telekit/example/__init__.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0}/telekit/example/example_handlers/complete_hotel.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0}/telekit/example/example_handlers/counter.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0}/telekit/example/example_handlers/dsl.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0}/telekit/example/example_handlers/faq.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0}/telekit/example/example_handlers/hotel.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0}/telekit/example/example_handlers/on_text.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0}/telekit/example/example_handlers/pyapi.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0}/telekit/example/example_handlers/qr.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0}/telekit/example/example_handlers/quiz.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0}/telekit/example/example_server.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0}/telekit/parameters.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0}/telekit/styles.py +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0}/telekit.egg-info/dependency_links.txt +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0}/telekit.egg-info/requires.txt +0 -0
- {telekit-2.2.0a2 → telekit-2.3.0}/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.0
|
|
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, text styling via `Bold()`, `Italic()`, built-in emoji game results for `🎲 🎯 🏀 ⚽ 🎳 🎰`, and much more out of the box. Its declarative design makes bots easier to read, maintain, and extend.
|
|
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
|
|
|
@@ -101,7 +102,8 @@ Even in its beta stage, Telekit accelerates bot development, offering typed comm
|
|
|
101
102
|
- [Dialogue](https://github.com/Romashkaa/telekit/blob/main/docs/examples/dialogue.md)
|
|
102
103
|
- [Risk Game](https://github.com/Romashkaa/telekit/blob/main/docs/examples/risk_game.md)
|
|
103
104
|
- [Counter](https://github.com/Romashkaa/telekit/blob/main/docs/examples/counter.md)
|
|
104
|
-
- [Quiz (
|
|
105
|
+
- [Quiz (DSL)](https://github.com/Romashkaa/telekit/blob/main/docs/examples/quiz.md)
|
|
106
|
+
- [Hotel (DSL)](https://github.com/Romashkaa/telekit/blob/main/docs/examples/complete_hotel.md)
|
|
105
107
|
- [More...](https://github.com/Romashkaa/telekit/blob/main/docs/examples/examples.md)
|
|
106
108
|
|
|
107
109
|
## Overview
|
|
@@ -347,6 +349,42 @@ telekit.Server(BOT_TOKEN).polling()
|
|
|
347
349
|
|
|
348
350
|
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.
|
|
349
351
|
|
|
352
|
+
### Traits
|
|
353
|
+
|
|
354
|
+
Traits are reusable behavior modules you can mix into any handler.
|
|
355
|
+
|
|
356
|
+
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.
|
|
357
|
+
|
|
358
|
+
```py
|
|
359
|
+
from telekit.traits import CalendarPick
|
|
360
|
+
|
|
361
|
+
class CalendarHandler(CalendarPick, telekit.Handler):
|
|
362
|
+
|
|
363
|
+
@classmethod
|
|
364
|
+
def init_handler(cls) -> None:
|
|
365
|
+
cls.on.command("calendar").invoke(cls.handle)
|
|
366
|
+
|
|
367
|
+
def handle(self) -> None:
|
|
368
|
+
self.chain.sender.set_title("📅 Choose a date")
|
|
369
|
+
self.chain.sender.set_message("Select any date — past or future:")
|
|
370
|
+
self.chain.sender.set_remove_text(False)
|
|
371
|
+
|
|
372
|
+
self.calendar_pick(self.handle_date) # HERE
|
|
373
|
+
|
|
374
|
+
def handle_date(self, date: datetime.date) -> None:
|
|
375
|
+
self.chain.sender.set_text(f"You picked: {date}")
|
|
376
|
+
self.chain.send()
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
<details>
|
|
380
|
+
<summary>Result</summary>
|
|
381
|
+
<table>
|
|
382
|
+
<tr>
|
|
383
|
+
<td><img src="./docs/images/calendar.png" alt="Telekit Calendar Example" width="500"></td>
|
|
384
|
+
</tr>
|
|
385
|
+
</table>
|
|
386
|
+
</details>
|
|
387
|
+
|
|
350
388
|
### Example Bot
|
|
351
389
|
|
|
352
390
|
You can launch an example bot by **running the following code**:
|
|
@@ -367,6 +405,7 @@ It includes example commands, dialogs, keyboards, and style usage.
|
|
|
367
405
|
- **Styles API** for rich text (`Bold`, `Italic`, `Links`) with **automatic escaping**.
|
|
368
406
|
- Deep linking and **typed command parameters**.
|
|
369
407
|
- **Built-in DSL** for menus, FAQs, and simple bots.
|
|
408
|
+
- Reusable **Traits** for composable, plug-and-play behavior (for example, a built-in declarative calendar picker).
|
|
370
409
|
- **Zero-code** [Obsidian Canvas](https://github.com/Romashkaa/telekit/blob/main/docs/examples/canvas_faq.md) mode.
|
|
371
410
|
- Seamless integration with **pyTelegramBotAPI**.
|
|
372
411
|
|
|
@@ -378,29 +417,6 @@ It tries to make Telegram bot development easier.
|
|
|
378
417
|
|
|
379
418
|
---
|
|
380
419
|
|
|
381
|
-
# Changes in version 2.
|
|
382
|
-
|
|
383
|
-
## New Button Types
|
|
384
|
-
|
|
385
|
-
| **Name** | **Description** |
|
|
386
|
-
|----------------------|----------------------------------------------------------|
|
|
387
|
-
| `AlertButton` | A callback button that shows a popup alert when pressed. |
|
|
388
|
-
| `NotificationButton` | A callback button that shows a notification. |
|
|
389
|
-
|
|
390
|
-
## User Improvements
|
|
391
|
-
|
|
392
|
-
| **Name** | **Description** |
|
|
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. |
|
|
420
|
+
# Changes in version 2.3.0
|
|
404
421
|
|
|
405
|
-
|
|
406
|
-
- Added `__repr__`.
|
|
422
|
+
[View changelog on GitHub](https://github.com/Romashkaa/telekit/blob/main/CHANGELOG.md)
|
|
@@ -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, text styling via `Bold()`, `Italic()`, built-in emoji game results for `🎲 🎯 🏀 ⚽ 🎳 🎰`, and much more out of the box. Its declarative design makes bots easier to read, maintain, and extend.
|
|
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
|
|
|
@@ -65,7 +66,8 @@ Even in its beta stage, Telekit accelerates bot development, offering typed comm
|
|
|
65
66
|
- [Dialogue](https://github.com/Romashkaa/telekit/blob/main/docs/examples/dialogue.md)
|
|
66
67
|
- [Risk Game](https://github.com/Romashkaa/telekit/blob/main/docs/examples/risk_game.md)
|
|
67
68
|
- [Counter](https://github.com/Romashkaa/telekit/blob/main/docs/examples/counter.md)
|
|
68
|
-
- [Quiz (
|
|
69
|
+
- [Quiz (DSL)](https://github.com/Romashkaa/telekit/blob/main/docs/examples/quiz.md)
|
|
70
|
+
- [Hotel (DSL)](https://github.com/Romashkaa/telekit/blob/main/docs/examples/complete_hotel.md)
|
|
69
71
|
- [More...](https://github.com/Romashkaa/telekit/blob/main/docs/examples/examples.md)
|
|
70
72
|
|
|
71
73
|
## Overview
|
|
@@ -311,6 +313,42 @@ telekit.Server(BOT_TOKEN).polling()
|
|
|
311
313
|
|
|
312
314
|
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.
|
|
313
315
|
|
|
316
|
+
### Traits
|
|
317
|
+
|
|
318
|
+
Traits are reusable behavior modules you can mix into any handler.
|
|
319
|
+
|
|
320
|
+
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.
|
|
321
|
+
|
|
322
|
+
```py
|
|
323
|
+
from telekit.traits import CalendarPick
|
|
324
|
+
|
|
325
|
+
class CalendarHandler(CalendarPick, telekit.Handler):
|
|
326
|
+
|
|
327
|
+
@classmethod
|
|
328
|
+
def init_handler(cls) -> None:
|
|
329
|
+
cls.on.command("calendar").invoke(cls.handle)
|
|
330
|
+
|
|
331
|
+
def handle(self) -> None:
|
|
332
|
+
self.chain.sender.set_title("📅 Choose a date")
|
|
333
|
+
self.chain.sender.set_message("Select any date — past or future:")
|
|
334
|
+
self.chain.sender.set_remove_text(False)
|
|
335
|
+
|
|
336
|
+
self.calendar_pick(self.handle_date) # HERE
|
|
337
|
+
|
|
338
|
+
def handle_date(self, date: datetime.date) -> None:
|
|
339
|
+
self.chain.sender.set_text(f"You picked: {date}")
|
|
340
|
+
self.chain.send()
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
<details>
|
|
344
|
+
<summary>Result</summary>
|
|
345
|
+
<table>
|
|
346
|
+
<tr>
|
|
347
|
+
<td><img src="./docs/images/calendar.png" alt="Telekit Calendar Example" width="500"></td>
|
|
348
|
+
</tr>
|
|
349
|
+
</table>
|
|
350
|
+
</details>
|
|
351
|
+
|
|
314
352
|
### Example Bot
|
|
315
353
|
|
|
316
354
|
You can launch an example bot by **running the following code**:
|
|
@@ -331,6 +369,7 @@ It includes example commands, dialogs, keyboards, and style usage.
|
|
|
331
369
|
- **Styles API** for rich text (`Bold`, `Italic`, `Links`) with **automatic escaping**.
|
|
332
370
|
- Deep linking and **typed command parameters**.
|
|
333
371
|
- **Built-in DSL** for menus, FAQs, and simple bots.
|
|
372
|
+
- Reusable **Traits** for composable, plug-and-play behavior (for example, a built-in declarative calendar picker).
|
|
334
373
|
- **Zero-code** [Obsidian Canvas](https://github.com/Romashkaa/telekit/blob/main/docs/examples/canvas_faq.md) mode.
|
|
335
374
|
- Seamless integration with **pyTelegramBotAPI**.
|
|
336
375
|
|
|
@@ -28,8 +28,7 @@ def readme():
|
|
|
28
28
|
return f.read()
|
|
29
29
|
|
|
30
30
|
def changelog():
|
|
31
|
-
|
|
32
|
-
return f.read()
|
|
31
|
+
return f"[View changelog on GitHub](https://github.com/Romashkaa/telekit/blob/main/CHANGELOG.md)"
|
|
33
32
|
|
|
34
33
|
def install_requires():
|
|
35
34
|
with open('telekit/requirements.txt', 'r') as f:
|
|
@@ -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
|
|
@@ -35,12 +36,15 @@ from . import parameters
|
|
|
35
36
|
from . import inline_buttons
|
|
36
37
|
from . import dices
|
|
37
38
|
from . import utils
|
|
39
|
+
from . import traits
|
|
40
|
+
from . import debug
|
|
38
41
|
|
|
39
42
|
Styles = styles.Styles
|
|
40
43
|
|
|
41
44
|
from ._version import __version__
|
|
42
45
|
|
|
43
46
|
__all__ = [
|
|
47
|
+
"traits",
|
|
44
48
|
"utils",
|
|
45
49
|
"senders",
|
|
46
50
|
"types",
|
|
@@ -54,6 +58,7 @@ __all__ = [
|
|
|
54
58
|
|
|
55
59
|
"Server",
|
|
56
60
|
"Chain",
|
|
61
|
+
"Trait",
|
|
57
62
|
"Handler",
|
|
58
63
|
"CallbackQueryHandler",
|
|
59
64
|
|
|
@@ -64,4 +69,5 @@ __all__ = [
|
|
|
64
69
|
"enable_file_logging",
|
|
65
70
|
"chapters",
|
|
66
71
|
"example",
|
|
72
|
+
"debug",
|
|
67
73
|
]
|
|
@@ -40,15 +40,17 @@ class CallbackQueryHandler:
|
|
|
40
40
|
bot (TeleBot): The Telegram bot instance to be used for sending messages.
|
|
41
41
|
"""
|
|
42
42
|
cls.bot = bot
|
|
43
|
-
cls.user_button_callbacks: dict[int, dict[str, Callable[[
|
|
43
|
+
cls.user_button_callbacks: dict[int, dict[str, Callable[[CallbackQuery], None]]] = {}
|
|
44
44
|
|
|
45
45
|
@bot.callback_query_handler(func=lambda call: True)
|
|
46
|
-
def handle(call:
|
|
46
|
+
def handle(call: CallbackQuery) -> None:
|
|
47
47
|
if not call.data:
|
|
48
48
|
return
|
|
49
49
|
|
|
50
50
|
if call.data.startswith(cls.INLINE_BUTTON):
|
|
51
51
|
cls._handle_inline_button(call)
|
|
52
|
+
elif call.data.startswith(cls.STATIC_BUTTON):
|
|
53
|
+
cls._handle_static_button(call)
|
|
52
54
|
elif call.data.startswith(cls.SUGGEST):
|
|
53
55
|
cls._handle_suggestion(call)
|
|
54
56
|
else:
|
|
@@ -59,7 +61,7 @@ class CallbackQueryHandler:
|
|
|
59
61
|
# –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
|
|
60
62
|
|
|
61
63
|
@classmethod
|
|
62
|
-
def _handle_inline_button(cls, call:
|
|
64
|
+
def _handle_inline_button(cls, call: CallbackQuery):
|
|
63
65
|
if not call.data:
|
|
64
66
|
cls.bot.answer_callback_query(call.id, text=cls._invalid_data_answer[0], show_alert=cls._invalid_data_answer[1])
|
|
65
67
|
return
|
|
@@ -76,10 +78,19 @@ class CallbackQueryHandler:
|
|
|
76
78
|
cls.bot.answer_callback_query(call.id, text=cls._button_is_no_active_answer[0], show_alert=cls._button_is_no_active_answer[1])
|
|
77
79
|
return
|
|
78
80
|
|
|
79
|
-
|
|
81
|
+
if not getattr(callback, "_persistent", False):
|
|
82
|
+
cls.remove_user_button_callbacks(call.from_user.id)
|
|
80
83
|
|
|
81
84
|
callback(call)
|
|
82
85
|
|
|
86
|
+
# –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
|
|
87
|
+
# Static Buttons Handling
|
|
88
|
+
# –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
|
|
89
|
+
|
|
90
|
+
@classmethod
|
|
91
|
+
def _handle_static_button(cls, call: CallbackQuery):
|
|
92
|
+
cls.bot.answer_callback_query(call.id, text="")
|
|
93
|
+
|
|
83
94
|
# –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
|
|
84
95
|
# Suggestion Handling
|
|
85
96
|
# –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
|
|
@@ -87,7 +98,7 @@ class CallbackQueryHandler:
|
|
|
87
98
|
@classmethod
|
|
88
99
|
def _handle_suggestion(cls, call: CallbackQuery):
|
|
89
100
|
text: str = str(call.data)[len(cls.SUGGEST):]
|
|
90
|
-
cls.simulate(call.message, text, from_user=call.from_user)
|
|
101
|
+
cls.simulate(call.message, text, from_user=call.from_user) # pyright: ignore[reportArgumentType]
|
|
91
102
|
cls.bot.answer_callback_query(call.id)
|
|
92
103
|
|
|
93
104
|
@classmethod
|
|
@@ -126,9 +137,10 @@ class CallbackQueryHandler:
|
|
|
126
137
|
# Query Types
|
|
127
138
|
# –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
|
|
128
139
|
|
|
140
|
+
STATIC_BUTTON: str = "static_button"
|
|
129
141
|
INLINE_BUTTON: str = "inline_button:"
|
|
130
142
|
SUGGEST: str = "suggest:"
|
|
131
|
-
|
|
143
|
+
|
|
132
144
|
@classmethod
|
|
133
145
|
def suggest(cls, suggestion: str):
|
|
134
146
|
return f"{cls.SUGGEST}{suggestion}"
|
|
@@ -26,13 +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
|
-
# Logging
|
|
32
|
-
from ._logger import logger
|
|
33
|
-
library = logger.library
|
|
34
|
-
|
|
35
32
|
# Chain modules
|
|
33
|
+
from ._chain_base import library
|
|
36
34
|
from ._chain_inline_keyboards_logic import ChainInlineKeyboardLogic
|
|
37
35
|
from ._chain_entry_logic import ChainEntryLogic, TextDocument
|
|
38
36
|
|
|
@@ -157,7 +155,7 @@ class Chain(ChainInlineKeyboardLogic, ChainEntryLogic):
|
|
|
157
155
|
self.sender.set_edit_message(None)
|
|
158
156
|
self._previous_message = message
|
|
159
157
|
|
|
160
|
-
if
|
|
158
|
+
if Debug.timeout_warnings and _handler and not _timeout:
|
|
161
159
|
library.warning(
|
|
162
160
|
"Next-message handler is active, but no timeout was set for the chain. "
|
|
163
161
|
"This may cause the bot to wait indefinitely."
|
|
@@ -30,11 +30,14 @@ from . import senders
|
|
|
30
30
|
from . import _input_handler
|
|
31
31
|
from . import _timeout
|
|
32
32
|
|
|
33
|
+
# Logging
|
|
34
|
+
from .debug import Debug
|
|
35
|
+
from ._logger import logger
|
|
36
|
+
library = logger.library
|
|
37
|
+
|
|
33
38
|
class ChainBase:
|
|
34
39
|
|
|
35
40
|
bot: telebot.TeleBot
|
|
36
|
-
|
|
37
|
-
_timeout_warnings_enabled: bool = True
|
|
38
41
|
|
|
39
42
|
@classmethod
|
|
40
43
|
def _init(cls, bot: telebot.TeleBot):
|
|
@@ -49,6 +52,8 @@ class ChainBase:
|
|
|
49
52
|
def __init__(self, chat_id: int, *, previous_message: Message | None = None):
|
|
50
53
|
self.chat_id = chat_id
|
|
51
54
|
self.sender = senders.Sender(chat_id)
|
|
55
|
+
self.received = None
|
|
56
|
+
|
|
52
57
|
self._handler = _input_handler.InputHandler(chat_id)
|
|
53
58
|
self._previous_message = previous_message
|
|
54
59
|
self._timeout_handler = _timeout.TimeoutHandler()
|
|
@@ -200,4 +205,5 @@ class ChainBase:
|
|
|
200
205
|
# Timeout API
|
|
201
206
|
|
|
202
207
|
def disable_timeout_warnings(self, value: bool = True) -> None:
|
|
203
|
-
|
|
208
|
+
""".. deprecated:: Set ``Debug.timeout_warnings`` directly."""
|
|
209
|
+
Debug.timeout_warnings = not value
|
|
@@ -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
|
|
@@ -40,8 +40,7 @@ if typing.TYPE_CHECKING:
|
|
|
40
40
|
class ChainInlineKeyboardLogic(ChainBase):
|
|
41
41
|
def set_inline_keyboard(
|
|
42
42
|
self,
|
|
43
|
-
keyboard: dict[str,
|
|
44
|
-
*,
|
|
43
|
+
keyboard: dict[str, Any],
|
|
45
44
|
row_width: int | Iterable[int] = 1
|
|
46
45
|
) -> None:
|
|
47
46
|
"""
|
|
@@ -118,7 +117,6 @@ class ChainInlineKeyboardLogic(ChainBase):
|
|
|
118
117
|
def inline_keyboard[Caption: str, Value](
|
|
119
118
|
self,
|
|
120
119
|
keyboard: dict[Caption, Value],
|
|
121
|
-
*,
|
|
122
120
|
row_width: int | Iterable[int] = 1,
|
|
123
121
|
enable_special_buttons: bool = True
|
|
124
122
|
) -> Callable[[Callable[[Value], None]], None]:
|
|
@@ -151,15 +149,24 @@ class ChainInlineKeyboardLogic(ChainBase):
|
|
|
151
149
|
buttons: list[InlineKeyboardButton] = []
|
|
152
150
|
|
|
153
151
|
for index, (caption, value) in enumerate(keyboard.items()):
|
|
154
|
-
if enable_special_buttons and isinstance(value, InlineButton):
|
|
152
|
+
if enable_special_buttons and isinstance(value, InlineButton) and not isinstance(value, CallbackButton):
|
|
155
153
|
buttons.append(value._compile(caption))
|
|
156
154
|
else:
|
|
155
|
+
if isinstance(value, CallbackButton):
|
|
156
|
+
invoker = value.build_invoker(self._cancel_timeout_and_handlers)
|
|
157
|
+
else:
|
|
158
|
+
invoker = self._get_invoker_with_argument(func, value)
|
|
159
|
+
|
|
157
160
|
callback_data = CallbackQueryHandler.inline_button(f"{index}:{random.randint(1000, 9999)}")
|
|
158
|
-
callback_functions[callback_data] =
|
|
161
|
+
callback_functions[callback_data] = invoker
|
|
162
|
+
|
|
163
|
+
kwargs: dict[str, Any] = getattr(invoker, "_kwargs", None) or {}
|
|
164
|
+
|
|
159
165
|
buttons.append(
|
|
160
166
|
InlineKeyboardButton(
|
|
161
167
|
text=caption,
|
|
162
|
-
callback_data=callback_data
|
|
168
|
+
callback_data=callback_data,
|
|
169
|
+
**kwargs
|
|
163
170
|
)
|
|
164
171
|
)
|
|
165
172
|
|
|
@@ -174,7 +181,6 @@ class ChainInlineKeyboardLogic(ChainBase):
|
|
|
174
181
|
def inline_choice(
|
|
175
182
|
self,
|
|
176
183
|
choices: list[Any] | tuple[Any, ...] | dict[str, Any | InlineButton],
|
|
177
|
-
*,
|
|
178
184
|
row_width: int | Iterable[int] = 1,
|
|
179
185
|
enable_special_buttons: bool = True
|
|
180
186
|
) -> Callable[[Callable[[Any], None]], None]:
|
|
@@ -210,7 +216,6 @@ class ChainInlineKeyboardLogic(ChainBase):
|
|
|
210
216
|
self,
|
|
211
217
|
func: Callable[[Any], None],
|
|
212
218
|
choices: list[Any] | tuple[Any, ...] | dict[str, Any | InlineButton],
|
|
213
|
-
*,
|
|
214
219
|
row_width: int | Iterable[int] = 1,
|
|
215
220
|
enable_special_buttons: bool = True
|
|
216
221
|
) -> None:
|
|
@@ -242,18 +247,34 @@ class ChainInlineKeyboardLogic(ChainBase):
|
|
|
242
247
|
buttons: list[InlineKeyboardButton] = []
|
|
243
248
|
|
|
244
249
|
if not isinstance(choices, dict):
|
|
250
|
+
for c in choices:
|
|
251
|
+
if type(c).__str__ is object.__str__:
|
|
252
|
+
library.warning(
|
|
253
|
+
f"{type(c).__name__} does not implement __str__. "
|
|
254
|
+
f"Consider passing a dict with explicit labels.",
|
|
255
|
+
stacklevel=3
|
|
256
|
+
)
|
|
245
257
|
choices = {str(c): c for c in choices}
|
|
246
258
|
|
|
247
259
|
for index, (caption, value) in enumerate(choices.items()):
|
|
248
|
-
if enable_special_buttons and isinstance(value, InlineButton):
|
|
260
|
+
if enable_special_buttons and isinstance(value, InlineButton) and not isinstance(value, CallbackButton):
|
|
249
261
|
buttons.append(value._compile(caption))
|
|
250
262
|
else:
|
|
263
|
+
if isinstance(value, CallbackButton):
|
|
264
|
+
invoker = value.build_invoker(self._cancel_timeout_and_handlers)
|
|
265
|
+
else:
|
|
266
|
+
invoker = self._get_invoker_with_argument(func, value)
|
|
267
|
+
|
|
251
268
|
callback_data = CallbackQueryHandler.inline_button(f"{index}:{random.randint(1000, 9999)}")
|
|
252
|
-
callback_functions[callback_data] =
|
|
269
|
+
callback_functions[callback_data] = invoker
|
|
270
|
+
|
|
271
|
+
kwargs: dict[str, Any] = getattr(invoker, "_kwargs", None) or {}
|
|
272
|
+
|
|
253
273
|
buttons.append(
|
|
254
274
|
InlineKeyboardButton(
|
|
255
275
|
text=caption,
|
|
256
|
-
callback_data=callback_data
|
|
276
|
+
callback_data=callback_data,
|
|
277
|
+
**kwargs
|
|
257
278
|
)
|
|
258
279
|
)
|
|
259
280
|
|
|
@@ -314,21 +335,21 @@ class ChainInlineKeyboardLogic(ChainBase):
|
|
|
314
335
|
|
|
315
336
|
self.sender.set_reply_markup(markup)
|
|
316
337
|
|
|
317
|
-
def
|
|
318
|
-
def
|
|
338
|
+
def _get_invoker_with_argument(self, callback: Callable, argument: Any, query_answer: tuple[str, bool] | None = None) -> Callable[[CallbackQuery], None]:
|
|
339
|
+
def invoker(call: CallbackQuery) -> None:
|
|
319
340
|
self._cancel_timeout_and_handlers()
|
|
320
|
-
|
|
341
|
+
callback(argument)
|
|
321
342
|
self._answer_callback_query(call, query_answer)
|
|
322
343
|
|
|
323
|
-
return
|
|
344
|
+
return invoker
|
|
324
345
|
|
|
325
|
-
def
|
|
326
|
-
def
|
|
346
|
+
def _get_invoker(self, callback: Callable[..., None], query_answer: tuple[str, bool] | None = None) -> Callable[[CallbackQuery], None]:
|
|
347
|
+
def invoker(call: CallbackQuery):
|
|
327
348
|
self._cancel_timeout_and_handlers()
|
|
328
|
-
|
|
349
|
+
callback()
|
|
329
350
|
self._answer_callback_query(call, query_answer)
|
|
330
351
|
|
|
331
|
-
return
|
|
352
|
+
return invoker
|
|
332
353
|
|
|
333
354
|
def _answer_callback_query(self, call: CallbackQuery, query_answer: tuple[str, bool] | None = None):
|
|
334
355
|
if query_answer is None:
|
|
@@ -369,8 +390,4 @@ class ChainInlineKeyboardLogic(ChainBase):
|
|
|
369
390
|
rows.append(buttons[index:index + last_width])
|
|
370
391
|
index += last_width
|
|
371
392
|
|
|
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
|
|
393
|
+
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
|