405 lines
11 KiB
Markdown
405 lines
11 KiB
Markdown
# Telegram Bot 按钮与键盘实现指南
|
||
|
||
> 完整的 Telegram Bot 交互式功能开发参考
|
||
|
||
---
|
||
|
||
## 📋 目录
|
||
|
||
1. [按钮和键盘类型](#按钮和键盘类型)
|
||
2. [实现方式对比](#实现方式对比)
|
||
3. [核心代码示例](#核心代码示例)
|
||
4. [最佳实践](#最佳实践)
|
||
|
||
---
|
||
|
||
## 按钮和键盘类型
|
||
|
||
### 1. Inline Keyboard(内联键盘)
|
||
|
||
**特点**:
|
||
- 显示在消息下方
|
||
- 点击后触发回调,不发送消息
|
||
- 支持回调数据、URL、切换查询等
|
||
|
||
**应用场景**:确认/取消、菜单导航、分页控制、设置选项
|
||
|
||
### 2. Reply Keyboard(底部虚拟键盘)
|
||
|
||
**特点**:
|
||
- 显示在输入框上方
|
||
- 点击后发送文本消息
|
||
- 可设置持久化或一次性
|
||
|
||
**应用场景**:快捷命令、常用操作、表单输入、主菜单
|
||
|
||
### 3. Bot Command Menu(命令菜单)
|
||
|
||
**特点**:
|
||
- 显示在输入框左侧 "/" 按钮
|
||
- 通过 BotFather 或 API 设置
|
||
- 提供命令列表和描述
|
||
|
||
**应用场景**:功能索引、新用户引导、快速命令访问
|
||
|
||
### 4. 类型对比
|
||
|
||
| 特性 | Inline | Reply | Command Menu |
|
||
|------|--------|-------|--------------|
|
||
| 位置 | 消息下方 | 输入框上方 | "/" 菜单 |
|
||
| 触发 | 回调查询 | 文本消息 | 命令 |
|
||
| 持久化 | 随消息 | 可配置 | 始终存在 |
|
||
| 场景 | 临时交互 | 常驻功能 | 命令索引 |
|
||
|
||
---
|
||
|
||
## 实现方式对比
|
||
|
||
### python-telegram-bot(推荐 Bot 开发)
|
||
|
||
**优点**:
|
||
- 官方推荐,完整的 Handler 系统
|
||
- 丰富的按钮和键盘支持
|
||
- 异步版本性能优异
|
||
|
||
**安装**:
|
||
```bash
|
||
pip install python-telegram-bot==20.7
|
||
```
|
||
|
||
### Telethon(适合用户账号自动化)
|
||
|
||
**优点**:
|
||
- 完整的 MTProto API 访问
|
||
- 可使用用户账号和 Bot
|
||
- 强大的消息监听能力
|
||
|
||
**安装**:
|
||
```bash
|
||
pip install telethon cryptg
|
||
```
|
||
|
||
---
|
||
|
||
## 核心代码示例
|
||
|
||
### 1. Inline Keyboard 实现
|
||
|
||
**python-telegram-bot:**
|
||
```python
|
||
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
|
||
from telegram.ext import Application, CommandHandler, CallbackQueryHandler, ContextTypes
|
||
|
||
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||
"""显示内联键盘"""
|
||
keyboard = [
|
||
[
|
||
InlineKeyboardButton("📊 查看数据", callback_data="view_data"),
|
||
InlineKeyboardButton("⚙️ 设置", callback_data="settings"),
|
||
],
|
||
[
|
||
InlineKeyboardButton("🔗 访问网站", url="https://example.com"),
|
||
],
|
||
]
|
||
reply_markup = InlineKeyboardMarkup(keyboard)
|
||
await update.message.reply_text("请选择:", reply_markup=reply_markup)
|
||
|
||
async def button_callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||
"""处理按钮点击"""
|
||
query = update.callback_query
|
||
await query.answer() # 必须调用
|
||
|
||
if query.data == "view_data":
|
||
await query.edit_message_text("显示数据...")
|
||
elif query.data == "settings":
|
||
await query.edit_message_text("设置选项...")
|
||
|
||
# 注册处理器
|
||
app = Application.builder().token("TOKEN").build()
|
||
app.add_handler(CommandHandler("start", start))
|
||
app.add_handler(CallbackQueryHandler(button_callback))
|
||
app.run_polling()
|
||
```
|
||
|
||
**Telethon:**
|
||
```python
|
||
from telethon import TelegramClient, events, Button
|
||
|
||
client = TelegramClient('bot', api_id, api_hash).start(bot_token=BOT_TOKEN)
|
||
|
||
@client.on(events.NewMessage(pattern='/start'))
|
||
async def start(event):
|
||
buttons = [
|
||
[Button.inline("📊 查看数据", b"view_data"), Button.inline("⚙️ 设置", b"settings")],
|
||
[Button.url("🔗 访问网站", "https://example.com")]
|
||
]
|
||
await event.respond("请选择:", buttons=buttons)
|
||
|
||
@client.on(events.CallbackQuery)
|
||
async def callback(event):
|
||
if event.data == b"view_data":
|
||
await event.edit("显示数据...")
|
||
elif event.data == b"settings":
|
||
await event.edit("设置选项...")
|
||
|
||
client.run_until_disconnected()
|
||
```
|
||
|
||
### 2. Reply Keyboard 实现
|
||
|
||
**python-telegram-bot:**
|
||
```python
|
||
from telegram import KeyboardButton, ReplyKeyboardMarkup, ReplyKeyboardRemove
|
||
|
||
async def menu(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||
"""显示底部键盘"""
|
||
keyboard = [
|
||
[KeyboardButton("📊 查看数据"), KeyboardButton("⚙️ 设置")],
|
||
[KeyboardButton("📚 帮助"), KeyboardButton("❌ 隐藏键盘")],
|
||
]
|
||
reply_markup = ReplyKeyboardMarkup(
|
||
keyboard,
|
||
resize_keyboard=True,
|
||
one_time_keyboard=False
|
||
)
|
||
await update.message.reply_text("菜单已激活", reply_markup=reply_markup)
|
||
|
||
async def handle_text(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||
"""处理文本消息"""
|
||
text = update.message.text
|
||
if text == "📊 查看数据":
|
||
await update.message.reply_text("显示数据...")
|
||
elif text == "❌ 隐藏键盘":
|
||
await update.message.reply_text("已隐藏", reply_markup=ReplyKeyboardRemove())
|
||
```
|
||
|
||
**Telethon:**
|
||
```python
|
||
@client.on(events.NewMessage(pattern='/menu'))
|
||
async def menu(event):
|
||
buttons = [
|
||
[Button.text("📊 查看数据"), Button.text("⚙️ 设置")],
|
||
[Button.text("📚 帮助"), Button.text("❌ 隐藏键盘")]
|
||
]
|
||
await event.respond("菜单已激活", buttons=buttons)
|
||
|
||
@client.on(events.NewMessage)
|
||
async def handle_text(event):
|
||
if event.text == "📊 查看数据":
|
||
await event.respond("显示数据...")
|
||
```
|
||
|
||
### 3. Bot Command Menu 设置
|
||
|
||
**通过 BotFather:**
|
||
```
|
||
1. 发送 /setcommands 到 @BotFather
|
||
2. 选择你的 Bot
|
||
3. 输入命令列表(每行格式:command - description)
|
||
|
||
start - 启动机器人
|
||
help - 获取帮助
|
||
menu - 显示主菜单
|
||
settings - 配置设置
|
||
```
|
||
|
||
**通过 API(python-telegram-bot):**
|
||
```python
|
||
from telegram import BotCommand
|
||
|
||
async def set_commands(app: Application):
|
||
"""设置命令菜单"""
|
||
commands = [
|
||
BotCommand("start", "启动机器人"),
|
||
BotCommand("help", "获取帮助"),
|
||
BotCommand("menu", "显示主菜单"),
|
||
BotCommand("settings", "配置设置"),
|
||
]
|
||
await app.bot.set_my_commands(commands)
|
||
|
||
# 在启动时调用
|
||
app.post_init = set_commands
|
||
```
|
||
|
||
### 4. 项目结构示例
|
||
|
||
```
|
||
telegram_bot/
|
||
├── bot.py # 主程序
|
||
├── config.py # 配置管理
|
||
├── requirements.txt
|
||
├── .env
|
||
├── handlers/
|
||
│ ├── command_handlers.py # 命令处理器
|
||
│ ├── callback_handlers.py # 回调处理器
|
||
│ └── message_handlers.py # 消息处理器
|
||
├── keyboards/
|
||
│ ├── inline_keyboards.py # 内联键盘布局
|
||
│ └── reply_keyboards.py # 回复键盘布局
|
||
└── utils/
|
||
├── logger.py # 日志
|
||
└── database.py # 数据库
|
||
```
|
||
|
||
**模块化示例(keyboards/inline_keyboards.py):**
|
||
```python
|
||
from telegram import InlineKeyboardButton, InlineKeyboardMarkup
|
||
|
||
def get_main_menu():
|
||
"""主菜单键盘"""
|
||
return InlineKeyboardMarkup([
|
||
[
|
||
InlineKeyboardButton("📊 数据", callback_data="data"),
|
||
InlineKeyboardButton("⚙️ 设置", callback_data="settings"),
|
||
],
|
||
[InlineKeyboardButton("📚 帮助", callback_data="help")],
|
||
])
|
||
|
||
def get_data_menu():
|
||
"""数据菜单键盘"""
|
||
return InlineKeyboardMarkup([
|
||
[
|
||
InlineKeyboardButton("📈 实时", callback_data="data_realtime"),
|
||
InlineKeyboardButton("📊 历史", callback_data="data_history"),
|
||
],
|
||
[InlineKeyboardButton("⬅️ 返回", callback_data="back")],
|
||
])
|
||
```
|
||
|
||
---
|
||
|
||
## 最佳实践
|
||
|
||
### 1. Handler 优先级
|
||
|
||
```python
|
||
# 先注册先匹配,按从特殊到通用的顺序
|
||
app.add_handler(CommandHandler("start", start)) # 1. 特定命令
|
||
app.add_handler(CallbackQueryHandler(callback)) # 2. 回调查询
|
||
app.add_handler(ConversationHandler(...)) # 3. 对话流程
|
||
app.add_handler(MessageHandler(filters.TEXT, text_msg)) # 4. 通用消息(最后)
|
||
```
|
||
|
||
### 2. 错误处理
|
||
|
||
```python
|
||
async def error_handler(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||
"""全局错误处理"""
|
||
logger.error(f"更新 {update} 引起错误", exc_info=context.error)
|
||
|
||
# 通知用户
|
||
if update and update.effective_message:
|
||
await update.effective_message.reply_text("操作失败,请重试")
|
||
|
||
app.add_error_handler(error_handler)
|
||
```
|
||
|
||
### 3. 回调数据管理
|
||
|
||
```python
|
||
# 使用结构化的 callback_data
|
||
callback_data = "action:page:item" # 例如 "view:1:product_123"
|
||
|
||
# 解析回调数据
|
||
async def callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||
query = update.callback_query
|
||
parts = query.data.split(":")
|
||
action, page, item = parts
|
||
|
||
if action == "view":
|
||
await show_item(query, page, item)
|
||
```
|
||
|
||
### 4. 键盘设计原则
|
||
|
||
- **简洁**:每行最多 2-3 个按钮
|
||
- **清晰**:使用 emoji 增强识别度
|
||
- **一致**:保持统一的布局风格
|
||
- **响应**:及时反馈用户操作
|
||
|
||
### 5. 安全考虑
|
||
|
||
```python
|
||
# 验证用户权限
|
||
ADMIN_IDS = [123456789]
|
||
|
||
async def admin_only(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||
user_id = update.effective_user.id
|
||
if user_id not in ADMIN_IDS:
|
||
await update.message.reply_text("无权限")
|
||
return
|
||
|
||
# 执行管理员操作
|
||
```
|
||
|
||
### 6. 部署方案
|
||
|
||
**Webhook(推荐生产环境):**
|
||
```python
|
||
from flask import Flask, request
|
||
|
||
app_flask = Flask(__name__)
|
||
|
||
@app_flask.route('/webhook', methods=['POST'])
|
||
def webhook():
|
||
update = Update.de_json(request.get_json(), bot)
|
||
application.update_queue.put(update)
|
||
return "OK"
|
||
|
||
# 设置 webhook
|
||
bot.set_webhook(f"https://yourdomain.com/webhook")
|
||
```
|
||
|
||
**Systemd Service(Linux):**
|
||
```ini
|
||
[Unit]
|
||
Description=Telegram Bot
|
||
After=network.target
|
||
|
||
[Service]
|
||
Type=simple
|
||
User=your_user
|
||
WorkingDirectory=/path/to/bot
|
||
ExecStart=/path/to/venv/bin/python bot.py
|
||
Restart=always
|
||
|
||
[Install]
|
||
WantedBy=multi-user.target
|
||
```
|
||
|
||
### 7. 常用库版本
|
||
|
||
```txt
|
||
# requirements.txt
|
||
python-telegram-bot==20.7
|
||
python-dotenv==1.0.0
|
||
aiosqlite==0.19.0
|
||
httpx==0.25.2
|
||
```
|
||
|
||
---
|
||
|
||
## 快速参考
|
||
|
||
### Inline Keyboard 按钮类型
|
||
|
||
```python
|
||
InlineKeyboardButton("文本", callback_data="data") # 回调按钮
|
||
InlineKeyboardButton("链接", url="https://...") # URL按钮
|
||
InlineKeyboardButton("切换", switch_inline_query="") # 内联查询
|
||
InlineKeyboardButton("登录", login_url=...) # 登录按钮
|
||
InlineKeyboardButton("支付", pay=True) # 支付按钮
|
||
InlineKeyboardButton("应用", web_app=WebAppInfo(...)) # Mini App
|
||
```
|
||
|
||
### 常用事件类型
|
||
|
||
- `events.NewMessage` - 新消息
|
||
- `events.CallbackQuery` - 回调查询
|
||
- `events.InlineQuery` - 内联查询
|
||
- `events.ChatAction` - 群组动作
|
||
|
||
---
|
||
|
||
**这份指南涵盖了 Telegram Bot 按钮和键盘的所有核心实现!**
|