Эта статья предназначена для новичков. Я намерено опускаю сложные детали и нюансы, чтобы материал воспринимался легче.

Всем вновь привет! Продолжаем цикл статей, посвящённых разработке телеграм бота на PyTelegramBotAPI.

Сегодня мы полностью доделаем код нашего бота, напишем аннотацию типов, приготовим проект к деплою.


<<< Предыдущий урок

А начнём мы, конечно, с чего-нибудь новенького, а именно - покажем уведомление при нажатии на кнопку, вместо сообщения. Напомню, что на прошлом уроке мы сделали две клавиатуры - Reply и Inline, но нас сейчас интересует вторая. Сейчас при нажатии на кнопку сообщение отредактируется, а сама клавиатура пропадёт. Но мы сделаем круче, пусть пр нажатии на кнопку показывается уведомление, а сообщение просто удаляется, мне кажется так будет красивее и лаконичнее (ну и новый функционал на примере разберём).

Есть два вида уведомлений:

1)

Уведомление появляется сверху
Уведомление появляется сверху

2)

Уведомление появляется в центре экрана
Уведомление появляется в центре экрана

Я для своего бота буду использовать первый вариант уведомлений, мне кажется он подойдёт лучше.

Чтобы удалить сообщение надо вызвать соответствующий метод и передать в него id чата и id сообщения. Полный код функции save_btn будет выглядеть так:

@bot.callback_query_handler(func=lambda call: call.data == 'save_data')
def save_btn(call):
    message = call.message
    chat_id = message.chat.id
    message_id = message.message_id
    bot.answer_callback_query(call.id, text="Данные сохранены")
    bot.delete_message(chat_id=chat_id, message_id=message_id)

Удаление сообщения происходит на 7 строке.

Теперь давайте отправим фото. А отправлять мы его будем, когда пользовать напишет команду /start. Напомню, что у нас уже есть обработчик этой команды - это функция welcome. Отправить фото легко, сохраняем его в папку с проектом, открываем файл и читаем его в бинарном виде, а далее вызываем метод send_photo передав туда id чата и прочтённые бинарные данные.

Код изменённой функции welcome выглядит так:

@bot.message_handler(commands=['start'])
def welcome(message):
    chat_id = message.chat.id
    keyboard = telebot.types.ReplyKeyboardMarkup()
    button_save = telebot.types.InlineKeyboardButton(
        text="Написать в поддержку")
    keyboard.add(button_save)
    bot.send_message(chat_id,
                     'Добро пожаловать в бота сбора обратной связи',
                     reply_markup=keyboard)
    with open('feedback.jpeg', 'rb') as file:
        photo = file.read()
    bot.send_photo(chat_id, photo)

Вот так это будет выглядеть:

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

Для хранения ID чата администраторов я буду использовать кортеж (чтобы получить id чата, вы можете вывести print'ом переменную chat_id).

bot = telebot.TeleBot(TOKEN)

users = {}

administrators = (814401631,)

Запишем это в самом верху, сразу после переменной users.

Теперь модифицируем функцию save_btn, чтобы она запрашивала отзыв и после вызывала другую функцию с помощью уже знакомого нам register_next_step_handler.

@bot.callback_query_handler(func=lambda call: call.data == 'save_data')
def save_btn(call):
    message = call.message
    chat_id = message.chat.id
    message_id = message.message_id
    bot.answer_callback_query(call.id, text="Данные сохранены")
    bot.delete_message(chat_id=chat_id, message_id=message_id)
    bot.send_message(chat_id, 'Введите текст отзыва: ')
    bot.register_next_step_handler(message, send_feedback_administrators)

А теперь создадим и саму функцию send_feedback_administrators, он будет отправлять всем указанным в кортеже администраторам сообщение в формате: %Фамилия% %Имя% оставил отзыв: %Текст отзыва%

def send_feedback_administrators(message):
    feedback = message.text
    user = users[message.chat.id]
    name = user['name']
    surname = user['surname']
    for admin_chat_id in administrators:
        bot.send_message(admin_chat_id,
                         f'{surname} {name} оставил отзыв: {feedback}')

Теперь посмотрим как это выглядит:

Отлично, всё прекрасно работает!

Итоговый код выглядит так:

import telebot
from config import TOKEN

bot = telebot.TeleBot(TOKEN)

users = {}

administrators = (814401631,)


@bot.message_handler(commands=['start'])
def welcome(message):
    chat_id = message.chat.id
    keyboard = telebot.types.ReplyKeyboardMarkup()
    button_save = telebot.types.InlineKeyboardButton(
        text="Написать в поддержку")
    keyboard.add(button_save)
    bot.send_message(chat_id,
                     'Добро пожаловать в бота сбора обратной связи',
                     reply_markup=keyboard)
    with open('feedback.jpeg', 'rb') as file:
        photo = file.read()
    bot.send_photo(chat_id, photo)


@bot.message_handler(
    func=lambda message: message.text == 'Написать в поддержку')
def write_to_support(message):
    chat_id = message.chat.id
    bot.send_message(chat_id, 'Введите своё имя')
    users[chat_id] = {}
    bot.register_next_step_handler(message, save_username)


def save_username(message):
    chat_id = message.chat.id
    name = message.text
    users[chat_id]['name'] = name
    bot.send_message(chat_id, f'Отлично, {name}. Теперь укажи свою фамилию')
    bot.register_next_step_handler(message, save_surname)


def save_surname(message):
    chat_id = message.chat.id
    surname = message.text
    users[chat_id]['surname'] = surname
    keyboard = telebot.types.InlineKeyboardMarkup()
    button_save = telebot.types.InlineKeyboardButton(text="Сохранить",
                                                     callback_data='save_data')
    button_change = telebot.types.InlineKeyboardButton(text="Изменить",
                                                       callback_data='change_data')
    keyboard.add(button_save, button_change)

    bot.send_message(chat_id, f'Сохранить данные?', reply_markup=keyboard)


@bot.message_handler(commands=['who_i'])
def who_i(message):
    chat_id = message.chat.id
    name = users[chat_id]['name']
    surname = users[chat_id]['surname']
    bot.send_message(chat_id, f'Вы: {name} {surname}')


@bot.callback_query_handler(func=lambda call: call.data == 'save_data')
def save_btn(call):
    message = call.message
    chat_id = message.chat.id
    message_id = message.message_id
    bot.answer_callback_query(call.id, text="Данные сохранены")
    bot.delete_message(chat_id=chat_id, message_id=message_id)
    bot.send_message(chat_id, 'Введите текст отзыва: ')
    bot.register_next_step_handler(message, send_feedback_administrators)


def send_feedback_administrators(message):
    feedback = message.text
    user = users[message.chat.id]
    name = user['name']
    surname = user['surname']
    for admin_chat_id in administrators:
        bot.send_message(admin_chat_id,
                         f'{surname} {name} оставил отзыв: {feedback}')


@bot.callback_query_handler(func=lambda call: call.data == 'change_data')
def save_btn(call):
    message = call.message
    chat_id = message.chat.id
    message_id = message.message_id
    bot.edit_message_text(chat_id=chat_id, message_id=message_id,
                          text='Изменение данных!')
    write_to_support(message)


if __name__ == '__main__':
    print('Бот запущен!')
    bot.infinity_polling()

А теперь, подведём итоги, сегодня мы сделали:

  1. Отправку уведомлений, удаление сообщений

  2. Отправку фото

  3. Закончили всю техническую часть бота, связанную именно с разработкой

<<< Предыдущий урок


Что ж, вот четвёртая, предпоследняя статья из цикла подошла к концу. Сейчас мы получили работающего бота, но... работающего только у нас на компьютере. А это значит, что стоит нам выключить наше устройство и бот сразу перестанет работать.

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

Код с этого урока на GitHub.

Комментарии (1)


  1. Seruios
    27.11.2023 05:35

    Не обязательно делать photo = file.read(), достаточно просто вставить file в send_photo(). Так же я бы очень посоветовал переписать register next step handler под состояния states.