Создаем Telegram бота

Введение

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

Примеры ботов: https://vc.ru/services/401115-podborka-iz-100-poleznyh-telegram-botov-2022

Вспоминая функции

Функция - это часть кода программы, которую можно повторно использовать. У функции есть имя, список параметров и возвращаемое значение.

Возвращаемое значение - это результат работы функции. Рассмотрим простой пример:

def getTwo():
    return 2

a = getTwo()
print(a)

В данном примере мы определили функцию getTwo, она не принимает параметров и всегда возвращает (return) число 2. Написав a = getTwo(), мы вызвали функцию и результат ее работы попал в переменную a. На следующей строчке программа выведет 2.

Список параметров (или аргументов) - это значения, которые необходимы функции для выполнения задачи. Рассмотрим второй пример:

def sum(a, b):
    return a + b

print(sum(23, 5)) # выведет 28
print(sum(-3, 6)) # выведет 3

Здесь функция принимает два параметра и возвращает их сумму.

Внутри функций мы можем использовать любые переменные и операции с единственным ограничением:

  1. переменные внутри функции недоступны вне этой функции;
  2. переменные извне функции доступны только для чтения (если не испольуется ключевое слово global).

Телеграм боты

Учебник: https://groosha.gitbooks.io/telegram-bot-lessons/content/chapter1.html

Веб-версия Telegram: http://web.telegram.org/, https://webtelegram.net/

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

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

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

Далее мы с вами научимся писать программы на питоне, которые, используя этот токен, смогут общаться с пользователями.

Устанавливаем библиотеки

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

Для установки сторонних библиотек вместе с питоном на компьютер устанавливается программа pip.exe, которая умеет автоматически находить и скачивать библиотеки по их названию. Единственный нюанс - она работает только через командную строку (терминал).

Чтобы установить библиотеки нам потребуется открыть командную строку и последовательно ввести три команды.

pip install pytelegrambotapi

PS. Если у вас установлен питон, но компьютер все равно не находит pip, путь к нему нужно добавить в системную переменную PATH.

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

pip install --user pytelegrambotapi

Бот-попугай

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

import telebot

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

# подключаемся к телеграму
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)

Стикеры

Еще пример: бот, которому можно прислать стикер и получить стикер в ответ (он запоминает ранее присланные стикеры от всех пользователей).

import telebot
import random

token = "ТОКЕН"
bot = telebot.TeleBot(token=token)
stickers = []

@bot.message_handler(content_types=['sticker'])
def echo(message):
    user = message.chat.id
    sticker = message.sticker.file_id

    stickers.append(sticker)

    bot.send_sticker(user,random.choice(stickers))

bot.polling(none_stop=True)

Что такое словарь?

Списки - это очень круто! Можно хранить много данных и не придумывать имя каждому элементу. Но...

Рассмотрим другую задачу: нам нужно сделать базу клиентов для магазина комиксов. Магазин хочет помнить имя, возраст и любимые комиксы каждого клиента. Мы можем попробовать складывать это в список, но получится примерно так:

clients = ["Петька", 23, ["Майор гром", "Супермен"], "Чапаев", 43, ["Бэтмен"]]

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

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

Более естественный пример - это обычный бумажный словарь. Это книга, в которой перечислены слова и их определения. Слова - это ключи, а определения - значения.

Словарь - множество пар ключ-значение.

В примере выше весь список клиентов - это и правда список, а вот отдельный клиент тянет на словарь.

Один клиент будет выглядеть так:

client = {"name": "Петька", "age": 23, "favorites": ["Майор гром", "Супермен"]}
# это словарь

А весь список клиентов:

clients = [
    {"name": "Петька", "age": 23, "favorites": ["Майор гром", "Супермен"]},
    {"name": "Чапаев", "age": 43, "favorites": ["Бэтмен"]}
]
# это список словарей

Как работать со словарями?

Работа со словарями очень похожа на работу со списками, только вместо номера элемента используется его ключ (имя).

client = {"name": "Петька", "age": 23, "favorites": ["Майор гром", "Супермен"]}

print("Как зовут клиента?")
print(client["name"])
print('-' * 15)

print("Сколько ему лет?")
print(client["age"])
print('-' * 15)

print("А можно вывести всю информацию о нем?")
print(client)
# {'name': 'Петька', 'age': 23, 'favorites': ['Майор гром', 'Супермен']}
print('-' * 15)

print("А в столбик?")
for key in client:
    print("{} - {}".format(key, client[key]))
# словарь это много пар ключ-значение. Чтобы посмотреть на все элементы надо перебрать все ключи
print('-' * 15)

print("Он любит кошек или собак?")
if "pet" in client:
    print(client["pet"])
else:
    print("Мы не знаем")
# так можно проверить, есть ли ключ в словаре
print('-' * 15)

print("Он любит кошек, запишите это!")
client["pets"] = "кошка"
print(client)
# так можно добавить в словарь новый элемент
# {'name': 'Петька', 'age': 23, 'favorites': ['Майор гром', 'Супермен'], 'pets': 'кошка'}
print('-' * 15)

print("Возраст не нужен, удалите!")
del client["age"]
print(client)
# удаляем элемент и словаря
# {'name': 'Петька', 'favorites': ['Майор гром', 'Супермен'], 'pets': 'кошка'}
print('-' * 15)

# кстати, можно вывести все ключи:
print(list(client.keys()))
# ['name', 'favorites', 'pets']
print('-' * 15)

# и отдельно все значения
print(list(client.values()))
# ['Петька', ['Майор гром', 'Супермен'], 'кошка']
print('-' * 15)

# а еще из словаря можно сделать список пар, вот так:
print(list(client.items()))
# [('name', 'Петька'), ('favorites', ['Майор гром', 'Супермен']), ('pets', 'кошка')]
print('-' * 15)

print("Ксати, он говорил, что бэтмен тоже ничего.")
client["favorites"].append("Бэтмен")
print(client)
# {'name': 'Петька', 'favorites': ['Майор гром', 'Супермен', 'Бэтмен'], 'pets': 'кошка'}

Привязка данных к пользователю

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

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

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

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

Реализация:

import telebot

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

token = "ВАШ ТОКЕН"
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)

Запланированный ответ на конкретное сообщение

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

Попробуем с ее помощью провексти квест...

import telebot

bot = telebot.TeleBot(token="ВСТАВЬ СЮДА ТОКЕН")

@bot.message_handler(commands=['start'])
def start(message):
    # кто нам написал? узнаем id чата!
    chat_id = message.chat.id
    # отправляем приветствие
    answer = bot.send_message(chat_id, "Добро пожаловать в квест. Первое задание...")
    # в ответ на приветствие просим вызвать функцию point1
    bot.register_next_step_handler(answer, point1)

# название функции мы придумали сами, но параметр message обязателен
def point1(message):
    # кто нам написал? узнаем id чата!
    chat_id = message.chat.id
    # что написали? узнаем текст входящего сообщения
    # по сути это замена input
    text = message.text

    # если ответ верен
    if text == "Ответ 1":
        # пишем об этом пользвоателю и задаем следующие вопрос
        answer = bot.send_message(chat_id, "Правильный ответ. Второе задание...")
        # в ответ на сообщение просим вызвать функцию point2
        bot.register_next_step_handler(answer, point2)
    else:
        # пишем, что ответ неверен
        answer = bot.send_message(chat_id, "Нет, попробуйте еще раз...")
        # в ответ на сообщение просим вызвать функцию point1
        bot.register_next_step_handler(answer, point1)

# все по аналогии с point1
def point2(message):
    chat_id = message.chat.id
    text = message.text

    if text == "Ответ 2":
        answer = bot.send_message(chat_id, "Правильный ответ. Вы прошли квест!\nЧтобы повторить напишите /start.")
    else:
        answer = bot.send_message(chat_id, "Нет, попробуйте еще раз...")
        bot.register_next_step_handler(answer, point2)

bot.polling(none_stop=True)

Получение фотографий

Следующий пример показывает, как скачать и сохранить картинку, которую прислал пользователь. Не забудьте создать папку images, в которую мы будем сохранять полученные картинки.

import uuid
import telebot
from PIL import Image

token = "ВАШ ТОКЕН"
bot = telebot.TeleBot(token=token)

def process(filename):
    # обработка фотографии
    pass

@bot.message_handler(content_types=['photo'])
def photo(message):
    # скачивание файла
    file_id = message.photo[-1].file_id
    path = bot.get_file(file_id)
    downloaded_file = bot.download_file(path.file_path)

    # узнаем расширение и случайное придумываем имя
    extn = '.' + str(path.file_path).split('.')[-1]
    name = 'images/' + str(uuid.uuid4()) + extn

    # создаем файл и записываем туда данные
    with open(name, 'wb') as new_file:
        new_file.write(downloaded_file)

    # обрабатываем картинку фильтром
    process(name)

    # открываем файл и отправляем его пользователю
    with open(name, 'rb') as new_file:
        bot.send_photo(message.chat.id, new_file.read())

bot.polling(none_stop=True)

Отправка сообщений в произвольное время

Ваш телеграм бот может писать пользователю в произвольное время, если пользователь хоть раз с ним контактировал и вы запомнили id чата с ним.

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

import telebot
import time
from threading import Thread

token = "ТОКЕН"
bot = telebot.TeleBot(token=token)

users = []

@bot.message_handler(commands=['start', 'help'])
def start(message):
    user = message.chat.id
    bot.send_message(user, "https://www.youtube.com/watch?v=rdg4lkgsQ04")

@bot.message_handler(commands=['spam'])
def add_user(message):
    global user
    user = message.chat.id
    if user not in users:
        users.append(user)
    bot.send_message(user, "Берем сироп вишневый!")

@bot.message_handler(commands=['stop'])
def remove_user(message):
    user = message.chat.id
    users.remove(user)
    bot.send_message(user, "Все, все.")

def spam():
    global users
    while True:
        for user in users:
            bot.send_message(user, "Затем сироп вишневый!")
        time.sleep(2)

# запускаем цикл спама в отдельном потоке
spam_thread = Thread(target=spam)
spam_thread.start()

bot.polling(none_stop=True)

Получаем аудиосообщения

import struct

import telebot
import uuid

bot = telebot.TeleBot("ВСТАВЬ ТОКЕН СЮДА")

def download_audio(message):
    voice_id = message.voice.file_id
    file_description = bot.get_file(voice_id)
    downloaded_file = bot.download_file(file_description.file_path)

    filename = 'audio/' + str(uuid.uuid4()) + '.oga'

    with open(filename, 'wb') as file:
        file.write(downloaded_file)

    return filename

def convert_audio(path):
    import subprocess
    src_filename = path
    dest_filename = path + '.wav'

    subprocess.run(['ffmpeg', '-i', src_filename, dest_filename])
    return dest_filename

def speed_up(path):
    import wave

    audio = wave.open(path)
    data = audio.readframes(audio.getnframes())
    values = struct.unpack(f'<{ len(data) // 2 }h', data)
    values2 = []
    for i in range(0, len(values), 2):
        values2.append(values[i])

    data = struct.pack(f'<{ len(values2) }h', *values2)

    audio2 = wave.open(path + '.2.wav', 'wb')
    audio2.setnchannels(audio.getnchannels())
    audio2.setframerate(audio.getframerate())
    audio2.setsampwidth(audio.getsampwidth())
    audio2.writeframes(data)

    return path + '.2.wav'

@bot.message_handler(content_types=['voice'])
def voice(message):
    path = download_audio(message)
    path = convert_audio(path)
    path = speed_up(path)

    bot.send_audio(message.chat.id, open(path, 'rb').read())
    print(path)

bot.polling(none_stop=True)