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
astrbot/core/platform/manager.py
CHANGED
|
@@ -1,23 +1,28 @@
|
|
|
1
|
-
import traceback
|
|
2
1
|
import asyncio
|
|
2
|
+
import traceback
|
|
3
|
+
from asyncio import Queue
|
|
4
|
+
|
|
5
|
+
from astrbot.core import logger
|
|
3
6
|
from astrbot.core.config.astrbot_config import AstrBotConfig
|
|
7
|
+
from astrbot.core.star.star_handler import EventType, star_handlers_registry, star_map
|
|
8
|
+
|
|
4
9
|
from .platform import Platform
|
|
5
|
-
from typing import List
|
|
6
|
-
from asyncio import Queue
|
|
7
10
|
from .register import platform_cls_map
|
|
8
|
-
from astrbot.core import logger
|
|
9
11
|
from .sources.webchat.webchat_adapter import WebChatAdapter
|
|
10
12
|
|
|
11
13
|
|
|
12
14
|
class PlatformManager:
|
|
13
15
|
def __init__(self, config: AstrBotConfig, event_queue: Queue):
|
|
14
|
-
self.platform_insts:
|
|
16
|
+
self.platform_insts: list[Platform] = []
|
|
15
17
|
"""加载的 Platform 的实例"""
|
|
16
18
|
|
|
17
19
|
self._inst_map = {}
|
|
18
20
|
|
|
19
21
|
self.platforms_config = config["platform"]
|
|
20
22
|
self.settings = config["platform_settings"]
|
|
23
|
+
"""NOTE: 这里是 default 的配置文件,以保证最大的兼容性;
|
|
24
|
+
这个配置中的 unique_session 需要特殊处理,
|
|
25
|
+
约定整个项目中对 unique_session 的引用都从 default 的配置中获取"""
|
|
21
26
|
self.event_queue = event_queue
|
|
22
27
|
|
|
23
28
|
async def initialize(self):
|
|
@@ -32,7 +37,7 @@ class PlatformManager:
|
|
|
32
37
|
webchat_inst = WebChatAdapter({}, self.settings, self.event_queue)
|
|
33
38
|
self.platform_insts.append(webchat_inst)
|
|
34
39
|
asyncio.create_task(
|
|
35
|
-
self._task_wrapper(asyncio.create_task(webchat_inst.run(), name="webchat"))
|
|
40
|
+
self._task_wrapper(asyncio.create_task(webchat_inst.run(), name="webchat")),
|
|
36
41
|
)
|
|
37
42
|
|
|
38
43
|
async def load_platform(self, platform_config: dict):
|
|
@@ -43,7 +48,7 @@ class PlatformManager:
|
|
|
43
48
|
return
|
|
44
49
|
|
|
45
50
|
logger.info(
|
|
46
|
-
f"载入 {platform_config['type']}({platform_config['id']}) 平台适配器 ..."
|
|
51
|
+
f"载入 {platform_config['type']}({platform_config['id']}) 平台适配器 ...",
|
|
47
52
|
)
|
|
48
53
|
match platform_config["type"]:
|
|
49
54
|
case "aiocqhttp":
|
|
@@ -58,30 +63,58 @@ class PlatformManager:
|
|
|
58
63
|
from .sources.qqofficial_webhook.qo_webhook_adapter import (
|
|
59
64
|
QQOfficialWebhookPlatformAdapter, # noqa: F401
|
|
60
65
|
)
|
|
61
|
-
case "
|
|
62
|
-
from .sources.
|
|
63
|
-
|
|
66
|
+
case "wechatpadpro":
|
|
67
|
+
from .sources.wechatpadpro.wechatpadpro_adapter import (
|
|
68
|
+
WeChatPadProAdapter, # noqa: F401
|
|
64
69
|
)
|
|
65
70
|
case "lark":
|
|
66
|
-
from .sources.lark.lark_adapter import
|
|
71
|
+
from .sources.lark.lark_adapter import (
|
|
72
|
+
LarkPlatformAdapter, # noqa: F401
|
|
73
|
+
)
|
|
67
74
|
case "dingtalk":
|
|
68
75
|
from .sources.dingtalk.dingtalk_adapter import (
|
|
69
76
|
DingtalkPlatformAdapter, # noqa: F401
|
|
70
77
|
)
|
|
71
78
|
case "telegram":
|
|
72
|
-
from .sources.telegram.tg_adapter import
|
|
79
|
+
from .sources.telegram.tg_adapter import (
|
|
80
|
+
TelegramPlatformAdapter, # noqa: F401
|
|
81
|
+
)
|
|
73
82
|
case "wecom":
|
|
74
|
-
from .sources.wecom.wecom_adapter import
|
|
83
|
+
from .sources.wecom.wecom_adapter import (
|
|
84
|
+
WecomPlatformAdapter, # noqa: F401
|
|
85
|
+
)
|
|
86
|
+
case "wecom_ai_bot":
|
|
87
|
+
from .sources.wecom_ai_bot.wecomai_adapter import (
|
|
88
|
+
WecomAIBotAdapter, # noqa: F401
|
|
89
|
+
)
|
|
90
|
+
case "weixin_official_account":
|
|
91
|
+
from .sources.weixin_official_account.weixin_offacc_adapter import (
|
|
92
|
+
WeixinOfficialAccountPlatformAdapter, # noqa: F401
|
|
93
|
+
)
|
|
94
|
+
case "discord":
|
|
95
|
+
from .sources.discord.discord_platform_adapter import (
|
|
96
|
+
DiscordPlatformAdapter, # noqa: F401
|
|
97
|
+
)
|
|
98
|
+
case "misskey":
|
|
99
|
+
from .sources.misskey.misskey_adapter import (
|
|
100
|
+
MisskeyPlatformAdapter, # noqa: F401
|
|
101
|
+
)
|
|
102
|
+
case "slack":
|
|
103
|
+
from .sources.slack.slack_adapter import SlackAdapter # noqa: F401
|
|
104
|
+
case "satori":
|
|
105
|
+
from .sources.satori.satori_adapter import (
|
|
106
|
+
SatoriPlatformAdapter, # noqa: F401
|
|
107
|
+
)
|
|
75
108
|
except (ImportError, ModuleNotFoundError) as e:
|
|
76
109
|
logger.error(
|
|
77
|
-
f"加载平台适配器 {platform_config['type']} 失败,原因:{e}。请检查依赖库是否安装。提示:可以在 管理面板->控制台->安装Pip库 中安装依赖库。"
|
|
110
|
+
f"加载平台适配器 {platform_config['type']} 失败,原因:{e}。请检查依赖库是否安装。提示:可以在 管理面板->控制台->安装Pip库 中安装依赖库。",
|
|
78
111
|
)
|
|
79
112
|
except Exception as e:
|
|
80
113
|
logger.error(f"加载平台适配器 {platform_config['type']} 失败,原因:{e}。")
|
|
81
114
|
|
|
82
115
|
if platform_config["type"] not in platform_cls_map:
|
|
83
116
|
logger.error(
|
|
84
|
-
f"未找到适用于 {platform_config['type']}({platform_config['id']}) 平台适配器,请检查是否已经安装或者名称填写错误"
|
|
117
|
+
f"未找到适用于 {platform_config['type']}({platform_config['id']}) 平台适配器,请检查是否已经安装或者名称填写错误",
|
|
85
118
|
)
|
|
86
119
|
return
|
|
87
120
|
cls_type = platform_cls_map[platform_config["type"]]
|
|
@@ -97,9 +130,20 @@ class PlatformManager:
|
|
|
97
130
|
asyncio.create_task(
|
|
98
131
|
inst.run(),
|
|
99
132
|
name=f"platform_{platform_config['type']}_{platform_config['id']}",
|
|
100
|
-
)
|
|
101
|
-
)
|
|
133
|
+
),
|
|
134
|
+
),
|
|
102
135
|
)
|
|
136
|
+
handlers = star_handlers_registry.get_handlers_by_event_type(
|
|
137
|
+
EventType.OnPlatformLoadedEvent,
|
|
138
|
+
)
|
|
139
|
+
for handler in handlers:
|
|
140
|
+
try:
|
|
141
|
+
logger.info(
|
|
142
|
+
f"hook(on_platform_loaded) -> {star_map[handler.handler_module_path].name} - {handler.handler_name}",
|
|
143
|
+
)
|
|
144
|
+
await handler.handler()
|
|
145
|
+
except Exception:
|
|
146
|
+
logger.error(traceback.format_exc())
|
|
103
147
|
|
|
104
148
|
async def _task_wrapper(self, task: asyncio.Task):
|
|
105
149
|
try:
|
|
@@ -137,7 +181,7 @@ class PlatformManager:
|
|
|
137
181
|
inst
|
|
138
182
|
for inst in self.platform_insts
|
|
139
183
|
if inst.client_self_id == client_id
|
|
140
|
-
)
|
|
184
|
+
),
|
|
141
185
|
)
|
|
142
186
|
except Exception:
|
|
143
187
|
logger.warning(f"可能未完全移除 {platform_id} 平台适配器")
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
from astrbot.core.platform.message_type import MessageType
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass
|
|
7
|
+
class MessageSession:
|
|
8
|
+
"""描述一条消息在 AstrBot 中对应的会话的唯一标识。
|
|
9
|
+
如果您需要实例化 MessageSession,请不要给 platform_id 赋值(或者同时给 platform_name 和 platform_id 赋值相同值)。它会在 __post_init__ 中自动设置为 platform_name 的值。
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
platform_name: str
|
|
13
|
+
"""平台适配器实例的唯一标识符。自 AstrBot v4.0.0 起,该字段实际为 platform_id。"""
|
|
14
|
+
message_type: MessageType
|
|
15
|
+
session_id: str
|
|
16
|
+
platform_id: str | None = None
|
|
17
|
+
|
|
18
|
+
def __str__(self):
|
|
19
|
+
return f"{self.platform_id}:{self.message_type.value}:{self.session_id}"
|
|
20
|
+
|
|
21
|
+
def __post_init__(self):
|
|
22
|
+
self.platform_id = self.platform_name
|
|
23
|
+
|
|
24
|
+
@staticmethod
|
|
25
|
+
def from_str(session_str: str):
|
|
26
|
+
platform_id, message_type, session_id = session_str.split(":")
|
|
27
|
+
return MessageSession(platform_id, MessageType(message_type), session_id)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
MessageSesion = MessageSession # back compatibility
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import abc
|
|
2
2
|
import uuid
|
|
3
|
-
from typing import Awaitable, Any
|
|
4
3
|
from asyncio import Queue
|
|
5
|
-
from .
|
|
6
|
-
from
|
|
4
|
+
from collections.abc import Awaitable
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
7
|
from astrbot.core.message.message_event_result import MessageChain
|
|
8
|
-
from .astr_message_event import MessageSesion
|
|
9
8
|
from astrbot.core.utils.metrics import Metric
|
|
10
9
|
|
|
10
|
+
from .astr_message_event import AstrMessageEvent
|
|
11
|
+
from .message_session import MessageSesion
|
|
12
|
+
from .platform_metadata import PlatformMetadata
|
|
13
|
+
|
|
11
14
|
|
|
12
15
|
class Platform(abc.ABC):
|
|
13
16
|
def __init__(self, event_queue: Queue):
|
|
@@ -18,42 +21,31 @@ class Platform(abc.ABC):
|
|
|
18
21
|
|
|
19
22
|
@abc.abstractmethod
|
|
20
23
|
def run(self) -> Awaitable[Any]:
|
|
21
|
-
"""
|
|
22
|
-
得到一个平台的运行实例,需要返回一个协程对象。
|
|
23
|
-
"""
|
|
24
|
+
"""得到一个平台的运行实例,需要返回一个协程对象。"""
|
|
24
25
|
raise NotImplementedError
|
|
25
26
|
|
|
26
27
|
async def terminate(self):
|
|
27
|
-
"""
|
|
28
|
-
终止一个平台的运行实例。
|
|
29
|
-
"""
|
|
30
|
-
...
|
|
28
|
+
"""终止一个平台的运行实例。"""
|
|
31
29
|
|
|
32
30
|
@abc.abstractmethod
|
|
33
31
|
def meta(self) -> PlatformMetadata:
|
|
34
|
-
"""
|
|
35
|
-
得到一个平台的元数据。
|
|
36
|
-
"""
|
|
32
|
+
"""得到一个平台的元数据。"""
|
|
37
33
|
raise NotImplementedError
|
|
38
34
|
|
|
39
35
|
async def send_by_session(
|
|
40
|
-
self,
|
|
36
|
+
self,
|
|
37
|
+
session: MessageSesion,
|
|
38
|
+
message_chain: MessageChain,
|
|
41
39
|
) -> Awaitable[Any]:
|
|
42
|
-
"""
|
|
43
|
-
通过会话发送消息。该方法旨在让插件能够直接通过**可持久化的会话数据**发送消息,而不需要保存 event 对象。
|
|
40
|
+
"""通过会话发送消息。该方法旨在让插件能够直接通过**可持久化的会话数据**发送消息,而不需要保存 event 对象。
|
|
44
41
|
|
|
45
42
|
异步方法。
|
|
46
43
|
"""
|
|
47
44
|
await Metric.upload(msg_event_tick=1, adapter_name=self.meta().name)
|
|
48
45
|
|
|
49
46
|
def commit_event(self, event: AstrMessageEvent):
|
|
50
|
-
"""
|
|
51
|
-
提交一个事件到事件队列。
|
|
52
|
-
"""
|
|
47
|
+
"""提交一个事件到事件队列。"""
|
|
53
48
|
self._event_queue.put_nowait(event)
|
|
54
49
|
|
|
55
50
|
def get_client(self):
|
|
56
|
-
"""
|
|
57
|
-
获取平台的客户端对象。
|
|
58
|
-
"""
|
|
59
|
-
pass
|
|
51
|
+
"""获取平台的客户端对象。"""
|
|
@@ -4,13 +4,18 @@ from dataclasses import dataclass
|
|
|
4
4
|
@dataclass
|
|
5
5
|
class PlatformMetadata:
|
|
6
6
|
name: str
|
|
7
|
-
"""
|
|
7
|
+
"""平台的名称,即平台的类型,如 aiocqhttp, discord, slack"""
|
|
8
8
|
description: str
|
|
9
9
|
"""平台的描述"""
|
|
10
|
-
id: str = None
|
|
10
|
+
id: str | None = None
|
|
11
11
|
"""平台的唯一标识符,用于配置中识别特定平台"""
|
|
12
12
|
|
|
13
|
-
default_config_tmpl: dict = None
|
|
13
|
+
default_config_tmpl: dict | None = None
|
|
14
14
|
"""平台的默认配置模板"""
|
|
15
|
-
adapter_display_name: str = None
|
|
15
|
+
adapter_display_name: str | None = None
|
|
16
16
|
"""显示在 WebUI 配置页中的平台名称,如空则是 name"""
|
|
17
|
+
logo_path: str | None = None
|
|
18
|
+
"""平台适配器的 logo 文件路径(相对于插件目录)"""
|
|
19
|
+
|
|
20
|
+
support_streaming_message: bool = True
|
|
21
|
+
"""平台是否支持真实流式传输"""
|
|
@@ -1,28 +1,31 @@
|
|
|
1
|
-
from typing import List, Dict, Type
|
|
2
|
-
from .platform_metadata import PlatformMetadata
|
|
3
1
|
from astrbot.core import logger
|
|
4
2
|
|
|
5
|
-
|
|
3
|
+
from .platform_metadata import PlatformMetadata
|
|
4
|
+
|
|
5
|
+
platform_registry: list[PlatformMetadata] = []
|
|
6
6
|
"""维护了通过装饰器注册的平台适配器"""
|
|
7
|
-
platform_cls_map:
|
|
7
|
+
platform_cls_map: dict[str, type] = {}
|
|
8
8
|
"""维护了平台适配器名称和适配器类的映射"""
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
def register_platform_adapter(
|
|
12
12
|
adapter_name: str,
|
|
13
13
|
desc: str,
|
|
14
|
-
default_config_tmpl: dict = None,
|
|
15
|
-
adapter_display_name: str = None,
|
|
14
|
+
default_config_tmpl: dict | None = None,
|
|
15
|
+
adapter_display_name: str | None = None,
|
|
16
|
+
logo_path: str | None = None,
|
|
17
|
+
support_streaming_message: bool = True,
|
|
16
18
|
):
|
|
17
19
|
"""用于注册平台适配器的带参装饰器。
|
|
18
20
|
|
|
19
21
|
default_config_tmpl 指定了平台适配器的默认配置模板。用户填写好后将会作为 platform_config 传入你的 Platform 类的实现类。
|
|
22
|
+
logo_path 指定了平台适配器的 logo 文件路径,是相对于插件目录的路径。
|
|
20
23
|
"""
|
|
21
24
|
|
|
22
25
|
def decorator(cls):
|
|
23
26
|
if adapter_name in platform_cls_map:
|
|
24
27
|
raise ValueError(
|
|
25
|
-
f"平台适配器 {adapter_name} 已经注册过了,可能发生了适配器命名冲突。"
|
|
28
|
+
f"平台适配器 {adapter_name} 已经注册过了,可能发生了适配器命名冲突。",
|
|
26
29
|
)
|
|
27
30
|
|
|
28
31
|
# 添加必备选项
|
|
@@ -39,6 +42,8 @@ def register_platform_adapter(
|
|
|
39
42
|
description=desc,
|
|
40
43
|
default_config_tmpl=default_config_tmpl,
|
|
41
44
|
adapter_display_name=adapter_display_name,
|
|
45
|
+
logo_path=logo_path,
|
|
46
|
+
support_streaming_message=support_streaming_message,
|
|
42
47
|
)
|
|
43
48
|
platform_registry.append(pm)
|
|
44
49
|
platform_cls_map[adapter_name] = cls
|
|
@@ -1,90 +1,166 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import re
|
|
3
|
-
from
|
|
4
|
-
|
|
3
|
+
from collections.abc import AsyncGenerator
|
|
4
|
+
|
|
5
|
+
from aiocqhttp import CQHttp, Event
|
|
6
|
+
|
|
5
7
|
from astrbot.api.event import AstrMessageEvent, MessageChain
|
|
6
|
-
from astrbot.api.message_components import
|
|
8
|
+
from astrbot.api.message_components import (
|
|
9
|
+
BaseMessageComponent,
|
|
10
|
+
File,
|
|
11
|
+
Image,
|
|
12
|
+
Node,
|
|
13
|
+
Nodes,
|
|
14
|
+
Plain,
|
|
15
|
+
Record,
|
|
16
|
+
Video,
|
|
17
|
+
)
|
|
7
18
|
from astrbot.api.platform import Group, MessageMember
|
|
8
19
|
|
|
9
20
|
|
|
10
21
|
class AiocqhttpMessageEvent(AstrMessageEvent):
|
|
11
22
|
def __init__(
|
|
12
|
-
self,
|
|
23
|
+
self,
|
|
24
|
+
message_str,
|
|
25
|
+
message_obj,
|
|
26
|
+
platform_meta,
|
|
27
|
+
session_id,
|
|
28
|
+
bot: CQHttp,
|
|
13
29
|
):
|
|
14
30
|
super().__init__(message_str, message_obj, platform_meta, session_id)
|
|
15
31
|
self.bot = bot
|
|
16
32
|
|
|
33
|
+
@staticmethod
|
|
34
|
+
async def _from_segment_to_dict(segment: BaseMessageComponent) -> dict:
|
|
35
|
+
"""修复部分字段"""
|
|
36
|
+
if isinstance(segment, (Image, Record)):
|
|
37
|
+
# For Image and Record segments, we convert them to base64
|
|
38
|
+
bs64 = await segment.convert_to_base64()
|
|
39
|
+
return {
|
|
40
|
+
"type": segment.type.lower(),
|
|
41
|
+
"data": {
|
|
42
|
+
"file": f"base64://{bs64}",
|
|
43
|
+
},
|
|
44
|
+
}
|
|
45
|
+
if isinstance(segment, File):
|
|
46
|
+
# For File segments, we need to handle the file differently
|
|
47
|
+
d = await segment.to_dict()
|
|
48
|
+
return d
|
|
49
|
+
if isinstance(segment, Video):
|
|
50
|
+
d = await segment.to_dict()
|
|
51
|
+
return d
|
|
52
|
+
# For other segments, we simply convert them to a dict by calling toDict
|
|
53
|
+
return segment.toDict()
|
|
54
|
+
|
|
17
55
|
@staticmethod
|
|
18
56
|
async def _parse_onebot_json(message_chain: MessageChain):
|
|
19
57
|
"""解析成 OneBot json 格式"""
|
|
20
58
|
ret = []
|
|
21
59
|
for segment in message_chain.chain:
|
|
22
|
-
d = segment.toDict()
|
|
23
60
|
if isinstance(segment, Plain):
|
|
24
|
-
|
|
25
|
-
d["data"]["text"] = segment.text.strip()
|
|
26
|
-
# 如果是空文本或者只带换行符的文本,不发送
|
|
27
|
-
if not d["data"]["text"]:
|
|
61
|
+
if not segment.text.strip():
|
|
28
62
|
continue
|
|
29
|
-
|
|
30
|
-
# convert to base64
|
|
31
|
-
bs64 = await segment.convert_to_base64()
|
|
32
|
-
d["data"] = {
|
|
33
|
-
"file": f"base64://{bs64}",
|
|
34
|
-
}
|
|
35
|
-
elif isinstance(segment, At):
|
|
36
|
-
d["data"] = {
|
|
37
|
-
"qq": str(segment.qq) # 转换为字符串
|
|
38
|
-
}
|
|
63
|
+
d = await AiocqhttpMessageEvent._from_segment_to_dict(segment)
|
|
39
64
|
ret.append(d)
|
|
40
65
|
return ret
|
|
41
66
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
67
|
+
@classmethod
|
|
68
|
+
async def _dispatch_send(
|
|
69
|
+
cls,
|
|
70
|
+
bot: CQHttp,
|
|
71
|
+
event: Event | None,
|
|
72
|
+
is_group: bool,
|
|
73
|
+
session_id: str,
|
|
74
|
+
messages: list[dict],
|
|
75
|
+
):
|
|
76
|
+
# session_id 必须是纯数字字符串
|
|
77
|
+
session_id = int(session_id) if session_id.isdigit() else None
|
|
78
|
+
|
|
79
|
+
if is_group and isinstance(session_id, int):
|
|
80
|
+
await bot.send_group_msg(group_id=session_id, message=messages)
|
|
81
|
+
elif not is_group and isinstance(session_id, int):
|
|
82
|
+
await bot.send_private_msg(user_id=session_id, message=messages)
|
|
83
|
+
elif isinstance(event, Event): # 最后兜底
|
|
84
|
+
await bot.send(event=event, message=messages)
|
|
85
|
+
else:
|
|
86
|
+
raise ValueError(
|
|
87
|
+
f"无法发送消息:缺少有效的数字 session_id({session_id}) 或 event({event})",
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
@classmethod
|
|
91
|
+
async def send_message(
|
|
92
|
+
cls,
|
|
93
|
+
bot: CQHttp,
|
|
94
|
+
message_chain: MessageChain,
|
|
95
|
+
event: Event | None = None,
|
|
96
|
+
is_group: bool = False,
|
|
97
|
+
session_id: str | None = None,
|
|
98
|
+
):
|
|
99
|
+
"""发送消息至 QQ 协议端(aiocqhttp)。
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
bot (CQHttp): aiocqhttp 机器人实例
|
|
103
|
+
message_chain (MessageChain): 要发送的消息链
|
|
104
|
+
event (Event | None, optional): aiocqhttp 事件对象.
|
|
105
|
+
is_group (bool, optional): 是否为群消息.
|
|
106
|
+
session_id (str | None, optional): 会话 ID(群号或 QQ 号
|
|
107
|
+
|
|
108
|
+
"""
|
|
109
|
+
# 转发消息、文件消息不能和普通消息混在一起发送
|
|
110
|
+
send_one_by_one = any(
|
|
111
|
+
isinstance(seg, (Node, Nodes, File)) for seg in message_chain.chain
|
|
112
|
+
)
|
|
113
|
+
if not send_one_by_one:
|
|
114
|
+
ret = await cls._parse_onebot_json(message_chain)
|
|
115
|
+
if not ret:
|
|
116
|
+
return
|
|
117
|
+
await cls._dispatch_send(bot, event, is_group, session_id, ret)
|
|
46
118
|
return
|
|
47
|
-
|
|
48
|
-
send_one_by_one = False
|
|
49
|
-
for seg in message.chain:
|
|
119
|
+
for seg in message_chain.chain:
|
|
50
120
|
if isinstance(seg, (Node, Nodes)):
|
|
51
|
-
#
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
nodes = Nodes([seg])
|
|
62
|
-
seg = nodes
|
|
63
|
-
|
|
64
|
-
payload = seg.toDict()
|
|
65
|
-
if self.get_group_id():
|
|
66
|
-
payload["group_id"] = self.get_group_id()
|
|
67
|
-
await self.bot.call_action("send_group_forward_msg", **payload)
|
|
68
|
-
else:
|
|
69
|
-
payload["user_id"] = self.get_sender_id()
|
|
70
|
-
await self.bot.call_action(
|
|
71
|
-
"send_private_forward_msg", **payload
|
|
72
|
-
)
|
|
121
|
+
# 合并转发消息
|
|
122
|
+
if isinstance(seg, Node):
|
|
123
|
+
nodes = Nodes([seg])
|
|
124
|
+
seg = nodes
|
|
125
|
+
|
|
126
|
+
payload = await seg.to_dict()
|
|
127
|
+
|
|
128
|
+
if is_group:
|
|
129
|
+
payload["group_id"] = session_id
|
|
130
|
+
await bot.call_action("send_group_forward_msg", **payload)
|
|
73
131
|
else:
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
132
|
+
payload["user_id"] = session_id
|
|
133
|
+
await bot.call_action("send_private_forward_msg", **payload)
|
|
134
|
+
elif isinstance(seg, File):
|
|
135
|
+
d = await cls._from_segment_to_dict(seg)
|
|
136
|
+
await cls._dispatch_send(bot, event, is_group, session_id, [d])
|
|
137
|
+
else:
|
|
138
|
+
messages = await cls._parse_onebot_json(MessageChain([seg]))
|
|
139
|
+
if not messages:
|
|
140
|
+
continue
|
|
141
|
+
await cls._dispatch_send(bot, event, is_group, session_id, messages)
|
|
142
|
+
await asyncio.sleep(0.5)
|
|
83
143
|
|
|
144
|
+
async def send(self, message: MessageChain):
|
|
145
|
+
"""发送消息"""
|
|
146
|
+
event = getattr(self.message_obj, "raw_message", None)
|
|
147
|
+
|
|
148
|
+
is_group = bool(self.get_group_id())
|
|
149
|
+
session_id = self.get_group_id() if is_group else self.get_sender_id()
|
|
150
|
+
|
|
151
|
+
await self.send_message(
|
|
152
|
+
bot=self.bot,
|
|
153
|
+
message_chain=message,
|
|
154
|
+
event=event, # 不强制要求一定是 Event
|
|
155
|
+
is_group=is_group,
|
|
156
|
+
session_id=session_id,
|
|
157
|
+
)
|
|
84
158
|
await super().send(message)
|
|
85
159
|
|
|
86
160
|
async def send_streaming(
|
|
87
|
-
self,
|
|
161
|
+
self,
|
|
162
|
+
generator: AsyncGenerator,
|
|
163
|
+
use_fallback: bool = False,
|
|
88
164
|
):
|
|
89
165
|
if not use_fallback:
|
|
90
166
|
buffer = None
|
|
@@ -94,7 +170,7 @@ class AiocqhttpMessageEvent(AstrMessageEvent):
|
|
|
94
170
|
else:
|
|
95
171
|
buffer.chain.extend(chain.chain)
|
|
96
172
|
if not buffer:
|
|
97
|
-
return
|
|
173
|
+
return None
|
|
98
174
|
buffer.squash_plain()
|
|
99
175
|
await self.send(buffer)
|
|
100
176
|
return await super().send_streaming(generator, use_fallback)
|
|
@@ -130,7 +206,7 @@ class AiocqhttpMessageEvent(AstrMessageEvent):
|
|
|
130
206
|
group_id=group_id,
|
|
131
207
|
)
|
|
132
208
|
|
|
133
|
-
members:
|
|
209
|
+
members: list[dict] = await self.bot.call_action(
|
|
134
210
|
"get_group_member_list",
|
|
135
211
|
group_id=group_id,
|
|
136
212
|
)
|