Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
Приветствую всяк смотрящий на моем первом посте на Хабре. Очень долго я шел к тому, чтоб решиться написать свой неинтересный рассказ и разместить его тут. И да, это очередной пост о том, как кто-то написал скучного бота. Но я получил опыт, который возможно пригодится мне когда-то. Поэтому хотелось бы закрепить. Я расскажу об этапах создания некоторого функционала, идей и с чем я столкнулся и что я узнал не из интернета, а на своём опыте. Возможно кому-то будет полезно.
Однажды, зайдя в чат дома между катками доты, я увидел бота который дает возможность кикать пользователей путем голосования в чате. Нехитрое изобретение, я решил повторить и тогда я впервые познакомился с 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 я отказался вообще.
Далее, будучи живя в новом ЖК, да на столько новым что тут не было первый ГОД маршруток до метро, а до метро на машине 15 минут чтоб вы понимали, люди кооперировались на такси или личных авто и так родился чат Попутчиков. Люди писали часто уточняли что как где встретиться и сколько мест есть, сколько стоит и так далее. в общем в чате на 1000 человек разобрать кто с кем едет тяжело и я предложил решение.
Нажав кнопку в чате, ищу попутчиков в такси, вызывается предложение, сначала мы выбираем:
количество свободных мест в авто;
откуда будет поездка (от какого корпуса или от какого метро);
выбирается час поездки;
выбирается минуты поездки(интервал в 15 минут для уменьшения кнопок на экране).
И по итогу мы получаем этакий бла бла кар, готовое решение в закрепленных сообщениях к которому можно присоединиться только стольким количествам человек которые могут влезть в авто. А также с помощью подключения к этой функции API Яндекс такси люди видят сколько примерно стоит такси. к сожалению не на момент вызова планируемого такси, а на момент создания поездки. Яндекс еще не умеет в предсказания... Нам помогут только кофейная гуща и карты таро.
Также бот фильтрует мат в сообщениях, проверяя их простой проверкой соответствия. Тут тоже есть о чем сказать. перед проверкой текста лучше всего, наверное, проводить лемматизацию текста. Очень советую изучить ребятам, которые еще не знакомы с этим понятием данный пункт и решение от Яндекса, библиотеку pymystem3. Но я подумал что это слишком нагрузит сервер. и не стал. просто проверяю корень слов на соответствие. Сервер чувствует себя нормально. Даже не пробовал лемматизировать все сообщения. Возможно в будущем попробую.
Есть еще небольшие функции, например рассылка по всем чатам сообщения одной командой, захват сообщения для предложения в новости прямо из чата, автоматическое поднятие объявления в чате продаж из рук в руки в ЖК (у всех же есть такой. там еще торгуют детской одеждой мамаши) и прочие мелкие нужности, которые делают жизнь нашего ЖК чуток лучше.
Стоимость сервера отбивает небольшая реклама, которая может быть прикреплена в каждое сообщение от бота. 2 строки ниже текста. и не мешает никому, и охват не маленький.
На этом рассказ оканчиваю. Не знаю интересно было или не очень, но вот решил поделиться что давно лежало в голове. Были конечно и не удавшиеся идеи. Самая яркая из них - т.к. в домах умные домофоны от Ростелеком, один из них выходит прям на очередь на маршрутку. было бы круто либо получать доступ к трансляции и показывать её прямо по прямой ссылке, либо, вероятно с помощью библиотеки cv2 попробовать сделать информацию о том насколько загружена остановка. Однако РТ не дает возможности вынуть поток видео куда либо с их сайта. либо я пока не решил эту задачу.
Git
mosx1/bot_chats (github.com)
Хейтеры, люблю вас.