Prevent Telegram flood control on all methods with chat_id as parameter
This commit is contained in:
parent
7582b4cce4
commit
e47dd1d458
@ -6,6 +6,7 @@ A simple aiohttp asyncronous web client is used to make requests.
|
||||
|
||||
# Standard library modules
|
||||
import asyncio
|
||||
import datetime
|
||||
import json
|
||||
import logging
|
||||
|
||||
@ -55,17 +56,57 @@ class TelegramBot(object):
|
||||
close=False
|
||||
)
|
||||
}
|
||||
_absolute_cooldown_timedelta = datetime.timedelta(seconds=1/30)
|
||||
_per_chat_cooldown_timedelta = datetime.timedelta(seconds=1)
|
||||
_allowed_messages_per_group_per_minute = 20
|
||||
|
||||
def __init__(self, token):
|
||||
"""Set bot token and store HTTP sessions."""
|
||||
self._token = token
|
||||
self.sessions = dict()
|
||||
self._flood_wait = 0
|
||||
self.last_sending_time = dict(
|
||||
absolute=(
|
||||
datetime.datetime.now()
|
||||
- self.absolute_cooldown_timedelta
|
||||
)
|
||||
)
|
||||
|
||||
@property
|
||||
def token(self):
|
||||
"""Telegram API bot token."""
|
||||
return self._token
|
||||
|
||||
@property
|
||||
def flood_wait(self):
|
||||
"""Seconds to wait before next API requests."""
|
||||
return self._flood_wait
|
||||
|
||||
@property
|
||||
def absolute_cooldown_timedelta(self):
|
||||
"""Return time delta to wait between messages (any chat).
|
||||
|
||||
Return class value (all bots have the same limits).
|
||||
"""
|
||||
return self.__class__._absolute_cooldown_timedelta
|
||||
|
||||
@property
|
||||
def per_chat_cooldown_timedelta(self):
|
||||
"""Return time delta to wait between messages in a chat.
|
||||
|
||||
Return class value (all bots have the same limits).
|
||||
"""
|
||||
return self.__class__._per_chat_cooldown_timedelta
|
||||
|
||||
@property
|
||||
def allowed_messages_per_group_per_minute(self):
|
||||
"""Return maximum number of messages allowed in a group per minute.
|
||||
|
||||
Group, supergroup and channels are considered.
|
||||
Return class value (all bots have the same limits).
|
||||
"""
|
||||
return self.__class__._allowed_messages_per_group_per_minute
|
||||
|
||||
@staticmethod
|
||||
def check_telegram_api_json(response):
|
||||
"""Take a json Telegram response, check it and return its content.
|
||||
@ -135,6 +176,80 @@ class TelegramBot(object):
|
||||
session_must_be_closed = True
|
||||
return session, session_must_be_closed
|
||||
|
||||
def set_flood_wait(self, flood_wait):
|
||||
"""Wait `flood_wait` seconds before next request."""
|
||||
self._flood_wait = flood_wait
|
||||
|
||||
async def prevent_flooding(self, chat_id):
|
||||
"""Await until request may be sent safely.
|
||||
|
||||
Telegram flood control won't allow too many API requests in a small
|
||||
period.
|
||||
Exact limits are unknown, but less than 30 total private chat messages
|
||||
per second, less than 1 private message per chat and less than 20
|
||||
group chat messages per chat per minute should be safe.
|
||||
"""
|
||||
now = datetime.datetime.now
|
||||
if type(chat_id) is int and chat_id > 0:
|
||||
while (
|
||||
now() < (
|
||||
self.last_sending_time['absolute']
|
||||
+ self.absolute_cooldown_timedelta
|
||||
)
|
||||
) or (
|
||||
chat_id in self.last_sending_time
|
||||
and (
|
||||
now() < (
|
||||
self.last_sending_time[chat_id]
|
||||
+ self.per_chat_cooldown_timedelta
|
||||
)
|
||||
)
|
||||
):
|
||||
await asyncio.sleep(
|
||||
self.absolute_cooldown_timedelta.seconds
|
||||
)
|
||||
self.last_sending_time[chat_id] = now()
|
||||
else:
|
||||
while (
|
||||
now() < (
|
||||
self.last_sending_time['absolute']
|
||||
+ self.absolute_cooldown_timedelta
|
||||
)
|
||||
) or (
|
||||
chat_id in self.last_sending_time
|
||||
and len(
|
||||
[
|
||||
sending_datetime
|
||||
for sending_datetime in self.last_sending_time[chat_id]
|
||||
if sending_datetime >= (
|
||||
now()
|
||||
- datetime.timedelta(minutes=1)
|
||||
)
|
||||
]
|
||||
) >= self.allowed_messages_per_group_per_minute
|
||||
) or (
|
||||
chat_id in self.last_sending_time
|
||||
and len(self.last_sending_time[chat_id]) > 0
|
||||
and now() < (
|
||||
self.last_sending_time[chat_id][-1]
|
||||
+ self.per_chat_cooldown_timedelta
|
||||
)
|
||||
):
|
||||
await asyncio.sleep(0.5)
|
||||
if chat_id not in self.last_sending_time:
|
||||
self.last_sending_time[chat_id] = []
|
||||
self.last_sending_time[chat_id].append(now())
|
||||
self.last_sending_time[chat_id] = [
|
||||
sending_datetime
|
||||
for sending_datetime in self.last_sending_time[chat_id]
|
||||
if sending_datetime >= (
|
||||
now()
|
||||
- self.longest_cooldown_timedelta
|
||||
)
|
||||
]
|
||||
self.last_sending_time['absolute'] = now()
|
||||
return
|
||||
|
||||
async def api_request(self, method, parameters={}, exclude=[]):
|
||||
"""Return the result of a Telegram bot API request, or an Exception.
|
||||
|
||||
@ -142,9 +257,11 @@ class TelegramBot(object):
|
||||
will be closed on `Bot.app.cleanup`.
|
||||
Result may be a Telegram API json response, None, or Exception.
|
||||
"""
|
||||
# TODO prevent Telegram flood control
|
||||
response_object = None
|
||||
session, session_must_be_closed = self.get_session(method)
|
||||
# Prevent Telegram flood control for all methodsd having a `chat_id`
|
||||
if 'chat_id' in parameters:
|
||||
await self.prevent_flooding(parameters['chat_id'])
|
||||
parameters = self.adapt_parameters(parameters, exclude=exclude)
|
||||
try:
|
||||
async with session.post(
|
||||
@ -157,7 +274,21 @@ class TelegramBot(object):
|
||||
await response.json() # Telegram returns json objects
|
||||
)
|
||||
except TelegramError as e:
|
||||
logging.error(f"API {e}")
|
||||
logging.error(f"API error response - {e}")
|
||||
if e.code == 420: # Flood error!
|
||||
try:
|
||||
flood_wait = int(
|
||||
e.description.split('_')[-1]
|
||||
) + 30
|
||||
except Exception as e:
|
||||
logging.error(f"{e}")
|
||||
flood_wait = 5*60
|
||||
logging.critical(
|
||||
"Telegram antiflood control triggered!\n"
|
||||
f"Wait {flood_wait} seconds before making another "
|
||||
"request"
|
||||
)
|
||||
self.set_flood_wait(flood_wait)
|
||||
return e
|
||||
except Exception as e:
|
||||
logging.error(f"{e}", exc_info=True)
|
||||
|
Loading…
x
Reference in New Issue
Block a user