diff --git a/src/Globals/global_defines_app.php b/src/Globals/global_defines_app.php index 6f70fe2f..475f6482 100644 --- a/src/Globals/global_defines_app.php +++ b/src/Globals/global_defines_app.php @@ -36,6 +36,13 @@ const ZM_REPLY_NONE = 0; // 默认回复,不带任何东西 const ZM_REPLY_MENTION = 1; // 回复时 @ 该用户 const ZM_REPLY_QUOTE = 2; // 回复时引用该消息 +const ZM_PROMPT_NONE = 0; // 使用 prompt() 不附加任何内容 +const ZM_PROMPT_MENTION_USER = 1; // 回复提示语句时 at 该用户 +const ZM_PROMPT_QUOTE_USER = 2; // 回复提示语句时引用该用户的消息 +const ZM_PROMPT_TIMEOUT_MENTION_USER = 4; // 回复超时时 at 该用户 +const ZM_PROMPT_TIMEOUT_QUOTE_SELF = 8; // 回复超时时引用自己回复的提示语句 +const ZM_PROMPT_TIMEOUT_QUOTE_USER = 16; // 回复超时时引用用户的消息 + const LOAD_MODE_VENDOR = 0; // 从 vendor 加载 const LOAD_MODE_SRC = 1; // 从 src 加载 diff --git a/src/ZM/Context/BotContext.php b/src/ZM/Context/BotContext.php index 414d8f4f..ffed750a 100644 --- a/src/ZM/Context/BotContext.php +++ b/src/ZM/Context/BotContext.php @@ -9,6 +9,7 @@ use DI\NotFoundException; use OneBot\Driver\Coroutine\Adaptive; use OneBot\Driver\Event\Http\HttpRequestEvent; use OneBot\Driver\Event\WebSocket\WebSocketMessageEvent; +use OneBot\V12\Object\ActionResponse; use OneBot\V12\Object\MessageSegment; use OneBot\V12\Object\OneBotEvent; use ZM\Context\Trait\BotActionTrait; @@ -85,14 +86,15 @@ class BotContext implements ContextInterface * * @param array|MessageSegment|string|\Stringable $prompt 等待前发送的消息文本 * @param int $timeout 等待超时时间(单位为秒,默认为 600 秒) - * @param string $timeout_prompt 超时后提示的消息内容 + * @param array|MessageSegment|string|\Stringable $timeout_prompt 超时后提示的消息内容 * @param bool $return_string 是否只返回 text 格式的字符串消息(默认为 false) + * @param int $option prompt 功能的选项参数 * @throws DependencyException * @throws NotFoundException * @throws OneBot12Exception * @throws WaitTimeoutException */ - public function prompt(string|\Stringable|MessageSegment|array $prompt = '', int $timeout = 600, string $timeout_prompt = '', bool $return_string = false): array|string + public function prompt(string|\Stringable|MessageSegment|array $prompt = '', int $timeout = 600, string|\Stringable|MessageSegment|array $timeout_prompt = '', bool $return_string = false, int $option = ZM_PROMPT_NONE): array|string { if (!container()->has('bot.event')) { throw new OneBot12Exception('bot()->prompt() can only be used in message event'); @@ -105,7 +107,8 @@ class BotContext implements ContextInterface // 开始等待输入 logger()->debug('Waiting user for prompt...'); if ($prompt !== '') { - $this->reply($prompt); + $prompt = $this->applyPromptMode($option, $prompt, $event); + $reply_resp = $this->reply($prompt); } if (($co = Adaptive::getCoroutine()) === null) { throw new OneBot12Exception('Coroutine is not supported yet, prompt() not works'); @@ -121,7 +124,13 @@ class BotContext implements ContextInterface $result = $co->suspend(); OneBot12Adapter::removeContextPrompt($cid); if ($result === '') { - throw new WaitTimeoutException($this, $timeout_prompt); + throw new WaitTimeoutException( + $this, + $timeout_prompt, + prompt_response: isset($reply_resp) && $reply_resp instanceof ActionResponse ? $reply_resp : null, + user_event: $event, + prompt_option: $option + ); } if ($result instanceof OneBotEvent && $result->type === 'message') { return $return_string ? $result->getMessageString() : $result->getMessage(); @@ -214,4 +223,30 @@ class BotContext implements ContextInterface array_unshift($message_segments, new MessageSegment('mention', ['user_id' => $event->getUserId()])); } } + + /** + * 匹配更改 prompt reply 的特殊格式 + * + * @param int $option prompt 模式 + * @param array|MessageSegment|string|\Stringable $prompt 消息或消息段 + * @param OneBotEvent $event 事件对象 + * @return array 消息段 + */ + private function applyPromptMode(int $option, array|string|\Stringable|MessageSegment $prompt, OneBotEvent $event): array + { + // 先格式化消息 + if ($prompt instanceof MessageSegment) { + $prompt = [$prompt]; + } elseif (is_string($prompt) || $prompt instanceof \Stringable) { + $prompt = [strval($prompt)]; + } + // 然后这里只验证 MENTION 和 QUOTE + if (($option & ZM_PROMPT_MENTION_USER) === ZM_PROMPT_MENTION_USER) { + $prompt = [new MessageSegment('mention', ['user_id' => $event->getUserId()]), ...$prompt]; + } + if (($option & ZM_PROMPT_QUOTE_USER) === ZM_PROMPT_QUOTE_USER) { + $prompt = [new MessageSegment('reply', ['message_id' => $event->getMessageId(), 'user_id' => $event->getUserId()]), ...$prompt]; + } + return $prompt; + } } diff --git a/src/ZM/Exception/WaitTimeoutException.php b/src/ZM/Exception/WaitTimeoutException.php index 2c9e453c..3514a8d4 100644 --- a/src/ZM/Exception/WaitTimeoutException.php +++ b/src/ZM/Exception/WaitTimeoutException.php @@ -4,13 +4,51 @@ declare(strict_types=1); namespace ZM\Exception; +use OneBot\V12\Object\ActionResponse; +use OneBot\V12\Object\MessageSegment; +use OneBot\V12\Object\OneBotEvent; + class WaitTimeoutException extends ZMException { - public $module; + private ?array $timeout_prompt; - public function __construct($module, $message = '', $code = 0, \Throwable $previous = null) + public function __construct( + public mixed $module, + string|\MessageSegment|array|\Stringable $timeout_prompt = '', + private ?ActionResponse $prompt_response = null, + private ?OneBotEvent $user_event = null, + private int $prompt_option = ZM_PROMPT_NONE, + \Throwable $previous = null + ) { + parent::__construct('wait timeout!', 0, $previous); + if ($timeout_prompt === '') { + $this->timeout_prompt = null; + } elseif ($timeout_prompt instanceof MessageSegment) { + $this->timeout_prompt = [$timeout_prompt]; + } elseif (is_string($timeout_prompt) || $timeout_prompt instanceof \Stringable) { + $this->timeout_prompt = [strval($timeout_prompt)]; + } else { + $this->timeout_prompt = $timeout_prompt; + } + } + + public function getTimeoutPrompt(): ?array { - parent::__construct($message, $code, $previous); - $this->module = $module; + return $this->timeout_prompt; + } + + public function getPromptResponse(): ?ActionResponse + { + return $this->prompt_response; + } + + public function getUserEvent(): ?OneBotEvent + { + return $this->user_event; + } + + public function getPromptOption(): int + { + return $this->prompt_option; } } diff --git a/src/ZM/Framework.php b/src/ZM/Framework.php index 1f5ca718..fdae420f 100644 --- a/src/ZM/Framework.php +++ b/src/ZM/Framework.php @@ -46,7 +46,7 @@ class Framework public const VERSION_ID = 669; /** @var string 版本名称 */ - public const VERSION = '3.0.0-beta5'; + public const VERSION = '3.0.0-beta6'; /** @var array 传入的参数 */ protected array $argv; diff --git a/src/ZM/Plugin/OneBot12Adapter.php b/src/ZM/Plugin/OneBot12Adapter.php index 43b8485e..42a450ce 100644 --- a/src/ZM/Plugin/OneBot12Adapter.php +++ b/src/ZM/Plugin/OneBot12Adapter.php @@ -343,7 +343,19 @@ class OneBot12Adapter extends ZMPlugin try { $handler->handleAll($obj); } catch (WaitTimeoutException $e) { - bot()->reply($e->getMessage()); + // 这里是处理 prompt() 下超时的情况的 + if ($e->getTimeoutPrompt() === null) { + return; + } + if (($e->getPromptOption() & ZM_PROMPT_TIMEOUT_MENTION_USER) === ZM_PROMPT_TIMEOUT_MENTION_USER && ($ev = $e->getUserEvent()) !== null) { + $prompt = [MessageSegment::mention($ev->getUserId()), ...$e->getTimeoutPrompt()]; + } + if (($e->getPromptOption() & ZM_PROMPT_TIMEOUT_QUOTE_SELF) === ZM_PROMPT_TIMEOUT_QUOTE_SELF && ($rsp = $e->getPromptResponse()) !== null && ($ev = $e->getUserEvent()) !== null) { + $prompt = [MessageSegment::reply($rsp->data['message_id'], $ev->self['user_id']), ...$e->getTimeoutPrompt()]; + } elseif (($e->getPromptOption() & ZM_PROMPT_TIMEOUT_QUOTE_USER) === ZM_PROMPT_TIMEOUT_QUOTE_USER && ($ev = $e->getUserEvent()) !== null) { + $prompt = [MessageSegment::reply($ev->getMessageId(), $ev->getUserId()), ...$e->getTimeoutPrompt()]; + } + bot()->reply($prompt ?? $e->getTimeoutPrompt()); } } elseif (isset($body['status'], $body['retcode'])) { // 如果含有 status,retcode 字段,表明是 action 的 response