Привет! Помоги мне поправить ошибку:
2024-10-16 00:38:27,248 - __main__ - INFO - База данных и таблицы инициализированы успешно.
Traceback (most recent call last):
  File "/data/user/0/ru.iiec.pydroid3/files/accomp_files/iiec_run/iiec_run.py", line 31, in <module>    start(fakepyfile,mainpyfile)
  File "/data/user/0/ru.iiec.pydroid3/files/accomp_files/iiec_run/iiec_run.py", line 30, in start
    exec(open(mainpyfile).read(),  __main__.__dict__)
  File "<string>", line 1535, in <module>
NameError: name 'handle_block_duration' is not defined

[Program finished]

Вот мой код ниже. Можешь сразу выслать готовый, чтобы мне осталось только вставить. Прошу, не удаляй ничего лишнего, всё только по факту:
import os
import re
import pytz
import sqlite3
import asyncio
from datetime import datetime, timedelta
from telegram import (
    Update,
    ReplyKeyboardMarkup,
    InlineKeyboardMarkup,
    InlineKeyboardButton
)
from telegram.ext import (
    ApplicationBuilder,
    CommandHandler,
    ContextTypes,
    MessageHandler,
    filters,
    CallbackQueryHandler,
)
from dotenv import load_dotenv
from apscheduler.schedulers.asyncio import AsyncIOScheduler
import logging
import traceback

# Шаг 1: Загрузка токена из .env файла
load_dotenv('token.env')  # Убедитесь, что файл token.env находится в той же директории
TOKEN = os.getenv("BOT_TOKEN")
if not TOKEN:
    raise ValueError("Токен бота не найден в переменных окружения. Пожалуйста, проверьте файл token.env")

# Шаг 2: Настройка логирования
logging.basicConfig(
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    level=logging.INFO
)
logger = logging.getLogger(__name__)

# Шаг 3: Инициализация базы данных
def init_db():
    with sqlite3.connect('university.db') as conn:
        c = conn.cursor()
        # Создание таблиц
        c.execute('''CREATE TABLE IF NOT EXISTS schedule
                     (course INTEGER, direction TEXT, week_type TEXT, day TEXT, slot INTEGER, time TEXT, details TEXT)''')
        c.execute('''CREATE TABLE IF NOT EXISTS users
                     (id INTEGER PRIMARY KEY, username TEXT, course INTEGER, direction TEXT, 
                      subscribed_schedule_changes INTEGER DEFAULT 1,
                      subscribed_homework INTEGER DEFAULT 1,
                      subscribed_announcements INTEGER DEFAULT 1,
                      role TEXT DEFAULT 'student')''')
        c.execute('''CREATE TABLE IF NOT EXISTS admins
                     (id INTEGER PRIMARY KEY)''')
        c.execute('''CREATE TABLE IF NOT EXISTS blocked_users
                     (id INTEGER PRIMARY KEY, blocked_until DATETIME)''')
        c.execute('''CREATE TABLE IF NOT EXISTS schedule_changes
                     (id INTEGER PRIMARY KEY AUTOINCREMENT, course INTEGER, direction TEXT, week_type TEXT, day TEXT, slot INTEGER, 
                      old_details TEXT, new_details TEXT, changed_at DATETIME DEFAULT CURRENT_TIMESTAMP, notified INTEGER DEFAULT 0)''')
        c.execute('''CREATE TABLE IF NOT EXISTS announcements
                     (id INTEGER PRIMARY KEY AUTOINCREMENT, sender_id INTEGER, message TEXT, photo TEXT, sent_at DATETIME DEFAULT CURRENT_TIMESTAMP)''')
        c.execute('''CREATE TABLE IF NOT EXISTS homework_senders
                     (id INTEGER PRIMARY KEY)''')

        # Добавление вашего ID администратора (1108429314)
        ADMIN_ID = 1108429314
        c.execute("INSERT OR IGNORE INTO admins (id) VALUES (?)", (ADMIN_ID,))

        # Пример заполнения таблицы расписания (вы можете изменить или расширить эти данные)
        # Убедитесь, что данные соответствуют формату таблицы schedule
        sample_schedule = [
            (1, 'Ф', 'чётная', 'Понедельник', 1, '09:00-10:30', 'Математика'),
            (1, 'Ф', 'чётная', 'Понедельник', 2, '10:45-12:15', 'Физика'),
            (1, 'Ф', 'нечётная', 'Вторник', 1, '09:00-10:30', 'Химия'),
            (2, 'У', 'чётная', 'Среда', 1, '11:00-12:30', 'Биология'),
            # Добавьте больше расписаний по необходимости
        ]
        c.executemany('''INSERT INTO schedule (course, direction, week_type, day, slot, time, details) 
                     VALUES (?, ?, ?, ?, ?, ?, ?)''', sample_schedule)

        conn.commit()
        logger.info("База данных и таблицы инициализированы успешно.")

init_db()

# Шаг 4: Определение вспомогательных функций

def extract_command(text):
    """
    Извлекает текст команды из сообщения, удаляя эмодзи и приводя текст к нижнему регистру.
    Например, "👤 Личный кабинет" -> "личный кабинет"
    """
    # Удаление всех символов, не являющихся буквами русского или английского алфавита и пробелами
    cleaned_text = re.sub(r'[^\wа-яА-ЯёЁ\s]+', '', text)
    return cleaned_text.strip().lower()

def get_day_name(weekday_num):
    days = ["Понедельник", "Вторник", "Среда",
            "Четверг", "Пятница", "Суббота", "Воскресенье"]
    return days[weekday_num]

def get_week_type(date_obj):
    # Определение стартовой даты (первый понедельник года)
    start_of_year = date_obj.replace(month=1, day=1, hour=0, minute=0, second=0, microsecond=0)
    if start_of_year.weekday() != 0:
        start_of_year += timedelta(days=(7 - start_of_year.weekday()))
    delta_weeks = (date_obj - start_of_year).days // 7
    return "чётная" if delta_weeks % 2 == 0 else "нечётная"

def get_week_type_short(date_obj):
    return "Чётная" if get_week_type(date_obj) == "чётная" else "Нечётная"

def is_user_blocked(user_id):
    with sqlite3.connect('university.db') as conn:
        c = conn.cursor()
        c.execute("SELECT blocked_until FROM blocked_users WHERE id = ?", (user_id,))
        result = c.fetchone()
    if result:
        blocked_until = result[0]
        if blocked_until == '9999-12-31 23:59:59':
            return blocked_until
        try:
            blocked_until_dt = datetime.strptime(blocked_until, "%Y-%m-%d %H:%M:%S")
            blocked_until_dt = pytz.timezone('Asia/Omsk').localize(blocked_until_dt)
            if blocked_until_dt > datetime.now(pytz.timezone('Asia/Omsk')):
                return blocked_until_dt
            else:
                # Автоматически разблокировать пользователя, если время блокировки истекло
                unblock_user(user_id)
        except Exception as e:
            logger.error(f"Ошибка при обработке даты блокировки для пользователя {user_id}: {e}")
    return None

def unblock_user(user_id):
    with sqlite3.connect('university.db') as conn:
        c = conn.cursor()
        c.execute("DELETE FROM blocked_users WHERE id = ?", (user_id,))
        conn.commit()
    logger.info(f"Пользователь {user_id} разблокирован.")

def is_admin_user(user_id):
    with sqlite3.connect('university.db') as conn:
        c = conn.cursor()
        c.execute("SELECT 1 FROM admins WHERE id = ?", (user_id,))
        is_admin = c.fetchone() is not None
    return is_admin

def is_homework_sender(user_id):
    with sqlite3.connect('university.db') as conn:
        c = conn.cursor()
        c.execute("SELECT 1 FROM homework_senders WHERE id = ?", (user_id,))
        is_sender = c.fetchone() is not None
    return is_sender

def get_schedule_for_day(course, direction, week_type, day):
    """
    Функция для получения расписания на конкретный день.
    """
    with sqlite3.connect('university.db') as conn:
        c = conn.cursor()
        c.execute('''SELECT slot, time, details FROM schedule 
                     WHERE course = ? AND direction = ? AND week_type = ? AND day = ?
                     ORDER BY slot''', (course, direction, week_type, day))
        schedule = c.fetchall()
    
    if not schedule:
        return "📭 Нет расписания на этот день."
    
    schedule_text = ""
    for slot, time, details in schedule:
        schedule_text += f"🕒 Пара {slot} ({time}): {details}\n"
    
    return schedule_text

# Шаг 5: Определение основных функций бота

async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    await update.message.reply_text('👋 Привет! Поменяй группу и курс на соответствующие в своём личном кабинете.')
    await personal_cabinet(update, context)
    logger.info(f"Пользователь {update.effective_user.id} запустил бота и открыл личный кабинет.")

    keyboard = [
        ["🔢 Выбрать дату расписания", "📅 Расписание на завтра", "📅 Расписание на сегодня"],
        ["📆 Понедельник", "📆 Вторник", "📆 Среда"],
        ["📆 Четверг", "📆 Пятница", "📆 Суббота"],
        ["👤 Личный кабинет"]
    ]

    reply_markup = ReplyKeyboardMarkup(keyboard, one_time_keyboard=True, resize_keyboard=True)
    await update.message.reply_text('✅ Выбери опцию:', reply_markup=reply_markup)
    logger.info(f"Пользователь {update.effective_user.id} получил меню опций.")

async def personal_cabinet(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    user_id = update.effective_user.id
    username = update.effective_user.username or "No username"
    first_name = update.effective_user.first_name or "No first name"
    last_name = update.effective_user.last_name or ""

    with sqlite3.connect('university.db') as conn:
        c = conn.cursor()
        c.execute("""SELECT username, course, direction, subscribed_schedule_changes, 
                            subscribed_homework, subscribed_announcements, role 
                     FROM users WHERE id = ?""", (user_id,))
        user = c.fetchone()

        if not user:
            c.execute("""INSERT INTO users 
                         (id, username, course, direction) 
                         VALUES (?, ?, ?, ?)""", 
                      (user_id, username, 1, "Ф"))
            conn.commit()
            user = (username, 1, "Ф", 1, 1, 1, 'student')
            logger.info(f"Новый пользователь добавлен: ID={user_id}, Username={username}")
        else:
            c.execute("UPDATE users SET username = ? WHERE id = ?", (username, user_id))
            conn.commit()
            logger.info(f"Пользователь обновлён: ID={user_id}, Username={username}")

    is_admin = is_admin_user(user_id)
    is_sender = is_homework_sender(user_id)

    # Проверка блокировки пользователя
    blocked_until = is_user_blocked(user_id)
    if blocked_until:
        if blocked_until == '9999-12-31 23:59:59':
            msg = "🔒 Вы заблокированы навсегда! Напишите для разблокировки администратору: @admin_username"
        else:
            remaining = blocked_until.strftime('%d.%m.%Y %H:%M:%S')
            msg = f"🔒 Вы заблокированы! До разблокировки: {remaining}"
        await update.message.reply_text(msg)
        logger.info(f"Заблокированный пользователь {user_id} попытался открыть личный кабинет.")
        return

    current_date = datetime.now(pytz.timezone('Asia/Omsk'))
    week_number = current_date.isocalendar()[1]
    week_type = get_week_type(current_date)
    week_status = f"{week_number} {week_type}"

    role = "👑 Преподаватель" if is_admin else ("📢 Рассыльщик домашки" if is_sender else "🎓 Студент")
    subscribe_state_homework = "ВКЛ." if user[4] else "ВЫКЛ."
    subscribe_state_schedule = "ВКЛ." if user[3] else "ВЫКЛ."
    subscribe_state_announcements = "ВКЛ." if user[5] else "ВЫКЛ."

    message_text = (
        f"**Личный кабинет**\n\n"
        f"**Имя:** {first_name} {last_name}\n"
        f"**Ник:** @{user[0]}\n"
        f"**ID:** {user_id}\n\n"
        f"**Роль:** {role}\n"
        f"**Группа:** {user[2]}\n"
        f"**Курс:** {user[1]}\n\n"
        f"**Неделя:** {week_status}\n"
        f"**Дата:** {current_date.strftime('%d.%m.%Y')}\n"
        f"**Время:** {current_date.strftime('%H:%M:%S')}\n\n"
        f"**Уведомления:**\n"
        f"📚 Домашнее задание: {subscribe_state_homework}\n"
        f"🕒 Изменение расписания: {subscribe_state_schedule}\n"
        f"📢 Объявления: {subscribe_state_announcements}"
    )

    keyboard = [
        [
            InlineKeyboardButton("🔄 Изменить группу", callback_data='change_direction'),
            InlineKeyboardButton("🔄 Изменить курс", callback_data='change_course')
        ],
        [InlineKeyboardButton("🔔 Уведомления", callback_data='manage_notifications')]
    ]

    if is_admin:
        keyboard.append([InlineKeyboardButton("⚙️ Для администраторов", callback_data='admin_panel')])
    if is_sender:
        keyboard.append([InlineKeyboardButton("📚 Отправить домашку", callback_data='send_homework')])

    reply_markup = InlineKeyboardMarkup(keyboard)

    if update.callback_query:
        await update.callback_query.edit_message_text(text=message_text, reply_markup=reply_markup, parse_mode='Markdown')
    else:
        await update.message.reply_text(text=message_text, reply_markup=reply_markup, parse_mode='Markdown')
    logger.info(f"Пользователь {user_id} открыл личный кабинет.")

async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    user_id = update.message.from_user.id
    user_message = update.message.text.strip()
    
    # Нормализация команды
    command = extract_command(user_message)
    
    # Проверка блокировки пользователя
    blocked_until = is_user_blocked(user_id)
    if blocked_until:
        if blocked_until == '9999-12-31 23:59:59':
            msg = "🔒 Вы заблокированы навсегда! Напишите для разблокировки администратору: @admin_username"
        else:
            remaining = blocked_until.strftime('%d.%m.%Y %H:%M:%S')
            msg = f"🔒 Вы заблокированы! До разблокировки: {remaining}"
        await update.message.reply_text(msg)
        logger.info(f"Заблокированный пользователь {user_id} попытался взаимодействовать с ботом.")
        return

    if context.user_data.get('awaiting_date', False):
        if user_message.isdigit():
            day = int(user_message)
            current_date = datetime.now(pytz.timezone('Asia/Omsk'))
            current_month = current_date.month
            current_year = current_date.year
            try:
                date_obj = datetime(current_year, current_month, day, tzinfo=pytz.timezone('Asia/Omsk'))
                if date_obj.month != current_month:
                    raise ValueError
                await show_schedule_by_date(update, context, date_obj)
                logger.info(f"Пользователь {user_id} запросил расписание на дату: {day}.{current_month}.{current_year}.")
            except ValueError:
                await update.message.reply_text('❌ Неверный день. Пожалуйста, введи число от 1 до последнего дня месяца.')
                logger.warning(f"Пользователь {user_id} ввёл неверный день для расписания: {user_message}")
            finally:
                context.user_data['awaiting_date'] = False
        else:
            await update.message.reply_text('🔢 Пожалуйста, введи число для получения расписания.')
            logger.warning(f"Пользователь {user_id} ввёл нечисловое значение при запросе расписания.")
        return

    if context.user_data.get('is_sending_announcement', False):
        await collect_announcement(update, context)
        return

    # Обработка админских действий и рассыльщиков домашки
    action = context.user_data.get('action')
    if action in ['block_user', 'unblock_user', 'add_homework_sender', 'remove_homework_sender']:
        await handle_admin_actions(update, context)
        return

    # Обработка запросов на расписание
    schedule_commands = [
        "расписание на сегодня",
        "расписание на завтра",
        "понедельник",
        "вторник",
        "среда",
        "четверг",
        "пятница",
        "суббота",
        "воскресенье"
    ]
    if command in schedule_commands:
        await show_schedule(update, context, command)
        return

    if command == "выбрать дату расписания":
        context.user_data['awaiting_date'] = True
        await update.message.reply_text("🔢 Введи необходимое число этого месяца:")
        logger.info(f"Пользователь {user_id} выбрал опцию 'Выбрать дату расписания'.")
        return

    if command == "личный кабинет":
        await personal_cabinet(update, context)
        return

    # Если ничего не подходит
    await update.message.reply_text('❓ Выбери опцию из меню.')
    logger.info(f"Пользователь {user_id} отправил неизвестный запрос: {user_message}")

async def handle_callback(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    query = update.callback_query
    user_id = query.from_user.id
    data = query.data

    with sqlite3.connect('university.db') as conn:
        c = conn.cursor()
        if data == 'change_course':
            c.execute("SELECT course FROM users WHERE id = ?", (user_id,))
            result = c.fetchone()
            if result:
                current_course = result[0]
                new_course = 1 if current_course == 4 else current_course + 1
                c.execute("UPDATE users SET course = ? WHERE id = ?", (new_course, user_id))
                conn.commit()
                logger.info(f"Пользователь {user_id} изменил курс на {new_course}")
                await query.answer(f"✅ Курс изменён на {new_course}")
                await personal_cabinet(update, context)
            else:
                await query.answer("❌ Пользователь не найден.")
                logger.error(f"Пользователь {user_id} не найден при изменении курса.")

        elif data == 'change_direction':
            c.execute("SELECT direction FROM users WHERE id = ?", (user_id,))
            result = c.fetchone()
            if result:
                current_direction = result[0]
                directions = ['Ф', 'У', 'ФМ', 'ГМУ', 'БИ']
                if current_direction in directions:
                    new_direction_index = (directions.index(current_direction) + 1) % len(directions)
                    new_direction = directions[new_direction_index]
                    c.execute("UPDATE users SET direction = ? WHERE id = ?", (new_direction, user_id))
                    conn.commit()
                    logger.info(f"Пользователь {user_id} изменил группу на {new_direction}")
                    await query.answer(f"✅ Группа изменена на {new_direction}")
                    await personal_cabinet(update, context)
                else:
                    await query.answer("❌ Неизвестная группа.")
                    logger.error(f"Пользователь {user_id} имеет неизвестную группу: {current_direction}")
            else:
                await query.answer("❌ Пользователь не найден.")
                logger.error(f"Пользователь {user_id} не найден при изменении группы.")

        elif data == 'manage_notifications':
            c.execute("""SELECT subscribed_homework, subscribed_schedule_changes, subscribed_announcements 
                         FROM users WHERE id = ?""", (user_id,))
            subs = c.fetchone()
            if subs:
                subscribe_homework = "✅ ВКЛ." if subs[0] else "❌ ВЫКЛ."
                subscribe_schedule = "✅ ВКЛ." if subs[1] else "❌ ВЫКЛ."
                subscribe_announcements = "✅ ВКЛ." if subs[2] else "❌ ВЫКЛ."

                message = (
                    f"**Управление уведомлениями:**\n\n"
                    f"📚 Домашнее задание: {subscribe_homework}\n"
                    f"🕒 Изменение расписания: {subscribe_schedule}\n"
                    f"📢 Объявления: {subscribe_announcements}"
                )

                buttons = [
                    [
                        InlineKeyboardButton("📚 Домашнее задание", callback_data='toggle_homework_subscription')
                    ],
                    [
                        InlineKeyboardButton("🕒 Изменение расписания", callback_data='toggle_schedule_subscription')
                    ],
                    [
                        InlineKeyboardButton("📢 Объявления", callback_data='toggle_announcement_subscription')
                    ],
                    [
                        InlineKeyboardButton("🔙 Назад", callback_data='back_to_cabinet')
                    ]
                ]
                reply_markup = InlineKeyboardMarkup(buttons)
                await query.answer()
                await query.edit_message_text(text=message, reply_markup=reply_markup, parse_mode='Markdown')
                logger.info(f"Пользователь {user_id} зашел в управление уведомлениями.")
            else:
                await query.answer("❌ Ошибка при получении подписок.")
                logger.error(f"Не удалось получить подписки для пользователя {user_id}.")

        elif data.startswith('toggle_') and 'subscription' in data:
            if data == 'toggle_homework_subscription':
                c.execute("UPDATE users SET subscribed_homework = 1 - subscribed_homework WHERE id = ?", (user_id,))
                conn.commit()
                await query.answer("✅ Подписка на домашнее задание обновлена")
                logger.info(f"Пользователь {user_id} изменил подписку на домашнее задание.")
            elif data == 'toggle_schedule_subscription':
                c.execute("UPDATE users SET subscribed_schedule_changes = 1 - subscribed_schedule_changes WHERE id = ?", (user_id,))
                conn.commit()
                await query.answer("✅ Подписка на изменение расписания обновлена")
                logger.info(f"Пользователь {user_id} изменил подписку на изменение расписания.")
            elif data == 'toggle_announcement_subscription':
                c.execute("UPDATE users SET subscribed_announcements = 1 - subscribed_announcements WHERE id = ?", (user_id,))
                conn.commit()
                await query.answer("✅ Подписка на объявления обновлена")
                logger.info(f"Пользователь {user_id} изменил подписку на объявления.")
            await personal_cabinet(update, context)

        elif data == 'admin_panel' and is_admin_user(user_id):
            # Админ-панель
            buttons = [
                [
                    InlineKeyboardButton("📢 Рассылка объявлений", callback_data='send_announcement'),
                    InlineKeyboardButton("🌐 Глобальная рассылка", callback_data='send_global_announcement')
                ],
                [
                    InlineKeyboardButton("➕ Добавить рассыльщика домашки", callback_data='add_homework_sender'),
                    InlineKeyboardButton("➖ Удалить рассыльщика домашки", callback_data='remove_homework_sender')
                ],
                [
                    InlineKeyboardButton("📊 Статистика", callback_data='view_statistics')
                ],
                [
                    InlineKeyboardButton("🔒 Заблокировать пользователя", callback_data='block_user'),
                    InlineKeyboardButton("🔓 Разблокировать пользователя", callback_data='unblock_user'),
                    InlineKeyboardButton("📋 Список заблокированных", callback_data='list_blocked_users')
                ],
                [
                    InlineKeyboardButton("🔙 Назад", callback_data='back_to_cabinet')
                ]
            ]
            reply_markup = InlineKeyboardMarkup(buttons)
            await query.answer()
            await query.edit_message_text("**⚙️ Админ-панель:**", reply_markup=reply_markup, parse_mode='Markdown')
            logger.info(f"Администратор {user_id} зашел в админ-панель.")

        elif data == 'view_statistics' and is_admin_user(user_id):
            # Собираем статистику
            with sqlite3.connect('university.db') as conn_stat:
                c_stat = conn_stat.cursor()
                total_users = c_stat.execute("SELECT COUNT(*) FROM users").fetchone()[0]
                new_today = c_stat.execute("""
                    SELECT COUNT(*) FROM users 
                    WHERE DATE(datetime('now', 'localtime')) = DATE(datetime('now', 'localtime'), 'start of day')
                """).fetchone()[0]
                new_yesterday = c_stat.execute("""
                    SELECT COUNT(*) FROM users 
                    WHERE DATE(datetime('now', 'localtime'), '-1 day') = DATE(datetime('now', 'localtime'), 'start of day', '-1 day')
                """).fetchone()[0]
                active_today = c_stat.execute("""
                    SELECT COUNT(DISTINCT id) FROM schedule_changes 
                    WHERE DATE(changed_at, 'localtime') = DATE('now', 'localtime')
                """).fetchone()[0]
                active_yesterday = c_stat.execute("""
                    SELECT COUNT(DISTINCT id) FROM schedule_changes 
                    WHERE DATE(changed_at, 'localtime') = DATE('now', 'localtime', '-1 day')
                """).fetchone()[0]
                most_active_group = c_stat.execute("""
                    SELECT direction, course, COUNT(*) as cnt 
                    FROM schedule_changes 
                    GROUP BY direction, course 
                    ORDER BY cnt DESC LIMIT 1
                """).fetchone()
                most_active_group_text = f"{most_active_group[0]}{most_active_group[1]}" if most_active_group else "Не доступно"

            stats = (
                f"📊 **Статистика Бота**\n\n"
                f"✅ **Всего пользователей:** {total_users}\n"
                f"🆕 **Новых сегодня:** {new_today}\n"
                f"🆕 **Новых вчера:** {new_yesterday}\n"
                f"🔄 **Активные сегодня:** {active_today}\n"
                f"🔄 **Активные вчера:** {active_yesterday}\n"
                f"🏆 **Самая активная группа:** {most_active_group_text}"
            )

            await query.answer()
            await query.edit_message_text(stats, parse_mode='Markdown')
            logger.info(f"Администратор {user_id} просмотрел статистику.")

        elif data in ['add_homework_sender', 'remove_homework_sender', 'block_user', 'unblock_user']:
            action_map = {
                'add_homework_sender': 'add_homework_sender',
                'remove_homework_sender': 'remove_homework_sender',
                'block_user': 'block_user',
                'unblock_user': 'unblock_user'
            }
            context.user_data['action'] = action_map[data]
            action_text_map = {
                'add_homework_sender': "✅ Введите ID пользователя для добавления в рассыльщики домашки:",
                'remove_homework_sender': "✅ Введите ID пользователя для удаления из рассыльщиков домашки:",
                'block_user': "✅ Введите ID пользователя для блокировки:",
                'unblock_user': "✅ Введите ID пользователя для разблокировки:"
            }
            await query.answer()
            await query.edit_message_text(action_text_map[data])
            logger.info(f"Администратор {user_id} инициировал действие: {data}")

        elif data == 'list_blocked_users' and is_admin_user(user_id):
            await send_blocked_users_list(query, context)
            logger.info(f"Администратор {user_id} запросил список заблокированных пользователей.")

        elif data in ['send_announcement', 'send_global_announcement'] and is_admin_user(user_id):
            announcement_type_map = {
                'send_announcement': 'group',
                'send_global_announcement': 'global'
            }
            context.user_data['announcement_for'] = announcement_type_map[data]
            context.user_data['is_sending_announcement'] = True
            prompt_map = {
                'group': "✅ Введите сообщение для рассылки вашей группе:",
                'global': "✅ Введите сообщение для глобальной рассылки:",
            }
            await query.answer()
            await query.edit_message_text(
                f"{prompt_map[context.user_data['announcement_for']]}\n\n*Вы можете отправить фотографию вместе с сообщением.*",
                parse_mode='Markdown'
            )
            logger.info(f"Администратор {user_id} начал рассылку: {data}")

        elif data == 'back_to_cabinet':
            await personal_cabinet(update, context)
            logger.info(f"Пользователь {user_id} вернулся в личный кабинет.")

    async def handle_block_duration(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
        query = update.callback_query
        data = query.data
        user_id = query.from_user.id

        with sqlite3.connect('university.db') as conn:
            c = conn.cursor()
            if data.startswith('block_duration_'):
                duration_key = data.replace('block_duration_', '')
                duration_map = {
                    '1_hour': timedelta(hours=1),
                    '1_day': timedelta(days=1),
                    '1_week': timedelta(weeks=1),
                    '1_year': timedelta(days=365),
                    'forever': None
                }
                delta = duration_map.get(duration_key)

                target_id = context.user_data.get('block_target_id')
                if not target_id:
                    await query.answer("❌ Ошибка: целевой пользователь не найден.")
                    logger.error("Целевой пользователь для блокировки не найден.")
                    return

                if delta:
                    blocked_until = datetime.now(pytz.timezone('Asia/Omsk')) + delta
                    c.execute("""INSERT OR REPLACE INTO blocked_users 
                                 (id, blocked_until) VALUES (?, ?)""", 
                              (target_id, blocked_until.strftime("%Y-%m-%d %H:%M:%S")))
                    await query.answer(f"✅ Пользователь {target_id} заблокирован до {blocked_until.strftime('%d.%m.%Y %H:%M:%S')}.")
                    logger.info(f"Пользователь {target_id} заблокирован до {blocked_until}.")
                else:
                    c.execute("""INSERT OR REPLACE INTO blocked_users 
                                 (id, blocked_until) VALUES (?, ?)""", 
                              (target_id, '9999-12-31 23:59:59'))
                    await query.answer(f"✅ Пользователь {target_id} заблокирован навсегда.")
                    logger.info(f"Пользователь {target_id} заблокирован навсегда.")

                conn.commit()
                await query.edit_message_text(f"✅ Пользователь {target_id} успешно заблокирован.")

            elif data == 'cancel_action':
                await query.answer("❌ Действие отменено.")
                await query.edit_message_text("✅ Действие отменено.")
                logger.info(f"Пользователь {user_id} отменил действие блокировки.")

    async def send_blocked_users_list(query: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
        user_id = query.from_user.id
        with sqlite3.connect('university.db') as conn:
            c = conn.cursor()
            c.execute("""SELECT u.id, u.username, b.blocked_until 
                         FROM blocked_users b 
                         JOIN users u ON b.id = u.id 
                         ORDER BY b.blocked_until DESC""")
            blocked = c.fetchall()

        if not blocked:
            await query.answer()
            await query.edit_message_text("📋 **Список заблокированных пользователей пуст.**", parse_mode='Markdown')
            logger.info("Запрошен пустой список заблокированных пользователей.")
            return

        # Разделение на страницы по 20 пользователей
        page = context.user_data.get('blocked_users_page', 0)
        per_page = 20
        total = len(blocked)
        total_pages = (total + per_page - 1) // per_page

        start = page * per_page
        end = start + per_page
        page_users = blocked[start:end]

        def get_page_text(page_num, page_users):
            text = f"📋 **Список заблокированных пользователей** (Страница {page_num + 1}/{total_pages}):\n\n"
            for idx, (uid, uname, blocked_until) in enumerate(page_users, start=1 + start):
                if blocked_until == '9999-12-31 23:59:59':
                    remaining = "🔒 Навсегда"
                else:
                    try:
                        blocked_until_dt = datetime.strptime(blocked_until, "%Y-%m-%d %H:%M:%S")
                        blocked_until_dt = pytz.timezone('Asia/Omsk').localize(blocked_until_dt)
                        remaining = f"До {blocked_until_dt.strftime('%d.%m.%Y %H:%M:%S')}"
                    except:
                        remaining = blocked_until
                uname_display = uname if uname != "No username" else uid
                text += f"{idx}. @{uname_display} - `{uid}`\n📆 {remaining}\n\n"
            return text

        page_text = get_page_text(page, page_users)
        await query.answer()
        await query.edit_message_text(page_text, parse_mode='Markdown')
        logger.info(f"Пользователь {user_id} запросил список заблокированных пользователей (Страница {page + 1}/{total_pages}).")

        # Добавление кнопок навигации
        buttons = []
        navigation_buttons = []
        if total_pages > 1:
            if page > 0:
                navigation_buttons.append(InlineKeyboardButton("⬅️ Назад", callback_data='blocked_prev_page'))
            if page < total_pages - 1:
                navigation_buttons.append(InlineKeyboardButton("Вперед ➡️", callback_data='blocked_next_page'))
        if navigation_buttons:
            buttons.append(navigation_buttons)
        buttons.append([InlineKeyboardButton("❌ Закрыть", callback_data='close')])
        reply_markup = InlineKeyboardMarkup(buttons)
        await query.edit_message_reply_markup(reply_markup)
        logger.info(f"Пользователь {user_id} просмотрел страницу {page + 1} из {total_pages} в списке заблокированных.")

async def handle_blocked_users_pagination(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    query = update.callback_query
    data = query.data

    if 'blocked_users_page' not in context.user_data:
        context.user_data['blocked_users_page'] = 0

    page = context.user_data.get('blocked_users_page', 0)
    with sqlite3.connect('university.db') as conn:
        c = conn.cursor()
        c.execute("SELECT COUNT(*) FROM blocked_users")
        total = c.fetchone()[0]
    per_page = 20
    total_pages = (total + per_page - 1) // per_page

    if data == 'blocked_prev_page' and page > 0:
        page -= 1
    elif data == 'blocked_next_page' and page < total_pages - 1:
        page += 1
    elif data == 'close':
        await query.answer()
        await query.delete_message()
        logger.info(f"Пользователь {query.from_user.id} закрыл список заблокированных пользователей.")
        return
    else:
        await query.answer("❌ Нет дальнейших страниц.")
        logger.warning(f"Пользователь {query.from_user.id} попытался перейти на неверную страницу в списке заблокированных пользователей.")
        return

    context.user_data['blocked_users_page'] = page
    with sqlite3.connect('university.db') as conn:
        c = conn.cursor()
        c.execute("SELECT u.id, u.username, b.blocked_until FROM blocked_users b JOIN users u ON b.id = u.id ORDER BY b.blocked_until DESC")
        blocked = c.fetchall()

    start = page * per_page
    end = start + per_page
    page_users = blocked[start:end]

    page_text = (
        f"📋 **Список заблокированных пользователей** (Страница {page + 1}/{total_pages}):\n\n" +
        "\n".join([
            f"{idx + start + 1}. @{uname if uname != 'No username' else uid} - `{uid}`\n📆 {'🔒 Навсегда' if blocked_until == '9999-12-31 23:59:59' else 'До ' + datetime.strptime(blocked_until, '%Y-%m-%d %H:%M:%S').strftime('%d.%m.%Y %H:%M:%S')}"
            for idx, (uid, uname, blocked_until) in enumerate(page_users)
        ])
    )
    await query.answer()
    await query.edit_message_text(page_text, parse_mode='Markdown')
    logger.info(f"Пользователь {query.from_user.id} переместился на страницу {page + 1} из {total_pages} в списке заблокированных.")

    # Добавление кнопок навигации
    buttons = []
    navigation_buttons = []
    if total_pages > 1:
        if page > 0:
            navigation_buttons.append(InlineKeyboardButton("⬅️ Назад", callback_data='blocked_prev_page'))
        if page < total_pages - 1:
            navigation_buttons.append(InlineKeyboardButton("Вперед ➡️", callback_data='blocked_next_page'))
    if navigation_buttons:
        buttons.append(navigation_buttons)
    buttons.append([InlineKeyboardButton("❌ Закрыть", callback_data='close')])
    reply_markup = InlineKeyboardMarkup(buttons)
    await query.edit_message_reply_markup(reply_markup)
    logger.info(f"Пользователь {query.from_user.id} перешёл на страницу {page + 1} из {total_pages} в списке заблокированных.")

async def collect_announcement(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    if not context.user_data.get('is_sending_announcement', False):
        return

    message_text = update.message.text
    photo = None
    if update.message.photo:
        photo = update.message.photo[-1].file_id  # Берем самое высокое качество

    context.user_data['message_text'] = message_text
    context.user_data['photo'] = photo
    context.user_data['is_sending_announcement'] = False

    confirm_message = f"📤 **Вот ваш текст:**\n\n{message_text}\n\n❓ Хотите отправить?"
    buttons = [
        [
            InlineKeyboardButton("✅ Да", callback_data='confirm_send'),
            InlineKeyboardButton("❌ Нет", callback_data='cancel_send')
        ]
    ]
    reply_markup = InlineKeyboardMarkup(buttons)

    await update.message.reply_text(confirm_message, reply_markup=reply_markup, parse_mode='Markdown')
    logger.info(f"Пользователь {update.effective_user.id} подтвердил рассылку сообщения.")

async def confirm_send(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    query = update.callback_query
    data = query.data

    if data == 'confirm_send':
        message_text = context.user_data.get('message_text')
        photo = context.user_data.get('photo')
        announcement_for = context.user_data.get('announcement_for')
        sender_id = update.effective_user.id

        await query.answer("📨 Отправка начинается...")
        await send_announcement(context, announcement_for, message_text, photo, sender_id)
        await query.edit_message_text("✅ Рассылка успешно отправлена.")
        logger.info(f"Пользователь {sender_id} отправил рассылку.")

    elif data == 'cancel_send':
        await query.answer("❌ Рассылка отменена.")
        await query.edit_message_text("❌ Рассылка отменена.")
        logger.info(f"Пользователь {update.effective_user.id} отменил рассылку.")

    # Очистка данных
    context.user_data['message_text'] = None
    context.user_data['photo'] = None
    context.user_data['announcement_for'] = None

async def send_announcement(context: ContextTypes.DEFAULT_TYPE, announcement_for, message_text, photo, sender_id):
    with sqlite3.connect('university.db') as conn:
        c = conn.cursor()

        if announcement_for == 'global':
            c.execute("SELECT id FROM users WHERE subscribed_announcements = 1")
            subscribers = {row[0] for row in c.fetchall()}
            c.execute("SELECT id FROM admins")
            admins = {row[0] for row in c.fetchall()}
            all_recipients = subscribers | admins

        elif announcement_for == 'group':
            # Предполагается, что announcement_for содержит кортеж (course, direction)
            course, direction = context.user_data.get('announcement_for_course'), context.user_data.get('announcement_for_direction')
            c.execute("""SELECT id FROM users 
                         WHERE course = ? AND direction = ? AND subscribed_announcements = 1""",
                      (course, direction))
            subscribers = {row[0] for row in c.fetchall()}
            c.execute("SELECT id FROM admins")
            admins = {row[0] for row in c.fetchall()}
            all_recipients = subscribers | admins

        else:
            all_recipients = set()

    # Отправка сообщений
    for recipient in all_recipients:
        try:
            if photo:
                await context.bot.send_photo(chat_id=recipient, photo=photo, caption=message_text, parse_mode='Markdown')
            else:
                await context.bot.send_message(chat_id=recipient, text=message_text, parse_mode='Markdown')
            logger.info(f"Сообщение отправлено пользователю {recipient}.")
        except Exception as e:
            logger.error(f"❌ Не удалось отправить сообщение пользователю {recipient}: {e}")

    # Запись в таблицу announcements
    with sqlite3.connect('university.db') as conn:
        c = conn.cursor()
        c.execute("""INSERT INTO announcements (sender_id, message, photo) 
                     VALUES (?, ?, ?)""", 
                  (sender_id, message_text, photo))
        conn.commit()
    logger.info(f"Сообщение от пользователя {sender_id} записано в таблицу announcements.")

async def show_schedule(update: Update, context: ContextTypes.DEFAULT_TYPE, day_command: str) -> None:
    user_id = update.message.from_user.id

    with sqlite3.connect('university.db') as conn:
        c = conn.cursor()
        c.execute("SELECT course, direction FROM users WHERE id = ?", (user_id,))
        user_info = c.fetchone()

        if not user_info:
            await personal_cabinet(update, context)
            c.execute("SELECT course, direction FROM users WHERE id = ?", (user_id,))
            user_info = c.fetchone()

            if not user_info:
                await update.message.reply_text("❌ Ошибка при добавлении пользователя в базу данных.")
                logger.error(f"Не удалось добавить пользователя {user_id} в базу данных.")
                return

        course, direction = user_info

    current_date = datetime.now(pytz.timezone('Asia/Omsk'))

    schedule_command = day_command.lower()
    if schedule_command == "расписание на сегодня":
        target_date = current_date
    elif schedule_command == "расписание на завтра":
        target_date = current_date + timedelta(days=1)
    elif schedule_command in ["понедельник", "вторник", "среда", "четверг", "пятница", "суббота"]:
        days_map = {
            "понедельник": 0,
            "вторник": 1,
            "среда": 2,
            "четверг": 3,
            "пятница": 4,
            "суббота": 5
        }
        target_weekday = days_map[schedule_command]
        today_weekday = current_date.weekday()
        days_ahead = (target_weekday - today_weekday) % 7
        target_date = current_date + timedelta(days=days_ahead)
    else:
        await update.message.reply_text("❌ Неверный запрос. Выберите опцию из меню.")
        logger.warning(f"Пользователь {user_id} ввёл неверный запрос: {day_command}")
        return

    day_name = get_day_name(target_date.weekday())
    week_type_short = get_week_type_short(target_date)
    week_type = get_week_type(target_date)
    day_schedule = get_schedule_for_day(course, direction, week_type, day_name)

    message_text = f"📅 **{day_name} {target_date.strftime('%d.%m.%Y')} ({week_type_short})**:\n\n{day_schedule}"
    await update.message.reply_text(message_text, parse_mode='Markdown')
    logger.info(f"Пользователь {user_id} запросил расписание на {day_name} {target_date.strftime('%d.%m.%Y')}.")

async def show_schedule_by_date(update: Update, context: ContextTypes.DEFAULT_TYPE, date_obj: datetime) -> None:
    user_id = update.message.from_user.id

    with sqlite3.connect('university.db') as conn:
        c = conn.cursor()
        c.execute("""SELECT course, direction, 
                            subscribed_schedule_changes, subscribed_homework, subscribed_announcements, role 
                     FROM users WHERE id = ?""", (user_id,))
        user_info = c.fetchone()

    if not user_info:
        await personal_cabinet(update, context)
        with sqlite3.connect('university.db') as conn_retry:
            c_retry = conn_retry.cursor()
            c_retry.execute("""SELECT course, direction, 
                                subscribed_schedule_changes, subscribed_homework, subscribed_announcements, role 
                         FROM users WHERE id = ?""", (user_id,))
            user_info = c_retry.fetchone()

            if not user_info:
                await update.message.reply_text("❌ Ошибка при добавлении пользователя в базу данных.")
                logger.error(f"Не удалось добавить пользователя {user_id} в базу данных.")
                return

    course, direction, _, _, _, role = user_info

    day_name = get_day_name(date_obj.weekday())
    week_type_short = get_week_type_short(date_obj)
    week_type = get_week_type(date_obj)
    day_schedule = get_schedule_for_day(course, direction, week_type, day_name)

    message_text = f"📅 **{day_name} {date_obj.strftime('%d.%m.%Y')} ({week_type_short})**:\n\n{day_schedule}"
    await update.message.reply_text(message_text, parse_mode='Markdown')
    logger.info(f"Пользователь {user_id} запросил расписание на {day_name} {date_obj.strftime('%d.%m.%Y')}.")

async def handle_admin_actions(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    user_id = update.message.from_user.id
    user_message = update.message.text.strip()

    action = context.user_data.get('action')
    if not action:
        await update.message.reply_text("❓ Нет активных действий. Выберите опцию из меню.")
        logger.warning(f"Пользователь {user_id} попытался выполнить админское действие без активного контекста.")
        return

    with sqlite3.connect('university.db') as conn:
        c = conn.cursor()
        if action == 'block_user':
            if not user_message.isdigit():
                await update.message.reply_text("❌ Неверный формат ID. Пожалуйста, укажите численный ID пользователя.")
                logger.warning(f"Администратор {user_id} ввёл неверный формат ID для блокировки: {user_message}")
                return
            target_id = int(user_message)
            if target_id == user_id:
                await update.message.reply_text("❌ Вы не можете заблокировать самого себя.")
                logger.warning(f"Администратор {user_id} попытался заблокировать себя.")
                return
            c.execute("SELECT 1 FROM users WHERE id = ?", (target_id,))
            if not c.fetchone():
                await update.message.reply_text("❌ Пользователь с таким ID не найден.")
                logger.warning(f"Администратор {user_id} попытался заблокировать несуществующего пользователя: {target_id}")
                return

            # Запрос длительности блокировки
            context.user_data['block_target_id'] = target_id
            buttons = [
                [
                    InlineKeyboardButton("🕒 1 час", callback_data='block_duration_1_hour'),
                    InlineKeyboardButton("🕐 1 день", callback_data='block_duration_1_day')
                ],
                [
                    InlineKeyboardButton("🕑 1 неделя", callback_data='block_duration_1_week'),
                    InlineKeyboardButton("🕙 1 год", callback_data='block_duration_1_year')
                ],
                [
                    InlineKeyboardButton("🔒 Навсегда", callback_data='block_duration_forever'),
                    InlineKeyboardButton("❌ Отмена", callback_data='cancel_action')
                ]
            ]
            reply_markup = InlineKeyboardMarkup(buttons)
            await update.message.reply_text("✅ Выберите длительность блокировки:", reply_markup=reply_markup)
            logger.info(f"Администратор {user_id} начал процесс блокировки пользователя {target_id}.")

        elif action == 'unblock_user':
            if not user_message.isdigit():
                await update.message.reply_text("❌ Неверный формат ID. Пожалуйста, укажите численный ID пользователя.")
                logger.warning(f"Администратор {user_id} ввёл неверный формат ID для разблокировки: {user_message}")
                return
            target_id = int(user_message)
            c.execute("SELECT 1 FROM blocked_users WHERE id = ?", (target_id,))
            if not c.fetchone():
                await update.message.reply_text("❌ Этот пользователь не заблокирован.")
                logger.warning(f"Администратор {user_id} попытался разблокировать не заблокированного пользователя: {target_id}")
                return
            unblock_user(target_id)
            await update.message.reply_text(f"✅ Пользователь {target_id} успешно разблокирован.")
            logger.info(f"Администратор {user_id} разблокировал пользователя {target_id}.")

        elif action == 'add_homework_sender':
            if not user_message.isdigit():
                await update.message.reply_text("❌ Неверный формат ID. Пожалуйста, укажите численный ID пользователя.")
                logger.warning(f"Администратор {user_id} ввёл неверный формат ID для добавления рассыльщика: {user_message}")
                return
            target_id = int(user_message)
            if is_homework_sender(target_id):
                await update.message.reply_text("❌ Пользователь уже является рассыльщиком домашки.")
                logger.info(f"Администратор {user_id} попытался добавить уже существующего рассыльщика: {target_id}")
                return
            c.execute("SELECT 1 FROM users WHERE id = ?", (target_id,))
            if not c.fetchone():
                await update.message.reply_text("❌ Пользователь с таким ID не найден.")
                logger.warning(f"Администратор {user_id} попытался добавить несуществующего пользователя в рассыльщики: {target_id}")
                return
            c.execute("INSERT INTO homework_senders (id) VALUES (?)", (target_id,))
            conn.commit()
            await update.message.reply_text(f"✅ Пользователь {target_id} добавлен в рассыльщики домашки.")
            logger.info(f"Администратор {user_id} добавил пользователя {target_id} в рассыльщики домашки.")

        elif action == 'remove_homework_sender':
            if not user_message.isdigit():
                await update.message.reply_text("❌ Неверный формат ID. Пожалуйста, укажите численный ID пользователя.")
                logger.warning(f"Администратор {user_id} ввёл неверный формат ID для удаления рассыльщика: {user_message}")
                return
            target_id = int(user_message)
            if not is_homework_sender(target_id):
                await update.message.reply_text("❌ Пользователь не является рассыльщиком домашки.")
                logger.info(f"Администратор {user_id} попытался удалить нерассылщика: {target_id}")
                return
            c.execute("DELETE FROM homework_senders WHERE id = ?", (target_id,))
            conn.commit()
            await update.message.reply_text(f"✅ Пользователь {target_id} удалён из рассыльщиков домашки.")
            logger.info(f"Администратор {user_id} удалил пользователя {target_id} из рассыльщиков домашки.")

    # Очистка действия
    context.user_data['action'] = None
    await personal_cabinet(update, context)
    logger.info(f"Администратор {user_id} завершил действие '{action}'.")

async def handle_blocked_users_pagination(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    query = update.callback_query
    data = query.data

    if 'blocked_users_page' not in context.user_data:
        context.user_data['blocked_users_page'] = 0

    page = context.user_data.get('blocked_users_page', 0)
    with sqlite3.connect('university.db') as conn:
        c = conn.cursor()
        c.execute("SELECT COUNT(*) FROM blocked_users")
        total = c.fetchone()[0]
    per_page = 20
    total_pages = (total + per_page - 1) // per_page

    if data == 'blocked_prev_page' and page > 0:
        page -= 1
    elif data == 'blocked_next_page' and page < total_pages - 1:
        page += 1
    elif data == 'close':
        await query.answer()
        await query.delete_message()
        logger.info(f"Пользователь {query.from_user.id} закрыл список заблокированных пользователей.")
        return
    else:
        await query.answer("❌ Нет дальнейших страниц.")
        logger.warning(f"Пользователь {query.from_user.id} попытался перейти на неверную страницу в списке заблокированных пользователей.")
        return

    context.user_data['blocked_users_page'] = page
    with sqlite3.connect('university.db') as conn:
        c = conn.cursor()
        c.execute("SELECT u.id, u.username, b.blocked_until FROM blocked_users b JOIN users u ON b.id = u.id ORDER BY b.blocked_until DESC")
        blocked = c.fetchall()

    start = page * per_page
    end = start + per_page
    page_users = blocked[start:end]

    page_text = (
        f"📋 **Список заблокированных пользователей** (Страница {page + 1}/{total_pages}):\n\n" +
        "\n".join([
            f"{idx + start + 1}. @{uname if uname != 'No username' else uid} - `{uid}`\n📆 {'🔒 Навсегда' if blocked_until == '9999-12-31 23:59:59' else 'До ' + datetime.strptime(blocked_until, '%Y-%m-%d %H:%M:%S').strftime('%d.%m.%Y %H:%M:%S')}"
            for idx, (uid, uname, blocked_until) in enumerate(page_users)
        ])
    )
    await query.answer()
    await query.edit_message_text(page_text, parse_mode='Markdown')
    logger.info(f"Пользователь {query.from_user.id} переместился на страницу {page + 1} из {total_pages} в списке заблокированных.")

    # Добавление кнопок навигации
    buttons = []
    navigation_buttons = []
    if total_pages > 1:
        if page > 0:
            navigation_buttons.append(InlineKeyboardButton("⬅️ Назад", callback_data='blocked_prev_page'))
        if page < total_pages - 1:
            navigation_buttons.append(InlineKeyboardButton("Вперед ➡️", callback_data='blocked_next_page'))
    if navigation_buttons:
        buttons.append(navigation_buttons)
    buttons.append([InlineKeyboardButton("❌ Закрыть", callback_data='close')])
    reply_markup = InlineKeyboardMarkup(buttons)
    await query.edit_message_reply_markup(reply_markup)
    logger.info(f"Пользователь {query.from_user.id} перешёл на страницу {page + 1} из {total_pages} в списке заблокированных.")

async def send_blocked_users_list(query: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    user_id = query.from_user.id
    with sqlite3.connect('university.db') as conn:
        c = conn.cursor()
        c.execute("""SELECT u.id, u.username, b.blocked_until 
                     FROM blocked_users b 
                     JOIN users u ON b.id = u.id 
                     ORDER BY b.blocked_until DESC""")
        blocked = c.fetchall()

    if not blocked:
        await query.answer()
        await query.edit_message_text("📋 **Список заблокированных пользователей пуст.**", parse_mode='Markdown')
        logger.info("Запрошен пустой список заблокированных пользователей.")
        return

    # Разделение на страницы по 20 пользователей
    page = context.user_data.get('blocked_users_page', 0)
    per_page = 20
    total = len(blocked)
    total_pages = (total + per_page - 1) // per_page

    start = page * per_page
    end = start + per_page
    page_users = blocked[start:end]

    def get_page_text(page_num, page_users):
        text = f"📋 **Список заблокированных пользователей** (Страница {page_num + 1}/{total_pages}):\n\n"
        for idx, (uid, uname, blocked_until) in enumerate(page_users, start=1 + start):
            if blocked_until == '9999-12-31 23:59:59':
                remaining = "🔒 Навсегда"
            else:
                try:
                    blocked_until_dt = datetime.strptime(blocked_until, "%Y-%m-%d %H:%M:%S")
                    blocked_until_dt = pytz.timezone('Asia/Omsk').localize(blocked_until_dt)
                    remaining = f"До {blocked_until_dt.strftime('%d.%m.%Y %H:%M:%S')}"
                except:
                    remaining = blocked_until
            uname_display = uname if uname != "No username" else uid
            text += f"{idx}. @{uname_display} - `{uid}`\n📆 {remaining}\n\n"
        return text

    page_text = get_page_text(page, page_users)
    await query.answer()
    await query.edit_message_text(page_text, parse_mode='Markdown')
    logger.info(f"Пользователь {user_id} запросил список заблокированных пользователей (Страница {page + 1}/{total_pages}).")

    # Добавление кнопок навигации
    buttons = []
    navigation_buttons = []
    if total_pages > 1:
        if page > 0:
            navigation_buttons.append(InlineKeyboardButton("⬅️ Назад", callback_data='blocked_prev_page'))
        if page < total_pages - 1:
            navigation_buttons.append(InlineKeyboardButton("Вперед ➡️", callback_data='blocked_next_page'))
    if navigation_buttons:
        buttons.append(navigation_buttons)
    buttons.append([InlineKeyboardButton("❌ Закрыть", callback_data='close')])
    reply_markup = InlineKeyboardMarkup(buttons)
    await query.edit_message_reply_markup(reply_markup)
    logger.info(f"Пользователь {user_id} просмотрел страницу {page + 1} из {total_pages} в списке заблокированных.")

async def collect_announcement(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    if not context.user_data.get('is_sending_announcement', False):
        return

    message_text = update.message.text
    photo = None
    if update.message.photo:
        photo = update.message.photo[-1].file_id  # Берем самое высокое качество

    context.user_data['message_text'] = message_text
    context.user_data['photo'] = photo
    context.user_data['is_sending_announcement'] = False

    confirm_message = f"📤 **Вот ваш текст:**\n\n{message_text}\n\n❓ Хотите отправить?"
    buttons = [
        [
            InlineKeyboardButton("✅ Да", callback_data='confirm_send'),
            InlineKeyboardButton("❌ Нет", callback_data='cancel_send')
        ]
    ]
    reply_markup = InlineKeyboardMarkup(buttons)

    await update.message.reply_text(confirm_message, reply_markup=reply_markup, parse_mode='Markdown')
    logger.info(f"Пользователь {update.effective_user.id} подтвердил рассылку сообщения.")

async def confirm_send(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    query = update.callback_query
    data = query.data

    if data == 'confirm_send':
        message_text = context.user_data.get('message_text')
        photo = context.user_data.get('photo')
        announcement_for = context.user_data.get('announcement_for')
        sender_id = update.effective_user.id

        await query.answer("📨 Отправка начинается...")
        await send_announcement(context, announcement_for, message_text, photo, sender_id)
        await query.edit_message_text("✅ Рассылка успешно отправлена.")
        logger.info(f"Пользователь {sender_id} отправил рассылку.")

    elif data == 'cancel_send':
        await query.answer("❌ Рассылка отменена.")
        await query.edit_message_text("❌ Рассылка отменена.")
        logger.info(f"Пользователь {update.effective_user.id} отменил рассылку.")

    # Очистка данных
    context.user_data['message_text'] = None
    context.user_data['photo'] = None
    context.user_data['announcement_for'] = None

async def send_announcement(context: ContextTypes.DEFAULT_TYPE, announcement_for, message_text, photo, sender_id):
    with sqlite3.connect('university.db') as conn:
        c = conn.cursor()

        if announcement_for == 'global':
            c.execute("SELECT id FROM users WHERE subscribed_announcements = 1")
            subscribers = {row[0] for row in c.fetchall()}
            c.execute("SELECT id FROM admins")
            admins = {row[0] for row in c.fetchall()}
            all_recipients = subscribers | admins

        elif announcement_for == 'group':
            # Предполагается, что announcement_for содержит кортеж (course, direction)
            course, direction = context.user_data.get('announcement_for_course'), context.user_data.get('announcement_for_direction')
            c.execute("""SELECT id FROM users 
                         WHERE course = ? AND direction = ? AND subscribed_announcements = 1""",
                      (course, direction))
            subscribers = {row[0] for row in c.fetchall()}
            c.execute("SELECT id FROM admins")
            admins = {row[0] for row in c.fetchall()}
            all_recipients = subscribers | admins

        else:
            all_recipients = set()

    # Отправка сообщений
    for recipient in all_recipients:
        try:
            if photo:
                await context.bot.send_photo(chat_id=recipient, photo=photo, caption=message_text, parse_mode='Markdown')
            else:
                await context.bot.send_message(chat_id=recipient, text=message_text, parse_mode='Markdown')
            logger.info(f"Сообщение отправлено пользователю {recipient}.")
        except Exception as e:
            logger.error(f"❌ Не удалось отправить сообщение пользователю {recipient}: {e}")

    # Запись в таблицу announcements
    with sqlite3.connect('university.db') as conn:
        c = conn.cursor()
        c.execute("""INSERT INTO announcements (sender_id, message, photo) 
                     VALUES (?, ?, ?)""", 
                  (sender_id, message_text, photo))
        conn.commit()
    logger.info(f"Сообщение от пользователя {sender_id} записано в таблицу announcements.")

async def show_schedule(update: Update, context: ContextTypes.DEFAULT_TYPE, day_command: str) -> None:
    user_id = update.message.from_user.id

    with sqlite3.connect('university.db') as conn:
        c = conn.cursor()
        c.execute("SELECT course, direction FROM users WHERE id = ?", (user_id,))
        user_info = c.fetchone()

        if not user_info:
            await personal_cabinet(update, context)
            c.execute("SELECT course, direction FROM users WHERE id = ?", (user_id,))
            user_info = c.fetchone()

            if not user_info:
                await update.message.reply_text("❌ Ошибка при добавлении пользователя в базу данных.")
                logger.error(f"Не удалось добавить пользователя {user_id} в базу данных.")
                return

        course, direction = user_info

    current_date = datetime.now(pytz.timezone('Asia/Omsk'))

    schedule_command = day_command.lower()
    if schedule_command == "расписание на сегодня":
        target_date = current_date
    elif schedule_command == "расписание на завтра":
        target_date = current_date + timedelta(days=1)
    elif schedule_command in ["понедельник", "вторник", "среда", "четверг", "пятница", "суббота"]:
        days_map = {
            "понедельник": 0,
            "вторник": 1,
            "среда": 2,
            "четверг": 3,
            "пятница": 4,
            "суббота": 5
        }
        target_weekday = days_map[schedule_command]
        today_weekday = current_date.weekday()
        days_ahead = (target_weekday - today_weekday) % 7
        target_date = current_date + timedelta(days=days_ahead)
    else:
        await update.message.reply_text("❌ Неверный запрос. Выберите опцию из меню.")
        logger.warning(f"Пользователь {user_id} ввёл неверный запрос: {day_command}")
        return

    day_name = get_day_name(target_date.weekday())
    week_type_short = get_week_type_short(target_date)
    week_type = get_week_type(target_date)
    day_schedule = get_schedule_for_day(course, direction, week_type, day_name)

    message_text = f"📅 **{day_name} {target_date.strftime('%d.%m.%Y')} ({week_type_short})**:\n\n{day_schedule}"
    await update.message.reply_text(message_text, parse_mode='Markdown')
    logger.info(f"Пользователь {user_id} запросил расписание на {day_name} {target_date.strftime('%d.%m.%Y')}.")

async def show_schedule_by_date(update: Update, context: ContextTypes.DEFAULT_TYPE, date_obj: datetime) -> None:
    user_id = update.message.from_user.id

    with sqlite3.connect('university.db') as conn:
        c = conn.cursor()
        c.execute("""SELECT course, direction, 
                            subscribed_schedule_changes, subscribed_homework, subscribed_announcements, role 
                     FROM users WHERE id = ?""", (user_id,))
        user_info = c.fetchone()

    if not user_info:
        await personal_cabinet(update, context)
        with sqlite3.connect('university.db') as conn_retry:
            c_retry = conn_retry.cursor()
            c_retry.execute("""SELECT course, direction, 
                                subscribed_schedule_changes, subscribed_homework, subscribed_announcements, role 
                         FROM users WHERE id = ?""", (user_id,))
            user_info = c_retry.fetchone()

            if not user_info:
                await update.message.reply_text("❌ Ошибка при добавлении пользователя в базу данных.")
                logger.error(f"Не удалось добавить пользователя {user_id} в базу данных.")
                return

    course, direction, _, _, _, role = user_info

    day_name = get_day_name(date_obj.weekday())
    week_type_short = get_week_type_short(date_obj)
    week_type = get_week_type(date_obj)
    day_schedule = get_schedule_for_day(course, direction, week_type, day_name)

    message_text = f"📅 **{day_name} {date_obj.strftime('%d.%m.%Y')} ({week_type_short})**:\n\n{day_schedule}"
    await update.message.reply_text(message_text, parse_mode='Markdown')
    logger.info(f"Пользователь {user_id} запросил расписание на {day_name} {date_obj.strftime('%d.%m.%Y')}.")

# Шаг 6: Обработчики для рассылки объявлений и домашки

async def handle_admin_actions(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    user_id = update.message.from_user.id
    user_message = update.message.text.strip()

    action = context.user_data.get('action')
    if not action:
        await update.message.reply_text("❓ Нет активных действий. Выберите опцию из меню.")
        logger.warning(f"Пользователь {user_id} попытался выполнить админское действие без активного контекста.")
        return

    with sqlite3.connect('university.db') as conn:
        c = conn.cursor()
        if action == 'block_user':
            if not user_message.isdigit():
                await update.message.reply_text("❌ Неверный формат ID. Пожалуйста, укажите численный ID пользователя.")
                logger.warning(f"Администратор {user_id} ввёл неверный формат ID для блокировки: {user_message}")
                return
            target_id = int(user_message)
            if target_id == user_id:
                await update.message.reply_text("❌ Вы не можете заблокировать самого себя.")
                logger.warning(f"Администратор {user_id} попытался заблокировать себя.")
                return
            c.execute("SELECT 1 FROM users WHERE id = ?", (target_id,))
            if not c.fetchone():
                await update.message.reply_text("❌ Пользователь с таким ID не найден.")
                logger.warning(f"Администратор {user_id} попытался заблокировать несуществующего пользователя: {target_id}")
                return

            # Запрос длительности блокировки
            context.user_data['block_target_id'] = target_id
            buttons = [
                [
                    InlineKeyboardButton("🕒 1 час", callback_data='block_duration_1_hour'),
                    InlineKeyboardButton("🕐 1 день", callback_data='block_duration_1_day')
                ],
                [
                    InlineKeyboardButton("🕑 1 неделя", callback_data='block_duration_1_week'),
                    InlineKeyboardButton("🕙 1 год", callback_data='block_duration_1_year')
                ],
                [
                    InlineKeyboardButton("🔒 Навсегда", callback_data='block_duration_forever'),
                    InlineKeyboardButton("❌ Отмена", callback_data='cancel_action')
                ]
            ]
            reply_markup = InlineKeyboardMarkup(buttons)
            await update.message.reply_text("✅ Выберите длительность блокировки:", reply_markup=reply_markup)
            logger.info(f"Администратор {user_id} начал процесс блокировки пользователя {target_id}.")

        elif action == 'unblock_user':
            if not user_message.isdigit():
                await update.message.reply_text("❌ Неверный формат ID. Пожалуйста, укажите численный ID пользователя.")
                logger.warning(f"Администратор {user_id} ввёл неверный формат ID для разблокировки: {user_message}")
                return
            target_id = int(user_message)
            c.execute("SELECT 1 FROM blocked_users WHERE id = ?", (target_id,))
            if not c.fetchone():
                await update.message.reply_text("❌ Этот пользователь не заблокирован.")
                logger.warning(f"Администратор {user_id} попытался разблокировать не заблокированного пользователя: {target_id}")
                return
            unblock_user(target_id)
            await update.message.reply_text(f"✅ Пользователь {target_id} успешно разблокирован.")
            logger.info(f"Администратор {user_id} разблокировал пользователя {target_id}.")

        elif action == 'add_homework_sender':
            if not user_message.isdigit():
                await update.message.reply_text("❌ Неверный формат ID. Пожалуйста, укажите численный ID пользователя.")
                logger.warning(f"Администратор {user_id} ввёл неверный формат ID для добавления рассыльщика: {user_message}")
                return
            target_id = int(user_message)
            if is_homework_sender(target_id):
                await update.message.reply_text("❌ Пользователь уже является рассыльщиком домашки.")
                logger.info(f"Администратор {user_id} попытался добавить уже существующего рассыльщика: {target_id}")
                return
            c.execute("SELECT 1 FROM users WHERE id = ?", (target_id,))
            if not c.fetchone():
                await update.message.reply_text("❌ Пользователь с таким ID не найден.")
                logger.warning(f"Администратор {user_id} попытался добавить несуществующего пользователя в рассыльщики: {target_id}")
                return
            c.execute("INSERT INTO homework_senders (id) VALUES (?)", (target_id,))
            conn.commit()
            await update.message.reply_text(f"✅ Пользователь {target_id} добавлен в рассыльщики домашки.")
            logger.info(f"Администратор {user_id} добавил пользователя {target_id} в рассыльщики домашки.")

        elif action == 'remove_homework_sender':
            if not user_message.isdigit():
                await update.message.reply_text("❌ Неверный формат ID. Пожалуйста, укажите численный ID пользователя.")
                logger.warning(f"Администратор {user_id} ввёл неверный формат ID для удаления рассыльщика: {user_message}")
                return
            target_id = int(user_message)
            if not is_homework_sender(target_id):
                await update.message.reply_text("❌ Пользователь не является рассыльщиком домашки.")
                logger.info(f"Администратор {user_id} попытался удалить нерассылщика: {target_id}")
                return
            c.execute("DELETE FROM homework_senders WHERE id = ?", (target_id,))
            conn.commit()
            await update.message.reply_text(f"✅ Пользователь {target_id} удалён из рассыльщиков домашки.")
            logger.info(f"Администратор {user_id} удалил пользователя {target_id} из рассыльщиков домашки.")

    # Очистка действия
    context.user_data['action'] = None
    await personal_cabinet(update, context)
    logger.info(f"Администратор {user_id} завершил действие '{action}'.")

async def error_handler(update: object, context: ContextTypes.DEFAULT_TYPE) -> None:
    logger.error(msg="Exception while handling an update:", exc_info=context.error)
    
    # Получение списка администраторов из базы данных
    try:
        with sqlite3.connect('university.db') as conn:
            c = conn.cursor()
            c.execute("SELECT id FROM admins")
            admin_ids = [row[0] for row in c.fetchall()]
    except Exception as db_error:
        logger.error(f"Не удалось получить admin_ids из базы данных: {db_error}")
        admin_ids = []
    
    # Отправка сообщения всем администраторам о произошедшей ошибке
    for admin_id in admin_ids:
        try:
            await context.bot.send_message(
                chat_id=admin_id,
                text=f"❌ Произошла ошибка:\n\n{traceback.format_exc()}"
            )
            logger.info(f"Сообщение об ошибке отправлено администратору {admin_id}.")
        except Exception as e:
            logger.error(f"Не удалось отправить сообщение администратору {admin_id}: {e}")

async def notify_schedule_changes(application: ContextTypes.DEFAULT_TYPE) -> None:
    with sqlite3.connect('university.db') as conn:
        c = conn.cursor()
        # Получаем все непросмотренные изменения расписания
        c.execute("SELECT id, course, direction, week_type, day, slot, old_details, new_details FROM schedule_changes WHERE notified = 0")
        changes = c.fetchall()

    for change in changes:
        change_id, course, direction, week_type, day, slot, old_details, new_details = change
        # Получаем пользователей, подписанных на изменения расписания
        with sqlite3.connect('university.db') as conn_subs:
            c_subs = conn_subs.cursor()
            c_subs.execute("""SELECT id FROM users 
                             WHERE course = ? AND direction = ? AND subscribed_schedule_changes = 1""",
                          (course, direction))
            subscribers = {row[0] for row in c_subs.fetchall()}

        day_name = day  # Предполагается, что day уже на русском
        message_text = (
            f"📅 **Расписание на {day_name} изменилось!**\n\n"
            f"**Старая информация:**\n{old_details}\n\n"
            f"**Новая информация:**\n{new_details}"
        )

        for subscriber in subscribers:
            try:
                await application.bot.send_message(chat_id=subscriber, text=message_text, parse_mode='Markdown')
                logger.info(f"Уведомление отправлено пользователю {subscriber}.")
            except Exception as e:
                logger.error(f"❌ Не удалось отправить сообщение пользователю {subscriber}: {e}")

        # Помечаем изменения как уведомленные
        with sqlite3.connect('university.db') as conn_update:
            c_update = conn_update.cursor()
            c_update.execute("UPDATE schedule_changes SET notified = 1 WHERE id = ?", (change_id,))
            conn_update.commit()
            logger.info(f"Изменение расписания ID={change_id} помечено как уведомленное.")

def schedule_jobs(application):
    scheduler = AsyncIOScheduler(timezone='Asia/Omsk')
    scheduler.add_job(check_schedule_changes, 'interval', hours=1, id='check_schedule_changes')
    scheduler.add_job(notify_schedule_changes, 'interval', minutes=5, args=[application], id='notify_schedule_changes')
    scheduler.start()
    logger.info("Планировщик запущен.")

def check_schedule_changes():
    # Реализуйте логику проверки изменений расписания здесь
    pass

# Функция для планировщика уже интегрирована

# Шаг 10: Основная функция запуска бота
if __name__ == '__main__':
    application = ApplicationBuilder().token(TOKEN).build()

    # Добавляем обработчики
    application.add_handler(CommandHandler("start", start))
    application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_message))
    application.add_handler(CallbackQueryHandler(handle_callback, pattern='^(change_course|change_direction|manage_notifications|add_homework_sender|remove_homework_sender|block_user|unblock_user|send_announcement|send_global_announcement|view_statistics|list_blocked_users|blocked_prev_page|blocked_next_page|confirm_send|cancel_send|close)$'))
    application.add_handler(CallbackQueryHandler(handle_block_duration, pattern='^block_duration_.*|^cancel_action$'))

    # Добавляем обработчик ошибок
    application.add_error_handler(error_handler)

    # Запуск планировщика
    schedule_jobs(application)

    # Запуск бота
    application.run_polling()