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

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

Всем привет, вот и пришло время для второй части цикла статей посвященных разработке ТГ ботов на PyTelegramBotAPI.

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


Напомню, что в прошлый раз мы получили токен через BotFather, и написали функцию отправляющую приветственное сообщение на команду /start.

Давайте модернизируем нашего бота. Будем запрашивать имя и фамилию и сохранять эти данные.

Для сохранения данных можно использовать:

Я в своём примере остановлюсь на словарях. Почему? Всё просто, БД (база данных) несомненно лучший вариант, но надо уметь с ней работать, знать язык запросов (SQL). Файлы - тоже неплохо. Но у них ряд минусов. Во первых при деплое (разворачивании нашего проекта на сервере) к нему может не быть прав, файл занимает дополнительное место на диске, работа с файлами содержит некоторые другие нюансы (например: проблема с кодировкой на Windows). Да и не нужно использовать для такой задачи файлы или БД, можно обойтись и обычными словарями.

Как мы знаем, словари могут хранить любую информации в удобном виде (через ключ-значение), в нашем случае у нас будет два ключа: name и surname. Но, тут возможен очень неприятный баг, который может всплыть только при релизе нашего проекта. Если два пользователя в одно и тоже время воспользуются нашим ботом, то данные в словаре смешаются. Пример:

Пользователь1 вводит имя, например, Иван. Далее он вводит свою фамилию, Александрович. Но, пока Пользователь1 вводил свою фамилию, зашёл Пользователь2 и начал вводить своё имя, он ввел Никита. И из-за того, что у нас один словарь для всех клиентов, данные одного смешались с данными другого. Имя и фамилия Пользователя1 будет не Иван Александрович, а Никита Александрович. Если вы не поняли, прочтите этот абзац заново, это достаточно важно.

Для устранения подобного бага введём ещё один ключ, это будет уникальный id чата. Мы его использовали в прошлый раз для отправки сообщений. Тогда наша структура будет выглядеть так:

1) ID первого чата

1.1) Имя пользователя

1.2) Фамилия

2) ID второго чата

2.1) Имя

2.2) Фамилия

...

Если приводить пример с кодом, то это будет выглядеть как-то так:

users = {
    123456: {
        'name': 'Henry',
        'surname': 'Ford'
    },
    374519: {
        'name': 'Ivan',
        'surname': 'Ivanov'
    }
}

Добавим переменную users в нашего бота, и перейдем к написанию функций.

Для начала сделаем функцию сохранения имени:

import telebot
from config import TOKEN

bot = telebot.TeleBot(TOKEN)

users = {}


@bot.message_handler(commands=['start'])
def welcome(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
    bot.send_message(chat_id,
                     f'Отлично, {name}. Теперь укажи свою фамилию')


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

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

На верху (6 строка) был добавлен пустой словарь, он и будет хранить данные пользователей. Далее в функции welcome (14 строка) мы создаем ключ у словаря users, который является id чата. Т.е. наш словарь сейчас выглядит так:

users = {
  548269: {}
}

После этого, на 15 строчке мы определили следующее действие нашего бота. Не понятно? Сейчас объясню: мы должны запросить у пользователя имя, это понятно, но как это сделать, если до этого мы работали с командами (например команда /start ). А имя это не команда. В этом случае, нам не поможет декоратор message_handler. Необходимо указать боту, что после запуска функции welcome надо запустить другую функцию, которая и будет записываться имя. Другими словами register_next_step_handler - функция позволяющая задать следующий шаг бота. В этой функции мы указываем два аргумента: message - основной объект в телеграм ботах, который содержит всю необходимую информацию (id чата, текст сообщения и тд), а также ссылка на функцию, которая будет далее вызываться.

Надеюсь понятно, если нет - задавайте вопрос в комментариях, постараюсь на все ответить.

Идём дальше. Разберём функцию save_username.

Эта функция нужна для сохранения имени пользователя в файл. В начале функции мы создаём переменные chat_id и name. Первая переменная хранит id чата, это как мы помним уникальный идентификатор чата, используется, например, чтобы отправить сообщение (а в нашем случае ещё как ключ словаря). Вторая переменная - name - хранит текст, который ввёл пользователя в сообщении (копирует текст сообщения). А так как в функции welcome мы запросили имя, то текущий текст это имя.

Далее записываем указанное имя в словарь, и просим пользователя ввести фамилию. Сейчас наш словарь выглядит так (пример):

users = {
  548269: {
    'name': 'Iv'
  }
}

Давайте теперь создадим функцию для записи фамилии. А также функцию, которая будет выводить данные пользователя (имя и фамилию).

import telebot
from config import TOKEN

bot = telebot.TeleBot(TOKEN)

users = {}


@bot.message_handler(commands=['start'])
def welcome(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
    bot.send_message(chat_id, f'Ваши данные успешно сохранены!')


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


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

Код очень схож с предыдущим. Добавилась фукнция save_surname, которая сохраняет фамилию. Она работает аналогично с save_name. А у save_name добавился register_next_step_handler, так как нам после ввода имени, необходимо следом ввести и фамилию.

Также появилась новая команда /who_i, её функция (who_i) отправляет имя и фамилию текущего пользователя.

Запустим и проверим бота:

Отлично, всё получилось! Разберём наши достижения, сегодня мы:

  1. Создали аналог базы данных, с помощью словарей

  2. Сделали цепочку вызовов через register_next_step_handler

  3. Значительно увеличили функционал бота


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

Следующая часть выйдет через три дня.

Код бота на GitHub: https://github.com/Ryize/CollectClientsFeedbackBot

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


  1. naz95naz
    21.11.2023 05:59

    Для более удобной навигации в pyTelegramBotAPI уже реализована система состояний. Вместе с тем также реализовано State Memory Storage для хранения данных, и тогда не нужно создавать глобальных словарей.

    Смотрите примеры в репозитории фреймворка на GitHub