- Добавлены мини-истории для чтения с выбором жанра и вопросами - Кнопка показа/скрытия перевода истории - Количество вопросов берётся из настроек пользователя - Слово дня генерируется глобально в 00:00 UTC - Кнопка "Практика" открывает меню выбора режима - Убран автоматический create_all при запуске (только миграции) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
689 lines
24 KiB
Python
689 lines
24 KiB
Python
"""Handler для мини-историй (Reading Practice)."""
|
||
|
||
from aiogram import Router, F
|
||
from aiogram.filters import Command
|
||
from aiogram.types import Message, CallbackQuery, InlineKeyboardMarkup, InlineKeyboardButton
|
||
from aiogram.fsm.context import FSMContext
|
||
from aiogram.fsm.state import State, StatesGroup
|
||
|
||
from database.db import async_session_maker
|
||
from database.models import MiniStory, StoryGenre, WordSource
|
||
from services.user_service import UserService
|
||
from services.vocabulary_service import VocabularyService
|
||
from services.ai_service import ai_service
|
||
from utils.i18n import t, get_user_lang, get_user_translation_lang
|
||
from utils.levels import get_user_level_for_language
|
||
|
||
router = Router()
|
||
|
||
|
||
class StoryStates(StatesGroup):
|
||
"""Состояния для чтения истории"""
|
||
reading = State() # Чтение истории
|
||
questions = State() # Ответы на вопросы
|
||
|
||
|
||
GENRE_EMOJI = {
|
||
"dialogue": "🗣",
|
||
"news": "📰",
|
||
"story": "🎭",
|
||
"letter": "📧",
|
||
"recipe": "🍳"
|
||
}
|
||
|
||
|
||
def get_genre_keyboard(lang: str) -> InlineKeyboardMarkup:
|
||
"""Клавиатура выбора жанра"""
|
||
return InlineKeyboardMarkup(inline_keyboard=[
|
||
[
|
||
InlineKeyboardButton(
|
||
text=f"🗣 {t(lang, 'story.genre.dialogue')}",
|
||
callback_data="story_genre_dialogue"
|
||
),
|
||
InlineKeyboardButton(
|
||
text=f"📰 {t(lang, 'story.genre.news')}",
|
||
callback_data="story_genre_news"
|
||
),
|
||
],
|
||
[
|
||
InlineKeyboardButton(
|
||
text=f"🎭 {t(lang, 'story.genre.story')}",
|
||
callback_data="story_genre_story"
|
||
),
|
||
InlineKeyboardButton(
|
||
text=f"📧 {t(lang, 'story.genre.letter')}",
|
||
callback_data="story_genre_letter"
|
||
),
|
||
],
|
||
[
|
||
InlineKeyboardButton(
|
||
text=f"🍳 {t(lang, 'story.genre.recipe')}",
|
||
callback_data="story_genre_recipe"
|
||
),
|
||
],
|
||
])
|
||
|
||
|
||
def format_story_text(story: MiniStory, lang: str, show_translation: bool = False) -> str:
|
||
"""Форматировать текст истории"""
|
||
emoji = GENRE_EMOJI.get(story.genre.value, "📖")
|
||
|
||
text = f"{emoji} <b>{story.title}</b>\n"
|
||
text += f"<i>{t(lang, 'story.level')}: {story.level} • {story.word_count} {t(lang, 'story.words')}</i>\n"
|
||
text += "─" * 20 + "\n\n"
|
||
text += story.content
|
||
|
||
if show_translation and story.translation:
|
||
text += "\n\n" + "─" * 20
|
||
text += f"\n\n🌐 <b>{t(lang, 'story.translation')}:</b>\n\n"
|
||
text += f"<i>{story.translation}</i>"
|
||
|
||
text += "\n\n" + "─" * 20
|
||
|
||
return text
|
||
|
||
|
||
def get_story_keyboard(story_id: int, lang: str, show_translation: bool = False) -> InlineKeyboardMarkup:
|
||
"""Клавиатура под историей"""
|
||
# Кнопка перевода - показать или скрыть
|
||
if show_translation:
|
||
translation_btn = InlineKeyboardButton(
|
||
text=f"🌐 {t(lang, 'story.hide_translation')}",
|
||
callback_data=f"story_hide_translation_{story_id}"
|
||
)
|
||
else:
|
||
translation_btn = InlineKeyboardButton(
|
||
text=f"🌐 {t(lang, 'story.show_translation')}",
|
||
callback_data=f"story_show_translation_{story_id}"
|
||
)
|
||
|
||
return InlineKeyboardMarkup(inline_keyboard=[
|
||
[translation_btn],
|
||
[
|
||
InlineKeyboardButton(
|
||
text=f"📝 {t(lang, 'story.questions_btn')}",
|
||
callback_data=f"story_questions_{story_id}"
|
||
),
|
||
],
|
||
[
|
||
InlineKeyboardButton(
|
||
text=f"📚 {t(lang, 'story.vocab_btn')}",
|
||
callback_data=f"story_vocab_{story_id}"
|
||
),
|
||
],
|
||
[
|
||
InlineKeyboardButton(
|
||
text=f"🔄 {t(lang, 'story.new_btn')}",
|
||
callback_data="story_new"
|
||
),
|
||
],
|
||
])
|
||
|
||
|
||
@router.message(Command("story"))
|
||
async def cmd_story(message: Message, state: FSMContext):
|
||
"""Обработчик команды /story"""
|
||
await state.clear()
|
||
|
||
async with async_session_maker() as session:
|
||
user = await UserService.get_user_by_telegram_id(session, message.from_user.id)
|
||
|
||
if not user:
|
||
await message.answer(t('ru', 'common.start_first'))
|
||
return
|
||
|
||
lang = get_user_lang(user)
|
||
|
||
text = f"📖 <b>{t(lang, 'story.title')}</b>\n\n"
|
||
text += t(lang, 'story.choose_genre')
|
||
|
||
await message.answer(text, reply_markup=get_genre_keyboard(lang))
|
||
|
||
|
||
@router.callback_query(F.data == "story_new")
|
||
async def story_new_callback(callback: CallbackQuery, state: FSMContext):
|
||
"""Показать выбор жанра для новой истории"""
|
||
await callback.answer()
|
||
await state.clear()
|
||
|
||
async with async_session_maker() as session:
|
||
user = await UserService.get_user_by_telegram_id(session, callback.from_user.id)
|
||
lang = get_user_lang(user)
|
||
|
||
text = f"📖 <b>{t(lang, 'story.title')}</b>\n\n"
|
||
text += t(lang, 'story.choose_genre')
|
||
|
||
await callback.message.edit_text(text, reply_markup=get_genre_keyboard(lang))
|
||
|
||
|
||
@router.callback_query(F.data.startswith("story_genre_"))
|
||
async def story_genre_callback(callback: CallbackQuery, state: FSMContext):
|
||
"""Генерация истории выбранного жанра"""
|
||
await callback.answer()
|
||
|
||
genre = callback.data.replace("story_genre_", "")
|
||
|
||
async with async_session_maker() as session:
|
||
user = await UserService.get_user_by_telegram_id(session, callback.from_user.id)
|
||
lang = get_user_lang(user)
|
||
learning_lang = user.learning_language or 'en'
|
||
level = get_user_level_for_language(user)
|
||
translation_lang = get_user_translation_lang(user)
|
||
|
||
# Показываем индикатор генерации
|
||
await callback.message.edit_text(t(lang, 'story.generating'))
|
||
|
||
# Получаем количество вопросов из настроек
|
||
tasks_count = getattr(user, 'tasks_count', 5) or 5
|
||
|
||
# Генерируем историю
|
||
story_data = await ai_service.generate_mini_story(
|
||
genre=genre,
|
||
level=level,
|
||
learning_lang=learning_lang,
|
||
translation_lang=translation_lang,
|
||
user_id=user.id,
|
||
num_questions=tasks_count
|
||
)
|
||
|
||
if not story_data:
|
||
await callback.message.edit_text(
|
||
t(lang, 'story.failed'),
|
||
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
|
||
[InlineKeyboardButton(
|
||
text=f"🔄 {t(lang, 'story.try_again')}",
|
||
callback_data="story_new"
|
||
)]
|
||
])
|
||
)
|
||
return
|
||
|
||
# Сохраняем историю в БД
|
||
story = MiniStory(
|
||
user_id=user.id,
|
||
title=story_data.get('title', 'Story'),
|
||
content=story_data.get('content', ''),
|
||
translation=story_data.get('translation', ''),
|
||
genre=StoryGenre(genre),
|
||
learning_lang=learning_lang,
|
||
level=level,
|
||
word_count=story_data.get('word_count', 0),
|
||
vocabulary=story_data.get('vocabulary', []),
|
||
questions=story_data.get('questions', [])
|
||
)
|
||
session.add(story)
|
||
await session.commit()
|
||
await session.refresh(story)
|
||
|
||
# Сохраняем ID истории в состоянии
|
||
await state.update_data(story_id=story.id)
|
||
await state.set_state(StoryStates.reading)
|
||
|
||
# Показываем историю
|
||
text = format_story_text(story, lang)
|
||
await callback.message.edit_text(
|
||
text,
|
||
reply_markup=get_story_keyboard(story.id, lang)
|
||
)
|
||
|
||
|
||
@router.callback_query(F.data.startswith("story_show_translation_"))
|
||
async def story_show_translation_callback(callback: CallbackQuery):
|
||
"""Показать перевод истории"""
|
||
await callback.answer()
|
||
|
||
story_id = int(callback.data.replace("story_show_translation_", ""))
|
||
|
||
async with async_session_maker() as session:
|
||
user = await UserService.get_user_by_telegram_id(session, callback.from_user.id)
|
||
lang = get_user_lang(user)
|
||
|
||
story = await session.get(MiniStory, story_id)
|
||
if not story:
|
||
await callback.answer(t(lang, 'story.not_found'), show_alert=True)
|
||
return
|
||
|
||
text = format_story_text(story, lang, show_translation=True)
|
||
await callback.message.edit_text(
|
||
text,
|
||
reply_markup=get_story_keyboard(story.id, lang, show_translation=True)
|
||
)
|
||
|
||
|
||
@router.callback_query(F.data.startswith("story_hide_translation_"))
|
||
async def story_hide_translation_callback(callback: CallbackQuery):
|
||
"""Скрыть перевод истории"""
|
||
await callback.answer()
|
||
|
||
story_id = int(callback.data.replace("story_hide_translation_", ""))
|
||
|
||
async with async_session_maker() as session:
|
||
user = await UserService.get_user_by_telegram_id(session, callback.from_user.id)
|
||
lang = get_user_lang(user)
|
||
|
||
story = await session.get(MiniStory, story_id)
|
||
if not story:
|
||
await callback.answer(t(lang, 'story.not_found'), show_alert=True)
|
||
return
|
||
|
||
text = format_story_text(story, lang, show_translation=False)
|
||
await callback.message.edit_text(
|
||
text,
|
||
reply_markup=get_story_keyboard(story.id, lang, show_translation=False)
|
||
)
|
||
|
||
|
||
@router.callback_query(F.data.startswith("story_vocab_"))
|
||
async def story_vocab_callback(callback: CallbackQuery):
|
||
"""Показать словарь истории"""
|
||
await callback.answer()
|
||
|
||
story_id = int(callback.data.replace("story_vocab_", ""))
|
||
|
||
async with async_session_maker() as session:
|
||
user = await UserService.get_user_by_telegram_id(session, callback.from_user.id)
|
||
lang = get_user_lang(user)
|
||
|
||
story = await session.get(MiniStory, story_id)
|
||
if not story:
|
||
await callback.answer(t(lang, 'story.not_found'), show_alert=True)
|
||
return
|
||
|
||
vocabulary = story.vocabulary or []
|
||
|
||
if not vocabulary:
|
||
await callback.answer(t(lang, 'story.no_vocab'), show_alert=True)
|
||
return
|
||
|
||
# Формируем текст со словами
|
||
text = f"📚 <b>{t(lang, 'story.vocabulary')}</b>\n\n"
|
||
|
||
keyboard_buttons = []
|
||
for i, word_data in enumerate(vocabulary[:10]):
|
||
word = word_data.get('word', '')
|
||
translation = word_data.get('translation', '')
|
||
transcription = word_data.get('transcription', '')
|
||
|
||
if transcription:
|
||
text += f"• <b>{word}</b> [{transcription}] — {translation}\n"
|
||
else:
|
||
text += f"• <b>{word}</b> — {translation}\n"
|
||
|
||
# Кнопка добавления слова
|
||
keyboard_buttons.append([
|
||
InlineKeyboardButton(
|
||
text=f"➕ {word}",
|
||
callback_data=f"story_addword_{story_id}_{i}"
|
||
)
|
||
])
|
||
|
||
# Кнопка "Добавить все"
|
||
keyboard_buttons.append([
|
||
InlineKeyboardButton(
|
||
text=f"➕ {t(lang, 'story.add_all')}",
|
||
callback_data=f"story_addall_{story_id}"
|
||
)
|
||
])
|
||
|
||
# Кнопка назад
|
||
keyboard_buttons.append([
|
||
InlineKeyboardButton(
|
||
text=f"⬅️ {t(lang, 'story.back')}",
|
||
callback_data=f"story_back_{story_id}"
|
||
)
|
||
])
|
||
|
||
await callback.message.edit_text(
|
||
text,
|
||
reply_markup=InlineKeyboardMarkup(inline_keyboard=keyboard_buttons)
|
||
)
|
||
|
||
|
||
@router.callback_query(F.data.startswith("story_addword_"))
|
||
async def story_addword_callback(callback: CallbackQuery):
|
||
"""Добавить одно слово из истории"""
|
||
parts = callback.data.split("_")
|
||
story_id = int(parts[2])
|
||
word_idx = int(parts[3])
|
||
|
||
async with async_session_maker() as session:
|
||
user = await UserService.get_user_by_telegram_id(session, callback.from_user.id)
|
||
lang = get_user_lang(user)
|
||
|
||
story = await session.get(MiniStory, story_id)
|
||
if not story or not story.vocabulary:
|
||
await callback.answer(t(lang, 'story.not_found'), show_alert=True)
|
||
return
|
||
|
||
if word_idx >= len(story.vocabulary):
|
||
await callback.answer(t(lang, 'story.word_not_found'), show_alert=True)
|
||
return
|
||
|
||
word_data = story.vocabulary[word_idx]
|
||
word = word_data.get('word', '')
|
||
translation = word_data.get('translation', '')
|
||
transcription = word_data.get('transcription')
|
||
|
||
# Проверяем, нет ли уже
|
||
existing = await VocabularyService.get_word_by_original(
|
||
session, user.id, word, source_lang=story.learning_lang
|
||
)
|
||
|
||
if existing:
|
||
await callback.answer(t(lang, 'words.already_exists', word=word), show_alert=True)
|
||
return
|
||
|
||
# Добавляем слово
|
||
translation_lang = get_user_translation_lang(user)
|
||
await VocabularyService.add_word(
|
||
session=session,
|
||
user_id=user.id,
|
||
word_original=word,
|
||
word_translation=translation,
|
||
source_lang=story.learning_lang,
|
||
translation_lang=translation_lang,
|
||
transcription=transcription,
|
||
difficulty_level=story.level,
|
||
source=WordSource.IMPORT
|
||
)
|
||
await session.commit()
|
||
|
||
await callback.answer(t(lang, 'story.word_added', word=word), show_alert=True)
|
||
|
||
|
||
@router.callback_query(F.data.startswith("story_addall_"))
|
||
async def story_addall_callback(callback: CallbackQuery):
|
||
"""Добавить все слова из истории"""
|
||
story_id = int(callback.data.replace("story_addall_", ""))
|
||
|
||
async with async_session_maker() as session:
|
||
user = await UserService.get_user_by_telegram_id(session, callback.from_user.id)
|
||
lang = get_user_lang(user)
|
||
|
||
story = await session.get(MiniStory, story_id)
|
||
if not story or not story.vocabulary:
|
||
await callback.answer(t(lang, 'story.not_found'), show_alert=True)
|
||
return
|
||
|
||
translation_lang = get_user_translation_lang(user)
|
||
added = 0
|
||
|
||
for word_data in story.vocabulary:
|
||
word = word_data.get('word', '')
|
||
translation = word_data.get('translation', '')
|
||
transcription = word_data.get('transcription')
|
||
|
||
# Проверяем дубликаты
|
||
existing = await VocabularyService.get_word_by_original(
|
||
session, user.id, word, source_lang=story.learning_lang
|
||
)
|
||
|
||
if not existing:
|
||
await VocabularyService.add_word(
|
||
session=session,
|
||
user_id=user.id,
|
||
word_original=word,
|
||
word_translation=translation,
|
||
source_lang=story.learning_lang,
|
||
translation_lang=translation_lang,
|
||
transcription=transcription,
|
||
difficulty_level=story.level,
|
||
source=WordSource.IMPORT
|
||
)
|
||
added += 1
|
||
|
||
await session.commit()
|
||
await callback.answer(t(lang, 'story.words_added', n=added), show_alert=True)
|
||
|
||
|
||
@router.callback_query(F.data.startswith("story_back_"))
|
||
async def story_back_callback(callback: CallbackQuery):
|
||
"""Вернуться к истории"""
|
||
await callback.answer()
|
||
|
||
story_id = int(callback.data.replace("story_back_", ""))
|
||
|
||
async with async_session_maker() as session:
|
||
user = await UserService.get_user_by_telegram_id(session, callback.from_user.id)
|
||
lang = get_user_lang(user)
|
||
|
||
story = await session.get(MiniStory, story_id)
|
||
if not story:
|
||
await callback.message.edit_text(t(lang, 'story.not_found'))
|
||
return
|
||
|
||
text = format_story_text(story, lang)
|
||
await callback.message.edit_text(
|
||
text,
|
||
reply_markup=get_story_keyboard(story.id, lang)
|
||
)
|
||
|
||
|
||
@router.callback_query(F.data.startswith("story_questions_"))
|
||
async def story_questions_callback(callback: CallbackQuery, state: FSMContext):
|
||
"""Показать вопросы по истории"""
|
||
await callback.answer()
|
||
|
||
story_id = int(callback.data.replace("story_questions_", ""))
|
||
|
||
async with async_session_maker() as session:
|
||
user = await UserService.get_user_by_telegram_id(session, callback.from_user.id)
|
||
lang = get_user_lang(user)
|
||
|
||
story = await session.get(MiniStory, story_id)
|
||
if not story:
|
||
await callback.message.edit_text(t(lang, 'story.not_found'))
|
||
return
|
||
|
||
questions = story.questions or []
|
||
if not questions:
|
||
await callback.answer(t(lang, 'story.no_questions'), show_alert=True)
|
||
return
|
||
|
||
# Сохраняем состояние
|
||
await state.update_data(
|
||
story_id=story_id,
|
||
current_question=0,
|
||
correct_answers=0,
|
||
total_questions=len(questions)
|
||
)
|
||
await state.set_state(StoryStates.questions)
|
||
|
||
# Показываем первый вопрос
|
||
await show_question(callback.message, story, 0, lang, edit=True)
|
||
|
||
|
||
async def show_question(message: Message, story: MiniStory, q_idx: int, lang: str, edit: bool = False):
|
||
"""Показать вопрос"""
|
||
questions = story.questions or []
|
||
|
||
if q_idx >= len(questions):
|
||
return
|
||
|
||
q = questions[q_idx]
|
||
total = len(questions)
|
||
|
||
text = f"📝 <b>{t(lang, 'story.question')} {q_idx + 1}/{total}</b>\n\n"
|
||
text += f"{q.get('question', '')}\n"
|
||
|
||
# Кнопки с вариантами ответов
|
||
options = q.get('options', [])
|
||
keyboard_buttons = []
|
||
|
||
for i, option in enumerate(options):
|
||
keyboard_buttons.append([
|
||
InlineKeyboardButton(
|
||
text=option,
|
||
callback_data=f"story_answer_{story.id}_{q_idx}_{i}"
|
||
)
|
||
])
|
||
|
||
keyboard = InlineKeyboardMarkup(inline_keyboard=keyboard_buttons)
|
||
|
||
if edit:
|
||
await message.edit_text(text, reply_markup=keyboard)
|
||
else:
|
||
await message.answer(text, reply_markup=keyboard)
|
||
|
||
|
||
@router.callback_query(F.data.startswith("story_answer_"))
|
||
async def story_answer_callback(callback: CallbackQuery, state: FSMContext):
|
||
"""Обработка ответа на вопрос"""
|
||
parts = callback.data.split("_")
|
||
story_id = int(parts[2])
|
||
q_idx = int(parts[3])
|
||
answer_idx = int(parts[4])
|
||
|
||
async with async_session_maker() as session:
|
||
user = await UserService.get_user_by_telegram_id(session, callback.from_user.id)
|
||
lang = get_user_lang(user)
|
||
|
||
story = await session.get(MiniStory, story_id)
|
||
if not story:
|
||
await callback.answer(t(lang, 'story.not_found'), show_alert=True)
|
||
return
|
||
|
||
questions = story.questions or []
|
||
if q_idx >= len(questions):
|
||
await callback.answer(t(lang, 'story.question_not_found'), show_alert=True)
|
||
return
|
||
|
||
q = questions[q_idx]
|
||
correct = q.get('correct', 0)
|
||
options = q.get('options', [])
|
||
|
||
# Получаем данные состояния
|
||
data = await state.get_data()
|
||
correct_answers = data.get('correct_answers', 0)
|
||
total_questions = data.get('total_questions', len(questions))
|
||
|
||
# Проверяем ответ
|
||
is_correct = (answer_idx == correct)
|
||
if is_correct:
|
||
correct_answers += 1
|
||
|
||
# Показываем результат ответа
|
||
text = f"📝 <b>{t(lang, 'story.question')} {q_idx + 1}/{total_questions}</b>\n\n"
|
||
text += f"{q.get('question', '')}\n\n"
|
||
|
||
for i, option in enumerate(options):
|
||
if i == correct:
|
||
text += f"✅ {option}\n"
|
||
elif i == answer_idx and not is_correct:
|
||
text += f"❌ {option}\n"
|
||
else:
|
||
text += f"○ {option}\n"
|
||
|
||
if is_correct:
|
||
text += f"\n{t(lang, 'story.correct')}"
|
||
await callback.answer("✅", show_alert=False)
|
||
else:
|
||
text += f"\n{t(lang, 'story.incorrect')}"
|
||
await callback.answer("❌", show_alert=False)
|
||
|
||
# Обновляем состояние
|
||
await state.update_data(correct_answers=correct_answers)
|
||
|
||
# Следующий вопрос или результаты
|
||
next_q_idx = q_idx + 1
|
||
|
||
if next_q_idx < total_questions:
|
||
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
||
[InlineKeyboardButton(
|
||
text=f"➡️ {t(lang, 'story.next_question')}",
|
||
callback_data=f"story_nextq_{story_id}_{next_q_idx}"
|
||
)]
|
||
])
|
||
else:
|
||
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
||
[InlineKeyboardButton(
|
||
text=f"📊 {t(lang, 'story.show_results')}",
|
||
callback_data=f"story_results_{story_id}_{correct_answers}"
|
||
)]
|
||
])
|
||
|
||
await callback.message.edit_text(text, reply_markup=keyboard)
|
||
|
||
|
||
@router.callback_query(F.data.startswith("story_nextq_"))
|
||
async def story_nextq_callback(callback: CallbackQuery, state: FSMContext):
|
||
"""Следующий вопрос"""
|
||
await callback.answer()
|
||
|
||
parts = callback.data.split("_")
|
||
story_id = int(parts[2])
|
||
q_idx = int(parts[3])
|
||
|
||
async with async_session_maker() as session:
|
||
user = await UserService.get_user_by_telegram_id(session, callback.from_user.id)
|
||
lang = get_user_lang(user)
|
||
|
||
story = await session.get(MiniStory, story_id)
|
||
if not story:
|
||
await callback.message.edit_text(t(lang, 'story.not_found'))
|
||
return
|
||
|
||
await show_question(callback.message, story, q_idx, lang, edit=True)
|
||
|
||
|
||
@router.callback_query(F.data.startswith("story_results_"))
|
||
async def story_results_callback(callback: CallbackQuery, state: FSMContext):
|
||
"""Показать результаты"""
|
||
await callback.answer()
|
||
await state.clear()
|
||
|
||
parts = callback.data.split("_")
|
||
story_id = int(parts[2])
|
||
correct_answers = int(parts[3])
|
||
|
||
async with async_session_maker() as session:
|
||
user = await UserService.get_user_by_telegram_id(session, callback.from_user.id)
|
||
lang = get_user_lang(user)
|
||
|
||
story = await session.get(MiniStory, story_id)
|
||
if not story:
|
||
await callback.message.edit_text(t(lang, 'story.not_found'))
|
||
return
|
||
|
||
total = len(story.questions or [])
|
||
|
||
# Обновляем статус истории
|
||
story.is_completed = True
|
||
story.correct_answers = correct_answers
|
||
await session.commit()
|
||
|
||
# Определяем эмодзи по результату
|
||
percentage = (correct_answers / total * 100) if total > 0 else 0
|
||
if percentage >= 80:
|
||
emoji = "🎉"
|
||
comment = t(lang, 'story.result_excellent')
|
||
elif percentage >= 50:
|
||
emoji = "👍"
|
||
comment = t(lang, 'story.result_good')
|
||
else:
|
||
emoji = "📚"
|
||
comment = t(lang, 'story.result_practice')
|
||
|
||
text = f"{emoji} <b>{t(lang, 'story.results_title')}</b>\n\n"
|
||
text += f"📖 {story.title}\n\n"
|
||
text += f"{t(lang, 'story.correct_answers')}: <b>{correct_answers}/{total}</b>\n"
|
||
text += f"{t(lang, 'story.accuracy')}: <b>{percentage:.0f}%</b>\n\n"
|
||
text += f"<i>{comment}</i>"
|
||
|
||
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
||
[
|
||
InlineKeyboardButton(
|
||
text=f"📚 {t(lang, 'story.vocab_btn')}",
|
||
callback_data=f"story_vocab_{story_id}"
|
||
),
|
||
],
|
||
[
|
||
InlineKeyboardButton(
|
||
text=f"🔄 {t(lang, 'story.new_btn')}",
|
||
callback_data="story_new"
|
||
),
|
||
],
|
||
])
|
||
|
||
await callback.message.edit_text(text, reply_markup=keyboard)
|