Initial commit: VPN Telegram bot (sanitized defaults)
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
"""Простой in-memory rate limit (на процесс)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
from collections import defaultdict
|
||||
from threading import Lock
|
||||
|
||||
|
||||
class RateLimiter:
|
||||
def __init__(self) -> None:
|
||||
self._hits: dict[str, list[float]] = defaultdict(list)
|
||||
self._lock = Lock()
|
||||
|
||||
def hit(self, key: str, *, limit: int, window_sec: float) -> bool:
|
||||
"""True если запрос разрешён, False если превышен лимит."""
|
||||
now = time.monotonic()
|
||||
with self._lock:
|
||||
bucket = self._hits[key]
|
||||
cutoff = now - window_sec
|
||||
self._hits[key] = [t for t in bucket if t >= cutoff]
|
||||
if len(self._hits[key]) >= limit:
|
||||
return False
|
||||
self._hits[key].append(now)
|
||||
return True
|
||||
|
||||
|
||||
limiter = RateLimiter()
|
||||
|
||||
|
||||
def client_ip(request) -> str:
|
||||
forwarded = (request.headers.get("X-Forwarded-For") or "").split(",")[0].strip()
|
||||
if forwarded:
|
||||
return forwarded
|
||||
peer = request.remote
|
||||
return str(peer or "unknown")
|
||||
|
||||
|
||||
def allow(request, bucket: str, *, limit: int = 20, window_sec: float = 60.0) -> bool:
|
||||
ip = client_ip(request)
|
||||
return limiter.hit(f"{bucket}:{ip}", limit=limit, window_sec=window_sec)
|
||||
Reference in New Issue
Block a user