AstrBot 4.10.1__py3-none-any.whl → 4.10.3__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/builtin_stars/astrbot/long_term_memory.py +186 -0
- astrbot/builtin_stars/astrbot/main.py +128 -0
- astrbot/builtin_stars/astrbot/metadata.yaml +4 -0
- astrbot/builtin_stars/astrbot/process_llm_request.py +245 -0
- astrbot/builtin_stars/builtin_commands/commands/__init__.py +31 -0
- astrbot/builtin_stars/builtin_commands/commands/admin.py +77 -0
- astrbot/builtin_stars/builtin_commands/commands/alter_cmd.py +173 -0
- astrbot/builtin_stars/builtin_commands/commands/conversation.py +366 -0
- astrbot/builtin_stars/builtin_commands/commands/help.py +88 -0
- astrbot/builtin_stars/builtin_commands/commands/llm.py +20 -0
- astrbot/builtin_stars/builtin_commands/commands/persona.py +142 -0
- astrbot/builtin_stars/builtin_commands/commands/plugin.py +120 -0
- astrbot/builtin_stars/builtin_commands/commands/provider.py +329 -0
- astrbot/builtin_stars/builtin_commands/commands/setunset.py +36 -0
- astrbot/builtin_stars/builtin_commands/commands/sid.py +36 -0
- astrbot/builtin_stars/builtin_commands/commands/t2i.py +23 -0
- astrbot/builtin_stars/builtin_commands/commands/tool.py +31 -0
- astrbot/builtin_stars/builtin_commands/commands/tts.py +36 -0
- astrbot/builtin_stars/builtin_commands/commands/utils/rst_scene.py +26 -0
- astrbot/builtin_stars/builtin_commands/main.py +237 -0
- astrbot/builtin_stars/builtin_commands/metadata.yaml +4 -0
- astrbot/builtin_stars/python_interpreter/main.py +537 -0
- astrbot/builtin_stars/python_interpreter/metadata.yaml +4 -0
- astrbot/builtin_stars/python_interpreter/requirements.txt +1 -0
- astrbot/builtin_stars/python_interpreter/shared/api.py +22 -0
- astrbot/builtin_stars/reminder/main.py +266 -0
- astrbot/builtin_stars/reminder/metadata.yaml +4 -0
- astrbot/builtin_stars/session_controller/main.py +114 -0
- astrbot/builtin_stars/session_controller/metadata.yaml +5 -0
- astrbot/builtin_stars/web_searcher/engines/__init__.py +111 -0
- astrbot/builtin_stars/web_searcher/engines/bing.py +30 -0
- astrbot/builtin_stars/web_searcher/engines/sogo.py +52 -0
- astrbot/builtin_stars/web_searcher/main.py +436 -0
- astrbot/builtin_stars/web_searcher/metadata.yaml +4 -0
- astrbot/cli/__init__.py +1 -1
- astrbot/core/agent/message.py +9 -0
- astrbot/core/agent/runners/tool_loop_agent_runner.py +2 -1
- astrbot/core/backup/__init__.py +26 -0
- astrbot/core/backup/constants.py +77 -0
- astrbot/core/backup/exporter.py +476 -0
- astrbot/core/backup/importer.py +761 -0
- astrbot/core/config/default.py +1 -1
- astrbot/core/log.py +1 -1
- astrbot/core/pipeline/process_stage/method/agent_sub_stages/internal.py +1 -1
- astrbot/core/pipeline/waking_check/stage.py +2 -1
- astrbot/core/provider/entities.py +32 -9
- astrbot/core/provider/provider.py +3 -1
- astrbot/core/provider/sources/anthropic_source.py +80 -27
- astrbot/core/provider/sources/fishaudio_tts_api_source.py +14 -6
- astrbot/core/provider/sources/gemini_source.py +75 -26
- astrbot/core/provider/sources/openai_source.py +68 -25
- astrbot/core/star/command_management.py +45 -4
- astrbot/core/star/context.py +1 -1
- astrbot/core/star/star_manager.py +11 -13
- astrbot/core/utils/astrbot_path.py +34 -0
- astrbot/dashboard/routes/__init__.py +2 -0
- astrbot/dashboard/routes/backup.py +589 -0
- astrbot/dashboard/routes/command.py +2 -1
- astrbot/dashboard/routes/log.py +44 -10
- astrbot/dashboard/server.py +8 -1
- {astrbot-4.10.1.dist-info → astrbot-4.10.3.dist-info}/METADATA +2 -2
- {astrbot-4.10.1.dist-info → astrbot-4.10.3.dist-info}/RECORD +65 -26
- {astrbot-4.10.1.dist-info → astrbot-4.10.3.dist-info}/WHEEL +0 -0
- {astrbot-4.10.1.dist-info → astrbot-4.10.3.dist-info}/entry_points.txt +0 -0
- {astrbot-4.10.1.dist-info → astrbot-4.10.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import builtins
|
|
2
|
+
|
|
3
|
+
from astrbot.api import sp, star
|
|
4
|
+
from astrbot.api.event import AstrMessageEvent, MessageEventResult
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class PersonaCommands:
|
|
8
|
+
def __init__(self, context: star.Context):
|
|
9
|
+
self.context = context
|
|
10
|
+
|
|
11
|
+
async def persona(self, message: AstrMessageEvent):
|
|
12
|
+
l = message.message_str.split(" ") # noqa: E741
|
|
13
|
+
umo = message.unified_msg_origin
|
|
14
|
+
|
|
15
|
+
curr_persona_name = "无"
|
|
16
|
+
cid = await self.context.conversation_manager.get_curr_conversation_id(umo)
|
|
17
|
+
default_persona = await self.context.persona_manager.get_default_persona_v3(
|
|
18
|
+
umo=umo,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
force_applied_persona_id = (
|
|
22
|
+
await sp.get_async(
|
|
23
|
+
scope="umo", scope_id=umo, key="session_service_config", default={}
|
|
24
|
+
)
|
|
25
|
+
).get("persona_id")
|
|
26
|
+
|
|
27
|
+
curr_cid_title = "无"
|
|
28
|
+
if cid:
|
|
29
|
+
conv = await self.context.conversation_manager.get_conversation(
|
|
30
|
+
unified_msg_origin=umo,
|
|
31
|
+
conversation_id=cid,
|
|
32
|
+
create_if_not_exists=True,
|
|
33
|
+
)
|
|
34
|
+
if conv is None:
|
|
35
|
+
message.set_result(
|
|
36
|
+
MessageEventResult().message(
|
|
37
|
+
"当前对话不存在,请先使用 /new 新建一个对话。",
|
|
38
|
+
),
|
|
39
|
+
)
|
|
40
|
+
return
|
|
41
|
+
if not conv.persona_id and conv.persona_id != "[%None]":
|
|
42
|
+
curr_persona_name = default_persona["name"]
|
|
43
|
+
else:
|
|
44
|
+
curr_persona_name = conv.persona_id
|
|
45
|
+
|
|
46
|
+
if force_applied_persona_id:
|
|
47
|
+
curr_persona_name = f"{curr_persona_name} (自定义规则)"
|
|
48
|
+
|
|
49
|
+
curr_cid_title = conv.title if conv.title else "新对话"
|
|
50
|
+
curr_cid_title += f"({cid[:4]})"
|
|
51
|
+
|
|
52
|
+
if len(l) == 1:
|
|
53
|
+
message.set_result(
|
|
54
|
+
MessageEventResult()
|
|
55
|
+
.message(
|
|
56
|
+
f"""[Persona]
|
|
57
|
+
|
|
58
|
+
- 人格情景列表: `/persona list`
|
|
59
|
+
- 设置人格情景: `/persona 人格`
|
|
60
|
+
- 人格情景详细信息: `/persona view 人格`
|
|
61
|
+
- 取消人格: `/persona unset`
|
|
62
|
+
|
|
63
|
+
默认人格情景: {default_persona["name"]}
|
|
64
|
+
当前对话 {curr_cid_title} 的人格情景: {curr_persona_name}
|
|
65
|
+
|
|
66
|
+
配置人格情景请前往管理面板-配置页
|
|
67
|
+
""",
|
|
68
|
+
)
|
|
69
|
+
.use_t2i(False),
|
|
70
|
+
)
|
|
71
|
+
elif l[1] == "list":
|
|
72
|
+
parts = ["人格列表:\n"]
|
|
73
|
+
for persona in self.context.provider_manager.personas:
|
|
74
|
+
parts.append(f"- {persona['name']}\n")
|
|
75
|
+
parts.append("\n\n*输入 `/persona view 人格名` 查看人格详细信息")
|
|
76
|
+
msg = "".join(parts)
|
|
77
|
+
message.set_result(MessageEventResult().message(msg))
|
|
78
|
+
elif l[1] == "view":
|
|
79
|
+
if len(l) == 2:
|
|
80
|
+
message.set_result(MessageEventResult().message("请输入人格情景名"))
|
|
81
|
+
return
|
|
82
|
+
ps = l[2].strip()
|
|
83
|
+
if persona := next(
|
|
84
|
+
builtins.filter(
|
|
85
|
+
lambda persona: persona["name"] == ps,
|
|
86
|
+
self.context.provider_manager.personas,
|
|
87
|
+
),
|
|
88
|
+
None,
|
|
89
|
+
):
|
|
90
|
+
msg = f"人格{ps}的详细信息:\n"
|
|
91
|
+
msg += f"{persona['prompt']}\n"
|
|
92
|
+
else:
|
|
93
|
+
msg = f"人格{ps}不存在"
|
|
94
|
+
message.set_result(MessageEventResult().message(msg))
|
|
95
|
+
elif l[1] == "unset":
|
|
96
|
+
if not cid:
|
|
97
|
+
message.set_result(
|
|
98
|
+
MessageEventResult().message("当前没有对话,无法取消人格。"),
|
|
99
|
+
)
|
|
100
|
+
return
|
|
101
|
+
await self.context.conversation_manager.update_conversation_persona_id(
|
|
102
|
+
message.unified_msg_origin,
|
|
103
|
+
"[%None]",
|
|
104
|
+
)
|
|
105
|
+
message.set_result(MessageEventResult().message("取消人格成功。"))
|
|
106
|
+
else:
|
|
107
|
+
ps = "".join(l[1:]).strip()
|
|
108
|
+
if not cid:
|
|
109
|
+
message.set_result(
|
|
110
|
+
MessageEventResult().message(
|
|
111
|
+
"当前没有对话,请先开始对话或使用 /new 创建一个对话。",
|
|
112
|
+
),
|
|
113
|
+
)
|
|
114
|
+
return
|
|
115
|
+
if persona := next(
|
|
116
|
+
builtins.filter(
|
|
117
|
+
lambda persona: persona["name"] == ps,
|
|
118
|
+
self.context.provider_manager.personas,
|
|
119
|
+
),
|
|
120
|
+
None,
|
|
121
|
+
):
|
|
122
|
+
await self.context.conversation_manager.update_conversation_persona_id(
|
|
123
|
+
message.unified_msg_origin,
|
|
124
|
+
ps,
|
|
125
|
+
)
|
|
126
|
+
force_warn_msg = ""
|
|
127
|
+
if force_applied_persona_id:
|
|
128
|
+
force_warn_msg = (
|
|
129
|
+
"提醒:由于自定义规则,您现在切换的人格将不会生效。"
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
message.set_result(
|
|
133
|
+
MessageEventResult().message(
|
|
134
|
+
f"设置成功。如果您正在切换到不同的人格,请注意使用 /reset 来清空上下文,防止原人格对话影响现人格。{force_warn_msg}",
|
|
135
|
+
),
|
|
136
|
+
)
|
|
137
|
+
else:
|
|
138
|
+
message.set_result(
|
|
139
|
+
MessageEventResult().message(
|
|
140
|
+
"不存在该人格情景。使用 /persona list 查看所有。",
|
|
141
|
+
),
|
|
142
|
+
)
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
from astrbot.api import star
|
|
2
|
+
from astrbot.api.event import AstrMessageEvent, MessageEventResult
|
|
3
|
+
from astrbot.core import DEMO_MODE, logger
|
|
4
|
+
from astrbot.core.star.filter.command import CommandFilter
|
|
5
|
+
from astrbot.core.star.filter.command_group import CommandGroupFilter
|
|
6
|
+
from astrbot.core.star.star_handler import StarHandlerMetadata, star_handlers_registry
|
|
7
|
+
from astrbot.core.star.star_manager import PluginManager
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class PluginCommands:
|
|
11
|
+
def __init__(self, context: star.Context):
|
|
12
|
+
self.context = context
|
|
13
|
+
|
|
14
|
+
async def plugin_ls(self, event: AstrMessageEvent):
|
|
15
|
+
"""获取已经安装的插件列表。"""
|
|
16
|
+
parts = ["已加载的插件:\n"]
|
|
17
|
+
for plugin in self.context.get_all_stars():
|
|
18
|
+
line = f"- `{plugin.name}` By {plugin.author}: {plugin.desc}"
|
|
19
|
+
if not plugin.activated:
|
|
20
|
+
line += " (未启用)"
|
|
21
|
+
parts.append(line + "\n")
|
|
22
|
+
|
|
23
|
+
if len(parts) == 1:
|
|
24
|
+
plugin_list_info = "没有加载任何插件。"
|
|
25
|
+
else:
|
|
26
|
+
plugin_list_info = "".join(parts)
|
|
27
|
+
|
|
28
|
+
plugin_list_info += "\n使用 /plugin help <插件名> 查看插件帮助和加载的指令。\n使用 /plugin on/off <插件名> 启用或者禁用插件。"
|
|
29
|
+
event.set_result(
|
|
30
|
+
MessageEventResult().message(f"{plugin_list_info}").use_t2i(False),
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
async def plugin_off(self, event: AstrMessageEvent, plugin_name: str = ""):
|
|
34
|
+
"""禁用插件"""
|
|
35
|
+
if DEMO_MODE:
|
|
36
|
+
event.set_result(MessageEventResult().message("演示模式下无法禁用插件。"))
|
|
37
|
+
return
|
|
38
|
+
if not plugin_name:
|
|
39
|
+
event.set_result(
|
|
40
|
+
MessageEventResult().message("/plugin off <插件名> 禁用插件。"),
|
|
41
|
+
)
|
|
42
|
+
return
|
|
43
|
+
await self.context._star_manager.turn_off_plugin(plugin_name) # type: ignore
|
|
44
|
+
event.set_result(MessageEventResult().message(f"插件 {plugin_name} 已禁用。"))
|
|
45
|
+
|
|
46
|
+
async def plugin_on(self, event: AstrMessageEvent, plugin_name: str = ""):
|
|
47
|
+
"""启用插件"""
|
|
48
|
+
if DEMO_MODE:
|
|
49
|
+
event.set_result(MessageEventResult().message("演示模式下无法启用插件。"))
|
|
50
|
+
return
|
|
51
|
+
if not plugin_name:
|
|
52
|
+
event.set_result(
|
|
53
|
+
MessageEventResult().message("/plugin on <插件名> 启用插件。"),
|
|
54
|
+
)
|
|
55
|
+
return
|
|
56
|
+
await self.context._star_manager.turn_on_plugin(plugin_name) # type: ignore
|
|
57
|
+
event.set_result(MessageEventResult().message(f"插件 {plugin_name} 已启用。"))
|
|
58
|
+
|
|
59
|
+
async def plugin_get(self, event: AstrMessageEvent, plugin_repo: str = ""):
|
|
60
|
+
"""安装插件"""
|
|
61
|
+
if DEMO_MODE:
|
|
62
|
+
event.set_result(MessageEventResult().message("演示模式下无法安装插件。"))
|
|
63
|
+
return
|
|
64
|
+
if not plugin_repo:
|
|
65
|
+
event.set_result(
|
|
66
|
+
MessageEventResult().message("/plugin get <插件仓库地址> 安装插件"),
|
|
67
|
+
)
|
|
68
|
+
return
|
|
69
|
+
logger.info(f"准备从 {plugin_repo} 安装插件。")
|
|
70
|
+
if self.context._star_manager:
|
|
71
|
+
star_mgr: PluginManager = self.context._star_manager
|
|
72
|
+
try:
|
|
73
|
+
await star_mgr.install_plugin(plugin_repo) # type: ignore
|
|
74
|
+
event.set_result(MessageEventResult().message("安装插件成功。"))
|
|
75
|
+
except Exception as e:
|
|
76
|
+
logger.error(f"安装插件失败: {e}")
|
|
77
|
+
event.set_result(MessageEventResult().message(f"安装插件失败: {e}"))
|
|
78
|
+
return
|
|
79
|
+
|
|
80
|
+
async def plugin_help(self, event: AstrMessageEvent, plugin_name: str = ""):
|
|
81
|
+
"""获取插件帮助"""
|
|
82
|
+
if not plugin_name:
|
|
83
|
+
event.set_result(
|
|
84
|
+
MessageEventResult().message("/plugin help <插件名> 查看插件信息。"),
|
|
85
|
+
)
|
|
86
|
+
return
|
|
87
|
+
plugin = self.context.get_registered_star(plugin_name)
|
|
88
|
+
if plugin is None:
|
|
89
|
+
event.set_result(MessageEventResult().message("未找到此插件。"))
|
|
90
|
+
return
|
|
91
|
+
help_msg = ""
|
|
92
|
+
help_msg += f"\n\n✨ 作者: {plugin.author}\n✨ 版本: {plugin.version}"
|
|
93
|
+
command_handlers = []
|
|
94
|
+
command_names = []
|
|
95
|
+
for handler in star_handlers_registry:
|
|
96
|
+
assert isinstance(handler, StarHandlerMetadata)
|
|
97
|
+
if handler.handler_module_path != plugin.module_path:
|
|
98
|
+
continue
|
|
99
|
+
for filter_ in handler.event_filters:
|
|
100
|
+
if isinstance(filter_, CommandFilter):
|
|
101
|
+
command_handlers.append(handler)
|
|
102
|
+
command_names.append(filter_.command_name)
|
|
103
|
+
break
|
|
104
|
+
if isinstance(filter_, CommandGroupFilter):
|
|
105
|
+
command_handlers.append(handler)
|
|
106
|
+
command_names.append(filter_.group_name)
|
|
107
|
+
|
|
108
|
+
if len(command_handlers) > 0:
|
|
109
|
+
parts = ["\n\n🔧 指令列表:\n"]
|
|
110
|
+
for i in range(len(command_handlers)):
|
|
111
|
+
line = f"- {command_names[i]}"
|
|
112
|
+
if command_handlers[i].desc:
|
|
113
|
+
line += f": {command_handlers[i].desc}"
|
|
114
|
+
parts.append(line + "\n")
|
|
115
|
+
parts.append("\nTip: 指令的触发需要添加唤醒前缀,默认为 /。")
|
|
116
|
+
help_msg += "".join(parts)
|
|
117
|
+
|
|
118
|
+
ret = f"🧩 插件 {plugin_name} 帮助信息:\n" + help_msg
|
|
119
|
+
ret += "更多帮助信息请查看插件仓库 README。"
|
|
120
|
+
event.set_result(MessageEventResult().message(ret).use_t2i(False))
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import re
|
|
3
|
+
|
|
4
|
+
from astrbot import logger
|
|
5
|
+
from astrbot.api import star
|
|
6
|
+
from astrbot.api.event import AstrMessageEvent, MessageEventResult
|
|
7
|
+
from astrbot.core.provider.entities import ProviderType
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ProviderCommands:
|
|
11
|
+
def __init__(self, context: star.Context):
|
|
12
|
+
self.context = context
|
|
13
|
+
|
|
14
|
+
def _log_reachability_failure(
|
|
15
|
+
self,
|
|
16
|
+
provider,
|
|
17
|
+
provider_capability_type: ProviderType | None,
|
|
18
|
+
err_code: str,
|
|
19
|
+
err_reason: str,
|
|
20
|
+
):
|
|
21
|
+
"""记录不可达原因到日志。"""
|
|
22
|
+
meta = provider.meta()
|
|
23
|
+
logger.warning(
|
|
24
|
+
"Provider reachability check failed: id=%s type=%s code=%s reason=%s",
|
|
25
|
+
meta.id,
|
|
26
|
+
provider_capability_type.name if provider_capability_type else "unknown",
|
|
27
|
+
err_code,
|
|
28
|
+
err_reason,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
async def _test_provider_capability(self, provider):
|
|
32
|
+
"""测试单个 provider 的可用性"""
|
|
33
|
+
meta = provider.meta()
|
|
34
|
+
provider_capability_type = meta.provider_type
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
await provider.test()
|
|
38
|
+
return True, None, None
|
|
39
|
+
except Exception as e:
|
|
40
|
+
err_code = "TEST_FAILED"
|
|
41
|
+
err_reason = str(e)
|
|
42
|
+
self._log_reachability_failure(
|
|
43
|
+
provider, provider_capability_type, err_code, err_reason
|
|
44
|
+
)
|
|
45
|
+
return False, err_code, err_reason
|
|
46
|
+
|
|
47
|
+
async def provider(
|
|
48
|
+
self,
|
|
49
|
+
event: AstrMessageEvent,
|
|
50
|
+
idx: str | int | None = None,
|
|
51
|
+
idx2: int | None = None,
|
|
52
|
+
):
|
|
53
|
+
"""查看或者切换 LLM Provider"""
|
|
54
|
+
umo = event.unified_msg_origin
|
|
55
|
+
cfg = self.context.get_config(umo).get("provider_settings", {})
|
|
56
|
+
reachability_check_enabled = cfg.get("reachability_check", True)
|
|
57
|
+
|
|
58
|
+
if idx is None:
|
|
59
|
+
parts = ["## 载入的 LLM 提供商\n"]
|
|
60
|
+
|
|
61
|
+
# 获取所有类型的提供商
|
|
62
|
+
llms = list(self.context.get_all_providers())
|
|
63
|
+
ttss = self.context.get_all_tts_providers()
|
|
64
|
+
stts = self.context.get_all_stt_providers()
|
|
65
|
+
|
|
66
|
+
# 构造待检测列表: [(provider, type_label), ...]
|
|
67
|
+
all_providers = []
|
|
68
|
+
all_providers.extend([(p, "llm") for p in llms])
|
|
69
|
+
all_providers.extend([(p, "tts") for p in ttss])
|
|
70
|
+
all_providers.extend([(p, "stt") for p in stts])
|
|
71
|
+
|
|
72
|
+
# 并发测试连通性
|
|
73
|
+
if reachability_check_enabled:
|
|
74
|
+
if all_providers:
|
|
75
|
+
await event.send(
|
|
76
|
+
MessageEventResult().message(
|
|
77
|
+
"正在进行提供商可达性测试,请稍候..."
|
|
78
|
+
)
|
|
79
|
+
)
|
|
80
|
+
check_results = await asyncio.gather(
|
|
81
|
+
*[self._test_provider_capability(p) for p, _ in all_providers],
|
|
82
|
+
return_exceptions=True,
|
|
83
|
+
)
|
|
84
|
+
else:
|
|
85
|
+
# 用 None 表示未检测
|
|
86
|
+
check_results = [None for _ in all_providers]
|
|
87
|
+
|
|
88
|
+
# 整合结果
|
|
89
|
+
display_data = []
|
|
90
|
+
for (p, p_type), reachable in zip(all_providers, check_results):
|
|
91
|
+
meta = p.meta()
|
|
92
|
+
id_ = meta.id
|
|
93
|
+
error_code = None
|
|
94
|
+
|
|
95
|
+
if isinstance(reachable, Exception):
|
|
96
|
+
# 异常情况下兜底处理,避免单个 provider 导致列表失败
|
|
97
|
+
self._log_reachability_failure(
|
|
98
|
+
p,
|
|
99
|
+
None,
|
|
100
|
+
reachable.__class__.__name__,
|
|
101
|
+
str(reachable),
|
|
102
|
+
)
|
|
103
|
+
reachable_flag = False
|
|
104
|
+
error_code = reachable.__class__.__name__
|
|
105
|
+
elif isinstance(reachable, tuple):
|
|
106
|
+
reachable_flag, error_code, _ = reachable
|
|
107
|
+
else:
|
|
108
|
+
reachable_flag = reachable
|
|
109
|
+
|
|
110
|
+
# 根据类型构建显示名称
|
|
111
|
+
if p_type == "llm":
|
|
112
|
+
info = f"{id_} ({meta.model})"
|
|
113
|
+
else:
|
|
114
|
+
info = f"{id_}"
|
|
115
|
+
|
|
116
|
+
# 确定状态标记
|
|
117
|
+
if reachable_flag is True:
|
|
118
|
+
mark = " ✅"
|
|
119
|
+
elif reachable_flag is False:
|
|
120
|
+
if error_code:
|
|
121
|
+
mark = f" ❌(错误码: {error_code})"
|
|
122
|
+
else:
|
|
123
|
+
mark = " ❌"
|
|
124
|
+
else:
|
|
125
|
+
mark = "" # 不支持检测时不显示标记
|
|
126
|
+
|
|
127
|
+
display_data.append(
|
|
128
|
+
{
|
|
129
|
+
"type": p_type,
|
|
130
|
+
"info": info,
|
|
131
|
+
"mark": mark,
|
|
132
|
+
"provider": p,
|
|
133
|
+
}
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
# 分组输出
|
|
137
|
+
# 1. LLM
|
|
138
|
+
llm_data = [d for d in display_data if d["type"] == "llm"]
|
|
139
|
+
for i, d in enumerate(llm_data):
|
|
140
|
+
line = f"{i + 1}. {d['info']}{d['mark']}"
|
|
141
|
+
provider_using = self.context.get_using_provider(umo=umo)
|
|
142
|
+
if (
|
|
143
|
+
provider_using
|
|
144
|
+
and provider_using.meta().id == d["provider"].meta().id
|
|
145
|
+
):
|
|
146
|
+
line += " (当前使用)"
|
|
147
|
+
parts.append(line + "\n")
|
|
148
|
+
|
|
149
|
+
# 2. TTS
|
|
150
|
+
tts_data = [d for d in display_data if d["type"] == "tts"]
|
|
151
|
+
if tts_data:
|
|
152
|
+
parts.append("\n## 载入的 TTS 提供商\n")
|
|
153
|
+
for i, d in enumerate(tts_data):
|
|
154
|
+
line = f"{i + 1}. {d['info']}{d['mark']}"
|
|
155
|
+
tts_using = self.context.get_using_tts_provider(umo=umo)
|
|
156
|
+
if tts_using and tts_using.meta().id == d["provider"].meta().id:
|
|
157
|
+
line += " (当前使用)"
|
|
158
|
+
parts.append(line + "\n")
|
|
159
|
+
|
|
160
|
+
# 3. STT
|
|
161
|
+
stt_data = [d for d in display_data if d["type"] == "stt"]
|
|
162
|
+
if stt_data:
|
|
163
|
+
parts.append("\n## 载入的 STT 提供商\n")
|
|
164
|
+
for i, d in enumerate(stt_data):
|
|
165
|
+
line = f"{i + 1}. {d['info']}{d['mark']}"
|
|
166
|
+
stt_using = self.context.get_using_stt_provider(umo=umo)
|
|
167
|
+
if stt_using and stt_using.meta().id == d["provider"].meta().id:
|
|
168
|
+
line += " (当前使用)"
|
|
169
|
+
parts.append(line + "\n")
|
|
170
|
+
|
|
171
|
+
parts.append("\n使用 /provider <序号> 切换 LLM 提供商。")
|
|
172
|
+
ret = "".join(parts)
|
|
173
|
+
|
|
174
|
+
if ttss:
|
|
175
|
+
ret += "\n使用 /provider tts <序号> 切换 TTS 提供商。"
|
|
176
|
+
if stts:
|
|
177
|
+
ret += "\n使用 /provider stt <序号> 切换 STT 提供商。"
|
|
178
|
+
if not reachability_check_enabled:
|
|
179
|
+
ret += "\n已跳过提供商可达性检测,如需检测请在配置文件中开启。"
|
|
180
|
+
|
|
181
|
+
event.set_result(MessageEventResult().message(ret))
|
|
182
|
+
elif idx == "tts":
|
|
183
|
+
if idx2 is None:
|
|
184
|
+
event.set_result(MessageEventResult().message("请输入序号。"))
|
|
185
|
+
return
|
|
186
|
+
if idx2 > len(self.context.get_all_tts_providers()) or idx2 < 1:
|
|
187
|
+
event.set_result(MessageEventResult().message("无效的提供商序号。"))
|
|
188
|
+
return
|
|
189
|
+
provider = self.context.get_all_tts_providers()[idx2 - 1]
|
|
190
|
+
id_ = provider.meta().id
|
|
191
|
+
await self.context.provider_manager.set_provider(
|
|
192
|
+
provider_id=id_,
|
|
193
|
+
provider_type=ProviderType.TEXT_TO_SPEECH,
|
|
194
|
+
umo=umo,
|
|
195
|
+
)
|
|
196
|
+
event.set_result(MessageEventResult().message(f"成功切换到 {id_}。"))
|
|
197
|
+
elif idx == "stt":
|
|
198
|
+
if idx2 is None:
|
|
199
|
+
event.set_result(MessageEventResult().message("请输入序号。"))
|
|
200
|
+
return
|
|
201
|
+
if idx2 > len(self.context.get_all_stt_providers()) or idx2 < 1:
|
|
202
|
+
event.set_result(MessageEventResult().message("无效的提供商序号。"))
|
|
203
|
+
return
|
|
204
|
+
provider = self.context.get_all_stt_providers()[idx2 - 1]
|
|
205
|
+
id_ = provider.meta().id
|
|
206
|
+
await self.context.provider_manager.set_provider(
|
|
207
|
+
provider_id=id_,
|
|
208
|
+
provider_type=ProviderType.SPEECH_TO_TEXT,
|
|
209
|
+
umo=umo,
|
|
210
|
+
)
|
|
211
|
+
event.set_result(MessageEventResult().message(f"成功切换到 {id_}。"))
|
|
212
|
+
elif isinstance(idx, int):
|
|
213
|
+
if idx > len(self.context.get_all_providers()) or idx < 1:
|
|
214
|
+
event.set_result(MessageEventResult().message("无效的提供商序号。"))
|
|
215
|
+
return
|
|
216
|
+
provider = self.context.get_all_providers()[idx - 1]
|
|
217
|
+
id_ = provider.meta().id
|
|
218
|
+
await self.context.provider_manager.set_provider(
|
|
219
|
+
provider_id=id_,
|
|
220
|
+
provider_type=ProviderType.CHAT_COMPLETION,
|
|
221
|
+
umo=umo,
|
|
222
|
+
)
|
|
223
|
+
event.set_result(MessageEventResult().message(f"成功切换到 {id_}。"))
|
|
224
|
+
else:
|
|
225
|
+
event.set_result(MessageEventResult().message("无效的参数。"))
|
|
226
|
+
|
|
227
|
+
async def model_ls(
|
|
228
|
+
self,
|
|
229
|
+
message: AstrMessageEvent,
|
|
230
|
+
idx_or_name: int | str | None = None,
|
|
231
|
+
):
|
|
232
|
+
"""查看或者切换模型"""
|
|
233
|
+
prov = self.context.get_using_provider(message.unified_msg_origin)
|
|
234
|
+
if not prov:
|
|
235
|
+
message.set_result(
|
|
236
|
+
MessageEventResult().message("未找到任何 LLM 提供商。请先配置。"),
|
|
237
|
+
)
|
|
238
|
+
return
|
|
239
|
+
# 定义正则表达式匹配 API 密钥
|
|
240
|
+
api_key_pattern = re.compile(r"key=[^&'\" ]+")
|
|
241
|
+
|
|
242
|
+
if idx_or_name is None:
|
|
243
|
+
models = []
|
|
244
|
+
try:
|
|
245
|
+
models = await prov.get_models()
|
|
246
|
+
except BaseException as e:
|
|
247
|
+
err_msg = api_key_pattern.sub("key=***", str(e))
|
|
248
|
+
message.set_result(
|
|
249
|
+
MessageEventResult()
|
|
250
|
+
.message("获取模型列表失败: " + err_msg)
|
|
251
|
+
.use_t2i(False),
|
|
252
|
+
)
|
|
253
|
+
return
|
|
254
|
+
parts = ["下面列出了此模型提供商可用模型:"]
|
|
255
|
+
for i, model in enumerate(models, 1):
|
|
256
|
+
parts.append(f"\n{i}. {model}")
|
|
257
|
+
|
|
258
|
+
curr_model = prov.get_model() or "无"
|
|
259
|
+
parts.append(f"\n当前模型: [{curr_model}]")
|
|
260
|
+
parts.append(
|
|
261
|
+
"\nTips: 使用 /model <模型名/编号>,即可实时更换模型。如目标模型不存在于上表,请输入模型名。"
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
ret = "".join(parts)
|
|
265
|
+
message.set_result(MessageEventResult().message(ret).use_t2i(False))
|
|
266
|
+
elif isinstance(idx_or_name, int):
|
|
267
|
+
models = []
|
|
268
|
+
try:
|
|
269
|
+
models = await prov.get_models()
|
|
270
|
+
except BaseException as e:
|
|
271
|
+
message.set_result(
|
|
272
|
+
MessageEventResult().message("获取模型列表失败: " + str(e)),
|
|
273
|
+
)
|
|
274
|
+
return
|
|
275
|
+
if idx_or_name > len(models) or idx_or_name < 1:
|
|
276
|
+
message.set_result(MessageEventResult().message("模型序号错误。"))
|
|
277
|
+
else:
|
|
278
|
+
try:
|
|
279
|
+
new_model = models[idx_or_name - 1]
|
|
280
|
+
prov.set_model(new_model)
|
|
281
|
+
except BaseException as e:
|
|
282
|
+
message.set_result(
|
|
283
|
+
MessageEventResult().message("切换模型未知错误: " + str(e)),
|
|
284
|
+
)
|
|
285
|
+
message.set_result(
|
|
286
|
+
MessageEventResult().message(
|
|
287
|
+
f"切换模型成功。当前提供商: [{prov.meta().id}] 当前模型: [{prov.get_model()}]",
|
|
288
|
+
),
|
|
289
|
+
)
|
|
290
|
+
else:
|
|
291
|
+
prov.set_model(idx_or_name)
|
|
292
|
+
message.set_result(
|
|
293
|
+
MessageEventResult().message(f"切换模型到 {prov.get_model()}。"),
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
async def key(self, message: AstrMessageEvent, index: int | None = None):
|
|
297
|
+
prov = self.context.get_using_provider(message.unified_msg_origin)
|
|
298
|
+
if not prov:
|
|
299
|
+
message.set_result(
|
|
300
|
+
MessageEventResult().message("未找到任何 LLM 提供商。请先配置。"),
|
|
301
|
+
)
|
|
302
|
+
return
|
|
303
|
+
|
|
304
|
+
if index is None:
|
|
305
|
+
keys_data = prov.get_keys()
|
|
306
|
+
curr_key = prov.get_current_key()
|
|
307
|
+
parts = ["Key:"]
|
|
308
|
+
for i, k in enumerate(keys_data, 1):
|
|
309
|
+
parts.append(f"\n{i}. {k[:8]}")
|
|
310
|
+
|
|
311
|
+
parts.append(f"\n当前 Key: {curr_key[:8]}")
|
|
312
|
+
parts.append("\n当前模型: " + prov.get_model())
|
|
313
|
+
parts.append("\n使用 /key <idx> 切换 Key。")
|
|
314
|
+
|
|
315
|
+
ret = "".join(parts)
|
|
316
|
+
message.set_result(MessageEventResult().message(ret).use_t2i(False))
|
|
317
|
+
else:
|
|
318
|
+
keys_data = prov.get_keys()
|
|
319
|
+
if index > len(keys_data) or index < 1:
|
|
320
|
+
message.set_result(MessageEventResult().message("Key 序号错误。"))
|
|
321
|
+
else:
|
|
322
|
+
try:
|
|
323
|
+
new_key = keys_data[index - 1]
|
|
324
|
+
prov.set_key(new_key)
|
|
325
|
+
except BaseException as e:
|
|
326
|
+
message.set_result(
|
|
327
|
+
MessageEventResult().message(f"切换 Key 未知错误: {e!s}"),
|
|
328
|
+
)
|
|
329
|
+
message.set_result(MessageEventResult().message("切换 Key 成功。"))
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from astrbot.api import sp, star
|
|
2
|
+
from astrbot.api.event import AstrMessageEvent, MessageEventResult
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class SetUnsetCommands:
|
|
6
|
+
def __init__(self, context: star.Context):
|
|
7
|
+
self.context = context
|
|
8
|
+
|
|
9
|
+
async def set_variable(self, event: AstrMessageEvent, key: str, value: str):
|
|
10
|
+
"""设置会话变量"""
|
|
11
|
+
uid = event.unified_msg_origin
|
|
12
|
+
session_var = await sp.session_get(uid, "session_variables", {})
|
|
13
|
+
session_var[key] = value
|
|
14
|
+
await sp.session_put(uid, "session_variables", session_var)
|
|
15
|
+
|
|
16
|
+
event.set_result(
|
|
17
|
+
MessageEventResult().message(
|
|
18
|
+
f"会话 {uid} 变量 {key} 存储成功。使用 /unset 移除。",
|
|
19
|
+
),
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
async def unset_variable(self, event: AstrMessageEvent, key: str):
|
|
23
|
+
"""移除会话变量"""
|
|
24
|
+
uid = event.unified_msg_origin
|
|
25
|
+
session_var = await sp.session_get(uid, "session_variables", {})
|
|
26
|
+
|
|
27
|
+
if key not in session_var:
|
|
28
|
+
event.set_result(
|
|
29
|
+
MessageEventResult().message("没有那个变量名。格式 /unset 变量名。"),
|
|
30
|
+
)
|
|
31
|
+
else:
|
|
32
|
+
del session_var[key]
|
|
33
|
+
await sp.session_put(uid, "session_variables", session_var)
|
|
34
|
+
event.set_result(
|
|
35
|
+
MessageEventResult().message(f"会话 {uid} 变量 {key} 移除成功。"),
|
|
36
|
+
)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""会话ID命令"""
|
|
2
|
+
|
|
3
|
+
from astrbot.api import star
|
|
4
|
+
from astrbot.api.event import AstrMessageEvent, MessageEventResult
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class SIDCommand:
|
|
8
|
+
"""会话ID命令类"""
|
|
9
|
+
|
|
10
|
+
def __init__(self, context: star.Context):
|
|
11
|
+
self.context = context
|
|
12
|
+
|
|
13
|
+
async def sid(self, event: AstrMessageEvent):
|
|
14
|
+
"""获取消息来源信息"""
|
|
15
|
+
sid = event.unified_msg_origin
|
|
16
|
+
user_id = str(event.get_sender_id())
|
|
17
|
+
umo_platform = event.session.platform_id
|
|
18
|
+
umo_msg_type = event.session.message_type.value
|
|
19
|
+
umo_session_id = event.session.session_id
|
|
20
|
+
ret = (
|
|
21
|
+
f"UMO: 「{sid}」 此值可用于设置白名单。\n"
|
|
22
|
+
f"UID: 「{user_id}」 此值可用于设置管理员。\n"
|
|
23
|
+
f"消息会话来源信息:\n"
|
|
24
|
+
f" 机器人 ID: 「{umo_platform}」\n"
|
|
25
|
+
f" 消息类型: 「{umo_msg_type}」\n"
|
|
26
|
+
f" 会话 ID: 「{umo_session_id}」\n"
|
|
27
|
+
f"消息来源可用于配置机器人的配置文件路由。"
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
if (
|
|
31
|
+
self.context.get_config()["platform_settings"]["unique_session"]
|
|
32
|
+
and event.get_group_id()
|
|
33
|
+
):
|
|
34
|
+
ret += f"\n\n当前处于独立会话模式, 此群 ID: 「{event.get_group_id()}」, 也可将此 ID 加入白名单来放行整个群聊。"
|
|
35
|
+
|
|
36
|
+
event.set_result(MessageEventResult().message(ret).use_t2i(False))
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""文本转图片命令"""
|
|
2
|
+
|
|
3
|
+
from astrbot.api import star
|
|
4
|
+
from astrbot.api.event import AstrMessageEvent, MessageEventResult
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class T2ICommand:
|
|
8
|
+
"""文本转图片命令类"""
|
|
9
|
+
|
|
10
|
+
def __init__(self, context: star.Context):
|
|
11
|
+
self.context = context
|
|
12
|
+
|
|
13
|
+
async def t2i(self, event: AstrMessageEvent):
|
|
14
|
+
"""开关文本转图片"""
|
|
15
|
+
config = self.context.get_config(umo=event.unified_msg_origin)
|
|
16
|
+
if config["t2i"]:
|
|
17
|
+
config["t2i"] = False
|
|
18
|
+
config.save_config()
|
|
19
|
+
event.set_result(MessageEventResult().message("已关闭文本转图片模式。"))
|
|
20
|
+
return
|
|
21
|
+
config["t2i"] = True
|
|
22
|
+
config.save_config()
|
|
23
|
+
event.set_result(MessageEventResult().message("已开启文本转图片模式。"))
|