Создаем 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
Здесь функция принимает два параметра и возвращает их сумму.
Внутри функций мы можем использовать любые переменные и операции с единственным ограничением:
- переменные внутри функции недоступны вне этой функции;
- переменные извне функции доступны только для чтения (если не испольуется ключевое слово
global
).
Телеграм боты
Учебник: https://groosha.gitbooks.io/telegram-bot-lessons/content/chapter1.html
Веб-версия Telegram: http://web.telegram.org/, https://webtelegram.net/
Телеграм бот - это программа, которая запущена на вашем компьютере и общается с серверами телеграма через интернет. Можно представить, что при регистрации бота, телеграм выделяет нам почтовый ящик, через который мы можем получать сообщения от пользователей и отправлять их им.
Чтобы создать собственного бота, сперва его надо зарегистрировать. Для этого в телеграме нужно добавить бота
@BotFather
и следовать инструкциям.После того, как мы введем название бота,
@BotFather
сообщит нам токен - пароль к нашему почтовому ящику.
Далее мы с вами научимся писать программы на питоне, которые, используя этот токен, смогут общаться с пользователями.
Устанавливаем библиотеки
Одно из ключевых преимуществ питона - наличие огромного количества встроенных и сторонних библиотек. Для того, чтобы сделать телеграм-бота, который будет присылать нам статьи из википедии по нашему запросу, нам потребуется установить сразу три библиотеки.
- pyTelegramBotApi - для создания бота.
Для установки сторонних библиотек вместе с питоном на компьютер устанавливается программа 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)