Хардкорная разработка под Телеграм. Бот-модератор своими руками. Часть 1

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

А давайте напишем своего крутого бота-модератора чатов на Python. Пусть он сможет чистить чат, банить участников и выдавать им предупреждения, приветствовать новых участников чата и не только.

Мы сделаем полноценного масштабируемого бота с учётом лимитов и особенностей Телеграма. Начнём с того, что создадим структуру проекта и научим бота реагировать на простые команды.

Для прохождения туториала вы должны знать Python и понимать, что такое асинхронность и декораторы. Мы будем пользоваться библиотекой Telethon для работы с Telegram API (подробнее ниже) и библиотекой Databases с SQLAlchemy Core для баз данных (уже со следующей части).

Вы можете посмотреть код этой части туториала на GitHub.

Создание бота

Перед тем, как начать писать код, вам нужно будет создать самого бота. Это делается через официального бота BotFather.

Он попросит вас указать имя бота и его юзернейм. Когда вы сделаете своего "Робота Васю 2077", вы получите токен — с помощью него можно управлять ботом.

Telegram API

В своей статье "Всё, о чём должен знать разработчик Телеграм-ботов" я объяснял, чем отличается Telegram API от Telegram Bot API.

Bot API более ограниченный: например, с его помощью боты не могут получать список участников группы или старые сообщения. И хотя для этого туториала эти возможности не понадобятся, я предпочитаю в любом случае использовать именно Telegram API. А то представьте, как обидно: вот пишете вы бота, развиваете его, и вдруг вам становится нужна какая-то фича, из-за которой всего бота придётся переписывать на другом API. И сразу грустненько становится.

Итак, для работы с Telegram API мы будем использовать библиотеку Telethon:

$ pip install telethon

Так как Telegram API изначально был предназначен для создания клиентов мессенджера, для его использования вам нужно будет зарегистрировать своё приложение на my.telegram.org. Да, даже если вы используете API только для запуска ботов.

Форму вы можете заполнить любым способом. Вы получите api_id и api_hash вашего "приложения". Они нам понадобятся.

Создаём проект

Для начала создадим проект с такой структурой:

app/
    __init__.py
    __main__.py
    handlers.py
config.py

В handlers.py будут находиться хендлеры — обработчики событий.

В config.py будем хранить секретные данные. Давайте сразу его заполним:

BOT_TOKEN = 'токен-вашего-бота'
API_ID = 123456789
API_HASH = 'ваше-значение'

Конечно, вы можете хранить эти данные любым удобным для вас способом. Но я буду далее использовать файл config.

Итак, давайте приступим к заполнению __init__.py. Главное, что нам нужно из библиотеки telethon — класс TelegramClient. Именно с его помощью мы сможем авторизоваться через бота.

Позже нам понадобится хранить информацию о боте (его id, юзернейм и так далее). Поэтому давайте сразу сделаем свой собственный класс, который будет наследоваться от TelegramClient:

import logging
from telethon import TelegramClient
import config

class Bot(TelegramClient):
    def init(self, *args, **kwargs):
        super().init(*args, **kwargs)
        self.me = None  # тут будет информация о боте

# создаём бота, используя данные API
bot = Bot('bot', config.API_ID, config.API_HASH)

Пока что мы только создали объект бота и ничего больше. Чуть позже мы реализуем авторизацию бота с помощью токена. (Указанная строка 'bot'будет названием файла сессии: он создастся после авторизации.)

Зададим для бота parse_mode — режим разметки по умолчанию. Он будет использоваться при отправке и получении сообщений с разметкой (жирный текст, курсив, ссылки и так далее). Выберем HTML.

И заодно настроим логгинг:

bot.parse_mode = 'HTML'
logging.basicConfig(level=logging.INFO)

Когда объект bot уже создан, нам нужно зарегистрировать все хендлеры: для этого импортируем app.handlers (сейчас в том файле ничего нет).

import app.handlers

Теперь напишем функцию, которая будет авторизовывать бота и обрабатывать его апдейты.

async def start():
    # Подключиться к серверу
    await bot.connect()
    
    # Войти через токен. Метод sign_in возвращает информацию о боте. Мы сразу сохраним её в bot.me
    bot.me = await bot.sign_in(bot_token=config.BOT_TOKEN)
    
    # Начать получать апдейты от Телеграма и запустить все хендлеры
    await bot.run_until_disconnected()

И, наконец, функцию run, которая запускает нашу асинхронную функцию start:

def run():
    bot.loop.run_until_complete(start())
Теперь __init__.py выглядит вот так
import logging

from telethon import TelegramClient

import config


class Bot(TelegramClient):
    def __init__(self, *args):
        super().__init__(*args)
        self.me = None


bot = Bot('bot', config.API_ID, config.API_HASH)
bot.parse_mode = 'HTML'
logging.basicConfig(level=logging.INFO)

import app.handlers


async def start():
    await bot.connect()
    bot.me = await bot.sign_in(bot_token=config.BOT_TOKEN)
    await bot.run_until_disconnected()


def run():
    bot.loop.run_until_complete(start())

Переходим к хендлерам.

Как я уже говорил, в handlers.py мы будем обрабатывать события. Давайте будем ловить события о добавлении бота в группу.

Как это сделать? Мы должны ловить именно системные сообщения (это сообщения вида "добавил пользователя в группу", "изменил название группы", "закрепил сообщения" и так далее). Если это системное сообщение:
а) было в группе,
б) говорит о том, что какой-то пользователь добавил другого пользователя,
в) относится именно к боту,
то это значит, что нашего бота добавили в группу. Пусть тогда бот напишет в эту группу: "Приветствую, господа!"

Чтобы использовать событие с новыми системными сообщениями, нам понадобится класс telethon.events.ChatAction.

Всё это будет выглядеть так:

from telethon import events

from app import bot

@bot.on(events.ChatAction())
async def on_join(event: events.ChatAction.Event):
    if event.is_group and event.user_added and event.user_id == bot.me.id:
				await bot.send_message(event.chat.id, 'Приветствую, господа!')

Декоратором @bot.on мы привязываем нашу функцию к нужному событию. Функция принимает объект типа "событие о системном сообщении". Если условия выполняются, то отправляется сообщение.

Теперь последний штрих — файл __main__.py В нём мы просто импортируем и запускаем нашу функцию run:

from app import run

run()

Готово! Бота можно запускать.

$ python -m app

Подробнее о хендлерах

Давайте немножко упростим нашу функцию с приветствием:

@bot.on(events.ChatAction(func=lambda e: e.is_group and e.user_added and e.user_id == bot.me.id))
async def on_join(event: events.ChatAction.Event):
    await event.respond('Приветствую, господа!')

Теперь мы передаём в конструктор ChatAction аргумент func — это функция для фильтрации событий. Сюда мы перенесли условие. Теперь хендлер будет срабатывать только для нужных событий.

Также обратите внимание на функцию event.respond. Она отправляет сообщение в чат, из которого пришёл event. На самом деле это просто сокращение для функции bot.send_message, которую мы использовали выше.

Ну что ж, если у нас всё работает, то можно и поиграться! Вы можете попробовать написать свои хендлеры. Например:

Простой триггер

Пример хендлера, который будет отвечать на каждый вопрос "Ты кто?":

...
from telethon.tl.custom import Message
...

@bot.on(events.NewMessage(func=lambda e: e.text.lower() == 'ты кто?'))
async def who_are_you(event: Message):
    await event.respond('Я умный, красивый, в меру упитанный мужчина в полном расцвете сил!')

Message — это просто удобный тип для события NewMessage.

Чтобы бот видел сообщения-не-команды, сделайте его админом или отключите privacy mode.

Отправка котиков

Пример хендлера, который кидает картинку с котиком по команде /cat.

...
from telethon.tl.custom import Message
...

@bot.on(events.NewMessage(func=lambda e: e.text.lower() == '/cat'))
async def send_cat(event: Message):
    await bot.send_message(event.chat.id, file='path/to/cat.png')

Если вы много раз используете одну и ту же картинку, загрузите её на сервер Телеграма с помощью метода bot.upload_file() и используйте несколько раз.

Бросок кубика

Пример хендлера, который кидает кубик по команде /dice (Анимация эмодзи кубика)

...
from telethon.tl.custom import Message
from telethon.tl.types import InputMediaDice
...

@bot.on(events.NewMessage(func=lambda e: e.text.lower() == '/dice'))
async def send_dice(event: Message):
    await bot.send_message(event.chat.id, file=InputMediaDice('?'))
Приветствие новых участников

Пример хендлера, который приветствует пользователей, которые зашли в чат:

@bot.on(events.ChatAction(func=lambda e: (e.user_added or e.user_joined) and e.user_id != bot.me.id))
async def greet(event: events.ChatAction.Event):
    await event.respond('Приветик!')

Но мы пришли сюда не за этим. Мы хотим сделать команды и другие фичи для администраторов группы! Для этого нам нужно уметь отличать админов от простых участников группы. Этим мы займёмся в следующей части туториала. Мы подключим базу данных и научимся хитрым способом получать админов.

Продолжение следует.


Напомню, что вы можете посмотреть получившийся код на GitHub. Любые вопросы задавайте в комментариях. Наверняка на них отвечу я или кто-нибудь ещё. И спасибо vanutp за справочную информацию для статьи :)

Источник: https://habr.com/ru/post/553968/


Интересные статьи

Интересные статьи

Речь пойдёт подключении дисплея AT070TN94 с параллельным интерфейсом к контроллеру STM32H743, начиная от выбора электронных компонентов, заканчивая настройкой блока LTDC....
Участники выставки потребительской электроники Consumer Electronics Show каждый год расширяют привычные представления о гаджетах для мониторинга и поддержания здоров...
Продолжение статьи: «Как взлететь на батарейках или немного теории электропарамотора. Часть 1» habr.com/ru/post/475174 4. Описание электропарамотора SkyMax. В предыдущей статье уже фигурир...
Введение Я работаю программистом в отделе разработки и тестирования средств защиты мобильных платформ компании «Код безопасности». Перед командой мобильной разработки была поставлена задача порт...
В больших проектах, состоящих из десятков и сотен взаимодействующих сервисов, всё чаще становится обязательным подход к документации как к коду — docs as code. Я покажу, как можно применять эт...