Download webapi/ratelimit.py from SupantBT/Beltbot: direct link, hf CLI and curl.
- Browser
- Download file 4.08 kB
-
https://huggingface.co/spaces/SupantBT/Beltbot/resolve/main/webapi/ratelimit.py
- Command line
-
hf download hf://spaces/SupantBT/Beltbot/webapi/ratelimit.py
-
curl -L -o ratelimit.py https://huggingface.co/spaces/SupantBT/Beltbot/resolve/main/webapi/ratelimit.py
4.08 kB
| """ | |
| webapi/ratelimit.py — rate limit ต่อ IP แบบหน้าต่างเลื่อน (sliding window) ในหน่วยความจำ | |
| Space รันโปรเซสเดียว จึงเก็บในหน่วยความจำได้ รีสตาร์ตแล้วนับใหม่ (ยอมรับได้) | |
| ไม่มี I/O ไม่มี dependency — เทสต์ได้ด้วย python ล้วน โดยส่ง now เข้าไปเอง | |
| """ | |
| from __future__ import annotations | |
| import threading | |
| import time | |
| from collections import defaultdict, deque | |
| from dataclasses import dataclass | |
| from typing import Optional | |
| class Verdict: | |
| ok: bool | |
| retry_after_s: int = 0 | |
| reason: str = "" # per_minute | per_hour | images_per_hour | global_day | |
| class RateLimiter: | |
| def __init__(self, per_minute: int, per_hour: int, images_per_hour: int, global_per_day: int): | |
| self.limits = {"per_minute": (per_minute, 60), "per_hour": (per_hour, 3600), | |
| "images_per_hour": (images_per_hour, 3600)} | |
| self.global_per_day = global_per_day | |
| self._hits: dict[tuple[str, str], deque] = defaultdict(deque) | |
| self._global: deque = deque() | |
| self._lock = threading.Lock() | |
| def _prune(q: deque, window: int, now: float) -> None: | |
| while q and q[0] <= now - window: | |
| q.popleft() | |
| def check(self, ip: str, has_image: bool, now: Optional[float] = None) -> Verdict: | |
| """ตรวจและนับในครั้งเดียว — ถ้าไม่ผ่านจะไม่นับครั้งนี้""" | |
| now = time.time() if now is None else now | |
| with self._lock: | |
| keys = ["per_minute", "per_hour"] + (["images_per_hour"] if has_image else []) | |
| for k in keys: | |
| limit, window = self.limits[k] | |
| if limit <= 0: | |
| continue | |
| q = self._hits[(ip, k)] | |
| self._prune(q, window, now) | |
| if len(q) >= limit: | |
| return Verdict(False, max(1, int(q[0] + window - now) + 1), k) | |
| if self.global_per_day > 0: | |
| self._prune(self._global, 86400, now) | |
| if len(self._global) >= self.global_per_day: | |
| return Verdict(False, max(1, int(self._global[0] + 86400 - now) + 1), "global_day") | |
| self._global.append(now) | |
| for k in keys: | |
| if self.limits[k][0] > 0: | |
| self._hits[(ip, k)].append(now) | |
| return Verdict(True) | |
| def sweep(self, now: Optional[float] = None) -> None: | |
| """ลบ IP ที่ไม่มีการใช้งานในชั่วโมงที่ผ่านมา กันหน่วยความจำโต""" | |
| now = time.time() if now is None else now | |
| with self._lock: | |
| for key in list(self._hits): | |
| q = self._hits[key] | |
| self._prune(q, 3600, now) | |
| if not q: | |
| del self._hits[key] | |
| MESSAGES_TH = { | |
| "per_minute": "ส่งข้อความถี่เกินไปครับ รบกวนรอสักครู่แล้วลองใหม่", | |
| "per_hour": "ใช้งานครบจำนวนต่อชั่วโมงแล้วครับ รบกวนลองใหม่ภายหลัง", | |
| "images_per_hour": "อัปโหลดรูปครบจำนวนต่อชั่วโมงแล้วครับ พิมพ์รหัสรุ่นแทนได้เลย หรือลองส่งรูปใหม่ภายหลัง", | |
| "global_day": "ระบบมีผู้ใช้งานเต็มโควตาของวันนี้แล้วครับ รบกวนติดต่อฝ่ายขายโดยตรง หรือลองใหม่พรุ่งนี้", | |
| } | |