You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在aiogram v3中实现带勾选标记的Telegram机器人多选内联按钮

修复aiogram v3多选内联按钮勾选与保存功能问题

核心问题分析

你的代码存在三个关键错误,导致勾选标记失效、保存按钮不显示:

  • 全局user_choice变量被所有用户共享,无法区分不同用户的选择
  • select_choco函数中,产品按钮的callback_data错误绑定为列表而非单个产品
  • save_choice_callback中错误覆盖全局变量,且删除消息后非法调用query.message.answer

修复步骤与完整代码

以下是修正后的代码,解决了所有问题并实现需求:

from aiogram import Bot, Dispatcher, types, Router, F
from aiogram.filters import Command
from aiogram.types import Message, InlineKeyboardButton, InlineKeyboardMarkup
import asyncio

BOT_TOKEN = 'my-private-token'

bot = Bot(token=BOT_TOKEN)
dp = Dispatcher()
router = Router()

# 用字典存储每个用户的选择,key为用户ID,value为选择的产品集合
user_selections = {}

@router.message(Command('shop'))
async def menu_cmd(m: Message):
    choice = InlineKeyboardMarkup(inline_keyboard=[
        [InlineKeyboardButton(text='🍎Фрукти', callback_data='fruits'), InlineKeyboardButton(text='🍫Шоколад', callback_data='chocolate')],
        [InlineKeyboardButton(text='Скасувати ❌', callback_data='cancel')],
    ])
    await m.answer('Привіт в бота. Вибери: 👇', reply_markup=choice)
    

@router.callback_query(F.data == 'cancel')
async def cancel_callback(query: types.CallbackQuery):
    await query.message.delete()
    await query.answer('')

async def show_fruits(user_id: int):
    product_ft = ['🍎Яблуко', '🍌Банан']
    buttons = []
    # 获取当前用户的选择,不存在则初始化空集合
    user_choice = user_selections.get(user_id, set())
    
    for product in product_ft:
        button_text = f'{product}✅' if product in user_choice else product 
        button = types.InlineKeyboardButton(text=button_text, callback_data=f'fruit_{product}')
        buttons.append([button])

    if user_choice:
        buttons.append([types.InlineKeyboardButton(text='✔️Зберегти вибір', callback_data='save_choice')])
    
    buttons.append([types.InlineKeyboardButton(text='↩️Назад', callback_data='back')]) 

    choice_fruits = types.InlineKeyboardMarkup(inline_keyboard=buttons)
    return choice_fruits

@router.callback_query(F.data == 'fruits')
async def select_fruits(query: types.CallbackQuery):
    fruits_keyboard = await show_fruits(query.from_user.id)
    await query.message.edit_text('Вибери фрукт: ', reply_markup=fruits_keyboard)
    await query.answer()

@router.callback_query(F.data.startswith('fruit_'))
async def fruits_select(query: types.CallbackQuery):
    user_id = query.from_user.id
    # 提取产品名称,去掉前缀
    product_fruits = query.data.replace('fruit_', '')
    
    # 初始化用户选择集合
    if user_id not in user_selections:
        user_selections[user_id] = set()
    
    if product_fruits in user_selections[user_id]:
        user_selections[user_id].remove(product_fruits)
    else:
        user_selections[user_id].add(product_fruits)

    fruits_keyboard = await show_fruits(user_id)
    await query.message.edit_text('Вибери фрукт: ', reply_markup=fruits_keyboard)
    await query.answer()
    
@router.callback_query(F.data == 'save_choice')
async def save_choice_callback(query: types.CallbackQuery):
    user_id = query.from_user.id
    # 获取当前用户的选择
    user_choice = user_selections.get(user_id, set())
    
    # 先发送保存成功消息,再删除原消息
    await query.message.answer(f'Вибір збережено. Переглянути його ви можете завдяки команді - /my_choice')
    await query.message.delete()
    print(f'Вибір користувача {user_id}: {user_choice} збережено.')

@router.callback_query(F.data == 'back')
async def back_callback(query: types.CallbackQuery):
    choice = InlineKeyboardMarkup(inline_keyboard=[
        [InlineKeyboardButton(text='🍎Фрукти', callback_data='fruits'), InlineKeyboardButton(text='🍫Шоколад', callback_data='chocolate')],
        [InlineKeyboardButton(text='Скасувати ❌', callback_data='cancel')],
    ])
    await query.message.edit_text('Привіт в бота. Вибери: 👇', reply_markup=choice)

async def select_choco(user_id: int):
    product_choco = ['🍫Мілка', '🍬Снікерс']
    buttons = []
    user_choice = user_selections.get(user_id, set())

    for product in product_choco:
        button_text = f'{product}✅' if product in user_choice else product
        # 修复callback_data错误,使用单个产品而非列表
        button = types.InlineKeyboardButton(text=button_text, callback_data=f'choco_{product}')
        buttons.append([button])

    if user_choice:
        buttons.append([types.InlineKeyboardButton(text='✔️Зберегти вибір', callback_data='save_choice')])
    
    buttons.append([types.InlineKeyboardButton(text='↩️Назад', callback_data='back')]) 

    choice_choco = types.InlineKeyboardMarkup(inline_keyboard=buttons)
    return choice_choco

@router.callback_query(F.data == 'chocolate')
async def choco_review(query: types.CallbackQuery):
    choco_keyboard = await select_choco(query.from_user.id)
    await query.message.edit_text('Вибери шоколадку: ', reply_markup=choco_keyboard)
    await query.answer()

@router.callback_query(F.data.startswith('choco_'))
async def choco_select(query: types.CallbackQuery):
    user_id = query.from_user.id
    product_choco = query.data.replace('choco_', '')
    
    if user_id not in user_selections:
        user_selections[user_id] = set()
    
    if product_choco in user_selections[user_id]:
        user_selections[user_id].remove(product_choco)
    else:
        user_selections[user_id].add(product_choco)

    choco_keyboard = await select_choco(user_id)
    await query.message.edit_text('Виберіть шоколадку: ', reply_markup=choco_keyboard)
    await query.answer()

def go_to_start():
    kb_start = InlineKeyboardMarkup(
        inline_keyboard=
        [
            [InlineKeyboardButton(text='На головне меню', callback_data='go_start')]
        ])
    return kb_start

@router.callback_query(F.data == 'go_start')
async def go_start_callback(query: types.CallbackQuery):
    choice = InlineKeyboardMarkup(inline_keyboard=[
        [InlineKeyboardButton(text='🍎Фрукти', callback_data='fruits'), InlineKeyboardButton(text='🍫Шоколад', callback_data='chocolate')],
        [InlineKeyboardButton(text='Скасувати ❌', callback_data='cancel')],
    ])
    await query.message.edit_text('Привіт в бота. Вибери: 👇', reply_markup=choice)

@router.message(Command('my_choice'))
async def mychoice_cmd(m: types.Message):
    user_id = m.from_user.id
    user_choice = user_selections.get(user_id, set())
    show_kb_start = go_to_start()
    if user_choice:
        await m.reply(f'🧺Ось список товарів у вашому кошику: {', '.join(user_choice)}', reply_markup=show_kb_start)
    else:
        await m.reply('❌Ваш кошик ще пустий. Добавити товари можете завдяки команді /shop або просто натисніть на кнопку "Перейти на головне меню"')

async def main():
    dp.include_router(router)
    print("Bot is running...")
    await dp.start_polling(bot)

if __name__ == '__main__':
    asyncio.run(main())

关键改进说明

  • 用户选择隔离:用user_selections字典存储每个用户的选择,避免不同用户之间的选择冲突
  • 修复按钮回调数据:为水果和巧克力产品的回调数据添加前缀(fruit_/choco_),避免产品名称与其他回调数据冲突,同时修复了巧克力按钮的回调数据错误
  • 修正保存逻辑:先发送保存成功消息再删除原消息,避免消息已删除后的非法调用,同时正确获取当前用户的选择
  • 动态按钮文本:根据当前用户的选择集合动态生成带✅标记的按钮文本,确保勾选状态正确更新

内容的提问来源于stack exchange,提问作者bredlik25

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 11:14:53