Почему aiogram выдаёт KeyError при обработке callback_query из личных сообщений?
вылезает ошибка
Cause exception while process update id=691239273 by bot id=8510264806
KeyError: 7926248376
Traceback (most recent call last):
File "C:\Users\alanm\PycharmProjects\Uno\.venv\Lib\site-packages\aiogram\dispatcher\dispatcher.py", line 309, in _process_update
response = await self.feed_update(bot, update, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\alanm\PycharmProjects\Uno\.venv\Lib\site-packages\aiogram\dispatcher\dispatcher.py", line 158, in feed_update
response = await self.update.wrap_outer_middleware(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
...<7 lines>...
)
^
File "C:\Users\alanm\PycharmProjects\Uno\.venv\Lib\site-packages\aiogram\dispatcher\middlewares\error.py", line 25, in __call__
return await handler(event, data)
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\alanm\PycharmProjects\Uno\.venv\Lib\site-packages\aiogram\dispatcher\middlewares\user_context.py", line 56, in __call__
return await handler(event, data)
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\alanm\PycharmProjects\Uno\.venv\Lib\site-packages\aiogram\fsm\middleware.py", line 42, in __call__
return await handler(event, data)
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\alanm\PycharmProjects\Uno\.venv\Lib\site-packages\aiogram\dispatcher\event\telegram.py", line 121, in trigger
return await wrapped_inner(event, kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\alanm\PycharmProjects\Uno\.venv\Lib\site-packages\aiogram\dispatcher\event\handler.py", line 43, in call
return await wrapped()
^^^^^^^^^^^^^^^
File "C:\Users\alanm\PycharmProjects\Uno\.venv\Lib\site-packages\aiogram\dispatcher\dispatcher.py", line 276, in _listen_update
return await self.propagate_event(update_type=update_type, event=event, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\alanm\PycharmProjects\Uno\.venv\Lib\site-packages\aiogram\dispatcher\router.py", line 146, in propagate_event
return await observer.wrap_outer_middleware(_wrapped, event=event, data=kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\alanm\PycharmProjects\Uno\.venv\Lib\site-packages\aiogram\dispatcher\router.py", line 141, in _wrapped
return await self._propagate_event(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
observer=observer, update_type=update_type, event=telegram_event, **data
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)
^
File "C:\Users\alanm\PycharmProjects\Uno\.venv\Lib\site-packages\aiogram\dispatcher\router.py", line 166, in _propagate_event
response = await observer.trigger(event, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\alanm\PycharmProjects\Uno\.venv\Lib\site-packages\aiogram\dispatcher\event\telegram.py", line 121, in trigger
return await wrapped_inner(event, kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\alanm\PycharmProjects\Uno\.venv\Lib\site-packages\aiogram\dispatcher\event\handler.py", line 43, in call
return await wrapped()
^^^^^^^^^^^^^^^
File "C:\Users\alanm\PycharmProjects\Uno\main.py", line 174, in play
g = games[cb.message.chat.id]
~~~~~^^^^^^^^^^^^^^^^^^^^
KeyError: 7926248376
import random
import asyncio
from aiogram import Bot, Dispatcher
from aiogram.types import Message, InlineKeyboardMarkup, InlineKeyboardButton, CallbackQuery
from aiogram.filters import Command
TOKEN = "ТОКЕН"
bot = Bot(token=TOKEN)
dp = Dispatcher()
games = {}
COLOR_EMOJI = {
"Синяя": "?",
"Красная": "?",
"Желтая": "?",
"Зеленая": "?",
}
class UnoGame:
def __init__(self, chat_id):
self.chat_id = chat_id
self.players = []
self.settings = {
"cards_per_player": 7,
"allow_plus2": True,
"allow_skip": True,
"allow_reverse": True,
"allow_plus4": True,
"max_players": 4
}
self.started = False
self.deck = self.generate_deck()
self.hands = {}
self.current_card = None
self.current_color = None
self.turn = 0
self.direction = 1
def generate_deck(self):
colors = ["Синяя", "Красная", "Желтая", "Зеленая"]
values = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
deck = []
for color in colors:
for v in values:
deck.append(f"{color} {v}")
deck.append(f"{color} +2")
deck.append(f"{color} Пропуск")
deck.append(f"{color} Реверс")
for _ in range(4): deck.append("+4")
for _ in range(4): deck.append("Смена цвета")
random.shuffle(deck)
return deck
def deal(self):
for p in self.players:
self.hands[p] = [self.deck.pop() for _ in range(self.settings["cards_per_player"])]
self.current_card = self.deck.pop()
if self.current_card in ["+4", "Смена цвета"]:
self.current_color = random.choice(["Синяя", "Красная", "Желтая", "Зеленая"])
else:
self.current_color = self.current_card.split()[0]
def is_valid(self, card):
if card == "+4" or card == "Смена цвета":
return True
color, value = card.split()[0], " ".join(card.split()[1:])
top_val = " ".join(self.current_card.split()[1:]) if self.current_card not in ["+4", "Смена цвета"] else None
return color == self.current_color or value == top_val
def next_turn(self):
self.turn = (self.turn + self.direction) % len(self.players)
def settings_keyboard(chat_id):
g = games[chat_id]
return InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text=f"Карт: {g.settings['cards_per_player']}", callback_data="set_cards")],
[InlineKeyboardButton(text=f"+2: {'Вкл' if g.settings['allow_plus2'] else 'Выкл'}", callback_data="set_plus2")],
[InlineKeyboardButton(text=f"Пропуск: {'Вкл' if g.settings['allow_skip'] else 'Выкл'}", callback_data="set_skip")],
[InlineKeyboardButton(text=f"Реверс: {'Вкл' if g.settings['allow_reverse'] else 'Выкл'}", callback_data="set_rev")],
[InlineKeyboardButton(text=f"+4: {'Вкл' if g.settings['allow_plus4'] else 'Выкл'}", callback_data="set_plus4")],
[InlineKeyboardButton(text=f"Макс игроков: {g.settings['max_players']}", callback_data="set_max")],
[InlineKeyboardButton(text="▶ Начать игру", callback_data="start_uno")]
])
def hand_keyboard(pid, chat_id):
g = games[chat_id]
kb = []
for card in g.hands[pid]:
kb.append([InlineKeyboardButton(text=card, callback_data=f"play_{card}")])
kb.append([InlineKeyboardButton(text="Взять карту", callback_data="take")])
return InlineKeyboardMarkup(inline_keyboard=kb)
def choose_color_keyboard():
return InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="? Синяя", callback_data="color_Синяя")],
[InlineKeyboardButton(text="? Красная", callback_data="color_Красная")],
[InlineKeyboardButton(text="? Желтая", callback_data="color_Желтая")],
[InlineKeyboardButton(text="? Зеленая", callback_data="color_Зеленая")]
])
@dp.message(Command("game"))
async def game_cmd(msg: Message):
if msg.chat.id not in games:
games[msg.chat.id] = UnoGame(msg.chat.id)
await msg.answer("UNO — набор игроков!", reply_markup=InlineKeyboardMarkup(inline_keyboard=[[InlineKeyboardButton(text="Присоединиться", callback_data="join")]]))
@dp.callback_query(lambda c: c.data == "join")
async def join(cb: CallbackQuery):
g = games[cb.message.chat.id]
if cb.from_user.id in g.players:
return await cb.answer("Ты уже в игре!")
if len(g.players) >= g.settings["max_players"]:
return await cb.answer("Лобби заполнено!")
g.players.append(cb.from_user.id)
await cb.message.answer(f"Игрок {cb.from_user.first_name} вошёл в игру!")
if len(g.players) >= 2:
await cb.message.answer("Настройки:", reply_markup=settings_keyboard(cb.message.chat.id))
@dp.callback_query(lambda c: c.data.startswith("set_"))
async def set_update(cb: CallbackQuery):
g = games[cb.message.chat.id]
if cb.data == "set_cards":
g.settings["cards_per_player"] = (g.settings["cards_per_player"] % 12) + 1
if cb.data == "set_plus2": g.settings["allow_plus2"] ^= True
if cb.data == "set_skip": g.settings["allow_skip"] ^= True
if cb.data == "set_rev": g.settings["allow_reverse"] ^= True
if cb.data == "set_plus4": g.settings["allow_plus4"] ^= True
if cb.data == "set_max":
g.settings["max_players"] = (g.settings["max_players"] % 8) + 2
await cb.message.edit_reply_markup(reply_markup=settings_keyboard(cb.message.chat.id))
await cb.answer()
@dp.callback_query(lambda c: c.data == "start_uno")
async def start_game(cb: CallbackQuery):
g = games[cb.message.chat.id]
g.started = True
g.deal()
await cb.message.answer(f"Игра началась! Верхняя карта: {g.current_card}")
cur = g.players[g.turn]
await bot.send_message(cur, f"Твой ход! Цвет: {g.current_color}", reply_markup=hand_keyboard(cur, g.chat_id))
@dp.callback_query(lambda c: c.data.startswith("play_"))
async def play(cb: CallbackQuery):
g = games[cb.message.chat.id]
pid = cb.from_user.id
if pid != g.players[g.turn]: return await cb.answer("Не твой ход!")
card = cb.data.replace("play_", "")
if not g.is_valid(card):
return await cb.answer("Карта не подходит!")
g.hands[pid].remove(card)
g.current_card = card
if card == "+4" or card == "Смена цвета":
await cb.message.edit_text(f"Ты сыграл {card}. Выбери цвет:")
return await cb.message.edit_reply_markup(reply_markup=choose_color_keyboard())
color = card.split()[0]
g.current_color = color
effects = " ".join(card.split()[1:])
if effects == "+2" and g.settings["allow_plus2"]:
target = g.players[(g.turn + g.direction) % len(g.players)]
g.hands[target].extend([g.deck.pop(), g.deck.pop()])
if effects == "Пропуск" and g.settings["allow_skip"]:
g.next_turn()
if effects == "Реверс" and g.settings["allow_reverse"]:
g.direction *= -1
if len(g.hands[pid]) == 0:
await bot.send_message(g.chat_id, f"? Победил {cb.from_user.first_name}!")
games.pop(g.chat_id)
return
g.next_turn()
nxt = g.players[g.turn]
await cb.message.edit_text(f"Ты сыграл {card}")
await bot.send_message(nxt, f"Твой ход! Цвет: {g.current_color}", reply_markup=hand_keyboard(nxt, g.chat_id))
@dp.callback_query(lambda c: c.data.startswith("color_"))
async def set_color(cb: CallbackQuery):
g = games[cb.message.chat.id]
new_color = cb.data.split("_")[1]
g.current_color = new_color
await cb.message.edit_text(f"Цвет выбран: {new_color}")
g.next_turn()
nxt = g.players[g.turn]
await bot.send_message(nxt, f"Твой ход! Цвет: {g.current_color}", reply_markup=hand_keyboard(nxt, g.chat_id))
@dp.callback_query(lambda c: c.data == "take")
async def take(cb: CallbackQuery):
g = games[cb.message.chat.id]
pid = cb.from_user.id
if pid != g.players[g.turn]:
return await cb.answer("Сейчас не твой ход!")
if not g.deck:
g.deck = g.generate_deck()
random.shuffle(g.deck)
card = g.deck.pop()
g.hands[pid].append(card)
await cb.message.edit_text(f"Ты взял карту: {card}")
await cb.answer()
await bot.send_message(pid, "Твоя рука обновлена:", reply_markup=hand_keyboard(pid, g.chat_id))
g.next_turn()
nxt = g.players[g.turn]
await bot.send_message(nxt, f"Твой ход! Цвет: {g.current_color}", reply_markup=hand_keyboard(nxt, g.chat_id))
def collect_all_cards(game: UnoGame):
all_cards = []
full = game.generate_deck()
used = []
for hand in game.hands.values():
used.extend(hand)
if game.current_card:
used.append(game.current_card)
new_deck = [c for c in full if c not in used]
random.shuffle(new_deck)
return new_deck
@dp.callback_query(lambda c: c.data.startswith("color_"))
async def set_color(cb: CallbackQuery):
g = games[cb.message.chat.id]
new_color = cb.data.split("_", 1)[1]
prev_card = g.current_card
g.current_color = new_color
await cb.message.edit_text(f"Цвет выбран: {new_color}")
await cb.answer(f"Выбран цвет: {new_color}")
if prev_card == "+4" and g.settings.get("allow_plus4", True):
target_index = (g.turn + g.direction) % len(g.players)
target = g.players[target_index]
while len(g.deck) < 4:
g.deck.extend(collect_all_cards(g))
if not g.deck:
g.deck = g.generate_deck()
random.shuffle(g.deck)
drawn = [g.deck.pop() for _ in range(4)]
g.hands[target].extend(drawn)
try:
await bot.send_message(target, f"Тебе дали +4 карты: {', '.join(drawn)}")
except Exception:
pass
g.next_turn()
nxt = g.players[g.turn]
await bot.send_message(nxt, f"Твой ход! Цвет: {g.current_color}", reply_markup=hand_keyboard(nxt, g.chat_id))
def ensure_deck_has(game: UnoGame, need: int):
if len(game.deck) >= need:
return
new_cards = collect_all_cards(game)
game.deck.extend(new_cards)
random.shuffle(game.deck)
@dp.message(Command("help"))
async def help_cmd(message: Message):
help_text = (
"<b>UNO — Помощь и правила</b>\n\n"
"Это Telegram-бот для игры в UNO. Кратко и подробно о возможностях и командах:\n\n"
"<b>Команды</b>:\n"
"/game — создать лобби и начать набор игроков.\n"
"/help — показать эту справку.\n\n"
"<b>Как присоединиться</b>:\n"
"1. В чате группы вызовите /game.\n"
"2. Нажмите кнопку «Присоединиться» в лобби.\n"
"3. Как только в лобби будет ≥2 игрока — появятся настройки игры.\n\n"
"<b>Настройки (в лобби)</b>:\n"
"• <i>Карт</i> — количество карт, выдаваемых каждому игроку (по умолчанию 7).\n"
"• <i>+2</i>, <i>Пропуск</i>, <i>Реверс</i>, <i>+4</i> — включение/выключение соответствующих эффектов.\n"
"• <i>Макс игроков</i> — ограничение размера лобби.\n"
"• Нажмите ▶ «Начать игру», чтобы раздать карты и начать.\n\n"
"<b>Игровой процесс</b>:\n"
"• Игроку на телефон/личный чат присылается клавиатура — его карты (inline-кнопки).\n"
"• Нажмите кнопку с картой, чтобы сыграть её. Бот проверит корректность хода.\n"
"• Кнопка «Взять карту» — взять одну карту из колоды (ход переходит дальше).\n"
"• После хода бот передаст ход следующему игроку и пришлёт ему клавиатуру с картами.\n\n"
"<b>Специальные карты</b>:\n"
"• <b>+2</b> — следующий игрок берёт 2 карты (если включён в настройках).\n"
"• <b>Пропуск</b> — следующий игрок пропускает ход.\n"
"• <b>Реверс</b> — меняет направление очередности.\n"
"• <b>Смена цвета</b> — джокер: игрок выбирает новый цвет через кнопки.\n"
"• <b>+4</b> — джокер со штрафом: игрок выбирает цвет, а следующий игрок берёт 4 карты (если включён).\n\n"
"<b>Поведение джокеров</b>:\n"
"• Когда вы сыграли «Смена цвета» или «+4», бот пришлёт кнопки выбора цвета (????).\n"
"• После выбора цвета игра продолжается с новым верхним цветом.\n\n"
"<b>Победа</b>:\n"
"• Игрок, который избавился от всех карт — победитель. Бот объявит победителя в чате.\n\n"
"<b>Колода и восстановление</b>:\n"
"• Если колода пустеет, бот автоматически перемешает использованные карты и восстановит колоду.\n\n"
"Удачи и хорошей игры! ?"
)
kb = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="▶ Начать игру", callback_data="start_uno")]
])
await message.answer(help_text, reply_markup=kb, parse_mode="HTML")
async def main():
await dp.start_polling(bot)
if __name__ == "__main__":
try:
asyncio.run(main())
except (KeyboardInterrupt, SystemExit):
print("Bot stopped")
в чем проблема подскажите?