python telegram боты

Создаем собственный текстовый квест в Telegram

Ростислав Бородин

Адмирал Преподаватель

Телеграм боты - это крутой способ взаимодействия с пользователем прямо в привычном ему мессенджере. Это гораздо быстрее и чаще удобнее, чем писать полноценное мобильное приложение. В этой статье мы постраемся создать собственного Telegram бота, который будет предлагать пользователю разыграть полноценный текстовый квест.

Нюансы сюжета вам придется продумать самостоятельно, ну а вся техническая часть - под катом!


Для начала давайте познакомимся с Telegram ботами, по ссылке представлено множество примеров таких программ, от прогноза погоды до общения с рандомным собеседником: https://uip.me/2016/04/50-popular-telegram-bots/.

> Учебник: https://groosha.gitbooks.io/telegram-bot-lessons/content/chapter1.html > Веб-версия Telegram: http://web.telegram.org/

Технически, телеграм бот - это программа, которая запущена на вашем компьютере и общается с серверами телеграма через интернет. Можно представить, что при регистрации бота, телеграм выделяет нам почтовый ящик, через который мы можем получать сообщения от пользователей и отправлять их им.

Чтобы создать собственного бота, сперва его надо зарегистрировать. Для этого в телеграме нужно добавить бота @BotFather и следовать инструкциям. После того, как мы введем название бота, @BotFather сообщит нам токен - пароль к нашему почтовому ящику.

После получения токена нам потребуется установить библиотеку pyTelegramBotApi, как и обычно, это делается через pip:

pip install pytelegrambotapi

Пробуем создать бота

Давайте сделаем нашего первого бота - бота-попугая. На все сообщения он будет отвечать повторением.

import telebot

token = "ВСТАВЬТЕ СЮДА ТОКЕН"
# Обходим блокировку с помощью прокси
telebot.apihelper.proxy = {'https': 'socks5h://geek:socks@t.geekclass.ru:7777'}
# подключаемся к телеграму
bot = telebot.TeleBot(token=token)

# content_types=['text'] - сработает, если нам прислали текстовое сообщение
@bot.message_handler(content_types=['text'])
def echo(message):
    # message - входящее сообщение
    # message.text - это его текст
    # message.chat.id - это номер его автора
    text = message.text
    user = message.chat.id

    #отправляем картинку с попугаем
    bot.send_photo(user, "https://i.ytimg.com/vi/R-RbmqzRC9c/maxresdefault.jpg")

    #отправляем сообщение тому же пользователю с тем же текстом
    bot.send_message(user, text)

# поллинг - вечный цикл с обновлением входящих сообщений
bot.polling(none_stop=True)

Работает так:

Помимо текстовых сообщений, в телеграме есть команды, они начинаются со слэша, например, /start или /help. Их тоже можно обрабатывать.

Давайте добавим пояснение к нашему боту, которое объяснит пользователю, что он делает.

import telebot

token = "ВСТАВЬ СЮДА ТОКЕН"

# подключаемся к телеграму
bot = telebot.TeleBot(token=token)

# реагируем на команды /start и /help
@bot.message_handler(commands=['start', 'help'])
def help(message):
    user = message.chat.id
    bot.send_message(user, "Это бот попугай! Просто пришли и я повторю.")

# content_types=['text'] - сработает, если нам прислали текстовое сообщение
@bot.message_handler(content_types=['text'])
def echo(message):
    # message - входящее сообщение
    # message.text - это его текст
    # message.chat.id - это номер его автора
    text = message.text
    user = message.chat.id

    #отправляем картинку с попугаем
    bot.send_photo(user, "https://i.ytimg.com/vi/R-RbmqzRC9c/maxresdefault.jpg")

    #отправляем сообщение тому же пользователю с тем же текстом
    bot.send_message(user, text)

# поллинг - вечный цикл с обновлением входящих сообщений
bot.polling(none_stop=True)

Взаимодействуем с несколькими пользователями одновременно

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

Если мы будем использовать переменную, то сможем сохранить сообщение только одного пользователя. Например, Вася попросил запомнить слово kitten. Мы положим эту строку в переменную note. А затем Петя, попросить запомнить слово puppy, и мы снова положим это переменную note. Когда Вася попросит нам напомнить его последнее сообщение, мы напишем ему puppy вместо kitten. Совершенно не годится!

> Удобнее всего хранить все данные, которые привязаны к конкретному пользователю в словаре. Ключем в этом словаре будет id пользователя, а значением - произвольные данные.

Предположим, что наш словарь называется notes - заметки. Теперь, когда Вася (id88000) пришлет слово kitten мы положим его в notes[88000], а слово puppy от Пети (id5300) - в notes[5300]. Посколько теперь мы используем разные переменные для хранения слова, сообщения от разных пользователей не будут путаться.

Реализация:

import telebot

token = "ВАШ ТОКЕН"
# Обходим блокировку с помощью прокси
telebot.apihelper.proxy = {'https': 'socks5h://geek:socks@t.geekclass.ru:7777'}
bot = telebot.TeleBot(token=token)
notes = {}

@bot.message_handler(commands=['remind'])
def remind(message):
    user_id = message.chat.id
    if user_id not in notes:
        bot.send_message(user_id, "Вы мне еще не писали.")
    else:
        bot.send_message(user_id, notes[user_id])

@bot.message_handler(content_types=['text'])
def remember(message):
    user_id = message.chat.id
    notes[user_id] = message.text
    bot.send_message(user_id, "Я запомнил")

bot.polling(none_stop=True)

Добавляем кнопки

Следующий пример демонстрирует, как добавить несколько кнопок к сообщению и реагировать на их нажатия.

import telebot
from telebot import types

token = "ВАШ ТОКЕН"
# Обходим блокировку с помощью прокси
telebot.apihelper.proxy = {'https': 'socks5h://geek:socks@t.geekclass.ru:7777'}
bot = telebot.TeleBot(token=token)

@bot.message_handler(commands=["start"])
def repeat_all_messages(message):
    # создаем клавиатуру
    keyboard = types.InlineKeyboardMarkup()

    # добавляем на нее две кнопки
    button1 = types.InlineKeyboardButton(text="Кнопка 1", callback_data="button1")
    button2 = types.InlineKeyboardButton(text="Кнопка 2", callback_data="button2")
    keyboard.add(button1)
    keyboard.add(button2)

    # отправляем сообщение пользователю
    bot.send_message(message.chat.id, "Нажмите кнопку!", reply_markup=keyboard)

# функция запустится, когда пользователь нажмет на кнопку
@bot.callback_query_handler(func=lambda call: True)
def callback_inline(call):
    if call.message:
        if call.data == "button1":
            bot.send_message(call.message.chat.id, "Вы нажали на первую кнопку.")
        if call.data == "button2":
            bot.send_message(call.message.chat.id, "Вы нажали на вторую кнопку.")

bot.polling(none_stop=True)

Собираем текстовый квест

from telebot import TeleBot, types
from random import randint
from secret import token

pictures = {
    0: "https://storage.geekclass.ru/images/760e484b-a099-4a7a-a722-5aec9a933614.jpg",
    1: "https://storage.geekclass.ru/images/4637fc41-08df-466a-b112-aa577dba6c1d.jpg",
    2: "https://storage.geekclass.ru/images/c2a2a60c-9c7b-4c3a-b663-42d2559bf869.jpg"
}

states = {}
inventories = {}
# Обходим блокировку с помощью прокси
telebot.apihelper.proxy = {'https': 'socks5h://geek:socks@t.geekclass.ru:7777'}
bot = TeleBot(token)

@bot.message_handler(commands=["start"])
def start_game(message):
    user = message.chat.id

    states[user] = 0
    inventories[user] = []

    bot.send_message(user, "Добро пожаловать в игру!")

    process_state(user, states[user], inventories[user])

@bot.callback_query_handler(func=lambda call: True)
def user_answer(call):
    user = call.message.chat.id
    process_answer(user, call.data)

def process_state(user, state, inventory):
    kb = types.InlineKeyboardMarkup()

    bot.send_photo(user, pictures[state])

    if state == 0:
        kb.add(types.InlineKeyboardButton(text="пойти направо", callback_data="1"))
        kb.add(types.InlineKeyboardButton(text="пойти налево", callback_data="2"))

        bot.send_message(user, "Вы в оказались в темном подземелье, перед вами два прохода.", reply_markup=kb)

    if state == 1:
        kb.add(types.InlineKeyboardButton(text="переплыть", callback_data="1"))
        kb.add(types.InlineKeyboardButton(text="вернуться", callback_data="2"))

        bot.send_message(user, "Перед вами большое подземное озеро, а вдали виднеется маленький остров.", reply_markup=kb)

    if state == 2:
        bot.send_message(user, "Вы выиграли.")

def process_answer(user, answer):
    if states[user] == 0:
        if answer == "1":
            states[user] = 1
        else:
            if "key" in inventories[user]:
                bot.send_message(user,
                                 "Перед вами закрытая дверь. Вы пробуете открыть ее ключем, и дверь поддается. Кажется, это выход.")
                states[user] = 2
            else:
                bot.send_message(user, "Перед вами закрытая дверь, и, кажется, без ключа ее не открыть. Придется вернуться обратно.")
                states[user] = 0

    elif states[user] == 1:
        if answer == "2":
            bot.send_message(user,
                             "И правда, не стоит штурмовать неизвестные воды. Возвращаемся назад...")
            states[user] = 0
        else:
            bot.send_message(user,
                             "Вы пробуете переплыть озеро...")

            chance = randint(0, 100)
            if chance > 30:
                bot.send_message(user,
                                 "Вода оказалось теплой, а в сундуке на острове вы нашли старый ключ. Стоит вернутся обратно.")
                inventories[user].append("key")
                states[user] = 0
            else:
                bot.send_message(user,
                                 "На середине озера вас подхватывают волны и возвращают обратно.")
                states[user] = 1

    process_state(user, states[user], inventories[user])

bot.polling(none_stop=True)