AstrBot 4.7.4__py3-none-any.whl → 4.9.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.
- 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_run_util.py +15 -1
- astrbot/core/astr_agent_tool_exec.py +5 -1
- astrbot/core/config/astrbot_config.py +4 -0
- astrbot/core/config/default.py +116 -1
- astrbot/core/core_lifecycle.py +1 -1
- astrbot/core/db/__init__.py +32 -4
- 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 +56 -1
- 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/agent_sub_stages/third_party.py +1 -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 +4 -2
- astrbot/core/pipeline/result_decorate/stage.py +68 -21
- 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 +71 -9
- astrbot/core/platform/platform.py +109 -4
- 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 +13 -8
- astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py +28 -22
- 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 +53 -26
- astrbot/core/platform/sources/discord/discord_platform_event.py +29 -8
- astrbot/core/platform/sources/lark/lark_adapter.py +178 -22
- 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 +3 -5
- astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py +64 -18
- astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py +14 -10
- astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_adapter.py +36 -11
- astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_server.py +15 -2
- astrbot/core/platform/sources/satori/satori_adapter.py +1 -2
- astrbot/core/platform/sources/slack/client.py +58 -40
- astrbot/core/platform/sources/slack/slack_adapter.py +36 -16
- astrbot/core/platform/sources/slack/slack_event.py +11 -10
- astrbot/core/platform/sources/telegram/tg_adapter.py +2 -3
- astrbot/core/platform/sources/telegram/tg_event.py +23 -27
- astrbot/core/platform/sources/webchat/webchat_adapter.py +97 -31
- astrbot/core/platform/sources/webchat/webchat_event.py +35 -35
- astrbot/core/platform/sources/wechatpadpro/wechatpadpro_adapter.py +27 -11
- astrbot/core/platform/sources/wecom/wecom_adapter.py +75 -36
- astrbot/core/platform/sources/wecom/wecom_event.py +3 -3
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_adapter.py +26 -9
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_event.py +3 -3
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_server.py +27 -5
- astrbot/core/platform/sources/weixin_official_account/weixin_offacc_adapter.py +81 -35
- astrbot/core/platform/sources/weixin_official_account/weixin_offacc_event.py +11 -8
- astrbot/core/platform_message_history_mgr.py +3 -3
- 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 +44 -12
- 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 +2 -2
- astrbot/core/utils/version_comparator.py +6 -3
- astrbot/core/utils/webhook_utils.py +66 -0
- astrbot/dashboard/routes/__init__.py +2 -0
- astrbot/dashboard/routes/chat.py +311 -76
- astrbot/dashboard/routes/config.py +14 -5
- astrbot/dashboard/routes/knowledge_base.py +254 -79
- astrbot/dashboard/routes/log.py +13 -8
- astrbot/dashboard/routes/platform.py +100 -0
- astrbot/dashboard/routes/plugin.py +108 -51
- astrbot/dashboard/routes/route.py +2 -0
- astrbot/dashboard/server.py +9 -4
- {astrbot-4.7.4.dist-info → astrbot-4.9.0.dist-info}/METADATA +50 -37
- {astrbot-4.7.4.dist-info → astrbot-4.9.0.dist-info}/RECORD +111 -108
- {astrbot-4.7.4.dist-info → astrbot-4.9.0.dist-info}/WHEEL +0 -0
- {astrbot-4.7.4.dist-info → astrbot-4.9.0.dist-info}/entry_points.txt +0 -0
- {astrbot-4.7.4.dist-info → astrbot-4.9.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -22,6 +22,7 @@ from astrbot.api.platform import (
|
|
|
22
22
|
PlatformMetadata,
|
|
23
23
|
)
|
|
24
24
|
from astrbot.core.platform.astr_message_event import MessageSesion
|
|
25
|
+
from astrbot.core.utils.webhook_utils import log_webhook_info
|
|
25
26
|
|
|
26
27
|
from ...register import register_platform_adapter
|
|
27
28
|
from .wecomai_api import (
|
|
@@ -103,9 +104,7 @@ class WecomAIBotAdapter(Platform):
|
|
|
103
104
|
platform_settings: dict,
|
|
104
105
|
event_queue: asyncio.Queue,
|
|
105
106
|
) -> None:
|
|
106
|
-
super().__init__(event_queue)
|
|
107
|
-
|
|
108
|
-
self.config = platform_config
|
|
107
|
+
super().__init__(platform_config, event_queue)
|
|
109
108
|
self.settings = platform_settings
|
|
110
109
|
|
|
111
110
|
# 初始化配置参数
|
|
@@ -122,6 +121,7 @@ class WecomAIBotAdapter(Platform):
|
|
|
122
121
|
"wecomaibot_friend_message_welcome_text",
|
|
123
122
|
"",
|
|
124
123
|
)
|
|
124
|
+
self.unified_webhook_mode = self.config.get("unified_webhook_mode", False)
|
|
125
125
|
|
|
126
126
|
# 平台元数据
|
|
127
127
|
self.metadata = PlatformMetadata(
|
|
@@ -425,17 +425,34 @@ class WecomAIBotAdapter(Platform):
|
|
|
425
425
|
|
|
426
426
|
def run(self) -> Awaitable[Any]:
|
|
427
427
|
"""运行适配器,同时启动HTTP服务器和队列监听器"""
|
|
428
|
-
logger.info("启动企业微信智能机器人适配器,监听 %s:%d", self.host, self.port)
|
|
429
428
|
|
|
430
429
|
async def run_both():
|
|
431
|
-
#
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
self.
|
|
435
|
-
|
|
430
|
+
# 如果启用统一 webhook 模式,则不启动独立服务器
|
|
431
|
+
webhook_uuid = self.config.get("webhook_uuid")
|
|
432
|
+
if self.unified_webhook_mode and webhook_uuid:
|
|
433
|
+
log_webhook_info(f"{self.meta().id}(企业微信智能机器人)", webhook_uuid)
|
|
434
|
+
# 只运行队列监听器
|
|
435
|
+
await self.queue_listener.run()
|
|
436
|
+
else:
|
|
437
|
+
logger.info(
|
|
438
|
+
"启动企业微信智能机器人适配器,监听 %s:%d", self.host, self.port
|
|
439
|
+
)
|
|
440
|
+
# 同时运行HTTP服务器和队列监听器
|
|
441
|
+
await asyncio.gather(
|
|
442
|
+
self.server.start_server(),
|
|
443
|
+
self.queue_listener.run(),
|
|
444
|
+
)
|
|
436
445
|
|
|
437
446
|
return run_both()
|
|
438
447
|
|
|
448
|
+
async def webhook_callback(self, request: Any) -> Any:
|
|
449
|
+
"""统一 Webhook 回调入口"""
|
|
450
|
+
# 根据请求方法分发到不同的处理函数
|
|
451
|
+
if request.method == "GET":
|
|
452
|
+
return await self.server.handle_verify(request)
|
|
453
|
+
else:
|
|
454
|
+
return await self.server.handle_callback(request)
|
|
455
|
+
|
|
439
456
|
async def terminate(self):
|
|
440
457
|
"""终止适配器"""
|
|
441
458
|
logger.info("企业微信智能机器人适配器正在关闭...")
|
|
@@ -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设计"""
|
|
@@ -59,8 +59,19 @@ class WecomAIBotServer:
|
|
|
59
59
|
)
|
|
60
60
|
|
|
61
61
|
async def verify_url(self):
|
|
62
|
-
"""
|
|
63
|
-
|
|
62
|
+
"""内部服务器的 GET 验证入口"""
|
|
63
|
+
return await self.handle_verify(quart.request)
|
|
64
|
+
|
|
65
|
+
async def handle_verify(self, request):
|
|
66
|
+
"""处理 URL 验证请求,可被统一 webhook 入口复用
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
request: Quart 请求对象
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
验证响应元组 (content, status_code, headers)
|
|
73
|
+
"""
|
|
74
|
+
args = request.args
|
|
64
75
|
msg_signature = args.get("msg_signature")
|
|
65
76
|
timestamp = args.get("timestamp")
|
|
66
77
|
nonce = args.get("nonce")
|
|
@@ -81,8 +92,19 @@ class WecomAIBotServer:
|
|
|
81
92
|
return result, 200, {"Content-Type": "text/plain"}
|
|
82
93
|
|
|
83
94
|
async def handle_message(self):
|
|
84
|
-
"""
|
|
85
|
-
|
|
95
|
+
"""内部服务器的 POST 消息回调入口"""
|
|
96
|
+
return await self.handle_callback(quart.request)
|
|
97
|
+
|
|
98
|
+
async def handle_callback(self, request):
|
|
99
|
+
"""处理消息回调,可被统一 webhook 入口复用
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
request: Quart 请求对象
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
响应元组 (content, status_code, headers)
|
|
106
|
+
"""
|
|
107
|
+
args = request.args
|
|
86
108
|
msg_signature = args.get("msg_signature")
|
|
87
109
|
timestamp = args.get("timestamp")
|
|
88
110
|
nonce = args.get("nonce")
|
|
@@ -102,7 +124,7 @@ class WecomAIBotServer:
|
|
|
102
124
|
|
|
103
125
|
try:
|
|
104
126
|
# 获取请求体
|
|
105
|
-
post_data = await
|
|
127
|
+
post_data = await request.get_data()
|
|
106
128
|
|
|
107
129
|
# 确保 post_data 是 bytes 类型
|
|
108
130
|
if isinstance(post_data, str):
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import sys
|
|
3
3
|
import uuid
|
|
4
|
+
from collections.abc import Awaitable, Callable
|
|
5
|
+
from typing import Any, cast
|
|
4
6
|
|
|
5
7
|
import quart
|
|
6
8
|
from requests import Response
|
|
@@ -22,6 +24,7 @@ from astrbot.api.platform import (
|
|
|
22
24
|
)
|
|
23
25
|
from astrbot.core import logger
|
|
24
26
|
from astrbot.core.platform.astr_message_event import MessageSesion
|
|
27
|
+
from astrbot.core.utils.webhook_utils import log_webhook_info
|
|
25
28
|
|
|
26
29
|
from .weixin_offacc_event import WeixinOfficialAccountPlatformEvent
|
|
27
30
|
|
|
@@ -31,10 +34,10 @@ else:
|
|
|
31
34
|
from typing_extensions import override
|
|
32
35
|
|
|
33
36
|
|
|
34
|
-
class
|
|
37
|
+
class WeixinOfficialAccountServer:
|
|
35
38
|
def __init__(self, event_queue: asyncio.Queue, config: dict):
|
|
36
39
|
self.server = quart.Quart(__name__)
|
|
37
|
-
self.port = int(config.get("port"))
|
|
40
|
+
self.port = int(cast(int | str, config.get("port")))
|
|
38
41
|
self.callback_server_host = config.get("callback_server_host", "0.0.0.0")
|
|
39
42
|
self.token = config.get("token")
|
|
40
43
|
self.encoding_aes_key = config.get("encoding_aes_key")
|
|
@@ -53,13 +56,25 @@ class WecomServer:
|
|
|
53
56
|
|
|
54
57
|
self.event_queue = event_queue
|
|
55
58
|
|
|
56
|
-
self.callback = None
|
|
59
|
+
self.callback: Callable[[BaseMessage], Awaitable[None]] | None = None
|
|
57
60
|
self.shutdown_event = asyncio.Event()
|
|
58
61
|
|
|
59
62
|
async def verify(self):
|
|
60
|
-
|
|
63
|
+
"""内部服务器的 GET 验证入口"""
|
|
64
|
+
return await self.handle_verify(quart.request)
|
|
61
65
|
|
|
62
|
-
|
|
66
|
+
async def handle_verify(self, request) -> str:
|
|
67
|
+
"""处理验证请求,可被统一 webhook 入口复用
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
request: Quart 请求对象
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
验证响应
|
|
74
|
+
"""
|
|
75
|
+
logger.info(f"验证请求有效性: {request.args}")
|
|
76
|
+
|
|
77
|
+
args = request.args
|
|
63
78
|
if not args.get("signature", None):
|
|
64
79
|
logger.error("未知的响应,请检查回调地址是否填写正确。")
|
|
65
80
|
return "err"
|
|
@@ -77,10 +92,22 @@ class WecomServer:
|
|
|
77
92
|
return "err"
|
|
78
93
|
|
|
79
94
|
async def callback_command(self):
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
95
|
+
"""内部服务器的 POST 回调入口"""
|
|
96
|
+
return await self.handle_callback(quart.request)
|
|
97
|
+
|
|
98
|
+
async def handle_callback(self, request) -> str:
|
|
99
|
+
"""处理回调请求,可被统一 webhook 入口复用
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
request: Quart 请求对象
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
响应内容
|
|
106
|
+
"""
|
|
107
|
+
data = await request.get_data()
|
|
108
|
+
msg_signature = request.args.get("msg_signature")
|
|
109
|
+
timestamp = request.args.get("timestamp")
|
|
110
|
+
nonce = request.args.get("nonce")
|
|
84
111
|
try:
|
|
85
112
|
xml = self.crypto.decrypt_message(data, msg_signature, timestamp, nonce)
|
|
86
113
|
except InvalidSignatureException:
|
|
@@ -88,6 +115,9 @@ class WecomServer:
|
|
|
88
115
|
raise
|
|
89
116
|
else:
|
|
90
117
|
msg = parse_message(xml)
|
|
118
|
+
if not msg:
|
|
119
|
+
logger.error("解析失败。msg为None。")
|
|
120
|
+
raise
|
|
91
121
|
logger.info(f"解析成功: {msg}")
|
|
92
122
|
|
|
93
123
|
if self.callback:
|
|
@@ -123,8 +153,7 @@ class WeixinOfficialAccountPlatformAdapter(Platform):
|
|
|
123
153
|
platform_settings: dict,
|
|
124
154
|
event_queue: asyncio.Queue,
|
|
125
155
|
) -> None:
|
|
126
|
-
super().__init__(event_queue)
|
|
127
|
-
self.config = platform_config
|
|
156
|
+
super().__init__(platform_config, event_queue)
|
|
128
157
|
self.settingss = platform_settings
|
|
129
158
|
self.client_self_id = uuid.uuid4().hex[:8]
|
|
130
159
|
self.api_base_url = platform_config.get(
|
|
@@ -132,6 +161,7 @@ class WeixinOfficialAccountPlatformAdapter(Platform):
|
|
|
132
161
|
"https://api.weixin.qq.com/cgi-bin/",
|
|
133
162
|
)
|
|
134
163
|
self.active_send_mode = self.config.get("active_send_mode", False)
|
|
164
|
+
self.unified_webhook_mode = platform_config.get("unified_webhook_mode", False)
|
|
135
165
|
|
|
136
166
|
if not self.api_base_url:
|
|
137
167
|
self.api_base_url = "https://api.weixin.qq.com/cgi-bin/"
|
|
@@ -143,14 +173,14 @@ class WeixinOfficialAccountPlatformAdapter(Platform):
|
|
|
143
173
|
if not self.api_base_url.endswith("/"):
|
|
144
174
|
self.api_base_url += "/"
|
|
145
175
|
|
|
146
|
-
self.server =
|
|
176
|
+
self.server = WeixinOfficialAccountServer(self._event_queue, self.config)
|
|
147
177
|
|
|
148
178
|
self.client = WeChatClient(
|
|
149
179
|
self.config["appid"].strip(),
|
|
150
180
|
self.config["secret"].strip(),
|
|
151
181
|
)
|
|
152
182
|
|
|
153
|
-
self.client.API_BASE_URL
|
|
183
|
+
self.client.__setattr__("API_BASE_URL", self.api_base_url)
|
|
154
184
|
|
|
155
185
|
# 微信公众号必须 5 秒内进行回复,否则会重试 3 次,我们需要对其进行消息排重
|
|
156
186
|
# msgid -> Future
|
|
@@ -162,11 +192,11 @@ class WeixinOfficialAccountPlatformAdapter(Platform):
|
|
|
162
192
|
await self.convert_message(msg, None)
|
|
163
193
|
else:
|
|
164
194
|
if msg.id in self.wexin_event_workers:
|
|
165
|
-
future = self.wexin_event_workers[msg.id]
|
|
195
|
+
future = self.wexin_event_workers[str(cast(str | int, msg.id))]
|
|
166
196
|
logger.debug(f"duplicate message id checked: {msg.id}")
|
|
167
197
|
else:
|
|
168
198
|
future = asyncio.get_event_loop().create_future()
|
|
169
|
-
self.wexin_event_workers[msg.id] = future
|
|
199
|
+
self.wexin_event_workers[str(cast(str | int, msg.id))] = future
|
|
170
200
|
await self.convert_message(msg, future)
|
|
171
201
|
# I love shield so much!
|
|
172
202
|
result = await asyncio.wait_for(
|
|
@@ -174,7 +204,7 @@ class WeixinOfficialAccountPlatformAdapter(Platform):
|
|
|
174
204
|
60,
|
|
175
205
|
) # wait for 60s
|
|
176
206
|
logger.debug(f"Got future result: {result}")
|
|
177
|
-
self.wexin_event_workers.pop(msg.id, None)
|
|
207
|
+
self.wexin_event_workers.pop(str(cast(str | int, msg.id)), None)
|
|
178
208
|
return result # xml. see weixin_offacc_event.py
|
|
179
209
|
except asyncio.TimeoutError:
|
|
180
210
|
pass
|
|
@@ -202,38 +232,53 @@ class WeixinOfficialAccountPlatformAdapter(Platform):
|
|
|
202
232
|
|
|
203
233
|
@override
|
|
204
234
|
async def run(self):
|
|
205
|
-
|
|
235
|
+
# 如果启用统一 webhook 模式,则不启动独立服务器
|
|
236
|
+
webhook_uuid = self.config.get("webhook_uuid")
|
|
237
|
+
if self.unified_webhook_mode and webhook_uuid:
|
|
238
|
+
log_webhook_info(f"{self.meta().id}(微信公众平台)", webhook_uuid)
|
|
239
|
+
# 保持运行状态,等待 shutdown
|
|
240
|
+
await self.server.shutdown_event.wait()
|
|
241
|
+
else:
|
|
242
|
+
await self.server.start_polling()
|
|
243
|
+
|
|
244
|
+
async def webhook_callback(self, request: Any) -> Any:
|
|
245
|
+
"""统一 Webhook 回调入口"""
|
|
246
|
+
# 根据请求方法分发到不同的处理函数
|
|
247
|
+
if request.method == "GET":
|
|
248
|
+
return await self.server.handle_verify(request)
|
|
249
|
+
else:
|
|
250
|
+
return await self.server.handle_callback(request)
|
|
206
251
|
|
|
207
252
|
async def convert_message(
|
|
208
253
|
self,
|
|
209
254
|
msg,
|
|
210
|
-
future: asyncio.Future = None,
|
|
255
|
+
future: asyncio.Future | None = None,
|
|
211
256
|
) -> AstrBotMessage | None:
|
|
212
257
|
abm = AstrBotMessage()
|
|
213
258
|
if isinstance(msg, TextMessage):
|
|
214
|
-
abm.message_str = msg.content
|
|
259
|
+
abm.message_str = cast(str, msg.content)
|
|
215
260
|
abm.self_id = str(msg.target)
|
|
216
|
-
abm.message = [Plain(msg.content)]
|
|
261
|
+
abm.message = [Plain(cast(str, msg.content))]
|
|
217
262
|
abm.type = MessageType.FRIEND_MESSAGE
|
|
218
263
|
abm.sender = MessageMember(
|
|
219
|
-
msg.source,
|
|
220
|
-
msg.source,
|
|
264
|
+
cast(str, msg.source),
|
|
265
|
+
cast(str, msg.source),
|
|
221
266
|
)
|
|
222
|
-
abm.message_id = msg.id
|
|
223
|
-
abm.timestamp = msg.time
|
|
267
|
+
abm.message_id = str(cast(str | int, msg.id))
|
|
268
|
+
abm.timestamp = cast(int, msg.time)
|
|
224
269
|
abm.session_id = abm.sender.user_id
|
|
225
270
|
elif msg.type == "image":
|
|
226
271
|
assert isinstance(msg, ImageMessage)
|
|
227
272
|
abm.message_str = "[图片]"
|
|
228
273
|
abm.self_id = str(msg.target)
|
|
229
|
-
abm.message = [Image(file=msg.image, url=msg.image)]
|
|
274
|
+
abm.message = [Image(file=cast(str, msg.image), url=cast(str, msg.image))]
|
|
230
275
|
abm.type = MessageType.FRIEND_MESSAGE
|
|
231
276
|
abm.sender = MessageMember(
|
|
232
|
-
msg.source,
|
|
233
|
-
msg.source,
|
|
277
|
+
cast(str, msg.source),
|
|
278
|
+
cast(str, msg.source),
|
|
234
279
|
)
|
|
235
|
-
abm.message_id = msg.id
|
|
236
|
-
abm.timestamp = msg.time
|
|
280
|
+
abm.message_id = str(cast(str | int, msg.id))
|
|
281
|
+
abm.timestamp = cast(int, msg.time)
|
|
237
282
|
abm.session_id = abm.sender.user_id
|
|
238
283
|
elif msg.type == "voice":
|
|
239
284
|
assert isinstance(msg, VoiceMessage)
|
|
@@ -265,15 +310,16 @@ class WeixinOfficialAccountPlatformAdapter(Platform):
|
|
|
265
310
|
abm.message = [Record(file=path_wav, url=path_wav)]
|
|
266
311
|
abm.type = MessageType.FRIEND_MESSAGE
|
|
267
312
|
abm.sender = MessageMember(
|
|
268
|
-
msg.source,
|
|
269
|
-
msg.source,
|
|
313
|
+
cast(str, msg.source),
|
|
314
|
+
cast(str, msg.source),
|
|
270
315
|
)
|
|
271
|
-
abm.message_id = msg.id
|
|
272
|
-
abm.timestamp = msg.time
|
|
316
|
+
abm.message_id = str(cast(str | int, msg.id))
|
|
317
|
+
abm.timestamp = cast(int, msg.time)
|
|
273
318
|
abm.session_id = abm.sender.user_id
|
|
274
319
|
else:
|
|
275
320
|
logger.warning(f"暂未实现的事件: {msg.type}")
|
|
276
|
-
future
|
|
321
|
+
if future:
|
|
322
|
+
future.set_result(None)
|
|
277
323
|
return
|
|
278
324
|
# 很不优雅 :(
|
|
279
325
|
abm.raw_message = {
|
|
@@ -303,4 +349,4 @@ class WeixinOfficialAccountPlatformAdapter(Platform):
|
|
|
303
349
|
await self.server.server.shutdown()
|
|
304
350
|
except Exception as _:
|
|
305
351
|
pass
|
|
306
|
-
logger.info("微信公众平台
|
|
352
|
+
logger.info("微信公众平台 适配器已被关闭")
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import uuid
|
|
3
|
+
from typing import cast
|
|
3
4
|
|
|
4
5
|
from wechatpy import WeChatClient
|
|
5
6
|
from wechatpy.replies import ImageReply, TextReply, VoiceReply
|
|
@@ -13,7 +14,7 @@ try:
|
|
|
13
14
|
import pydub
|
|
14
15
|
except Exception:
|
|
15
16
|
logger.warning(
|
|
16
|
-
"检测到 pydub 库未安装,微信公众平台将无法语音收发。如需使用语音,请前往管理面板 ->
|
|
17
|
+
"检测到 pydub 库未安装,微信公众平台将无法语音收发。如需使用语音,请前往管理面板 -> 平台日志 -> 安装 Pip 库安装 pydub。",
|
|
17
18
|
)
|
|
18
19
|
|
|
19
20
|
|
|
@@ -85,7 +86,9 @@ class WeixinOfficialAccountPlatformEvent(AstrMessageEvent):
|
|
|
85
86
|
|
|
86
87
|
async def send(self, message: MessageChain):
|
|
87
88
|
message_obj = self.message_obj
|
|
88
|
-
active_send_mode = message_obj.raw_message.get(
|
|
89
|
+
active_send_mode = cast(dict, message_obj.raw_message).get(
|
|
90
|
+
"active_send_mode", False
|
|
91
|
+
)
|
|
89
92
|
for comp in message.chain:
|
|
90
93
|
if isinstance(comp, Plain):
|
|
91
94
|
# Split long text messages if needed
|
|
@@ -96,10 +99,10 @@ class WeixinOfficialAccountPlatformEvent(AstrMessageEvent):
|
|
|
96
99
|
else:
|
|
97
100
|
reply = TextReply(
|
|
98
101
|
content=chunk,
|
|
99
|
-
message=self.message_obj.raw_message["message"],
|
|
102
|
+
message=cast(dict, self.message_obj.raw_message)["message"],
|
|
100
103
|
)
|
|
101
104
|
xml = reply.render()
|
|
102
|
-
future = self.message_obj.raw_message["future"]
|
|
105
|
+
future = cast(dict, self.message_obj.raw_message)["future"]
|
|
103
106
|
assert isinstance(future, asyncio.Future)
|
|
104
107
|
future.set_result(xml)
|
|
105
108
|
await asyncio.sleep(0.5) # Avoid sending too fast
|
|
@@ -125,10 +128,10 @@ class WeixinOfficialAccountPlatformEvent(AstrMessageEvent):
|
|
|
125
128
|
else:
|
|
126
129
|
reply = ImageReply(
|
|
127
130
|
media_id=response["media_id"],
|
|
128
|
-
message=self.message_obj.raw_message["message"],
|
|
131
|
+
message=cast(dict, self.message_obj.raw_message)["message"],
|
|
129
132
|
)
|
|
130
133
|
xml = reply.render()
|
|
131
|
-
future = self.message_obj.raw_message["future"]
|
|
134
|
+
future = cast(dict, self.message_obj.raw_message)["future"]
|
|
132
135
|
assert isinstance(future, asyncio.Future)
|
|
133
136
|
future.set_result(xml)
|
|
134
137
|
|
|
@@ -160,10 +163,10 @@ class WeixinOfficialAccountPlatformEvent(AstrMessageEvent):
|
|
|
160
163
|
else:
|
|
161
164
|
reply = VoiceReply(
|
|
162
165
|
media_id=response["media_id"],
|
|
163
|
-
message=self.message_obj.raw_message["message"],
|
|
166
|
+
message=cast(dict, self.message_obj.raw_message)["message"],
|
|
164
167
|
)
|
|
165
168
|
xml = reply.render()
|
|
166
|
-
future = self.message_obj.raw_message["future"]
|
|
169
|
+
future = cast(dict, self.message_obj.raw_message)["future"]
|
|
167
170
|
assert isinstance(future, asyncio.Future)
|
|
168
171
|
future.set_result(xml)
|
|
169
172
|
|
|
@@ -10,12 +10,12 @@ class PlatformMessageHistoryManager:
|
|
|
10
10
|
self,
|
|
11
11
|
platform_id: str,
|
|
12
12
|
user_id: str,
|
|
13
|
-
content:
|
|
13
|
+
content: dict, # TODO: parse from message chain
|
|
14
14
|
sender_id: str | None = None,
|
|
15
15
|
sender_name: str | None = None,
|
|
16
|
-
):
|
|
16
|
+
) -> PlatformMessageHistory:
|
|
17
17
|
"""Insert a new platform message history record."""
|
|
18
|
-
await self.db.insert_platform_message_history(
|
|
18
|
+
return await self.db.insert_platform_message_history(
|
|
19
19
|
platform_id=platform_id,
|
|
20
20
|
user_id=user_id,
|
|
21
21
|
content=content,
|
|
@@ -4,7 +4,7 @@ import asyncio
|
|
|
4
4
|
import copy
|
|
5
5
|
import json
|
|
6
6
|
import os
|
|
7
|
-
from collections.abc import Awaitable, Callable
|
|
7
|
+
from collections.abc import AsyncGenerator, Awaitable, Callable
|
|
8
8
|
from typing import Any
|
|
9
9
|
|
|
10
10
|
import aiohttp
|
|
@@ -118,7 +118,7 @@ class FunctionToolManager:
|
|
|
118
118
|
name: str,
|
|
119
119
|
func_args: list[dict],
|
|
120
120
|
desc: str,
|
|
121
|
-
handler: Callable[..., Awaitable[Any]],
|
|
121
|
+
handler: Callable[..., Awaitable[Any] | AsyncGenerator[Any]],
|
|
122
122
|
) -> FuncTool:
|
|
123
123
|
params = {
|
|
124
124
|
"type": "object", # hard-coded here
|
|
@@ -140,7 +140,7 @@ class FunctionToolManager:
|
|
|
140
140
|
name: str,
|
|
141
141
|
func_args: list,
|
|
142
142
|
desc: str,
|
|
143
|
-
handler: Callable[..., Awaitable[Any]],
|
|
143
|
+
handler: Callable[..., Awaitable[Any] | AsyncGenerator[Any]],
|
|
144
144
|
) -> None:
|
|
145
145
|
"""添加函数调用工具
|
|
146
146
|
|