Привет! Реши проблему ниже с моим кодом, который я вышлю после ошибки.

Ошибка:
No error handlers are registered, logging exception.
Traceback (most recent call last):
  File "/data/user/0/ru.iiec.pydroid3/files/aarch64-linux-android/lib/python3.11/site-packages/telegram/ext/_application.py", line 1335, in process_update
    await coroutine
  File "/data/user/0/ru.iiec.pydroid3/files/aarch64-linux-android/lib/python3.11/site-packages/telegram/ext/_handlers/basehandler.py", line 158, in handle_update
    return await self.callback(update, context)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<string>", line 940, in start
  File "<string>", line 153, in personal_cabinet
NameError: name 'get_week_type' is not defined
No error handlers are registered, logging exception.
Traceback (most recent call last):
  File "/data/user/0/ru.iiec.pydroid3/files/aarch64-linux-android/lib/python3.11/site-packages/telegram/ext/_application.py", line 1335, in process_update
    await coroutine
  File "/data/user/0/ru.iiec.pydroid3/files/aarch64-linux-android/lib/python3.11/site-packages/telegram/ext/_handlers/basehandler.py", line 158, in handle_update
    return await self.callback(update, context)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<string>", line 866, in handle_message
NameError: name 'show_schedule' is not defined
No error handlers are registered, logging exception.
Traceback (most recent call last):
  File "/data/user/0/ru.iiec.pydroid3/files/aarch64-linux-android/lib/python3.11/site-packages/telegram/ext/_application.py", line 1335, in process_update
    await coroutine
  File "/data/user/0/ru.iiec.pydroid3/files/aarch64-linux-android/lib/python3.11/site-packages/telegram/ext/_handlers/basehandler.py", line 158, in handle_update
    return await self.callback(update, context)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<string>", line 873, in handle_message
  File "<string>", line 153, in personal_cabinet
NameError: name 'get_week_type' is not defined

Код:
import os
import pytz
import sqlite3
from datetime import datetime, timedelta
from telegram import (
    Update,
    ReplyKeyboardMarkup,
    InlineKeyboardMarkup,
    InlineKeyboardButton,
    InputMediaPhoto
)
from telegram.ext import (
    ApplicationBuilder,
    CommandHandler,
    ContextTypes,
    MessageHandler,
    filters,
    CallbackQueryHandler,
    CallbackContext
)
from dotenv import load_dotenv
from apscheduler.schedulers.asyncio import AsyncIOScheduler

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

# Инициализация базы данных с добавленными столбцами для уведомлений и ролей
def init_db():
    conn = sqlite3.connect('university.db')
    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')''')  # Добавлено поле role
    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)''')
    
    # Остальной код инициализации расписания...
    # Здесь вы можете заполнить расписание, как у вас было ранее
    
    conn.commit()
    conn.close()

init_db()

# Функция для проверки блокировки пользователя
def is_user_blocked(user_id):
    conn = sqlite3.connect('university.db')
    c = conn.cursor()
    c.execute("SELECT blocked_until FROM blocked_users WHERE id = ?", (user_id,))
    result = c.fetchone()
    conn.close()
    if result:
        blocked_until = result[0]
        if blocked_until == '9999-12-31 23:59:59':
            return blocked_until
        blocked_until_dt = datetime.strptime(blocked_until, "%Y-%m-%d %H:%M:%S")
        if blocked_until_dt > datetime.now():
            return blocked_until_dt
        else:
            # Автоматически разблокировать пользователя, если время блокировки истекло
            unblock_user(user_id)
    return None

# Функция для разблокировки пользователя
def unblock_user(user_id):
    conn = sqlite3.connect('university.db')
    c = conn.cursor()
    c.execute("DELETE FROM blocked_users WHERE id = ?", (user_id,))
    conn.commit()
    conn.close()

# Функция для проверки, является ли пользователь администратором
def is_admin_user(user_id):
    conn = sqlite3.connect('university.db')
    c = conn.cursor()
    c.execute("SELECT 1 FROM admins WHERE id = ?", (user_id,))
    is_admin = c.fetchone() is not None
    conn.close()
    return is_admin

# Функция для проверки, является ли пользователь рассыльщиком домашки
def is_homework_sender(user_id):
    conn = sqlite3.connect('university.db')
    c = conn.cursor()
    c.execute("SELECT 1 FROM homework_senders WHERE id = ?", (user_id,))
    is_sender = c.fetchone() is not None
    conn.close()
    return is_sender

# Обновленная функция personal_cabinet с кнопкой "Уведомления" и разделом для админов
async def personal_cabinet(update: Update, context: ContextTypes.DEFAULT_TYPE):
    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 ""

    conn = sqlite3.connect('university.db')
    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')
    else:
        c.execute("UPDATE users SET username = ? WHERE id = ?", (username, user_id))
        conn.commit()

    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)
        conn.close()
        return

    conn.close()

    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 = "Отписаться от уведомлений университета" if user[3] or user[4] or 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')}"
    )

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

    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')

# Обработчик кнопок личного кабинета и админ-панели
async def handle_callback(update: Update, context: CallbackContext):
    query = update.callback_query
    user_id = query.from_user.id
    data = query.data
    conn = sqlite3.connect('university.db')
    c = conn.cursor()

    if data == 'change_course':
        c.execute("SELECT course FROM users WHERE id = ?", (user_id,))
        current_course = c.fetchone()[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()
        await query.answer(f"Курс изменён на {new_course}")
        await personal_cabinet(update, context)

    elif data == 'change_direction':
        c.execute("SELECT direction FROM users WHERE id = ?", (user_id,))
        current_direction = c.fetchone()[0]
        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()
        await query.answer(f"Группа изменена на {new_direction}")
        await personal_cabinet(update, context)

    elif data == 'toggle_subscription':
        # Переключение подписок
        c.execute("""UPDATE users 
                     SET 
                         subscribed_schedule_changes = 1 - subscribed_schedule_changes,
                         subscribed_homework = 1 - subscribed_homework,
                         subscribed_announcements = 1 - subscribed_announcements 
                     WHERE id = ?""", (user_id,))
        conn.commit()
        await query.answer("Подписки обновлены")
        await personal_cabinet(update, context)

    elif data == 'manage_notifications':
        # Отправка меню управления уведомлениями
        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("Выберите категории уведомлений:", reply_markup=reply_markup)

    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("Подписка на домашнее задание обновлена")
        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("Подписка на изменение расписания обновлена")
        elif data == 'toggle_announcement_subscription':
            c.execute("UPDATE users SET subscribed_announcements = 1 - subscribed_announcements WHERE id = ?", (user_id,))
            conn.commit()
            await query.answer("Подписка на объявления обновлена")
        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')

    elif data == 'view_statistics' and is_admin_user(user_id):
        # Собираем статистику
        total_users = c.execute("SELECT COUNT(*) FROM users").fetchone()[0]
        new_today = c.execute("""
            SELECT COUNT(*) FROM users 
            WHERE DATE(datetime('now', 'localtime')) = DATE(datetime('now', 'localtime'), 'start of day')
        """).fetchone()[0]
        new_yesterday = c.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.execute("""
            SELECT COUNT(DISTINCT id) FROM schedule_changes 
            WHERE DATE(changed_at, 'localtime') = DATE('now', 'localtime')
        """).fetchone()[0]
        active_yesterday = c.execute("""
            SELECT COUNT(DISTINCT id) FROM schedule_changes 
            WHERE DATE(changed_at, 'localtime') = DATE('now', 'localtime', '-1 day')
        """).fetchone()[0]
        most_active_group = c.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')

    elif data == 'add_homework_sender' and is_admin_user(user_id):
        context.user_data['action'] = 'add_homework_sender'
        await query.answer("Введите @id пользователя для добавления в рассыльщики домашки:")
        await query.edit_message_text("Введите @id пользователя для добавления в рассыльщики домашки:")

    elif data == 'remove_homework_sender' and is_admin_user(user_id):
        context.user_data['action'] = 'remove_homework_sender'
        await query.answer("Введите @id пользователя для удаления из рассыльщиков домашки:")
        await query.edit_message_text("Введите @id пользователя для удаления из рассыльщиков домашки:")

    elif data == 'block_user' and is_admin_user(user_id):
        context.user_data['action'] = 'block_user'
        await query.answer("Введите @id пользователя для блокировки:")
        await query.edit_message_text("Введите @id пользователя для блокировки:")

    elif data == 'unblock_user' and is_admin_user(user_id):
        context.user_data['action'] = 'unblock_user'
        await query.answer("Введите @id пользователя для разблокировки:")
        await query.edit_message_text("Введите @id пользователя для разблокировки:")

    elif data == 'list_blocked_users' and is_admin_user(user_id):
        await send_blocked_users_list(update, context)

    elif data == 'send_announcement' and is_admin_user(user_id):
        context.user_data['announcement_for'] = 'group'
        context.user_data['is_sending_announcement'] = True
        context.user_data['announcement_type'] = 'announcement'
        await query.answer("Введите сообщение для рассылки вашей группе:")
        await query.edit_message_text("Введите сообщение для рассылки вашей группе:\n\n*Вы можете отправить фотографию вместе с сообщением.*", parse_mode='Markdown')

    elif data == 'send_global_announcement' and is_admin_user(user_id):
        context.user_data['announcement_for'] = 'global'
        context.user_data['is_sending_announcement'] = True
        context.user_data['announcement_type'] = 'announcement'
        await query.answer("Введите сообщение для глобальной рассылки:")
        await query.edit_message_text("Введите сообщение для глобальной рассылки:\n\n*Вы можете отправить фотографию вместе с сообщением.*", parse_mode='Markdown')

    elif data == 'send_homework' and is_homework_sender(user_id):
        context.user_data['announcement_for'] = 'homework'
        context.user_data['is_sending_announcement'] = True
        context.user_data['announcement_type'] = 'homework'
        await query.answer("Введите сообщение для рассылки домашки:")
        await query.edit_message_text("Введите сообщение для рассылки домашки:\n\n*Вы можете отправить фотографию вместе с сообщением.*", parse_mode='Markdown')

    elif data == 'back_to_cabinet':
        await personal_cabinet(update, context)

    conn.close()
    return

# Обработчик текстовых сообщений для админских действий и рассыльщиков домашки
async def handle_admin_actions(update: Update, context: ContextTypes.DEFAULT_TYPE):
    user_id = update.message.from_user.id
    user_message = update.message.text.strip()
    conn = sqlite3.connect('university.db')
    c = conn.cursor()
    action = context.user_data.get('action')

    if action == 'block_user':
        user_input = user_message.lstrip('@')
        if not user_input.isdigit():
            await update.message.reply_text("Неверный формат @id. Пожалуйста, укажите численный ID пользователя.")
            return
        target_id = int(user_input)
        c.execute("SELECT 1 FROM users WHERE id = ?", (target_id,))
        if not c.fetchone():
            await update.message.reply_text("Пользователь с таким 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)

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

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

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

    context.user_data['action'] = None
    conn.close()

# Обработчик кнопок длительности блокировки
async def handle_block_duration(update: Update, context: CallbackContext):
    query = update.callback_query
    data = query.data
    user_id = query.from_user.id
    conn = sqlite3.connect('university.db')
    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("Ошибка: целевой пользователь не найден.")
            return

        if delta:
            blocked_until = datetime.now() + 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')}.")
        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} заблокирован навсегда.")

        conn.commit()
        await query.edit_message_text(f"Пользователь {target_id} успешно заблокирован.")
    
    elif data == 'cancel_action':
        await query.answer("Действие отменено.")
        await query.edit_message_text("Действие отменено.")

    conn.close()

# Функция для отправки списка заблокированных пользователей с пагинацией
async def send_blocked_users_list(update: Update, context: ContextTypes.DEFAULT_TYPE):
    query = update.callback_query
    user_id = query.from_user.id
    conn = sqlite3.connect('university.db')
    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()
    conn.close()

    if not blocked:
        await query.answer()
        await query.edit_message_text("Список заблокированных пользователей пуст.")
        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):
        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:
                blocked_until_dt = datetime.strptime(blocked_until, "%Y-%m-%d %H:%M:%S")
                remaining = f"{blocked_until_dt.strftime('%d.%m.%Y %H:%M:%S')}"
            text += f"{idx}. @{uname} - {uid}\nДо разблокировки: {remaining}\n\n"
        return text

    page_text = get_page_text(page)
    await query.answer()
    await query.edit_message_text(page_text, parse_mode='Markdown')

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

# Обработчик навигации по страницам заблокированных пользователей
async def handle_blocked_users_pagination(update: Update, context: CallbackContext):
    query = update.callback_query
    data = query.data
    user_id = query.from_user.id

    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)
    conn = sqlite3.connect('university.db')
    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()
        return
    else:
        await query.answer("Нет дальнейших страниц.")
        return

    context.user_data['blocked_users_page'] = page
    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()
    conn.close()

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

    def get_page_text(page_num):
        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:
                blocked_until_dt = datetime.strptime(blocked_until, "%Y-%m-%d %H:%M:%S")
                remaining = f"{blocked_until_dt.strftime('%d.%m.%Y %H:%M:%S')}"
            text += f"{idx}. @{uname} - {uid}\nДо разблокировки: {remaining}\n\n"
        return text

    page_text = get_page_text(page)
    await query.answer()
    await query.edit_message_text(page_text, parse_mode='Markdown')

    # Обновление кнопок навигации
    buttons = []
    if total_pages > 1:
        if page > 0:
            buttons.append(InlineKeyboardButton("⬅️ Назад", callback_data='blocked_prev_page'))
        if page < total_pages - 1:
            buttons.append(InlineKeyboardButton("Вперед ➡️", callback_data='blocked_next_page'))
    buttons.append(InlineKeyboardButton("Закрыть", callback_data='close'))
    reply_markup = InlineKeyboardMarkup([buttons])
    await query.edit_message_reply_markup(reply_markup)

# Функция для обработки рассылки объявлений и домашки
async def collect_announcement(update: Update, context: ContextTypes.DEFAULT_TYPE):
    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)

# Функция отправки объявлений и домашки с поддержкой фотографий
async def send_announcement(context: CallbackContext):
    announcement_for = context.job.context.get('announcement_for')
    message_text = context.job.context.get('message_text')
    photo = context.job.context.get('photo')
    announcement_type = context.job.context.get('announcement_type')

    if not announcement_for or not message_text:
        return

    context.job.context['announcement_for'] = None
    context.job.context['message_text'] = None
    context.job.context['photo'] = None
    context.job.context['announcement_type'] = None

    conn = sqlite3.connect('university.db')
    c = conn.cursor()

    if announcement_for == 'global':
        c.execute("SELECT id FROM users WHERE subscribed_announcements = 1")
        subscribers = c.fetchall()
        c.execute("SELECT id FROM admins")
        admins = c.fetchall()
    elif announcement_for == 'homework':
        c.execute("SELECT id FROM homework_senders")
        senders = c.fetchall()
        # Предполагается, что рассыльщики домашки отправляют только своей группе
        # Здесь необходимо определить, какую группу они представляют
        # Для упрощения предположим, что каждая рассыльщик имеет одну группу
        c.execute("SELECT course, direction FROM users WHERE id = ?", (context.job.context.get('sender_id'),))
        sender_info = c.fetchone()
        if sender_info:
            course, direction = sender_info
            c.execute("""SELECT id FROM users 
                         WHERE course = ? AND direction = ? AND subscribed_homework = 1""",
                      (course, direction))
            subscribers = c.fetchall()
            c.execute("SELECT id FROM admins")
            admins = c.fetchall()
        else:
            subscribers = []
            admins = []
    else:
        # Для группировки, если необходимо
        course, direction = announcement_for
        c.execute("""SELECT id FROM users 
                     WHERE course = ? AND direction = ? AND subscribed_announcements = 1""",
                  (course, direction))
        subscribers = c.fetchall()
        c.execute("SELECT id FROM admins")
        admins = c.fetchall()

    conn.close()

    if announcement_for == 'homework':
        all_recipients = set(subscribers)  # Только пользователи
    else:
        all_recipients = set(subscribers) | set(admins)  # Пользователи и админы

    for recipient in all_recipients:
        try:
            if photo:
                await context.bot.send_photo(chat_id=recipient[0], photo=photo, caption=message_text)
            else:
                await context.bot.send_message(chat_id=recipient[0], text=message_text)
        except Exception as e:
            print(f"Не удалось отправить сообщение пользователю {recipient[0]}: {e}")

    # Запись в таблицу announcements
    conn = sqlite3.connect('university.db')
    c = conn.cursor()
    c.execute("""INSERT INTO announcements (sender_id, message, photo) 
                 VALUES (?, ?, ?)""", 
              (context.job.context.get('sender_id', None), message_text, photo))
    conn.commit()
    conn.close()

# Функция отправки уведомлений о смене расписания
async def notify_schedule_changes(context: CallbackContext):
    conn = sqlite3.connect('university.db')
    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
        # Получаем пользователей, подписанных на изменения расписания
        c.execute("""SELECT id FROM users 
                     WHERE course = ? AND direction = ? AND subscribed_schedule_changes = 1""",
                  (course, direction))
        subscribers = c.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 context.bot.send_message(chat_id=subscriber[0], text=message_text, parse_mode='Markdown')
            except Exception as e:
                print(f"Не удалось отправить сообщение пользователю {subscriber[0]}: {e}")

        # Помечаем изменения как уведомленные
        c.execute("UPDATE schedule_changes SET notified = 1 WHERE id = ?", (change_id,))
    
    conn.commit()
    conn.close()

# Функция для добавления новых расписаний и фиксации изменений
def check_schedule_changes():
    conn = sqlite3.connect('university.db')
    c = conn.cursor()
    
    # Получаем текущее расписание
    c.execute("SELECT course, direction, week_type, day, slot, details FROM schedule")
    current_schedule = c.fetchall()
    
    # Получаем последние изменения расписания
    c.execute("SELECT course, direction, week_type, day, slot, details FROM schedule_changes ORDER BY changed_at DESC LIMIT 100")
    previous_changes = c.fetchall()
    
    # Преобразуем списки в множества для удобства поиска изменений
    current_set = set(current_schedule)
    previous_set = set(previous_changes)
    
    # Определяем изменения
    added = current_set - previous_set
    removed = previous_set - current_set

    for entry in added:
        course, direction, week_type, day, slot, details = entry
        c.execute("""INSERT INTO schedule_changes 
                     (course, direction, week_type, day, slot, old_details, new_details) 
                     VALUES (?, ?, ?, ?, ?, ?, ?)""",
                  (course, direction, week_type, day, slot, "", details))
    
    for entry in removed:
        course, direction, week_type, day, slot, details = entry
        c.execute("""INSERT INTO schedule_changes 
                     (course, direction, week_type, day, slot, old_details, new_details) 
                     VALUES (?, ?, ?, ?, ?, ?, ?)""",
                  (course, direction, week_type, day, slot, details, ""))
    
    conn.commit()
    conn.close()

# Функция отправки домашки
async def send_homework_button(update: Update, context: ContextTypes.DEFAULT_TYPE):
    # Эта функция не нужна, так как кнопка "Отправить домашку" обрабатывается в handle_callback
    pass

# Обработчик сообщений для админских действий и проверок
async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    user_id = update.message.from_user.id
    user_message = update.message.text.strip()

    # Проверка блокировки пользователя
    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)
        return

    if context.user_data.get('awaiting_date', False):
        if user_message.isdigit():
            day = int(user_message)
            current_month = datetime.now(pytz.timezone('Asia/Omsk')).month
            current_year = datetime.now(pytz.timezone('Asia/Omsk')).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)
            except ValueError:
                await update.message.reply_text('Неверный день. Пожалуйста, введи число от 1 до последнего дня месяца.')
            finally:
                context.user_data['awaiting_date'] = False
        else:
            await update.message.reply_text('Пожалуйста, введи число для получения расписания.')
        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

    if user_message.lower() in ["расписание на сегодня", "расписание на завтра", 
                                "понедельник", "вторник", "среда",
                                "четверг", "пятница", "суббота"]:
        await show_schedule(update, context, user_message)

    elif user_message.lower() == "выбрать дату расписания":
        context.user_data['awaiting_date'] = True
        await update.message.reply_text("Введи необходимое число этого месяца:")

    elif user_message.lower() == "личный кабинет":
        await personal_cabinet(update, context)

    else:
        await update.message.reply_text('Выбери опцию из меню.')

# Функция отправки расписания по дате (без изменений)
async def show_schedule_by_date(update: Update, context: ContextTypes.DEFAULT_TYPE, date_obj):
    user_id = update.message.from_user.id

    conn = sqlite3.connect('university.db')
    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)
        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 update.message.reply_text("Ошибка при добавлении пользователя в базу данных.")
            return

    course, direction, _, _, _, role = user_info
    conn.close()

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

    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)

# Функция для получения расписания
def get_schedule_for_day(course, direction, day_name, date):
    week_type = get_week_type(date)
    conn = sqlite3.connect('university.db')
    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_name)
    )
    results = c.fetchall()
    conn.close()

    if not results:
        return "Нет занятий"

    day_schedule = ""
    for slot, time, details in results:
        if details:
            day_schedule += f"{slot}. {time}\n{details}\n\n"

    return day_schedule.strip() if day_schedule else "Нет занятий"

# Функция для отправки списка заблокированных пользователей с пагинацией
async def send_blocked_users_list(update: Update, context: ContextTypes.DEFAULT_TYPE):
    await send_blocked_users_list(update, context)

# Функция главного старта бота
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    await update.message.reply_text('Привет! Поменяй группу и курс на соответствующие в своём личном кабинете.')
    await personal_cabinet(update, context)

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

    reply_markup = ReplyKeyboardMarkup(keyboard, one_time_keyboard=True, resize_keyboard=True)

    await update.message.reply_text('Выбери опцию:', reply_markup=reply_markup)

# Функция отправки домашки
async def send_homework(update: Update, context: ContextTypes.DEFAULT_TYPE):
    # Эта функция не используется, так как рассылка обрабатывается через объявления
    pass

# Основная функция запуска бота
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))
    application.add_handler(CallbackQueryHandler(handle_blocked_users_pagination, pattern='blocked_(prev|next)_page'))
    application.add_handler(MessageHandler(filters.TEXT & (filters.Regex("^@?\d+$")), handle_admin_actions))
    application.add_handler(CallbackQueryHandler(handle_block_duration, pattern='block_duration_.*|cancel_action'))

    # Запуск планировщика
    scheduler = AsyncIOScheduler()
    scheduler.add_job(check_schedule_changes, 'interval', hours=1)  # Проверка каждый час
    scheduler.add_job(notify_schedule_changes, 'interval', minutes=5, args=[application.job_queue])  # Отправка уведомлений каждые 5 минут
    scheduler.start()

    application.run_polling()

Прошу, проверь мой код и на другие возможные ошибки. Вышли полный код