Привет! Реши проблему ниже с моим кодом, который я вышлю после ошибки. Сделай так, чтобы admin_id запрашивался в базе данных, а не в файле .env

Ошибка:
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 33, in <module>
ValueError: ID администратора не найден в переменных окружения. Пожалуйста, добавьте ADMIN_ID в файл .env

[Program finished]

Код:
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
import logging
import traceback

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

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

# Инициализация базы данных с добавленными столбцами для уведомлений и ролей
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')''')  # Добавлено поле 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()

init_db()

# Функция для получения названия дня недели на русском
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
        blocked_until_dt = pytz.timezone('Asia/Omsk').localize(datetime.strptime(blocked_until, "%Y-%m-%d %H:%M:%S"))
        if blocked_until_dt > datetime.now(pytz.timezone('Asia/Omsk')):
            return blocked_until_dt
        else:
            # Автоматически разблокировать пользователя, если время блокировки истекло
            unblock_user(user_id)
    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()

# Функция для проверки, является ли пользователь администратором
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

# Добавим обработчик ошибок
async def error_handler(update: object, context: ContextTypes.DEFAULT_TYPE) -> None:
    logger.error(msg="Exception while handling an update:", exc_info=context.error)
    
    # Отправка сообщения администратору о произошедшей ошибке
    if ADMIN_ID:
        try:
            await context.bot.send_message(
                chat_id=int(ADMIN_ID),
                text=f"❌ Произошла ошибка:\n\n{traceback.format_exc()}"
            )
        except Exception as e:
            logger.error(f"Не удалось отправить сообщение администратору: {e}")

# Обновлённая функция 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 ""

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

# Обработчик кнопок личного кабинета и админ-панели
async def handle_callback(update: Update, context: CallbackContext):
    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,))
            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 == 'manage_notifications':
            # Отправка меню управления уведомлениями с отображением текущего состояния
            user_id = query.from_user.id
            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')
            else:
                await query.answer("❌ Ошибка при получении подписок.")
        
        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):
            # Собираем статистику
            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')

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

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

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

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

    # Обработчик текстовых сообщений для админских действий и рассыльщиков домашки
    async def handle_admin_actions(update: Update, context: ContextTypes.DEFAULT_TYPE):
        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("❓ Нет активных действий. Выберите опцию из меню.")
            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 пользователя.")
                    return
                target_id = int(user_message)
                if target_id == user_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

                # Запрос длительности блокировки
                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':
                if not user_message.isdigit():
                    await update.message.reply_text("❌ Неверный формат ID. Пожалуйста, укажите численный ID пользователя.")
                    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("❌ Этот пользователь не заблокирован.")
                    return
                unblock_user(target_id)
                await update.message.reply_text(f"✅ Пользователь {target_id} успешно разблокирован.")

            elif action == 'add_homework_sender':
                if not user_message.isdigit():
                    await update.message.reply_text("❌ Неверный формат ID. Пожалуйста, укажите численный ID пользователя.")
                    return
                target_id = int(user_message)
                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':
                if not user_message.isdigit():
                    await update.message.reply_text("❌ Неверный формат ID. Пожалуйста, укажите численный ID пользователя.")
                    return
                target_id = int(user_message)
                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
        await personal_cabinet(update, context)

# Обработчик кнопок длительности блокировки
async def handle_block_duration(update: Update, context: CallbackContext):
    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("❌ Ошибка: целевой пользователь не найден.")
                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')}.")

            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("✅ Действие отменено.")

# Функция для отправки списка заблокированных пользователей с пагинацией
async def send_blocked_users_list(query, context):
    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')
        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:
                try:
                    blocked_until_dt = pytz.timezone('Asia/Omsk').localize(datetime.strptime(blocked_until, "%Y-%m-%d %H:%M:%S"))
                    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)
    await query.answer()
    await query.edit_message_text(page_text, parse_mode='Markdown')

    # Добавление кнопок навигации
    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)

# Обработчик навигации по страницам заблокированных пользователей
async def handle_blocked_users_pagination(update: Update, context: CallbackContext):
    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()
        return
    else:
        await query.answer("❌ Нет дальнейших страниц.")
        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]

    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:
                try:
                    blocked_until_dt = pytz.timezone('Asia/Omsk').localize(datetime.strptime(blocked_until, "%Y-%m-%d %H:%M:%S"))
                    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)
    await query.answer()
    await query.edit_message_text(page_text, parse_mode='Markdown')

    # Добавление кнопок навигации
    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)

# Функция для обработки рассылки объявлений и домашки
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 confirm_send(update: Update, context: ContextTypes.DEFAULT_TYPE):
    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')
        announcement_type = context.user_data.get('announcement_type')
        sender_id = update.effective_user.id

        await query.answer("📨 Отправка начинается...")
        await send_announcement(context.bot, announcement_for, message_text, photo, announcement_type, sender_id)
        await query.edit_message_text("✅ Рассылка успешно отправлена.")

    elif data == 'cancel_send':
        await query.answer("❌ Рассылка отменена.")
        await query.edit_message_text("❌ Рассылка отменена.")

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

# Функция отправки объявлений и домашки с поддержкой фотографий
async def send_announcement(bot, announcement_for, message_text, photo, announcement_type, 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 == 'homework':
            c.execute("SELECT id FROM homework_senders")
            senders = {row[0] for row in c.fetchall()}
            all_recipients = senders

        else:
            # Для группировки (если необходимо)
            course, direction = announcement_for
            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

    # Отправка сообщений
    for recipient in all_recipients:
        try:
            if photo:
                await bot.send_photo(chat_id=recipient, photo=photo, caption=message_text, parse_mode='Markdown')
            else:
                await bot.send_message(chat_id=recipient, text=message_text, parse_mode='Markdown')
        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()

# Функция отправки уведомлений о смене расписания
async def notify_schedule_changes(context: CallbackContext):
    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 context.bot.send_message(chat_id=subscriber, text=message_text, parse_mode='Markdown')
            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()

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

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

# Функция отправки расписания по дате
async def show_schedule_by_date(update: Update, context: ContextTypes.DEFAULT_TYPE, date_obj):
    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("❌ Ошибка при добавлении пользователя в базу данных.")
                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')

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

    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 show_schedule(update: Update, context: ContextTypes.DEFAULT_TYPE, user_message):
    if user_message.startswith("📅 Расписание"):
        user_message_lower = user_message.lower().replace("📅 ", "")
    elif user_message.startswith("📆 "):
        user_message_lower = user_message.lower().replace("📆 ", "")
    else:
        user_message_lower = user_message.lower()

    if "на сегодня" in user_message_lower:
        date_obj = datetime.now(pytz.timezone('Asia/Omsk'))
    elif "на завтра" in user_message_lower:
        date_obj = datetime.now(pytz.timezone('Asia/Omsk')) + timedelta(days=1)
    elif user_message_lower in ["понедельник", "вторник", "среда",
                                "четверг", "пятница", "суббота", "воскресенье"]:
        days_mapping = {
            "понедельник": 0,
            "вторник": 1,
            "среда": 2,
            "четверг": 3,
            "пятница": 4,
            "суббота": 5,
            "воскресенье": 6
        }
        target_weekday = days_mapping[user_message_lower]
        today = datetime.now(pytz.timezone('Asia/Omsk'))
        days_ahead = target_weekday - today.weekday()
        if days_ahead < 0:
            days_ahead += 7
        date_obj = today + timedelta(days=days_ahead)
    else:
        await update.message.reply_text("❌ Неверный запрос. Пожалуйста, выберите опцию из меню.")
        return

    await show_schedule_by_date(update, context, date_obj)

# Функция главного старта бота
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)

# Функция отправки домашки
# Эта функция теперь связана с `handle_callback`, больше не нужна отдельно

# Функция обработки текстовых сообщений
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 any(keyword in user_message.lower() for keyword in ["расписание на сегодня", "расписание на завтра", 
                                                        "понедельник", "вторник", "среда",
                                                        "четверг", "пятница", "суббота", "воскресенье"]):
        await show_schedule(update, context, user_message)
        return

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

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

    # Если ничего не подходит
    await update.message.reply_text('❓ Выбери опцию из меню.')

# Функция для обработки рассылки объявлений и домашки
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)

# Функция подтверждения или отмены рассылки
# Уже реализована выше как confirm_send

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

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

    # Запуск планировщика
    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()

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