AstrBot 4.1.4__py3-none-any.whl → 4.1.5__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 (39) hide show
  1. astrbot/core/agent/agent.py +1 -1
  2. astrbot/core/agent/mcp_client.py +3 -1
  3. astrbot/core/agent/runners/tool_loop_agent_runner.py +6 -27
  4. astrbot/core/agent/tool.py +28 -17
  5. astrbot/core/config/default.py +50 -14
  6. astrbot/core/db/sqlite.py +15 -1
  7. astrbot/core/pipeline/content_safety_check/stage.py +1 -1
  8. astrbot/core/pipeline/content_safety_check/strategies/baidu_aip.py +1 -1
  9. astrbot/core/pipeline/content_safety_check/strategies/keywords.py +1 -1
  10. astrbot/core/pipeline/context_utils.py +4 -1
  11. astrbot/core/pipeline/process_stage/method/llm_request.py +23 -4
  12. astrbot/core/pipeline/process_stage/method/star_request.py +8 -6
  13. astrbot/core/platform/manager.py +4 -0
  14. astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py +2 -1
  15. astrbot/core/platform/sources/misskey/misskey_adapter.py +391 -0
  16. astrbot/core/platform/sources/misskey/misskey_api.py +404 -0
  17. astrbot/core/platform/sources/misskey/misskey_event.py +123 -0
  18. astrbot/core/platform/sources/misskey/misskey_utils.py +327 -0
  19. astrbot/core/platform/sources/satori/satori_adapter.py +290 -24
  20. astrbot/core/platform/sources/satori/satori_event.py +9 -0
  21. astrbot/core/platform/sources/telegram/tg_event.py +0 -1
  22. astrbot/core/provider/entities.py +13 -3
  23. astrbot/core/provider/func_tool_manager.py +4 -4
  24. astrbot/core/provider/manager.py +35 -19
  25. astrbot/core/star/context.py +26 -12
  26. astrbot/core/star/filter/command_group.py +4 -4
  27. astrbot/core/star/filter/platform_adapter_type.py +10 -5
  28. astrbot/core/star/register/star.py +3 -1
  29. astrbot/core/star/register/star_handler.py +65 -36
  30. astrbot/core/star/session_plugin_manager.py +3 -0
  31. astrbot/core/star/star_handler.py +4 -4
  32. astrbot/core/star/star_manager.py +10 -4
  33. astrbot/core/star/star_tools.py +6 -2
  34. astrbot/core/star/updator.py +3 -0
  35. {astrbot-4.1.4.dist-info → astrbot-4.1.5.dist-info}/METADATA +6 -7
  36. {astrbot-4.1.4.dist-info → astrbot-4.1.5.dist-info}/RECORD +39 -35
  37. {astrbot-4.1.4.dist-info → astrbot-4.1.5.dist-info}/WHEEL +0 -0
  38. {astrbot-4.1.4.dist-info → astrbot-4.1.5.dist-info}/entry_points.txt +0 -0
  39. {astrbot-4.1.4.dist-info → astrbot-4.1.5.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,327 @@
1
+ """Misskey 平台适配器通用工具函数"""
2
+
3
+ from typing import Dict, Any, List, Tuple, Optional, Union
4
+ import astrbot.api.message_components as Comp
5
+ from astrbot.api.platform import AstrBotMessage, MessageMember, MessageType
6
+
7
+
8
+ def serialize_message_chain(chain: List[Any]) -> Tuple[str, bool]:
9
+ """将消息链序列化为文本字符串"""
10
+ text_parts = []
11
+ has_at = False
12
+
13
+ def process_component(component):
14
+ nonlocal has_at
15
+ if isinstance(component, Comp.Plain):
16
+ return component.text
17
+ elif isinstance(component, Comp.File):
18
+ file_name = getattr(component, "name", "文件")
19
+ return f"[文件: {file_name}]"
20
+ elif isinstance(component, Comp.At):
21
+ has_at = True
22
+ return f"@{component.qq}"
23
+ elif hasattr(component, "text"):
24
+ text = getattr(component, "text", "")
25
+ if "@" in text:
26
+ has_at = True
27
+ return text
28
+ else:
29
+ return str(component)
30
+
31
+ for component in chain:
32
+ if isinstance(component, Comp.Node) and component.content:
33
+ for node_comp in component.content:
34
+ result = process_component(node_comp)
35
+ if result:
36
+ text_parts.append(result)
37
+ else:
38
+ result = process_component(component)
39
+ if result:
40
+ text_parts.append(result)
41
+
42
+ return "".join(text_parts), has_at
43
+
44
+
45
+ def resolve_message_visibility(
46
+ user_id: Optional[str],
47
+ user_cache: Dict[str, Any],
48
+ self_id: Optional[str],
49
+ default_visibility: str = "public",
50
+ ) -> Tuple[str, Optional[List[str]]]:
51
+ """解析 Misskey 消息的可见性设置"""
52
+ visibility = default_visibility
53
+ visible_user_ids = None
54
+
55
+ if user_id and user_cache:
56
+ user_info = user_cache.get(user_id)
57
+ if user_info:
58
+ original_visibility = user_info.get("visibility", default_visibility)
59
+ if original_visibility == "specified":
60
+ visibility = "specified"
61
+ original_visible_users = user_info.get("visible_user_ids", [])
62
+ users_to_include = [user_id]
63
+ if self_id:
64
+ users_to_include.append(self_id)
65
+ visible_user_ids = list(set(original_visible_users + users_to_include))
66
+ visible_user_ids = [uid for uid in visible_user_ids if uid]
67
+ else:
68
+ visibility = original_visibility
69
+
70
+ return visibility, visible_user_ids
71
+
72
+
73
+ def resolve_visibility_from_raw_message(
74
+ raw_message: Dict[str, Any], self_id: Optional[str] = None
75
+ ) -> Tuple[str, Optional[List[str]]]:
76
+ """从原始消息数据中解析可见性设置"""
77
+ visibility = "public"
78
+ visible_user_ids = None
79
+
80
+ if not raw_message:
81
+ return visibility, visible_user_ids
82
+
83
+ original_visibility = raw_message.get("visibility", "public")
84
+ if original_visibility == "specified":
85
+ visibility = "specified"
86
+ original_visible_users = raw_message.get("visibleUserIds", [])
87
+ sender_id = raw_message.get("userId", "")
88
+
89
+ users_to_include = []
90
+ if sender_id:
91
+ users_to_include.append(sender_id)
92
+ if self_id:
93
+ users_to_include.append(self_id)
94
+
95
+ visible_user_ids = list(set(original_visible_users + users_to_include))
96
+ visible_user_ids = [uid for uid in visible_user_ids if uid]
97
+ else:
98
+ visibility = original_visibility
99
+
100
+ return visibility, visible_user_ids
101
+
102
+
103
+ def is_valid_user_session_id(session_id: Union[str, Any]) -> bool:
104
+ """检查 session_id 是否是有效的聊天用户 session_id (仅限chat%前缀)"""
105
+ if not isinstance(session_id, str) or "%" not in session_id:
106
+ return False
107
+
108
+ parts = session_id.split("%")
109
+ return (
110
+ len(parts) == 2
111
+ and parts[0] == "chat"
112
+ and bool(parts[1])
113
+ and parts[1] != "unknown"
114
+ )
115
+
116
+
117
+ def is_valid_room_session_id(session_id: Union[str, Any]) -> bool:
118
+ """检查 session_id 是否是有效的房间 session_id (仅限room%前缀)"""
119
+ if not isinstance(session_id, str) or "%" not in session_id:
120
+ return False
121
+
122
+ parts = session_id.split("%")
123
+ return (
124
+ len(parts) == 2
125
+ and parts[0] == "room"
126
+ and bool(parts[1])
127
+ and parts[1] != "unknown"
128
+ )
129
+
130
+
131
+ def extract_user_id_from_session_id(session_id: str) -> str:
132
+ """从 session_id 中提取用户 ID"""
133
+ if "%" in session_id:
134
+ parts = session_id.split("%")
135
+ if len(parts) >= 2:
136
+ return parts[1]
137
+ return session_id
138
+
139
+
140
+ def extract_room_id_from_session_id(session_id: str) -> str:
141
+ """从 session_id 中提取房间 ID"""
142
+ if "%" in session_id:
143
+ parts = session_id.split("%")
144
+ if len(parts) >= 2 and parts[0] == "room":
145
+ return parts[1]
146
+ return session_id
147
+
148
+
149
+ def add_at_mention_if_needed(
150
+ text: str, user_info: Optional[Dict[str, Any]], has_at: bool = False
151
+ ) -> str:
152
+ """如果需要且没有@用户,则添加@用户"""
153
+ if has_at or not user_info:
154
+ return text
155
+
156
+ username = user_info.get("username")
157
+ nickname = user_info.get("nickname")
158
+
159
+ if username:
160
+ mention = f"@{username}"
161
+ if not text.startswith(mention):
162
+ text = f"{mention}\n{text}".strip()
163
+ elif nickname:
164
+ mention = f"@{nickname}"
165
+ if not text.startswith(mention):
166
+ text = f"{mention}\n{text}".strip()
167
+
168
+ return text
169
+
170
+
171
+ def create_file_component(file_info: Dict[str, Any]) -> Tuple[Any, str]:
172
+ """创建文件组件和描述文本"""
173
+ file_url = file_info.get("url", "")
174
+ file_name = file_info.get("name", "未知文件")
175
+ file_type = file_info.get("type", "")
176
+
177
+ if file_type.startswith("image/"):
178
+ return Comp.Image(url=file_url, file=file_name), f"图片[{file_name}]"
179
+ elif file_type.startswith("audio/"):
180
+ return Comp.Record(url=file_url, file=file_name), f"音频[{file_name}]"
181
+ elif file_type.startswith("video/"):
182
+ return Comp.Video(url=file_url, file=file_name), f"视频[{file_name}]"
183
+ else:
184
+ return Comp.File(name=file_name, url=file_url), f"文件[{file_name}]"
185
+
186
+
187
+ def process_files(
188
+ message: AstrBotMessage, files: list, include_text_parts: bool = True
189
+ ) -> list:
190
+ """处理文件列表,添加到消息组件中并返回文本描述"""
191
+ file_parts = []
192
+ for file_info in files:
193
+ component, part_text = create_file_component(file_info)
194
+ message.message.append(component)
195
+ if include_text_parts:
196
+ file_parts.append(part_text)
197
+ return file_parts
198
+
199
+
200
+ def extract_sender_info(
201
+ raw_data: Dict[str, Any], is_chat: bool = False
202
+ ) -> Dict[str, Any]:
203
+ """提取发送者信息"""
204
+ if is_chat:
205
+ sender = raw_data.get("fromUser", {})
206
+ sender_id = str(sender.get("id", "") or raw_data.get("fromUserId", ""))
207
+ else:
208
+ sender = raw_data.get("user", {})
209
+ sender_id = str(sender.get("id", ""))
210
+
211
+ return {
212
+ "sender": sender,
213
+ "sender_id": sender_id,
214
+ "nickname": sender.get("name", sender.get("username", "")),
215
+ "username": sender.get("username", ""),
216
+ }
217
+
218
+
219
+ def create_base_message(
220
+ raw_data: Dict[str, Any],
221
+ sender_info: Dict[str, Any],
222
+ client_self_id: str,
223
+ is_chat: bool = False,
224
+ room_id: Optional[str] = None,
225
+ unique_session: bool = False,
226
+ ) -> AstrBotMessage:
227
+ """创建基础消息对象"""
228
+ message = AstrBotMessage()
229
+ message.raw_message = raw_data
230
+ message.message = []
231
+
232
+ message.sender = MessageMember(
233
+ user_id=sender_info["sender_id"],
234
+ nickname=sender_info["nickname"],
235
+ )
236
+
237
+ if room_id:
238
+ session_prefix = "room"
239
+ session_id = f"{session_prefix}%{room_id}"
240
+ if unique_session:
241
+ session_id += f"_{sender_info['sender_id']}"
242
+ message.type = MessageType.GROUP_MESSAGE
243
+ message.group_id = room_id
244
+ elif is_chat:
245
+ session_prefix = "chat"
246
+ session_id = f"{session_prefix}%{sender_info['sender_id']}"
247
+ message.type = MessageType.FRIEND_MESSAGE
248
+ else:
249
+ session_prefix = "note"
250
+ session_id = f"{session_prefix}%{sender_info['sender_id']}"
251
+ message.type = MessageType.FRIEND_MESSAGE
252
+
253
+ message.session_id = (
254
+ session_id if sender_info["sender_id"] else f"{session_prefix}%unknown"
255
+ )
256
+ message.message_id = str(raw_data.get("id", ""))
257
+ message.self_id = client_self_id
258
+
259
+ return message
260
+
261
+
262
+ def process_at_mention(
263
+ message: AstrBotMessage, raw_text: str, bot_username: str, client_self_id: str
264
+ ) -> Tuple[List[str], str]:
265
+ """处理@提及逻辑,返回消息部分列表和处理后的文本"""
266
+ message_parts = []
267
+
268
+ if not raw_text:
269
+ return message_parts, ""
270
+
271
+ if bot_username and raw_text.startswith(f"@{bot_username}"):
272
+ at_mention = f"@{bot_username}"
273
+ message.message.append(Comp.At(qq=client_self_id))
274
+ remaining_text = raw_text[len(at_mention) :].strip()
275
+ if remaining_text:
276
+ message.message.append(Comp.Plain(remaining_text))
277
+ message_parts.append(remaining_text)
278
+ return message_parts, remaining_text
279
+ else:
280
+ message.message.append(Comp.Plain(raw_text))
281
+ message_parts.append(raw_text)
282
+ return message_parts, raw_text
283
+
284
+
285
+ def cache_user_info(
286
+ user_cache: Dict[str, Any],
287
+ sender_info: Dict[str, Any],
288
+ raw_data: Dict[str, Any],
289
+ client_self_id: str,
290
+ is_chat: bool = False,
291
+ ):
292
+ """缓存用户信息"""
293
+ if is_chat:
294
+ user_cache_data = {
295
+ "username": sender_info["username"],
296
+ "nickname": sender_info["nickname"],
297
+ "visibility": "specified",
298
+ "visible_user_ids": [client_self_id, sender_info["sender_id"]],
299
+ }
300
+ else:
301
+ user_cache_data = {
302
+ "username": sender_info["username"],
303
+ "nickname": sender_info["nickname"],
304
+ "visibility": raw_data.get("visibility", "public"),
305
+ "visible_user_ids": raw_data.get("visibleUserIds", []),
306
+ }
307
+
308
+ user_cache[sender_info["sender_id"]] = user_cache_data
309
+
310
+
311
+ def cache_room_info(
312
+ user_cache: Dict[str, Any], raw_data: Dict[str, Any], client_self_id: str
313
+ ):
314
+ """缓存房间信息"""
315
+ room_data = raw_data.get("toRoom")
316
+ room_id = raw_data.get("toRoomId")
317
+
318
+ if room_data and room_id:
319
+ room_cache_key = f"room:{room_id}"
320
+ user_cache[room_cache_key] = {
321
+ "room_id": room_id,
322
+ "room_name": room_data.get("name", ""),
323
+ "room_description": room_data.get("description", ""),
324
+ "owner_id": room_data.get("ownerId", ""),
325
+ "visibility": "specified",
326
+ "visible_user_ids": [client_self_id],
327
+ }