Приветствую всяк смотрящий на моем первом посте на Хабре. Очень долго я шел к тому, чтоб решиться написать свой неинтересный рассказ и разместить его тут. И да, это очередной пост о том, как кто-то написал скучного бота. Но я получил опыт, который возможно пригодится мне когда-то. Поэтому хотелось бы закрепить. Я расскажу об этапах создания некоторого функционала, идей и с чем я столкнулся и что я узнал не из интернета, а на своём опыте. Возможно кому-то будет полезно.

Однажды, зайдя в чат дома между катками доты, я увидел бота, который дает возможность кикать пользователей путем голосования в чате. Нехитрое изобретение. Решив повторить тогда я впервые познакомился с Telegram Bot API. В частности с библиотекой telebot. И тут первое что хотел бы отметить. На момент написания того самого первого бота, в данной библиотеке использовалась функция polling(), для поддержки бота в сети при простое. Однако она была не идеальной и через буквально 10 минут простоя бот всё же полностью терял соединение и не принимал запросы. На тот момент решением стало вот такая вещь:

def updateConnect():
    while True:
        api = requests.post(f"https://api.telegram.org/bot/{token}/getUpdates".format(token))
        time.sleep(10)

Бесконечный цикл запущенный в tread для постоянного подталкивания бота к подключению при простое каждые 10 с. Сейчас polling() заменили на infinity_polling() и возможно в этом нужны уже нет. Боты без веб хуков стали работать максимально стабильно.

Далее админы этих чатов предложили добавить функцию бана человека по всем чатам дома автоматически одной кнопкой. Для этого бот конечно же должен находиться во всех чатах домов и быть администратором каждого чата. 15 минут и бот живет во всех чатах. Теперь власть над домом в моих руках ХАХАХАХА. И открыто поле для экспериментов. К слову, получилось даже больше чем хотелось. Функция была полезна тем, что если есть люди, которые по каким-то причинам попали в чат не своего дома и пытаются, например, использовать скрытую рекламу или слишком токсичные. Можно разом посмотреть где этот человек еще сидит и удалить его, нажав на соответствующие кнопки:

Вот пример.
Вот пример.

Код функции таков:

def armagedon(self, user=None):
        if user == None:
            user = self.userid
        keyword_ban = types.InlineKeyboardMarkup()
        Botton_all_chats = types.InlineKeyboardButton(text = "Удалить из всех чатов", callback_data='{"key": "ban_all", "chat": "all"}')
        keyword_ban.add(Botton_all_chats)
        for id in self.list_chats.keys():
            try:
                status_chat_user = bot.get_chat_member(id, user)
                if "left, kicked".count(status_chat_user.status) != 0:
                    continue
                title = self.list_chats[id]["title"]
                Botton = types.InlineKeyboardButton(text = title, callback_data='{"key": "ban_all", "chat":' + str(id) + '}')
                keyword_ban.add(Botton)
            except Exception:
                logging.error("Пользователь не смог вызвать команду армагедон.")
        bot.send_message(self.chat_id, "Выберите в каком чате вы хотите заблокировать пользователя [" + str(self.username) + "](tg://user?id\=" + str(self.userid) + "):", reply_markup=keyword_ban, parse_mode="MarkdownV2")

Тут хотелось бы остановиться на моменте передачи ботом callback_data через inline кнопки. Всё дело в том что у него есть ограничения по размену данной строки и к сожалению туда получится передать ни объект, ни словарь. И даже у самих строк передаваемых размер не должен превышать определенных значений. Поэтому было решено написать универсальный инструмент. Для этого я по умолчанию использую в callback_data формат json, первым тегом у меня всегда идет KEY, что собственно передает название кнопки функции обработчику, а уже за ними вся остальная информация. Однако её должно быть максимально мало. Поэтому зачастую приходится использовать ООП. Ставить функцию как метод класса, объявлять в нем объект какие-то параметры которые и присваивать нужные значения уже из этого объекта в функции обработчик, возможно, и обошлись бы и без создания класса, если бы callback_data вмещал в себя больше информации. Но есть как есть. Как выглядит мой типичный callback_data я показал в предыдущем листинге, а сейчас сам обработчик кнопок:

@bot.callback_query_handler(func=lambda call: True)
def callback_woker(call):
    call_data_json = call.data
    call_data = json.loads(call_data_json)
    if call_data['key'] == "kik":
        pass

Далее для развития новостного канала ЖК я добавил возможность писать боту в личку, а он в свою очередь отправляет в специально созданный заранее чат это сообщение. Админы нажатием кнопок могут исполнить с ним следующие действия:

Пример предложенной публикации
Пример предложенной публикации

В целом было просто написать это. Я думаю все понимают. просто скопировать всё. Однако тут я столкнулся с ограничениями, судя по всему, самого API. Дело в том что оказалось нужно предусмотреть все handler-ы для каждого типа сообщений. Фото, видео и текст, но и это еще не всё. То что мы привыкли скидывать как собранное воедино и фото и видео контент в одном сообщении в телеграмме бот также может отправить как тип

sendMediaGroup

А всё дело в том что бот не может отправить такое сообщение с текстом. Он отправит файлы вместе

sendMediaGroup
sendMediaGroup

Без возможности сделать подпись как на следующем фото:

как хотелось бы
как хотелось бы

Поэтому предложить можно только одно фото или видео с текстом. От типов sendMediaGroup я отказался вообще в пользу sendPhoto и sendVideo.

Далее, будучи живя в новом ЖК, да на столько новым что тут не было первый ГОД маршруток до метро, а до метро на машине 15 минут чтоб вы понимали, люди кооперировались на такси или личных авто и так родился чат попутчиков. Люди писали часто уточняли что как где встретиться и сколько мест есть, сколько стоит и так далее. В общем в чате на 1000 человек разобрать кто с кем едет тяжело и я предложил решение.

Поездка
Поездка

Нажав кнопку в чате, ищу попутчиков в такси, вызывается предложение, сначала мы выбираем:

  • количество свободных мест в авто;

  • откуда будет поездка (от какого корпуса или от какого метро);

  • выбирается час поездки;

  • выбирается минуты поездки(интервал в 15 минут для уменьшения кнопок на экране).

И по итогу мы получаем этакий бла бла кар, готовое решение в закрепленных сообщениях к которому можно присоединиться только стольким количествам человек которые могут влезть в авто. А также с помощью подключения к этой функции API Яндекс такси люди видят сколько примерно стоит такси. К сожалению не на момент вызова планируемого такси, а на момент создания поездки. Яндекс еще не умеет в предсказания... Нам помогут только кофейная гуща и карты таро.

Также бот фильтрует мат в сообщениях, проверяя их простой проверкой соответствия. Тут тоже есть о чем сказать. Перед проверкой текста лучше всего, наверное, проводить лемматизацию текста. Очень советую изучить ребятам, которые еще не знакомы с этим понятием данный пункт и решение от Яндекса, библиотеку pymystem3. Но я подумал что это слишком нагрузит сервер. и не стал. просто проверяю корень слов на соответствие. Сервер чувствует себя нормально. Даже не пробовал лемматизировать все сообщения. Возможно в будущем попробую.

Есть еще небольшие функции, например рассылка по всем чатам сообщения одной командой, захват сообщения для предложения в новости прямо из чата, автоматическое поднятие объявления в чате продаж из рук в руки в ЖК (у всех же есть такой. там еще торгуют детской одеждой мамаши) и прочие мелкие нужности, которые делают жизнь нашего ЖК чуток лучше.

Стоимость сервера отбивает небольшая реклама, которая может быть прикреплена в каждое сообщение от бота. 2 строки ниже текста. и не мешает никому, и охват не маленький.

На этом рассказ оканчиваю. Не знаю интересно было или не очень, но вот решил поделиться что давно лежало в голове. Были конечно и не удавшиеся идеи. Самая яркая из них - т.к. в домах умные домофоны от Ростелеком, один из них выходит прям на очередь на маршрутку. было бы круто либо получать доступ к трансляции и показывать её прямо по прямой ссылке, либо, вероятно с помощью библиотеки cv2 попробовать сделать информацию о том насколько загружена остановка. Однако РТ не дает возможности вынуть поток видео куда либо с их сайта. Либо я пока не решил эту задачу.

Хейтеры, люблю вас.

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


  1. Tungsten017
    31.01.2023 19:53

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


    1. Mirzapch
      31.01.2023 21:30
      +1

      А ещё после написания статьи можно её перечитать, и исправить ошибки.

      Хотя, новое поколение примерно так и разговаривает... Автор, вы случайно не в 21 веке рождены?


      1. mosx1 Автор
        31.01.2023 22:11
        +2

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


  1. BadHandycap
    31.01.2023 20:02
    +1

    И client id, и API-key, и адрес дома... Вы не из Яндекса случайно?


    1. mosx1 Автор
      31.01.2023 20:02

      Мы из гетто????


  1. kisskin
    31.01.2023 21:47
    +1

    Я бы сказал, что весьма достойный бот! Уважаю разработчиков, которые пишут практичные вещи, упрощающие и упорядочивающие жизнь людей, а не всякую высосанную из воспаленных мозгов хрень (скругленные края, плиточный интерфейс и т.п.) Данный бот как раз из полезных разработок с весьма интересными идеями.


    1. BadHandycap
      31.01.2023 22:12

      Поддерживаю. Вполне достойно сделано.


  1. Kuch
    01.02.2023 08:30

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


    1. mosx1 Автор
      01.02.2023 09:56

      Пока пример. К сожалению не нахожу параметра capture в sendMedia