feat: Implement adapter runtime state management and update handling

- Added support for adapter runtime state updates in the PluginRunnerSupervisor.
- Introduced new payload classes: AdapterStateUpdatePayload and AdapterStateUpdateResultPayload for handling state updates.
- Implemented methods to bind and unbind routes based on adapter connection status.
- Enhanced the NapCat adapter to report connection state and manage runtime state.
- Added tests for adapter runtime state synchronization and database session behavior in the statistic module.
- Updated existing methods to ensure proper handling of adapter state and route bindings.
This commit is contained in:
DrSmoothl
2026-03-21 21:47:22 +08:00
parent dd20cd4992
commit 4e2e7a279e
11 changed files with 1219 additions and 79 deletions

View File

@@ -1,6 +1,6 @@
from dataclasses import dataclass
from dataclasses import asdict, dataclass
from datetime import datetime
from typing import Optional, List
from typing import Any, List, Mapping, Optional, Sequence
import json
@@ -15,6 +15,76 @@ class GroupCardnameInfo:
group_cardname: str
def _normalize_group_cardname_item(raw_item: Mapping[str, Any]) -> Optional[GroupCardnameInfo]:
"""将单条群名片数据规范化为统一结构。
Args:
raw_item: 原始群名片字典,必须包含 `group_id` 和 `group_cardname`。
Returns:
Optional[GroupCardnameInfo]: 规范化后的群名片信息;若数据不完整则返回 ``None``。
"""
group_id = str(raw_item.get("group_id") or "").strip()
group_cardname = str(raw_item.get("group_cardname") or "").strip()
if not group_id or not group_cardname:
return None
return GroupCardnameInfo(group_id=group_id, group_cardname=group_cardname)
def parse_group_cardname_json(group_cardname_json: Optional[str]) -> Optional[List[GroupCardnameInfo]]:
"""解析数据库中的群名片 JSON 字段。
Args:
group_cardname_json: 数据库存储的群名片 JSON 字符串。
Returns:
Optional[List[GroupCardnameInfo]]: 解析并规范化后的群名片列表;若字段为空或无有效项则返回 ``None``。
Raises:
json.JSONDecodeError: 当 JSON 文本格式非法时抛出。
TypeError: 当输入值类型不符合 `json.loads()` 要求时抛出。
"""
if not group_cardname_json:
return None
raw_items = json.loads(group_cardname_json)
if not isinstance(raw_items, list):
return None
normalized_items: List[GroupCardnameInfo] = []
for raw_item in raw_items:
if not isinstance(raw_item, Mapping):
continue
if normalized_item := _normalize_group_cardname_item(raw_item):
normalized_items.append(normalized_item)
return normalized_items or None
def dump_group_cardname_records(
group_cardname_records: Optional[Sequence[GroupCardnameInfo | Mapping[str, Any]]],
) -> str:
"""将群名片列表序列化为数据库使用的标准 JSON 字符串。
Args:
group_cardname_records: 待序列化的群名片列表,支持 `GroupCardnameInfo`
对象和包含 `group_id` / `group_cardname` 的字典。
Returns:
str: 统一使用 `group_cardname` 键名的 JSON 字符串。
"""
normalized_items: List[GroupCardnameInfo] = []
for raw_item in group_cardname_records or []:
if isinstance(raw_item, GroupCardnameInfo):
normalized_items.append(raw_item)
continue
if isinstance(raw_item, Mapping):
if normalized_item := _normalize_group_cardname_item(raw_item):
normalized_items.append(normalized_item)
return json.dumps([asdict(item) for item in normalized_items], ensure_ascii=False)
class MaiPersonInfo(BaseDatabaseDataModel[PersonInfo]):
def __init__(
self,
@@ -58,9 +128,16 @@ class MaiPersonInfo(BaseDatabaseDataModel[PersonInfo]):
"""最后一次被认识的时间"""
@classmethod
def from_db_instance(cls, db_record: "PersonInfo"):
nickname_json = json.loads(db_record.group_cardname) if db_record.group_cardname else None
group_cardname_list = [GroupCardnameInfo(**item) for item in nickname_json] if nickname_json else None
def from_db_instance(cls, db_record: "PersonInfo") -> "MaiPersonInfo":
"""从数据库记录构造人物信息数据模型。
Args:
db_record: 数据库中的人物信息记录。
Returns:
MaiPersonInfo: 转换后的数据模型对象。
"""
group_cardname_list = parse_group_cardname_json(db_record.group_cardname)
memory_points = json.loads(db_record.memory_points) if db_record.memory_points else None
return cls(
is_known=db_record.is_known,
@@ -78,9 +155,12 @@ class MaiPersonInfo(BaseDatabaseDataModel[PersonInfo]):
)
def to_db_instance(self) -> "PersonInfo":
group_cardname = (
json.dumps([gc.__dict__ for gc in self.group_cardname_list]) if self.group_cardname_list else None
)
"""将当前数据模型转换为数据库记录对象。
Returns:
PersonInfo: 可直接写入数据库的模型实例。
"""
group_cardname = dump_group_cardname_records(self.group_cardname_list)
return PersonInfo(
is_known=self.is_known,
person_id=self.person_id,