Telegram-бот: от первой строчки до размещения

Эта статья о том, как создать Telegram-бот для автоматизации нескольких рутинных задач. Если вас интересует только код, то вот ссылка. Он несложный, и его вполне можно понять самостоятельно.

Что этот бот будет делать?

Отправлять письма

Это совсем не тупо, но мне кажется, надо пояснить. Всю дорогу по ходу учебы мне нужно было отправлять кучу писем, и все они должны были быть определенного формата. К тому же gmail просто бесит, он красный и все такое! При помощи бота я смогу отправлять письма через чат. Я просто пишу в чат, а бот пусть форматирует это и отправляет по мейлу.

Сохранять всякое разное

Я очень много использую Telegram и часто натыкаюсь там на интересные для меня ссылки или просто на полезную информацию. Хотелось бы, чтобы бот сохранял все то, что я ему пересылаю, в базе данных SQL.

Что мы будем использовать при создании бота

Для API Telegram есть оболочки практически на любом языке программирования. Начиная от Python, PHP и Java и заканчивая Node.js. Мы, разумеется, будем использовать Python. Код данной оболочки находится здесь, можете убедиться сами. Также можно поучаствовать в развитии данного проекта (если сможете, конечно). Оболочки для других языков можно найти здесь.

Конечно плагиат!

Наш бот

Чат с BotFather для получения ключей API

Создание Telegram-бота это весьма забавный процесс так как, собственно, весь он строится на вашем общении с Telegram-ботом. Зовут этого бота BotFather (Бот-Отец). В приложении Telegram его можно найти по имени и начать с ним беседу.

Для начала беседы наберите /start.

Надеюсь, Бота-матери не существует….

Вы увидите список команд, при помощи которых можно создавать боты, редактировать их и управлять ними. Так как мы зашли в первый раз, то выбираем /newbot.

После введения команды /newbot вам предстоит выбрать имя и ник (username) для вашего бота. Имя — это то, как пользователи будут видеть этого бота в своем контакт-листе. А ник — имя, по которому этот бот можно будет найти в приложении Telegram. Это то же самое как и ник в Twitter, он должен быть уникальным, и чем короче, тем лучше.

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

Именно!

Код

Начнем с того, что импортируем все необходимое.

from telegram.ext import Updater, CommandHandler, MessageHandler, Filters, ConversationHandler
from telegram import ReplyKeyboardMarkup, ReplyKeyboardRemove
import logging
import sqlite3
from mail import send_mail
from sensitive import tok, user_id, name, username

Предназначение библиотек logging и sqlite3 вполне видно из их названий. Они будут использоваться для логирования и сохранения всего, что нам потребуется. В файле sensitive содержаться мои учетные данные, а в telegram.ext хранятся все необходимые обработчики.

Updater — это класс, в котором используется telegram.ext.Dispatcher. Он предоставляет интерфейс для telegram.Bot, чтобы можно было сосредоточиться исключительно на программировании бота. Его цель — получать обновления от Telegram и доставлять их указанному диспетчеру. Он также запускается в отдельном потоке, поэтому пользователь может взаимодействовать с ботом, например, из командной строки. Диспетчер поддерживает обработчики для различных типов данных: обновления от Telegram, основные текстовые команды и даже произвольные типы. Класс Updater может быть использован как служба опроса, а для получения обновлений можно использовать вебхук (webhook). Это достигается с помощью классов WebhookServer и WebhookHandler.

CommandHandler — это класс для обработки команд Telegram. Команды — это сообщения Telegram, начинающиеся с /, за которыми, возможно, следует символ @, имя бота и / или дополнительный текст.

MessageHandler — класс для обработки Telegram-сообщений. Они могут содержать текст, различные медиафайлы или обновления статуса.

ConversationHandler — это класс для ведения диалога с одним пользователем путем управления четырьмя коллекциями других обработчиков. Обратите внимание, что ни сообщения в каналах Telegram, ни групповые взаимодействия с несколькими пользователями не управляются экземплярами данного класса.

Команды

Для каждой команды написана отдельная функция. А все остальное — для работы с SQL и строками.

Я — великий «программист»!
TOKEN = tok
# Enable logging
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
                    level=logging.INFO)
logger = logging.getLogger(__name__)
TO, SUBJECT, MESSAGE = range(3)
# Define a few command handlers. These usually take the two arguments bot and
# update. Error handlers also receive the raised TelegramError object in error.
def start(bot, update):
    """Send a message when the command /start is issued."""
    user = update.message.from_user
    send = f"{user.username} started your bot. \n First name {user.first_name} \n ID:{user.id}"
    bot.send_message(chat_id=user_id, text=send)
    update.message.reply_text('Hi!')
def help(bot, update):
    """Send a message when the command /help is issued."""
    update.message.reply_text('COMMANDS \n /view_events@eventattendbot \n /add_event@eventattendbot \n '
                              '/remove_event@eventattendbot \n')
def add_event(bot, update):
    message = update.message.text
    link = message.split()[1]
    user = update.message.from_user['username']
    with sqlite3.connect("events.db") as con:
        conn = con.cursor()
        # conn.execute('''CREATE TABLE EVENT
        #              (ID INTEGER PRIMARY KEY AUTOINCREMENT NOT       NULL,
        #              LINK           TEXT    NOT NULL);''')
        conn.execute("INSERT INTO EVENT (LINK) VALUES (?)", (link, ));
        con.commit()
    update.message.reply_text("@" + str(user) + ' Added An Event Link!')
def remove_event(bot, update):
    message = update.message.text
    link = message.split()[1]
    user = update.message.from_user['username']
    with sqlite3.connect("events.db") as con:
        conn = con.cursor()
        conn.execute("DELETE FROM EVENT WHERE LINK=?", (link,))
        con.commit()
    update.message.reply_text("@" + str(user) + ' Deleted An Event Link!')
def view_events(bot, update):
    with sqlite3.connect("events.db") as conn:
        cur = conn.cursor()
        cur.execute("SELECT * FROM EVENT ORDER BY ID DESC")
        all = cur.fetchall()
        links = " LINKS \n\n"
        for a in range(len(all)):
            link = all[a][1]
            links += str((a+1)) + ". " + str(link) + "\n\n"
        links += "\n\n"
        update.message.reply_text(links)
def error(bot, update, error):
    """Log Errors caused by Updates."""
logger.warning('Update "%s" caused error "%s"', update, error)

Email

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

Пожалуйста, прекратите!
def email(bot, update):
    id = update.message.from_user.id
    if id == user_id and update.message.from_user.first_name ==                  name and update.message.from_user.username == username:
        update.message.reply_text("Give me an email address",
                                  reply_markup=ReplyKeyboardMarkup([['arushssdkbva91@gmail.com']],
                                                                   one_time_keyboard=True))
    return TO
def to(bot, update, user_data):
    user = update.message.from_user
    key = f"{user.id} to"
    value = update.message.text
    user_data[key] = value
    logger.info("email request by %s to %s", user.first_name,         update.message.text)
    update.message.reply_text("Now, the Subject for the email",    reply_markup=ReplyKeyboardRemove())
    return SUBJECT
def subject(bot, update, user_data):
    user = update.message.from_user
    key = f"{user.id} subject"
    value = update.message.text
    user_data[key] = value
    logger.info("email subject %s", update.message.text)
    update.message.reply_text("Now, the Body for the email")
    return MESSAGE
def body(bot, update, user_data):
    user = update.message.from_user
    logger.info("email body %s", update.message.text)
    email_to = user_data[f"{user.id} to"]
    email_subject = user_data[f"{user.id} subject"]
    send_mail(email_to, email_subject, update.message.text)
    del user_data[f"{user.id} to"]
    del user_data[f"{user.id} subject"]
    update.message.reply_text(f"email sent!")
    return ConversationHandler.END
def cancel(bot, update):
    update.message.reply_text('Canceled.')
    return ConversationHandler.END

Как вы могли заметить, функцию send_mail() я импортировал из модуля mail. Для отправки почтовых сообщений используется протокол SMTP.

def send_mail(to, subject, body, my=me):
    smt = smtplib.SMTP('smtp.gmail.com', 587)
    smt.ehlo()
    smt.starttls()
    smt.login(user=my, password=pwd)
    sub = subject
    body = body
    message = "Subject: " + sub + "\n" + body + "\n"
   smt.sendmail(my, to, message)
   smt.quit()

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

Вот и всё?

Вообще-то нет. Вам еще осталось все это запустить.

У меня дома все не на месте…
def main():
    """Start the bot."""
    updater = Updater(TOKEN)
    dp = updater.dispatcher
    dp.add_handler(CommandHandler("start", start))
    dp.add_handler(CommandHandler("help", help))
    dp.add_handler(CommandHandler("add_event", add_event))
    dp.add_handler(CommandHandler("view_events", view_events))
    dp.add_handler(CommandHandler("remove_event", remove_event))
    email_handler = ConversationHandler(
        entry_points=[CommandHandler('email', email)],
        states={
            TO: [MessageHandler(Filters.text, to, pass_user_data=True)],
            SUBJECT: [MessageHandler(Filters.text, subject, pass_user_data=True)],
            MESSAGE: [MessageHandler(Filters.text, body, pass_user_data=True)]
        },
        fallbacks=[CommandHandler('cancel', cancel)]
    )
    dp.add_handler(email_handler)
    dp.add_error_handler(error)
    updater.start_polling()
    updater.idle()

Размещение кода

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

Сие никому не ведомо…
  1. Pythonanywhere : бесплатно и просто. Откройте терминал и в нем запустите файл с вашим ботом. Но не ждите, что он будет работать долго.
  2. Heroku: не будем изобретать колесо. Вот здесь дана пошаговая инструкция по размещению на сервисе Heroku.

Вот здесь также собраны инструкции по размещению Telegram-ботов.