AstrBot 4.5.1__py3-none-any.whl → 4.5.2__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 (244) hide show
  1. astrbot/api/__init__.py +10 -11
  2. astrbot/api/event/__init__.py +5 -6
  3. astrbot/api/event/filter/__init__.py +37 -36
  4. astrbot/api/platform/__init__.py +7 -8
  5. astrbot/api/provider/__init__.py +7 -7
  6. astrbot/api/star/__init__.py +3 -4
  7. astrbot/api/util/__init__.py +2 -2
  8. astrbot/cli/__main__.py +5 -5
  9. astrbot/cli/commands/__init__.py +3 -3
  10. astrbot/cli/commands/cmd_conf.py +19 -16
  11. astrbot/cli/commands/cmd_init.py +3 -2
  12. astrbot/cli/commands/cmd_plug.py +8 -10
  13. astrbot/cli/commands/cmd_run.py +5 -6
  14. astrbot/cli/utils/__init__.py +6 -6
  15. astrbot/cli/utils/basic.py +14 -14
  16. astrbot/cli/utils/plugin.py +24 -15
  17. astrbot/cli/utils/version_comparator.py +10 -12
  18. astrbot/core/__init__.py +8 -6
  19. astrbot/core/agent/agent.py +3 -2
  20. astrbot/core/agent/handoff.py +6 -2
  21. astrbot/core/agent/hooks.py +9 -6
  22. astrbot/core/agent/mcp_client.py +50 -15
  23. astrbot/core/agent/message.py +168 -0
  24. astrbot/core/agent/response.py +2 -1
  25. astrbot/core/agent/run_context.py +2 -3
  26. astrbot/core/agent/runners/base.py +10 -13
  27. astrbot/core/agent/runners/tool_loop_agent_runner.py +52 -51
  28. astrbot/core/agent/tool.py +60 -41
  29. astrbot/core/agent/tool_executor.py +9 -3
  30. astrbot/core/astr_agent_context.py +3 -1
  31. astrbot/core/astrbot_config_mgr.py +29 -9
  32. astrbot/core/config/__init__.py +2 -2
  33. astrbot/core/config/astrbot_config.py +28 -26
  34. astrbot/core/config/default.py +4 -6
  35. astrbot/core/conversation_mgr.py +105 -36
  36. astrbot/core/core_lifecycle.py +68 -54
  37. astrbot/core/db/__init__.py +33 -18
  38. astrbot/core/db/migration/helper.py +12 -10
  39. astrbot/core/db/migration/migra_3_to_4.py +53 -34
  40. astrbot/core/db/migration/migra_45_to_46.py +1 -1
  41. astrbot/core/db/migration/shared_preferences_v3.py +2 -1
  42. astrbot/core/db/migration/sqlite_v3.py +26 -23
  43. astrbot/core/db/po.py +27 -18
  44. astrbot/core/db/sqlite.py +74 -45
  45. astrbot/core/db/vec_db/base.py +10 -14
  46. astrbot/core/db/vec_db/faiss_impl/document_storage.py +90 -77
  47. astrbot/core/db/vec_db/faiss_impl/embedding_storage.py +9 -3
  48. astrbot/core/db/vec_db/faiss_impl/vec_db.py +36 -31
  49. astrbot/core/event_bus.py +8 -6
  50. astrbot/core/file_token_service.py +6 -5
  51. astrbot/core/initial_loader.py +7 -5
  52. astrbot/core/knowledge_base/chunking/__init__.py +1 -3
  53. astrbot/core/knowledge_base/chunking/base.py +1 -0
  54. astrbot/core/knowledge_base/chunking/fixed_size.py +2 -0
  55. astrbot/core/knowledge_base/chunking/recursive.py +16 -10
  56. astrbot/core/knowledge_base/kb_db_sqlite.py +50 -48
  57. astrbot/core/knowledge_base/kb_helper.py +30 -17
  58. astrbot/core/knowledge_base/kb_mgr.py +6 -7
  59. astrbot/core/knowledge_base/models.py +10 -4
  60. astrbot/core/knowledge_base/parsers/__init__.py +3 -5
  61. astrbot/core/knowledge_base/parsers/base.py +1 -0
  62. astrbot/core/knowledge_base/parsers/markitdown_parser.py +2 -1
  63. astrbot/core/knowledge_base/parsers/pdf_parser.py +2 -1
  64. astrbot/core/knowledge_base/parsers/text_parser.py +1 -0
  65. astrbot/core/knowledge_base/parsers/util.py +1 -1
  66. astrbot/core/knowledge_base/retrieval/__init__.py +6 -8
  67. astrbot/core/knowledge_base/retrieval/manager.py +17 -14
  68. astrbot/core/knowledge_base/retrieval/rank_fusion.py +7 -3
  69. astrbot/core/knowledge_base/retrieval/sparse_retriever.py +11 -5
  70. astrbot/core/log.py +21 -13
  71. astrbot/core/message/components.py +123 -217
  72. astrbot/core/message/message_event_result.py +24 -24
  73. astrbot/core/persona_mgr.py +20 -11
  74. astrbot/core/pipeline/__init__.py +7 -7
  75. astrbot/core/pipeline/content_safety_check/stage.py +13 -9
  76. astrbot/core/pipeline/content_safety_check/strategies/__init__.py +1 -2
  77. astrbot/core/pipeline/content_safety_check/strategies/baidu_aip.py +12 -13
  78. astrbot/core/pipeline/content_safety_check/strategies/keywords.py +1 -0
  79. astrbot/core/pipeline/content_safety_check/strategies/strategy.py +6 -6
  80. astrbot/core/pipeline/context.py +4 -1
  81. astrbot/core/pipeline/context_utils.py +77 -7
  82. astrbot/core/pipeline/preprocess_stage/stage.py +12 -9
  83. astrbot/core/pipeline/process_stage/method/llm_request.py +125 -72
  84. astrbot/core/pipeline/process_stage/method/star_request.py +19 -17
  85. astrbot/core/pipeline/process_stage/stage.py +13 -10
  86. astrbot/core/pipeline/process_stage/utils.py +6 -5
  87. astrbot/core/pipeline/rate_limit_check/stage.py +37 -36
  88. astrbot/core/pipeline/respond/stage.py +23 -20
  89. astrbot/core/pipeline/result_decorate/stage.py +31 -23
  90. astrbot/core/pipeline/scheduler.py +12 -8
  91. astrbot/core/pipeline/session_status_check/stage.py +12 -8
  92. astrbot/core/pipeline/stage.py +10 -4
  93. astrbot/core/pipeline/waking_check/stage.py +24 -18
  94. astrbot/core/pipeline/whitelist_check/stage.py +10 -7
  95. astrbot/core/platform/__init__.py +6 -6
  96. astrbot/core/platform/astr_message_event.py +76 -110
  97. astrbot/core/platform/astrbot_message.py +11 -13
  98. astrbot/core/platform/manager.py +16 -15
  99. astrbot/core/platform/message_session.py +5 -3
  100. astrbot/core/platform/platform.py +16 -24
  101. astrbot/core/platform/platform_metadata.py +4 -4
  102. astrbot/core/platform/register.py +8 -8
  103. astrbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py +23 -15
  104. astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py +51 -33
  105. astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py +42 -27
  106. astrbot/core/platform/sources/dingtalk/dingtalk_event.py +7 -3
  107. astrbot/core/platform/sources/discord/client.py +9 -6
  108. astrbot/core/platform/sources/discord/components.py +18 -14
  109. astrbot/core/platform/sources/discord/discord_platform_adapter.py +45 -30
  110. astrbot/core/platform/sources/discord/discord_platform_event.py +38 -30
  111. astrbot/core/platform/sources/lark/lark_adapter.py +23 -17
  112. astrbot/core/platform/sources/lark/lark_event.py +21 -14
  113. astrbot/core/platform/sources/misskey/misskey_adapter.py +107 -67
  114. astrbot/core/platform/sources/misskey/misskey_api.py +153 -129
  115. astrbot/core/platform/sources/misskey/misskey_event.py +20 -15
  116. astrbot/core/platform/sources/misskey/misskey_utils.py +74 -62
  117. astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py +63 -44
  118. astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py +41 -26
  119. astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_adapter.py +36 -17
  120. astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_event.py +3 -1
  121. astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_server.py +12 -7
  122. astrbot/core/platform/sources/satori/satori_adapter.py +56 -38
  123. astrbot/core/platform/sources/satori/satori_event.py +34 -25
  124. astrbot/core/platform/sources/slack/client.py +11 -9
  125. astrbot/core/platform/sources/slack/slack_adapter.py +52 -36
  126. astrbot/core/platform/sources/slack/slack_event.py +34 -24
  127. astrbot/core/platform/sources/telegram/tg_adapter.py +38 -18
  128. astrbot/core/platform/sources/telegram/tg_event.py +32 -18
  129. astrbot/core/platform/sources/webchat/webchat_adapter.py +27 -17
  130. astrbot/core/platform/sources/webchat/webchat_event.py +14 -10
  131. astrbot/core/platform/sources/wechatpadpro/wechatpadpro_adapter.py +115 -120
  132. astrbot/core/platform/sources/wechatpadpro/wechatpadpro_message_event.py +9 -8
  133. astrbot/core/platform/sources/wechatpadpro/xml_data_parser.py +15 -16
  134. astrbot/core/platform/sources/wecom/wecom_adapter.py +35 -18
  135. astrbot/core/platform/sources/wecom/wecom_event.py +55 -48
  136. astrbot/core/platform/sources/wecom/wecom_kf.py +34 -44
  137. astrbot/core/platform/sources/wecom/wecom_kf_message.py +26 -10
  138. astrbot/core/platform/sources/wecom_ai_bot/WXBizJsonMsgCrypt.py +18 -10
  139. astrbot/core/platform/sources/wecom_ai_bot/__init__.py +3 -5
  140. astrbot/core/platform/sources/wecom_ai_bot/ierror.py +0 -1
  141. astrbot/core/platform/sources/wecom_ai_bot/wecomai_adapter.py +61 -37
  142. astrbot/core/platform/sources/wecom_ai_bot/wecomai_api.py +67 -28
  143. astrbot/core/platform/sources/wecom_ai_bot/wecomai_event.py +8 -9
  144. astrbot/core/platform/sources/wecom_ai_bot/wecomai_queue_mgr.py +18 -9
  145. astrbot/core/platform/sources/wecom_ai_bot/wecomai_server.py +14 -12
  146. astrbot/core/platform/sources/wecom_ai_bot/wecomai_utils.py +22 -12
  147. astrbot/core/platform/sources/weixin_official_account/weixin_offacc_adapter.py +40 -26
  148. astrbot/core/platform/sources/weixin_official_account/weixin_offacc_event.py +47 -45
  149. astrbot/core/platform_message_history_mgr.py +5 -3
  150. astrbot/core/provider/__init__.py +2 -3
  151. astrbot/core/provider/entites.py +8 -8
  152. astrbot/core/provider/entities.py +61 -75
  153. astrbot/core/provider/func_tool_manager.py +59 -55
  154. astrbot/core/provider/manager.py +32 -22
  155. astrbot/core/provider/provider.py +72 -46
  156. astrbot/core/provider/register.py +7 -7
  157. astrbot/core/provider/sources/anthropic_source.py +48 -30
  158. astrbot/core/provider/sources/azure_tts_source.py +17 -13
  159. astrbot/core/provider/sources/coze_api_client.py +27 -17
  160. astrbot/core/provider/sources/coze_source.py +104 -87
  161. astrbot/core/provider/sources/dashscope_source.py +18 -11
  162. astrbot/core/provider/sources/dashscope_tts.py +36 -23
  163. astrbot/core/provider/sources/dify_source.py +25 -20
  164. astrbot/core/provider/sources/edge_tts_source.py +21 -17
  165. astrbot/core/provider/sources/fishaudio_tts_api_source.py +22 -14
  166. astrbot/core/provider/sources/gemini_embedding_source.py +12 -13
  167. astrbot/core/provider/sources/gemini_source.py +72 -58
  168. astrbot/core/provider/sources/gemini_tts_source.py +8 -6
  169. astrbot/core/provider/sources/gsv_selfhosted_source.py +17 -14
  170. astrbot/core/provider/sources/gsvi_tts_source.py +11 -7
  171. astrbot/core/provider/sources/minimax_tts_api_source.py +50 -40
  172. astrbot/core/provider/sources/openai_embedding_source.py +6 -8
  173. astrbot/core/provider/sources/openai_source.py +77 -69
  174. astrbot/core/provider/sources/openai_tts_api_source.py +14 -6
  175. astrbot/core/provider/sources/sensevoice_selfhosted_source.py +13 -11
  176. astrbot/core/provider/sources/vllm_rerank_source.py +10 -4
  177. astrbot/core/provider/sources/volcengine_tts.py +38 -31
  178. astrbot/core/provider/sources/whisper_api_source.py +14 -12
  179. astrbot/core/provider/sources/whisper_selfhosted_source.py +15 -11
  180. astrbot/core/provider/sources/xinference_rerank_source.py +16 -8
  181. astrbot/core/provider/sources/xinference_stt_provider.py +35 -25
  182. astrbot/core/star/__init__.py +16 -11
  183. astrbot/core/star/config.py +10 -15
  184. astrbot/core/star/context.py +97 -75
  185. astrbot/core/star/filter/__init__.py +4 -3
  186. astrbot/core/star/filter/command.py +30 -28
  187. astrbot/core/star/filter/command_group.py +27 -24
  188. astrbot/core/star/filter/custom_filter.py +6 -5
  189. astrbot/core/star/filter/event_message_type.py +4 -2
  190. astrbot/core/star/filter/permission.py +4 -2
  191. astrbot/core/star/filter/platform_adapter_type.py +4 -2
  192. astrbot/core/star/filter/regex.py +4 -2
  193. astrbot/core/star/register/__init__.py +19 -19
  194. astrbot/core/star/register/star.py +6 -2
  195. astrbot/core/star/register/star_handler.py +96 -73
  196. astrbot/core/star/session_llm_manager.py +48 -14
  197. astrbot/core/star/session_plugin_manager.py +29 -15
  198. astrbot/core/star/star.py +1 -2
  199. astrbot/core/star/star_handler.py +13 -8
  200. astrbot/core/star/star_manager.py +151 -59
  201. astrbot/core/star/star_tools.py +44 -37
  202. astrbot/core/star/updator.py +10 -10
  203. astrbot/core/umop_config_router.py +10 -4
  204. astrbot/core/updator.py +13 -5
  205. astrbot/core/utils/astrbot_path.py +3 -5
  206. astrbot/core/utils/dify_api_client.py +33 -15
  207. astrbot/core/utils/io.py +66 -42
  208. astrbot/core/utils/log_pipe.py +1 -1
  209. astrbot/core/utils/metrics.py +7 -7
  210. astrbot/core/utils/path_util.py +15 -16
  211. astrbot/core/utils/pip_installer.py +5 -5
  212. astrbot/core/utils/session_waiter.py +19 -20
  213. astrbot/core/utils/shared_preferences.py +45 -20
  214. astrbot/core/utils/t2i/__init__.py +4 -1
  215. astrbot/core/utils/t2i/network_strategy.py +35 -26
  216. astrbot/core/utils/t2i/renderer.py +11 -5
  217. astrbot/core/utils/t2i/template_manager.py +14 -15
  218. astrbot/core/utils/tencent_record_helper.py +19 -13
  219. astrbot/core/utils/version_comparator.py +10 -13
  220. astrbot/core/zip_updator.py +43 -40
  221. astrbot/dashboard/routes/__init__.py +18 -18
  222. astrbot/dashboard/routes/auth.py +10 -8
  223. astrbot/dashboard/routes/chat.py +30 -21
  224. astrbot/dashboard/routes/config.py +92 -75
  225. astrbot/dashboard/routes/conversation.py +46 -39
  226. astrbot/dashboard/routes/file.py +4 -2
  227. astrbot/dashboard/routes/knowledge_base.py +47 -40
  228. astrbot/dashboard/routes/log.py +9 -4
  229. astrbot/dashboard/routes/persona.py +19 -16
  230. astrbot/dashboard/routes/plugin.py +69 -55
  231. astrbot/dashboard/routes/route.py +3 -1
  232. astrbot/dashboard/routes/session_management.py +130 -116
  233. astrbot/dashboard/routes/stat.py +34 -34
  234. astrbot/dashboard/routes/t2i.py +15 -12
  235. astrbot/dashboard/routes/tools.py +47 -52
  236. astrbot/dashboard/routes/update.py +32 -28
  237. astrbot/dashboard/server.py +30 -26
  238. astrbot/dashboard/utils.py +8 -4
  239. {astrbot-4.5.1.dist-info → astrbot-4.5.2.dist-info}/METADATA +2 -1
  240. astrbot-4.5.2.dist-info/RECORD +261 -0
  241. astrbot-4.5.1.dist-info/RECORD +0 -260
  242. {astrbot-4.5.1.dist-info → astrbot-4.5.2.dist-info}/WHEEL +0 -0
  243. {astrbot-4.5.1.dist-info → astrbot-4.5.2.dist-info}/entry_points.txt +0 -0
  244. {astrbot-4.5.1.dist-info → astrbot-4.5.2.dist-info}/licenses/LICENSE +0 -0
@@ -1,10 +1,13 @@
1
1
  from defusedxml import ElementTree as eT
2
+
2
3
  from astrbot.api import logger
3
4
  from astrbot.api.message_components import (
4
- WechatEmoji as Emoji,
5
- Plain,
6
- Image,
7
5
  BaseMessageComponent,
6
+ Image,
7
+ Plain,
8
+ )
9
+ from astrbot.api.message_components import (
10
+ WechatEmoji as Emoji,
8
11
  )
9
12
 
10
13
 
@@ -15,7 +18,7 @@ class GeweDataParser:
15
18
  is_private_chat: bool = False,
16
19
  cached_texts=None,
17
20
  cached_images=None,
18
- raw_message: dict = None,
21
+ raw_message: dict | None = None,
19
22
  downloader=None,
20
23
  ):
21
24
  self._xml = None
@@ -47,9 +50,7 @@ class GeweDataParser:
47
50
  raise
48
51
 
49
52
  async def parse_mutil_49(self) -> list[BaseMessageComponent] | None:
50
- """
51
- 处理 msg_type == 49 的多种 appmsg 类型(目前支持 type==57)
52
- """
53
+ """处理 msg_type == 49 的多种 appmsg 类型(目前支持 type==57)"""
53
54
  try:
54
55
  appmsg_type = self._format_to_xml().findtext(".//appmsg/type")
55
56
  if appmsg_type == "57":
@@ -59,9 +60,7 @@ class GeweDataParser:
59
60
  return None
60
61
 
61
62
  async def parse_reply(self) -> list[BaseMessageComponent]:
62
- """
63
- 处理 type == 57 的引用消息:支持文本(1)、图片(3)、嵌套49(49)
64
- """
63
+ """处理 type == 57 的引用消息:支持文本(1)、图片(3)、嵌套49(49)"""
65
64
  components = []
66
65
 
67
66
  try:
@@ -96,7 +95,9 @@ class GeweDataParser:
96
95
  )
97
96
  if cdn_url and self.downloader:
98
97
  image_resp = await self.downloader(
99
- self.from_user_name, self.to_user_name, self.msg_id
98
+ self.from_user_name,
99
+ self.to_user_name,
100
+ self.msg_id,
100
101
  )
101
102
  quoted_image_b64 = (
102
103
  image_resp.get("Data", {})
@@ -111,11 +112,11 @@ class GeweDataParser:
111
112
  [
112
113
  Image.fromBase64(quoted_image_b64),
113
114
  Plain(f"[引用] {nickname}: [引用的图片]"),
114
- ]
115
+ ],
115
116
  )
116
117
  else:
117
118
  components.append(
118
- Plain(f"[引用] {nickname}: [引用的图片 - 未能获取]")
119
+ Plain(f"[引用] {nickname}: [引用的图片 - 未能获取]"),
119
120
  )
120
121
 
121
122
  case 49: # 嵌套引用
@@ -143,9 +144,7 @@ class GeweDataParser:
143
144
  return components
144
145
 
145
146
  def parse_emoji(self) -> Emoji | None:
146
- """
147
- 处理 msg_type == 47 的表情消息(emoji)
148
- """
147
+ """处理 msg_type == 47 的表情消息(emoji)"""
149
148
  try:
150
149
  emoji_element = self._format_to_xml().find(".//emoji")
151
150
  if emoji_element is not None:
@@ -41,10 +41,14 @@ class WecomServer:
41
41
  self.port = int(config.get("port"))
42
42
  self.callback_server_host = config.get("callback_server_host", "0.0.0.0")
43
43
  self.server.add_url_rule(
44
- "/callback/command", view_func=self.verify, methods=["GET"]
44
+ "/callback/command",
45
+ view_func=self.verify,
46
+ methods=["GET"],
45
47
  )
46
48
  self.server.add_url_rule(
47
- "/callback/command", view_func=self.callback_command, methods=["POST"]
49
+ "/callback/command",
50
+ view_func=self.callback_command,
51
+ methods=["POST"],
48
52
  )
49
53
  self.event_queue = event_queue
50
54
 
@@ -94,7 +98,7 @@ class WecomServer:
94
98
 
95
99
  async def start_polling(self):
96
100
  logger.info(
97
- f"将在 {self.callback_server_host}:{self.port} 端口启动 企业微信 适配器。"
101
+ f"将在 {self.callback_server_host}:{self.port} 端口启动 企业微信 适配器。",
98
102
  )
99
103
  await self.server.run_task(
100
104
  host=self.callback_server_host,
@@ -109,21 +113,24 @@ class WecomServer:
109
113
  @register_platform_adapter("wecom", "wecom 适配器")
110
114
  class WecomPlatformAdapter(Platform):
111
115
  def __init__(
112
- self, platform_config: dict, platform_settings: dict, event_queue: asyncio.Queue
116
+ self,
117
+ platform_config: dict,
118
+ platform_settings: dict,
119
+ event_queue: asyncio.Queue,
113
120
  ) -> None:
114
121
  super().__init__(event_queue)
115
122
  self.config = platform_config
116
123
  self.settingss = platform_settings
117
124
  self.client_self_id = uuid.uuid4().hex[:8]
118
125
  self.api_base_url = platform_config.get(
119
- "api_base_url", "https://qyapi.weixin.qq.com/cgi-bin/"
126
+ "api_base_url",
127
+ "https://qyapi.weixin.qq.com/cgi-bin/",
120
128
  )
121
129
 
122
130
  if not self.api_base_url:
123
131
  self.api_base_url = "https://qyapi.weixin.qq.com/cgi-bin/"
124
132
 
125
- if self.api_base_url.endswith("/"):
126
- self.api_base_url = self.api_base_url[:-1]
133
+ self.api_base_url = self.api_base_url.removesuffix("/")
127
134
  if not self.api_base_url.endswith("/cgi-bin"):
128
135
  self.api_base_url += "/cgi-bin"
129
136
 
@@ -165,7 +172,8 @@ class WecomPlatformAdapter(Platform):
165
172
  return None
166
173
 
167
174
  msg_new = await asyncio.get_event_loop().run_in_executor(
168
- None, get_latest_msg_item
175
+ None,
176
+ get_latest_msg_item,
169
177
  )
170
178
  if msg_new:
171
179
  await self.convert_wechat_kf_message(msg_new)
@@ -176,7 +184,9 @@ class WecomPlatformAdapter(Platform):
176
184
 
177
185
  @override
178
186
  async def send_by_session(
179
- self, session: MessageSesion, message_chain: MessageChain
187
+ self,
188
+ session: MessageSesion,
189
+ message_chain: MessageChain,
180
190
  ):
181
191
  await super().send_by_session(session, message_chain)
182
192
 
@@ -195,10 +205,11 @@ class WecomPlatformAdapter(Platform):
195
205
  try:
196
206
  acc_list = (
197
207
  await loop.run_in_executor(
198
- None, self.wechat_kf_api.get_account_list
208
+ None,
209
+ self.wechat_kf_api.get_account_list,
199
210
  )
200
211
  ).get("account_list", [])
201
- logger.debug(f"获取到微信客服列表: {str(acc_list)}")
212
+ logger.debug(f"获取到微信客服列表: {acc_list!s}")
202
213
  for acc in acc_list:
203
214
  name = acc.get("name", None)
204
215
  if name != self.kf_name:
@@ -206,7 +217,7 @@ class WecomPlatformAdapter(Platform):
206
217
  open_kfid = acc.get("open_kfid", None)
207
218
  if not open_kfid:
208
219
  logger.error("获取微信客服失败,open_kfid 为空。")
209
- logger.debug(f"Found open_kfid: {str(open_kfid)}")
220
+ logger.debug(f"Found open_kfid: {open_kfid!s}")
210
221
  kf_url = (
211
222
  await loop.run_in_executor(
212
223
  None,
@@ -216,7 +227,7 @@ class WecomPlatformAdapter(Platform):
216
227
  )
217
228
  ).get("url", "")
218
229
  logger.info(
219
- f"请打开以下链接,在微信扫码以获取客服微信: https://api.cl2wm.cn/api/qrcode/code?text={kf_url}"
230
+ f"请打开以下链接,在微信扫码以获取客服微信: https://api.cl2wm.cn/api/qrcode/code?text={kf_url}",
220
231
  )
221
232
  except Exception as e:
222
233
  logger.error(e)
@@ -256,7 +267,9 @@ class WecomPlatformAdapter(Platform):
256
267
  assert isinstance(msg, VoiceMessage)
257
268
 
258
269
  resp: Response = await asyncio.get_event_loop().run_in_executor(
259
- None, self.client.media.download, msg.media_id
270
+ None,
271
+ self.client.media.download,
272
+ msg.media_id,
260
273
  )
261
274
  temp_dir = os.path.join(get_astrbot_data_path(), "temp")
262
275
  path = os.path.join(temp_dir, f"wecom_{msg.media_id}.amr")
@@ -294,8 +307,8 @@ class WecomPlatformAdapter(Platform):
294
307
  await self.handle_msg(abm)
295
308
 
296
309
  async def convert_wechat_kf_message(self, msg: dict) -> AstrBotMessage | None:
297
- msgtype = msg.get("msgtype", None)
298
- external_userid = msg.get("external_userid", None)
310
+ msgtype = msg.get("msgtype")
311
+ external_userid = msg.get("external_userid")
299
312
  abm = AstrBotMessage()
300
313
  abm.raw_message = msg
301
314
  abm.raw_message["_wechat_kf_flag"] = None # 方便处理
@@ -312,7 +325,9 @@ class WecomPlatformAdapter(Platform):
312
325
  elif msgtype == "image":
313
326
  media_id = msg.get("image", {}).get("media_id", "")
314
327
  resp: Response = await asyncio.get_event_loop().run_in_executor(
315
- None, self.client.media.download, media_id
328
+ None,
329
+ self.client.media.download,
330
+ media_id,
316
331
  )
317
332
  path = f"data/temp/wechat_kf_{media_id}.jpg"
318
333
  with open(path, "wb") as f:
@@ -321,7 +336,9 @@ class WecomPlatformAdapter(Platform):
321
336
  elif msgtype == "voice":
322
337
  media_id = msg.get("voice", {}).get("media_id", "")
323
338
  resp: Response = await asyncio.get_event_loop().run_in_executor(
324
- None, self.client.media.download, media_id
339
+ None,
340
+ self.client.media.download,
341
+ media_id,
325
342
  )
326
343
 
327
344
  temp_dir = os.path.join(get_astrbot_data_path(), "temp")
@@ -1,22 +1,23 @@
1
+ import asyncio
1
2
  import os
2
3
  import uuid
3
- import asyncio
4
- from astrbot.api.event import AstrMessageEvent, MessageChain
5
- from astrbot.api.platform import AstrBotMessage, PlatformMetadata
6
- from astrbot.api.message_components import Plain, Image, Record
4
+
7
5
  from wechatpy.enterprise import WeChatClient
8
- from .wecom_kf_message import WeChatKFMessage
9
6
 
10
7
  from astrbot.api import logger
8
+ from astrbot.api.event import AstrMessageEvent, MessageChain
9
+ from astrbot.api.message_components import Image, Plain, Record
10
+ from astrbot.api.platform import AstrBotMessage, PlatformMetadata
11
11
  from astrbot.core.utils.astrbot_path import get_astrbot_data_path
12
12
 
13
+ from .wecom_kf_message import WeChatKFMessage
14
+
13
15
  try:
14
16
  import pydub
15
17
  except Exception:
16
18
  logger.warning(
17
- "检测到 pydub 库未安装,企业微信将无法语音收发。如需使用语音,请前往管理面板 -> 控制台 -> 安装 Pip 库安装 pydub。"
19
+ "检测到 pydub 库未安装,企业微信将无法语音收发。如需使用语音,请前往管理面板 -> 控制台 -> 安装 Pip 库安装 pydub。",
18
20
  )
19
- pass
20
21
 
21
22
 
22
23
  class WecomPlatformEvent(AstrMessageEvent):
@@ -33,7 +34,9 @@ class WecomPlatformEvent(AstrMessageEvent):
33
34
 
34
35
  @staticmethod
35
36
  async def send_with_client(
36
- client: WeChatClient, message: MessageChain, user_name: str
37
+ client: WeChatClient,
38
+ message: MessageChain,
39
+ user_name: str,
37
40
  ):
38
41
  pass
39
42
 
@@ -44,44 +47,44 @@ class WecomPlatformEvent(AstrMessageEvent):
44
47
  plain (str): 要分割的长文本
45
48
  Returns:
46
49
  list[str]: 分割后的文本列表
50
+
47
51
  """
48
52
  if len(plain) <= 2048:
49
53
  return [plain]
50
- else:
51
- result = []
52
- start = 0
53
- while start < len(plain):
54
- # 剩下的字符串长度<2048时结束
55
- if start + 2048 >= len(plain):
56
- result.append(plain[start:])
54
+ result = []
55
+ start = 0
56
+ while start < len(plain):
57
+ # 剩下的字符串长度<2048时结束
58
+ if start + 2048 >= len(plain):
59
+ result.append(plain[start:])
60
+ break
61
+
62
+ # 向前搜索分割标点符号
63
+ end = min(start + 2048, len(plain))
64
+ cut_position = end
65
+ for i in range(end, start, -1):
66
+ if i < len(plain) and plain[i - 1] in [
67
+ "。",
68
+ "!",
69
+ "?",
70
+ ".",
71
+ "!",
72
+ "?",
73
+ "\n",
74
+ ";",
75
+ ";",
76
+ ]:
77
+ cut_position = i
57
78
  break
58
79
 
59
- # 向前搜索分割标点符号
60
- end = min(start + 2048, len(plain))
80
+ # 没找到合适的位置分割, 直接切分
81
+ if cut_position == end and end < len(plain):
61
82
  cut_position = end
62
- for i in range(end, start, -1):
63
- if i < len(plain) and plain[i - 1] in [
64
- "。",
65
- "!",
66
- "?",
67
- ".",
68
- "!",
69
- "?",
70
- "\n",
71
- ";",
72
- ";",
73
- ]:
74
- cut_position = i
75
- break
76
-
77
- # 没找到合适的位置分割, 直接切分
78
- if cut_position == end and end < len(plain):
79
- cut_position = end
80
-
81
- result.append(plain[start:cut_position])
82
- start = cut_position
83
-
84
- return result
83
+
84
+ result.append(plain[start:cut_position])
85
+ start = cut_position
86
+
87
+ return result
85
88
 
86
89
  async def send(self, message: MessageChain):
87
90
  message_obj = self.message_obj
@@ -111,7 +114,7 @@ class WecomPlatformEvent(AstrMessageEvent):
111
114
  except Exception as e:
112
115
  logger.error(f"微信客服上传图片失败: {e}")
113
116
  await self.send(
114
- MessageChain().message(f"微信客服上传图片失败: {e}")
117
+ MessageChain().message(f"微信客服上传图片失败: {e}"),
115
118
  )
116
119
  return
117
120
  logger.debug(f"微信客服上传图片返回: {response}")
@@ -126,7 +129,8 @@ class WecomPlatformEvent(AstrMessageEvent):
126
129
  temp_dir = os.path.join(get_astrbot_data_path(), "temp")
127
130
  record_path_amr = os.path.join(temp_dir, f"{uuid.uuid4()}.amr")
128
131
  pydub.AudioSegment.from_wav(record_path).export(
129
- record_path_amr, format="amr"
132
+ record_path_amr,
133
+ format="amr",
130
134
  )
131
135
 
132
136
  with open(record_path_amr, "rb") as f:
@@ -135,7 +139,7 @@ class WecomPlatformEvent(AstrMessageEvent):
135
139
  except Exception as e:
136
140
  logger.error(f"微信客服上传语音失败: {e}")
137
141
  await self.send(
138
- MessageChain().message(f"微信客服上传语音失败: {e}")
142
+ MessageChain().message(f"微信客服上传语音失败: {e}"),
139
143
  )
140
144
  return
141
145
  logger.info(f"微信客服上传语音返回: {response}")
@@ -154,7 +158,9 @@ class WecomPlatformEvent(AstrMessageEvent):
154
158
  plain_chunks = await self.split_plain(comp.text)
155
159
  for chunk in plain_chunks:
156
160
  self.client.message.send_text(
157
- message_obj.self_id, message_obj.session_id, chunk
161
+ message_obj.self_id,
162
+ message_obj.session_id,
163
+ chunk,
158
164
  )
159
165
  await asyncio.sleep(0.5) # Avoid sending too fast
160
166
  elif isinstance(comp, Image):
@@ -166,7 +172,7 @@ class WecomPlatformEvent(AstrMessageEvent):
166
172
  except Exception as e:
167
173
  logger.error(f"企业微信上传图片失败: {e}")
168
174
  await self.send(
169
- MessageChain().message(f"企业微信上传图片失败: {e}")
175
+ MessageChain().message(f"企业微信上传图片失败: {e}"),
170
176
  )
171
177
  return
172
178
  logger.debug(f"企业微信上传图片返回: {response}")
@@ -181,7 +187,8 @@ class WecomPlatformEvent(AstrMessageEvent):
181
187
  temp_dir = os.path.join(get_astrbot_data_path(), "temp")
182
188
  record_path_amr = os.path.join(temp_dir, f"{uuid.uuid4()}.amr")
183
189
  pydub.AudioSegment.from_wav(record_path).export(
184
- record_path_amr, format="amr"
190
+ record_path_amr,
191
+ format="amr",
185
192
  )
186
193
 
187
194
  with open(record_path_amr, "rb") as f:
@@ -190,7 +197,7 @@ class WecomPlatformEvent(AstrMessageEvent):
190
197
  except Exception as e:
191
198
  logger.error(f"企业微信上传语音失败: {e}")
192
199
  await self.send(
193
- MessageChain().message(f"企业微信上传语音失败: {e}")
200
+ MessageChain().message(f"企业微信上传语音失败: {e}"),
194
201
  )
195
202
  return
196
203
  logger.info(f"企业微信上传语音返回: {response}")
@@ -212,7 +219,7 @@ class WecomPlatformEvent(AstrMessageEvent):
212
219
  else:
213
220
  buffer.chain.extend(chain.chain)
214
221
  if not buffer:
215
- return
222
+ return None
216
223
  buffer.squash_plain()
217
224
  await self.send(buffer)
218
225
  return await super().send_streaming(generator, use_fallback)
@@ -1,7 +1,4 @@
1
- # -*- coding: utf-8 -*-
2
-
3
- """
4
- The MIT License (MIT)
1
+ """The MIT License (MIT)
5
2
 
6
3
  Copyright (c) 2014-2020 messense
7
4
 
@@ -28,15 +25,13 @@ from wechatpy.client.api.base import BaseWeChatAPI
28
25
 
29
26
 
30
27
  class WeChatKF(BaseWeChatAPI):
31
- """
32
- 微信客服接口
28
+ """微信客服接口
33
29
 
34
30
  https://work.weixin.qq.com/api/doc/90000/90135/94670
35
31
  """
36
32
 
37
33
  def sync_msg(self, token, open_kfid, cursor="", limit=1000):
38
- """
39
- 微信客户发送的消息、接待人员在企业微信回复的消息、发送消息接口发送失败事件(如被用户拒收)
34
+ """微信客户发送的消息、接待人员在企业微信回复的消息、发送消息接口发送失败事件(如被用户拒收)
40
35
  、客户点击菜单消息的回复消息,可以通过该接口获取具体的消息内容和事件。不支持读取通过发送消息接口发送的消息。
41
36
  支持的消息类型:文本、图片、语音、视频、文件、位置、链接、名片、小程序、事件。
42
37
 
@@ -57,8 +52,7 @@ class WeChatKF(BaseWeChatAPI):
57
52
  return self._post("kf/sync_msg", data=data)
58
53
 
59
54
  def get_service_state(self, open_kfid, external_userid):
60
- """
61
- 获取会话状态
55
+ """获取会话状态
62
56
 
63
57
  ID 状态 说明
64
58
  0 未处理 新会话接入。可选择:1.直接用API自动回复消息。2.放进待接入池等待接待人员接待。3.指定接待人员进行接待
@@ -78,10 +72,13 @@ class WeChatKF(BaseWeChatAPI):
78
72
  return self._post("kf/service_state/get", data=data)
79
73
 
80
74
  def trans_service_state(
81
- self, open_kfid, external_userid, service_state, servicer_userid=""
75
+ self,
76
+ open_kfid,
77
+ external_userid,
78
+ service_state,
79
+ servicer_userid="",
82
80
  ):
83
- """
84
- 变更会话状态
81
+ """变更会话状态
85
82
 
86
83
  :param open_kfid: 客服帐号ID
87
84
  :param external_userid: 微信客户的external_userid
@@ -98,8 +95,7 @@ class WeChatKF(BaseWeChatAPI):
98
95
  return self._post("kf/service_state/trans", data=data)
99
96
 
100
97
  def get_servicer_list(self, open_kfid):
101
- """
102
- 获取接待人员列表
98
+ """获取接待人员列表
103
99
 
104
100
  :param open_kfid: 客服帐号ID
105
101
  :return: 接口调用结果
@@ -110,8 +106,7 @@ class WeChatKF(BaseWeChatAPI):
110
106
  return self._get("kf/servicer/list", params=data)
111
107
 
112
108
  def add_servicer(self, open_kfid, userid_list):
113
- """
114
- 添加接待人员
109
+ """添加接待人员
115
110
  添加指定客服帐号的接待人员。
116
111
 
117
112
  :param open_kfid: 客服帐号ID
@@ -128,8 +123,7 @@ class WeChatKF(BaseWeChatAPI):
128
123
  return self._post("kf/servicer/add", data=data)
129
124
 
130
125
  def del_servicer(self, open_kfid, userid_list):
131
- """
132
- 删除接待人员
126
+ """删除接待人员
133
127
  从客服帐号删除接待人员
134
128
 
135
129
  :param open_kfid: 客服帐号ID
@@ -146,8 +140,7 @@ class WeChatKF(BaseWeChatAPI):
146
140
  return self._post("kf/servicer/del", data=data)
147
141
 
148
142
  def batchget_customer(self, external_userid_list):
149
- """
150
- 客户基本信息获取
143
+ """客户基本信息获取
151
144
 
152
145
  :param external_userid_list: external_userid列表
153
146
  :return: 接口调用结果
@@ -161,16 +154,14 @@ class WeChatKF(BaseWeChatAPI):
161
154
  return self._post("kf/customer/batchget", data=data)
162
155
 
163
156
  def get_account_list(self):
164
- """
165
- 获取客服帐号列表
157
+ """获取客服帐号列表
166
158
 
167
159
  :return: 接口调用结果
168
160
  """
169
161
  return self._get("kf/account/list")
170
162
 
171
163
  def add_contact_way(self, open_kfid, scene):
172
- """
173
- 获取客服帐号链接
164
+ """获取客服帐号链接
174
165
 
175
166
  :param open_kfid: 客服帐号ID
176
167
  :param scene: 场景值,字符串类型,由开发者自定义。不多于32字节;字符串取值范围(正则表达式):[0-9a-zA-Z_-]*
@@ -180,18 +171,21 @@ class WeChatKF(BaseWeChatAPI):
180
171
  return self._post("kf/add_contact_way", data=data)
181
172
 
182
173
  def get_upgrade_service_config(self):
183
- """
184
- 获取配置的专员与客户群
174
+ """获取配置的专员与客户群
185
175
 
186
176
  :return: 接口调用结果
187
177
  """
188
178
  return self._get("kf/customer/get_upgrade_service_config")
189
179
 
190
180
  def upgrade_service(
191
- self, open_kfid, external_userid, service_type, member=None, groupchat=None
181
+ self,
182
+ open_kfid,
183
+ external_userid,
184
+ service_type,
185
+ member=None,
186
+ groupchat=None,
192
187
  ):
193
- """
194
- 为客户升级为专员或客户群服务
188
+ """为客户升级为专员或客户群服务
195
189
 
196
190
  :param open_kfid: 客服帐号ID
197
191
  :param external_userid: 微信客户的external_userid
@@ -200,7 +194,6 @@ class WeChatKF(BaseWeChatAPI):
200
194
  :param groupchat: 推荐的客户群,type等于2时有效
201
195
  :return: 接口调用结果
202
196
  """
203
-
204
197
  data = {
205
198
  "open_kfid": open_kfid,
206
199
  "external_userid": external_userid,
@@ -213,20 +206,17 @@ class WeChatKF(BaseWeChatAPI):
213
206
  return self._post("kf/customer/upgrade_service", data=data)
214
207
 
215
208
  def cancel_upgrade_service(self, open_kfid, external_userid):
216
- """
217
- 为客户取消推荐
209
+ """为客户取消推荐
218
210
 
219
211
  :param open_kfid: 客服帐号ID
220
212
  :param external_userid: 微信客户的external_userid
221
213
  :return: 接口调用结果
222
214
  """
223
-
224
215
  data = {"open_kfid": open_kfid, "external_userid": external_userid}
225
216
  return self._post("kf/customer/cancel_upgrade_service", data=data)
226
217
 
227
218
  def send_msg_on_event(self, code, msgtype, msg_content, msgid=None):
228
- """
229
- 当特定的事件回调消息包含code字段,可以此code为凭证,调用该接口给用户发送相应事件场景下的消息,如客服欢迎语。
219
+ """当特定的事件回调消息包含code字段,可以此code为凭证,调用该接口给用户发送相应事件场景下的消息,如客服欢迎语。
230
220
  支持发送消息类型:文本、菜单消息。
231
221
 
232
222
  :param code: 事件响应消息对应的code。通过事件回调下发,仅可使用一次。
@@ -236,7 +226,6 @@ class WeChatKF(BaseWeChatAPI):
236
226
  字符串取值范围(正则表达式):[0-9a-zA-Z_-]*
237
227
  :return: 接口调用结果
238
228
  """
239
-
240
229
  data = {"code": code, "msgtype": msgtype}
241
230
  if msgid:
242
231
  data["msgid"] = msgid
@@ -244,8 +233,7 @@ class WeChatKF(BaseWeChatAPI):
244
233
  return self._post("kf/send_msg_on_event", data=data)
245
234
 
246
235
  def get_corp_statistic(self, start_time, end_time, open_kfid=None):
247
- """
248
- 获取「客户数据统计」企业汇总数据
236
+ """获取「客户数据统计」企业汇总数据
249
237
 
250
238
  :param start_time: 开始时间
251
239
  :param end_time: 结束时间
@@ -256,10 +244,13 @@ class WeChatKF(BaseWeChatAPI):
256
244
  return self._post("kf/get_corp_statistic", data=data)
257
245
 
258
246
  def get_servicer_statistic(
259
- self, start_time, end_time, open_kfid=None, servicer_userid=None
247
+ self,
248
+ start_time,
249
+ end_time,
250
+ open_kfid=None,
251
+ servicer_userid=None,
260
252
  ):
261
- """
262
- 获取「客户数据统计」接待人员明细数据
253
+ """获取「客户数据统计」接待人员明细数据
263
254
 
264
255
  :param start_time: 开始时间
265
256
  :param end_time: 结束时间
@@ -276,8 +267,7 @@ class WeChatKF(BaseWeChatAPI):
276
267
  return self._post("kf/get_servicer_statistic", data=data)
277
268
 
278
269
  def account_update(self, open_kfid, name, media_id):
279
- """
280
- 修改客服账号
270
+ """修改客服账号
281
271
 
282
272
  :param open_kfid: 客服帐号ID
283
273
  :param name: 客服名称