新增飞书机器人适配器全套功能,包括: - 基础适配器入口与工具导出 - 消息格式化、卡片渲染、回复调度逻辑 - 会话ID生成、模型覆盖策略 - 消息发送缓存、顺序队列管理 - 飞书签名验证、加解密webhook请求 - 审批权限校验、机器人菜单事件处理 - 文档评论、钉消息、语音转码处理 - 静态/动态目录管理、子代理生命周期管理 - 各类工具集:聊天、云盘、文档、知识库API封装
135 lines
4.9 KiB
Python
135 lines
4.9 KiB
Python
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})"
|