Skip to content

Commit 41af09b

Browse files
committed
chore: add rate limiting for update commands
1 parent 4272dbb commit 41af09b

File tree

2 files changed

+63
-9
lines changed

2 files changed

+63
-9
lines changed

swibots/bot_app.py

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from swibots.api.common.models import User
2222
from swibots.api.callback import AppBar, AppPage
2323
from swibots.api.common.events import Event
24+
from swibots.rate_limiter import AsyncRateLimiter
2425
from swibots.utils import (
2526
DownloadProgress,
2627
IOClient,
@@ -81,8 +82,10 @@ def __init__(
8182
if email and password:
8283
auth_result = self.auth_service.login(email, password)
8384
token = auth_result.access_token
85+
8486
if not token:
8587
raise TokenInvalidError(f"'token' for the bot can't be '{token}'")
88+
8689
self.token = token
8790
self._user_type = Bot
8891
self._bot_info: BotInfo | None = None
@@ -95,14 +98,17 @@ def __init__(
9598
self._bot_description = bot_description
9699
self.auto_update_bot = auto_update_bot
97100
self._loop = loop or asyncio.get_event_loop()
98-
try:
99-
self.user = self.auth_service.get_me_sync(user_type=self._user_type)
100-
self.name = self.user.name
101-
except Exception as er:
102-
logging.error(er)
103-
# In case, if issues with auth service
104-
self.user = self._user_type(app=self)
105-
self.name = ""
101+
102+
self.user = self.auth_service.get_me_sync(user_type=self._user_type)
103+
self.name = self.user.name
104+
105+
self.rate_limit = AsyncRateLimiter(storage_file=f"{self.user.id}.pkl")
106+
107+
# Add rate limit functions
108+
self.update_bot_commands = self.rate_limit.limit(
109+
"update_bot_commands", 10, 15 * 60
110+
)(self.update_bot_commands)
111+
106112
if app_bar is None:
107113
app_bar = AppBar(self.name, left_icon=self.user.imageurl)
108114
self.app_bar = app_bar
@@ -253,8 +259,9 @@ async def update_bot_commands(self):
253259
commands=commands,
254260
preview=self._app_preview,
255261
)
256-
262+
log.info("Updating bot commands...")
257263
self._bot_info = await self.update_bot_info(self._bot_info)
264+
log.info("Bot commands updated")
258265

259266
async def _on_chat_service_start(self, _):
260267
await self.chat_service.subscribe_to_notifications(callback=self.on_chat_event)
@@ -459,5 +466,8 @@ def run(self, task: Callable = None):
459466
log.exception(e)
460467
run(self.stop())
461468

469+
def limit(self, key: str, times: int, seconds: int):
470+
pass
471+
462472

463473
BotApp = Client

swibots/rate_limiter.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import os, pickle, time, aiofiles
2+
3+
4+
class AsyncRateLimiter:
5+
def __init__(self, storage_file='rate_limit_data.pkl'):
6+
self.storage_file = storage_file
7+
self.rate_limit_data = {}
8+
9+
async def _load_data(self):
10+
if os.path.exists(self.storage_file):
11+
async with aiofiles.open(self.storage_file, 'rb') as f:
12+
content = await f.read()
13+
self.rate_limit_data = pickle.loads(content)
14+
15+
async def _save_data(self):
16+
async with aiofiles.open(self.storage_file, 'wb') as f:
17+
await f.write(pickle.dumps(self.rate_limit_data))
18+
19+
def limit(self, key: str, times: int, seconds: int):
20+
def decorator(func):
21+
async def async_wrapper(*args, **kwargs):
22+
current_time = time.time()
23+
24+
await self._load_data()
25+
26+
if key not in self.rate_limit_data:
27+
self.rate_limit_data[key] = []
28+
29+
self.rate_limit_data[key] = [t for t in self.rate_limit_data[key] if current_time - t < seconds]
30+
31+
if len(self.rate_limit_data[key]) >= times:
32+
raise Exception(f"Rate limit exceeded: {times} calls in {seconds} seconds")
33+
34+
# Record the call
35+
self.rate_limit_data[key].append(current_time)
36+
await self._save_data()
37+
return await func(*args, **kwargs)
38+
39+
return async_wrapper
40+
return decorator
41+
42+
43+
class RateLimitException(Exception):
44+
pass

0 commit comments

Comments
 (0)