Вывод календаря пользователю вместо ручного ввода даты

Есть телеграм-бот, написанный на pyTelegramBotAPI. Ниже я привел два метода (убрав запросы, чтобы сократить) где пользователь после авторизации может получить результаты или протокол. Для этого после авторизации пользователю требуется ввести дату. Сейчас он это делает вручную, но я хочу чтобы вместо ручного ввода пользователю выводился полноценный календарь с текущим месяцем и датами, а также возможностью листать месяцы, чтобы он выбрал дату из календаря, а не вводил вручную. Как такое можно реализовать?

Не хочется пока что переходить на более сложные библиотеки для ботов типа aiogram, по возможности на библиотеке pyTelegramBotAPI хочу реализовать т.к. бот уже работает.

##################################### АУТЕНТИФИКАЦИЯ ПАЦИЕНТА. НАЧАЛО #####################################
# Функция аутентификации по логину и паролю
def authenticate_user(message, next_step):
    chat_id = message.chat.id
    user = message.from_user
    if message.text == 'Отмена':
        handle_cancel(chat_id)
        return

    login = message.text.strip()
    msg = bot.send_message(message.chat.id, "Введите ваш пароль:", reply_markup=get_cancel_keyboard())
    bot.register_next_step_handler(msg, lambda m: process_password(m, login, next_step, user))

def process_password(message, login, next_step, user):
    chat_id = message.chat.id
    if message.text == 'Отмена':
        handle_cancel(chat_id)
        return

    password = message.text.strip()

    try:
        with pool.acquire() as connection:
            with connection.cursor() as cursor:
                query = """ЗАПРОС"""
                cursor.execute(query, {'login': login.upper(), 'password': password.upper()})
                result = cursor.fetchone()

                if result:
                    patientid = result[0]
                    next_step(chat_id, patientid, user)
                else:
                    msg = bot.send_message(message.chat.id, f"Неправильные данные. Пожалуйста, попробуйте снова. {LOGIN_TEXT}", reply_markup=get_cancel_keyboard())
                    bot.register_next_step_handler(msg, lambda m: authenticate_user(m, next_step))
    except Exception as e:
        log_error(user, e, "process_password")
        bot.send_message(message.chat.id, "Произошла ошибка при обработке вашего запроса.")
##################################### АУТЕНТИФИКАЦИЯ ПАЦИЕНТА. КОНЕЦ #####################################

##################################### ОБРАБОТКА /results #####################################
# Обработчик команды /results
@bot.message_handler(commands=['results'])
def handle_results(message):
    bot.send_message(message.chat.id, LOGIN_TEXT, reply_markup=get_cancel_keyboard())
    bot.register_next_step_handler(message, lambda m: authenticate_user(m, ask_res_date))

def ask_res_date(chat_id, patientid, user):
    msg = bot.send_message(chat_id, "Введите дату (в формате ДД.ММ.ГГГГ):", reply_markup=get_cancel_keyboard())
    bot.register_next_step_handler(msg, lambda m: get_results(m, patientid, user))

def get_results(message, patientid, user):
    chat_id = message.chat.id
    if message.text == 'Отмена':
        handle_cancel(chat_id)
        return

    try:
        res_date = datetime.strptime(message.text.strip(), '%d.%m.%Y').date()

        with pool.acquire() as connection:
            with connection.cursor() as cursor:
                query = """ЗАПРОС"""
                cursor.execute(query, {'patientid': patientid, 'res_date': res_date})
                results = cursor.fetchall()

                if results:
                    bot.send_message(message.chat.id, "Ваши результаты формируются...")
                    data = []
                    for row in results:
                        data.append({
                            //данные
                        })

                    //отправка пользователю

                    # Удаляем клавиатуру после вывода данных
                    bot.send_message(chat_id, f"Результаты сформированы.", reply_markup=types.ReplyKeyboardRemove())
                else:
                    bot.send_message(chat_id, f"Готовых результатов не найдено.", reply_markup=types.ReplyKeyboardRemove())
                    bot.register_next_step_handler(msg, lambda m: get_results(m, patientid, user))
    except ValueError as e:
        log_error(user, e, "get_results")
        msg = bot.send_message(chat_id, "Неправильный формат даты. Пожалуйста, введите дату в формате ДД.ММ.ГГГ:", reply_markup=get_cancel_keyboard())
        bot.register_next_step_handler(msg, lambda m: get_results(m, patientid, user))
    except Exception as e:
        log_error(user, e, "get_results")
        bot.send_message(chat_id, "Произошла ошибка при обработке вашего запроса.", reply_markup=get_cancel_keyboard())

##################################### /protocols #####################################
# Обработчик команды /protocols
@bot.message_handler(commands=['protocols'])
def handle_protocols(message):
    bot.send_message(message.chat.id, LOGIN_TEXT, reply_markup=get_cancel_keyboard())
    bot.register_next_step_handler(message, lambda m: authenticate_user(m, ask_protocol_date))

def ask_protocol_date(chat_id, patientid, user):
    msg = bot.send_message(chat_id, "Введите дату (в формате ДД.ММ.ГГГГ):", reply_markup=get_cancel_keyboard())
    bot.register_next_step_handler(msg, lambda m: get_prots(m, patientid, user))

def get_prots(message, patientid, user):
    chat_id = message.chat.id
    if message.text == 'Отмена':
        handle_cancel(chat_id)
        return

    try:
        prot_date = datetime.strptime(message.text.strip(), '%d.%m.%Y').date()

        with pool.acquire() as connection:
            with connection.cursor() as cursor:
                query = """ЗАПРС"""
                cursor.execute(query, {'patientid': patientid, 'prot_date': prot_date})
                results = cursor.fetchall()

                pdf_files = []
                if results:
                    bot.send_message(message.chat.id, "Ваши данные формируются...")
                    for row in results:
                        if row[8]:  # Проверяем, есть ли значение в поле 'fr'
                            data = {
                                //данные
                            }
                            
                            //Отправка пользователю

                    # Удаляем клавиатуру после вывода данных
                    bot.send_message(chat_id, f"Данные сформированы.", reply_markup=types.ReplyKeyboardRemove())
                else:
                    bot.send_message(chat_id, f"Данные не найдены.", reply_markup=types.ReplyKeyboardRemove())
                    msg = bot.send_message(message.chat.id, "Введите дату (в формате ДД.ММ.ГГГГ):", reply_markup=get_cancel_keyboard())
                    bot.register_next_step_handler(msg, lambda m: get_prots(m, patientid, user))
    except ValueError as e:
        log_error(user, e, "get_prots")
        msg = bot.send_message(chat_id, "Неправильный формат даты. Пожалуйста, введите дату в формате ДД.ММ.ГГГ:", reply_markup=get_cancel_keyboard())
        bot.register_next_step_handler(msg, lambda m: get_prots(m, patientid, user))
    except Exception as e:
        log_error(user, e, "get_prots")
        bot.send_message(chat_id, "Произошла ошибка при обработке вашего запроса.", reply_markup=get_cancel_keyboard())

# Функция для создания кнопок
def get_cancel_keyboard():
    keyboard = types.ReplyKeyboardMarkup(resize_keyboard=True, one_time_keyboard=True)
    cancel_button = types.KeyboardButton("Отмена")
    keyboard.add(cancel_button)
    return keyboard

# Общая команда для отмены действий
def handle_cancel(chat_id):
    bot.send_message(chat_id, "Действие отменено", reply_markup=types.ReplyKeyboardRemove())

if __name__ == '__main__':
    main()

Ответы (2 шт):

Автор решения: Vladimir Ignatenko

Чисто в теории вам надо выводить сообщение бота в котором будет "календарь" на месяц. И каждый день - это ссылка, которая будет командой для бота, что это выбранная дата. По этой команде бота делает то, что он обычно делается когда пользователь указывает дату вручную.

Плюс две ссылки с командами для переключения месяца (вперед и назад). По этой команде выводится еще одно сообщение бота, с календарем на указанный месяц. Предыдущее сообщение с календарем можно стирать, а можно оставить. Тут как вам удобней.

Чисто в теории все должно работать. Но вот реализация этого всего не очень простая.

→ Ссылка
Автор решения: Dezmonder

В телеграме нет возможности ввода даты календарём, соответственно то что вы хотите сделать, невозможно реализовать "из коробки".

Уже предлагали вариант с командами. Из других могу предположить, что можно такое реализовать инлайн кнопками или с помощью стороннего приложения (с приложениями сам не работал, но, вроде они поддерживают колбэки). С добавлением внутреннего браузера появился вариант, например открывать в браузере ваш календарь, и при взаимодействии с ним, например копировать команду с датой в буфер обмена.

→ Ссылка