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,473 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import re
|
|
3
|
+
import sys
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
import discord
|
|
7
|
+
from discord.abc import Messageable
|
|
8
|
+
from discord.channel import DMChannel
|
|
9
|
+
|
|
10
|
+
from astrbot import logger
|
|
11
|
+
from astrbot.api.event import MessageChain
|
|
12
|
+
from astrbot.api.message_components import File, Image, Plain
|
|
13
|
+
from astrbot.api.platform import (
|
|
14
|
+
AstrBotMessage,
|
|
15
|
+
MessageMember,
|
|
16
|
+
MessageType,
|
|
17
|
+
Platform,
|
|
18
|
+
PlatformMetadata,
|
|
19
|
+
register_platform_adapter,
|
|
20
|
+
)
|
|
21
|
+
from astrbot.core.platform.astr_message_event import MessageSesion
|
|
22
|
+
from astrbot.core.star.filter.command import CommandFilter
|
|
23
|
+
from astrbot.core.star.filter.command_group import CommandGroupFilter
|
|
24
|
+
from astrbot.core.star.star import star_map
|
|
25
|
+
from astrbot.core.star.star_handler import StarHandlerMetadata, star_handlers_registry
|
|
26
|
+
|
|
27
|
+
from .client import DiscordBotClient
|
|
28
|
+
from .discord_platform_event import DiscordPlatformEvent
|
|
29
|
+
|
|
30
|
+
if sys.version_info >= (3, 12):
|
|
31
|
+
from typing import override
|
|
32
|
+
else:
|
|
33
|
+
from typing_extensions import override
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# 注册平台适配器
|
|
37
|
+
@register_platform_adapter(
|
|
38
|
+
"discord", "Discord 适配器 (基于 Pycord)", support_streaming_message=False
|
|
39
|
+
)
|
|
40
|
+
class DiscordPlatformAdapter(Platform):
|
|
41
|
+
def __init__(
|
|
42
|
+
self,
|
|
43
|
+
platform_config: dict,
|
|
44
|
+
platform_settings: dict,
|
|
45
|
+
event_queue: asyncio.Queue,
|
|
46
|
+
) -> None:
|
|
47
|
+
super().__init__(event_queue)
|
|
48
|
+
self.config = platform_config
|
|
49
|
+
self.settings = platform_settings
|
|
50
|
+
self.client_self_id = None
|
|
51
|
+
self.registered_handlers = []
|
|
52
|
+
# 指令注册相关
|
|
53
|
+
self.enable_command_register = self.config.get("discord_command_register", True)
|
|
54
|
+
self.guild_id = self.config.get("discord_guild_id_for_debug", None)
|
|
55
|
+
self.activity_name = self.config.get("discord_activity_name", None)
|
|
56
|
+
self.shutdown_event = asyncio.Event()
|
|
57
|
+
self._polling_task = None
|
|
58
|
+
|
|
59
|
+
@override
|
|
60
|
+
async def send_by_session(
|
|
61
|
+
self,
|
|
62
|
+
session: MessageSesion,
|
|
63
|
+
message_chain: MessageChain,
|
|
64
|
+
):
|
|
65
|
+
"""通过会话发送消息"""
|
|
66
|
+
# 创建一个 message_obj 以便在 event 中使用
|
|
67
|
+
message_obj = AstrBotMessage()
|
|
68
|
+
if "_" in session.session_id:
|
|
69
|
+
session.session_id = session.session_id.split("_")[1]
|
|
70
|
+
channel_id_str = session.session_id
|
|
71
|
+
channel = None
|
|
72
|
+
try:
|
|
73
|
+
channel_id = int(channel_id_str)
|
|
74
|
+
channel = self.client.get_channel(channel_id)
|
|
75
|
+
except (ValueError, TypeError):
|
|
76
|
+
logger.warning(f"[Discord] Invalid channel ID format: {channel_id_str}")
|
|
77
|
+
|
|
78
|
+
if channel:
|
|
79
|
+
message_obj.type = self._get_message_type(channel)
|
|
80
|
+
message_obj.group_id = self._get_channel_id(channel)
|
|
81
|
+
else:
|
|
82
|
+
logger.warning(
|
|
83
|
+
f"[Discord] Can't get channel info for {channel_id_str}, will guess message type.",
|
|
84
|
+
)
|
|
85
|
+
message_obj.type = MessageType.GROUP_MESSAGE
|
|
86
|
+
message_obj.group_id = session.session_id
|
|
87
|
+
|
|
88
|
+
message_obj.message_str = message_chain.get_plain_text()
|
|
89
|
+
message_obj.sender = MessageMember(
|
|
90
|
+
user_id=str(self.client_self_id),
|
|
91
|
+
nickname=self.client.user.display_name,
|
|
92
|
+
)
|
|
93
|
+
message_obj.self_id = self.client_self_id
|
|
94
|
+
message_obj.session_id = session.session_id
|
|
95
|
+
message_obj.message = message_chain.chain
|
|
96
|
+
|
|
97
|
+
# 创建临时事件对象来发送消息
|
|
98
|
+
temp_event = DiscordPlatformEvent(
|
|
99
|
+
message_str=message_chain.get_plain_text(),
|
|
100
|
+
message_obj=message_obj,
|
|
101
|
+
platform_meta=self.meta(),
|
|
102
|
+
session_id=session.session_id,
|
|
103
|
+
client=self.client,
|
|
104
|
+
)
|
|
105
|
+
await temp_event.send(message_chain)
|
|
106
|
+
await super().send_by_session(session, message_chain)
|
|
107
|
+
|
|
108
|
+
@override
|
|
109
|
+
def meta(self) -> PlatformMetadata:
|
|
110
|
+
"""返回平台元数据"""
|
|
111
|
+
return PlatformMetadata(
|
|
112
|
+
"discord",
|
|
113
|
+
"Discord 适配器",
|
|
114
|
+
id=self.config.get("id"),
|
|
115
|
+
default_config_tmpl=self.config,
|
|
116
|
+
support_streaming_message=False,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
@override
|
|
120
|
+
async def run(self):
|
|
121
|
+
"""主要运行逻辑"""
|
|
122
|
+
|
|
123
|
+
# 初始化回调函数
|
|
124
|
+
async def on_received(message_data):
|
|
125
|
+
logger.debug(f"[Discord] 收到消息: {message_data}")
|
|
126
|
+
if self.client_self_id is None:
|
|
127
|
+
self.client_self_id = message_data.get("bot_id")
|
|
128
|
+
abm = await self.convert_message(data=message_data)
|
|
129
|
+
await self.handle_msg(abm)
|
|
130
|
+
|
|
131
|
+
# 初始化 Discord 客户端
|
|
132
|
+
token = str(self.config.get("discord_token"))
|
|
133
|
+
if not token:
|
|
134
|
+
logger.error("[Discord] Bot Token 未配置。请在配置文件中正确设置 token。")
|
|
135
|
+
return
|
|
136
|
+
|
|
137
|
+
proxy = self.config.get("discord_proxy") or None
|
|
138
|
+
self.client = DiscordBotClient(token, proxy)
|
|
139
|
+
self.client.on_message_received = on_received
|
|
140
|
+
|
|
141
|
+
async def callback():
|
|
142
|
+
if self.enable_command_register:
|
|
143
|
+
await self._collect_and_register_commands()
|
|
144
|
+
if self.activity_name:
|
|
145
|
+
await self.client.change_presence(
|
|
146
|
+
status=discord.Status.online,
|
|
147
|
+
activity=discord.CustomActivity(name=self.activity_name),
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
self.client.on_ready_once_callback = callback
|
|
151
|
+
|
|
152
|
+
try:
|
|
153
|
+
self._polling_task = asyncio.create_task(self.client.start_polling())
|
|
154
|
+
await self.shutdown_event.wait()
|
|
155
|
+
except discord.errors.LoginFailure:
|
|
156
|
+
logger.error("[Discord] 登录失败。请检查你的 Bot Token 是否正确。")
|
|
157
|
+
except discord.errors.ConnectionClosed:
|
|
158
|
+
logger.warning("[Discord] 与 Discord 的连接已关闭。")
|
|
159
|
+
except Exception as e:
|
|
160
|
+
logger.error(f"[Discord] 适配器运行时发生意外错误: {e}", exc_info=True)
|
|
161
|
+
|
|
162
|
+
def _get_message_type(
|
|
163
|
+
self,
|
|
164
|
+
channel: Messageable,
|
|
165
|
+
guild_id: int | None = None,
|
|
166
|
+
) -> MessageType:
|
|
167
|
+
"""根据 channel 对象和 guild_id 判断消息类型"""
|
|
168
|
+
if guild_id is not None:
|
|
169
|
+
return MessageType.GROUP_MESSAGE
|
|
170
|
+
if isinstance(channel, DMChannel) or getattr(channel, "guild", None) is None:
|
|
171
|
+
return MessageType.FRIEND_MESSAGE
|
|
172
|
+
return MessageType.GROUP_MESSAGE
|
|
173
|
+
|
|
174
|
+
def _get_channel_id(self, channel: Messageable) -> str:
|
|
175
|
+
"""根据 channel 对象获取ID"""
|
|
176
|
+
return str(getattr(channel, "id", None))
|
|
177
|
+
|
|
178
|
+
def _convert_message_to_abm(self, data: dict) -> AstrBotMessage:
|
|
179
|
+
"""将普通消息转换为 AstrBotMessage"""
|
|
180
|
+
message: discord.Message = data["message"]
|
|
181
|
+
|
|
182
|
+
content = message.content
|
|
183
|
+
|
|
184
|
+
# 如果机器人被@,移除@部分
|
|
185
|
+
# 剥离 User Mention (<@id>, <@!id>)
|
|
186
|
+
if self.client and self.client.user:
|
|
187
|
+
mention_str = f"<@{self.client.user.id}>"
|
|
188
|
+
mention_str_nickname = f"<@!{self.client.user.id}>"
|
|
189
|
+
if content.startswith(mention_str):
|
|
190
|
+
content = content[len(mention_str) :].lstrip()
|
|
191
|
+
elif content.startswith(mention_str_nickname):
|
|
192
|
+
content = content[len(mention_str_nickname) :].lstrip()
|
|
193
|
+
|
|
194
|
+
# 剥离 Role Mention(bot 拥有的任一角色被提及,<@&role_id>)
|
|
195
|
+
if (
|
|
196
|
+
hasattr(message, "role_mentions")
|
|
197
|
+
and hasattr(message, "guild")
|
|
198
|
+
and message.guild
|
|
199
|
+
):
|
|
200
|
+
bot_member = (
|
|
201
|
+
message.guild.get_member(self.client.user.id)
|
|
202
|
+
if self.client and self.client.user
|
|
203
|
+
else None
|
|
204
|
+
)
|
|
205
|
+
if bot_member and hasattr(bot_member, "roles"):
|
|
206
|
+
for role in bot_member.roles:
|
|
207
|
+
role_mention_str = f"<@&{role.id}>"
|
|
208
|
+
if content.startswith(role_mention_str):
|
|
209
|
+
content = content[len(role_mention_str) :].lstrip()
|
|
210
|
+
break # 只剥离第一个匹配的角色 mention
|
|
211
|
+
|
|
212
|
+
abm = AstrBotMessage()
|
|
213
|
+
abm.type = self._get_message_type(message.channel)
|
|
214
|
+
abm.group_id = self._get_channel_id(message.channel)
|
|
215
|
+
abm.message_str = content
|
|
216
|
+
abm.sender = MessageMember(
|
|
217
|
+
user_id=str(message.author.id),
|
|
218
|
+
nickname=message.author.display_name,
|
|
219
|
+
)
|
|
220
|
+
message_chain = []
|
|
221
|
+
if abm.message_str:
|
|
222
|
+
message_chain.append(Plain(text=abm.message_str))
|
|
223
|
+
if message.attachments:
|
|
224
|
+
for attachment in message.attachments:
|
|
225
|
+
if attachment.content_type and attachment.content_type.startswith(
|
|
226
|
+
"image/",
|
|
227
|
+
):
|
|
228
|
+
message_chain.append(
|
|
229
|
+
Image(file=attachment.url, filename=attachment.filename),
|
|
230
|
+
)
|
|
231
|
+
else:
|
|
232
|
+
message_chain.append(
|
|
233
|
+
File(name=attachment.filename, url=attachment.url),
|
|
234
|
+
)
|
|
235
|
+
abm.message = message_chain
|
|
236
|
+
abm.raw_message = message
|
|
237
|
+
abm.self_id = self.client_self_id
|
|
238
|
+
abm.session_id = str(message.channel.id)
|
|
239
|
+
abm.message_id = str(message.id)
|
|
240
|
+
return abm
|
|
241
|
+
|
|
242
|
+
async def convert_message(self, data: dict) -> AstrBotMessage:
|
|
243
|
+
"""将平台消息转换成 AstrBotMessage"""
|
|
244
|
+
# 由于 on_interaction 已被禁用,我们只处理普通消息
|
|
245
|
+
return self._convert_message_to_abm(data)
|
|
246
|
+
|
|
247
|
+
async def handle_msg(self, message: AstrBotMessage, followup_webhook=None):
|
|
248
|
+
"""处理消息"""
|
|
249
|
+
message_event = DiscordPlatformEvent(
|
|
250
|
+
message_str=message.message_str,
|
|
251
|
+
message_obj=message,
|
|
252
|
+
platform_meta=self.meta(),
|
|
253
|
+
session_id=message.session_id,
|
|
254
|
+
client=self.client,
|
|
255
|
+
interaction_followup_webhook=followup_webhook,
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
# 检查是否为斜杠指令
|
|
259
|
+
is_slash_command = message_event.interaction_followup_webhook is not None
|
|
260
|
+
|
|
261
|
+
# 检查是否被@(User Mention 或 Bot 拥有的 Role Mention)
|
|
262
|
+
is_mention = False
|
|
263
|
+
# User Mention
|
|
264
|
+
if (
|
|
265
|
+
self.client
|
|
266
|
+
and self.client.user
|
|
267
|
+
and hasattr(message.raw_message, "mentions")
|
|
268
|
+
):
|
|
269
|
+
if self.client.user in message.raw_message.mentions:
|
|
270
|
+
is_mention = True
|
|
271
|
+
# Role Mention(Bot 拥有的角色被提及)
|
|
272
|
+
if not is_mention and hasattr(message.raw_message, "role_mentions"):
|
|
273
|
+
bot_member = None
|
|
274
|
+
if hasattr(message.raw_message, "guild") and message.raw_message.guild:
|
|
275
|
+
try:
|
|
276
|
+
bot_member = message.raw_message.guild.get_member(
|
|
277
|
+
self.client.user.id,
|
|
278
|
+
)
|
|
279
|
+
except Exception:
|
|
280
|
+
bot_member = None
|
|
281
|
+
if bot_member and hasattr(bot_member, "roles"):
|
|
282
|
+
bot_roles = set(bot_member.roles)
|
|
283
|
+
mentioned_roles = set(message.raw_message.role_mentions)
|
|
284
|
+
if (
|
|
285
|
+
bot_roles
|
|
286
|
+
and mentioned_roles
|
|
287
|
+
and bot_roles.intersection(mentioned_roles)
|
|
288
|
+
):
|
|
289
|
+
is_mention = True
|
|
290
|
+
|
|
291
|
+
# 如果是斜杠指令或被@的消息,设置为唤醒状态
|
|
292
|
+
if is_slash_command or is_mention:
|
|
293
|
+
message_event.is_wake = True
|
|
294
|
+
message_event.is_at_or_wake_command = True
|
|
295
|
+
|
|
296
|
+
self.commit_event(message_event)
|
|
297
|
+
|
|
298
|
+
@override
|
|
299
|
+
async def terminate(self):
|
|
300
|
+
"""终止适配器"""
|
|
301
|
+
logger.info("[Discord] 正在终止适配器... (step 1: cancel polling task)")
|
|
302
|
+
self.shutdown_event.set()
|
|
303
|
+
# 优先 cancel polling_task
|
|
304
|
+
if self._polling_task:
|
|
305
|
+
self._polling_task.cancel()
|
|
306
|
+
try:
|
|
307
|
+
await asyncio.wait_for(self._polling_task, timeout=10)
|
|
308
|
+
except asyncio.CancelledError:
|
|
309
|
+
logger.info("[Discord] polling_task 已取消。")
|
|
310
|
+
except Exception as e:
|
|
311
|
+
logger.warning(f"[Discord] polling_task 取消异常: {e}")
|
|
312
|
+
logger.info("[Discord] 正在清理已注册的斜杠指令... (step 2)")
|
|
313
|
+
# 清理指令
|
|
314
|
+
if self.enable_command_register and self.client:
|
|
315
|
+
try:
|
|
316
|
+
await asyncio.wait_for(
|
|
317
|
+
self.client.sync_commands(
|
|
318
|
+
commands=[],
|
|
319
|
+
guild_ids=[self.guild_id] if self.guild_id else None,
|
|
320
|
+
),
|
|
321
|
+
timeout=10,
|
|
322
|
+
)
|
|
323
|
+
logger.info("[Discord] 指令清理完成。")
|
|
324
|
+
except Exception as e:
|
|
325
|
+
logger.error(f"[Discord] 清理指令时发生错误: {e}", exc_info=True)
|
|
326
|
+
logger.info("[Discord] 正在关闭 Discord 客户端... (step 3)")
|
|
327
|
+
if self.client and hasattr(self.client, "close"):
|
|
328
|
+
try:
|
|
329
|
+
await asyncio.wait_for(self.client.close(), timeout=10)
|
|
330
|
+
except Exception as e:
|
|
331
|
+
logger.warning(f"[Discord] 客户端关闭异常: {e}")
|
|
332
|
+
logger.info("[Discord] 适配器已终止。")
|
|
333
|
+
|
|
334
|
+
def register_handler(self, handler_info):
|
|
335
|
+
"""注册处理器信息"""
|
|
336
|
+
self.registered_handlers.append(handler_info)
|
|
337
|
+
|
|
338
|
+
async def _collect_and_register_commands(self):
|
|
339
|
+
"""收集所有指令并注册到Discord"""
|
|
340
|
+
logger.info("[Discord] 开始收集并注册斜杠指令...")
|
|
341
|
+
registered_commands = []
|
|
342
|
+
|
|
343
|
+
for handler_md in star_handlers_registry:
|
|
344
|
+
if not star_map[handler_md.handler_module_path].activated:
|
|
345
|
+
continue
|
|
346
|
+
for event_filter in handler_md.event_filters:
|
|
347
|
+
cmd_info = self._extract_command_info(event_filter, handler_md)
|
|
348
|
+
if not cmd_info:
|
|
349
|
+
continue
|
|
350
|
+
|
|
351
|
+
cmd_name, description, cmd_filter_instance = cmd_info
|
|
352
|
+
|
|
353
|
+
# 创建动态回调
|
|
354
|
+
callback = self._create_dynamic_callback(cmd_name)
|
|
355
|
+
|
|
356
|
+
# 创建一个通用的参数选项来接收所有文本输入
|
|
357
|
+
options = [
|
|
358
|
+
discord.Option(
|
|
359
|
+
name="params",
|
|
360
|
+
description="指令的所有参数",
|
|
361
|
+
type=discord.SlashCommandOptionType.string,
|
|
362
|
+
required=False,
|
|
363
|
+
),
|
|
364
|
+
]
|
|
365
|
+
|
|
366
|
+
# 创建SlashCommand
|
|
367
|
+
slash_command = discord.SlashCommand(
|
|
368
|
+
name=cmd_name,
|
|
369
|
+
description=description,
|
|
370
|
+
func=callback,
|
|
371
|
+
options=options,
|
|
372
|
+
guild_ids=[self.guild_id] if self.guild_id else None,
|
|
373
|
+
)
|
|
374
|
+
self.client.add_application_command(slash_command)
|
|
375
|
+
registered_commands.append(cmd_name)
|
|
376
|
+
|
|
377
|
+
if registered_commands:
|
|
378
|
+
logger.info(
|
|
379
|
+
f"[Discord] 准备同步 {len(registered_commands)} 个指令: {', '.join(registered_commands)}",
|
|
380
|
+
)
|
|
381
|
+
else:
|
|
382
|
+
logger.info("[Discord] 没有发现可注册的指令。")
|
|
383
|
+
|
|
384
|
+
# 使用 Pycord 的方法同步指令
|
|
385
|
+
# 注意:这可能需要一些时间,并且有频率限制
|
|
386
|
+
await self.client.sync_commands()
|
|
387
|
+
logger.info("[Discord] 指令同步完成。")
|
|
388
|
+
|
|
389
|
+
def _create_dynamic_callback(self, cmd_name: str):
|
|
390
|
+
"""为每个指令动态创建一个异步回调函数"""
|
|
391
|
+
|
|
392
|
+
async def dynamic_callback(
|
|
393
|
+
ctx: discord.ApplicationContext, params: str | None = None
|
|
394
|
+
):
|
|
395
|
+
# 将平台特定的前缀'/'剥离,以适配通用的CommandFilter
|
|
396
|
+
logger.debug(f"[Discord] 回调函数触发: {cmd_name}")
|
|
397
|
+
logger.debug(f"[Discord] 回调函数参数: {ctx}")
|
|
398
|
+
logger.debug(f"[Discord] 回调函数参数: {params}")
|
|
399
|
+
message_str_for_filter = cmd_name
|
|
400
|
+
if params:
|
|
401
|
+
message_str_for_filter += f" {params}"
|
|
402
|
+
|
|
403
|
+
logger.debug(
|
|
404
|
+
f"[Discord] 斜杠指令 '{cmd_name}' 被触发。 "
|
|
405
|
+
f"原始参数: '{params}'. "
|
|
406
|
+
f"构建的指令字符串: '{message_str_for_filter}'",
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
# 尝试立即响应,防止超时
|
|
410
|
+
followup_webhook = None
|
|
411
|
+
try:
|
|
412
|
+
await ctx.defer()
|
|
413
|
+
followup_webhook = ctx.followup
|
|
414
|
+
except Exception as e:
|
|
415
|
+
logger.warning(f"[Discord] 指令 '{cmd_name}' defer 失败: {e}")
|
|
416
|
+
|
|
417
|
+
# 2. 构建 AstrBotMessage
|
|
418
|
+
abm = AstrBotMessage()
|
|
419
|
+
abm.type = self._get_message_type(ctx.channel, ctx.guild_id)
|
|
420
|
+
abm.group_id = self._get_channel_id(ctx.channel)
|
|
421
|
+
abm.message_str = message_str_for_filter
|
|
422
|
+
abm.sender = MessageMember(
|
|
423
|
+
user_id=str(ctx.author.id),
|
|
424
|
+
nickname=ctx.author.display_name,
|
|
425
|
+
)
|
|
426
|
+
abm.message = [Plain(text=message_str_for_filter)]
|
|
427
|
+
abm.raw_message = ctx.interaction
|
|
428
|
+
abm.self_id = self.client_self_id
|
|
429
|
+
abm.session_id = str(ctx.channel_id)
|
|
430
|
+
abm.message_id = str(ctx.interaction.id)
|
|
431
|
+
|
|
432
|
+
# 3. 将消息和 webhook 分别交给 handle_msg 处理
|
|
433
|
+
await self.handle_msg(abm, followup_webhook)
|
|
434
|
+
|
|
435
|
+
return dynamic_callback
|
|
436
|
+
|
|
437
|
+
@staticmethod
|
|
438
|
+
def _extract_command_info(
|
|
439
|
+
event_filter: Any,
|
|
440
|
+
handler_metadata: StarHandlerMetadata,
|
|
441
|
+
) -> tuple[str, str, CommandFilter] | None:
|
|
442
|
+
"""从事件过滤器中提取指令信息"""
|
|
443
|
+
cmd_name = None
|
|
444
|
+
# is_group = False
|
|
445
|
+
cmd_filter_instance = None
|
|
446
|
+
|
|
447
|
+
if isinstance(event_filter, CommandFilter):
|
|
448
|
+
# 暂不支持子指令注册为斜杠指令
|
|
449
|
+
if (
|
|
450
|
+
event_filter.parent_command_names
|
|
451
|
+
and event_filter.parent_command_names != [""]
|
|
452
|
+
):
|
|
453
|
+
return None
|
|
454
|
+
cmd_name = event_filter.command_name
|
|
455
|
+
cmd_filter_instance = event_filter
|
|
456
|
+
|
|
457
|
+
elif isinstance(event_filter, CommandGroupFilter):
|
|
458
|
+
# 暂不支持指令组直接注册为斜杠指令,因为它们没有 handle 方法
|
|
459
|
+
return None
|
|
460
|
+
|
|
461
|
+
if not cmd_name:
|
|
462
|
+
return None
|
|
463
|
+
|
|
464
|
+
# Discord 斜杠指令名称规范
|
|
465
|
+
if not re.match(r"^[a-z0-9_-]{1,32}$", cmd_name):
|
|
466
|
+
logger.debug(f"[Discord] 跳过不符合规范的指令: {cmd_name}")
|
|
467
|
+
return None
|
|
468
|
+
|
|
469
|
+
description = handler_metadata.desc or f"指令: {cmd_name}"
|
|
470
|
+
if len(description) > 100:
|
|
471
|
+
description = f"{description[:97]}..."
|
|
472
|
+
|
|
473
|
+
return cmd_name, description, cmd_filter_instance
|