CoinAPI token rotation

This commit is contained in:
DarkCat09 2023-06-01 17:39:46 +04:00
parent 2573253e2b
commit b98c16f642
3 changed files with 45 additions and 10 deletions

View file

@ -1,5 +1,5 @@
DEBUG=false # debug logging DEBUG=false # debug logging
TIMEOUT=2 # http requests timeout TIMEOUT=2 # http requests timeout
NDIGITS=3 # digits after floating point or after zeroes NDIGITS=3 # digits after floating point or after zeroes
COINAPI_KEY= # coinapi key COINAPI_KEYS=["key"] # coinapi keys list
TELEGRAM_TOKEN= # telegram bot token TELEGRAM_TOKEN= # telegram bot token

View file

@ -10,7 +10,7 @@ https://t.me/Shirino_bot
Вставьте в файл `.env` в формате: Вставьте в файл `.env` в формате:
``` ```
COINAPI_KEY=Токен от апи CoinAPI COINAPI_KEYS=["Токен от CoinAPI"]
TELEGRAM_TOKEN=Токен Telegram-бота TELEGRAM_TOKEN=Токен Telegram-бота
``` ```
@ -24,3 +24,13 @@ TIMEOUT=таймаут для библиотеки requests, в секундах
Ставьте pylint и mypy для статической проверки кода. Ставьте pylint и mypy для статической проверки кода.
Конфиги уже есть в репозитории. Конфиги уже есть в репозитории.
После проверок можете открывать PR. После проверок можете открывать PR.
## Почему энв для CoinAPI -- список?
Можно получить несколько ключей на разные почтовые ящики
и все ключи вписать в список:
```
COINAPI_KEYS=["первый", "второй", "и так далее"]
```
Если вдруг один из них будет заблокирован по рейтлимиту,
бот автоматически переключиться на следующий (token rotation).

33
main.py
View file

@ -31,7 +31,7 @@ class Settings(BaseSettings):
debug: bool debug: bool
timeout: int = 2 timeout: int = 2
ndigits: int = 3 ndigits: int = 3
coinapi_key: str coinapi_keys: List[str]
telegram_token: str telegram_token: str
class Config: 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) bot = Bot(token=settings.telegram_token)
dp = Dispatcher(bot) dp = Dispatcher(bot)
@ -143,8 +145,15 @@ class CurrencyConverter:
return True return True
def coinapi(self) -> None: def coinapi(self, depth: int = coinapi_len) -> None:
"""Get data from CoinAPI (for cryptocurrencies)""" """Get data from CoinAPI (for cryptocurrencies)
Args:
depth (int, optional): Counter protecting from infinite recursion
"""
if depth <= 0:
raise RecursionError('Рейтлимит на всех токенах')
resp = requests.get( resp = requests.get(
( (
@ -152,11 +161,16 @@ class CurrencyConverter:
f'/{self.conv_currency}' f'/{self.conv_currency}'
), ),
headers={ headers={
'X-CoinAPI-Key': settings.coinapi_key, 'X-CoinAPI-Key': settings.coinapi_keys[coinapi_active[0]],
}, },
timeout=settings.timeout, 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() data: Dict[str, Any] = resp.json()
rate = data.get('rate') rate = data.get('rate')
if rate is None: if rate is None:
@ -164,6 +178,17 @@ class CurrencyConverter:
self.conv_amount = float(rate * self.amount) 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() @dp.inline_handler()
async def currency(inline_query: InlineQuery) -> None: async def currency(inline_query: InlineQuery) -> None: