diff --git a/src/ZM/Event/Listener/WorkerEventListener.php b/src/ZM/Event/Listener/WorkerEventListener.php index 42b0c809..6dc8d705 100644 --- a/src/ZM/Event/Listener/WorkerEventListener.php +++ b/src/ZM/Event/Listener/WorkerEventListener.php @@ -13,7 +13,7 @@ use ZM\Annotation\AnnotationParser; use ZM\Annotation\Framework\Init; use ZM\Exception\ZMKnownException; use ZM\Framework; -use ZM\Plugin\CommandManualPlugin; +use ZM\Plugin\CommandManual\CommandManualPlugin; use ZM\Plugin\OneBot12Adapter; use ZM\Plugin\PluginManager; use ZM\Process\ProcessStateManager; diff --git a/src/ZM/Plugin/CommandManual/CommandManualPlugin.php b/src/ZM/Plugin/CommandManual/CommandManualPlugin.php new file mode 100644 index 00000000..6e7a89c5 --- /dev/null +++ b/src/ZM/Plugin/CommandManual/CommandManualPlugin.php @@ -0,0 +1,128 @@ + 'command', 'header' => false, 'indent' => false], + ['type' => 'description', 'header' => false, 'indent' => false], + ['type' => 'usage', 'header' => false, 'indent' => false], + ['type' => 'arguments', 'header' => '可用参数:', 'indent' => true], + ['type' => 'examples', 'header' => '使用示例:', 'indent' => true], + ]; + + /** + * 命令手册工厂,键为优先级,值为工厂 + * + * @var array + */ + private static array $manual_factories = [ + 10 => StaticManualFactory::class, + ]; + + /** + * 命令列表,键为命令名,值为命令实例 + * + * @var array + */ + private array $commands = []; + + /** + * 命令邻近注解,键为命令名,值为邻近注解数组 + * + * @var array + */ + private array $adjacent_annotations = []; + + public function __construct(AnnotationParser $parser) + { + parent::__construct(__DIR__); + + if (config('command_manual.template') !== null) { + $this->template = config('command_manual.template'); + } + + $parser->addSpecialParser(BotCommand::class, [$this, 'parseBotCommand']); + $parser->addSpecialParser(CommandHelp::class, fn () => true); + + $this->addBotCommand( + BotCommand::make('help', 'help', level: 10) + ->withArgument('command', '要查询的指令名', required: true) + ->on([$this, 'onHelp']) + ); + } + + /** + * 添加命令手册工厂 + * + * @param array|callable|string $factory 工厂 + * @param int $priority 优先级 + */ + public static function addManualFactory(array|callable|string $factory, int $priority = 20): void + { + self::$manual_factories[$priority] = $factory; + logger()->debug('命令手册工厂已添加 {factory} 优先级 {priority}', compact('factory', 'priority')); + } + + /** + * 解析 BotCommand 的参数和帮助 + * + * @param BotCommand $command 命令对象 + * @param null|array $adjacent_annotations 同一个方法的所有注解 + */ + public function parseBotCommand(BotCommand $command, ?array $adjacent_annotations = null): ?bool + { + $this->commands[$command->name] = $command; + $this->adjacent_annotations[$command->name] = $adjacent_annotations ?? []; + return null; + } + + /** + * 命令手册获取命令 + * + * @param BotContext $context 上下文 + */ + public function onHelp(BotContext $context): void + { + $command_name = $context->getParam('command'); + $command = $this->commands[$command_name] ?? null; + if ($command === null) { + $context->reply('命令不存在'); + return; + } + $adjacent_annotations = $this->adjacent_annotations[$command_name] ?? []; + + // 遍历工厂,直到找到一个返回非空的工厂 + foreach (array_reverse(self::$manual_factories) as $factory) { + $manual = container()->call( + $factory, + [ + 'context' => $context, + 'command' => $command, + 'template' => $this->template, + 'adjacent_annotations' => $adjacent_annotations, + ] + ); + if ($manual !== null) { + $context->reply($manual); + return; + } + } + $context->reply("未找到指令 {$command} 的帮助"); + } +} diff --git a/src/ZM/Plugin/CommandManual/StaticManualFactory.php b/src/ZM/Plugin/CommandManual/StaticManualFactory.php new file mode 100644 index 00000000..7db0f5fd --- /dev/null +++ b/src/ZM/Plugin/CommandManual/StaticManualFactory.php @@ -0,0 +1,74 @@ +getSectionContent($command, $v['type'], $help); + $this->addSection($section, $content, $v); + } + return $section; + } + + protected function getSectionContent(BotCommand $command, string $type, CommandHelp $help): string + { + switch ($type) { + case 'command': + return $command->name; + case 'description': + return $help->description; + case 'usage': + return $help->usage; + case 'arguments': + $ret = ''; + foreach ($command->getArguments() as $argument) { + /* @var CommandArgument $argument */ + $ret .= $argument->name . ' - ' . $argument->description . PHP_EOL; + } + return $ret; + case 'examples': + return $help->example; + default: + return ''; + } + } + + protected function addSection(string &$section, string $content, array $options): void + { + if (!$content) { + return; + } + if ($options['header']) { + $section .= $options['header'] . PHP_EOL; + } + if ($options['indent']) { + $content = ' ' . str_replace(PHP_EOL, PHP_EOL . ' ', $content); + $content = rtrim($content); + } + $section .= $content . PHP_EOL; + } +} diff --git a/src/ZM/Plugin/CommandManualPlugin.php b/src/ZM/Plugin/CommandManualPlugin.php deleted file mode 100644 index 5646ad20..00000000 --- a/src/ZM/Plugin/CommandManualPlugin.php +++ /dev/null @@ -1,127 +0,0 @@ - 'command', 'header' => false, 'indent' => false], - ['type' => 'description', 'header' => false, 'indent' => false], - ['type' => 'usage', 'header' => false, 'indent' => false], - ['type' => 'arguments', 'header' => '可用参数:', 'indent' => true], - ['type' => 'examples', 'header' => '使用示例:', 'indent' => true], - ]; - - /** - * 命令(帮助)列表,键为命令名,值为命令帮助 - * - * @var array - */ - private array $command_list = []; - - public function __construct(AnnotationParser $parser) - { - parent::__construct(__DIR__); - - if (config('command_manual.template') !== null) { - $this->template = config('command_manual.template'); - } - - $parser->addSpecialParser(BotCommand::class, [$this, 'parseBotCommand']); - $parser->addSpecialParser(CommandHelp::class, fn () => false); - - $this->addBotCommand( - BotCommand::make('help', 'help') - ->withArgument('command', '要查询的指令名', required: true) - ->on([$this, 'onHelp']) - ); - logger()->info('CommandManualPlugin loaded.'); - } - - /** - * 解析 BotCommand 的参数和帮助 - * - * @param BotCommand $command 命令对象 - * @param null|array $same_method_annotations 同一个方法的所有注解 - */ - public function parseBotCommand(BotCommand $command, ?array $same_method_annotations = null): ?bool - { - if ($same_method_annotations) { - foreach ($same_method_annotations as $v) { - if ($v instanceof CommandHelp) { - $help = $v; - break; - } - } - } - $help = $help ?? new CommandHelp('', '', ''); - $section = ''; - foreach ($this->template as $v) { - $content = $this->getSectionContent($command, $v['type'], $help); - $this->addSection($section, $content, $v); - } - $this->command_list[$command->name] = $section; - return true; - } - - public function onHelp(BotContext $context): void - { - $command = $context->getParam('command'); - if (isset($this->command_list[$command])) { - $context->reply($this->command_list[$command]); - } else { - $context->reply('未找到指令 ' . $command); - } - } - - private function addSection(string &$section, string $content, array $options): void - { - if (!$content) { - return; - } - if ($options['header']) { - $section .= $options['header'] . PHP_EOL; - } - if ($options['indent']) { - $content = ' ' . str_replace(PHP_EOL, PHP_EOL . ' ', $content); - $content = rtrim($content); - } - $section .= $content . PHP_EOL; - } - - private function getSectionContent(BotCommand $command, string $type, CommandHelp $help): string - { - switch ($type) { - case 'command': - return $command->name; - case 'description': - return $help->description; - case 'usage': - return $help->usage; - case 'arguments': - $ret = ''; - foreach ($command->getArguments() as $argument) { - /* @var CommandArgument $argument */ - $ret .= $argument->name . ' - ' . $argument->description . PHP_EOL; - } - return $ret; - case 'examples': - return $help->example; - default: - return ''; - } - } -}