ErisPulse-OneBot11Adapter 3.6.2__tar.gz → 3.7.0__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.6.2 → erispulse_onebot11adapter-3.7.0}/ErisPulse_OneBot11Adapter.egg-info/PKG-INFO +1 -1
  2. {erispulse_onebot11adapter-3.6.2 → erispulse_onebot11adapter-3.7.0}/OneBotAdapter/Core.py +199 -345
  3. {erispulse_onebot11adapter-3.6.2 → erispulse_onebot11adapter-3.7.0}/PKG-INFO +1 -1
  4. {erispulse_onebot11adapter-3.6.2 → erispulse_onebot11adapter-3.7.0}/pyproject.toml +1 -1
  5. {erispulse_onebot11adapter-3.6.2 → erispulse_onebot11adapter-3.7.0}/ErisPulse_OneBot11Adapter.egg-info/SOURCES.txt +0 -0
  6. {erispulse_onebot11adapter-3.6.2 → erispulse_onebot11adapter-3.7.0}/ErisPulse_OneBot11Adapter.egg-info/dependency_links.txt +0 -0
  7. {erispulse_onebot11adapter-3.6.2 → erispulse_onebot11adapter-3.7.0}/ErisPulse_OneBot11Adapter.egg-info/entry_points.txt +0 -0
  8. {erispulse_onebot11adapter-3.6.2 → erispulse_onebot11adapter-3.7.0}/ErisPulse_OneBot11Adapter.egg-info/requires.txt +0 -0
  9. {erispulse_onebot11adapter-3.6.2 → erispulse_onebot11adapter-3.7.0}/ErisPulse_OneBot11Adapter.egg-info/top_level.txt +0 -0
  10. {erispulse_onebot11adapter-3.6.2 → erispulse_onebot11adapter-3.7.0}/LICENSE +0 -0
  11. {erispulse_onebot11adapter-3.6.2 → erispulse_onebot11adapter-3.7.0}/OneBotAdapter/Converter.py +0 -0
  12. {erispulse_onebot11adapter-3.6.2 → erispulse_onebot11adapter-3.7.0}/OneBotAdapter/__init__.py +0 -0
  13. {erispulse_onebot11adapter-3.6.2 → erispulse_onebot11adapter-3.7.0}/README.md +0 -0
  14. {erispulse_onebot11adapter-3.6.2 → erispulse_onebot11adapter-3.7.0}/setup.cfg +0 -0
  15. {erispulse_onebot11adapter-3.6.2 → erispulse_onebot11adapter-3.7.0}/test/test.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ErisPulse-OneBot11Adapter
3
- Version: 3.6.2
3
+ Version: 3.7.0
4
4
  Summary: ErisPulse的OneBotV11协议适配模块,异步的OneBot触发器
5
5
  Author-email: wsu2059q <wsu2059@qq.com>
6
6
  License: MIT License
@@ -13,10 +13,10 @@ from dataclasses import dataclass
13
13
  from ErisPulse import sdk
14
14
  from ErisPulse.Core import router
15
15
 
16
-
17
16
  @dataclass
18
17
  class OneBotAccountConfig:
19
18
  """OneBot11 账户配置"""
19
+
20
20
  bot_id: str # 机器人ID(必填,用于SDK路由)
21
21
  mode: str # "server" or "client"
22
22
  server_path: Optional[str] = "/"
@@ -39,17 +39,11 @@ class OneBotAdapter(sdk.BaseAdapter):
39
39
 
40
40
  def __init__(self, adapter, target_type=None, target_id=None, account_id=None):
41
41
  super().__init__(adapter, target_type, target_id, account_id)
42
- self._at_user_ids = [] # @的用户列表
43
- self._reply_message_id = None # 回复的消息ID
44
- self._at_all = False # 是否@全体
42
+ self._at_user_ids = []
43
+ self._reply_message_id = None
44
+ self._at_all = False
45
45
 
46
46
  def _get_msg_type_by_filetype(self, file: Union[str, bytes]) -> str:
47
- """
48
- 根据文件内容或路径识别文件类型,返回对应的消息段类型
49
-
50
- :param file: 文件内容(bytes)或路径(str)
51
- :return: 消息段类型(image/record/video)
52
- """
53
47
  try:
54
48
  if isinstance(file, bytes):
55
49
  kind = filetype.guess(file)
@@ -57,177 +51,116 @@ class OneBotAdapter(sdk.BaseAdapter):
57
51
  kind = filetype.guess(file)
58
52
  except Exception:
59
53
  kind = None
60
-
54
+
61
55
  if kind is None:
62
- return "image" # 默认作为图片尝试
63
-
64
- # 根据MIME类型判断
65
- if kind.mime.startswith('image/'):
66
56
  return "image"
67
- elif kind.mime.startswith('audio/'):
57
+ if kind.mime.startswith("image/"):
58
+ return "image"
59
+ elif kind.mime.startswith("audio/"):
68
60
  return "record"
69
- elif kind.mime.startswith('video/'):
61
+ elif kind.mime.startswith("video/"):
70
62
  return "video"
71
63
  else:
72
- return "image" # 未知类型默认作为图片
64
+ return "image"
73
65
 
74
66
  def _build_message_array(self, message: Union[str, List[Dict]]) -> List[Dict]:
75
- """
76
- 构建消息数组,包含链式修饰
77
-
78
- :param message: 消息内容(字符串或消息段数组)
79
- :return: 完整的消息段数组
80
- """
81
67
  message_list = []
82
-
83
- # 添加回复
68
+
84
69
  if self._reply_message_id:
85
- message_list.append({
86
- "type": "reply",
87
- "data": {"id": str(self._reply_message_id)}
88
- })
89
-
90
- # 添加@全体
70
+ message_list.append(
71
+ {"type": "reply", "data": {"id": str(self._reply_message_id)}}
72
+ )
73
+
91
74
  if self._at_all:
92
- message_list.append({
93
- "type": "at",
94
- "data": {"qq": "all"}
95
- })
96
-
97
- # 添加@用户
75
+ message_list.append({"type": "at", "data": {"qq": "all"}})
76
+
98
77
  for user_info in self._at_user_ids:
99
78
  user_id = user_info["qq"]
100
79
  name = user_info.get("name")
101
80
  at_data = {"qq": user_id}
102
81
  if name:
103
82
  at_data["name"] = name
104
- message_list.append({
105
- "type": "at",
106
- "data": at_data
107
- })
108
-
109
- # 添加消息内容
83
+ message_list.append({"type": "at", "data": at_data})
84
+
110
85
  if isinstance(message, str):
111
- message_list.append({
112
- "type": "text",
113
- "data": {"text": message}
114
- })
86
+ message_list.append({"type": "text", "data": {"text": message}})
115
87
  else:
116
- # 添加消息段数组,并在需要的地方插入空格分隔
117
88
  for segment in message:
118
89
  message_list.append(segment)
119
-
120
- # 在相邻的文本段之间添加空格
90
+
121
91
  self._insert_text_separators(message_list)
122
-
123
92
  return message_list
124
-
93
+
125
94
  def _insert_text_separators(self, message_list: List[Dict]):
126
- """
127
- 在相邻的文本消息段之间插入空格分隔
128
-
129
- :param message_list: 消息段数组
130
- """
131
95
  result = []
132
96
  for i, segment in enumerate(message_list):
133
97
  seg_type = segment.get("type", "")
134
-
135
- # 添加当前段
136
98
  result.append(segment)
137
-
138
- # 检查是否需要在当前段和下一段之间添加空格
99
+
139
100
  if i < len(message_list) - 1:
140
101
  next_seg = message_list[i + 1]
141
102
  next_type = next_seg.get("type", "")
142
-
143
- # 在特定类型的消息段之间添加空格
144
- # 当前段和下一段都是文本
103
+
145
104
  if seg_type == "text" and next_type == "text":
146
105
  result.append({"type": "text", "data": {"text": " "}})
147
- # 当前段是 @,下一段是文本(且文本不以空格开头)
148
106
  elif seg_type == "at" and next_type == "text":
149
107
  next_text = next_seg.get("data", {}).get("text", "")
150
108
  if next_text and not next_text.startswith(" "):
151
109
  result.append({"type": "text", "data": {"text": " "}})
152
- # 当前段是文本(且不以空格结尾),下一段是 @
153
110
  elif seg_type == "text" and next_type == "at":
154
111
  current_text = segment.get("data", {}).get("text", "")
155
112
  if current_text and not current_text.endswith(" "):
156
113
  result.append({"type": "text", "data": {"text": " "}})
157
-
158
- # 替换原数组
114
+
159
115
  message_list.clear()
160
116
  message_list.extend(result)
161
117
 
162
- def _send(self, message_array: List[Dict]):
163
- """
164
- 发送消息(使用数组格式)
165
-
166
- :param message_array: OneBot11 消息段数组
167
- :return: asyncio.Task
168
- """
169
- # 添加链式修饰
170
- if self._at_user_ids or self._at_all or self._reply_message_id:
171
- message_array = self._build_message_array(message_array)
172
- else:
173
- # 没有链式修饰时,也需要添加分隔符
174
- self._insert_text_separators(message_array)
175
-
176
- return asyncio.create_task(
177
- self._adapter.call_api(
178
- endpoint="send_msg",
179
- account_id=self._account_id,
180
- message_type="private" if self._target_type == "user" else "group",
181
- user_id=self._target_id if self._target_type == "user" else None,
182
- group_id=self._target_id if self._target_type == "group" else None,
183
- message=message_array
184
- )
185
- )
118
+ def _reset_modifiers(self):
119
+ self._at_user_ids = []
120
+ self._reply_message_id = None
121
+ self._at_all = False
122
+
123
+ # ============ 标准发送方法(委托给 Raw_ob12) ============
186
124
 
187
125
  def Text(self, text: str):
188
- """发送文本消息"""
189
- return self._send([{"type": "text", "data": {"text": text}}])
126
+ return self.Raw_ob12([{"type": "text", "data": {"text": text}}])
190
127
 
191
128
  def Image(self, file: Union[str, bytes], filename: str = "image.png"):
192
- """发送图片"""
193
- return self._send_media("image", file, filename)
129
+ return self.Raw_ob12(
130
+ [{"type": "image", "data": {"file": file, "file_name": filename}}]
131
+ )
194
132
 
195
133
  def Voice(self, file: Union[str, bytes], filename: str = "voice.amr"):
196
- """发送语音"""
197
- return self._send_media("record", file, filename)
134
+ return self.Raw_ob12(
135
+ [{"type": "audio", "data": {"file": file, "file_name": filename}}]
136
+ )
198
137
 
199
138
  def Video(self, file: Union[str, bytes], filename: str = "video.mp4"):
200
- """发送视频"""
201
- return self._send_media("video", file, filename)
139
+ return self.Raw_ob12(
140
+ [{"type": "video", "data": {"file": file, "file_name": filename}}]
141
+ )
202
142
 
203
143
  def Face(self, id: Union[str, int]):
204
- """发送表情"""
205
- return self._send([{"type": "face", "data": {"id": str(id)}}])
144
+ return self.Raw_ob12([{"type": "face", "data": {"id": str(id)}}])
145
+
146
+ def File(self, file: Union[str, bytes], filename: str = "file.dat"):
147
+ return self.Raw_ob12(
148
+ [{"type": "file", "data": {"file": file, "file_name": filename}}]
149
+ )
206
150
 
207
151
  def Raw_ob12(self, message, **kwargs):
208
- """
209
- 发送原始 OneBot12 格式消息
210
-
211
- 将 OneBot12 格式转换为 OneBot11 格式发送
212
-
213
- :param message: OneBot12 消息段或消息段数组
214
- :param kwargs: 额外参数
215
- :return: asyncio.Task
216
- """
217
- # 处理单条消息段的情况
218
152
  if isinstance(message, dict):
219
153
  message = [message]
220
-
221
- # 转换为 OneBot11 格式
154
+
222
155
  ob11_message = self._convert_ob12_to_ob11(message)
223
-
224
- # 添加链式修饰
156
+
225
157
  if self._at_user_ids or self._at_all or self._reply_message_id:
226
158
  ob11_message = self._build_message_array(ob11_message)
227
159
  else:
228
- # 没有链式修饰时,也需要添加分隔符
229
160
  self._insert_text_separators(ob11_message)
230
-
161
+
162
+ self._reset_modifiers()
163
+
231
164
  return asyncio.create_task(
232
165
  self._adapter.call_api(
233
166
  endpoint="send_msg",
@@ -236,255 +169,99 @@ class OneBotAdapter(sdk.BaseAdapter):
236
169
  user_id=self._target_id if self._target_type == "user" else None,
237
170
  group_id=self._target_id if self._target_type == "group" else None,
238
171
  message=ob11_message,
239
- **kwargs
172
+ **kwargs,
240
173
  )
241
174
  )
242
175
 
243
176
  def At(self, user_id: Union[str, int], name: str = None):
244
- """
245
- @用户(可多次调用)
246
- :param user_id: 用户ID
247
- :param name: 自定义@名称(可选)
248
- :return: self,支持链式调用
249
- """
250
177
  self._at_user_ids.append({"qq": str(user_id), "name": name})
251
178
  return self
252
179
 
253
180
  def AtAll(self):
254
- """
255
- @全体成员
256
- :return: self,支持链式调用
257
- """
258
181
  self._at_all = True
259
182
  return self
260
183
 
261
184
  def Reply(self, message_id: Union[str, int]):
262
- """
263
- 回复消息
264
- :param message_id: 消息ID
265
- :return: self,支持链式调用
266
- """
267
185
  self._reply_message_id = str(message_id)
268
186
  return self
269
187
 
270
188
  def Recall(self, message_id: Union[str, int]):
271
- """撤回消息"""
272
189
  return asyncio.create_task(
273
190
  self._adapter.call_api(
274
191
  endpoint="delete_msg",
275
192
  account_id=self._account_id,
276
- message_id=message_id
193
+ message_id=message_id,
277
194
  )
278
195
  )
279
196
 
280
- def File(self, file: Union[str, bytes], filename: str = "file.dat"):
281
- """
282
- 发送文件
283
-
284
- 使用 OneBot11 的 file 消息段发送文件
285
-
286
- :param file: 文件内容(bytes)或路径/URL(str)
287
- :param filename: 文件名
288
- :return: asyncio.Task
289
- """
290
- if isinstance(file, bytes):
291
- # bytes 类型,优先尝试 base64 方式
292
- try:
293
- b64_data = base64.b64encode(file).decode('utf-8')
294
- return self._send([{
295
- "type": "file",
296
- "data": {
297
- "file": f"base64://{b64_data}",
298
- "name": filename
299
- }
300
- }])
301
- except Exception as e:
302
- self._adapter.logger.warning(f"Base64发送文件失败: {str(e)}")
303
- # 创建临时文件
304
- temp_dir = os.path.join(tempfile.gettempdir(), "onebot_media")
305
- os.makedirs(temp_dir, exist_ok=True)
306
- unique_filename = f"{uuid.uuid4().hex}_{filename}"
307
- filepath = os.path.join(temp_dir, unique_filename)
308
-
309
- try:
310
- with open(filepath, "wb") as f:
311
- f.write(file)
312
-
313
- task = self._send([{
314
- "type": "file",
315
- "data": {
316
- "file": filepath,
317
- "name": filename
318
- }
319
- }])
320
-
321
- # 延迟删除,确保发送完成
322
- async def delayed_cleanup():
323
- await asyncio.sleep(1)
324
- try:
325
- os.remove(filepath)
326
- except Exception:
327
- pass
328
- asyncio.create_task(delayed_cleanup())
329
-
330
- return task
331
- except Exception:
332
- pass
333
- else:
334
- # 路径或 URL,直接发送
335
- return self._send([{
336
- "type": "file",
337
- "data": {
338
- "file": file,
339
- "name": filename
340
- }
341
- }])
342
-
343
197
  def _convert_ob12_to_ob11(self, message: List[Dict]) -> List[Dict]:
344
198
  """
345
199
  将 OneBot12 消息段数组转换为 OneBot11 格式
346
-
200
+
347
201
  :param message: OneBot12 消息段数组
348
202
  :return: OneBot11 消息段数组
349
203
  """
350
204
  ob11_message = []
351
-
205
+
352
206
  for segment in message:
353
207
  seg_type = segment.get("type", "")
354
208
  seg_data = segment.get("data", {})
355
-
209
+
356
210
  # 文本消息
357
211
  if seg_type == "text":
358
- ob11_message.append({
359
- "type": "text",
360
- "data": {"text": seg_data.get("text", "")}
361
- })
362
-
212
+ ob11_message.append(
213
+ {"type": "text", "data": {"text": seg_data.get("text", "")}}
214
+ )
215
+
363
216
  # 图片
364
217
  elif seg_type == "image":
365
218
  file = seg_data.get("file") or seg_data.get("url", "")
366
- ob11_message.append({
367
- "type": "image",
368
- "data": {"file": file}
369
- })
370
-
219
+ ob11_message.append({"type": "image", "data": {"file": file}})
220
+
371
221
  # 语音/音频
372
222
  elif seg_type == "audio" or seg_type == "record":
373
223
  file = seg_data.get("file") or seg_data.get("url", "")
374
- ob11_message.append({
375
- "type": "record",
376
- "data": {"file": file}
377
- })
378
-
224
+ ob11_message.append({"type": "record", "data": {"file": file}})
225
+
379
226
  # 视频
380
227
  elif seg_type == "video":
381
228
  file = seg_data.get("file") or seg_data.get("url", "")
382
- ob11_message.append({
383
- "type": "video",
384
- "data": {"file": file}
385
- })
386
-
229
+ ob11_message.append({"type": "video", "data": {"file": file}})
230
+
231
+ # 文件
232
+ elif seg_type == "file":
233
+ file = seg_data.get("file") or seg_data.get("url", "")
234
+ file_name = seg_data.get("file_name", "")
235
+ data = {"file": file}
236
+ if file_name:
237
+ data["name"] = file_name
238
+ ob11_message.append({"type": "file", "data": data})
239
+
387
240
  # 表情
388
241
  elif seg_type == "face":
389
242
  face_id = seg_data.get("id", "")
390
- ob11_message.append({
391
- "type": "face",
392
- "data": {"id": face_id}
393
- })
394
-
243
+ ob11_message.append({"type": "face", "data": {"id": face_id}})
244
+
395
245
  # @用户(mention)
396
246
  elif seg_type == "mention":
397
247
  user_id = seg_data.get("user_id", "")
398
- ob11_message.append({
399
- "type": "at",
400
- "data": {"qq": str(user_id)}
401
- })
402
-
248
+ ob11_message.append({"type": "at", "data": {"qq": str(user_id)}})
249
+
403
250
  # 回复
404
251
  elif seg_type == "reply":
405
252
  msg_id = seg_data.get("message_id", "")
406
- ob11_message.append({
407
- "type": "reply",
408
- "data": {"id": msg_id}
409
- })
410
-
253
+ ob11_message.append({"type": "reply", "data": {"id": msg_id}})
254
+
411
255
  # OneBot11 扩展消息段(直接保留)
412
256
  elif seg_type.startswith("onebot11_"):
413
257
  cq_type = seg_type[10:] # 去掉 onebot11_ 前缀
414
- ob11_message.append({
415
- "type": cq_type,
416
- "data": seg_data
417
- })
418
-
258
+ ob11_message.append({"type": cq_type, "data": seg_data})
259
+
419
260
  # 其他未知类型,直接保留
420
261
  else:
421
- ob11_message.append({
422
- "type": seg_type,
423
- "data": seg_data
424
- })
425
-
426
- return ob11_message
262
+ ob11_message.append({"type": seg_type, "data": seg_data})
427
263
 
428
- def _send_media(self, msg_type: str, file: Union[str, bytes], filename: str):
429
- """
430
- 发送媒体文件
431
-
432
- :param msg_type: 消息类型(image/record/video)
433
- :param file: 文件内容(bytes)或 URL(str)
434
- :param filename: 文件名
435
- :return: asyncio.Task
436
- """
437
- if isinstance(file, bytes):
438
- return self._send_bytes(msg_type, file, filename)
439
- else:
440
- return self._send([{"type": msg_type, "data": {"file": file}}])
441
-
442
- def _send_bytes(self, msg_type: str, data: bytes, filename: str):
443
- """
444
- 发送二进制文件
445
-
446
- :param msg_type: 消息类型
447
- :param data: 二进制数据
448
- :param filename: 文件名
449
- :return: asyncio.Task
450
- """
451
- # 优先尝试 base64 方式
452
- if msg_type in ["image", "record"]:
453
- try:
454
- b64_data = base64.b64encode(data).decode('utf-8')
455
- return self._send([{
456
- "type": msg_type,
457
- "data": {"file": f"base64://{b64_data}"}
458
- }])
459
- except Exception as e:
460
- self._adapter.logger.warning(f"Base64发送失败: {str(e)}")
461
-
462
- # 创建临时文件
463
- temp_dir = os.path.join(tempfile.gettempdir(), "onebot_media")
464
- os.makedirs(temp_dir, exist_ok=True)
465
- unique_filename = f"{uuid.uuid4().hex}_{filename}"
466
- filepath = os.path.join(temp_dir, unique_filename)
467
-
468
- try:
469
- with open(filepath, "wb") as f:
470
- f.write(data)
471
-
472
- return self._send([{
473
- "type": msg_type,
474
- "data": {"file": filepath}
475
- }])
476
- finally:
477
- try:
478
- # 延迟删除,确保发送完成
479
- async def delayed_cleanup():
480
- await asyncio.sleep(1)
481
- try:
482
- os.remove(filepath)
483
- except Exception:
484
- pass
485
- asyncio.create_task(delayed_cleanup())
486
- except Exception:
487
- pass
264
+ return ob11_message
488
265
 
489
266
  def __init__(self, sdk):
490
267
  super().__init__()
@@ -494,18 +271,18 @@ class OneBotAdapter(sdk.BaseAdapter):
494
271
 
495
272
  # 加载配置
496
273
  self.accounts: Dict[str, OneBotAccountConfig] = self._load_account_configs()
497
-
274
+
498
275
  # 连接池 - 每个账户一个连接
499
276
  self._api_response_futures: Dict[str, Dict[str, asyncio.Future]] = {}
500
277
  self.sessions: Dict[str, aiohttp.ClientSession] = {}
501
278
  self.connections: Dict[str, aiohttp.ClientWebSocketResponse] = {}
502
-
279
+
503
280
  # 重连任务
504
281
  self.reconnect_tasks: Dict[str, asyncio.Task] = {}
505
-
282
+
506
283
  # 初始化状态
507
284
  self._is_running = False
508
-
285
+
509
286
  # 默认配置
510
287
  self.default_retry_interval = 30
511
288
  self.default_timeout = 30
@@ -515,16 +292,17 @@ class OneBotAdapter(sdk.BaseAdapter):
515
292
  def _setup_converter(self):
516
293
  """设置转换器"""
517
294
  from .Converter import OneBot11Converter
295
+
518
296
  converter = OneBot11Converter()
519
297
  return converter.convert
520
298
 
521
299
  def _load_account_configs(self) -> Dict[str, OneBotAccountConfig]:
522
300
  """加载多账户配置"""
523
301
  accounts = {}
524
-
302
+
525
303
  # 检查新格式配置
526
304
  account_configs = self.sdk.config.getConfig("OneBotv11_Adapter.accounts", {})
527
-
305
+
528
306
  if not account_configs:
529
307
  # 检查旧格式配置
530
308
  old_config = self.sdk.config.getConfig("OneBotv11_Adapter")
@@ -533,7 +311,7 @@ class OneBotAdapter(sdk.BaseAdapter):
533
311
  mode = old_config.get("mode", "server")
534
312
  server_config = old_config.get("server", {})
535
313
  client_config = old_config.get("client", {})
536
-
314
+
537
315
  account_configs = {
538
316
  "default": {
539
317
  "bot_id": "default",
@@ -542,7 +320,7 @@ class OneBotAdapter(sdk.BaseAdapter):
542
320
  "server_token": server_config.get("token", ""),
543
321
  "client_url": client_config.get("url", "ws://127.0.0.1:3001"),
544
322
  "client_token": client_config.get("token", ""),
545
- "enabled": True
323
+ "enabled": True,
546
324
  }
547
325
  }
548
326
  else:
@@ -556,12 +334,14 @@ class OneBotAdapter(sdk.BaseAdapter):
556
334
  "server_token": "",
557
335
  "client_url": "ws://127.0.0.1:3001",
558
336
  "client_token": "",
559
- "enabled": True
337
+ "enabled": True,
560
338
  }
561
339
  }
562
-
340
+
563
341
  try:
564
- self.sdk.config.setConfig("OneBotv11_Adapter.accounts", default_config)
342
+ self.sdk.config.setConfig(
343
+ "OneBotv11_Adapter.accounts", default_config
344
+ )
565
345
  account_configs = default_config
566
346
  except Exception as e:
567
347
  self.logger.error(f"保存默认配置失败: {str(e)}")
@@ -572,7 +352,7 @@ class OneBotAdapter(sdk.BaseAdapter):
572
352
  if "bot_id" not in config or not config["bot_id"]:
573
353
  self.logger.error(f"账户 {account_name} 缺少bot_id,已跳过")
574
354
  continue
575
-
355
+
576
356
  accounts[account_name] = OneBotAccountConfig(
577
357
  bot_id=config["bot_id"],
578
358
  mode=config.get("mode", "server"),
@@ -581,16 +361,16 @@ class OneBotAdapter(sdk.BaseAdapter):
581
361
  client_url=config.get("client_url", "ws://127.0.0.1:3001"),
582
362
  client_token=config.get("client_token", ""),
583
363
  enabled=config.get("enabled", True),
584
- name=account_name
364
+ name=account_name,
585
365
  )
586
-
366
+
587
367
  self.logger.info(f"OneBot11适配器初始化完成,加载 {len(accounts)} 个账户")
588
368
  return accounts
589
369
 
590
370
  async def call_api(self, endpoint: str, account_id: str = None, **params):
591
371
  """
592
372
  调用 OneBot API
593
-
373
+
594
374
  :param endpoint: API端点
595
375
  :param account_id: 账户名或bot_id
596
376
  :param params: 其他参数
@@ -613,7 +393,7 @@ class OneBotAdapter(sdk.BaseAdapter):
613
393
  break
614
394
  else:
615
395
  raise ValueError(f"找不到账户 {account_id}")
616
-
396
+
617
397
  if not account.enabled:
618
398
  raise ValueError(f"账户 {account_name} 已禁用")
619
399
 
@@ -632,11 +412,7 @@ class OneBotAdapter(sdk.BaseAdapter):
632
412
  future = asyncio.get_event_loop().create_future()
633
413
  self._api_response_futures[account_name][echo] = future
634
414
 
635
- payload = {
636
- "action": endpoint,
637
- "params": params,
638
- "echo": echo
639
- }
415
+ payload = {"action": endpoint, "params": params, "echo": echo}
640
416
 
641
417
  try:
642
418
  await connection.send_str(json.dumps(payload))
@@ -662,7 +438,7 @@ class OneBotAdapter(sdk.BaseAdapter):
662
438
  "message_id": str(raw_response.get("message_id", "")),
663
439
  "message": raw_response.get("message", ""),
664
440
  "onebot_raw": raw_response,
665
- "self": {"user_id": account.bot_id}
441
+ "self": {"user_id": account.bot_id},
666
442
  }
667
443
 
668
444
  if "echo" in params:
@@ -682,7 +458,7 @@ class OneBotAdapter(sdk.BaseAdapter):
682
458
  "message_id": "",
683
459
  "message": f"账户 {account_name} API调用超时: {endpoint}",
684
460
  "onebot_raw": None,
685
- "self": {"user_id": account.bot_id}
461
+ "self": {"user_id": account.bot_id},
686
462
  }
687
463
 
688
464
  if "echo" in params:
@@ -691,9 +467,13 @@ class OneBotAdapter(sdk.BaseAdapter):
691
467
  return timeout_response
692
468
 
693
469
  finally:
470
+
694
471
  async def cleanup():
695
472
  await asyncio.sleep(0.1)
696
- if account_name in self._api_response_futures and echo in self._api_response_futures[account_name]:
473
+ if (
474
+ account_name in self._api_response_futures
475
+ and echo in self._api_response_futures[account_name]
476
+ ):
697
477
  del self._api_response_futures[account_name][echo]
698
478
 
699
479
  asyncio.create_task(cleanup())
@@ -719,8 +499,20 @@ class OneBotAdapter(sdk.BaseAdapter):
719
499
 
720
500
  while self._is_running:
721
501
  try:
722
- self.connections[account_name] = await self.sessions[account_name].ws_connect(url, headers=headers)
723
- self.logger.info(f"账户 {account_name} (bot_id: {account.bot_id}) 连接成功")
502
+ self.connections[account_name] = await self.sessions[
503
+ account_name
504
+ ].ws_connect(url, headers=headers)
505
+ self.logger.info(
506
+ f"账户 {account_name} (bot_id: {account.bot_id}) 连接成功"
507
+ )
508
+ await self.adapter.emit(
509
+ {
510
+ "type": "meta",
511
+ "detail_type": "connect",
512
+ "platform": "onebot11",
513
+ "self": {"platform": "onebot11", "user_id": account.bot_id},
514
+ }
515
+ )
724
516
  asyncio.create_task(self._listen(account_name))
725
517
  return
726
518
  except Exception as e:
@@ -747,12 +539,28 @@ class OneBotAdapter(sdk.BaseAdapter):
747
539
  except Exception as e:
748
540
  self.logger.error(f"账户 {account_name} 监听异常: {str(e)}")
749
541
  finally:
542
+ try:
543
+ await self.adapter.emit(
544
+ {
545
+ "type": "meta",
546
+ "detail_type": "disconnect",
547
+ "platform": "onebot11",
548
+ "self": {
549
+ "platform": "onebot11",
550
+ "user_id": account.bot_id if account else "",
551
+ },
552
+ }
553
+ )
554
+ except Exception:
555
+ pass
750
556
  if account_name in self.connections:
751
557
  del self.connections[account_name]
752
558
 
753
559
  if self._is_running and account.enabled and account.mode == "client":
754
560
  self.logger.info(f"账户 {account_name} 开始重连...")
755
- self.reconnect_tasks[account_name] = asyncio.create_task(self.connect(account_name))
561
+ self.reconnect_tasks[account_name] = asyncio.create_task(
562
+ self.connect(account_name)
563
+ )
756
564
 
757
565
  async def _handle_message(self, raw_msg: str, account_name: str):
758
566
  """处理WebSocket消息"""
@@ -764,7 +572,9 @@ class OneBotAdapter(sdk.BaseAdapter):
764
572
 
765
573
  # 处理API响应
766
574
  if "echo" in data:
767
- future = self._api_response_futures.get(account_name, {}).get(data["echo"])
575
+ future = self._api_response_futures.get(account_name, {}).get(
576
+ data["echo"]
577
+ )
768
578
  if future and not future.done():
769
579
  future.set_result(data)
770
580
  return
@@ -773,7 +583,9 @@ class OneBotAdapter(sdk.BaseAdapter):
773
583
  if hasattr(self.adapter, "emit"):
774
584
  onebot_event = self.convert(data)
775
585
  if onebot_event:
776
- if "self" not in onebot_event or not onebot_event.get("self", {}).get("user_id"):
586
+ if "self" not in onebot_event or not onebot_event.get(
587
+ "self", {}
588
+ ).get("user_id"):
777
589
  onebot_event["self"] = {"user_id": account.bot_id}
778
590
  await self.adapter.emit(onebot_event)
779
591
 
@@ -786,10 +598,24 @@ class OneBotAdapter(sdk.BaseAdapter):
786
598
  """WebSocket连接处理器"""
787
599
  account = self.accounts.get(account_name)
788
600
  if account:
789
- self.logger.info(f"账户 {account_name} (bot_id: {account.bot_id}) 客户端已连接")
601
+ self.logger.info(
602
+ f"账户 {account_name} (bot_id: {account.bot_id}) 客户端已连接"
603
+ )
790
604
 
791
605
  self.connections[account_name] = websocket
792
606
 
607
+ await self.adapter.emit(
608
+ {
609
+ "type": "meta",
610
+ "detail_type": "connect",
611
+ "platform": "onebot11",
612
+ "self": {
613
+ "platform": "onebot11",
614
+ "user_id": account.bot_id if account else "",
615
+ },
616
+ }
617
+ )
618
+
793
619
  try:
794
620
  while True:
795
621
  data = await websocket.receive_text()
@@ -799,6 +625,20 @@ class OneBotAdapter(sdk.BaseAdapter):
799
625
  except Exception as e:
800
626
  self.logger.error(f"账户 {account_name} WebSocket处理异常: {str(e)}")
801
627
  finally:
628
+ try:
629
+ await self.adapter.emit(
630
+ {
631
+ "type": "meta",
632
+ "detail_type": "disconnect",
633
+ "platform": "onebot11",
634
+ "self": {
635
+ "platform": "onebot11",
636
+ "user_id": account.bot_id if account else "",
637
+ },
638
+ }
639
+ )
640
+ except Exception:
641
+ pass
802
642
  if account_name in self.connections:
803
643
  del self.connections[account_name]
804
644
 
@@ -810,7 +650,9 @@ class OneBotAdapter(sdk.BaseAdapter):
810
650
 
811
651
  account = self.accounts[account_name]
812
652
  if account.server_token:
813
- client_token = websocket.headers.get("Authorization", "").replace("Bearer ", "")
653
+ client_token = websocket.headers.get("Authorization", "").replace(
654
+ "Bearer ", ""
655
+ )
814
656
  if not client_token:
815
657
  query = dict(websocket.query_params)
816
658
  client_token = query.get("token", "")
@@ -830,18 +672,20 @@ class OneBotAdapter(sdk.BaseAdapter):
830
672
  def make_ws_handler(name):
831
673
  async def handler(ws):
832
674
  await self._ws_handler(ws, name)
675
+
833
676
  return handler
834
677
 
835
678
  def make_auth_handler(name):
836
679
  async def handler(ws):
837
680
  return await self._auth_handler(ws, name)
681
+
838
682
  return handler
839
683
 
840
684
  router.register_websocket(
841
685
  f"onebot11_{account_name}",
842
686
  path,
843
687
  make_ws_handler(account_name),
844
- auth_handler=make_auth_handler(account_name)
688
+ auth_handler=make_auth_handler(account_name),
845
689
  )
846
690
  self.logger.info(f"已注册账户 {account_name} 的Server路由: {path}")
847
691
 
@@ -849,14 +693,24 @@ class OneBotAdapter(sdk.BaseAdapter):
849
693
  """启动适配器"""
850
694
  self._is_running = True
851
695
 
852
- server_accounts = [name for name, acc in self.accounts.items() if acc.mode == "server" and acc.enabled]
853
- client_accounts = [name for name, acc in self.accounts.items() if acc.mode == "client" and acc.enabled]
696
+ server_accounts = [
697
+ name
698
+ for name, acc in self.accounts.items()
699
+ if acc.mode == "server" and acc.enabled
700
+ ]
701
+ client_accounts = [
702
+ name
703
+ for name, acc in self.accounts.items()
704
+ if acc.mode == "client" and acc.enabled
705
+ ]
854
706
 
855
707
  if server_accounts:
856
708
  await self.register_websocket()
857
709
 
858
710
  for account_name in client_accounts:
859
- self.reconnect_tasks[account_name] = asyncio.create_task(self.connect(account_name))
711
+ self.reconnect_tasks[account_name] = asyncio.create_task(
712
+ self.connect(account_name)
713
+ )
860
714
 
861
715
  enabled_count = len(server_accounts) + len(client_accounts)
862
716
  self.logger.info(f"OneBot11适配器启动完成,共 {enabled_count} 个账户")
@@ -885,4 +739,4 @@ class OneBotAdapter(sdk.BaseAdapter):
885
739
  self.logger.error(f"关闭session失败: {str(e)}")
886
740
  self.sessions.clear()
887
741
 
888
- self.logger.info("OneBot11适配器已关闭")
742
+ self.logger.info("OneBot11适配器已关闭")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ErisPulse-OneBot11Adapter
3
- Version: 3.6.2
3
+ Version: 3.7.0
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.6.2"
3
+ version = "3.7.0"
4
4
  description = "ErisPulse的OneBotV11协议适配模块,异步的OneBot触发器"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"