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,313 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import base64
|
|
3
|
+
import binascii
|
|
4
|
+
from collections.abc import AsyncGenerator
|
|
5
|
+
from io import BytesIO
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import discord
|
|
9
|
+
|
|
10
|
+
from astrbot import logger
|
|
11
|
+
from astrbot.api.event import AstrMessageEvent, MessageChain
|
|
12
|
+
from astrbot.api.message_components import (
|
|
13
|
+
BaseMessageComponent,
|
|
14
|
+
File,
|
|
15
|
+
Image,
|
|
16
|
+
Plain,
|
|
17
|
+
Reply,
|
|
18
|
+
)
|
|
19
|
+
from astrbot.api.platform import AstrBotMessage, At, PlatformMetadata
|
|
20
|
+
|
|
21
|
+
from .client import DiscordBotClient
|
|
22
|
+
from .components import DiscordEmbed, DiscordView
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# 自定义Discord视图组件(兼容旧版本)
|
|
26
|
+
class DiscordViewComponent(BaseMessageComponent):
|
|
27
|
+
type: str = "discord_view"
|
|
28
|
+
|
|
29
|
+
def __init__(self, view: discord.ui.View):
|
|
30
|
+
self.view = view
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class DiscordPlatformEvent(AstrMessageEvent):
|
|
34
|
+
def __init__(
|
|
35
|
+
self,
|
|
36
|
+
message_str: str,
|
|
37
|
+
message_obj: AstrBotMessage,
|
|
38
|
+
platform_meta: PlatformMetadata,
|
|
39
|
+
session_id: str,
|
|
40
|
+
client: DiscordBotClient,
|
|
41
|
+
interaction_followup_webhook: discord.Webhook | None = None,
|
|
42
|
+
):
|
|
43
|
+
super().__init__(message_str, message_obj, platform_meta, session_id)
|
|
44
|
+
self.client = client
|
|
45
|
+
self.interaction_followup_webhook = interaction_followup_webhook
|
|
46
|
+
|
|
47
|
+
async def send(self, message: MessageChain):
|
|
48
|
+
"""发送消息到Discord平台"""
|
|
49
|
+
# 解析消息链为 Discord 所需的对象
|
|
50
|
+
try:
|
|
51
|
+
(
|
|
52
|
+
content,
|
|
53
|
+
files,
|
|
54
|
+
view,
|
|
55
|
+
embeds,
|
|
56
|
+
reference_message_id,
|
|
57
|
+
) = await self._parse_to_discord(message)
|
|
58
|
+
except Exception as e:
|
|
59
|
+
logger.error(f"[Discord] 解析消息链时失败: {e}", exc_info=True)
|
|
60
|
+
return
|
|
61
|
+
|
|
62
|
+
kwargs = {}
|
|
63
|
+
if content:
|
|
64
|
+
kwargs["content"] = content
|
|
65
|
+
if files:
|
|
66
|
+
kwargs["files"] = files
|
|
67
|
+
if view:
|
|
68
|
+
kwargs["view"] = view
|
|
69
|
+
if embeds:
|
|
70
|
+
kwargs["embeds"] = embeds
|
|
71
|
+
if reference_message_id and not self.interaction_followup_webhook:
|
|
72
|
+
kwargs["reference"] = self.client.get_message(int(reference_message_id))
|
|
73
|
+
if not kwargs:
|
|
74
|
+
logger.debug("[Discord] 尝试发送空消息,已忽略。")
|
|
75
|
+
return
|
|
76
|
+
|
|
77
|
+
# 根据上下文执行发送/回复操作
|
|
78
|
+
try:
|
|
79
|
+
# -- 斜杠指令/交互上下文 --
|
|
80
|
+
if self.interaction_followup_webhook:
|
|
81
|
+
await self.interaction_followup_webhook.send(**kwargs)
|
|
82
|
+
|
|
83
|
+
# -- 常规消息上下文 --
|
|
84
|
+
else:
|
|
85
|
+
channel = await self._get_channel()
|
|
86
|
+
if not channel:
|
|
87
|
+
return
|
|
88
|
+
await channel.send(**kwargs)
|
|
89
|
+
|
|
90
|
+
except Exception as e:
|
|
91
|
+
logger.error(f"[Discord] 发送消息时发生未知错误: {e}", exc_info=True)
|
|
92
|
+
|
|
93
|
+
await super().send(message)
|
|
94
|
+
|
|
95
|
+
async def send_streaming(
|
|
96
|
+
self, generator: AsyncGenerator[MessageChain, None], use_fallback: bool = False
|
|
97
|
+
):
|
|
98
|
+
buffer = None
|
|
99
|
+
async for chain in generator:
|
|
100
|
+
if not buffer:
|
|
101
|
+
buffer = chain
|
|
102
|
+
else:
|
|
103
|
+
buffer.chain.extend(chain.chain)
|
|
104
|
+
if not buffer:
|
|
105
|
+
return None
|
|
106
|
+
buffer.squash_plain()
|
|
107
|
+
await self.send(buffer)
|
|
108
|
+
return await super().send_streaming(generator, use_fallback)
|
|
109
|
+
|
|
110
|
+
async def _get_channel(self) -> discord.abc.Messageable | None:
|
|
111
|
+
"""获取当前事件对应的频道对象"""
|
|
112
|
+
try:
|
|
113
|
+
channel_id = int(self.session_id)
|
|
114
|
+
return self.client.get_channel(
|
|
115
|
+
channel_id,
|
|
116
|
+
) or await self.client.fetch_channel(channel_id)
|
|
117
|
+
except (ValueError, discord.errors.NotFound, discord.errors.Forbidden):
|
|
118
|
+
logger.error(f"[Discord] 无法获取频道 {self.session_id}")
|
|
119
|
+
return None
|
|
120
|
+
|
|
121
|
+
async def _parse_to_discord(
|
|
122
|
+
self,
|
|
123
|
+
message: MessageChain,
|
|
124
|
+
) -> tuple[str, list[discord.File], discord.ui.View | None, list[discord.Embed]]:
|
|
125
|
+
"""将 MessageChain 解析为 Discord 发送所需的内容"""
|
|
126
|
+
content_parts = []
|
|
127
|
+
files = []
|
|
128
|
+
view = None
|
|
129
|
+
embeds = []
|
|
130
|
+
reference_message_id = None
|
|
131
|
+
for i in message.chain: # 遍历消息链
|
|
132
|
+
if isinstance(i, Plain): # 如果是文字类型的
|
|
133
|
+
content_parts.append(i.text)
|
|
134
|
+
elif isinstance(i, Reply):
|
|
135
|
+
reference_message_id = i.id
|
|
136
|
+
elif isinstance(i, At):
|
|
137
|
+
content_parts.append(f"<@{i.qq}>")
|
|
138
|
+
elif isinstance(i, Image):
|
|
139
|
+
logger.debug(f"[Discord] 开始处理 Image 组件: {i}")
|
|
140
|
+
try:
|
|
141
|
+
filename = getattr(i, "filename", None)
|
|
142
|
+
file_content = getattr(i, "file", None)
|
|
143
|
+
|
|
144
|
+
if not file_content:
|
|
145
|
+
logger.warning(f"[Discord] Image 组件没有 file 属性: {i}")
|
|
146
|
+
continue
|
|
147
|
+
|
|
148
|
+
discord_file = None
|
|
149
|
+
|
|
150
|
+
# 1. URL
|
|
151
|
+
if file_content.startswith("http"):
|
|
152
|
+
logger.debug(f"[Discord] 处理 URL 图片: {file_content}")
|
|
153
|
+
embed = discord.Embed().set_image(url=file_content)
|
|
154
|
+
embeds.append(embed)
|
|
155
|
+
continue
|
|
156
|
+
|
|
157
|
+
# 2. File URI
|
|
158
|
+
if file_content.startswith("file:///"):
|
|
159
|
+
logger.debug(f"[Discord] 处理 File URI: {file_content}")
|
|
160
|
+
path = Path(file_content[8:])
|
|
161
|
+
if await asyncio.to_thread(path.exists):
|
|
162
|
+
file_bytes = await asyncio.to_thread(path.read_bytes)
|
|
163
|
+
discord_file = discord.File(
|
|
164
|
+
BytesIO(file_bytes),
|
|
165
|
+
filename=filename or path.name,
|
|
166
|
+
)
|
|
167
|
+
else:
|
|
168
|
+
logger.warning(f"[Discord] 图片文件不存在: {path}")
|
|
169
|
+
|
|
170
|
+
# 3. Base64 URI
|
|
171
|
+
elif file_content.startswith("base64://"):
|
|
172
|
+
logger.debug("[Discord] 处理 Base64 URI")
|
|
173
|
+
b64_data = file_content.split("base64://", 1)[1]
|
|
174
|
+
missing_padding = len(b64_data) % 4
|
|
175
|
+
if missing_padding:
|
|
176
|
+
b64_data += "=" * (4 - missing_padding)
|
|
177
|
+
img_bytes = base64.b64decode(b64_data)
|
|
178
|
+
discord_file = discord.File(
|
|
179
|
+
BytesIO(img_bytes),
|
|
180
|
+
filename=filename or "image.png",
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
# 4. 裸 Base64 或本地路径
|
|
184
|
+
else:
|
|
185
|
+
try:
|
|
186
|
+
logger.debug("[Discord] 尝试作为裸 Base64 处理")
|
|
187
|
+
b64_data = file_content
|
|
188
|
+
missing_padding = len(b64_data) % 4
|
|
189
|
+
if missing_padding:
|
|
190
|
+
b64_data += "=" * (4 - missing_padding)
|
|
191
|
+
img_bytes = base64.b64decode(b64_data)
|
|
192
|
+
discord_file = discord.File(
|
|
193
|
+
BytesIO(img_bytes),
|
|
194
|
+
filename=filename or "image.png",
|
|
195
|
+
)
|
|
196
|
+
except (ValueError, TypeError, binascii.Error):
|
|
197
|
+
logger.debug(
|
|
198
|
+
f"[Discord] 裸 Base64 解码失败,作为本地路径处理: {file_content}",
|
|
199
|
+
)
|
|
200
|
+
path = Path(file_content)
|
|
201
|
+
if await asyncio.to_thread(path.exists):
|
|
202
|
+
file_bytes = await asyncio.to_thread(path.read_bytes)
|
|
203
|
+
discord_file = discord.File(
|
|
204
|
+
BytesIO(file_bytes),
|
|
205
|
+
filename=filename or path.name,
|
|
206
|
+
)
|
|
207
|
+
else:
|
|
208
|
+
logger.warning(f"[Discord] 图片文件不存在: {path}")
|
|
209
|
+
|
|
210
|
+
if discord_file:
|
|
211
|
+
files.append(discord_file)
|
|
212
|
+
|
|
213
|
+
except Exception:
|
|
214
|
+
# 使用 getattr 来安全地访问 i.file,以防 i 本身就是问题
|
|
215
|
+
file_info = getattr(i, "file", "未知")
|
|
216
|
+
logger.error(
|
|
217
|
+
f"[Discord] 处理图片时发生未知严重错误: {file_info}",
|
|
218
|
+
exc_info=True,
|
|
219
|
+
)
|
|
220
|
+
elif isinstance(i, File):
|
|
221
|
+
try:
|
|
222
|
+
file_path_str = await i.get_file()
|
|
223
|
+
if file_path_str:
|
|
224
|
+
path = Path(file_path_str)
|
|
225
|
+
if await asyncio.to_thread(path.exists):
|
|
226
|
+
file_bytes = await asyncio.to_thread(path.read_bytes)
|
|
227
|
+
files.append(
|
|
228
|
+
discord.File(BytesIO(file_bytes), filename=i.name),
|
|
229
|
+
)
|
|
230
|
+
else:
|
|
231
|
+
logger.warning(
|
|
232
|
+
f"[Discord] 获取文件失败,路径不存在: {file_path_str}",
|
|
233
|
+
)
|
|
234
|
+
else:
|
|
235
|
+
logger.warning(f"[Discord] 获取文件失败: {i.name}")
|
|
236
|
+
except Exception as e:
|
|
237
|
+
logger.warning(f"[Discord] 处理文件失败: {i.name}, 错误: {e}")
|
|
238
|
+
elif isinstance(i, DiscordEmbed):
|
|
239
|
+
# Discord Embed消息
|
|
240
|
+
embeds.append(i.to_discord_embed())
|
|
241
|
+
elif isinstance(i, DiscordView):
|
|
242
|
+
# Discord视图组件(按钮、选择菜单等)
|
|
243
|
+
view = i.to_discord_view()
|
|
244
|
+
elif isinstance(i, DiscordViewComponent):
|
|
245
|
+
# 如果消息链中包含Discord视图组件(兼容旧版本)
|
|
246
|
+
if isinstance(i.view, discord.ui.View):
|
|
247
|
+
view = i.view
|
|
248
|
+
else:
|
|
249
|
+
logger.debug(f"[Discord] 忽略了不支持的消息组件: {i.type}")
|
|
250
|
+
|
|
251
|
+
content = "".join(content_parts)
|
|
252
|
+
if len(content) > 2000:
|
|
253
|
+
logger.warning("[Discord] 消息内容超过2000字符,将被截断。")
|
|
254
|
+
content = content[:2000]
|
|
255
|
+
return content, files, view, embeds, reference_message_id
|
|
256
|
+
|
|
257
|
+
async def react(self, emoji: str):
|
|
258
|
+
"""对原消息添加反应"""
|
|
259
|
+
try:
|
|
260
|
+
if hasattr(self.message_obj, "raw_message") and hasattr(
|
|
261
|
+
self.message_obj.raw_message,
|
|
262
|
+
"add_reaction",
|
|
263
|
+
):
|
|
264
|
+
await self.message_obj.raw_message.add_reaction(emoji)
|
|
265
|
+
except Exception as e:
|
|
266
|
+
logger.error(f"[Discord] 添加反应失败: {e}")
|
|
267
|
+
|
|
268
|
+
def is_slash_command(self) -> bool:
|
|
269
|
+
"""判断是否为斜杠命令"""
|
|
270
|
+
return (
|
|
271
|
+
hasattr(self.message_obj, "raw_message")
|
|
272
|
+
and hasattr(self.message_obj.raw_message, "type")
|
|
273
|
+
and self.message_obj.raw_message.type
|
|
274
|
+
== discord.InteractionType.application_command
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
def is_button_interaction(self) -> bool:
|
|
278
|
+
"""判断是否为按钮交互"""
|
|
279
|
+
return (
|
|
280
|
+
hasattr(self.message_obj, "raw_message")
|
|
281
|
+
and hasattr(self.message_obj.raw_message, "type")
|
|
282
|
+
and self.message_obj.raw_message.type == discord.InteractionType.component
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
def get_interaction_custom_id(self) -> str:
|
|
286
|
+
"""获取交互组件的custom_id"""
|
|
287
|
+
if self.is_button_interaction():
|
|
288
|
+
try:
|
|
289
|
+
return self.message_obj.raw_message.data.get("custom_id", "")
|
|
290
|
+
except Exception:
|
|
291
|
+
pass
|
|
292
|
+
return ""
|
|
293
|
+
|
|
294
|
+
def is_mentioned(self) -> bool:
|
|
295
|
+
"""判断机器人是否被@"""
|
|
296
|
+
if hasattr(self.message_obj, "raw_message") and hasattr(
|
|
297
|
+
self.message_obj.raw_message,
|
|
298
|
+
"mentions",
|
|
299
|
+
):
|
|
300
|
+
return any(
|
|
301
|
+
mention.id == int(self.message_obj.self_id)
|
|
302
|
+
for mention in self.message_obj.raw_message.mentions
|
|
303
|
+
)
|
|
304
|
+
return False
|
|
305
|
+
|
|
306
|
+
def get_mention_clean_content(self) -> str:
|
|
307
|
+
"""获取去除@后的清洁内容"""
|
|
308
|
+
if hasattr(self.message_obj, "raw_message") and hasattr(
|
|
309
|
+
self.message_obj.raw_message,
|
|
310
|
+
"clean_content",
|
|
311
|
+
):
|
|
312
|
+
return self.message_obj.raw_message.clean_content
|
|
313
|
+
return self.message_str
|
|
@@ -1,30 +1,37 @@
|
|
|
1
|
-
import base64
|
|
2
1
|
import asyncio
|
|
2
|
+
import base64
|
|
3
3
|
import json
|
|
4
4
|
import re
|
|
5
5
|
import uuid
|
|
6
|
-
import astrbot.api.message_components as Comp
|
|
7
6
|
|
|
7
|
+
import lark_oapi as lark
|
|
8
|
+
from lark_oapi.api.im.v1 import *
|
|
9
|
+
|
|
10
|
+
import astrbot.api.message_components as Comp
|
|
11
|
+
from astrbot import logger
|
|
12
|
+
from astrbot.api.event import MessageChain
|
|
8
13
|
from astrbot.api.platform import (
|
|
9
|
-
Platform,
|
|
10
14
|
AstrBotMessage,
|
|
11
15
|
MessageMember,
|
|
12
16
|
MessageType,
|
|
17
|
+
Platform,
|
|
13
18
|
PlatformMetadata,
|
|
14
19
|
)
|
|
15
|
-
from astrbot.api.event import MessageChain
|
|
16
20
|
from astrbot.core.platform.astr_message_event import MessageSesion
|
|
17
|
-
|
|
21
|
+
|
|
18
22
|
from ...register import register_platform_adapter
|
|
19
|
-
from
|
|
20
|
-
import lark_oapi as lark
|
|
21
|
-
from lark_oapi.api.im.v1 import *
|
|
23
|
+
from .lark_event import LarkMessageEvent
|
|
22
24
|
|
|
23
25
|
|
|
24
|
-
@register_platform_adapter(
|
|
26
|
+
@register_platform_adapter(
|
|
27
|
+
"lark", "飞书机器人官方 API 适配器", support_streaming_message=False
|
|
28
|
+
)
|
|
25
29
|
class LarkPlatformAdapter(Platform):
|
|
26
30
|
def __init__(
|
|
27
|
-
self,
|
|
31
|
+
self,
|
|
32
|
+
platform_config: dict,
|
|
33
|
+
platform_settings: dict,
|
|
34
|
+
event_queue: asyncio.Queue,
|
|
28
35
|
) -> None:
|
|
29
36
|
super().__init__(event_queue)
|
|
30
37
|
|
|
@@ -65,14 +72,16 @@ class LarkPlatformAdapter(Platform):
|
|
|
65
72
|
)
|
|
66
73
|
|
|
67
74
|
async def send_by_session(
|
|
68
|
-
self,
|
|
75
|
+
self,
|
|
76
|
+
session: MessageSesion,
|
|
77
|
+
message_chain: MessageChain,
|
|
69
78
|
):
|
|
70
79
|
res = await LarkMessageEvent._convert_to_lark(message_chain, self.lark_api)
|
|
71
80
|
wrapped = {
|
|
72
81
|
"zh_cn": {
|
|
73
82
|
"title": "",
|
|
74
83
|
"content": res,
|
|
75
|
-
}
|
|
84
|
+
},
|
|
76
85
|
}
|
|
77
86
|
|
|
78
87
|
if session.message_type == MessageType.GROUP_MESSAGE:
|
|
@@ -91,7 +100,7 @@ class LarkPlatformAdapter(Platform):
|
|
|
91
100
|
.content(json.dumps(wrapped))
|
|
92
101
|
.msg_type("post")
|
|
93
102
|
.uuid(str(uuid.uuid4()))
|
|
94
|
-
.build()
|
|
103
|
+
.build(),
|
|
95
104
|
)
|
|
96
105
|
.build()
|
|
97
106
|
)
|
|
@@ -108,6 +117,7 @@ class LarkPlatformAdapter(Platform):
|
|
|
108
117
|
name="lark",
|
|
109
118
|
description="飞书机器人官方 API 适配器",
|
|
110
119
|
id=self.config.get("id"),
|
|
120
|
+
support_streaming_message=False,
|
|
111
121
|
)
|
|
112
122
|
|
|
113
123
|
async def convert_msg(self, event: lark.im.v1.P2ImMessageReceiveV1):
|
|
@@ -160,7 +170,7 @@ class LarkPlatformAdapter(Platform):
|
|
|
160
170
|
content_json_b = _ls
|
|
161
171
|
elif message.message_type == "image":
|
|
162
172
|
content_json_b = [
|
|
163
|
-
{"tag": "img", "image_key": content_json_b["image_key"], "style": []}
|
|
173
|
+
{"tag": "img", "image_key": content_json_b["image_key"], "style": []},
|
|
164
174
|
]
|
|
165
175
|
|
|
166
176
|
if message.message_type in ("post", "image"):
|
|
@@ -200,11 +210,10 @@ class LarkPlatformAdapter(Platform):
|
|
|
200
210
|
abm.session_id = abm.group_id
|
|
201
211
|
else:
|
|
202
212
|
abm.session_id = abm.sender.user_id
|
|
213
|
+
elif abm.type == MessageType.GROUP_MESSAGE:
|
|
214
|
+
abm.session_id = f"{abm.sender.user_id}%{abm.group_id}" # 也保留群组id
|
|
203
215
|
else:
|
|
204
|
-
|
|
205
|
-
abm.session_id = f"{abm.sender.user_id}%{abm.group_id}" # 也保留群组id
|
|
206
|
-
else:
|
|
207
|
-
abm.session_id = abm.sender.user_id
|
|
216
|
+
abm.session_id = abm.sender.user_id
|
|
208
217
|
|
|
209
218
|
logger.debug(abm)
|
|
210
219
|
await self.handle_msg(abm)
|
|
@@ -1,25 +1,34 @@
|
|
|
1
|
+
import base64
|
|
1
2
|
import json
|
|
3
|
+
import os
|
|
2
4
|
import uuid
|
|
3
|
-
import base64
|
|
4
|
-
import lark_oapi as lark
|
|
5
5
|
from io import BytesIO
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
from astrbot.api.message_components import Plain, Image as AstrBotImage, At
|
|
9
|
-
from astrbot.core.utils.io import download_image_by_url
|
|
6
|
+
|
|
7
|
+
import lark_oapi as lark
|
|
10
8
|
from lark_oapi.api.im.v1 import *
|
|
9
|
+
|
|
11
10
|
from astrbot import logger
|
|
11
|
+
from astrbot.api.event import AstrMessageEvent, MessageChain
|
|
12
|
+
from astrbot.api.message_components import At, Plain
|
|
13
|
+
from astrbot.api.message_components import Image as AstrBotImage
|
|
14
|
+
from astrbot.core.utils.astrbot_path import get_astrbot_data_path
|
|
15
|
+
from astrbot.core.utils.io import download_image_by_url
|
|
12
16
|
|
|
13
17
|
|
|
14
18
|
class LarkMessageEvent(AstrMessageEvent):
|
|
15
19
|
def __init__(
|
|
16
|
-
self,
|
|
20
|
+
self,
|
|
21
|
+
message_str,
|
|
22
|
+
message_obj,
|
|
23
|
+
platform_meta,
|
|
24
|
+
session_id,
|
|
25
|
+
bot: lark.Client,
|
|
17
26
|
):
|
|
18
27
|
super().__init__(message_str, message_obj, platform_meta, session_id)
|
|
19
28
|
self.bot = bot
|
|
20
29
|
|
|
21
30
|
@staticmethod
|
|
22
|
-
async def _convert_to_lark(message: MessageChain, lark_client: lark.Client) ->
|
|
31
|
+
async def _convert_to_lark(message: MessageChain, lark_client: lark.Client) -> list:
|
|
23
32
|
ret = []
|
|
24
33
|
_stage = []
|
|
25
34
|
for comp in message.chain:
|
|
@@ -40,7 +49,8 @@ class LarkMessageEvent(AstrMessageEvent):
|
|
|
40
49
|
base64_str = comp.file.removeprefix("base64://")
|
|
41
50
|
image_data = base64.b64decode(base64_str)
|
|
42
51
|
# save as temp file
|
|
43
|
-
|
|
52
|
+
temp_dir = os.path.join(get_astrbot_data_path(), "temp")
|
|
53
|
+
file_path = os.path.join(temp_dir, f"{uuid.uuid4()}_test.jpg")
|
|
44
54
|
with open(file_path, "wb") as f:
|
|
45
55
|
f.write(BytesIO(image_data).getvalue())
|
|
46
56
|
else:
|
|
@@ -55,7 +65,7 @@ class LarkMessageEvent(AstrMessageEvent):
|
|
|
55
65
|
CreateImageRequestBody.builder()
|
|
56
66
|
.image_type("message")
|
|
57
67
|
.image(image_file)
|
|
58
|
-
.build()
|
|
68
|
+
.build(),
|
|
59
69
|
)
|
|
60
70
|
.build()
|
|
61
71
|
)
|
|
@@ -80,7 +90,7 @@ class LarkMessageEvent(AstrMessageEvent):
|
|
|
80
90
|
"zh_cn": {
|
|
81
91
|
"title": "",
|
|
82
92
|
"content": res,
|
|
83
|
-
}
|
|
93
|
+
},
|
|
84
94
|
}
|
|
85
95
|
|
|
86
96
|
request = (
|
|
@@ -92,7 +102,7 @@ class LarkMessageEvent(AstrMessageEvent):
|
|
|
92
102
|
.msg_type("post")
|
|
93
103
|
.uuid(str(uuid.uuid4()))
|
|
94
104
|
.reply_in_thread(False)
|
|
95
|
-
.build()
|
|
105
|
+
.build(),
|
|
96
106
|
)
|
|
97
107
|
.build()
|
|
98
108
|
)
|
|
@@ -104,6 +114,22 @@ class LarkMessageEvent(AstrMessageEvent):
|
|
|
104
114
|
|
|
105
115
|
await super().send(message)
|
|
106
116
|
|
|
117
|
+
async def react(self, emoji: str):
|
|
118
|
+
request = (
|
|
119
|
+
CreateMessageReactionRequest.builder()
|
|
120
|
+
.message_id(self.message_obj.message_id)
|
|
121
|
+
.request_body(
|
|
122
|
+
CreateMessageReactionRequestBody.builder()
|
|
123
|
+
.reaction_type(Emoji.builder().emoji_type(emoji).build())
|
|
124
|
+
.build(),
|
|
125
|
+
)
|
|
126
|
+
.build()
|
|
127
|
+
)
|
|
128
|
+
response = await self.bot.im.v1.message_reaction.acreate(request)
|
|
129
|
+
if not response.success():
|
|
130
|
+
logger.error(f"发送飞书表情回应失败({response.code}): {response.msg}")
|
|
131
|
+
return
|
|
132
|
+
|
|
107
133
|
async def send_streaming(self, generator, use_fallback: bool = False):
|
|
108
134
|
buffer = None
|
|
109
135
|
async for chain in generator:
|
|
@@ -112,7 +138,7 @@ class LarkMessageEvent(AstrMessageEvent):
|
|
|
112
138
|
else:
|
|
113
139
|
buffer.chain.extend(chain.chain)
|
|
114
140
|
if not buffer:
|
|
115
|
-
return
|
|
141
|
+
return None
|
|
116
142
|
buffer.squash_plain()
|
|
117
143
|
await self.send(buffer)
|
|
118
144
|
return await super().send_streaming(generator, use_fallback)
|