mirror of
https://github.com/zhamao-robot/zhamao-framework.git
synced 2026-07-22 16:15:34 +08:00
refactor bot action sender, add BotMap to mark bot
This commit is contained in:
133
src/ZM/Plugin/OneBot/BotMap.php
Normal file
133
src/ZM/Plugin/OneBot/BotMap.php
Normal file
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZM\Plugin\OneBot;
|
||||
|
||||
use OneBot\V12\Object\OneBotEvent;
|
||||
use ZM\Context\BotConnectContext;
|
||||
use ZM\Context\BotContext;
|
||||
use ZM\Exception\OneBot12Exception;
|
||||
|
||||
/**
|
||||
* 用于记录多个机器人对应的 fd、flag、状态等的全局关系表(基于反向 WS 类型连接才可用)
|
||||
*/
|
||||
class BotMap
|
||||
{
|
||||
/**
|
||||
* @internal 仅允许框架内部使用
|
||||
* @var array 存储动作 echo 的协程 ID 对应表
|
||||
*/
|
||||
public static array $bot_coroutines = [];
|
||||
|
||||
/**
|
||||
* @var array<string, array<string, bool>> 机器人上下文对象列表
|
||||
*/
|
||||
private static array $bot_status = [];
|
||||
|
||||
/**
|
||||
* @var array<string, array<string, BotContext>> 机器人上下文缓存对象,避免重复创建
|
||||
*/
|
||||
private static array $bot_ctx_cache = [];
|
||||
|
||||
/**
|
||||
* 机器人对应连接 fd
|
||||
* 例如:{ "qq": { "123456": [1,2] } }
|
||||
*
|
||||
* @var array<string, array<string, array>> 机器人对应连接 fd
|
||||
*/
|
||||
private static array $bot_fds = [];
|
||||
|
||||
public static function getConnectContext(int $flag, int $fd): BotConnectContext
|
||||
{
|
||||
return new BotConnectContext($flag, $fd);
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册机器人
|
||||
*
|
||||
* @param int|string $bot_id 机器人 ID
|
||||
* @param string $platform 机器人平台
|
||||
* @param bool $status 机器人状态
|
||||
* @param int $fd 绑定的反向 ws 连接的客户端对应 fd
|
||||
* @param int $flag fd 所在 server 监听端口
|
||||
*/
|
||||
public static function registerBotWithFd(string|int $bot_id, string $platform, bool $status, int $fd, int $flag): bool
|
||||
{
|
||||
logger()->debug('正在注册机器人:' . "{$platform}:{$bot_id}, fd:{$fd}, flag:{$flag}");
|
||||
self::$bot_fds[$platform][strval($bot_id)] = [$flag, $fd];
|
||||
self::$bot_status[$platform][strval($bot_id)] = $status;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有机器人对应的 fd
|
||||
*
|
||||
* @return array<string, array<string, array>>
|
||||
*/
|
||||
public static function getBotFds(): array
|
||||
{
|
||||
return self::$bot_fds;
|
||||
}
|
||||
|
||||
public static function getBotFd(string|int $bot_id, string $platform): ?array
|
||||
{
|
||||
return self::$bot_fds[$platform][$bot_id] ?? null;
|
||||
}
|
||||
|
||||
public static function unregisterBot(string|int $bot_id, string $platform): void
|
||||
{
|
||||
logger()->debug('取消注册 bot: ' . $bot_id);
|
||||
unset(self::$bot_fds[$platform][$bot_id], self::$bot_status[$platform][$bot_id], self::$bot_ctx_cache[$platform][$bot_id]);
|
||||
}
|
||||
|
||||
public static function unregisterBotByFd(int $flag, int $fd): void
|
||||
{
|
||||
$unreg_list = [];
|
||||
foreach (self::$bot_fds as $platform => $bots) {
|
||||
foreach ($bots as $bot_id => $bot_fd) {
|
||||
if ($bot_fd[0] === $flag && $bot_fd[1] = $fd) {
|
||||
$unreg_list[] = [$platform, $bot_id];
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach ($unreg_list as $item) {
|
||||
self::unregisterBot($item[1], $item[0]);
|
||||
}
|
||||
}
|
||||
|
||||
public static function getBotContext(string|int $bot_id = '', string $platform = ''): BotContext
|
||||
{
|
||||
if (isset(self::$bot_ctx_cache[$platform][$bot_id])) {
|
||||
return self::$bot_ctx_cache[$platform][$bot_id];
|
||||
}
|
||||
// 如果传入的是空,说明需要通过 cid 来获取事件绑定的机器人,并且机器人没有
|
||||
if ($bot_id === '' && $platform === '') {
|
||||
if (!container()->has(OneBotEvent::class)) {
|
||||
throw new OneBot12Exception('无法在不指定机器人平台、机器人 ID 的情况下在非机器人事件回调内获取机器人上下文');
|
||||
}
|
||||
$event = container()->get(OneBotEvent::class);
|
||||
if (($event->self['platform'] ?? null) === null) {
|
||||
throw new OneBot12Exception('无法在不包含机器人 ID 的事件回调内获取机器人上下文');
|
||||
}
|
||||
// 有,那就通过事件本身的 self 字段来获取一下
|
||||
$self = $event->self;
|
||||
return self::$bot_ctx_cache[$self['platform']][$self['user_id']] = new BotContext($self['user_id'], $self['platform']);
|
||||
}
|
||||
// 传入的 platform 为空,但 ID 不为空,那么就模糊搜索一个平台的 ID 下的机器人 ID 返回
|
||||
if ($platform === '') {
|
||||
foreach (self::$bot_fds as $platform => $bot_ids) {
|
||||
foreach ($bot_ids as $id => $fd_map) {
|
||||
if ($id === $bot_id) {
|
||||
return self::$bot_ctx_cache[$platform][$id] = new BotContext($id, $platform);
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new OneBot12Exception('未找到 ID 为 ' . $bot_id . ' 的机器人');
|
||||
}
|
||||
if (!isset(self::$bot_fds[$platform][$bot_id])) {
|
||||
throw new OneBot12Exception('未找到 ' . $platform . ' 平台下 ID 为 ' . $bot_id . ' 的机器人');
|
||||
}
|
||||
return self::$bot_ctx_cache[$platform][$bot_id] = new BotContext($bot_id, $platform);
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,12 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZM\Plugin;
|
||||
namespace ZM\Plugin\OneBot;
|
||||
|
||||
use Choir\Http\HttpFactory;
|
||||
use OneBot\Driver\Coroutine\Adaptive;
|
||||
use OneBot\Driver\Event\StopException;
|
||||
use OneBot\Driver\Event\WebSocket\WebSocketCloseEvent;
|
||||
use OneBot\Driver\Event\WebSocket\WebSocketMessageEvent;
|
||||
use OneBot\Driver\Event\WebSocket\WebSocketOpenEvent;
|
||||
use OneBot\V12\Exception\OneBotException;
|
||||
@@ -28,6 +29,7 @@ use ZM\Context\BotContext;
|
||||
use ZM\Exception\InterruptException;
|
||||
use ZM\Exception\OneBot12Exception;
|
||||
use ZM\Exception\WaitTimeoutException;
|
||||
use ZM\Plugin\ZMPlugin;
|
||||
use ZM\Utils\ConnectionUtil;
|
||||
use ZM\Utils\MessageUtil;
|
||||
|
||||
@@ -49,19 +51,20 @@ class OneBot12Adapter extends ZMPlugin
|
||||
*/
|
||||
private static array $context_prompt_queue = [];
|
||||
|
||||
public function __construct(string $submodule = '', ?AnnotationParser $parser = null)
|
||||
public function __construct(string $submodule = 'onebot12', ?AnnotationParser $parser = null)
|
||||
{
|
||||
switch ($submodule) {
|
||||
case '':
|
||||
case 'onebot12':
|
||||
// 处理所有 OneBot 12 的反向 WS 握手事件
|
||||
$this->addEvent(WebSocketOpenEvent::class, [$this, 'handleWSReverseOpen']);
|
||||
$this->addEvent(WebSocketMessageEvent::class, [$this, 'handleWSReverseMessage']);
|
||||
$this->addEvent(WebSocketCloseEvent::class, [$this, 'handleWSReverseClose']);
|
||||
// 在 BotEvent 内处理 BotCommand
|
||||
$this->addBotEvent(BotEvent::make(type: 'message', level: 15)->on([$this, 'handleBotCommand']));
|
||||
// 在 BotEvent 内处理需要等待回复的 CommandArgument
|
||||
$this->addBotEvent(BotEvent::make(type: 'message', level: 49)->on([$this, 'handleCommandArgument']));
|
||||
$this->addBotEvent(BotEvent::make(type: 'message', level: 50)->on([$this, 'handleContextPrompt']));
|
||||
$this->addBotEvent(BotEvent::make(type: 'meta', detail_type: 'status_update', level: 50)->on([$this, 'handleStatusUpdate']));
|
||||
// 处理和声明所有 BotCommand 下的 CommandArgument
|
||||
$parser->addSpecialParser(BotCommand::class, [$this, 'parseBotCommand']);
|
||||
// 不需要给列表写入 CommandArgument
|
||||
@@ -165,6 +168,44 @@ class OneBot12Adapter extends ZMPlugin
|
||||
$this->callBotCommand($ctx, $command);
|
||||
}
|
||||
|
||||
/**
|
||||
* [CALLBACK] 处理 status_update 事件,更新 BotMap
|
||||
*
|
||||
* @param OneBotEvent $event 机器人事件
|
||||
*/
|
||||
public function handleStatusUpdate(OneBotEvent $event, WebSocketMessageEvent $message_event): void
|
||||
{
|
||||
$status = $event->get('status');
|
||||
$old = BotMap::getBotFds();
|
||||
if (($status['good'] ?? false) === true) {
|
||||
foreach (($status['bots'] ?? []) as $bot) {
|
||||
BotMap::registerBotWithFd(
|
||||
bot_id: $bot['self']['user_id'],
|
||||
platform: $bot['self']['platform'],
|
||||
status: $bot['good'] ?? false,
|
||||
fd: $message_event->getFd(),
|
||||
flag: $message_event->getSocketFlag()
|
||||
);
|
||||
if (isset($old[$bot['self']['platform']][$bot['self']['user_id']])) {
|
||||
unset($old[$bot['self']['platform']][$bot['self']['user_id']]);
|
||||
}
|
||||
logger()->error("[{$bot['self']['platform']}.{$bot['self']['user_id']}] 已接入,状态:" . (($bot['good'] ?? false) ? 'OK' : 'Not OK'));
|
||||
}
|
||||
} else {
|
||||
logger()->debug('该实现状态目前不是正常的,不处理 bots 列表');
|
||||
$old = [];
|
||||
}
|
||||
foreach ($old as $platform => $bot_ids) {
|
||||
if (empty($bot_ids)) {
|
||||
continue;
|
||||
}
|
||||
foreach ($bot_ids as $id => $flag_fd) {
|
||||
logger()->debug("[{$platform}.{$id}] 已断开!");
|
||||
BotMap::unregisterBot($id, $platform);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* [CALLBACK] 处理需要等待回复的 CommandArgument
|
||||
*
|
||||
@@ -339,6 +380,14 @@ class OneBot12Adapter extends ZMPlugin
|
||||
|
||||
// 绑定容器
|
||||
ContainerRegistrant::registerOBEventServices($obj);
|
||||
if ($obj->getSelf() !== null) {
|
||||
$bot_id = $obj->self['user_id'];
|
||||
$platform = $obj->self['platform'];
|
||||
if (BotMap::getBotFd($bot_id, $platform) === null) {
|
||||
BotMap::registerBotWithFd($bot_id, $platform, true, $event->getFd(), $event->getSocketFlag());
|
||||
}
|
||||
container()->set(BotContext::class, bot($obj->self['user_id'], $obj->self['platform']));
|
||||
}
|
||||
|
||||
// 调用 BotEvent 事件
|
||||
$handler = new AnnotationHandler(BotEvent::class);
|
||||
@@ -387,6 +436,17 @@ class OneBot12Adapter extends ZMPlugin
|
||||
}
|
||||
}
|
||||
|
||||
public function handleWSReverseClose(WebSocketCloseEvent $event)
|
||||
{
|
||||
// 忽略非 OneBot 12 的消息
|
||||
$impl = ConnectionUtil::getConnection($event->getFd())['impl'] ?? null;
|
||||
if ($impl === null) {
|
||||
return;
|
||||
}
|
||||
// 在关闭连接的时候
|
||||
BotMap::unregisterBotByFd($event->getSocketFlag(), $event->getFd());
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据事件匹配规则
|
||||
*
|
||||
Reference in New Issue
Block a user