diff --git a/nonebot/consts.py b/nonebot/consts.py
index 701307d3f18a..0cf4056fe06f 100644
--- a/nonebot/consts.py
+++ b/nonebot/consts.py
@@ -22,6 +22,10 @@
"""当前 `reject` 目标存储 key"""
REJECT_CACHE_TARGET: Literal["_next_target"] = "_next_target"
"""下一个 `reject` 目标存储 key"""
+PAUSE_PROMPT_RESULT_KEY: Literal["_pause_result"] = "_pause_result"
+"""`pause` prompt 发送结果存储 key"""
+REJECT_PROMPT_RESULT_KEY: Literal["_reject_{key}_result"] = "_reject_{key}_result"
+"""`reject` prompt 发送结果存储 key"""
# used by Rule
PREFIX_KEY: Literal["_prefix"] = "_prefix"
diff --git a/nonebot/internal/matcher/matcher.py b/nonebot/internal/matcher/matcher.py
index 7f18effa5ce2..164b7b89edcc 100644
--- a/nonebot/internal/matcher/matcher.py
+++ b/nonebot/internal/matcher/matcher.py
@@ -27,8 +27,10 @@
from nonebot.consts import (
ARG_KEY,
LAST_RECEIVE_KEY,
+ PAUSE_PROMPT_RESULT_KEY,
RECEIVE_KEY,
REJECT_CACHE_TARGET,
+ REJECT_PROMPT_RESULT_KEY,
REJECT_TARGET,
)
from nonebot.dependencies import Dependent, Param
@@ -560,8 +562,8 @@ async def send(
"""
bot = current_bot.get()
event = current_event.get()
- state = current_matcher.get().state
if isinstance(message, MessageTemplate):
+ state = current_matcher.get().state
_message = message.format(**state)
else:
_message = message
@@ -597,8 +599,15 @@ async def pause(
kwargs: {ref}`nonebot.adapters.Bot.send` 的参数,
请参考对应 adapter 的 bot 对象 api
"""
+ try:
+ matcher = current_matcher.get()
+ except Exception:
+ matcher = None
+
if prompt is not None:
- await cls.send(prompt, **kwargs)
+ result = await cls.send(prompt, **kwargs)
+ if matcher is not None:
+ matcher.state[PAUSE_PROMPT_RESULT_KEY] = result
raise PausedException
@classmethod
@@ -615,8 +624,19 @@ async def reject(
kwargs: {ref}`nonebot.adapters.Bot.send` 的参数,
请参考对应 adapter 的 bot 对象 api
"""
+ try:
+ matcher = current_matcher.get()
+ key = matcher.get_target()
+ except Exception:
+ matcher = None
+ key = None
+
+ key = REJECT_PROMPT_RESULT_KEY.format(key=key) if key is not None else None
+
if prompt is not None:
- await cls.send(prompt, **kwargs)
+ result = await cls.send(prompt, **kwargs)
+ if key is not None and matcher:
+ matcher.state[key] = result
raise RejectedException
@classmethod
@@ -636,9 +656,12 @@ async def reject_arg(
请参考对应 adapter 的 bot 对象 api
"""
matcher = current_matcher.get()
- matcher.set_target(ARG_KEY.format(key=key))
+ arg_key = ARG_KEY.format(key=key)
+ matcher.set_target(arg_key)
+
if prompt is not None:
- await cls.send(prompt, **kwargs)
+ result = await cls.send(prompt, **kwargs)
+ matcher.state[REJECT_PROMPT_RESULT_KEY.format(key=arg_key)] = result
raise RejectedException
@classmethod
@@ -658,9 +681,12 @@ async def reject_receive(
请参考对应 adapter 的 bot 对象 api
"""
matcher = current_matcher.get()
- matcher.set_target(RECEIVE_KEY.format(id=id))
+ receive_key = RECEIVE_KEY.format(id=id)
+ matcher.set_target(receive_key)
+
if prompt is not None:
- await cls.send(prompt, **kwargs)
+ result = await cls.send(prompt, **kwargs)
+ matcher.state[REJECT_PROMPT_RESULT_KEY.format(key=receive_key)] = result
raise RejectedException
@classmethod
diff --git a/nonebot/internal/params.py b/nonebot/internal/params.py
index 89d11990aa21..86f776d63ee2 100644
--- a/nonebot/internal/params.py
+++ b/nonebot/internal/params.py
@@ -18,6 +18,7 @@
from pydantic.fields import FieldInfo as PydanticFieldInfo
from nonebot.compat import FieldInfo, ModelField, PydanticUndefined, extract_field_info
+from nonebot.consts import ARG_KEY, REJECT_PROMPT_RESULT_KEY
from nonebot.dependencies import Dependent, Param
from nonebot.dependencies.utils import check_field_type
from nonebot.exception import SkippedException
@@ -39,7 +40,7 @@
)
if TYPE_CHECKING:
- from nonebot.adapters import Bot, Event
+ from nonebot.adapters import Bot, Event, Message
from nonebot.matcher import Matcher
@@ -522,10 +523,10 @@ async def _check( # pyright: ignore[reportIncompatibleMethodOverride]
class ArgInner:
def __init__(
- self, key: Optional[str], type: Literal["message", "str", "plaintext"]
+ self, key: Optional[str], type: Literal["message", "str", "plaintext", "prompt"]
) -> None:
self.key: Optional[str] = key
- self.type: Literal["message", "str", "plaintext"] = type
+ self.type: Literal["message", "str", "plaintext", "prompt"] = type
def __repr__(self) -> str:
return f"ArgInner(key={self.key!r}, type={self.type!r})"
@@ -546,6 +547,11 @@ def ArgPlainText(key: Optional[str] = None) -> str:
return ArgInner(key, "plaintext") # type: ignore
+def ArgPromptResult(key: Optional[str] = None) -> Any:
+ """`arg` prompt 发送结果"""
+ return ArgInner(key, "prompt")
+
+
class ArgParam(Param):
"""Arg 注入参数
@@ -559,7 +565,7 @@ def __init__(
self,
*args,
key: str,
- type: Literal["message", "str", "plaintext"],
+ type: Literal["message", "str", "plaintext", "prompt"],
**kwargs: Any,
) -> None:
super().__init__(*args, **kwargs)
@@ -584,15 +590,32 @@ def _check_param(
async def _solve( # pyright: ignore[reportIncompatibleMethodOverride]
self, matcher: "Matcher", **kwargs: Any
) -> Any:
- message = matcher.get_arg(self.key)
- if message is None:
- return message
if self.type == "message":
- return message
+ return self._solve_message(matcher)
elif self.type == "str":
- return str(message)
+ return self._solve_str(matcher)
+ elif self.type == "plaintext":
+ return self._solve_plaintext(matcher)
+ elif self.type == "prompt":
+ return self._solve_prompt(matcher)
else:
- return message.extract_plain_text()
+ raise ValueError(f"Unknown Arg type: {self.type}")
+
+ def _solve_message(self, matcher: "Matcher") -> Optional["Message"]:
+ return matcher.get_arg(self.key)
+
+ def _solve_str(self, matcher: "Matcher") -> Optional[str]:
+ message = matcher.get_arg(self.key)
+ return str(message) if message is not None else None
+
+ def _solve_plaintext(self, matcher: "Matcher") -> Optional[str]:
+ message = matcher.get_arg(self.key)
+ return message.extract_plain_text() if message is not None else None
+
+ def _solve_prompt(self, matcher: "Matcher") -> Optional[Any]:
+ return matcher.state.get(
+ REJECT_PROMPT_RESULT_KEY.format(key=ARG_KEY.format(key=self.key))
+ )
class ExceptionParam(Param):
diff --git a/nonebot/params.py b/nonebot/params.py
index a4400e7d5132..c25010511178 100644
--- a/nonebot/params.py
+++ b/nonebot/params.py
@@ -19,9 +19,12 @@
ENDSWITH_KEY,
FULLMATCH_KEY,
KEYWORD_KEY,
+ PAUSE_PROMPT_RESULT_KEY,
PREFIX_KEY,
RAW_CMD_KEY,
+ RECEIVE_KEY,
REGEX_MATCHED,
+ REJECT_PROMPT_RESULT_KEY,
SHELL_ARGS,
SHELL_ARGV,
STARTSWITH_KEY,
@@ -29,6 +32,7 @@
from nonebot.internal.params import Arg as Arg
from nonebot.internal.params import ArgParam as ArgParam
from nonebot.internal.params import ArgPlainText as ArgPlainText
+from nonebot.internal.params import ArgPromptResult as ArgPromptResult
from nonebot.internal.params import ArgStr as ArgStr
from nonebot.internal.params import BotParam as BotParam
from nonebot.internal.params import DefaultParam as DefaultParam
@@ -252,6 +256,26 @@ def _last_received(matcher: "Matcher") -> Any:
return Depends(_last_received, use_cache=False)
+def ReceivePromptResult(id: Optional[str] = None) -> Any:
+ """`receive` prompt 发送结果"""
+
+ def _receive_prompt_result(matcher: "Matcher") -> Any:
+ return matcher.state.get(
+ REJECT_PROMPT_RESULT_KEY.format(key=RECEIVE_KEY.format(id=id))
+ )
+
+ return Depends(_receive_prompt_result, use_cache=False)
+
+
+def PausePromptResult() -> Any:
+ """`pause` prompt 发送结果"""
+
+ def _pause_prompt_result(matcher: "Matcher") -> Any:
+ return matcher.state.get(PAUSE_PROMPT_RESULT_KEY)
+
+ return Depends(_pause_prompt_result, use_cache=False)
+
+
__autodoc__ = {
"Arg": True,
"ArgStr": True,
@@ -265,4 +289,5 @@ def _last_received(matcher: "Matcher") -> Any:
"DefaultParam": True,
"MatcherParam": True,
"ExceptionParam": True,
+ "ArgPromptResult": True,
}
diff --git a/tests/plugins/param/param_arg.py b/tests/plugins/param/param_arg.py
index 6bf64ded3742..c807228cf789 100644
--- a/tests/plugins/param/param_arg.py
+++ b/tests/plugins/param/param_arg.py
@@ -1,7 +1,7 @@
-from typing import Annotated
+from typing import Annotated, Any
from nonebot.adapters import Message
-from nonebot.params import Arg, ArgPlainText, ArgStr
+from nonebot.params import Arg, ArgPlainText, ArgPromptResult, ArgStr
async def arg(key: Message = Arg()) -> Message:
@@ -28,6 +28,10 @@ async def annotated_arg_plain_text(key: Annotated[str, ArgPlainText()]) -> str:
return key
+async def annotated_arg_prompt_result(key: Annotated[Any, ArgPromptResult()]) -> Any:
+ return key
+
+
# test dependency priority
async def annotated_prior_arg(key: Annotated[str, ArgStr("foo")] = ArgPlainText()):
return key
diff --git a/tests/plugins/param/param_matcher.py b/tests/plugins/param/param_matcher.py
index 6e8ec2fcab5e..dd90b1f68f54 100644
--- a/tests/plugins/param/param_matcher.py
+++ b/tests/plugins/param/param_matcher.py
@@ -1,8 +1,13 @@
-from typing import TypeVar, Union
+from typing import Any, TypeVar, Union
from nonebot.adapters import Event
from nonebot.matcher import Matcher
-from nonebot.params import LastReceived, Received
+from nonebot.params import (
+ LastReceived,
+ PausePromptResult,
+ Received,
+ ReceivePromptResult,
+)
async def matcher(m: Matcher) -> Matcher:
@@ -59,3 +64,11 @@ async def receive(e: Event = Received("test")) -> Event:
async def last_receive(e: Event = LastReceived()) -> Event:
return e
+
+
+async def receive_prompt_result(result: Any = ReceivePromptResult("test")) -> Any:
+ return result
+
+
+async def pause_prompt_result(result: Any = PausePromptResult()) -> Any:
+ return result
diff --git a/tests/test_param.py b/tests/test_param.py
index c2001d56a254..bf561c9ecb87 100644
--- a/tests/test_param.py
+++ b/tests/test_param.py
@@ -1,3 +1,4 @@
+from contextlib import suppress
import re
from exceptiongroup import BaseExceptionGroup
@@ -5,6 +6,7 @@
import pytest
from nonebot.consts import (
+ ARG_KEY,
CMD_ARG_KEY,
CMD_KEY,
CMD_START_KEY,
@@ -14,13 +16,14 @@
KEYWORD_KEY,
PREFIX_KEY,
RAW_CMD_KEY,
+ RECEIVE_KEY,
REGEX_MATCHED,
SHELL_ARGS,
SHELL_ARGV,
STARTSWITH_KEY,
)
from nonebot.dependencies import Dependent
-from nonebot.exception import TypeMisMatch
+from nonebot.exception import PausedException, RejectedException, TypeMisMatch
from nonebot.matcher import Matcher
from nonebot.params import (
ArgParam,
@@ -469,8 +472,10 @@ async def test_matcher(app: App):
matcher,
not_legacy_matcher,
not_matcher,
+ pause_prompt_result,
postpone_matcher,
receive,
+ receive_prompt_result,
sub_matcher,
union_matcher,
)
@@ -538,12 +543,42 @@ async def test_matcher(app: App):
ctx.pass_params(matcher=fake_matcher)
ctx.should_return(event_next)
+ fake_matcher.set_target(RECEIVE_KEY.format(id="test"), cache=False)
+
+ async with app.test_api() as ctx:
+ bot = ctx.create_bot()
+ ctx.should_call_send(event, "test", result=True, bot=bot)
+ with fake_matcher.ensure_context(bot, event):
+ with suppress(RejectedException):
+ await fake_matcher.reject("test")
+
+ async with app.test_dependent(
+ receive_prompt_result, allow_types=[MatcherParam, DependParam]
+ ) as ctx:
+ ctx.pass_params(matcher=fake_matcher)
+ ctx.should_return(True)
+
+ async with app.test_api() as ctx:
+ bot = ctx.create_bot()
+ ctx.should_call_send(event, "test", result=False, bot=bot)
+ with fake_matcher.ensure_context(bot, event):
+ fake_matcher.set_target("test")
+ with suppress(PausedException):
+ await fake_matcher.pause("test")
+
+ async with app.test_dependent(
+ pause_prompt_result, allow_types=[MatcherParam, DependParam]
+ ) as ctx:
+ ctx.pass_params(matcher=fake_matcher)
+ ctx.should_return(False)
+
@pytest.mark.anyio
async def test_arg(app: App):
from plugins.param.param_arg import (
annotated_arg,
annotated_arg_plain_text,
+ annotated_arg_prompt_result,
annotated_arg_str,
annotated_multi_arg,
annotated_prior_arg,
@@ -553,6 +588,7 @@ async def test_arg(app: App):
)
matcher = Matcher()
+ event = make_fake_event()()
message = FakeMessage("text")
matcher.set_arg("key", message)
@@ -582,6 +618,21 @@ async def test_arg(app: App):
ctx.pass_params(matcher=matcher)
ctx.should_return(message.extract_plain_text())
+ matcher.set_target(ARG_KEY.format(key="key"), cache=False)
+
+ async with app.test_api() as ctx:
+ bot = ctx.create_bot()
+ ctx.should_call_send(event, "test", result="arg", bot=bot)
+ with matcher.ensure_context(bot, event):
+ with suppress(RejectedException):
+ await matcher.reject("test")
+
+ async with app.test_dependent(
+ annotated_arg_prompt_result, allow_types=[ArgParam]
+ ) as ctx:
+ ctx.pass_params(matcher=matcher)
+ ctx.should_return("arg")
+
async with app.test_dependent(annotated_multi_arg, allow_types=[ArgParam]) as ctx:
ctx.pass_params(matcher=matcher)
ctx.should_return(message.extract_plain_text())
diff --git a/website/docs/advanced/dependency.mdx b/website/docs/advanced/dependency.mdx
index 3efc35cd2263..e7946b39c89c 100644
--- a/website/docs/advanced/dependency.mdx
+++ b/website/docs/advanced/dependency.mdx
@@ -1224,6 +1224,37 @@ async def _(foo: Event = LastReceived()): ...
+### ReceivePromptResult
+
+获取某次 `receive` 发送提示消息的结果。
+
+
+
+
+```python {6}
+from typing import Any, Annotated
+
+from nonebot.params import ReceivePromptResult
+
+@matcher.receive("id", prompt="prompt")
+async def _(result: Annotated[Any, ReceivePromptResult("id")]): ...
+```
+
+
+
+
+```python {6}
+from typing import Any
+
+from nonebot.params import ReceivePromptResult
+
+@matcher.receive("id", prompt="prompt")
+async def _(result: Any = ReceivePromptResult("id")): ...
+```
+
+
+
+
### Arg
获取某次 `got` 接收的参数。如果 `Arg` 参数留空,则使用函数的参数名作为要获取的参数。
@@ -1318,3 +1349,75 @@ async def _(foo: str = ArgPlainText("key")): ...
+
+### ArgPromptResult
+
+获取某次 `got` 发送提示消息的结果。如果 `Arg` 参数留空,则使用函数的参数名作为要获取的参数。
+
+
+
+
+```python {6,7}
+from typing import Any, Annotated
+
+from nonebot.params import ArgPromptResult
+
+@matcher.got("key", prompt="prompt")
+async def _(result: Annotated[Any, ArgPromptResult()]): ...
+async def _(result: Annotated[Any, ArgPromptResult("key")]): ...
+```
+
+
+
+
+```python {6,7}
+from typing import Any
+
+from nonebot.params import ArgPromptResult
+
+@matcher.got("key", prompt="prompt")
+async def _(result: Any = ArgPromptResult()): ...
+async def _(result: Any = ArgPromptResult("key")): ...
+```
+
+
+
+
+### PausePromptResult
+
+获取最近一次 `pause` 发送提示消息的结果。
+
+
+
+
+```python {6}
+from typing import Any, Annotated
+
+from nonebot.params import PausePromptResult
+
+@matcher.handle()
+async def _():
+ await matcher.pause(prompt="prompt")
+
+@matcher.handle()
+async def _(result: Annotated[Any, PausePromptResult()]): ...
+```
+
+
+
+
+```python {6}
+from typing import Any
+
+from nonebot.params import PausePromptResult
+
+@matcher.handle()
+async def _():
+ await matcher.pause(prompt="prompt")
+
+@matcher.handle()
+async def _(result: Any = PausePromptResult()): ...
+```
+
+
+