Создание своего собственного телеграм-бота является одним из увлекательных и полезных способов практического применения программирования. Я решил создать своего бота как вариант добавления нового увлекательного проекта в свой портфолио, а также для создания комфорта и удобства для пользователей, которые используют телеграм в своей повседневной жизни.

После анализа нескольких библиотек для создания телеграм-ботов, я выбрал библиотеку Telebot, которая предоставляет богатый набор инструментов для разработки функционально-насыщенных ботов.

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

Тестировать бота мы будем с помощью моей жены.

Библиотека Telebot для телеграм ботов на Python предоставляет ряд основных функций:

  1. TeleBot(token) – конструктор класса, который принимает токен вашего бота.

  2. send_message(chat_id, text) – отправляет сообщение пользователю с указанным chatid.

  3. sendphoto(chatid, photo) – отправляет фотографию пользователю с указанным chatid.

  4. send_audio(chat_id, audio) – отправляет аудио файл пользователю с указанным chatid.

  5. senddocument(chatid, document) – отправляет документ пользователю с указанным chatid.

  6. send_video(chat_id, video) – отправляет видео пользователю с указанным chatid.

  7. sendlocation(chatid, latitude, longitude) – отправляет геопозицию пользователю с указанным chatid.

  8. reply_to(message, text) – отправляет ответ на сообщение.

  9. edit_message_text(chat_id, message_id, text) – редактирует текст сообщения.

  10. delete_message(chat_id, message_id) – удаляет сообщение.

  11. get_updates() – получает все обновления.

  12. get_chat_member(chat_id, user_id) – получает информацию о пользователе в конкретном чате.

  13. get_chat(chat_id) – получает информацию о чате.

Начнем с импорта необходимых библиотек.

import telebot # сама библиотека telebot
import time # необходим для cрока /mute и автоматического размута после срока мута

Создаем строчку для вписания токена бота, который можно получить от https://t.me/BotFather и так же создаем сам объект бота

bot = telebot.TeleBot('TOKEN') # в TOKEN мы вводим непосредственно сам полученный токен.

Затем определяем словарь для хранения статистики чата.

Далее идут обработчики команд:

  • команда /start выводит приветственное сообщение.

  • команда /help выводит список доступных команд.

Словарь для хранения статистики:

stats = {}

Обработчик команды /start:

@bot.message_handler(commands=['start'])
def start(message):
    bot.reply_to(message, "Привет! Я бот для управления чатом. Напиши /help, чтобы узнать, что я умею.")

Обработчик команды /help:

@bot.message_handler(commands=['help'])
def help(message):
    bot.reply_to(message, "/kick - кикнуть пользователя\n/mute - замутить пользователя на определенное время\n/unmute - размутить пользователя\n/stats - показать статистику чата\n/selfstat - показать свою статистику")

Обработчик команды /kick:

В обработчике команды /kick проверяется, было ли оно использовано в ответ на сообщение пользователя. Если да, то определяется chat_id (идентификатор чата) и user_id (идентификатор пользователя), на которого нужно накинуть мут. Затем проверяется статус пользователя (администратор или нет). Если пользователь является администратором, то выводится сообщение, что кикнуть его невозможно. В противном случае кик происходит с помощью метода kick_chat_member() и выводится соответствующее сообщение.

@bot.message_handler(commands=['kick'])
def kick_user(message):
    if message.reply_to_message:
        chat_id = message.chat.id
        user_id = message.reply_to_message.from_user.id
        user_status = bot.get_chat_member(chat_id, user_id).status
        if user_status == 'administrator' or user_status == 'creator':
            bot.reply_to(message, "Невозможно кикнуть администратора.")
        else:
            bot.kick_chat_member(chat_id, user_id)
            bot.reply_to(message, f"Пользователь {message.reply_to_message.from_user.username} был кикнут.")
    else:
        bot.reply_to(message, "Эта команда должна быть использована в ответ на сообщение пользователя, которого вы хотите кикнуть.")

Обработчик команды /mute и /unmute:

Обработчик команды /mute ограничивает возможности отправки сообщений выбранного пользователя на определенное количество времени. Эта команда принимает аргумент - время в минутах, на которое нужно замутить (ограничить) пользователя. Если аргумент не указан, то устанавливается значение по умолчанию - 1 минута. Если аргумент был указан, то проверяется его корректность и максимальное значение (не больше 1 дня). Если время указано верно, то с помощью метода restrictchatmember() устанавливается ограничение на отправку сообщений пользователем на заданное время. В случае успеха выводится соответствующее сообщение.

Обработчик команды /unmute снимает мут с выбранного пользователя. При этом с помощью метода restrictchatmember() включаются все возможности пользователя для отправки сообщений. В случае успеха также выводится соответствующее сообщение.

Оба обработчика проверяют, было ли вызвано данная команда в ответ на сообщение пользователя. Если да, то определяются chatid (идентификатор чата) и userid (идентификатор пользователя), на которого нужно накинуть мут. Также проверяется статус пользователя (администратор или нет). Если пользователь является администратором, то выводится сообщение, что замутить его невозможно. Если команда использована без ответа на сообщение пользователя, которого нужно замутить/размутить, то выводится соответствующее сообщение.

Обработчик команды /mute:

@bot.message_handler(commands=['mute'])
def mute_user(message):
    if message.reply_to_message:
        chat_id = message.chat.id
        user_id = message.reply_to_message.from_user.id
        user_status = bot.get_chat_member(chat_id, user_id).status
        if user_status == 'administrator' or user_status == 'creator':
            bot.reply_to(message, "Невозможно замутить администратора.")
        else:
            duration = 60 # Значение по умолчанию - 1 минута
            args = message.text.split()[1:]
            if args:
                try:
                    duration = int(args[0])
                except ValueError:
                    bot.reply_to(message, "Неправильный формат времени.")
                    return
                if duration < 1:
                    bot.reply_to(message, "Время должно быть положительным числом.")
                    return
                if duration > 1440:
                    bot.reply_to(message, "Максимальное время - 1 день.")
                    return
            bot.restrict_chat_member(chat_id, user_id, until_date=time.time()+duration*60)
            bot.reply_to(message, f"Пользователь {message.reply_to_message.from_user.username} замучен на {duration} минут.")
    else:
        bot.reply_to(message, "Эта команда должна быть использована в ответ на сообщение пользователя, которого вы хотите замутить.")
Тест функции /mute
Тест функции /mute
Результат функции для пользователя
Результат функции для пользователя

Обработчик команды /unmute:

@bot.message_handler(commands=['unmute'])
def unmute_user(message):
    if message.reply_to_message:
        chat_id = message.chat.id
        user_id = message.reply_to_message.from_user.id
        bot.restrict_chat_member(chat_id, user_id, can_send_messages=True, can_send_media_messages=True, can_send_other_messages=True, can_add_web_page_previews=True)
        bot.reply_to(message, f"Пользователь {message.reply_to_message.from_user.username} размучен.")
    else:
        bot.reply_to(message, "Эта команда должна быть использована в ответ на сообщение пользователя, которого вы хотите размутить.")
Тестируем функцию /unmute
Тестируем функцию /unmute

Обработчик команды /stats:

@bot.message_handler(commands=['stats'])
def chat_stats(message):
    chat_id = message.chat.id
    if chat_id not in stats:
        bot.reply_to(message, "Статистика чата пуста.")
    else:
        total_messages = stats[chat_id]['total_messages']
        unique_users = len(stats[chat_id]['users'])
        bot.reply_to(message, f"Статистика чата:\nВсего сообщений: {total_messages}\nУникальных пользователей: {unique_users}")

Обработчик команды /selfstat:

@bot.message_handler(commands=['selfstat'])
def user_stats(message):
    chat_id = message.chat.id
    user_id = message.from_user.id
    username = message.from_user.username
    if chat_id not in stats:
        bot.reply_to(message, "Статистика чата пуста.")
    else:
        if user_id not in stats[chat_id]['users']:
            bot.reply_to(message, "Вы еще не отправляли сообщений в этом чате.")
        else:
            user_messages = stats[chat_id]['users'][user_id]['messages']
            total_messages = stats[chat_id]['total_messages']
            percentage = round(user_messages / total_messages * 100, 2)
            bot.reply_to(message, f"Статистика для пользователя @{username}:\nВсего сообщений: {user_messages}\nПроцент от общего количества сообщений: {percentage}%")

Данный код содержит два обработчика команд: /stats и /selfstat для вывода статистики чата и статистики конкретного пользователя соответственно.

Обработчик команды /stats:

  1. Определяется chat_id текущего чата.

  2. Проверяется, есть ли статистика этого чата в словаре stats.

  3. Если статистика отсутствует, сообщается об этом.

  4. Если статистика есть, выводится общее количество сообщений в чате и количество уникальных пользователей по мнению бота.

Тестируем функцию /stats
Тестируем функцию /stats

Обработчик команды /selfstat:

  1. Определяется chat_id текущего чата и user_id отправителя сообщения.

  2. Проверяется, есть ли статистика этого чата в словаре stats.

  3. Если статистика отсутствует, сообщается об этом.

  4. Если статистика есть, проверяется, были ли сообщения отправлены пользователем в этом чате.

  5. Если пользователь не отправлял сообщений в чате, сообщается об этом.

  6. Если пользователь есть в статистике, выводится количество сообщений, отправленных данным пользователем в чат и процентное соотношение этого количества к общему количеству сообщений в чате.

Тестируем функцию /selfstat
Тестируем функцию /selfstat

Делаем защиту от спама по ключевым словам:

Список запрещенных слов:

bad_words = ['анкета', 'ссылка', 'уникальное предложение']

Функция для проверки наличия запрещенных слов в сообщении:

def check_message(message):
    for word in bad_words:
        if word in message.text.lower():
            return True
    return False

Обработчик сообщений:

@bot.message_handler(func=lambda message: True)
def handle_message(message):
    # проверяем сообщение на наличие запрещенных слов
    if check_message(message):
        # если есть хотя бы одно запрещенное слово, кикаем пользователя
        bot.kick_chat_member(message.chat.id, message.from_user.id)
        bot.send_message(message.chat.id, f"Пользователь {message.from_user.username} был удален из чата за использование запрещенных слов")
    else:
        # если запрещенных слов нет, обрабатываем сообщение дальше
        print(message.text)

Сначала в переменную badwords заносятся все запрещенные слова, которые будут использоваться для проверки сообщений.

Затем создается функция checkmessage, которая принимает сообщение и проверяет наличие в нем любого из запрещенных слов. Если одно из запрещенных слов есть в сообщении, функция возвращает True, иначе - False.

Далее создается обработчик сообщений, который будет вызываться каждый раз, когда пользователь отправляет сообщение в чат. В нем используется лямбда-функция, которая всегда возвращает значение True, т.е. она будет обрабатывать все сообщения.

С помощью функции checkmessage проверяется каждое сообщение на наличие запрещенных слов. Если в сообщении есть запрещенное слово, то бот кикает пользователя, отправляет сообщение о причине кика и завершает обработку сообщения. Если запрещенных слов нет, то бот просто выводит сообщение в консоль.

Проверяем работу защиты от спама
Проверяем работу защиты от спама

И еще одна важная строчка:

bot.infinity_polling(none_stop=True)

Эта строчка запускает бота в бесконечном режиме ожидания новых сообщений от пользователей. Метод bot.infinity_polling() является альтернативой методу bot.polling() и запускает бота в режиме бесконечного ожидания новых сообщений. Это значит, что бот будет постоянно проверять наличие новых сообщений и обрабатывать их. Параметр none_stop=True указывает на то, что бот будет продолжать работать даже в случае ошибок или проблем с подключением. Это важно, чтобы бот всегда был доступен для пользователей и мог обрабатывать их запросы.

Заключение

Бот, созданный в рамках данной статьи, является прекрасным примером простого, но полезного бота для управления чатом в Telegram. Кроме того, такой бот может быть использован для сбора статистических данных и аналитики сообщений в чате. Для этого можно реализовать дополнительную функцию /stats, которая будет отображать различные статистические данные, например, количество сообщений за день/неделю/месяц, наиболее активных пользователей и т.д. Для обработки и анализа данных можно использовать библиотеку Pandas, которая предоставляет мощные инструменты для работы с табличными данными и статистическим анализом.

Ссылка на код на github

Комментарии (6)


  1. cartonworld
    00.00.0000 00:00
    +2

    весь код будет без табуляции .. я так и не разобрался, как добавлять табуляцию на хабре

    @bot.message_handler(commands=['unmute'])
    def unmute_user(message):
      if message.reply_to_message:
        chat_id = message.chat.id
        user_id = message.reply_to_message.from_user.id
    # ...

    Просто нажал [Tab] в начале нескольких строк (редактор вместо tab добавляет два пробела), всё ок ????

    Если вставить (Ctrl + V) кусок кода из IDE/github, то получается так:

    # Обработчик команды /start
    @bot.message_handler(commands=['start'])
    def start(message):
        bot.reply_to(message, "Привет! Я бот для управления чатом. Напиши /help, чтобы узнать, что я умею.")
    
    # Обработчик команды /help


    1. badcasedaily1 Автор
      00.00.0000 00:00
      +1

      Благодарю. Выяснилось, что добавления окошка "код" и форматирование выделением текста - разные вещи. Отредактировал


  1. Jury_78
    00.00.0000 00:00
    +3

    Тестируем на жене

    Просится фраза: В процессе теста ни одна жена не пострадала. :)


  1. 4uku
    00.00.0000 00:00

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

    В нескольких хендлерах вы проверяете статус юзера на админа ифами, нарушаете DRY :) может вынести в отдельную функцию и использовать оператор in?

    Переменная duration с параметром по умолчанию намекает на то, что это константа, как и bad_words, можно вынести отдельно.

    В хендлерк mute_user сно использовать моржовый оператор, где проверка args.


    1. 4uku
      00.00.0000 00:00

      В хендлере mute_user можно*


  1. Aleksjar
    00.00.0000 00:00
    +1

    Как интересную опцию можно рассмотреть возможность по подозрительным словам отправлять сообщение на проверку с помощью open AI API чтобы нейросеть дала свое мнение спам ли это. Не помню сколько поддерживает запросов бесплатный тариф, но если делать так именно с подозрительными сообщения то возможно, что уложитесь во все лимиты и не будете банить за фразы типо «ребят, я тут вчера такую дурацкую анкету заполнял при отборе на позицию».