Напиши мне все команды pip install, которые понадобятся для запуска следующего кода: import pytz
import sqlite3
from datetime import datetime, timedelta
from telegram import Update, ReplyKeyboardMarkup, InlineKeyboardMarkup, InlineKeyboardButton
from telegram.ext import ApplicationBuilder, CommandHandler, ContextTypes, MessageHandler, filters, CallbackQueryHandler, CallbackContext

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

def is_even_week(date):
    week_number = date.isocalendar()[1]
    return week_number % 2 == 0

def get_week_type(date):
    return 'чётная' if is_even_week(date) else 'нечётная'

def get_week_type_short(date):
    return 'чёт' if is_even_week(date) else 'нечёт'

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 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 handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    user_message = update.message.text

    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

    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 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 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)
    else:

        c.execute("UPDATE users SET username = ? WHERE id = ?", (username, user_id))
        conn.commit()

    c.execute("SELECT 1 FROM admins WHERE id = ?", (user_id,))
    is_admin = c.fetchone() is not None

    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 "Студент"
    subscribe_state = "Отписаться от уведомлений университета" if user[3] 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(subscribe_state, callback_data='toggle_subscription')]
    ]

    if is_admin:
        keyboard.append([
            InlineKeyboardButton("Сделать рассылку", callback_data='send_announcement'),
            InlineKeyboardButton("Глобальная рассылка", callback_data='send_global_announcement')
        ])

    reply_markup = InlineKeyboardMarkup(keyboard)

    if update.callback_query:
        await update.callback_query.edit_message_text(text=message_text, reply_markup=reply_markup)
    else:
        await update.message.reply_text(text=message_text, reply_markup=reply_markup)

async def handle_callback(update: Update, context: CallbackContext):
    query = update.callback_query
    user_id = query.from_user.id
    conn = sqlite3.connect('university.db')
    c = conn.cursor()

    if query.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 query.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 query.data == 'toggle_subscription':
        c.execute("SELECT subscribed FROM users WHERE id = ?", (user_id,))
        current_subscription = c.fetchone()[0]
        new_subscription = 0 if current_subscription else 1
        c.execute("UPDATE users SET subscribed = ? WHERE id = ?", (new_subscription, user_id))
        conn.commit()
        await query.answer("Подписка обновлена")
        await personal_cabinet(update, context)

    elif query.data == 'send_announcement' and is_admin_user(user_id):
        c.execute("SELECT course, direction FROM users WHERE id = ?", (user_id,))
        admin_info = c.fetchone()
        context.user_data['announcement_for'] = admin_info[:2]
        context.user_data['is_sending_announcement'] = True
        await query.answer("Введите сообщение для рассылки вашей группе")

    elif query.data == 'send_global_announcement' and is_admin_user(user_id):
        context.user_data['announcement_for'] = 'global'
        context.user_data['is_sending_announcement'] = True
        await query.answer("Введите сообщение для глобальной рассылки")

    elif query.data == 'confirm_send':
        await send_announcement(context)
        await update.callback_query.edit_message_text("Успешная отправка.")
        context.user_data['announcement_for'] = None
        await query.answer("Рассылка отправлена.")

    elif query.data == 'cancel_send':
        await update.callback_query.edit_message_text("Отправка отменена.")
        context.user_data['announcement_for'] = None
        await query.answer("Рассылка отменена.")

    conn.close()

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
    if not message_text:
        return

    context.user_data['message_text'] = message_text
    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: ContextTypes.DEFAULT_TYPE):
    announcement_for = context.user_data.get('announcement_for')
    message_text = context.user_data.get('message_text')

    if not announcement_for or not message_text:
        return

    context.user_data['announcement_for'] = None
    context.user_data['message_text'] = None

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

    if announcement_for == 'global':
        c.execute("SELECT id FROM users WHERE subscribed = 1")
        subscribers = c.fetchall()
        c.execute("SELECT id FROM admins")
        admins = c.fetchall()
    else:
        course, direction = announcement_for
        c.execute("SELECT id FROM users WHERE course = ? AND direction = ? AND subscribed = 1", (course, direction))
        subscribers = c.fetchall()
        c.execute("SELECT id FROM admins")
        admins = c.fetchall()

    conn.close()

    all_recipients = set(subscribers) | set(admins)

    for recipient in all_recipients:
        try:
            await context.bot.send_message(chat_id=recipient[0], text=message_text)
        except Exception as e:
            print(f"Failed to send message to user {recipient[0]}: {e}")

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

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

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

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

        if not user_info:
            await update.message.reply_text("Ошибка при добавлении пользователя в базу данных.")
            return

    course, direction = user_info
    conn.close()

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

    days_map = {
        "расписание на сегодня": 0,
        "расписание на завтра": 1,
        "понедельник": 0,
        "вторник": 1,
        "среда": 2,
        "четверг": 3,
        "пятница": 4,
        "суббота": 5
    }
    day_index = days_map[day_command.lower()]

    if day_command.lower() == "расписание на сегодня":
        target_date = current_date
    elif day_command.lower() == "расписание на завтра":
        target_date = current_date + timedelta(days=1)
    else:
        day_offset = (day_index - current_weekday) % 7
        target_date = current_date + timedelta(days=day_offset)

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

    message_text = f"{day_name} {target_date.strftime('%d.%m.%Y')} ({week_type_short}):\n\n{day_schedule}"
    await update.message.reply_text(message_text)

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 FROM users WHERE id = ?", (user_id,))
    user_info = c.fetchone()

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

        if not user_info:
            await update.message.reply_text("Ошибка при добавлении пользователя в базу данных.")
            return

    course, direction = 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)

if __name__ == '__main__':
    application = ApplicationBuilder().token("<TRUFFLEHOG_REDACTED_TELEGRAMBOTTOKEN>").build()

    application.add_handler(CommandHandler("start", start))
    application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_message))
    application.add_handler(CallbackQueryHandler(handle_callback))

    application.run_polling()