from __future__ import annotations import logging import time from dataclasses import dataclass, field from typing import Any logger = logging.getLogger(__name__) @dataclass class FeishuRateLimiter: max_requests_per_minute: int = 100 _timestamps: list[float] = field(default_factory=list) def acquire(self) -> bool: now = time.monotonic() window = now - 60 self._timestamps = [t for t in self._timestamps if t > window] if len(self._timestamps) >= self.max_requests_per_minute: return False self._timestamps.append(now) return True def reset(self) -> None: self._timestamps.clear() @dataclass class FeishuAccount: name: str app_id: str = "" app_secret: str = "" bot_open_id: str = "" platform: str = "feishu" domain: str = "" encrypt_key: str = "" verify_token: str = "" enabled: bool = True config: dict[str, Any] = field(default_factory=dict) rate_limiter: FeishuRateLimiter = field(default_factory=FeishuRateLimiter) class FeishuAccountManager: def __init__(self, config: dict[str, Any] | None = None): self._accounts: dict[str, FeishuAccount] = {} self._default_account: str = "" if config: self._load_from_config(config) def _load_from_config(self, config: dict[str, Any]) -> None: accounts_cfg = config.get("accounts", {}) if not accounts_cfg: main = FeishuAccount( name="default", app_id=config.get("app_id", ""), app_secret=config.get("app_secret", ""), bot_open_id=config.get("bot_open_id", ""), platform=config.get("platform", "feishu"), domain=config.get("domain", ""), encrypt_key=config.get("encrypt_key", ""), verify_token=config.get("verify_token", ""), config=config, ) self._accounts["default"] = main self._default_account = "default" return self._default_account = config.get("defaultAccount", "") for name, acct_cfg in accounts_cfg.items(): if not isinstance(acct_cfg, dict): continue merged = {**config, **acct_cfg} rpm = acct_cfg.get("rateLimitPerMinute", acct_cfg.get("rate_limit_per_minute", 100)) account = FeishuAccount( name=name, app_id=acct_cfg.get("appId", acct_cfg.get("app_id", "")), app_secret=acct_cfg.get("appSecret", acct_cfg.get("app_secret", "")), bot_open_id=acct_cfg.get("botOpenId", acct_cfg.get("bot_open_id", "")), platform=acct_cfg.get("platform", merged.get("platform", "feishu")), domain=acct_cfg.get("domain", merged.get("domain", "")), encrypt_key=acct_cfg.get("encryptKey", acct_cfg.get("encrypt_key", "")), verify_token=acct_cfg.get("verifyToken", acct_cfg.get("verify_token", "")), enabled=acct_cfg.get("enabled", True), config=merged, rate_limiter=FeishuRateLimiter(max_requests_per_minute=rpm if isinstance(rpm, int) else 100), ) self._accounts[name] = account if self._default_account not in self._accounts and self._accounts: self._default_account = next(iter(self._accounts)) def get_account(self, name: str = "") -> FeishuAccount | None: key = name or self._default_account account = self._accounts.get(key) if account and account.enabled: return account return None def get_default_account(self) -> FeishuAccount | None: return self.get_account(self._default_account) def list_enabled(self) -> list[FeishuAccount]: return [a for a in self._accounts.values() if a.enabled] def list_all(self) -> list[FeishuAccount]: return list(self._accounts.values()) def check_rate_limit(self, name: str = "") -> bool: account = self.get_account(name) if account is None: return False return account.rate_limiter.acquire() def delete_account(self, name: str) -> bool: if name == self._default_account: logger.warning("[FeishuAccount] Cannot delete default account '%s'", name) return False if name not in self._accounts: logger.warning("[FeishuAccount] Account '%s' not found", name) return False self._accounts.pop(name) logger.info("[FeishuAccount] Deleted account '%s'", name) return True def get_account_status_line(self, name: str = "") -> str: account = self.get_account(name) if name else self.get_default_account() if account is None: return "unknown: no account configured" if account.enabled: status = "active" else: status = "disabled" return f"{account.name}: {status} (platform={account.platform})"