Урок 10

Bot API v3. Автоматизируем работу в группах

⁠⁠ С момента публикации предыдущего урока и по состоянию на момент написания этого, Telegram выпустил одно крупное обновление Bot API (3.0), а также несколько мелких (3.1-3.3). Сразу отмечу, ни отправка видеосообщений, ни платежи (по ним есть отличный пример в репозитории pyTelegramBotAPI), ни работа со стикерами рассмотрены не будут.

Удаляем сообщения

Начнём с того, что научим нашего бота в группе удалять сообщения, в которых есть ссылки. Добавим бота в группу и назначим его администратором с правом удаления и блокировки (пригодится позже).

Выдача прав боту

Дабы избежать неприятных ситуаций, нам необходимо определить две вещи: в какой именно группе он будет удалять сообщения и как отличить сообщения с ссылками от всех остальных? Для начала узнаем и запишем куда-нибудь ID нашей группы. А что делать с ссылками? Неужели нам придётся использовать регулярные выражения, создавая себе ещё одну проблему? Конечно, нет! Все «особые» элементы, будь то ссылки, @юзернеймы, команды ботов и т.д. складываются в массив entities в объекте Message, нам остаётся лишь проверить тип объекта и решить, удалять конкретное сообщение или нет. За последнее отвечает метод delete_message, принимающий на вход два аргумента: ID чата и ID сообщения.

Перейдём непосредственно к коду. Дабы упростить себе жизнь, зададим нужные условия срабатывания (нужный ID чата и непустой массив entities) сразу в хэндлер, это сэкономит нам несколько лишних проверок.

GROUP_ID = -10012345  # Ваш ID группы

@bot.message_handler(func=lambda message: message.entities is not None and message.chat.id == GROUP_ID)
def delete_links(message):
    for entity in message.entities:  # Пройдёмся по всем entities в поисках ссылок
        # url - обычная ссылка, text_link - ссылка, скрытая под текстом
        if entity.type in ["url", "text_link"]: 
            # Мы можем не проверять chat.id, он проверяется ещё в хэндлере 
            bot.delete_message(message.chat.id, message.message_id)
        else:
            return

Запустим бота и попробуем отправить сообщение с ссылкой. Если вы всё сделали правильно, оно мгновенно исчезнет и в разделе «Недавние действия» (Recent Actions) появится запись об удалённом сообщении.

Сообщение удалено

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

Read-Only и прочие «мягкие» наказания

Представьте, что у вас есть группа, например, “Международный клуб любителей мяса”, в котором люди на разных языках делятся своими впечатлениями от поедания свинины, говядины, баранины и т.д.
Конечно, время от времени в чатик будут приходить вегетарианцы и высказывать недовольство, но так как мы терпеливые люди, не будем банить веганов, а просто запретим им писать сообщения некоторое время, дабы они успокоились и вели дискуссию в рамках тематики чата. Усложним себе задачу и будем оповещать пользователей о временном ограничении прав, исходя из их языковой принадлежности.

Начиная с Telegram 4.1, у администраторов групп появилась возможность точечно настраивать права и ограничения пользователей. В Bot API за операцию ограничения ответственен метод restrict_chat_member, принимающий на вход ID чата, ID юзера, список ограничений, а также параметр until_date со значением времени (Unix Time), до которого эти ограничения действуют, причём если указать время с разницей меньше 30 секунд или больше 366 дней от текущего, Telegram воспринимает это, как «навсегда». В нашем случае Read-Only режим будет выдаваться на 10 минут, т.е. 600 секунд.

Давайте теперь разберёмся, на каком языке отвечать пользователю. В объекте User есть поле language_code, содержащее языковую метку пользователя. Не всё так просто, ведь в зависимости от настроек системы и местоположения пользователя, его языковая метка может быть ru, en-GB, en-US или вообще какой-нибудь nan-Hant-TW. Подробно о строении таких меток можно прочесть здесь. В нашем случае задача немного упрощается, т.к. нам нужен только первый элемент (сам язык), независимо от региона (будем считать, к примеру, что «английский» английский и американский английский для нас одинаковы). Напишем наипростейшую определялку языка, которая будет возвращать ru для русского языка и en для всех остальных. В реальной жизни, конечно, стоит сделать поддержку большего числа языков.

def get_language(lang_code):
    # Иногда language_code может быть None
    if not lang_code:
        return "en"
    if "-" in lang_code:
        lang_code = lang_code.split("-")[0]
    if lang_code == "ru":
        return "ru"
    else:
        return "en"

И подготовим небольшой JSON со строками:

strings = {
    "ru": {
        "ro_msg": "Вам запрещено отправлять сюда сообщения в течение 10 минут."
    },
    "en": {
        "ro_msg": "You're not allowed to send messages here for 10 minutes."
    }

Теперь напишем обработчик, который будет реагировать на набор фраз, выдавать режим Read-Only пользователю на 10 минут и уведомлять его на родном языке. Не забудьте импортировать метод time из одноимённого модуля!

restricted_messages = ["я веган", "i am vegan"]

# Выдаём Read-only за определённые фразы
@bot.message_handler(func=lambda message: message.text and message.text.lower() in restricted_messages and message.chat.id == GROUP_ID)
def set_ro(message):
    bot.restrict_chat_member(message.chat.id, message.from_user.id, until_date=time()+600)
    bot.send_message(message.chat.id, strings.get(get_language(message.from_user.language_code)).get("ro_msg"),
                     reply_to_message_id=message.message_id)

Запустим бота и попросим людей с разными language_code выступить в роли противников мяса:

Бот-полиглот

Заключение

В этом уроке мы кратко ознакомились с новыми фишками третьей версии Telegram Bot API, научились удалять сообщения, если они соответствуют одному из заданных критериев и научились «мягко» ограничивать пользователей, не удаляя их из группы. Помимо restrict_chat_member существует метод promote_chat_member для наделения пользователя определёнными администраторскими правами, он остаётся для самостоятельного изучения.

Исходный код бота этого урока, как обычно, расположен на Github.

← Урок №9 Урок №11 →