Skip to content

Commit 8f99dc7

Browse files
committed
onebot适配器增加web面板注册配置,增加http正反向
1 parent b92b7ba commit 8f99dc7

8 files changed

Lines changed: 1119 additions & 96 deletions

File tree

modules/onebot_adapter/adapter.py

Lines changed: 137 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
convert_lifecycle_event,
3030
convert_message_event,
3131
)
32+
from modules.onebot_adapter.lib.http_server import OneBotHTTPServer
33+
from modules.onebot_adapter.lib.http_webhook import OneBotHTTPWebhook
3234
from modules.onebot_adapter.lib.id_mapper import IDMapper
3335
from modules.onebot_adapter.lib.ws_server import OneBotWSServer
3436

@@ -62,6 +64,8 @@ class OneBotAdapter:
6264
cfg: OneBotConfig
6365
id_mapper: IDMapper | None
6466
ws_server: OneBotWSServer | None
67+
http_server: OneBotHTTPServer | None
68+
http_webhook: OneBotHTTPWebhook | None
6569

6670
def __init__(self, module_ctx: ModuleContext) -> None:
6771
self._mctx = module_ctx # ModuleContext
@@ -71,6 +75,8 @@ def __init__(self, module_ctx: ModuleContext) -> None:
7175
# 基础设施
7276
self.id_mapper: IDMapper | None = None
7377
self.ws_server: OneBotWSServer | None = None
78+
self.http_server: OneBotHTTPServer | None = None
79+
self.http_webhook: OneBotHTTPWebhook | None = None
7480
self._hook_adapter = HookAdapter(self.log)
7581

7682
# Action 上下文 (DI 容器)
@@ -85,12 +91,19 @@ def __init__(self, module_ctx: ModuleContext) -> None:
8591
# ==================== 生命周期 ====================
8692

8793
async def start(self) -> None:
88-
"""启动适配器: 配置 → ID 映射 → Hook → WS → Action 路由"""
89-
90-
# 1. 加载配置
91-
raw_config = self._mctx.ensure_config(OneBotConfig.defaults(), comments=OneBotConfig.comments())
94+
"""启动适配器: 配置 → ID 映射 → Hook → 网络连接 → Action 路由"""
95+
96+
# 1. 加载配置 (含旧版扁平配置自动迁移)
97+
raw_config = self._mctx.read_config()
98+
migrated = OneBotConfig.migrate_legacy(raw_config)
99+
if migrated is not None:
100+
self._mctx.save_config(migrated, comments=OneBotConfig.comments())
101+
raw_config = migrated
102+
self.log.info('旧版配置已迁移为 connections 列表')
103+
else:
104+
raw_config = self._mctx.ensure_config(OneBotConfig.defaults(), comments=OneBotConfig.comments())
92105
self.cfg = OneBotConfig.from_dict(raw_config)
93-
self.log.info(f'配置: path={self.cfg.ws_path}, token={"***" if self.cfg.has_token else "(无)"}')
106+
self.log.info(f'配置: {len(self.cfg.connections)} 个连接')
94107

95108
# 2. 初始化 ID 映射器
96109
db_path = self._mctx.get_data_path('id_mapping.db')
@@ -111,24 +124,49 @@ async def start(self) -> None:
111124
# 6. 构建 Action 注册表 (Command 模式)
112125
self._action_registry = ActionRegistry.create_default(self._actx)
113126

114-
# 7. 启动 WebSocket 服务
115-
await self._start_ws_server()
127+
# 7. 启动网络连接 (WS 正/反 + HTTP 正/反)
128+
await self._start_network()
116129

117130
async def stop(self) -> None:
118-
"""停止适配器: Hook → WS → ID 映射"""
131+
"""停止适配器: Hook → 网络 → ID 映射"""
119132
# 1. 卸载 Hook
120133
self._hook_adapter.uninstall()
121134

122-
# 2. 停止 WS
123-
if self.ws_server:
124-
await self.ws_server.stop()
135+
# 2. 停止网络连接
136+
await self._stop_network()
125137

126138
# 3. 关闭 ID 映射
127139
if self.id_mapper:
128140
await self.id_mapper.close()
129141

130142
self.log.info('OneBot 适配器已停止')
131143

144+
async def apply_config(self, raw_config: dict) -> None:
145+
"""保存并应用新配置 (Web 面板调用): 重启网络连接层"""
146+
self.cfg = OneBotConfig.from_dict(raw_config)
147+
self._mctx.save_config(
148+
{
149+
'connections': self.cfg.connections,
150+
'heartbeat_interval': self.cfg.heartbeat_interval,
151+
'debug': self.cfg.debug,
152+
},
153+
comments=OneBotConfig.comments(),
154+
)
155+
await self._stop_network()
156+
await self._start_network()
157+
self.log.info('网络配置已重新应用')
158+
159+
def network_status(self) -> dict:
160+
"""汇总各连接状态 (供 Web 面板展示)"""
161+
ws_st = self.ws_server.status() if self.ws_server else {'forward': {}, 'reverse': {}}
162+
return {
163+
'ws_server': ws_st.get('forward', {}),
164+
'ws_reverse': ws_st.get('reverse', {}),
165+
'http_server': self.http_server.status() if self.http_server else {},
166+
'http_webhook': self.http_webhook.status() if self.http_webhook else {},
167+
'port': self._get_framework_port(),
168+
}
169+
132170
# ==================== 配置辅助 ====================
133171

134172
def _build_qq_map(self) -> None:
@@ -199,56 +237,114 @@ def _get_framework_port() -> int:
199237
except Exception:
200238
return 5001
201239

202-
# ==================== WebSocket 服务 ====================
240+
# ==================== 网络连接 (WS 正/反 + HTTP 正/反) ====================
203241

204-
async def _start_ws_server(self) -> None:
205-
"""创建并启动 WebSocket 服务 (正向 + 反向 + 心跳)"""
242+
async def _start_network(self) -> None:
243+
"""按 connections 配置启动所有网络连接"""
244+
port = self._get_framework_port()
206245

207-
# 解析反向 WS 配置
208-
reverse_entries = [
209-
{'url': str(e.get('url', '')), 'appid': str(e.get('appid', ''))}
210-
for e in (self.cfg.reverse_ws_urls or [])
211-
if isinstance(e, dict) and str(e.get('url', '')).strip()
246+
# 正向 WS (服务端)
247+
ws_forward_entries = [
248+
{'name': c['name'], 'path': c['path'], 'token': c['access_token'], 'appid': c['appid'], 'enable': True} for c in self.cfg.by_type('ws_server')
249+
]
250+
# 反向 WS (客户端)
251+
ws_reverse_entries = [
252+
{
253+
'name': c['name'],
254+
'url': c['url'],
255+
'appid': c['appid'],
256+
'token': c['access_token'],
257+
'reconnect_interval': c['reconnect_interval'],
258+
}
259+
for c in self.cfg.by_type('ws_reverse')
260+
if c['url'].strip()
212261
]
213262

214263
self.ws_server = OneBotWSServer(
215-
access_token=self.cfg.access_token,
216264
heartbeat_interval=self.cfg.heartbeat_interval,
217265
on_action=self._handle_action,
218266
default_qq=self._actx.default_qq,
219267
qq_map=self._actx.qq_map,
220268
log=self.log,
221-
ws_path=self.cfg.ws_path,
222-
reverse_entries=reverse_entries,
223-
reconnect_interval=self.cfg.reconnect_interval,
269+
forward_entries=ws_forward_entries,
270+
reverse_entries=ws_reverse_entries,
224271
debug=self.cfg.debug,
225272
)
226273

227-
# 正向 WS: 挂载到框架 aiohttp app
228274
app = self._get_framework_app()
229275
if app:
230-
self.ws_server.attach(app)
231-
port = self._get_framework_port()
232-
self.log.info(f'正向 WS 已挂载: ws://0.0.0.0:{port}{self.cfg.ws_path}')
233-
else:
276+
for path in self.ws_server.attach(app):
277+
self.log.info(f'正向 WS 已挂载: ws://0.0.0.0:{port}{path}')
278+
elif ws_forward_entries:
234279
self.log.warning('无法获取框架 aiohttp app, 正向 WS 未挂载')
235280

236-
# 反向 WS: 主动连接外部服务器
237281
await self.ws_server.start_reverse()
238-
if reverse_entries:
239-
self.log.info(f'反向 WS 已启动: {len(reverse_entries)} 个连接')
282+
if ws_reverse_entries:
283+
self.log.info(f'反向 WS 已启动: {len(ws_reverse_entries)} 个连接')
240284

241-
# 心跳
242285
self.ws_server.start_heartbeat()
243286

287+
# 正向 HTTP (OneBot HTTP API)
288+
http_entries = [
289+
{'name': c['name'], 'path': c['path'], 'token': c['access_token'], 'appid': c['appid'], 'enable': True} for c in self.cfg.by_type('http_server')
290+
]
291+
self.http_server = OneBotHTTPServer(
292+
entries=http_entries,
293+
on_action=self._handle_action,
294+
log=self.log,
295+
debug=self.cfg.debug,
296+
)
297+
if app:
298+
for path in self.http_server.attach(app):
299+
self.log.info(f'正向 HTTP 已挂载: http://0.0.0.0:{port}{path}')
300+
elif http_entries:
301+
self.log.warning('无法获取框架 aiohttp app, 正向 HTTP 未挂载')
302+
303+
# 反向 HTTP (事件 POST 上报)
304+
webhook_entries = [
305+
{
306+
'name': c['name'],
307+
'url': c['url'],
308+
'appid': c['appid'],
309+
'token': c['access_token'],
310+
'secret': c['secret'],
311+
'timeout': c['timeout'],
312+
}
313+
for c in self.cfg.by_type('http_webhook')
314+
if c['url'].strip()
315+
]
316+
self.http_webhook = OneBotHTTPWebhook(
317+
entries=webhook_entries,
318+
on_action=self._handle_action,
319+
log=self.log,
320+
debug=self.cfg.debug,
321+
)
322+
await self.http_webhook.start()
323+
if webhook_entries:
324+
self.log.info(f'HTTP 上报已启动: {len(webhook_entries)} 个目标')
325+
326+
async def _stop_network(self) -> None:
327+
"""停止所有网络连接"""
328+
if self.ws_server:
329+
await self.ws_server.stop()
330+
self.ws_server = None
331+
if self.http_server:
332+
self.http_server.detach()
333+
self.http_server = None
334+
if self.http_webhook:
335+
await self.http_webhook.stop()
336+
self.http_webhook = None
337+
244338
# ==================== 事件处理 (Observer) ====================
245339

246340
async def _on_raw_event(self, event: Event, bot: Any) -> None:
247341
"""on_raw_event 回调 — 将事件转为 OneBot 格式推送到 WS 客户端
248342
249343
同时动态更新 ActionContext 中的 senders / log_services / qq_map。
250344
"""
251-
if not self.ws_server or not self.ws_server.has_clients:
345+
has_ws = self.ws_server is not None and self.ws_server.has_clients
346+
has_webhook = self.http_webhook is not None and self.http_webhook.has_targets
347+
if not has_ws and not has_webhook:
252348
return
253349

254350
appid = event.appid
@@ -267,10 +363,12 @@ async def _on_raw_event(self, event: Event, bot: Any) -> None:
267363
rq = getattr(bot, 'robot_qq', '') or ''
268364
if rq:
269365
self._actx.qq_map[aid] = int(rq)
270-
self.ws_server.qq_map = self._actx.qq_map
366+
if self.ws_server:
367+
self.ws_server.qq_map = self._actx.qq_map
271368
if not self._actx.default_qq:
272369
self._actx.default_qq = int(rq)
273-
self.ws_server._default_qq = self._actx.default_qq
370+
if self.ws_server:
371+
self.ws_server._default_qq = self._actx.default_qq
274372

275373
self_qq = self._actx.qq_map.get(aid, self._actx.default_qq) or self._actx.default_qq
276374

@@ -285,7 +383,10 @@ async def _on_raw_event(self, event: Event, bot: Any) -> None:
285383
ob_event = await convert_message_event(event, self.id_mapper, self_qq)
286384

287385
if ob_event:
288-
await self.ws_server.broadcast(ob_event, appid=aid)
386+
if has_ws:
387+
await self.ws_server.broadcast(ob_event, appid=aid)
388+
if has_webhook:
389+
self.http_webhook.push(ob_event, appid=aid)
289390

290391
def _cache_msg_id(self, event: Event, appid: int) -> None:
291392
"""缓存消息 ID 用于后续回复"""

0 commit comments

Comments
 (0)