refactor: 给kook适配器尽可能覆盖pydantic数据类,使其更容易获取数据和校验消息结构#5719
refactor: 给kook适配器尽可能覆盖pydantic数据类,使其更容易获取数据和校验消息结构#5719shuiping233 wants to merge 13 commits intoAstrBotDevs:masterfrom
Conversation
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! 此拉取请求对KOOK适配器进行了重大重构,通过全面引入Pydantic数据类来处理KOOK平台的WebSocket事件和API响应。这一改进旨在为适配器提供更强大的类型安全、自动数据验证和更清晰的数据结构定义,从而显著提升代码的健壮性、可维护性和开发效率。它简化了消息的解析和构造过程,使得适配器在处理复杂数据时更加可靠和易于理解。 Highlights
Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Hey - 我发现了 4 个问题,并给出了一些整体反馈:
- 在
_parse_card_message中,当KookCardMessageContainer.from_dict抛出ValidationError时,你目前只是记录了错误然后继续执行,这会导致card_list未定义并在运行时出错;建议在记录日志后立刻返回一个安全的兜底值(例如return [], "")。 KookCardMessageContainer.from_dict当前返回的是一个裸的list[KookCardMessage],而不是KookCardMessageContainer实例,这对于一个定义在该类型上的 classmethod 来说有些出乎意料;建议返回cls(card_list),以便调用方始终获得容器类型。kookGatewayIndexData的命名(首字母小写)与其他 Pydantic 模型(如KookUserMeData等)不一致;建议重命名为KookGatewayIndexData以保持清晰和一致性。
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In `_parse_card_message`, when `KookCardMessageContainer.from_dict` raises a `ValidationError` you only log the error and continue, which will leave `card_list` undefined and lead to a runtime error; consider returning a safe fallback (e.g., `return [], ""`) immediately after logging.
- `KookCardMessageContainer.from_dict` currently returns a bare `list[KookCardMessage]` rather than an instance of `KookCardMessageContainer`, which is surprising for a classmethod on that type; consider returning `cls(card_list)` so callers consistently get the container type.
- The naming of `kookGatewayIndexData` (lowercase leading letter) is inconsistent with the rest of the Pydantic models (`KookUserMeData`, etc.); consider renaming it to `KookGatewayIndexData` for clarity and consistency.
## Individual Comments
### Comment 1
<location path="astrbot/core/platform/sources/kook/kook_adapter.py" line_range="276-277" />
<code_context>
+ try:
+ card_list = KookCardMessageContainer.from_dict(json.loads(content))
+
+ except pydantic.ValidationError as e:
+ logger.error(f"[KOOK] 解析卡片消息错误: {e}")
+ logger.error(f"[KOOK] 原始消息内容: {data.to_json()}")
</code_context>
<issue_to_address>
**issue (bug_risk):** Catching pydantic.ValidationError here will currently raise NameError and also leaves card_list undefined.
Consider importing `pydantic` in this module and returning early from `_parse_card_message` on validation failure (e.g. `return [], ""`) so later code doesn’t iterate over an undefined `card_list`. Alternatively, ensure the exception handling path always initializes `card_list` before it is used.
</issue_to_address>
### Comment 2
<location path="astrbot/core/platform/sources/kook/kook_types.py" line_range="253-254" />
<code_context>
- return json.dumps(self.to_dict(), indent=indent, ensure_ascii=ensure_ascii)
-
class KookCardMessageContainer(list[KookCardMessage]):
"""卡片消息容器(列表),此类型可以直接to_json后发送出去"""
</code_context>
<issue_to_address>
**suggestion (bug_risk):** from_dict on KookCardMessageContainer returns a plain list instead of an instance of cls, which can be surprising.
`from_dict` currently returns a plain `list[KookCardMessage]` (`[KookCardMessage.from_dict(item) for item in raw_data]`), which conflicts with being defined on `KookCardMessageContainer` and with the type hint/`to_json` behavior. This inconsistency can cause subtle type/behavior issues. Consider returning a `KookCardMessageContainer` instance instead, e.g. `cls(KookCardMessage.from_dict(item) for item in raw_data)`.
</issue_to_address>
### Comment 3
<location path="astrbot/core/platform/sources/kook/kook_types.py" line_range="220" />
<code_context>
# 所有模块的联合类型
-AnyModule = (
+AnyModule = Annotated[
HeaderModule
| SectionModule
</code_context>
<issue_to_address>
**issue (complexity):** Consider simplifying the new KOOK models by removing unnecessary discriminated unions and base-class abstractions while keeping the same typing and behavior.
You can reduce the new abstraction/indirection without losing any of the new functionality.
### 1. Drop discriminated `AnyModule` while keeping enums/literals
You already have `KookModuleType` and per‑module `type: Literal[... ]`, so you don’t actually need a discriminated union here. Pydantic will still serialize/deserialize correctly as long as the field names/types match KOOK’s payloads.
Current:
```python
AnyModule = Annotated[
HeaderModule
| SectionModule
| ImageGroupModule
| ContainerModule
| ActionGroupModule
| ContextModule
| DividerModule
| FileModule
| CountdownModule
| InviteModule,
Field(discriminator="type"),
]
```
This can be simplified to:
```python
AnyModule = (
HeaderModule
| SectionModule
| ImageGroupModule
| ContainerModule
| ActionGroupModule
| ContextModule
| DividerModule
| FileModule
| CountdownModule
| InviteModule
)
```
`KookCardMessage.modules: list[AnyModule]` will still validate/serialize fine because each concrete module has a fixed `type` literal; the only thing you lose is automatic discriminated union resolution, which you’re not relying on anywhere else.
### 2. Simplify `KookWebsocketEvent` by moving parsing to the client
The discriminated union + pre‑validator on `KookWebsocketEvent` introduces quite a bit of protocol‑specific cleverness into the type layer:
```python
class KookWebsocketEvent(KookBaseDataClass):
signal: KookMessageSignal = Field(..., alias="s")
data: Annotated[
KookMessageEvent | KookHelloEvent | KookResumeAckEvent | None,
Field(discriminator="signal"),
] = Field(None, alias="d")
@model_validator(mode="before")
@classmethod
def _inject_signal_into_data(cls, data: Any) -> Any:
...
```
Since you still match on `event.signal` in the client, you can simplify `KookWebsocketEvent` to treat `data` as an untyped payload and do the per‑signal parsing where you already branch:
```python
class KookWebsocketEvent(KookBaseDataClass):
signal: KookMessageSignal = Field(..., alias="s")
d: dict[str, Any] | None = Field(None, alias="d")
sn: int | None = None
```
Then, in your client code:
```python
async def _handle_signal(self, raw_event: dict[str, Any]) -> None:
event = KookWebsocketEvent.from_dict(raw_event)
match event.signal:
case KookMessageSignal.MESSAGE:
msg = KookMessageEvent.from_dict(event.d or {})
# handle message
case KookMessageSignal.HELLO:
hello = KookHelloEvent.from_dict(event.d or {})
# handle hello
case KookMessageSignal.RESUME_ACK:
resume = KookResumeAckEvent.from_dict(event.d or {})
# handle resume-ack
# ...
```
You retain the same strong typing for `KookMessageEvent`, `KookHelloEvent`, `KookResumeAckEvent`, but the WebSocket envelope becomes a straightforward representation of the raw protocol, without discriminator plumbing or mutation in a pre‑validator.
### 3. Narrow the use of `KookBaseDataClass` to where it adds value
Right now `KookBaseDataClass` mostly renames `model_validate`/`model_dump`/`model_dump_json` and turns on `extra="allow"` globally:
```python
class KookBaseDataClass(BaseModel):
model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)
@classmethod
def from_dict(cls, raw_data: dict):
return cls.model_validate(raw_data)
@classmethod
def from_json(cls, raw_data: str | bytes | bytearray):
return cls.model_validate_json(raw_data)
def to_dict(self, by_alias=False, exclude_none=True) -> dict:
return self.model_dump(by_alias=by_alias, exclude_none=exclude_none)
def to_json(...):
return self.model_dump_json(...)
```
If you want to keep the friendlier `from_*`/`to_*` names (they’re convenient for HTTP/WS payloads), consider:
* Using `KookBaseDataClass` only for **API/WS payload models** (`KookMessageEvent`, `KookUserMeResponse`, etc.).
* Letting card models (`KookCardMessage`, `KookCardModelBase` and friends) inherit directly from `BaseModel` since they already have simple usage and don’t need `extra="allow"`:
```python
class KookCardModelBase(BaseModel):
type: str
class KookCardMessage(BaseModel):
type: Literal[KookModuleType.CARD] = KookModuleType.CARD
# ...
```
This reduces one layer of inheritance/indirection in the hot path for card handling, while keeping the convenience helpers where they actually simplify call‑sites (HTTP and WebSocket parsing).
</issue_to_address>
### Comment 4
<location path="astrbot/core/platform/sources/kook/kook_adapter.py" line_range="268" />
<code_context>
- def _parse_card_message(self, data: dict) -> tuple[list, str]:
- content = data.get("content", "[]")
+ def _parse_card_message(self, data: KookMessageEvent) -> tuple[list, str]:
+ content = data.content
if not isinstance(content, str):
</code_context>
<issue_to_address>
**issue (complexity):** Consider simplifying `_parse_card_message` to a single linear traversal with a generic image-extraction helper so the hot path depends only on a few core fields instead of many module-specific helpers and pattern matches.
You can keep the typed models and still reduce complexity/coupling a bit in the hot path with small, local changes.
### 1. Make `_parse_card_message` robust and more linear
Right now, if `KookCardMessageContainer.from_dict` fails, `card_list` is undefined and the function continues. Also, the pattern-matching + helpers cause a lot of jumping for a very narrow extraction (text + image URLs).
You can keep the typed models but make the traversal more linear and self‑contained:
```python
def _parse_card_message(self, data: KookMessageEvent) -> tuple[list, str]:
content = data.content
if not isinstance(content, str):
content = str(content)
try:
card_list = KookCardMessageContainer.from_dict(json.loads(content))
except pydantic.ValidationError as e:
logger.error(f"[KOOK] 解析卡片消息错误: {e}")
logger.error(f"[KOOK] 原始消息内容: {data.to_json()}")
# 明确失败返回,避免 card_list 未定义
return [Plain(text="[卡片消息解析失败]")], "[卡片消息解析失败]"
text_parts: list[str] = []
images: list[str] = []
for card in card_list:
for module in card.modules:
module_type = module.type
if module_type == "section":
# inline 原来的 _handle_section_text
text = getattr(module, "text", None)
if isinstance(text, (KmarkdownElement, PlainTextElement)):
if text.content:
text_parts.append(text.content)
elif module_type in ("container", "image-group"):
# 复用一个简洁的图片提取 helper(见下)
images.extend(self._extract_image_urls(module.elements))
elif module_type == "header":
header_text = getattr(getattr(module, "text", None), "content", None)
if header_text:
text_parts.append(header_text)
else:
logger.debug(f"[KOOK] 跳过或未处理模块: {module_type}")
text = "".join(text_parts)
message: list = []
if text:
message.append(Plain(text=text))
for img_url in images:
message.append(Image(file=img_url))
return message, text
```
### 2. Replace two module‑specific helpers with one focused image extractor
Instead of `_handle_section_text` and `_handle_image_group(module: ContainerModule | ImageGroupModule)`, you can keep a single helper that only depends on the *elements*, not on the specific module classes. This keeps the adapter less tightly coupled to the full card schema:
```python
def _extract_image_urls(self, elements) -> list[str]:
"""从 elements 中提取合法图片 URL,不关心具体 module 类型。"""
valid_urls: list[str] = []
for el in elements or []:
image_src = getattr(el, "src", None)
if not isinstance(image_src, str):
logger.warning(
f'[KOOK] 处理卡片中的图片时发生错误,图片url "{image_src}" 应该为str类型, 而不是 "{type(image_src)}" '
)
continue
if not image_src.startswith(("http://", "https://")):
logger.warning(f"[KOOK] 屏蔽非http图片url: {image_src}")
continue
valid_urls.append(image_src)
return valid_urls
```
This way:
- The hot path only cares about `module.type`, `module.text.content`, and `elements[].src`, similar to the original dict-based version.
- You still benefit from typed models and validation, but the adapter logic is less tightly coupled to every specific module class.
- The number of helper methods shrinks and the control flow becomes easier to read without losing functionality.
</issue_to_address>帮我变得更有用!请在每条评论上点击 👍 或 👎,我会根据你的反馈改进后续的代码审查。
Original comment in English
Hey - I've found 4 issues, and left some high level feedback:
- In
_parse_card_message, whenKookCardMessageContainer.from_dictraises aValidationErroryou only log the error and continue, which will leavecard_listundefined and lead to a runtime error; consider returning a safe fallback (e.g.,return [], "") immediately after logging. KookCardMessageContainer.from_dictcurrently returns a barelist[KookCardMessage]rather than an instance ofKookCardMessageContainer, which is surprising for a classmethod on that type; consider returningcls(card_list)so callers consistently get the container type.- The naming of
kookGatewayIndexData(lowercase leading letter) is inconsistent with the rest of the Pydantic models (KookUserMeData, etc.); consider renaming it toKookGatewayIndexDatafor clarity and consistency.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In `_parse_card_message`, when `KookCardMessageContainer.from_dict` raises a `ValidationError` you only log the error and continue, which will leave `card_list` undefined and lead to a runtime error; consider returning a safe fallback (e.g., `return [], ""`) immediately after logging.
- `KookCardMessageContainer.from_dict` currently returns a bare `list[KookCardMessage]` rather than an instance of `KookCardMessageContainer`, which is surprising for a classmethod on that type; consider returning `cls(card_list)` so callers consistently get the container type.
- The naming of `kookGatewayIndexData` (lowercase leading letter) is inconsistent with the rest of the Pydantic models (`KookUserMeData`, etc.); consider renaming it to `KookGatewayIndexData` for clarity and consistency.
## Individual Comments
### Comment 1
<location path="astrbot/core/platform/sources/kook/kook_adapter.py" line_range="276-277" />
<code_context>
+ try:
+ card_list = KookCardMessageContainer.from_dict(json.loads(content))
+
+ except pydantic.ValidationError as e:
+ logger.error(f"[KOOK] 解析卡片消息错误: {e}")
+ logger.error(f"[KOOK] 原始消息内容: {data.to_json()}")
</code_context>
<issue_to_address>
**issue (bug_risk):** Catching pydantic.ValidationError here will currently raise NameError and also leaves card_list undefined.
Consider importing `pydantic` in this module and returning early from `_parse_card_message` on validation failure (e.g. `return [], ""`) so later code doesn’t iterate over an undefined `card_list`. Alternatively, ensure the exception handling path always initializes `card_list` before it is used.
</issue_to_address>
### Comment 2
<location path="astrbot/core/platform/sources/kook/kook_types.py" line_range="253-254" />
<code_context>
- return json.dumps(self.to_dict(), indent=indent, ensure_ascii=ensure_ascii)
-
class KookCardMessageContainer(list[KookCardMessage]):
"""卡片消息容器(列表),此类型可以直接to_json后发送出去"""
</code_context>
<issue_to_address>
**suggestion (bug_risk):** from_dict on KookCardMessageContainer returns a plain list instead of an instance of cls, which can be surprising.
`from_dict` currently returns a plain `list[KookCardMessage]` (`[KookCardMessage.from_dict(item) for item in raw_data]`), which conflicts with being defined on `KookCardMessageContainer` and with the type hint/`to_json` behavior. This inconsistency can cause subtle type/behavior issues. Consider returning a `KookCardMessageContainer` instance instead, e.g. `cls(KookCardMessage.from_dict(item) for item in raw_data)`.
</issue_to_address>
### Comment 3
<location path="astrbot/core/platform/sources/kook/kook_types.py" line_range="220" />
<code_context>
# 所有模块的联合类型
-AnyModule = (
+AnyModule = Annotated[
HeaderModule
| SectionModule
</code_context>
<issue_to_address>
**issue (complexity):** Consider simplifying the new KOOK models by removing unnecessary discriminated unions and base-class abstractions while keeping the same typing and behavior.
You can reduce the new abstraction/indirection without losing any of the new functionality.
### 1. Drop discriminated `AnyModule` while keeping enums/literals
You already have `KookModuleType` and per‑module `type: Literal[... ]`, so you don’t actually need a discriminated union here. Pydantic will still serialize/deserialize correctly as long as the field names/types match KOOK’s payloads.
Current:
```python
AnyModule = Annotated[
HeaderModule
| SectionModule
| ImageGroupModule
| ContainerModule
| ActionGroupModule
| ContextModule
| DividerModule
| FileModule
| CountdownModule
| InviteModule,
Field(discriminator="type"),
]
```
This can be simplified to:
```python
AnyModule = (
HeaderModule
| SectionModule
| ImageGroupModule
| ContainerModule
| ActionGroupModule
| ContextModule
| DividerModule
| FileModule
| CountdownModule
| InviteModule
)
```
`KookCardMessage.modules: list[AnyModule]` will still validate/serialize fine because each concrete module has a fixed `type` literal; the only thing you lose is automatic discriminated union resolution, which you’re not relying on anywhere else.
### 2. Simplify `KookWebsocketEvent` by moving parsing to the client
The discriminated union + pre‑validator on `KookWebsocketEvent` introduces quite a bit of protocol‑specific cleverness into the type layer:
```python
class KookWebsocketEvent(KookBaseDataClass):
signal: KookMessageSignal = Field(..., alias="s")
data: Annotated[
KookMessageEvent | KookHelloEvent | KookResumeAckEvent | None,
Field(discriminator="signal"),
] = Field(None, alias="d")
@model_validator(mode="before")
@classmethod
def _inject_signal_into_data(cls, data: Any) -> Any:
...
```
Since you still match on `event.signal` in the client, you can simplify `KookWebsocketEvent` to treat `data` as an untyped payload and do the per‑signal parsing where you already branch:
```python
class KookWebsocketEvent(KookBaseDataClass):
signal: KookMessageSignal = Field(..., alias="s")
d: dict[str, Any] | None = Field(None, alias="d")
sn: int | None = None
```
Then, in your client code:
```python
async def _handle_signal(self, raw_event: dict[str, Any]) -> None:
event = KookWebsocketEvent.from_dict(raw_event)
match event.signal:
case KookMessageSignal.MESSAGE:
msg = KookMessageEvent.from_dict(event.d or {})
# handle message
case KookMessageSignal.HELLO:
hello = KookHelloEvent.from_dict(event.d or {})
# handle hello
case KookMessageSignal.RESUME_ACK:
resume = KookResumeAckEvent.from_dict(event.d or {})
# handle resume-ack
# ...
```
You retain the same strong typing for `KookMessageEvent`, `KookHelloEvent`, `KookResumeAckEvent`, but the WebSocket envelope becomes a straightforward representation of the raw protocol, without discriminator plumbing or mutation in a pre‑validator.
### 3. Narrow the use of `KookBaseDataClass` to where it adds value
Right now `KookBaseDataClass` mostly renames `model_validate`/`model_dump`/`model_dump_json` and turns on `extra="allow"` globally:
```python
class KookBaseDataClass(BaseModel):
model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)
@classmethod
def from_dict(cls, raw_data: dict):
return cls.model_validate(raw_data)
@classmethod
def from_json(cls, raw_data: str | bytes | bytearray):
return cls.model_validate_json(raw_data)
def to_dict(self, by_alias=False, exclude_none=True) -> dict:
return self.model_dump(by_alias=by_alias, exclude_none=exclude_none)
def to_json(...):
return self.model_dump_json(...)
```
If you want to keep the friendlier `from_*`/`to_*` names (they’re convenient for HTTP/WS payloads), consider:
* Using `KookBaseDataClass` only for **API/WS payload models** (`KookMessageEvent`, `KookUserMeResponse`, etc.).
* Letting card models (`KookCardMessage`, `KookCardModelBase` and friends) inherit directly from `BaseModel` since they already have simple usage and don’t need `extra="allow"`:
```python
class KookCardModelBase(BaseModel):
type: str
class KookCardMessage(BaseModel):
type: Literal[KookModuleType.CARD] = KookModuleType.CARD
# ...
```
This reduces one layer of inheritance/indirection in the hot path for card handling, while keeping the convenience helpers where they actually simplify call‑sites (HTTP and WebSocket parsing).
</issue_to_address>
### Comment 4
<location path="astrbot/core/platform/sources/kook/kook_adapter.py" line_range="268" />
<code_context>
- def _parse_card_message(self, data: dict) -> tuple[list, str]:
- content = data.get("content", "[]")
+ def _parse_card_message(self, data: KookMessageEvent) -> tuple[list, str]:
+ content = data.content
if not isinstance(content, str):
</code_context>
<issue_to_address>
**issue (complexity):** Consider simplifying `_parse_card_message` to a single linear traversal with a generic image-extraction helper so the hot path depends only on a few core fields instead of many module-specific helpers and pattern matches.
You can keep the typed models and still reduce complexity/coupling a bit in the hot path with small, local changes.
### 1. Make `_parse_card_message` robust and more linear
Right now, if `KookCardMessageContainer.from_dict` fails, `card_list` is undefined and the function continues. Also, the pattern-matching + helpers cause a lot of jumping for a very narrow extraction (text + image URLs).
You can keep the typed models but make the traversal more linear and self‑contained:
```python
def _parse_card_message(self, data: KookMessageEvent) -> tuple[list, str]:
content = data.content
if not isinstance(content, str):
content = str(content)
try:
card_list = KookCardMessageContainer.from_dict(json.loads(content))
except pydantic.ValidationError as e:
logger.error(f"[KOOK] 解析卡片消息错误: {e}")
logger.error(f"[KOOK] 原始消息内容: {data.to_json()}")
# 明确失败返回,避免 card_list 未定义
return [Plain(text="[卡片消息解析失败]")], "[卡片消息解析失败]"
text_parts: list[str] = []
images: list[str] = []
for card in card_list:
for module in card.modules:
module_type = module.type
if module_type == "section":
# inline 原来的 _handle_section_text
text = getattr(module, "text", None)
if isinstance(text, (KmarkdownElement, PlainTextElement)):
if text.content:
text_parts.append(text.content)
elif module_type in ("container", "image-group"):
# 复用一个简洁的图片提取 helper(见下)
images.extend(self._extract_image_urls(module.elements))
elif module_type == "header":
header_text = getattr(getattr(module, "text", None), "content", None)
if header_text:
text_parts.append(header_text)
else:
logger.debug(f"[KOOK] 跳过或未处理模块: {module_type}")
text = "".join(text_parts)
message: list = []
if text:
message.append(Plain(text=text))
for img_url in images:
message.append(Image(file=img_url))
return message, text
```
### 2. Replace two module‑specific helpers with one focused image extractor
Instead of `_handle_section_text` and `_handle_image_group(module: ContainerModule | ImageGroupModule)`, you can keep a single helper that only depends on the *elements*, not on the specific module classes. This keeps the adapter less tightly coupled to the full card schema:
```python
def _extract_image_urls(self, elements) -> list[str]:
"""从 elements 中提取合法图片 URL,不关心具体 module 类型。"""
valid_urls: list[str] = []
for el in elements or []:
image_src = getattr(el, "src", None)
if not isinstance(image_src, str):
logger.warning(
f'[KOOK] 处理卡片中的图片时发生错误,图片url "{image_src}" 应该为str类型, 而不是 "{type(image_src)}" '
)
continue
if not image_src.startswith(("http://", "https://")):
logger.warning(f"[KOOK] 屏蔽非http图片url: {image_src}")
continue
valid_urls.append(image_src)
return valid_urls
```
This way:
- The hot path only cares about `module.type`, `module.text.content`, and `elements[].src`, similar to the original dict-based version.
- You still benefit from typed models and validation, but the adapter logic is less tightly coupled to every specific module class.
- The number of helper methods shrinks and the control flow becomes easier to read without losing functionality.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
Modifications / 改动点
astrbot/core/platform/sources/kook, 将大部分ws消息事件和请求接口定义成pydantic数据类,并将大量字段get方法改成更好使用的类型判断tests/test_kook, pydantic的to_json行为和之前有点不同,所以要跟着修改一下测试用例Screenshots or Test Results / 运行截图或测试结果
Checklist / 检查清单
requirements.txt和pyproject.toml文件相应位置。/ I have ensured that no new dependencies are introduced, OR if new dependencies are introduced, they have been added to the appropriate locations inrequirements.txtandpyproject.toml.Summary by Sourcery
重构 KOOK 适配器和客户端,以在 WebSocket 事件、API 响应以及卡片消息结构中使用强类型的 Pydantic 模型,并相应调整消息解析、自发消息过滤以及测试。
Enhancements:
Tests:
Original summary in English
Summary by Sourcery
Refactor the KOOK adapter and client to use strongly-typed Pydantic models for WebSocket events, API responses, and card message structures, and adjust message parsing, self-message filtering, and tests accordingly.
Enhancements:
Tests: