mirror of
https://github.com/AstrBotDevs/AstrBot
synced 2026-07-15 17:30:13 +08:00
* feat: 实现kook适配器响应`@`角色(role)的能力 * refactor: kook适配器处理role时,`At`组件保留`@`角色的名称而不是id * fix: kook适配器处理role时,role_id的判断问题 * refactor: 移除kook适配器中的一个# type: ignore * fix: 修复kook适配器 role mention转换成`At`组件时保留不是角色名称的bug; * unittest: 给kook适配器添加带有role mention的事件消息的单测,并添加消息组件转换判断单测 * unittest: 部分重构test_kook_event.py和test_kook_types.py 单测 * unittest: 添加kook适配器的 `user/me` `user/view` 接口响应数据验证单测 * fix: 修复kook适配器接收频道权限更新消息会报错的bug * fix: 不额外处理kook的道具消息 * fix: 使用async with self._http_client.get * refactor: kook适配器转换文本内容为消息组件时,只strip mention之间的空格 * fix: 修复 role_mention_counter 计数不正确的问题 * fix: 修复kook适配器发送卡片失败的问题;区分两类kook 数据类的to_dict to_json行为 * chore: 添加注释 * refactor: 重构kook适配器的角色缓存功能,使其无锁,性能更好且具备良好的重试机制 * refactor: kook适配器的channel_id 改为 guild_id * feat: kook适配器响应频道角色更新事件时不再清空整个角色id缓存,而是只清理特定频道的角色id缓存 * unittest: 添加kook适配器的update_role事件的数据类验证单测 * refactor: 补上了一些打印的日志消息文本 refactor: 补上了一些打印的日志消息文本 refactor: 补上了一些打印的日志消息文本 * refactor: 修复kook适配器潜在可能的类型问题 * refactor: `clean_roles_cache`重命名为`clear_guild_roles_cache`
157 lines
4.8 KiB
Python
157 lines
4.8 KiB
Python
import asyncio
|
|
from dataclasses import dataclass, field
|
|
import json
|
|
from pathlib import Path
|
|
from typing import Any
|
|
from unittest.mock import AsyncMock
|
|
|
|
import pytest
|
|
|
|
from astrbot.core.message.components import (
|
|
At,
|
|
AtAll,
|
|
BaseMessageComponent,
|
|
Plain,
|
|
Record,
|
|
)
|
|
from astrbot.core.platform.sources.kook.kook_client import KookClient
|
|
from astrbot.core.platform.sources.kook.kook_config import KookConfig
|
|
from astrbot.core.platform.sources.kook.kook_types import (
|
|
KookMessageEventData,
|
|
KookWebsocketEvent,
|
|
)
|
|
from tests.test_kook.shared import (
|
|
KookEventDataPath,
|
|
mock_http_client,
|
|
mock_kook_roles_record,
|
|
)
|
|
|
|
TEST_BOT_ID = 1234567891
|
|
TEST_BOT_USERNAME = "test_username"
|
|
TEST_BOT_NICKNAME = "test_nickname"
|
|
|
|
|
|
def mock_kook_client(config: KookConfig, event_callback):
|
|
class MockKookClient:
|
|
def __init__(self, config, callback):
|
|
self.bot_id = TEST_BOT_ID
|
|
self.bot_nickname = TEST_BOT_NICKNAME
|
|
self.bot_username = TEST_BOT_USERNAME
|
|
self.http_client = mock_http_client()
|
|
self.connect = AsyncMock()
|
|
self.close = AsyncMock()
|
|
|
|
async def __aenter__(self):
|
|
return self
|
|
|
|
async def __aexit__(self, *args):
|
|
pass
|
|
|
|
return MockKookClient(config, event_callback)
|
|
|
|
|
|
def get_json_field(content: dict, json_field_path: list[str | int]) -> Any:
|
|
expend_value = content
|
|
for key in json_field_path:
|
|
expend_value = expend_value[key]
|
|
return expend_value
|
|
|
|
|
|
@dataclass
|
|
class JsonFieldPaths:
|
|
message_str: list[int | str] = field(default_factory=list)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.parametrize(
|
|
"expected_json_data_path, expected_message_str, expected_message_components",
|
|
[
|
|
(
|
|
KookEventDataPath.GROUP_MESSAGE_WITH_MENTION,
|
|
["d", "extra", "kmarkdown", "raw_content"],
|
|
[
|
|
# 这里默认机器人一定属于某个角色id
|
|
At(qq=TEST_BOT_ID, name="some_role"),
|
|
Plain(text="/help"),
|
|
At(qq=3351526782, name="some_username"),
|
|
AtAll(qq="all", name=""),
|
|
],
|
|
),
|
|
(
|
|
KookEventDataPath.GROUP_MESSAGE,
|
|
["d", "extra", "kmarkdown", "raw_content"],
|
|
[Plain(text="done!")],
|
|
),
|
|
(
|
|
KookEventDataPath.MESSAGE_WITH_CARD_1,
|
|
"[audio]",
|
|
[
|
|
Plain(text="[audio]"),
|
|
Record(
|
|
file="https://img.kookapp.cn/attachments/2026-03/03/69a6841c3125d.wav",
|
|
url="",
|
|
text=None,
|
|
path=None,
|
|
),
|
|
],
|
|
),
|
|
(
|
|
KookEventDataPath.MESSAGE_WITH_CARD_2,
|
|
["d", "extra", "kmarkdown", "raw_content"],
|
|
[
|
|
Plain(text="(met)"),
|
|
Plain(text="all(met) #hello \\*\\*world\\*\\* [audio]\n😆"),
|
|
Record(
|
|
file="https://img.kookapp.cn/attachments/2026-03/03/69a6841c3125d.wav",
|
|
url="",
|
|
text=None,
|
|
path=None,
|
|
),
|
|
],
|
|
),
|
|
(
|
|
KookEventDataPath.PRIVATE_MESSAGE,
|
|
["d", "extra", "kmarkdown", "raw_content"],
|
|
[Plain(text="/help")],
|
|
),
|
|
],
|
|
)
|
|
async def test_kook_event_warp_message(
|
|
expected_json_data_path: Path,
|
|
expected_message_str: list[int | str] | str,
|
|
expected_message_components: list[BaseMessageComponent],
|
|
):
|
|
monkeypatch = pytest.MonkeyPatch()
|
|
monkeypatch.setattr(
|
|
"astrbot.core.platform.sources.kook.kook_adapter.KookClient", mock_kook_client
|
|
)
|
|
monkeypatch.setattr(
|
|
"astrbot.core.platform.sources.kook.kook_adapter.KookRolesRecord",
|
|
mock_kook_roles_record,
|
|
)
|
|
|
|
from astrbot.core.platform.sources.kook.kook_adapter import KookPlatformAdapter
|
|
|
|
adapter = KookPlatformAdapter({}, {}, asyncio.Queue())
|
|
|
|
raw_event_str = expected_json_data_path.read_text(encoding="utf-8")
|
|
raw_event = json.loads(raw_event_str)
|
|
event = KookWebsocketEvent.from_json(
|
|
raw_event_str,
|
|
)
|
|
assert isinstance(event.data, KookMessageEventData)
|
|
|
|
astrbotMessage = await adapter.convert_message(event.data)
|
|
assert astrbotMessage.self_id == TEST_BOT_ID
|
|
assert astrbotMessage.sender.user_id == raw_event["d"]["author_id"]
|
|
assert (
|
|
astrbotMessage.sender.nickname == raw_event["d"]["extra"]["author"]["username"]
|
|
)
|
|
assert astrbotMessage.raw_message == raw_event["d"]
|
|
assert astrbotMessage.message_id == raw_event["d"]["msg_id"]
|
|
assert astrbotMessage.message == expected_message_components
|
|
if isinstance(expected_message_str, str):
|
|
assert astrbotMessage.message_str == expected_message_str
|
|
else:
|
|
assert get_json_field(raw_event, expected_message_str)
|