AstrBot 4.3.3__py3-none-any.whl → 4.5.0__py3-none-any.whl

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 (83) hide show
  1. astrbot/core/agent/mcp_client.py +18 -4
  2. astrbot/core/agent/runners/tool_loop_agent_runner.py +31 -2
  3. astrbot/core/astr_agent_context.py +1 -0
  4. astrbot/core/astrbot_config_mgr.py +23 -51
  5. astrbot/core/config/default.py +139 -14
  6. astrbot/core/conversation_mgr.py +36 -1
  7. astrbot/core/core_lifecycle.py +24 -5
  8. astrbot/core/db/migration/migra_45_to_46.py +44 -0
  9. astrbot/core/db/vec_db/base.py +33 -2
  10. astrbot/core/db/vec_db/faiss_impl/document_storage.py +310 -52
  11. astrbot/core/db/vec_db/faiss_impl/embedding_storage.py +31 -3
  12. astrbot/core/db/vec_db/faiss_impl/vec_db.py +81 -23
  13. astrbot/core/file_token_service.py +6 -1
  14. astrbot/core/initial_loader.py +6 -3
  15. astrbot/core/knowledge_base/chunking/__init__.py +11 -0
  16. astrbot/core/knowledge_base/chunking/base.py +24 -0
  17. astrbot/core/knowledge_base/chunking/fixed_size.py +57 -0
  18. astrbot/core/knowledge_base/chunking/recursive.py +155 -0
  19. astrbot/core/knowledge_base/kb_db_sqlite.py +299 -0
  20. astrbot/core/knowledge_base/kb_helper.py +348 -0
  21. astrbot/core/knowledge_base/kb_mgr.py +287 -0
  22. astrbot/core/knowledge_base/models.py +114 -0
  23. astrbot/core/knowledge_base/parsers/__init__.py +15 -0
  24. astrbot/core/knowledge_base/parsers/base.py +50 -0
  25. astrbot/core/knowledge_base/parsers/markitdown_parser.py +25 -0
  26. astrbot/core/knowledge_base/parsers/pdf_parser.py +100 -0
  27. astrbot/core/knowledge_base/parsers/text_parser.py +41 -0
  28. astrbot/core/knowledge_base/parsers/util.py +13 -0
  29. astrbot/core/knowledge_base/retrieval/__init__.py +16 -0
  30. astrbot/core/knowledge_base/retrieval/hit_stopwords.txt +767 -0
  31. astrbot/core/knowledge_base/retrieval/manager.py +273 -0
  32. astrbot/core/knowledge_base/retrieval/rank_fusion.py +138 -0
  33. astrbot/core/knowledge_base/retrieval/sparse_retriever.py +130 -0
  34. astrbot/core/pipeline/process_stage/method/llm_request.py +61 -21
  35. astrbot/core/pipeline/process_stage/utils.py +80 -0
  36. astrbot/core/pipeline/scheduler.py +1 -1
  37. astrbot/core/platform/astr_message_event.py +8 -7
  38. astrbot/core/platform/manager.py +4 -0
  39. astrbot/core/platform/sources/misskey/misskey_adapter.py +380 -44
  40. astrbot/core/platform/sources/misskey/misskey_api.py +581 -45
  41. astrbot/core/platform/sources/misskey/misskey_event.py +76 -41
  42. astrbot/core/platform/sources/misskey/misskey_utils.py +254 -43
  43. astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_server.py +2 -1
  44. astrbot/core/platform/sources/satori/satori_adapter.py +27 -1
  45. astrbot/core/platform/sources/satori/satori_event.py +270 -77
  46. astrbot/core/platform/sources/webchat/webchat_adapter.py +0 -1
  47. astrbot/core/platform/sources/wecom_ai_bot/WXBizJsonMsgCrypt.py +289 -0
  48. astrbot/core/platform/sources/wecom_ai_bot/__init__.py +17 -0
  49. astrbot/core/platform/sources/wecom_ai_bot/ierror.py +20 -0
  50. astrbot/core/platform/sources/wecom_ai_bot/wecomai_adapter.py +445 -0
  51. astrbot/core/platform/sources/wecom_ai_bot/wecomai_api.py +378 -0
  52. astrbot/core/platform/sources/wecom_ai_bot/wecomai_event.py +149 -0
  53. astrbot/core/platform/sources/wecom_ai_bot/wecomai_queue_mgr.py +148 -0
  54. astrbot/core/platform/sources/wecom_ai_bot/wecomai_server.py +166 -0
  55. astrbot/core/platform/sources/wecom_ai_bot/wecomai_utils.py +199 -0
  56. astrbot/core/provider/manager.py +14 -9
  57. astrbot/core/provider/provider.py +67 -0
  58. astrbot/core/provider/sources/anthropic_source.py +4 -4
  59. astrbot/core/provider/sources/dashscope_source.py +10 -9
  60. astrbot/core/provider/sources/dify_source.py +6 -8
  61. astrbot/core/provider/sources/gemini_embedding_source.py +1 -2
  62. astrbot/core/provider/sources/openai_embedding_source.py +1 -2
  63. astrbot/core/provider/sources/openai_source.py +18 -15
  64. astrbot/core/provider/sources/openai_tts_api_source.py +1 -1
  65. astrbot/core/star/context.py +3 -0
  66. astrbot/core/star/star.py +6 -0
  67. astrbot/core/star/star_manager.py +13 -7
  68. astrbot/core/umop_config_router.py +81 -0
  69. astrbot/core/updator.py +1 -1
  70. astrbot/core/utils/io.py +23 -12
  71. astrbot/dashboard/routes/__init__.py +2 -0
  72. astrbot/dashboard/routes/config.py +137 -9
  73. astrbot/dashboard/routes/knowledge_base.py +1065 -0
  74. astrbot/dashboard/routes/plugin.py +24 -5
  75. astrbot/dashboard/routes/tools.py +14 -0
  76. astrbot/dashboard/routes/update.py +1 -1
  77. astrbot/dashboard/server.py +6 -0
  78. astrbot/dashboard/utils.py +161 -0
  79. {astrbot-4.3.3.dist-info → astrbot-4.5.0.dist-info}/METADATA +91 -55
  80. {astrbot-4.3.3.dist-info → astrbot-4.5.0.dist-info}/RECORD +83 -50
  81. {astrbot-4.3.3.dist-info → astrbot-4.5.0.dist-info}/WHEEL +0 -0
  82. {astrbot-4.3.3.dist-info → astrbot-4.5.0.dist-info}/entry_points.txt +0 -0
  83. {astrbot-4.3.3.dist-info → astrbot-4.5.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ #########################################################################
4
+ # Author: jonyqin
5
+ # Created Time: Thu 11 Sep 2014 01:53:58 PM CST
6
+ # File Name: ierror.py
7
+ # Description:定义错误码含义
8
+ #########################################################################
9
+ WXBizMsgCrypt_OK = 0
10
+ WXBizMsgCrypt_ValidateSignature_Error = -40001
11
+ WXBizMsgCrypt_ParseJson_Error = -40002
12
+ WXBizMsgCrypt_ComputeSignature_Error = -40003
13
+ WXBizMsgCrypt_IllegalAesKey = -40004
14
+ WXBizMsgCrypt_ValidateCorpid_Error = -40005
15
+ WXBizMsgCrypt_EncryptAES_Error = -40006
16
+ WXBizMsgCrypt_DecryptAES_Error = -40007
17
+ WXBizMsgCrypt_IllegalBuffer = -40008
18
+ WXBizMsgCrypt_EncodeBase64_Error = -40009
19
+ WXBizMsgCrypt_DecodeBase64_Error = -40010
20
+ WXBizMsgCrypt_GenReturnJson_Error = -40011
@@ -0,0 +1,445 @@
1
+ """
2
+ 企业微信智能机器人平台适配器
3
+ 基于企业微信智能机器人 API 的消息平台适配器,支持 HTTP 回调
4
+ 参考webchat_adapter.py的队列机制,实现异步消息处理和流式响应
5
+ """
6
+
7
+ import time
8
+ import asyncio
9
+ import uuid
10
+ import hashlib
11
+ import base64
12
+ from typing import Awaitable, Any, Dict, Optional, Callable
13
+
14
+
15
+ from astrbot.api.platform import (
16
+ Platform,
17
+ AstrBotMessage,
18
+ MessageMember,
19
+ MessageType,
20
+ PlatformMetadata,
21
+ )
22
+ from astrbot.api.event import MessageChain
23
+ from astrbot.api.message_components import Plain, At, Image
24
+ from astrbot.api import logger
25
+ from astrbot.core.platform.astr_message_event import MessageSesion
26
+ from ...register import register_platform_adapter
27
+
28
+ from .wecomai_api import (
29
+ WecomAIBotAPIClient,
30
+ WecomAIBotMessageParser,
31
+ WecomAIBotStreamMessageBuilder,
32
+ )
33
+ from .wecomai_event import WecomAIBotMessageEvent
34
+ from .wecomai_server import WecomAIBotServer
35
+ from .wecomai_queue_mgr import wecomai_queue_mgr, WecomAIQueueMgr
36
+ from .wecomai_utils import (
37
+ WecomAIBotConstants,
38
+ format_session_id,
39
+ generate_random_string,
40
+ process_encrypted_image,
41
+ )
42
+
43
+
44
+ class WecomAIQueueListener:
45
+ """企业微信智能机器人队列监听器,参考webchat的QueueListener设计"""
46
+
47
+ def __init__(
48
+ self, queue_mgr: WecomAIQueueMgr, callback: Callable[[dict], Awaitable[None]]
49
+ ) -> None:
50
+ self.queue_mgr = queue_mgr
51
+ self.callback = callback
52
+ self.running_tasks = set()
53
+
54
+ async def listen_to_queue(self, session_id: str):
55
+ """监听特定会话的队列"""
56
+ queue = self.queue_mgr.get_or_create_queue(session_id)
57
+ while True:
58
+ try:
59
+ data = await queue.get()
60
+ await self.callback(data)
61
+ except Exception as e:
62
+ logger.error(f"处理会话 {session_id} 消息时发生错误: {e}")
63
+ break
64
+
65
+ async def run(self):
66
+ """监控新会话队列并启动监听器"""
67
+ monitored_sessions = set()
68
+
69
+ while True:
70
+ # 检查新会话
71
+ current_sessions = set(self.queue_mgr.queues.keys())
72
+ new_sessions = current_sessions - monitored_sessions
73
+
74
+ # 为新会话启动监听器
75
+ for session_id in new_sessions:
76
+ task = asyncio.create_task(self.listen_to_queue(session_id))
77
+ self.running_tasks.add(task)
78
+ task.add_done_callback(self.running_tasks.discard)
79
+ monitored_sessions.add(session_id)
80
+ logger.debug(f"[WecomAI] 为会话启动监听器: {session_id}")
81
+
82
+ # 清理已不存在的会话
83
+ removed_sessions = monitored_sessions - current_sessions
84
+ monitored_sessions -= removed_sessions
85
+
86
+ # 清理过期的待处理响应
87
+ self.queue_mgr.cleanup_expired_responses()
88
+
89
+ await asyncio.sleep(1) # 每秒检查一次新会话
90
+
91
+
92
+ @register_platform_adapter(
93
+ "wecom_ai_bot", "企业微信智能机器人适配器,支持 HTTP 回调接收消息"
94
+ )
95
+ class WecomAIBotAdapter(Platform):
96
+ """企业微信智能机器人适配器"""
97
+
98
+ def __init__(
99
+ self, platform_config: dict, platform_settings: dict, event_queue: asyncio.Queue
100
+ ) -> None:
101
+ super().__init__(event_queue)
102
+
103
+ self.config = platform_config
104
+ self.settings = platform_settings
105
+
106
+ # 初始化配置参数
107
+ self.token = self.config["token"]
108
+ self.encoding_aes_key = self.config["encoding_aes_key"]
109
+ self.port = int(self.config["port"])
110
+ self.host = self.config.get("callback_server_host", "0.0.0.0")
111
+ self.bot_name = self.config.get("wecom_ai_bot_name", "")
112
+ self.initial_respond_text = self.config.get(
113
+ "wecomaibot_init_respond_text", "💭 思考中..."
114
+ )
115
+ self.friend_message_welcome_text = self.config.get(
116
+ "wecomaibot_friend_message_welcome_text", ""
117
+ )
118
+
119
+ # 平台元数据
120
+ self.metadata = PlatformMetadata(
121
+ name="wecom_ai_bot",
122
+ description="企业微信智能机器人适配器,支持 HTTP 回调接收消息",
123
+ id=self.config.get("id", "wecom_ai_bot"),
124
+ )
125
+
126
+ # 初始化 API 客户端
127
+ self.api_client = WecomAIBotAPIClient(self.token, self.encoding_aes_key)
128
+
129
+ # 初始化 HTTP 服务器
130
+ self.server = WecomAIBotServer(
131
+ host=self.host,
132
+ port=self.port,
133
+ api_client=self.api_client,
134
+ message_handler=self._process_message,
135
+ )
136
+
137
+ # 事件循环和关闭信号
138
+ self.shutdown_event = asyncio.Event()
139
+
140
+ # 队列监听器
141
+ self.queue_listener = WecomAIQueueListener(
142
+ wecomai_queue_mgr, self._handle_queued_message
143
+ )
144
+
145
+ async def _handle_queued_message(self, data: dict):
146
+ """处理队列中的消息,类似webchat的callback"""
147
+ try:
148
+ abm = await self.convert_message(data)
149
+ await self.handle_msg(abm)
150
+ except Exception as e:
151
+ logger.error(f"处理队列消息时发生异常: {e}")
152
+
153
+ async def _process_message(
154
+ self, message_data: Dict[str, Any], callback_params: Dict[str, str]
155
+ ) -> Optional[str]:
156
+ """处理接收到的消息
157
+
158
+ Args:
159
+ message_data: 解密后的消息数据
160
+ callback_params: 回调参数 (nonce, timestamp)
161
+
162
+ Returns:
163
+ 加密后的响应消息,无需响应时返回 None
164
+ """
165
+ msgtype = message_data.get("msgtype")
166
+ if not msgtype:
167
+ logger.warning(f"消息类型未知,忽略: {message_data}")
168
+ return None
169
+ session_id = self._extract_session_id(message_data)
170
+ if msgtype in ("text", "image", "mixed"):
171
+ # user sent a text / image / mixed message
172
+ try:
173
+ # create a brand-new unique stream_id for this message session
174
+ stream_id = f"{session_id}_{generate_random_string(10)}"
175
+ await self._enqueue_message(
176
+ message_data, callback_params, stream_id, session_id
177
+ )
178
+ wecomai_queue_mgr.set_pending_response(stream_id, callback_params)
179
+
180
+ resp = WecomAIBotStreamMessageBuilder.make_text_stream(
181
+ stream_id, self.initial_respond_text, False
182
+ )
183
+ return await self.api_client.encrypt_message(
184
+ resp, callback_params["nonce"], callback_params["timestamp"]
185
+ )
186
+ except Exception as e:
187
+ logger.error("处理消息时发生异常: %s", e)
188
+ return None
189
+ elif msgtype == "stream":
190
+ # wechat server is requesting for updates of a stream
191
+ stream_id = message_data["stream"]["id"]
192
+ if not wecomai_queue_mgr.has_back_queue(stream_id):
193
+ logger.error(f"Cannot find back queue for stream_id: {stream_id}")
194
+
195
+ # 返回结束标志,告诉微信服务器流已结束
196
+ end_message = WecomAIBotStreamMessageBuilder.make_text_stream(
197
+ stream_id, "", True
198
+ )
199
+ resp = await self.api_client.encrypt_message(
200
+ end_message,
201
+ callback_params["nonce"],
202
+ callback_params["timestamp"],
203
+ )
204
+ return resp
205
+ queue = wecomai_queue_mgr.get_or_create_back_queue(stream_id)
206
+ if queue.empty():
207
+ logger.debug(
208
+ f"No new messages in back queue for stream_id: {stream_id}"
209
+ )
210
+ return None
211
+
212
+ # aggregate all delta chains in the back queue
213
+ latest_plain_content = ""
214
+ image_base64 = []
215
+ finish = False
216
+ while not queue.empty():
217
+ msg = await queue.get()
218
+ if msg["type"] == "plain":
219
+ latest_plain_content = msg["data"] or ""
220
+ elif msg["type"] == "image":
221
+ image_base64.append(msg["image_data"])
222
+ elif msg["type"] == "end":
223
+ # stream end
224
+ finish = True
225
+ wecomai_queue_mgr.remove_queues(stream_id)
226
+ break
227
+ else:
228
+ pass
229
+ logger.debug(
230
+ f"Aggregated content: {latest_plain_content}, image: {len(image_base64)}, finish: {finish}"
231
+ )
232
+ if latest_plain_content or image_base64:
233
+ msg_items = []
234
+ if finish and image_base64:
235
+ for img_b64 in image_base64:
236
+ # get md5 of image
237
+ img_data = base64.b64decode(img_b64)
238
+ img_md5 = hashlib.md5(img_data).hexdigest()
239
+ msg_items.append(
240
+ {
241
+ "msgtype": WecomAIBotConstants.MSG_TYPE_IMAGE,
242
+ "image": {"base64": img_b64, "md5": img_md5},
243
+ }
244
+ )
245
+ image_base64 = []
246
+
247
+ plain_message = WecomAIBotStreamMessageBuilder.make_mixed_stream(
248
+ stream_id, latest_plain_content, msg_items, finish
249
+ )
250
+ encrypted_message = await self.api_client.encrypt_message(
251
+ plain_message,
252
+ callback_params["nonce"],
253
+ callback_params["timestamp"],
254
+ )
255
+ if encrypted_message:
256
+ logger.debug(
257
+ f"Stream message sent successfully, stream_id: {stream_id}"
258
+ )
259
+ else:
260
+ logger.error("消息加密失败")
261
+ return encrypted_message
262
+ return None
263
+ elif msgtype == "event":
264
+ event = message_data.get("event")
265
+ if event == "enter_chat" and self.friend_message_welcome_text:
266
+ # 用户进入会话,发送欢迎消息
267
+ try:
268
+ resp = WecomAIBotStreamMessageBuilder.make_text(
269
+ self.friend_message_welcome_text
270
+ )
271
+ return await self.api_client.encrypt_message(
272
+ resp,
273
+ callback_params["nonce"],
274
+ callback_params["timestamp"],
275
+ )
276
+ except Exception as e:
277
+ logger.error("处理欢迎消息时发生异常: %s", e)
278
+ return None
279
+ pass
280
+
281
+ def _extract_session_id(self, message_data: Dict[str, Any]) -> str:
282
+ """从消息数据中提取会话ID"""
283
+ user_id = message_data.get("from", {}).get("userid", "default_user")
284
+ return format_session_id("wecomai", user_id)
285
+
286
+ async def _enqueue_message(
287
+ self,
288
+ message_data: Dict[str, Any],
289
+ callback_params: Dict[str, str],
290
+ stream_id: str,
291
+ session_id: str,
292
+ ):
293
+ """将消息放入队列进行异步处理"""
294
+ input_queue = wecomai_queue_mgr.get_or_create_queue(stream_id)
295
+ _ = wecomai_queue_mgr.get_or_create_back_queue(stream_id)
296
+ message_payload = {
297
+ "message_data": message_data,
298
+ "callback_params": callback_params,
299
+ "session_id": session_id,
300
+ "stream_id": stream_id,
301
+ }
302
+ await input_queue.put(message_payload)
303
+ logger.debug(f"[WecomAI] 消息已入队: {stream_id}")
304
+
305
+ async def convert_message(self, payload: dict) -> AstrBotMessage:
306
+ """转换队列中的消息数据为AstrBotMessage,类似webchat的convert_message"""
307
+ message_data = payload["message_data"]
308
+ session_id = payload["session_id"]
309
+ # callback_params = payload["callback_params"] # 保留但暂时不使用
310
+
311
+ # 解析消息内容
312
+ msgtype = message_data.get("msgtype")
313
+ content = ""
314
+ image_base64 = []
315
+
316
+ _img_url_to_process = []
317
+ msg_items = []
318
+
319
+ if msgtype == WecomAIBotConstants.MSG_TYPE_TEXT:
320
+ content = WecomAIBotMessageParser.parse_text_message(message_data)
321
+ elif msgtype == WecomAIBotConstants.MSG_TYPE_IMAGE:
322
+ _img_url_to_process.append(
323
+ WecomAIBotMessageParser.parse_image_message(message_data)
324
+ )
325
+ elif msgtype == WecomAIBotConstants.MSG_TYPE_MIXED:
326
+ # 提取混合消息中的文本内容
327
+ msg_items = WecomAIBotMessageParser.parse_mixed_message(message_data)
328
+ text_parts = []
329
+ for item in msg_items or []:
330
+ if item.get("msgtype") == WecomAIBotConstants.MSG_TYPE_TEXT:
331
+ text_content = item.get("text", {}).get("content", "")
332
+ if text_content:
333
+ text_parts.append(text_content)
334
+ elif item.get("msgtype") == WecomAIBotConstants.MSG_TYPE_IMAGE:
335
+ image_url = item.get("image", {}).get("url", "")
336
+ if image_url:
337
+ _img_url_to_process.append(image_url)
338
+ content = " ".join(text_parts) if text_parts else ""
339
+ else:
340
+ content = f"[{msgtype}消息]"
341
+
342
+ # 并行处理图片下载和解密
343
+ if _img_url_to_process:
344
+ tasks = [
345
+ process_encrypted_image(url, self.encoding_aes_key)
346
+ for url in _img_url_to_process
347
+ ]
348
+ results = await asyncio.gather(*tasks)
349
+ for success, result in results:
350
+ if success:
351
+ image_base64.append(result)
352
+ else:
353
+ logger.error(f"处理加密图片失败: {result}")
354
+
355
+ # 构建 AstrBotMessage
356
+ abm = AstrBotMessage()
357
+ abm.self_id = self.bot_name
358
+ abm.message_str = content or "[未知消息]"
359
+ abm.message_id = str(uuid.uuid4())
360
+ abm.timestamp = int(time.time())
361
+ abm.raw_message = payload
362
+
363
+ # 发送者信息
364
+ abm.sender = MessageMember(
365
+ user_id=message_data.get("from", {}).get("userid", "unknown"),
366
+ nickname=message_data.get("from", {}).get("userid", "unknown"),
367
+ )
368
+
369
+ # 消息类型
370
+ abm.type = (
371
+ MessageType.GROUP_MESSAGE
372
+ if message_data.get("chattype") == "group"
373
+ else MessageType.FRIEND_MESSAGE
374
+ )
375
+ abm.session_id = session_id
376
+
377
+ # 消息内容
378
+ abm.message = []
379
+
380
+ # 处理 At
381
+ if self.bot_name and f"@{self.bot_name}" in abm.message_str:
382
+ abm.message_str = abm.message_str.replace(f"@{self.bot_name}", "").strip()
383
+ abm.message.append(At(qq=self.bot_name, name=self.bot_name))
384
+ abm.message.append(Plain(abm.message_str))
385
+ if image_base64:
386
+ for img_b64 in image_base64:
387
+ abm.message.append(Image.fromBase64(img_b64))
388
+
389
+ logger.debug(f"WecomAIAdapter: {abm.message}")
390
+ return abm
391
+
392
+ async def send_by_session(
393
+ self, session: MessageSesion, message_chain: MessageChain
394
+ ):
395
+ """通过会话发送消息"""
396
+ # 企业微信智能机器人主要通过回调响应,这里记录日志
397
+ logger.info("会话发送消息: %s -> %s", session.session_id, message_chain)
398
+ await super().send_by_session(session, message_chain)
399
+
400
+ def run(self) -> Awaitable[Any]:
401
+ """运行适配器,同时启动HTTP服务器和队列监听器"""
402
+ logger.info("启动企业微信智能机器人适配器,监听 %s:%d", self.host, self.port)
403
+
404
+ async def run_both():
405
+ # 同时运行HTTP服务器和队列监听器
406
+ await asyncio.gather(
407
+ self.server.start_server(),
408
+ self.queue_listener.run(),
409
+ )
410
+
411
+ return run_both()
412
+
413
+ async def terminate(self):
414
+ """终止适配器"""
415
+ logger.info("企业微信智能机器人适配器正在关闭...")
416
+ self.shutdown_event.set()
417
+ await self.server.shutdown()
418
+
419
+ def meta(self) -> PlatformMetadata:
420
+ """获取平台元数据"""
421
+ return self.metadata
422
+
423
+ async def handle_msg(self, message: AstrBotMessage):
424
+ """处理消息,创建消息事件并提交到事件队列"""
425
+ try:
426
+ message_event = WecomAIBotMessageEvent(
427
+ message_str=message.message_str,
428
+ message_obj=message,
429
+ platform_meta=self.meta(),
430
+ session_id=message.session_id,
431
+ api_client=self.api_client,
432
+ )
433
+
434
+ self.commit_event(message_event)
435
+
436
+ except Exception as e:
437
+ logger.error("处理消息时发生异常: %s", e)
438
+
439
+ def get_client(self) -> WecomAIBotAPIClient:
440
+ """获取 API 客户端"""
441
+ return self.api_client
442
+
443
+ def get_server(self) -> WecomAIBotServer:
444
+ """获取 HTTP 服务器实例"""
445
+ return self.server