AstrBot 4.8.0__py3-none-any.whl → 4.9.1__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/cli/__init__.py +1 -1
- astrbot/core/agent/runners/tool_loop_agent_runner.py +0 -1
- astrbot/core/agent/tool.py +7 -2
- astrbot/core/astr_agent_tool_exec.py +5 -1
- astrbot/core/config/astrbot_config.py +4 -0
- astrbot/core/config/default.py +72 -1
- astrbot/core/config/i18n_utils.py +1 -0
- astrbot/core/core_lifecycle.py +1 -1
- astrbot/core/db/__init__.py +2 -3
- astrbot/core/db/migration/migra_3_to_4.py +2 -0
- astrbot/core/db/migration/sqlite_v3.py +6 -4
- astrbot/core/db/po.py +16 -15
- astrbot/core/db/sqlite.py +4 -3
- astrbot/core/db/vec_db/faiss_impl/embedding_storage.py +2 -0
- astrbot/core/event_bus.py +6 -1
- astrbot/core/knowledge_base/retrieval/manager.py +5 -1
- astrbot/core/log.py +2 -1
- astrbot/core/message/components.py +9 -3
- astrbot/core/persona_mgr.py +2 -2
- astrbot/core/pipeline/content_safety_check/stage.py +1 -1
- astrbot/core/pipeline/context_utils.py +2 -1
- astrbot/core/pipeline/process_stage/method/star_request.py +1 -2
- astrbot/core/pipeline/process_stage/stage.py +1 -1
- astrbot/core/pipeline/respond/stage.py +8 -2
- astrbot/core/pipeline/result_decorate/stage.py +89 -22
- astrbot/core/pipeline/scheduler.py +5 -1
- astrbot/core/pipeline/waking_check/stage.py +10 -0
- astrbot/core/platform/astr_message_event.py +5 -3
- astrbot/core/platform/astrbot_message.py +2 -2
- astrbot/core/platform/manager.py +4 -0
- astrbot/core/platform/platform.py +11 -3
- astrbot/core/platform/platform_metadata.py +1 -1
- astrbot/core/platform/register.py +1 -0
- astrbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py +8 -6
- astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py +9 -5
- astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py +24 -16
- astrbot/core/platform/sources/dingtalk/dingtalk_event.py +5 -2
- astrbot/core/platform/sources/discord/client.py +16 -4
- astrbot/core/platform/sources/discord/components.py +2 -2
- astrbot/core/platform/sources/discord/discord_platform_adapter.py +52 -24
- astrbot/core/platform/sources/discord/discord_platform_event.py +29 -8
- astrbot/core/platform/sources/lark/lark_adapter.py +183 -20
- astrbot/core/platform/sources/lark/lark_event.py +39 -4
- astrbot/core/platform/sources/lark/server.py +206 -0
- astrbot/core/platform/sources/misskey/misskey_adapter.py +2 -3
- astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py +62 -18
- astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py +13 -7
- astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_adapter.py +5 -3
- astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_server.py +2 -1
- astrbot/core/platform/sources/slack/client.py +9 -2
- astrbot/core/platform/sources/slack/slack_adapter.py +15 -9
- astrbot/core/platform/sources/slack/slack_event.py +8 -7
- astrbot/core/platform/sources/telegram/tg_adapter.py +1 -1
- astrbot/core/platform/sources/telegram/tg_event.py +23 -27
- astrbot/core/platform/sources/webchat/webchat_adapter.py +2 -2
- astrbot/core/platform/sources/webchat/webchat_event.py +2 -2
- astrbot/core/platform/sources/wechatpadpro/wechatpadpro_adapter.py +26 -9
- astrbot/core/platform/sources/wecom/wecom_adapter.py +25 -28
- astrbot/core/platform/sources/wecom/wecom_event.py +2 -2
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_event.py +3 -3
- astrbot/core/platform/sources/weixin_official_account/weixin_offacc_adapter.py +30 -25
- astrbot/core/platform/sources/weixin_official_account/weixin_offacc_event.py +10 -7
- astrbot/core/provider/func_tool_manager.py +3 -3
- astrbot/core/provider/manager.py +130 -74
- astrbot/core/provider/provider.py +12 -1
- astrbot/core/provider/sources/azure_tts_source.py +31 -9
- astrbot/core/provider/sources/bailian_rerank_source.py +4 -0
- astrbot/core/provider/sources/dashscope_tts.py +3 -2
- astrbot/core/provider/sources/edge_tts_source.py +1 -1
- astrbot/core/provider/sources/fishaudio_tts_api_source.py +5 -4
- astrbot/core/provider/sources/gemini_embedding_source.py +15 -5
- astrbot/core/provider/sources/gemini_source.py +12 -10
- astrbot/core/provider/sources/minimax_tts_api_source.py +4 -2
- astrbot/core/provider/sources/openai_embedding_source.py +2 -2
- astrbot/core/provider/sources/openai_source.py +4 -0
- astrbot/core/provider/sources/sensevoice_selfhosted_source.py +5 -2
- astrbot/core/provider/sources/vllm_rerank_source.py +1 -0
- astrbot/core/provider/sources/whisper_api_source.py +1 -1
- astrbot/core/provider/sources/whisper_selfhosted_source.py +6 -2
- astrbot/core/provider/sources/xinference_rerank_source.py +10 -2
- astrbot/core/star/context.py +2 -2
- astrbot/core/star/register/star_handler.py +22 -5
- astrbot/core/star/star_handler.py +85 -4
- astrbot/core/updator.py +3 -3
- astrbot/core/utils/io.py +1 -1
- astrbot/core/utils/session_waiter.py +17 -10
- astrbot/core/utils/shared_preferences.py +32 -0
- astrbot/core/utils/t2i/__init__.py +2 -2
- astrbot/core/utils/t2i/local_strategy.py +25 -31
- astrbot/core/utils/tencent_record_helper.py +1 -1
- astrbot/core/utils/version_comparator.py +6 -3
- astrbot/core/utils/webhook_utils.py +19 -0
- astrbot/dashboard/routes/chat.py +14 -9
- astrbot/dashboard/routes/config.py +10 -20
- astrbot/dashboard/routes/conversation.py +91 -1
- astrbot/dashboard/routes/knowledge_base.py +253 -78
- astrbot/dashboard/routes/log.py +13 -8
- astrbot/dashboard/routes/platform.py +1 -1
- astrbot/dashboard/routes/plugin.py +113 -52
- astrbot/dashboard/routes/route.py +2 -0
- astrbot/dashboard/server.py +6 -3
- {astrbot-4.8.0.dist-info → astrbot-4.9.1.dist-info}/METADATA +9 -1
- {astrbot-4.8.0.dist-info → astrbot-4.9.1.dist-info}/RECORD +106 -105
- {astrbot-4.8.0.dist-info → astrbot-4.9.1.dist-info}/WHEEL +0 -0
- {astrbot-4.8.0.dist-info → astrbot-4.9.1.dist-info}/entry_points.txt +0 -0
- {astrbot-4.8.0.dist-info → astrbot-4.9.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import re
|
|
3
|
-
from collections.abc import AsyncGenerator
|
|
3
|
+
from collections.abc import AsyncGenerator, Iterable
|
|
4
|
+
from typing import cast
|
|
4
5
|
|
|
5
6
|
from slack_sdk.web.async_client import AsyncWebClient
|
|
6
7
|
|
|
@@ -38,7 +39,7 @@ class SlackMessageEvent(AstrMessageEvent):
|
|
|
38
39
|
if isinstance(segment, Image):
|
|
39
40
|
# upload file
|
|
40
41
|
url = segment.url or segment.file
|
|
41
|
-
if url.startswith("http"):
|
|
42
|
+
if url and url.startswith("http"):
|
|
42
43
|
return {
|
|
43
44
|
"type": "image",
|
|
44
45
|
"image_url": url,
|
|
@@ -55,7 +56,7 @@ class SlackMessageEvent(AstrMessageEvent):
|
|
|
55
56
|
"type": "section",
|
|
56
57
|
"text": {"type": "mrkdwn", "text": "图片上传失败"},
|
|
57
58
|
}
|
|
58
|
-
image_url = response["files"][0]["url_private"]
|
|
59
|
+
image_url = cast(list, response["files"])[0]["url_private"]
|
|
59
60
|
logger.debug(f"Slack file upload response: {response}")
|
|
60
61
|
return {
|
|
61
62
|
"type": "image",
|
|
@@ -77,7 +78,7 @@ class SlackMessageEvent(AstrMessageEvent):
|
|
|
77
78
|
"type": "section",
|
|
78
79
|
"text": {"type": "mrkdwn", "text": "文件上传失败"},
|
|
79
80
|
}
|
|
80
|
-
file_url = response["files"][0]["permalink"]
|
|
81
|
+
file_url = cast(list, response["files"])[0]["permalink"]
|
|
81
82
|
return {
|
|
82
83
|
"type": "section",
|
|
83
84
|
"text": {
|
|
@@ -225,10 +226,10 @@ class SlackMessageEvent(AstrMessageEvent):
|
|
|
225
226
|
)
|
|
226
227
|
|
|
227
228
|
members = []
|
|
228
|
-
for member_id in members_response["members"]:
|
|
229
|
+
for member_id in cast(Iterable, members_response["members"]):
|
|
229
230
|
try:
|
|
230
231
|
user_info = await self.web_client.users_info(user=member_id)
|
|
231
|
-
user_data = user_info["user"]
|
|
232
|
+
user_data = cast(dict, user_info["user"])
|
|
232
233
|
members.append(
|
|
233
234
|
MessageMember(
|
|
234
235
|
user_id=member_id,
|
|
@@ -240,7 +241,7 @@ class SlackMessageEvent(AstrMessageEvent):
|
|
|
240
241
|
# 如果获取用户信息失败,使用默认信息
|
|
241
242
|
members.append(MessageMember(user_id=member_id, nickname=member_id))
|
|
242
243
|
|
|
243
|
-
channel_data = channel_info["channel"]
|
|
244
|
+
channel_data = cast(dict, channel_info["channel"])
|
|
244
245
|
return Group(
|
|
245
246
|
group_id=channel_id,
|
|
246
247
|
group_name=channel_data.get("name", ""),
|
|
@@ -424,6 +424,6 @@ class TelegramPlatformAdapter(Platform):
|
|
|
424
424
|
if self.application.updater is not None:
|
|
425
425
|
await self.application.updater.stop()
|
|
426
426
|
|
|
427
|
-
logger.info("Telegram
|
|
427
|
+
logger.info("Telegram 适配器已被关闭")
|
|
428
428
|
except Exception as e:
|
|
429
429
|
logger.error(f"Telegram 适配器关闭时出错: {e}")
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import os
|
|
3
3
|
import re
|
|
4
|
+
from typing import Any, cast
|
|
4
5
|
|
|
5
6
|
import telegramify_markdown
|
|
6
7
|
from telegram import ReactionTypeCustomEmoji, ReactionTypeEmoji
|
|
@@ -17,8 +18,6 @@ from astrbot.api.message_components import (
|
|
|
17
18
|
Reply,
|
|
18
19
|
)
|
|
19
20
|
from astrbot.api.platform import AstrBotMessage, MessageType, PlatformMetadata
|
|
20
|
-
from astrbot.core.utils.astrbot_path import get_astrbot_data_path
|
|
21
|
-
from astrbot.core.utils.io import download_file
|
|
22
21
|
|
|
23
22
|
|
|
24
23
|
class TelegramPlatformEvent(AstrMessageEvent):
|
|
@@ -97,7 +96,7 @@ class TelegramPlatformEvent(AstrMessageEvent):
|
|
|
97
96
|
"chat_id": user_name,
|
|
98
97
|
}
|
|
99
98
|
if has_reply:
|
|
100
|
-
payload["reply_to_message_id"] = reply_message_id
|
|
99
|
+
payload["reply_to_message_id"] = str(reply_message_id)
|
|
101
100
|
if message_thread_id:
|
|
102
101
|
payload["message_thread_id"] = message_thread_id
|
|
103
102
|
|
|
@@ -110,33 +109,30 @@ class TelegramPlatformEvent(AstrMessageEvent):
|
|
|
110
109
|
try:
|
|
111
110
|
md_text = telegramify_markdown.markdownify(
|
|
112
111
|
chunk,
|
|
113
|
-
max_line_length=None,
|
|
114
112
|
normalize_whitespace=False,
|
|
115
113
|
)
|
|
116
114
|
await client.send_message(
|
|
117
115
|
text=md_text,
|
|
118
116
|
parse_mode="MarkdownV2",
|
|
119
|
-
**payload,
|
|
117
|
+
**cast(Any, payload),
|
|
120
118
|
)
|
|
121
119
|
except Exception as e:
|
|
122
120
|
logger.warning(
|
|
123
121
|
f"MarkdownV2 send failed: {e}. Using plain text instead.",
|
|
124
122
|
)
|
|
125
|
-
await client.send_message(text=chunk, **payload)
|
|
123
|
+
await client.send_message(text=chunk, **cast(Any, payload))
|
|
126
124
|
elif isinstance(i, Image):
|
|
127
125
|
image_path = await i.convert_to_file_path()
|
|
128
|
-
await client.send_photo(photo=image_path, **payload)
|
|
126
|
+
await client.send_photo(photo=image_path, **cast(Any, payload))
|
|
129
127
|
elif isinstance(i, File):
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
await client.send_document(document=i.file, filename=i.name, **payload)
|
|
128
|
+
path = await i.get_file()
|
|
129
|
+
name = i.name or os.path.basename(path)
|
|
130
|
+
await client.send_document(
|
|
131
|
+
document=path, filename=name, **cast(Any, payload)
|
|
132
|
+
)
|
|
137
133
|
elif isinstance(i, Record):
|
|
138
134
|
path = await i.convert_to_file_path()
|
|
139
|
-
await client.send_voice(voice=path, **payload)
|
|
135
|
+
await client.send_voice(voice=path, **cast(Any, payload))
|
|
140
136
|
|
|
141
137
|
async def send(self, message: MessageChain):
|
|
142
138
|
if self.get_message_type() == MessageType.GROUP_MESSAGE:
|
|
@@ -214,24 +210,23 @@ class TelegramPlatformEvent(AstrMessageEvent):
|
|
|
214
210
|
delta += i.text
|
|
215
211
|
elif isinstance(i, Image):
|
|
216
212
|
image_path = await i.convert_to_file_path()
|
|
217
|
-
await self.client.send_photo(
|
|
213
|
+
await self.client.send_photo(
|
|
214
|
+
photo=image_path, **cast(Any, payload)
|
|
215
|
+
)
|
|
218
216
|
continue
|
|
219
217
|
elif isinstance(i, File):
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
path = os.path.join(temp_dir, i.name)
|
|
223
|
-
await download_file(i.file, path)
|
|
224
|
-
i.file = path
|
|
218
|
+
path = await i.get_file()
|
|
219
|
+
name = i.name or os.path.basename(path)
|
|
225
220
|
|
|
226
221
|
await self.client.send_document(
|
|
227
|
-
document=
|
|
228
|
-
filename=
|
|
229
|
-
**payload,
|
|
222
|
+
document=path,
|
|
223
|
+
filename=name,
|
|
224
|
+
**cast(Any, payload),
|
|
230
225
|
)
|
|
231
226
|
continue
|
|
232
227
|
elif isinstance(i, Record):
|
|
233
228
|
path = await i.convert_to_file_path()
|
|
234
|
-
await self.client.send_voice(voice=path, **payload)
|
|
229
|
+
await self.client.send_voice(voice=path, **cast(Any, payload))
|
|
235
230
|
continue
|
|
236
231
|
else:
|
|
237
232
|
logger.warning(f"不支持的消息类型: {type(i)}")
|
|
@@ -260,7 +255,9 @@ class TelegramPlatformEvent(AstrMessageEvent):
|
|
|
260
255
|
else:
|
|
261
256
|
# delta 长度一般不会大于 4096,因此这里直接发送
|
|
262
257
|
try:
|
|
263
|
-
msg = await self.client.send_message(
|
|
258
|
+
msg = await self.client.send_message(
|
|
259
|
+
text=delta, **cast(Any, payload)
|
|
260
|
+
)
|
|
264
261
|
current_content = delta
|
|
265
262
|
except Exception as e:
|
|
266
263
|
logger.warning(f"发送消息失败(streaming): {e!s}")
|
|
@@ -274,7 +271,6 @@ class TelegramPlatformEvent(AstrMessageEvent):
|
|
|
274
271
|
try:
|
|
275
272
|
markdown_text = telegramify_markdown.markdownify(
|
|
276
273
|
delta,
|
|
277
|
-
max_line_length=None,
|
|
278
274
|
normalize_whitespace=False,
|
|
279
275
|
)
|
|
280
276
|
await self.client.edit_message_text(
|
|
@@ -2,7 +2,7 @@ import asyncio
|
|
|
2
2
|
import os
|
|
3
3
|
import time
|
|
4
4
|
import uuid
|
|
5
|
-
from collections.abc import
|
|
5
|
+
from collections.abc import Callable, Coroutine
|
|
6
6
|
from typing import Any
|
|
7
7
|
|
|
8
8
|
from astrbot import logger
|
|
@@ -207,7 +207,7 @@ class WebChatAdapter(Platform):
|
|
|
207
207
|
abm.raw_message = data
|
|
208
208
|
return abm
|
|
209
209
|
|
|
210
|
-
def run(self) ->
|
|
210
|
+
def run(self) -> Coroutine[Any, Any, None]:
|
|
211
211
|
async def callback(data: tuple):
|
|
212
212
|
abm = await self.convert_message(data)
|
|
213
213
|
await self.handle_msg(abm)
|
|
@@ -101,9 +101,9 @@ class WebChatMessageEvent(AstrMessageEvent):
|
|
|
101
101
|
|
|
102
102
|
return data
|
|
103
103
|
|
|
104
|
-
async def send(self, message: MessageChain):
|
|
104
|
+
async def send(self, message: MessageChain | None):
|
|
105
105
|
await WebChatMessageEvent._send(message, session_id=self.session_id)
|
|
106
|
-
await super().send(
|
|
106
|
+
await super().send(MessageChain([]))
|
|
107
107
|
|
|
108
108
|
async def send_streaming(self, generator, use_fallback: bool = False):
|
|
109
109
|
final_data = ""
|
|
@@ -4,6 +4,7 @@ import json
|
|
|
4
4
|
import os
|
|
5
5
|
import time
|
|
6
6
|
import traceback
|
|
7
|
+
from typing import cast
|
|
7
8
|
|
|
8
9
|
import aiohttp
|
|
9
10
|
import anyio
|
|
@@ -69,7 +70,7 @@ class WeChatPadProAdapter(Platform):
|
|
|
69
70
|
)
|
|
70
71
|
self.base_url = f"http://{self.host}:{self.port}"
|
|
71
72
|
self.auth_key = None # 用于保存生成的授权码
|
|
72
|
-
self.wxid = None # 用于保存登录成功后的 wxid
|
|
73
|
+
self.wxid: str | None = None # 用于保存登录成功后的 wxid
|
|
73
74
|
self.credentials_file = os.path.join(
|
|
74
75
|
get_astrbot_data_path(),
|
|
75
76
|
"wechatpadpro_credentials.json",
|
|
@@ -398,7 +399,7 @@ class WeChatPadProAdapter(Platform):
|
|
|
398
399
|
)
|
|
399
400
|
await asyncio.sleep(5)
|
|
400
401
|
|
|
401
|
-
async def handle_websocket_message(self, message: str):
|
|
402
|
+
async def handle_websocket_message(self, message: str | bytes):
|
|
402
403
|
"""处理从 WebSocket 接收到的消息。"""
|
|
403
404
|
logger.debug(f"收到 WebSocket 消息: {message}")
|
|
404
405
|
try:
|
|
@@ -430,10 +431,13 @@ class WeChatPadProAdapter(Platform):
|
|
|
430
431
|
|
|
431
432
|
async def convert_message(self, raw_message: dict) -> AstrBotMessage | None:
|
|
432
433
|
"""将 WeChatPadPro 原始消息转换为 AstrBotMessage。"""
|
|
434
|
+
if self.wxid is None:
|
|
435
|
+
logger.error("WeChatPadPro 适配器未登录或未获取到 wxid,无法处理消息。")
|
|
436
|
+
return None
|
|
433
437
|
abm = AstrBotMessage()
|
|
434
438
|
abm.raw_message = raw_message
|
|
435
439
|
abm.message_id = str(raw_message.get("msg_id"))
|
|
436
|
-
abm.timestamp = raw_message.get("create_time")
|
|
440
|
+
abm.timestamp = cast(int, raw_message.get("create_time"))
|
|
437
441
|
abm.self_id = self.wxid
|
|
438
442
|
|
|
439
443
|
if int(time.time()) - abm.timestamp > 180:
|
|
@@ -446,7 +450,7 @@ class WeChatPadProAdapter(Platform):
|
|
|
446
450
|
to_user_name = raw_message.get("to_user_name", {}).get("str", "")
|
|
447
451
|
content = raw_message.get("content", {}).get("str", "")
|
|
448
452
|
push_content = raw_message.get("push_content", "")
|
|
449
|
-
msg_type = raw_message.get("msg_type")
|
|
453
|
+
msg_type = cast(int, raw_message.get("msg_type"))
|
|
450
454
|
|
|
451
455
|
abm.message_str = ""
|
|
452
456
|
abm.message = []
|
|
@@ -574,7 +578,7 @@ class WeChatPadProAdapter(Platform):
|
|
|
574
578
|
from_user_name: str,
|
|
575
579
|
to_user_name: str,
|
|
576
580
|
msg_id: int,
|
|
577
|
-
):
|
|
581
|
+
) -> dict | None:
|
|
578
582
|
"""下载原始图片。"""
|
|
579
583
|
url = f"{self.base_url}/message/GetMsgBigImg"
|
|
580
584
|
params = {"key": self.auth_key}
|
|
@@ -725,12 +729,15 @@ class WeChatPadProAdapter(Platform):
|
|
|
725
729
|
# 图片消息
|
|
726
730
|
from_user_name = raw_message.get("from_user_name", {}).get("str", "")
|
|
727
731
|
to_user_name = raw_message.get("to_user_name", {}).get("str", "")
|
|
728
|
-
msg_id = raw_message.get("msg_id")
|
|
732
|
+
msg_id = cast(int, raw_message.get("msg_id"))
|
|
729
733
|
image_resp = await self._download_raw_image(
|
|
730
734
|
from_user_name,
|
|
731
735
|
to_user_name,
|
|
732
736
|
msg_id,
|
|
733
737
|
)
|
|
738
|
+
if image_resp is None:
|
|
739
|
+
logger.error(f"下载图片失败: msg_id={msg_id}")
|
|
740
|
+
return
|
|
734
741
|
image_bs64_data = (
|
|
735
742
|
image_resp.get("Data", {}).get("Data", {}).get("Buffer", None)
|
|
736
743
|
)
|
|
@@ -771,6 +778,9 @@ class WeChatPadProAdapter(Platform):
|
|
|
771
778
|
bufid = 0
|
|
772
779
|
to_user_name = raw_message.get("to_user_name", {}).get("str", "")
|
|
773
780
|
new_msg_id = raw_message.get("new_msg_id")
|
|
781
|
+
if new_msg_id is None:
|
|
782
|
+
logger.error("语音消息缺少 new_msg_id")
|
|
783
|
+
return
|
|
774
784
|
data_parser = GeweDataParser(
|
|
775
785
|
content=content,
|
|
776
786
|
is_private_chat=(abm.type != MessageType.GROUP_MESSAGE),
|
|
@@ -778,6 +788,9 @@ class WeChatPadProAdapter(Platform):
|
|
|
778
788
|
)
|
|
779
789
|
|
|
780
790
|
voicemsg = data_parser._format_to_xml().find("voicemsg")
|
|
791
|
+
if voicemsg is None:
|
|
792
|
+
logger.error("无法从 XML 解析 voicemsg 节点")
|
|
793
|
+
return
|
|
781
794
|
bufid = voicemsg.get("bufid") or "0"
|
|
782
795
|
length = int(voicemsg.get("length") or 0)
|
|
783
796
|
voice_resp = await self.download_voice(
|
|
@@ -786,6 +799,9 @@ class WeChatPadProAdapter(Platform):
|
|
|
786
799
|
bufid=bufid,
|
|
787
800
|
length=length,
|
|
788
801
|
)
|
|
802
|
+
if voice_resp is None:
|
|
803
|
+
logger.error(f"下载语音失败: new_msg_id={new_msg_id}")
|
|
804
|
+
return
|
|
789
805
|
voice_bs64_data = voice_resp.get("Data", {}).get("Base64", None)
|
|
790
806
|
if voice_bs64_data:
|
|
791
807
|
voice_bs64_data = base64.b64decode(voice_bs64_data)
|
|
@@ -827,7 +843,8 @@ class WeChatPadProAdapter(Platform):
|
|
|
827
843
|
try:
|
|
828
844
|
if self.ws_handle_task:
|
|
829
845
|
self.ws_handle_task.cancel()
|
|
830
|
-
self._shutdown_event
|
|
846
|
+
if self._shutdown_event is not None:
|
|
847
|
+
self._shutdown_event.set()
|
|
831
848
|
except Exception:
|
|
832
849
|
pass
|
|
833
850
|
|
|
@@ -894,8 +911,8 @@ class WeChatPadProAdapter(Platform):
|
|
|
894
911
|
|
|
895
912
|
async def get_contact_details_list(
|
|
896
913
|
self,
|
|
897
|
-
room_wx_id_list: list[str] = None,
|
|
898
|
-
user_names: list[str] = None,
|
|
914
|
+
room_wx_id_list: list[str] | None = None,
|
|
915
|
+
user_names: list[str] | None = None,
|
|
899
916
|
) -> dict | None:
|
|
900
917
|
"""获取联系人详情列表。"""
|
|
901
918
|
if room_wx_id_list is None:
|
|
@@ -2,7 +2,8 @@ import asyncio
|
|
|
2
2
|
import os
|
|
3
3
|
import sys
|
|
4
4
|
import uuid
|
|
5
|
-
from
|
|
5
|
+
from collections.abc import Awaitable, Callable
|
|
6
|
+
from typing import Any, cast
|
|
6
7
|
|
|
7
8
|
import quart
|
|
8
9
|
from requests import Response
|
|
@@ -40,7 +41,7 @@ else:
|
|
|
40
41
|
class WecomServer:
|
|
41
42
|
def __init__(self, event_queue: asyncio.Queue, config: dict):
|
|
42
43
|
self.server = quart.Quart(__name__)
|
|
43
|
-
self.port = int(config.get("port"))
|
|
44
|
+
self.port = int(cast(str, config.get("port")))
|
|
44
45
|
self.callback_server_host = config.get("callback_server_host", "0.0.0.0")
|
|
45
46
|
self.server.add_url_rule(
|
|
46
47
|
"/callback/command",
|
|
@@ -60,7 +61,7 @@ class WecomServer:
|
|
|
60
61
|
config["corpid"].strip(),
|
|
61
62
|
)
|
|
62
63
|
|
|
63
|
-
self.callback = None
|
|
64
|
+
self.callback: Callable[[BaseMessage], Awaitable[None]] | None = None
|
|
64
65
|
self.shutdown_event = asyncio.Event()
|
|
65
66
|
|
|
66
67
|
async def verify(self):
|
|
@@ -114,7 +115,7 @@ class WecomServer:
|
|
|
114
115
|
logger.error("解密失败,签名异常,请检查配置。")
|
|
115
116
|
raise
|
|
116
117
|
else:
|
|
117
|
-
msg = parse_message(xml)
|
|
118
|
+
msg = cast(BaseMessage, parse_message(xml))
|
|
118
119
|
logger.info(f"解析成功: {msg}")
|
|
119
120
|
|
|
120
121
|
if self.callback:
|
|
@@ -176,10 +177,10 @@ class WecomPlatformAdapter(Platform):
|
|
|
176
177
|
# inject
|
|
177
178
|
self.wechat_kf_api = WeChatKF(client=self.client)
|
|
178
179
|
self.wechat_kf_message_api = WeChatKFMessage(self.client)
|
|
179
|
-
self.client.kf
|
|
180
|
-
self.client.kf_message
|
|
180
|
+
self.client.__setattr__("kf", self.wechat_kf_api)
|
|
181
|
+
self.client.__setattr__("kf_message", self.wechat_kf_message_api)
|
|
181
182
|
|
|
182
|
-
self.client.API_BASE_URL
|
|
183
|
+
self.client.__setattr__("API_BASE_URL", self.api_base_url)
|
|
183
184
|
|
|
184
185
|
async def callback(msg: BaseMessage):
|
|
185
186
|
if msg.type == "unknown" and msg._data["Event"] == "kf_msg_or_event":
|
|
@@ -278,37 +279,33 @@ class WecomPlatformAdapter(Platform):
|
|
|
278
279
|
|
|
279
280
|
async def convert_message(self, msg: BaseMessage) -> AstrBotMessage | None:
|
|
280
281
|
abm = AstrBotMessage()
|
|
281
|
-
if msg
|
|
282
|
-
assert isinstance(msg, TextMessage)
|
|
282
|
+
if isinstance(msg, TextMessage):
|
|
283
283
|
abm.message_str = msg.content
|
|
284
284
|
abm.self_id = str(msg.agent)
|
|
285
285
|
abm.message = [Plain(msg.content)]
|
|
286
286
|
abm.type = MessageType.FRIEND_MESSAGE
|
|
287
287
|
abm.sender = MessageMember(
|
|
288
|
-
msg.source,
|
|
289
|
-
msg.source,
|
|
288
|
+
cast(str, msg.source),
|
|
289
|
+
cast(str, msg.source),
|
|
290
290
|
)
|
|
291
|
-
abm.message_id = msg.id
|
|
292
|
-
abm.timestamp = msg.time
|
|
291
|
+
abm.message_id = str(msg.id)
|
|
292
|
+
abm.timestamp = int(cast(int | str, msg.time))
|
|
293
293
|
abm.session_id = abm.sender.user_id
|
|
294
294
|
abm.raw_message = msg
|
|
295
|
-
elif msg
|
|
296
|
-
assert isinstance(msg, ImageMessage)
|
|
295
|
+
elif isinstance(msg, ImageMessage):
|
|
297
296
|
abm.message_str = "[图片]"
|
|
298
297
|
abm.self_id = str(msg.agent)
|
|
299
298
|
abm.message = [Image(file=msg.image, url=msg.image)]
|
|
300
299
|
abm.type = MessageType.FRIEND_MESSAGE
|
|
301
300
|
abm.sender = MessageMember(
|
|
302
|
-
msg.source,
|
|
303
|
-
msg.source,
|
|
301
|
+
cast(str, msg.source),
|
|
302
|
+
cast(str, msg.source),
|
|
304
303
|
)
|
|
305
|
-
abm.message_id = msg.id
|
|
306
|
-
abm.timestamp = msg.time
|
|
304
|
+
abm.message_id = str(msg.id)
|
|
305
|
+
abm.timestamp = int(cast(int | str, msg.time))
|
|
307
306
|
abm.session_id = abm.sender.user_id
|
|
308
307
|
abm.raw_message = msg
|
|
309
|
-
elif msg
|
|
310
|
-
assert isinstance(msg, VoiceMessage)
|
|
311
|
-
|
|
308
|
+
elif isinstance(msg, VoiceMessage):
|
|
312
309
|
resp: Response = await asyncio.get_event_loop().run_in_executor(
|
|
313
310
|
None,
|
|
314
311
|
self.client.media.download,
|
|
@@ -335,11 +332,11 @@ class WecomPlatformAdapter(Platform):
|
|
|
335
332
|
abm.message = [Record(file=path_wav, url=path_wav)]
|
|
336
333
|
abm.type = MessageType.FRIEND_MESSAGE
|
|
337
334
|
abm.sender = MessageMember(
|
|
338
|
-
msg.source,
|
|
339
|
-
msg.source,
|
|
335
|
+
cast(str, msg.source),
|
|
336
|
+
cast(str, msg.source),
|
|
340
337
|
)
|
|
341
|
-
abm.message_id = msg.id
|
|
342
|
-
abm.timestamp = msg.time
|
|
338
|
+
abm.message_id = str(msg.id)
|
|
339
|
+
abm.timestamp = int(cast(int | str, msg.time))
|
|
343
340
|
abm.session_id = abm.sender.user_id
|
|
344
341
|
abm.raw_message = msg
|
|
345
342
|
else:
|
|
@@ -351,7 +348,7 @@ class WecomPlatformAdapter(Platform):
|
|
|
351
348
|
|
|
352
349
|
async def convert_wechat_kf_message(self, msg: dict) -> AstrBotMessage | None:
|
|
353
350
|
msgtype = msg.get("msgtype")
|
|
354
|
-
external_userid = msg.get("external_userid")
|
|
351
|
+
external_userid = cast(str, msg.get("external_userid"))
|
|
355
352
|
abm = AstrBotMessage()
|
|
356
353
|
abm.raw_message = msg
|
|
357
354
|
abm.raw_message["_wechat_kf_flag"] = None # 方便处理
|
|
@@ -425,4 +422,4 @@ class WecomPlatformAdapter(Platform):
|
|
|
425
422
|
await self.server.server.shutdown()
|
|
426
423
|
except Exception as _:
|
|
427
424
|
pass
|
|
428
|
-
logger.info("企业微信
|
|
425
|
+
logger.info("企业微信 适配器已被关闭")
|
|
@@ -93,10 +93,10 @@ class WecomPlatformEvent(AstrMessageEvent):
|
|
|
93
93
|
if is_wechat_kf:
|
|
94
94
|
# 微信客服
|
|
95
95
|
kf_message_api = getattr(self.client, "kf_message", None)
|
|
96
|
-
if not kf_message_api:
|
|
96
|
+
if not isinstance(kf_message_api, WeChatKFMessage):
|
|
97
97
|
logger.warning("未找到微信客服发送消息方法。")
|
|
98
98
|
return
|
|
99
|
-
|
|
99
|
+
|
|
100
100
|
user_id = self.get_sender_id()
|
|
101
101
|
for comp in message.chain:
|
|
102
102
|
if isinstance(comp, Plain):
|
|
@@ -39,7 +39,7 @@ class WecomAIBotMessageEvent(AstrMessageEvent):
|
|
|
39
39
|
|
|
40
40
|
@staticmethod
|
|
41
41
|
async def _send(
|
|
42
|
-
message_chain: MessageChain,
|
|
42
|
+
message_chain: MessageChain | None,
|
|
43
43
|
stream_id: str,
|
|
44
44
|
queue_mgr: WecomAIQueueMgr,
|
|
45
45
|
streaming: bool = False,
|
|
@@ -90,7 +90,7 @@ class WecomAIBotMessageEvent(AstrMessageEvent):
|
|
|
90
90
|
|
|
91
91
|
return data
|
|
92
92
|
|
|
93
|
-
async def send(self, message: MessageChain):
|
|
93
|
+
async def send(self, message: MessageChain | None):
|
|
94
94
|
"""发送消息"""
|
|
95
95
|
raw = self.message_obj.raw_message
|
|
96
96
|
assert isinstance(raw, dict), (
|
|
@@ -98,7 +98,7 @@ class WecomAIBotMessageEvent(AstrMessageEvent):
|
|
|
98
98
|
)
|
|
99
99
|
stream_id = raw.get("stream_id", self.session_id)
|
|
100
100
|
await WecomAIBotMessageEvent._send(message, stream_id, self.queue_mgr)
|
|
101
|
-
await super().send(
|
|
101
|
+
await super().send(MessageChain([]))
|
|
102
102
|
|
|
103
103
|
async def send_streaming(self, generator, use_fallback=False):
|
|
104
104
|
"""流式发送消息,参考webchat的send_streaming设计"""
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import sys
|
|
3
3
|
import uuid
|
|
4
|
-
from
|
|
4
|
+
from collections.abc import Awaitable, Callable
|
|
5
|
+
from typing import Any, cast
|
|
5
6
|
|
|
6
7
|
import quart
|
|
7
8
|
from requests import Response
|
|
@@ -36,7 +37,7 @@ else:
|
|
|
36
37
|
class WeixinOfficialAccountServer:
|
|
37
38
|
def __init__(self, event_queue: asyncio.Queue, config: dict):
|
|
38
39
|
self.server = quart.Quart(__name__)
|
|
39
|
-
self.port = int(config.get("port"))
|
|
40
|
+
self.port = int(cast(int | str, config.get("port")))
|
|
40
41
|
self.callback_server_host = config.get("callback_server_host", "0.0.0.0")
|
|
41
42
|
self.token = config.get("token")
|
|
42
43
|
self.encoding_aes_key = config.get("encoding_aes_key")
|
|
@@ -55,7 +56,7 @@ class WeixinOfficialAccountServer:
|
|
|
55
56
|
|
|
56
57
|
self.event_queue = event_queue
|
|
57
58
|
|
|
58
|
-
self.callback = None
|
|
59
|
+
self.callback: Callable[[BaseMessage], Awaitable[None]] | None = None
|
|
59
60
|
self.shutdown_event = asyncio.Event()
|
|
60
61
|
|
|
61
62
|
async def verify(self):
|
|
@@ -114,6 +115,9 @@ class WeixinOfficialAccountServer:
|
|
|
114
115
|
raise
|
|
115
116
|
else:
|
|
116
117
|
msg = parse_message(xml)
|
|
118
|
+
if not msg:
|
|
119
|
+
logger.error("解析失败。msg为None。")
|
|
120
|
+
raise
|
|
117
121
|
logger.info(f"解析成功: {msg}")
|
|
118
122
|
|
|
119
123
|
if self.callback:
|
|
@@ -176,7 +180,7 @@ class WeixinOfficialAccountPlatformAdapter(Platform):
|
|
|
176
180
|
self.config["secret"].strip(),
|
|
177
181
|
)
|
|
178
182
|
|
|
179
|
-
self.client.API_BASE_URL
|
|
183
|
+
self.client.__setattr__("API_BASE_URL", self.api_base_url)
|
|
180
184
|
|
|
181
185
|
# 微信公众号必须 5 秒内进行回复,否则会重试 3 次,我们需要对其进行消息排重
|
|
182
186
|
# msgid -> Future
|
|
@@ -188,11 +192,11 @@ class WeixinOfficialAccountPlatformAdapter(Platform):
|
|
|
188
192
|
await self.convert_message(msg, None)
|
|
189
193
|
else:
|
|
190
194
|
if msg.id in self.wexin_event_workers:
|
|
191
|
-
future = self.wexin_event_workers[msg.id]
|
|
195
|
+
future = self.wexin_event_workers[str(cast(str | int, msg.id))]
|
|
192
196
|
logger.debug(f"duplicate message id checked: {msg.id}")
|
|
193
197
|
else:
|
|
194
198
|
future = asyncio.get_event_loop().create_future()
|
|
195
|
-
self.wexin_event_workers[msg.id] = future
|
|
199
|
+
self.wexin_event_workers[str(cast(str | int, msg.id))] = future
|
|
196
200
|
await self.convert_message(msg, future)
|
|
197
201
|
# I love shield so much!
|
|
198
202
|
result = await asyncio.wait_for(
|
|
@@ -200,7 +204,7 @@ class WeixinOfficialAccountPlatformAdapter(Platform):
|
|
|
200
204
|
60,
|
|
201
205
|
) # wait for 60s
|
|
202
206
|
logger.debug(f"Got future result: {result}")
|
|
203
|
-
self.wexin_event_workers.pop(msg.id, None)
|
|
207
|
+
self.wexin_event_workers.pop(str(cast(str | int, msg.id)), None)
|
|
204
208
|
return result # xml. see weixin_offacc_event.py
|
|
205
209
|
except asyncio.TimeoutError:
|
|
206
210
|
pass
|
|
@@ -248,33 +252,33 @@ class WeixinOfficialAccountPlatformAdapter(Platform):
|
|
|
248
252
|
async def convert_message(
|
|
249
253
|
self,
|
|
250
254
|
msg,
|
|
251
|
-
future: asyncio.Future = None,
|
|
255
|
+
future: asyncio.Future | None = None,
|
|
252
256
|
) -> AstrBotMessage | None:
|
|
253
257
|
abm = AstrBotMessage()
|
|
254
258
|
if isinstance(msg, TextMessage):
|
|
255
|
-
abm.message_str = msg.content
|
|
259
|
+
abm.message_str = cast(str, msg.content)
|
|
256
260
|
abm.self_id = str(msg.target)
|
|
257
|
-
abm.message = [Plain(msg.content)]
|
|
261
|
+
abm.message = [Plain(cast(str, msg.content))]
|
|
258
262
|
abm.type = MessageType.FRIEND_MESSAGE
|
|
259
263
|
abm.sender = MessageMember(
|
|
260
|
-
msg.source,
|
|
261
|
-
msg.source,
|
|
264
|
+
cast(str, msg.source),
|
|
265
|
+
cast(str, msg.source),
|
|
262
266
|
)
|
|
263
|
-
abm.message_id = msg.id
|
|
264
|
-
abm.timestamp = msg.time
|
|
267
|
+
abm.message_id = str(cast(str | int, msg.id))
|
|
268
|
+
abm.timestamp = cast(int, msg.time)
|
|
265
269
|
abm.session_id = abm.sender.user_id
|
|
266
270
|
elif msg.type == "image":
|
|
267
271
|
assert isinstance(msg, ImageMessage)
|
|
268
272
|
abm.message_str = "[图片]"
|
|
269
273
|
abm.self_id = str(msg.target)
|
|
270
|
-
abm.message = [Image(file=msg.image, url=msg.image)]
|
|
274
|
+
abm.message = [Image(file=cast(str, msg.image), url=cast(str, msg.image))]
|
|
271
275
|
abm.type = MessageType.FRIEND_MESSAGE
|
|
272
276
|
abm.sender = MessageMember(
|
|
273
|
-
msg.source,
|
|
274
|
-
msg.source,
|
|
277
|
+
cast(str, msg.source),
|
|
278
|
+
cast(str, msg.source),
|
|
275
279
|
)
|
|
276
|
-
abm.message_id = msg.id
|
|
277
|
-
abm.timestamp = msg.time
|
|
280
|
+
abm.message_id = str(cast(str | int, msg.id))
|
|
281
|
+
abm.timestamp = cast(int, msg.time)
|
|
278
282
|
abm.session_id = abm.sender.user_id
|
|
279
283
|
elif msg.type == "voice":
|
|
280
284
|
assert isinstance(msg, VoiceMessage)
|
|
@@ -306,15 +310,16 @@ class WeixinOfficialAccountPlatformAdapter(Platform):
|
|
|
306
310
|
abm.message = [Record(file=path_wav, url=path_wav)]
|
|
307
311
|
abm.type = MessageType.FRIEND_MESSAGE
|
|
308
312
|
abm.sender = MessageMember(
|
|
309
|
-
msg.source,
|
|
310
|
-
msg.source,
|
|
313
|
+
cast(str, msg.source),
|
|
314
|
+
cast(str, msg.source),
|
|
311
315
|
)
|
|
312
|
-
abm.message_id = msg.id
|
|
313
|
-
abm.timestamp = msg.time
|
|
316
|
+
abm.message_id = str(cast(str | int, msg.id))
|
|
317
|
+
abm.timestamp = cast(int, msg.time)
|
|
314
318
|
abm.session_id = abm.sender.user_id
|
|
315
319
|
else:
|
|
316
320
|
logger.warning(f"暂未实现的事件: {msg.type}")
|
|
317
|
-
future
|
|
321
|
+
if future:
|
|
322
|
+
future.set_result(None)
|
|
318
323
|
return
|
|
319
324
|
# 很不优雅 :(
|
|
320
325
|
abm.raw_message = {
|
|
@@ -344,4 +349,4 @@ class WeixinOfficialAccountPlatformAdapter(Platform):
|
|
|
344
349
|
await self.server.server.shutdown()
|
|
345
350
|
except Exception as _:
|
|
346
351
|
pass
|
|
347
|
-
logger.info("微信公众平台
|
|
352
|
+
logger.info("微信公众平台 适配器已被关闭")
|