From b98c16f64218d4c3e9078c55368c863ae8e9eb95 Mon Sep 17 00:00:00 2001 From: DarkCat09 Date: Thu, 1 Jun 2023 17:39:46 +0400 Subject: [PATCH] CoinAPI token rotation --- .env_sample | 10 +++++----- README.md | 12 +++++++++++- main.py | 33 +++++++++++++++++++++++++++++---- 3 files changed, 45 insertions(+), 10 deletions(-) diff --git a/.env_sample b/.env_sample index 6fded31..1ef3619 100644 --- a/.env_sample +++ b/.env_sample @@ -1,5 +1,5 @@ -DEBUG=false # debug logging -TIMEOUT=2 # http requests timeout -NDIGITS=3 # digits after floating point or after zeroes -COINAPI_KEY= # coinapi key -TELEGRAM_TOKEN= # telegram bot token +DEBUG=false # debug logging +TIMEOUT=2 # http requests timeout +NDIGITS=3 # digits after floating point or after zeroes +COINAPI_KEYS=["key"] # coinapi keys list +TELEGRAM_TOKEN= # telegram bot token diff --git a/README.md b/README.md index f2a5ee3..f9c6d49 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ https://t.me/Shirino_bot Вставьте в файл `.env` в формате: ``` -COINAPI_KEY=Токен от апи CoinAPI +COINAPI_KEYS=["Токен от CoinAPI"] TELEGRAM_TOKEN=Токен Telegram-бота ``` @@ -24,3 +24,13 @@ TIMEOUT=таймаут для библиотеки requests, в секундах Ставьте pylint и mypy для статической проверки кода. Конфиги уже есть в репозитории. После проверок можете открывать PR. + +## Почему энв для CoinAPI -- список? +Можно получить несколько ключей на разные почтовые ящики +и все ключи вписать в список: +``` +COINAPI_KEYS=["первый", "второй", "и так далее"] +``` + +Если вдруг один из них будет заблокирован по рейтлимиту, +бот автоматически переключиться на следующий (token rotation). diff --git a/main.py b/main.py index dbed455..b432045 100644 --- a/main.py +++ b/main.py @@ -31,7 +31,7 @@ class Settings(BaseSettings): debug: bool timeout: int = 2 ndigits: int = 3 - coinapi_key: str + coinapi_keys: List[str] telegram_token: str class Config: @@ -57,6 +57,8 @@ if settings.debug: # --- +coinapi_len = len(settings.coinapi_keys) +coinapi_active = [0] # API key index bot = Bot(token=settings.telegram_token) dp = Dispatcher(bot) @@ -143,8 +145,15 @@ class CurrencyConverter: return True - def coinapi(self) -> None: - """Get data from CoinAPI (for cryptocurrencies)""" + def coinapi(self, depth: int = coinapi_len) -> None: + """Get data from CoinAPI (for cryptocurrencies) + + Args: + depth (int, optional): Counter protecting from infinite recursion + """ + + if depth <= 0: + raise RecursionError('Рейтлимит на всех токенах') resp = requests.get( ( @@ -152,11 +161,16 @@ class CurrencyConverter: f'/{self.conv_currency}' ), headers={ - 'X-CoinAPI-Key': settings.coinapi_key, + 'X-CoinAPI-Key': settings.coinapi_keys[coinapi_active[0]], }, timeout=settings.timeout, ) + if resp.status_code == 429: + log.warning('CoinAPI ratelimited, rotating token') + rotate_token(settings.coinapi_keys, coinapi_active) + self.coinapi(depth - 1) + data: Dict[str, Any] = resp.json() rate = data.get('rate') if rate is None: @@ -164,6 +178,17 @@ class CurrencyConverter: self.conv_amount = float(rate * self.amount) +def rotate_token(lst: List[str], active: List[int]) -> None: + """Rotates API key to prevent ratelimits + + Args: + lst (List[str]): Keys list + active (List[str]): Mutable object with current key index + """ + + active[0] = (active[0] + 1) % len(lst) + + @dp.inline_handler() async def currency(inline_query: InlineQuery) -> None: