mirror of
https://github.com/AstrBotDevs/AstrBot
synced 2026-07-16 09:40:30 +08:00
268 lines
10 KiB
Python
268 lines
10 KiB
Python
"""旧版插件兼容测试夹具。
|
||
|
||
这个样例故意覆盖当前仍被兼容层支持的旧 API:
|
||
- CommandComponent / AstrMessageEvent / MessageChain
|
||
- filter.command / regex / permission / permission_type /
|
||
event_message_type / platform_adapter_type
|
||
- LegacyContext 的 conversation_manager / llm_generate / tool_loop_agent /
|
||
send_message / put|get|delete_kv_data / call_context_function
|
||
- 消息组件及其旧字段别名/工厂方法
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from astrbot_sdk import provide_capability
|
||
from astrbot_sdk.api.components.command import CommandComponent
|
||
from astrbot_sdk.api.event import AstrMessageEvent, filter
|
||
from astrbot_sdk.api.event.filter import (
|
||
ADMIN,
|
||
EventMessageType,
|
||
PermissionType,
|
||
PlatformAdapterType,
|
||
)
|
||
from astrbot_sdk.api.message import MessageChain
|
||
from astrbot_sdk.api.message_components import (
|
||
At,
|
||
AtAll,
|
||
Face,
|
||
File,
|
||
Image,
|
||
Node,
|
||
Plain,
|
||
Record,
|
||
Reply,
|
||
Video,
|
||
)
|
||
from astrbot_sdk.api.star.context import Context
|
||
from loguru import logger
|
||
|
||
|
||
class CompatHelper:
|
||
__compat_component_name__ = "CompatHelper"
|
||
|
||
async def shout(self, text: str) -> str:
|
||
return text.upper()
|
||
|
||
|
||
class HelloCommand(CommandComponent):
|
||
"""测试旧版 CommandComponent 兼容性。"""
|
||
|
||
def __init__(self, context: Context):
|
||
self.context = context
|
||
self.context._register_component(CompatHelper())
|
||
|
||
@filter.command("hello", alias={"hi"}, priority=10, desc="问候命令")
|
||
async def hello(self, event: AstrMessageEvent):
|
||
"""基本命令测试。"""
|
||
ret = await self.context.conversation_manager.new_conversation("hello")
|
||
logger.info("New conversation created: {}", ret)
|
||
yield event.plain_result(f"Hello, Astrbot! Created conversation ID: {ret}")
|
||
|
||
@filter.command("echo")
|
||
async def echo(self, event: AstrMessageEvent):
|
||
"""测试消息获取和 extra 状态。"""
|
||
text = event.get_message_str()
|
||
event.set_extra("last_echo", text)
|
||
extra = event.get_extra("last_echo")
|
||
event.clear_extra()
|
||
group = await event.get_group()
|
||
yield event.plain_result(
|
||
f"Echo: {text}|sender={event.get_sender_id()}|sender_name={event.get_sender_name()}|"
|
||
f"platform={event.get_platform_name()}:{event.get_platform_id()}|"
|
||
f"type={event.get_message_type().value}|messages={len(event.get_messages())}|"
|
||
f"self={event.get_self_id()}|private={event.is_private_chat()}|"
|
||
f"wake={event.is_wake_up()}|group={group is not None}|extra={extra}"
|
||
)
|
||
|
||
@filter.command("chain")
|
||
async def test_chain(self, event: AstrMessageEvent):
|
||
"""测试 MessageChain 构建、发送与 react。"""
|
||
chain = (
|
||
MessageChain()
|
||
.message("Hello")
|
||
.message(" ")
|
||
.at("user", "12345")
|
||
.at_all()
|
||
.message(" check this image: ")
|
||
.url_image("https://example.com/test.png")
|
||
.file_image("C:/tmp/test.png")
|
||
.base64_image("base64://fixture")
|
||
.use_t2i(True)
|
||
.squash_plain()
|
||
)
|
||
|
||
await event.send(chain)
|
||
await event.react(":thumbsup:")
|
||
yield event.plain_result(
|
||
f"Chain sent with {len(chain.to_payload())} components and t2i={chain.use_t2i_}"
|
||
)
|
||
|
||
@filter.command("db")
|
||
async def test_db(self, event: AstrMessageEvent):
|
||
"""测试 KV 数据库操作。"""
|
||
await self.context.put_kv_data("test_key", {"value": "test_data"})
|
||
data = await self.context.get_kv_data("test_key")
|
||
await self.context.delete_kv_data("test_key")
|
||
deleted = await self.context.get_kv_data("test_key", "missing")
|
||
logger.info("Got data from db: {}", data)
|
||
yield event.plain_result(f"DB test: stored={data} deleted={deleted}")
|
||
|
||
@filter.command("conversation")
|
||
async def test_conversation(self, event: AstrMessageEvent):
|
||
"""测试会话管理器和 call_context_function。"""
|
||
umo = f"platform:{event.get_session_id()}"
|
||
cid = await self.context.conversation_manager.new_conversation(
|
||
unified_msg_origin=umo,
|
||
title="Compat Chat",
|
||
persona_id="assistant",
|
||
)
|
||
await self.context.conversation_manager.add_message_pair(cid, "hello", "world")
|
||
await self.context.conversation_manager.update_conversation(
|
||
unified_msg_origin=umo,
|
||
conversation_id=cid,
|
||
title="Compat Chat Updated",
|
||
)
|
||
await self.context.conversation_manager.update_conversation_title(umo, "Compat")
|
||
await self.context.conversation_manager.update_conversation_persona_id(
|
||
umo,
|
||
"compat-persona",
|
||
)
|
||
current_id = await self.context.conversation_manager.get_curr_conversation_id(
|
||
umo
|
||
)
|
||
conv = await self.context.conversation_manager.get_conversation(umo, cid)
|
||
all_conversations = await self.context.conversation_manager.get_conversations(
|
||
umo
|
||
)
|
||
helper_result = await self.context.call_context_function(
|
||
"CompatHelper.shout",
|
||
{"text": "compat"},
|
||
)
|
||
await self.context.conversation_manager.switch_conversation(umo, cid)
|
||
await self.context.conversation_manager.delete_conversation(umo, cid)
|
||
yield event.plain_result(
|
||
f"conversation={current_id}|messages={len((conv or {}).get('content', []))}|"
|
||
f"all={len(all_conversations)}|helper={helper_result['data']}"
|
||
)
|
||
|
||
@filter.command("ai")
|
||
async def test_ai(self, event: AstrMessageEvent):
|
||
"""测试旧版 AI compat 入口。"""
|
||
llm_resp = await self.context.llm_generate(
|
||
chat_provider_id="provider-demo",
|
||
prompt="legacy hello",
|
||
contexts=[{"role": "user", "content": "hi"}],
|
||
)
|
||
agent_resp = await self.context.tool_loop_agent(
|
||
chat_provider_id="provider-demo",
|
||
prompt="legacy hello",
|
||
tools=[{"name": "search"}],
|
||
max_steps=3,
|
||
)
|
||
yield event.plain_result(f"LLM:{llm_resp.text}|AGENT:{agent_resp.text}")
|
||
|
||
@filter.command("sendmsg")
|
||
async def test_send_message(self, event: AstrMessageEvent):
|
||
"""测试 LegacyContext.send_message。"""
|
||
chain = (
|
||
MessageChain()
|
||
.message("compat send ")
|
||
.at("legacy-user", "10001")
|
||
.url_image("https://example.com/send.png")
|
||
)
|
||
await self.context.send_message(event.get_session_id(), chain)
|
||
yield event.plain_result("send_message invoked")
|
||
|
||
@filter.command("components")
|
||
async def test_components(self, event: AstrMessageEvent):
|
||
"""测试消息组件和 chain_result/image_result。"""
|
||
components = [
|
||
Plain(text="Hello"),
|
||
At(qq="123", name="legacy_user"),
|
||
AtAll(),
|
||
Image.fromURL("https://example.com/img.png"),
|
||
Image.fromFileSystem("C:/tmp/local.png"),
|
||
Record.fromFileSystem("C:/tmp/sound.wav"),
|
||
Video.fromURL("https://example.com/video.mp4"),
|
||
File(name="demo.txt", file="https://example.com/demo.txt"),
|
||
Reply(id="reply-1"),
|
||
Node(uin="10001", name="node_user", content=[Plain(text="node")]),
|
||
Face(id=1),
|
||
]
|
||
component_dicts = [component.to_dict() for component in components]
|
||
logger.info("Components: {}", component_dicts)
|
||
yield event.chain_result(components)
|
||
yield event.image_result("https://example.com/components.png")
|
||
|
||
@filter.command("state")
|
||
async def test_event_state(self, event: AstrMessageEvent):
|
||
"""测试事件状态方法。"""
|
||
result = event.make_result().message("state-ok")
|
||
event.set_result(result)
|
||
before_stop = event.is_stopped()
|
||
event.stop_event()
|
||
after_stop = event.is_stopped()
|
||
event.continue_event()
|
||
after_continue = event.is_stopped()
|
||
event.should_call_llm(True)
|
||
stored = event.get_result()
|
||
event.clear_result()
|
||
yield event.plain_result(
|
||
f"before={before_stop}|after_stop={after_stop}|after_continue={after_continue}|"
|
||
f"call_llm={event.call_llm}|stored={stored is not None}"
|
||
)
|
||
|
||
@filter.regex(r"^ping.*")
|
||
async def ping_regex(self, event: AstrMessageEvent):
|
||
"""测试正则匹配。"""
|
||
yield event.plain_result("Pong from regex!")
|
||
|
||
@filter.permission(ADMIN)
|
||
@filter.command("admin")
|
||
async def admin_only(self, event: AstrMessageEvent):
|
||
"""测试 permission(ADMIN)。"""
|
||
yield event.plain_result(f"Admin command executed by {event.is_admin()}")
|
||
|
||
@filter.permission_type(PermissionType.ADMIN)
|
||
@filter.command("admin_type")
|
||
async def admin_type_only(self, event: AstrMessageEvent):
|
||
"""测试 permission_type(PermissionType.ADMIN)。"""
|
||
yield event.plain_result(f"Admin type command executed by {event.is_admin()}")
|
||
|
||
@filter.event_message_type(EventMessageType.GROUP_MESSAGE)
|
||
async def group_only(self, event: AstrMessageEvent):
|
||
"""测试消息类型过滤。"""
|
||
yield event.plain_result("Group message received")
|
||
|
||
@filter.platform_adapter_type(PlatformAdapterType.AIOCQHTTP)
|
||
async def cqhttp_only(self, event: AstrMessageEvent):
|
||
"""测试平台过滤。"""
|
||
yield event.plain_result("CQHttp platform detected")
|
||
|
||
@provide_capability(
|
||
"compat.echo",
|
||
description="使用 legacy Context 回显输入文本",
|
||
input_schema={
|
||
"type": "object",
|
||
"properties": {"text": {"type": "string"}},
|
||
"required": ["text"],
|
||
},
|
||
output_schema={
|
||
"type": "object",
|
||
"properties": {
|
||
"echo": {"type": "string"},
|
||
"plugin_id": {"type": "string"},
|
||
},
|
||
"required": ["echo", "plugin_id"],
|
||
},
|
||
)
|
||
async def echo_capability(self, payload: dict):
|
||
"""测试旧版插件 capability 能走 LegacyContext。"""
|
||
text = str(payload.get("text", ""))
|
||
await self.context.put_kv_data("compat_capability", {"text": text})
|
||
stored = await self.context.get_kv_data("compat_capability", {})
|
||
return {
|
||
"echo": str(stored.get("text", "")),
|
||
"plugin_id": self.context.plugin_id,
|
||
}
|