ForcePilot/backend/package/yuxi/channels/adapters/twitch/rate_limiter.py
Kris 59cd13cf84 feat(twitch): 实现完整的Twitch聊天适配器模块
新增Twitch IRC协议相关的全套实现,包括:
1. 基础工具类:令牌处理、消息格式化、速率限制、消息去重
2. 核心适配器组件:IRC解析器、消息归一化、外发消息处理
3. API客户端:Helix API封装、认证提供者
4. 配置与部署:配置校验、设置向导
5. 辅助功能:配对管理、健康检查、目标解析等
2026-05-12 00:50:10 +08:00

63 lines
2.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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