telebot-py 1.5.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.
- telebot_py-1.5.0/.gitignore +47 -0
- telebot_py-1.5.0/LICENSE +21 -0
- telebot_py-1.5.0/PKG-INFO +176 -0
- telebot_py-1.5.0/README.md +141 -0
- telebot_py-1.5.0/examples/conversation.py +212 -0
- telebot_py-1.5.0/examples/echo_bot.py +69 -0
- telebot_py-1.5.0/examples/persistence.py +139 -0
- telebot_py-1.5.0/examples/plugins_i18n.py +146 -0
- telebot_py-1.5.0/examples/ptb_reference_echo.py +88 -0
- telebot_py-1.5.0/examples/scheduler.py +132 -0
- telebot_py-1.5.0/examples/webhook.py +88 -0
- telebot_py-1.5.0/pyproject.toml +98 -0
- telebot_py-1.5.0/src/telebot_py/__init__.py +55 -0
- telebot_py-1.5.0/src/telebot_py/bot/__init__.py +61 -0
- telebot_py-1.5.0/src/telebot_py/bot/base.py +108 -0
- telebot_py-1.5.0/src/telebot_py/bot/bulk.py +140 -0
- telebot_py-1.5.0/src/telebot_py/bot/chat_management.py +374 -0
- telebot_py-1.5.0/src/telebot_py/bot/chats.py +166 -0
- telebot_py-1.5.0/src/telebot_py/bot/client.py +264 -0
- telebot_py-1.5.0/src/telebot_py/bot/edits.py +371 -0
- telebot_py-1.5.0/src/telebot_py/bot/errors.py +52 -0
- telebot_py-1.5.0/src/telebot_py/bot/files.py +56 -0
- telebot_py-1.5.0/src/telebot_py/bot/games.py +154 -0
- telebot_py-1.5.0/src/telebot_py/bot/inline.py +131 -0
- telebot_py-1.5.0/src/telebot_py/bot/invite_links.py +122 -0
- telebot_py-1.5.0/src/telebot_py/bot/media.py +776 -0
- telebot_py-1.5.0/src/telebot_py/bot/members.py +280 -0
- telebot_py-1.5.0/src/telebot_py/bot/messages.py +355 -0
- telebot_py-1.5.0/src/telebot_py/bot/payments.py +373 -0
- telebot_py-1.5.0/src/telebot_py/bot/profile.py +268 -0
- telebot_py-1.5.0/src/telebot_py/bot/reactions.py +125 -0
- telebot_py-1.5.0/src/telebot_py/bot/retry.py +50 -0
- telebot_py-1.5.0/src/telebot_py/bot/stickers.py +455 -0
- telebot_py-1.5.0/src/telebot_py/bot/stories_gifts.py +54 -0
- telebot_py-1.5.0/src/telebot_py/bot/topics.py +71 -0
- telebot_py-1.5.0/src/telebot_py/bot/webhook.py +93 -0
- telebot_py-1.5.0/src/telebot_py/components/__init__.py +50 -0
- telebot_py-1.5.0/src/telebot_py/components/inline_query.py +411 -0
- telebot_py-1.5.0/src/telebot_py/components/keyboard/__init__.py +6 -0
- telebot_py-1.5.0/src/telebot_py/components/keyboard/inline.py +137 -0
- telebot_py-1.5.0/src/telebot_py/components/keyboard/reply.py +240 -0
- telebot_py-1.5.0/src/telebot_py/components/menu/__init__.py +25 -0
- telebot_py-1.5.0/src/telebot_py/components/menu/menu.py +201 -0
- telebot_py-1.5.0/src/telebot_py/components/menu/renderer.py +114 -0
- telebot_py-1.5.0/src/telebot_py/components/menu/types.py +79 -0
- telebot_py-1.5.0/src/telebot_py/components/pagination.py +182 -0
- telebot_py-1.5.0/src/telebot_py/ext_errors.py +17 -0
- telebot_py-1.5.0/src/telebot_py/filters/__init__.py +40 -0
- telebot_py-1.5.0/src/telebot_py/filters/base.py +138 -0
- telebot_py-1.5.0/src/telebot_py/filters/matchers.py +190 -0
- telebot_py-1.5.0/src/telebot_py/kernel/__init__.py +21 -0
- telebot_py-1.5.0/src/telebot_py/kernel/app.py +487 -0
- telebot_py-1.5.0/src/telebot_py/kernel/builder.py +208 -0
- telebot_py-1.5.0/src/telebot_py/kernel/context.py +98 -0
- telebot_py-1.5.0/src/telebot_py/kernel/dispatcher.py +227 -0
- telebot_py-1.5.0/src/telebot_py/kernel/lifecycle.py +26 -0
- telebot_py-1.5.0/src/telebot_py/kernel/polling.py +155 -0
- telebot_py-1.5.0/src/telebot_py/kernel/runners.py +180 -0
- telebot_py-1.5.0/src/telebot_py/kernel/webhook.py +414 -0
- telebot_py-1.5.0/src/telebot_py/plugins/__init__.py +15 -0
- telebot_py-1.5.0/src/telebot_py/plugins/i18n.py +174 -0
- telebot_py-1.5.0/src/telebot_py/plugins/manager.py +242 -0
- telebot_py-1.5.0/src/telebot_py/plugins/plugin.py +70 -0
- telebot_py-1.5.0/src/telebot_py/py.typed +0 -0
- telebot_py-1.5.0/src/telebot_py/routing/__init__.py +75 -0
- telebot_py-1.5.0/src/telebot_py/routing/async_conversation/__init__.py +26 -0
- telebot_py-1.5.0/src/telebot_py/routing/async_conversation/conversation.py +200 -0
- telebot_py-1.5.0/src/telebot_py/routing/async_conversation/handler.py +162 -0
- telebot_py-1.5.0/src/telebot_py/routing/async_conversation/manager.py +326 -0
- telebot_py-1.5.0/src/telebot_py/routing/async_conversation/types.py +49 -0
- telebot_py-1.5.0/src/telebot_py/routing/conversation.py +262 -0
- telebot_py-1.5.0/src/telebot_py/routing/handlers/__init__.py +64 -0
- telebot_py-1.5.0/src/telebot_py/routing/handlers/base.py +93 -0
- telebot_py-1.5.0/src/telebot_py/routing/handlers/business.py +52 -0
- telebot_py-1.5.0/src/telebot_py/routing/handlers/callback_query.py +87 -0
- telebot_py-1.5.0/src/telebot_py/routing/handlers/chat_member.py +81 -0
- telebot_py-1.5.0/src/telebot_py/routing/handlers/chat_request.py +81 -0
- telebot_py-1.5.0/src/telebot_py/routing/handlers/command.py +95 -0
- telebot_py-1.5.0/src/telebot_py/routing/handlers/inline_query.py +157 -0
- telebot_py-1.5.0/src/telebot_py/routing/handlers/message.py +49 -0
- telebot_py-1.5.0/src/telebot_py/routing/handlers/payment.py +67 -0
- telebot_py-1.5.0/src/telebot_py/routing/handlers/reaction.py +120 -0
- telebot_py-1.5.0/src/telebot_py/routing/handlers/type.py +55 -0
- telebot_py-1.5.0/src/telebot_py/routing/linear_conversation.py +236 -0
- telebot_py-1.5.0/src/telebot_py/scheduler/__init__.py +22 -0
- telebot_py-1.5.0/src/telebot_py/scheduler/job.py +203 -0
- telebot_py-1.5.0/src/telebot_py/scheduler/queue.py +313 -0
- telebot_py-1.5.0/src/telebot_py/scheduler/rrule/__init__.py +6 -0
- telebot_py-1.5.0/src/telebot_py/scheduler/rrule/rule.py +159 -0
- telebot_py-1.5.0/src/telebot_py/scheduler/rrule/types.py +69 -0
- telebot_py-1.5.0/src/telebot_py/storage/__init__.py +18 -0
- telebot_py-1.5.0/src/telebot_py/storage/base.py +401 -0
- telebot_py-1.5.0/src/telebot_py/storage/driver.py +76 -0
- telebot_py-1.5.0/src/telebot_py/storage/json.py +132 -0
- telebot_py-1.5.0/src/telebot_py/storage/memory.py +50 -0
- telebot_py-1.5.0/src/telebot_py/storage/sqlite.py +310 -0
- telebot_py-1.5.0/src/telebot_py/types/__init__.py +232 -0
- telebot_py-1.5.0/src/telebot_py/types/base.py +122 -0
- telebot_py-1.5.0/src/telebot_py/types/business.py +160 -0
- telebot_py-1.5.0/src/telebot_py/types/callback_query.py +39 -0
- telebot_py-1.5.0/src/telebot_py/types/chat.py +360 -0
- telebot_py-1.5.0/src/telebot_py/types/chat_members.py +262 -0
- telebot_py-1.5.0/src/telebot_py/types/common.py +251 -0
- telebot_py-1.5.0/src/telebot_py/types/files.py +42 -0
- telebot_py-1.5.0/src/telebot_py/types/games.py +47 -0
- telebot_py-1.5.0/src/telebot_py/types/keyboards.py +280 -0
- telebot_py-1.5.0/src/telebot_py/types/media.py +189 -0
- telebot_py-1.5.0/src/telebot_py/types/message.py +286 -0
- telebot_py-1.5.0/src/telebot_py/types/message_extras.py +250 -0
- telebot_py-1.5.0/src/telebot_py/types/payments.py +169 -0
- telebot_py-1.5.0/src/telebot_py/types/reactions.py +106 -0
- telebot_py-1.5.0/src/telebot_py/types/stickers.py +123 -0
- telebot_py-1.5.0/src/telebot_py/types/topics.py +122 -0
- telebot_py-1.5.0/src/telebot_py/types/update.py +189 -0
- telebot_py-1.5.0/src/telebot_py/types/user.py +46 -0
- telebot_py-1.5.0/src/telebot_py/utils/__init__.py +0 -0
- telebot_py-1.5.0/tests/conftest.py +188 -0
- telebot_py-1.5.0/tests/integration/__init__.py +0 -0
- telebot_py-1.5.0/tests/integration/test_dispatch_flow.py +412 -0
- telebot_py-1.5.0/tests/integration/test_kernel_wiring.py +341 -0
- telebot_py-1.5.0/tests/integration/test_lifecycle.py +256 -0
- telebot_py-1.5.0/tests/integration/test_live_smoke.py +54 -0
- telebot_py-1.5.0/tests/integration/test_persistence_restart.py +160 -0
- telebot_py-1.5.0/tests/integration/test_polling.py +357 -0
- telebot_py-1.5.0/tests/integration/test_webhook.py +348 -0
- telebot_py-1.5.0/tests/unit/__init__.py +0 -0
- telebot_py-1.5.0/tests/unit/bot/__init__.py +0 -0
- telebot_py-1.5.0/tests/unit/bot/helpers.py +51 -0
- telebot_py-1.5.0/tests/unit/bot/test_bot_methods.py +532 -0
- telebot_py-1.5.0/tests/unit/bot/test_errors.py +61 -0
- telebot_py-1.5.0/tests/unit/bot/test_methods_bulk.py +92 -0
- telebot_py-1.5.0/tests/unit/bot/test_methods_chat_management.py +190 -0
- telebot_py-1.5.0/tests/unit/bot/test_methods_edits.py +201 -0
- telebot_py-1.5.0/tests/unit/bot/test_methods_files.py +66 -0
- telebot_py-1.5.0/tests/unit/bot/test_methods_games.py +96 -0
- telebot_py-1.5.0/tests/unit/bot/test_methods_inline.py +77 -0
- telebot_py-1.5.0/tests/unit/bot/test_methods_invite_links.py +96 -0
- telebot_py-1.5.0/tests/unit/bot/test_methods_media.py +336 -0
- telebot_py-1.5.0/tests/unit/bot/test_methods_members.py +144 -0
- telebot_py-1.5.0/tests/unit/bot/test_methods_payments.py +148 -0
- telebot_py-1.5.0/tests/unit/bot/test_methods_profile.py +144 -0
- telebot_py-1.5.0/tests/unit/bot/test_methods_reactions.py +115 -0
- telebot_py-1.5.0/tests/unit/bot/test_methods_stickers.py +266 -0
- telebot_py-1.5.0/tests/unit/bot/test_methods_stories_gifts.py +60 -0
- telebot_py-1.5.0/tests/unit/bot/test_methods_topics.py +74 -0
- telebot_py-1.5.0/tests/unit/bot/test_retry.py +292 -0
- telebot_py-1.5.0/tests/unit/components/__init__.py +0 -0
- telebot_py-1.5.0/tests/unit/components/test_components.py +346 -0
- telebot_py-1.5.0/tests/unit/filters/__init__.py +0 -0
- telebot_py-1.5.0/tests/unit/filters/test_filters.py +251 -0
- telebot_py-1.5.0/tests/unit/kernel/__init__.py +0 -0
- telebot_py-1.5.0/tests/unit/kernel/test_application.py +284 -0
- telebot_py-1.5.0/tests/unit/plugins/__init__.py +0 -0
- telebot_py-1.5.0/tests/unit/plugins/test_i18n.py +236 -0
- telebot_py-1.5.0/tests/unit/plugins/test_plugin_manager.py +280 -0
- telebot_py-1.5.0/tests/unit/routing/__init__.py +0 -0
- telebot_py-1.5.0/tests/unit/routing/test_async_conversation.py +331 -0
- telebot_py-1.5.0/tests/unit/routing/test_conversation.py +431 -0
- telebot_py-1.5.0/tests/unit/routing/test_handlers.py +258 -0
- telebot_py-1.5.0/tests/unit/routing/test_handlers_extended.py +633 -0
- telebot_py-1.5.0/tests/unit/routing/test_linear_conversation.py +196 -0
- telebot_py-1.5.0/tests/unit/scheduler/__init__.py +0 -0
- telebot_py-1.5.0/tests/unit/scheduler/test_jobqueue.py +341 -0
- telebot_py-1.5.0/tests/unit/scheduler/test_rrule.py +227 -0
- telebot_py-1.5.0/tests/unit/storage/__init__.py +0 -0
- telebot_py-1.5.0/tests/unit/storage/test_persistence.py +249 -0
- telebot_py-1.5.0/tests/unit/types/__init__.py +0 -0
- telebot_py-1.5.0/tests/unit/types/test_types.py +282 -0
- telebot_py-1.5.0/tests/unit/types/test_types_fidelity.py +1996 -0
- telebot_py-1.5.0/tests/unit/utils/__init__.py +0 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
node_modules/
|
|
2
|
+
dist/
|
|
3
|
+
coverage/
|
|
4
|
+
docs/
|
|
5
|
+
python-telegram-bot/
|
|
6
|
+
**/.DS_Store
|
|
7
|
+
*.log
|
|
8
|
+
.env*
|
|
9
|
+
.idea/
|
|
10
|
+
.vscode/
|
|
11
|
+
.claude/
|
|
12
|
+
|
|
13
|
+
# SQLite test databases & artifacts
|
|
14
|
+
*.sqlite
|
|
15
|
+
*.sqlite-wal
|
|
16
|
+
*.sqlite-shm
|
|
17
|
+
*.sqlite-journal
|
|
18
|
+
*.db
|
|
19
|
+
*.tsbuildinfo
|
|
20
|
+
|
|
21
|
+
# CodeGraph generated index (SQLite db, daemon socket/pid)
|
|
22
|
+
.codegraph/
|
|
23
|
+
|
|
24
|
+
# Release pipeline artifact (written by semantic-release successCmd in CI)
|
|
25
|
+
RELEASE_VERSION
|
|
26
|
+
|
|
27
|
+
# Python (packages/python)
|
|
28
|
+
__pycache__/
|
|
29
|
+
*.py[cod]
|
|
30
|
+
*.egg-info/
|
|
31
|
+
.venv/
|
|
32
|
+
venv/
|
|
33
|
+
.pytest_cache/
|
|
34
|
+
.mypy_cache/
|
|
35
|
+
.ruff_cache/
|
|
36
|
+
.coverage
|
|
37
|
+
coverage.xml
|
|
38
|
+
htmlcov/
|
|
39
|
+
# Sphinx output is ignored, but the docs SOURCE must stay tracked
|
|
40
|
+
!packages/python/docs/
|
|
41
|
+
packages/python/docs/_build/
|
|
42
|
+
|
|
43
|
+
# Go (packages/go)
|
|
44
|
+
*.exe
|
|
45
|
+
*.test
|
|
46
|
+
*.out
|
|
47
|
+
|
telebot_py-1.5.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Nam088
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: telebot-py
|
|
3
|
+
Version: 1.5.0
|
|
4
|
+
Summary: Async-first Telegram Bot framework for Python, mirroring python-telegram-bot's API
|
|
5
|
+
Project-URL: Homepage, https://github.com/Nam088/telegram-bot-node
|
|
6
|
+
Project-URL: Repository, https://github.com/Nam088/telegram-bot-node
|
|
7
|
+
Project-URL: Issues, https://github.com/Nam088/telegram-bot-node/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/Nam088/telegram-bot-node/releases
|
|
9
|
+
Author: Nam088
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: asyncio,bot,framework,python-telegram-bot,telegram
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Framework :: AsyncIO
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Topic :: Communications :: Chat
|
|
24
|
+
Classifier: Typing :: Typed
|
|
25
|
+
Requires-Python: >=3.10
|
|
26
|
+
Requires-Dist: httpx>=0.27
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
29
|
+
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
|
|
30
|
+
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
|
|
31
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
32
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
33
|
+
Requires-Dist: sphinx>=7.0; extra == 'dev'
|
|
34
|
+
Description-Content-Type: text/markdown
|
|
35
|
+
|
|
36
|
+
# telebot-py
|
|
37
|
+
|
|
38
|
+
> Async-first Telegram Bot Framework for Python, mirroring [python-telegram-bot](https://python-telegram-bot.org)'s public API.
|
|
39
|
+
|
|
40
|
+
<!-- Badges: activate once the package is published and CI workflows are live.
|
|
41
|
+
[](https://pypi.org/project/telebot-py/)
|
|
42
|
+
[](https://pypi.org/project/telebot-py/)
|
|
43
|
+
[](https://github.com/Nam088/telegram-bot-node/actions/workflows/ci.yml)
|
|
44
|
+
-->
|
|
45
|
+
|
|
46
|
+
`telebot-py` is the third language implementation of this repository's Telegram bot
|
|
47
|
+
framework, alongside [`telebot-ts`](../node) (TypeScript) and
|
|
48
|
+
[`telebot-go`](../go) (Go). It is built from scratch in native Python
|
|
49
|
+
(asyncio-first) and mirrors the python-telegram-bot API in native snake_case,
|
|
50
|
+
including native `&` / `|` / `~` filter operators.
|
|
51
|
+
|
|
52
|
+
## Features
|
|
53
|
+
|
|
54
|
+
- **Bot client** over `httpx` (the single required runtime dependency) with retry
|
|
55
|
+
semantics (429/5xx exponential backoff, `retry_after` honored) and a pluggable
|
|
56
|
+
transport for offline testing.
|
|
57
|
+
- **Application kernel**: builder, dispatcher with ordered handler groups,
|
|
58
|
+
long polling and webhook modes, full lifecycle management.
|
|
59
|
+
- **Handlers**: command, message, callback query, conversation
|
|
60
|
+
(standard/linear/async forms), and the extended handler set.
|
|
61
|
+
- **Composable filters** with native Python operators.
|
|
62
|
+
- **Scheduler**: `JobQueue` with one-shot, repeating, and RRule schedules.
|
|
63
|
+
- **Persistence**: memory, JSON file, and SQLite backends behind one contract.
|
|
64
|
+
- **Plugins** with hooks, ordering, and built-in i18n; **components** for menus
|
|
65
|
+
and keyboards.
|
|
66
|
+
|
|
67
|
+
## Installation
|
|
68
|
+
|
|
69
|
+
Requires Python 3.10+. telebot-py is versioned in lockstep with telebot-ts and
|
|
70
|
+
telebot-go (currently `1.4.0`). Install from PyPI:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
pip install telebot-py
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Or from source:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
pip install packages/python # from the repo root
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Quick Start
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
from telebot_py import ApplicationBuilder, CallbackContext, CommandHandler, MessageHandler, filters
|
|
86
|
+
from telebot_py.types import Update
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
async def start(update: Update, context: CallbackContext) -> None:
|
|
90
|
+
await context.bot.send_message(chat_id=update.effective_chat.id, text="Hello!")
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
async def echo(update: Update, context: CallbackContext) -> None:
|
|
94
|
+
await context.bot.send_message(
|
|
95
|
+
chat_id=update.effective_chat.id, text=update.effective_message.text
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
app = ApplicationBuilder().token("YOUR_BOT_TOKEN").build()
|
|
100
|
+
app.add_handler(CommandHandler("start", start))
|
|
101
|
+
app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, echo))
|
|
102
|
+
app.run_polling()
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Documentation
|
|
106
|
+
|
|
107
|
+
Full API reference (kernel, bot, routing, filters, scheduler, storage, plugins,
|
|
108
|
+
components, types) lives in [`docs/`](docs/) and is generated with Sphinx from
|
|
109
|
+
the in-source docstrings. The published site is served by GitHub Pages at
|
|
110
|
+
`https://nam088.github.io/telegram-bot-node/python/` (see the combined
|
|
111
|
+
[docs site](https://nam088.github.io/telegram-bot-node/)).
|
|
112
|
+
|
|
113
|
+
Build the docs locally (requires the `[dev]` extra):
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
cd packages/python
|
|
117
|
+
source .venv/bin/activate
|
|
118
|
+
sphinx-build -W --keep-going docs docs/_build
|
|
119
|
+
open docs/_build/index.html # macOS
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
`-W --keep-going` treats warnings as errors, matching CI. `docs/_build/` is
|
|
123
|
+
gitignored and never committed.
|
|
124
|
+
|
|
125
|
+
## Development
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
cd packages/python
|
|
129
|
+
python3 -m venv .venv && source .venv/bin/activate
|
|
130
|
+
pip install -e ".[dev]"
|
|
131
|
+
|
|
132
|
+
ruff check src tests scripts && ruff format --check src tests scripts
|
|
133
|
+
mypy --strict src
|
|
134
|
+
pytest --cov=telebot_py --cov-fail-under=80
|
|
135
|
+
python scripts/parity_audit.py # node/go/python API parity (CI-enforced)
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Tests that need a live bot are marked `@pytest.mark.live` and auto-skip unless
|
|
139
|
+
`TEST_BOT_TOKEN` is set. The default suite runs fully offline.
|
|
140
|
+
|
|
141
|
+
## Releasing
|
|
142
|
+
|
|
143
|
+
Releases are automated: pushing a tag of the form `packages/python/vX.Y.Z`
|
|
144
|
+
triggers [`python-release.yml`](../../.github/workflows/python-release.yml),
|
|
145
|
+
which builds the package, generates changelog notes from conventional commits
|
|
146
|
+
via [git-cliff](https://git-cliff.org) (scoped to `packages/python/*` in
|
|
147
|
+
[`cliff.toml`](cliff.toml)), creates a GitHub Release, and publishes to PyPI
|
|
148
|
+
using trusted publishing (OIDC — no stored API tokens).
|
|
149
|
+
|
|
150
|
+
To cut a release locally and verify it before tagging:
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
cd packages/python
|
|
154
|
+
source .venv/bin/activate
|
|
155
|
+
|
|
156
|
+
python -m build # produces dist/telebot_py-X.Y.Z.tar.gz + .whl
|
|
157
|
+
twine check dist/* # validates metadata/README rendering
|
|
158
|
+
sphinx-build -W --keep-going docs docs/_build # docs gate
|
|
159
|
+
|
|
160
|
+
git tag packages/python/vX.Y.Z
|
|
161
|
+
git push origin packages/python/vX.Y.Z
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Pre-release checklist (manual steps, not automatable offline):
|
|
165
|
+
|
|
166
|
+
1. Confirm the PyPI name is still available: `pip index versions telebot-py`
|
|
167
|
+
or browse <https://pypi.org/project/telebot-py/>. If squatted, the fallback
|
|
168
|
+
name is `telebot-python` (update `pyproject.toml` `[project].name` and the
|
|
169
|
+
workflow `packages-dir` accordingly).
|
|
170
|
+
2. Bump `version` in [`pyproject.toml`](pyproject.toml) — `docs/conf.py` and
|
|
171
|
+
the build backend both read it from there.
|
|
172
|
+
3. Ensure CI (`ci.yml`) and the parity audit are green on the commit to tag.
|
|
173
|
+
|
|
174
|
+
## License
|
|
175
|
+
|
|
176
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# telebot-py
|
|
2
|
+
|
|
3
|
+
> Async-first Telegram Bot Framework for Python, mirroring [python-telegram-bot](https://python-telegram-bot.org)'s public API.
|
|
4
|
+
|
|
5
|
+
<!-- Badges: activate once the package is published and CI workflows are live.
|
|
6
|
+
[](https://pypi.org/project/telebot-py/)
|
|
7
|
+
[](https://pypi.org/project/telebot-py/)
|
|
8
|
+
[](https://github.com/Nam088/telegram-bot-node/actions/workflows/ci.yml)
|
|
9
|
+
-->
|
|
10
|
+
|
|
11
|
+
`telebot-py` is the third language implementation of this repository's Telegram bot
|
|
12
|
+
framework, alongside [`telebot-ts`](../node) (TypeScript) and
|
|
13
|
+
[`telebot-go`](../go) (Go). It is built from scratch in native Python
|
|
14
|
+
(asyncio-first) and mirrors the python-telegram-bot API in native snake_case,
|
|
15
|
+
including native `&` / `|` / `~` filter operators.
|
|
16
|
+
|
|
17
|
+
## Features
|
|
18
|
+
|
|
19
|
+
- **Bot client** over `httpx` (the single required runtime dependency) with retry
|
|
20
|
+
semantics (429/5xx exponential backoff, `retry_after` honored) and a pluggable
|
|
21
|
+
transport for offline testing.
|
|
22
|
+
- **Application kernel**: builder, dispatcher with ordered handler groups,
|
|
23
|
+
long polling and webhook modes, full lifecycle management.
|
|
24
|
+
- **Handlers**: command, message, callback query, conversation
|
|
25
|
+
(standard/linear/async forms), and the extended handler set.
|
|
26
|
+
- **Composable filters** with native Python operators.
|
|
27
|
+
- **Scheduler**: `JobQueue` with one-shot, repeating, and RRule schedules.
|
|
28
|
+
- **Persistence**: memory, JSON file, and SQLite backends behind one contract.
|
|
29
|
+
- **Plugins** with hooks, ordering, and built-in i18n; **components** for menus
|
|
30
|
+
and keyboards.
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
Requires Python 3.10+. telebot-py is versioned in lockstep with telebot-ts and
|
|
35
|
+
telebot-go (currently `1.4.0`). Install from PyPI:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install telebot-py
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Or from source:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install packages/python # from the repo root
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Quick Start
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from telebot_py import ApplicationBuilder, CallbackContext, CommandHandler, MessageHandler, filters
|
|
51
|
+
from telebot_py.types import Update
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
async def start(update: Update, context: CallbackContext) -> None:
|
|
55
|
+
await context.bot.send_message(chat_id=update.effective_chat.id, text="Hello!")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
async def echo(update: Update, context: CallbackContext) -> None:
|
|
59
|
+
await context.bot.send_message(
|
|
60
|
+
chat_id=update.effective_chat.id, text=update.effective_message.text
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
app = ApplicationBuilder().token("YOUR_BOT_TOKEN").build()
|
|
65
|
+
app.add_handler(CommandHandler("start", start))
|
|
66
|
+
app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, echo))
|
|
67
|
+
app.run_polling()
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Documentation
|
|
71
|
+
|
|
72
|
+
Full API reference (kernel, bot, routing, filters, scheduler, storage, plugins,
|
|
73
|
+
components, types) lives in [`docs/`](docs/) and is generated with Sphinx from
|
|
74
|
+
the in-source docstrings. The published site is served by GitHub Pages at
|
|
75
|
+
`https://nam088.github.io/telegram-bot-node/python/` (see the combined
|
|
76
|
+
[docs site](https://nam088.github.io/telegram-bot-node/)).
|
|
77
|
+
|
|
78
|
+
Build the docs locally (requires the `[dev]` extra):
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
cd packages/python
|
|
82
|
+
source .venv/bin/activate
|
|
83
|
+
sphinx-build -W --keep-going docs docs/_build
|
|
84
|
+
open docs/_build/index.html # macOS
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
`-W --keep-going` treats warnings as errors, matching CI. `docs/_build/` is
|
|
88
|
+
gitignored and never committed.
|
|
89
|
+
|
|
90
|
+
## Development
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
cd packages/python
|
|
94
|
+
python3 -m venv .venv && source .venv/bin/activate
|
|
95
|
+
pip install -e ".[dev]"
|
|
96
|
+
|
|
97
|
+
ruff check src tests scripts && ruff format --check src tests scripts
|
|
98
|
+
mypy --strict src
|
|
99
|
+
pytest --cov=telebot_py --cov-fail-under=80
|
|
100
|
+
python scripts/parity_audit.py # node/go/python API parity (CI-enforced)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Tests that need a live bot are marked `@pytest.mark.live` and auto-skip unless
|
|
104
|
+
`TEST_BOT_TOKEN` is set. The default suite runs fully offline.
|
|
105
|
+
|
|
106
|
+
## Releasing
|
|
107
|
+
|
|
108
|
+
Releases are automated: pushing a tag of the form `packages/python/vX.Y.Z`
|
|
109
|
+
triggers [`python-release.yml`](../../.github/workflows/python-release.yml),
|
|
110
|
+
which builds the package, generates changelog notes from conventional commits
|
|
111
|
+
via [git-cliff](https://git-cliff.org) (scoped to `packages/python/*` in
|
|
112
|
+
[`cliff.toml`](cliff.toml)), creates a GitHub Release, and publishes to PyPI
|
|
113
|
+
using trusted publishing (OIDC — no stored API tokens).
|
|
114
|
+
|
|
115
|
+
To cut a release locally and verify it before tagging:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
cd packages/python
|
|
119
|
+
source .venv/bin/activate
|
|
120
|
+
|
|
121
|
+
python -m build # produces dist/telebot_py-X.Y.Z.tar.gz + .whl
|
|
122
|
+
twine check dist/* # validates metadata/README rendering
|
|
123
|
+
sphinx-build -W --keep-going docs docs/_build # docs gate
|
|
124
|
+
|
|
125
|
+
git tag packages/python/vX.Y.Z
|
|
126
|
+
git push origin packages/python/vX.Y.Z
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Pre-release checklist (manual steps, not automatable offline):
|
|
130
|
+
|
|
131
|
+
1. Confirm the PyPI name is still available: `pip index versions telebot-py`
|
|
132
|
+
or browse <https://pypi.org/project/telebot-py/>. If squatted, the fallback
|
|
133
|
+
name is `telebot-python` (update `pyproject.toml` `[project].name` and the
|
|
134
|
+
workflow `packages-dir` accordingly).
|
|
135
|
+
2. Bump `version` in [`pyproject.toml`](pyproject.toml) — `docs/conf.py` and
|
|
136
|
+
the build backend both read it from there.
|
|
137
|
+
3. Ensure CI (`ci.yml`) and the parity audit are green on the commit to tag.
|
|
138
|
+
|
|
139
|
+
## License
|
|
140
|
+
|
|
141
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
"""telebot_py conversations demo (L2): all three conversation forms, one bot.
|
|
2
|
+
|
|
3
|
+
Run with a bot token from @BotFather::
|
|
4
|
+
|
|
5
|
+
export TELEGRAM_BOT_TOKEN="123456:ABC..."
|
|
6
|
+
python examples/conversation.py
|
|
7
|
+
|
|
8
|
+
Commands:
|
|
9
|
+
/signup State-machine conversation (ConversationHandler): name -> age ->
|
|
10
|
+
confirm, persisted in SQLite; restart the bot mid-flow and it
|
|
11
|
+
resumes where it left off (SC-005).
|
|
12
|
+
/survey Linear conversation (LinearConversationHandler): three ordered
|
|
13
|
+
steps that advance automatically, one matching reply per step.
|
|
14
|
+
/profile Async conversation (AsyncConversationHandler): one ``async def``
|
|
15
|
+
flow reading answers with ``await conv.ask(...)``.
|
|
16
|
+
/cancel Aborts the signup/survey conversations from any step.
|
|
17
|
+
|
|
18
|
+
Press Ctrl+C for a graceful shutdown; conversation state survives in
|
|
19
|
+
``conversation_demo.db``.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import logging
|
|
25
|
+
import os
|
|
26
|
+
|
|
27
|
+
from telebot_py import (
|
|
28
|
+
Application,
|
|
29
|
+
AsyncConversationHandler,
|
|
30
|
+
CallbackContext,
|
|
31
|
+
CommandHandler,
|
|
32
|
+
ConversationHandler,
|
|
33
|
+
LinearConversationHandler,
|
|
34
|
+
MessageHandler,
|
|
35
|
+
filters,
|
|
36
|
+
)
|
|
37
|
+
from telebot_py.routing import END, AsyncConversation
|
|
38
|
+
from telebot_py.storage import SQLitePersistence
|
|
39
|
+
from telebot_py.types import Update
|
|
40
|
+
|
|
41
|
+
logging.basicConfig(format="%(asctime)s %(name)s %(levelname)s %(message)s", level=logging.INFO)
|
|
42
|
+
logging.getLogger("httpx").setLevel(logging.WARNING)
|
|
43
|
+
logger = logging.getLogger("conversation_demo")
|
|
44
|
+
|
|
45
|
+
NAME, AGE, CONFIRM = range(3)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
async def signup_entry(update: Update, context: CallbackContext) -> int:
|
|
49
|
+
"""Start the signup conversation by asking for a name."""
|
|
50
|
+
chat = update.effective_chat
|
|
51
|
+
if chat is not None:
|
|
52
|
+
await context.bot.send_message(chat_id=chat.id, text="What is your name?")
|
|
53
|
+
return NAME
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
async def signup_name(update: Update, context: CallbackContext) -> int:
|
|
57
|
+
"""Store the name and move on to the age question."""
|
|
58
|
+
message = update.effective_message
|
|
59
|
+
chat = update.effective_chat
|
|
60
|
+
if message is None or message.text is None or chat is None or context.user_data is None:
|
|
61
|
+
return NAME
|
|
62
|
+
context.user_data["name"] = message.text
|
|
63
|
+
await context.bot.send_message(chat_id=chat.id, text=f"Nice to meet you, {message.text}! Age?")
|
|
64
|
+
return AGE
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
async def signup_age(update: Update, context: CallbackContext) -> int:
|
|
68
|
+
"""Store the age and ask for confirmation."""
|
|
69
|
+
message = update.effective_message
|
|
70
|
+
chat = update.effective_chat
|
|
71
|
+
if message is None or message.text is None or chat is None or context.user_data is None:
|
|
72
|
+
return AGE
|
|
73
|
+
context.user_data["age"] = message.text
|
|
74
|
+
await context.bot.send_message(chat_id=chat.id, text="Save this? Reply yes or no.")
|
|
75
|
+
return CONFIRM
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
async def signup_confirm_yes(update: Update, context: CallbackContext) -> int:
|
|
79
|
+
"""Confirm and close the conversation."""
|
|
80
|
+
chat = update.effective_chat
|
|
81
|
+
data = context.user_data or {}
|
|
82
|
+
if chat is not None:
|
|
83
|
+
await context.bot.send_message(
|
|
84
|
+
chat_id=chat.id,
|
|
85
|
+
text=f"Saved: {data.get('name')}, age {data.get('age')}. Thanks!",
|
|
86
|
+
)
|
|
87
|
+
return END
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
async def signup_confirm_no(update: Update, context: CallbackContext) -> int:
|
|
91
|
+
"""Reject the entered data and ask for the age again."""
|
|
92
|
+
chat = update.effective_chat
|
|
93
|
+
if chat is not None:
|
|
94
|
+
await context.bot.send_message(chat_id=chat.id, text="Alright, how old are you?")
|
|
95
|
+
return AGE
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
async def signup_cancel(update: Update, context: CallbackContext) -> int:
|
|
99
|
+
"""Abort the conversation from any state."""
|
|
100
|
+
chat = update.effective_chat
|
|
101
|
+
if chat is not None:
|
|
102
|
+
await context.bot.send_message(chat_id=chat.id, text="Cancelled. Send /signup to retry.")
|
|
103
|
+
return END
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def build_signup_handler() -> ConversationHandler:
|
|
107
|
+
"""Persistent state-machine conversation: name -> age -> confirm."""
|
|
108
|
+
return ConversationHandler(
|
|
109
|
+
entry_points=[CommandHandler("signup", signup_entry)],
|
|
110
|
+
states={
|
|
111
|
+
NAME: [MessageHandler(filters.TEXT & ~filters.COMMAND, signup_name)],
|
|
112
|
+
AGE: [MessageHandler(filters.TEXT & ~filters.COMMAND, signup_age)],
|
|
113
|
+
CONFIRM: [
|
|
114
|
+
MessageHandler(filters.Regex(r"(?i)^yes$"), signup_confirm_yes),
|
|
115
|
+
MessageHandler(filters.Regex(r"(?i)^no$"), signup_confirm_no),
|
|
116
|
+
],
|
|
117
|
+
},
|
|
118
|
+
fallbacks=[CommandHandler("cancel", signup_cancel)],
|
|
119
|
+
name="signup",
|
|
120
|
+
persistent=True,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
async def survey_intro(update: Update, context: CallbackContext) -> None:
|
|
125
|
+
"""Announce the survey before the first step runs."""
|
|
126
|
+
chat = update.effective_chat
|
|
127
|
+
if chat is not None:
|
|
128
|
+
await context.bot.send_message(chat_id=chat.id, text="Survey time! Favourite colour?")
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
async def survey_colour(update: Update, context: CallbackContext) -> None:
|
|
132
|
+
"""Record the colour answer; the step index advances automatically."""
|
|
133
|
+
message = update.effective_message
|
|
134
|
+
chat = update.effective_chat
|
|
135
|
+
if message is None or message.text is None or chat is None or context.user_data is None:
|
|
136
|
+
return
|
|
137
|
+
context.user_data["colour"] = message.text
|
|
138
|
+
await context.bot.send_message(chat_id=chat.id, text="Rate this bot from 1 to 5:")
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
async def survey_rating(update: Update, context: CallbackContext) -> None:
|
|
142
|
+
"""Record the numeric rating answer."""
|
|
143
|
+
message = update.effective_message
|
|
144
|
+
chat = update.effective_chat
|
|
145
|
+
if message is None or message.text is None or chat is None or context.user_data is None:
|
|
146
|
+
return
|
|
147
|
+
context.user_data["rating"] = message.text
|
|
148
|
+
await context.bot.send_message(chat_id=chat.id, text="Any final words?")
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
async def survey_comment(update: Update, context: CallbackContext) -> None:
|
|
152
|
+
"""Record the closing comment; the flow ends after this last step."""
|
|
153
|
+
message = update.effective_message
|
|
154
|
+
chat = update.effective_chat
|
|
155
|
+
if message is None or message.text is None or chat is None or context.user_data is None:
|
|
156
|
+
return
|
|
157
|
+
context.user_data["comment"] = message.text
|
|
158
|
+
await context.bot.send_message(chat_id=chat.id, text="Thanks for taking the survey!")
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def build_survey_handler() -> LinearConversationHandler:
|
|
162
|
+
"""Persistent linear conversation advancing exactly one step per reply."""
|
|
163
|
+
return LinearConversationHandler(
|
|
164
|
+
entry_points=[CommandHandler("survey", survey_intro)],
|
|
165
|
+
steps=[
|
|
166
|
+
[MessageHandler(filters.TEXT & ~filters.COMMAND, survey_colour)],
|
|
167
|
+
[MessageHandler(filters.Regex(r"^[1-5]$"), survey_rating)],
|
|
168
|
+
[MessageHandler(filters.TEXT & ~filters.COMMAND, survey_comment)],
|
|
169
|
+
],
|
|
170
|
+
fallbacks=[CommandHandler("cancel", signup_cancel)],
|
|
171
|
+
name="survey",
|
|
172
|
+
persistent=True,
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
async def profile_flow(conv: AsyncConversation, context: CallbackContext) -> None:
|
|
177
|
+
"""Ask-and-wait conversation written as one straight-line coroutine."""
|
|
178
|
+
name = await conv.ask("What should I call you?")
|
|
179
|
+
age = await conv.ask(f"Hi {name}! How old are you?")
|
|
180
|
+
chat_id = conv.chat_id if conv.chat_id is not None else conv.user_id
|
|
181
|
+
if chat_id is not None:
|
|
182
|
+
await context.bot.send_message(chat_id=chat_id, text=f"Saved {name}, {age}.")
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
async def on_error(update: Update | None, context: CallbackContext) -> None:
|
|
186
|
+
"""Log handler errors; the polling loop keeps running (FR-013)."""
|
|
187
|
+
logger.error("Error while processing update %r: %r", update, context.error)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def main() -> None:
|
|
191
|
+
"""Build the application with SQLite persistence and poll until Ctrl+C."""
|
|
192
|
+
token = os.environ.get("TELEGRAM_BOT_TOKEN") or os.environ.get("TEST_BOT_TOKEN")
|
|
193
|
+
if not token:
|
|
194
|
+
raise SystemExit("Set TELEGRAM_BOT_TOKEN to a bot token from @BotFather first.")
|
|
195
|
+
|
|
196
|
+
app = (
|
|
197
|
+
Application.builder()
|
|
198
|
+
.token(token)
|
|
199
|
+
.persistence(SQLitePersistence("conversation_demo.db"))
|
|
200
|
+
.build()
|
|
201
|
+
)
|
|
202
|
+
app.add_handler(build_signup_handler())
|
|
203
|
+
app.add_handler(build_survey_handler())
|
|
204
|
+
app.add_handler(AsyncConversationHandler(profile_flow, entry_command="profile", name="profile"))
|
|
205
|
+
app.add_error_handler(on_error)
|
|
206
|
+
|
|
207
|
+
logger.info("Conversation demo is up; try /signup, /survey, or /profile.")
|
|
208
|
+
app.run_polling()
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
if __name__ == "__main__":
|
|
212
|
+
main()
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""telebot_py quickstart (L1): a live echo bot.
|
|
2
|
+
|
|
3
|
+
Run with a bot token from @BotFather::
|
|
4
|
+
|
|
5
|
+
export TELEGRAM_BOT_TOKEN="123456:ABC..."
|
|
6
|
+
python examples/echo_bot.py
|
|
7
|
+
|
|
8
|
+
Send ``/start`` for a greeting; any other text message is echoed back.
|
|
9
|
+
Press Ctrl+C for a clean, graceful shutdown (run_polling installs the
|
|
10
|
+
SIGINT/SIGTERM handlers, drains in-flight updates, and closes the client).
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import logging
|
|
16
|
+
import os
|
|
17
|
+
|
|
18
|
+
from telebot_py import Application, CallbackContext, CommandHandler, MessageHandler, filters
|
|
19
|
+
from telebot_py.types import Update
|
|
20
|
+
|
|
21
|
+
logging.basicConfig(format="%(asctime)s %(name)s %(levelname)s %(message)s", level=logging.INFO)
|
|
22
|
+
logging.getLogger("httpx").setLevel(logging.WARNING)
|
|
23
|
+
logger = logging.getLogger("echo_bot")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
async def start(update: Update, context: CallbackContext) -> None:
|
|
27
|
+
"""Reply with a greeting when the user sends ``/start``."""
|
|
28
|
+
user = update.effective_user
|
|
29
|
+
chat = update.effective_chat
|
|
30
|
+
if chat is None:
|
|
31
|
+
return
|
|
32
|
+
name = user.first_name if user is not None else "there"
|
|
33
|
+
await context.bot.send_message(
|
|
34
|
+
chat_id=chat.id,
|
|
35
|
+
text=f"Hi {name}! Send me any text and I will echo it back.",
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
async def echo(update: Update, context: CallbackContext) -> None:
|
|
40
|
+
"""Echo every non-command text message back to its chat."""
|
|
41
|
+
message = update.effective_message
|
|
42
|
+
chat = update.effective_chat
|
|
43
|
+
if message is None or message.text is None or chat is None:
|
|
44
|
+
return
|
|
45
|
+
await context.bot.send_message(chat_id=chat.id, text=message.text)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
async def on_error(update: Update | None, context: CallbackContext) -> None:
|
|
49
|
+
"""Log handler errors; the polling loop keeps running (FR-013)."""
|
|
50
|
+
logger.error("Error while processing update %r: %r", update, context.error)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def main() -> None:
|
|
54
|
+
"""Build the application, register handlers, and poll until Ctrl+C."""
|
|
55
|
+
token = os.environ.get("TELEGRAM_BOT_TOKEN") or os.environ.get("TEST_BOT_TOKEN")
|
|
56
|
+
if not token:
|
|
57
|
+
raise SystemExit("Set TELEGRAM_BOT_TOKEN to a bot token from @BotFather first.")
|
|
58
|
+
|
|
59
|
+
app = Application.builder().token(token).build()
|
|
60
|
+
app.add_handler(CommandHandler("start", start))
|
|
61
|
+
app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, echo))
|
|
62
|
+
app.add_error_handler(on_error)
|
|
63
|
+
|
|
64
|
+
logger.info("Echo bot is up; press Ctrl+C to stop.")
|
|
65
|
+
app.run_polling()
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
if __name__ == "__main__":
|
|
69
|
+
main()
|