devops
Some checks reported errors
continuous-integration/drone/push Build encountered an error

This commit is contained in:
2025-09-11 08:02:35 +09:00
parent 398729a4a0
commit fcf27c1639
13 changed files with 585 additions and 297 deletions

View File

@@ -12,10 +12,7 @@ trigger:
- push - push
- pull_request - pull_request
# Глобальные переменные # Примечание: Глобальные переменные определяются в шагах
environment:
IMAGE_NAME: quiz-bot
REGISTRY: localhost:5000 # Локальный registry или замените на ваш
steps: steps:
# 1. Клонирование и подготовка # 1. Клонирование и подготовка

View File

@@ -18,10 +18,10 @@ RUN pip install --no-cache-dir --upgrade pip && \
# Production stage # Production stage
FROM python:3.12-slim FROM python:3.12-slim
# Создаем пользователя для безопасности # Создание пользователя и группы для безопасности
RUN groupadd -r quizbot && useradd -r -g quizbot quizbot RUN groupadd -r quizbot && useradd -r -g quizbot quizbot
# Устанавливаем системные зависимости # Установка sqlite3 для работы с базой данных
RUN apt-get update && apt-get install -y \ RUN apt-get update && apt-get install -y \
sqlite3 \ sqlite3 \
&& rm -rf /var/lib/apt/lists/* \ && rm -rf /var/lib/apt/lists/* \
@@ -34,9 +34,10 @@ ENV PATH="/opt/venv/bin:$PATH"
# Создаем рабочую директорию # Создаем рабочую директорию
WORKDIR /app WORKDIR /app
# Создаем необходимые директории # Создание директорий с правильными правами доступа
RUN mkdir -p /app/data /app/logs && \ RUN mkdir -p /app/data /app/logs && \
chown -R quizbot:quizbot /app chown -R quizbot:quizbot /app && \
chmod -R 775 /app/data /app/logs
# Копируем код приложения # Копируем код приложения
COPY --chown=quizbot:quizbot . . COPY --chown=quizbot:quizbot . .

View File

@@ -1,16 +1,19 @@
import os import os
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import List from typing import List
from dotenv import load_dotenv from dotenv import load_dotenv
load_dotenv() load_dotenv()
def get_admin_ids() -> List[int]: def get_admin_ids() -> List[int]:
admin_str = os.getenv("ADMIN_IDS", "") admin_str = os.getenv("ADMIN_IDS", "")
if admin_str: if admin_str:
return [int(x) for x in admin_str.split(",") if x.strip()] return [int(x) for x in admin_str.split(",") if x.strip()]
return [] return []
@dataclass @dataclass
class Config: class Config:
bot_token: str = os.getenv("BOT_TOKEN", "") bot_token: str = os.getenv("BOT_TOKEN", "")
@@ -26,4 +29,5 @@ class Config:
guest_mode_enabled: bool = os.getenv("GUEST_MODE_ENABLED", "true").lower() == "true" guest_mode_enabled: bool = os.getenv("GUEST_MODE_ENABLED", "true").lower() == "true"
test_mode_enabled: bool = os.getenv("TEST_MODE_ENABLED", "true").lower() == "true" test_mode_enabled: bool = os.getenv("TEST_MODE_ENABLED", "true").lower() == "true"
config = Config() config = Config()

0
data/korean_level_1.csv Normal file → Executable file
View File

0
data/korean_level_2.csv Normal file → Executable file
View File

0
data/korean_level_3.csv Normal file → Executable file
View File

0
data/korean_level_4.csv Normal file → Executable file
View File

0
data/korean_level_5.csv Normal file → Executable file
View File

BIN
data/quiz_bot.db Normal file → Executable file

Binary file not shown.

View File

@@ -7,15 +7,15 @@ services:
dockerfile: Dockerfile dockerfile: Dockerfile
container_name: quiz-bot container_name: quiz-bot
restart: unless-stopped restart: unless-stopped
user: "0:0"
environment: environment:
- BOT_TOKEN=${BOT_TOKEN} - BOT_TOKEN=${BOT_TOKEN}
- DATABASE_PATH=data/quiz_bot.db - DATABASE_PATH=data/quiz_bot.db
- CSV_DATA_PATH=data/ - CSV_DATA_PATH=data/
- LOG_LEVEL=INFO - LOG_LEVEL=INFO
volumes: volumes:
# Персистентное хранение данных - "./data:/app/data"
- ./data:/app/data - "./logs:/app/logs"
- ./logs:/app/logs
networks: networks:
- quiz-bot-network - quiz-bot-network
healthcheck: healthcheck:
@@ -24,7 +24,12 @@ services:
timeout: 10s timeout: 10s
retries: 3 retries: 3
start_period: 60s start_period: 60s
# Ограничения ресурсов command: >
sh -c "
chown -R quizbot:quizbot /app/data /app/logs &&
chmod -R 775 /app/data /app/logs &&
python -m src.bot
"
deploy: deploy:
resources: resources:
limits: limits:

View File

@@ -5,3 +5,14 @@ pandas==2.1.4
python-dotenv==1.0.0 python-dotenv==1.0.0
asyncio-mqtt==0.16.1 asyncio-mqtt==0.16.1
loguru==0.7.2 loguru==0.7.2
pytest==7.4.0
pytest-asyncio==0.22.0
black
isort
flake8
mypy
pytest
pytest-asyncio
pytest-cov
safety
bandit

View File

@@ -5,16 +5,18 @@
import asyncio import asyncio
import logging import logging
import os
import random import random
import sys
from aiogram import Bot, Dispatcher, F from aiogram import Bot, Dispatcher, F
from aiogram.filters import Command, StateFilter from aiogram.filters import Command, StateFilter
from aiogram.fsm.context import FSMContext from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup from aiogram.fsm.state import State, StatesGroup
from aiogram.fsm.storage.memory import MemoryStorage from aiogram.fsm.storage.memory import MemoryStorage
from aiogram.types import Message, CallbackQuery, InlineKeyboardMarkup, InlineKeyboardButton, InaccessibleMessage from aiogram.types import (CallbackQuery, InaccessibleMessage,
InlineKeyboardButton, InlineKeyboardMarkup, Message)
from aiogram.utils.keyboard import InlineKeyboardBuilder from aiogram.utils.keyboard import InlineKeyboardBuilder
import sys
import os
# Добавляем путь к проекту # Добавляем путь к проекту
project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
@@ -27,12 +29,14 @@ from src.services.csv_service import CSVQuizLoader, QuizGenerator
# Настройка логирования # Настройка логирования
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
class QuizStates(StatesGroup): class QuizStates(StatesGroup):
choosing_mode = State() choosing_mode = State()
choosing_category = State() choosing_category = State()
choosing_level = State() choosing_level = State()
in_quiz = State() in_quiz = State()
class QuizBot: class QuizBot:
def __init__(self): def __init__(self):
self.bot = Bot(token=config.bot_token) self.bot = Bot(token=config.bot_token)
@@ -71,16 +75,26 @@ class QuizBot:
username=user.username, username=user.username,
first_name=user.first_name, first_name=user.first_name,
last_name=user.last_name, last_name=user.last_name,
language_code=user.language_code or 'ru' language_code=user.language_code or "ru",
) )
await state.set_state(QuizStates.choosing_mode) await state.set_state(QuizStates.choosing_mode)
keyboard = InlineKeyboardMarkup(inline_keyboard=[ keyboard = InlineKeyboardMarkup(
[InlineKeyboardButton(text="🎯 Гостевой режим (QUIZ)", callback_data="guest_mode")], inline_keyboard=[
[InlineKeyboardButton(text="📚 Тестирование по материалам", callback_data="test_mode")], [
[InlineKeyboardButton(text="📊 Моя статистика", callback_data="stats")], InlineKeyboardButton(
]) text="🎯 Гостевой режим (QUIZ)", callback_data="guest_mode"
)
],
[
InlineKeyboardButton(
text="📚 Тестирование по материалам", callback_data="test_mode"
)
],
[InlineKeyboardButton(text="📊 Моя статистика", callback_data="stats")],
]
)
await message.answer( await message.answer(
f"👋 Добро пожаловать в Quiz Bot, {user.first_name}!\n\n" f"👋 Добро пожаловать в Quiz Bot, {user.first_name}!\n\n"
@@ -88,7 +102,7 @@ class QuizBot:
"📚 <b>Тестирование</b> - серьезное изучение материалов с результатами\n\n" "📚 <b>Тестирование</b> - серьезное изучение материалов с результатами\n\n"
"Выберите режим работы:", "Выберите режим работы:",
reply_markup=keyboard, reply_markup=keyboard,
parse_mode='HTML' parse_mode="HTML",
) )
async def help_command(self, message: Message): async def help_command(self, message: Message):
@@ -115,17 +129,21 @@ class QuizBot:
📊 <b>Доступные категории:</b> 📊 <b>Доступные категории:</b>
• Корейский язык (уровни 1-5) • Корейский язык (уровни 1-5)
• Более 120 уникальных вопросов""" • Более 120 уникальных вопросов"""
await message.answer(help_text, parse_mode='HTML') await message.answer(help_text, parse_mode="HTML")
async def stats_command(self, message: Message): async def stats_command(self, message: Message):
"""Обработка команды /stats""" """Обработка команды /stats"""
user_stats = await self.db.get_user_stats(message.from_user.id) user_stats = await self.db.get_user_stats(message.from_user.id)
if not user_stats or user_stats['total_questions'] == 0: if not user_stats or user_stats["total_questions"] == 0:
await message.answer("📊 У вас пока нет статистики. Пройдите первый тест!") await message.answer("📊 У вас пока нет статистики. Пройдите первый тест!")
return return
accuracy = (user_stats['correct_answers'] / user_stats['total_questions']) * 100 if user_stats['total_questions'] > 0 else 0 accuracy = (
(user_stats["correct_answers"] / user_stats["total_questions"]) * 100
if user_stats["total_questions"] > 0
else 0
)
stats_text = f"""📊 <b>Ваша статистика:</b> stats_text = f"""📊 <b>Ваша статистика:</b>
@@ -136,11 +154,17 @@ class QuizBot:
🏆 Лучший результат: {user_stats['best_score'] or 0:.1f}% 🏆 Лучший результат: {user_stats['best_score'] or 0:.1f}%
📊 Средний балл: {user_stats['average_score'] or 0:.1f}%""" 📊 Средний балл: {user_stats['average_score'] or 0:.1f}%"""
keyboard = InlineKeyboardMarkup(inline_keyboard=[ keyboard = InlineKeyboardMarkup(
[InlineKeyboardButton(text="🏠 Главное меню", callback_data="back_to_menu")] inline_keyboard=[
]) [
InlineKeyboardButton(
text="🏠 Главное меню", callback_data="back_to_menu"
)
]
]
)
await message.answer(stats_text, reply_markup=keyboard, parse_mode='HTML') await message.answer(stats_text, reply_markup=keyboard, parse_mode="HTML")
async def stop_command(self, message: Message): async def stop_command(self, message: Message):
"""Остановка текущего теста""" """Остановка текущего теста"""
@@ -151,42 +175,60 @@ class QuizBot:
else: else:
await message.answer("У вас нет активного теста.") await message.answer("У вас нет активного теста.")
keyboard = InlineKeyboardMarkup(inline_keyboard=[ keyboard = InlineKeyboardMarkup(
[InlineKeyboardButton(text="🏠 Главное меню", callback_data="back_to_menu")] inline_keyboard=[
]) [
InlineKeyboardButton(
text="🏠 Главное меню", callback_data="back_to_menu"
)
]
]
)
await message.answer("Что бы вы хотели сделать дальше?", reply_markup=keyboard) await message.answer("Что бы вы хотели сделать дальше?", reply_markup=keyboard)
async def guest_mode_handler(self, callback: CallbackQuery, state: FSMContext): async def guest_mode_handler(self, callback: CallbackQuery, state: FSMContext):
"""Обработка выбора гостевого режима""" """Обработка выбора гостевого режима"""
await state.update_data(mode='guest') await state.update_data(mode="guest")
await state.set_state(QuizStates.choosing_category) await state.set_state(QuizStates.choosing_category)
keyboard = InlineKeyboardMarkup(inline_keyboard=[ keyboard = InlineKeyboardMarkup(
[InlineKeyboardButton(text="🇰🇷 Корейский язык", callback_data="category_korean")], inline_keyboard=[
[InlineKeyboardButton(text="🔙 Назад", callback_data="back_to_menu")] [
]) InlineKeyboardButton(
text="🇰🇷 Корейский язык", callback_data="category_korean"
)
],
[InlineKeyboardButton(text="🔙 Назад", callback_data="back_to_menu")],
]
)
await callback.message.edit_text( await callback.message.edit_text(
"🎯 <b>Гостевой режим</b>\n\nВыберите категорию для викторины:", "🎯 <b>Гостевой режим</b>\n\nВыберите категорию для викторины:",
reply_markup=keyboard, reply_markup=keyboard,
parse_mode='HTML' parse_mode="HTML",
) )
await callback.answer() await callback.answer()
async def test_mode_handler(self, callback: CallbackQuery, state: FSMContext): async def test_mode_handler(self, callback: CallbackQuery, state: FSMContext):
"""Обработка выбора режима тестирования""" """Обработка выбора режима тестирования"""
await state.update_data(mode='test') await state.update_data(mode="test")
await state.set_state(QuizStates.choosing_category) await state.set_state(QuizStates.choosing_category)
keyboard = InlineKeyboardMarkup(inline_keyboard=[ keyboard = InlineKeyboardMarkup(
[InlineKeyboardButton(text="🇰🇷 Корейский язык", callback_data="category_korean")], inline_keyboard=[
[InlineKeyboardButton(text="🔙 Назад", callback_data="back_to_menu")] [
]) InlineKeyboardButton(
text="🇰🇷 Корейский язык", callback_data="category_korean"
)
],
[InlineKeyboardButton(text="🔙 Назад", callback_data="back_to_menu")],
]
)
await callback.message.edit_text( await callback.message.edit_text(
"📚 <b>Режим тестирования</b>\n\nВыберите категорию для серьезного изучения:", "📚 <b>Режим тестирования</b>\n\nВыберите категорию для серьезного изучения:",
reply_markup=keyboard, reply_markup=keyboard,
parse_mode='HTML' parse_mode="HTML",
) )
await callback.answer() await callback.answer()
@@ -195,19 +237,41 @@ class QuizBot:
category = callback.data.split("_")[1] category = callback.data.split("_")[1]
await state.update_data(category=category) await state.update_data(category=category)
keyboard = InlineKeyboardMarkup(inline_keyboard=[ keyboard = InlineKeyboardMarkup(
[InlineKeyboardButton(text="🥉 Уровень 1 (начальный)", callback_data="level_1")], inline_keyboard=[
[InlineKeyboardButton(text="🥈 Уровень 2 (базовый)", callback_data="level_2")], [
[InlineKeyboardButton(text="🥇 Уровень 3 (средний)", callback_data="level_3")], InlineKeyboardButton(
[InlineKeyboardButton(text="🏆 Уровень 4 (продвинутый)", callback_data="level_4")], text="🥉 Уровень 1 (начальный)", callback_data="level_1"
[InlineKeyboardButton(text="💎 Уровень 5 (эксперт)", callback_data="level_5")], )
[InlineKeyboardButton(text="🔙 Назад", callback_data="back_to_menu")] ],
]) [
InlineKeyboardButton(
text="🥈 Уровень 2 (базовый)", callback_data="level_2"
)
],
[
InlineKeyboardButton(
text="🥇 Уровень 3 (средний)", callback_data="level_3"
)
],
[
InlineKeyboardButton(
text="🏆 Уровень 4 (продвинутый)", callback_data="level_4"
)
],
[
InlineKeyboardButton(
text="💎 Уровень 5 (эксперт)", callback_data="level_5"
)
],
[InlineKeyboardButton(text="🔙 Назад", callback_data="back_to_menu")],
]
)
await callback.message.edit_text( await callback.message.edit_text(
f"🇰🇷 <b>Корейский язык</b>\n\nВыберите уровень сложности:", f"🇰🇷 <b>Корейский язык</b>\n\nВыберите уровень сложности:",
reply_markup=keyboard, reply_markup=keyboard,
parse_mode='HTML' parse_mode="HTML",
) )
await callback.answer() await callback.answer()
@@ -223,26 +287,34 @@ class QuizBot:
if not questions: if not questions:
await callback.message.edit_text( await callback.message.edit_text(
"❌ Вопросы для этого уровня пока недоступны.", "❌ Вопросы для этого уровня пока недоступны.",
reply_markup=InlineKeyboardMarkup(inline_keyboard=[ reply_markup=InlineKeyboardMarkup(
[InlineKeyboardButton(text="🔙 Назад", callback_data="back_to_menu")] inline_keyboard=[
]) [
InlineKeyboardButton(
text="🔙 Назад", callback_data="back_to_menu"
)
]
]
),
) )
await callback.answer() await callback.answer()
return return
# Определяем количество вопросов # Определяем количество вопросов
questions_count = 5 if data['mode'] == 'guest' else 10 questions_count = 5 if data["mode"] == "guest" else 10
# Берем случайные вопросы # Берем случайные вопросы
selected_questions = random.sample(questions, min(questions_count, len(questions))) selected_questions = random.sample(
questions, min(questions_count, len(questions))
)
# Создаем тестовую запись в БД # Создаем тестовую запись в БД
test_id = await self.db.add_test( test_id = await self.db.add_test(
name=f"{data['category'].title()} Level {level}", name=f"{data['category'].title()} Level {level}",
description=f"Тест по {data['category']} языку, уровень {level}", description=f"Тест по {data['category']} языку, уровень {level}",
level=level, level=level,
category=data['category'], category=data["category"],
csv_file=filename csv_file=filename,
) )
# Начинаем сессию # Начинаем сессию
@@ -250,7 +322,7 @@ class QuizBot:
user_id=callback.from_user.id, user_id=callback.from_user.id,
test_id=test_id or 1, test_id=test_id or 1,
questions=selected_questions, questions=selected_questions,
mode=data['mode'] mode=data["mode"],
) )
await state.set_state(QuizStates.in_quiz) await state.set_state(QuizStates.in_quiz)
@@ -260,13 +332,13 @@ class QuizBot:
def shuffle_answers(self, question_data: dict) -> dict: def shuffle_answers(self, question_data: dict) -> dict:
"""Перемешивает варианты ответов и обновляет правильный ответ""" """Перемешивает варианты ответов и обновляет правильный ответ"""
options = [ options = [
question_data['option1'], question_data["option1"],
question_data['option2'], question_data["option2"],
question_data['option3'], question_data["option3"],
question_data['option4'] question_data["option4"],
] ]
correct_answer_text = options[question_data['correct_answer'] - 1] correct_answer_text = options[question_data["correct_answer"] - 1]
# Перемешиваем варианты # Перемешиваем варианты
random.shuffle(options) random.shuffle(options)
@@ -276,37 +348,40 @@ class QuizBot:
# Обновляем данные вопроса # Обновляем данные вопроса
shuffled_question = question_data.copy() shuffled_question = question_data.copy()
shuffled_question['option1'] = options[0] shuffled_question["option1"] = options[0]
shuffled_question['option2'] = options[1] shuffled_question["option2"] = options[1]
shuffled_question['option3'] = options[2] shuffled_question["option3"] = options[2]
shuffled_question['option4'] = options[3] shuffled_question["option4"] = options[3]
shuffled_question['correct_answer'] = new_correct_position shuffled_question["correct_answer"] = new_correct_position
return shuffled_question return shuffled_question
async def show_question_safe(self, callback: CallbackQuery, user_id: int, question_index: int): async def show_question_safe(
self, callback: CallbackQuery, user_id: int, question_index: int
):
"""Безопасный показ вопроса через callback""" """Безопасный показ вопроса через callback"""
session = await self.db.get_active_session(user_id) session = await self.db.get_active_session(user_id)
if not session or question_index >= len(session['questions_data']): if not session or question_index >= len(session["questions_data"]):
return return
question = session['questions_data'][question_index] question = session["questions_data"][question_index]
# Перемешиваем варианты ответов только в тестовом режиме # Перемешиваем варианты ответов только в тестовом режиме
if session['mode'] == 'test': if session["mode"] == "test":
question = self.shuffle_answers(question) question = self.shuffle_answers(question)
session['questions_data'][question_index] = question session["questions_data"][question_index] = question
await self.db.update_session_questions(user_id, session['questions_data']) await self.db.update_session_questions(user_id, session["questions_data"])
total_questions = len(session['questions_data']) total_questions = len(session["questions_data"])
# Создаем клавиатуру с ответами # Создаем клавиатуру с ответами
keyboard_builder = InlineKeyboardBuilder() keyboard_builder = InlineKeyboardBuilder()
for i in range(1, 5): for i in range(1, 5):
keyboard_builder.add(InlineKeyboardButton( keyboard_builder.add(
text=f"{i}. {question[f'option{i}']}", InlineKeyboardButton(
callback_data=f"answer_{i}" text=f"{i}. {question[f'option{i}']}", callback_data=f"answer_{i}"
)) )
)
keyboard_builder.adjust(1) keyboard_builder.adjust(1)
@@ -318,11 +393,25 @@ class QuizBot:
# Безопасная отправка сообщения # Безопасная отправка сообщения
if callback.message and not isinstance(callback.message, InaccessibleMessage): if callback.message and not isinstance(callback.message, InaccessibleMessage):
try: try:
await callback.message.edit_text(question_text, reply_markup=keyboard_builder.as_markup(), parse_mode='HTML') await callback.message.edit_text(
question_text,
reply_markup=keyboard_builder.as_markup(),
parse_mode="HTML",
)
except Exception: except Exception:
await self.bot.send_message(callback.from_user.id, question_text, reply_markup=keyboard_builder.as_markup(), parse_mode='HTML') await self.bot.send_message(
callback.from_user.id,
question_text,
reply_markup=keyboard_builder.as_markup(),
parse_mode="HTML",
)
else: else:
await self.bot.send_message(callback.from_user.id, question_text, reply_markup=keyboard_builder.as_markup(), parse_mode='HTML') await self.bot.send_message(
callback.from_user.id,
question_text,
reply_markup=keyboard_builder.as_markup(),
parse_mode="HTML",
)
async def answer_handler(self, callback: CallbackQuery, state: FSMContext): async def answer_handler(self, callback: CallbackQuery, state: FSMContext):
"""Обработка ответа на вопрос""" """Обработка ответа на вопрос"""
@@ -334,33 +423,43 @@ class QuizBot:
await callback.answer("❌ Сессия не найдена") await callback.answer("❌ Сессия не найдена")
return return
current_q_index = session['current_question'] current_q_index = session["current_question"]
question = session['questions_data'][current_q_index] question = session["questions_data"][current_q_index]
is_correct = answer == question['correct_answer'] is_correct = answer == question["correct_answer"]
mode = session['mode'] mode = session["mode"]
# Обновляем счетчик правильных ответов # Обновляем счетчик правильных ответов
if is_correct: if is_correct:
session['correct_count'] += 1 session["correct_count"] += 1
# Обновляем прогресс в базе # Обновляем прогресс в базе
await self.db.update_session_progress( await self.db.update_session_progress(
user_id, current_q_index + 1, session['correct_count'] user_id, current_q_index + 1, session["correct_count"]
) )
# Проверяем, есть ли еще вопросы # Проверяем, есть ли еще вопросы
if current_q_index + 1 >= len(session['questions_data']): if current_q_index + 1 >= len(session["questions_data"]):
# Тест завершен # Тест завершен
score = (session['correct_count'] / len(session['questions_data'])) * 100 score = (session["correct_count"] / len(session["questions_data"])) * 100
await self.db.finish_session(user_id, score) await self.db.finish_session(user_id, score)
keyboard = InlineKeyboardMarkup(inline_keyboard=[ keyboard = InlineKeyboardMarkup(
[InlineKeyboardButton(text="🏠 Главное меню", callback_data="back_to_menu")], inline_keyboard=[
[InlineKeyboardButton(text="📊 Моя статистика", callback_data="stats")] [
]) InlineKeyboardButton(
text="🏠 Главное меню", callback_data="back_to_menu"
)
],
[
InlineKeyboardButton(
text="📊 Моя статистика", callback_data="stats"
)
],
]
)
# Разный текст для разных режимов # Разный текст для разных режимов
if mode == 'test': if mode == "test":
final_text = ( final_text = (
f"🎉 <b>Тест завершен!</b>\n\n" f"🎉 <b>Тест завершен!</b>\n\n"
f"📊 Результат: {session['correct_count']}/{len(session['questions_data'])}\n" f"📊 Результат: {session['correct_count']}/{len(session['questions_data'])}\n"
@@ -369,7 +468,11 @@ class QuizBot:
f"💡 Результат сохранен в вашей статистике" f"💡 Результат сохранен в вашей статистике"
) )
else: else:
result_text = "✅ Правильно!" if is_correct else f"❌ Неправильно. Правильный ответ: {question['correct_answer']}" result_text = (
"✅ Правильно!"
if is_correct
else f"❌ Неправильно. Правильный ответ: {question['correct_answer']}"
)
final_text = ( final_text = (
f"{result_text}\n\n" f"{result_text}\n\n"
f"🎉 <b>Викторина завершена!</b>\n\n" f"🎉 <b>Викторина завершена!</b>\n\n"
@@ -379,34 +482,68 @@ class QuizBot:
) )
# Безопасная отправка сообщения # Безопасная отправка сообщения
if callback.message and not isinstance(callback.message, InaccessibleMessage): if callback.message and not isinstance(
callback.message, InaccessibleMessage
):
try: try:
await callback.message.edit_text(final_text, reply_markup=keyboard, parse_mode='HTML') await callback.message.edit_text(
final_text, reply_markup=keyboard, parse_mode="HTML"
)
except Exception: except Exception:
await self.bot.send_message(callback.from_user.id, final_text, reply_markup=keyboard, parse_mode='HTML') await self.bot.send_message(
callback.from_user.id,
final_text,
reply_markup=keyboard,
parse_mode="HTML",
)
else: else:
await self.bot.send_message(callback.from_user.id, final_text, reply_markup=keyboard, parse_mode='HTML') await self.bot.send_message(
callback.from_user.id,
final_text,
reply_markup=keyboard,
parse_mode="HTML",
)
else: else:
# Есть еще вопросы # Есть еще вопросы
if mode == 'test': if mode == "test":
# В тестовом режиме сразу переходим к следующему вопросу # В тестовом режиме сразу переходим к следующему вопросу
await self.show_question_safe(callback, callback.from_user.id, current_q_index + 1) await self.show_question_safe(
callback, callback.from_user.id, current_q_index + 1
)
else: else:
# В гостевом режиме показываем результат и кнопку "Следующий" # В гостевом режиме показываем результат и кнопку "Следующий"
result_text = "✅ Правильно!" if is_correct else f"❌ Неправильно. Правильный ответ: {question['correct_answer']}" result_text = (
"✅ Правильно!"
if is_correct
else f"❌ Неправильно. Правильный ответ: {question['correct_answer']}"
)
keyboard = InlineKeyboardMarkup(inline_keyboard=[ keyboard = InlineKeyboardMarkup(
[InlineKeyboardButton(text="➡️ Следующий вопрос", callback_data="next_question")] inline_keyboard=[
]) [
InlineKeyboardButton(
text="➡️ Следующий вопрос", callback_data="next_question"
)
]
]
)
# Безопасная отправка сообщения # Безопасная отправка сообщения
if callback.message and not isinstance(callback.message, InaccessibleMessage): if callback.message and not isinstance(
callback.message, InaccessibleMessage
):
try: try:
await callback.message.edit_text(result_text, reply_markup=keyboard) await callback.message.edit_text(
result_text, reply_markup=keyboard
)
except Exception: except Exception:
await self.bot.send_message(callback.from_user.id, result_text, reply_markup=keyboard) await self.bot.send_message(
callback.from_user.id, result_text, reply_markup=keyboard
)
else: else:
await self.bot.send_message(callback.from_user.id, result_text, reply_markup=keyboard) await self.bot.send_message(
callback.from_user.id, result_text, reply_markup=keyboard
)
await callback.answer() await callback.answer()
@@ -414,24 +551,30 @@ class QuizBot:
"""Переход к следующему вопросу""" """Переход к следующему вопросу"""
session = await self.db.get_active_session(callback.from_user.id) session = await self.db.get_active_session(callback.from_user.id)
if session: if session:
await self.show_question_safe(callback, callback.from_user.id, session['current_question']) await self.show_question_safe(
callback, callback.from_user.id, session["current_question"]
)
await callback.answer() await callback.answer()
async def stats_callback_handler(self, callback: CallbackQuery): async def stats_callback_handler(self, callback: CallbackQuery):
"""Обработчик кнопки статистики через callback""" """Обработчик кнопки статистики через callback"""
user_stats = await self.db.get_user_stats(callback.from_user.id) user_stats = await self.db.get_user_stats(callback.from_user.id)
if not user_stats or user_stats['total_questions'] == 0: if not user_stats or user_stats["total_questions"] == 0:
stats_text = "📊 У вас пока нет статистики. Пройдите первый тест!" stats_text = "📊 У вас пока нет статистики. Пройдите первый тест!"
else: else:
accuracy = (user_stats['correct_answers'] / user_stats['total_questions']) * 100 if user_stats['total_questions'] > 0 else 0 accuracy = (
(user_stats["correct_answers"] / user_stats["total_questions"]) * 100
if user_stats["total_questions"] > 0
else 0
)
# Получаем дополнительную статистику # Получаем дополнительную статистику
recent_results = await self.db.get_recent_results(callback.from_user.id, 3) recent_results = await self.db.get_recent_results(callback.from_user.id, 3)
category_stats = await self.db.get_category_stats(callback.from_user.id) category_stats = await self.db.get_category_stats(callback.from_user.id)
best_score = user_stats['best_score'] or 0 best_score = user_stats["best_score"] or 0
avg_score = user_stats['average_score'] or 0 avg_score = user_stats["average_score"] or 0
stats_text = f"""📊 <b>Ваша статистика:</b> stats_text = f"""📊 <b>Ваша статистика:</b>
@@ -451,29 +594,56 @@ class QuizBot:
if category_stats: if category_stats:
stats_text += "\n\n🏷️ <b>По категориям:</b>" stats_text += "\n\n🏷️ <b>По категориям:</b>"
for cat_stat in category_stats[:2]: for cat_stat in category_stats[:2]:
cat_accuracy = (cat_stat['correct_answers'] / cat_stat['total_questions']) * 100 if cat_stat['total_questions'] > 0 else 0 cat_accuracy = (
(cat_stat["correct_answers"] / cat_stat["total_questions"])
* 100
if cat_stat["total_questions"] > 0
else 0
)
stats_text += f"\n📖 {cat_stat['category']}: {cat_stat['attempts']} попыток, {cat_accuracy:.1f}% точность" stats_text += f"\n📖 {cat_stat['category']}: {cat_stat['attempts']} попыток, {cat_accuracy:.1f}% точность"
# Добавляем последние результаты # Добавляем последние результаты
if recent_results: if recent_results:
stats_text += "\n\n📈 <b>Последние результаты:</b>" stats_text += "\n\n📈 <b>Последние результаты:</b>"
for result in recent_results: for result in recent_results:
mode_emoji = "🎯" if result['mode'] == 'guest' else "📚" mode_emoji = "🎯" if result["mode"] == "guest" else "📚"
stats_text += f"\n{mode_emoji} {result['score']:.1f}% ({result['correct_answers']}/{result['questions_asked']})" stats_text += f"\n{mode_emoji} {result['score']:.1f}% ({result['correct_answers']}/{result['questions_asked']})"
keyboard = InlineKeyboardMarkup(inline_keyboard=[ keyboard = InlineKeyboardMarkup(
[InlineKeyboardButton(text="🏠 Главное меню", callback_data="back_to_menu")], inline_keyboard=[
[InlineKeyboardButton(text="🔄 Обновить статистику", callback_data="stats")] [
]) InlineKeyboardButton(
text="🏠 Главное меню", callback_data="back_to_menu"
)
],
[
InlineKeyboardButton(
text="🔄 Обновить статистику", callback_data="stats"
)
],
]
)
# Безопасная отправка сообщения # Безопасная отправка сообщения
if callback.message and not isinstance(callback.message, InaccessibleMessage): if callback.message and not isinstance(callback.message, InaccessibleMessage):
try: try:
await callback.message.edit_text(stats_text, reply_markup=keyboard, parse_mode='HTML') await callback.message.edit_text(
stats_text, reply_markup=keyboard, parse_mode="HTML"
)
except Exception: except Exception:
await self.bot.send_message(callback.from_user.id, stats_text, reply_markup=keyboard, parse_mode='HTML') await self.bot.send_message(
callback.from_user.id,
stats_text,
reply_markup=keyboard,
parse_mode="HTML",
)
else: else:
await self.bot.send_message(callback.from_user.id, stats_text, reply_markup=keyboard, parse_mode='HTML') await self.bot.send_message(
callback.from_user.id,
stats_text,
reply_markup=keyboard,
parse_mode="HTML",
)
await callback.answer() await callback.answer()
async def back_to_menu(self, callback: CallbackQuery, state: FSMContext): async def back_to_menu(self, callback: CallbackQuery, state: FSMContext):
@@ -488,29 +658,50 @@ class QuizBot:
username=user.username, username=user.username,
first_name=user.first_name, first_name=user.first_name,
last_name=user.last_name, last_name=user.last_name,
language_code=user.language_code or 'ru' language_code=user.language_code or "ru",
) )
await state.set_state(QuizStates.choosing_mode) await state.set_state(QuizStates.choosing_mode)
keyboard = InlineKeyboardMarkup(inline_keyboard=[ keyboard = InlineKeyboardMarkup(
[InlineKeyboardButton(text="🎯 Гостевой режим (QUIZ)", callback_data="guest_mode")], inline_keyboard=[
[InlineKeyboardButton(text="📚 Тестирование по материалам", callback_data="test_mode")], [
[InlineKeyboardButton(text="📊 Моя статистика", callback_data="stats")], InlineKeyboardButton(
]) text="🎯 Гостевой режим (QUIZ)", callback_data="guest_mode"
)
],
[
InlineKeyboardButton(
text="📚 Тестирование по материалам", callback_data="test_mode"
)
],
[InlineKeyboardButton(text="📊 Моя статистика", callback_data="stats")],
]
)
text = (f"👋 Добро пожаловать в Quiz Bot, {user.first_name}!\n\n" text = (
"🎯 <b>Гостевой режим</b> - быстрая викторина для развлечения\n" f"👋 Добро пожаловать в Quiz Bot, {user.first_name}!\n\n"
"📚 <b>Тестирование</b> - серьезное изучение материалов с результатами\n\n" "🎯 <b>Гостевой режим</b> - быстрая викторина для развлечения\n"
"Выберите режим работы:") "📚 <b>Тестирование</b> - серьезное изучение материалов с результатами\n\n"
"Выберите режим работы:"
)
if callback.message and not isinstance(callback.message, InaccessibleMessage): if callback.message and not isinstance(callback.message, InaccessibleMessage):
try: try:
await callback.message.edit_text(text, reply_markup=keyboard, parse_mode='HTML') await callback.message.edit_text(
text, reply_markup=keyboard, parse_mode="HTML"
)
except Exception: except Exception:
await self.bot.send_message(callback.from_user.id, text, reply_markup=keyboard, parse_mode='HTML') await self.bot.send_message(
callback.from_user.id,
text,
reply_markup=keyboard,
parse_mode="HTML",
)
else: else:
await self.bot.send_message(callback.from_user.id, text, reply_markup=keyboard, parse_mode='HTML') await self.bot.send_message(
callback.from_user.id, text, reply_markup=keyboard, parse_mode="HTML"
)
await callback.answer() await callback.answer()
def get_grade(self, score: float) -> str: def get_grade(self, score: float) -> str:
@@ -527,7 +718,10 @@ class QuizBot:
async def start(self): async def start(self):
"""Запуск бота""" """Запуск бота"""
# Проверяем токен # Проверяем токен
if not config.bot_token or config.bot_token in ['your_bot_token_here', 'test_token_for_demo_purposes']: if not config.bot_token or config.bot_token in [
"your_bot_token_here",
"test_token_for_demo_purposes",
]:
print("❌ Ошибка: не настроен BOT_TOKEN в файле .env") print("❌ Ошибка: не настроен BOT_TOKEN в файле .env")
return False return False
@@ -544,10 +738,12 @@ class QuizBot:
logging.error(f"Error starting bot: {e}") logging.error(f"Error starting bot: {e}")
return False return False
async def main(): async def main():
"""Главная функция""" """Главная функция"""
bot = QuizBot() bot = QuizBot()
await bot.start() await bot.start()
if __name__ == "__main__": if __name__ == "__main__":
asyncio.run(main()) asyncio.run(main())

View File

@@ -1,7 +1,9 @@
import aiosqlite
import logging
from typing import List, Dict, Optional, Tuple, Union
import json import json
import logging
from typing import Dict, List, Optional, Tuple, Union
import aiosqlite
class DatabaseManager: class DatabaseManager:
def __init__(self, db_path: str): def __init__(self, db_path: str):
@@ -11,7 +13,8 @@ class DatabaseManager:
"""Инициализация базы данных и создание таблиц""" """Инициализация базы данных и создание таблиц"""
async with aiosqlite.connect(self.db_path) as db: async with aiosqlite.connect(self.db_path) as db:
# Таблица пользователей # Таблица пользователей
await db.execute(""" await db.execute(
"""
CREATE TABLE IF NOT EXISTS users ( CREATE TABLE IF NOT EXISTS users (
user_id INTEGER PRIMARY KEY, user_id INTEGER PRIMARY KEY,
username TEXT, username TEXT,
@@ -23,10 +26,12 @@ class DatabaseManager:
total_questions INTEGER DEFAULT 0, total_questions INTEGER DEFAULT 0,
correct_answers INTEGER DEFAULT 0 correct_answers INTEGER DEFAULT 0
) )
""") """
)
# Таблица тестов # Таблица тестов
await db.execute(""" await db.execute(
"""
CREATE TABLE IF NOT EXISTS tests ( CREATE TABLE IF NOT EXISTS tests (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL, name TEXT NOT NULL,
@@ -37,10 +42,12 @@ class DatabaseManager:
created_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, created_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
is_active BOOLEAN DEFAULT TRUE is_active BOOLEAN DEFAULT TRUE
) )
""") """
)
# Таблица вопросов # Таблица вопросов
await db.execute(""" await db.execute(
"""
CREATE TABLE IF NOT EXISTS questions ( CREATE TABLE IF NOT EXISTS questions (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
test_id INTEGER, test_id INTEGER,
@@ -52,10 +59,12 @@ class DatabaseManager:
correct_answer INTEGER NOT NULL, correct_answer INTEGER NOT NULL,
FOREIGN KEY (test_id) REFERENCES tests (id) FOREIGN KEY (test_id) REFERENCES tests (id)
) )
""") """
)
# Таблица результатов # Таблица результатов
await db.execute(""" await db.execute(
"""
CREATE TABLE IF NOT EXISTS results ( CREATE TABLE IF NOT EXISTS results (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER, user_id INTEGER,
@@ -70,10 +79,12 @@ class DatabaseManager:
FOREIGN KEY (user_id) REFERENCES users (user_id), FOREIGN KEY (user_id) REFERENCES users (user_id),
FOREIGN KEY (test_id) REFERENCES tests (id) FOREIGN KEY (test_id) REFERENCES tests (id)
) )
""") """
)
# Таблица активных сессий # Таблица активных сессий
await db.execute(""" await db.execute(
"""
CREATE TABLE IF NOT EXISTS active_sessions ( CREATE TABLE IF NOT EXISTS active_sessions (
user_id INTEGER PRIMARY KEY, user_id INTEGER PRIMARY KEY,
test_id INTEGER, test_id INTEGER,
@@ -85,22 +96,32 @@ class DatabaseManager:
FOREIGN KEY (user_id) REFERENCES users (user_id), FOREIGN KEY (user_id) REFERENCES users (user_id),
FOREIGN KEY (test_id) REFERENCES tests (id) FOREIGN KEY (test_id) REFERENCES tests (id)
) )
""") """
)
await db.commit() await db.commit()
logging.info("Database initialized successfully") logging.info("Database initialized successfully")
async def register_user(self, user_id: int, username: Optional[str] = None, async def register_user(
first_name: Optional[str] = None, last_name: Optional[str] = None, self,
language_code: str = 'ru', is_guest: bool = True) -> bool: user_id: int,
username: Optional[str] = None,
first_name: Optional[str] = None,
last_name: Optional[str] = None,
language_code: str = "ru",
is_guest: bool = True,
) -> bool:
"""Регистрация нового пользователя""" """Регистрация нового пользователя"""
try: try:
async with aiosqlite.connect(self.db_path) as db: async with aiosqlite.connect(self.db_path) as db:
await db.execute(""" await db.execute(
"""
INSERT OR REPLACE INTO users INSERT OR REPLACE INTO users
(user_id, username, first_name, last_name, language_code, is_guest) (user_id, username, first_name, last_name, language_code, is_guest)
VALUES (?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?)
""", (user_id, username, first_name, last_name, language_code, is_guest)) """,
(user_id, username, first_name, last_name, language_code, is_guest),
)
await db.commit() await db.commit()
return True return True
except Exception as e: except Exception as e:
@@ -123,15 +144,19 @@ class DatabaseManager:
logging.error(f"Error getting user {user_id}: {e}") logging.error(f"Error getting user {user_id}: {e}")
return None return None
async def add_test(self, name: str, description: str, level: int, async def add_test(
category: str, csv_file: str) -> Optional[int]: self, name: str, description: str, level: int, category: str, csv_file: str
) -> Optional[int]:
"""Добавление нового теста""" """Добавление нового теста"""
try: try:
async with aiosqlite.connect(self.db_path) as db: async with aiosqlite.connect(self.db_path) as db:
cursor = await db.execute(""" cursor = await db.execute(
"""
INSERT INTO tests (name, description, level, category, csv_file) INSERT INTO tests (name, description, level, category, csv_file)
VALUES (?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?)
""", (name, description, level, category, csv_file)) """,
(name, description, level, category, csv_file),
)
await db.commit() await db.commit()
return cursor.lastrowid return cursor.lastrowid
except Exception as e: except Exception as e:
@@ -145,7 +170,7 @@ class DatabaseManager:
if category: if category:
cursor = await db.execute( cursor = await db.execute(
"SELECT * FROM tests WHERE category = ? AND is_active = TRUE ORDER BY level", "SELECT * FROM tests WHERE category = ? AND is_active = TRUE ORDER BY level",
(category,) (category,),
) )
else: else:
cursor = await db.execute( cursor = await db.execute(
@@ -163,12 +188,22 @@ class DatabaseManager:
try: try:
async with aiosqlite.connect(self.db_path) as db: async with aiosqlite.connect(self.db_path) as db:
for q in questions: for q in questions:
await db.execute(""" await db.execute(
"""
INSERT INTO questions INSERT INTO questions
(test_id, question, option1, option2, option3, option4, correct_answer) (test_id, question, option1, option2, option3, option4, correct_answer)
VALUES (?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?)
""", (test_id, q['question'], q['option1'], q['option2'], """,
q['option3'], q['option4'], q['correct_answer'])) (
test_id,
q["question"],
q["option1"],
q["option2"],
q["option3"],
q["option4"],
q["correct_answer"],
),
)
await db.commit() await db.commit()
return True return True
except Exception as e: except Exception as e:
@@ -179,10 +214,13 @@ class DatabaseManager:
"""Получение случайных вопросов из теста""" """Получение случайных вопросов из теста"""
try: try:
async with aiosqlite.connect(self.db_path) as db: async with aiosqlite.connect(self.db_path) as db:
cursor = await db.execute(""" cursor = await db.execute(
"""
SELECT * FROM questions WHERE test_id = ? SELECT * FROM questions WHERE test_id = ?
ORDER BY RANDOM() LIMIT ? ORDER BY RANDOM() LIMIT ?
""", (test_id, count)) """,
(test_id, count),
)
rows = await cursor.fetchall() rows = await cursor.fetchall()
columns = [description[0] for description in cursor.description] columns = [description[0] for description in cursor.description]
return [dict(zip(columns, row)) for row in rows] return [dict(zip(columns, row)) for row in rows]
@@ -190,17 +228,21 @@ class DatabaseManager:
logging.error(f"Error getting random questions: {e}") logging.error(f"Error getting random questions: {e}")
return [] return []
async def start_session(self, user_id: int, test_id: int, async def start_session(
questions: List[Dict], mode: str) -> bool: self, user_id: int, test_id: int, questions: List[Dict], mode: str
) -> bool:
"""Начало новой сессии викторины""" """Начало новой сессии викторины"""
try: try:
async with aiosqlite.connect(self.db_path) as db: async with aiosqlite.connect(self.db_path) as db:
questions_json = json.dumps(questions) questions_json = json.dumps(questions)
await db.execute(""" await db.execute(
"""
INSERT OR REPLACE INTO active_sessions INSERT OR REPLACE INTO active_sessions
(user_id, test_id, questions_data, mode) (user_id, test_id, questions_data, mode)
VALUES (?, ?, ?, ?) VALUES (?, ?, ?, ?)
""", (user_id, test_id, questions_json, mode)) """,
(user_id, test_id, questions_json, mode),
)
await db.commit() await db.commit()
return True return True
except Exception as e: except Exception as e:
@@ -218,39 +260,48 @@ class DatabaseManager:
if row: if row:
columns = [description[0] for description in cursor.description] columns = [description[0] for description in cursor.description]
session = dict(zip(columns, row)) session = dict(zip(columns, row))
session['questions_data'] = json.loads(session['questions_data']) session["questions_data"] = json.loads(session["questions_data"])
return session return session
return None return None
except Exception as e: except Exception as e:
logging.error(f"Error getting active session: {e}") logging.error(f"Error getting active session: {e}")
return None return None
async def update_session_progress(self, user_id: int, question_num: int, async def update_session_progress(
correct_count: int) -> bool: self, user_id: int, question_num: int, correct_count: int
) -> bool:
"""Обновление прогресса сессии""" """Обновление прогресса сессии"""
try: try:
async with aiosqlite.connect(self.db_path) as db: async with aiosqlite.connect(self.db_path) as db:
await db.execute(""" await db.execute(
"""
UPDATE active_sessions UPDATE active_sessions
SET current_question = ?, correct_count = ? SET current_question = ?, correct_count = ?
WHERE user_id = ? WHERE user_id = ?
""", (question_num, correct_count, user_id)) """,
(question_num, correct_count, user_id),
)
await db.commit() await db.commit()
return True return True
except Exception as e: except Exception as e:
logging.error(f"Error updating session progress: {e}") logging.error(f"Error updating session progress: {e}")
return False return False
async def update_session_questions(self, user_id: int, questions_data: list) -> bool: async def update_session_questions(
self, user_id: int, questions_data: list
) -> bool:
"""Обновление данных вопросов в сессии (например, после перемешивания)""" """Обновление данных вопросов в сессии (например, после перемешивания)"""
try: try:
async with aiosqlite.connect(self.db_path) as db: async with aiosqlite.connect(self.db_path) as db:
questions_json = json.dumps(questions_data, ensure_ascii=False) questions_json = json.dumps(questions_data, ensure_ascii=False)
await db.execute(""" await db.execute(
"""
UPDATE active_sessions UPDATE active_sessions
SET questions_data = ? SET questions_data = ?
WHERE user_id = ? WHERE user_id = ?
""", (questions_json, user_id)) """,
(questions_json, user_id),
)
await db.commit() await db.commit()
return True return True
except Exception as e: except Exception as e:
@@ -267,23 +318,37 @@ class DatabaseManager:
return False return False
# Сохраняем результат # Сохраняем результат
await db.execute(""" await db.execute(
"""
INSERT INTO results INSERT INTO results
(user_id, test_id, mode, questions_asked, correct_answers, score) (user_id, test_id, mode, questions_asked, correct_answers, score)
VALUES (?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?)
""", (user_id, session['test_id'], session['mode'], """,
len(session['questions_data']), session['correct_count'], score)) (
user_id,
session["test_id"],
session["mode"],
len(session["questions_data"]),
session["correct_count"],
score,
),
)
# Обновляем статистику пользователя # Обновляем статистику пользователя
await db.execute(""" await db.execute(
"""
UPDATE users UPDATE users
SET total_questions = total_questions + ?, SET total_questions = total_questions + ?,
correct_answers = correct_answers + ? correct_answers = correct_answers + ?
WHERE user_id = ? WHERE user_id = ?
""", (len(session['questions_data']), session['correct_count'], user_id)) """,
(len(session["questions_data"]), session["correct_count"], user_id),
)
# Удаляем активную сессию # Удаляем активную сессию
await db.execute("DELETE FROM active_sessions WHERE user_id = ?", (user_id,)) await db.execute(
"DELETE FROM active_sessions WHERE user_id = ?", (user_id,)
)
await db.commit() await db.commit()
return True return True
@@ -295,7 +360,8 @@ class DatabaseManager:
"""Получение статистики пользователя""" """Получение статистики пользователя"""
try: try:
async with aiosqlite.connect(self.db_path) as db: async with aiosqlite.connect(self.db_path) as db:
cursor = await db.execute(""" cursor = await db.execute(
"""
SELECT SELECT
u.total_questions, u.total_questions,
u.correct_answers, u.correct_answers,
@@ -308,7 +374,9 @@ class DatabaseManager:
LEFT JOIN results r ON u.user_id = r.user_id LEFT JOIN results r ON u.user_id = r.user_id
WHERE u.user_id = ? WHERE u.user_id = ?
GROUP BY u.user_id GROUP BY u.user_id
""", (user_id,)) """,
(user_id,),
)
row = await cursor.fetchone() row = await cursor.fetchone()
if row: if row:
@@ -324,7 +392,8 @@ class DatabaseManager:
"""Получение последних результатов пользователя""" """Получение последних результатов пользователя"""
try: try:
async with aiosqlite.connect(self.db_path) as db: async with aiosqlite.connect(self.db_path) as db:
cursor = await db.execute(""" cursor = await db.execute(
"""
SELECT SELECT
r.mode, r.mode,
r.questions_asked, r.questions_asked,
@@ -338,7 +407,9 @@ class DatabaseManager:
WHERE r.user_id = ? WHERE r.user_id = ?
ORDER BY r.end_time DESC ORDER BY r.end_time DESC
LIMIT ? LIMIT ?
""", (user_id, limit)) """,
(user_id, limit),
)
rows = await cursor.fetchall() rows = await cursor.fetchall()
columns = [description[0] for description in cursor.description] columns = [description[0] for description in cursor.description]
@@ -351,7 +422,8 @@ class DatabaseManager:
"""Получение статистики по категориям""" """Получение статистики по категориям"""
try: try:
async with aiosqlite.connect(self.db_path) as db: async with aiosqlite.connect(self.db_path) as db:
cursor = await db.execute(""" cursor = await db.execute(
"""
SELECT SELECT
t.category, t.category,
COUNT(r.id) as attempts, COUNT(r.id) as attempts,
@@ -364,7 +436,9 @@ class DatabaseManager:
WHERE r.user_id = ? WHERE r.user_id = ?
GROUP BY t.category GROUP BY t.category
ORDER BY attempts DESC ORDER BY attempts DESC
""", (user_id,)) """,
(user_id,),
)
rows = await cursor.fetchall() rows = await cursor.fetchall()
columns = [description[0] for description in cursor.description] columns = [description[0] for description in cursor.description]