ErisPulse-OneBot11Adapter 3.7.0__tar.gz → 3.7.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (15) hide show
  1. {erispulse_onebot11adapter-3.7.0 → erispulse_onebot11adapter-3.7.1}/ErisPulse_OneBot11Adapter.egg-info/PKG-INFO +1 -1
  2. {erispulse_onebot11adapter-3.7.0 → erispulse_onebot11adapter-3.7.1}/OneBotAdapter/Core.py +325 -126
  3. {erispulse_onebot11adapter-3.7.0 → erispulse_onebot11adapter-3.7.1}/PKG-INFO +1 -1
  4. {erispulse_onebot11adapter-3.7.0 → erispulse_onebot11adapter-3.7.1}/pyproject.toml +1 -1
  5. {erispulse_onebot11adapter-3.7.0 → erispulse_onebot11adapter-3.7.1}/ErisPulse_OneBot11Adapter.egg-info/SOURCES.txt +0 -0
  6. {erispulse_onebot11adapter-3.7.0 → erispulse_onebot11adapter-3.7.1}/ErisPulse_OneBot11Adapter.egg-info/dependency_links.txt +0 -0
  7. {erispulse_onebot11adapter-3.7.0 → erispulse_onebot11adapter-3.7.1}/ErisPulse_OneBot11Adapter.egg-info/entry_points.txt +0 -0
  8. {erispulse_onebot11adapter-3.7.0 → erispulse_onebot11adapter-3.7.1}/ErisPulse_OneBot11Adapter.egg-info/requires.txt +0 -0
  9. {erispulse_onebot11adapter-3.7.0 → erispulse_onebot11adapter-3.7.1}/ErisPulse_OneBot11Adapter.egg-info/top_level.txt +0 -0
  10. {erispulse_onebot11adapter-3.7.0 → erispulse_onebot11adapter-3.7.1}/LICENSE +0 -0
  11. {erispulse_onebot11adapter-3.7.0 → erispulse_onebot11adapter-3.7.1}/OneBotAdapter/Converter.py +0 -0
  12. {erispulse_onebot11adapter-3.7.0 → erispulse_onebot11adapter-3.7.1}/OneBotAdapter/__init__.py +0 -0
  13. {erispulse_onebot11adapter-3.7.0 → erispulse_onebot11adapter-3.7.1}/README.md +0 -0
  14. {erispulse_onebot11adapter-3.7.0 → erispulse_onebot11adapter-3.7.1}/setup.cfg +0 -0
  15. {erispulse_onebot11adapter-3.7.0 → erispulse_onebot11adapter-3.7.1}/test/test.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ErisPulse-OneBot11Adapter
3
- Version: 3.7.0
3
+ Version: 3.7.1
4
4
  Summary: ErisPulse的OneBotV11协议适配模块,异步的OneBot触发器
5
5
  Author-email: wsu2059q <wsu2059@qq.com>
6
6
  License: MIT License
@@ -2,16 +2,14 @@
2
2
  import asyncio
3
3
  import json
4
4
  import aiohttp
5
- import base64
6
- import os
7
- import tempfile
8
- import uuid
9
- import filetype
10
5
  from fastapi import WebSocket, WebSocketDisconnect
11
- from typing import Dict, List, Optional, Union
6
+ from typing import Any, Dict, List, Optional, Union
7
+ from collections.abc import Awaitable
12
8
  from dataclasses import dataclass
13
9
  from ErisPulse import sdk
14
10
  from ErisPulse.Core import router
11
+ from ErisPulse.Core.Bases.adapter import RequestDSL
12
+
15
13
 
16
14
  @dataclass
17
15
  class OneBotAccountConfig:
@@ -32,10 +30,24 @@ class OneBotAdapter(sdk.BaseAdapter):
32
30
  OneBot11 平台适配器实现
33
31
 
34
32
  使用 OneBot11 消息段数组格式,避免 CQ 码字符串拼接
33
+
34
+ {!--< tips >!--}
35
+ 1. 支持多账户管理,每个账户有独立的 bot_id
36
+ 2. 支持 self_id → account_name 自动映射,event.reply() 无需关心账户配置
37
+ 3. 提供 WebSocket Server/Client 混合运行模式
38
+ 4. 完整的 DSL 消息发送和请求操作接口
39
+ {!--< /tips >!--}
35
40
  """
36
41
 
37
42
  class Send(sdk.BaseAdapter.Send):
38
- """消息发送DSL实现"""
43
+ """消息发送DSL实现
44
+
45
+ {!--< tips >!--}
46
+ 1. 支持 Text/Image/Voice/Video/Face/File 发送方法
47
+ 2. At/AtAll/Reply 修饰器自动转换为 OneBot11 消息段
48
+ 3. Raw_ob12 自动将 OneBot12 消息段转换为 OneBot11 格式
49
+ {!--< /tips >!--}
50
+ """
39
51
 
40
52
  def __init__(self, adapter, target_type=None, target_id=None, account_id=None):
41
53
  super().__init__(adapter, target_type, target_id, account_id)
@@ -43,29 +55,16 @@ class OneBotAdapter(sdk.BaseAdapter):
43
55
  self._reply_message_id = None
44
56
  self._at_all = False
45
57
 
46
- def _get_msg_type_by_filetype(self, file: Union[str, bytes]) -> str:
47
- try:
48
- if isinstance(file, bytes):
49
- kind = filetype.guess(file)
50
- else:
51
- kind = filetype.guess(file)
52
- except Exception:
53
- kind = None
54
-
55
- if kind is None:
56
- return "image"
57
- if kind.mime.startswith("image/"):
58
- return "image"
59
- elif kind.mime.startswith("audio/"):
60
- return "record"
61
- elif kind.mime.startswith("video/"):
62
- return "video"
63
- else:
64
- return "image"
58
+ def _reset_modifiers(self):
59
+ self._at_user_ids = []
60
+ self._reply_message_id = None
61
+ self._at_all = False
65
62
 
66
- def _build_message_array(self, message: Union[str, List[Dict]]) -> List[Dict]:
63
+ def _build_ob11_message(self, message: Union[str, List[Dict]]) -> List[Dict]:
64
+ """构建完整的 OneBot11 消息段数组(含修饰器)"""
67
65
  message_list = []
68
66
 
67
+ # 修饰器按顺序添加到消息段前
69
68
  if self._reply_message_id:
70
69
  message_list.append(
71
70
  {"type": "reply", "data": {"id": str(self._reply_message_id)}}
@@ -75,23 +74,22 @@ class OneBotAdapter(sdk.BaseAdapter):
75
74
  message_list.append({"type": "at", "data": {"qq": "all"}})
76
75
 
77
76
  for user_info in self._at_user_ids:
78
- user_id = user_info["qq"]
79
- name = user_info.get("name")
80
- at_data = {"qq": user_id}
81
- if name:
82
- at_data["name"] = name
77
+ at_data = {"qq": user_info["qq"]}
78
+ if user_info.get("name"):
79
+ at_data["name"] = user_info["name"]
83
80
  message_list.append({"type": "at", "data": at_data})
84
81
 
82
+ # 消息内容
85
83
  if isinstance(message, str):
86
84
  message_list.append({"type": "text", "data": {"text": message}})
87
85
  else:
88
- for segment in message:
89
- message_list.append(segment)
86
+ message_list.extend(message)
90
87
 
91
88
  self._insert_text_separators(message_list)
92
89
  return message_list
93
90
 
94
91
  def _insert_text_separators(self, message_list: List[Dict]):
92
+ """在 at/text 段之间自动插入空格"""
95
93
  result = []
96
94
  for i, segment in enumerate(message_list):
97
95
  seg_type = segment.get("type", "")
@@ -115,85 +113,115 @@ class OneBotAdapter(sdk.BaseAdapter):
115
113
  message_list.clear()
116
114
  message_list.extend(result)
117
115
 
118
- def _reset_modifiers(self):
119
- self._at_user_ids = []
120
- self._reply_message_id = None
121
- self._at_all = False
122
-
123
116
  # ============ 标准发送方法(委托给 Raw_ob12) ============
124
117
 
125
118
  def Text(self, text: str):
119
+ """发送文本消息"""
126
120
  return self.Raw_ob12([{"type": "text", "data": {"text": text}}])
127
121
 
128
122
  def Image(self, file: Union[str, bytes], filename: str = "image.png"):
123
+ """发送图片消息"""
129
124
  return self.Raw_ob12(
130
125
  [{"type": "image", "data": {"file": file, "file_name": filename}}]
131
126
  )
132
127
 
133
128
  def Voice(self, file: Union[str, bytes], filename: str = "voice.amr"):
129
+ """发送语音消息"""
134
130
  return self.Raw_ob12(
135
131
  [{"type": "audio", "data": {"file": file, "file_name": filename}}]
136
132
  )
137
133
 
138
134
  def Video(self, file: Union[str, bytes], filename: str = "video.mp4"):
135
+ """发送视频消息"""
139
136
  return self.Raw_ob12(
140
137
  [{"type": "video", "data": {"file": file, "file_name": filename}}]
141
138
  )
142
139
 
143
140
  def Face(self, id: Union[str, int]):
141
+ """发送表情消息"""
144
142
  return self.Raw_ob12([{"type": "face", "data": {"id": str(id)}}])
145
143
 
146
144
  def File(self, file: Union[str, bytes], filename: str = "file.dat"):
145
+ """发送文件消息"""
147
146
  return self.Raw_ob12(
148
147
  [{"type": "file", "data": {"file": file, "file_name": filename}}]
149
148
  )
150
149
 
150
+ # ============ Raw_ob12(反向转换核心) ============
151
+
151
152
  def Raw_ob12(self, message, **kwargs):
153
+ """
154
+ 发送 OneBot12 格式消息段,自动转换为 OneBot11 格式
155
+
156
+ :param message: OneBot12 消息段(dict 或 list[dict])
157
+ :param kwargs: 额外参数
158
+ :return: asyncio.Task
159
+ """
152
160
  if isinstance(message, dict):
153
161
  message = [message]
154
162
 
155
- ob11_message = self._convert_ob12_to_ob11(message)
163
+ # OneBot12 → OneBot11 格式转换
164
+ ob11_segments = self._convert_ob12_to_ob11(message)
156
165
 
157
- if self._at_user_ids or self._at_all or self._reply_message_id:
158
- ob11_message = self._build_message_array(ob11_message)
166
+ # 合并修饰器
167
+ has_modifiers = self._at_user_ids or self._at_all or self._reply_message_id
168
+ if has_modifiers:
169
+ ob11_message = self._build_ob11_message(ob11_segments)
159
170
  else:
160
- self._insert_text_separators(ob11_message)
171
+ self._insert_text_separators(ob11_segments)
172
+ ob11_message = ob11_segments
161
173
 
162
174
  self._reset_modifiers()
163
175
 
164
- return asyncio.create_task(
165
- self._adapter.call_api(
166
- endpoint="send_msg",
167
- account_id=self._account_id,
168
- message_type="private" if self._target_type == "user" else "group",
169
- user_id=self._target_id if self._target_type == "user" else None,
170
- group_id=self._target_id if self._target_type == "group" else None,
171
- message=ob11_message,
172
- **kwargs,
173
- )
174
- )
176
+ async def _do_send():
177
+ params = {
178
+ "endpoint": "send_msg",
179
+ "account_id": self._account_id,
180
+ "message_type": "private"
181
+ if self._target_type == "user"
182
+ else "group",
183
+ "message": ob11_message,
184
+ }
185
+ if self._target_type == "user":
186
+ params["user_id"] = self._target_id
187
+ else:
188
+ params["group_id"] = self._target_id
189
+ params.update(kwargs)
190
+ return await self._adapter.call_api(**params)
191
+
192
+ return asyncio.create_task(_do_send())
193
+
194
+ # ============ 修饰器方法 ============
175
195
 
176
196
  def At(self, user_id: Union[str, int], name: str = None):
197
+ """@指定用户"""
177
198
  self._at_user_ids.append({"qq": str(user_id), "name": name})
178
199
  return self
179
200
 
180
201
  def AtAll(self):
202
+ """@全体成员"""
181
203
  self._at_all = True
182
204
  return self
183
205
 
184
206
  def Reply(self, message_id: Union[str, int]):
207
+ """回复指定消息"""
185
208
  self._reply_message_id = str(message_id)
186
209
  return self
187
210
 
211
+ # ============ 其他操作方法 ============
212
+
188
213
  def Recall(self, message_id: Union[str, int]):
214
+ """撤回消息"""
189
215
  return asyncio.create_task(
190
216
  self._adapter.call_api(
191
217
  endpoint="delete_msg",
192
218
  account_id=self._account_id,
193
- message_id=message_id,
219
+ message_id=str(message_id),
194
220
  )
195
221
  )
196
222
 
223
+ # ============ 内部转换方法 ============
224
+
197
225
  def _convert_ob12_to_ob11(self, message: List[Dict]) -> List[Dict]:
198
226
  """
199
227
  将 OneBot12 消息段数组转换为 OneBot11 格式
@@ -207,28 +235,19 @@ class OneBotAdapter(sdk.BaseAdapter):
207
235
  seg_type = segment.get("type", "")
208
236
  seg_data = segment.get("data", {})
209
237
 
210
- # 文本消息
211
238
  if seg_type == "text":
212
239
  ob11_message.append(
213
240
  {"type": "text", "data": {"text": seg_data.get("text", "")}}
214
241
  )
215
-
216
- # 图片
217
242
  elif seg_type == "image":
218
243
  file = seg_data.get("file") or seg_data.get("url", "")
219
244
  ob11_message.append({"type": "image", "data": {"file": file}})
220
-
221
- # 语音/音频
222
- elif seg_type == "audio" or seg_type == "record":
245
+ elif seg_type in ("audio", "record"):
223
246
  file = seg_data.get("file") or seg_data.get("url", "")
224
247
  ob11_message.append({"type": "record", "data": {"file": file}})
225
-
226
- # 视频
227
248
  elif seg_type == "video":
228
249
  file = seg_data.get("file") or seg_data.get("url", "")
229
250
  ob11_message.append({"type": "video", "data": {"file": file}})
230
-
231
- # 文件
232
251
  elif seg_type == "file":
233
252
  file = seg_data.get("file") or seg_data.get("url", "")
234
253
  file_name = seg_data.get("file_name", "")
@@ -236,33 +255,84 @@ class OneBotAdapter(sdk.BaseAdapter):
236
255
  if file_name:
237
256
  data["name"] = file_name
238
257
  ob11_message.append({"type": "file", "data": data})
239
-
240
- # 表情
241
258
  elif seg_type == "face":
242
- face_id = seg_data.get("id", "")
243
- ob11_message.append({"type": "face", "data": {"id": face_id}})
244
-
245
- # @用户(mention)
259
+ ob11_message.append(
260
+ {"type": "face", "data": {"id": seg_data.get("id", "")}}
261
+ )
246
262
  elif seg_type == "mention":
247
- user_id = seg_data.get("user_id", "")
248
- ob11_message.append({"type": "at", "data": {"qq": str(user_id)}})
249
-
250
- # 回复
263
+ ob11_message.append(
264
+ {"type": "at", "data": {"qq": str(seg_data.get("user_id", ""))}}
265
+ )
251
266
  elif seg_type == "reply":
252
- msg_id = seg_data.get("message_id", "")
253
- ob11_message.append({"type": "reply", "data": {"id": msg_id}})
254
-
255
- # OneBot11 扩展消息段(直接保留)
267
+ ob11_message.append(
268
+ {
269
+ "type": "reply",
270
+ "data": {"id": seg_data.get("message_id", "")},
271
+ }
272
+ )
256
273
  elif seg_type.startswith("onebot11_"):
257
- cq_type = seg_type[10:] # 去掉 onebot11_ 前缀
274
+ cq_type = seg_type[10:]
258
275
  ob11_message.append({"type": cq_type, "data": seg_data})
259
-
260
- # 其他未知类型,直接保留
261
276
  else:
262
277
  ob11_message.append({"type": seg_type, "data": seg_data})
263
278
 
264
279
  return ob11_message
265
280
 
281
+ class Request(RequestDSL):
282
+ """请求操作实现(好友请求、群邀请等)
283
+
284
+ {!--< tips >!--}
285
+ 1. 使用 adapter.Request("flag").accept() 同意请求
286
+ 2. 使用 adapter.Request("flag").reject() 拒绝请求
287
+ 3. 通过 event.approve() / event.reject() 便捷操作
288
+ {!--< /tips >!--}
289
+ """
290
+
291
+ def accept(self, **kwargs):
292
+ """同意请求"""
293
+ return self._create_task(self._do_action(approve=True, **kwargs))
294
+
295
+ def reject(self, **kwargs):
296
+ """拒绝请求"""
297
+ return self._create_task(self._do_action(approve=False, **kwargs))
298
+
299
+ async def _do_action(self, approve: bool, **kwargs) -> dict[str, Any]:
300
+ """
301
+ 执行请求操作
302
+
303
+ :param approve: 是否同意
304
+ :param kwargs: 额外参数(如 comment 备注)
305
+ :return: 标准响应格式
306
+ """
307
+ try:
308
+ result = await self._adapter.call_api(
309
+ endpoint="set_friend_add_request"
310
+ if kwargs.get("_request_type") != "group"
311
+ else "set_group_add_request",
312
+ account_id=self._account_id,
313
+ flag=self._request_id,
314
+ approve=approve,
315
+ **{k: v for k, v in kwargs.items() if not k.startswith("_")},
316
+ )
317
+
318
+ return {
319
+ "status": result.get("status", "ok"),
320
+ "retcode": result.get("retcode", 0),
321
+ "data": result.get("data"),
322
+ "message_id": "",
323
+ "message": result.get("message", ""),
324
+ "onebot11_raw": result.get("onebot11_raw", result),
325
+ }
326
+ except Exception as e:
327
+ return {
328
+ "status": "failed",
329
+ "retcode": 34000,
330
+ "data": None,
331
+ "message_id": "",
332
+ "message": str(e),
333
+ "onebot11_raw": None,
334
+ }
335
+
266
336
  def __init__(self, sdk):
267
337
  super().__init__()
268
338
  self.sdk = sdk
@@ -272,6 +342,10 @@ class OneBotAdapter(sdk.BaseAdapter):
272
342
  # 加载配置
273
343
  self.accounts: Dict[str, OneBotAccountConfig] = self._load_account_configs()
274
344
 
345
+ # 映射: raw self_id (OneBot事件中的真实ID) → account_name
346
+ # 收到事件时自动填充,call_api 据此解析 account_id
347
+ self._self_id_map: Dict[str, str] = {}
348
+
275
349
  # 连接池 - 每个账户一个连接
276
350
  self._api_response_futures: Dict[str, Dict[str, asyncio.Future]] = {}
277
351
  self.sessions: Dict[str, aiohttp.ClientSession] = {}
@@ -287,8 +361,12 @@ class OneBotAdapter(sdk.BaseAdapter):
287
361
  self.default_retry_interval = 30
288
362
  self.default_timeout = 30
289
363
 
364
+ # 转换器
290
365
  self.convert = self._setup_converter()
291
366
 
367
+ # 注册平台事件扩展方法
368
+ self._register_event_methods()
369
+
292
370
  def _setup_converter(self):
293
371
  """设置转换器"""
294
372
  from .Converter import OneBot11Converter
@@ -296,6 +374,48 @@ class OneBotAdapter(sdk.BaseAdapter):
296
374
  converter = OneBot11Converter()
297
375
  return converter.convert
298
376
 
377
+ def _register_event_methods(self):
378
+ """注册 OneBot11 平台特有的 Event 方法"""
379
+ try:
380
+ from ErisPulse.Core.Event import register_event_mixin
381
+
382
+ class OneBot11EventMixin:
383
+ """OneBot11 平台事件扩展方法"""
384
+
385
+ def get_raw_self_id(self) -> str:
386
+ """获取 OneBot 原始 self_id(机器人真实 QQ 号)"""
387
+ return self.get("self", {}).get("user_id", "")
388
+
389
+ def get_sender_info(self) -> dict:
390
+ """获取完整发送者信息"""
391
+ return self.get("onebot11_raw", {}).get("sender", {})
392
+
393
+ def get_sender_role(self) -> str:
394
+ """获取发送者在群中的角色(owner/admin/member)"""
395
+ return (
396
+ self.get("onebot11_raw", {}).get("sender", {}).get("role", "")
397
+ )
398
+
399
+ def get_sender_level(self) -> int:
400
+ """获取发送者等级"""
401
+ return (
402
+ self.get("onebot11_raw", {}).get("sender", {}).get("level", 0)
403
+ )
404
+
405
+ def get_sender_title(self) -> str:
406
+ """获取发送者群头衔"""
407
+ return (
408
+ self.get("onebot11_raw", {}).get("sender", {}).get("title", "")
409
+ )
410
+
411
+ def is_system_message(self) -> bool:
412
+ """判断是否为系统消息"""
413
+ return self.get("sub_type") == "system"
414
+
415
+ register_event_mixin("onebot11", OneBot11EventMixin)
416
+ except Exception as e:
417
+ self.logger.warning(f"注册 OneBot11 事件扩展方法失败: {e}")
418
+
299
419
  def _load_account_configs(self) -> Dict[str, OneBotAccountConfig]:
300
420
  """加载多账户配置"""
301
421
  accounts = {}
@@ -354,7 +474,7 @@ class OneBotAdapter(sdk.BaseAdapter):
354
474
  continue
355
475
 
356
476
  accounts[account_name] = OneBotAccountConfig(
357
- bot_id=config["bot_id"],
477
+ bot_id=str(config["bot_id"]),
358
478
  mode=config.get("mode", "server"),
359
479
  server_path=config.get("server_path", "/"),
360
480
  server_token=config.get("server_token", ""),
@@ -367,32 +487,52 @@ class OneBotAdapter(sdk.BaseAdapter):
367
487
  self.logger.info(f"OneBot11适配器初始化完成,加载 {len(accounts)} 个账户")
368
488
  return accounts
369
489
 
490
+ def _resolve_account(self, account_id: str = None) -> tuple:
491
+ """
492
+ 解析账户,返回 (account_config, account_name)
493
+
494
+ 查找顺序:
495
+ 1. 账户名精确匹配
496
+ 2. self_id 映射匹配(OneBot 事件中的真实 ID)
497
+ 3. bot_id 匹配
498
+ 4. 回退到第一个可用账户(仅当 account_id 为 None 时)
499
+
500
+ :param account_id: 账户标识(账户名 / self_id / bot_id)
501
+ :return: (OneBotAccountConfig, account_name)
502
+ :raises ValueError: 找不到匹配的账户
503
+ """
504
+ if account_id is None:
505
+ if not self.accounts:
506
+ raise ValueError("没有配置任何OneBot账户")
507
+ account_name = next(iter(self.accounts.keys()))
508
+ return self.accounts[account_name], account_name
509
+
510
+ # 1. 账户名精确匹配
511
+ if account_id in self.accounts:
512
+ return self.accounts[account_id], account_id
513
+
514
+ # 2. self_id 映射匹配
515
+ if str(account_id) in self._self_id_map:
516
+ account_name = self._self_id_map[str(account_id)]
517
+ return self.accounts[account_name], account_name
518
+
519
+ # 3. bot_id 匹配
520
+ for account_name, acc_config in self.accounts.items():
521
+ if str(acc_config.bot_id) == str(account_id):
522
+ return acc_config, account_name
523
+
524
+ raise ValueError(f"找不到账户 {account_id}")
525
+
370
526
  async def call_api(self, endpoint: str, account_id: str = None, **params):
371
527
  """
372
528
  调用 OneBot API
373
529
 
374
530
  :param endpoint: API端点
375
- :param account_id: 账户名或bot_id
531
+ :param account_id: 账户名 / self_id / bot_id(可选)
376
532
  :param params: 其他参数
377
533
  :return: 标准化响应
378
534
  """
379
- # 确定使用的账户
380
- if account_id is None:
381
- if not self.accounts:
382
- raise ValueError("没有配置任何OneBot账户")
383
- account = next(iter(self.accounts.values()))
384
- account_name = next(iter(self.accounts.keys()))
385
- else:
386
- if account_id in self.accounts:
387
- account = self.accounts[account_id]
388
- account_name = account_id
389
- else:
390
- for account_name, acc_config in self.accounts.items():
391
- if acc_config.bot_id == account_id:
392
- account = acc_config
393
- break
394
- else:
395
- raise ValueError(f"找不到账户 {account_id}")
535
+ account, account_name = self._resolve_account(account_id)
396
536
 
397
537
  if not account.enabled:
398
538
  raise ValueError(f"账户 {account_name} 已禁用")
@@ -401,14 +541,14 @@ class OneBotAdapter(sdk.BaseAdapter):
401
541
  if not connection:
402
542
  raise ConnectionError(f"账户 {account_name} 尚未连接")
403
543
 
404
- if connection.closed:
544
+ if hasattr(connection, "closed") and connection.closed:
405
545
  raise ConnectionError(f"账户 {account_name} 的连接已关闭")
406
546
 
407
547
  # 创建响应Future
408
548
  if account_name not in self._api_response_futures:
409
549
  self._api_response_futures[account_name] = {}
410
550
 
411
- echo = str(hash((str(params), account_name)))
551
+ echo = str(hash((str(params), account_name, endpoint)))
412
552
  future = asyncio.get_event_loop().create_future()
413
553
  self._api_response_futures[account_name][echo] = future
414
554
 
@@ -417,14 +557,29 @@ class OneBotAdapter(sdk.BaseAdapter):
417
557
  try:
418
558
  await connection.send_str(json.dumps(payload))
419
559
  except Exception as e:
420
- self.logger.error(f"账户 {account_name} 发送请求失败: {str(e)}")
560
+ self.logger.error(
561
+ f"账户 {account_name} (bot_id: {account.bot_id}) 发送请求失败: {str(e)}"
562
+ )
421
563
  if echo in self._api_response_futures[account_name]:
422
564
  del self._api_response_futures[account_name][echo]
423
565
  raise
424
566
 
425
567
  try:
568
+ self.logger.debug(
569
+ f"账户 {account_name} (bot_id: {account.bot_id}) 请求: {payload}"
570
+ )
571
+
426
572
  raw_response = await asyncio.wait_for(future, timeout=self.default_timeout)
427
573
 
574
+ self.logger.debug(
575
+ f"账户 {account_name} (bot_id: {account.bot_id}) 响应: {raw_response}"
576
+ )
577
+
578
+ # 从 data 中提取 message_id(OneBot11 标准)
579
+ message_id = ""
580
+ if isinstance(raw_response.get("data"), dict):
581
+ message_id = str(raw_response["data"].get("message_id", ""))
582
+
428
583
  # 标准化响应
429
584
  status = "ok"
430
585
  retcode = raw_response.get("retcode", 0)
@@ -435,9 +590,9 @@ class OneBotAdapter(sdk.BaseAdapter):
435
590
  "status": status,
436
591
  "retcode": retcode,
437
592
  "data": raw_response.get("data"),
438
- "message_id": str(raw_response.get("message_id", "")),
593
+ "message_id": message_id,
439
594
  "message": raw_response.get("message", ""),
440
- "onebot_raw": raw_response,
595
+ "onebot11_raw": raw_response,
441
596
  "self": {"user_id": account.bot_id},
442
597
  }
443
598
 
@@ -447,7 +602,9 @@ class OneBotAdapter(sdk.BaseAdapter):
447
602
  return standardized_response
448
603
 
449
604
  except asyncio.TimeoutError:
450
- self.logger.error(f"账户 {account_name} API调用超时: {endpoint}")
605
+ self.logger.error(
606
+ f"账户 {account_name} (bot_id: {account.bot_id}) API调用超时: {endpoint}"
607
+ )
451
608
  if not future.done():
452
609
  future.cancel()
453
610
 
@@ -456,8 +613,8 @@ class OneBotAdapter(sdk.BaseAdapter):
456
613
  "retcode": 33001,
457
614
  "data": None,
458
615
  "message_id": "",
459
- "message": f"账户 {account_name} API调用超时: {endpoint}",
460
- "onebot_raw": None,
616
+ "message": f"账户 {account_name} (bot_id: {account.bot_id}) API调用超时: {endpoint}",
617
+ "onebot11_raw": None,
461
618
  "self": {"user_id": account.bot_id},
462
619
  }
463
620
 
@@ -516,7 +673,9 @@ class OneBotAdapter(sdk.BaseAdapter):
516
673
  asyncio.create_task(self._listen(account_name))
517
674
  return
518
675
  except Exception as e:
519
- self.logger.error(f"账户 {account_name} 连接失败: {str(e)}")
676
+ self.logger.error(
677
+ f"账户 {account_name} (bot_id: {account.bot_id}) 连接失败: {str(e)}"
678
+ )
520
679
  await asyncio.sleep(retry_interval)
521
680
 
522
681
  async def _listen(self, account_name: str):
@@ -532,12 +691,18 @@ class OneBotAdapter(sdk.BaseAdapter):
532
691
  if msg.type == aiohttp.WSMsgType.TEXT:
533
692
  asyncio.create_task(self._handle_message(msg.data, account_name))
534
693
  elif msg.type == aiohttp.WSMsgType.CLOSED:
535
- self.logger.info(f"账户 {account_name} 连接已关闭")
694
+ self.logger.info(
695
+ f"账户 {account_name} (bot_id: {account.bot_id}) 连接已关闭"
696
+ )
536
697
  break
537
698
  elif msg.type == aiohttp.WSMsgType.ERROR:
538
- self.logger.error(f"账户 {account_name} WebSocket错误")
699
+ self.logger.error(
700
+ f"账户 {account_name} (bot_id: {account.bot_id}) WebSocket错误"
701
+ )
539
702
  except Exception as e:
540
- self.logger.error(f"账户 {account_name} 监听异常: {str(e)}")
703
+ self.logger.error(
704
+ f"账户 {account_name} (bot_id: {account.bot_id}) 监听异常: {str(e)}"
705
+ )
541
706
  finally:
542
707
  try:
543
708
  await self.adapter.emit(
@@ -557,7 +722,9 @@ class OneBotAdapter(sdk.BaseAdapter):
557
722
  del self.connections[account_name]
558
723
 
559
724
  if self._is_running and account.enabled and account.mode == "client":
560
- self.logger.info(f"账户 {account_name} 开始重连...")
725
+ self.logger.info(
726
+ f"账户 {account_name} (bot_id: {account.bot_id}) 开始重连..."
727
+ )
561
728
  self.reconnect_tasks[account_name] = asyncio.create_task(
562
729
  self.connect(account_name)
563
730
  )
@@ -583,10 +750,13 @@ class OneBotAdapter(sdk.BaseAdapter):
583
750
  if hasattr(self.adapter, "emit"):
584
751
  onebot_event = self.convert(data)
585
752
  if onebot_event:
586
- if "self" not in onebot_event or not onebot_event.get(
587
- "self", {}
588
- ).get("user_id"):
589
- onebot_event["self"] = {"user_id": account.bot_id}
753
+ # 记录 self_id account_name 映射(用于 event.reply() 回路)
754
+ raw_self_id = onebot_event.get("self", {}).get("user_id", "")
755
+ if raw_self_id and str(raw_self_id) not in self._self_id_map:
756
+ self._self_id_map[str(raw_self_id)] = account_name
757
+ self.logger.info(
758
+ f"映射 self_id {raw_self_id} → 账户 {account_name}"
759
+ )
590
760
  await self.adapter.emit(onebot_event)
591
761
 
592
762
  except json.JSONDecodeError:
@@ -621,9 +791,13 @@ class OneBotAdapter(sdk.BaseAdapter):
621
791
  data = await websocket.receive_text()
622
792
  asyncio.create_task(self._handle_message(data, account_name))
623
793
  except WebSocketDisconnect:
624
- self.logger.info(f"账户 {account_name} 客户端断开连接")
794
+ self.logger.info(
795
+ f"账户 {account_name} (bot_id: {account.bot_id}) 客户端断开连接"
796
+ )
625
797
  except Exception as e:
626
- self.logger.error(f"账户 {account_name} WebSocket处理异常: {str(e)}")
798
+ self.logger.error(
799
+ f"账户 {account_name} (bot_id: {account.bot_id}) WebSocket处理异常: {str(e)}"
800
+ )
627
801
  finally:
628
802
  try:
629
803
  await self.adapter.emit(
@@ -658,7 +832,9 @@ class OneBotAdapter(sdk.BaseAdapter):
658
832
  client_token = query.get("token", "")
659
833
 
660
834
  if client_token != account.server_token:
661
- self.logger.warning(f"账户 {account_name} Token无效")
835
+ self.logger.warning(
836
+ f"账户 {account_name} (bot_id: {account.bot_id}) Token无效"
837
+ )
662
838
  await websocket.close(code=1008)
663
839
  return False
664
840
  return True
@@ -687,7 +863,9 @@ class OneBotAdapter(sdk.BaseAdapter):
687
863
  make_ws_handler(account_name),
688
864
  auth_handler=make_auth_handler(account_name),
689
865
  )
690
- self.logger.info(f"已注册账户 {account_name} 的Server路由: {path}")
866
+ self.logger.info(
867
+ f"已注册账户 {account_name} (bot_id: {account.bot_id}) 的Server路由: {path}"
868
+ )
691
869
 
692
870
  async def start(self):
693
871
  """启动适配器"""
@@ -708,6 +886,10 @@ class OneBotAdapter(sdk.BaseAdapter):
708
886
  await self.register_websocket()
709
887
 
710
888
  for account_name in client_accounts:
889
+ account = self.accounts[account_name]
890
+ self.logger.info(
891
+ f"启动Client模式账户: {account_name} (bot_id: {account.bot_id})"
892
+ )
711
893
  self.reconnect_tasks[account_name] = asyncio.create_task(
712
894
  self.connect(account_name)
713
895
  )
@@ -719,24 +901,41 @@ class OneBotAdapter(sdk.BaseAdapter):
719
901
  """关闭适配器"""
720
902
  self._is_running = False
721
903
 
904
+ # 取消重连任务
722
905
  for task in self.reconnect_tasks.values():
723
906
  if not task.done():
724
907
  task.cancel()
725
908
  self.reconnect_tasks.clear()
726
909
 
910
+ # 关闭所有连接
727
911
  for account_name, connection in self.connections.items():
912
+ account = self.accounts.get(account_name)
728
913
  try:
729
- if not connection.closed:
914
+ if hasattr(connection, "closed") and not connection.closed:
730
915
  await connection.close()
731
916
  except Exception as e:
732
- self.logger.error(f"关闭连接失败: {str(e)}")
917
+ self.logger.error(
918
+ f"关闭账户 {account_name} (bot_id: {account.bot_id}) 连接失败: {str(e)}"
919
+ )
733
920
  self.connections.clear()
734
921
 
735
- for session in self.sessions.values():
922
+ # 关闭所有 session
923
+ for account_name, session in self.sessions.items():
924
+ account = self.accounts.get(account_name)
736
925
  try:
737
926
  await session.close()
738
927
  except Exception as e:
739
- self.logger.error(f"关闭session失败: {str(e)}")
928
+ self.logger.error(
929
+ f"关闭账户 {account_name} (bot_id: {account.bot_id}) session失败: {str(e)}"
930
+ )
740
931
  self.sessions.clear()
741
932
 
933
+ # 清理平台事件方法注册
934
+ try:
935
+ from ErisPulse.Core.Event import unregister_platform_event_methods
936
+
937
+ unregister_platform_event_methods("onebot11")
938
+ except Exception:
939
+ pass
940
+
742
941
  self.logger.info("OneBot11适配器已关闭")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ErisPulse-OneBot11Adapter
3
- Version: 3.7.0
3
+ Version: 3.7.1
4
4
  Summary: ErisPulse的OneBotV11协议适配模块,异步的OneBot触发器
5
5
  Author-email: wsu2059q <wsu2059@qq.com>
6
6
  License: MIT License
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ErisPulse-OneBot11Adapter"
3
- version = "3.7.0"
3
+ version = "3.7.1"
4
4
  description = "ErisPulse的OneBotV11协议适配模块,异步的OneBot触发器"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"