AstrBot 4.7.4__py3-none-any.whl → 4.9.0__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/cli/__init__.py +1 -1
- astrbot/core/agent/runners/tool_loop_agent_runner.py +0 -1
- astrbot/core/agent/tool.py +7 -2
- astrbot/core/astr_agent_run_util.py +15 -1
- astrbot/core/astr_agent_tool_exec.py +5 -1
- astrbot/core/config/astrbot_config.py +4 -0
- astrbot/core/config/default.py +116 -1
- astrbot/core/core_lifecycle.py +1 -1
- astrbot/core/db/__init__.py +32 -4
- astrbot/core/db/migration/migra_3_to_4.py +2 -0
- astrbot/core/db/migration/sqlite_v3.py +6 -4
- astrbot/core/db/po.py +16 -15
- astrbot/core/db/sqlite.py +56 -1
- astrbot/core/db/vec_db/faiss_impl/embedding_storage.py +2 -0
- astrbot/core/event_bus.py +6 -1
- astrbot/core/knowledge_base/retrieval/manager.py +5 -1
- astrbot/core/log.py +2 -1
- astrbot/core/message/components.py +9 -3
- astrbot/core/persona_mgr.py +2 -2
- astrbot/core/pipeline/content_safety_check/stage.py +1 -1
- astrbot/core/pipeline/context_utils.py +2 -1
- astrbot/core/pipeline/process_stage/method/agent_sub_stages/third_party.py +1 -1
- astrbot/core/pipeline/process_stage/method/star_request.py +1 -2
- astrbot/core/pipeline/process_stage/stage.py +1 -1
- astrbot/core/pipeline/respond/stage.py +4 -2
- astrbot/core/pipeline/result_decorate/stage.py +68 -21
- astrbot/core/pipeline/scheduler.py +5 -1
- astrbot/core/pipeline/waking_check/stage.py +10 -0
- astrbot/core/platform/astr_message_event.py +5 -3
- astrbot/core/platform/astrbot_message.py +2 -2
- astrbot/core/platform/manager.py +71 -9
- astrbot/core/platform/platform.py +109 -4
- astrbot/core/platform/platform_metadata.py +1 -1
- astrbot/core/platform/register.py +1 -0
- astrbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py +8 -6
- astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py +13 -8
- astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py +28 -22
- astrbot/core/platform/sources/dingtalk/dingtalk_event.py +5 -2
- astrbot/core/platform/sources/discord/client.py +16 -4
- astrbot/core/platform/sources/discord/components.py +2 -2
- astrbot/core/platform/sources/discord/discord_platform_adapter.py +53 -26
- astrbot/core/platform/sources/discord/discord_platform_event.py +29 -8
- astrbot/core/platform/sources/lark/lark_adapter.py +178 -22
- astrbot/core/platform/sources/lark/lark_event.py +39 -4
- astrbot/core/platform/sources/lark/server.py +206 -0
- astrbot/core/platform/sources/misskey/misskey_adapter.py +3 -5
- astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py +64 -18
- astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py +14 -10
- astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_adapter.py +36 -11
- astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_server.py +15 -2
- astrbot/core/platform/sources/satori/satori_adapter.py +1 -2
- astrbot/core/platform/sources/slack/client.py +58 -40
- astrbot/core/platform/sources/slack/slack_adapter.py +36 -16
- astrbot/core/platform/sources/slack/slack_event.py +11 -10
- astrbot/core/platform/sources/telegram/tg_adapter.py +2 -3
- astrbot/core/platform/sources/telegram/tg_event.py +23 -27
- astrbot/core/platform/sources/webchat/webchat_adapter.py +97 -31
- astrbot/core/platform/sources/webchat/webchat_event.py +35 -35
- astrbot/core/platform/sources/wechatpadpro/wechatpadpro_adapter.py +27 -11
- astrbot/core/platform/sources/wecom/wecom_adapter.py +75 -36
- astrbot/core/platform/sources/wecom/wecom_event.py +3 -3
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_adapter.py +26 -9
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_event.py +3 -3
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_server.py +27 -5
- astrbot/core/platform/sources/weixin_official_account/weixin_offacc_adapter.py +81 -35
- astrbot/core/platform/sources/weixin_official_account/weixin_offacc_event.py +11 -8
- astrbot/core/platform_message_history_mgr.py +3 -3
- astrbot/core/provider/func_tool_manager.py +3 -3
- astrbot/core/provider/manager.py +130 -74
- astrbot/core/provider/provider.py +12 -1
- astrbot/core/provider/sources/azure_tts_source.py +31 -9
- astrbot/core/provider/sources/bailian_rerank_source.py +4 -0
- astrbot/core/provider/sources/dashscope_tts.py +3 -2
- astrbot/core/provider/sources/edge_tts_source.py +1 -1
- astrbot/core/provider/sources/fishaudio_tts_api_source.py +5 -4
- astrbot/core/provider/sources/gemini_embedding_source.py +15 -5
- astrbot/core/provider/sources/gemini_source.py +12 -10
- astrbot/core/provider/sources/minimax_tts_api_source.py +4 -2
- astrbot/core/provider/sources/openai_embedding_source.py +2 -2
- astrbot/core/provider/sources/openai_source.py +4 -0
- astrbot/core/provider/sources/sensevoice_selfhosted_source.py +5 -2
- astrbot/core/provider/sources/vllm_rerank_source.py +1 -0
- astrbot/core/provider/sources/whisper_api_source.py +44 -12
- astrbot/core/provider/sources/whisper_selfhosted_source.py +6 -2
- astrbot/core/provider/sources/xinference_rerank_source.py +10 -2
- astrbot/core/star/context.py +2 -2
- astrbot/core/star/register/star_handler.py +22 -5
- astrbot/core/star/star_handler.py +85 -4
- astrbot/core/updator.py +3 -3
- astrbot/core/utils/io.py +1 -1
- astrbot/core/utils/session_waiter.py +17 -10
- astrbot/core/utils/shared_preferences.py +32 -0
- astrbot/core/utils/t2i/__init__.py +2 -2
- astrbot/core/utils/t2i/local_strategy.py +25 -31
- astrbot/core/utils/tencent_record_helper.py +2 -2
- astrbot/core/utils/version_comparator.py +6 -3
- astrbot/core/utils/webhook_utils.py +66 -0
- astrbot/dashboard/routes/__init__.py +2 -0
- astrbot/dashboard/routes/chat.py +311 -76
- astrbot/dashboard/routes/config.py +14 -5
- astrbot/dashboard/routes/knowledge_base.py +254 -79
- astrbot/dashboard/routes/log.py +13 -8
- astrbot/dashboard/routes/platform.py +100 -0
- astrbot/dashboard/routes/plugin.py +108 -51
- astrbot/dashboard/routes/route.py +2 -0
- astrbot/dashboard/server.py +9 -4
- {astrbot-4.7.4.dist-info → astrbot-4.9.0.dist-info}/METADATA +50 -37
- {astrbot-4.7.4.dist-info → astrbot-4.9.0.dist-info}/RECORD +111 -108
- {astrbot-4.7.4.dist-info → astrbot-4.9.0.dist-info}/WHEEL +0 -0
- {astrbot-4.7.4.dist-info → astrbot-4.9.0.dist-info}/entry_points.txt +0 -0
- {astrbot-4.7.4.dist-info → astrbot-4.9.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import abc
|
|
2
2
|
import uuid
|
|
3
3
|
from asyncio import Queue
|
|
4
|
-
from collections.abc import
|
|
4
|
+
from collections.abc import Coroutine
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from enum import Enum
|
|
5
8
|
from typing import Any
|
|
6
9
|
|
|
7
10
|
from astrbot.core.message.message_event_result import MessageChain
|
|
@@ -12,15 +15,100 @@ from .message_session import MessageSesion
|
|
|
12
15
|
from .platform_metadata import PlatformMetadata
|
|
13
16
|
|
|
14
17
|
|
|
18
|
+
class PlatformStatus(Enum):
|
|
19
|
+
"""平台运行状态"""
|
|
20
|
+
|
|
21
|
+
PENDING = "pending" # 待启动
|
|
22
|
+
RUNNING = "running" # 运行中
|
|
23
|
+
ERROR = "error" # 发生错误
|
|
24
|
+
STOPPED = "stopped" # 已停止
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class PlatformError:
|
|
29
|
+
"""平台错误信息"""
|
|
30
|
+
|
|
31
|
+
message: str
|
|
32
|
+
timestamp: datetime = field(default_factory=datetime.now)
|
|
33
|
+
traceback: str | None = None
|
|
34
|
+
|
|
35
|
+
|
|
15
36
|
class Platform(abc.ABC):
|
|
16
|
-
def __init__(self, event_queue: Queue):
|
|
37
|
+
def __init__(self, config: dict, event_queue: Queue):
|
|
17
38
|
super().__init__()
|
|
39
|
+
# 平台配置
|
|
40
|
+
self.config = config
|
|
18
41
|
# 维护了消息平台的事件队列,EventBus 会从这里取出事件并处理。
|
|
19
42
|
self._event_queue = event_queue
|
|
20
43
|
self.client_self_id = uuid.uuid4().hex
|
|
21
44
|
|
|
45
|
+
# 平台运行状态
|
|
46
|
+
self._status: PlatformStatus = PlatformStatus.PENDING
|
|
47
|
+
self._errors: list[PlatformError] = []
|
|
48
|
+
self._started_at: datetime | None = None
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def status(self) -> PlatformStatus:
|
|
52
|
+
"""获取平台运行状态"""
|
|
53
|
+
return self._status
|
|
54
|
+
|
|
55
|
+
@status.setter
|
|
56
|
+
def status(self, value: PlatformStatus):
|
|
57
|
+
"""设置平台运行状态"""
|
|
58
|
+
self._status = value
|
|
59
|
+
if value == PlatformStatus.RUNNING and self._started_at is None:
|
|
60
|
+
self._started_at = datetime.now()
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def errors(self) -> list[PlatformError]:
|
|
64
|
+
"""获取错误列表"""
|
|
65
|
+
return self._errors
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def last_error(self) -> PlatformError | None:
|
|
69
|
+
"""获取最近的错误"""
|
|
70
|
+
return self._errors[-1] if self._errors else None
|
|
71
|
+
|
|
72
|
+
def record_error(self, message: str, traceback_str: str | None = None):
|
|
73
|
+
"""记录一个错误"""
|
|
74
|
+
self._errors.append(PlatformError(message=message, traceback=traceback_str))
|
|
75
|
+
self._status = PlatformStatus.ERROR
|
|
76
|
+
|
|
77
|
+
def clear_errors(self):
|
|
78
|
+
"""清除错误记录"""
|
|
79
|
+
self._errors.clear()
|
|
80
|
+
if self._status == PlatformStatus.ERROR:
|
|
81
|
+
self._status = PlatformStatus.RUNNING
|
|
82
|
+
|
|
83
|
+
def unified_webhook(self) -> bool:
|
|
84
|
+
"""是否正在使用统一 Webhook 模式"""
|
|
85
|
+
return bool(
|
|
86
|
+
self.config.get("unified_webhook_mode", False)
|
|
87
|
+
and self.config.get("webhook_uuid")
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
def get_stats(self) -> dict:
|
|
91
|
+
"""获取平台统计信息"""
|
|
92
|
+
meta = self.meta()
|
|
93
|
+
return {
|
|
94
|
+
"id": meta.id or self.config.get("id"),
|
|
95
|
+
"type": meta.name,
|
|
96
|
+
"display_name": meta.adapter_display_name or meta.name,
|
|
97
|
+
"status": self._status.value,
|
|
98
|
+
"started_at": self._started_at.isoformat() if self._started_at else None,
|
|
99
|
+
"error_count": len(self._errors),
|
|
100
|
+
"last_error": {
|
|
101
|
+
"message": self.last_error.message,
|
|
102
|
+
"timestamp": self.last_error.timestamp.isoformat(),
|
|
103
|
+
"traceback": self.last_error.traceback,
|
|
104
|
+
}
|
|
105
|
+
if self.last_error
|
|
106
|
+
else None,
|
|
107
|
+
"unified_webhook": self.unified_webhook(),
|
|
108
|
+
}
|
|
109
|
+
|
|
22
110
|
@abc.abstractmethod
|
|
23
|
-
def run(self) ->
|
|
111
|
+
def run(self) -> Coroutine[Any, Any, None]:
|
|
24
112
|
"""得到一个平台的运行实例,需要返回一个协程对象。"""
|
|
25
113
|
raise NotImplementedError
|
|
26
114
|
|
|
@@ -36,7 +124,7 @@ class Platform(abc.ABC):
|
|
|
36
124
|
self,
|
|
37
125
|
session: MessageSesion,
|
|
38
126
|
message_chain: MessageChain,
|
|
39
|
-
) ->
|
|
127
|
+
) -> None:
|
|
40
128
|
"""通过会话发送消息。该方法旨在让插件能够直接通过**可持久化的会话数据**发送消息,而不需要保存 event 对象。
|
|
41
129
|
|
|
42
130
|
异步方法。
|
|
@@ -49,3 +137,20 @@ class Platform(abc.ABC):
|
|
|
49
137
|
|
|
50
138
|
def get_client(self):
|
|
51
139
|
"""获取平台的客户端对象。"""
|
|
140
|
+
|
|
141
|
+
async def webhook_callback(self, request: Any) -> Any:
|
|
142
|
+
"""统一 Webhook 回调入口。
|
|
143
|
+
|
|
144
|
+
支持统一 Webhook 模式的平台需要实现此方法。
|
|
145
|
+
当 Dashboard 收到 /api/platform/webhook/{uuid} 请求时,会调用此方法。
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
request: Quart 请求对象
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
响应内容,格式取决于具体平台的要求
|
|
152
|
+
|
|
153
|
+
Raises:
|
|
154
|
+
NotImplementedError: 平台未实现统一 Webhook 模式
|
|
155
|
+
"""
|
|
156
|
+
raise NotImplementedError(f"平台 {self.meta().name} 未实现统一 Webhook 模式")
|
|
@@ -70,16 +70,18 @@ class AiocqhttpMessageEvent(AstrMessageEvent):
|
|
|
70
70
|
bot: CQHttp,
|
|
71
71
|
event: Event | None,
|
|
72
72
|
is_group: bool,
|
|
73
|
-
session_id: str,
|
|
73
|
+
session_id: str | None,
|
|
74
74
|
messages: list[dict],
|
|
75
75
|
):
|
|
76
76
|
# session_id 必须是纯数字字符串
|
|
77
|
-
|
|
77
|
+
session_id_int = (
|
|
78
|
+
int(session_id) if session_id and session_id.isdigit() else None
|
|
79
|
+
)
|
|
78
80
|
|
|
79
|
-
if is_group and isinstance(
|
|
80
|
-
await bot.send_group_msg(group_id=
|
|
81
|
-
elif not is_group and isinstance(
|
|
82
|
-
await bot.send_private_msg(user_id=
|
|
81
|
+
if is_group and isinstance(session_id_int, int):
|
|
82
|
+
await bot.send_group_msg(group_id=session_id_int, message=messages)
|
|
83
|
+
elif not is_group and isinstance(session_id_int, int):
|
|
84
|
+
await bot.send_private_msg(user_id=session_id_int, message=messages)
|
|
83
85
|
elif isinstance(event, Event): # 最后兜底
|
|
84
86
|
await bot.send(event=event, message=messages)
|
|
85
87
|
else:
|
|
@@ -4,7 +4,7 @@ import logging
|
|
|
4
4
|
import time
|
|
5
5
|
import uuid
|
|
6
6
|
from collections.abc import Awaitable
|
|
7
|
-
from typing import Any
|
|
7
|
+
from typing import Any, cast
|
|
8
8
|
|
|
9
9
|
from aiocqhttp import CQHttp, Event
|
|
10
10
|
from aiocqhttp.exceptions import ActionFailed
|
|
@@ -38,9 +38,8 @@ class AiocqhttpAdapter(Platform):
|
|
|
38
38
|
platform_settings: dict,
|
|
39
39
|
event_queue: asyncio.Queue,
|
|
40
40
|
) -> None:
|
|
41
|
-
super().__init__(event_queue)
|
|
41
|
+
super().__init__(platform_config, event_queue)
|
|
42
42
|
|
|
43
|
-
self.config = platform_config
|
|
44
43
|
self.settings = platform_settings
|
|
45
44
|
self.unique_session = platform_settings["unique_session"]
|
|
46
45
|
self.host = platform_config["ws_reverse_host"]
|
|
@@ -49,7 +48,7 @@ class AiocqhttpAdapter(Platform):
|
|
|
49
48
|
self.metadata = PlatformMetadata(
|
|
50
49
|
name="aiocqhttp",
|
|
51
50
|
description="适用于 OneBot 标准的消息平台适配器,支持反向 WebSockets。",
|
|
52
|
-
id=self.config.get("id"),
|
|
51
|
+
id=cast(str, self.config.get("id")),
|
|
53
52
|
support_streaming_message=False,
|
|
54
53
|
)
|
|
55
54
|
|
|
@@ -128,7 +127,9 @@ class AiocqhttpAdapter(Platform):
|
|
|
128
127
|
"""OneBot V11 请求类事件"""
|
|
129
128
|
abm = AstrBotMessage()
|
|
130
129
|
abm.self_id = str(event.self_id)
|
|
131
|
-
abm.sender = MessageMember(
|
|
130
|
+
abm.sender = MessageMember(
|
|
131
|
+
user_id=str(event.user_id), nickname=str(event.user_id)
|
|
132
|
+
)
|
|
132
133
|
abm.type = MessageType.OTHER_MESSAGE
|
|
133
134
|
if event.get("group_id"):
|
|
134
135
|
abm.type = MessageType.GROUP_MESSAGE
|
|
@@ -154,7 +155,9 @@ class AiocqhttpAdapter(Platform):
|
|
|
154
155
|
"""OneBot V11 通知类事件"""
|
|
155
156
|
abm = AstrBotMessage()
|
|
156
157
|
abm.self_id = str(event.self_id)
|
|
157
|
-
abm.sender = MessageMember(
|
|
158
|
+
abm.sender = MessageMember(
|
|
159
|
+
user_id=str(event.user_id), nickname=str(event.user_id)
|
|
160
|
+
)
|
|
158
161
|
abm.type = MessageType.OTHER_MESSAGE
|
|
159
162
|
if event.get("group_id"):
|
|
160
163
|
abm.group_id = str(event.group_id)
|
|
@@ -193,6 +196,7 @@ class AiocqhttpAdapter(Platform):
|
|
|
193
196
|
@param event: 事件对象
|
|
194
197
|
@param get_reply: 是否获取回复消息。这个参数是为了防止多个回复嵌套。
|
|
195
198
|
"""
|
|
199
|
+
assert event.sender is not None
|
|
196
200
|
abm = AstrBotMessage()
|
|
197
201
|
abm.self_id = str(event.self_id)
|
|
198
202
|
abm.sender = MessageMember(
|
|
@@ -202,6 +206,7 @@ class AiocqhttpAdapter(Platform):
|
|
|
202
206
|
if event["message_type"] == "group":
|
|
203
207
|
abm.type = MessageType.GROUP_MESSAGE
|
|
204
208
|
abm.group_id = str(event.group_id)
|
|
209
|
+
abm.group = Group(str(event.group_id))
|
|
205
210
|
abm.group.group_name = event.get("group_name", "N/A")
|
|
206
211
|
elif event["message_type"] == "private":
|
|
207
212
|
abm.type = MessageType.FRIEND_MESSAGE
|
|
@@ -227,7 +232,7 @@ class AiocqhttpAdapter(Platform):
|
|
|
227
232
|
await self.bot.send(event, err)
|
|
228
233
|
except BaseException as e:
|
|
229
234
|
logger.error(f"回复消息失败: {e}")
|
|
230
|
-
|
|
235
|
+
raise ValueError(err)
|
|
231
236
|
|
|
232
237
|
# 按消息段类型类型适配
|
|
233
238
|
for t, m_group in itertools.groupby(event.message, key=lambda x: x["type"]):
|
|
@@ -416,7 +421,7 @@ class AiocqhttpAdapter(Platform):
|
|
|
416
421
|
|
|
417
422
|
async def shutdown_trigger_placeholder(self):
|
|
418
423
|
await self.shutdown_event.wait()
|
|
419
|
-
logger.info("aiocqhttp
|
|
424
|
+
logger.info("aiocqhttp 适配器已被关闭")
|
|
420
425
|
|
|
421
426
|
def meta(self) -> PlatformMetadata:
|
|
422
427
|
return self.metadata
|
|
@@ -2,6 +2,7 @@ import asyncio
|
|
|
2
2
|
import os
|
|
3
3
|
import threading
|
|
4
4
|
import uuid
|
|
5
|
+
from typing import cast
|
|
5
6
|
|
|
6
7
|
import aiohttp
|
|
7
8
|
import dingtalk_stream
|
|
@@ -47,21 +48,21 @@ class DingtalkPlatformAdapter(Platform):
|
|
|
47
48
|
platform_settings: dict,
|
|
48
49
|
event_queue: asyncio.Queue,
|
|
49
50
|
) -> None:
|
|
50
|
-
super().__init__(event_queue)
|
|
51
|
-
|
|
52
|
-
self.config = platform_config
|
|
51
|
+
super().__init__(platform_config, event_queue)
|
|
53
52
|
|
|
54
53
|
self.unique_session = platform_settings["unique_session"]
|
|
55
54
|
|
|
56
55
|
self.client_id = platform_config["client_id"]
|
|
57
56
|
self.client_secret = platform_config["client_secret"]
|
|
58
57
|
|
|
58
|
+
outer_self = self
|
|
59
|
+
|
|
59
60
|
class AstrCallbackClient(dingtalk_stream.ChatbotHandler):
|
|
60
|
-
async def process(
|
|
61
|
+
async def process(self, message: dingtalk_stream.CallbackMessage):
|
|
61
62
|
logger.debug(f"dingtalk: {message.data}")
|
|
62
63
|
im = dingtalk_stream.ChatbotMessage.from_dict(message.data)
|
|
63
|
-
abm = await
|
|
64
|
-
await
|
|
64
|
+
abm = await outer_self.convert_msg(im)
|
|
65
|
+
await outer_self.handle_msg(abm)
|
|
65
66
|
|
|
66
67
|
return AckMessage.STATUS_OK, "OK"
|
|
67
68
|
|
|
@@ -75,14 +76,15 @@ class DingtalkPlatformAdapter(Platform):
|
|
|
75
76
|
self.client,
|
|
76
77
|
)
|
|
77
78
|
self.client_ = client # 用于 websockets 的 client
|
|
79
|
+
self._shutdown_event: threading.Event | None = None
|
|
78
80
|
|
|
79
|
-
def _id_to_sid(self, dingtalk_id: str | None) -> str
|
|
81
|
+
def _id_to_sid(self, dingtalk_id: str | None) -> str:
|
|
80
82
|
if not dingtalk_id:
|
|
81
|
-
return dingtalk_id
|
|
83
|
+
return dingtalk_id or "unknown"
|
|
82
84
|
prefix = "$:LWCP_v1:$"
|
|
83
85
|
if dingtalk_id.startswith(prefix):
|
|
84
86
|
return dingtalk_id[len(prefix) :]
|
|
85
|
-
return dingtalk_id
|
|
87
|
+
return dingtalk_id or "unknown"
|
|
86
88
|
|
|
87
89
|
async def send_by_session(
|
|
88
90
|
self,
|
|
@@ -95,7 +97,7 @@ class DingtalkPlatformAdapter(Platform):
|
|
|
95
97
|
return PlatformMetadata(
|
|
96
98
|
name="dingtalk",
|
|
97
99
|
description="钉钉机器人官方 API 适配器",
|
|
98
|
-
id=self.config.get("id"),
|
|
100
|
+
id=cast(str, self.config.get("id")),
|
|
99
101
|
support_streaming_message=False,
|
|
100
102
|
)
|
|
101
103
|
|
|
@@ -106,7 +108,7 @@ class DingtalkPlatformAdapter(Platform):
|
|
|
106
108
|
abm = AstrBotMessage()
|
|
107
109
|
abm.message = []
|
|
108
110
|
abm.message_str = ""
|
|
109
|
-
abm.timestamp = int(message.create_at / 1000)
|
|
111
|
+
abm.timestamp = int(cast(int, message.create_at) / 1000)
|
|
110
112
|
abm.type = (
|
|
111
113
|
MessageType.GROUP_MESSAGE
|
|
112
114
|
if message.conversation_type == "2"
|
|
@@ -117,7 +119,7 @@ class DingtalkPlatformAdapter(Platform):
|
|
|
117
119
|
nickname=message.sender_nick,
|
|
118
120
|
)
|
|
119
121
|
abm.self_id = self._id_to_sid(message.chatbot_user_id)
|
|
120
|
-
abm.message_id = message.message_id
|
|
122
|
+
abm.message_id = cast(str, message.message_id)
|
|
121
123
|
abm.raw_message = message
|
|
122
124
|
|
|
123
125
|
if abm.type == MessageType.GROUP_MESSAGE:
|
|
@@ -134,14 +136,16 @@ class DingtalkPlatformAdapter(Platform):
|
|
|
134
136
|
else:
|
|
135
137
|
abm.session_id = abm.sender.user_id
|
|
136
138
|
|
|
137
|
-
message_type: str = message.message_type
|
|
139
|
+
message_type: str = cast(str, message.message_type)
|
|
138
140
|
match message_type:
|
|
139
141
|
case "text":
|
|
140
142
|
abm.message_str = message.text.content.strip()
|
|
141
143
|
abm.message.append(Plain(abm.message_str))
|
|
142
144
|
case "richText":
|
|
143
|
-
rtc: dingtalk_stream.RichTextContent =
|
|
144
|
-
|
|
145
|
+
rtc: dingtalk_stream.RichTextContent = cast(
|
|
146
|
+
dingtalk_stream.RichTextContent, message.rich_text_content
|
|
147
|
+
)
|
|
148
|
+
contents: list[dict] = cast(list[dict], rtc.rich_text_list)
|
|
145
149
|
for content in contents:
|
|
146
150
|
plains = ""
|
|
147
151
|
if "text" in content:
|
|
@@ -150,7 +154,7 @@ class DingtalkPlatformAdapter(Platform):
|
|
|
150
154
|
elif "type" in content and content["type"] == "picture":
|
|
151
155
|
f_path = await self.download_ding_file(
|
|
152
156
|
content["downloadCode"],
|
|
153
|
-
message.robot_code,
|
|
157
|
+
cast(str, message.robot_code),
|
|
154
158
|
"jpg",
|
|
155
159
|
)
|
|
156
160
|
abm.message.append(Image.fromFileSystem(f_path))
|
|
@@ -195,7 +199,7 @@ class DingtalkPlatformAdapter(Platform):
|
|
|
195
199
|
logger.error(
|
|
196
200
|
f"下载钉钉文件失败: {resp.status}, {await resp.text()}",
|
|
197
201
|
)
|
|
198
|
-
return
|
|
202
|
+
return ""
|
|
199
203
|
resp_data = await resp.json()
|
|
200
204
|
download_url = resp_data["data"]["downloadUrl"]
|
|
201
205
|
await download_file(download_url, f_path)
|
|
@@ -215,7 +219,7 @@ class DingtalkPlatformAdapter(Platform):
|
|
|
215
219
|
logger.error(
|
|
216
220
|
f"获取钉钉机器人 access_token 失败: {resp.status}, {await resp.text()}",
|
|
217
221
|
)
|
|
218
|
-
return
|
|
222
|
+
return ""
|
|
219
223
|
return (await resp.json())["data"]["accessToken"]
|
|
220
224
|
|
|
221
225
|
async def handle_msg(self, abm: AstrBotMessage):
|
|
@@ -241,7 +245,7 @@ class DingtalkPlatformAdapter(Platform):
|
|
|
241
245
|
task.result()
|
|
242
246
|
except Exception as e:
|
|
243
247
|
if "Graceful shutdown" in str(e):
|
|
244
|
-
logger.info("
|
|
248
|
+
logger.info("钉钉适配器已被关闭")
|
|
245
249
|
return
|
|
246
250
|
logger.error(f"钉钉机器人启动失败: {e}")
|
|
247
251
|
|
|
@@ -252,9 +256,11 @@ class DingtalkPlatformAdapter(Platform):
|
|
|
252
256
|
def monkey_patch_close():
|
|
253
257
|
raise KeyboardInterrupt("Graceful shutdown")
|
|
254
258
|
|
|
255
|
-
self.client_.
|
|
256
|
-
|
|
257
|
-
|
|
259
|
+
if self.client_.websocket is not None:
|
|
260
|
+
self.client_.open_connection = monkey_patch_close
|
|
261
|
+
await self.client_.websocket.close(code=1000, reason="Graceful shutdown")
|
|
262
|
+
if self._shutdown_event is not None:
|
|
263
|
+
self._shutdown_event.set()
|
|
258
264
|
|
|
259
265
|
def get_client(self):
|
|
260
266
|
return self.client
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
from typing import cast
|
|
2
3
|
|
|
3
4
|
import dingtalk_stream
|
|
4
5
|
|
|
@@ -32,7 +33,7 @@ class DingtalkMessageEvent(AstrMessageEvent):
|
|
|
32
33
|
client.reply_markdown,
|
|
33
34
|
segment.text,
|
|
34
35
|
segment.text,
|
|
35
|
-
self.message_obj.raw_message,
|
|
36
|
+
cast(dingtalk_stream.ChatbotMessage, self.message_obj.raw_message),
|
|
36
37
|
)
|
|
37
38
|
elif isinstance(segment, Comp.Image):
|
|
38
39
|
markdown_str = ""
|
|
@@ -53,7 +54,9 @@ class DingtalkMessageEvent(AstrMessageEvent):
|
|
|
53
54
|
client.reply_markdown,
|
|
54
55
|
"😄",
|
|
55
56
|
markdown_str,
|
|
56
|
-
|
|
57
|
+
cast(
|
|
58
|
+
dingtalk_stream.ChatbotMessage, self.message_obj.raw_message
|
|
59
|
+
),
|
|
57
60
|
)
|
|
58
61
|
logger.debug(f"send image: {ret}")
|
|
59
62
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import sys
|
|
2
|
+
from collections.abc import Awaitable, Callable
|
|
2
3
|
|
|
3
4
|
import discord
|
|
4
5
|
|
|
@@ -27,13 +28,16 @@ class DiscordBotClient(discord.Bot):
|
|
|
27
28
|
super().__init__(intents=intents, proxy=proxy)
|
|
28
29
|
|
|
29
30
|
# 回调函数
|
|
30
|
-
self.on_message_received = None
|
|
31
|
-
self.on_ready_once_callback = None
|
|
31
|
+
self.on_message_received: Callable[[dict], Awaitable[None]] | None = None
|
|
32
|
+
self.on_ready_once_callback: Callable[[], Awaitable[None]] | None = None
|
|
32
33
|
self._ready_once_fired = False
|
|
33
34
|
|
|
34
|
-
@override
|
|
35
35
|
async def on_ready(self):
|
|
36
36
|
"""当机器人成功连接并准备就绪时触发"""
|
|
37
|
+
if self.user is None:
|
|
38
|
+
logger.error("[Discord] 客户端未正确加载用户信息 (self.user is None)")
|
|
39
|
+
return
|
|
40
|
+
|
|
37
41
|
logger.info(f"[Discord] 已作为 {self.user} (ID: {self.user.id}) 登录")
|
|
38
42
|
logger.info("[Discord] 客户端已准备就绪。")
|
|
39
43
|
|
|
@@ -49,6 +53,9 @@ class DiscordBotClient(discord.Bot):
|
|
|
49
53
|
|
|
50
54
|
def _create_message_data(self, message: discord.Message) -> dict:
|
|
51
55
|
"""从 discord.Message 创建数据字典"""
|
|
56
|
+
if self.user is None:
|
|
57
|
+
raise RuntimeError("Bot is not ready: self.user is None")
|
|
58
|
+
|
|
52
59
|
is_mentioned = self.user in message.mentions
|
|
53
60
|
return {
|
|
54
61
|
"message": message,
|
|
@@ -66,6 +73,12 @@ class DiscordBotClient(discord.Bot):
|
|
|
66
73
|
|
|
67
74
|
def _create_interaction_data(self, interaction: discord.Interaction) -> dict:
|
|
68
75
|
"""从 discord.Interaction 创建数据字典"""
|
|
76
|
+
if self.user is None:
|
|
77
|
+
raise RuntimeError("Bot is not ready: self.user is None")
|
|
78
|
+
|
|
79
|
+
if interaction.user is None:
|
|
80
|
+
raise ValueError("Interaction received without a valid user")
|
|
81
|
+
|
|
69
82
|
return {
|
|
70
83
|
"interaction": interaction,
|
|
71
84
|
"bot_id": str(self.user.id),
|
|
@@ -80,7 +93,6 @@ class DiscordBotClient(discord.Bot):
|
|
|
80
93
|
"type": "interaction",
|
|
81
94
|
}
|
|
82
95
|
|
|
83
|
-
@override
|
|
84
96
|
async def on_message(self, message: discord.Message):
|
|
85
97
|
"""当接收到消息时触发"""
|
|
86
98
|
if message.author.bot:
|
|
@@ -97,8 +97,8 @@ class DiscordView(BaseMessageComponent):
|
|
|
97
97
|
|
|
98
98
|
def __init__(
|
|
99
99
|
self,
|
|
100
|
-
components: list[BaseMessageComponent] = None,
|
|
101
|
-
timeout: float = None,
|
|
100
|
+
components: list[BaseMessageComponent] | None = None,
|
|
101
|
+
timeout: float | None = None,
|
|
102
102
|
):
|
|
103
103
|
self.components = components or []
|
|
104
104
|
self.timeout = timeout
|