AstrBot 4.0.0b5__py3-none-any.whl → 4.1.1__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/event/filter/__init__.py +2 -0
- astrbot/core/config/default.py +73 -3
- astrbot/core/initial_loader.py +4 -1
- astrbot/core/message/components.py +59 -50
- astrbot/core/pipeline/result_decorate/stage.py +5 -1
- astrbot/core/platform/manager.py +25 -3
- astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py +11 -4
- astrbot/core/platform/sources/satori/satori_adapter.py +482 -0
- astrbot/core/platform/sources/satori/satori_event.py +221 -0
- astrbot/core/platform/sources/telegram/tg_adapter.py +0 -1
- astrbot/core/provider/sources/openai_source.py +14 -5
- astrbot/core/provider/sources/vllm_rerank_source.py +6 -0
- astrbot/core/star/__init__.py +7 -5
- astrbot/core/star/filter/command.py +9 -3
- astrbot/core/star/filter/platform_adapter_type.py +3 -0
- astrbot/core/star/register/__init__.py +2 -0
- astrbot/core/star/register/star_handler.py +18 -4
- astrbot/core/star/star_handler.py +9 -1
- astrbot/core/star/star_tools.py +116 -21
- astrbot/core/utils/t2i/network_strategy.py +11 -18
- astrbot/core/utils/t2i/renderer.py +8 -2
- astrbot/core/utils/t2i/template/astrbot_powershell.html +184 -0
- astrbot/core/utils/t2i/template_manager.py +112 -0
- astrbot/dashboard/routes/chat.py +6 -1
- astrbot/dashboard/routes/config.py +10 -49
- astrbot/dashboard/routes/route.py +19 -2
- astrbot/dashboard/routes/t2i.py +230 -0
- astrbot/dashboard/server.py +13 -4
- {astrbot-4.0.0b5.dist-info → astrbot-4.1.1.dist-info}/METADATA +39 -52
- {astrbot-4.0.0b5.dist-info → astrbot-4.1.1.dist-info}/RECORD +33 -28
- {astrbot-4.0.0b5.dist-info → astrbot-4.1.1.dist-info}/WHEEL +0 -0
- {astrbot-4.0.0b5.dist-info → astrbot-4.1.1.dist-info}/entry_points.txt +0 -0
- {astrbot-4.0.0b5.dist-info → astrbot-4.1.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -7,6 +7,7 @@ from astrbot.core.star.register import (
|
|
|
7
7
|
register_permission_type as permission_type,
|
|
8
8
|
register_custom_filter as custom_filter,
|
|
9
9
|
register_on_astrbot_loaded as on_astrbot_loaded,
|
|
10
|
+
register_on_platform_loaded as on_platform_loaded,
|
|
10
11
|
register_on_llm_request as on_llm_request,
|
|
11
12
|
register_on_llm_response as on_llm_response,
|
|
12
13
|
register_llm_tool as llm_tool,
|
|
@@ -41,6 +42,7 @@ __all__ = [
|
|
|
41
42
|
"custom_filter",
|
|
42
43
|
"PermissionType",
|
|
43
44
|
"on_astrbot_loaded",
|
|
45
|
+
"on_platform_loaded",
|
|
44
46
|
"on_llm_request",
|
|
45
47
|
"llm_tool",
|
|
46
48
|
"on_decorating_result",
|
astrbot/core/config/default.py
CHANGED
|
@@ -6,7 +6,7 @@ import os
|
|
|
6
6
|
|
|
7
7
|
from astrbot.core.utils.astrbot_path import get_astrbot_data_path
|
|
8
8
|
|
|
9
|
-
VERSION = "4.
|
|
9
|
+
VERSION = "4.1.1"
|
|
10
10
|
DB_PATH = os.path.join(get_astrbot_data_path(), "data_v4.db")
|
|
11
11
|
|
|
12
12
|
# 默认配置
|
|
@@ -56,7 +56,7 @@ DEFAULT_CONFIG = {
|
|
|
56
56
|
"wake_prefix": "",
|
|
57
57
|
"web_search": False,
|
|
58
58
|
"websearch_provider": "default",
|
|
59
|
-
"websearch_tavily_key":
|
|
59
|
+
"websearch_tavily_key": [],
|
|
60
60
|
"web_search_link": False,
|
|
61
61
|
"display_reasoning_text": False,
|
|
62
62
|
"identifier": False,
|
|
@@ -103,6 +103,7 @@ DEFAULT_CONFIG = {
|
|
|
103
103
|
"t2i_strategy": "remote",
|
|
104
104
|
"t2i_endpoint": "",
|
|
105
105
|
"t2i_use_file_service": False,
|
|
106
|
+
"t2i_active_template": "base",
|
|
106
107
|
"http_proxy": "",
|
|
107
108
|
"no_proxy": ["localhost", "127.0.0.1", "::1"],
|
|
108
109
|
"dashboard": {
|
|
@@ -246,8 +247,49 @@ CONFIG_METADATA_2 = {
|
|
|
246
247
|
"slack_webhook_port": 6197,
|
|
247
248
|
"slack_webhook_path": "/astrbot-slack-webhook/callback",
|
|
248
249
|
},
|
|
250
|
+
"Satori": {
|
|
251
|
+
"id": "satori",
|
|
252
|
+
"type": "satori",
|
|
253
|
+
"enable": False,
|
|
254
|
+
"satori_api_base_url": "http://localhost:5140/satori/v1",
|
|
255
|
+
"satori_endpoint": "ws://127.0.0.1:5140/satori/v1/events",
|
|
256
|
+
"satori_token": "",
|
|
257
|
+
"satori_auto_reconnect": True,
|
|
258
|
+
"satori_heartbeat_interval": 10,
|
|
259
|
+
"satori_reconnect_delay": 5,
|
|
260
|
+
},
|
|
249
261
|
},
|
|
250
262
|
"items": {
|
|
263
|
+
"satori_api_base_url": {
|
|
264
|
+
"description": "Satori API Base URL",
|
|
265
|
+
"type": "string",
|
|
266
|
+
"hint": "The base URL for the Satori API.",
|
|
267
|
+
},
|
|
268
|
+
"satori_endpoint": {
|
|
269
|
+
"description": "Satori WebSocket Endpoint",
|
|
270
|
+
"type": "string",
|
|
271
|
+
"hint": "The WebSocket endpoint for Satori events.",
|
|
272
|
+
},
|
|
273
|
+
"satori_token": {
|
|
274
|
+
"description": "Satori Token",
|
|
275
|
+
"type": "string",
|
|
276
|
+
"hint": "The token used for authenticating with the Satori API.",
|
|
277
|
+
},
|
|
278
|
+
"satori_auto_reconnect": {
|
|
279
|
+
"description": "Enable Auto Reconnect",
|
|
280
|
+
"type": "bool",
|
|
281
|
+
"hint": "Whether to automatically reconnect the WebSocket on disconnection.",
|
|
282
|
+
},
|
|
283
|
+
"satori_heartbeat_interval": {
|
|
284
|
+
"description": "Satori Heartbeat Interval",
|
|
285
|
+
"type": "int",
|
|
286
|
+
"hint": "The interval (in seconds) for sending heartbeat messages.",
|
|
287
|
+
},
|
|
288
|
+
"satori_reconnect_delay": {
|
|
289
|
+
"description": "Satori Reconnect Delay",
|
|
290
|
+
"type": "int",
|
|
291
|
+
"hint": "The delay (in seconds) before attempting to reconnect.",
|
|
292
|
+
},
|
|
251
293
|
"slack_connection_mode": {
|
|
252
294
|
"description": "Slack Connection Mode",
|
|
253
295
|
"type": "string",
|
|
@@ -557,6 +599,7 @@ CONFIG_METADATA_2 = {
|
|
|
557
599
|
"api_base": "https://api.openai.com/v1",
|
|
558
600
|
"timeout": 120,
|
|
559
601
|
"model_config": {"model": "gpt-4o-mini", "temperature": 0.4},
|
|
602
|
+
"custom_extra_body": {},
|
|
560
603
|
"modalities": ["text", "image", "tool_use"],
|
|
561
604
|
"hint": "也兼容所有与 OpenAI API 兼容的服务。",
|
|
562
605
|
},
|
|
@@ -571,6 +614,7 @@ CONFIG_METADATA_2 = {
|
|
|
571
614
|
"api_base": "",
|
|
572
615
|
"timeout": 120,
|
|
573
616
|
"model_config": {"model": "gpt-4o-mini", "temperature": 0.4},
|
|
617
|
+
"custom_extra_body": {},
|
|
574
618
|
"modalities": ["text", "image", "tool_use"],
|
|
575
619
|
},
|
|
576
620
|
"xAI": {
|
|
@@ -583,6 +627,7 @@ CONFIG_METADATA_2 = {
|
|
|
583
627
|
"api_base": "https://api.x.ai/v1",
|
|
584
628
|
"timeout": 120,
|
|
585
629
|
"model_config": {"model": "grok-2-latest", "temperature": 0.4},
|
|
630
|
+
"custom_extra_body": {},
|
|
586
631
|
"modalities": ["text", "image", "tool_use"],
|
|
587
632
|
},
|
|
588
633
|
"Anthropic": {
|
|
@@ -612,6 +657,7 @@ CONFIG_METADATA_2 = {
|
|
|
612
657
|
"key": ["ollama"], # ollama 的 key 默认是 ollama
|
|
613
658
|
"api_base": "http://localhost:11434/v1",
|
|
614
659
|
"model_config": {"model": "llama3.1-8b", "temperature": 0.4},
|
|
660
|
+
"custom_extra_body": {},
|
|
615
661
|
"modalities": ["text", "image", "tool_use"],
|
|
616
662
|
},
|
|
617
663
|
"LM Studio": {
|
|
@@ -625,6 +671,7 @@ CONFIG_METADATA_2 = {
|
|
|
625
671
|
"model_config": {
|
|
626
672
|
"model": "llama-3.1-8b",
|
|
627
673
|
},
|
|
674
|
+
"custom_extra_body": {},
|
|
628
675
|
"modalities": ["text", "image", "tool_use"],
|
|
629
676
|
},
|
|
630
677
|
"Gemini(OpenAI兼容)": {
|
|
@@ -640,6 +687,7 @@ CONFIG_METADATA_2 = {
|
|
|
640
687
|
"model": "gemini-1.5-flash",
|
|
641
688
|
"temperature": 0.4,
|
|
642
689
|
},
|
|
690
|
+
"custom_extra_body": {},
|
|
643
691
|
"modalities": ["text", "image", "tool_use"],
|
|
644
692
|
},
|
|
645
693
|
"Gemini": {
|
|
@@ -680,6 +728,7 @@ CONFIG_METADATA_2 = {
|
|
|
680
728
|
"api_base": "https://api.deepseek.com/v1",
|
|
681
729
|
"timeout": 120,
|
|
682
730
|
"model_config": {"model": "deepseek-chat", "temperature": 0.4},
|
|
731
|
+
"custom_extra_body": {},
|
|
683
732
|
"modalities": ["text", "image", "tool_use"],
|
|
684
733
|
},
|
|
685
734
|
"302.AI": {
|
|
@@ -692,6 +741,7 @@ CONFIG_METADATA_2 = {
|
|
|
692
741
|
"api_base": "https://api.302.ai/v1",
|
|
693
742
|
"timeout": 120,
|
|
694
743
|
"model_config": {"model": "gpt-4.1-mini", "temperature": 0.4},
|
|
744
|
+
"custom_extra_body": {},
|
|
695
745
|
"modalities": ["text", "image", "tool_use"],
|
|
696
746
|
},
|
|
697
747
|
"硅基流动": {
|
|
@@ -707,6 +757,7 @@ CONFIG_METADATA_2 = {
|
|
|
707
757
|
"model": "deepseek-ai/DeepSeek-V3",
|
|
708
758
|
"temperature": 0.4,
|
|
709
759
|
},
|
|
760
|
+
"custom_extra_body": {},
|
|
710
761
|
"modalities": ["text", "image", "tool_use"],
|
|
711
762
|
},
|
|
712
763
|
"PPIO派欧云": {
|
|
@@ -722,6 +773,7 @@ CONFIG_METADATA_2 = {
|
|
|
722
773
|
"model": "deepseek/deepseek-r1",
|
|
723
774
|
"temperature": 0.4,
|
|
724
775
|
},
|
|
776
|
+
"custom_extra_body": {},
|
|
725
777
|
},
|
|
726
778
|
"优云智算": {
|
|
727
779
|
"id": "compshare",
|
|
@@ -735,6 +787,7 @@ CONFIG_METADATA_2 = {
|
|
|
735
787
|
"model_config": {
|
|
736
788
|
"model": "moonshotai/Kimi-K2-Instruct",
|
|
737
789
|
},
|
|
790
|
+
"custom_extra_body": {},
|
|
738
791
|
"modalities": ["text", "image", "tool_use"],
|
|
739
792
|
},
|
|
740
793
|
"Kimi": {
|
|
@@ -747,6 +800,7 @@ CONFIG_METADATA_2 = {
|
|
|
747
800
|
"timeout": 120,
|
|
748
801
|
"api_base": "https://api.moonshot.cn/v1",
|
|
749
802
|
"model_config": {"model": "moonshot-v1-8k", "temperature": 0.4},
|
|
803
|
+
"custom_extra_body": {},
|
|
750
804
|
"modalities": ["text", "image", "tool_use"],
|
|
751
805
|
},
|
|
752
806
|
"智谱 AI": {
|
|
@@ -805,6 +859,7 @@ CONFIG_METADATA_2 = {
|
|
|
805
859
|
"timeout": 120,
|
|
806
860
|
"api_base": "https://api-inference.modelscope.cn/v1",
|
|
807
861
|
"model_config": {"model": "Qwen/Qwen3-32B", "temperature": 0.4},
|
|
862
|
+
"custom_extra_body": {},
|
|
808
863
|
"modalities": ["text", "image", "tool_use"],
|
|
809
864
|
},
|
|
810
865
|
"FastGPT": {
|
|
@@ -816,6 +871,7 @@ CONFIG_METADATA_2 = {
|
|
|
816
871
|
"key": [],
|
|
817
872
|
"api_base": "https://api.fastgpt.in/api/v1",
|
|
818
873
|
"timeout": 60,
|
|
874
|
+
"custom_extra_body": {},
|
|
819
875
|
},
|
|
820
876
|
"Whisper(API)": {
|
|
821
877
|
"id": "whisper",
|
|
@@ -1060,6 +1116,12 @@ CONFIG_METADATA_2 = {
|
|
|
1060
1116
|
"render_type": "checkbox",
|
|
1061
1117
|
"hint": "模型支持的模态。如所填写的模型不支持图像,请取消勾选图像。",
|
|
1062
1118
|
},
|
|
1119
|
+
"custom_extra_body": {
|
|
1120
|
+
"description": "自定义请求体参数",
|
|
1121
|
+
"type": "dict",
|
|
1122
|
+
"items": {},
|
|
1123
|
+
"hint": "此处添加的键值对将被合并到发送给 API 的 extra_body 中。值可以是字符串、数字或布尔值。",
|
|
1124
|
+
},
|
|
1063
1125
|
"provider": {
|
|
1064
1126
|
"type": "string",
|
|
1065
1127
|
"invisible": True,
|
|
@@ -1896,7 +1958,9 @@ CONFIG_METADATA_3 = {
|
|
|
1896
1958
|
},
|
|
1897
1959
|
"provider_settings.websearch_tavily_key": {
|
|
1898
1960
|
"description": "Tavily API Key",
|
|
1899
|
-
"type": "
|
|
1961
|
+
"type": "list",
|
|
1962
|
+
"items": {"type": "string"},
|
|
1963
|
+
"hint": "可添加多个 Key 进行轮询。",
|
|
1900
1964
|
"condition": {
|
|
1901
1965
|
"provider_settings.websearch_provider": "tavily",
|
|
1902
1966
|
},
|
|
@@ -2293,6 +2357,12 @@ CONFIG_METADATA_3_SYSTEM = {
|
|
|
2293
2357
|
},
|
|
2294
2358
|
"_special": "t2i_template",
|
|
2295
2359
|
},
|
|
2360
|
+
"t2i_active_template": {
|
|
2361
|
+
"description": "当前应用的文转图渲染模板",
|
|
2362
|
+
"type": "string",
|
|
2363
|
+
"hint": "此处的值由文转图模板管理页面进行维护。",
|
|
2364
|
+
"invisible": True,
|
|
2365
|
+
},
|
|
2296
2366
|
"log_level": {
|
|
2297
2367
|
"description": "控制台日志级别",
|
|
2298
2368
|
"type": "string",
|
astrbot/core/initial_loader.py
CHANGED
|
@@ -22,6 +22,7 @@ class InitialLoader:
|
|
|
22
22
|
self.db = db
|
|
23
23
|
self.logger = logger
|
|
24
24
|
self.log_broker = log_broker
|
|
25
|
+
self.webui_dir: str | None = None
|
|
25
26
|
|
|
26
27
|
async def start(self):
|
|
27
28
|
core_lifecycle = AstrBotCoreLifecycle(self.log_broker, self.db)
|
|
@@ -35,8 +36,10 @@ class InitialLoader:
|
|
|
35
36
|
|
|
36
37
|
core_task = core_lifecycle.start()
|
|
37
38
|
|
|
39
|
+
webui_dir = self.webui_dir
|
|
40
|
+
|
|
38
41
|
self.dashboard_server = AstrBotDashboard(
|
|
39
|
-
core_lifecycle, self.db, core_lifecycle.dashboard_shutdown_event
|
|
42
|
+
core_lifecycle, self.db, core_lifecycle.dashboard_shutdown_event, webui_dir
|
|
40
43
|
)
|
|
41
44
|
task = asyncio.gather(
|
|
42
45
|
core_task, self.dashboard_server.run()
|
|
@@ -37,7 +37,7 @@ from astrbot.core.utils.astrbot_path import get_astrbot_data_path
|
|
|
37
37
|
from astrbot.core.utils.io import download_file, download_image_by_url, file_to_base64
|
|
38
38
|
|
|
39
39
|
|
|
40
|
-
class ComponentType(Enum):
|
|
40
|
+
class ComponentType(str, Enum):
|
|
41
41
|
Plain = "Plain" # 纯文本消息
|
|
42
42
|
Face = "Face" # QQ表情
|
|
43
43
|
Record = "Record" # 语音
|
|
@@ -108,7 +108,7 @@ class BaseMessageComponent(BaseModel):
|
|
|
108
108
|
|
|
109
109
|
|
|
110
110
|
class Plain(BaseMessageComponent):
|
|
111
|
-
type
|
|
111
|
+
type = ComponentType.Plain
|
|
112
112
|
text: str
|
|
113
113
|
convert: T.Optional[bool] = True # 若为 False 则直接发送未转换 CQ 码的消息
|
|
114
114
|
|
|
@@ -128,8 +128,9 @@ class Plain(BaseMessageComponent):
|
|
|
128
128
|
async def to_dict(self):
|
|
129
129
|
return {"type": "text", "data": {"text": self.text}}
|
|
130
130
|
|
|
131
|
+
|
|
131
132
|
class Face(BaseMessageComponent):
|
|
132
|
-
type
|
|
133
|
+
type = ComponentType.Face
|
|
133
134
|
id: int
|
|
134
135
|
|
|
135
136
|
def __init__(self, **_):
|
|
@@ -137,7 +138,7 @@ class Face(BaseMessageComponent):
|
|
|
137
138
|
|
|
138
139
|
|
|
139
140
|
class Record(BaseMessageComponent):
|
|
140
|
-
type
|
|
141
|
+
type = ComponentType.Record
|
|
141
142
|
file: T.Optional[str] = ""
|
|
142
143
|
magic: T.Optional[bool] = False
|
|
143
144
|
url: T.Optional[str] = ""
|
|
@@ -164,19 +165,24 @@ class Record(BaseMessageComponent):
|
|
|
164
165
|
return Record(file=url, **_)
|
|
165
166
|
raise Exception("not a valid url")
|
|
166
167
|
|
|
168
|
+
@staticmethod
|
|
169
|
+
def fromBase64(bs64_data: str, **_):
|
|
170
|
+
return Record(file=f"base64://{bs64_data}", **_)
|
|
171
|
+
|
|
167
172
|
async def convert_to_file_path(self) -> str:
|
|
168
173
|
"""将这个语音统一转换为本地文件路径。这个方法避免了手动判断语音数据类型,直接返回语音数据的本地路径(如果是网络 URL, 则会自动进行下载)。
|
|
169
174
|
|
|
170
175
|
Returns:
|
|
171
176
|
str: 语音的本地路径,以绝对路径表示。
|
|
172
177
|
"""
|
|
173
|
-
if
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
178
|
+
if not self.file:
|
|
179
|
+
raise Exception(f"not a valid file: {self.file}")
|
|
180
|
+
if self.file.startswith("file:///"):
|
|
181
|
+
return self.file[8:]
|
|
182
|
+
elif self.file.startswith("http"):
|
|
177
183
|
file_path = await download_image_by_url(self.file)
|
|
178
184
|
return os.path.abspath(file_path)
|
|
179
|
-
elif self.file
|
|
185
|
+
elif self.file.startswith("base64://"):
|
|
180
186
|
bs64_data = self.file.removeprefix("base64://")
|
|
181
187
|
image_bytes = base64.b64decode(bs64_data)
|
|
182
188
|
temp_dir = os.path.join(get_astrbot_data_path(), "temp")
|
|
@@ -185,8 +191,7 @@ class Record(BaseMessageComponent):
|
|
|
185
191
|
f.write(image_bytes)
|
|
186
192
|
return os.path.abspath(file_path)
|
|
187
193
|
elif os.path.exists(self.file):
|
|
188
|
-
|
|
189
|
-
return os.path.abspath(file_path)
|
|
194
|
+
return os.path.abspath(self.file)
|
|
190
195
|
else:
|
|
191
196
|
raise Exception(f"not a valid file: {self.file}")
|
|
192
197
|
|
|
@@ -197,12 +202,14 @@ class Record(BaseMessageComponent):
|
|
|
197
202
|
str: 语音的 base64 编码,不以 base64:// 或者 data:image/jpeg;base64, 开头。
|
|
198
203
|
"""
|
|
199
204
|
# convert to base64
|
|
200
|
-
if
|
|
205
|
+
if not self.file:
|
|
206
|
+
raise Exception(f"not a valid file: {self.file}")
|
|
207
|
+
if self.file.startswith("file:///"):
|
|
201
208
|
bs64_data = file_to_base64(self.file[8:])
|
|
202
|
-
elif self.file
|
|
209
|
+
elif self.file.startswith("http"):
|
|
203
210
|
file_path = await download_image_by_url(self.file)
|
|
204
211
|
bs64_data = file_to_base64(file_path)
|
|
205
|
-
elif self.file
|
|
212
|
+
elif self.file.startswith("base64://"):
|
|
206
213
|
bs64_data = self.file
|
|
207
214
|
elif os.path.exists(self.file):
|
|
208
215
|
bs64_data = file_to_base64(self.file)
|
|
@@ -236,7 +243,7 @@ class Record(BaseMessageComponent):
|
|
|
236
243
|
|
|
237
244
|
|
|
238
245
|
class Video(BaseMessageComponent):
|
|
239
|
-
type
|
|
246
|
+
type = ComponentType.Video
|
|
240
247
|
file: str
|
|
241
248
|
cover: T.Optional[str] = ""
|
|
242
249
|
c: T.Optional[int] = 2
|
|
@@ -322,7 +329,7 @@ class Video(BaseMessageComponent):
|
|
|
322
329
|
|
|
323
330
|
|
|
324
331
|
class At(BaseMessageComponent):
|
|
325
|
-
type
|
|
332
|
+
type = ComponentType.At
|
|
326
333
|
qq: T.Union[int, str] # 此处str为all时代表所有人
|
|
327
334
|
name: T.Optional[str] = ""
|
|
328
335
|
|
|
@@ -344,28 +351,28 @@ class AtAll(At):
|
|
|
344
351
|
|
|
345
352
|
|
|
346
353
|
class RPS(BaseMessageComponent): # TODO
|
|
347
|
-
type
|
|
354
|
+
type = ComponentType.RPS
|
|
348
355
|
|
|
349
356
|
def __init__(self, **_):
|
|
350
357
|
super().__init__(**_)
|
|
351
358
|
|
|
352
359
|
|
|
353
360
|
class Dice(BaseMessageComponent): # TODO
|
|
354
|
-
type
|
|
361
|
+
type = ComponentType.Dice
|
|
355
362
|
|
|
356
363
|
def __init__(self, **_):
|
|
357
364
|
super().__init__(**_)
|
|
358
365
|
|
|
359
366
|
|
|
360
367
|
class Shake(BaseMessageComponent): # TODO
|
|
361
|
-
type
|
|
368
|
+
type = ComponentType.Shake
|
|
362
369
|
|
|
363
370
|
def __init__(self, **_):
|
|
364
371
|
super().__init__(**_)
|
|
365
372
|
|
|
366
373
|
|
|
367
374
|
class Anonymous(BaseMessageComponent): # TODO
|
|
368
|
-
type
|
|
375
|
+
type = ComponentType.Anonymous
|
|
369
376
|
ignore: T.Optional[bool] = False
|
|
370
377
|
|
|
371
378
|
def __init__(self, **_):
|
|
@@ -373,7 +380,7 @@ class Anonymous(BaseMessageComponent): # TODO
|
|
|
373
380
|
|
|
374
381
|
|
|
375
382
|
class Share(BaseMessageComponent):
|
|
376
|
-
type
|
|
383
|
+
type = ComponentType.Share
|
|
377
384
|
url: str
|
|
378
385
|
title: str
|
|
379
386
|
content: T.Optional[str] = ""
|
|
@@ -384,7 +391,7 @@ class Share(BaseMessageComponent):
|
|
|
384
391
|
|
|
385
392
|
|
|
386
393
|
class Contact(BaseMessageComponent): # TODO
|
|
387
|
-
type
|
|
394
|
+
type = ComponentType.Contact
|
|
388
395
|
_type: str # type 字段冲突
|
|
389
396
|
id: T.Optional[int] = 0
|
|
390
397
|
|
|
@@ -393,7 +400,7 @@ class Contact(BaseMessageComponent): # TODO
|
|
|
393
400
|
|
|
394
401
|
|
|
395
402
|
class Location(BaseMessageComponent): # TODO
|
|
396
|
-
type
|
|
403
|
+
type = ComponentType.Location
|
|
397
404
|
lat: float
|
|
398
405
|
lon: float
|
|
399
406
|
title: T.Optional[str] = ""
|
|
@@ -404,7 +411,7 @@ class Location(BaseMessageComponent): # TODO
|
|
|
404
411
|
|
|
405
412
|
|
|
406
413
|
class Music(BaseMessageComponent):
|
|
407
|
-
type
|
|
414
|
+
type = ComponentType.Music
|
|
408
415
|
_type: str
|
|
409
416
|
id: T.Optional[int] = 0
|
|
410
417
|
url: T.Optional[str] = ""
|
|
@@ -421,7 +428,7 @@ class Music(BaseMessageComponent):
|
|
|
421
428
|
|
|
422
429
|
|
|
423
430
|
class Image(BaseMessageComponent):
|
|
424
|
-
type
|
|
431
|
+
type = ComponentType.Image
|
|
425
432
|
file: T.Optional[str] = ""
|
|
426
433
|
_type: T.Optional[str] = ""
|
|
427
434
|
subType: T.Optional[int] = 0
|
|
@@ -464,14 +471,15 @@ class Image(BaseMessageComponent):
|
|
|
464
471
|
Returns:
|
|
465
472
|
str: 图片的本地路径,以绝对路径表示。
|
|
466
473
|
"""
|
|
467
|
-
url = self.url
|
|
468
|
-
if
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
474
|
+
url = self.url or self.file
|
|
475
|
+
if not url:
|
|
476
|
+
raise ValueError("No valid file or URL provided")
|
|
477
|
+
if url.startswith("file:///"):
|
|
478
|
+
return url[8:]
|
|
479
|
+
elif url.startswith("http"):
|
|
472
480
|
image_file_path = await download_image_by_url(url)
|
|
473
481
|
return os.path.abspath(image_file_path)
|
|
474
|
-
elif url
|
|
482
|
+
elif url.startswith("base64://"):
|
|
475
483
|
bs64_data = url.removeprefix("base64://")
|
|
476
484
|
image_bytes = base64.b64decode(bs64_data)
|
|
477
485
|
temp_dir = os.path.join(get_astrbot_data_path(), "temp")
|
|
@@ -480,8 +488,7 @@ class Image(BaseMessageComponent):
|
|
|
480
488
|
f.write(image_bytes)
|
|
481
489
|
return os.path.abspath(image_file_path)
|
|
482
490
|
elif os.path.exists(url):
|
|
483
|
-
|
|
484
|
-
return os.path.abspath(image_file_path)
|
|
491
|
+
return os.path.abspath(url)
|
|
485
492
|
else:
|
|
486
493
|
raise Exception(f"not a valid file: {url}")
|
|
487
494
|
|
|
@@ -492,13 +499,15 @@ class Image(BaseMessageComponent):
|
|
|
492
499
|
str: 图片的 base64 编码,不以 base64:// 或者 data:image/jpeg;base64, 开头。
|
|
493
500
|
"""
|
|
494
501
|
# convert to base64
|
|
495
|
-
url = self.url
|
|
496
|
-
if
|
|
502
|
+
url = self.url or self.file
|
|
503
|
+
if not url:
|
|
504
|
+
raise ValueError("No valid file or URL provided")
|
|
505
|
+
if url.startswith("file:///"):
|
|
497
506
|
bs64_data = file_to_base64(url[8:])
|
|
498
|
-
elif url
|
|
507
|
+
elif url.startswith("http"):
|
|
499
508
|
image_file_path = await download_image_by_url(url)
|
|
500
509
|
bs64_data = file_to_base64(image_file_path)
|
|
501
|
-
elif url
|
|
510
|
+
elif url.startswith("base64://"):
|
|
502
511
|
bs64_data = url
|
|
503
512
|
elif os.path.exists(url):
|
|
504
513
|
bs64_data = file_to_base64(url)
|
|
@@ -532,7 +541,7 @@ class Image(BaseMessageComponent):
|
|
|
532
541
|
|
|
533
542
|
|
|
534
543
|
class Reply(BaseMessageComponent):
|
|
535
|
-
type
|
|
544
|
+
type = ComponentType.Reply
|
|
536
545
|
id: T.Union[str, int]
|
|
537
546
|
"""所引用的消息 ID"""
|
|
538
547
|
chain: T.Optional[T.List["BaseMessageComponent"]] = []
|
|
@@ -558,7 +567,7 @@ class Reply(BaseMessageComponent):
|
|
|
558
567
|
|
|
559
568
|
|
|
560
569
|
class RedBag(BaseMessageComponent):
|
|
561
|
-
type
|
|
570
|
+
type = ComponentType.RedBag
|
|
562
571
|
title: str
|
|
563
572
|
|
|
564
573
|
def __init__(self, **_):
|
|
@@ -566,7 +575,7 @@ class RedBag(BaseMessageComponent):
|
|
|
566
575
|
|
|
567
576
|
|
|
568
577
|
class Poke(BaseMessageComponent):
|
|
569
|
-
type: str =
|
|
578
|
+
type: str = ComponentType.Poke
|
|
570
579
|
id: T.Optional[int] = 0
|
|
571
580
|
qq: T.Optional[int] = 0
|
|
572
581
|
|
|
@@ -576,7 +585,7 @@ class Poke(BaseMessageComponent):
|
|
|
576
585
|
|
|
577
586
|
|
|
578
587
|
class Forward(BaseMessageComponent):
|
|
579
|
-
type
|
|
588
|
+
type = ComponentType.Forward
|
|
580
589
|
id: str
|
|
581
590
|
|
|
582
591
|
def __init__(self, **_):
|
|
@@ -586,7 +595,7 @@ class Forward(BaseMessageComponent):
|
|
|
586
595
|
class Node(BaseMessageComponent):
|
|
587
596
|
"""群合并转发消息"""
|
|
588
597
|
|
|
589
|
-
type
|
|
598
|
+
type = ComponentType.Node
|
|
590
599
|
id: T.Optional[int] = 0 # 忽略
|
|
591
600
|
name: T.Optional[str] = "" # qq昵称
|
|
592
601
|
uin: T.Optional[str] = "0" # qq号
|
|
@@ -638,7 +647,7 @@ class Node(BaseMessageComponent):
|
|
|
638
647
|
|
|
639
648
|
|
|
640
649
|
class Nodes(BaseMessageComponent):
|
|
641
|
-
type
|
|
650
|
+
type = ComponentType.Nodes
|
|
642
651
|
nodes: T.List[Node]
|
|
643
652
|
|
|
644
653
|
def __init__(self, nodes: T.List[Node], **_):
|
|
@@ -664,7 +673,7 @@ class Nodes(BaseMessageComponent):
|
|
|
664
673
|
|
|
665
674
|
|
|
666
675
|
class Xml(BaseMessageComponent):
|
|
667
|
-
type
|
|
676
|
+
type = ComponentType.Xml
|
|
668
677
|
data: str
|
|
669
678
|
resid: T.Optional[int] = 0
|
|
670
679
|
|
|
@@ -673,7 +682,7 @@ class Xml(BaseMessageComponent):
|
|
|
673
682
|
|
|
674
683
|
|
|
675
684
|
class Json(BaseMessageComponent):
|
|
676
|
-
type
|
|
685
|
+
type = ComponentType.Json
|
|
677
686
|
data: T.Union[str, dict]
|
|
678
687
|
resid: T.Optional[int] = 0
|
|
679
688
|
|
|
@@ -684,7 +693,7 @@ class Json(BaseMessageComponent):
|
|
|
684
693
|
|
|
685
694
|
|
|
686
695
|
class CardImage(BaseMessageComponent):
|
|
687
|
-
type
|
|
696
|
+
type = ComponentType.CardImage
|
|
688
697
|
file: str
|
|
689
698
|
cache: T.Optional[bool] = True
|
|
690
699
|
minwidth: T.Optional[int] = 400
|
|
@@ -703,7 +712,7 @@ class CardImage(BaseMessageComponent):
|
|
|
703
712
|
|
|
704
713
|
|
|
705
714
|
class TTS(BaseMessageComponent):
|
|
706
|
-
type
|
|
715
|
+
type = ComponentType.TTS
|
|
707
716
|
text: str
|
|
708
717
|
|
|
709
718
|
def __init__(self, **_):
|
|
@@ -711,7 +720,7 @@ class TTS(BaseMessageComponent):
|
|
|
711
720
|
|
|
712
721
|
|
|
713
722
|
class Unknown(BaseMessageComponent):
|
|
714
|
-
type
|
|
723
|
+
type = ComponentType.Unknown
|
|
715
724
|
text: str
|
|
716
725
|
|
|
717
726
|
def toString(self):
|
|
@@ -723,7 +732,7 @@ class File(BaseMessageComponent):
|
|
|
723
732
|
文件消息段
|
|
724
733
|
"""
|
|
725
734
|
|
|
726
|
-
type
|
|
735
|
+
type = ComponentType.File
|
|
727
736
|
name: T.Optional[str] = "" # 名字
|
|
728
737
|
file_: T.Optional[str] = "" # 本地路径
|
|
729
738
|
url: T.Optional[str] = "" # url
|
|
@@ -853,7 +862,7 @@ class File(BaseMessageComponent):
|
|
|
853
862
|
|
|
854
863
|
|
|
855
864
|
class WechatEmoji(BaseMessageComponent):
|
|
856
|
-
type
|
|
865
|
+
type = ComponentType.WechatEmoji
|
|
857
866
|
md5: T.Optional[str] = ""
|
|
858
867
|
md5_len: T.Optional[int] = 0
|
|
859
868
|
cdnurl: T.Optional[str] = ""
|
|
@@ -36,6 +36,7 @@ class ResultDecorateStage(Stage):
|
|
|
36
36
|
self.t2i_word_threshold = 150
|
|
37
37
|
self.t2i_strategy = ctx.astrbot_config["t2i_strategy"]
|
|
38
38
|
self.t2i_use_network = self.t2i_strategy == "remote"
|
|
39
|
+
self.t2i_active_template = ctx.astrbot_config["t2i_active_template"]
|
|
39
40
|
|
|
40
41
|
self.forward_threshold = ctx.astrbot_config["platform_settings"][
|
|
41
42
|
"forward_threshold"
|
|
@@ -247,7 +248,10 @@ class ResultDecorateStage(Stage):
|
|
|
247
248
|
render_start = time.time()
|
|
248
249
|
try:
|
|
249
250
|
url = await html_renderer.render_t2i(
|
|
250
|
-
plain_str,
|
|
251
|
+
plain_str,
|
|
252
|
+
return_url=True,
|
|
253
|
+
use_network=self.t2i_use_network,
|
|
254
|
+
template_name=self.t2i_active_template,
|
|
251
255
|
)
|
|
252
256
|
except BaseException:
|
|
253
257
|
logger.error("文本转图片失败,使用文本发送。")
|