django-telegram-bot-api 0.1.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.
- django_telegram_bot_api-0.1.0/PKG-INFO +351 -0
- django_telegram_bot_api-0.1.0/README.md +338 -0
- django_telegram_bot_api-0.1.0/pyproject.toml +22 -0
- django_telegram_bot_api-0.1.0/src/django_telegram/__init__.py +7 -0
- django_telegram_bot_api-0.1.0/src/django_telegram/conf.py +33 -0
- django_telegram_bot_api-0.1.0/src/django_telegram/dispatcher.py +62 -0
- django_telegram_bot_api-0.1.0/src/django_telegram/exceptions.py +20 -0
- django_telegram_bot_api-0.1.0/src/django_telegram/handlers.py +10 -0
- django_telegram_bot_api-0.1.0/src/django_telegram/py.typed +0 -0
- django_telegram_bot_api-0.1.0/src/django_telegram/telegram.py +5195 -0
- django_telegram_bot_api-0.1.0/src/django_telegram/telegram_models.py +3433 -0
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: django-telegram-bot-api
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A typed Telegram Bot API client for Django
|
|
5
|
+
Author: reza
|
|
6
|
+
Author-email: reza <rza.karampour@gmail.com>
|
|
7
|
+
Requires-Dist: django>=6.1.2
|
|
8
|
+
Requires-Dist: djangorestframework>=3.18.3
|
|
9
|
+
Requires-Dist: pydantic
|
|
10
|
+
Requires-Dist: requests>=2.34.2
|
|
11
|
+
Requires-Python: >=3.13
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# Django Telegram Bot
|
|
15
|
+
|
|
16
|
+
The fastest way to build a production-ready Telegram bot on Django.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
## 🚀 Overview
|
|
20
|
+
|
|
21
|
+
# django-telegram
|
|
22
|
+
A typed Telegram Bot API client for Django.
|
|
23
|
+
|
|
24
|
+
`django-telegram` gives you every Telegram Bot API method as a plain Python method, and every Telegram type as a [Pydantic](https://docs.pydantic.dev/) model. You call a method, and you get a typed object back instead of a raw dictionary.
|
|
25
|
+
|
|
26
|
+
## What it does
|
|
27
|
+
|
|
28
|
+
- **All Bot API methods in one place.** Sending a message, answering a callback query, setting a webhook, and the rest of the API are available as methods on a single `Telegram` client.
|
|
29
|
+
- **All Telegram types included.** Updates, messages, users, chats, keyboards, and every other object defined by Telegram are Pydantic models, so you get validation and editor autocompletion.
|
|
30
|
+
- **Responses are objects.** Access `message.chat.id` or `update.callback_query.data` directly instead of digging through nested dictionaries.
|
|
31
|
+
- **Built for Django.** Configuration is read from your Django settings, and the library fits into your own views and URLs.
|
|
32
|
+
- **Synchronous.** No event loop or async runtime required.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## ⚙️ Installation
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install django_telegram
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
or with `uv`:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
uv add django_telegram
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## ⚡ Quick Start
|
|
51
|
+
|
|
52
|
+
Here's the minimal setup to get a bot responding to `/start`.
|
|
53
|
+
|
|
54
|
+
### 1. Add the app and configure settings
|
|
55
|
+
### 1. Configure settings
|
|
56
|
+
|
|
57
|
+
Add a `TELEGRAM` dictionary to your Django settings. You do **not** need to add `django_telegram` to `INSTALLED_APPS`.
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
# settings.py
|
|
61
|
+
TELEGRAM = {
|
|
62
|
+
"BOT_TOKEN": "your-telegram-bot-token",
|
|
63
|
+
"TM_WEBHOOK_URL": "https://api.telegram.org",
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
| Key | Required | Default | Description |
|
|
68
|
+
|---|---|---|---|
|
|
69
|
+
| `BOT_TOKEN` | Yes | n/a | Your bot token from [@BotFather](https://t.me/BotFather). |
|
|
70
|
+
| `TM_WEBHOOK_URL` | Yes | n/a | Base URL of the Telegram Bot API. Use `https://api.telegram.org`, or the address of your own local Bot API server. |
|
|
71
|
+
| `PROXY_SOCKS` | No | `""` | SOCKS proxy for outgoing requests, e.g. `127.0.0.1:1080`. |
|
|
72
|
+
|
|
73
|
+
If a required key is missing, `ImproperlyConfigured` is raised and the message tells you which one.
|
|
74
|
+
|
|
75
|
+
> [!TIP]
|
|
76
|
+
> Keep secrets out of your repository by reading them from environment variables:
|
|
77
|
+
>
|
|
78
|
+
> ```python
|
|
79
|
+
> import os
|
|
80
|
+
>
|
|
81
|
+
> TELEGRAM = {
|
|
82
|
+
> "BOT_TOKEN": os.environ["TELEGRAM_BOT_TOKEN"],
|
|
83
|
+
> "TM_WEBHOOK_URL": os.getenv("TELEGRAM_API_URL", "https://api.telegram.org"),
|
|
84
|
+
> }
|
|
85
|
+
> ```
|
|
86
|
+
|
|
87
|
+
Once the settings are in place, the client needs no arguments:
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
from django_telegram import Telegram
|
|
91
|
+
|
|
92
|
+
bot = Telegram() # reads BOT_TOKEN and TM_WEBHOOK_URL from settings
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
You can still pass a token explicitly, for example in tests or when running several bots:
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
bot = Telegram(token="123456:ABC-DEF")
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### 2. Add the webhook URL
|
|
102
|
+
|
|
103
|
+
Create a Django app for your bot (here it is called `bot`):
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
python manage.py startapp bot
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Add a view that receives Telegram's updates and passes them to the dispatcher:
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
# bot/views.py
|
|
113
|
+
import logging
|
|
114
|
+
|
|
115
|
+
from django_telegram import Dispatcher, Update
|
|
116
|
+
from rest_framework import status
|
|
117
|
+
from rest_framework.request import Request
|
|
118
|
+
from rest_framework.response import Response
|
|
119
|
+
from rest_framework.views import APIView
|
|
120
|
+
|
|
121
|
+
from .handlers import command_handlers # noqa: F401 (registers your handler classes)
|
|
122
|
+
|
|
123
|
+
logger = logging.getLogger(__name__)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class TelegramWebhookView(APIView):
|
|
127
|
+
authentication_classes = []
|
|
128
|
+
permission_classes = []
|
|
129
|
+
|
|
130
|
+
def post(self, request: Request) -> Response:
|
|
131
|
+
try:
|
|
132
|
+
update = Update(**request.data)
|
|
133
|
+
Dispatcher(update).dispatch()
|
|
134
|
+
except Exception:
|
|
135
|
+
logger.exception("Error while processing Telegram update")
|
|
136
|
+
|
|
137
|
+
# Always answer 200, otherwise Telegram keeps retrying the same update.
|
|
138
|
+
return Response({"ok": True}, status=status.HTTP_200_OK)
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Add the route to the app:
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
# bot/urls.py
|
|
145
|
+
from django.urls import path
|
|
146
|
+
|
|
147
|
+
from .views import TelegramWebhookView
|
|
148
|
+
|
|
149
|
+
app_name = "bot"
|
|
150
|
+
|
|
151
|
+
urlpatterns = [
|
|
152
|
+
path("webhook/", TelegramWebhookView.as_view(), name="webhook"),
|
|
153
|
+
]
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Include it in your project URLs:
|
|
157
|
+
|
|
158
|
+
```python
|
|
159
|
+
# config/urls.py
|
|
160
|
+
from django.urls import include, path
|
|
161
|
+
|
|
162
|
+
urlpatterns = [
|
|
163
|
+
path("bot/", include("bot.urls", namespace="bot")),
|
|
164
|
+
]
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Your webhook is now served at `https://your-domain.com/bot/webhook/`.
|
|
168
|
+
|
|
169
|
+
> [!IMPORTANT]
|
|
170
|
+
> **Every app that defines handlers must import them in its view module.**
|
|
171
|
+
>
|
|
172
|
+
> The dispatcher finds your handlers by looking at the subclasses of `CommandHandler`, `MessageHandler`, and the other base classes. A class only exists once the file that defines it has been imported, so if the file is never imported, the dispatcher sees nothing and the update is silently ignored.
|
|
173
|
+
>
|
|
174
|
+
> Importing your handlers at the top of the webhook view (`from .handlers import command_handlers`) guarantees they are loaded as soon as Django loads your URLs, with no extra registration step.
|
|
175
|
+
|
|
176
|
+
### 3. Write your handlers
|
|
177
|
+
|
|
178
|
+
Create a `handlers` package inside your app and add one file per handler type:
|
|
179
|
+
|
|
180
|
+
```text
|
|
181
|
+
bot/
|
|
182
|
+
├── __init__.py
|
|
183
|
+
├── urls.py
|
|
184
|
+
├── views.py
|
|
185
|
+
└── handlers/
|
|
186
|
+
├── __init__.py
|
|
187
|
+
└── command_handlers.py
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Handlers inherit from one of the base classes and implement `handle(self, update)`. Commands (any message that starts with `/`) go to `CommandHandler`:
|
|
191
|
+
|
|
192
|
+
```python
|
|
193
|
+
# bot/handlers/command_handlers.py
|
|
194
|
+
from django.conf import settings
|
|
195
|
+
from django_telegram import CommandHandler, Telegram, Update
|
|
196
|
+
from django_telegram.telegram_models import KeyboardButton, ReplyKeyboardMarkup
|
|
197
|
+
|
|
198
|
+
bot = Telegram(token=settings.TELEGRAM["BOT_TOKEN"])
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
class Commands(CommandHandler):
|
|
202
|
+
|
|
203
|
+
def handle(self, update: Update):
|
|
204
|
+
chat_id = update.message.chat.id
|
|
205
|
+
text = update.message.text
|
|
206
|
+
|
|
207
|
+
if text.startswith("/start"):
|
|
208
|
+
bot.send_message(chat_id=chat_id, text="hello 3>")
|
|
209
|
+
|
|
210
|
+
elif text.startswith("/products"):
|
|
211
|
+
bot.send_message(
|
|
212
|
+
chat_id=chat_id,
|
|
213
|
+
text="choices one !",
|
|
214
|
+
reply_markup=ReplyKeyboardMarkup(
|
|
215
|
+
keyboard=[
|
|
216
|
+
[
|
|
217
|
+
KeyboardButton(text="product 1"),
|
|
218
|
+
KeyboardButton(text="product 2"),
|
|
219
|
+
],
|
|
220
|
+
[
|
|
221
|
+
KeyboardButton(text="product 3"),
|
|
222
|
+
KeyboardButton(text="product 4"),
|
|
223
|
+
],
|
|
224
|
+
],
|
|
225
|
+
resize_keyboard=True,
|
|
226
|
+
),
|
|
227
|
+
)
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
The dispatcher does not look at the command name. Every command is delivered to your `CommandHandler` subclass, and you decide what to do for `/start`, `/products`, and so on inside `handle()`.
|
|
231
|
+
|
|
232
|
+
When your app grows to several handler files, import them all from the package so the view only needs one import:
|
|
233
|
+
|
|
234
|
+
```python
|
|
235
|
+
# bot/__init__.py
|
|
236
|
+
from . import command_handlers # noqa: F401
|
|
237
|
+
# from . import message_handlers # noqa: F401
|
|
238
|
+
# from . import callback_query_handlers # noqa: F401
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### 4. Handle plain messages
|
|
242
|
+
|
|
243
|
+
Any message that is not a command (no leading `/`) and has no media goes to `MessageHandler`. Create a new file next to your command handlers:
|
|
244
|
+
|
|
245
|
+
```text
|
|
246
|
+
bot/handlers/
|
|
247
|
+
├── __init__.py
|
|
248
|
+
├── command_handlers.py
|
|
249
|
+
└── message_handlers.py ← new
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
In this example, when the user sends `product 1` (the text of the reply keyboard button from the previous step), the bot answers with a message that has an inline button attached:
|
|
253
|
+
|
|
254
|
+
```python
|
|
255
|
+
# bot/handlers/message_handlers.py
|
|
256
|
+
from django.conf import settings
|
|
257
|
+
from django_telegram import MessageHandler, Telegram, Update
|
|
258
|
+
from django_telegram.telegram_models import InlineKeyboardButton, InlineKeyboardMarkup
|
|
259
|
+
|
|
260
|
+
bot = Telegram(token=settings.TELEGRAM["BOT_TOKEN"])
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
class MyMessageHandler(MessageHandler):
|
|
264
|
+
|
|
265
|
+
def handle(self, update: Update):
|
|
266
|
+
chat_id = update.message.chat.id
|
|
267
|
+
text = update.message.text
|
|
268
|
+
|
|
269
|
+
if text == "product 1":
|
|
270
|
+
bot.send_message(
|
|
271
|
+
chat_id=chat_id,
|
|
272
|
+
text="product 1",
|
|
273
|
+
reply_markup=InlineKeyboardMarkup(
|
|
274
|
+
inline_keyboard=[
|
|
275
|
+
[
|
|
276
|
+
InlineKeyboardButton(text="product 1", callback_data="pro1"),
|
|
277
|
+
],
|
|
278
|
+
],
|
|
279
|
+
),
|
|
280
|
+
)
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
The `callback_data` value (`"pro1"`) is what Telegram sends back to your bot when the user presses the button. You will handle it in the next step.
|
|
284
|
+
|
|
285
|
+
Now register the new file. A handler class only exists once its file has been imported, so add it to `handlers/__init__.py`:
|
|
286
|
+
|
|
287
|
+
```python
|
|
288
|
+
# bot/handlers/__init__.py
|
|
289
|
+
from . import (
|
|
290
|
+
command_handlers, # noqa: F401
|
|
291
|
+
message_handlers, # noqa: F401
|
|
292
|
+
)
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
Send `product 1` to your bot: it replies with a message and an inline button.
|
|
296
|
+
|
|
297
|
+
### 5. Handle inline button presses (callback queries)
|
|
298
|
+
|
|
299
|
+
When the user presses an inline button, Telegram sends a callback query instead of a message. These go to `CallbackQueryHandler`. Create another file:
|
|
300
|
+
|
|
301
|
+
```text
|
|
302
|
+
bot/handlers/
|
|
303
|
+
├── __init__.py
|
|
304
|
+
├── callback_query_handlers.py ← new
|
|
305
|
+
├── command_handlers.py
|
|
306
|
+
└── message_handlers.py
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
```python
|
|
310
|
+
# bot/handlers/callback_query_handlers.py
|
|
311
|
+
from django.conf import settings
|
|
312
|
+
from django_telegram import CallbackQueryHandler, Telegram, Update
|
|
313
|
+
|
|
314
|
+
bot = Telegram(token=settings.TELEGRAM["BOT_TOKEN"])
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
class MyCallbackQueryHandler(CallbackQueryHandler):
|
|
318
|
+
|
|
319
|
+
def handle(self, update: Update):
|
|
320
|
+
callback_data = update.callback_query.data
|
|
321
|
+
|
|
322
|
+
if callback_data == "pro1":
|
|
323
|
+
bot.answer_callback_query(
|
|
324
|
+
callback_query_id=update.callback_query.id,
|
|
325
|
+
text="hi this is product 1",
|
|
326
|
+
show_alert=True,
|
|
327
|
+
)
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
`answer_callback_query` is required for every callback query: it stops the loading spinner on the button. With `show_alert=True` the text is shown as a pop-up alert instead of a small toast.
|
|
331
|
+
|
|
332
|
+
Register the file the same way:
|
|
333
|
+
|
|
334
|
+
```python
|
|
335
|
+
# bot/handlers/__init__.py
|
|
336
|
+
from . import (
|
|
337
|
+
callback_query_handlers, # noqa: F401
|
|
338
|
+
command_handlers, # noqa: F401
|
|
339
|
+
message_handlers, # noqa: F401
|
|
340
|
+
)
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
### Summary
|
|
344
|
+
|
|
345
|
+
| User action | Update type | Base class | Example file |
|
|
346
|
+
|---|---|---|---|
|
|
347
|
+
| Sends `/start` | command | `CommandHandler` | `command_handlers.py` |
|
|
348
|
+
| Sends `product 1` | text message | `MessageHandler` | `message_handlers.py` |
|
|
349
|
+
| Presses an inline button | callback query | `CallbackQueryHandler` | `callback_query_handlers.py` |
|
|
350
|
+
|
|
351
|
+
Each new handler file needs one line in `handlers/__init__.py`. Your webhook view only needs `from . import handlers`.
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
# Django Telegram Bot
|
|
2
|
+
|
|
3
|
+
The fastest way to build a production-ready Telegram bot on Django.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
## 🚀 Overview
|
|
7
|
+
|
|
8
|
+
# django-telegram
|
|
9
|
+
A typed Telegram Bot API client for Django.
|
|
10
|
+
|
|
11
|
+
`django-telegram` gives you every Telegram Bot API method as a plain Python method, and every Telegram type as a [Pydantic](https://docs.pydantic.dev/) model. You call a method, and you get a typed object back instead of a raw dictionary.
|
|
12
|
+
|
|
13
|
+
## What it does
|
|
14
|
+
|
|
15
|
+
- **All Bot API methods in one place.** Sending a message, answering a callback query, setting a webhook, and the rest of the API are available as methods on a single `Telegram` client.
|
|
16
|
+
- **All Telegram types included.** Updates, messages, users, chats, keyboards, and every other object defined by Telegram are Pydantic models, so you get validation and editor autocompletion.
|
|
17
|
+
- **Responses are objects.** Access `message.chat.id` or `update.callback_query.data` directly instead of digging through nested dictionaries.
|
|
18
|
+
- **Built for Django.** Configuration is read from your Django settings, and the library fits into your own views and URLs.
|
|
19
|
+
- **Synchronous.** No event loop or async runtime required.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## ⚙️ Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install django_telegram
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
or with `uv`:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
uv add django_telegram
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## ⚡ Quick Start
|
|
38
|
+
|
|
39
|
+
Here's the minimal setup to get a bot responding to `/start`.
|
|
40
|
+
|
|
41
|
+
### 1. Add the app and configure settings
|
|
42
|
+
### 1. Configure settings
|
|
43
|
+
|
|
44
|
+
Add a `TELEGRAM` dictionary to your Django settings. You do **not** need to add `django_telegram` to `INSTALLED_APPS`.
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
# settings.py
|
|
48
|
+
TELEGRAM = {
|
|
49
|
+
"BOT_TOKEN": "your-telegram-bot-token",
|
|
50
|
+
"TM_WEBHOOK_URL": "https://api.telegram.org",
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
| Key | Required | Default | Description |
|
|
55
|
+
|---|---|---|---|
|
|
56
|
+
| `BOT_TOKEN` | Yes | n/a | Your bot token from [@BotFather](https://t.me/BotFather). |
|
|
57
|
+
| `TM_WEBHOOK_URL` | Yes | n/a | Base URL of the Telegram Bot API. Use `https://api.telegram.org`, or the address of your own local Bot API server. |
|
|
58
|
+
| `PROXY_SOCKS` | No | `""` | SOCKS proxy for outgoing requests, e.g. `127.0.0.1:1080`. |
|
|
59
|
+
|
|
60
|
+
If a required key is missing, `ImproperlyConfigured` is raised and the message tells you which one.
|
|
61
|
+
|
|
62
|
+
> [!TIP]
|
|
63
|
+
> Keep secrets out of your repository by reading them from environment variables:
|
|
64
|
+
>
|
|
65
|
+
> ```python
|
|
66
|
+
> import os
|
|
67
|
+
>
|
|
68
|
+
> TELEGRAM = {
|
|
69
|
+
> "BOT_TOKEN": os.environ["TELEGRAM_BOT_TOKEN"],
|
|
70
|
+
> "TM_WEBHOOK_URL": os.getenv("TELEGRAM_API_URL", "https://api.telegram.org"),
|
|
71
|
+
> }
|
|
72
|
+
> ```
|
|
73
|
+
|
|
74
|
+
Once the settings are in place, the client needs no arguments:
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
from django_telegram import Telegram
|
|
78
|
+
|
|
79
|
+
bot = Telegram() # reads BOT_TOKEN and TM_WEBHOOK_URL from settings
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
You can still pass a token explicitly, for example in tests or when running several bots:
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
bot = Telegram(token="123456:ABC-DEF")
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### 2. Add the webhook URL
|
|
89
|
+
|
|
90
|
+
Create a Django app for your bot (here it is called `bot`):
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
python manage.py startapp bot
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Add a view that receives Telegram's updates and passes them to the dispatcher:
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
# bot/views.py
|
|
100
|
+
import logging
|
|
101
|
+
|
|
102
|
+
from django_telegram import Dispatcher, Update
|
|
103
|
+
from rest_framework import status
|
|
104
|
+
from rest_framework.request import Request
|
|
105
|
+
from rest_framework.response import Response
|
|
106
|
+
from rest_framework.views import APIView
|
|
107
|
+
|
|
108
|
+
from .handlers import command_handlers # noqa: F401 (registers your handler classes)
|
|
109
|
+
|
|
110
|
+
logger = logging.getLogger(__name__)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class TelegramWebhookView(APIView):
|
|
114
|
+
authentication_classes = []
|
|
115
|
+
permission_classes = []
|
|
116
|
+
|
|
117
|
+
def post(self, request: Request) -> Response:
|
|
118
|
+
try:
|
|
119
|
+
update = Update(**request.data)
|
|
120
|
+
Dispatcher(update).dispatch()
|
|
121
|
+
except Exception:
|
|
122
|
+
logger.exception("Error while processing Telegram update")
|
|
123
|
+
|
|
124
|
+
# Always answer 200, otherwise Telegram keeps retrying the same update.
|
|
125
|
+
return Response({"ok": True}, status=status.HTTP_200_OK)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Add the route to the app:
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
# bot/urls.py
|
|
132
|
+
from django.urls import path
|
|
133
|
+
|
|
134
|
+
from .views import TelegramWebhookView
|
|
135
|
+
|
|
136
|
+
app_name = "bot"
|
|
137
|
+
|
|
138
|
+
urlpatterns = [
|
|
139
|
+
path("webhook/", TelegramWebhookView.as_view(), name="webhook"),
|
|
140
|
+
]
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Include it in your project URLs:
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
# config/urls.py
|
|
147
|
+
from django.urls import include, path
|
|
148
|
+
|
|
149
|
+
urlpatterns = [
|
|
150
|
+
path("bot/", include("bot.urls", namespace="bot")),
|
|
151
|
+
]
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Your webhook is now served at `https://your-domain.com/bot/webhook/`.
|
|
155
|
+
|
|
156
|
+
> [!IMPORTANT]
|
|
157
|
+
> **Every app that defines handlers must import them in its view module.**
|
|
158
|
+
>
|
|
159
|
+
> The dispatcher finds your handlers by looking at the subclasses of `CommandHandler`, `MessageHandler`, and the other base classes. A class only exists once the file that defines it has been imported, so if the file is never imported, the dispatcher sees nothing and the update is silently ignored.
|
|
160
|
+
>
|
|
161
|
+
> Importing your handlers at the top of the webhook view (`from .handlers import command_handlers`) guarantees they are loaded as soon as Django loads your URLs, with no extra registration step.
|
|
162
|
+
|
|
163
|
+
### 3. Write your handlers
|
|
164
|
+
|
|
165
|
+
Create a `handlers` package inside your app and add one file per handler type:
|
|
166
|
+
|
|
167
|
+
```text
|
|
168
|
+
bot/
|
|
169
|
+
├── __init__.py
|
|
170
|
+
├── urls.py
|
|
171
|
+
├── views.py
|
|
172
|
+
└── handlers/
|
|
173
|
+
├── __init__.py
|
|
174
|
+
└── command_handlers.py
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Handlers inherit from one of the base classes and implement `handle(self, update)`. Commands (any message that starts with `/`) go to `CommandHandler`:
|
|
178
|
+
|
|
179
|
+
```python
|
|
180
|
+
# bot/handlers/command_handlers.py
|
|
181
|
+
from django.conf import settings
|
|
182
|
+
from django_telegram import CommandHandler, Telegram, Update
|
|
183
|
+
from django_telegram.telegram_models import KeyboardButton, ReplyKeyboardMarkup
|
|
184
|
+
|
|
185
|
+
bot = Telegram(token=settings.TELEGRAM["BOT_TOKEN"])
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
class Commands(CommandHandler):
|
|
189
|
+
|
|
190
|
+
def handle(self, update: Update):
|
|
191
|
+
chat_id = update.message.chat.id
|
|
192
|
+
text = update.message.text
|
|
193
|
+
|
|
194
|
+
if text.startswith("/start"):
|
|
195
|
+
bot.send_message(chat_id=chat_id, text="hello 3>")
|
|
196
|
+
|
|
197
|
+
elif text.startswith("/products"):
|
|
198
|
+
bot.send_message(
|
|
199
|
+
chat_id=chat_id,
|
|
200
|
+
text="choices one !",
|
|
201
|
+
reply_markup=ReplyKeyboardMarkup(
|
|
202
|
+
keyboard=[
|
|
203
|
+
[
|
|
204
|
+
KeyboardButton(text="product 1"),
|
|
205
|
+
KeyboardButton(text="product 2"),
|
|
206
|
+
],
|
|
207
|
+
[
|
|
208
|
+
KeyboardButton(text="product 3"),
|
|
209
|
+
KeyboardButton(text="product 4"),
|
|
210
|
+
],
|
|
211
|
+
],
|
|
212
|
+
resize_keyboard=True,
|
|
213
|
+
),
|
|
214
|
+
)
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
The dispatcher does not look at the command name. Every command is delivered to your `CommandHandler` subclass, and you decide what to do for `/start`, `/products`, and so on inside `handle()`.
|
|
218
|
+
|
|
219
|
+
When your app grows to several handler files, import them all from the package so the view only needs one import:
|
|
220
|
+
|
|
221
|
+
```python
|
|
222
|
+
# bot/__init__.py
|
|
223
|
+
from . import command_handlers # noqa: F401
|
|
224
|
+
# from . import message_handlers # noqa: F401
|
|
225
|
+
# from . import callback_query_handlers # noqa: F401
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### 4. Handle plain messages
|
|
229
|
+
|
|
230
|
+
Any message that is not a command (no leading `/`) and has no media goes to `MessageHandler`. Create a new file next to your command handlers:
|
|
231
|
+
|
|
232
|
+
```text
|
|
233
|
+
bot/handlers/
|
|
234
|
+
├── __init__.py
|
|
235
|
+
├── command_handlers.py
|
|
236
|
+
└── message_handlers.py ← new
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
In this example, when the user sends `product 1` (the text of the reply keyboard button from the previous step), the bot answers with a message that has an inline button attached:
|
|
240
|
+
|
|
241
|
+
```python
|
|
242
|
+
# bot/handlers/message_handlers.py
|
|
243
|
+
from django.conf import settings
|
|
244
|
+
from django_telegram import MessageHandler, Telegram, Update
|
|
245
|
+
from django_telegram.telegram_models import InlineKeyboardButton, InlineKeyboardMarkup
|
|
246
|
+
|
|
247
|
+
bot = Telegram(token=settings.TELEGRAM["BOT_TOKEN"])
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
class MyMessageHandler(MessageHandler):
|
|
251
|
+
|
|
252
|
+
def handle(self, update: Update):
|
|
253
|
+
chat_id = update.message.chat.id
|
|
254
|
+
text = update.message.text
|
|
255
|
+
|
|
256
|
+
if text == "product 1":
|
|
257
|
+
bot.send_message(
|
|
258
|
+
chat_id=chat_id,
|
|
259
|
+
text="product 1",
|
|
260
|
+
reply_markup=InlineKeyboardMarkup(
|
|
261
|
+
inline_keyboard=[
|
|
262
|
+
[
|
|
263
|
+
InlineKeyboardButton(text="product 1", callback_data="pro1"),
|
|
264
|
+
],
|
|
265
|
+
],
|
|
266
|
+
),
|
|
267
|
+
)
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
The `callback_data` value (`"pro1"`) is what Telegram sends back to your bot when the user presses the button. You will handle it in the next step.
|
|
271
|
+
|
|
272
|
+
Now register the new file. A handler class only exists once its file has been imported, so add it to `handlers/__init__.py`:
|
|
273
|
+
|
|
274
|
+
```python
|
|
275
|
+
# bot/handlers/__init__.py
|
|
276
|
+
from . import (
|
|
277
|
+
command_handlers, # noqa: F401
|
|
278
|
+
message_handlers, # noqa: F401
|
|
279
|
+
)
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
Send `product 1` to your bot: it replies with a message and an inline button.
|
|
283
|
+
|
|
284
|
+
### 5. Handle inline button presses (callback queries)
|
|
285
|
+
|
|
286
|
+
When the user presses an inline button, Telegram sends a callback query instead of a message. These go to `CallbackQueryHandler`. Create another file:
|
|
287
|
+
|
|
288
|
+
```text
|
|
289
|
+
bot/handlers/
|
|
290
|
+
├── __init__.py
|
|
291
|
+
├── callback_query_handlers.py ← new
|
|
292
|
+
├── command_handlers.py
|
|
293
|
+
└── message_handlers.py
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
```python
|
|
297
|
+
# bot/handlers/callback_query_handlers.py
|
|
298
|
+
from django.conf import settings
|
|
299
|
+
from django_telegram import CallbackQueryHandler, Telegram, Update
|
|
300
|
+
|
|
301
|
+
bot = Telegram(token=settings.TELEGRAM["BOT_TOKEN"])
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
class MyCallbackQueryHandler(CallbackQueryHandler):
|
|
305
|
+
|
|
306
|
+
def handle(self, update: Update):
|
|
307
|
+
callback_data = update.callback_query.data
|
|
308
|
+
|
|
309
|
+
if callback_data == "pro1":
|
|
310
|
+
bot.answer_callback_query(
|
|
311
|
+
callback_query_id=update.callback_query.id,
|
|
312
|
+
text="hi this is product 1",
|
|
313
|
+
show_alert=True,
|
|
314
|
+
)
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
`answer_callback_query` is required for every callback query: it stops the loading spinner on the button. With `show_alert=True` the text is shown as a pop-up alert instead of a small toast.
|
|
318
|
+
|
|
319
|
+
Register the file the same way:
|
|
320
|
+
|
|
321
|
+
```python
|
|
322
|
+
# bot/handlers/__init__.py
|
|
323
|
+
from . import (
|
|
324
|
+
callback_query_handlers, # noqa: F401
|
|
325
|
+
command_handlers, # noqa: F401
|
|
326
|
+
message_handlers, # noqa: F401
|
|
327
|
+
)
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### Summary
|
|
331
|
+
|
|
332
|
+
| User action | Update type | Base class | Example file |
|
|
333
|
+
|---|---|---|---|
|
|
334
|
+
| Sends `/start` | command | `CommandHandler` | `command_handlers.py` |
|
|
335
|
+
| Sends `product 1` | text message | `MessageHandler` | `message_handlers.py` |
|
|
336
|
+
| Presses an inline button | callback query | `CallbackQueryHandler` | `callback_query_handlers.py` |
|
|
337
|
+
|
|
338
|
+
Each new handler file needs one line in `handlers/__init__.py`. Your webhook view only needs `from . import handlers`.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "django-telegram-bot-api"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "A typed Telegram Bot API client for Django"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "reza", email = "rza.karampour@gmail.com" }
|
|
8
|
+
]
|
|
9
|
+
requires-python = ">=3.13"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"django>=6.1.2",
|
|
12
|
+
"djangorestframework>=3.18.3",
|
|
13
|
+
"pydantic",
|
|
14
|
+
"requests>=2.34.2",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[build-system]
|
|
18
|
+
requires = ["uv_build>=0.9.18,<0.10.0"]
|
|
19
|
+
build-backend = "uv_build"
|
|
20
|
+
|
|
21
|
+
[tool.uv.build-backend]
|
|
22
|
+
module-name = "django_telegram"
|