Пишем бота для принятия решений

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

В этом туториале вы напишете телеграм-бота для принятия особо важных стратегических решений:

Перед тем как начать…

Очень советуем не просто читать этот туториал, а проходить его вместе с нами. Копируйте кусочки кода из него и запускайте их по мере прочтения текста. Так вы поймёте происходящее гораздо лучше.

1. Создайте бота

Пользоваться будем библиотекой ptbot. Перейдите в его документацию и следуйте инструкциям в разделе “Как начать пользоваться”.

Если вы всё сделали правильно, то от бота в Telegram прилетит сообщение:

2. Заставьте бота говорить

Сейчас бот сообщает о своём запуске, а после этого замолкает. Какой-то он не слишком разговорчивый, надо его разговорить! Разберёмся что заставляет его отправить сообщение о запуске. Похоже дело в этой строчке:

bot.send_message(TG_CHAT_ID, "Бот запущен")

Давайте её менять:

import ptbot

TG_TOKEN = '958423683:AAEAtJ5Lde5YYfu8GldVhSGCAsxAYbzUIYg'  # подставьте свой ключ API
TG_CHAT_ID = '228593533'  # подставьте свой ID
bot = ptbot.Bot(TG_TOKEN)
bot.send_message(TG_CHAT_ID, "Привет!")
bot.send_message(TG_CHAT_ID, "Как дела?")

Круто, похоже, она и правда отвечает за отправку сообщений:

3. Научите бота принимать решения

Бот умеет присылать заготовленный текст. А как сделать так, чтобы он отвечал пользователям разными сообщениями? Для этого пригодится библиотека random. Её устанавливать не нужно, она встроена в Python:

import random

answers = ("да", "нет", "это возможно")
choice = random.choice(answers)
print("Думаю, ", choice)

В переменной answers лежит сразу 3 строки. Такой приём называется упаковка. Она передаётся в функцию random.choice, и та случайным образом выбирает одну из них. Случайный ответ складывается в переменную choice. Попробуйте запустить этот код несколько раз и вы будете получать разные результаты:

Хорошо, выбор есть. Теперь его можно отправить пользователю:

import random

answers = ("да", "нет", "это возможно")
choice = random.choice(answers)
bot.send_message(TG_CHAT_ID, "Думаю, ", choice)

Похоже, этот код с ошибкой! Вот что он выводит:

TypeError: send_message() takes 3 positional arguments but 4 were given

Это ошибка, на которую часто натыкаются начинающие питонисты. Дело в том, что команда print() принимает любое количество аргументов: сколько вы ей передадите, столько она и выведет. Пример:

print("Один аргумент")
print("Два", "аргумента")
print("Т", "р", "и")

А вот функция send_message принимает строго два: chat_id и текст сообщения.

Чтобы исправить ошибку, “склейте” свои кусочки сообщения в одну переменную перед отправкой:

message = "Думаю, {}".format(choice)
bot.send_message(TG_CHAT_ID, message)

Вот что получилось:

4. Соберите код принятия решения в функцию

Теперь ваш бот умеет принимать решения. Но чтобы он это делал, его нужно запускать несколько раз. Жать на зелёную кнопку в Repl – занятие не очень интересное, хорошо бы его автоматизировать. Поможет функция:

def choose():
    answers = ("да", "нет", "это возможно")
    choice = random.choice(answers)
    message = "Думаю, {}".format(choice)
    bot.send_message(TG_CHAT_ID, message)

choose() – это функция. Такая же, как print() или random.choice(), только здесь вы создаёте её самостоятельно.

После того, как вы объявили функцию, вы можете её вызывать. Так же, как вы делали это с print(), т.е. просто напишите её название и поставьте скобки:

def choose():
    answers = ("да", "нет", "это возможно")
    choice = random.choice(answers)
    message = "Думаю, {}".format(choice)
    bot.send_message(TG_CHAT_ID, message)

choose()
choose()

Бот пришлёт сразу 2 сообщения:

Чтобы создать новую функцию, нужно не так-то много действий. Каждая функция начинается со слова def. Это сокращение от define, т.е. “определить”. Так код понимает разницу между созданием функции и её использованием:

def choose():  # Это создание
    ...


choose()  # Это использование

Второе правило: весь код функции пишется с отступом в 4 пробела:

Конечно, не обязательно жать 4 раза на клавишу “пробел”. То же самое делает клавиша TAB на левом крае клавиатуры.

Последний нюанс. Всё, что происходит в функции, остаётся внутри неё. Например, все переменные, которые мы создали во время работы функции, окажутся недоступны извне:

def choose():
    answers = ("да", "нет", "это возможно")
    choice = random.choice(answers)
    message = "Думаю, {}".format(choice)
    bot.send_message(TG_CHAT_ID, message)

print(answers)  # NameError: name 'answers' is not defined

То есть всё, что внутри функции, остальному коду недоступно.

Функция – это отдельный кусочек кода, который можно запустить попозже

5. Сделайте выбор неслучайным

Сейчас бот случайным образом выбирает между “да”, “нет” и “возможно”. Позже вы сможете прокачать бота, чтобы он выдавал более осознанные ответы. Но всё равно по коже пробегают мурашки: что он там сейчас наотвечает? И ведь вы даже не можете понять какие из ответов ваши, а какие принадлежат боту! Надо что-то придумать, чтобы бот записывал что и кому он ответил.

Ну давайте поступим по-простому: пусть функция выводит в консоль кто и что спрашивал у бота:

def choose():
    answers = ("да", "нет", "это возможно")
    choice = random.choice(answers)
    message = "Думаю, {}".format(choice)
    bot.send_message(TG_CHAT_ID, message)
    print("Что сюда писать???") # <-- поменяли здесь

choose()

print() понятно, а дальше что?.. Что в него писать?.. В функции нет таких переменных… Чтобы они появились, нужно добавить функции аргументы:

def choose(chat_id, question):
    answers = ("да", "нет", "это возможно")
    choice = random.choice(answers)
    message = "Думаю, {}".format(choice)
    bot.send_message(TG_CHAT_ID, message)
    print("Мне написал пользователь с ID:", chat_id)
    print("Он спрашивал:", question)
    print("Я ответил:", message)

Теперь функция получает 2 аргумента: ID пользователя в Telegram и его вопрос. А вот как функцией теперь пользоваться:

choose(TG_CHAT_ID, "Привет! Можно мне повысить зарплату?")

Если сделать вот так, по-старинке:

choose()

Вы получите ошибку:

TypeError: choose() takes 2 positional arguments but 0 was given

Знакомая ошибка! Вы сталкивались с ней в пункте 3 этого туториала. Только теперь вы недодаёте функции аргументов, ведь теперь она принимает сразу 2!

Последний тюнинг: разрешим боту общаться с другими пользователями. Для этого в методе send_message заменим TG_CHAT_ID на chat_id. TG_CHAT_ID – это ваш id, поэтому бот мог писать только вам. Вместо него мы передадим методу chat_id, это id любого, кто пишет боту. Этот id мы передаем при обращении к функции choose:

def choose(chat_id, question):
    answers = ("да", "нет", "это возможно")
    choice = random.choice(answers)
    message = "Думаю, {}".format(choice)
    bot.send_message(chat_id, message)  # <-- поменяли здесь
    print("Мне написал пользователь с ID:", chat_id)
    print("Он спрашивал:", question)
    print("Я ответил:", message)

choose(TG_CHAT_ID, "Привет! Можно мне повысить зарплату?")

6. Подключите функцию к боту

У нас есть функция, которая принимает решения. Но бот всё ещё не реагирует на сообщения в чате и отвечает только вам. Просто пишет всякие сообщения, когда вы жмёте на кропку Run. Как заставить его ждать сообщений, а уже после этого отвечать?

Такая функция есть в ptbot:

def choose(chat_id, question):
    answers = ("да", "нет", "это возможно")
    choice = random.choice(answers)
    message = "Думаю, {}".format(choice)
    bot.send_message(chat_id, message)
    print("Мне написал пользователь с ID:", chat_id)
    print("Он спрашивал:", question)
    print("Я ответил:", message)

bot = ptbot.Bot(TG_TOKEN)
bot.reply_on_message(choose)  # <---- Новая строчка!
bot.run_bot()  # <---- Новая строчка!

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

bot.reply_on_message(choose)

Чтобы лучше понимать что произошло, давайте посмотрим на такой код:

print(print)

Выведется что-то новенькое: <built-in function print>. Вы вывели саму функцию принт! Так можно делать с любыми функциями:

print(random.choice)

Теперь выведется вот такое колдунство:

<bound method Random.choice of <random.Random object at 0x008D71E8>>

Название функции – это переменная, в которой лежит функция

Этой переменной можно орудовать как любой другой. Передавать как аргумент в другие функции, или класть в другие переменные:

a = print
a("Привет, мир!")

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

Так вот, вернёмся к нашему коду:

def choose(chat_id, question):
    answers = ("да", "нет", "это возможно")
    choice = random.choice(answers)
    message = "Думаю, {}".format(choice)
    bot.send_message(chat_id, message)
    print("Мне написал пользователь с ID:", chat_id)
    print("Он спрашивал:", question)
    print("Я ответил:", message)

bot = ptbot.Bot(TG_TOKEN)
bot.reply_on_message(choose)  # <---- Внимание сюда!
bot.run_bot()

Здесь вы передаёте в метод bot.reply_on_message вашу функцию choose:

bot.reply_on_message(choose)

Метод reply_on_message будет ждать до тех пор, пока боту кто-нибудь не напишет. Как только боту напишет человек, reply_on_message запустит вашу функцию choose, вот так: choose(chat_id, question). То есть теперь функцию запустят за вас, вам самим её вызывать не нужно.

На гифке ниже можно посмотреть как теперь работает бот. Теперь он отвечает только после того, как вы ему написали:

7. Создайте иллюзию раздумий

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

В ptbot есть метод, отвечающий за таймеры, с помощью таймера можно сделать эту задержку:

def notify():
    print("Прошло 5 секунд!")


bot = ptbot.Bot(TG_TOKEN)
bot.create_timer(5, notify)
bot.run_bot()

Если запустить такой код, то таймер запустится сразу же, как только вы нажали Run. Когда пройдёт 5 секунд, запустится функция notify. Она выведет в консоль, что 5 секунд прошло.

Вам нужен другой алгоритм:

  1. В самом начале бот ждёт сообщений от пользователя.
  2. Если боту написали, он ставит таймер на 5 секунд
  3. Когда таймер прошёл, бот запустит функцию choose

А вот как это сделать:

def wait():
    bot.create_timer(5, choose)

def choose(chat_id, question):
    answers = ("да", "нет", "это возможно")
    choice = random.choice(answers)
    message = "Думаю, {}".format(choice)
    bot.send_message(chat_id, message)
    print("Мне написал пользователь с ID:", chat_id)
    print("Он спрашивал:", question)
    print("Я ответил:", message)

bot = ptbot.Bot(TG_TOKEN)
bot.reply_on_message(wait)
bot.run_bot()

Когда боту пишут, он запускает функцию wait. Она ничего никому не пишет, а ставит таймер на 5 секунд.

Когда таймер пройдёт, запустится функция choose. Она запустится потому что мы передали её в метод bot.create_timer(5, choose).

Эта функция сделает то же самое, что и раньше: выберет случайный вариант ответа и отправит сообщение пользователю.

Запускаем… Опа, ошибка!

TypeError: wait() takes 0 positional arguments but 2 were given

Знакомый TypeError, снова функция хотела меньше аргументов, чем получила.

Когда вы передали функцию wait в reply_on_message, он подсунул в неё 2 аргумента: chat_id, question. Нужно указать их при объявлении def wait()::

def wait(chat_id, question):
    bot.create_timer(5, choose)

Запускаем ещё раз… Теперь бот подождал 5 секунд и снова выдал TypeError:

TypeError: choose() missing 2 required positional arguments: 'chat_id' and 'question'

На этот раз ситуация обратная, у choose есть 2 аргумента, chat_id и question, но их ему никто не передал.

Для этого нужно воспользоваться фичей библиотеки и заставить функцию wait “поделиться” аргументами:

def wait(chat_id, question):
    bot.create_timer(5, choose, chat_id=chat_id, question=question)

Теперь бот наконец работает!

Напоследок стоит навести красоту, чтобы вас на смущало question=question. Эти аргументы можно переименовать как угодно, вот например:

def wait(chat_id, question):
    bot.create_timer(5, choose, author_id=chat_id, message=question)

def choose(author_id, message):
    answers = ("да", "нет", "это возможно")
    choice = random.choice(answers)
    answer = "Думаю, {}".format(choice)
    bot.send_message(author_id, answer)
    print("Мне написал пользователь с ID:", author_id)
    print("Он спрашивал:", message)
    print("Я ответил:", answer)

bot = ptbot.Bot(TG_TOKEN)
bot.reply_on_message(wait)
bot.run_bot()

Называйте аргументы как хотите. Главное, чтобы было понятно

Это было интересно… а что дальше?

Теперь вы можете писать простых ботов. Вы можете реализовать ещё несколько идей с использованием ptbot. Для них нужно знать конструкции if и for, но в остальном они очень простые и потребуют не больше сотни строк:

  • Бот, который каждый год поздравляет вас с Новым годом/Днём рождения или напоминает о годовщине свадьбы.
  • Бот для крестиков-ноликов
  • Бот для игры виселицу

Когда вы перерастёте возможности библиотеки ptbot, можно осваивать её старшего брата: python-telegram-bot. Эта библиотека умеет более крутые фичи и на ней можно писать более сложных ботов. Обращению с ней мы учим в курсе Чат-боты на Python.


Попробуйте бесплатные уроки по Python

Получите крутое код-ревью от практикующих программистов с разбором ошибок и рекомендациями, на что обратить внимание — бесплатно.

Переходите на страницу учебных модулей «Девмана» и выбирайте тему.