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.
Files changed (170) hide show
  1. telebot_py-1.5.0/.gitignore +47 -0
  2. telebot_py-1.5.0/LICENSE +21 -0
  3. telebot_py-1.5.0/PKG-INFO +176 -0
  4. telebot_py-1.5.0/README.md +141 -0
  5. telebot_py-1.5.0/examples/conversation.py +212 -0
  6. telebot_py-1.5.0/examples/echo_bot.py +69 -0
  7. telebot_py-1.5.0/examples/persistence.py +139 -0
  8. telebot_py-1.5.0/examples/plugins_i18n.py +146 -0
  9. telebot_py-1.5.0/examples/ptb_reference_echo.py +88 -0
  10. telebot_py-1.5.0/examples/scheduler.py +132 -0
  11. telebot_py-1.5.0/examples/webhook.py +88 -0
  12. telebot_py-1.5.0/pyproject.toml +98 -0
  13. telebot_py-1.5.0/src/telebot_py/__init__.py +55 -0
  14. telebot_py-1.5.0/src/telebot_py/bot/__init__.py +61 -0
  15. telebot_py-1.5.0/src/telebot_py/bot/base.py +108 -0
  16. telebot_py-1.5.0/src/telebot_py/bot/bulk.py +140 -0
  17. telebot_py-1.5.0/src/telebot_py/bot/chat_management.py +374 -0
  18. telebot_py-1.5.0/src/telebot_py/bot/chats.py +166 -0
  19. telebot_py-1.5.0/src/telebot_py/bot/client.py +264 -0
  20. telebot_py-1.5.0/src/telebot_py/bot/edits.py +371 -0
  21. telebot_py-1.5.0/src/telebot_py/bot/errors.py +52 -0
  22. telebot_py-1.5.0/src/telebot_py/bot/files.py +56 -0
  23. telebot_py-1.5.0/src/telebot_py/bot/games.py +154 -0
  24. telebot_py-1.5.0/src/telebot_py/bot/inline.py +131 -0
  25. telebot_py-1.5.0/src/telebot_py/bot/invite_links.py +122 -0
  26. telebot_py-1.5.0/src/telebot_py/bot/media.py +776 -0
  27. telebot_py-1.5.0/src/telebot_py/bot/members.py +280 -0
  28. telebot_py-1.5.0/src/telebot_py/bot/messages.py +355 -0
  29. telebot_py-1.5.0/src/telebot_py/bot/payments.py +373 -0
  30. telebot_py-1.5.0/src/telebot_py/bot/profile.py +268 -0
  31. telebot_py-1.5.0/src/telebot_py/bot/reactions.py +125 -0
  32. telebot_py-1.5.0/src/telebot_py/bot/retry.py +50 -0
  33. telebot_py-1.5.0/src/telebot_py/bot/stickers.py +455 -0
  34. telebot_py-1.5.0/src/telebot_py/bot/stories_gifts.py +54 -0
  35. telebot_py-1.5.0/src/telebot_py/bot/topics.py +71 -0
  36. telebot_py-1.5.0/src/telebot_py/bot/webhook.py +93 -0
  37. telebot_py-1.5.0/src/telebot_py/components/__init__.py +50 -0
  38. telebot_py-1.5.0/src/telebot_py/components/inline_query.py +411 -0
  39. telebot_py-1.5.0/src/telebot_py/components/keyboard/__init__.py +6 -0
  40. telebot_py-1.5.0/src/telebot_py/components/keyboard/inline.py +137 -0
  41. telebot_py-1.5.0/src/telebot_py/components/keyboard/reply.py +240 -0
  42. telebot_py-1.5.0/src/telebot_py/components/menu/__init__.py +25 -0
  43. telebot_py-1.5.0/src/telebot_py/components/menu/menu.py +201 -0
  44. telebot_py-1.5.0/src/telebot_py/components/menu/renderer.py +114 -0
  45. telebot_py-1.5.0/src/telebot_py/components/menu/types.py +79 -0
  46. telebot_py-1.5.0/src/telebot_py/components/pagination.py +182 -0
  47. telebot_py-1.5.0/src/telebot_py/ext_errors.py +17 -0
  48. telebot_py-1.5.0/src/telebot_py/filters/__init__.py +40 -0
  49. telebot_py-1.5.0/src/telebot_py/filters/base.py +138 -0
  50. telebot_py-1.5.0/src/telebot_py/filters/matchers.py +190 -0
  51. telebot_py-1.5.0/src/telebot_py/kernel/__init__.py +21 -0
  52. telebot_py-1.5.0/src/telebot_py/kernel/app.py +487 -0
  53. telebot_py-1.5.0/src/telebot_py/kernel/builder.py +208 -0
  54. telebot_py-1.5.0/src/telebot_py/kernel/context.py +98 -0
  55. telebot_py-1.5.0/src/telebot_py/kernel/dispatcher.py +227 -0
  56. telebot_py-1.5.0/src/telebot_py/kernel/lifecycle.py +26 -0
  57. telebot_py-1.5.0/src/telebot_py/kernel/polling.py +155 -0
  58. telebot_py-1.5.0/src/telebot_py/kernel/runners.py +180 -0
  59. telebot_py-1.5.0/src/telebot_py/kernel/webhook.py +414 -0
  60. telebot_py-1.5.0/src/telebot_py/plugins/__init__.py +15 -0
  61. telebot_py-1.5.0/src/telebot_py/plugins/i18n.py +174 -0
  62. telebot_py-1.5.0/src/telebot_py/plugins/manager.py +242 -0
  63. telebot_py-1.5.0/src/telebot_py/plugins/plugin.py +70 -0
  64. telebot_py-1.5.0/src/telebot_py/py.typed +0 -0
  65. telebot_py-1.5.0/src/telebot_py/routing/__init__.py +75 -0
  66. telebot_py-1.5.0/src/telebot_py/routing/async_conversation/__init__.py +26 -0
  67. telebot_py-1.5.0/src/telebot_py/routing/async_conversation/conversation.py +200 -0
  68. telebot_py-1.5.0/src/telebot_py/routing/async_conversation/handler.py +162 -0
  69. telebot_py-1.5.0/src/telebot_py/routing/async_conversation/manager.py +326 -0
  70. telebot_py-1.5.0/src/telebot_py/routing/async_conversation/types.py +49 -0
  71. telebot_py-1.5.0/src/telebot_py/routing/conversation.py +262 -0
  72. telebot_py-1.5.0/src/telebot_py/routing/handlers/__init__.py +64 -0
  73. telebot_py-1.5.0/src/telebot_py/routing/handlers/base.py +93 -0
  74. telebot_py-1.5.0/src/telebot_py/routing/handlers/business.py +52 -0
  75. telebot_py-1.5.0/src/telebot_py/routing/handlers/callback_query.py +87 -0
  76. telebot_py-1.5.0/src/telebot_py/routing/handlers/chat_member.py +81 -0
  77. telebot_py-1.5.0/src/telebot_py/routing/handlers/chat_request.py +81 -0
  78. telebot_py-1.5.0/src/telebot_py/routing/handlers/command.py +95 -0
  79. telebot_py-1.5.0/src/telebot_py/routing/handlers/inline_query.py +157 -0
  80. telebot_py-1.5.0/src/telebot_py/routing/handlers/message.py +49 -0
  81. telebot_py-1.5.0/src/telebot_py/routing/handlers/payment.py +67 -0
  82. telebot_py-1.5.0/src/telebot_py/routing/handlers/reaction.py +120 -0
  83. telebot_py-1.5.0/src/telebot_py/routing/handlers/type.py +55 -0
  84. telebot_py-1.5.0/src/telebot_py/routing/linear_conversation.py +236 -0
  85. telebot_py-1.5.0/src/telebot_py/scheduler/__init__.py +22 -0
  86. telebot_py-1.5.0/src/telebot_py/scheduler/job.py +203 -0
  87. telebot_py-1.5.0/src/telebot_py/scheduler/queue.py +313 -0
  88. telebot_py-1.5.0/src/telebot_py/scheduler/rrule/__init__.py +6 -0
  89. telebot_py-1.5.0/src/telebot_py/scheduler/rrule/rule.py +159 -0
  90. telebot_py-1.5.0/src/telebot_py/scheduler/rrule/types.py +69 -0
  91. telebot_py-1.5.0/src/telebot_py/storage/__init__.py +18 -0
  92. telebot_py-1.5.0/src/telebot_py/storage/base.py +401 -0
  93. telebot_py-1.5.0/src/telebot_py/storage/driver.py +76 -0
  94. telebot_py-1.5.0/src/telebot_py/storage/json.py +132 -0
  95. telebot_py-1.5.0/src/telebot_py/storage/memory.py +50 -0
  96. telebot_py-1.5.0/src/telebot_py/storage/sqlite.py +310 -0
  97. telebot_py-1.5.0/src/telebot_py/types/__init__.py +232 -0
  98. telebot_py-1.5.0/src/telebot_py/types/base.py +122 -0
  99. telebot_py-1.5.0/src/telebot_py/types/business.py +160 -0
  100. telebot_py-1.5.0/src/telebot_py/types/callback_query.py +39 -0
  101. telebot_py-1.5.0/src/telebot_py/types/chat.py +360 -0
  102. telebot_py-1.5.0/src/telebot_py/types/chat_members.py +262 -0
  103. telebot_py-1.5.0/src/telebot_py/types/common.py +251 -0
  104. telebot_py-1.5.0/src/telebot_py/types/files.py +42 -0
  105. telebot_py-1.5.0/src/telebot_py/types/games.py +47 -0
  106. telebot_py-1.5.0/src/telebot_py/types/keyboards.py +280 -0
  107. telebot_py-1.5.0/src/telebot_py/types/media.py +189 -0
  108. telebot_py-1.5.0/src/telebot_py/types/message.py +286 -0
  109. telebot_py-1.5.0/src/telebot_py/types/message_extras.py +250 -0
  110. telebot_py-1.5.0/src/telebot_py/types/payments.py +169 -0
  111. telebot_py-1.5.0/src/telebot_py/types/reactions.py +106 -0
  112. telebot_py-1.5.0/src/telebot_py/types/stickers.py +123 -0
  113. telebot_py-1.5.0/src/telebot_py/types/topics.py +122 -0
  114. telebot_py-1.5.0/src/telebot_py/types/update.py +189 -0
  115. telebot_py-1.5.0/src/telebot_py/types/user.py +46 -0
  116. telebot_py-1.5.0/src/telebot_py/utils/__init__.py +0 -0
  117. telebot_py-1.5.0/tests/conftest.py +188 -0
  118. telebot_py-1.5.0/tests/integration/__init__.py +0 -0
  119. telebot_py-1.5.0/tests/integration/test_dispatch_flow.py +412 -0
  120. telebot_py-1.5.0/tests/integration/test_kernel_wiring.py +341 -0
  121. telebot_py-1.5.0/tests/integration/test_lifecycle.py +256 -0
  122. telebot_py-1.5.0/tests/integration/test_live_smoke.py +54 -0
  123. telebot_py-1.5.0/tests/integration/test_persistence_restart.py +160 -0
  124. telebot_py-1.5.0/tests/integration/test_polling.py +357 -0
  125. telebot_py-1.5.0/tests/integration/test_webhook.py +348 -0
  126. telebot_py-1.5.0/tests/unit/__init__.py +0 -0
  127. telebot_py-1.5.0/tests/unit/bot/__init__.py +0 -0
  128. telebot_py-1.5.0/tests/unit/bot/helpers.py +51 -0
  129. telebot_py-1.5.0/tests/unit/bot/test_bot_methods.py +532 -0
  130. telebot_py-1.5.0/tests/unit/bot/test_errors.py +61 -0
  131. telebot_py-1.5.0/tests/unit/bot/test_methods_bulk.py +92 -0
  132. telebot_py-1.5.0/tests/unit/bot/test_methods_chat_management.py +190 -0
  133. telebot_py-1.5.0/tests/unit/bot/test_methods_edits.py +201 -0
  134. telebot_py-1.5.0/tests/unit/bot/test_methods_files.py +66 -0
  135. telebot_py-1.5.0/tests/unit/bot/test_methods_games.py +96 -0
  136. telebot_py-1.5.0/tests/unit/bot/test_methods_inline.py +77 -0
  137. telebot_py-1.5.0/tests/unit/bot/test_methods_invite_links.py +96 -0
  138. telebot_py-1.5.0/tests/unit/bot/test_methods_media.py +336 -0
  139. telebot_py-1.5.0/tests/unit/bot/test_methods_members.py +144 -0
  140. telebot_py-1.5.0/tests/unit/bot/test_methods_payments.py +148 -0
  141. telebot_py-1.5.0/tests/unit/bot/test_methods_profile.py +144 -0
  142. telebot_py-1.5.0/tests/unit/bot/test_methods_reactions.py +115 -0
  143. telebot_py-1.5.0/tests/unit/bot/test_methods_stickers.py +266 -0
  144. telebot_py-1.5.0/tests/unit/bot/test_methods_stories_gifts.py +60 -0
  145. telebot_py-1.5.0/tests/unit/bot/test_methods_topics.py +74 -0
  146. telebot_py-1.5.0/tests/unit/bot/test_retry.py +292 -0
  147. telebot_py-1.5.0/tests/unit/components/__init__.py +0 -0
  148. telebot_py-1.5.0/tests/unit/components/test_components.py +346 -0
  149. telebot_py-1.5.0/tests/unit/filters/__init__.py +0 -0
  150. telebot_py-1.5.0/tests/unit/filters/test_filters.py +251 -0
  151. telebot_py-1.5.0/tests/unit/kernel/__init__.py +0 -0
  152. telebot_py-1.5.0/tests/unit/kernel/test_application.py +284 -0
  153. telebot_py-1.5.0/tests/unit/plugins/__init__.py +0 -0
  154. telebot_py-1.5.0/tests/unit/plugins/test_i18n.py +236 -0
  155. telebot_py-1.5.0/tests/unit/plugins/test_plugin_manager.py +280 -0
  156. telebot_py-1.5.0/tests/unit/routing/__init__.py +0 -0
  157. telebot_py-1.5.0/tests/unit/routing/test_async_conversation.py +331 -0
  158. telebot_py-1.5.0/tests/unit/routing/test_conversation.py +431 -0
  159. telebot_py-1.5.0/tests/unit/routing/test_handlers.py +258 -0
  160. telebot_py-1.5.0/tests/unit/routing/test_handlers_extended.py +633 -0
  161. telebot_py-1.5.0/tests/unit/routing/test_linear_conversation.py +196 -0
  162. telebot_py-1.5.0/tests/unit/scheduler/__init__.py +0 -0
  163. telebot_py-1.5.0/tests/unit/scheduler/test_jobqueue.py +341 -0
  164. telebot_py-1.5.0/tests/unit/scheduler/test_rrule.py +227 -0
  165. telebot_py-1.5.0/tests/unit/storage/__init__.py +0 -0
  166. telebot_py-1.5.0/tests/unit/storage/test_persistence.py +249 -0
  167. telebot_py-1.5.0/tests/unit/types/__init__.py +0 -0
  168. telebot_py-1.5.0/tests/unit/types/test_types.py +282 -0
  169. telebot_py-1.5.0/tests/unit/types/test_types_fidelity.py +1996 -0
  170. 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
+
@@ -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
+ [![PyPI version](https://img.shields.io/pypi/v/telebot-py)](https://pypi.org/project/telebot-py/)
42
+ [![Python](https://img.shields.io/pypi/pyversions/telebot-py)](https://pypi.org/project/telebot-py/)
43
+ [![CI](https://github.com/Nam088/telegram-bot-node/actions/workflows/ci.yml/badge.svg)](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
+ [![PyPI version](https://img.shields.io/pypi/v/telebot-py)](https://pypi.org/project/telebot-py/)
7
+ [![Python](https://img.shields.io/pypi/pyversions/telebot-py)](https://pypi.org/project/telebot-py/)
8
+ [![CI](https://github.com/Nam088/telegram-bot-node/actions/workflows/ci.yml/badge.svg)](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()