AstrBot 4.1.4__py3-none-any.whl → 4.1.6__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/core/agent/agent.py +1 -1
- astrbot/core/agent/mcp_client.py +3 -1
- astrbot/core/agent/runners/tool_loop_agent_runner.py +6 -27
- astrbot/core/agent/tool.py +28 -17
- astrbot/core/config/default.py +50 -14
- astrbot/core/db/sqlite.py +16 -1
- astrbot/core/pipeline/content_safety_check/stage.py +1 -1
- astrbot/core/pipeline/content_safety_check/strategies/baidu_aip.py +1 -1
- astrbot/core/pipeline/content_safety_check/strategies/keywords.py +1 -1
- astrbot/core/pipeline/context_utils.py +4 -1
- astrbot/core/pipeline/process_stage/method/llm_request.py +23 -4
- astrbot/core/pipeline/process_stage/method/star_request.py +8 -6
- astrbot/core/platform/manager.py +4 -0
- astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py +2 -1
- astrbot/core/platform/sources/misskey/misskey_adapter.py +391 -0
- astrbot/core/platform/sources/misskey/misskey_api.py +404 -0
- astrbot/core/platform/sources/misskey/misskey_event.py +123 -0
- astrbot/core/platform/sources/misskey/misskey_utils.py +327 -0
- astrbot/core/platform/sources/satori/satori_adapter.py +290 -24
- astrbot/core/platform/sources/satori/satori_event.py +9 -0
- astrbot/core/platform/sources/telegram/tg_event.py +0 -1
- astrbot/core/provider/entities.py +13 -3
- astrbot/core/provider/func_tool_manager.py +4 -4
- astrbot/core/provider/manager.py +53 -24
- astrbot/core/star/context.py +31 -14
- astrbot/core/star/filter/command_group.py +4 -4
- astrbot/core/star/filter/platform_adapter_type.py +10 -5
- astrbot/core/star/register/star.py +3 -1
- astrbot/core/star/register/star_handler.py +65 -36
- astrbot/core/star/session_plugin_manager.py +3 -0
- astrbot/core/star/star_handler.py +4 -4
- astrbot/core/star/star_manager.py +10 -4
- astrbot/core/star/star_tools.py +6 -2
- astrbot/core/star/updator.py +3 -0
- {astrbot-4.1.4.dist-info → astrbot-4.1.6.dist-info}/METADATA +6 -7
- {astrbot-4.1.4.dist-info → astrbot-4.1.6.dist-info}/RECORD +39 -35
- {astrbot-4.1.4.dist-info → astrbot-4.1.6.dist-info}/WHEEL +0 -0
- {astrbot-4.1.4.dist-info → astrbot-4.1.6.dist-info}/entry_points.txt +0 -0
- {astrbot-4.1.4.dist-info → astrbot-4.1.6.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
|
+
}
|