AstrBot 3.5.6__py3-none-any.whl → 4.7.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/api/__init__.py +16 -4
- astrbot/api/all.py +2 -1
- astrbot/api/event/__init__.py +5 -6
- astrbot/api/event/filter/__init__.py +37 -34
- astrbot/api/platform/__init__.py +7 -8
- astrbot/api/provider/__init__.py +8 -7
- astrbot/api/star/__init__.py +3 -4
- astrbot/api/util/__init__.py +2 -2
- astrbot/cli/__init__.py +1 -0
- astrbot/cli/__main__.py +18 -197
- astrbot/cli/commands/__init__.py +6 -0
- astrbot/cli/commands/cmd_conf.py +209 -0
- astrbot/cli/commands/cmd_init.py +56 -0
- astrbot/cli/commands/cmd_plug.py +245 -0
- astrbot/cli/commands/cmd_run.py +62 -0
- astrbot/cli/utils/__init__.py +18 -0
- astrbot/cli/utils/basic.py +76 -0
- astrbot/cli/utils/plugin.py +246 -0
- astrbot/cli/utils/version_comparator.py +90 -0
- astrbot/core/__init__.py +17 -19
- astrbot/core/agent/agent.py +14 -0
- astrbot/core/agent/handoff.py +38 -0
- astrbot/core/agent/hooks.py +30 -0
- astrbot/core/agent/mcp_client.py +385 -0
- astrbot/core/agent/message.py +175 -0
- astrbot/core/agent/response.py +14 -0
- astrbot/core/agent/run_context.py +22 -0
- astrbot/core/agent/runners/__init__.py +3 -0
- astrbot/core/agent/runners/base.py +65 -0
- astrbot/core/agent/runners/coze/coze_agent_runner.py +367 -0
- astrbot/core/agent/runners/coze/coze_api_client.py +324 -0
- astrbot/core/agent/runners/dashscope/dashscope_agent_runner.py +403 -0
- astrbot/core/agent/runners/dify/dify_agent_runner.py +336 -0
- astrbot/core/agent/runners/dify/dify_api_client.py +195 -0
- astrbot/core/agent/runners/tool_loop_agent_runner.py +400 -0
- astrbot/core/agent/tool.py +285 -0
- astrbot/core/agent/tool_executor.py +17 -0
- astrbot/core/astr_agent_context.py +19 -0
- astrbot/core/astr_agent_hooks.py +36 -0
- astrbot/core/astr_agent_run_util.py +80 -0
- astrbot/core/astr_agent_tool_exec.py +246 -0
- astrbot/core/astrbot_config_mgr.py +275 -0
- astrbot/core/config/__init__.py +2 -2
- astrbot/core/config/astrbot_config.py +60 -20
- astrbot/core/config/default.py +1972 -453
- astrbot/core/config/i18n_utils.py +110 -0
- astrbot/core/conversation_mgr.py +285 -75
- astrbot/core/core_lifecycle.py +167 -62
- astrbot/core/db/__init__.py +305 -102
- astrbot/core/db/migration/helper.py +69 -0
- astrbot/core/db/migration/migra_3_to_4.py +357 -0
- astrbot/core/db/migration/migra_45_to_46.py +44 -0
- astrbot/core/db/migration/migra_webchat_session.py +131 -0
- astrbot/core/db/migration/shared_preferences_v3.py +48 -0
- astrbot/core/db/migration/sqlite_v3.py +497 -0
- astrbot/core/db/po.py +259 -55
- astrbot/core/db/sqlite.py +773 -528
- astrbot/core/db/vec_db/base.py +73 -0
- astrbot/core/db/vec_db/faiss_impl/__init__.py +3 -0
- astrbot/core/db/vec_db/faiss_impl/document_storage.py +392 -0
- astrbot/core/db/vec_db/faiss_impl/embedding_storage.py +93 -0
- astrbot/core/db/vec_db/faiss_impl/sqlite_init.sql +17 -0
- astrbot/core/db/vec_db/faiss_impl/vec_db.py +204 -0
- astrbot/core/event_bus.py +26 -22
- astrbot/core/exceptions.py +9 -0
- astrbot/core/file_token_service.py +98 -0
- astrbot/core/initial_loader.py +19 -10
- astrbot/core/knowledge_base/chunking/__init__.py +9 -0
- astrbot/core/knowledge_base/chunking/base.py +25 -0
- astrbot/core/knowledge_base/chunking/fixed_size.py +59 -0
- astrbot/core/knowledge_base/chunking/recursive.py +161 -0
- astrbot/core/knowledge_base/kb_db_sqlite.py +301 -0
- astrbot/core/knowledge_base/kb_helper.py +642 -0
- astrbot/core/knowledge_base/kb_mgr.py +330 -0
- astrbot/core/knowledge_base/models.py +120 -0
- astrbot/core/knowledge_base/parsers/__init__.py +13 -0
- astrbot/core/knowledge_base/parsers/base.py +51 -0
- astrbot/core/knowledge_base/parsers/markitdown_parser.py +26 -0
- astrbot/core/knowledge_base/parsers/pdf_parser.py +101 -0
- astrbot/core/knowledge_base/parsers/text_parser.py +42 -0
- astrbot/core/knowledge_base/parsers/url_parser.py +103 -0
- astrbot/core/knowledge_base/parsers/util.py +13 -0
- astrbot/core/knowledge_base/prompts.py +65 -0
- astrbot/core/knowledge_base/retrieval/__init__.py +14 -0
- astrbot/core/knowledge_base/retrieval/hit_stopwords.txt +767 -0
- astrbot/core/knowledge_base/retrieval/manager.py +276 -0
- astrbot/core/knowledge_base/retrieval/rank_fusion.py +142 -0
- astrbot/core/knowledge_base/retrieval/sparse_retriever.py +136 -0
- astrbot/core/log.py +21 -15
- astrbot/core/message/components.py +413 -287
- astrbot/core/message/message_event_result.py +35 -24
- astrbot/core/persona_mgr.py +192 -0
- astrbot/core/pipeline/__init__.py +14 -14
- 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 +13 -14
- astrbot/core/pipeline/content_safety_check/strategies/keywords.py +2 -1
- astrbot/core/pipeline/content_safety_check/strategies/strategy.py +6 -6
- astrbot/core/pipeline/context.py +7 -1
- astrbot/core/pipeline/context_utils.py +107 -0
- astrbot/core/pipeline/preprocess_stage/stage.py +63 -36
- astrbot/core/pipeline/process_stage/method/agent_request.py +48 -0
- astrbot/core/pipeline/process_stage/method/agent_sub_stages/internal.py +464 -0
- astrbot/core/pipeline/process_stage/method/agent_sub_stages/third_party.py +202 -0
- astrbot/core/pipeline/process_stage/method/star_request.py +26 -32
- astrbot/core/pipeline/process_stage/stage.py +21 -15
- astrbot/core/pipeline/process_stage/utils.py +125 -0
- astrbot/core/pipeline/rate_limit_check/stage.py +34 -36
- astrbot/core/pipeline/respond/stage.py +142 -101
- astrbot/core/pipeline/result_decorate/stage.py +124 -57
- astrbot/core/pipeline/scheduler.py +21 -16
- astrbot/core/pipeline/session_status_check/stage.py +37 -0
- astrbot/core/pipeline/stage.py +11 -76
- astrbot/core/pipeline/waking_check/stage.py +69 -33
- astrbot/core/pipeline/whitelist_check/stage.py +10 -7
- astrbot/core/platform/__init__.py +6 -6
- astrbot/core/platform/astr_message_event.py +107 -129
- astrbot/core/platform/astrbot_message.py +32 -12
- astrbot/core/platform/manager.py +62 -18
- astrbot/core/platform/message_session.py +30 -0
- astrbot/core/platform/platform.py +16 -24
- astrbot/core/platform/platform_metadata.py +9 -4
- astrbot/core/platform/register.py +12 -7
- astrbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py +136 -60
- astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py +126 -46
- astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py +63 -31
- astrbot/core/platform/sources/dingtalk/dingtalk_event.py +30 -26
- astrbot/core/platform/sources/discord/client.py +129 -0
- astrbot/core/platform/sources/discord/components.py +139 -0
- astrbot/core/platform/sources/discord/discord_platform_adapter.py +473 -0
- astrbot/core/platform/sources/discord/discord_platform_event.py +313 -0
- astrbot/core/platform/sources/lark/lark_adapter.py +27 -18
- astrbot/core/platform/sources/lark/lark_event.py +39 -13
- astrbot/core/platform/sources/misskey/misskey_adapter.py +770 -0
- astrbot/core/platform/sources/misskey/misskey_api.py +964 -0
- astrbot/core/platform/sources/misskey/misskey_event.py +163 -0
- astrbot/core/platform/sources/misskey/misskey_utils.py +550 -0
- astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py +149 -33
- 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 +14 -8
- astrbot/core/platform/sources/satori/satori_adapter.py +792 -0
- astrbot/core/platform/sources/satori/satori_event.py +432 -0
- astrbot/core/platform/sources/slack/client.py +164 -0
- astrbot/core/platform/sources/slack/slack_adapter.py +416 -0
- astrbot/core/platform/sources/slack/slack_event.py +253 -0
- astrbot/core/platform/sources/telegram/tg_adapter.py +100 -43
- astrbot/core/platform/sources/telegram/tg_event.py +136 -36
- astrbot/core/platform/sources/webchat/webchat_adapter.py +72 -22
- astrbot/core/platform/sources/webchat/webchat_event.py +46 -22
- astrbot/core/platform/sources/webchat/webchat_queue_mgr.py +35 -0
- astrbot/core/platform/sources/wechatpadpro/wechatpadpro_adapter.py +926 -0
- astrbot/core/platform/sources/wechatpadpro/wechatpadpro_message_event.py +178 -0
- astrbot/core/platform/sources/wechatpadpro/xml_data_parser.py +159 -0
- astrbot/core/platform/sources/wecom/wecom_adapter.py +169 -27
- astrbot/core/platform/sources/wecom/wecom_event.py +162 -77
- astrbot/core/platform/sources/wecom/wecom_kf.py +279 -0
- astrbot/core/platform/sources/wecom/wecom_kf_message.py +196 -0
- astrbot/core/platform/sources/wecom_ai_bot/WXBizJsonMsgCrypt.py +297 -0
- astrbot/core/platform/sources/wecom_ai_bot/__init__.py +15 -0
- astrbot/core/platform/sources/wecom_ai_bot/ierror.py +19 -0
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_adapter.py +472 -0
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_api.py +417 -0
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_event.py +152 -0
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_queue_mgr.py +153 -0
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_server.py +168 -0
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_utils.py +209 -0
- astrbot/core/platform/sources/weixin_official_account/weixin_offacc_adapter.py +306 -0
- astrbot/core/platform/sources/weixin_official_account/weixin_offacc_event.py +186 -0
- astrbot/core/platform_message_history_mgr.py +49 -0
- astrbot/core/provider/__init__.py +2 -3
- astrbot/core/provider/entites.py +8 -8
- astrbot/core/provider/entities.py +154 -98
- astrbot/core/provider/func_tool_manager.py +446 -458
- astrbot/core/provider/manager.py +345 -207
- astrbot/core/provider/provider.py +188 -73
- astrbot/core/provider/register.py +9 -7
- astrbot/core/provider/sources/anthropic_source.py +295 -115
- astrbot/core/provider/sources/azure_tts_source.py +224 -0
- astrbot/core/provider/sources/bailian_rerank_source.py +236 -0
- astrbot/core/provider/sources/dashscope_tts.py +138 -14
- astrbot/core/provider/sources/edge_tts_source.py +24 -19
- astrbot/core/provider/sources/fishaudio_tts_api_source.py +58 -13
- astrbot/core/provider/sources/gemini_embedding_source.py +61 -0
- astrbot/core/provider/sources/gemini_source.py +310 -132
- astrbot/core/provider/sources/gemini_tts_source.py +81 -0
- astrbot/core/provider/sources/groq_source.py +15 -0
- astrbot/core/provider/sources/gsv_selfhosted_source.py +151 -0
- astrbot/core/provider/sources/gsvi_tts_source.py +14 -7
- astrbot/core/provider/sources/minimax_tts_api_source.py +159 -0
- astrbot/core/provider/sources/openai_embedding_source.py +40 -0
- astrbot/core/provider/sources/openai_source.py +241 -145
- astrbot/core/provider/sources/openai_tts_api_source.py +18 -7
- astrbot/core/provider/sources/sensevoice_selfhosted_source.py +13 -11
- astrbot/core/provider/sources/vllm_rerank_source.py +71 -0
- astrbot/core/provider/sources/volcengine_tts.py +115 -0
- astrbot/core/provider/sources/whisper_api_source.py +18 -13
- astrbot/core/provider/sources/whisper_selfhosted_source.py +19 -12
- astrbot/core/provider/sources/xinference_rerank_source.py +116 -0
- astrbot/core/provider/sources/xinference_stt_provider.py +197 -0
- astrbot/core/provider/sources/zhipu_source.py +6 -73
- astrbot/core/star/__init__.py +43 -11
- astrbot/core/star/config.py +17 -18
- astrbot/core/star/context.py +362 -138
- astrbot/core/star/filter/__init__.py +4 -3
- astrbot/core/star/filter/command.py +111 -35
- astrbot/core/star/filter/command_group.py +46 -34
- 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 +45 -12
- astrbot/core/star/filter/regex.py +4 -2
- astrbot/core/star/register/__init__.py +19 -15
- astrbot/core/star/register/star.py +41 -13
- astrbot/core/star/register/star_handler.py +236 -86
- astrbot/core/star/session_llm_manager.py +280 -0
- astrbot/core/star/session_plugin_manager.py +170 -0
- astrbot/core/star/star.py +36 -43
- astrbot/core/star/star_handler.py +47 -85
- astrbot/core/star/star_manager.py +442 -260
- astrbot/core/star/star_tools.py +167 -45
- astrbot/core/star/updator.py +17 -20
- astrbot/core/umop_config_router.py +106 -0
- astrbot/core/updator.py +38 -13
- astrbot/core/utils/astrbot_path.py +39 -0
- astrbot/core/utils/command_parser.py +1 -1
- astrbot/core/utils/io.py +119 -60
- astrbot/core/utils/log_pipe.py +1 -1
- astrbot/core/utils/metrics.py +11 -10
- astrbot/core/utils/migra_helper.py +73 -0
- astrbot/core/utils/path_util.py +63 -62
- astrbot/core/utils/pip_installer.py +37 -15
- astrbot/core/utils/session_lock.py +29 -0
- astrbot/core/utils/session_waiter.py +19 -20
- astrbot/core/utils/shared_preferences.py +174 -34
- astrbot/core/utils/t2i/__init__.py +4 -1
- astrbot/core/utils/t2i/local_strategy.py +386 -238
- astrbot/core/utils/t2i/network_strategy.py +109 -49
- astrbot/core/utils/t2i/renderer.py +29 -14
- astrbot/core/utils/t2i/template/astrbot_powershell.html +184 -0
- astrbot/core/utils/t2i/template_manager.py +111 -0
- astrbot/core/utils/tencent_record_helper.py +115 -1
- astrbot/core/utils/version_comparator.py +10 -13
- astrbot/core/zip_updator.py +112 -65
- astrbot/dashboard/routes/__init__.py +20 -13
- astrbot/dashboard/routes/auth.py +20 -9
- astrbot/dashboard/routes/chat.py +297 -141
- astrbot/dashboard/routes/config.py +652 -55
- astrbot/dashboard/routes/conversation.py +107 -37
- astrbot/dashboard/routes/file.py +26 -0
- astrbot/dashboard/routes/knowledge_base.py +1244 -0
- astrbot/dashboard/routes/log.py +27 -2
- astrbot/dashboard/routes/persona.py +202 -0
- astrbot/dashboard/routes/plugin.py +197 -139
- astrbot/dashboard/routes/route.py +27 -7
- astrbot/dashboard/routes/session_management.py +354 -0
- astrbot/dashboard/routes/stat.py +85 -18
- astrbot/dashboard/routes/static_file.py +5 -2
- astrbot/dashboard/routes/t2i.py +233 -0
- astrbot/dashboard/routes/tools.py +184 -120
- astrbot/dashboard/routes/update.py +59 -36
- astrbot/dashboard/server.py +96 -36
- astrbot/dashboard/utils.py +165 -0
- astrbot-4.7.0.dist-info/METADATA +294 -0
- astrbot-4.7.0.dist-info/RECORD +274 -0
- {astrbot-3.5.6.dist-info → astrbot-4.7.0.dist-info}/WHEEL +1 -1
- astrbot/core/db/plugin/sqlite_impl.py +0 -112
- astrbot/core/db/sqlite_init.sql +0 -50
- astrbot/core/pipeline/platform_compatibility/stage.py +0 -56
- astrbot/core/pipeline/process_stage/method/llm_request.py +0 -606
- astrbot/core/platform/sources/gewechat/client.py +0 -806
- astrbot/core/platform/sources/gewechat/downloader.py +0 -55
- astrbot/core/platform/sources/gewechat/gewechat_event.py +0 -255
- astrbot/core/platform/sources/gewechat/gewechat_platform_adapter.py +0 -103
- astrbot/core/platform/sources/gewechat/xml_data_parser.py +0 -110
- astrbot/core/provider/sources/dashscope_source.py +0 -203
- astrbot/core/provider/sources/dify_source.py +0 -281
- astrbot/core/provider/sources/llmtuner_source.py +0 -132
- astrbot/core/rag/embedding/openai_source.py +0 -20
- astrbot/core/rag/knowledge_db_mgr.py +0 -94
- astrbot/core/rag/store/__init__.py +0 -9
- astrbot/core/rag/store/chroma_db.py +0 -42
- astrbot/core/utils/dify_api_client.py +0 -152
- astrbot-3.5.6.dist-info/METADATA +0 -249
- astrbot-3.5.6.dist-info/RECORD +0 -158
- {astrbot-3.5.6.dist-info → astrbot-4.7.0.dist-info}/entry_points.txt +0 -0
- {astrbot-3.5.6.dist-info → astrbot-4.7.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import base64
|
|
3
|
+
import re
|
|
4
|
+
import time
|
|
5
|
+
import uuid
|
|
6
|
+
from collections.abc import Awaitable
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
import aiohttp
|
|
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 *
|
|
16
|
+
from astrbot.api.platform import (
|
|
17
|
+
AstrBotMessage,
|
|
18
|
+
MessageMember,
|
|
19
|
+
MessageType,
|
|
20
|
+
Platform,
|
|
21
|
+
PlatformMetadata,
|
|
22
|
+
)
|
|
23
|
+
from astrbot.core.platform.astr_message_event import MessageSesion
|
|
24
|
+
|
|
25
|
+
from ...register import register_platform_adapter
|
|
26
|
+
from .client import SlackSocketClient, SlackWebhookClient
|
|
27
|
+
from .slack_event import SlackMessageEvent
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@register_platform_adapter(
|
|
31
|
+
"slack",
|
|
32
|
+
"适用于 Slack 的消息平台适配器,支持 Socket Mode 和 Webhook Mode。",
|
|
33
|
+
support_streaming_message=False,
|
|
34
|
+
)
|
|
35
|
+
class SlackAdapter(Platform):
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
platform_config: dict,
|
|
39
|
+
platform_settings: dict,
|
|
40
|
+
event_queue: asyncio.Queue,
|
|
41
|
+
) -> None:
|
|
42
|
+
super().__init__(event_queue)
|
|
43
|
+
|
|
44
|
+
self.config = platform_config
|
|
45
|
+
self.settings = platform_settings
|
|
46
|
+
self.unique_session = platform_settings.get("unique_session", False)
|
|
47
|
+
|
|
48
|
+
self.bot_token = platform_config.get("bot_token")
|
|
49
|
+
self.app_token = platform_config.get("app_token")
|
|
50
|
+
self.signing_secret = platform_config.get("signing_secret")
|
|
51
|
+
self.connection_mode = platform_config.get("slack_connection_mode", "socket")
|
|
52
|
+
self.webhook_host = platform_config.get("slack_webhook_host", "0.0.0.0")
|
|
53
|
+
self.webhook_port = platform_config.get("slack_webhook_port", 3000)
|
|
54
|
+
self.webhook_path = platform_config.get(
|
|
55
|
+
"slack_webhook_path",
|
|
56
|
+
"/astrbot-slack-webhook/callback",
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
if not self.bot_token:
|
|
60
|
+
raise ValueError("Slack bot_token 是必需的")
|
|
61
|
+
|
|
62
|
+
if self.connection_mode == "socket" and not self.app_token:
|
|
63
|
+
raise ValueError("Socket Mode 需要 app_token")
|
|
64
|
+
|
|
65
|
+
if self.connection_mode == "webhook" and not self.signing_secret:
|
|
66
|
+
raise ValueError("Webhook Mode 需要 signing_secret")
|
|
67
|
+
|
|
68
|
+
self.metadata = PlatformMetadata(
|
|
69
|
+
name="slack",
|
|
70
|
+
description="适用于 Slack 的消息平台适配器,支持 Socket Mode 和 Webhook Mode。",
|
|
71
|
+
id=self.config.get("id"),
|
|
72
|
+
support_streaming_message=False,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
# 初始化 Slack Web Client
|
|
76
|
+
self.web_client = AsyncWebClient(token=self.bot_token, logger=logger)
|
|
77
|
+
self.socket_client = None
|
|
78
|
+
self.webhook_client = None
|
|
79
|
+
|
|
80
|
+
self.bot_self_id = None
|
|
81
|
+
|
|
82
|
+
async def send_by_session(
|
|
83
|
+
self,
|
|
84
|
+
session: MessageSesion,
|
|
85
|
+
message_chain: MessageChain,
|
|
86
|
+
):
|
|
87
|
+
blocks, text = await SlackMessageEvent._parse_slack_blocks(
|
|
88
|
+
message_chain=message_chain,
|
|
89
|
+
web_client=self.web_client,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
try:
|
|
93
|
+
if session.message_type == MessageType.GROUP_MESSAGE:
|
|
94
|
+
# 发送到频道
|
|
95
|
+
channel_id = (
|
|
96
|
+
session.session_id.split("_")[-1]
|
|
97
|
+
if "_" in session.session_id
|
|
98
|
+
else session.session_id
|
|
99
|
+
)
|
|
100
|
+
await self.web_client.chat_postMessage(
|
|
101
|
+
channel=channel_id,
|
|
102
|
+
text=text,
|
|
103
|
+
blocks=blocks if blocks else None,
|
|
104
|
+
)
|
|
105
|
+
else:
|
|
106
|
+
# 发送私信
|
|
107
|
+
await self.web_client.chat_postMessage(
|
|
108
|
+
channel=session.session_id,
|
|
109
|
+
text=text,
|
|
110
|
+
blocks=blocks if blocks else None,
|
|
111
|
+
)
|
|
112
|
+
except Exception as e:
|
|
113
|
+
logger.error(f"Slack 发送消息失败: {e}")
|
|
114
|
+
|
|
115
|
+
await super().send_by_session(session, message_chain)
|
|
116
|
+
|
|
117
|
+
async def convert_message(self, event: dict) -> AstrBotMessage:
|
|
118
|
+
logger.debug(f"[slack] RawMessage {event}")
|
|
119
|
+
|
|
120
|
+
abm = AstrBotMessage()
|
|
121
|
+
abm.self_id = self.bot_self_id
|
|
122
|
+
|
|
123
|
+
# 获取用户信息
|
|
124
|
+
user_id = event.get("user", "")
|
|
125
|
+
try:
|
|
126
|
+
user_info = await self.web_client.users_info(user=user_id)
|
|
127
|
+
user_data = user_info["user"]
|
|
128
|
+
user_name = user_data.get("real_name") or user_data.get("name", user_id)
|
|
129
|
+
except Exception:
|
|
130
|
+
user_name = user_id
|
|
131
|
+
|
|
132
|
+
abm.sender = MessageMember(user_id=user_id, nickname=user_name)
|
|
133
|
+
|
|
134
|
+
# 判断消息类型
|
|
135
|
+
channel_id = event.get("channel", "")
|
|
136
|
+
try:
|
|
137
|
+
channel_info = await self.web_client.conversations_info(channel=channel_id)
|
|
138
|
+
is_im = channel_info["channel"]["is_im"]
|
|
139
|
+
|
|
140
|
+
if is_im:
|
|
141
|
+
abm.type = MessageType.FRIEND_MESSAGE
|
|
142
|
+
else:
|
|
143
|
+
abm.type = MessageType.GROUP_MESSAGE
|
|
144
|
+
abm.group_id = channel_id
|
|
145
|
+
except Exception:
|
|
146
|
+
# 默认作为群组消息处理
|
|
147
|
+
abm.type = MessageType.GROUP_MESSAGE
|
|
148
|
+
abm.group_id = channel_id
|
|
149
|
+
|
|
150
|
+
# 设置会话ID
|
|
151
|
+
if self.unique_session and abm.type == MessageType.GROUP_MESSAGE:
|
|
152
|
+
abm.session_id = f"{user_id}_{channel_id}"
|
|
153
|
+
else:
|
|
154
|
+
abm.session_id = (
|
|
155
|
+
channel_id if abm.type == MessageType.GROUP_MESSAGE else user_id
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
abm.message_id = event.get("client_msg_id", uuid.uuid4().hex)
|
|
159
|
+
abm.timestamp = int(float(event.get("ts", time.time())))
|
|
160
|
+
|
|
161
|
+
# 处理消息内容
|
|
162
|
+
message_text = event.get("text", "")
|
|
163
|
+
abm.message_str = message_text
|
|
164
|
+
abm.message = []
|
|
165
|
+
|
|
166
|
+
# 优先使用 blocks 字段解析消息
|
|
167
|
+
if event.get("blocks"):
|
|
168
|
+
abm.message = self._parse_blocks(event["blocks"])
|
|
169
|
+
# 更新 message_str
|
|
170
|
+
abm.message_str = ""
|
|
171
|
+
for component in abm.message:
|
|
172
|
+
if isinstance(component, Plain):
|
|
173
|
+
abm.message_str += component.text
|
|
174
|
+
elif message_text:
|
|
175
|
+
# 处理传统的文本消息
|
|
176
|
+
if "<@" in message_text:
|
|
177
|
+
mentions = re.findall(r"<@([^>]+)>", message_text)
|
|
178
|
+
for mention in mentions:
|
|
179
|
+
try:
|
|
180
|
+
mentioned_user = await self.web_client.users_info(user=mention)
|
|
181
|
+
user_data = mentioned_user["user"]
|
|
182
|
+
user_name = user_data.get("real_name") or user_data.get(
|
|
183
|
+
"name",
|
|
184
|
+
mention,
|
|
185
|
+
)
|
|
186
|
+
abm.message.append(At(qq=mention, name=user_name))
|
|
187
|
+
except Exception:
|
|
188
|
+
abm.message.append(At(qq=mention, name=""))
|
|
189
|
+
|
|
190
|
+
# 清理消息文本中的@标记
|
|
191
|
+
if clean_text := re.sub(r"<@[^>]+>", "", message_text).strip():
|
|
192
|
+
abm.message.append(Plain(text=clean_text))
|
|
193
|
+
else:
|
|
194
|
+
abm.message.append(Plain(text=message_text))
|
|
195
|
+
|
|
196
|
+
# 处理文件附件
|
|
197
|
+
if "files" in event:
|
|
198
|
+
for file_info in event["files"]:
|
|
199
|
+
file_name = file_info.get("name", "unknown")
|
|
200
|
+
file_url = file_info.get("url_private", "")
|
|
201
|
+
if file_info.get("mimetype", "").startswith("image/"):
|
|
202
|
+
file_url = await self.get_file_base64(file_url)
|
|
203
|
+
abm.message.append(Image.fromBase64(base64=file_url))
|
|
204
|
+
else:
|
|
205
|
+
# TODO: 下载鉴权
|
|
206
|
+
abm.message.append(
|
|
207
|
+
File(name=file_name, file=file_url, url=file_url),
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
abm.raw_message = event
|
|
211
|
+
return abm
|
|
212
|
+
|
|
213
|
+
def _parse_blocks(self, blocks: list) -> list:
|
|
214
|
+
"""解析 Slack blocks 格式的消息内容"""
|
|
215
|
+
message_components = []
|
|
216
|
+
|
|
217
|
+
for block in blocks:
|
|
218
|
+
block_type = block.get("type", "")
|
|
219
|
+
|
|
220
|
+
if block_type == "rich_text":
|
|
221
|
+
# 处理富文本块
|
|
222
|
+
elements = block.get("elements", [])
|
|
223
|
+
for element in elements:
|
|
224
|
+
if element.get("type") == "rich_text_section":
|
|
225
|
+
# 处理富文本段落
|
|
226
|
+
section_elements = element.get("elements", [])
|
|
227
|
+
text_parts = []
|
|
228
|
+
for section_element in section_elements:
|
|
229
|
+
element_type = section_element.get("type", "")
|
|
230
|
+
|
|
231
|
+
if element_type == "text":
|
|
232
|
+
# 普通文本
|
|
233
|
+
text_parts.append(section_element.get("text", ""))
|
|
234
|
+
elif element_type == "user":
|
|
235
|
+
# @用户提及
|
|
236
|
+
user_id = section_element.get("user_id", "")
|
|
237
|
+
if user_id:
|
|
238
|
+
# 将之前的文本内容先添加到组件中
|
|
239
|
+
text_content = "".join(text_parts)
|
|
240
|
+
if text_content.strip():
|
|
241
|
+
message_components.append(
|
|
242
|
+
Plain(text=text_content),
|
|
243
|
+
)
|
|
244
|
+
text_parts = []
|
|
245
|
+
# 添加@提及组件
|
|
246
|
+
message_components.append(At(qq=user_id, name=""))
|
|
247
|
+
elif element_type == "channel":
|
|
248
|
+
# #频道提及
|
|
249
|
+
channel_id = section_element.get("channel_id", "")
|
|
250
|
+
text_parts.append(f"#{channel_id}")
|
|
251
|
+
elif element_type == "link":
|
|
252
|
+
# 链接
|
|
253
|
+
url = section_element.get("url", "")
|
|
254
|
+
link_text = section_element.get("text", url)
|
|
255
|
+
text_parts.append(f"[{link_text}]({url})")
|
|
256
|
+
elif element_type == "emoji":
|
|
257
|
+
# 表情符号
|
|
258
|
+
emoji_name = section_element.get("name", "")
|
|
259
|
+
text_parts.append(f":{emoji_name}:")
|
|
260
|
+
|
|
261
|
+
text_content = "".join(text_parts)
|
|
262
|
+
|
|
263
|
+
if text_content.strip():
|
|
264
|
+
message_components.append(Plain(text=text_content))
|
|
265
|
+
|
|
266
|
+
elif element.get("type") == "rich_text_list":
|
|
267
|
+
# 处理列表
|
|
268
|
+
list_items = element.get("elements", [])
|
|
269
|
+
list_text = ""
|
|
270
|
+
for item in list_items:
|
|
271
|
+
if item.get("type") == "rich_text_section":
|
|
272
|
+
item_elements = item.get("elements", [])
|
|
273
|
+
item_text = ""
|
|
274
|
+
for item_element in item_elements:
|
|
275
|
+
if item_element.get("type") == "text":
|
|
276
|
+
item_text += item_element.get("text", "")
|
|
277
|
+
list_text += f"• {item_text}\n"
|
|
278
|
+
|
|
279
|
+
if list_text.strip():
|
|
280
|
+
message_components.append(Plain(text=list_text.strip()))
|
|
281
|
+
|
|
282
|
+
elif block_type == "section":
|
|
283
|
+
# 处理段落块
|
|
284
|
+
if "text" in block:
|
|
285
|
+
text_obj = block["text"]
|
|
286
|
+
if text_obj.get("type") == "mrkdwn":
|
|
287
|
+
text_content = text_obj.get("text", "")
|
|
288
|
+
message_components.append(Plain(text=text_content))
|
|
289
|
+
|
|
290
|
+
return message_components
|
|
291
|
+
|
|
292
|
+
async def _handle_socket_event(self, req: SocketModeRequest):
|
|
293
|
+
"""处理 Socket Mode 事件"""
|
|
294
|
+
if req.type == "events_api":
|
|
295
|
+
# 事件 API
|
|
296
|
+
event = req.payload.get("event", {})
|
|
297
|
+
|
|
298
|
+
# 忽略机器人自己的消息和消息编辑
|
|
299
|
+
if event.get("subtype") in [
|
|
300
|
+
"bot_message",
|
|
301
|
+
"message_changed",
|
|
302
|
+
"message_deleted",
|
|
303
|
+
]:
|
|
304
|
+
return
|
|
305
|
+
|
|
306
|
+
if event.get("bot_id"):
|
|
307
|
+
return
|
|
308
|
+
|
|
309
|
+
if event.get("type") in ["message", "app_mention"]:
|
|
310
|
+
abm = await self.convert_message(event)
|
|
311
|
+
if abm:
|
|
312
|
+
await self.handle_msg(abm)
|
|
313
|
+
|
|
314
|
+
async def get_bot_user_id(self):
|
|
315
|
+
auth_info = await self.web_client.auth_test()
|
|
316
|
+
return auth_info.get("user_id")
|
|
317
|
+
|
|
318
|
+
async def get_file_base64(self, url: str) -> str:
|
|
319
|
+
"""下载 Slack 文件并返回 Base64 编码的内容"""
|
|
320
|
+
headers = {"Authorization": f"Bearer {self.bot_token}"}
|
|
321
|
+
async with aiohttp.ClientSession() as session:
|
|
322
|
+
async with session.get(url, headers=headers) as resp:
|
|
323
|
+
if resp.status == 200:
|
|
324
|
+
content = await resp.read()
|
|
325
|
+
base64_content = base64.b64encode(content).decode("utf-8")
|
|
326
|
+
return base64_content
|
|
327
|
+
logger.error(
|
|
328
|
+
f"Failed to download slack file: {resp.status} {await resp.text()}",
|
|
329
|
+
)
|
|
330
|
+
raise Exception(f"下载文件失败: {resp.status}")
|
|
331
|
+
|
|
332
|
+
async def run(self) -> Awaitable[Any]:
|
|
333
|
+
self.bot_self_id = await self.get_bot_user_id()
|
|
334
|
+
logger.info(f"Slack auth test OK. Bot ID: {self.bot_self_id}")
|
|
335
|
+
|
|
336
|
+
if self.connection_mode == "socket":
|
|
337
|
+
if not self.app_token:
|
|
338
|
+
raise ValueError("Socket Mode 需要 app_token")
|
|
339
|
+
|
|
340
|
+
# 创建 Socket 客户端
|
|
341
|
+
self.socket_client = SlackSocketClient(
|
|
342
|
+
self.web_client,
|
|
343
|
+
self.app_token,
|
|
344
|
+
self._handle_socket_event,
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
logger.info("Slack 适配器 (Socket Mode) 启动中...")
|
|
348
|
+
await self.socket_client.start()
|
|
349
|
+
|
|
350
|
+
elif self.connection_mode == "webhook":
|
|
351
|
+
if not self.signing_secret:
|
|
352
|
+
raise ValueError("Webhook Mode 需要 signing_secret")
|
|
353
|
+
|
|
354
|
+
# 创建 Webhook 客户端
|
|
355
|
+
self.webhook_client = SlackWebhookClient(
|
|
356
|
+
self.web_client,
|
|
357
|
+
self.signing_secret,
|
|
358
|
+
self.webhook_host,
|
|
359
|
+
self.webhook_port,
|
|
360
|
+
self.webhook_path,
|
|
361
|
+
self._handle_webhook_event,
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
logger.info(
|
|
365
|
+
f"Slack 适配器 (Webhook Mode) 启动中,监听 {self.webhook_host}:{self.webhook_port}{self.webhook_path}...",
|
|
366
|
+
)
|
|
367
|
+
await self.webhook_client.start()
|
|
368
|
+
|
|
369
|
+
else:
|
|
370
|
+
raise ValueError(
|
|
371
|
+
f"不支持的连接模式: {self.connection_mode},请使用 'socket' 或 'webhook'",
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
async def _handle_webhook_event(self, event_data: dict):
|
|
375
|
+
"""处理 Webhook 事件"""
|
|
376
|
+
event = event_data.get("event", {})
|
|
377
|
+
|
|
378
|
+
# 忽略机器人自己的消息和消息编辑
|
|
379
|
+
if event.get("subtype") in [
|
|
380
|
+
"bot_message",
|
|
381
|
+
"message_changed",
|
|
382
|
+
"message_deleted",
|
|
383
|
+
]:
|
|
384
|
+
return
|
|
385
|
+
|
|
386
|
+
if event.get("bot_id"):
|
|
387
|
+
return
|
|
388
|
+
|
|
389
|
+
if event.get("type") in ["message", "app_mention"]:
|
|
390
|
+
abm = await self.convert_message(event)
|
|
391
|
+
if abm:
|
|
392
|
+
await self.handle_msg(abm)
|
|
393
|
+
|
|
394
|
+
async def terminate(self):
|
|
395
|
+
if self.socket_client:
|
|
396
|
+
await self.socket_client.stop()
|
|
397
|
+
if self.webhook_client:
|
|
398
|
+
await self.webhook_client.stop()
|
|
399
|
+
logger.info("Slack 适配器已被优雅地关闭")
|
|
400
|
+
|
|
401
|
+
def meta(self) -> PlatformMetadata:
|
|
402
|
+
return self.metadata
|
|
403
|
+
|
|
404
|
+
async def handle_msg(self, message: AstrBotMessage):
|
|
405
|
+
message_event = SlackMessageEvent(
|
|
406
|
+
message_str=message.message_str,
|
|
407
|
+
message_obj=message,
|
|
408
|
+
platform_meta=self.meta(),
|
|
409
|
+
session_id=message.session_id,
|
|
410
|
+
web_client=self.web_client,
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
self.commit_event(message_event)
|
|
414
|
+
|
|
415
|
+
def get_client(self):
|
|
416
|
+
return self.web_client
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import re
|
|
3
|
+
from collections.abc import AsyncGenerator
|
|
4
|
+
|
|
5
|
+
from slack_sdk.web.async_client import AsyncWebClient
|
|
6
|
+
|
|
7
|
+
from astrbot.api import logger
|
|
8
|
+
from astrbot.api.event import AstrMessageEvent, MessageChain
|
|
9
|
+
from astrbot.api.message_components import (
|
|
10
|
+
BaseMessageComponent,
|
|
11
|
+
File,
|
|
12
|
+
Image,
|
|
13
|
+
Plain,
|
|
14
|
+
)
|
|
15
|
+
from astrbot.api.platform import Group, MessageMember
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class SlackMessageEvent(AstrMessageEvent):
|
|
19
|
+
def __init__(
|
|
20
|
+
self,
|
|
21
|
+
message_str,
|
|
22
|
+
message_obj,
|
|
23
|
+
platform_meta,
|
|
24
|
+
session_id,
|
|
25
|
+
web_client: AsyncWebClient,
|
|
26
|
+
):
|
|
27
|
+
super().__init__(message_str, message_obj, platform_meta, session_id)
|
|
28
|
+
self.web_client = web_client
|
|
29
|
+
|
|
30
|
+
@staticmethod
|
|
31
|
+
async def _from_segment_to_slack_block(
|
|
32
|
+
segment: BaseMessageComponent,
|
|
33
|
+
web_client: AsyncWebClient,
|
|
34
|
+
) -> dict:
|
|
35
|
+
"""将消息段转换为 Slack 块格式"""
|
|
36
|
+
if isinstance(segment, Plain):
|
|
37
|
+
return {"type": "section", "text": {"type": "mrkdwn", "text": segment.text}}
|
|
38
|
+
if isinstance(segment, Image):
|
|
39
|
+
# upload file
|
|
40
|
+
url = segment.url or segment.file
|
|
41
|
+
if url.startswith("http"):
|
|
42
|
+
return {
|
|
43
|
+
"type": "image",
|
|
44
|
+
"image_url": url,
|
|
45
|
+
"alt_text": "图片",
|
|
46
|
+
}
|
|
47
|
+
path = await segment.convert_to_file_path()
|
|
48
|
+
response = await web_client.files_upload_v2(
|
|
49
|
+
file=path,
|
|
50
|
+
filename="image.jpg",
|
|
51
|
+
)
|
|
52
|
+
if not response["ok"]:
|
|
53
|
+
logger.error(f"Slack file upload failed: {response['error']}")
|
|
54
|
+
return {
|
|
55
|
+
"type": "section",
|
|
56
|
+
"text": {"type": "mrkdwn", "text": "图片上传失败"},
|
|
57
|
+
}
|
|
58
|
+
image_url = response["files"][0]["url_private"]
|
|
59
|
+
logger.debug(f"Slack file upload response: {response}")
|
|
60
|
+
return {
|
|
61
|
+
"type": "image",
|
|
62
|
+
"slack_file": {
|
|
63
|
+
"url": image_url,
|
|
64
|
+
},
|
|
65
|
+
"alt_text": "图片",
|
|
66
|
+
}
|
|
67
|
+
if isinstance(segment, File):
|
|
68
|
+
# upload file
|
|
69
|
+
url = segment.url or segment.file
|
|
70
|
+
response = await web_client.files_upload_v2(
|
|
71
|
+
file=url,
|
|
72
|
+
filename=segment.name or "file",
|
|
73
|
+
)
|
|
74
|
+
if not response["ok"]:
|
|
75
|
+
logger.error(f"Slack file upload failed: {response['error']}")
|
|
76
|
+
return {
|
|
77
|
+
"type": "section",
|
|
78
|
+
"text": {"type": "mrkdwn", "text": "文件上传失败"},
|
|
79
|
+
}
|
|
80
|
+
file_url = response["files"][0]["permalink"]
|
|
81
|
+
return {
|
|
82
|
+
"type": "section",
|
|
83
|
+
"text": {
|
|
84
|
+
"type": "mrkdwn",
|
|
85
|
+
"text": f"文件: <{file_url}|{segment.name or '文件'}>",
|
|
86
|
+
},
|
|
87
|
+
}
|
|
88
|
+
return {"type": "section", "text": {"type": "mrkdwn", "text": str(segment)}}
|
|
89
|
+
|
|
90
|
+
@staticmethod
|
|
91
|
+
async def _parse_slack_blocks(
|
|
92
|
+
message_chain: MessageChain,
|
|
93
|
+
web_client: AsyncWebClient,
|
|
94
|
+
):
|
|
95
|
+
"""解析成 Slack 块格式"""
|
|
96
|
+
blocks = []
|
|
97
|
+
text_content = ""
|
|
98
|
+
|
|
99
|
+
for segment in message_chain.chain:
|
|
100
|
+
if isinstance(segment, Plain):
|
|
101
|
+
text_content += segment.text
|
|
102
|
+
else:
|
|
103
|
+
# 如果有文本内容,先添加文本块
|
|
104
|
+
if text_content.strip():
|
|
105
|
+
blocks.append(
|
|
106
|
+
{
|
|
107
|
+
"type": "section",
|
|
108
|
+
"text": {"type": "mrkdwn", "text": text_content},
|
|
109
|
+
},
|
|
110
|
+
)
|
|
111
|
+
text_content = ""
|
|
112
|
+
|
|
113
|
+
# 添加其他类型的块
|
|
114
|
+
block = await SlackMessageEvent._from_segment_to_slack_block(
|
|
115
|
+
segment,
|
|
116
|
+
web_client,
|
|
117
|
+
)
|
|
118
|
+
blocks.append(block)
|
|
119
|
+
|
|
120
|
+
# 如果最后还有文本内容
|
|
121
|
+
if text_content.strip():
|
|
122
|
+
blocks.append(
|
|
123
|
+
{"type": "section", "text": {"type": "mrkdwn", "text": text_content}},
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
return blocks, "" if blocks else text_content
|
|
127
|
+
|
|
128
|
+
async def send(self, message: MessageChain):
|
|
129
|
+
blocks, text = await SlackMessageEvent._parse_slack_blocks(
|
|
130
|
+
message,
|
|
131
|
+
self.web_client,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
try:
|
|
135
|
+
if self.get_group_id():
|
|
136
|
+
# 发送到频道
|
|
137
|
+
await self.web_client.chat_postMessage(
|
|
138
|
+
channel=self.get_group_id(),
|
|
139
|
+
text=text,
|
|
140
|
+
blocks=blocks or None,
|
|
141
|
+
)
|
|
142
|
+
else:
|
|
143
|
+
# 发送私信
|
|
144
|
+
await self.web_client.chat_postMessage(
|
|
145
|
+
channel=self.get_sender_id(),
|
|
146
|
+
text=text,
|
|
147
|
+
blocks=blocks or None,
|
|
148
|
+
)
|
|
149
|
+
except Exception:
|
|
150
|
+
# 如果块发送失败,尝试只发送文本
|
|
151
|
+
parts = []
|
|
152
|
+
for segment in message.chain:
|
|
153
|
+
if isinstance(segment, Plain):
|
|
154
|
+
parts.append(segment.text)
|
|
155
|
+
elif isinstance(segment, File):
|
|
156
|
+
parts.append(f" [文件: {segment.name}] ")
|
|
157
|
+
elif isinstance(segment, Image):
|
|
158
|
+
parts.append(" [图片] ")
|
|
159
|
+
fallback_text = "".join(parts)
|
|
160
|
+
|
|
161
|
+
if self.get_group_id():
|
|
162
|
+
await self.web_client.chat_postMessage(
|
|
163
|
+
channel=self.get_group_id(),
|
|
164
|
+
text=fallback_text,
|
|
165
|
+
)
|
|
166
|
+
else:
|
|
167
|
+
await self.web_client.chat_postMessage(
|
|
168
|
+
channel=self.get_sender_id(),
|
|
169
|
+
text=fallback_text,
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
await super().send(message)
|
|
173
|
+
|
|
174
|
+
async def send_streaming(
|
|
175
|
+
self,
|
|
176
|
+
generator: AsyncGenerator,
|
|
177
|
+
use_fallback: bool = False,
|
|
178
|
+
):
|
|
179
|
+
if not use_fallback:
|
|
180
|
+
buffer = None
|
|
181
|
+
async for chain in generator:
|
|
182
|
+
if not buffer:
|
|
183
|
+
buffer = chain
|
|
184
|
+
else:
|
|
185
|
+
buffer.chain.extend(chain.chain)
|
|
186
|
+
if not buffer:
|
|
187
|
+
return None
|
|
188
|
+
buffer.squash_plain()
|
|
189
|
+
await self.send(buffer)
|
|
190
|
+
return await super().send_streaming(generator, use_fallback)
|
|
191
|
+
|
|
192
|
+
buffer = ""
|
|
193
|
+
pattern = re.compile(r"[^。?!~…]+[。?!~…]+")
|
|
194
|
+
|
|
195
|
+
async for chain in generator:
|
|
196
|
+
if isinstance(chain, MessageChain):
|
|
197
|
+
for comp in chain.chain:
|
|
198
|
+
if isinstance(comp, Plain):
|
|
199
|
+
buffer += comp.text
|
|
200
|
+
if any(p in buffer for p in "。?!~…"):
|
|
201
|
+
buffer = await self.process_buffer(buffer, pattern)
|
|
202
|
+
else:
|
|
203
|
+
await self.send(MessageChain(chain=[comp]))
|
|
204
|
+
await asyncio.sleep(1.5) # 限速
|
|
205
|
+
|
|
206
|
+
if buffer.strip():
|
|
207
|
+
await self.send(MessageChain([Plain(buffer)]))
|
|
208
|
+
return await super().send_streaming(generator, use_fallback)
|
|
209
|
+
|
|
210
|
+
async def get_group(self, group_id=None, **kwargs):
|
|
211
|
+
if group_id:
|
|
212
|
+
channel_id = group_id
|
|
213
|
+
elif self.get_group_id():
|
|
214
|
+
channel_id = self.get_group_id()
|
|
215
|
+
else:
|
|
216
|
+
return None
|
|
217
|
+
|
|
218
|
+
try:
|
|
219
|
+
# 获取频道信息
|
|
220
|
+
channel_info = await self.web_client.conversations_info(channel=channel_id)
|
|
221
|
+
|
|
222
|
+
# 获取频道成员
|
|
223
|
+
members_response = await self.web_client.conversations_members(
|
|
224
|
+
channel=channel_id,
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
members = []
|
|
228
|
+
for member_id in members_response["members"]:
|
|
229
|
+
try:
|
|
230
|
+
user_info = await self.web_client.users_info(user=member_id)
|
|
231
|
+
user_data = user_info["user"]
|
|
232
|
+
members.append(
|
|
233
|
+
MessageMember(
|
|
234
|
+
user_id=member_id,
|
|
235
|
+
nickname=user_data.get("real_name")
|
|
236
|
+
or user_data.get("name", member_id),
|
|
237
|
+
),
|
|
238
|
+
)
|
|
239
|
+
except Exception:
|
|
240
|
+
# 如果获取用户信息失败,使用默认信息
|
|
241
|
+
members.append(MessageMember(user_id=member_id, nickname=member_id))
|
|
242
|
+
|
|
243
|
+
channel_data = channel_info["channel"]
|
|
244
|
+
return Group(
|
|
245
|
+
group_id=channel_id,
|
|
246
|
+
group_name=channel_data.get("name", ""),
|
|
247
|
+
group_avatar="",
|
|
248
|
+
group_admins=[], # Slack 的管理员信息需要特殊权限获取
|
|
249
|
+
group_owner=channel_data.get("creator", ""),
|
|
250
|
+
members=members,
|
|
251
|
+
)
|
|
252
|
+
except Exception:
|
|
253
|
+
return None
|