AstrBot 3.5.6__py3-none-any.whl → 4.7.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.
Files changed (288) hide show
  1. astrbot/api/__init__.py +16 -4
  2. astrbot/api/all.py +2 -1
  3. astrbot/api/event/__init__.py +5 -6
  4. astrbot/api/event/filter/__init__.py +37 -34
  5. astrbot/api/platform/__init__.py +7 -8
  6. astrbot/api/provider/__init__.py +8 -7
  7. astrbot/api/star/__init__.py +3 -4
  8. astrbot/api/util/__init__.py +2 -2
  9. astrbot/cli/__init__.py +1 -0
  10. astrbot/cli/__main__.py +18 -197
  11. astrbot/cli/commands/__init__.py +6 -0
  12. astrbot/cli/commands/cmd_conf.py +209 -0
  13. astrbot/cli/commands/cmd_init.py +56 -0
  14. astrbot/cli/commands/cmd_plug.py +245 -0
  15. astrbot/cli/commands/cmd_run.py +62 -0
  16. astrbot/cli/utils/__init__.py +18 -0
  17. astrbot/cli/utils/basic.py +76 -0
  18. astrbot/cli/utils/plugin.py +246 -0
  19. astrbot/cli/utils/version_comparator.py +90 -0
  20. astrbot/core/__init__.py +17 -19
  21. astrbot/core/agent/agent.py +14 -0
  22. astrbot/core/agent/handoff.py +38 -0
  23. astrbot/core/agent/hooks.py +30 -0
  24. astrbot/core/agent/mcp_client.py +385 -0
  25. astrbot/core/agent/message.py +175 -0
  26. astrbot/core/agent/response.py +14 -0
  27. astrbot/core/agent/run_context.py +22 -0
  28. astrbot/core/agent/runners/__init__.py +3 -0
  29. astrbot/core/agent/runners/base.py +65 -0
  30. astrbot/core/agent/runners/coze/coze_agent_runner.py +367 -0
  31. astrbot/core/agent/runners/coze/coze_api_client.py +324 -0
  32. astrbot/core/agent/runners/dashscope/dashscope_agent_runner.py +403 -0
  33. astrbot/core/agent/runners/dify/dify_agent_runner.py +336 -0
  34. astrbot/core/agent/runners/dify/dify_api_client.py +195 -0
  35. astrbot/core/agent/runners/tool_loop_agent_runner.py +400 -0
  36. astrbot/core/agent/tool.py +285 -0
  37. astrbot/core/agent/tool_executor.py +17 -0
  38. astrbot/core/astr_agent_context.py +19 -0
  39. astrbot/core/astr_agent_hooks.py +36 -0
  40. astrbot/core/astr_agent_run_util.py +80 -0
  41. astrbot/core/astr_agent_tool_exec.py +246 -0
  42. astrbot/core/astrbot_config_mgr.py +275 -0
  43. astrbot/core/config/__init__.py +2 -2
  44. astrbot/core/config/astrbot_config.py +60 -20
  45. astrbot/core/config/default.py +1972 -453
  46. astrbot/core/config/i18n_utils.py +110 -0
  47. astrbot/core/conversation_mgr.py +285 -75
  48. astrbot/core/core_lifecycle.py +167 -62
  49. astrbot/core/db/__init__.py +305 -102
  50. astrbot/core/db/migration/helper.py +69 -0
  51. astrbot/core/db/migration/migra_3_to_4.py +357 -0
  52. astrbot/core/db/migration/migra_45_to_46.py +44 -0
  53. astrbot/core/db/migration/migra_webchat_session.py +131 -0
  54. astrbot/core/db/migration/shared_preferences_v3.py +48 -0
  55. astrbot/core/db/migration/sqlite_v3.py +497 -0
  56. astrbot/core/db/po.py +259 -55
  57. astrbot/core/db/sqlite.py +773 -528
  58. astrbot/core/db/vec_db/base.py +73 -0
  59. astrbot/core/db/vec_db/faiss_impl/__init__.py +3 -0
  60. astrbot/core/db/vec_db/faiss_impl/document_storage.py +392 -0
  61. astrbot/core/db/vec_db/faiss_impl/embedding_storage.py +93 -0
  62. astrbot/core/db/vec_db/faiss_impl/sqlite_init.sql +17 -0
  63. astrbot/core/db/vec_db/faiss_impl/vec_db.py +204 -0
  64. astrbot/core/event_bus.py +26 -22
  65. astrbot/core/exceptions.py +9 -0
  66. astrbot/core/file_token_service.py +98 -0
  67. astrbot/core/initial_loader.py +19 -10
  68. astrbot/core/knowledge_base/chunking/__init__.py +9 -0
  69. astrbot/core/knowledge_base/chunking/base.py +25 -0
  70. astrbot/core/knowledge_base/chunking/fixed_size.py +59 -0
  71. astrbot/core/knowledge_base/chunking/recursive.py +161 -0
  72. astrbot/core/knowledge_base/kb_db_sqlite.py +301 -0
  73. astrbot/core/knowledge_base/kb_helper.py +642 -0
  74. astrbot/core/knowledge_base/kb_mgr.py +330 -0
  75. astrbot/core/knowledge_base/models.py +120 -0
  76. astrbot/core/knowledge_base/parsers/__init__.py +13 -0
  77. astrbot/core/knowledge_base/parsers/base.py +51 -0
  78. astrbot/core/knowledge_base/parsers/markitdown_parser.py +26 -0
  79. astrbot/core/knowledge_base/parsers/pdf_parser.py +101 -0
  80. astrbot/core/knowledge_base/parsers/text_parser.py +42 -0
  81. astrbot/core/knowledge_base/parsers/url_parser.py +103 -0
  82. astrbot/core/knowledge_base/parsers/util.py +13 -0
  83. astrbot/core/knowledge_base/prompts.py +65 -0
  84. astrbot/core/knowledge_base/retrieval/__init__.py +14 -0
  85. astrbot/core/knowledge_base/retrieval/hit_stopwords.txt +767 -0
  86. astrbot/core/knowledge_base/retrieval/manager.py +276 -0
  87. astrbot/core/knowledge_base/retrieval/rank_fusion.py +142 -0
  88. astrbot/core/knowledge_base/retrieval/sparse_retriever.py +136 -0
  89. astrbot/core/log.py +21 -15
  90. astrbot/core/message/components.py +413 -287
  91. astrbot/core/message/message_event_result.py +35 -24
  92. astrbot/core/persona_mgr.py +192 -0
  93. astrbot/core/pipeline/__init__.py +14 -14
  94. astrbot/core/pipeline/content_safety_check/stage.py +13 -9
  95. astrbot/core/pipeline/content_safety_check/strategies/__init__.py +1 -2
  96. astrbot/core/pipeline/content_safety_check/strategies/baidu_aip.py +13 -14
  97. astrbot/core/pipeline/content_safety_check/strategies/keywords.py +2 -1
  98. astrbot/core/pipeline/content_safety_check/strategies/strategy.py +6 -6
  99. astrbot/core/pipeline/context.py +7 -1
  100. astrbot/core/pipeline/context_utils.py +107 -0
  101. astrbot/core/pipeline/preprocess_stage/stage.py +63 -36
  102. astrbot/core/pipeline/process_stage/method/agent_request.py +48 -0
  103. astrbot/core/pipeline/process_stage/method/agent_sub_stages/internal.py +464 -0
  104. astrbot/core/pipeline/process_stage/method/agent_sub_stages/third_party.py +202 -0
  105. astrbot/core/pipeline/process_stage/method/star_request.py +26 -32
  106. astrbot/core/pipeline/process_stage/stage.py +21 -15
  107. astrbot/core/pipeline/process_stage/utils.py +125 -0
  108. astrbot/core/pipeline/rate_limit_check/stage.py +34 -36
  109. astrbot/core/pipeline/respond/stage.py +142 -101
  110. astrbot/core/pipeline/result_decorate/stage.py +124 -57
  111. astrbot/core/pipeline/scheduler.py +21 -16
  112. astrbot/core/pipeline/session_status_check/stage.py +37 -0
  113. astrbot/core/pipeline/stage.py +11 -76
  114. astrbot/core/pipeline/waking_check/stage.py +69 -33
  115. astrbot/core/pipeline/whitelist_check/stage.py +10 -7
  116. astrbot/core/platform/__init__.py +6 -6
  117. astrbot/core/platform/astr_message_event.py +107 -129
  118. astrbot/core/platform/astrbot_message.py +32 -12
  119. astrbot/core/platform/manager.py +62 -18
  120. astrbot/core/platform/message_session.py +30 -0
  121. astrbot/core/platform/platform.py +16 -24
  122. astrbot/core/platform/platform_metadata.py +9 -4
  123. astrbot/core/platform/register.py +12 -7
  124. astrbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py +136 -60
  125. astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py +126 -46
  126. astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py +63 -31
  127. astrbot/core/platform/sources/dingtalk/dingtalk_event.py +30 -26
  128. astrbot/core/platform/sources/discord/client.py +129 -0
  129. astrbot/core/platform/sources/discord/components.py +139 -0
  130. astrbot/core/platform/sources/discord/discord_platform_adapter.py +473 -0
  131. astrbot/core/platform/sources/discord/discord_platform_event.py +313 -0
  132. astrbot/core/platform/sources/lark/lark_adapter.py +27 -18
  133. astrbot/core/platform/sources/lark/lark_event.py +39 -13
  134. astrbot/core/platform/sources/misskey/misskey_adapter.py +770 -0
  135. astrbot/core/platform/sources/misskey/misskey_api.py +964 -0
  136. astrbot/core/platform/sources/misskey/misskey_event.py +163 -0
  137. astrbot/core/platform/sources/misskey/misskey_utils.py +550 -0
  138. astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py +149 -33
  139. astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py +41 -26
  140. astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_adapter.py +36 -17
  141. astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_event.py +3 -1
  142. astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_server.py +14 -8
  143. astrbot/core/platform/sources/satori/satori_adapter.py +792 -0
  144. astrbot/core/platform/sources/satori/satori_event.py +432 -0
  145. astrbot/core/platform/sources/slack/client.py +164 -0
  146. astrbot/core/platform/sources/slack/slack_adapter.py +416 -0
  147. astrbot/core/platform/sources/slack/slack_event.py +253 -0
  148. astrbot/core/platform/sources/telegram/tg_adapter.py +100 -43
  149. astrbot/core/platform/sources/telegram/tg_event.py +136 -36
  150. astrbot/core/platform/sources/webchat/webchat_adapter.py +72 -22
  151. astrbot/core/platform/sources/webchat/webchat_event.py +46 -22
  152. astrbot/core/platform/sources/webchat/webchat_queue_mgr.py +35 -0
  153. astrbot/core/platform/sources/wechatpadpro/wechatpadpro_adapter.py +926 -0
  154. astrbot/core/platform/sources/wechatpadpro/wechatpadpro_message_event.py +178 -0
  155. astrbot/core/platform/sources/wechatpadpro/xml_data_parser.py +159 -0
  156. astrbot/core/platform/sources/wecom/wecom_adapter.py +169 -27
  157. astrbot/core/platform/sources/wecom/wecom_event.py +162 -77
  158. astrbot/core/platform/sources/wecom/wecom_kf.py +279 -0
  159. astrbot/core/platform/sources/wecom/wecom_kf_message.py +196 -0
  160. astrbot/core/platform/sources/wecom_ai_bot/WXBizJsonMsgCrypt.py +297 -0
  161. astrbot/core/platform/sources/wecom_ai_bot/__init__.py +15 -0
  162. astrbot/core/platform/sources/wecom_ai_bot/ierror.py +19 -0
  163. astrbot/core/platform/sources/wecom_ai_bot/wecomai_adapter.py +472 -0
  164. astrbot/core/platform/sources/wecom_ai_bot/wecomai_api.py +417 -0
  165. astrbot/core/platform/sources/wecom_ai_bot/wecomai_event.py +152 -0
  166. astrbot/core/platform/sources/wecom_ai_bot/wecomai_queue_mgr.py +153 -0
  167. astrbot/core/platform/sources/wecom_ai_bot/wecomai_server.py +168 -0
  168. astrbot/core/platform/sources/wecom_ai_bot/wecomai_utils.py +209 -0
  169. astrbot/core/platform/sources/weixin_official_account/weixin_offacc_adapter.py +306 -0
  170. astrbot/core/platform/sources/weixin_official_account/weixin_offacc_event.py +186 -0
  171. astrbot/core/platform_message_history_mgr.py +49 -0
  172. astrbot/core/provider/__init__.py +2 -3
  173. astrbot/core/provider/entites.py +8 -8
  174. astrbot/core/provider/entities.py +154 -98
  175. astrbot/core/provider/func_tool_manager.py +446 -458
  176. astrbot/core/provider/manager.py +345 -207
  177. astrbot/core/provider/provider.py +188 -73
  178. astrbot/core/provider/register.py +9 -7
  179. astrbot/core/provider/sources/anthropic_source.py +295 -115
  180. astrbot/core/provider/sources/azure_tts_source.py +224 -0
  181. astrbot/core/provider/sources/bailian_rerank_source.py +236 -0
  182. astrbot/core/provider/sources/dashscope_tts.py +138 -14
  183. astrbot/core/provider/sources/edge_tts_source.py +24 -19
  184. astrbot/core/provider/sources/fishaudio_tts_api_source.py +58 -13
  185. astrbot/core/provider/sources/gemini_embedding_source.py +61 -0
  186. astrbot/core/provider/sources/gemini_source.py +310 -132
  187. astrbot/core/provider/sources/gemini_tts_source.py +81 -0
  188. astrbot/core/provider/sources/groq_source.py +15 -0
  189. astrbot/core/provider/sources/gsv_selfhosted_source.py +151 -0
  190. astrbot/core/provider/sources/gsvi_tts_source.py +14 -7
  191. astrbot/core/provider/sources/minimax_tts_api_source.py +159 -0
  192. astrbot/core/provider/sources/openai_embedding_source.py +40 -0
  193. astrbot/core/provider/sources/openai_source.py +241 -145
  194. astrbot/core/provider/sources/openai_tts_api_source.py +18 -7
  195. astrbot/core/provider/sources/sensevoice_selfhosted_source.py +13 -11
  196. astrbot/core/provider/sources/vllm_rerank_source.py +71 -0
  197. astrbot/core/provider/sources/volcengine_tts.py +115 -0
  198. astrbot/core/provider/sources/whisper_api_source.py +18 -13
  199. astrbot/core/provider/sources/whisper_selfhosted_source.py +19 -12
  200. astrbot/core/provider/sources/xinference_rerank_source.py +116 -0
  201. astrbot/core/provider/sources/xinference_stt_provider.py +197 -0
  202. astrbot/core/provider/sources/zhipu_source.py +6 -73
  203. astrbot/core/star/__init__.py +43 -11
  204. astrbot/core/star/config.py +17 -18
  205. astrbot/core/star/context.py +362 -138
  206. astrbot/core/star/filter/__init__.py +4 -3
  207. astrbot/core/star/filter/command.py +111 -35
  208. astrbot/core/star/filter/command_group.py +46 -34
  209. astrbot/core/star/filter/custom_filter.py +6 -5
  210. astrbot/core/star/filter/event_message_type.py +4 -2
  211. astrbot/core/star/filter/permission.py +4 -2
  212. astrbot/core/star/filter/platform_adapter_type.py +45 -12
  213. astrbot/core/star/filter/regex.py +4 -2
  214. astrbot/core/star/register/__init__.py +19 -15
  215. astrbot/core/star/register/star.py +41 -13
  216. astrbot/core/star/register/star_handler.py +236 -86
  217. astrbot/core/star/session_llm_manager.py +280 -0
  218. astrbot/core/star/session_plugin_manager.py +170 -0
  219. astrbot/core/star/star.py +36 -43
  220. astrbot/core/star/star_handler.py +47 -85
  221. astrbot/core/star/star_manager.py +442 -260
  222. astrbot/core/star/star_tools.py +167 -45
  223. astrbot/core/star/updator.py +17 -20
  224. astrbot/core/umop_config_router.py +106 -0
  225. astrbot/core/updator.py +38 -13
  226. astrbot/core/utils/astrbot_path.py +39 -0
  227. astrbot/core/utils/command_parser.py +1 -1
  228. astrbot/core/utils/io.py +119 -60
  229. astrbot/core/utils/log_pipe.py +1 -1
  230. astrbot/core/utils/metrics.py +11 -10
  231. astrbot/core/utils/migra_helper.py +73 -0
  232. astrbot/core/utils/path_util.py +63 -62
  233. astrbot/core/utils/pip_installer.py +37 -15
  234. astrbot/core/utils/session_lock.py +29 -0
  235. astrbot/core/utils/session_waiter.py +19 -20
  236. astrbot/core/utils/shared_preferences.py +174 -34
  237. astrbot/core/utils/t2i/__init__.py +4 -1
  238. astrbot/core/utils/t2i/local_strategy.py +386 -238
  239. astrbot/core/utils/t2i/network_strategy.py +109 -49
  240. astrbot/core/utils/t2i/renderer.py +29 -14
  241. astrbot/core/utils/t2i/template/astrbot_powershell.html +184 -0
  242. astrbot/core/utils/t2i/template_manager.py +111 -0
  243. astrbot/core/utils/tencent_record_helper.py +115 -1
  244. astrbot/core/utils/version_comparator.py +10 -13
  245. astrbot/core/zip_updator.py +112 -65
  246. astrbot/dashboard/routes/__init__.py +20 -13
  247. astrbot/dashboard/routes/auth.py +20 -9
  248. astrbot/dashboard/routes/chat.py +297 -141
  249. astrbot/dashboard/routes/config.py +652 -55
  250. astrbot/dashboard/routes/conversation.py +107 -37
  251. astrbot/dashboard/routes/file.py +26 -0
  252. astrbot/dashboard/routes/knowledge_base.py +1244 -0
  253. astrbot/dashboard/routes/log.py +27 -2
  254. astrbot/dashboard/routes/persona.py +202 -0
  255. astrbot/dashboard/routes/plugin.py +197 -139
  256. astrbot/dashboard/routes/route.py +27 -7
  257. astrbot/dashboard/routes/session_management.py +354 -0
  258. astrbot/dashboard/routes/stat.py +85 -18
  259. astrbot/dashboard/routes/static_file.py +5 -2
  260. astrbot/dashboard/routes/t2i.py +233 -0
  261. astrbot/dashboard/routes/tools.py +184 -120
  262. astrbot/dashboard/routes/update.py +59 -36
  263. astrbot/dashboard/server.py +96 -36
  264. astrbot/dashboard/utils.py +165 -0
  265. astrbot-4.7.0.dist-info/METADATA +294 -0
  266. astrbot-4.7.0.dist-info/RECORD +274 -0
  267. {astrbot-3.5.6.dist-info → astrbot-4.7.0.dist-info}/WHEEL +1 -1
  268. astrbot/core/db/plugin/sqlite_impl.py +0 -112
  269. astrbot/core/db/sqlite_init.sql +0 -50
  270. astrbot/core/pipeline/platform_compatibility/stage.py +0 -56
  271. astrbot/core/pipeline/process_stage/method/llm_request.py +0 -606
  272. astrbot/core/platform/sources/gewechat/client.py +0 -806
  273. astrbot/core/platform/sources/gewechat/downloader.py +0 -55
  274. astrbot/core/platform/sources/gewechat/gewechat_event.py +0 -255
  275. astrbot/core/platform/sources/gewechat/gewechat_platform_adapter.py +0 -103
  276. astrbot/core/platform/sources/gewechat/xml_data_parser.py +0 -110
  277. astrbot/core/provider/sources/dashscope_source.py +0 -203
  278. astrbot/core/provider/sources/dify_source.py +0 -281
  279. astrbot/core/provider/sources/llmtuner_source.py +0 -132
  280. astrbot/core/rag/embedding/openai_source.py +0 -20
  281. astrbot/core/rag/knowledge_db_mgr.py +0 -94
  282. astrbot/core/rag/store/__init__.py +0 -9
  283. astrbot/core/rag/store/chroma_db.py +0 -42
  284. astrbot/core/utils/dify_api_client.py +0 -152
  285. astrbot-3.5.6.dist-info/METADATA +0 -249
  286. astrbot-3.5.6.dist-info/RECORD +0 -158
  287. {astrbot-3.5.6.dist-info → astrbot-4.7.0.dist-info}/entry_points.txt +0 -0
  288. {astrbot-3.5.6.dist-info → astrbot-4.7.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,473 @@
1
+ import asyncio
2
+ import re
3
+ import sys
4
+ from typing import Any
5
+
6
+ import discord
7
+ from discord.abc import Messageable
8
+ from discord.channel import DMChannel
9
+
10
+ from astrbot import logger
11
+ from astrbot.api.event import MessageChain
12
+ from astrbot.api.message_components import File, Image, Plain
13
+ from astrbot.api.platform import (
14
+ AstrBotMessage,
15
+ MessageMember,
16
+ MessageType,
17
+ Platform,
18
+ PlatformMetadata,
19
+ register_platform_adapter,
20
+ )
21
+ from astrbot.core.platform.astr_message_event import MessageSesion
22
+ from astrbot.core.star.filter.command import CommandFilter
23
+ from astrbot.core.star.filter.command_group import CommandGroupFilter
24
+ from astrbot.core.star.star import star_map
25
+ from astrbot.core.star.star_handler import StarHandlerMetadata, star_handlers_registry
26
+
27
+ from .client import DiscordBotClient
28
+ from .discord_platform_event import DiscordPlatformEvent
29
+
30
+ if sys.version_info >= (3, 12):
31
+ from typing import override
32
+ else:
33
+ from typing_extensions import override
34
+
35
+
36
+ # 注册平台适配器
37
+ @register_platform_adapter(
38
+ "discord", "Discord 适配器 (基于 Pycord)", support_streaming_message=False
39
+ )
40
+ class DiscordPlatformAdapter(Platform):
41
+ def __init__(
42
+ self,
43
+ platform_config: dict,
44
+ platform_settings: dict,
45
+ event_queue: asyncio.Queue,
46
+ ) -> None:
47
+ super().__init__(event_queue)
48
+ self.config = platform_config
49
+ self.settings = platform_settings
50
+ self.client_self_id = None
51
+ self.registered_handlers = []
52
+ # 指令注册相关
53
+ self.enable_command_register = self.config.get("discord_command_register", True)
54
+ self.guild_id = self.config.get("discord_guild_id_for_debug", None)
55
+ self.activity_name = self.config.get("discord_activity_name", None)
56
+ self.shutdown_event = asyncio.Event()
57
+ self._polling_task = None
58
+
59
+ @override
60
+ async def send_by_session(
61
+ self,
62
+ session: MessageSesion,
63
+ message_chain: MessageChain,
64
+ ):
65
+ """通过会话发送消息"""
66
+ # 创建一个 message_obj 以便在 event 中使用
67
+ message_obj = AstrBotMessage()
68
+ if "_" in session.session_id:
69
+ session.session_id = session.session_id.split("_")[1]
70
+ channel_id_str = session.session_id
71
+ channel = None
72
+ try:
73
+ channel_id = int(channel_id_str)
74
+ channel = self.client.get_channel(channel_id)
75
+ except (ValueError, TypeError):
76
+ logger.warning(f"[Discord] Invalid channel ID format: {channel_id_str}")
77
+
78
+ if channel:
79
+ message_obj.type = self._get_message_type(channel)
80
+ message_obj.group_id = self._get_channel_id(channel)
81
+ else:
82
+ logger.warning(
83
+ f"[Discord] Can't get channel info for {channel_id_str}, will guess message type.",
84
+ )
85
+ message_obj.type = MessageType.GROUP_MESSAGE
86
+ message_obj.group_id = session.session_id
87
+
88
+ message_obj.message_str = message_chain.get_plain_text()
89
+ message_obj.sender = MessageMember(
90
+ user_id=str(self.client_self_id),
91
+ nickname=self.client.user.display_name,
92
+ )
93
+ message_obj.self_id = self.client_self_id
94
+ message_obj.session_id = session.session_id
95
+ message_obj.message = message_chain.chain
96
+
97
+ # 创建临时事件对象来发送消息
98
+ temp_event = DiscordPlatformEvent(
99
+ message_str=message_chain.get_plain_text(),
100
+ message_obj=message_obj,
101
+ platform_meta=self.meta(),
102
+ session_id=session.session_id,
103
+ client=self.client,
104
+ )
105
+ await temp_event.send(message_chain)
106
+ await super().send_by_session(session, message_chain)
107
+
108
+ @override
109
+ def meta(self) -> PlatformMetadata:
110
+ """返回平台元数据"""
111
+ return PlatformMetadata(
112
+ "discord",
113
+ "Discord 适配器",
114
+ id=self.config.get("id"),
115
+ default_config_tmpl=self.config,
116
+ support_streaming_message=False,
117
+ )
118
+
119
+ @override
120
+ async def run(self):
121
+ """主要运行逻辑"""
122
+
123
+ # 初始化回调函数
124
+ async def on_received(message_data):
125
+ logger.debug(f"[Discord] 收到消息: {message_data}")
126
+ if self.client_self_id is None:
127
+ self.client_self_id = message_data.get("bot_id")
128
+ abm = await self.convert_message(data=message_data)
129
+ await self.handle_msg(abm)
130
+
131
+ # 初始化 Discord 客户端
132
+ token = str(self.config.get("discord_token"))
133
+ if not token:
134
+ logger.error("[Discord] Bot Token 未配置。请在配置文件中正确设置 token。")
135
+ return
136
+
137
+ proxy = self.config.get("discord_proxy") or None
138
+ self.client = DiscordBotClient(token, proxy)
139
+ self.client.on_message_received = on_received
140
+
141
+ async def callback():
142
+ if self.enable_command_register:
143
+ await self._collect_and_register_commands()
144
+ if self.activity_name:
145
+ await self.client.change_presence(
146
+ status=discord.Status.online,
147
+ activity=discord.CustomActivity(name=self.activity_name),
148
+ )
149
+
150
+ self.client.on_ready_once_callback = callback
151
+
152
+ try:
153
+ self._polling_task = asyncio.create_task(self.client.start_polling())
154
+ await self.shutdown_event.wait()
155
+ except discord.errors.LoginFailure:
156
+ logger.error("[Discord] 登录失败。请检查你的 Bot Token 是否正确。")
157
+ except discord.errors.ConnectionClosed:
158
+ logger.warning("[Discord] 与 Discord 的连接已关闭。")
159
+ except Exception as e:
160
+ logger.error(f"[Discord] 适配器运行时发生意外错误: {e}", exc_info=True)
161
+
162
+ def _get_message_type(
163
+ self,
164
+ channel: Messageable,
165
+ guild_id: int | None = None,
166
+ ) -> MessageType:
167
+ """根据 channel 对象和 guild_id 判断消息类型"""
168
+ if guild_id is not None:
169
+ return MessageType.GROUP_MESSAGE
170
+ if isinstance(channel, DMChannel) or getattr(channel, "guild", None) is None:
171
+ return MessageType.FRIEND_MESSAGE
172
+ return MessageType.GROUP_MESSAGE
173
+
174
+ def _get_channel_id(self, channel: Messageable) -> str:
175
+ """根据 channel 对象获取ID"""
176
+ return str(getattr(channel, "id", None))
177
+
178
+ def _convert_message_to_abm(self, data: dict) -> AstrBotMessage:
179
+ """将普通消息转换为 AstrBotMessage"""
180
+ message: discord.Message = data["message"]
181
+
182
+ content = message.content
183
+
184
+ # 如果机器人被@,移除@部分
185
+ # 剥离 User Mention (<@id>, <@!id>)
186
+ if self.client and self.client.user:
187
+ mention_str = f"<@{self.client.user.id}>"
188
+ mention_str_nickname = f"<@!{self.client.user.id}>"
189
+ if content.startswith(mention_str):
190
+ content = content[len(mention_str) :].lstrip()
191
+ elif content.startswith(mention_str_nickname):
192
+ content = content[len(mention_str_nickname) :].lstrip()
193
+
194
+ # 剥离 Role Mention(bot 拥有的任一角色被提及,<@&role_id>)
195
+ if (
196
+ hasattr(message, "role_mentions")
197
+ and hasattr(message, "guild")
198
+ and message.guild
199
+ ):
200
+ bot_member = (
201
+ message.guild.get_member(self.client.user.id)
202
+ if self.client and self.client.user
203
+ else None
204
+ )
205
+ if bot_member and hasattr(bot_member, "roles"):
206
+ for role in bot_member.roles:
207
+ role_mention_str = f"<@&{role.id}>"
208
+ if content.startswith(role_mention_str):
209
+ content = content[len(role_mention_str) :].lstrip()
210
+ break # 只剥离第一个匹配的角色 mention
211
+
212
+ abm = AstrBotMessage()
213
+ abm.type = self._get_message_type(message.channel)
214
+ abm.group_id = self._get_channel_id(message.channel)
215
+ abm.message_str = content
216
+ abm.sender = MessageMember(
217
+ user_id=str(message.author.id),
218
+ nickname=message.author.display_name,
219
+ )
220
+ message_chain = []
221
+ if abm.message_str:
222
+ message_chain.append(Plain(text=abm.message_str))
223
+ if message.attachments:
224
+ for attachment in message.attachments:
225
+ if attachment.content_type and attachment.content_type.startswith(
226
+ "image/",
227
+ ):
228
+ message_chain.append(
229
+ Image(file=attachment.url, filename=attachment.filename),
230
+ )
231
+ else:
232
+ message_chain.append(
233
+ File(name=attachment.filename, url=attachment.url),
234
+ )
235
+ abm.message = message_chain
236
+ abm.raw_message = message
237
+ abm.self_id = self.client_self_id
238
+ abm.session_id = str(message.channel.id)
239
+ abm.message_id = str(message.id)
240
+ return abm
241
+
242
+ async def convert_message(self, data: dict) -> AstrBotMessage:
243
+ """将平台消息转换成 AstrBotMessage"""
244
+ # 由于 on_interaction 已被禁用,我们只处理普通消息
245
+ return self._convert_message_to_abm(data)
246
+
247
+ async def handle_msg(self, message: AstrBotMessage, followup_webhook=None):
248
+ """处理消息"""
249
+ message_event = DiscordPlatformEvent(
250
+ message_str=message.message_str,
251
+ message_obj=message,
252
+ platform_meta=self.meta(),
253
+ session_id=message.session_id,
254
+ client=self.client,
255
+ interaction_followup_webhook=followup_webhook,
256
+ )
257
+
258
+ # 检查是否为斜杠指令
259
+ is_slash_command = message_event.interaction_followup_webhook is not None
260
+
261
+ # 检查是否被@(User Mention 或 Bot 拥有的 Role Mention)
262
+ is_mention = False
263
+ # User Mention
264
+ if (
265
+ self.client
266
+ and self.client.user
267
+ and hasattr(message.raw_message, "mentions")
268
+ ):
269
+ if self.client.user in message.raw_message.mentions:
270
+ is_mention = True
271
+ # Role Mention(Bot 拥有的角色被提及)
272
+ if not is_mention and hasattr(message.raw_message, "role_mentions"):
273
+ bot_member = None
274
+ if hasattr(message.raw_message, "guild") and message.raw_message.guild:
275
+ try:
276
+ bot_member = message.raw_message.guild.get_member(
277
+ self.client.user.id,
278
+ )
279
+ except Exception:
280
+ bot_member = None
281
+ if bot_member and hasattr(bot_member, "roles"):
282
+ bot_roles = set(bot_member.roles)
283
+ mentioned_roles = set(message.raw_message.role_mentions)
284
+ if (
285
+ bot_roles
286
+ and mentioned_roles
287
+ and bot_roles.intersection(mentioned_roles)
288
+ ):
289
+ is_mention = True
290
+
291
+ # 如果是斜杠指令或被@的消息,设置为唤醒状态
292
+ if is_slash_command or is_mention:
293
+ message_event.is_wake = True
294
+ message_event.is_at_or_wake_command = True
295
+
296
+ self.commit_event(message_event)
297
+
298
+ @override
299
+ async def terminate(self):
300
+ """终止适配器"""
301
+ logger.info("[Discord] 正在终止适配器... (step 1: cancel polling task)")
302
+ self.shutdown_event.set()
303
+ # 优先 cancel polling_task
304
+ if self._polling_task:
305
+ self._polling_task.cancel()
306
+ try:
307
+ await asyncio.wait_for(self._polling_task, timeout=10)
308
+ except asyncio.CancelledError:
309
+ logger.info("[Discord] polling_task 已取消。")
310
+ except Exception as e:
311
+ logger.warning(f"[Discord] polling_task 取消异常: {e}")
312
+ logger.info("[Discord] 正在清理已注册的斜杠指令... (step 2)")
313
+ # 清理指令
314
+ if self.enable_command_register and self.client:
315
+ try:
316
+ await asyncio.wait_for(
317
+ self.client.sync_commands(
318
+ commands=[],
319
+ guild_ids=[self.guild_id] if self.guild_id else None,
320
+ ),
321
+ timeout=10,
322
+ )
323
+ logger.info("[Discord] 指令清理完成。")
324
+ except Exception as e:
325
+ logger.error(f"[Discord] 清理指令时发生错误: {e}", exc_info=True)
326
+ logger.info("[Discord] 正在关闭 Discord 客户端... (step 3)")
327
+ if self.client and hasattr(self.client, "close"):
328
+ try:
329
+ await asyncio.wait_for(self.client.close(), timeout=10)
330
+ except Exception as e:
331
+ logger.warning(f"[Discord] 客户端关闭异常: {e}")
332
+ logger.info("[Discord] 适配器已终止。")
333
+
334
+ def register_handler(self, handler_info):
335
+ """注册处理器信息"""
336
+ self.registered_handlers.append(handler_info)
337
+
338
+ async def _collect_and_register_commands(self):
339
+ """收集所有指令并注册到Discord"""
340
+ logger.info("[Discord] 开始收集并注册斜杠指令...")
341
+ registered_commands = []
342
+
343
+ for handler_md in star_handlers_registry:
344
+ if not star_map[handler_md.handler_module_path].activated:
345
+ continue
346
+ for event_filter in handler_md.event_filters:
347
+ cmd_info = self._extract_command_info(event_filter, handler_md)
348
+ if not cmd_info:
349
+ continue
350
+
351
+ cmd_name, description, cmd_filter_instance = cmd_info
352
+
353
+ # 创建动态回调
354
+ callback = self._create_dynamic_callback(cmd_name)
355
+
356
+ # 创建一个通用的参数选项来接收所有文本输入
357
+ options = [
358
+ discord.Option(
359
+ name="params",
360
+ description="指令的所有参数",
361
+ type=discord.SlashCommandOptionType.string,
362
+ required=False,
363
+ ),
364
+ ]
365
+
366
+ # 创建SlashCommand
367
+ slash_command = discord.SlashCommand(
368
+ name=cmd_name,
369
+ description=description,
370
+ func=callback,
371
+ options=options,
372
+ guild_ids=[self.guild_id] if self.guild_id else None,
373
+ )
374
+ self.client.add_application_command(slash_command)
375
+ registered_commands.append(cmd_name)
376
+
377
+ if registered_commands:
378
+ logger.info(
379
+ f"[Discord] 准备同步 {len(registered_commands)} 个指令: {', '.join(registered_commands)}",
380
+ )
381
+ else:
382
+ logger.info("[Discord] 没有发现可注册的指令。")
383
+
384
+ # 使用 Pycord 的方法同步指令
385
+ # 注意:这可能需要一些时间,并且有频率限制
386
+ await self.client.sync_commands()
387
+ logger.info("[Discord] 指令同步完成。")
388
+
389
+ def _create_dynamic_callback(self, cmd_name: str):
390
+ """为每个指令动态创建一个异步回调函数"""
391
+
392
+ async def dynamic_callback(
393
+ ctx: discord.ApplicationContext, params: str | None = None
394
+ ):
395
+ # 将平台特定的前缀'/'剥离,以适配通用的CommandFilter
396
+ logger.debug(f"[Discord] 回调函数触发: {cmd_name}")
397
+ logger.debug(f"[Discord] 回调函数参数: {ctx}")
398
+ logger.debug(f"[Discord] 回调函数参数: {params}")
399
+ message_str_for_filter = cmd_name
400
+ if params:
401
+ message_str_for_filter += f" {params}"
402
+
403
+ logger.debug(
404
+ f"[Discord] 斜杠指令 '{cmd_name}' 被触发。 "
405
+ f"原始参数: '{params}'. "
406
+ f"构建的指令字符串: '{message_str_for_filter}'",
407
+ )
408
+
409
+ # 尝试立即响应,防止超时
410
+ followup_webhook = None
411
+ try:
412
+ await ctx.defer()
413
+ followup_webhook = ctx.followup
414
+ except Exception as e:
415
+ logger.warning(f"[Discord] 指令 '{cmd_name}' defer 失败: {e}")
416
+
417
+ # 2. 构建 AstrBotMessage
418
+ abm = AstrBotMessage()
419
+ abm.type = self._get_message_type(ctx.channel, ctx.guild_id)
420
+ abm.group_id = self._get_channel_id(ctx.channel)
421
+ abm.message_str = message_str_for_filter
422
+ abm.sender = MessageMember(
423
+ user_id=str(ctx.author.id),
424
+ nickname=ctx.author.display_name,
425
+ )
426
+ abm.message = [Plain(text=message_str_for_filter)]
427
+ abm.raw_message = ctx.interaction
428
+ abm.self_id = self.client_self_id
429
+ abm.session_id = str(ctx.channel_id)
430
+ abm.message_id = str(ctx.interaction.id)
431
+
432
+ # 3. 将消息和 webhook 分别交给 handle_msg 处理
433
+ await self.handle_msg(abm, followup_webhook)
434
+
435
+ return dynamic_callback
436
+
437
+ @staticmethod
438
+ def _extract_command_info(
439
+ event_filter: Any,
440
+ handler_metadata: StarHandlerMetadata,
441
+ ) -> tuple[str, str, CommandFilter] | None:
442
+ """从事件过滤器中提取指令信息"""
443
+ cmd_name = None
444
+ # is_group = False
445
+ cmd_filter_instance = None
446
+
447
+ if isinstance(event_filter, CommandFilter):
448
+ # 暂不支持子指令注册为斜杠指令
449
+ if (
450
+ event_filter.parent_command_names
451
+ and event_filter.parent_command_names != [""]
452
+ ):
453
+ return None
454
+ cmd_name = event_filter.command_name
455
+ cmd_filter_instance = event_filter
456
+
457
+ elif isinstance(event_filter, CommandGroupFilter):
458
+ # 暂不支持指令组直接注册为斜杠指令,因为它们没有 handle 方法
459
+ return None
460
+
461
+ if not cmd_name:
462
+ return None
463
+
464
+ # Discord 斜杠指令名称规范
465
+ if not re.match(r"^[a-z0-9_-]{1,32}$", cmd_name):
466
+ logger.debug(f"[Discord] 跳过不符合规范的指令: {cmd_name}")
467
+ return None
468
+
469
+ description = handler_metadata.desc or f"指令: {cmd_name}"
470
+ if len(description) > 100:
471
+ description = f"{description[:97]}..."
472
+
473
+ return cmd_name, description, cmd_filter_instance