AstrBot 4.5.1__py3-none-any.whl → 4.5.2__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.
- astrbot/api/__init__.py +10 -11
- astrbot/api/event/__init__.py +5 -6
- astrbot/api/event/filter/__init__.py +37 -36
- astrbot/api/platform/__init__.py +7 -8
- astrbot/api/provider/__init__.py +7 -7
- astrbot/api/star/__init__.py +3 -4
- astrbot/api/util/__init__.py +2 -2
- astrbot/cli/__main__.py +5 -5
- astrbot/cli/commands/__init__.py +3 -3
- astrbot/cli/commands/cmd_conf.py +19 -16
- astrbot/cli/commands/cmd_init.py +3 -2
- astrbot/cli/commands/cmd_plug.py +8 -10
- astrbot/cli/commands/cmd_run.py +5 -6
- astrbot/cli/utils/__init__.py +6 -6
- astrbot/cli/utils/basic.py +14 -14
- astrbot/cli/utils/plugin.py +24 -15
- astrbot/cli/utils/version_comparator.py +10 -12
- astrbot/core/__init__.py +8 -6
- astrbot/core/agent/agent.py +3 -2
- astrbot/core/agent/handoff.py +6 -2
- astrbot/core/agent/hooks.py +9 -6
- astrbot/core/agent/mcp_client.py +50 -15
- astrbot/core/agent/message.py +168 -0
- astrbot/core/agent/response.py +2 -1
- astrbot/core/agent/run_context.py +2 -3
- astrbot/core/agent/runners/base.py +10 -13
- astrbot/core/agent/runners/tool_loop_agent_runner.py +52 -51
- astrbot/core/agent/tool.py +60 -41
- astrbot/core/agent/tool_executor.py +9 -3
- astrbot/core/astr_agent_context.py +3 -1
- astrbot/core/astrbot_config_mgr.py +29 -9
- astrbot/core/config/__init__.py +2 -2
- astrbot/core/config/astrbot_config.py +28 -26
- astrbot/core/config/default.py +4 -6
- astrbot/core/conversation_mgr.py +105 -36
- astrbot/core/core_lifecycle.py +68 -54
- astrbot/core/db/__init__.py +33 -18
- astrbot/core/db/migration/helper.py +12 -10
- astrbot/core/db/migration/migra_3_to_4.py +53 -34
- astrbot/core/db/migration/migra_45_to_46.py +1 -1
- astrbot/core/db/migration/shared_preferences_v3.py +2 -1
- astrbot/core/db/migration/sqlite_v3.py +26 -23
- astrbot/core/db/po.py +27 -18
- astrbot/core/db/sqlite.py +74 -45
- astrbot/core/db/vec_db/base.py +10 -14
- astrbot/core/db/vec_db/faiss_impl/document_storage.py +90 -77
- astrbot/core/db/vec_db/faiss_impl/embedding_storage.py +9 -3
- astrbot/core/db/vec_db/faiss_impl/vec_db.py +36 -31
- astrbot/core/event_bus.py +8 -6
- astrbot/core/file_token_service.py +6 -5
- astrbot/core/initial_loader.py +7 -5
- astrbot/core/knowledge_base/chunking/__init__.py +1 -3
- astrbot/core/knowledge_base/chunking/base.py +1 -0
- astrbot/core/knowledge_base/chunking/fixed_size.py +2 -0
- astrbot/core/knowledge_base/chunking/recursive.py +16 -10
- astrbot/core/knowledge_base/kb_db_sqlite.py +50 -48
- astrbot/core/knowledge_base/kb_helper.py +30 -17
- astrbot/core/knowledge_base/kb_mgr.py +6 -7
- astrbot/core/knowledge_base/models.py +10 -4
- astrbot/core/knowledge_base/parsers/__init__.py +3 -5
- astrbot/core/knowledge_base/parsers/base.py +1 -0
- astrbot/core/knowledge_base/parsers/markitdown_parser.py +2 -1
- astrbot/core/knowledge_base/parsers/pdf_parser.py +2 -1
- astrbot/core/knowledge_base/parsers/text_parser.py +1 -0
- astrbot/core/knowledge_base/parsers/util.py +1 -1
- astrbot/core/knowledge_base/retrieval/__init__.py +6 -8
- astrbot/core/knowledge_base/retrieval/manager.py +17 -14
- astrbot/core/knowledge_base/retrieval/rank_fusion.py +7 -3
- astrbot/core/knowledge_base/retrieval/sparse_retriever.py +11 -5
- astrbot/core/log.py +21 -13
- astrbot/core/message/components.py +123 -217
- astrbot/core/message/message_event_result.py +24 -24
- astrbot/core/persona_mgr.py +20 -11
- astrbot/core/pipeline/__init__.py +7 -7
- astrbot/core/pipeline/content_safety_check/stage.py +13 -9
- astrbot/core/pipeline/content_safety_check/strategies/__init__.py +1 -2
- astrbot/core/pipeline/content_safety_check/strategies/baidu_aip.py +12 -13
- astrbot/core/pipeline/content_safety_check/strategies/keywords.py +1 -0
- astrbot/core/pipeline/content_safety_check/strategies/strategy.py +6 -6
- astrbot/core/pipeline/context.py +4 -1
- astrbot/core/pipeline/context_utils.py +77 -7
- astrbot/core/pipeline/preprocess_stage/stage.py +12 -9
- astrbot/core/pipeline/process_stage/method/llm_request.py +125 -72
- astrbot/core/pipeline/process_stage/method/star_request.py +19 -17
- astrbot/core/pipeline/process_stage/stage.py +13 -10
- astrbot/core/pipeline/process_stage/utils.py +6 -5
- astrbot/core/pipeline/rate_limit_check/stage.py +37 -36
- astrbot/core/pipeline/respond/stage.py +23 -20
- astrbot/core/pipeline/result_decorate/stage.py +31 -23
- astrbot/core/pipeline/scheduler.py +12 -8
- astrbot/core/pipeline/session_status_check/stage.py +12 -8
- astrbot/core/pipeline/stage.py +10 -4
- astrbot/core/pipeline/waking_check/stage.py +24 -18
- astrbot/core/pipeline/whitelist_check/stage.py +10 -7
- astrbot/core/platform/__init__.py +6 -6
- astrbot/core/platform/astr_message_event.py +76 -110
- astrbot/core/platform/astrbot_message.py +11 -13
- astrbot/core/platform/manager.py +16 -15
- astrbot/core/platform/message_session.py +5 -3
- astrbot/core/platform/platform.py +16 -24
- astrbot/core/platform/platform_metadata.py +4 -4
- astrbot/core/platform/register.py +8 -8
- astrbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py +23 -15
- astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py +51 -33
- astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py +42 -27
- astrbot/core/platform/sources/dingtalk/dingtalk_event.py +7 -3
- astrbot/core/platform/sources/discord/client.py +9 -6
- astrbot/core/platform/sources/discord/components.py +18 -14
- astrbot/core/platform/sources/discord/discord_platform_adapter.py +45 -30
- astrbot/core/platform/sources/discord/discord_platform_event.py +38 -30
- astrbot/core/platform/sources/lark/lark_adapter.py +23 -17
- astrbot/core/platform/sources/lark/lark_event.py +21 -14
- astrbot/core/platform/sources/misskey/misskey_adapter.py +107 -67
- astrbot/core/platform/sources/misskey/misskey_api.py +153 -129
- astrbot/core/platform/sources/misskey/misskey_event.py +20 -15
- astrbot/core/platform/sources/misskey/misskey_utils.py +74 -62
- astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py +63 -44
- astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py +41 -26
- astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_adapter.py +36 -17
- astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_event.py +3 -1
- astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_server.py +12 -7
- astrbot/core/platform/sources/satori/satori_adapter.py +56 -38
- astrbot/core/platform/sources/satori/satori_event.py +34 -25
- astrbot/core/platform/sources/slack/client.py +11 -9
- astrbot/core/platform/sources/slack/slack_adapter.py +52 -36
- astrbot/core/platform/sources/slack/slack_event.py +34 -24
- astrbot/core/platform/sources/telegram/tg_adapter.py +38 -18
- astrbot/core/platform/sources/telegram/tg_event.py +32 -18
- astrbot/core/platform/sources/webchat/webchat_adapter.py +27 -17
- astrbot/core/platform/sources/webchat/webchat_event.py +14 -10
- astrbot/core/platform/sources/wechatpadpro/wechatpadpro_adapter.py +115 -120
- astrbot/core/platform/sources/wechatpadpro/wechatpadpro_message_event.py +9 -8
- astrbot/core/platform/sources/wechatpadpro/xml_data_parser.py +15 -16
- astrbot/core/platform/sources/wecom/wecom_adapter.py +35 -18
- astrbot/core/platform/sources/wecom/wecom_event.py +55 -48
- astrbot/core/platform/sources/wecom/wecom_kf.py +34 -44
- astrbot/core/platform/sources/wecom/wecom_kf_message.py +26 -10
- astrbot/core/platform/sources/wecom_ai_bot/WXBizJsonMsgCrypt.py +18 -10
- astrbot/core/platform/sources/wecom_ai_bot/__init__.py +3 -5
- astrbot/core/platform/sources/wecom_ai_bot/ierror.py +0 -1
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_adapter.py +61 -37
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_api.py +67 -28
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_event.py +8 -9
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_queue_mgr.py +18 -9
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_server.py +14 -12
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_utils.py +22 -12
- astrbot/core/platform/sources/weixin_official_account/weixin_offacc_adapter.py +40 -26
- astrbot/core/platform/sources/weixin_official_account/weixin_offacc_event.py +47 -45
- astrbot/core/platform_message_history_mgr.py +5 -3
- astrbot/core/provider/__init__.py +2 -3
- astrbot/core/provider/entites.py +8 -8
- astrbot/core/provider/entities.py +61 -75
- astrbot/core/provider/func_tool_manager.py +59 -55
- astrbot/core/provider/manager.py +32 -22
- astrbot/core/provider/provider.py +72 -46
- astrbot/core/provider/register.py +7 -7
- astrbot/core/provider/sources/anthropic_source.py +48 -30
- astrbot/core/provider/sources/azure_tts_source.py +17 -13
- astrbot/core/provider/sources/coze_api_client.py +27 -17
- astrbot/core/provider/sources/coze_source.py +104 -87
- astrbot/core/provider/sources/dashscope_source.py +18 -11
- astrbot/core/provider/sources/dashscope_tts.py +36 -23
- astrbot/core/provider/sources/dify_source.py +25 -20
- astrbot/core/provider/sources/edge_tts_source.py +21 -17
- astrbot/core/provider/sources/fishaudio_tts_api_source.py +22 -14
- astrbot/core/provider/sources/gemini_embedding_source.py +12 -13
- astrbot/core/provider/sources/gemini_source.py +72 -58
- astrbot/core/provider/sources/gemini_tts_source.py +8 -6
- astrbot/core/provider/sources/gsv_selfhosted_source.py +17 -14
- astrbot/core/provider/sources/gsvi_tts_source.py +11 -7
- astrbot/core/provider/sources/minimax_tts_api_source.py +50 -40
- astrbot/core/provider/sources/openai_embedding_source.py +6 -8
- astrbot/core/provider/sources/openai_source.py +77 -69
- astrbot/core/provider/sources/openai_tts_api_source.py +14 -6
- astrbot/core/provider/sources/sensevoice_selfhosted_source.py +13 -11
- astrbot/core/provider/sources/vllm_rerank_source.py +10 -4
- astrbot/core/provider/sources/volcengine_tts.py +38 -31
- astrbot/core/provider/sources/whisper_api_source.py +14 -12
- astrbot/core/provider/sources/whisper_selfhosted_source.py +15 -11
- astrbot/core/provider/sources/xinference_rerank_source.py +16 -8
- astrbot/core/provider/sources/xinference_stt_provider.py +35 -25
- astrbot/core/star/__init__.py +16 -11
- astrbot/core/star/config.py +10 -15
- astrbot/core/star/context.py +97 -75
- astrbot/core/star/filter/__init__.py +4 -3
- astrbot/core/star/filter/command.py +30 -28
- astrbot/core/star/filter/command_group.py +27 -24
- astrbot/core/star/filter/custom_filter.py +6 -5
- astrbot/core/star/filter/event_message_type.py +4 -2
- astrbot/core/star/filter/permission.py +4 -2
- astrbot/core/star/filter/platform_adapter_type.py +4 -2
- astrbot/core/star/filter/regex.py +4 -2
- astrbot/core/star/register/__init__.py +19 -19
- astrbot/core/star/register/star.py +6 -2
- astrbot/core/star/register/star_handler.py +96 -73
- astrbot/core/star/session_llm_manager.py +48 -14
- astrbot/core/star/session_plugin_manager.py +29 -15
- astrbot/core/star/star.py +1 -2
- astrbot/core/star/star_handler.py +13 -8
- astrbot/core/star/star_manager.py +151 -59
- astrbot/core/star/star_tools.py +44 -37
- astrbot/core/star/updator.py +10 -10
- astrbot/core/umop_config_router.py +10 -4
- astrbot/core/updator.py +13 -5
- astrbot/core/utils/astrbot_path.py +3 -5
- astrbot/core/utils/dify_api_client.py +33 -15
- astrbot/core/utils/io.py +66 -42
- astrbot/core/utils/log_pipe.py +1 -1
- astrbot/core/utils/metrics.py +7 -7
- astrbot/core/utils/path_util.py +15 -16
- astrbot/core/utils/pip_installer.py +5 -5
- astrbot/core/utils/session_waiter.py +19 -20
- astrbot/core/utils/shared_preferences.py +45 -20
- astrbot/core/utils/t2i/__init__.py +4 -1
- astrbot/core/utils/t2i/network_strategy.py +35 -26
- astrbot/core/utils/t2i/renderer.py +11 -5
- astrbot/core/utils/t2i/template_manager.py +14 -15
- astrbot/core/utils/tencent_record_helper.py +19 -13
- astrbot/core/utils/version_comparator.py +10 -13
- astrbot/core/zip_updator.py +43 -40
- astrbot/dashboard/routes/__init__.py +18 -18
- astrbot/dashboard/routes/auth.py +10 -8
- astrbot/dashboard/routes/chat.py +30 -21
- astrbot/dashboard/routes/config.py +92 -75
- astrbot/dashboard/routes/conversation.py +46 -39
- astrbot/dashboard/routes/file.py +4 -2
- astrbot/dashboard/routes/knowledge_base.py +47 -40
- astrbot/dashboard/routes/log.py +9 -4
- astrbot/dashboard/routes/persona.py +19 -16
- astrbot/dashboard/routes/plugin.py +69 -55
- astrbot/dashboard/routes/route.py +3 -1
- astrbot/dashboard/routes/session_management.py +130 -116
- astrbot/dashboard/routes/stat.py +34 -34
- astrbot/dashboard/routes/t2i.py +15 -12
- astrbot/dashboard/routes/tools.py +47 -52
- astrbot/dashboard/routes/update.py +32 -28
- astrbot/dashboard/server.py +30 -26
- astrbot/dashboard/utils.py +8 -4
- {astrbot-4.5.1.dist-info → astrbot-4.5.2.dist-info}/METADATA +2 -1
- astrbot-4.5.2.dist-info/RECORD +261 -0
- astrbot-4.5.1.dist-info/RECORD +0 -260
- {astrbot-4.5.1.dist-info → astrbot-4.5.2.dist-info}/WHEEL +0 -0
- {astrbot-4.5.1.dist-info → astrbot-4.5.2.dist-info}/entry_points.txt +0 -0
- {astrbot-4.5.1.dist-info → astrbot-4.5.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,19 +1,20 @@
|
|
|
1
|
-
"""
|
|
2
|
-
企业微信智能机器人 API 客户端
|
|
1
|
+
"""企业微信智能机器人 API 客户端
|
|
3
2
|
处理消息加密解密、API 调用等
|
|
4
3
|
"""
|
|
5
4
|
|
|
6
|
-
import json
|
|
7
5
|
import base64
|
|
8
6
|
import hashlib
|
|
9
|
-
|
|
10
|
-
from
|
|
7
|
+
import json
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
11
10
|
import aiohttp
|
|
11
|
+
from Crypto.Cipher import AES
|
|
12
12
|
|
|
13
|
-
from .WXBizJsonMsgCrypt import WXBizJsonMsgCrypt
|
|
14
|
-
from .wecomai_utils import WecomAIBotConstants
|
|
15
13
|
from astrbot import logger
|
|
16
14
|
|
|
15
|
+
from .wecomai_utils import WecomAIBotConstants
|
|
16
|
+
from .WXBizJsonMsgCrypt import WXBizJsonMsgCrypt
|
|
17
|
+
|
|
17
18
|
|
|
18
19
|
class WecomAIBotAPIClient:
|
|
19
20
|
"""企业微信智能机器人 API 客户端"""
|
|
@@ -24,14 +25,19 @@ class WecomAIBotAPIClient:
|
|
|
24
25
|
Args:
|
|
25
26
|
token: 企业微信机器人 Token
|
|
26
27
|
encoding_aes_key: 消息加密密钥
|
|
28
|
+
|
|
27
29
|
"""
|
|
28
30
|
self.token = token
|
|
29
31
|
self.encoding_aes_key = encoding_aes_key
|
|
30
32
|
self.wxcpt = WXBizJsonMsgCrypt(token, encoding_aes_key, "") # receiveid 为空串
|
|
31
33
|
|
|
32
34
|
async def decrypt_message(
|
|
33
|
-
self,
|
|
34
|
-
|
|
35
|
+
self,
|
|
36
|
+
encrypted_data: bytes,
|
|
37
|
+
msg_signature: str,
|
|
38
|
+
timestamp: str,
|
|
39
|
+
nonce: str,
|
|
40
|
+
) -> tuple[int, dict[str, Any] | None]:
|
|
35
41
|
"""解密企业微信消息
|
|
36
42
|
|
|
37
43
|
Args:
|
|
@@ -42,10 +48,14 @@ class WecomAIBotAPIClient:
|
|
|
42
48
|
|
|
43
49
|
Returns:
|
|
44
50
|
(错误码, 解密后的消息数据字典)
|
|
51
|
+
|
|
45
52
|
"""
|
|
46
53
|
try:
|
|
47
54
|
ret, decrypted_msg = self.wxcpt.DecryptMsg(
|
|
48
|
-
encrypted_data,
|
|
55
|
+
encrypted_data,
|
|
56
|
+
msg_signature,
|
|
57
|
+
timestamp,
|
|
58
|
+
nonce,
|
|
49
59
|
)
|
|
50
60
|
|
|
51
61
|
if ret != WecomAIBotConstants.SUCCESS:
|
|
@@ -70,8 +80,11 @@ class WecomAIBotAPIClient:
|
|
|
70
80
|
return WecomAIBotConstants.DECRYPT_ERROR, None
|
|
71
81
|
|
|
72
82
|
async def encrypt_message(
|
|
73
|
-
self,
|
|
74
|
-
|
|
83
|
+
self,
|
|
84
|
+
plain_message: str,
|
|
85
|
+
nonce: str,
|
|
86
|
+
timestamp: str,
|
|
87
|
+
) -> str | None:
|
|
75
88
|
"""加密消息
|
|
76
89
|
|
|
77
90
|
Args:
|
|
@@ -81,6 +94,7 @@ class WecomAIBotAPIClient:
|
|
|
81
94
|
|
|
82
95
|
Returns:
|
|
83
96
|
加密后的消息,失败时返回 None
|
|
97
|
+
|
|
84
98
|
"""
|
|
85
99
|
try:
|
|
86
100
|
ret, encrypted_msg = self.wxcpt.EncryptMsg(plain_message, nonce, timestamp)
|
|
@@ -97,7 +111,11 @@ class WecomAIBotAPIClient:
|
|
|
97
111
|
return None
|
|
98
112
|
|
|
99
113
|
def verify_url(
|
|
100
|
-
self,
|
|
114
|
+
self,
|
|
115
|
+
msg_signature: str,
|
|
116
|
+
timestamp: str,
|
|
117
|
+
nonce: str,
|
|
118
|
+
echostr: str,
|
|
101
119
|
) -> str:
|
|
102
120
|
"""验证回调 URL
|
|
103
121
|
|
|
@@ -109,10 +127,14 @@ class WecomAIBotAPIClient:
|
|
|
109
127
|
|
|
110
128
|
Returns:
|
|
111
129
|
验证结果字符串
|
|
130
|
+
|
|
112
131
|
"""
|
|
113
132
|
try:
|
|
114
133
|
ret, echo_result = self.wxcpt.VerifyURL(
|
|
115
|
-
msg_signature,
|
|
134
|
+
msg_signature,
|
|
135
|
+
timestamp,
|
|
136
|
+
nonce,
|
|
137
|
+
echostr,
|
|
116
138
|
)
|
|
117
139
|
|
|
118
140
|
if ret != WecomAIBotConstants.SUCCESS:
|
|
@@ -127,8 +149,10 @@ class WecomAIBotAPIClient:
|
|
|
127
149
|
return "verify fail"
|
|
128
150
|
|
|
129
151
|
async def process_encrypted_image(
|
|
130
|
-
self,
|
|
131
|
-
|
|
152
|
+
self,
|
|
153
|
+
image_url: str,
|
|
154
|
+
aes_key_base64: str | None = None,
|
|
155
|
+
) -> tuple[bool, bytes | str]:
|
|
132
156
|
"""下载并解密加密图片
|
|
133
157
|
|
|
134
158
|
Args:
|
|
@@ -137,6 +161,7 @@ class WecomAIBotAPIClient:
|
|
|
137
161
|
|
|
138
162
|
Returns:
|
|
139
163
|
(是否成功, 图片数据或错误信息)
|
|
164
|
+
|
|
140
165
|
"""
|
|
141
166
|
try:
|
|
142
167
|
# 下载图片
|
|
@@ -161,7 +186,7 @@ class WecomAIBotAPIClient:
|
|
|
161
186
|
|
|
162
187
|
# Base64 解码密钥
|
|
163
188
|
aes_key = base64.b64decode(
|
|
164
|
-
aes_key_base64 + "=" * (-len(aes_key_base64) % 4)
|
|
189
|
+
aes_key_base64 + "=" * (-len(aes_key_base64) % 4),
|
|
165
190
|
)
|
|
166
191
|
if len(aes_key) != 32:
|
|
167
192
|
raise ValueError("无效的 AES 密钥长度: 应为 32 字节")
|
|
@@ -183,17 +208,17 @@ class WecomAIBotAPIClient:
|
|
|
183
208
|
return True, decrypted_data
|
|
184
209
|
|
|
185
210
|
except aiohttp.ClientError as e:
|
|
186
|
-
error_msg = f"图片下载失败: {
|
|
211
|
+
error_msg = f"图片下载失败: {e!s}"
|
|
187
212
|
logger.error(error_msg)
|
|
188
213
|
return False, error_msg
|
|
189
214
|
|
|
190
215
|
except ValueError as e:
|
|
191
|
-
error_msg = f"参数错误: {
|
|
216
|
+
error_msg = f"参数错误: {e!s}"
|
|
192
217
|
logger.error(error_msg)
|
|
193
218
|
return False, error_msg
|
|
194
219
|
|
|
195
220
|
except Exception as e:
|
|
196
|
-
error_msg = f"图片处理异常: {
|
|
221
|
+
error_msg = f"图片处理异常: {e!s}"
|
|
197
222
|
logger.error(error_msg)
|
|
198
223
|
return False, error_msg
|
|
199
224
|
|
|
@@ -212,6 +237,7 @@ class WecomAIBotStreamMessageBuilder:
|
|
|
212
237
|
|
|
213
238
|
Returns:
|
|
214
239
|
JSON 格式的流消息字符串
|
|
240
|
+
|
|
215
241
|
"""
|
|
216
242
|
plain = {
|
|
217
243
|
"msgtype": WecomAIBotConstants.MSG_TYPE_STREAM,
|
|
@@ -221,7 +247,9 @@ class WecomAIBotStreamMessageBuilder:
|
|
|
221
247
|
|
|
222
248
|
@staticmethod
|
|
223
249
|
def make_image_stream(
|
|
224
|
-
stream_id: str,
|
|
250
|
+
stream_id: str,
|
|
251
|
+
image_data: bytes,
|
|
252
|
+
finish: bool = False,
|
|
225
253
|
) -> str:
|
|
226
254
|
"""构建图片流消息
|
|
227
255
|
|
|
@@ -232,6 +260,7 @@ class WecomAIBotStreamMessageBuilder:
|
|
|
232
260
|
|
|
233
261
|
Returns:
|
|
234
262
|
JSON 格式的流消息字符串
|
|
263
|
+
|
|
235
264
|
"""
|
|
236
265
|
image_md5 = hashlib.md5(image_data).hexdigest()
|
|
237
266
|
image_base64 = base64.b64encode(image_data).decode("utf-8")
|
|
@@ -245,7 +274,7 @@ class WecomAIBotStreamMessageBuilder:
|
|
|
245
274
|
{
|
|
246
275
|
"msgtype": WecomAIBotConstants.MSG_TYPE_IMAGE,
|
|
247
276
|
"image": {"base64": image_base64, "md5": image_md5},
|
|
248
|
-
}
|
|
277
|
+
},
|
|
249
278
|
],
|
|
250
279
|
},
|
|
251
280
|
}
|
|
@@ -253,7 +282,10 @@ class WecomAIBotStreamMessageBuilder:
|
|
|
253
282
|
|
|
254
283
|
@staticmethod
|
|
255
284
|
def make_mixed_stream(
|
|
256
|
-
stream_id: str,
|
|
285
|
+
stream_id: str,
|
|
286
|
+
content: str,
|
|
287
|
+
msg_items: list,
|
|
288
|
+
finish: bool = False,
|
|
257
289
|
) -> str:
|
|
258
290
|
"""构建混合类型流消息
|
|
259
291
|
|
|
@@ -265,6 +297,7 @@ class WecomAIBotStreamMessageBuilder:
|
|
|
265
297
|
|
|
266
298
|
Returns:
|
|
267
299
|
JSON 格式的流消息字符串
|
|
300
|
+
|
|
268
301
|
"""
|
|
269
302
|
plain = {
|
|
270
303
|
"msgtype": WecomAIBotConstants.MSG_TYPE_STREAM,
|
|
@@ -283,6 +316,7 @@ class WecomAIBotStreamMessageBuilder:
|
|
|
283
316
|
|
|
284
317
|
Returns:
|
|
285
318
|
JSON 格式的文本消息字符串
|
|
319
|
+
|
|
286
320
|
"""
|
|
287
321
|
plain = {"msgtype": "text", "text": {"content": content}}
|
|
288
322
|
return json.dumps(plain, ensure_ascii=False)
|
|
@@ -292,7 +326,7 @@ class WecomAIBotMessageParser:
|
|
|
292
326
|
"""企业微信智能机器人消息解析器"""
|
|
293
327
|
|
|
294
328
|
@staticmethod
|
|
295
|
-
def parse_text_message(data:
|
|
329
|
+
def parse_text_message(data: dict[str, Any]) -> str | None:
|
|
296
330
|
"""解析文本消息
|
|
297
331
|
|
|
298
332
|
Args:
|
|
@@ -300,6 +334,7 @@ class WecomAIBotMessageParser:
|
|
|
300
334
|
|
|
301
335
|
Returns:
|
|
302
336
|
文本内容,解析失败返回 None
|
|
337
|
+
|
|
303
338
|
"""
|
|
304
339
|
try:
|
|
305
340
|
return data.get("text", {}).get("content")
|
|
@@ -308,7 +343,7 @@ class WecomAIBotMessageParser:
|
|
|
308
343
|
return None
|
|
309
344
|
|
|
310
345
|
@staticmethod
|
|
311
|
-
def parse_image_message(data:
|
|
346
|
+
def parse_image_message(data: dict[str, Any]) -> str | None:
|
|
312
347
|
"""解析图片消息
|
|
313
348
|
|
|
314
349
|
Args:
|
|
@@ -316,6 +351,7 @@ class WecomAIBotMessageParser:
|
|
|
316
351
|
|
|
317
352
|
Returns:
|
|
318
353
|
图片 URL,解析失败返回 None
|
|
354
|
+
|
|
319
355
|
"""
|
|
320
356
|
try:
|
|
321
357
|
return data.get("image", {}).get("url")
|
|
@@ -324,7 +360,7 @@ class WecomAIBotMessageParser:
|
|
|
324
360
|
return None
|
|
325
361
|
|
|
326
362
|
@staticmethod
|
|
327
|
-
def parse_stream_message(data:
|
|
363
|
+
def parse_stream_message(data: dict[str, Any]) -> dict[str, Any] | None:
|
|
328
364
|
"""解析流消息
|
|
329
365
|
|
|
330
366
|
Args:
|
|
@@ -332,6 +368,7 @@ class WecomAIBotMessageParser:
|
|
|
332
368
|
|
|
333
369
|
Returns:
|
|
334
370
|
流消息数据,解析失败返回 None
|
|
371
|
+
|
|
335
372
|
"""
|
|
336
373
|
try:
|
|
337
374
|
stream_data = data.get("stream", {})
|
|
@@ -346,7 +383,7 @@ class WecomAIBotMessageParser:
|
|
|
346
383
|
return None
|
|
347
384
|
|
|
348
385
|
@staticmethod
|
|
349
|
-
def parse_mixed_message(data:
|
|
386
|
+
def parse_mixed_message(data: dict[str, Any]) -> list | None:
|
|
350
387
|
"""解析混合消息
|
|
351
388
|
|
|
352
389
|
Args:
|
|
@@ -354,6 +391,7 @@ class WecomAIBotMessageParser:
|
|
|
354
391
|
|
|
355
392
|
Returns:
|
|
356
393
|
消息项列表,解析失败返回 None
|
|
394
|
+
|
|
357
395
|
"""
|
|
358
396
|
try:
|
|
359
397
|
return data.get("mixed", {}).get("msg_item", [])
|
|
@@ -362,7 +400,7 @@ class WecomAIBotMessageParser:
|
|
|
362
400
|
return None
|
|
363
401
|
|
|
364
402
|
@staticmethod
|
|
365
|
-
def parse_event_message(data:
|
|
403
|
+
def parse_event_message(data: dict[str, Any]) -> dict[str, Any] | None:
|
|
366
404
|
"""解析事件消息
|
|
367
405
|
|
|
368
406
|
Args:
|
|
@@ -370,6 +408,7 @@ class WecomAIBotMessageParser:
|
|
|
370
408
|
|
|
371
409
|
Returns:
|
|
372
410
|
事件数据,解析失败返回 None
|
|
411
|
+
|
|
373
412
|
"""
|
|
374
413
|
try:
|
|
375
414
|
return data.get("event", {})
|
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
"""
|
|
2
|
-
企业微信智能机器人事件处理模块,处理消息事件的发送和接收
|
|
3
|
-
"""
|
|
1
|
+
"""企业微信智能机器人事件处理模块,处理消息事件的发送和接收"""
|
|
4
2
|
|
|
3
|
+
from astrbot.api import logger
|
|
5
4
|
from astrbot.api.event import AstrMessageEvent, MessageChain
|
|
6
5
|
from astrbot.api.message_components import (
|
|
7
6
|
Image,
|
|
8
7
|
Plain,
|
|
9
8
|
)
|
|
10
|
-
from astrbot.api import logger
|
|
11
9
|
|
|
12
10
|
from .wecomai_api import WecomAIBotAPIClient
|
|
13
11
|
from .wecomai_queue_mgr import wecomai_queue_mgr
|
|
@@ -32,6 +30,7 @@ class WecomAIBotMessageEvent(AstrMessageEvent):
|
|
|
32
30
|
platform_meta: 平台元数据
|
|
33
31
|
session_id: 会话 ID
|
|
34
32
|
api_client: API 客户端
|
|
33
|
+
|
|
35
34
|
"""
|
|
36
35
|
super().__init__(message_str, message_obj, platform_meta, session_id)
|
|
37
36
|
self.api_client = api_client
|
|
@@ -50,7 +49,7 @@ class WecomAIBotMessageEvent(AstrMessageEvent):
|
|
|
50
49
|
"type": "end",
|
|
51
50
|
"data": "",
|
|
52
51
|
"streaming": False,
|
|
53
|
-
}
|
|
52
|
+
},
|
|
54
53
|
)
|
|
55
54
|
return ""
|
|
56
55
|
|
|
@@ -64,7 +63,7 @@ class WecomAIBotMessageEvent(AstrMessageEvent):
|
|
|
64
63
|
"data": data,
|
|
65
64
|
"streaming": streaming,
|
|
66
65
|
"session_id": stream_id,
|
|
67
|
-
}
|
|
66
|
+
},
|
|
68
67
|
)
|
|
69
68
|
elif isinstance(comp, Image):
|
|
70
69
|
# 处理图片消息
|
|
@@ -77,7 +76,7 @@ class WecomAIBotMessageEvent(AstrMessageEvent):
|
|
|
77
76
|
"image_data": image_base64,
|
|
78
77
|
"streaming": streaming,
|
|
79
78
|
"session_id": stream_id,
|
|
80
|
-
}
|
|
79
|
+
},
|
|
81
80
|
)
|
|
82
81
|
else:
|
|
83
82
|
logger.warning("图片数据为空,跳过")
|
|
@@ -127,7 +126,7 @@ class WecomAIBotMessageEvent(AstrMessageEvent):
|
|
|
127
126
|
"data": final_data,
|
|
128
127
|
"streaming": True,
|
|
129
128
|
"session_id": self.session_id,
|
|
130
|
-
}
|
|
129
|
+
},
|
|
131
130
|
)
|
|
132
131
|
final_data = ""
|
|
133
132
|
continue
|
|
@@ -144,6 +143,6 @@ class WecomAIBotMessageEvent(AstrMessageEvent):
|
|
|
144
143
|
"data": final_data,
|
|
145
144
|
"streaming": True,
|
|
146
145
|
"session_id": self.session_id,
|
|
147
|
-
}
|
|
146
|
+
},
|
|
148
147
|
)
|
|
149
148
|
await super().send_streaming(generator, use_fallback)
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
"""
|
|
2
|
-
企业微信智能机器人队列管理器
|
|
1
|
+
"""企业微信智能机器人队列管理器
|
|
3
2
|
参考 webchat_queue_mgr.py,为企业微信智能机器人实现队列机制
|
|
4
3
|
支持异步消息处理和流式响应
|
|
5
4
|
"""
|
|
6
5
|
|
|
7
6
|
import asyncio
|
|
8
|
-
from typing import
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
9
|
from astrbot.api import logger
|
|
10
10
|
|
|
11
11
|
|
|
@@ -13,13 +13,13 @@ class WecomAIQueueMgr:
|
|
|
13
13
|
"""企业微信智能机器人队列管理器"""
|
|
14
14
|
|
|
15
15
|
def __init__(self) -> None:
|
|
16
|
-
self.queues:
|
|
16
|
+
self.queues: dict[str, asyncio.Queue] = {}
|
|
17
17
|
"""StreamID 到输入队列的映射 - 用于接收用户消息"""
|
|
18
18
|
|
|
19
|
-
self.back_queues:
|
|
19
|
+
self.back_queues: dict[str, asyncio.Queue] = {}
|
|
20
20
|
"""StreamID 到输出队列的映射 - 用于发送机器人响应"""
|
|
21
21
|
|
|
22
|
-
self.pending_responses:
|
|
22
|
+
self.pending_responses: dict[str, dict[str, Any]] = {}
|
|
23
23
|
"""待处理的响应缓存,用于流式响应"""
|
|
24
24
|
|
|
25
25
|
def get_or_create_queue(self, session_id: str) -> asyncio.Queue:
|
|
@@ -30,6 +30,7 @@ class WecomAIQueueMgr:
|
|
|
30
30
|
|
|
31
31
|
Returns:
|
|
32
32
|
输入队列实例
|
|
33
|
+
|
|
33
34
|
"""
|
|
34
35
|
if session_id not in self.queues:
|
|
35
36
|
self.queues[session_id] = asyncio.Queue()
|
|
@@ -44,6 +45,7 @@ class WecomAIQueueMgr:
|
|
|
44
45
|
|
|
45
46
|
Returns:
|
|
46
47
|
输出队列实例
|
|
48
|
+
|
|
47
49
|
"""
|
|
48
50
|
if session_id not in self.back_queues:
|
|
49
51
|
self.back_queues[session_id] = asyncio.Queue()
|
|
@@ -55,6 +57,7 @@ class WecomAIQueueMgr:
|
|
|
55
57
|
|
|
56
58
|
Args:
|
|
57
59
|
session_id: 会话ID
|
|
60
|
+
|
|
58
61
|
"""
|
|
59
62
|
if session_id in self.queues:
|
|
60
63
|
del self.queues[session_id]
|
|
@@ -76,6 +79,7 @@ class WecomAIQueueMgr:
|
|
|
76
79
|
|
|
77
80
|
Returns:
|
|
78
81
|
是否存在队列
|
|
82
|
+
|
|
79
83
|
"""
|
|
80
84
|
return session_id in self.queues
|
|
81
85
|
|
|
@@ -87,15 +91,17 @@ class WecomAIQueueMgr:
|
|
|
87
91
|
|
|
88
92
|
Returns:
|
|
89
93
|
是否存在输出队列
|
|
94
|
+
|
|
90
95
|
"""
|
|
91
96
|
return session_id in self.back_queues
|
|
92
97
|
|
|
93
|
-
def set_pending_response(self, session_id: str, callback_params:
|
|
98
|
+
def set_pending_response(self, session_id: str, callback_params: dict[str, str]):
|
|
94
99
|
"""设置待处理的响应参数
|
|
95
100
|
|
|
96
101
|
Args:
|
|
97
102
|
session_id: 会话ID
|
|
98
103
|
callback_params: 回调参数(nonce, timestamp等)
|
|
104
|
+
|
|
99
105
|
"""
|
|
100
106
|
self.pending_responses[session_id] = {
|
|
101
107
|
"callback_params": callback_params,
|
|
@@ -103,7 +109,7 @@ class WecomAIQueueMgr:
|
|
|
103
109
|
}
|
|
104
110
|
logger.debug(f"[WecomAI] 设置待处理响应: {session_id}")
|
|
105
111
|
|
|
106
|
-
def get_pending_response(self, session_id: str) ->
|
|
112
|
+
def get_pending_response(self, session_id: str) -> dict[str, Any] | None:
|
|
107
113
|
"""获取待处理的响应参数
|
|
108
114
|
|
|
109
115
|
Args:
|
|
@@ -111,6 +117,7 @@ class WecomAIQueueMgr:
|
|
|
111
117
|
|
|
112
118
|
Returns:
|
|
113
119
|
响应参数,如果不存在则返回None
|
|
120
|
+
|
|
114
121
|
"""
|
|
115
122
|
return self.pending_responses.get(session_id)
|
|
116
123
|
|
|
@@ -119,6 +126,7 @@ class WecomAIQueueMgr:
|
|
|
119
126
|
|
|
120
127
|
Args:
|
|
121
128
|
max_age_seconds: 最大存活时间(秒)
|
|
129
|
+
|
|
122
130
|
"""
|
|
123
131
|
current_time = asyncio.get_event_loop().time()
|
|
124
132
|
expired_sessions = []
|
|
@@ -131,11 +139,12 @@ class WecomAIQueueMgr:
|
|
|
131
139
|
del self.pending_responses[session_id]
|
|
132
140
|
logger.debug(f"[WecomAI] 清理过期响应: {session_id}")
|
|
133
141
|
|
|
134
|
-
def get_stats(self) ->
|
|
142
|
+
def get_stats(self) -> dict[str, int]:
|
|
135
143
|
"""获取队列统计信息
|
|
136
144
|
|
|
137
145
|
Returns:
|
|
138
146
|
统计信息字典
|
|
147
|
+
|
|
139
148
|
"""
|
|
140
149
|
return {
|
|
141
150
|
"input_queues": len(self.queues),
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
"""
|
|
2
|
-
企业微信智能机器人 HTTP 服务器
|
|
1
|
+
"""企业微信智能机器人 HTTP 服务器
|
|
3
2
|
处理企业微信智能机器人的 HTTP 回调请求
|
|
4
3
|
"""
|
|
5
4
|
|
|
6
5
|
import asyncio
|
|
7
|
-
from
|
|
6
|
+
from collections.abc import Callable
|
|
7
|
+
from typing import Any
|
|
8
8
|
|
|
9
9
|
import quart
|
|
10
|
+
|
|
10
11
|
from astrbot.api import logger
|
|
11
12
|
|
|
12
13
|
from .wecomai_api import WecomAIBotAPIClient
|
|
@@ -21,9 +22,7 @@ class WecomAIBotServer:
|
|
|
21
22
|
host: str,
|
|
22
23
|
port: int,
|
|
23
24
|
api_client: WecomAIBotAPIClient,
|
|
24
|
-
message_handler:
|
|
25
|
-
Callable[[Dict[str, Any], Dict[str, str]], Any]
|
|
26
|
-
] = None,
|
|
25
|
+
message_handler: Callable[[dict[str, Any], dict[str, str]], Any] | None = None,
|
|
27
26
|
):
|
|
28
27
|
"""初始化服务器
|
|
29
28
|
|
|
@@ -32,6 +31,7 @@ class WecomAIBotServer:
|
|
|
32
31
|
port: 监听端口
|
|
33
32
|
api_client: API客户端实例
|
|
34
33
|
message_handler: 消息处理回调函数
|
|
34
|
+
|
|
35
35
|
"""
|
|
36
36
|
self.host = host
|
|
37
37
|
self.port = port
|
|
@@ -45,7 +45,6 @@ class WecomAIBotServer:
|
|
|
45
45
|
|
|
46
46
|
def _setup_routes(self):
|
|
47
47
|
"""设置 Quart 路由"""
|
|
48
|
-
|
|
49
48
|
# 使用 Quart 的 add_url_rule 方法添加路由
|
|
50
49
|
self.app.add_url_rule(
|
|
51
50
|
"/webhook/wecom-ai-bot",
|
|
@@ -98,7 +97,7 @@ class WecomAIBotServer:
|
|
|
98
97
|
assert nonce is not None
|
|
99
98
|
|
|
100
99
|
logger.debug(
|
|
101
|
-
f"收到消息回调,msg_signature={msg_signature}, timestamp={timestamp}, nonce={nonce}"
|
|
100
|
+
f"收到消息回调,msg_signature={msg_signature}, timestamp={timestamp}, nonce={nonce}",
|
|
102
101
|
)
|
|
103
102
|
|
|
104
103
|
try:
|
|
@@ -111,7 +110,10 @@ class WecomAIBotServer:
|
|
|
111
110
|
|
|
112
111
|
# 解密消息
|
|
113
112
|
ret_code, message_data = await self.api_client.decrypt_message(
|
|
114
|
-
post_data,
|
|
113
|
+
post_data,
|
|
114
|
+
msg_signature,
|
|
115
|
+
timestamp,
|
|
116
|
+
nonce,
|
|
115
117
|
)
|
|
116
118
|
|
|
117
119
|
if ret_code != WecomAIBotConstants.SUCCESS or not message_data:
|
|
@@ -123,7 +125,8 @@ class WecomAIBotServer:
|
|
|
123
125
|
if self.message_handler:
|
|
124
126
|
try:
|
|
125
127
|
response = await self.message_handler(
|
|
126
|
-
message_data,
|
|
128
|
+
message_data,
|
|
129
|
+
{"nonce": nonce, "timestamp": timestamp},
|
|
127
130
|
)
|
|
128
131
|
except Exception as e:
|
|
129
132
|
logger.error("消息处理器执行异常: %s", e)
|
|
@@ -131,8 +134,7 @@ class WecomAIBotServer:
|
|
|
131
134
|
|
|
132
135
|
if response:
|
|
133
136
|
return response, 200, {"Content-Type": "text/plain"}
|
|
134
|
-
|
|
135
|
-
return "success", 200, {"Content-Type": "text/plain"}
|
|
137
|
+
return "success", 200, {"Content-Type": "text/plain"}
|
|
136
138
|
|
|
137
139
|
except Exception as e:
|
|
138
140
|
logger.error("处理消息时发生异常: %s", e)
|
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
"""
|
|
2
|
-
企业微信智能机器人工具模块
|
|
1
|
+
"""企业微信智能机器人工具模块
|
|
3
2
|
提供常量定义、工具函数和辅助方法
|
|
4
3
|
"""
|
|
5
4
|
|
|
6
|
-
import
|
|
7
|
-
import random
|
|
8
|
-
import hashlib
|
|
5
|
+
import asyncio
|
|
9
6
|
import base64
|
|
7
|
+
import hashlib
|
|
8
|
+
import secrets
|
|
9
|
+
import string
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
10
12
|
import aiohttp
|
|
11
|
-
import asyncio
|
|
12
13
|
from Crypto.Cipher import AES
|
|
13
|
-
|
|
14
|
+
|
|
14
15
|
from astrbot.api import logger
|
|
15
16
|
|
|
16
17
|
|
|
@@ -49,9 +50,10 @@ def generate_random_string(length: int = 10) -> str:
|
|
|
49
50
|
|
|
50
51
|
Returns:
|
|
51
52
|
随机字符串
|
|
53
|
+
|
|
52
54
|
"""
|
|
53
55
|
letters = string.ascii_letters + string.digits
|
|
54
|
-
return "".join(
|
|
56
|
+
return "".join(secrets.choice(letters) for _ in range(length))
|
|
55
57
|
|
|
56
58
|
|
|
57
59
|
def calculate_image_md5(image_data: bytes) -> str:
|
|
@@ -62,6 +64,7 @@ def calculate_image_md5(image_data: bytes) -> str:
|
|
|
62
64
|
|
|
63
65
|
Returns:
|
|
64
66
|
MD5 哈希值(十六进制字符串)
|
|
67
|
+
|
|
65
68
|
"""
|
|
66
69
|
return hashlib.md5(image_data).hexdigest()
|
|
67
70
|
|
|
@@ -74,6 +77,7 @@ def encode_image_base64(image_data: bytes) -> str:
|
|
|
74
77
|
|
|
75
78
|
Returns:
|
|
76
79
|
Base64 编码的字符串
|
|
80
|
+
|
|
77
81
|
"""
|
|
78
82
|
return base64.b64encode(image_data).decode("utf-8")
|
|
79
83
|
|
|
@@ -87,11 +91,12 @@ def format_session_id(session_type: str, session_id: str) -> str:
|
|
|
87
91
|
|
|
88
92
|
Returns:
|
|
89
93
|
格式化后的会话 ID
|
|
94
|
+
|
|
90
95
|
"""
|
|
91
96
|
return f"wecom_ai_bot_{session_type}_{session_id}"
|
|
92
97
|
|
|
93
98
|
|
|
94
|
-
def parse_session_id(formatted_session_id: str) ->
|
|
99
|
+
def parse_session_id(formatted_session_id: str) -> tuple[str, str]:
|
|
95
100
|
"""解析格式化的会话 ID
|
|
96
101
|
|
|
97
102
|
Args:
|
|
@@ -99,6 +104,7 @@ def parse_session_id(formatted_session_id: str) -> Tuple[str, str]:
|
|
|
99
104
|
|
|
100
105
|
Returns:
|
|
101
106
|
(会话类型, 原始会话ID)
|
|
107
|
+
|
|
102
108
|
"""
|
|
103
109
|
parts = formatted_session_id.split("_", 3)
|
|
104
110
|
if (
|
|
@@ -120,6 +126,7 @@ def safe_json_loads(json_str: str, default: Any = None) -> Any:
|
|
|
120
126
|
|
|
121
127
|
Returns:
|
|
122
128
|
解析结果或默认值
|
|
129
|
+
|
|
123
130
|
"""
|
|
124
131
|
import json
|
|
125
132
|
|
|
@@ -139,13 +146,15 @@ def format_error_response(error_code: int, error_msg: str) -> str:
|
|
|
139
146
|
|
|
140
147
|
Returns:
|
|
141
148
|
格式化的错误响应字符串
|
|
149
|
+
|
|
142
150
|
"""
|
|
143
151
|
return f"Error {error_code}: {error_msg}"
|
|
144
152
|
|
|
145
153
|
|
|
146
154
|
async def process_encrypted_image(
|
|
147
|
-
image_url: str,
|
|
148
|
-
|
|
155
|
+
image_url: str,
|
|
156
|
+
aes_key_base64: str,
|
|
157
|
+
) -> tuple[bool, str]:
|
|
149
158
|
"""下载并解密加密图片
|
|
150
159
|
|
|
151
160
|
Args:
|
|
@@ -155,6 +164,7 @@ async def process_encrypted_image(
|
|
|
155
164
|
Returns:
|
|
156
165
|
Tuple[bool, str]: status 为 True 时 data 是解密后的图片数据的 base64 编码,
|
|
157
166
|
status 为 False 时 data 是错误信息
|
|
167
|
+
|
|
158
168
|
"""
|
|
159
169
|
# 1. 下载加密图片
|
|
160
170
|
logger.info("开始下载加密图片: %s", image_url)
|
|
@@ -165,7 +175,7 @@ async def process_encrypted_image(
|
|
|
165
175
|
encrypted_data = await response.read()
|
|
166
176
|
logger.info("图片下载成功,大小: %d 字节", len(encrypted_data))
|
|
167
177
|
except (aiohttp.ClientError, asyncio.TimeoutError) as e:
|
|
168
|
-
error_msg = f"下载图片失败: {
|
|
178
|
+
error_msg = f"下载图片失败: {e!s}"
|
|
169
179
|
logger.error(error_msg)
|
|
170
180
|
return False, error_msg
|
|
171
181
|
|