from __future__ import annotations import asyncio import time class RateLimiter: """Twitch 速率限制令牌桶 默认 20msg/30s (known user),Mod/VIP 切换至 100msg/30s。 mod 模式 60 秒后自动切回 known 模式。 """ MOD_TIMEOUT = 60.0 def __init__(self, limit: int = 20, window: float = 30.0, mod_limit: int = 100): self._default_limit = limit self._mod_limit = mod_limit self._current_limit = limit self._window = window self._tokens = float(limit) self._last_refill = time.monotonic() self._mod_until: float | None = None self._lock = asyncio.Lock() async def switch_to_mod(self) -> None: async with self._lock: if self._current_limit == self._mod_limit: return self._current_limit = self._mod_limit self._tokens = float(self._mod_limit) self._last_refill = time.monotonic() self._mod_until = time.monotonic() + self.MOD_TIMEOUT async def switch_to_known(self) -> None: async with self._lock: self._current_limit = self._default_limit self._tokens = float(self._default_limit) self._last_refill = time.monotonic() self._mod_until = None async def acquire(self) -> bool: async with self._lock: now = time.monotonic() if self._mod_until is not None and now >= self._mod_until: self._current_limit = self._default_limit self._mod_until = None elapsed = now - self._last_refill refill = elapsed * (self._current_limit / self._window) self._tokens = min(float(self._current_limit), self._tokens + refill) self._last_refill = now if self._tokens >= 1.0: self._tokens -= 1.0 return True return False @property def available_tokens(self) -> float: return self._tokens