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.
- {erispulse_onebot11adapter-3.6.2 → erispulse_onebot11adapter-3.7.0}/ErisPulse_OneBot11Adapter.egg-info/PKG-INFO +1 -1
- {erispulse_onebot11adapter-3.6.2 → erispulse_onebot11adapter-3.7.0}/OneBotAdapter/Core.py +199 -345
- {erispulse_onebot11adapter-3.6.2 → erispulse_onebot11adapter-3.7.0}/PKG-INFO +1 -1
- {erispulse_onebot11adapter-3.6.2 → erispulse_onebot11adapter-3.7.0}/pyproject.toml +1 -1
- {erispulse_onebot11adapter-3.6.2 → erispulse_onebot11adapter-3.7.0}/ErisPulse_OneBot11Adapter.egg-info/SOURCES.txt +0 -0
- {erispulse_onebot11adapter-3.6.2 → erispulse_onebot11adapter-3.7.0}/ErisPulse_OneBot11Adapter.egg-info/dependency_links.txt +0 -0
- {erispulse_onebot11adapter-3.6.2 → erispulse_onebot11adapter-3.7.0}/ErisPulse_OneBot11Adapter.egg-info/entry_points.txt +0 -0
- {erispulse_onebot11adapter-3.6.2 → erispulse_onebot11adapter-3.7.0}/ErisPulse_OneBot11Adapter.egg-info/requires.txt +0 -0
- {erispulse_onebot11adapter-3.6.2 → erispulse_onebot11adapter-3.7.0}/ErisPulse_OneBot11Adapter.egg-info/top_level.txt +0 -0
- {erispulse_onebot11adapter-3.6.2 → erispulse_onebot11adapter-3.7.0}/LICENSE +0 -0
- {erispulse_onebot11adapter-3.6.2 → erispulse_onebot11adapter-3.7.0}/OneBotAdapter/Converter.py +0 -0
- {erispulse_onebot11adapter-3.6.2 → erispulse_onebot11adapter-3.7.0}/OneBotAdapter/__init__.py +0 -0
- {erispulse_onebot11adapter-3.6.2 → erispulse_onebot11adapter-3.7.0}/README.md +0 -0
- {erispulse_onebot11adapter-3.6.2 → erispulse_onebot11adapter-3.7.0}/setup.cfg +0 -0
- {erispulse_onebot11adapter-3.6.2 → erispulse_onebot11adapter-3.7.0}/test/test.py +0 -0
|
@@ -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
|
|
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
|
-
|
|
57
|
+
if kind.mime.startswith("image/"):
|
|
58
|
+
return "image"
|
|
59
|
+
elif kind.mime.startswith("audio/"):
|
|
68
60
|
return "record"
|
|
69
|
-
elif kind.mime.startswith(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
384
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
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[
|
|
723
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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 = [
|
|
853
|
-
|
|
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(
|
|
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适配器已关闭")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{erispulse_onebot11adapter-3.6.2 → erispulse_onebot11adapter-3.7.0}/OneBotAdapter/Converter.py
RENAMED
|
File without changes
|
{erispulse_onebot11adapter-3.6.2 → erispulse_onebot11adapter-3.7.0}/OneBotAdapter/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|