实现了完整的Flock渠道接入能力,包含消息收发、Webhook事件监听、账号配置、安全校验、媒体文件处理等功能,支持私聊和群组聊天,适配ForcePilot插件规范。
170 lines
5.5 KiB
Python
170 lines
5.5 KiB
Python
from __future__ import annotations
|
||
|
||
import logging
|
||
import re
|
||
|
||
from .config import _apply_env_overrides, _dict_to_account
|
||
from .constants import FLOCK_GROUP_ID_PATTERN, FLOCK_USER_ID_PATTERN
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
_user_id_re = re.compile(FLOCK_USER_ID_PATTERN)
|
||
_group_id_re = re.compile(FLOCK_GROUP_ID_PATTERN)
|
||
|
||
|
||
def resolve_dm_policy(config: dict, account_id: str = "default") -> dict:
|
||
account_data = config.get("accounts", {}).get(account_id, {})
|
||
account = _dict_to_account(account_data)
|
||
account = _apply_env_overrides(account)
|
||
return {"mode": account.dm_policy, "allow_from": account.dm_allow_from}
|
||
|
||
|
||
def resolve_group_policy(config: dict, account_id: str = "default") -> dict:
|
||
account_data = config.get("accounts", {}).get(account_id, {})
|
||
account = _dict_to_account(account_data)
|
||
account = _apply_env_overrides(account)
|
||
return {"mode": account.group_policy, "group_allow_from": account.group_allow_from}
|
||
|
||
|
||
async def check_allowlist(
|
||
peer_id: str,
|
||
channel_type: str,
|
||
config: dict,
|
||
account_id: str = "default",
|
||
) -> dict:
|
||
account_data = config.get("accounts", {}).get(account_id, {})
|
||
account = _dict_to_account(account_data)
|
||
account = _apply_env_overrides(account)
|
||
|
||
if channel_type == "direct":
|
||
policy = account.dm_policy
|
||
allow_list = account.dm_allow_from
|
||
else:
|
||
policy = account.group_policy
|
||
allow_list = account.group_allow_from
|
||
|
||
if policy == "open":
|
||
return {"allowed": True, "reason": ""}
|
||
|
||
if policy == "disabled":
|
||
return {"allowed": False, "reason": f"{channel_type} messaging is disabled"}
|
||
|
||
if policy in ("allowlist", "pairing"):
|
||
if peer_id in allow_list:
|
||
return {"allowed": True, "reason": ""}
|
||
return {"allowed": False, "reason": f"Peer {peer_id} not in {channel_type} allow list"}
|
||
|
||
return {"allowed": False, "reason": f"Unknown policy: {policy}"}
|
||
|
||
|
||
def collect_warnings(
|
||
config: dict,
|
||
account_id: str | None = None,
|
||
account: dict | None = None,
|
||
) -> list[str]:
|
||
warnings: list[str] = []
|
||
|
||
if account is None:
|
||
if account_id:
|
||
account = config.get("accounts", {}).get(account_id, {})
|
||
if account is None:
|
||
return warnings
|
||
|
||
acct = _dict_to_account(account)
|
||
acct = _apply_env_overrides(acct)
|
||
|
||
if acct.dm_policy == "open":
|
||
warnings.append("DM 策略为 open,任何人可私信 Bot")
|
||
|
||
if acct.group_policy == "open":
|
||
warnings.append("Group 策略为 open,任何群组可 @Bot")
|
||
|
||
if not acct.outgoing_webhook_token and not acct.event_listener_token:
|
||
warnings.append("Outgoing Webhook token 和 Event Listener token 均未配置,Webhook 验证将跳过")
|
||
|
||
if acct.event_listener_token and not acct.app_secret:
|
||
warnings.append("Event Listener token 已配置但 app_secret 未配置,Event Token HMAC 签名验证将跳过")
|
||
|
||
if not acct.bot_token and not acct.incoming_webhook_url:
|
||
warnings.append("bot_token 和 incoming_webhook_url 均未配置,无法发送消息")
|
||
|
||
return warnings
|
||
|
||
|
||
def collect_audit_findings(
|
||
config,
|
||
account_id=None,
|
||
account=None,
|
||
source_config=None,
|
||
ordered_account_ids=None,
|
||
has_explicit_account_path=False,
|
||
) -> list[dict]:
|
||
findings: list[dict] = []
|
||
|
||
if account is None and account_id:
|
||
account = config.get("accounts", {}).get(account_id, {})
|
||
if account is None:
|
||
return findings
|
||
|
||
acct = _dict_to_account(account)
|
||
acct = _apply_env_overrides(acct)
|
||
|
||
if not acct.bot_token and not acct.incoming_webhook_url:
|
||
findings.append({
|
||
"severity": "error",
|
||
"category": "auth",
|
||
"message": "bot_token 和 incoming_webhook_url 均未配置,无法发送消息",
|
||
})
|
||
|
||
if acct.dm_policy == "open" and not acct.dm_allow_from:
|
||
findings.append({
|
||
"severity": "warning",
|
||
"category": "dm_policy",
|
||
"message": "DM 策略为 open 且未配置 dm_allow_from,任何人可私信 Bot",
|
||
})
|
||
|
||
if acct.group_policy == "open" and not acct.require_mention:
|
||
findings.append({
|
||
"severity": "warning",
|
||
"category": "group_policy",
|
||
"message": "Group 策略为 open 且 require_mention=False,任何群组消息都会被处理",
|
||
})
|
||
|
||
if acct.event_listener_token and not acct.app_secret:
|
||
findings.append({
|
||
"severity": "error",
|
||
"category": "security",
|
||
"message": "Event Listener token 已配置但 app_secret 未配置,Event Token HMAC 签名验证将跳过",
|
||
})
|
||
|
||
if not acct.event_listener_token and not acct.outgoing_webhook_token:
|
||
findings.append({
|
||
"severity": "info",
|
||
"category": "security",
|
||
"message": "Event Listener token 和 Outgoing Webhook token 均未配置,Webhook 来源无法验证",
|
||
})
|
||
|
||
return findings
|
||
|
||
|
||
def normalize_allow_entry(entry: str) -> str:
|
||
stripped = entry.strip()
|
||
if not stripped.startswith("u:") and not stripped.startswith("g:"):
|
||
stripped = f"u:{stripped}"
|
||
return stripped
|
||
|
||
|
||
def is_valid_flock_user_id(id_str: str) -> bool:
|
||
return bool(_user_id_re.match(id_str))
|
||
|
||
|
||
def is_valid_flock_group_id(id_str: str) -> bool:
|
||
return bool(_group_id_re.match(id_str))
|
||
|
||
|
||
def resolve_require_mention(config: dict, account_id: str = "default") -> bool:
|
||
account_data = config.get("accounts", {}).get(account_id, {})
|
||
account = _dict_to_account(account_data)
|
||
account = _apply_env_overrides(account)
|
||
return account.require_mention
|