Как сделать ограничение по времени на запросы для пользователя в Telegram боте

Довольно часто на Хабре появляются статьи о написании бота для Telegram, которые в своем роде, если откинуть уникальность идеи, являются самым обычным туториалом на тему «как получить сообщение от Telegram, обработать его и отправить ответ пользователю». Однако ни в одной из статей, прочтенных мной (конечно же, не берусь утверждать, что прочел их все, но тем не менее) я не встретил упоминания о лимитах отправки сообщений пользователям и как с ними работать. Кого заинтересовал, прошу под кат.

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

И вроде бы все хорошо, пока дневной онлайн не перевалил за отметку в двести пользователей. Вот тут и начались проблемы. Все чаще пользователи обращались ко мне с вопросом, почему бот не отвечает по несколько минут. Дискомфорт игрокам доставлялся сильнейший, особенно во время войн, когда пользователь пытался быстро восстановить армию для контратаки, а игра предательски висла и не отвечала ни на какие действия. Причем Telegram мог забанить как отсылку всех сообщений, так и только сообщения конкретного содержания при частом его использовании, например, покупка ресурсов и вербовка воинов, где использовались кнопки кастомной клавиатуры со стандартными количественными значениями.

Дата и время в Telegram боте

Опыт работы с API бота уже имелся, однако на меньшей аудитории и с меньшей интенсивностью отправки. Про лимиты тоже было известно, но реально сталкивался с ними только при работе с группами. Там все намного жестче, чем при работе с персональными чатами. Чтобы узнать больше о лимитах, достаточно обратится к FAQ на официальном сайте Telegram.

My bot is hitting limits, how do I avoid this?

When sending messages inside a particular chat, avoid sending more than one message per second. We may allow short bursts that go over this limit, but eventually you’ll begin receiving 429 errors.

If you’re sending bulk notifications to multiple users, the API will not allow more than 30 messages per second or so. Consider spreading out notifications over large intervals of 8—12 hours for best results.

Из указанного выше имеем, что нельзя отправлять конкретному пользователю сообщения чаще чем раз в секунду и не более 30 сообщений в секунду при массовой рассылке разным пользователям. Но допускаются некоторые погрешности. Следовательно нам необходимо каждые 1/30 секунд отправлять сообщение пользователю, проверяя, не посылали ли мы уже ему сообщение в течении текущей секунды, иначе послать сообщение для следующего пользователя, если тот прошел эту же проверку.

Еще по теме:  Названия для ТГ каналов с атмосферой

Так как разработка изначально велась на языке Go, где имеются каналы и сопрограммы, (они же горутины), то на ум пришла сразу же идея отправка отложенных сообщений. Сначала складываем обработанный ответ в канал, а в отдельном потоке разгребаем этот канал каждые дозволенные нам 1/30 секунд. Но идея с одним каналом для всех сообщений не сработала.

Задержка сообщений в Telegram боте

Достав из канала сообщение и убедившись, что этому пользователю мы пока слать сообщения не можем, нам необходимо это сообщение куда-то деть. Отправлять снова в этот же канал не хорошо, потому что мы сломаем хронологический порядок сообщений пользователя, а так же сильно отложим доставку этого сообщения при большом количестве активных игроков. Проверить сообщение, не доставая его из канала и перейти к следующему, насколько я знаю, мы тоже не можем.

Тогда появляется идея завести по каналу на пользователя. С этого момента по подробней.

// Мап каналов для сообщений, где ключом является id пользователя var deferredMessages = make(map[int]chan deferredMessage) // Здесь будем хранить время последней отправки сообщения для каждого пользователя var lastMessageTimes = make(map[int]int64) // chatId – id пользователя, которому шлем сообщения // method, params, photo – заранее подготовленные параметры для запроса согласно bot API Telegram // callback будем вызывать для обработки ошибок при обращении к API type deferredMessage struct < chatId int method string params map[string]string photo string callback func (SendError) >// Метод для отправки отложенного сообщения func MakeRequestDeferred(chatId int, method string, params map[string]string, photo string, callback func (SendError)) < dm := deferredMessage< chatId: chatId, method: method, params: params, photo: photo, callback: callback, >if _, ok := deferredMessages[chatId]; !ok < deferredMessages[chatId] = make(chan deferredMessage, 1000) >deferredMessages[chatId] // error.go, где ChatId – id пользователя type SendError struct < ChatId int Msg string >// Имплементация интерфейса error func (e *SendError) Error() string

Теперь с ходу для обработки получившегося набора каналов хочется воспользоваться select case конструкцией, однако проблема в том, что она описывает фиксированный набор каналов для каждого case, а в нашем случае набор каналов динамический, так как пользователи добавляются в процессе игры, создавая новые каналы для своих сообщений. В противном случае не обойтись без блокировок. Тогда, обратившись к Google, как обычно, на просторах StackOverflow нашлось отличное решение. А заключается оно в использовании функции Select из пакета reflect.

Еще по теме:  Как добавить комментарии в Телеграмм канал к посту через бота

Если коротко, то эта функция позволяет нам извлечь из заранее сформированного массива SelectCase’ов, каждый из которых содержит канал, сообщение, готовое для отправки. Принцип тот же, что и в select case, но с неопределенным числом каналов. То что нам и нужно.

func (c *Client) sendDeferredMessages() < // Создаем тикер с периодичностью 1/30 секунд timer := time.NewTicker(time.Second / 30) for range timer.C < // Формируем массив SelectCase’ов из каналов, пользователи которых готовы получить следующее сообщение cases := []reflect.SelectCase<>for userId, ch := range deferredMessages < if userCanReceiveMessage(userId) len(ch) >0 < // Формирование case cs := reflect.SelectCasecases = append(cases, cs) > > if len(cases) > 0 < // Достаем одно сообщение из всех каналов _, value, ok := reflect.Select(cases) if ok < dm := value.Interface().(deferredMessage) // Выполняем запрос к API _, err := c.makeRequest(dm.method, dm.params, dm.photo) if err != nil < dm.callback(SendError) > // Записываем пользователю время последней отправки сообщения. lastMessageTimes[dm.chatId] = time.Now().UnixNano() > > > > // Проверка может ли уже пользователь получить следующее сообщение func userCanReceiveMessage(userId int) bool

Теперь по порядку.

  • Для начала мы создаем таймер, который будет «тикать» каждые необходимые нам 1/30 секунд, и запускаем на нем цикл for.
  • После чего начинаем формировать необходимый нам массив SelectCase’ов, перебирая наш мап каналов, и складывая в массив только те непустые каналы, пользователи которых уже могут получать сообщения, то есть прошла одна секунда с момента прошлой отправки.
  • Создаем для каждого канала структуру reflect.SelectCase, в которой нам нужно заполнить два поля: Dir – направление (отправка в канал или извлечение из канала), в нашем случае устанавливаем флаг reflect.SelectRecv (извлечение) и Chan – собственно сам канал.
  • Закончив формировать массив SelectCase’ов, отдаем его в reflect.Select() и получаем на выходе id канала в массиве SelectCase’ов, значение, извлеченное из канала и флаг успешного выполнения операции. Если все хорошо, делаем запрос на API и получаем ответ. Получив ошибку, вызываем callback и передаем туда ошибку. Не забываем записать пользователю дату последней отправки сообщения

Вот так, вроде бы все просто. Теперь Telegram не придерется к нашему боту из-за частой отправки сообщений пользователю, а игрокам будет комфортно играть. Конечно, понятно, что при огромном количестве пользователей сообщения будут отправляться игроку все медленнее и медленнее, но это будет делаться равномерно, создавая меньше неудобств, чем при единичных блокировках на несколько минут, если не следовать лимитам.

Еще по теме:  Как работает секретный чат Телеграм

Кстати вспомним об оговорены в FAQ погрешностях. Я в своей реализации шлю пользователям два сообщения в секунду вместо одного и не раз в 1/30 секунду, а раз в 1/40, что намного чаще чем рекомендуется. Но пока проблем не возникало.

Исходный код клиента можно посмотреть на gitlab

Источник: savepearlharbor.com

Как сделать ограничение по времени на запросы для пользователя в Telegram боте?

Разрабатываю телеграм бота. Нужно сделать так, чтобы человек не смог заспамить бота командами, нужно чтобы присутствовал какой-нибудь таймер, то есть определенную команду можно вызывать раз в 30 секунд, например, как это лучше реализовать?

Ответы (1 шт):

Запомните время последнего использования команды и при каждой последующей попытке её использовать проверяйте прошло 30 секунд или нет

from datetime import datetime . здесь выполняете свою команду . last_time = datetime.now()

При последующем запросе команды проверяете сколько прошло секунд:

delta = datetime.now() — last_time if delta.second > 30: (выполняете то, что вам нужно) else: print(‘Извините 30 секунд ещё не прошло’)

Источник: husl.ru

Как ограничить кол-во запросов в секунду в Telegram боте?

У меня есть Telegram бот, который выдает бонусы людям. Бонус выдается один раз в сутки. Для того, чтобы получить бонус, нужно нажать на кнопку (неважно, inline или обычную). И вот ушлые люди нажимают на кнопку десятки раз за несколько секунд и получают не один, а несколько бонусов в итоге.

Функция, которая обрабатывает нажатие, выглядит примерно так:

1. Проверить, когда начислялся бонус последний раз 2. Если больше дня назад, то начислить бонус и обновить дату получения

То есть всего 2-3 команды, ошибки в них быть не может, они банальны и я сто раз их проверил.

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

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

  • Вопрос задан более трёх лет назад
  • 2045 просмотров

1 комментарий

Средний 1 комментарий

Источник: qna.habr.com

Рейтинг
( Пока оценок нет )
Загрузка ...