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
|
# Standard library modules
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import datetime
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
@ -55,17 +56,57 @@ class TelegramBot(object):
|
|||||||
close=False
|
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):
|
def __init__(self, token):
|
||||||
"""Set bot token and store HTTP sessions."""
|
"""Set bot token and store HTTP sessions."""
|
||||||
self._token = token
|
self._token = token
|
||||||
self.sessions = dict()
|
self.sessions = dict()
|
||||||
|
self._flood_wait = 0
|
||||||
|
self.last_sending_time = dict(
|
||||||
|
absolute=(
|
||||||
|
datetime.datetime.now()
|
||||||
|
- self.absolute_cooldown_timedelta
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def token(self):
|
def token(self):
|
||||||
"""Telegram API bot token."""
|
"""Telegram API bot token."""
|
||||||
return self._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
|
@staticmethod
|
||||||
def check_telegram_api_json(response):
|
def check_telegram_api_json(response):
|
||||||
"""Take a json Telegram response, check it and return its content.
|
"""Take a json Telegram response, check it and return its content.
|
||||||
@ -135,6 +176,80 @@ class TelegramBot(object):
|
|||||||
session_must_be_closed = True
|
session_must_be_closed = True
|
||||||
return session, session_must_be_closed
|
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=[]):
|
async def api_request(self, method, parameters={}, exclude=[]):
|
||||||
"""Return the result of a Telegram bot API request, or an Exception.
|
"""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`.
|
will be closed on `Bot.app.cleanup`.
|
||||||
Result may be a Telegram API json response, None, or Exception.
|
Result may be a Telegram API json response, None, or Exception.
|
||||||
"""
|
"""
|
||||||
# TODO prevent Telegram flood control
|
|
||||||
response_object = None
|
response_object = None
|
||||||
session, session_must_be_closed = self.get_session(method)
|
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)
|
parameters = self.adapt_parameters(parameters, exclude=exclude)
|
||||||
try:
|
try:
|
||||||
async with session.post(
|
async with session.post(
|
||||||
@ -157,7 +274,21 @@ class TelegramBot(object):
|
|||||||
await response.json() # Telegram returns json objects
|
await response.json() # Telegram returns json objects
|
||||||
)
|
)
|
||||||
except TelegramError as e:
|
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
|
return e
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"{e}", exc_info=True)
|
logging.error(f"{e}", exc_info=True)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user