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
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
"""
|
|
2
|
-
MIT License
|
|
1
|
+
"""MIT License
|
|
3
2
|
|
|
4
3
|
Copyright (c) 2021 Lxns-Network
|
|
5
4
|
|
|
@@ -22,73 +21,51 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
|
22
21
|
SOFTWARE.
|
|
23
22
|
"""
|
|
24
23
|
|
|
24
|
+
import asyncio
|
|
25
25
|
import base64
|
|
26
26
|
import json
|
|
27
27
|
import os
|
|
28
28
|
import uuid
|
|
29
|
-
import asyncio
|
|
30
|
-
import typing as T
|
|
31
29
|
from enum import Enum
|
|
30
|
+
|
|
32
31
|
from pydantic.v1 import BaseModel
|
|
33
|
-
from astrbot.core import logger
|
|
34
|
-
from astrbot.core.utils.io import download_image_by_url, file_to_base64, download_file
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
class ComponentType(Enum):
|
|
38
|
-
Plain = "Plain" # 纯文本消息
|
|
39
|
-
Face = "Face" # QQ表情
|
|
40
|
-
Record = "Record" # 语音
|
|
41
|
-
Video = "Video" # 视频
|
|
42
|
-
At = "At" # At
|
|
43
|
-
Node = "Node" # 转发消息的一个节点
|
|
44
|
-
Nodes = "Nodes" # 转发消息的多个节点
|
|
45
|
-
Poke = "Poke" # QQ 戳一戳
|
|
46
|
-
Image = "Image" # 图片
|
|
47
|
-
Reply = "Reply" # 回复
|
|
48
|
-
Forward = "Forward" # 转发消息
|
|
49
|
-
File = "File" # 文件
|
|
50
32
|
|
|
33
|
+
from astrbot.core import astrbot_config, file_token_service, logger
|
|
34
|
+
from astrbot.core.utils.astrbot_path import get_astrbot_data_path
|
|
35
|
+
from astrbot.core.utils.io import download_file, download_image_by_url, file_to_base64
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class ComponentType(str, Enum):
|
|
39
|
+
# Basic Segment Types
|
|
40
|
+
Plain = "Plain" # plain text message
|
|
41
|
+
Image = "Image" # image
|
|
42
|
+
Record = "Record" # audio
|
|
43
|
+
Video = "Video" # video
|
|
44
|
+
File = "File" # file attachment
|
|
45
|
+
|
|
46
|
+
# IM-specific Segment Types
|
|
47
|
+
Face = "Face" # Emoji segment for Tencent QQ platform
|
|
48
|
+
At = "At" # mention a user in IM apps
|
|
49
|
+
Node = "Node" # a node in a forwarded message
|
|
50
|
+
Nodes = "Nodes" # a forwarded message consisting of multiple nodes
|
|
51
|
+
Poke = "Poke" # a poke message for Tencent QQ platform
|
|
52
|
+
Reply = "Reply" # a reply message segment
|
|
53
|
+
Forward = "Forward" # a forwarded message segment
|
|
51
54
|
RPS = "RPS" # TODO
|
|
52
55
|
Dice = "Dice" # TODO
|
|
53
56
|
Shake = "Shake" # TODO
|
|
54
|
-
Anonymous = "Anonymous" # TODO
|
|
55
57
|
Share = "Share"
|
|
56
58
|
Contact = "Contact" # TODO
|
|
57
59
|
Location = "Location" # TODO
|
|
58
60
|
Music = "Music"
|
|
59
|
-
RedBag = "RedBag"
|
|
60
|
-
Xml = "Xml"
|
|
61
61
|
Json = "Json"
|
|
62
|
-
CardImage = "CardImage"
|
|
63
|
-
TTS = "TTS"
|
|
64
62
|
Unknown = "Unknown"
|
|
65
|
-
|
|
66
63
|
WechatEmoji = "WechatEmoji" # Wechat 下的 emoji 表情包
|
|
67
64
|
|
|
68
65
|
|
|
69
66
|
class BaseMessageComponent(BaseModel):
|
|
70
67
|
type: ComponentType
|
|
71
68
|
|
|
72
|
-
def toString(self):
|
|
73
|
-
output = f"[CQ:{self.type.lower()}"
|
|
74
|
-
for k, v in self.__dict__.items():
|
|
75
|
-
if k == "type" or v is None:
|
|
76
|
-
continue
|
|
77
|
-
if k == "_type":
|
|
78
|
-
k = "type"
|
|
79
|
-
if isinstance(v, bool):
|
|
80
|
-
v = 1 if v else 0
|
|
81
|
-
output += ",%s=%s" % (
|
|
82
|
-
k,
|
|
83
|
-
str(v)
|
|
84
|
-
.replace("&", "&")
|
|
85
|
-
.replace(",", ",")
|
|
86
|
-
.replace("[", "[")
|
|
87
|
-
.replace("]", "]"),
|
|
88
|
-
)
|
|
89
|
-
output += "]"
|
|
90
|
-
return output
|
|
91
|
-
|
|
92
69
|
def toDict(self):
|
|
93
70
|
data = {}
|
|
94
71
|
for k, v in self.__dict__.items():
|
|
@@ -99,25 +76,28 @@ class BaseMessageComponent(BaseModel):
|
|
|
99
76
|
data[k] = v
|
|
100
77
|
return {"type": self.type.lower(), "data": data}
|
|
101
78
|
|
|
79
|
+
async def to_dict(self) -> dict:
|
|
80
|
+
# 默认情况下,回退到旧的同步 toDict()
|
|
81
|
+
return self.toDict()
|
|
82
|
+
|
|
102
83
|
|
|
103
84
|
class Plain(BaseMessageComponent):
|
|
104
|
-
type
|
|
85
|
+
type = ComponentType.Plain
|
|
105
86
|
text: str
|
|
106
|
-
convert:
|
|
87
|
+
convert: bool | None = True
|
|
107
88
|
|
|
108
89
|
def __init__(self, text: str, convert: bool = True, **_):
|
|
109
90
|
super().__init__(text=text, convert=convert, **_)
|
|
110
91
|
|
|
111
|
-
def
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
)
|
|
92
|
+
def toDict(self):
|
|
93
|
+
return {"type": "text", "data": {"text": self.text.strip()}}
|
|
94
|
+
|
|
95
|
+
async def to_dict(self):
|
|
96
|
+
return {"type": "text", "data": {"text": self.text}}
|
|
117
97
|
|
|
118
98
|
|
|
119
99
|
class Face(BaseMessageComponent):
|
|
120
|
-
type
|
|
100
|
+
type = ComponentType.Face
|
|
121
101
|
id: int
|
|
122
102
|
|
|
123
103
|
def __init__(self, **_):
|
|
@@ -125,18 +105,18 @@ class Face(BaseMessageComponent):
|
|
|
125
105
|
|
|
126
106
|
|
|
127
107
|
class Record(BaseMessageComponent):
|
|
128
|
-
type
|
|
129
|
-
file:
|
|
130
|
-
magic:
|
|
131
|
-
url:
|
|
132
|
-
cache:
|
|
133
|
-
proxy:
|
|
134
|
-
timeout:
|
|
108
|
+
type = ComponentType.Record
|
|
109
|
+
file: str | None = ""
|
|
110
|
+
magic: bool | None = False
|
|
111
|
+
url: str | None = ""
|
|
112
|
+
cache: bool | None = True
|
|
113
|
+
proxy: bool | None = True
|
|
114
|
+
timeout: int | None = 0
|
|
135
115
|
# 额外
|
|
136
|
-
path:
|
|
116
|
+
path: str | None
|
|
137
117
|
|
|
138
|
-
def __init__(self, file:
|
|
139
|
-
for k in _
|
|
118
|
+
def __init__(self, file: str | None, **_):
|
|
119
|
+
for k in _:
|
|
140
120
|
if k == "url":
|
|
141
121
|
pass
|
|
142
122
|
# Protocol.warn(f"go-cqhttp doesn't support send {self.type} by {k}")
|
|
@@ -152,44 +132,52 @@ class Record(BaseMessageComponent):
|
|
|
152
132
|
return Record(file=url, **_)
|
|
153
133
|
raise Exception("not a valid url")
|
|
154
134
|
|
|
135
|
+
@staticmethod
|
|
136
|
+
def fromBase64(bs64_data: str, **_):
|
|
137
|
+
return Record(file=f"base64://{bs64_data}", **_)
|
|
138
|
+
|
|
155
139
|
async def convert_to_file_path(self) -> str:
|
|
156
140
|
"""将这个语音统一转换为本地文件路径。这个方法避免了手动判断语音数据类型,直接返回语音数据的本地路径(如果是网络 URL, 则会自动进行下载)。
|
|
157
141
|
|
|
158
142
|
Returns:
|
|
159
143
|
str: 语音的本地路径,以绝对路径表示。
|
|
144
|
+
|
|
160
145
|
"""
|
|
161
|
-
if
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
146
|
+
if not self.file:
|
|
147
|
+
raise Exception(f"not a valid file: {self.file}")
|
|
148
|
+
if self.file.startswith("file:///"):
|
|
149
|
+
return self.file[8:]
|
|
150
|
+
if self.file.startswith("http"):
|
|
165
151
|
file_path = await download_image_by_url(self.file)
|
|
166
152
|
return os.path.abspath(file_path)
|
|
167
|
-
|
|
153
|
+
if self.file.startswith("base64://"):
|
|
168
154
|
bs64_data = self.file.removeprefix("base64://")
|
|
169
155
|
image_bytes = base64.b64decode(bs64_data)
|
|
170
|
-
|
|
156
|
+
temp_dir = os.path.join(get_astrbot_data_path(), "temp")
|
|
157
|
+
file_path = os.path.join(temp_dir, f"{uuid.uuid4()}.jpg")
|
|
171
158
|
with open(file_path, "wb") as f:
|
|
172
159
|
f.write(image_bytes)
|
|
173
160
|
return os.path.abspath(file_path)
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
else:
|
|
178
|
-
raise Exception(f"not a valid file: {self.file}")
|
|
161
|
+
if os.path.exists(self.file):
|
|
162
|
+
return os.path.abspath(self.file)
|
|
163
|
+
raise Exception(f"not a valid file: {self.file}")
|
|
179
164
|
|
|
180
165
|
async def convert_to_base64(self) -> str:
|
|
181
166
|
"""将语音统一转换为 base64 编码。这个方法避免了手动判断语音数据类型,直接返回语音数据的 base64 编码。
|
|
182
167
|
|
|
183
168
|
Returns:
|
|
184
169
|
str: 语音的 base64 编码,不以 base64:// 或者 data:image/jpeg;base64, 开头。
|
|
170
|
+
|
|
185
171
|
"""
|
|
186
172
|
# convert to base64
|
|
187
|
-
if
|
|
173
|
+
if not self.file:
|
|
174
|
+
raise Exception(f"not a valid file: {self.file}")
|
|
175
|
+
if self.file.startswith("file:///"):
|
|
188
176
|
bs64_data = file_to_base64(self.file[8:])
|
|
189
|
-
elif self.file
|
|
177
|
+
elif self.file.startswith("http"):
|
|
190
178
|
file_path = await download_image_by_url(self.file)
|
|
191
179
|
bs64_data = file_to_base64(file_path)
|
|
192
|
-
elif self.file
|
|
180
|
+
elif self.file.startswith("base64://"):
|
|
193
181
|
bs64_data = self.file
|
|
194
182
|
elif os.path.exists(self.file):
|
|
195
183
|
bs64_data = file_to_base64(self.file)
|
|
@@ -198,19 +186,39 @@ class Record(BaseMessageComponent):
|
|
|
198
186
|
bs64_data = bs64_data.removeprefix("base64://")
|
|
199
187
|
return bs64_data
|
|
200
188
|
|
|
189
|
+
async def register_to_file_service(self) -> str:
|
|
190
|
+
"""将语音注册到文件服务。
|
|
191
|
+
|
|
192
|
+
Returns:
|
|
193
|
+
str: 注册后的URL
|
|
194
|
+
|
|
195
|
+
Raises:
|
|
196
|
+
Exception: 如果未配置 callback_api_base
|
|
197
|
+
|
|
198
|
+
"""
|
|
199
|
+
callback_host = astrbot_config.get("callback_api_base")
|
|
200
|
+
|
|
201
|
+
if not callback_host:
|
|
202
|
+
raise Exception("未配置 callback_api_base,文件服务不可用")
|
|
203
|
+
|
|
204
|
+
file_path = await self.convert_to_file_path()
|
|
205
|
+
|
|
206
|
+
token = await file_token_service.register_file(file_path)
|
|
207
|
+
|
|
208
|
+
logger.debug(f"已注册:{callback_host}/api/file/{token}")
|
|
209
|
+
|
|
210
|
+
return f"{callback_host}/api/file/{token}"
|
|
211
|
+
|
|
201
212
|
|
|
202
213
|
class Video(BaseMessageComponent):
|
|
203
|
-
type
|
|
214
|
+
type = ComponentType.Video
|
|
204
215
|
file: str
|
|
205
|
-
cover:
|
|
206
|
-
c:
|
|
216
|
+
cover: str | None = ""
|
|
217
|
+
c: int | None = 2
|
|
207
218
|
# 额外
|
|
208
|
-
path:
|
|
219
|
+
path: str | None = ""
|
|
209
220
|
|
|
210
221
|
def __init__(self, file: str, **_):
|
|
211
|
-
# for k in _.keys():
|
|
212
|
-
# if k == "c" and _[k] not in [2, 3]:
|
|
213
|
-
# logger.warn(f"Protocol: {k}={_[k]} doesn't match values")
|
|
214
222
|
super().__init__(file=file, **_)
|
|
215
223
|
|
|
216
224
|
@staticmethod
|
|
@@ -223,15 +231,84 @@ class Video(BaseMessageComponent):
|
|
|
223
231
|
return Video(file=url, **_)
|
|
224
232
|
raise Exception("not a valid url")
|
|
225
233
|
|
|
234
|
+
async def convert_to_file_path(self) -> str:
|
|
235
|
+
"""将这个视频统一转换为本地文件路径。这个方法避免了手动判断视频数据类型,直接返回视频数据的本地路径(如果是网络 URL,则会自动进行下载)。
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
str: 视频的本地路径,以绝对路径表示。
|
|
239
|
+
|
|
240
|
+
"""
|
|
241
|
+
url = self.file
|
|
242
|
+
if url and url.startswith("file:///"):
|
|
243
|
+
return url[8:]
|
|
244
|
+
if url and url.startswith("http"):
|
|
245
|
+
download_dir = os.path.join(get_astrbot_data_path(), "temp")
|
|
246
|
+
video_file_path = os.path.join(download_dir, f"{uuid.uuid4().hex}")
|
|
247
|
+
await download_file(url, video_file_path)
|
|
248
|
+
if os.path.exists(video_file_path):
|
|
249
|
+
return os.path.abspath(video_file_path)
|
|
250
|
+
raise Exception(f"download failed: {url}")
|
|
251
|
+
if os.path.exists(url):
|
|
252
|
+
return os.path.abspath(url)
|
|
253
|
+
raise Exception(f"not a valid file: {url}")
|
|
254
|
+
|
|
255
|
+
async def register_to_file_service(self):
|
|
256
|
+
"""将视频注册到文件服务。
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
str: 注册后的URL
|
|
260
|
+
|
|
261
|
+
Raises:
|
|
262
|
+
Exception: 如果未配置 callback_api_base
|
|
263
|
+
|
|
264
|
+
"""
|
|
265
|
+
callback_host = astrbot_config.get("callback_api_base")
|
|
266
|
+
|
|
267
|
+
if not callback_host:
|
|
268
|
+
raise Exception("未配置 callback_api_base,文件服务不可用")
|
|
269
|
+
|
|
270
|
+
file_path = await self.convert_to_file_path()
|
|
271
|
+
|
|
272
|
+
token = await file_token_service.register_file(file_path)
|
|
273
|
+
|
|
274
|
+
logger.debug(f"已注册:{callback_host}/api/file/{token}")
|
|
275
|
+
|
|
276
|
+
return f"{callback_host}/api/file/{token}"
|
|
277
|
+
|
|
278
|
+
async def to_dict(self):
|
|
279
|
+
"""需要和 toDict 区分开,toDict 是同步方法"""
|
|
280
|
+
url_or_path = self.file
|
|
281
|
+
if url_or_path.startswith("http"):
|
|
282
|
+
payload_file = url_or_path
|
|
283
|
+
elif callback_host := astrbot_config.get("callback_api_base"):
|
|
284
|
+
callback_host = str(callback_host).removesuffix("/")
|
|
285
|
+
token = await file_token_service.register_file(url_or_path)
|
|
286
|
+
payload_file = f"{callback_host}/api/file/{token}"
|
|
287
|
+
logger.debug(f"Generated video file callback link: {payload_file}")
|
|
288
|
+
else:
|
|
289
|
+
payload_file = url_or_path
|
|
290
|
+
return {
|
|
291
|
+
"type": "video",
|
|
292
|
+
"data": {
|
|
293
|
+
"file": payload_file,
|
|
294
|
+
},
|
|
295
|
+
}
|
|
296
|
+
|
|
226
297
|
|
|
227
298
|
class At(BaseMessageComponent):
|
|
228
|
-
type
|
|
229
|
-
qq:
|
|
230
|
-
name:
|
|
299
|
+
type = ComponentType.At
|
|
300
|
+
qq: int | str # 此处str为all时代表所有人
|
|
301
|
+
name: str | None = ""
|
|
231
302
|
|
|
232
303
|
def __init__(self, **_):
|
|
233
304
|
super().__init__(**_)
|
|
234
305
|
|
|
306
|
+
def toDict(self):
|
|
307
|
+
return {
|
|
308
|
+
"type": "at",
|
|
309
|
+
"data": {"qq": str(self.qq)},
|
|
310
|
+
}
|
|
311
|
+
|
|
235
312
|
|
|
236
313
|
class AtAll(At):
|
|
237
314
|
qq: str = "all"
|
|
@@ -241,74 +318,66 @@ class AtAll(At):
|
|
|
241
318
|
|
|
242
319
|
|
|
243
320
|
class RPS(BaseMessageComponent): # TODO
|
|
244
|
-
type
|
|
321
|
+
type = ComponentType.RPS
|
|
245
322
|
|
|
246
323
|
def __init__(self, **_):
|
|
247
324
|
super().__init__(**_)
|
|
248
325
|
|
|
249
326
|
|
|
250
327
|
class Dice(BaseMessageComponent): # TODO
|
|
251
|
-
type
|
|
328
|
+
type = ComponentType.Dice
|
|
252
329
|
|
|
253
330
|
def __init__(self, **_):
|
|
254
331
|
super().__init__(**_)
|
|
255
332
|
|
|
256
333
|
|
|
257
334
|
class Shake(BaseMessageComponent): # TODO
|
|
258
|
-
type
|
|
259
|
-
|
|
260
|
-
def __init__(self, **_):
|
|
261
|
-
super().__init__(**_)
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
class Anonymous(BaseMessageComponent): # TODO
|
|
265
|
-
type: ComponentType = "Anonymous"
|
|
266
|
-
ignore: T.Optional[bool] = False
|
|
335
|
+
type = ComponentType.Shake
|
|
267
336
|
|
|
268
337
|
def __init__(self, **_):
|
|
269
338
|
super().__init__(**_)
|
|
270
339
|
|
|
271
340
|
|
|
272
341
|
class Share(BaseMessageComponent):
|
|
273
|
-
type
|
|
342
|
+
type = ComponentType.Share
|
|
274
343
|
url: str
|
|
275
344
|
title: str
|
|
276
|
-
content:
|
|
277
|
-
image:
|
|
345
|
+
content: str | None = ""
|
|
346
|
+
image: str | None = ""
|
|
278
347
|
|
|
279
348
|
def __init__(self, **_):
|
|
280
349
|
super().__init__(**_)
|
|
281
350
|
|
|
282
351
|
|
|
283
352
|
class Contact(BaseMessageComponent): # TODO
|
|
284
|
-
type
|
|
353
|
+
type = ComponentType.Contact
|
|
285
354
|
_type: str # type 字段冲突
|
|
286
|
-
id:
|
|
355
|
+
id: int | None = 0
|
|
287
356
|
|
|
288
357
|
def __init__(self, **_):
|
|
289
358
|
super().__init__(**_)
|
|
290
359
|
|
|
291
360
|
|
|
292
361
|
class Location(BaseMessageComponent): # TODO
|
|
293
|
-
type
|
|
362
|
+
type = ComponentType.Location
|
|
294
363
|
lat: float
|
|
295
364
|
lon: float
|
|
296
|
-
title:
|
|
297
|
-
content:
|
|
365
|
+
title: str | None = ""
|
|
366
|
+
content: str | None = ""
|
|
298
367
|
|
|
299
368
|
def __init__(self, **_):
|
|
300
369
|
super().__init__(**_)
|
|
301
370
|
|
|
302
371
|
|
|
303
372
|
class Music(BaseMessageComponent):
|
|
304
|
-
type
|
|
373
|
+
type = ComponentType.Music
|
|
305
374
|
_type: str
|
|
306
|
-
id:
|
|
307
|
-
url:
|
|
308
|
-
audio:
|
|
309
|
-
title:
|
|
310
|
-
content:
|
|
311
|
-
image:
|
|
375
|
+
id: int | None = 0
|
|
376
|
+
url: str | None = ""
|
|
377
|
+
audio: str | None = ""
|
|
378
|
+
title: str | None = ""
|
|
379
|
+
content: str | None = ""
|
|
380
|
+
image: str | None = ""
|
|
312
381
|
|
|
313
382
|
def __init__(self, **_):
|
|
314
383
|
# for k in _.keys():
|
|
@@ -318,19 +387,19 @@ class Music(BaseMessageComponent):
|
|
|
318
387
|
|
|
319
388
|
|
|
320
389
|
class Image(BaseMessageComponent):
|
|
321
|
-
type
|
|
322
|
-
file:
|
|
323
|
-
_type:
|
|
324
|
-
subType:
|
|
325
|
-
url:
|
|
326
|
-
cache:
|
|
327
|
-
id:
|
|
328
|
-
c:
|
|
390
|
+
type = ComponentType.Image
|
|
391
|
+
file: str | None = ""
|
|
392
|
+
_type: str | None = ""
|
|
393
|
+
subType: int | None = 0
|
|
394
|
+
url: str | None = ""
|
|
395
|
+
cache: bool | None = True
|
|
396
|
+
id: int | None = 40000
|
|
397
|
+
c: int | None = 2
|
|
329
398
|
# 额外
|
|
330
|
-
path:
|
|
331
|
-
file_unique:
|
|
399
|
+
path: str | None = ""
|
|
400
|
+
file_unique: str | None = "" # 某些平台可能有图片缓存的唯一标识
|
|
332
401
|
|
|
333
|
-
def __init__(self, file:
|
|
402
|
+
def __init__(self, file: str | None, **_):
|
|
334
403
|
super().__init__(file=file, **_)
|
|
335
404
|
|
|
336
405
|
@staticmethod
|
|
@@ -360,41 +429,45 @@ class Image(BaseMessageComponent):
|
|
|
360
429
|
|
|
361
430
|
Returns:
|
|
362
431
|
str: 图片的本地路径,以绝对路径表示。
|
|
432
|
+
|
|
363
433
|
"""
|
|
364
|
-
url = self.url
|
|
365
|
-
if
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
434
|
+
url = self.url or self.file
|
|
435
|
+
if not url:
|
|
436
|
+
raise ValueError("No valid file or URL provided")
|
|
437
|
+
if url.startswith("file:///"):
|
|
438
|
+
return url[8:]
|
|
439
|
+
if url.startswith("http"):
|
|
369
440
|
image_file_path = await download_image_by_url(url)
|
|
370
441
|
return os.path.abspath(image_file_path)
|
|
371
|
-
|
|
442
|
+
if url.startswith("base64://"):
|
|
372
443
|
bs64_data = url.removeprefix("base64://")
|
|
373
444
|
image_bytes = base64.b64decode(bs64_data)
|
|
374
|
-
|
|
445
|
+
temp_dir = os.path.join(get_astrbot_data_path(), "temp")
|
|
446
|
+
image_file_path = os.path.join(temp_dir, f"{uuid.uuid4()}.jpg")
|
|
375
447
|
with open(image_file_path, "wb") as f:
|
|
376
448
|
f.write(image_bytes)
|
|
377
449
|
return os.path.abspath(image_file_path)
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
else:
|
|
382
|
-
raise Exception(f"not a valid file: {url}")
|
|
450
|
+
if os.path.exists(url):
|
|
451
|
+
return os.path.abspath(url)
|
|
452
|
+
raise Exception(f"not a valid file: {url}")
|
|
383
453
|
|
|
384
454
|
async def convert_to_base64(self) -> str:
|
|
385
455
|
"""将这个图片统一转换为 base64 编码。这个方法避免了手动判断图片数据类型,直接返回图片数据的 base64 编码。
|
|
386
456
|
|
|
387
457
|
Returns:
|
|
388
458
|
str: 图片的 base64 编码,不以 base64:// 或者 data:image/jpeg;base64, 开头。
|
|
459
|
+
|
|
389
460
|
"""
|
|
390
461
|
# convert to base64
|
|
391
|
-
url = self.url
|
|
392
|
-
if
|
|
462
|
+
url = self.url or self.file
|
|
463
|
+
if not url:
|
|
464
|
+
raise ValueError("No valid file or URL provided")
|
|
465
|
+
if url.startswith("file:///"):
|
|
393
466
|
bs64_data = file_to_base64(url[8:])
|
|
394
|
-
elif url
|
|
467
|
+
elif url.startswith("http"):
|
|
395
468
|
image_file_path = await download_image_by_url(url)
|
|
396
469
|
bs64_data = file_to_base64(image_file_path)
|
|
397
|
-
elif url
|
|
470
|
+
elif url.startswith("base64://"):
|
|
398
471
|
bs64_data = url
|
|
399
472
|
elif os.path.exists(url):
|
|
400
473
|
bs64_data = file_to_base64(url)
|
|
@@ -403,45 +476,60 @@ class Image(BaseMessageComponent):
|
|
|
403
476
|
bs64_data = bs64_data.removeprefix("base64://")
|
|
404
477
|
return bs64_data
|
|
405
478
|
|
|
479
|
+
async def register_to_file_service(self) -> str:
|
|
480
|
+
"""将图片注册到文件服务。
|
|
481
|
+
|
|
482
|
+
Returns:
|
|
483
|
+
str: 注册后的URL
|
|
484
|
+
|
|
485
|
+
Raises:
|
|
486
|
+
Exception: 如果未配置 callback_api_base
|
|
487
|
+
|
|
488
|
+
"""
|
|
489
|
+
callback_host = astrbot_config.get("callback_api_base")
|
|
490
|
+
|
|
491
|
+
if not callback_host:
|
|
492
|
+
raise Exception("未配置 callback_api_base,文件服务不可用")
|
|
493
|
+
|
|
494
|
+
file_path = await self.convert_to_file_path()
|
|
495
|
+
|
|
496
|
+
token = await file_token_service.register_file(file_path)
|
|
497
|
+
|
|
498
|
+
logger.debug(f"已注册:{callback_host}/api/file/{token}")
|
|
499
|
+
|
|
500
|
+
return f"{callback_host}/api/file/{token}"
|
|
501
|
+
|
|
406
502
|
|
|
407
503
|
class Reply(BaseMessageComponent):
|
|
408
|
-
type
|
|
409
|
-
id:
|
|
504
|
+
type = ComponentType.Reply
|
|
505
|
+
id: str | int
|
|
410
506
|
"""所引用的消息 ID"""
|
|
411
|
-
chain:
|
|
507
|
+
chain: list["BaseMessageComponent"] | None = []
|
|
412
508
|
"""被引用的消息段列表"""
|
|
413
|
-
sender_id:
|
|
509
|
+
sender_id: int | None | str = 0
|
|
414
510
|
"""被引用的消息对应的发送者的 ID"""
|
|
415
|
-
sender_nickname:
|
|
511
|
+
sender_nickname: str | None = ""
|
|
416
512
|
"""被引用的消息对应的发送者的昵称"""
|
|
417
|
-
time:
|
|
513
|
+
time: int | None = 0
|
|
418
514
|
"""被引用的消息发送时间"""
|
|
419
|
-
message_str:
|
|
515
|
+
message_str: str | None = ""
|
|
420
516
|
"""被引用的消息解析后的纯文本消息字符串"""
|
|
421
517
|
|
|
422
|
-
text:
|
|
518
|
+
text: str | None = ""
|
|
423
519
|
"""deprecated"""
|
|
424
|
-
qq:
|
|
520
|
+
qq: int | None = 0
|
|
425
521
|
"""deprecated"""
|
|
426
|
-
seq:
|
|
522
|
+
seq: int | None = 0
|
|
427
523
|
"""deprecated"""
|
|
428
524
|
|
|
429
525
|
def __init__(self, **_):
|
|
430
526
|
super().__init__(**_)
|
|
431
527
|
|
|
432
528
|
|
|
433
|
-
class RedBag(BaseMessageComponent):
|
|
434
|
-
type: ComponentType = "RedBag"
|
|
435
|
-
title: str
|
|
436
|
-
|
|
437
|
-
def __init__(self, **_):
|
|
438
|
-
super().__init__(**_)
|
|
439
|
-
|
|
440
|
-
|
|
441
529
|
class Poke(BaseMessageComponent):
|
|
442
|
-
type: str =
|
|
443
|
-
id:
|
|
444
|
-
qq:
|
|
530
|
+
type: str = ComponentType.Poke
|
|
531
|
+
id: int | None = 0
|
|
532
|
+
qq: int | None = 0
|
|
445
533
|
|
|
446
534
|
def __init__(self, type: str, **_):
|
|
447
535
|
type = f"Poke:{type}"
|
|
@@ -449,7 +537,7 @@ class Poke(BaseMessageComponent):
|
|
|
449
537
|
|
|
450
538
|
|
|
451
539
|
class Forward(BaseMessageComponent):
|
|
452
|
-
type
|
|
540
|
+
type = ComponentType.Forward
|
|
453
541
|
id: str
|
|
454
542
|
|
|
455
543
|
def __init__(self, **_):
|
|
@@ -459,57 +547,87 @@ class Forward(BaseMessageComponent):
|
|
|
459
547
|
class Node(BaseMessageComponent):
|
|
460
548
|
"""群合并转发消息"""
|
|
461
549
|
|
|
462
|
-
type
|
|
463
|
-
id:
|
|
464
|
-
name:
|
|
465
|
-
uin:
|
|
466
|
-
content:
|
|
467
|
-
seq:
|
|
468
|
-
time:
|
|
469
|
-
|
|
470
|
-
def __init__(self, content:
|
|
471
|
-
if isinstance(content,
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
_content = [node.toDict() for node in content]
|
|
475
|
-
else:
|
|
476
|
-
_content = ""
|
|
477
|
-
for chain in content:
|
|
478
|
-
_content += chain.toString()
|
|
479
|
-
content = _content
|
|
480
|
-
elif isinstance(content, Node):
|
|
481
|
-
content = content.toDict()
|
|
550
|
+
type = ComponentType.Node
|
|
551
|
+
id: int | None = 0 # 忽略
|
|
552
|
+
name: str | None = "" # qq昵称
|
|
553
|
+
uin: str | None = "0" # qq号
|
|
554
|
+
content: list[BaseMessageComponent] | None = []
|
|
555
|
+
seq: str | list | None = "" # 忽略
|
|
556
|
+
time: int | None = 0 # 忽略
|
|
557
|
+
|
|
558
|
+
def __init__(self, content: list[BaseMessageComponent], **_):
|
|
559
|
+
if isinstance(content, Node):
|
|
560
|
+
# back
|
|
561
|
+
content = [content]
|
|
482
562
|
super().__init__(content=content, **_)
|
|
483
563
|
|
|
484
|
-
def
|
|
485
|
-
|
|
486
|
-
|
|
564
|
+
async def to_dict(self):
|
|
565
|
+
data_content = []
|
|
566
|
+
for comp in self.content:
|
|
567
|
+
if isinstance(comp, (Image, Record)):
|
|
568
|
+
# For Image and Record segments, we convert them to base64
|
|
569
|
+
bs64 = await comp.convert_to_base64()
|
|
570
|
+
data_content.append(
|
|
571
|
+
{
|
|
572
|
+
"type": comp.type.lower(),
|
|
573
|
+
"data": {"file": f"base64://{bs64}"},
|
|
574
|
+
},
|
|
575
|
+
)
|
|
576
|
+
elif isinstance(comp, Plain):
|
|
577
|
+
# For Plain segments, we need to handle the plain differently
|
|
578
|
+
d = await comp.to_dict()
|
|
579
|
+
data_content.append(d)
|
|
580
|
+
elif isinstance(comp, File):
|
|
581
|
+
# For File segments, we need to handle the file differently
|
|
582
|
+
d = await comp.to_dict()
|
|
583
|
+
data_content.append(d)
|
|
584
|
+
elif isinstance(comp, (Node, Nodes)):
|
|
585
|
+
# For Node segments, we recursively convert them to dict
|
|
586
|
+
d = await comp.to_dict()
|
|
587
|
+
data_content.append(d)
|
|
588
|
+
else:
|
|
589
|
+
d = comp.toDict()
|
|
590
|
+
data_content.append(d)
|
|
591
|
+
return {
|
|
592
|
+
"type": "node",
|
|
593
|
+
"data": {
|
|
594
|
+
"user_id": str(self.uin),
|
|
595
|
+
"nickname": self.name,
|
|
596
|
+
"content": data_content,
|
|
597
|
+
},
|
|
598
|
+
}
|
|
487
599
|
|
|
488
600
|
|
|
489
601
|
class Nodes(BaseMessageComponent):
|
|
490
|
-
type
|
|
491
|
-
nodes:
|
|
602
|
+
type = ComponentType.Nodes
|
|
603
|
+
nodes: list[Node]
|
|
492
604
|
|
|
493
|
-
def __init__(self, nodes:
|
|
605
|
+
def __init__(self, nodes: list[Node], **_):
|
|
494
606
|
super().__init__(nodes=nodes, **_)
|
|
495
607
|
|
|
496
608
|
def toDict(self):
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
609
|
+
"""Deprecated. Use to_dict instead"""
|
|
610
|
+
ret = {
|
|
611
|
+
"messages": [],
|
|
612
|
+
}
|
|
613
|
+
for node in self.nodes:
|
|
614
|
+
d = node.toDict()
|
|
615
|
+
ret["messages"].append(d)
|
|
616
|
+
return ret
|
|
617
|
+
|
|
618
|
+
async def to_dict(self):
|
|
619
|
+
"""将 Nodes 转换为字典格式,适用于 OneBot JSON 格式"""
|
|
620
|
+
ret = {"messages": []}
|
|
621
|
+
for node in self.nodes:
|
|
622
|
+
d = await node.to_dict()
|
|
623
|
+
ret["messages"].append(d)
|
|
624
|
+
return ret
|
|
507
625
|
|
|
508
626
|
|
|
509
627
|
class Json(BaseMessageComponent):
|
|
510
|
-
type
|
|
511
|
-
data:
|
|
512
|
-
resid:
|
|
628
|
+
type = ComponentType.Json
|
|
629
|
+
data: str | dict
|
|
630
|
+
resid: int | None = 0
|
|
513
631
|
|
|
514
632
|
def __init__(self, data, **_):
|
|
515
633
|
if isinstance(data, dict):
|
|
@@ -517,80 +635,49 @@ class Json(BaseMessageComponent):
|
|
|
517
635
|
super().__init__(data=data, **_)
|
|
518
636
|
|
|
519
637
|
|
|
520
|
-
class CardImage(BaseMessageComponent):
|
|
521
|
-
type: ComponentType = "CardImage"
|
|
522
|
-
file: str
|
|
523
|
-
cache: T.Optional[bool] = True
|
|
524
|
-
minwidth: T.Optional[int] = 400
|
|
525
|
-
minheight: T.Optional[int] = 400
|
|
526
|
-
maxwidth: T.Optional[int] = 500
|
|
527
|
-
maxheight: T.Optional[int] = 500
|
|
528
|
-
source: T.Optional[str] = ""
|
|
529
|
-
icon: T.Optional[str] = ""
|
|
530
|
-
|
|
531
|
-
def __init__(self, **_):
|
|
532
|
-
super().__init__(**_)
|
|
533
|
-
|
|
534
|
-
@staticmethod
|
|
535
|
-
def fromFileSystem(path, **_):
|
|
536
|
-
return CardImage(file=f"file:///{os.path.abspath(path)}", **_)
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
class TTS(BaseMessageComponent):
|
|
540
|
-
type: ComponentType = "TTS"
|
|
541
|
-
text: str
|
|
542
|
-
|
|
543
|
-
def __init__(self, **_):
|
|
544
|
-
super().__init__(**_)
|
|
545
|
-
|
|
546
|
-
|
|
547
638
|
class Unknown(BaseMessageComponent):
|
|
548
|
-
type
|
|
639
|
+
type = ComponentType.Unknown
|
|
549
640
|
text: str
|
|
550
641
|
|
|
551
|
-
def toString(self):
|
|
552
|
-
return ""
|
|
553
|
-
|
|
554
642
|
|
|
555
643
|
class File(BaseMessageComponent):
|
|
556
|
-
"""
|
|
557
|
-
文件消息段
|
|
558
|
-
"""
|
|
644
|
+
"""文件消息段"""
|
|
559
645
|
|
|
560
|
-
type
|
|
561
|
-
name:
|
|
562
|
-
|
|
563
|
-
url:
|
|
564
|
-
_downloaded: bool = False # 是否已经下载
|
|
646
|
+
type = ComponentType.File
|
|
647
|
+
name: str | None = "" # 名字
|
|
648
|
+
file_: str | None = "" # 本地路径
|
|
649
|
+
url: str | None = "" # url
|
|
565
650
|
|
|
566
|
-
def __init__(self, name: str
|
|
567
|
-
|
|
651
|
+
def __init__(self, name: str, file: str = "", url: str = ""):
|
|
652
|
+
"""文件消息段。"""
|
|
653
|
+
super().__init__(name=name, file_=file, url=url)
|
|
568
654
|
|
|
569
655
|
@property
|
|
570
656
|
def file(self) -> str:
|
|
571
|
-
"""
|
|
572
|
-
获取文件路径,如果文件不存在但有URL,则同步下载文件
|
|
657
|
+
"""获取文件路径,如果文件不存在但有URL,则同步下载文件
|
|
573
658
|
|
|
574
659
|
Returns:
|
|
575
660
|
str: 文件路径
|
|
661
|
+
|
|
576
662
|
"""
|
|
577
|
-
if self.
|
|
578
|
-
return self.
|
|
663
|
+
if self.file_ and os.path.exists(self.file_):
|
|
664
|
+
return os.path.abspath(self.file_)
|
|
579
665
|
|
|
580
|
-
if self.url
|
|
666
|
+
if self.url:
|
|
581
667
|
try:
|
|
582
668
|
loop = asyncio.get_event_loop()
|
|
583
669
|
if loop.is_running():
|
|
584
670
|
logger.warning(
|
|
585
|
-
"不可以在异步上下文中同步等待下载!
|
|
671
|
+
"不可以在异步上下文中同步等待下载! "
|
|
672
|
+
"这个警告通常发生于某些逻辑试图通过 <File>.file 获取文件消息段的文件内容。"
|
|
673
|
+
"请使用 await get_file() 代替直接获取 <File>.file 字段",
|
|
586
674
|
)
|
|
587
675
|
return ""
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
loop.run_until_complete(self._download_file())
|
|
676
|
+
# 等待下载完成
|
|
677
|
+
loop.run_until_complete(self._download_file())
|
|
591
678
|
|
|
592
|
-
|
|
593
|
-
|
|
679
|
+
if self.file_ and os.path.exists(self.file_):
|
|
680
|
+
return os.path.abspath(self.file_)
|
|
594
681
|
except Exception as e:
|
|
595
682
|
logger.error(f"文件下载失败: {e}")
|
|
596
683
|
|
|
@@ -598,86 +685,125 @@ class File(BaseMessageComponent):
|
|
|
598
685
|
|
|
599
686
|
@file.setter
|
|
600
687
|
def file(self, value: str):
|
|
601
|
-
"""
|
|
602
|
-
向前兼容, 设置file属性, 传入的参数可能是文件路径或URL
|
|
688
|
+
"""向前兼容, 设置file属性, 传入的参数可能是文件路径或URL
|
|
603
689
|
|
|
604
690
|
Args:
|
|
605
691
|
value (str): 文件路径或URL
|
|
692
|
+
|
|
606
693
|
"""
|
|
607
694
|
if value.startswith("http://") or value.startswith("https://"):
|
|
608
695
|
self.url = value
|
|
609
696
|
else:
|
|
610
|
-
self.
|
|
697
|
+
self.file_ = value
|
|
611
698
|
|
|
612
|
-
async def get_file(self) -> str:
|
|
613
|
-
"""
|
|
614
|
-
异步获取文件
|
|
615
|
-
To 插件开发者: 请注意在使用后清理下载的文件, 以免占用过多空间
|
|
699
|
+
async def get_file(self, allow_return_url: bool = False) -> str:
|
|
700
|
+
"""异步获取文件。请注意在使用后清理下载的文件, 以免占用过多空间
|
|
616
701
|
|
|
702
|
+
Args:
|
|
703
|
+
allow_return_url: 是否允许以文件 http 下载链接的形式返回,这允许您自行控制是否需要下载文件。
|
|
704
|
+
注意,如果为 True,也可能返回文件路径。
|
|
617
705
|
Returns:
|
|
618
|
-
str:
|
|
706
|
+
str: 文件路径或者 http 下载链接
|
|
707
|
+
|
|
619
708
|
"""
|
|
620
|
-
if
|
|
621
|
-
return self.
|
|
709
|
+
if allow_return_url and self.url:
|
|
710
|
+
return self.url
|
|
711
|
+
|
|
712
|
+
if self.file_ and os.path.exists(self.file_):
|
|
713
|
+
return os.path.abspath(self.file_)
|
|
622
714
|
|
|
623
715
|
if self.url:
|
|
624
716
|
await self._download_file()
|
|
625
|
-
return self.
|
|
717
|
+
return os.path.abspath(self.file_)
|
|
626
718
|
|
|
627
719
|
return ""
|
|
628
720
|
|
|
629
721
|
async def _download_file(self):
|
|
630
722
|
"""下载文件"""
|
|
631
|
-
|
|
632
|
-
|
|
723
|
+
download_dir = os.path.join(get_astrbot_data_path(), "temp")
|
|
724
|
+
os.makedirs(download_dir, exist_ok=True)
|
|
725
|
+
file_path = os.path.join(download_dir, f"{uuid.uuid4().hex}")
|
|
726
|
+
await download_file(self.url, file_path)
|
|
727
|
+
self.file_ = os.path.abspath(file_path)
|
|
633
728
|
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
file_path = f"data/download/{filename}"
|
|
729
|
+
async def register_to_file_service(self):
|
|
730
|
+
"""将文件注册到文件服务。
|
|
637
731
|
|
|
638
|
-
|
|
732
|
+
Returns:
|
|
733
|
+
str: 注册后的URL
|
|
734
|
+
|
|
735
|
+
Raises:
|
|
736
|
+
Exception: 如果未配置 callback_api_base
|
|
737
|
+
|
|
738
|
+
"""
|
|
739
|
+
callback_host = astrbot_config.get("callback_api_base")
|
|
639
740
|
|
|
640
|
-
|
|
641
|
-
|
|
741
|
+
if not callback_host:
|
|
742
|
+
raise Exception("未配置 callback_api_base,文件服务不可用")
|
|
743
|
+
|
|
744
|
+
file_path = await self.get_file()
|
|
745
|
+
|
|
746
|
+
token = await file_token_service.register_file(file_path)
|
|
747
|
+
|
|
748
|
+
logger.debug(f"已注册:{callback_host}/api/file/{token}")
|
|
749
|
+
|
|
750
|
+
return f"{callback_host}/api/file/{token}"
|
|
751
|
+
|
|
752
|
+
async def to_dict(self):
|
|
753
|
+
"""需要和 toDict 区分开,toDict 是同步方法"""
|
|
754
|
+
url_or_path = await self.get_file(allow_return_url=True)
|
|
755
|
+
if url_or_path.startswith("http"):
|
|
756
|
+
payload_file = url_or_path
|
|
757
|
+
elif callback_host := astrbot_config.get("callback_api_base"):
|
|
758
|
+
callback_host = str(callback_host).removesuffix("/")
|
|
759
|
+
token = await file_token_service.register_file(url_or_path)
|
|
760
|
+
payload_file = f"{callback_host}/api/file/{token}"
|
|
761
|
+
logger.debug(f"Generated file callback link: {payload_file}")
|
|
762
|
+
else:
|
|
763
|
+
payload_file = url_or_path
|
|
764
|
+
return {
|
|
765
|
+
"type": "file",
|
|
766
|
+
"data": {
|
|
767
|
+
"name": self.name,
|
|
768
|
+
"file": payload_file,
|
|
769
|
+
},
|
|
770
|
+
}
|
|
642
771
|
|
|
643
772
|
|
|
644
773
|
class WechatEmoji(BaseMessageComponent):
|
|
645
|
-
type
|
|
646
|
-
md5:
|
|
647
|
-
md5_len:
|
|
648
|
-
cdnurl:
|
|
774
|
+
type = ComponentType.WechatEmoji
|
|
775
|
+
md5: str | None = ""
|
|
776
|
+
md5_len: int | None = 0
|
|
777
|
+
cdnurl: str | None = ""
|
|
649
778
|
|
|
650
779
|
def __init__(self, **_):
|
|
651
780
|
super().__init__(**_)
|
|
652
781
|
|
|
653
782
|
|
|
654
783
|
ComponentTypes = {
|
|
784
|
+
# Basic Message Segments
|
|
655
785
|
"plain": Plain,
|
|
656
786
|
"text": Plain,
|
|
657
|
-
"
|
|
787
|
+
"image": Image,
|
|
658
788
|
"record": Record,
|
|
659
789
|
"video": Video,
|
|
790
|
+
"file": File,
|
|
791
|
+
# IM-specific Message Segments
|
|
792
|
+
"face": Face,
|
|
660
793
|
"at": At,
|
|
661
794
|
"rps": RPS,
|
|
662
795
|
"dice": Dice,
|
|
663
796
|
"shake": Shake,
|
|
664
|
-
"anonymous": Anonymous,
|
|
665
797
|
"share": Share,
|
|
666
798
|
"contact": Contact,
|
|
667
799
|
"location": Location,
|
|
668
800
|
"music": Music,
|
|
669
|
-
"image": Image,
|
|
670
801
|
"reply": Reply,
|
|
671
|
-
"redbag": RedBag,
|
|
672
802
|
"poke": Poke,
|
|
673
803
|
"forward": Forward,
|
|
674
804
|
"node": Node,
|
|
675
805
|
"nodes": Nodes,
|
|
676
|
-
"xml": Xml,
|
|
677
806
|
"json": Json,
|
|
678
|
-
"cardimage": CardImage,
|
|
679
|
-
"tts": TTS,
|
|
680
807
|
"unknown": Unknown,
|
|
681
|
-
"file": File,
|
|
682
808
|
"WechatEmoji": WechatEmoji,
|
|
683
809
|
}
|