-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathOneBot.php
More file actions
338 lines (299 loc) · 11.5 KB
/
OneBot.php
File metadata and controls
338 lines (299 loc) · 11.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
<?php
declare(strict_types=1);
namespace OneBot\V12;
use Choir\WebSocket\FrameFactory;
use OneBot\Config\Config;
use OneBot\Config\RepositoryInterface;
use OneBot\Driver\Driver;
use OneBot\Driver\Event\DriverInitEvent;
use OneBot\Driver\Event\Http\HttpRequestEvent;
use OneBot\Driver\Event\Process\ManagerStartEvent;
use OneBot\Driver\Event\Process\ManagerStopEvent;
use OneBot\Driver\Event\Process\WorkerStartEvent;
use OneBot\Driver\Event\Process\WorkerStopEvent;
use OneBot\Driver\Event\WebSocket\WebSocketMessageEvent;
use OneBot\Driver\Event\WebSocket\WebSocketOpenEvent;
use OneBot\Driver\Interfaces\DriverInitPolicy;
use OneBot\Util\ObjectQueue;
use OneBot\Util\Singleton;
use OneBot\V12\Action\ActionHandlerBase;
use OneBot\V12\Exception\OneBotException;
use OneBot\V12\Object\OneBotEvent;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Log\LoggerInterface;
/**
* OneBot 入口类
* 一切从这里开始,这句话是真人写的,不是AI写的
*/
class OneBot
{
use Singleton;
/** @var Config|RepositoryInterface 配置实例 */
private $config;
/** @var string 实现名称 */
private string $implement_name;
/** @var string 实现平台 */
private string $platform;
/** @var string 机器人 ID */
private string $self_id;
/** @var Driver 驱动实例 */
private Driver $driver;
/** @var null|ActionHandlerBase 动作处理器 */
private ?ActionHandlerBase $base_action_handler = null;
/** @var array 动作处理回调们 */
private array $action_handlers = [];
private bool $bot_status = true;
/**
* 创建一个 OneBot 实例
* @param mixed $config
*/
public function __construct($config)
{
if (self::$instance !== null) {
throw new \RuntimeException('只能有一个OneBot实例!');
}
$this->validateConfig($config);
$this->config = $config;
$this->implement_name = $config->get('name');
$this->self_id = $config->get('self_id');
$this->platform = $config->get('platform');
if (!ob_logger_registered()) {
ob_logger_register($config->get('logger'));
$config->set('logger', null);
}
$this->driver = $config->get('driver');
$config->set('driver', null);
self::$instance = $this;
}
/**
* 获取日志实例
*/
public function getLogger(): LoggerInterface
{
return ob_logger();
}
/**
* 返回 OneBot 实现的名称
* @see https://12.onebot.dev/onebotrpc/data-protocol/event/
*/
public function getImplementName(): string
{
return $this->implement_name;
}
/**
* 返回平台名称
* @see https://12.onebot.dev/onebotrpc/data-protocol/event/
*/
public function getPlatform(): string
{
return $this->platform;
}
/**
* 返回 OneBot 实现自身的 ID
* @see https://12.onebot.dev/onebotrpc/data-protocol/event/
*/
public function getSelfId(): string
{
return $this->self_id;
}
/**
* @param int|string $self_id
*/
public function setSelfId($self_id): void
{
$this->self_id = $self_id;
}
/**
* 获取 Driver
*/
public function getDriver(): ?Driver
{
return $this->driver;
}
/**
* 获取配置实例
*/
public function getConfig(): Config
{
return $this->config;
}
/**
* 获取动作处理器实例
*/
public function getBaseActionHandler(): ?ActionHandlerBase
{
return $this->base_action_handler;
}
/**
* 设置动作处理器,用于处理 Action 的类(继承自 ActionBase 的类)
*
* @param ActionHandlerBase|string $handler 动作处理器
* @throws OneBotException
*/
public function setActionHandlerClass($handler): OneBot
{
if (is_string($handler) && is_a($handler, ActionHandlerBase::class, true)) {
$this->base_action_handler = new $handler();
} elseif ($handler instanceof ActionHandlerBase) {
$this->base_action_handler = $handler;
} else {
throw new OneBotException('CoreActionHandler必须extends ' . ActionHandlerBase::class);
}
return $this;
}
/**
* 动态插入动作处理器
*
* @return $this
*/
public function addActionHandler(string $action, callable $handler, array $options = []): OneBot
{
$this->action_handlers[$action] = [$handler, $options];
return $this;
}
/**
* 获取动态插入的动作处理器
*
* @return null|mixed
*/
public function getActionHandler(string $action)
{
return $this->action_handlers[$action] ?? null;
}
/**
* 获取所有动态插入的动作处理器
*/
public function getActionHandlers(): array
{
return $this->action_handlers;
}
/**
* 获取 HTTP Webhook 及反向 WebSocket 连接时请求的 Headers
*
* $addition 参数为自定义部分,将会被合并到头内
*/
public function getRequestHeaders(array $additions = [], string $access_token = ''): array
{
$default_headers = [
'User-Agent' => 'OneBot/12 (' . $this->getPlatform() . ') ' . $this->getImplementName() . '/' . $this->getAppVersion(),
'X-OneBot-Version' => '12',
'X-Impl' => $this->getImplementName(),
'X-Platform' => $this->getPlatform(),
'X-Self-ID' => $this->getSelfId(),
];
if ($access_token !== '') {
$default_headers['Authorization'] = 'Bearer ' . $access_token;
}
return array_merge($default_headers, $additions);
}
/**
* 获取 OneBot 实现的版本
*
* 通过 ONEBOT_APP_VERSION 常量定义,需自行在项目内 define 。
*/
public function getAppVersion(): string
{
return defined('ONEBOT_APP_VERSION') ? ONEBOT_APP_VERSION : ONEBOT_LIBOB_VERSION;
}
/**
* 触发 OneBot 事件,通过已知的方法发送事件
*
* 此方法默认会将 MetaEvent 排除,如果需要分发 MetaEvent 请自行获取 Driver 的 Socket 进行发送。
* 首先会对 HTTP Webhook 进行分发,并优先采用异步发送,如果没有支持异步的 Client,则会采用同步发送。
* 然后对所有连接到正向 WS 的客户端挨个发送一遍。
* 最后对所有连接到反向 WS 的发送一遍。
*
* @param OneBotEvent $event 事件对象
*/
public function dispatchEvent(OneBotEvent $event): void
{
ob_logger()->info('Dispatching event: ' . $event->type);
if ($event->type !== 'meta') { // 排除 meta_event,要不然队列速度爆炸
ObjectQueue::enqueue('ob_event', $event);
}
foreach ($this->driver->getHttpWebhookSockets() as $socket) {
if ($socket->getFlag() !== 1) {
continue;
}
$socket->post(json_encode($event->jsonSerialize()), $this->getRequestHeaders(), function (ResponseInterface $response) {
// TODO:编写 HTTP Webhook 响应的处理逻辑
}, function (RequestInterface $request) {});
}
$frame_str = FrameFactory::createTextFrame(json_encode($event->jsonSerialize())); // 创建文本帧
foreach ($this->driver->getWSServerSockets() as $socket) {
if ($socket->getFlag() !== 1) {
continue;
}
$socket->sendMultiple($frame_str);
}
foreach ($this->driver->getWSReverseSockets() as $socket) {
if ($socket->getFlag() !== 1 || !$socket->getClient()->isConnected()) {
continue;
}
$socket->send($frame_str);
}
}
/**
* 运行 OneBot 及 Driver 服务
*/
public function run(): void
{
$this->driver->initDriverProtocols($this->config->get('communications', []));
$this->addOneBotEvent();
ObjectQueue::limit('ob_event', 99999);
$this->driver->run();
}
public function getBotStatus(): bool
{
return $this->bot_status;
}
public function setBotStatus(bool $bot_status): void
{
$this->bot_status = $bot_status;
}
/**
* 这里是 OneBot 实现本身添加到 Driver 的事件
* 包含 HTTP 服务器接收 Request 和 WebSocket 服务器收到连接的事件
* 对应事件 id 为 http.request, websocket.open
* 如果你要二次开发或者添加新的事件,可以先继承此类,然后重写此方法
*/
protected function addOneBotEvent()
{
if (!defined('ONEBOT_EVENT_LEVEL')) {
define('ONEBOT_EVENT_LEVEL', 15);
}
// 监听 HTTP 服务器收到的请求事件
ob_event_provider()->addEventListener(HttpRequestEvent::getName(), [OneBotEventListener::getInstance(), 'onHttpRequest'], ONEBOT_EVENT_LEVEL);
// 监听 WS 服务器相关事件
ob_event_provider()->addEventListener(WebSocketOpenEvent::getName(), [OneBotEventListener::getInstance(), 'onWebSocketOpen'], ONEBOT_EVENT_LEVEL);
ob_event_provider()->addEventListener(WebSocketMessageEvent::getName(), [OneBotEventListener::getInstance(), 'onWebSocketMessage'], ONEBOT_EVENT_LEVEL);
// 监听 Worker 进程退出或启动的事件
ob_event_provider()->addEventListener(WorkerStartEvent::getName(), [OneBotEventListener::getInstance(), 'onWorkerStart'], ONEBOT_EVENT_LEVEL);
ob_event_provider()->addEventListener(WorkerStopEvent::getName(), [OneBotEventListener::getInstance(), 'onWorkerStop'], ONEBOT_EVENT_LEVEL);
// 监听 Manager 进程退出或启动事件(仅限 Swoole 驱动下的 SWOOLE_PROCESS 模式才能触发)
ob_event_provider()->addEventListener(ManagerStartEvent::getName(), [OneBotEventListener::getInstance(), 'onManagerStart'], ONEBOT_EVENT_LEVEL);
ob_event_provider()->addEventListener(ManagerStopEvent::getName(), [OneBotEventListener::getInstance(), 'onManagerStop'], ONEBOT_EVENT_LEVEL);
// 监听单进程无 Server 模式的相关事件(如纯 Client 情况下的启动模式)
ob_event_provider()->addEventListener(DriverInitEvent::getName(), [OneBotEventListener::getInstance(), 'onDriverInit'], ONEBOT_EVENT_LEVEL);
// 如果Init策略是FirstWorker,则给WorkerStart添加添加相关事件,让WorkerStart事件(#0)中再套娃执行DriverInit事件
if ($this->driver->getDriverInitPolicy() == DriverInitPolicy::MULTI_PROCESS_INIT_IN_FIRST_WORKER) {
ob_event_provider()->addEventListener(WorkerStartEvent::getName(), [OneBotEventListener::getInstance(), 'onFirstWorkerInit'], ONEBOT_EVENT_LEVEL);
}
}
/**
* @param Config|RepositoryInterface $config 配置文件
*/
protected function validateConfig($config): void
{
if (!($config instanceof Config) && !($config instanceof RepositoryInterface)) {
throw new \InvalidArgumentException('传入要验证的 Config 对象必须是 Config 或 RepositoryInterface 的实例');
}
if (!preg_match('/[a-z][\-a-z0-9]*(\.[\-a-z0-9]+)*/', $config->get('platform'))) {
throw new \InvalidArgumentException('配置的平台名称不合法,请参阅文档');
}
if (!preg_match('/[a-z][\-a-z0-9]*(\.[\-a-z0-9]+)*/', $config->get('name'))) {
throw new \InvalidArgumentException('配置的实现名称不合法,请参阅文档');
}
}
}