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.
Files changed (65) hide show
  1. astrbot/builtin_stars/astrbot/long_term_memory.py +186 -0
  2. astrbot/builtin_stars/astrbot/main.py +128 -0
  3. astrbot/builtin_stars/astrbot/metadata.yaml +4 -0
  4. astrbot/builtin_stars/astrbot/process_llm_request.py +245 -0
  5. astrbot/builtin_stars/builtin_commands/commands/__init__.py +31 -0
  6. astrbot/builtin_stars/builtin_commands/commands/admin.py +77 -0
  7. astrbot/builtin_stars/builtin_commands/commands/alter_cmd.py +173 -0
  8. astrbot/builtin_stars/builtin_commands/commands/conversation.py +366 -0
  9. astrbot/builtin_stars/builtin_commands/commands/help.py +88 -0
  10. astrbot/builtin_stars/builtin_commands/commands/llm.py +20 -0
  11. astrbot/builtin_stars/builtin_commands/commands/persona.py +142 -0
  12. astrbot/builtin_stars/builtin_commands/commands/plugin.py +120 -0
  13. astrbot/builtin_stars/builtin_commands/commands/provider.py +329 -0
  14. astrbot/builtin_stars/builtin_commands/commands/setunset.py +36 -0
  15. astrbot/builtin_stars/builtin_commands/commands/sid.py +36 -0
  16. astrbot/builtin_stars/builtin_commands/commands/t2i.py +23 -0
  17. astrbot/builtin_stars/builtin_commands/commands/tool.py +31 -0
  18. astrbot/builtin_stars/builtin_commands/commands/tts.py +36 -0
  19. astrbot/builtin_stars/builtin_commands/commands/utils/rst_scene.py +26 -0
  20. astrbot/builtin_stars/builtin_commands/main.py +237 -0
  21. astrbot/builtin_stars/builtin_commands/metadata.yaml +4 -0
  22. astrbot/builtin_stars/python_interpreter/main.py +537 -0
  23. astrbot/builtin_stars/python_interpreter/metadata.yaml +4 -0
  24. astrbot/builtin_stars/python_interpreter/requirements.txt +1 -0
  25. astrbot/builtin_stars/python_interpreter/shared/api.py +22 -0
  26. astrbot/builtin_stars/reminder/main.py +266 -0
  27. astrbot/builtin_stars/reminder/metadata.yaml +4 -0
  28. astrbot/builtin_stars/session_controller/main.py +114 -0
  29. astrbot/builtin_stars/session_controller/metadata.yaml +5 -0
  30. astrbot/builtin_stars/web_searcher/engines/__init__.py +111 -0
  31. astrbot/builtin_stars/web_searcher/engines/bing.py +30 -0
  32. astrbot/builtin_stars/web_searcher/engines/sogo.py +52 -0
  33. astrbot/builtin_stars/web_searcher/main.py +436 -0
  34. astrbot/builtin_stars/web_searcher/metadata.yaml +4 -0
  35. astrbot/cli/__init__.py +1 -1
  36. astrbot/core/agent/message.py +9 -0
  37. astrbot/core/agent/runners/tool_loop_agent_runner.py +2 -1
  38. astrbot/core/backup/__init__.py +26 -0
  39. astrbot/core/backup/constants.py +77 -0
  40. astrbot/core/backup/exporter.py +476 -0
  41. astrbot/core/backup/importer.py +761 -0
  42. astrbot/core/config/default.py +1 -1
  43. astrbot/core/log.py +1 -1
  44. astrbot/core/pipeline/process_stage/method/agent_sub_stages/internal.py +1 -1
  45. astrbot/core/pipeline/waking_check/stage.py +2 -1
  46. astrbot/core/provider/entities.py +32 -9
  47. astrbot/core/provider/provider.py +3 -1
  48. astrbot/core/provider/sources/anthropic_source.py +80 -27
  49. astrbot/core/provider/sources/fishaudio_tts_api_source.py +14 -6
  50. astrbot/core/provider/sources/gemini_source.py +75 -26
  51. astrbot/core/provider/sources/openai_source.py +68 -25
  52. astrbot/core/star/command_management.py +45 -4
  53. astrbot/core/star/context.py +1 -1
  54. astrbot/core/star/star_manager.py +11 -13
  55. astrbot/core/utils/astrbot_path.py +34 -0
  56. astrbot/dashboard/routes/__init__.py +2 -0
  57. astrbot/dashboard/routes/backup.py +589 -0
  58. astrbot/dashboard/routes/command.py +2 -1
  59. astrbot/dashboard/routes/log.py +44 -10
  60. astrbot/dashboard/server.py +8 -1
  61. {astrbot-4.10.1.dist-info → astrbot-4.10.3.dist-info}/METADATA +2 -2
  62. {astrbot-4.10.1.dist-info → astrbot-4.10.3.dist-info}/RECORD +65 -26
  63. {astrbot-4.10.1.dist-info → astrbot-4.10.3.dist-info}/WHEEL +0 -0
  64. {astrbot-4.10.1.dist-info → astrbot-4.10.3.dist-info}/entry_points.txt +0 -0
  65. {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("已开启文本转图片模式。"))