ForcePilot/backend/package/yuxi/channels/adapters/bluebubbles/config.py
Kris 1f78c44b03 refactor: 整理并清理项目中的冗余代码与格式问题
这是一个批量整理提交,包含以下主要改动:
1.  删除多处冗余的空行和未使用的导入
2.  修复文件末尾缺少换行符的问题
3.  调整部分模块的导入顺序与代码排版
4.  修复部分配置默认值与策略逻辑
5.  新增多个功能模块与辅助工具
6.  完善异常处理与日志记录
7.  修复速率限制、消息缓存、权限校验等逻辑bug
8.  废弃部分旧有API与配置项并添加警告提示
2026-05-12 14:51:53 +08:00

153 lines
5.7 KiB
Python

from __future__ import annotations
import os
from typing import Any, Literal
from pydantic import BaseModel, Field, field_validator
from yuxi.utils.logging_config import logger
class CatchupConfig(BaseModel):
enabled: bool = True
max_age_minutes: int = 120
per_run_limit: int = 50
first_run_lookback_minutes: int = 30
max_failure_retries: int = 10
class HealthMonitorConfig(BaseModel):
enabled: bool = True
class NetworkConfig(BaseModel):
dangerously_allow_private_network: bool = False
class MarkdownConfig(BaseModel):
enabled: bool = True
code_blocks: bool = True
class BlockStreamingCoalesceConfig(BaseModel):
enabled: bool = False
max_flush_interval_ms: int = 500
class GroupOverride(BaseModel):
system_prompt: str = ""
class BlueBubblesConfig(BaseModel):
enabled: bool = False
name: str = ""
server_url: str = Field(default="http://localhost:1234")
password: str = Field(default="")
request_timeout: float = 30.0
send_timeout_ms: int = 30000
reconnect_max_delay: float = 60.0
dm_policy: Literal["open", "pairing", "allowlist", "blocklist"] = "pairing"
group_policy: Literal["open", "pairing", "allowlist", "blocklist"] = "allowlist"
dm_allow_from: list[str] = []
group_allow_chats: list[str] = []
group_allow_from: list[str] = []
media_max_mb: int = 100
enable_stickers: bool = True
media_local_roots: list[str] = []
streaming_mode: Literal["off", "partial", "block"] = "partial"
edit_interval_ms: int = 500
block_streaming: bool = False
block_streaming_coalesce: BlockStreamingCoalesceConfig = Field(default_factory=BlockStreamingCoalesceConfig)
text_chunk_limit: int = 4000
chunk_mode: Literal["length", "newline"] = "newline"
tapback_enabled: bool = True
require_mention: bool = False
ai_vision_enabled: bool = False
streaming_typing_indicator: bool = True
message_order_check: bool = True
max_cache_entries: int = 2048
send_read_receipts: bool = True
dm_history_limit: int = 100
coalesce_same_sender_dms: bool = False
enrich_group_participants_from_contacts: bool = True
allow_private_network: bool = False
network: NetworkConfig = Field(default_factory=NetworkConfig)
config_writes: bool = True
webhook_path: str = "/bluebubbles/webhook"
webhook_secret: str = ""
markdown: MarkdownConfig = Field(default_factory=MarkdownConfig)
capabilities: list[str] = []
dms: dict[str, dict[str, Any]] = {}
default_account: str = ""
health_monitor: HealthMonitorConfig = Field(default_factory=HealthMonitorConfig)
catchup: CatchupConfig = Field(default_factory=CatchupConfig)
groups: dict[str, GroupOverride] = {}
auth_strategy: Literal["header", "query", "both"] = "header"
auth_header_name: str = "X-BB-Password"
tts_prefer_caf: bool = True
@field_validator("allow_private_network", mode="before")
@classmethod
def _deprecate_allow_private_network(cls, v, info) -> Any:
if v is True:
logger.warning(
"[BlueBubbles] 'allow_private_network' is deprecated, "
"use 'network.dangerously_allow_private_network' instead"
)
return v
@classmethod
def from_env(cls) -> BlueBubblesConfig:
dm_raw = os.getenv("BLUEBUBBLES_DM_ALLOW_FROM", "")
group_raw = os.getenv("BLUEBUBBLES_GROUP_ALLOW_CHATS", "")
media_roots_raw = os.getenv("BLUEBUBBLES_MEDIA_LOCAL_ROOTS", "")
return cls(
enabled=os.getenv("BLUEBUBBLES_ENABLED", "false").lower() == "true",
name=os.getenv("BLUEBUBBLES_NAME", ""),
server_url=os.getenv("BLUEBUBBLES_SERVER_URL", "http://localhost:1234"),
password=os.getenv("BLUEBUBBLES_SERVER_PASSWORD", ""),
request_timeout=float(os.getenv("BLUEBUBBLES_REQUEST_TIMEOUT", "30.0")),
send_timeout_ms=int(os.getenv("BLUEBUBBLES_SEND_TIMEOUT_MS", "30000")),
reconnect_max_delay=float(os.getenv("BLUEBUBBLES_RECONNECT_MAX_DELAY", "60.0")),
dm_policy=os.getenv("BLUEBUBBLES_DM_POLICY", "pairing"),
group_policy=os.getenv("BLUEBUBBLES_GROUP_POLICY", "allowlist"),
dm_allow_from=[x.strip() for x in dm_raw.split(",") if x.strip()] if dm_raw else [],
group_allow_chats=[x.strip() for x in group_raw.split(",") if x.strip()] if group_raw else [],
media_max_mb=int(os.getenv("BLUEBUBBLES_MEDIA_MAX_MB", "100")),
enable_stickers=os.getenv("BLUEBUBBLES_ENABLE_STICKERS", "true").lower() == "true",
media_local_roots=[x.strip() for x in media_roots_raw.split(",") if x.strip()] if media_roots_raw else [],
streaming_mode=os.getenv("BLUEBUBBLES_STREAMING_MODE", "partial"),
edit_interval_ms=int(os.getenv("BLUEBUBBLES_EDIT_INTERVAL_MS", "500")),
tapback_enabled=os.getenv("BLUEBUBBLES_TAPBACK_ENABLED", "true").lower() == "true",
require_mention=os.getenv("BLUEBUBBLES_REQUIRE_MENTION", "false").lower() == "true",
send_read_receipts=os.getenv("BLUEBUBBLES_SEND_READ_RECEIPTS", "true").lower() == "true",
text_chunk_limit=int(os.getenv("BLUEBUBBLES_TEXT_CHUNK_LIMIT", "4000")),
block_streaming=os.getenv("BLUEBUBBLES_BLOCK_STREAMING", "false").lower() == "true",
allow_private_network=os.getenv("BLUEBUBBLES_ALLOW_PRIVATE_NETWORK", "false").lower() == "true",
webhook_path=os.getenv("BLUEBUBBLES_WEBHOOK_PATH", "/bluebubbles/webhook"),
webhook_secret=os.getenv("BLUEBUBBLES_WEBHOOK_SECRET", ""),
auth_strategy=os.getenv("BLUEBUBBLES_AUTH_STRATEGY", "header"),
tts_prefer_caf=os.getenv("BLUEBUBBLES_TTS_PREFER_CAF", "true").lower() == "true",
)