Как сделать Телеграмм бот на Python

На обучающей платформе Skillbox появился новый бесплатный вариант обучения — bootcamp. Пока есть три варианта: дизайн, программирование (на Python) и маркетинг. Я начал проходить 2 вариант. Как раз в первых двух занятиях разбиралось как создавать Телеграмм бот на Python. Эта тема меня интересовала давно, а в лекциях всё было описано так легко и просто, что я всё-таки решил попробовать это повторить а может и дополнить чем-то своим. Предполагаю, что Вы владеете языком Python хотя бы на начальном уровне.

Регистрация Бота в Телеграмме

Чтобы создавать любого бота, вначале его нужно зарегистрировать в Телеграмме. Если Вы уже общаетесь в группах Телеграмма, то в окне поиска наберите «BotFather» или перейдите по ссылке из прошлого предложения. В результате запуститься так называемый «отец всех ботов».

Рис. 1 BotFather

Запускаем его командой «/start» и попадаем в его меню.

Рис. 2 Меню.

Выбираем команду «/newbot«. Отвечая на вопросы и используя подсказки BotFather, вводим имя Вашего бота (оно будет выводится сверху и использоваться для поиска — AlexTaaPython). Далее вводим так называемый username, который должен заканчиваться на Bot или _bot (у меня это AlexTaaPythonBot). В результате получаем сообщение об успешном создании бота. Позже можно будет добавить его описание и иконку, смотрите «/help» чтобы понять как это сделать. Для дальнейшей работы нам нужен так называемый token. Он нужен для доступа и управления нашим ботом из кода программы на Python. Это секретный «ключ» и чтобы использовали его только Вы, сохраните его и никому не передавайте во избежание непредвиденных последствий с Вашим ботом.

Рис. 3 Диалог с BotFather

Итак, мой бот зарегистрирован. Можно к нему обратиться, но он пока ничего не умеет.

Что будет уметь мой бот

Ботов принято использовать для автоматизации повторяющихся несложных рутинных задач. Если посмотреть заказы на биржах фриланса, то задание на разработку бота там одно из самых популярных. Бот может вести регистрацию на мероприятия, периодически собирать и отправлять информацию с сайтов, проводить мини опросы и т.п. Функциональность определяется его программной частью. Мне понравились две функции из лекций буткемпа: преобразование голоса в текст и обработка изображений и я решил их повторить. Чтобы от бота было больше пользы, я решил научить его составлять список дел, в том числе используя голосовой ввод. Если бота будут использовать несколько человек, то будет логично что они видят только свой список дел. Телеграмм предоставляет возможность отправлять файлы, изображения, голосовые сообщения, стикеры, писать текстовые сообщения, отправлять команды боту и т.д. Некоторые виды «трафика» я буду обрабатывать в своей программе, чтобы реализовать заявляемую функциональность.

Приветствие и меню

Итак, начнём писать программу для Телеграмм бот на Python. Это можно делать в любом текстовом редакторе, даже в блокноте. Но лучше в каком-то специализированном с подсветкой, подсказками и автодополнением, а также возможностью выполнять код и отлаживать его при необходимости. Один из лучших вариантов PyCharm. Я буду использовать Wing.101.8. Он простой и быстрый, позволяет выполнять и отлаживать программу, но почти нет раскрашивания и точно нет автодополнения и подсказки функций и переменных в классах и форматах функций. В результате всё пишешь сам, а если что подзабыл — лезешь в документацию и так лучше запоминаешь как бы » на кончиках пальцев».

Начнём с импортирования библиотек, создания бота и вывода приветствия.

import os
import telebot
# ↓↓↓ Ниже нужно вставить токен, который дал BotFather при регистрации
# Пример: token = '6000000000:AAAAAAA_OOOssssssss-TTTTTTTTT7bbQn0'
token = '< Ваш токен >'  # <<< Ваш токен

bot = telebot.TeleBot(token)

@bot.message_handler(commands=['start'])
def say_hi(message):
    # Функция, отправляющая "Привет" в ответ на команду /start
    bot.send_message(message.chat.id, 'Привет, ' + message.chat.first_name)
    menu_message(message)

# Запускаем бота. Он будет работать до тех пор, пока мы не прервём его Ctrl+C.
bot.polling()

Библиотека telebot обеспечивает создание, запуск бота и его взаимодействие с Телеграмм по API. Используя выданный BotFather токен, создаём нашего телебота в переменной bot. Далее, используя декоратор из библиотеки, создаём функцию — обработчик команды ‘/start‘. В ней выводим приветствие с именем вызывающего пользователя. В конце запускаем бота инструкцией bot.polling(). Всё, минимальный функционал реализован. Можете запустить и проверить.

После приветствия сразу вызовем функцию menu_message(message), отображающею меню, которая также доступна по команде «/menu«. Код функции ниже, а далее ещё три функции, обрабатывающие команды меню 1, 2, 3.

@bot.message_handler(commands=['menu'])
def menu_message(message):
    bot.send_message(message.chat.id, '''Посмотри что я умею:\n/1 Составлять список дел\n/2 Увеличивать на 1.8 контрастность картинки\n/3 Преобразовывать голосовое сообщение в текст''')
    
@bot.message_handler(commands=['1'])
def help_list(message):
    global mode
    mode = 1
    bot.send_message(message.chat.id, '''/new_list Новый список дел\n/add_event Добавить дело\n/del_event Удалить дело\n/show_events Показать список дел''')
    
@bot.message_handler(commands=['2'])
def help_photo(message):
    global mode
    mode = 0
    bot.send_message(message.chat.id, 'Нажав на иконку - скрепку, отправьте Вашу картинку')
    
@bot.message_handler(commands=['3'])
def help_transcript(message):
    global mode
    mode = 0
    bot.send_message(message.chat.id, 'Нажав и удерживая значок микрофона, проговорите своё сообщение')

Чтобы фрагмент выводимого текста использовать как кнопку для активации команды поставьте перед ним ‘/’ и Телеграмм выделит фрагмент текста до следующего пробела цветом и на него можно кликнуть. Если в декораторе после commands= в [ ] указан этот фрагмент, то будет выполнена предваряемая декоратором функция.

Преобразование голоса в текст

Сейчас голосовым управлением никого не удивишь. И как раз в первой лекции буткемпа разбиралось как голосовое сообщение преобразовать в текст. Для этого предлагается воспользоваться библиотекой SpeechRecognition для преобразования голоса в текст и библиотекой pydub для преобразования форматов файлов. Начинаем с установки библиотек командой pip install SpeechRecognition pydub. Далее импортируем библиотеки в проект:

import speech_recognition
from pydub import AudioSegment

Телеграм отправляет голосовые в формате .oga, а SpeechRecognition нужен формат .wav. Напишем функцию для преобразования файла:

def oga2wav(filename):
    # Конвертация формата файлов
    new_filename = filename.replace('.oga', '.wav')
    audio = AudioSegment.from_file(filename)
    audio.export(new_filename, format='wav')
    return new_filename

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

def download_file(bot, file_id):
    # Скачивание файла, который прислал пользователь
    file_info = bot.get_file(file_id)
    downloaded_file = bot.download_file(file_info.file_path)
    filename = file_id + file_info.file_path
    filename = filename.replace('/', '_')
    with open(filename, 'wb') as f:
        f.write(downloaded_file)
    return filename

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

def recognize_speech(oga_filename):
    # Перевод голоса в текст + удаление использованных файлов
    wav_filename = oga2wav(oga_filename)
    recognizer = speech_recognition.Recognizer()

    with speech_recognition.WavFile(wav_filename) as source:     
        wav_audio = recognizer.record(source)

    text = recognizer.recognize_google(wav_audio, language='ru')

    if os.path.exists(oga_filename):
        os.remove(oga_filename)

    if os.path.exists(wav_filename):
        os.remove(wav_filename)

    return text

Осталось собрать всё в функции обработки типа контента voice, фрагмент кода которой приведён ниже. Глобальная переменная mode определяет режим обработки в некоторых функциях бота. В режиме 0 пользователю возвращается введённое голосом сообщение в виде текста.

@bot.message_handler(content_types=['voice'])
def transcript(message):
    # Функция, отправляющая текст в ответ на голосовое
    filename = download_file(bot, message.voice.file_id)
    txt = recognize_speech(filename)
    global mode
    ...
    if mode == 0:
        bot.send_message(message.chat.id, txt)

Установка ffmpeg

При проверке работы только что написанного кода оказалось, что бот запускается и падает с ошибкой. Оказалось, что на моем ноуте нет кодеков библиотеки ffmpeg. Установка с помощью pip install не сработала из-за переноса страницы скачивания. Погуглив, выяснилось, что нужно скачать zip архив на сайте https://www.gyan.dev и распаковать его. Там три .exe файла и их нужно скопировать в папку с Python (у меня там Python310.exe). Что и откуда качать видно на рисунке ниже.

Рис. 4 Сайт www.gyan.dev

После копирования всё заработало. Кстати, при работе на сервере этой ошибки уже не было.

Пробуем обработку изображений

Во второй лекции упоминаемого выше буткемпа от Skillbox разбиралось как можно наложить фильтры на изображение. Чтобы получить опыт в такой обработке, я тоже решил реализовать это в моём боте. Для обработки изображений воспользуемся библиотекой pillow (документация). Во-первых, импортируем из неё нужные функции:

from PIL import Image, ImageEnhance, ImageFilter

Для обработки изображений в библиотеке доступны фильтры:

  • BLUR
  • CONTOUR
  • DETAIL
  • EDGE_ENHANCE
  • EDGE_ENHANCE_MORE
  • EMBOSS
  • FIND_EDGES
  • SHARPEN
  • SMOOTH
  • SMOOTH_MORE

Во-вторых, напишем функцию, повышающую контрастность изображения до 1.8, код ниже:

def transform_image(filename):
    # Считаем изображение с помощью Image.open
    source_image = Image.open(filename)
    # Увеличим контрастность
    enhanced_image = ImageEnhance.Contrast(source_image).enhance(1.8)
    # Вдавленное изображение - нам этого не надо
    # enhanced_image = source_image.filter(ImageFilter.EMBOSS)
    
    # Нужно конвертировать RGBA в RGB для сохранения в JPEG:
    enhanced_image = enhanced_image.convert('RGB')
    # Пересохраним изображение:
    enhanced_image.save(filename)
    return filename

В-третьих, напишем функцию обработки типа контента photo, фрагмент кода которой приведён ниже:

@bot.message_handler(content_types=['photo'])
def resend_photo(message):
    # Функция отправки обработанного изображения
    file_id = message.photo[-1].file_id
    filename = download_file(bot, file_id)

    # Трансформируем изображение
    transform_image(filename)

    image = open(filename, 'rb')
    bot.send_photo(message.chat.id, image)
    image.close()
    
    # Не забываем удалять ненужные изображения
    if os.path.exists(filename):
        os.remove(filename)

Телеграмм включает в сообщение 5 версий изображения. Максимальное качество — в последнем изображении в списке, поэтому для скачивания мы берем идентификатор из объекта message.photo[-1]. Чтобы обработать изображение используем написанную выше функцию transform_image и возвращаем пользователю то, что получилось функцией бота send_photo. В конце подчищаем за собой ненужный далее файл. Изображение для обработки добавляется по клику на скрепке, а примерный результат показан на рисунке ниже:

Рис. 5 Наложение фильтра на изображение

Список дел для каждого пользователя

Чтобы расширить функциональность бота, добавим возможность вести список дел для каждого пользователя. Списки дел будем хранить в словаре de = dict(), где ключом будет служить идентификатор пользователя message.from_user.id. Логику работы предлагаю следующую: по команде 1 главного меню переходим в меню списка и уже его команды используем для просмотра или изменения списка. Если нужно добавить или удалить дело из списка, то будем его писать текстом или использовать разработанное ранее преобразование голосового сообщения в текст. Режим работы задаём в переменной mode : добавить — 1, удалить — 2. В результате функции обработки text и voice будут такими, как в коде ниже:

@bot.message_handler(content_types=['text'])
def read_text(message):
    global mode
    global de
    user_id = message.from_user.id    
    txt = message.text
    print('text:', user_id, txt)
    if mode == 1:
        le = de.get(user_id, [])
        le.append(txt)
        de[user_id] = le
        bot.send_message(message.chat.id, show_events(le))
    if mode == 2:   
        le = de.get(user_id, [])
        if txt in le:
            le.remove(txt)
            de[user_id] = le
        else:
            bot.send_message(message.chat.id, f'Дела "{txt}" нет в списке !')
        bot.send_message(message.chat.id, show_events(le))
    if mode == 0:
        bot.send_message(message.chat.id, txt)

@bot.message_handler(content_types=['voice'])
def transcript(message):
    # Функция, отправляющая текст в ответ на голосовое
    filename = download_file(bot, message.voice.file_id)
    txt = recognize_speech(filename)
    global mode
    global de
    user_id = message.from_user.id    
    print('voice:', user_id, txt)
    if mode == 1:
        le = de.get(user_id, [])
        le.append(txt)
        de[user_id] = le
        bot.send_message(message.chat.id, show_events(le))
    if mode == 2:        
        le = de.get(user_id, [])
        if txt in le:
            le.remove(txt)
            de[user_id] = le
        else:
            bot.send_message(message.chat.id, f'Дела "{txt}" нет в списке !')
        bot.send_message(message.chat.id, show_events(le))
    if mode == 0:
        bot.send_message(message.chat.id, txt)

В обоих функциях логика работы похожа. Используем глобальные переменные mode и de, получаем для ключа словаря значение user_id, текстовое или преобразованное голосовое сообщение выводим в консоль для контроля. Далее добавляем его в список соответствующего пользователя, или пробуем удалить из списка если оно там есть. Проверка при удалении для упрощения реализации только на полное совпадение. Лог работы бота (то, что выводится в коде командами print) для двух разных пользователей на рисунке ниже:

Рис. 6 Лог работы бота

Реализация логики работы дополнительного меню для списка дел приведена в коде ниже:

def show_events(le):
    res = "Список дел :\n"
    for ev in le:
        res += ev + '\n'
    res += '''\n/new_list Новый список дел\n/add_event Добавить дело\n/del_event Удалить дело\n/show_events Показать список дел\n\n/menu Показать меню'''
    return res
    
@bot.message_handler(commands=['new_list'])
def new_list(message):
    global mode
    global de
    user_id = message.from_user.id
    print(user_id)
    mode = 1
    de[user_id] = list()
    bot.send_message(message.chat.id, '''Список дел пуст\n\n/add_event Добавить дело\n/del_event Удалить дело\n/show_events Показать список дел''')

@bot.message_handler(commands=['add_event'])
def add_list(message):
    global mode
    mode = 1
    bot.send_message(message.chat.id, 'Напишите или скажите что хотите сделать\n')
    
@bot.message_handler(commands=['del_event'])
def del_list(message):
    global mode
    mode = 2
    bot.send_message(message.chat.id, 'Напишите или скажите что хотите удалить\n')

@bot.message_handler(commands=['show_events'])
def show_myEvents(message):
    global mode
    global de
    user_id = message.from_user.id    
    mode = 1
    bot.send_message(message.chat.id, show_events(de.get(user_id, [])))

В результате работы Вас и бота может получиться список дел как на рисунке ниже (отображено добавление последних двух команд лога: голосовое — читать, текст — сходить в магазин):

Рис. 7 Добавление дел в список голосом или вводом текста

Телеграмм бот на Python готов — запускаем на сервере

Итак, я написал Телеграмм бот на Python и отладил его планируемую функциональность. Настало время запустить его на сервере в Интернете. В лекциях рекомендуют сделать это на сайте pythonanywhere.com. Буду действовать по их инструкции. Захожу на сайт и регистрирую там свою учетную запись. Справа вверху кликаю по нужному пункту меню, попадаю на страницу «Планы и цены». Для моего случая подойдёт бесплатный вариант для начинающих, хотя есть и другие тарифные планы.

Рис. 8 Регистрация аккаунта

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

Рис. 9 Добавляем файл

Переносим код …

Кликнув по кнопке «+ Open another file«, создаём файл с именем, например, «tg_bot.py» в подсказанной папке /home/AlexTAA. Возможно, её нужно будет перенабрать. Далее кликаем по кнопке «Open» и попадаем в редактор файла.

Рис. 10 Задаём имя файла

В это открывшееся окно редактора Вам нужно скопировать код телеграмм бота. Не забудьте его сохранить, кликнув по кнопке справа вверху.

Рис. 11 Копируем текст программы бота

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

Рис. 12 Создать консоль управления

В консоли нужно сначала установить необходимые для работы библиотеки. Действия аналогичны работе в любой консоли. Вводим команду «pip install pyTelegramBotAPI SpeechRecognition pydub«. В результате должен быть отображён отчёт об успешной установке.

Рис. 13 Устанавливаем необходимые библиотеки

Смотрим результат …

Теперь всё готово для запуска бота. Давайте это сделаем командой «python tg_bot.py«. В результате в Телеграмме наш бот снова ожил. Вводим там любые команды и видим как он их обрабатывает.

Рис. 14 Обработка команд серверным ботом

Чтобы завершить работу бота на сервере вводим комбинацию клавиш «Ctrl + C«. В консоли отображаются действия по команде «/new_list» и остановка бота.

Рис. 15 Запуск, работа и остановка бота

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

Рис. 16 Состояние рабочей области

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

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *