- 新增好友申请XML解析器,支持解析微信4.x Linux的fmessage系统消息 - 新增好友申请自动通过规则引擎,支持黑白名单、关键词、场景过滤 - 新增xdotool驱动的好友申请UI自动化流程 - 新增后台好友申请监听器,支持增量轮询与状态统计 - 新增配套API接口,支持查询申请列表、手动/自动通过、配置管理 - 新增多分辨率UI定位配置,适配1280x720和1920x1080分辨率 - 新增配置项支持自动通过功能的开关与规则配置
228 lines
7.7 KiB
Python
228 lines
7.7 KiB
Python
"""联系人域模型:联系人 / 联系人列表 / 群聊列表 / 群成员。"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import asyncio
|
||
import enum
|
||
from typing import TYPE_CHECKING, Optional
|
||
|
||
from pydantic import BaseModel, Field
|
||
|
||
if TYPE_CHECKING:
|
||
from woc_bridge.messaging.friend_watcher import FriendRequestInfo
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 联系人接口
|
||
# ---------------------------------------------------------------------------
|
||
class Contact(BaseModel):
|
||
"""联系人。"""
|
||
|
||
wxid: str
|
||
nickname: str = ""
|
||
remark: str = ""
|
||
avatar_url: str = ""
|
||
type: str = Field(
|
||
default="person",
|
||
description="person / group / official / system",
|
||
)
|
||
|
||
# 扩展字段:从 contact.db 的 contact 表读取
|
||
alias: Optional[str] = Field(default=None, description="微信号/别名")
|
||
encrypt_username: Optional[str] = Field(default=None, description="加密用户名")
|
||
quan_pin: Optional[str] = Field(default=None, description="全拼")
|
||
pin_yin_initial: Optional[str] = Field(default=None, description="拼音首字母")
|
||
big_head_url: Optional[str] = Field(default=None, description="高清头像 URL")
|
||
small_head_url: Optional[str] = Field(default=None, description="缩略头像 URL")
|
||
description: Optional[str] = Field(default=None, description="个性签名/描述")
|
||
local_type: Optional[int] = Field(default=None, description="联系人类型标记")
|
||
verify_flag: Optional[int] = Field(default=None, description="认证标记")
|
||
delete_flag: Optional[int] = Field(default=None, description="删除标记")
|
||
chat_room_type: Optional[int] = Field(default=None, description="群类型标记")
|
||
|
||
|
||
class ContactsResponse(BaseModel):
|
||
"""联系人列表响应。"""
|
||
|
||
contacts: list[Contact] = Field(default_factory=list)
|
||
total: int = 0
|
||
|
||
|
||
class GroupsResponse(BaseModel):
|
||
"""群聊列表响应。"""
|
||
|
||
groups: list[Contact] = Field(default_factory=list)
|
||
total: int = 0
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 群成员接口
|
||
# ---------------------------------------------------------------------------
|
||
class GroupMember(BaseModel):
|
||
"""群成员。"""
|
||
|
||
wxid: str
|
||
nickname: str = ""
|
||
display_name: str = ""
|
||
is_admin: bool = False
|
||
|
||
|
||
class GroupMembersResponse(BaseModel):
|
||
"""群成员列表响应。"""
|
||
|
||
group_wxid: str
|
||
members: list[GroupMember] = Field(default_factory=list)
|
||
total: int = 0
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 修改好友备注(experimental)
|
||
# ---------------------------------------------------------------------------
|
||
class SetRemarkRequest(BaseModel):
|
||
"""修改好友备注请求(experimental)。"""
|
||
|
||
remark: str = Field(description="新备注名")
|
||
display_name: Optional[str] = Field(
|
||
default=None,
|
||
description="会话显示名(备注/昵称)用于搜索框定位;"
|
||
"不传则从 contact.db 解析(修改前的 remark 优先,其次 nickname,回退 wxid)",
|
||
)
|
||
|
||
|
||
class SetRemarkResponse(BaseModel):
|
||
"""修改好友备注响应。"""
|
||
|
||
success: bool = Field(default=False)
|
||
error: Optional[str] = Field(default=None, description="失败时的错误描述")
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 添加好友(experimental)
|
||
# ---------------------------------------------------------------------------
|
||
class AddFriendRequest(BaseModel):
|
||
"""添加好友请求(experimental)。"""
|
||
|
||
keyword: str = Field(description="搜索关键词(wxid/手机号/微信号)")
|
||
message: str = Field(default="", description="可选好友验证消息")
|
||
|
||
|
||
class AddFriendResponse(BaseModel):
|
||
"""添加好友响应。"""
|
||
|
||
success: bool = Field(default=False)
|
||
error: Optional[str] = Field(default=None, description="失败时的错误描述")
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 好友申请自动通过
|
||
# ---------------------------------------------------------------------------
|
||
class FriendRequestItem(BaseModel):
|
||
"""好友申请条目(API 响应用)。"""
|
||
stranger_wxid: str
|
||
nickname: str = ""
|
||
verify_message: str = ""
|
||
scene: str = ""
|
||
create_time: int
|
||
|
||
|
||
class FriendRequestsResponse(BaseModel):
|
||
"""好友申请列表响应。"""
|
||
requests: list[FriendRequestItem] = Field(default_factory=list)
|
||
total: int = 0
|
||
|
||
|
||
class AcceptFriendRequest(BaseModel):
|
||
"""手动通过好友申请请求。"""
|
||
stranger_wxid: str = Field(description="申请人 wxid(含 @stranger 后缀)")
|
||
nickname: Optional[str] = Field(default=None, description="申请人昵称(UI 定位用)")
|
||
|
||
|
||
class AcceptFriendResponse(BaseModel):
|
||
"""通过好友申请响应。"""
|
||
success: bool = Field(default=False)
|
||
error: Optional[str] = Field(default=None)
|
||
|
||
|
||
class AutoAcceptStatus(BaseModel):
|
||
"""自动通过运行状态。"""
|
||
running: bool
|
||
enabled: bool
|
||
processed_count: int = 0
|
||
accepted_count: int = 0
|
||
rejected_count: int = 0
|
||
last_processed_time: Optional[int] = None
|
||
|
||
|
||
class AcceptRuleConfig(BaseModel):
|
||
"""自动通过规则配置。"""
|
||
enabled: bool = Field(default=False, description="全局开关")
|
||
accept_all: bool = Field(default=False, description="通过所有申请(忽略以下规则)")
|
||
whitelist_wxids: list[str] = Field(default_factory=list, description="白名单 wxid 列表")
|
||
whitelist_nicknames: list[str] = Field(default_factory=list, description="白名单昵称列表(精确匹配)")
|
||
keywords: list[str] = Field(default_factory=list, description="验证消息关键词列表(子串匹配)")
|
||
blacklist_wxids: list[str] = Field(default_factory=list, description="黑名单 wxid 列表")
|
||
blacklist_nicknames: list[str] = Field(default_factory=list, description="黑名单昵称列表")
|
||
allow_scenes: list[str] = Field(default_factory=list, description="允许的场景(空=不限制)")
|
||
|
||
|
||
class AcceptDecision(enum.Enum):
|
||
"""规则匹配决策结果。"""
|
||
ACCEPT = "accept"
|
||
REJECT = "reject"
|
||
SKIP = "skip"
|
||
|
||
|
||
class AcceptRuleEngine:
|
||
"""好友申请自动通过规则引擎。
|
||
|
||
按"黑名单 → 场景 → 白名单 → 关键词 → SKIP"优先级决策,
|
||
配置热更新受 asyncio.Lock 保护。
|
||
"""
|
||
|
||
def __init__(self, config: AcceptRuleConfig) -> None:
|
||
self._config = config
|
||
self._lock = asyncio.Lock()
|
||
|
||
async def get_config(self) -> AcceptRuleConfig:
|
||
"""获取当前规则配置(供 API 查询)。"""
|
||
async with self._lock:
|
||
return self._config
|
||
|
||
async def evaluate(self, req: "FriendRequestInfo") -> AcceptDecision:
|
||
async with self._lock:
|
||
cfg = self._config
|
||
|
||
if not cfg.enabled:
|
||
return AcceptDecision.SKIP
|
||
|
||
if cfg.accept_all:
|
||
return AcceptDecision.ACCEPT
|
||
|
||
# 黑名单优先
|
||
if req.stranger_wxid in cfg.blacklist_wxids:
|
||
return AcceptDecision.REJECT
|
||
if req.nickname in cfg.blacklist_nicknames:
|
||
return AcceptDecision.REJECT
|
||
|
||
# 场景过滤
|
||
if cfg.allow_scenes and req.scene not in cfg.allow_scenes:
|
||
return AcceptDecision.SKIP
|
||
|
||
# 白名单
|
||
if req.stranger_wxid in cfg.whitelist_wxids:
|
||
return AcceptDecision.ACCEPT
|
||
if req.nickname in cfg.whitelist_nicknames:
|
||
return AcceptDecision.ACCEPT
|
||
|
||
# 关键词
|
||
if cfg.keywords:
|
||
for kw in cfg.keywords:
|
||
if kw in req.verify_message:
|
||
return AcceptDecision.ACCEPT
|
||
|
||
return AcceptDecision.SKIP
|
||
|
||
async def update_config(self, config: AcceptRuleConfig) -> None:
|
||
async with self._lock:
|
||
self._config = config
|