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,55 +0,0 @@
1
- from astrbot import logger
2
- import aiohttp
3
- import json
4
-
5
-
6
- class GeweDownloader:
7
- def __init__(self, base_url: str, download_base_url: str, token: str):
8
- self.base_url = base_url
9
- self.download_base_url = download_base_url
10
- self.headers = {"Content-Type": "application/json", "X-GEWE-TOKEN": token}
11
-
12
- async def _post_json(self, baseurl: str, route: str, payload: dict):
13
- async with aiohttp.ClientSession() as session:
14
- async with session.post(
15
- f"{baseurl}{route}", headers=self.headers, json=payload
16
- ) as resp:
17
- return await resp.read()
18
-
19
- async def download_voice(self, appid: str, xml: str, msg_id: str):
20
- payload = {"appId": appid, "xml": xml, "msgId": msg_id}
21
- return await self._post_json(self.base_url, "/message/downloadVoice", payload)
22
-
23
- async def download_image(self, appid: str, xml: str) -> str:
24
- """返回一个可下载的 URL"""
25
- choices = [2, 3] # 2:常规图片 3:缩略图
26
-
27
- for choice in choices:
28
- try:
29
- payload = {"appId": appid, "xml": xml, "type": choice}
30
- data = await self._post_json(
31
- self.base_url, "/message/downloadImage", payload
32
- )
33
- json_blob = json.loads(data)
34
- if "fileUrl" in json_blob["data"]:
35
- return self.download_base_url + json_blob["data"]["fileUrl"]
36
-
37
- except BaseException as e:
38
- logger.error(f"gewe download image: {e}")
39
- continue
40
-
41
- raise Exception("无法下载图片")
42
-
43
- async def download_emoji_md5(self, app_id, emoji_md5):
44
- """下载emoji"""
45
- try:
46
- payload = {"appId": app_id, "emojiMd5": emoji_md5}
47
-
48
- # gewe 计划中的接口,暂时没有实现。返回代码404
49
- data = await self._post_json(
50
- self.base_url, "/message/downloadEmojiMd5", payload
51
- )
52
- json_blob = json.loads(data)
53
- return json_blob
54
- except BaseException as e:
55
- logger.error(f"gewe download emoji: {e}")
@@ -1,255 +0,0 @@
1
- import asyncio
2
- import re
3
- import wave
4
- import uuid
5
- import traceback
6
- import os
7
-
8
- from typing import AsyncGenerator
9
- from astrbot.core.utils.io import save_temp_img, download_file
10
- from astrbot.core.utils.tencent_record_helper import wav_to_tencent_silk
11
- from astrbot.api import logger
12
- from astrbot.api.event import AstrMessageEvent, MessageChain
13
- from astrbot.api.platform import AstrBotMessage, PlatformMetadata, Group, MessageMember
14
- from astrbot.api.message_components import (
15
- Plain,
16
- Image,
17
- Record,
18
- At,
19
- File,
20
- Video,
21
- WechatEmoji as Emoji,
22
- )
23
- from .client import SimpleGewechatClient
24
-
25
-
26
- def get_wav_duration(file_path):
27
- with wave.open(file_path, "rb") as wav_file:
28
- file_size = os.path.getsize(file_path)
29
- n_channels, sampwidth, framerate, n_frames = wav_file.getparams()[:4]
30
- if n_frames == 2147483647:
31
- duration = (file_size - 44) / (n_channels * sampwidth * framerate)
32
- elif n_frames == 0:
33
- duration = (file_size - 44) / (n_channels * sampwidth * framerate)
34
- else:
35
- duration = n_frames / float(framerate)
36
- return duration
37
-
38
-
39
- class GewechatPlatformEvent(AstrMessageEvent):
40
- def __init__(
41
- self,
42
- message_str: str,
43
- message_obj: AstrBotMessage,
44
- platform_meta: PlatformMetadata,
45
- session_id: str,
46
- client: SimpleGewechatClient,
47
- ):
48
- super().__init__(message_str, message_obj, platform_meta, session_id)
49
- self.client = client
50
-
51
- @staticmethod
52
- async def send_with_client(
53
- message: MessageChain, to_wxid: str, client: SimpleGewechatClient
54
- ):
55
- if not to_wxid:
56
- logger.error("无法获取到 to_wxid。")
57
- return
58
-
59
- # 检查@
60
- ats = []
61
- ats_names = []
62
- for comp in message.chain:
63
- if isinstance(comp, At):
64
- ats.append(comp.qq)
65
- ats_names.append(comp.name)
66
- has_at = False
67
-
68
- for comp in message.chain:
69
- if isinstance(comp, Plain):
70
- text = comp.text
71
- payload = {
72
- "to_wxid": to_wxid,
73
- "content": text,
74
- }
75
- if not has_at and ats:
76
- ats = f"{','.join(ats)}"
77
- ats_names = f"@{' @'.join(ats_names)}"
78
- text = f"{ats_names} {text}"
79
- payload["content"] = text
80
- payload["ats"] = ats
81
- has_at = True
82
- await client.post_text(**payload)
83
-
84
- elif isinstance(comp, Image):
85
- img_path = await comp.convert_to_file_path()
86
- # 为了安全,向 AstrBot 回调服务注册可被 gewechat 访问的文件,并获得文件 token
87
- token = await client._register_file(img_path)
88
- img_url = f"{client.file_server_url}/{token}"
89
- logger.debug(f"gewe callback img url: {img_url}")
90
- await client.post_image(to_wxid, img_url)
91
- elif isinstance(comp, Video):
92
- if comp.cover != "":
93
- await client.forward_video(to_wxid, comp.cover)
94
- else:
95
- try:
96
- from pyffmpeg import FFmpeg
97
- except (ImportError, ModuleNotFoundError):
98
- logger.error(
99
- "需要安装 pyffmpeg 库才能发送视频: pip install pyffmpeg"
100
- )
101
- raise ModuleNotFoundError(
102
- "需要安装 pyffmpeg 库才能发送视频: pip install pyffmpeg"
103
- )
104
-
105
- video_url = comp.file
106
- # 根据 url 下载视频
107
- if video_url.startswith("http"):
108
- video_filename = f"{uuid.uuid4()}.mp4"
109
- video_path = f"data/temp/{video_filename}"
110
- await download_file(video_url, video_path)
111
- else:
112
- video_path = video_url
113
-
114
- video_token = await client._register_file(video_path)
115
- video_callback_url = f"{client.file_server_url}/{video_token}"
116
-
117
- # 获取视频第一帧
118
- thumb_path = f"data/temp/gewechat_video_thumb_{uuid.uuid4()}.jpg"
119
-
120
- video_path = video_path.replace(" ", "\\ ")
121
- try:
122
- ff = FFmpeg()
123
- command = f"-i {video_path} -ss 0 -vframes 1 {thumb_path}"
124
- ff.options(command)
125
- thumb_token = await client._register_file(thumb_path)
126
- thumb_url = f"{client.file_server_url}/{thumb_token}"
127
- except Exception as e:
128
- logger.error(f"获取视频第一帧失败: {e}")
129
-
130
- # 获取视频时长
131
- try:
132
- from pyffmpeg import FFprobe
133
-
134
- # 创建 FFprobe 实例
135
- ffprobe = FFprobe(video_url)
136
- # 获取时长字符串
137
- duration_str = ffprobe.duration
138
- # 处理时长字符串
139
- video_duration = float(duration_str.replace(":", ""))
140
- except Exception as e:
141
- logger.error(f"获取时长失败: {e}")
142
- video_duration = 10
143
-
144
- # 发送视频
145
- await client.post_video(
146
- to_wxid, video_callback_url, thumb_url, video_duration
147
- )
148
-
149
- # 删除临时缩略图文件
150
- if os.path.exists(thumb_path):
151
- os.remove(thumb_path)
152
- elif isinstance(comp, Record):
153
- # 默认已经存在 data/temp 中
154
- record_url = comp.file
155
- record_path = await comp.convert_to_file_path()
156
-
157
- silk_path = f"data/temp/{uuid.uuid4()}.silk"
158
- try:
159
- duration = await wav_to_tencent_silk(record_path, silk_path)
160
- except Exception as e:
161
- logger.error(traceback.format_exc())
162
- await client.post_text(to_wxid, f"语音文件转换失败。{str(e)}")
163
- logger.info("Silk 语音文件格式转换至: " + record_path)
164
- if duration == 0:
165
- duration = get_wav_duration(record_path)
166
- token = await client._register_file(silk_path)
167
- record_url = f"{client.file_server_url}/{token}"
168
- logger.debug(f"gewe callback record url: {record_url}")
169
- await client.post_voice(to_wxid, record_url, duration * 1000)
170
- elif isinstance(comp, File):
171
- file_path = comp.file
172
- file_name = comp.name
173
- if file_path.startswith("file:///"):
174
- file_path = file_path[8:]
175
- elif file_path.startswith("http"):
176
- await download_file(file_path, f"data/temp/{file_name}")
177
- else:
178
- file_path = file_path
179
-
180
- token = await client._register_file(file_path)
181
- file_url = f"{client.file_server_url}/{token}"
182
- logger.debug(f"gewe callback file url: {file_url}")
183
- await client.post_file(to_wxid, file_url, file_name)
184
- elif isinstance(comp, Emoji):
185
- await client.post_emoji(to_wxid, comp.md5, comp.md5_len, comp.cdnurl)
186
- elif isinstance(comp, At):
187
- pass
188
- else:
189
- logger.debug(f"gewechat 忽略: {comp.type}")
190
-
191
- async def send(self, message: MessageChain):
192
- to_wxid = self.message_obj.raw_message.get("to_wxid", None)
193
- await GewechatPlatformEvent.send_with_client(message, to_wxid, self.client)
194
- await super().send(message)
195
-
196
- async def get_group(self, group_id=None, **kwargs):
197
- # 确定有效的 group_id
198
- if group_id is None:
199
- group_id = self.get_group_id()
200
-
201
- if not group_id:
202
- return None
203
-
204
- res = await self.client.get_group(group_id)
205
- data: dict = res["data"]
206
-
207
- if not data["chatroomId"]:
208
- return None
209
-
210
- members = [
211
- MessageMember(user_id=member["wxid"], nickname=member["nickName"])
212
- for member in data.get("memberList", [])
213
- ]
214
-
215
- return Group(
216
- group_id=data["chatroomId"],
217
- group_name=data.get("nickName"),
218
- group_avatar=data.get("smallHeadImgUrl"),
219
- group_owner=data.get("chatRoomOwner"),
220
- members=members,
221
- )
222
-
223
- async def send_streaming(
224
- self, generator: AsyncGenerator, use_fallback: bool = False
225
- ):
226
- if not use_fallback:
227
- buffer = None
228
- async for chain in generator:
229
- if not buffer:
230
- buffer = chain
231
- else:
232
- buffer.chain.extend(chain.chain)
233
- if not buffer:
234
- return
235
- buffer.squash_plain()
236
- await self.send(buffer)
237
- return await super().send_streaming(generator, use_fallback)
238
-
239
- buffer = ""
240
- pattern = re.compile(r"[^。?!~…]+[。?!~…]+")
241
-
242
- async for chain in generator:
243
- if isinstance(chain, MessageChain):
244
- for comp in chain.chain:
245
- if isinstance(comp, Plain):
246
- buffer += comp.text
247
- if any(p in buffer for p in "。?!~…"):
248
- buffer = await self.process_buffer(buffer, pattern)
249
- else:
250
- await self.send(MessageChain(chain=[comp]))
251
- await asyncio.sleep(1.5) # 限速
252
-
253
- if buffer.strip():
254
- await self.send(MessageChain([Plain(buffer)]))
255
- return await super().send_streaming(generator, use_fallback)
@@ -1,103 +0,0 @@
1
- import sys
2
- import asyncio
3
- import os
4
-
5
- from astrbot.api.platform import Platform, AstrBotMessage, MessageType, PlatformMetadata
6
- from astrbot.api.event import MessageChain
7
- from astrbot.core.platform.astr_message_event import MessageSesion
8
- from ...register import register_platform_adapter
9
- from .gewechat_event import GewechatPlatformEvent
10
- from .client import SimpleGewechatClient
11
- from astrbot import logger
12
-
13
- if sys.version_info >= (3, 12):
14
- from typing import override
15
- else:
16
- from typing_extensions import override
17
-
18
-
19
- @register_platform_adapter("gewechat", "基于 gewechat 的 Wechat 适配器")
20
- class GewechatPlatformAdapter(Platform):
21
- def __init__(
22
- self, platform_config: dict, platform_settings: dict, event_queue: asyncio.Queue
23
- ) -> None:
24
- super().__init__(event_queue)
25
- self.config = platform_config
26
- self.settingss = platform_settings
27
- self.test_mode = os.environ.get("TEST_MODE", "off") == "on"
28
- self.client = None
29
-
30
- self.client = SimpleGewechatClient(
31
- self.config["base_url"],
32
- self.config["nickname"],
33
- self.config["host"],
34
- self.config["port"],
35
- self._event_queue,
36
- )
37
-
38
- async def on_event_received(abm: AstrBotMessage):
39
- await self.handle_msg(abm)
40
-
41
- self.client.on_event_received = on_event_received
42
-
43
- @override
44
- async def send_by_session(
45
- self, session: MessageSesion, message_chain: MessageChain
46
- ):
47
- session_id = session.session_id
48
- if "#" in session_id:
49
- # unique session
50
- to_wxid = session_id.split("#")[1]
51
- else:
52
- to_wxid = session_id
53
-
54
- await GewechatPlatformEvent.send_with_client(
55
- message_chain, to_wxid, self.client
56
- )
57
-
58
- await super().send_by_session(session, message_chain)
59
-
60
- @override
61
- def meta(self) -> PlatformMetadata:
62
- return PlatformMetadata(
63
- name="gewechat",
64
- description="基于 gewechat 的 Wechat 适配器",
65
- id=self.config.get("id"),
66
- )
67
-
68
- async def terminate(self):
69
- self.client.shutdown_event.set()
70
- try:
71
- await self.client.server.shutdown()
72
- except Exception as _:
73
- pass
74
- logger.info("Gewechat 适配器已被优雅地关闭。")
75
-
76
- async def logout(self):
77
- await self.client.logout()
78
-
79
- @override
80
- def run(self):
81
- return self._run()
82
-
83
- async def _run(self):
84
- await self.client.login()
85
- await self.client.start_polling()
86
-
87
- async def handle_msg(self, message: AstrBotMessage):
88
- if message.type == MessageType.GROUP_MESSAGE:
89
- if self.settingss["unique_session"]:
90
- message.session_id = message.sender.user_id + "#" + message.group_id
91
-
92
- message_event = GewechatPlatformEvent(
93
- message_str=message.message_str,
94
- message_obj=message,
95
- platform_meta=self.meta(),
96
- session_id=message.session_id,
97
- client=self.client,
98
- )
99
-
100
- self.commit_event(message_event)
101
-
102
- def get_client(self) -> SimpleGewechatClient:
103
- return self.client
@@ -1,110 +0,0 @@
1
- from defusedxml import ElementTree as eT
2
- from astrbot.api import logger
3
- from astrbot.api.message_components import (
4
- WechatEmoji as Emoji,
5
- Reply,
6
- Plain,
7
- BaseMessageComponent,
8
- )
9
-
10
-
11
- class GeweDataParser:
12
- def __init__(self, data, is_private_chat):
13
- self.data = data
14
- self.is_private_chat = is_private_chat
15
-
16
- def _format_to_xml(self):
17
- return eT.fromstring(self.data)
18
-
19
- def parse_mutil_49(self) -> list[BaseMessageComponent] | None:
20
- appmsg_type = self._format_to_xml().find(".//appmsg/type")
21
- if appmsg_type is None:
22
- return
23
-
24
- match appmsg_type.text:
25
- case "57":
26
- return self.parse_reply()
27
-
28
- def parse_emoji(self) -> Emoji | None:
29
- try:
30
- emoji_element = self._format_to_xml().find(".//emoji")
31
- # 提取 md5 和 len 属性
32
- if emoji_element is not None:
33
- md5_value = emoji_element.get("md5")
34
- emoji_size = emoji_element.get("len")
35
- cdnurl = emoji_element.get("cdnurl")
36
-
37
- return Emoji(md5=md5_value, md5_len=emoji_size, cdnurl=cdnurl)
38
-
39
- except Exception as e:
40
- logger.error(f"gewechat: parse_emoji failed, {e}")
41
-
42
- def parse_reply(self) -> list[Reply, Plain] | None:
43
- """解析引用消息
44
-
45
- Returns:
46
- list[Reply, Plain]: 一个包含两个元素的列表。Reply 消息对象和引用者说的文本内容。微信平台下引用消息时只能发送文本消息。
47
- """
48
- try:
49
- replied_id = -1
50
- replied_uid = 0
51
- replied_nickname = ""
52
- replied_content = "" # 被引用者说的内容
53
- content = "" # 引用者说的内容
54
-
55
- root = self._format_to_xml()
56
- refermsg = root.find(".//refermsg")
57
- if refermsg is not None:
58
- # 被引用的信息
59
- svrid = refermsg.find("svrid")
60
- fromusr = refermsg.find("fromusr")
61
- displayname = refermsg.find("displayname")
62
- refermsg_content = refermsg.find("content")
63
- if svrid is not None:
64
- replied_id = svrid.text
65
- if fromusr is not None:
66
- replied_uid = fromusr.text
67
- if displayname is not None:
68
- replied_nickname = displayname.text
69
- if refermsg_content is not None:
70
- # 处理引用嵌套,包括嵌套公众号消息
71
- if refermsg_content.text.startswith(
72
- "<msg>"
73
- ) or refermsg_content.text.startswith("<?xml"):
74
- try:
75
- logger.debug("gewechat: Reference message is nested")
76
- refer_root = eT.fromstring(refermsg_content.text)
77
- img = refer_root.find("img")
78
- if img is not None:
79
- replied_content = "[图片]"
80
- else:
81
- app_msg = refer_root.find("appmsg")
82
- refermsg_content_title = app_msg.find("title")
83
- logger.debug(
84
- f"gewechat: Reference message nesting: {refermsg_content_title.text}"
85
- )
86
- replied_content = refermsg_content_title.text
87
- except Exception as e:
88
- logger.error(f"gewechat: nested failed, {e}")
89
- # 处理异常情况
90
- replied_content = refermsg_content.text
91
- else:
92
- replied_content = refermsg_content.text
93
-
94
- # 提取引用者说的内容
95
- title = root.find(".//appmsg/title")
96
- if title is not None:
97
- content = title.text
98
-
99
- reply_seg = Reply(
100
- id=replied_id,
101
- chain=[Plain(replied_content)],
102
- sender_id=replied_uid,
103
- sender_nickname=replied_nickname,
104
- message_str=replied_content,
105
- )
106
- plain_seg = Plain(content)
107
- return [reply_seg, plain_seg]
108
-
109
- except Exception as e:
110
- logger.error(f"gewechat: parse_reply failed, {e}")