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,173 @@
1
+ from astrbot.api import star
2
+ from astrbot.api.event import AstrMessageEvent, MessageChain
3
+ from astrbot.core.star.filter.command import CommandFilter
4
+ from astrbot.core.star.filter.command_group import CommandGroupFilter
5
+ from astrbot.core.star.filter.permission import PermissionTypeFilter
6
+ from astrbot.core.star.star import star_map
7
+ from astrbot.core.star.star_handler import StarHandlerMetadata, star_handlers_registry
8
+ from astrbot.core.utils.command_parser import CommandParserMixin
9
+
10
+ from .utils.rst_scene import RstScene
11
+
12
+
13
+ class AlterCmdCommands(CommandParserMixin):
14
+ def __init__(self, context: star.Context):
15
+ self.context = context
16
+
17
+ async def update_reset_permission(self, scene_key: str, perm_type: str):
18
+ """更新reset命令在特定场景下的权限设置"""
19
+ from astrbot.api import sp
20
+
21
+ alter_cmd_cfg = await sp.global_get("alter_cmd", {})
22
+ plugin_cfg = alter_cmd_cfg.get("astrbot", {})
23
+ reset_cfg = plugin_cfg.get("reset", {})
24
+ reset_cfg[scene_key] = perm_type
25
+ plugin_cfg["reset"] = reset_cfg
26
+ alter_cmd_cfg["astrbot"] = plugin_cfg
27
+ await sp.global_put("alter_cmd", alter_cmd_cfg)
28
+
29
+ async def alter_cmd(self, event: AstrMessageEvent):
30
+ token = self.parse_commands(event.message_str)
31
+ if token.len < 3:
32
+ await event.send(
33
+ MessageChain().message(
34
+ "该指令用于设置指令或指令组的权限。\n"
35
+ "格式: /alter_cmd <cmd_name> <admin/member>\n"
36
+ "例1: /alter_cmd c1 admin 将 c1 设为管理员指令\n"
37
+ "例2: /alter_cmd g1 c1 admin 将 g1 指令组的 c1 子指令设为管理员指令\n"
38
+ "/alter_cmd reset config 打开 reset 权限配置",
39
+ ),
40
+ )
41
+ return
42
+
43
+ # 兼容 reset scene 的专门配置
44
+ cmd_name = token.get(1)
45
+ cmd_type = token.get(2)
46
+
47
+ if cmd_name == "reset" and cmd_type == "config":
48
+ from astrbot.api import sp
49
+
50
+ alter_cmd_cfg = await sp.global_get("alter_cmd", {})
51
+ plugin_ = alter_cmd_cfg.get("astrbot", {})
52
+ reset_cfg = plugin_.get("reset", {})
53
+
54
+ group_unique_on = reset_cfg.get("group_unique_on", "admin")
55
+ group_unique_off = reset_cfg.get("group_unique_off", "admin")
56
+ private = reset_cfg.get("private", "member")
57
+
58
+ config_menu = f"""reset命令权限细粒度配置
59
+ 当前配置:
60
+ 1. 群聊+会话隔离开: {group_unique_on}
61
+ 2. 群聊+会话隔离关: {group_unique_off}
62
+ 3. 私聊: {private}
63
+ 修改指令格式:
64
+ /alter_cmd reset scene <场景编号> <admin/member>
65
+ 例如: /alter_cmd reset scene 2 member"""
66
+ await event.send(MessageChain().message(config_menu))
67
+ return
68
+
69
+ if cmd_name == "reset" and cmd_type == "scene" and token.len >= 4:
70
+ scene_num = token.get(3)
71
+ perm_type = token.get(4)
72
+
73
+ if scene_num is None or perm_type is None:
74
+ await event.send(MessageChain().message("场景编号和权限类型不能为空"))
75
+ return
76
+
77
+ if not scene_num.isdigit() or int(scene_num) < 1 or int(scene_num) > 3:
78
+ await event.send(
79
+ MessageChain().message("场景编号必须是 1-3 之间的数字"),
80
+ )
81
+ return
82
+
83
+ if perm_type not in ["admin", "member"]:
84
+ await event.send(
85
+ MessageChain().message("权限类型错误,只能是 admin 或 member"),
86
+ )
87
+ return
88
+
89
+ scene_num = int(scene_num)
90
+ scene = RstScene.from_index(scene_num)
91
+ scene_key = scene.key
92
+
93
+ await self.update_reset_permission(scene_key, perm_type)
94
+
95
+ await event.send(
96
+ MessageChain().message(
97
+ f"已将 reset 命令在{scene.name}场景下的权限设为{perm_type}",
98
+ ),
99
+ )
100
+ return
101
+
102
+ if cmd_type not in ["admin", "member"]:
103
+ await event.send(
104
+ MessageChain().message("指令类型错误,可选类型有 admin, member"),
105
+ )
106
+ return
107
+
108
+ # 查找指令
109
+ cmd_name = " ".join(token.tokens[1:-1])
110
+ cmd_type = token.get(-1)
111
+ found_command = None
112
+ cmd_group = False
113
+ for handler in star_handlers_registry:
114
+ assert isinstance(handler, StarHandlerMetadata)
115
+ for filter_ in handler.event_filters:
116
+ if isinstance(filter_, CommandFilter):
117
+ if filter_.equals(cmd_name):
118
+ found_command = handler
119
+ break
120
+ elif isinstance(filter_, CommandGroupFilter):
121
+ if filter_.equals(cmd_name):
122
+ found_command = handler
123
+ cmd_group = True
124
+ break
125
+
126
+ if not found_command:
127
+ await event.send(MessageChain().message("未找到该指令"))
128
+ return
129
+
130
+ found_plugin = star_map[found_command.handler_module_path]
131
+
132
+ from astrbot.api import sp
133
+
134
+ alter_cmd_cfg = await sp.global_get("alter_cmd", {})
135
+ plugin_ = alter_cmd_cfg.get(found_plugin.name, {})
136
+ cfg = plugin_.get(found_command.handler_name, {})
137
+ cfg["permission"] = cmd_type
138
+ plugin_[found_command.handler_name] = cfg
139
+ alter_cmd_cfg[found_plugin.name] = plugin_
140
+
141
+ await sp.global_put("alter_cmd", alter_cmd_cfg)
142
+
143
+ # 注入权限过滤器
144
+ found_permission_filter = False
145
+ for filter_ in found_command.event_filters:
146
+ if isinstance(filter_, PermissionTypeFilter):
147
+ if cmd_type == "admin":
148
+ from astrbot.api.event import filter
149
+
150
+ filter_.permission_type = filter.PermissionType.ADMIN
151
+ else:
152
+ from astrbot.api.event import filter
153
+
154
+ filter_.permission_type = filter.PermissionType.MEMBER
155
+ found_permission_filter = True
156
+ break
157
+ if not found_permission_filter:
158
+ from astrbot.api.event import filter
159
+
160
+ found_command.event_filters.insert(
161
+ 0,
162
+ PermissionTypeFilter(
163
+ filter.PermissionType.ADMIN
164
+ if cmd_type == "admin"
165
+ else filter.PermissionType.MEMBER,
166
+ ),
167
+ )
168
+ cmd_group_str = "指令组" if cmd_group else "指令"
169
+ await event.send(
170
+ MessageChain().message(
171
+ f"已将「{cmd_name}」{cmd_group_str} 的权限级别调整为 {cmd_type}。",
172
+ ),
173
+ )
@@ -0,0 +1,366 @@
1
+ import datetime
2
+
3
+ from astrbot.api import sp, star
4
+ from astrbot.api.event import AstrMessageEvent, MessageEventResult
5
+ from astrbot.core.platform.astr_message_event import MessageSession
6
+ from astrbot.core.platform.message_type import MessageType
7
+
8
+ from .utils.rst_scene import RstScene
9
+
10
+ THIRD_PARTY_AGENT_RUNNER_KEY = {
11
+ "dify": "dify_conversation_id",
12
+ "coze": "coze_conversation_id",
13
+ "dashscope": "dashscope_conversation_id",
14
+ }
15
+ THIRD_PARTY_AGENT_RUNNER_STR = ", ".join(THIRD_PARTY_AGENT_RUNNER_KEY.keys())
16
+
17
+
18
+ class ConversationCommands:
19
+ def __init__(self, context: star.Context):
20
+ self.context = context
21
+
22
+ async def _get_current_persona_id(self, session_id):
23
+ curr = await self.context.conversation_manager.get_curr_conversation_id(
24
+ session_id,
25
+ )
26
+ if not curr:
27
+ return None
28
+ conv = await self.context.conversation_manager.get_conversation(
29
+ session_id,
30
+ curr,
31
+ )
32
+ if not conv:
33
+ return None
34
+ return conv.persona_id
35
+
36
+ async def reset(self, message: AstrMessageEvent):
37
+ """重置 LLM 会话"""
38
+ umo = message.unified_msg_origin
39
+ cfg = self.context.get_config(umo=message.unified_msg_origin)
40
+ is_unique_session = cfg["platform_settings"]["unique_session"]
41
+ is_group = bool(message.get_group_id())
42
+
43
+ scene = RstScene.get_scene(is_group, is_unique_session)
44
+
45
+ alter_cmd_cfg = await sp.get_async("global", "global", "alter_cmd", {})
46
+ plugin_config = alter_cmd_cfg.get("astrbot", {})
47
+ reset_cfg = plugin_config.get("reset", {})
48
+
49
+ required_perm = reset_cfg.get(
50
+ scene.key,
51
+ "admin" if is_group and not is_unique_session else "member",
52
+ )
53
+
54
+ if required_perm == "admin" and message.role != "admin":
55
+ message.set_result(
56
+ MessageEventResult().message(
57
+ f"在{scene.name}场景下,reset命令需要管理员权限,"
58
+ f"您 (ID {message.get_sender_id()}) 不是管理员,无法执行此操作。",
59
+ ),
60
+ )
61
+ return
62
+
63
+ agent_runner_type = cfg["provider_settings"]["agent_runner_type"]
64
+ if agent_runner_type in THIRD_PARTY_AGENT_RUNNER_KEY:
65
+ await sp.remove_async(
66
+ scope="umo",
67
+ scope_id=umo,
68
+ key=THIRD_PARTY_AGENT_RUNNER_KEY[agent_runner_type],
69
+ )
70
+ message.set_result(MessageEventResult().message("重置对话成功。"))
71
+ return
72
+
73
+ if not self.context.get_using_provider(umo):
74
+ message.set_result(
75
+ MessageEventResult().message("未找到任何 LLM 提供商。请先配置。"),
76
+ )
77
+ return
78
+
79
+ cid = await self.context.conversation_manager.get_curr_conversation_id(umo)
80
+
81
+ if not cid:
82
+ message.set_result(
83
+ MessageEventResult().message(
84
+ "当前未处于对话状态,请 /switch 切换或者 /new 创建。",
85
+ ),
86
+ )
87
+ return
88
+
89
+ await self.context.conversation_manager.update_conversation(
90
+ umo,
91
+ cid,
92
+ [],
93
+ )
94
+
95
+ ret = "清除聊天历史成功!"
96
+
97
+ message.set_extra("_clean_ltm_session", True)
98
+
99
+ message.set_result(MessageEventResult().message(ret))
100
+
101
+ async def his(self, message: AstrMessageEvent, page: int = 1):
102
+ """查看对话记录"""
103
+ if not self.context.get_using_provider(message.unified_msg_origin):
104
+ message.set_result(
105
+ MessageEventResult().message("未找到任何 LLM 提供商。请先配置。"),
106
+ )
107
+ return
108
+
109
+ size_per_page = 6
110
+
111
+ conv_mgr = self.context.conversation_manager
112
+ umo = message.unified_msg_origin
113
+ session_curr_cid = await conv_mgr.get_curr_conversation_id(umo)
114
+
115
+ if not session_curr_cid:
116
+ session_curr_cid = await conv_mgr.new_conversation(
117
+ umo,
118
+ message.get_platform_id(),
119
+ )
120
+
121
+ contexts, total_pages = await conv_mgr.get_human_readable_context(
122
+ umo,
123
+ session_curr_cid,
124
+ page,
125
+ size_per_page,
126
+ )
127
+
128
+ parts = []
129
+ for context in contexts:
130
+ if len(context) > 150:
131
+ context = context[:150] + "..."
132
+ parts.append(f"{context}\n")
133
+
134
+ history = "".join(parts)
135
+ ret = (
136
+ f"当前对话历史记录:"
137
+ f"{history or '无历史记录'}\n\n"
138
+ f"第 {page} 页 | 共 {total_pages} 页\n"
139
+ f"*输入 /history 2 跳转到第 2 页"
140
+ )
141
+
142
+ message.set_result(MessageEventResult().message(ret).use_t2i(False))
143
+
144
+ async def convs(self, message: AstrMessageEvent, page: int = 1):
145
+ """查看对话列表"""
146
+ cfg = self.context.get_config(umo=message.unified_msg_origin)
147
+ agent_runner_type = cfg["provider_settings"]["agent_runner_type"]
148
+ if agent_runner_type in THIRD_PARTY_AGENT_RUNNER_KEY:
149
+ message.set_result(
150
+ MessageEventResult().message(
151
+ f"{THIRD_PARTY_AGENT_RUNNER_STR} 对话列表功能暂不支持。",
152
+ ),
153
+ )
154
+ return
155
+
156
+ size_per_page = 6
157
+ """获取所有对话列表"""
158
+ conversations_all = await self.context.conversation_manager.get_conversations(
159
+ message.unified_msg_origin,
160
+ )
161
+ """计算总页数"""
162
+ total_pages = (len(conversations_all) + size_per_page - 1) // size_per_page
163
+ """确保页码有效"""
164
+ page = max(1, min(page, total_pages))
165
+ """分页处理"""
166
+ start_idx = (page - 1) * size_per_page
167
+ end_idx = start_idx + size_per_page
168
+ conversations_paged = conversations_all[start_idx:end_idx]
169
+
170
+ parts = ["对话列表:\n---\n"]
171
+ """全局序号从当前页的第一个开始"""
172
+ global_index = start_idx + 1
173
+
174
+ """生成所有对话的标题字典"""
175
+ _titles = {}
176
+ for conv in conversations_all:
177
+ title = conv.title if conv.title else "新对话"
178
+ _titles[conv.cid] = title
179
+
180
+ """遍历分页后的对话生成列表显示"""
181
+ for conv in conversations_paged:
182
+ persona_id = conv.persona_id
183
+ if not persona_id or persona_id == "[%None]":
184
+ persona = await self.context.persona_manager.get_default_persona_v3(
185
+ umo=message.unified_msg_origin,
186
+ )
187
+ persona_id = persona["name"]
188
+ title = _titles.get(conv.cid, "新对话")
189
+ parts.append(
190
+ f"{global_index}. {title}({conv.cid[:4]})\n 人格情景: {persona_id}\n 上次更新: {datetime.datetime.fromtimestamp(conv.updated_at).strftime('%m-%d %H:%M')}\n"
191
+ )
192
+ global_index += 1
193
+
194
+ parts.append("---\n")
195
+ ret = "".join(parts)
196
+ curr_cid = await self.context.conversation_manager.get_curr_conversation_id(
197
+ message.unified_msg_origin,
198
+ )
199
+ if curr_cid:
200
+ """从所有对话的标题字典中获取标题"""
201
+ title = _titles.get(curr_cid, "新对话")
202
+ ret += f"\n当前对话: {title}({curr_cid[:4]})"
203
+ else:
204
+ ret += "\n当前对话: 无"
205
+
206
+ cfg = self.context.get_config(umo=message.unified_msg_origin)
207
+ unique_session = cfg["platform_settings"]["unique_session"]
208
+ if unique_session:
209
+ ret += "\n会话隔离粒度: 个人"
210
+ else:
211
+ ret += "\n会话隔离粒度: 群聊"
212
+
213
+ ret += f"\n第 {page} 页 | 共 {total_pages} 页"
214
+ ret += "\n*输入 /ls 2 跳转到第 2 页"
215
+
216
+ message.set_result(MessageEventResult().message(ret).use_t2i(False))
217
+ return
218
+
219
+ async def new_conv(self, message: AstrMessageEvent):
220
+ """创建新对话"""
221
+ cfg = self.context.get_config(umo=message.unified_msg_origin)
222
+ agent_runner_type = cfg["provider_settings"]["agent_runner_type"]
223
+ if agent_runner_type in THIRD_PARTY_AGENT_RUNNER_KEY:
224
+ await sp.remove_async(
225
+ scope="umo",
226
+ scope_id=message.unified_msg_origin,
227
+ key=THIRD_PARTY_AGENT_RUNNER_KEY[agent_runner_type],
228
+ )
229
+ message.set_result(MessageEventResult().message("已创建新对话。"))
230
+ return
231
+
232
+ cpersona = await self._get_current_persona_id(message.unified_msg_origin)
233
+ cid = await self.context.conversation_manager.new_conversation(
234
+ message.unified_msg_origin,
235
+ message.get_platform_id(),
236
+ persona_id=cpersona,
237
+ )
238
+
239
+ message.set_extra("_clean_ltm_session", True)
240
+
241
+ message.set_result(
242
+ MessageEventResult().message(f"切换到新对话: 新对话({cid[:4]})。"),
243
+ )
244
+
245
+ async def groupnew_conv(self, message: AstrMessageEvent, sid: str = ""):
246
+ """创建新群聊对话"""
247
+ if sid:
248
+ session = str(
249
+ MessageSession(
250
+ platform_name=message.platform_meta.id,
251
+ message_type=MessageType("GroupMessage"),
252
+ session_id=sid,
253
+ ),
254
+ )
255
+
256
+ cpersona = await self._get_current_persona_id(session)
257
+ cid = await self.context.conversation_manager.new_conversation(
258
+ session,
259
+ message.get_platform_id(),
260
+ persona_id=cpersona,
261
+ )
262
+ message.set_result(
263
+ MessageEventResult().message(
264
+ f"群聊 {session} 已切换到新对话: 新对话({cid[:4]})。",
265
+ ),
266
+ )
267
+ else:
268
+ message.set_result(
269
+ MessageEventResult().message("请输入群聊 ID。/groupnew 群聊ID。"),
270
+ )
271
+
272
+ async def switch_conv(
273
+ self,
274
+ message: AstrMessageEvent,
275
+ index: int | None = None,
276
+ ):
277
+ """通过 /ls 前面的序号切换对话"""
278
+ if not isinstance(index, int):
279
+ message.set_result(
280
+ MessageEventResult().message("类型错误,请输入数字对话序号。"),
281
+ )
282
+ return
283
+
284
+ if index is None:
285
+ message.set_result(
286
+ MessageEventResult().message(
287
+ "请输入对话序号。/switch 对话序号。/ls 查看对话 /new 新建对话",
288
+ ),
289
+ )
290
+ return
291
+ conversations = await self.context.conversation_manager.get_conversations(
292
+ message.unified_msg_origin,
293
+ )
294
+ if index > len(conversations) or index < 1:
295
+ message.set_result(
296
+ MessageEventResult().message("对话序号错误,请使用 /ls 查看"),
297
+ )
298
+ else:
299
+ conversation = conversations[index - 1]
300
+ title = conversation.title if conversation.title else "新对话"
301
+ await self.context.conversation_manager.switch_conversation(
302
+ message.unified_msg_origin,
303
+ conversation.cid,
304
+ )
305
+ message.set_result(
306
+ MessageEventResult().message(
307
+ f"切换到对话: {title}({conversation.cid[:4]})。",
308
+ ),
309
+ )
310
+
311
+ async def rename_conv(self, message: AstrMessageEvent, new_name: str = ""):
312
+ """重命名对话"""
313
+ if not new_name:
314
+ message.set_result(MessageEventResult().message("请输入新的对话名称。"))
315
+ return
316
+ await self.context.conversation_manager.update_conversation_title(
317
+ message.unified_msg_origin,
318
+ new_name,
319
+ )
320
+ message.set_result(MessageEventResult().message("重命名对话成功。"))
321
+
322
+ async def del_conv(self, message: AstrMessageEvent):
323
+ """删除当前对话"""
324
+ cfg = self.context.get_config(umo=message.unified_msg_origin)
325
+ is_unique_session = cfg["platform_settings"]["unique_session"]
326
+ if message.get_group_id() and not is_unique_session and message.role != "admin":
327
+ # 群聊,没开独立会话,发送人不是管理员
328
+ message.set_result(
329
+ MessageEventResult().message(
330
+ f"会话处于群聊,并且未开启独立会话,并且您 (ID {message.get_sender_id()}) 不是管理员,因此没有权限删除当前对话。",
331
+ ),
332
+ )
333
+ return
334
+
335
+ agent_runner_type = cfg["provider_settings"]["agent_runner_type"]
336
+ if agent_runner_type in THIRD_PARTY_AGENT_RUNNER_KEY:
337
+ await sp.remove_async(
338
+ scope="umo",
339
+ scope_id=message.unified_msg_origin,
340
+ key=THIRD_PARTY_AGENT_RUNNER_KEY[agent_runner_type],
341
+ )
342
+ message.set_result(MessageEventResult().message("重置对话成功。"))
343
+ return
344
+
345
+ session_curr_cid = (
346
+ await self.context.conversation_manager.get_curr_conversation_id(
347
+ message.unified_msg_origin,
348
+ )
349
+ )
350
+
351
+ if not session_curr_cid:
352
+ message.set_result(
353
+ MessageEventResult().message(
354
+ "当前未处于对话状态,请 /switch 序号 切换或 /new 创建。",
355
+ ),
356
+ )
357
+ return
358
+
359
+ await self.context.conversation_manager.delete_conversation(
360
+ message.unified_msg_origin,
361
+ session_curr_cid,
362
+ )
363
+
364
+ ret = "删除当前对话成功。不再处于对话状态,使用 /switch 序号 切换到其他对话或 /new 创建。"
365
+ message.set_extra("_clean_ltm_session", True)
366
+ message.set_result(MessageEventResult().message(ret))
@@ -0,0 +1,88 @@
1
+ import aiohttp
2
+
3
+ from astrbot.api import star
4
+ from astrbot.api.event import AstrMessageEvent, MessageEventResult
5
+ from astrbot.core.config.default import VERSION
6
+ from astrbot.core.star import command_management
7
+ from astrbot.core.utils.io import get_dashboard_version
8
+
9
+
10
+ class HelpCommand:
11
+ def __init__(self, context: star.Context):
12
+ self.context = context
13
+
14
+ async def _query_astrbot_notice(self):
15
+ try:
16
+ async with aiohttp.ClientSession(trust_env=True) as session:
17
+ async with session.get(
18
+ "https://astrbot.app/notice.json",
19
+ timeout=2,
20
+ ) as resp:
21
+ return (await resp.json())["notice"]
22
+ except BaseException:
23
+ return ""
24
+
25
+ async def _build_reserved_command_lines(self) -> list[str]:
26
+ """
27
+ 使用实时指令配置生成内置指令清单,确保重命名/禁用后与实际生效状态保持一致。
28
+ """
29
+ try:
30
+ commands = await command_management.list_commands()
31
+ except BaseException:
32
+ return []
33
+
34
+ lines: list[str] = []
35
+ hidden_commands = {"set", "unset", "websearch"}
36
+
37
+ def walk(items: list[dict], indent: int = 0):
38
+ for item in items:
39
+ if not item.get("reserved") or not item.get("enabled"):
40
+ continue
41
+ # 仅展示顶级指令或指令组
42
+ if item.get("type") == "sub_command":
43
+ continue
44
+ if item.get("parent_signature"):
45
+ continue
46
+
47
+ effective = (
48
+ item.get("effective_command")
49
+ or item.get("original_command")
50
+ or item.get("handler_name")
51
+ )
52
+ if not effective:
53
+ continue
54
+ if effective in hidden_commands:
55
+ continue
56
+
57
+ description = item.get("description") or ""
58
+ desc_text = f" - {description}" if description else ""
59
+ indent_prefix = " " * indent
60
+ lines.append(f"{indent_prefix}/{effective}{desc_text}")
61
+
62
+ walk(commands)
63
+ return lines
64
+
65
+ async def help(self, event: AstrMessageEvent):
66
+ """查看帮助"""
67
+ notice = ""
68
+ try:
69
+ notice = await self._query_astrbot_notice()
70
+ except BaseException:
71
+ pass
72
+
73
+ dashboard_version = await get_dashboard_version()
74
+ command_lines = await self._build_reserved_command_lines()
75
+ commands_section = (
76
+ "\n".join(command_lines) if command_lines else "暂无启用的内置指令"
77
+ )
78
+
79
+ msg_parts = [
80
+ f"AstrBot v{VERSION}(WebUI: {dashboard_version})",
81
+ "内置指令:",
82
+ commands_section,
83
+ ]
84
+ if notice:
85
+ msg_parts.append(notice)
86
+ msg = "\n".join(msg_parts)
87
+
88
+ event.set_result(MessageEventResult().message(msg).use_t2i(False))
@@ -0,0 +1,20 @@
1
+ from astrbot.api import star
2
+ from astrbot.api.event import AstrMessageEvent, MessageChain
3
+
4
+
5
+ class LLMCommands:
6
+ def __init__(self, context: star.Context):
7
+ self.context = context
8
+
9
+ async def llm(self, event: AstrMessageEvent):
10
+ """开启/关闭 LLM"""
11
+ cfg = self.context.get_config(umo=event.unified_msg_origin)
12
+ enable = cfg["provider_settings"].get("enable", True)
13
+ if enable:
14
+ cfg["provider_settings"]["enable"] = False
15
+ status = "关闭"
16
+ else:
17
+ cfg["provider_settings"]["enable"] = True
18
+ status = "开启"
19
+ cfg.save_config()
20
+ await event.send(MessageChain().message(f"{status} LLM 聊天功能。"))