AstrBot 4.5.0__py3-none-any.whl → 4.5.2__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 +10 -11
- astrbot/api/event/__init__.py +5 -6
- astrbot/api/event/filter/__init__.py +37 -36
- astrbot/api/platform/__init__.py +7 -8
- astrbot/api/provider/__init__.py +7 -7
- astrbot/api/star/__init__.py +3 -4
- astrbot/api/util/__init__.py +2 -2
- astrbot/cli/__main__.py +5 -5
- astrbot/cli/commands/__init__.py +3 -3
- astrbot/cli/commands/cmd_conf.py +19 -16
- astrbot/cli/commands/cmd_init.py +3 -2
- astrbot/cli/commands/cmd_plug.py +8 -10
- astrbot/cli/commands/cmd_run.py +5 -6
- astrbot/cli/utils/__init__.py +6 -6
- astrbot/cli/utils/basic.py +14 -14
- astrbot/cli/utils/plugin.py +24 -15
- astrbot/cli/utils/version_comparator.py +10 -12
- astrbot/core/__init__.py +8 -6
- astrbot/core/agent/agent.py +3 -2
- astrbot/core/agent/handoff.py +6 -2
- astrbot/core/agent/hooks.py +9 -6
- astrbot/core/agent/mcp_client.py +50 -15
- astrbot/core/agent/message.py +168 -0
- astrbot/core/agent/response.py +2 -1
- astrbot/core/agent/run_context.py +2 -3
- astrbot/core/agent/runners/base.py +10 -13
- astrbot/core/agent/runners/tool_loop_agent_runner.py +52 -51
- astrbot/core/agent/tool.py +60 -41
- astrbot/core/agent/tool_executor.py +9 -3
- astrbot/core/astr_agent_context.py +3 -1
- astrbot/core/astrbot_config_mgr.py +29 -9
- astrbot/core/config/__init__.py +2 -2
- astrbot/core/config/astrbot_config.py +28 -26
- astrbot/core/config/default.py +44 -6
- astrbot/core/conversation_mgr.py +105 -36
- astrbot/core/core_lifecycle.py +68 -54
- astrbot/core/db/__init__.py +33 -18
- astrbot/core/db/migration/helper.py +18 -13
- astrbot/core/db/migration/migra_3_to_4.py +53 -34
- astrbot/core/db/migration/migra_45_to_46.py +1 -1
- astrbot/core/db/migration/shared_preferences_v3.py +2 -1
- astrbot/core/db/migration/sqlite_v3.py +26 -23
- astrbot/core/db/po.py +27 -18
- astrbot/core/db/sqlite.py +74 -45
- astrbot/core/db/vec_db/base.py +10 -14
- astrbot/core/db/vec_db/faiss_impl/document_storage.py +90 -77
- astrbot/core/db/vec_db/faiss_impl/embedding_storage.py +9 -3
- astrbot/core/db/vec_db/faiss_impl/vec_db.py +36 -31
- astrbot/core/event_bus.py +8 -6
- astrbot/core/file_token_service.py +6 -5
- astrbot/core/initial_loader.py +7 -5
- astrbot/core/knowledge_base/chunking/__init__.py +1 -3
- astrbot/core/knowledge_base/chunking/base.py +1 -0
- astrbot/core/knowledge_base/chunking/fixed_size.py +2 -0
- astrbot/core/knowledge_base/chunking/recursive.py +16 -10
- astrbot/core/knowledge_base/kb_db_sqlite.py +50 -48
- astrbot/core/knowledge_base/kb_helper.py +30 -17
- astrbot/core/knowledge_base/kb_mgr.py +6 -7
- astrbot/core/knowledge_base/models.py +10 -4
- astrbot/core/knowledge_base/parsers/__init__.py +3 -5
- astrbot/core/knowledge_base/parsers/base.py +1 -0
- astrbot/core/knowledge_base/parsers/markitdown_parser.py +2 -1
- astrbot/core/knowledge_base/parsers/pdf_parser.py +2 -1
- astrbot/core/knowledge_base/parsers/text_parser.py +1 -0
- astrbot/core/knowledge_base/parsers/util.py +1 -1
- astrbot/core/knowledge_base/retrieval/__init__.py +6 -8
- astrbot/core/knowledge_base/retrieval/manager.py +17 -14
- astrbot/core/knowledge_base/retrieval/rank_fusion.py +7 -3
- astrbot/core/knowledge_base/retrieval/sparse_retriever.py +11 -5
- astrbot/core/log.py +21 -13
- astrbot/core/message/components.py +123 -217
- astrbot/core/message/message_event_result.py +24 -24
- astrbot/core/persona_mgr.py +20 -11
- astrbot/core/pipeline/__init__.py +7 -7
- 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 +12 -13
- astrbot/core/pipeline/content_safety_check/strategies/keywords.py +1 -0
- astrbot/core/pipeline/content_safety_check/strategies/strategy.py +6 -6
- astrbot/core/pipeline/context.py +4 -1
- astrbot/core/pipeline/context_utils.py +77 -7
- astrbot/core/pipeline/preprocess_stage/stage.py +12 -9
- astrbot/core/pipeline/process_stage/method/llm_request.py +125 -72
- astrbot/core/pipeline/process_stage/method/star_request.py +19 -17
- astrbot/core/pipeline/process_stage/stage.py +13 -10
- astrbot/core/pipeline/process_stage/utils.py +6 -5
- astrbot/core/pipeline/rate_limit_check/stage.py +37 -36
- astrbot/core/pipeline/respond/stage.py +23 -20
- astrbot/core/pipeline/result_decorate/stage.py +31 -23
- astrbot/core/pipeline/scheduler.py +12 -8
- astrbot/core/pipeline/session_status_check/stage.py +12 -8
- astrbot/core/pipeline/stage.py +10 -4
- astrbot/core/pipeline/waking_check/stage.py +24 -18
- astrbot/core/pipeline/whitelist_check/stage.py +10 -7
- astrbot/core/platform/__init__.py +6 -6
- astrbot/core/platform/astr_message_event.py +76 -110
- astrbot/core/platform/astrbot_message.py +11 -13
- astrbot/core/platform/manager.py +16 -15
- astrbot/core/platform/message_session.py +5 -3
- astrbot/core/platform/platform.py +16 -24
- astrbot/core/platform/platform_metadata.py +4 -4
- astrbot/core/platform/register.py +8 -8
- astrbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py +23 -15
- astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py +51 -33
- astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py +47 -29
- astrbot/core/platform/sources/dingtalk/dingtalk_event.py +7 -3
- astrbot/core/platform/sources/discord/client.py +9 -6
- astrbot/core/platform/sources/discord/components.py +18 -14
- astrbot/core/platform/sources/discord/discord_platform_adapter.py +45 -30
- astrbot/core/platform/sources/discord/discord_platform_event.py +38 -30
- astrbot/core/platform/sources/lark/lark_adapter.py +23 -17
- astrbot/core/platform/sources/lark/lark_event.py +21 -14
- astrbot/core/platform/sources/misskey/misskey_adapter.py +107 -67
- astrbot/core/platform/sources/misskey/misskey_api.py +153 -129
- astrbot/core/platform/sources/misskey/misskey_event.py +20 -15
- astrbot/core/platform/sources/misskey/misskey_utils.py +74 -62
- astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py +63 -44
- 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 +12 -7
- astrbot/core/platform/sources/satori/satori_adapter.py +56 -38
- astrbot/core/platform/sources/satori/satori_event.py +34 -25
- astrbot/core/platform/sources/slack/client.py +11 -9
- astrbot/core/platform/sources/slack/slack_adapter.py +52 -36
- astrbot/core/platform/sources/slack/slack_event.py +34 -24
- astrbot/core/platform/sources/telegram/tg_adapter.py +38 -18
- astrbot/core/platform/sources/telegram/tg_event.py +32 -18
- astrbot/core/platform/sources/webchat/webchat_adapter.py +27 -17
- astrbot/core/platform/sources/webchat/webchat_event.py +14 -10
- astrbot/core/platform/sources/wechatpadpro/wechatpadpro_adapter.py +115 -120
- astrbot/core/platform/sources/wechatpadpro/wechatpadpro_message_event.py +9 -8
- astrbot/core/platform/sources/wechatpadpro/xml_data_parser.py +15 -16
- astrbot/core/platform/sources/wecom/wecom_adapter.py +35 -18
- astrbot/core/platform/sources/wecom/wecom_event.py +55 -48
- astrbot/core/platform/sources/wecom/wecom_kf.py +34 -44
- astrbot/core/platform/sources/wecom/wecom_kf_message.py +26 -10
- astrbot/core/platform/sources/wecom_ai_bot/WXBizJsonMsgCrypt.py +18 -10
- astrbot/core/platform/sources/wecom_ai_bot/__init__.py +3 -5
- astrbot/core/platform/sources/wecom_ai_bot/ierror.py +0 -1
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_adapter.py +61 -37
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_api.py +67 -28
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_event.py +8 -9
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_queue_mgr.py +18 -9
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_server.py +14 -12
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_utils.py +22 -12
- astrbot/core/platform/sources/weixin_official_account/weixin_offacc_adapter.py +40 -26
- astrbot/core/platform/sources/weixin_official_account/weixin_offacc_event.py +47 -45
- astrbot/core/platform_message_history_mgr.py +5 -3
- astrbot/core/provider/__init__.py +2 -3
- astrbot/core/provider/entites.py +8 -8
- astrbot/core/provider/entities.py +61 -75
- astrbot/core/provider/func_tool_manager.py +59 -55
- astrbot/core/provider/manager.py +40 -22
- astrbot/core/provider/provider.py +72 -46
- astrbot/core/provider/register.py +7 -7
- astrbot/core/provider/sources/anthropic_source.py +48 -30
- astrbot/core/provider/sources/azure_tts_source.py +17 -13
- astrbot/core/provider/sources/coze_api_client.py +27 -17
- astrbot/core/provider/sources/coze_source.py +104 -87
- astrbot/core/provider/sources/dashscope_source.py +18 -11
- astrbot/core/provider/sources/dashscope_tts.py +36 -23
- astrbot/core/provider/sources/dify_source.py +25 -20
- astrbot/core/provider/sources/edge_tts_source.py +21 -17
- astrbot/core/provider/sources/fishaudio_tts_api_source.py +22 -14
- astrbot/core/provider/sources/gemini_embedding_source.py +12 -13
- astrbot/core/provider/sources/gemini_source.py +72 -58
- astrbot/core/provider/sources/gemini_tts_source.py +8 -6
- astrbot/core/provider/sources/gsv_selfhosted_source.py +17 -14
- astrbot/core/provider/sources/gsvi_tts_source.py +11 -7
- astrbot/core/provider/sources/minimax_tts_api_source.py +50 -40
- astrbot/core/provider/sources/openai_embedding_source.py +6 -8
- astrbot/core/provider/sources/openai_source.py +102 -69
- astrbot/core/provider/sources/openai_tts_api_source.py +14 -6
- astrbot/core/provider/sources/sensevoice_selfhosted_source.py +13 -11
- astrbot/core/provider/sources/vllm_rerank_source.py +10 -4
- astrbot/core/provider/sources/volcengine_tts.py +38 -31
- astrbot/core/provider/sources/whisper_api_source.py +14 -12
- astrbot/core/provider/sources/whisper_selfhosted_source.py +15 -11
- astrbot/core/provider/sources/xinference_rerank_source.py +116 -0
- astrbot/core/provider/sources/xinference_stt_provider.py +197 -0
- astrbot/core/star/__init__.py +16 -11
- astrbot/core/star/config.py +10 -15
- astrbot/core/star/context.py +109 -84
- astrbot/core/star/filter/__init__.py +4 -3
- astrbot/core/star/filter/command.py +30 -28
- astrbot/core/star/filter/command_group.py +27 -24
- 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 +4 -2
- astrbot/core/star/filter/regex.py +4 -2
- astrbot/core/star/register/__init__.py +19 -19
- astrbot/core/star/register/star.py +6 -2
- astrbot/core/star/register/star_handler.py +96 -73
- astrbot/core/star/session_llm_manager.py +48 -14
- astrbot/core/star/session_plugin_manager.py +29 -15
- astrbot/core/star/star.py +1 -2
- astrbot/core/star/star_handler.py +13 -8
- astrbot/core/star/star_manager.py +151 -59
- astrbot/core/star/star_tools.py +44 -37
- astrbot/core/star/updator.py +10 -10
- astrbot/core/umop_config_router.py +10 -4
- astrbot/core/updator.py +13 -5
- astrbot/core/utils/astrbot_path.py +3 -5
- astrbot/core/utils/dify_api_client.py +33 -15
- astrbot/core/utils/io.py +66 -42
- astrbot/core/utils/log_pipe.py +1 -1
- astrbot/core/utils/metrics.py +7 -7
- astrbot/core/utils/path_util.py +15 -16
- astrbot/core/utils/pip_installer.py +5 -5
- astrbot/core/utils/session_waiter.py +19 -20
- astrbot/core/utils/shared_preferences.py +45 -20
- astrbot/core/utils/t2i/__init__.py +4 -1
- astrbot/core/utils/t2i/network_strategy.py +35 -26
- astrbot/core/utils/t2i/renderer.py +11 -5
- astrbot/core/utils/t2i/template_manager.py +14 -15
- astrbot/core/utils/tencent_record_helper.py +19 -13
- astrbot/core/utils/version_comparator.py +10 -13
- astrbot/core/zip_updator.py +43 -40
- astrbot/dashboard/routes/__init__.py +18 -18
- astrbot/dashboard/routes/auth.py +10 -8
- astrbot/dashboard/routes/chat.py +30 -21
- astrbot/dashboard/routes/config.py +92 -75
- astrbot/dashboard/routes/conversation.py +46 -39
- astrbot/dashboard/routes/file.py +4 -2
- astrbot/dashboard/routes/knowledge_base.py +47 -40
- astrbot/dashboard/routes/log.py +9 -4
- astrbot/dashboard/routes/persona.py +19 -16
- astrbot/dashboard/routes/plugin.py +69 -55
- astrbot/dashboard/routes/route.py +3 -1
- astrbot/dashboard/routes/session_management.py +130 -116
- astrbot/dashboard/routes/stat.py +34 -34
- astrbot/dashboard/routes/t2i.py +15 -12
- astrbot/dashboard/routes/tools.py +47 -52
- astrbot/dashboard/routes/update.py +32 -28
- astrbot/dashboard/server.py +30 -26
- astrbot/dashboard/utils.py +8 -4
- {astrbot-4.5.0.dist-info → astrbot-4.5.2.dist-info}/METADATA +4 -2
- astrbot-4.5.2.dist-info/RECORD +261 -0
- astrbot-4.5.0.dist-info/RECORD +0 -258
- {astrbot-4.5.0.dist-info → astrbot-4.5.2.dist-info}/WHEEL +0 -0
- {astrbot-4.5.0.dist-info → astrbot-4.5.2.dist-info}/entry_points.txt +0 -0
- {astrbot-4.5.0.dist-info → astrbot-4.5.2.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
|
|
|
@@ -26,7 +25,6 @@ import asyncio
|
|
|
26
25
|
import base64
|
|
27
26
|
import json
|
|
28
27
|
import os
|
|
29
|
-
import typing as T
|
|
30
28
|
import uuid
|
|
31
29
|
from enum import Enum
|
|
32
30
|
|
|
@@ -38,60 +36,36 @@ from astrbot.core.utils.io import download_file, download_image_by_url, file_to_
|
|
|
38
36
|
|
|
39
37
|
|
|
40
38
|
class ComponentType(str, Enum):
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
|
54
54
|
RPS = "RPS" # TODO
|
|
55
55
|
Dice = "Dice" # TODO
|
|
56
56
|
Shake = "Shake" # TODO
|
|
57
|
-
Anonymous = "Anonymous" # TODO
|
|
58
57
|
Share = "Share"
|
|
59
58
|
Contact = "Contact" # TODO
|
|
60
59
|
Location = "Location" # TODO
|
|
61
60
|
Music = "Music"
|
|
62
|
-
RedBag = "RedBag"
|
|
63
|
-
Xml = "Xml"
|
|
64
61
|
Json = "Json"
|
|
65
|
-
CardImage = "CardImage"
|
|
66
|
-
TTS = "TTS"
|
|
67
62
|
Unknown = "Unknown"
|
|
68
|
-
|
|
69
63
|
WechatEmoji = "WechatEmoji" # Wechat 下的 emoji 表情包
|
|
70
64
|
|
|
71
65
|
|
|
72
66
|
class BaseMessageComponent(BaseModel):
|
|
73
67
|
type: ComponentType
|
|
74
68
|
|
|
75
|
-
def toString(self):
|
|
76
|
-
output = f"[CQ:{self.type.lower()}"
|
|
77
|
-
for k, v in self.__dict__.items():
|
|
78
|
-
if k == "type" or v is None:
|
|
79
|
-
continue
|
|
80
|
-
if k == "_type":
|
|
81
|
-
k = "type"
|
|
82
|
-
if isinstance(v, bool):
|
|
83
|
-
v = 1 if v else 0
|
|
84
|
-
output += ",%s=%s" % (
|
|
85
|
-
k,
|
|
86
|
-
str(v)
|
|
87
|
-
.replace("&", "&")
|
|
88
|
-
.replace(",", ",")
|
|
89
|
-
.replace("[", "[")
|
|
90
|
-
.replace("]", "]"),
|
|
91
|
-
)
|
|
92
|
-
output += "]"
|
|
93
|
-
return output
|
|
94
|
-
|
|
95
69
|
def toDict(self):
|
|
96
70
|
data = {}
|
|
97
71
|
for k, v in self.__dict__.items():
|
|
@@ -110,18 +84,11 @@ class BaseMessageComponent(BaseModel):
|
|
|
110
84
|
class Plain(BaseMessageComponent):
|
|
111
85
|
type = ComponentType.Plain
|
|
112
86
|
text: str
|
|
113
|
-
convert:
|
|
87
|
+
convert: bool | None = True
|
|
114
88
|
|
|
115
89
|
def __init__(self, text: str, convert: bool = True, **_):
|
|
116
90
|
super().__init__(text=text, convert=convert, **_)
|
|
117
91
|
|
|
118
|
-
def toString(self): # 没有 [CQ:plain] 这种东西,所以直接导出纯文本
|
|
119
|
-
if not self.convert:
|
|
120
|
-
return self.text
|
|
121
|
-
return (
|
|
122
|
-
self.text.replace("&", "&").replace("[", "[").replace("]", "]")
|
|
123
|
-
)
|
|
124
|
-
|
|
125
92
|
def toDict(self):
|
|
126
93
|
return {"type": "text", "data": {"text": self.text.strip()}}
|
|
127
94
|
|
|
@@ -139,17 +106,17 @@ class Face(BaseMessageComponent):
|
|
|
139
106
|
|
|
140
107
|
class Record(BaseMessageComponent):
|
|
141
108
|
type = ComponentType.Record
|
|
142
|
-
file:
|
|
143
|
-
magic:
|
|
144
|
-
url:
|
|
145
|
-
cache:
|
|
146
|
-
proxy:
|
|
147
|
-
timeout:
|
|
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
|
|
148
115
|
# 额外
|
|
149
|
-
path:
|
|
116
|
+
path: str | None
|
|
150
117
|
|
|
151
|
-
def __init__(self, file:
|
|
152
|
-
for k in _
|
|
118
|
+
def __init__(self, file: str | None, **_):
|
|
119
|
+
for k in _:
|
|
153
120
|
if k == "url":
|
|
154
121
|
pass
|
|
155
122
|
# Protocol.warn(f"go-cqhttp doesn't support send {self.type} by {k}")
|
|
@@ -174,15 +141,16 @@ class Record(BaseMessageComponent):
|
|
|
174
141
|
|
|
175
142
|
Returns:
|
|
176
143
|
str: 语音的本地路径,以绝对路径表示。
|
|
144
|
+
|
|
177
145
|
"""
|
|
178
146
|
if not self.file:
|
|
179
147
|
raise Exception(f"not a valid file: {self.file}")
|
|
180
148
|
if self.file.startswith("file:///"):
|
|
181
149
|
return self.file[8:]
|
|
182
|
-
|
|
150
|
+
if self.file.startswith("http"):
|
|
183
151
|
file_path = await download_image_by_url(self.file)
|
|
184
152
|
return os.path.abspath(file_path)
|
|
185
|
-
|
|
153
|
+
if self.file.startswith("base64://"):
|
|
186
154
|
bs64_data = self.file.removeprefix("base64://")
|
|
187
155
|
image_bytes = base64.b64decode(bs64_data)
|
|
188
156
|
temp_dir = os.path.join(get_astrbot_data_path(), "temp")
|
|
@@ -190,16 +158,16 @@ class Record(BaseMessageComponent):
|
|
|
190
158
|
with open(file_path, "wb") as f:
|
|
191
159
|
f.write(image_bytes)
|
|
192
160
|
return os.path.abspath(file_path)
|
|
193
|
-
|
|
161
|
+
if os.path.exists(self.file):
|
|
194
162
|
return os.path.abspath(self.file)
|
|
195
|
-
|
|
196
|
-
raise Exception(f"not a valid file: {self.file}")
|
|
163
|
+
raise Exception(f"not a valid file: {self.file}")
|
|
197
164
|
|
|
198
165
|
async def convert_to_base64(self) -> str:
|
|
199
166
|
"""将语音统一转换为 base64 编码。这个方法避免了手动判断语音数据类型,直接返回语音数据的 base64 编码。
|
|
200
167
|
|
|
201
168
|
Returns:
|
|
202
169
|
str: 语音的 base64 编码,不以 base64:// 或者 data:image/jpeg;base64, 开头。
|
|
170
|
+
|
|
203
171
|
"""
|
|
204
172
|
# convert to base64
|
|
205
173
|
if not self.file:
|
|
@@ -219,14 +187,14 @@ class Record(BaseMessageComponent):
|
|
|
219
187
|
return bs64_data
|
|
220
188
|
|
|
221
189
|
async def register_to_file_service(self) -> str:
|
|
222
|
-
"""
|
|
223
|
-
将语音注册到文件服务。
|
|
190
|
+
"""将语音注册到文件服务。
|
|
224
191
|
|
|
225
192
|
Returns:
|
|
226
193
|
str: 注册后的URL
|
|
227
194
|
|
|
228
195
|
Raises:
|
|
229
196
|
Exception: 如果未配置 callback_api_base
|
|
197
|
+
|
|
230
198
|
"""
|
|
231
199
|
callback_host = astrbot_config.get("callback_api_base")
|
|
232
200
|
|
|
@@ -245,10 +213,10 @@ class Record(BaseMessageComponent):
|
|
|
245
213
|
class Video(BaseMessageComponent):
|
|
246
214
|
type = ComponentType.Video
|
|
247
215
|
file: str
|
|
248
|
-
cover:
|
|
249
|
-
c:
|
|
216
|
+
cover: str | None = ""
|
|
217
|
+
c: int | None = 2
|
|
250
218
|
# 额外
|
|
251
|
-
path:
|
|
219
|
+
path: str | None = ""
|
|
252
220
|
|
|
253
221
|
def __init__(self, file: str, **_):
|
|
254
222
|
super().__init__(file=file, **_)
|
|
@@ -268,32 +236,31 @@ class Video(BaseMessageComponent):
|
|
|
268
236
|
|
|
269
237
|
Returns:
|
|
270
238
|
str: 视频的本地路径,以绝对路径表示。
|
|
239
|
+
|
|
271
240
|
"""
|
|
272
241
|
url = self.file
|
|
273
242
|
if url and url.startswith("file:///"):
|
|
274
243
|
return url[8:]
|
|
275
|
-
|
|
244
|
+
if url and url.startswith("http"):
|
|
276
245
|
download_dir = os.path.join(get_astrbot_data_path(), "temp")
|
|
277
246
|
video_file_path = os.path.join(download_dir, f"{uuid.uuid4().hex}")
|
|
278
247
|
await download_file(url, video_file_path)
|
|
279
248
|
if os.path.exists(video_file_path):
|
|
280
249
|
return os.path.abspath(video_file_path)
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
elif os.path.exists(url):
|
|
250
|
+
raise Exception(f"download failed: {url}")
|
|
251
|
+
if os.path.exists(url):
|
|
284
252
|
return os.path.abspath(url)
|
|
285
|
-
|
|
286
|
-
raise Exception(f"not a valid file: {url}")
|
|
253
|
+
raise Exception(f"not a valid file: {url}")
|
|
287
254
|
|
|
288
255
|
async def register_to_file_service(self):
|
|
289
|
-
"""
|
|
290
|
-
将视频注册到文件服务。
|
|
256
|
+
"""将视频注册到文件服务。
|
|
291
257
|
|
|
292
258
|
Returns:
|
|
293
259
|
str: 注册后的URL
|
|
294
260
|
|
|
295
261
|
Raises:
|
|
296
262
|
Exception: 如果未配置 callback_api_base
|
|
263
|
+
|
|
297
264
|
"""
|
|
298
265
|
callback_host = astrbot_config.get("callback_api_base")
|
|
299
266
|
|
|
@@ -330,8 +297,8 @@ class Video(BaseMessageComponent):
|
|
|
330
297
|
|
|
331
298
|
class At(BaseMessageComponent):
|
|
332
299
|
type = ComponentType.At
|
|
333
|
-
qq:
|
|
334
|
-
name:
|
|
300
|
+
qq: int | str # 此处str为all时代表所有人
|
|
301
|
+
name: str | None = ""
|
|
335
302
|
|
|
336
303
|
def __init__(self, **_):
|
|
337
304
|
super().__init__(**_)
|
|
@@ -371,20 +338,12 @@ class Shake(BaseMessageComponent): # TODO
|
|
|
371
338
|
super().__init__(**_)
|
|
372
339
|
|
|
373
340
|
|
|
374
|
-
class Anonymous(BaseMessageComponent): # TODO
|
|
375
|
-
type = ComponentType.Anonymous
|
|
376
|
-
ignore: T.Optional[bool] = False
|
|
377
|
-
|
|
378
|
-
def __init__(self, **_):
|
|
379
|
-
super().__init__(**_)
|
|
380
|
-
|
|
381
|
-
|
|
382
341
|
class Share(BaseMessageComponent):
|
|
383
342
|
type = ComponentType.Share
|
|
384
343
|
url: str
|
|
385
344
|
title: str
|
|
386
|
-
content:
|
|
387
|
-
image:
|
|
345
|
+
content: str | None = ""
|
|
346
|
+
image: str | None = ""
|
|
388
347
|
|
|
389
348
|
def __init__(self, **_):
|
|
390
349
|
super().__init__(**_)
|
|
@@ -393,7 +352,7 @@ class Share(BaseMessageComponent):
|
|
|
393
352
|
class Contact(BaseMessageComponent): # TODO
|
|
394
353
|
type = ComponentType.Contact
|
|
395
354
|
_type: str # type 字段冲突
|
|
396
|
-
id:
|
|
355
|
+
id: int | None = 0
|
|
397
356
|
|
|
398
357
|
def __init__(self, **_):
|
|
399
358
|
super().__init__(**_)
|
|
@@ -403,8 +362,8 @@ class Location(BaseMessageComponent): # TODO
|
|
|
403
362
|
type = ComponentType.Location
|
|
404
363
|
lat: float
|
|
405
364
|
lon: float
|
|
406
|
-
title:
|
|
407
|
-
content:
|
|
365
|
+
title: str | None = ""
|
|
366
|
+
content: str | None = ""
|
|
408
367
|
|
|
409
368
|
def __init__(self, **_):
|
|
410
369
|
super().__init__(**_)
|
|
@@ -413,12 +372,12 @@ class Location(BaseMessageComponent): # TODO
|
|
|
413
372
|
class Music(BaseMessageComponent):
|
|
414
373
|
type = ComponentType.Music
|
|
415
374
|
_type: str
|
|
416
|
-
id:
|
|
417
|
-
url:
|
|
418
|
-
audio:
|
|
419
|
-
title:
|
|
420
|
-
content:
|
|
421
|
-
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 = ""
|
|
422
381
|
|
|
423
382
|
def __init__(self, **_):
|
|
424
383
|
# for k in _.keys():
|
|
@@ -429,18 +388,18 @@ class Music(BaseMessageComponent):
|
|
|
429
388
|
|
|
430
389
|
class Image(BaseMessageComponent):
|
|
431
390
|
type = ComponentType.Image
|
|
432
|
-
file:
|
|
433
|
-
_type:
|
|
434
|
-
subType:
|
|
435
|
-
url:
|
|
436
|
-
cache:
|
|
437
|
-
id:
|
|
438
|
-
c:
|
|
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
|
|
439
398
|
# 额外
|
|
440
|
-
path:
|
|
441
|
-
file_unique:
|
|
399
|
+
path: str | None = ""
|
|
400
|
+
file_unique: str | None = "" # 某些平台可能有图片缓存的唯一标识
|
|
442
401
|
|
|
443
|
-
def __init__(self, file:
|
|
402
|
+
def __init__(self, file: str | None, **_):
|
|
444
403
|
super().__init__(file=file, **_)
|
|
445
404
|
|
|
446
405
|
@staticmethod
|
|
@@ -470,16 +429,17 @@ class Image(BaseMessageComponent):
|
|
|
470
429
|
|
|
471
430
|
Returns:
|
|
472
431
|
str: 图片的本地路径,以绝对路径表示。
|
|
432
|
+
|
|
473
433
|
"""
|
|
474
434
|
url = self.url or self.file
|
|
475
435
|
if not url:
|
|
476
436
|
raise ValueError("No valid file or URL provided")
|
|
477
437
|
if url.startswith("file:///"):
|
|
478
438
|
return url[8:]
|
|
479
|
-
|
|
439
|
+
if url.startswith("http"):
|
|
480
440
|
image_file_path = await download_image_by_url(url)
|
|
481
441
|
return os.path.abspath(image_file_path)
|
|
482
|
-
|
|
442
|
+
if url.startswith("base64://"):
|
|
483
443
|
bs64_data = url.removeprefix("base64://")
|
|
484
444
|
image_bytes = base64.b64decode(bs64_data)
|
|
485
445
|
temp_dir = os.path.join(get_astrbot_data_path(), "temp")
|
|
@@ -487,16 +447,16 @@ class Image(BaseMessageComponent):
|
|
|
487
447
|
with open(image_file_path, "wb") as f:
|
|
488
448
|
f.write(image_bytes)
|
|
489
449
|
return os.path.abspath(image_file_path)
|
|
490
|
-
|
|
450
|
+
if os.path.exists(url):
|
|
491
451
|
return os.path.abspath(url)
|
|
492
|
-
|
|
493
|
-
raise Exception(f"not a valid file: {url}")
|
|
452
|
+
raise Exception(f"not a valid file: {url}")
|
|
494
453
|
|
|
495
454
|
async def convert_to_base64(self) -> str:
|
|
496
455
|
"""将这个图片统一转换为 base64 编码。这个方法避免了手动判断图片数据类型,直接返回图片数据的 base64 编码。
|
|
497
456
|
|
|
498
457
|
Returns:
|
|
499
458
|
str: 图片的 base64 编码,不以 base64:// 或者 data:image/jpeg;base64, 开头。
|
|
459
|
+
|
|
500
460
|
"""
|
|
501
461
|
# convert to base64
|
|
502
462
|
url = self.url or self.file
|
|
@@ -517,14 +477,14 @@ class Image(BaseMessageComponent):
|
|
|
517
477
|
return bs64_data
|
|
518
478
|
|
|
519
479
|
async def register_to_file_service(self) -> str:
|
|
520
|
-
"""
|
|
521
|
-
将图片注册到文件服务。
|
|
480
|
+
"""将图片注册到文件服务。
|
|
522
481
|
|
|
523
482
|
Returns:
|
|
524
483
|
str: 注册后的URL
|
|
525
484
|
|
|
526
485
|
Raises:
|
|
527
486
|
Exception: 如果未配置 callback_api_base
|
|
487
|
+
|
|
528
488
|
"""
|
|
529
489
|
callback_host = astrbot_config.get("callback_api_base")
|
|
530
490
|
|
|
@@ -542,42 +502,34 @@ class Image(BaseMessageComponent):
|
|
|
542
502
|
|
|
543
503
|
class Reply(BaseMessageComponent):
|
|
544
504
|
type = ComponentType.Reply
|
|
545
|
-
id:
|
|
505
|
+
id: str | int
|
|
546
506
|
"""所引用的消息 ID"""
|
|
547
|
-
chain:
|
|
507
|
+
chain: list["BaseMessageComponent"] | None = []
|
|
548
508
|
"""被引用的消息段列表"""
|
|
549
|
-
sender_id:
|
|
509
|
+
sender_id: int | None | str = 0
|
|
550
510
|
"""被引用的消息对应的发送者的 ID"""
|
|
551
|
-
sender_nickname:
|
|
511
|
+
sender_nickname: str | None = ""
|
|
552
512
|
"""被引用的消息对应的发送者的昵称"""
|
|
553
|
-
time:
|
|
513
|
+
time: int | None = 0
|
|
554
514
|
"""被引用的消息发送时间"""
|
|
555
|
-
message_str:
|
|
515
|
+
message_str: str | None = ""
|
|
556
516
|
"""被引用的消息解析后的纯文本消息字符串"""
|
|
557
517
|
|
|
558
|
-
text:
|
|
518
|
+
text: str | None = ""
|
|
559
519
|
"""deprecated"""
|
|
560
|
-
qq:
|
|
520
|
+
qq: int | None = 0
|
|
561
521
|
"""deprecated"""
|
|
562
|
-
seq:
|
|
522
|
+
seq: int | None = 0
|
|
563
523
|
"""deprecated"""
|
|
564
524
|
|
|
565
525
|
def __init__(self, **_):
|
|
566
526
|
super().__init__(**_)
|
|
567
527
|
|
|
568
528
|
|
|
569
|
-
class RedBag(BaseMessageComponent):
|
|
570
|
-
type = ComponentType.RedBag
|
|
571
|
-
title: str
|
|
572
|
-
|
|
573
|
-
def __init__(self, **_):
|
|
574
|
-
super().__init__(**_)
|
|
575
|
-
|
|
576
|
-
|
|
577
529
|
class Poke(BaseMessageComponent):
|
|
578
530
|
type: str = ComponentType.Poke
|
|
579
|
-
id:
|
|
580
|
-
qq:
|
|
531
|
+
id: int | None = 0
|
|
532
|
+
qq: int | None = 0
|
|
581
533
|
|
|
582
534
|
def __init__(self, type: str, **_):
|
|
583
535
|
type = f"Poke:{type}"
|
|
@@ -596,12 +548,12 @@ class Node(BaseMessageComponent):
|
|
|
596
548
|
"""群合并转发消息"""
|
|
597
549
|
|
|
598
550
|
type = ComponentType.Node
|
|
599
|
-
id:
|
|
600
|
-
name:
|
|
601
|
-
uin:
|
|
602
|
-
content:
|
|
603
|
-
seq:
|
|
604
|
-
time:
|
|
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 # 忽略
|
|
605
557
|
|
|
606
558
|
def __init__(self, content: list[BaseMessageComponent], **_):
|
|
607
559
|
if isinstance(content, Node):
|
|
@@ -619,7 +571,7 @@ class Node(BaseMessageComponent):
|
|
|
619
571
|
{
|
|
620
572
|
"type": comp.type.lower(),
|
|
621
573
|
"data": {"file": f"base64://{bs64}"},
|
|
622
|
-
}
|
|
574
|
+
},
|
|
623
575
|
)
|
|
624
576
|
elif isinstance(comp, Plain):
|
|
625
577
|
# For Plain segments, we need to handle the plain differently
|
|
@@ -648,9 +600,9 @@ class Node(BaseMessageComponent):
|
|
|
648
600
|
|
|
649
601
|
class Nodes(BaseMessageComponent):
|
|
650
602
|
type = ComponentType.Nodes
|
|
651
|
-
nodes:
|
|
603
|
+
nodes: list[Node]
|
|
652
604
|
|
|
653
|
-
def __init__(self, nodes:
|
|
605
|
+
def __init__(self, nodes: list[Node], **_):
|
|
654
606
|
super().__init__(nodes=nodes, **_)
|
|
655
607
|
|
|
656
608
|
def toDict(self):
|
|
@@ -672,19 +624,10 @@ class Nodes(BaseMessageComponent):
|
|
|
672
624
|
return ret
|
|
673
625
|
|
|
674
626
|
|
|
675
|
-
class Xml(BaseMessageComponent):
|
|
676
|
-
type = ComponentType.Xml
|
|
677
|
-
data: str
|
|
678
|
-
resid: T.Optional[int] = 0
|
|
679
|
-
|
|
680
|
-
def __init__(self, **_):
|
|
681
|
-
super().__init__(**_)
|
|
682
|
-
|
|
683
|
-
|
|
684
627
|
class Json(BaseMessageComponent):
|
|
685
628
|
type = ComponentType.Json
|
|
686
|
-
data:
|
|
687
|
-
resid:
|
|
629
|
+
data: str | dict
|
|
630
|
+
resid: int | None = 0
|
|
688
631
|
|
|
689
632
|
def __init__(self, data, **_):
|
|
690
633
|
if isinstance(data, dict):
|
|
@@ -692,50 +635,18 @@ class Json(BaseMessageComponent):
|
|
|
692
635
|
super().__init__(data=data, **_)
|
|
693
636
|
|
|
694
637
|
|
|
695
|
-
class CardImage(BaseMessageComponent):
|
|
696
|
-
type = ComponentType.CardImage
|
|
697
|
-
file: str
|
|
698
|
-
cache: T.Optional[bool] = True
|
|
699
|
-
minwidth: T.Optional[int] = 400
|
|
700
|
-
minheight: T.Optional[int] = 400
|
|
701
|
-
maxwidth: T.Optional[int] = 500
|
|
702
|
-
maxheight: T.Optional[int] = 500
|
|
703
|
-
source: T.Optional[str] = ""
|
|
704
|
-
icon: T.Optional[str] = ""
|
|
705
|
-
|
|
706
|
-
def __init__(self, **_):
|
|
707
|
-
super().__init__(**_)
|
|
708
|
-
|
|
709
|
-
@staticmethod
|
|
710
|
-
def fromFileSystem(path, **_):
|
|
711
|
-
return CardImage(file=f"file:///{os.path.abspath(path)}", **_)
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
class TTS(BaseMessageComponent):
|
|
715
|
-
type = ComponentType.TTS
|
|
716
|
-
text: str
|
|
717
|
-
|
|
718
|
-
def __init__(self, **_):
|
|
719
|
-
super().__init__(**_)
|
|
720
|
-
|
|
721
|
-
|
|
722
638
|
class Unknown(BaseMessageComponent):
|
|
723
639
|
type = ComponentType.Unknown
|
|
724
640
|
text: str
|
|
725
641
|
|
|
726
|
-
def toString(self):
|
|
727
|
-
return ""
|
|
728
|
-
|
|
729
642
|
|
|
730
643
|
class File(BaseMessageComponent):
|
|
731
|
-
"""
|
|
732
|
-
文件消息段
|
|
733
|
-
"""
|
|
644
|
+
"""文件消息段"""
|
|
734
645
|
|
|
735
646
|
type = ComponentType.File
|
|
736
|
-
name:
|
|
737
|
-
file_:
|
|
738
|
-
url:
|
|
647
|
+
name: str | None = "" # 名字
|
|
648
|
+
file_: str | None = "" # 本地路径
|
|
649
|
+
url: str | None = "" # url
|
|
739
650
|
|
|
740
651
|
def __init__(self, name: str, file: str = "", url: str = ""):
|
|
741
652
|
"""文件消息段。"""
|
|
@@ -743,11 +654,11 @@ class File(BaseMessageComponent):
|
|
|
743
654
|
|
|
744
655
|
@property
|
|
745
656
|
def file(self) -> str:
|
|
746
|
-
"""
|
|
747
|
-
获取文件路径,如果文件不存在但有URL,则同步下载文件
|
|
657
|
+
"""获取文件路径,如果文件不存在但有URL,则同步下载文件
|
|
748
658
|
|
|
749
659
|
Returns:
|
|
750
660
|
str: 文件路径
|
|
661
|
+
|
|
751
662
|
"""
|
|
752
663
|
if self.file_ and os.path.exists(self.file_):
|
|
753
664
|
return os.path.abspath(self.file_)
|
|
@@ -757,19 +668,16 @@ class File(BaseMessageComponent):
|
|
|
757
668
|
loop = asyncio.get_event_loop()
|
|
758
669
|
if loop.is_running():
|
|
759
670
|
logger.warning(
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
"请使用 await get_file() 代替直接获取 <File>.file 字段"
|
|
764
|
-
)
|
|
671
|
+
"不可以在异步上下文中同步等待下载! "
|
|
672
|
+
"这个警告通常发生于某些逻辑试图通过 <File>.file 获取文件消息段的文件内容。"
|
|
673
|
+
"请使用 await get_file() 代替直接获取 <File>.file 字段",
|
|
765
674
|
)
|
|
766
675
|
return ""
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
loop.run_until_complete(self._download_file())
|
|
676
|
+
# 等待下载完成
|
|
677
|
+
loop.run_until_complete(self._download_file())
|
|
770
678
|
|
|
771
|
-
|
|
772
|
-
|
|
679
|
+
if self.file_ and os.path.exists(self.file_):
|
|
680
|
+
return os.path.abspath(self.file_)
|
|
773
681
|
except Exception as e:
|
|
774
682
|
logger.error(f"文件下载失败: {e}")
|
|
775
683
|
|
|
@@ -777,11 +685,11 @@ class File(BaseMessageComponent):
|
|
|
777
685
|
|
|
778
686
|
@file.setter
|
|
779
687
|
def file(self, value: str):
|
|
780
|
-
"""
|
|
781
|
-
向前兼容, 设置file属性, 传入的参数可能是文件路径或URL
|
|
688
|
+
"""向前兼容, 设置file属性, 传入的参数可能是文件路径或URL
|
|
782
689
|
|
|
783
690
|
Args:
|
|
784
691
|
value (str): 文件路径或URL
|
|
692
|
+
|
|
785
693
|
"""
|
|
786
694
|
if value.startswith("http://") or value.startswith("https://"):
|
|
787
695
|
self.url = value
|
|
@@ -796,6 +704,7 @@ class File(BaseMessageComponent):
|
|
|
796
704
|
注意,如果为 True,也可能返回文件路径。
|
|
797
705
|
Returns:
|
|
798
706
|
str: 文件路径或者 http 下载链接
|
|
707
|
+
|
|
799
708
|
"""
|
|
800
709
|
if allow_return_url and self.url:
|
|
801
710
|
return self.url
|
|
@@ -818,14 +727,14 @@ class File(BaseMessageComponent):
|
|
|
818
727
|
self.file_ = os.path.abspath(file_path)
|
|
819
728
|
|
|
820
729
|
async def register_to_file_service(self):
|
|
821
|
-
"""
|
|
822
|
-
将文件注册到文件服务。
|
|
730
|
+
"""将文件注册到文件服务。
|
|
823
731
|
|
|
824
732
|
Returns:
|
|
825
733
|
str: 注册后的URL
|
|
826
734
|
|
|
827
735
|
Raises:
|
|
828
736
|
Exception: 如果未配置 callback_api_base
|
|
737
|
+
|
|
829
738
|
"""
|
|
830
739
|
callback_host = astrbot_config.get("callback_api_base")
|
|
831
740
|
|
|
@@ -863,41 +772,38 @@ class File(BaseMessageComponent):
|
|
|
863
772
|
|
|
864
773
|
class WechatEmoji(BaseMessageComponent):
|
|
865
774
|
type = ComponentType.WechatEmoji
|
|
866
|
-
md5:
|
|
867
|
-
md5_len:
|
|
868
|
-
cdnurl:
|
|
775
|
+
md5: str | None = ""
|
|
776
|
+
md5_len: int | None = 0
|
|
777
|
+
cdnurl: str | None = ""
|
|
869
778
|
|
|
870
779
|
def __init__(self, **_):
|
|
871
780
|
super().__init__(**_)
|
|
872
781
|
|
|
873
782
|
|
|
874
783
|
ComponentTypes = {
|
|
784
|
+
# Basic Message Segments
|
|
875
785
|
"plain": Plain,
|
|
876
786
|
"text": Plain,
|
|
877
|
-
"
|
|
787
|
+
"image": Image,
|
|
878
788
|
"record": Record,
|
|
879
789
|
"video": Video,
|
|
790
|
+
"file": File,
|
|
791
|
+
# IM-specific Message Segments
|
|
792
|
+
"face": Face,
|
|
880
793
|
"at": At,
|
|
881
794
|
"rps": RPS,
|
|
882
795
|
"dice": Dice,
|
|
883
796
|
"shake": Shake,
|
|
884
|
-
"anonymous": Anonymous,
|
|
885
797
|
"share": Share,
|
|
886
798
|
"contact": Contact,
|
|
887
799
|
"location": Location,
|
|
888
800
|
"music": Music,
|
|
889
|
-
"image": Image,
|
|
890
801
|
"reply": Reply,
|
|
891
|
-
"redbag": RedBag,
|
|
892
802
|
"poke": Poke,
|
|
893
803
|
"forward": Forward,
|
|
894
804
|
"node": Node,
|
|
895
805
|
"nodes": Nodes,
|
|
896
|
-
"xml": Xml,
|
|
897
806
|
"json": Json,
|
|
898
|
-
"cardimage": CardImage,
|
|
899
|
-
"tts": TTS,
|
|
900
807
|
"unknown": Unknown,
|
|
901
|
-
"file": File,
|
|
902
808
|
"WechatEmoji": WechatEmoji,
|
|
903
809
|
}
|