add command manual plugin (#210)

This commit is contained in:
sunxyw 2022-12-28 22:05:03 +08:00 committed by GitHub
parent 383e0e22af
commit 0d24ae6192
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 179 additions and 0 deletions

View File

@ -73,6 +73,7 @@ $config['plugin'] = [
$config['native_plugin'] = [
'onebot12' => true, // OneBot v12 协议支持
'onebot12-ban-other-ws' => true, // OneBot v12 协议支持,禁止其他 WebSocket 连接
'command-manual' => true,
];
/* 静态文件读取器 */

View File

@ -7,7 +7,11 @@ namespace Module\Example;
use OneBot\Driver\Event\WebSocket\WebSocketMessageEvent;
use ZM\Annotation\Http\Route;
use ZM\Annotation\Middleware\Middleware;
use ZM\Annotation\OneBot\BotCommand;
use ZM\Annotation\OneBot\BotEvent;
use ZM\Annotation\OneBot\CommandArgument;
use ZM\Annotation\OneBot\CommandHelp;
use ZM\Context\BotContext;
use ZM\Middleware\TimerMiddleware;
class Hello123
@ -24,4 +28,12 @@ class Hello123
{
logger()->info("收到了 {$event->getType()}.{$event->getDetailType()} 事件");
}
#[BotCommand('echo', 'echo')]
#[CommandArgument('text', '要回复的内容', required: true)]
#[CommandHelp('复读机', '只需要发送 echo+内容 即可自动复读', 'echo 你好 会回复 你好')]
public function repeat(\OneBotEvent $event, BotContext $context): void
{
$context->reply($event->getMessage());
}
}

View File

@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace ZM\Annotation\OneBot;
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
use Doctrine\Common\Annotations\Annotation\Target;
use ZM\Annotation\AnnotationBase;
/**
* 机器人指令帮助注解
*
* @Annotation
* @NamedArgumentConstructor()
* @Target("METHOD")
*/
#[\Attribute(\Attribute::TARGET_METHOD)]
class CommandHelp extends AnnotationBase
{
public function __construct(
public string $description,
public string $usage,
public string $example,
) {
}
public static function make(
string $description,
string $usage,
string $example,
): CommandHelp {
return new static(...func_get_args());
}
}

View File

@ -12,6 +12,7 @@ use ZM\Annotation\AnnotationParser;
use ZM\Annotation\Framework\Init;
use ZM\Exception\ZMKnownException;
use ZM\Framework;
use ZM\Plugin\CommandManualPlugin;
use ZM\Plugin\OneBot12Adapter;
use ZM\Plugin\PluginManager;
use ZM\Process\ProcessStateManager;
@ -141,6 +142,7 @@ class WorkerEventListener
match ($name) {
'onebot12' => PluginManager::addPlugin(['name' => $name, 'internal' => true, 'object' => new OneBot12Adapter(parser: $parser)]),
'onebot12-ban-other-ws' => PluginManager::addPlugin(['name' => $name, 'internal' => true, 'object' => new OneBot12Adapter(submodule: $name)]),
'command-manual' => PluginManager::addPlugin(['name' => $name, 'internal' => true, 'object' => new CommandManualPlugin($parser)]),
};
}

View File

@ -0,0 +1,127 @@
<?php
declare(strict_types=1);
namespace ZM\Plugin;
use ZM\Annotation\AnnotationParser;
use ZM\Annotation\OneBot\BotCommand;
use ZM\Annotation\OneBot\CommandArgument;
use ZM\Annotation\OneBot\CommandHelp;
use ZM\Context\BotContext;
/**
* CommandManual 插件
*
* 用以生成、处理指令帮助
*/
class CommandManualPlugin extends ZMPlugin
{
private array $template = [
['type' => '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<string, string>
*/
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 '';
}
}
}

View File

@ -7,6 +7,7 @@ namespace ZM\Plugin;
use ZM\Annotation\AnnotationMap;
use ZM\Annotation\AnnotationParser;
use ZM\Annotation\Framework\BindEvent;
use ZM\Annotation\OneBot\BotCommand;
use ZM\Annotation\OneBot\BotEvent;
use ZM\Exception\PluginException;
use ZM\Store\FileSystem;
@ -191,6 +192,7 @@ class PluginManager
}
// 将 BotCommand 加入事件监听
foreach ($obj->getBotCommands() as $cmd) {
AnnotationMap::$_list[BotCommand::class][] = $cmd;
$parser->parseSpecial($cmd);
}
} elseif (isset($plugin['autoload'], $plugin['dir'])) {