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
@@ -1,19 +1,23 @@
1
- import uuid
2
1
  import asyncio
3
- from astrbot.api.event import AstrMessageEvent, MessageChain
4
- from astrbot.api.platform import AstrBotMessage, PlatformMetadata
5
- from astrbot.api.message_components import Plain, Image, Record
2
+ import os
3
+ import uuid
4
+
6
5
  from wechatpy.enterprise import WeChatClient
7
6
 
8
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
+ from astrbot.core.utils.astrbot_path import get_astrbot_data_path
12
+
13
+ from .wecom_kf_message import WeChatKFMessage
9
14
 
10
15
  try:
11
16
  import pydub
12
17
  except Exception:
13
18
  logger.warning(
14
- "检测到 pydub 库未安装,企业微信将无法语音收发。如需使用语音,请前往管理面板 -> 控制台 -> 安装 Pip 库安装 pydub。"
19
+ "检测到 pydub 库未安装,企业微信将无法语音收发。如需使用语音,请前往管理面板 -> 控制台 -> 安装 Pip 库安装 pydub。",
15
20
  )
16
- pass
17
21
 
18
22
 
19
23
  class WecomPlatformEvent(AstrMessageEvent):
@@ -30,7 +34,9 @@ class WecomPlatformEvent(AstrMessageEvent):
30
34
 
31
35
  @staticmethod
32
36
  async def send_with_client(
33
- client: WeChatClient, message: MessageChain, user_name: str
37
+ client: WeChatClient,
38
+ message: MessageChain,
39
+ user_name: str,
34
40
  ):
35
41
  pass
36
42
 
@@ -41,89 +47,168 @@ class WecomPlatformEvent(AstrMessageEvent):
41
47
  plain (str): 要分割的长文本
42
48
  Returns:
43
49
  list[str]: 分割后的文本列表
50
+
44
51
  """
45
52
  if len(plain) <= 2048:
46
53
  return [plain]
47
- else:
48
- result = []
49
- start = 0
50
- while start < len(plain):
51
- # 剩下的字符串长度<2048时结束
52
- if start + 2048 >= len(plain):
53
- 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
54
78
  break
55
-
56
- # 向前搜索分割标点符号
57
- end = min(start + 2048, len(plain))
79
+
80
+ # 没找到合适的位置分割, 直接切分
81
+ if cut_position == end and end < len(plain):
58
82
  cut_position = end
59
- for i in range(end, start, -1):
60
- if i < len(plain) and plain[i-1] in ["。", "!", "?", ".", "!", "?", "\n", ";", ";"]:
61
- cut_position = i
62
- break
63
-
64
- # 没找到合适的位置分割, 直接切分
65
- if cut_position == end and end < len(plain):
66
- cut_position = end
67
-
68
- result.append(plain[start:cut_position])
69
- start = cut_position
70
-
71
- return result
83
+
84
+ result.append(plain[start:cut_position])
85
+ start = cut_position
86
+
87
+ return result
72
88
 
73
89
  async def send(self, message: MessageChain):
74
90
  message_obj = self.message_obj
75
91
 
76
- for comp in message.chain:
77
- if isinstance(comp, Plain):
78
- # Split long text messages if needed
79
- plain_chunks = await self.split_plain(comp.text)
80
- for chunk in plain_chunks:
81
- self.client.message.send_text(
82
- message_obj.self_id, message_obj.session_id, chunk
83
- )
84
- await asyncio.sleep(0.5) # Avoid sending too fast
85
- elif isinstance(comp, Image):
86
- img_path = await comp.convert_to_file_path()
87
-
88
- with open(img_path, "rb") as f:
89
- try:
90
- response = self.client.media.upload("image", f)
91
- except Exception as e:
92
- logger.error(f"企业微信上传图片失败: {e}")
93
- await self.send(
94
- MessageChain().message(f"企业微信上传图片失败: {e}")
92
+ is_wechat_kf = hasattr(self.client, "kf_message")
93
+ if is_wechat_kf:
94
+ # 微信客服
95
+ kf_message_api = getattr(self.client, "kf_message", None)
96
+ if not kf_message_api:
97
+ logger.warning("未找到微信客服发送消息方法。")
98
+ return
99
+ assert isinstance(kf_message_api, WeChatKFMessage)
100
+ user_id = self.get_sender_id()
101
+ for comp in message.chain:
102
+ if isinstance(comp, Plain):
103
+ # Split long text messages if needed
104
+ plain_chunks = await self.split_plain(comp.text)
105
+ for chunk in plain_chunks:
106
+ kf_message_api.send_text(user_id, self.get_self_id(), chunk)
107
+ await asyncio.sleep(0.5) # Avoid sending too fast
108
+ elif isinstance(comp, Image):
109
+ img_path = await comp.convert_to_file_path()
110
+
111
+ with open(img_path, "rb") as f:
112
+ try:
113
+ response = self.client.media.upload("image", f)
114
+ except Exception as e:
115
+ logger.error(f"微信客服上传图片失败: {e}")
116
+ await self.send(
117
+ MessageChain().message(f"微信客服上传图片失败: {e}"),
118
+ )
119
+ return
120
+ logger.debug(f"微信客服上传图片返回: {response}")
121
+ kf_message_api.send_image(
122
+ user_id,
123
+ self.get_self_id(),
124
+ response["media_id"],
95
125
  )
96
- return
97
- logger.info(f"企业微信上传图片返回: {response}")
98
- self.client.message.send_image(
99
- message_obj.self_id,
100
- message_obj.session_id,
101
- response["media_id"],
126
+ elif isinstance(comp, Record):
127
+ record_path = await comp.convert_to_file_path()
128
+ # 转成amr
129
+ temp_dir = os.path.join(get_astrbot_data_path(), "temp")
130
+ record_path_amr = os.path.join(temp_dir, f"{uuid.uuid4()}.amr")
131
+ pydub.AudioSegment.from_wav(record_path).export(
132
+ record_path_amr,
133
+ format="amr",
102
134
  )
103
- elif isinstance(comp, Record):
104
- record_path = await comp.convert_to_file_path()
105
- # 转成amr
106
- record_path_amr = f"data/temp/{uuid.uuid4()}.amr"
107
- pydub.AudioSegment.from_wav(record_path).export(
108
- record_path_amr, format="amr"
109
- )
110
-
111
- with open(record_path_amr, "rb") as f:
112
- try:
113
- response = self.client.media.upload("voice", f)
114
- except Exception as e:
115
- logger.error(f"企业微信上传语音失败: {e}")
116
- await self.send(
117
- MessageChain().message(f"企业微信上传语音失败: {e}")
135
+
136
+ with open(record_path_amr, "rb") as f:
137
+ try:
138
+ response = self.client.media.upload("voice", f)
139
+ except Exception as e:
140
+ logger.error(f"微信客服上传语音失败: {e}")
141
+ await self.send(
142
+ MessageChain().message(f"微信客服上传语音失败: {e}"),
143
+ )
144
+ return
145
+ logger.info(f"微信客服上传语音返回: {response}")
146
+ kf_message_api.send_voice(
147
+ user_id,
148
+ self.get_self_id(),
149
+ response["media_id"],
118
150
  )
119
- return
120
- logger.info(f"企业微信上传语音返回: {response}")
121
- self.client.message.send_voice(
122
- message_obj.self_id,
123
- message_obj.session_id,
124
- response["media_id"],
151
+ else:
152
+ logger.warning(f"还没实现这个消息类型的发送逻辑: {comp.type}")
153
+ else:
154
+ # 企业微信应用
155
+ for comp in message.chain:
156
+ if isinstance(comp, Plain):
157
+ # Split long text messages if needed
158
+ plain_chunks = await self.split_plain(comp.text)
159
+ for chunk in plain_chunks:
160
+ self.client.message.send_text(
161
+ message_obj.self_id,
162
+ message_obj.session_id,
163
+ chunk,
164
+ )
165
+ await asyncio.sleep(0.5) # Avoid sending too fast
166
+ elif isinstance(comp, Image):
167
+ img_path = await comp.convert_to_file_path()
168
+
169
+ with open(img_path, "rb") as f:
170
+ try:
171
+ response = self.client.media.upload("image", f)
172
+ except Exception as e:
173
+ logger.error(f"企业微信上传图片失败: {e}")
174
+ await self.send(
175
+ MessageChain().message(f"企业微信上传图片失败: {e}"),
176
+ )
177
+ return
178
+ logger.debug(f"企业微信上传图片返回: {response}")
179
+ self.client.message.send_image(
180
+ message_obj.self_id,
181
+ message_obj.session_id,
182
+ response["media_id"],
183
+ )
184
+ elif isinstance(comp, Record):
185
+ record_path = await comp.convert_to_file_path()
186
+ # 转成amr
187
+ temp_dir = os.path.join(get_astrbot_data_path(), "temp")
188
+ record_path_amr = os.path.join(temp_dir, f"{uuid.uuid4()}.amr")
189
+ pydub.AudioSegment.from_wav(record_path).export(
190
+ record_path_amr,
191
+ format="amr",
125
192
  )
126
193
 
194
+ with open(record_path_amr, "rb") as f:
195
+ try:
196
+ response = self.client.media.upload("voice", f)
197
+ except Exception as e:
198
+ logger.error(f"企业微信上传语音失败: {e}")
199
+ await self.send(
200
+ MessageChain().message(f"企业微信上传语音失败: {e}"),
201
+ )
202
+ return
203
+ logger.info(f"企业微信上传语音返回: {response}")
204
+ self.client.message.send_voice(
205
+ message_obj.self_id,
206
+ message_obj.session_id,
207
+ response["media_id"],
208
+ )
209
+ else:
210
+ logger.warning(f"还没实现这个消息类型的发送逻辑: {comp.type}。")
211
+
127
212
  await super().send(message)
128
213
 
129
214
  async def send_streaming(self, generator, use_fallback: bool = False):
@@ -134,7 +219,7 @@ class WecomPlatformEvent(AstrMessageEvent):
134
219
  else:
135
220
  buffer.chain.extend(chain.chain)
136
221
  if not buffer:
137
- return
222
+ return None
138
223
  buffer.squash_plain()
139
224
  await self.send(buffer)
140
225
  return await super().send_streaming(generator, use_fallback)
@@ -0,0 +1,279 @@
1
+ """The MIT License (MIT)
2
+
3
+ Copyright (c) 2014-2020 messense
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+ """
23
+
24
+ from wechatpy.client.api.base import BaseWeChatAPI
25
+
26
+
27
+ class WeChatKF(BaseWeChatAPI):
28
+ """微信客服接口
29
+
30
+ https://work.weixin.qq.com/api/doc/90000/90135/94670
31
+ """
32
+
33
+ def sync_msg(self, token, open_kfid, cursor="", limit=1000):
34
+ """微信客户发送的消息、接待人员在企业微信回复的消息、发送消息接口发送失败事件(如被用户拒收)
35
+ 、客户点击菜单消息的回复消息,可以通过该接口获取具体的消息内容和事件。不支持读取通过发送消息接口发送的消息。
36
+ 支持的消息类型:文本、图片、语音、视频、文件、位置、链接、名片、小程序、事件。
37
+
38
+
39
+ :param token: 回调事件返回的token字段,10分钟内有效;可不填,如果不填接口有严格的频率限制。不多于128字节
40
+ :param open_kfid: 客服帐号ID
41
+ :param cursor: 上一次调用时返回的next_cursor,第一次拉取可以不填。不多于64字节
42
+ :param limit: 期望请求的数据量,默认值和最大值都为1000。
43
+ 注意:可能会出现返回条数少于limit的情况,需结合返回的has_more字段判断是否继续请求。
44
+ :return: 接口调用结果
45
+ """
46
+ data = {
47
+ "token": token,
48
+ "cursor": cursor,
49
+ "limit": limit,
50
+ "open_kfid": open_kfid,
51
+ }
52
+ return self._post("kf/sync_msg", data=data)
53
+
54
+ def get_service_state(self, open_kfid, external_userid):
55
+ """获取会话状态
56
+
57
+ ID 状态 说明
58
+ 0 未处理 新会话接入。可选择:1.直接用API自动回复消息。2.放进待接入池等待接待人员接待。3.指定接待人员进行接待
59
+ 1 由智能助手接待 可使用API回复消息。可选择转入待接入池或者指定接待人员处理。
60
+ 2 待接入池排队中 在待接入池中排队等待接待人员接入。可选择转为指定人员接待
61
+ 3 由人工接待 人工接待中。可选择结束会话
62
+ 4 已结束 会话已经结束。不允许变更会话状态,等待用户重新发起咨询
63
+
64
+ :param open_kfid: 客服帐号ID
65
+ :param external_userid: 微信客户的external_userid
66
+ :return: 接口调用结果
67
+ """
68
+ data = {
69
+ "open_kfid": open_kfid,
70
+ "external_userid": external_userid,
71
+ }
72
+ return self._post("kf/service_state/get", data=data)
73
+
74
+ def trans_service_state(
75
+ self,
76
+ open_kfid,
77
+ external_userid,
78
+ service_state,
79
+ servicer_userid="",
80
+ ):
81
+ """变更会话状态
82
+
83
+ :param open_kfid: 客服帐号ID
84
+ :param external_userid: 微信客户的external_userid
85
+ :param service_state: 当前的会话状态,状态定义参考概述中的表格
86
+ :return: 接口调用结果
87
+ """
88
+ data = {
89
+ "open_kfid": open_kfid,
90
+ "external_userid": external_userid,
91
+ "service_state": service_state,
92
+ }
93
+ if servicer_userid:
94
+ data["servicer_userid"] = servicer_userid
95
+ return self._post("kf/service_state/trans", data=data)
96
+
97
+ def get_servicer_list(self, open_kfid):
98
+ """获取接待人员列表
99
+
100
+ :param open_kfid: 客服帐号ID
101
+ :return: 接口调用结果
102
+ """
103
+ data = {
104
+ "open_kfid": open_kfid,
105
+ }
106
+ return self._get("kf/servicer/list", params=data)
107
+
108
+ def add_servicer(self, open_kfid, userid_list):
109
+ """添加接待人员
110
+ 添加指定客服帐号的接待人员。
111
+
112
+ :param open_kfid: 客服帐号ID
113
+ :param userid_list: 接待人员userid列表
114
+ :return: 接口调用结果
115
+ """
116
+ if not isinstance(userid_list, list):
117
+ userid_list = [userid_list]
118
+
119
+ data = {
120
+ "open_kfid": open_kfid,
121
+ "userid_list": userid_list,
122
+ }
123
+ return self._post("kf/servicer/add", data=data)
124
+
125
+ def del_servicer(self, open_kfid, userid_list):
126
+ """删除接待人员
127
+ 从客服帐号删除接待人员
128
+
129
+ :param open_kfid: 客服帐号ID
130
+ :param userid_list: 接待人员userid列表
131
+ :return: 接口调用结果
132
+ """
133
+ if not isinstance(userid_list, list):
134
+ userid_list = [userid_list]
135
+
136
+ data = {
137
+ "open_kfid": open_kfid,
138
+ "userid_list": userid_list,
139
+ }
140
+ return self._post("kf/servicer/del", data=data)
141
+
142
+ def batchget_customer(self, external_userid_list):
143
+ """客户基本信息获取
144
+
145
+ :param external_userid_list: external_userid列表
146
+ :return: 接口调用结果
147
+ """
148
+ if not isinstance(external_userid_list, list):
149
+ external_userid_list = [external_userid_list]
150
+
151
+ data = {
152
+ "external_userid_list": external_userid_list,
153
+ }
154
+ return self._post("kf/customer/batchget", data=data)
155
+
156
+ def get_account_list(self):
157
+ """获取客服帐号列表
158
+
159
+ :return: 接口调用结果
160
+ """
161
+ return self._get("kf/account/list")
162
+
163
+ def add_contact_way(self, open_kfid, scene):
164
+ """获取客服帐号链接
165
+
166
+ :param open_kfid: 客服帐号ID
167
+ :param scene: 场景值,字符串类型,由开发者自定义。不多于32字节;字符串取值范围(正则表达式):[0-9a-zA-Z_-]*
168
+ :return: 接口调用结果
169
+ """
170
+ data = {"open_kfid": open_kfid, "scene": scene}
171
+ return self._post("kf/add_contact_way", data=data)
172
+
173
+ def get_upgrade_service_config(self):
174
+ """获取配置的专员与客户群
175
+
176
+ :return: 接口调用结果
177
+ """
178
+ return self._get("kf/customer/get_upgrade_service_config")
179
+
180
+ def upgrade_service(
181
+ self,
182
+ open_kfid,
183
+ external_userid,
184
+ service_type,
185
+ member=None,
186
+ groupchat=None,
187
+ ):
188
+ """为客户升级为专员或客户群服务
189
+
190
+ :param open_kfid: 客服帐号ID
191
+ :param external_userid: 微信客户的external_userid
192
+ :param service_type: 表示是升级到专员服务还是客户群服务。1:专员服务。2:客户群服务
193
+ :param member: 推荐的服务专员,type等于1时有效
194
+ :param groupchat: 推荐的客户群,type等于2时有效
195
+ :return: 接口调用结果
196
+ """
197
+ data = {
198
+ "open_kfid": open_kfid,
199
+ "external_userid": external_userid,
200
+ "type": service_type,
201
+ }
202
+ if service_type == 1:
203
+ data["member"] = member
204
+ else:
205
+ data["groupchat"] = groupchat
206
+ return self._post("kf/customer/upgrade_service", data=data)
207
+
208
+ def cancel_upgrade_service(self, open_kfid, external_userid):
209
+ """为客户取消推荐
210
+
211
+ :param open_kfid: 客服帐号ID
212
+ :param external_userid: 微信客户的external_userid
213
+ :return: 接口调用结果
214
+ """
215
+ data = {"open_kfid": open_kfid, "external_userid": external_userid}
216
+ return self._post("kf/customer/cancel_upgrade_service", data=data)
217
+
218
+ def send_msg_on_event(self, code, msgtype, msg_content, msgid=None):
219
+ """当特定的事件回调消息包含code字段,可以此code为凭证,调用该接口给用户发送相应事件场景下的消息,如客服欢迎语。
220
+ 支持发送消息类型:文本、菜单消息。
221
+
222
+ :param code: 事件响应消息对应的code。通过事件回调下发,仅可使用一次。
223
+ :param msgtype: 消息类型。对不同的msgtype,有相应的结构描述,详见消息类型
224
+ :param msg_content: 目前支持文本与菜单消息,具体查看文档
225
+ :param msgid: 消息ID。如果请求参数指定了msgid,则原样返回,否则系统自动生成并返回。不多于32字节;
226
+ 字符串取值范围(正则表达式):[0-9a-zA-Z_-]*
227
+ :return: 接口调用结果
228
+ """
229
+ data = {"code": code, "msgtype": msgtype}
230
+ if msgid:
231
+ data["msgid"] = msgid
232
+ data.update(msg_content)
233
+ return self._post("kf/send_msg_on_event", data=data)
234
+
235
+ def get_corp_statistic(self, start_time, end_time, open_kfid=None):
236
+ """获取「客户数据统计」企业汇总数据
237
+
238
+ :param start_time: 开始时间
239
+ :param end_time: 结束时间
240
+ :param open_kfid: 客服帐号ID
241
+ :return: 接口调用结果
242
+ """
243
+ data = {"open_kfid": open_kfid, "start_time": start_time, "end_time": end_time}
244
+ return self._post("kf/get_corp_statistic", data=data)
245
+
246
+ def get_servicer_statistic(
247
+ self,
248
+ start_time,
249
+ end_time,
250
+ open_kfid=None,
251
+ servicer_userid=None,
252
+ ):
253
+ """获取「客户数据统计」接待人员明细数据
254
+
255
+ :param start_time: 开始时间
256
+ :param end_time: 结束时间
257
+ :param open_kfid: 客服帐号ID
258
+ :param servicer_userid: 接待人员
259
+ :return: 接口调用结果
260
+ """
261
+ data = {
262
+ "open_kfid": open_kfid,
263
+ "servicer_userid": servicer_userid,
264
+ "start_time": start_time,
265
+ "end_time": end_time,
266
+ }
267
+ return self._post("kf/get_servicer_statistic", data=data)
268
+
269
+ def account_update(self, open_kfid, name, media_id):
270
+ """修改客服账号
271
+
272
+ :param open_kfid: 客服帐号ID
273
+ :param name: 客服名称
274
+ :param media_id: 客服头像临时素材
275
+
276
+ :return: 接口调用结果
277
+ """
278
+ data = {"open_kfid": open_kfid, "name": name, "media_id": media_id}
279
+ return self._post("kf/account/update", data=data)