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
1
|
from typing import TYPE_CHECKING
|
|
2
|
+
|
|
2
3
|
from astrbot.api import logger
|
|
3
4
|
from astrbot.api.event import AstrMessageEvent, MessageChain
|
|
4
|
-
from astrbot.api.platform import AstrBotMessage, PlatformMetadata
|
|
5
5
|
from astrbot.api.message_components import (
|
|
6
|
-
Plain,
|
|
7
|
-
Image,
|
|
8
6
|
At,
|
|
9
7
|
File,
|
|
10
|
-
Record,
|
|
11
|
-
Video,
|
|
12
|
-
Reply,
|
|
13
8
|
Forward,
|
|
9
|
+
Image,
|
|
14
10
|
Node,
|
|
15
11
|
Nodes,
|
|
12
|
+
Plain,
|
|
13
|
+
Record,
|
|
14
|
+
Reply,
|
|
15
|
+
Video,
|
|
16
16
|
)
|
|
17
|
+
from astrbot.api.platform import AstrBotMessage, PlatformMetadata
|
|
17
18
|
|
|
18
19
|
if TYPE_CHECKING:
|
|
19
20
|
from .satori_adapter import SatoriPlatformAdapter
|
|
@@ -53,14 +54,17 @@ class SatoriPlatformEvent(AstrMessageEvent):
|
|
|
53
54
|
|
|
54
55
|
@classmethod
|
|
55
56
|
async def send_with_adapter(
|
|
56
|
-
cls,
|
|
57
|
+
cls,
|
|
58
|
+
adapter: "SatoriPlatformAdapter",
|
|
59
|
+
message: MessageChain,
|
|
60
|
+
session_id: str,
|
|
57
61
|
):
|
|
58
62
|
try:
|
|
59
63
|
content_parts = []
|
|
60
64
|
|
|
61
65
|
for component in message.chain:
|
|
62
66
|
component_content = await cls._convert_component_to_satori_static(
|
|
63
|
-
component
|
|
67
|
+
component,
|
|
64
68
|
)
|
|
65
69
|
if component_content:
|
|
66
70
|
content_parts.append(component_content)
|
|
@@ -92,12 +96,15 @@ class SatoriPlatformEvent(AstrMessageEvent):
|
|
|
92
96
|
user_id = user.get("id", "") if user else ""
|
|
93
97
|
|
|
94
98
|
result = await adapter.send_http_request(
|
|
95
|
-
"POST",
|
|
99
|
+
"POST",
|
|
100
|
+
"/message.create",
|
|
101
|
+
data,
|
|
102
|
+
platform,
|
|
103
|
+
user_id,
|
|
96
104
|
)
|
|
97
105
|
if result:
|
|
98
106
|
return result
|
|
99
|
-
|
|
100
|
-
return None
|
|
107
|
+
return None
|
|
101
108
|
|
|
102
109
|
except Exception as e:
|
|
103
110
|
logger.error(f"Satori 消息发送异常: {e}")
|
|
@@ -140,7 +147,11 @@ class SatoriPlatformEvent(AstrMessageEvent):
|
|
|
140
147
|
data = {"channel_id": channel_id, "content": content}
|
|
141
148
|
|
|
142
149
|
result = await self.adapter.send_http_request(
|
|
143
|
-
"POST",
|
|
150
|
+
"POST",
|
|
151
|
+
"/message.create",
|
|
152
|
+
data,
|
|
153
|
+
platform,
|
|
154
|
+
user_id,
|
|
144
155
|
)
|
|
145
156
|
if not result:
|
|
146
157
|
logger.error("Satori 消息发送失败")
|
|
@@ -178,9 +189,9 @@ class SatoriPlatformEvent(AstrMessageEvent):
|
|
|
178
189
|
img_chain = MessageChain(
|
|
179
190
|
[
|
|
180
191
|
Plain(
|
|
181
|
-
text=f'<img src="data:image/jpeg;base64,{image_base64}"/>'
|
|
182
|
-
)
|
|
183
|
-
]
|
|
192
|
+
text=f'<img src="data:image/jpeg;base64,{image_base64}"/>',
|
|
193
|
+
),
|
|
194
|
+
],
|
|
184
195
|
)
|
|
185
196
|
await self.send(img_chain)
|
|
186
197
|
except Exception as e:
|
|
@@ -209,10 +220,10 @@ class SatoriPlatformEvent(AstrMessageEvent):
|
|
|
209
220
|
)
|
|
210
221
|
return text
|
|
211
222
|
|
|
212
|
-
|
|
223
|
+
if isinstance(component, At):
|
|
213
224
|
if component.qq:
|
|
214
225
|
return f'<at id="{component.qq}"/>'
|
|
215
|
-
|
|
226
|
+
if component.name:
|
|
216
227
|
return f'<at name="{component.name}"/>'
|
|
217
228
|
|
|
218
229
|
elif isinstance(component, Image):
|
|
@@ -264,7 +275,7 @@ class SatoriPlatformEvent(AstrMessageEvent):
|
|
|
264
275
|
if node.content:
|
|
265
276
|
for content_component in node.content:
|
|
266
277
|
component_content = await self._convert_component_to_satori(
|
|
267
|
-
content_component
|
|
278
|
+
content_component,
|
|
268
279
|
)
|
|
269
280
|
if component_content:
|
|
270
281
|
content_parts.append(component_content)
|
|
@@ -302,10 +313,10 @@ class SatoriPlatformEvent(AstrMessageEvent):
|
|
|
302
313
|
)
|
|
303
314
|
return text
|
|
304
315
|
|
|
305
|
-
|
|
316
|
+
if isinstance(component, At):
|
|
306
317
|
if component.qq:
|
|
307
318
|
return f'<at id="{component.qq}"/>'
|
|
308
|
-
|
|
319
|
+
if component.name:
|
|
309
320
|
return f'<at name="{component.name}"/>'
|
|
310
321
|
|
|
311
322
|
elif isinstance(component, Image):
|
|
@@ -358,7 +369,7 @@ class SatoriPlatformEvent(AstrMessageEvent):
|
|
|
358
369
|
if node.content:
|
|
359
370
|
for content_component in node.content:
|
|
360
371
|
component_content = await cls._convert_component_to_satori_static(
|
|
361
|
-
content_component
|
|
372
|
+
content_component,
|
|
362
373
|
)
|
|
363
374
|
if component_content:
|
|
364
375
|
content_parts.append(component_content)
|
|
@@ -395,8 +406,7 @@ class SatoriPlatformEvent(AstrMessageEvent):
|
|
|
395
406
|
|
|
396
407
|
if node_parts:
|
|
397
408
|
return f"<message forward>{''.join(node_parts)}</message>"
|
|
398
|
-
|
|
399
|
-
return ""
|
|
409
|
+
return ""
|
|
400
410
|
|
|
401
411
|
except Exception as e:
|
|
402
412
|
logger.error(f"转换合并转发消息失败: {e}")
|
|
@@ -415,8 +425,7 @@ class SatoriPlatformEvent(AstrMessageEvent):
|
|
|
415
425
|
|
|
416
426
|
if node_parts:
|
|
417
427
|
return f"<message forward>{''.join(node_parts)}</message>"
|
|
418
|
-
|
|
419
|
-
return ""
|
|
428
|
+
return ""
|
|
420
429
|
|
|
421
430
|
except Exception as e:
|
|
422
431
|
logger.error(f"转换合并转发消息失败: {e}")
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
import json
|
|
2
|
-
import hmac
|
|
3
|
-
import hashlib
|
|
4
1
|
import asyncio
|
|
2
|
+
import hashlib
|
|
3
|
+
import hmac
|
|
4
|
+
import json
|
|
5
5
|
import logging
|
|
6
|
-
from
|
|
7
|
-
|
|
8
|
-
from
|
|
6
|
+
from collections.abc import Callable
|
|
7
|
+
|
|
8
|
+
from quart import Quart, Response, request
|
|
9
9
|
from slack_sdk.socket_mode.aiohttp import SocketModeClient
|
|
10
10
|
from slack_sdk.socket_mode.request import SocketModeRequest
|
|
11
11
|
from slack_sdk.socket_mode.response import SocketModeResponse
|
|
12
|
+
from slack_sdk.web.async_client import AsyncWebClient
|
|
13
|
+
|
|
12
14
|
from astrbot.api import logger
|
|
13
15
|
|
|
14
16
|
|
|
@@ -22,7 +24,7 @@ class SlackWebhookClient:
|
|
|
22
24
|
host: str = "0.0.0.0",
|
|
23
25
|
port: int = 3000,
|
|
24
26
|
path: str = "/slack/events",
|
|
25
|
-
event_handler:
|
|
27
|
+
event_handler: Callable | None = None,
|
|
26
28
|
):
|
|
27
29
|
self.web_client = web_client
|
|
28
30
|
self.signing_secret = signing_secret
|
|
@@ -93,7 +95,7 @@ class SlackWebhookClient:
|
|
|
93
95
|
async def start(self):
|
|
94
96
|
"""启动 Webhook 服务器"""
|
|
95
97
|
logger.info(
|
|
96
|
-
f"Slack Webhook 服务器启动中,监听 {self.host}:{self.port}{self.path}..."
|
|
98
|
+
f"Slack Webhook 服务器启动中,监听 {self.host}:{self.port}{self.path}...",
|
|
97
99
|
)
|
|
98
100
|
|
|
99
101
|
await self.app.run_task(
|
|
@@ -119,7 +121,7 @@ class SlackSocketClient:
|
|
|
119
121
|
self,
|
|
120
122
|
web_client: AsyncWebClient,
|
|
121
123
|
app_token: str,
|
|
122
|
-
event_handler:
|
|
124
|
+
event_handler: Callable | None = None,
|
|
123
125
|
):
|
|
124
126
|
self.web_client = web_client
|
|
125
127
|
self.app_token = app_token
|
|
@@ -1,34 +1,42 @@
|
|
|
1
|
-
import time
|
|
2
1
|
import asyncio
|
|
2
|
+
import base64
|
|
3
|
+
import re
|
|
4
|
+
import time
|
|
3
5
|
import uuid
|
|
6
|
+
from collections.abc import Awaitable
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
4
9
|
import aiohttp
|
|
5
|
-
import re
|
|
6
|
-
import base64
|
|
7
|
-
from typing import Awaitable, Any
|
|
8
|
-
from slack_sdk.web.async_client import AsyncWebClient
|
|
9
10
|
from slack_sdk.socket_mode.request import SocketModeRequest
|
|
11
|
+
from slack_sdk.web.async_client import AsyncWebClient
|
|
12
|
+
|
|
13
|
+
from astrbot.api import logger
|
|
14
|
+
from astrbot.api.event import MessageChain
|
|
15
|
+
from astrbot.api.message_components import *
|
|
10
16
|
from astrbot.api.platform import (
|
|
11
|
-
Platform,
|
|
12
17
|
AstrBotMessage,
|
|
13
18
|
MessageMember,
|
|
14
19
|
MessageType,
|
|
20
|
+
Platform,
|
|
15
21
|
PlatformMetadata,
|
|
16
22
|
)
|
|
17
|
-
from astrbot.api.event import MessageChain
|
|
18
|
-
from .slack_event import SlackMessageEvent
|
|
19
|
-
from .client import SlackWebhookClient, SlackSocketClient
|
|
20
|
-
from astrbot.api.message_components import * # noqa: F403
|
|
21
|
-
from astrbot.api import logger
|
|
22
23
|
from astrbot.core.platform.astr_message_event import MessageSesion
|
|
24
|
+
|
|
23
25
|
from ...register import register_platform_adapter
|
|
26
|
+
from .client import SlackSocketClient, SlackWebhookClient
|
|
27
|
+
from .slack_event import SlackMessageEvent
|
|
24
28
|
|
|
25
29
|
|
|
26
30
|
@register_platform_adapter(
|
|
27
|
-
"slack",
|
|
31
|
+
"slack",
|
|
32
|
+
"适用于 Slack 的消息平台适配器,支持 Socket Mode 和 Webhook Mode。",
|
|
28
33
|
)
|
|
29
34
|
class SlackAdapter(Platform):
|
|
30
35
|
def __init__(
|
|
31
|
-
self,
|
|
36
|
+
self,
|
|
37
|
+
platform_config: dict,
|
|
38
|
+
platform_settings: dict,
|
|
39
|
+
event_queue: asyncio.Queue,
|
|
32
40
|
) -> None:
|
|
33
41
|
super().__init__(event_queue)
|
|
34
42
|
|
|
@@ -43,7 +51,8 @@ class SlackAdapter(Platform):
|
|
|
43
51
|
self.webhook_host = platform_config.get("slack_webhook_host", "0.0.0.0")
|
|
44
52
|
self.webhook_port = platform_config.get("slack_webhook_port", 3000)
|
|
45
53
|
self.webhook_path = platform_config.get(
|
|
46
|
-
"slack_webhook_path",
|
|
54
|
+
"slack_webhook_path",
|
|
55
|
+
"/astrbot-slack-webhook/callback",
|
|
47
56
|
)
|
|
48
57
|
|
|
49
58
|
if not self.bot_token:
|
|
@@ -69,10 +78,13 @@ class SlackAdapter(Platform):
|
|
|
69
78
|
self.bot_self_id = None
|
|
70
79
|
|
|
71
80
|
async def send_by_session(
|
|
72
|
-
self,
|
|
81
|
+
self,
|
|
82
|
+
session: MessageSesion,
|
|
83
|
+
message_chain: MessageChain,
|
|
73
84
|
):
|
|
74
|
-
blocks, text = SlackMessageEvent._parse_slack_blocks(
|
|
75
|
-
message_chain=message_chain,
|
|
85
|
+
blocks, text = await SlackMessageEvent._parse_slack_blocks(
|
|
86
|
+
message_chain=message_chain,
|
|
87
|
+
web_client=self.web_client,
|
|
76
88
|
)
|
|
77
89
|
|
|
78
90
|
try:
|
|
@@ -150,7 +162,7 @@ class SlackAdapter(Platform):
|
|
|
150
162
|
abm.message = []
|
|
151
163
|
|
|
152
164
|
# 优先使用 blocks 字段解析消息
|
|
153
|
-
if
|
|
165
|
+
if event.get("blocks"):
|
|
154
166
|
abm.message = self._parse_blocks(event["blocks"])
|
|
155
167
|
# 更新 message_str
|
|
156
168
|
abm.message_str = ""
|
|
@@ -166,7 +178,8 @@ class SlackAdapter(Platform):
|
|
|
166
178
|
mentioned_user = await self.web_client.users_info(user=mention)
|
|
167
179
|
user_data = mentioned_user["user"]
|
|
168
180
|
user_name = user_data.get("real_name") or user_data.get(
|
|
169
|
-
"name",
|
|
181
|
+
"name",
|
|
182
|
+
mention,
|
|
170
183
|
)
|
|
171
184
|
abm.message.append(At(qq=mention, name=user_name))
|
|
172
185
|
except Exception:
|
|
@@ -189,7 +202,7 @@ class SlackAdapter(Platform):
|
|
|
189
202
|
else:
|
|
190
203
|
# TODO: 下载鉴权
|
|
191
204
|
abm.message.append(
|
|
192
|
-
File(name=file_name, file=file_url, url=file_url)
|
|
205
|
+
File(name=file_name, file=file_url, url=file_url),
|
|
193
206
|
)
|
|
194
207
|
|
|
195
208
|
abm.raw_message = event
|
|
@@ -209,39 +222,41 @@ class SlackAdapter(Platform):
|
|
|
209
222
|
if element.get("type") == "rich_text_section":
|
|
210
223
|
# 处理富文本段落
|
|
211
224
|
section_elements = element.get("elements", [])
|
|
212
|
-
|
|
213
|
-
|
|
225
|
+
text_parts = []
|
|
214
226
|
for section_element in section_elements:
|
|
215
227
|
element_type = section_element.get("type", "")
|
|
216
228
|
|
|
217
229
|
if element_type == "text":
|
|
218
230
|
# 普通文本
|
|
219
|
-
|
|
231
|
+
text_parts.append(section_element.get("text", ""))
|
|
220
232
|
elif element_type == "user":
|
|
221
233
|
# @用户提及
|
|
222
234
|
user_id = section_element.get("user_id", "")
|
|
223
235
|
if user_id:
|
|
224
236
|
# 将之前的文本内容先添加到组件中
|
|
237
|
+
text_content = "".join(text_parts)
|
|
225
238
|
if text_content.strip():
|
|
226
239
|
message_components.append(
|
|
227
|
-
Plain(text=text_content)
|
|
240
|
+
Plain(text=text_content),
|
|
228
241
|
)
|
|
229
|
-
|
|
242
|
+
text_parts = []
|
|
230
243
|
# 添加@提及组件
|
|
231
244
|
message_components.append(At(qq=user_id, name=""))
|
|
232
245
|
elif element_type == "channel":
|
|
233
246
|
# #频道提及
|
|
234
247
|
channel_id = section_element.get("channel_id", "")
|
|
235
|
-
|
|
248
|
+
text_parts.append(f"#{channel_id}")
|
|
236
249
|
elif element_type == "link":
|
|
237
250
|
# 链接
|
|
238
251
|
url = section_element.get("url", "")
|
|
239
252
|
link_text = section_element.get("text", url)
|
|
240
|
-
|
|
253
|
+
text_parts.append(f"[{link_text}]({url})")
|
|
241
254
|
elif element_type == "emoji":
|
|
242
255
|
# 表情符号
|
|
243
256
|
emoji_name = section_element.get("name", "")
|
|
244
|
-
|
|
257
|
+
text_parts.append(f":{emoji_name}:")
|
|
258
|
+
|
|
259
|
+
text_content = "".join(text_parts)
|
|
245
260
|
|
|
246
261
|
if text_content.strip():
|
|
247
262
|
message_components.append(Plain(text=text_content))
|
|
@@ -307,11 +322,10 @@ class SlackAdapter(Platform):
|
|
|
307
322
|
content = await resp.read()
|
|
308
323
|
base64_content = base64.b64encode(content).decode("utf-8")
|
|
309
324
|
return base64_content
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
raise Exception(f"下载文件失败: {resp.status}")
|
|
325
|
+
logger.error(
|
|
326
|
+
f"Failed to download slack file: {resp.status} {await resp.text()}",
|
|
327
|
+
)
|
|
328
|
+
raise Exception(f"下载文件失败: {resp.status}")
|
|
315
329
|
|
|
316
330
|
async def run(self) -> Awaitable[Any]:
|
|
317
331
|
self.bot_self_id = await self.get_bot_user_id()
|
|
@@ -323,7 +337,9 @@ class SlackAdapter(Platform):
|
|
|
323
337
|
|
|
324
338
|
# 创建 Socket 客户端
|
|
325
339
|
self.socket_client = SlackSocketClient(
|
|
326
|
-
self.web_client,
|
|
340
|
+
self.web_client,
|
|
341
|
+
self.app_token,
|
|
342
|
+
self._handle_socket_event,
|
|
327
343
|
)
|
|
328
344
|
|
|
329
345
|
logger.info("Slack 适配器 (Socket Mode) 启动中...")
|
|
@@ -344,13 +360,13 @@ class SlackAdapter(Platform):
|
|
|
344
360
|
)
|
|
345
361
|
|
|
346
362
|
logger.info(
|
|
347
|
-
f"Slack 适配器 (Webhook Mode) 启动中,监听 {self.webhook_host}:{self.webhook_port}{self.webhook_path}..."
|
|
363
|
+
f"Slack 适配器 (Webhook Mode) 启动中,监听 {self.webhook_host}:{self.webhook_port}{self.webhook_path}...",
|
|
348
364
|
)
|
|
349
365
|
await self.webhook_client.start()
|
|
350
366
|
|
|
351
367
|
else:
|
|
352
368
|
raise ValueError(
|
|
353
|
-
f"不支持的连接模式: {self.connection_mode},请使用 'socket' 或 'webhook'"
|
|
369
|
+
f"不支持的连接模式: {self.connection_mode},请使用 'socket' 或 'webhook'",
|
|
354
370
|
)
|
|
355
371
|
|
|
356
372
|
async def _handle_webhook_event(self, event_data: dict):
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import re
|
|
3
|
-
from
|
|
3
|
+
from collections.abc import AsyncGenerator
|
|
4
|
+
|
|
4
5
|
from slack_sdk.web.async_client import AsyncWebClient
|
|
6
|
+
|
|
7
|
+
from astrbot.api import logger
|
|
5
8
|
from astrbot.api.event import AstrMessageEvent, MessageChain
|
|
6
9
|
from astrbot.api.message_components import (
|
|
10
|
+
BaseMessageComponent,
|
|
11
|
+
File,
|
|
7
12
|
Image,
|
|
8
13
|
Plain,
|
|
9
|
-
File,
|
|
10
|
-
BaseMessageComponent,
|
|
11
14
|
)
|
|
12
15
|
from astrbot.api.platform import Group, MessageMember
|
|
13
|
-
from astrbot.api import logger
|
|
14
16
|
|
|
15
17
|
|
|
16
18
|
class SlackMessageEvent(AstrMessageEvent):
|
|
@@ -27,12 +29,13 @@ class SlackMessageEvent(AstrMessageEvent):
|
|
|
27
29
|
|
|
28
30
|
@staticmethod
|
|
29
31
|
async def _from_segment_to_slack_block(
|
|
30
|
-
segment: BaseMessageComponent,
|
|
32
|
+
segment: BaseMessageComponent,
|
|
33
|
+
web_client: AsyncWebClient,
|
|
31
34
|
) -> dict:
|
|
32
35
|
"""将消息段转换为 Slack 块格式"""
|
|
33
36
|
if isinstance(segment, Plain):
|
|
34
37
|
return {"type": "section", "text": {"type": "mrkdwn", "text": segment.text}}
|
|
35
|
-
|
|
38
|
+
if isinstance(segment, Image):
|
|
36
39
|
# upload file
|
|
37
40
|
url = segment.url or segment.file
|
|
38
41
|
if url.startswith("http"):
|
|
@@ -61,7 +64,7 @@ class SlackMessageEvent(AstrMessageEvent):
|
|
|
61
64
|
},
|
|
62
65
|
"alt_text": "图片",
|
|
63
66
|
}
|
|
64
|
-
|
|
67
|
+
if isinstance(segment, File):
|
|
65
68
|
# upload file
|
|
66
69
|
url = segment.url or segment.file
|
|
67
70
|
response = await web_client.files_upload_v2(
|
|
@@ -82,12 +85,12 @@ class SlackMessageEvent(AstrMessageEvent):
|
|
|
82
85
|
"text": f"文件: <{file_url}|{segment.name or '文件'}>",
|
|
83
86
|
},
|
|
84
87
|
}
|
|
85
|
-
|
|
86
|
-
return {"type": "section", "text": {"type": "mrkdwn", "text": str(segment)}}
|
|
88
|
+
return {"type": "section", "text": {"type": "mrkdwn", "text": str(segment)}}
|
|
87
89
|
|
|
88
90
|
@staticmethod
|
|
89
91
|
async def _parse_slack_blocks(
|
|
90
|
-
message_chain: MessageChain,
|
|
92
|
+
message_chain: MessageChain,
|
|
93
|
+
web_client: AsyncWebClient,
|
|
91
94
|
):
|
|
92
95
|
"""解析成 Slack 块格式"""
|
|
93
96
|
blocks = []
|
|
@@ -103,27 +106,29 @@ class SlackMessageEvent(AstrMessageEvent):
|
|
|
103
106
|
{
|
|
104
107
|
"type": "section",
|
|
105
108
|
"text": {"type": "mrkdwn", "text": text_content},
|
|
106
|
-
}
|
|
109
|
+
},
|
|
107
110
|
)
|
|
108
111
|
text_content = ""
|
|
109
112
|
|
|
110
113
|
# 添加其他类型的块
|
|
111
114
|
block = await SlackMessageEvent._from_segment_to_slack_block(
|
|
112
|
-
segment,
|
|
115
|
+
segment,
|
|
116
|
+
web_client,
|
|
113
117
|
)
|
|
114
118
|
blocks.append(block)
|
|
115
119
|
|
|
116
120
|
# 如果最后还有文本内容
|
|
117
121
|
if text_content.strip():
|
|
118
122
|
blocks.append(
|
|
119
|
-
{"type": "section", "text": {"type": "mrkdwn", "text": text_content}}
|
|
123
|
+
{"type": "section", "text": {"type": "mrkdwn", "text": text_content}},
|
|
120
124
|
)
|
|
121
125
|
|
|
122
126
|
return blocks, "" if blocks else text_content
|
|
123
127
|
|
|
124
128
|
async def send(self, message: MessageChain):
|
|
125
129
|
blocks, text = await SlackMessageEvent._parse_slack_blocks(
|
|
126
|
-
message,
|
|
130
|
+
message,
|
|
131
|
+
self.web_client,
|
|
127
132
|
)
|
|
128
133
|
|
|
129
134
|
try:
|
|
@@ -143,28 +148,33 @@ class SlackMessageEvent(AstrMessageEvent):
|
|
|
143
148
|
)
|
|
144
149
|
except Exception:
|
|
145
150
|
# 如果块发送失败,尝试只发送文本
|
|
146
|
-
|
|
151
|
+
parts = []
|
|
147
152
|
for segment in message.chain:
|
|
148
153
|
if isinstance(segment, Plain):
|
|
149
|
-
|
|
154
|
+
parts.append(segment.text)
|
|
150
155
|
elif isinstance(segment, File):
|
|
151
|
-
|
|
156
|
+
parts.append(f" [文件: {segment.name}] ")
|
|
152
157
|
elif isinstance(segment, Image):
|
|
153
|
-
|
|
158
|
+
parts.append(" [图片] ")
|
|
159
|
+
fallback_text = "".join(parts)
|
|
154
160
|
|
|
155
161
|
if self.get_group_id():
|
|
156
162
|
await self.web_client.chat_postMessage(
|
|
157
|
-
channel=self.get_group_id(),
|
|
163
|
+
channel=self.get_group_id(),
|
|
164
|
+
text=fallback_text,
|
|
158
165
|
)
|
|
159
166
|
else:
|
|
160
167
|
await self.web_client.chat_postMessage(
|
|
161
|
-
channel=self.get_sender_id(),
|
|
168
|
+
channel=self.get_sender_id(),
|
|
169
|
+
text=fallback_text,
|
|
162
170
|
)
|
|
163
171
|
|
|
164
172
|
await super().send(message)
|
|
165
173
|
|
|
166
174
|
async def send_streaming(
|
|
167
|
-
self,
|
|
175
|
+
self,
|
|
176
|
+
generator: AsyncGenerator,
|
|
177
|
+
use_fallback: bool = False,
|
|
168
178
|
):
|
|
169
179
|
if not use_fallback:
|
|
170
180
|
buffer = None
|
|
@@ -174,7 +184,7 @@ class SlackMessageEvent(AstrMessageEvent):
|
|
|
174
184
|
else:
|
|
175
185
|
buffer.chain.extend(chain.chain)
|
|
176
186
|
if not buffer:
|
|
177
|
-
return
|
|
187
|
+
return None
|
|
178
188
|
buffer.squash_plain()
|
|
179
189
|
await self.send(buffer)
|
|
180
190
|
return await super().send_streaming(generator, use_fallback)
|
|
@@ -211,7 +221,7 @@ class SlackMessageEvent(AstrMessageEvent):
|
|
|
211
221
|
|
|
212
222
|
# 获取频道成员
|
|
213
223
|
members_response = await self.web_client.conversations_members(
|
|
214
|
-
channel=channel_id
|
|
224
|
+
channel=channel_id,
|
|
215
225
|
)
|
|
216
226
|
|
|
217
227
|
members = []
|
|
@@ -224,7 +234,7 @@ class SlackMessageEvent(AstrMessageEvent):
|
|
|
224
234
|
user_id=member_id,
|
|
225
235
|
nickname=user_data.get("real_name")
|
|
226
236
|
or user_data.get("name", member_id),
|
|
227
|
-
)
|
|
237
|
+
),
|
|
228
238
|
)
|
|
229
239
|
except Exception:
|
|
230
240
|
# 如果获取用户信息失败,使用默认信息
|