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.
- {erispulse_onebot11adapter-3.7.0 → erispulse_onebot11adapter-3.7.1}/ErisPulse_OneBot11Adapter.egg-info/PKG-INFO +1 -1
- {erispulse_onebot11adapter-3.7.0 → erispulse_onebot11adapter-3.7.1}/OneBotAdapter/Core.py +325 -126
- {erispulse_onebot11adapter-3.7.0 → erispulse_onebot11adapter-3.7.1}/PKG-INFO +1 -1
- {erispulse_onebot11adapter-3.7.0 → erispulse_onebot11adapter-3.7.1}/pyproject.toml +1 -1
- {erispulse_onebot11adapter-3.7.0 → erispulse_onebot11adapter-3.7.1}/ErisPulse_OneBot11Adapter.egg-info/SOURCES.txt +0 -0
- {erispulse_onebot11adapter-3.7.0 → erispulse_onebot11adapter-3.7.1}/ErisPulse_OneBot11Adapter.egg-info/dependency_links.txt +0 -0
- {erispulse_onebot11adapter-3.7.0 → erispulse_onebot11adapter-3.7.1}/ErisPulse_OneBot11Adapter.egg-info/entry_points.txt +0 -0
- {erispulse_onebot11adapter-3.7.0 → erispulse_onebot11adapter-3.7.1}/ErisPulse_OneBot11Adapter.egg-info/requires.txt +0 -0
- {erispulse_onebot11adapter-3.7.0 → erispulse_onebot11adapter-3.7.1}/ErisPulse_OneBot11Adapter.egg-info/top_level.txt +0 -0
- {erispulse_onebot11adapter-3.7.0 → erispulse_onebot11adapter-3.7.1}/LICENSE +0 -0
- {erispulse_onebot11adapter-3.7.0 → erispulse_onebot11adapter-3.7.1}/OneBotAdapter/Converter.py +0 -0
- {erispulse_onebot11adapter-3.7.0 → erispulse_onebot11adapter-3.7.1}/OneBotAdapter/__init__.py +0 -0
- {erispulse_onebot11adapter-3.7.0 → erispulse_onebot11adapter-3.7.1}/README.md +0 -0
- {erispulse_onebot11adapter-3.7.0 → erispulse_onebot11adapter-3.7.1}/setup.cfg +0 -0
- {erispulse_onebot11adapter-3.7.0 → erispulse_onebot11adapter-3.7.1}/test/test.py +0 -0
|
@@ -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
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
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
|
-
|
|
163
|
+
# OneBot12 → OneBot11 格式转换
|
|
164
|
+
ob11_segments = self._convert_ob12_to_ob11(message)
|
|
156
165
|
|
|
157
|
-
|
|
158
|
-
|
|
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(
|
|
171
|
+
self._insert_text_separators(ob11_segments)
|
|
172
|
+
ob11_message = ob11_segments
|
|
161
173
|
|
|
162
174
|
self._reset_modifiers()
|
|
163
175
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
endpoint
|
|
167
|
-
account_id
|
|
168
|
-
message_type
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
message
|
|
172
|
-
|
|
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
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
# @用户(mention)
|
|
259
|
+
ob11_message.append(
|
|
260
|
+
{"type": "face", "data": {"id": seg_data.get("id", "")}}
|
|
261
|
+
)
|
|
246
262
|
elif seg_type == "mention":
|
|
247
|
-
|
|
248
|
-
|
|
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
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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:]
|
|
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:
|
|
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(
|
|
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":
|
|
593
|
+
"message_id": message_id,
|
|
439
594
|
"message": raw_response.get("message", ""),
|
|
440
|
-
"
|
|
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(
|
|
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
|
-
"
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
587
|
-
|
|
588
|
-
).
|
|
589
|
-
|
|
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(
|
|
794
|
+
self.logger.info(
|
|
795
|
+
f"账户 {account_name} (bot_id: {account.bot_id}) 客户端断开连接"
|
|
796
|
+
)
|
|
625
797
|
except Exception as e:
|
|
626
|
-
self.logger.error(
|
|
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(
|
|
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(
|
|
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(
|
|
917
|
+
self.logger.error(
|
|
918
|
+
f"关闭账户 {account_name} (bot_id: {account.bot_id}) 连接失败: {str(e)}"
|
|
919
|
+
)
|
|
733
920
|
self.connections.clear()
|
|
734
921
|
|
|
735
|
-
|
|
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(
|
|
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适配器已关闭")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{erispulse_onebot11adapter-3.7.0 → erispulse_onebot11adapter-3.7.1}/OneBotAdapter/Converter.py
RENAMED
|
File without changes
|
{erispulse_onebot11adapter-3.7.0 → erispulse_onebot11adapter-3.7.1}/OneBotAdapter/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|