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,806 +0,0 @@
1
- import asyncio
2
- import base64
3
- import datetime
4
- import os
5
- import re
6
- import uuid
7
- import threading
8
-
9
- import aiohttp
10
- import anyio
11
- import quart
12
-
13
- from astrbot.api import logger, sp
14
- from astrbot.api.message_components import Plain, Image, At, Record, Video
15
- from astrbot.api.platform import AstrBotMessage, MessageMember, MessageType
16
- from astrbot.core.utils.io import download_image_by_url
17
- from .downloader import GeweDownloader
18
-
19
- try:
20
- from .xml_data_parser import GeweDataParser
21
- except (ImportError, ModuleNotFoundError) as e:
22
- logger.warning(
23
- f"警告: 可能未安装 defusedxml 依赖库,将导致无法解析微信的 表情包、引用 类型的消息: {str(e)}"
24
- )
25
-
26
-
27
- class SimpleGewechatClient:
28
- """针对 Gewechat 的简单实现。
29
-
30
- @author: Soulter
31
- @website: https://github.com/Soulter
32
- """
33
-
34
- def __init__(
35
- self,
36
- base_url: str,
37
- nickname: str,
38
- host: str,
39
- port: int,
40
- event_queue: asyncio.Queue,
41
- ):
42
- self.base_url = base_url
43
- if self.base_url.endswith("/"):
44
- self.base_url = self.base_url[:-1]
45
-
46
- self.download_base_url = self.base_url.split(":")[:-1] # 去掉端口
47
- self.download_base_url = ":".join(self.download_base_url) + ":2532/download/"
48
-
49
- self.base_url += "/v2/api"
50
-
51
- logger.info(f"Gewechat API: {self.base_url}")
52
- logger.info(f"Gewechat 下载 API: {self.download_base_url}")
53
-
54
- if isinstance(port, str):
55
- port = int(port)
56
-
57
- self.token = None
58
- self.headers = {}
59
- self.nickname = nickname
60
- self.appid = sp.get(f"gewechat-appid-{nickname}", "")
61
-
62
- self.server = quart.Quart(__name__)
63
- self.server.add_url_rule(
64
- "/astrbot-gewechat/callback", view_func=self._callback, methods=["POST"]
65
- )
66
- self.server.add_url_rule(
67
- "/astrbot-gewechat/file/<file_token>",
68
- view_func=self._handle_file,
69
- methods=["GET"],
70
- )
71
-
72
- self.host = host
73
- self.port = port
74
- self.callback_url = f"http://{self.host}:{self.port}/astrbot-gewechat/callback"
75
- self.file_server_url = f"http://{self.host}:{self.port}/astrbot-gewechat/file"
76
-
77
- self.event_queue = event_queue
78
-
79
- self.multimedia_downloader = None
80
-
81
- self.userrealnames = {}
82
-
83
- self.shutdown_event = asyncio.Event()
84
-
85
- self.staged_files = {}
86
- """存储了允许外部访问的文件列表。auth_token: file_path。通过 register_file 方法注册。"""
87
-
88
- self.lock = asyncio.Lock()
89
-
90
- async def get_token_id(self):
91
- """获取 Gewechat Token。"""
92
- async with aiohttp.ClientSession() as session:
93
- async with session.post(f"{self.base_url}/tools/getTokenId") as resp:
94
- json_blob = await resp.json()
95
- self.token = json_blob["data"]
96
- logger.info(f"获取到 Gewechat Token: {self.token}")
97
- self.headers = {"X-GEWE-TOKEN": self.token}
98
-
99
- async def _convert(self, data: dict) -> AstrBotMessage:
100
- if "TypeName" in data:
101
- type_name = data["TypeName"]
102
- elif "type_name" in data:
103
- type_name = data["type_name"]
104
- else:
105
- raise Exception("无法识别的消息类型")
106
-
107
- # 以下没有业务处理,只是避免控制台打印太多的日志
108
- if type_name == "ModContacts":
109
- logger.info("gewechat下发:ModContacts消息通知。")
110
- return
111
- if type_name == "DelContacts":
112
- logger.info("gewechat下发:DelContacts消息通知。")
113
- return
114
-
115
- if type_name == "Offline":
116
- logger.critical("收到 gewechat 下线通知。")
117
- return
118
-
119
- d = None
120
- if "Data" in data:
121
- d = data["Data"]
122
- elif "data" in data:
123
- d = data["data"]
124
-
125
- if not d:
126
- logger.warning(f"消息不含 data 字段: {data}")
127
- return
128
-
129
- if "CreateTime" in d:
130
- # 得到系统 UTF+8 的 ts
131
- tz_offset = datetime.timedelta(hours=8)
132
- tz = datetime.timezone(tz_offset)
133
- ts = datetime.datetime.now(tz).timestamp()
134
- create_time = d["CreateTime"]
135
- if create_time < ts - 30:
136
- logger.warning(f"消息时间戳过旧: {create_time},当前时间戳: {ts}")
137
- return
138
-
139
- abm = AstrBotMessage()
140
-
141
- from_user_name = d["FromUserName"]["string"] # 消息来源
142
- d["to_wxid"] = from_user_name # 用于发信息
143
-
144
- abm.message_id = str(d.get("MsgId"))
145
- abm.session_id = from_user_name
146
- abm.self_id = data["Wxid"] # 机器人的 wxid
147
-
148
- user_id = "" # 发送人 wxid
149
- content = d["Content"]["string"] # 消息内容
150
-
151
- at_me = False
152
- at_wxids = []
153
- if "@chatroom" in from_user_name:
154
- abm.type = MessageType.GROUP_MESSAGE
155
- _t = content.split(":\n")
156
- user_id = _t[0]
157
- content = _t[1]
158
- # at
159
- msg_source = d["MsgSource"]
160
- if "\u2005" in content:
161
- # at
162
- # content = content.split('\u2005')[1]
163
- content = re.sub(r"@[^\u2005]*\u2005", "", content)
164
- at_wxids = re.findall(
165
- r"<atuserlist><!\[CDATA\[.*?(?:,|\b)([^,]+?)(?=,|\]\]></atuserlist>)",
166
- msg_source,
167
- )
168
-
169
- abm.group_id = from_user_name
170
-
171
- if (
172
- f"<atuserlist><![CDATA[,{abm.self_id}]]>" in msg_source
173
- or f"<atuserlist><![CDATA[{abm.self_id}]]>" in msg_source
174
- ):
175
- at_me = True
176
- if "在群聊中@了你" in d.get("PushContent", ""):
177
- at_me = True
178
- else:
179
- abm.type = MessageType.FRIEND_MESSAGE
180
- user_id = from_user_name
181
-
182
- # 检查消息是否由自己发送,若是则忽略
183
- # 已经有可配置项专门配置是否需要响应自己的消息,因此这里注释掉。
184
- # if user_id == abm.self_id:
185
- # logger.info("忽略自己发送的消息")
186
- # return None
187
-
188
- abm.message = []
189
-
190
- # 解析用户真实名字
191
- user_real_name = "unknown"
192
- if abm.group_id:
193
- if (
194
- abm.group_id not in self.userrealnames
195
- or user_id not in self.userrealnames[abm.group_id]
196
- ):
197
- # 获取群成员列表,并且缓存
198
- if abm.group_id not in self.userrealnames:
199
- self.userrealnames[abm.group_id] = {}
200
- member_list = await self.get_chatroom_member_list(abm.group_id)
201
- logger.debug(f"获取到 {abm.group_id} 的群成员列表。")
202
- if member_list and "memberList" in member_list:
203
- for member in member_list["memberList"]:
204
- self.userrealnames[abm.group_id][member["wxid"]] = member[
205
- "nickName"
206
- ]
207
- if user_id in self.userrealnames[abm.group_id]:
208
- user_real_name = self.userrealnames[abm.group_id][user_id]
209
- else:
210
- user_real_name = self.userrealnames[abm.group_id][user_id]
211
- else:
212
- try:
213
- info = (await self.get_user_or_group_info(user_id))["data"][0]
214
- user_real_name = info["nickName"]
215
- except Exception as e:
216
- logger.debug(f"获取用户 {user_id} 昵称失败: {e}")
217
- user_real_name = user_id
218
-
219
- if at_me:
220
- abm.message.insert(0, At(qq=abm.self_id, name=self.nickname))
221
- for wxid in at_wxids:
222
- # 群聊里 At 其他人的列表
223
- _username = self.userrealnames.get(abm.group_id, {}).get(wxid, wxid)
224
- abm.message.append(At(qq=wxid, name=_username))
225
-
226
- abm.sender = MessageMember(user_id, user_real_name)
227
- abm.raw_message = d
228
- abm.message_str = ""
229
-
230
- if user_id == "weixin":
231
- # 忽略微信团队消息
232
- return
233
-
234
- # 不同消息类型
235
- match d["MsgType"]:
236
- case 1:
237
- # 文本消息
238
- abm.message.append(Plain(content))
239
- abm.message_str = content
240
- case 3:
241
- # 图片消息
242
- file_url = await self.multimedia_downloader.download_image(
243
- self.appid, content
244
- )
245
- logger.debug(f"下载图片: {file_url}")
246
- file_path = await download_image_by_url(file_url)
247
- abm.message.append(Image(file=file_path, url=file_path))
248
-
249
- case 34:
250
- # 语音消息
251
- if "ImgBuf" in d and "buffer" in d["ImgBuf"]:
252
- voice_data = base64.b64decode(d["ImgBuf"]["buffer"])
253
- file_path = f"data/temp/gewe_voice_{abm.message_id}.silk"
254
-
255
- async with await anyio.open_file(file_path, "wb") as f:
256
- await f.write(voice_data)
257
- abm.message.append(Record(file=file_path, url=file_path))
258
-
259
- # 以下已知消息类型,没有业务处理,只是避免控制台打印太多的日志
260
- case 37: # 好友申请
261
- logger.info("消息类型(37):好友申请")
262
- case 42: # 名片
263
- logger.info("消息类型(42):名片")
264
- case 43: # 视频
265
- video = Video(file="", cover=content)
266
- abm.message.append(video)
267
- case 47: # emoji
268
- data_parser = GeweDataParser(content, abm.group_id == "")
269
- emoji = data_parser.parse_emoji()
270
- abm.message.append(emoji)
271
- case 48: # 地理位置
272
- logger.info("消息类型(48):地理位置")
273
- case 49: # 公众号/文件/小程序/引用/转账/红包/视频号/群聊邀请
274
- data_parser = GeweDataParser(content, abm.group_id == "")
275
- segments = data_parser.parse_mutil_49()
276
- if segments:
277
- abm.message.extend(segments)
278
- for seg in segments:
279
- if isinstance(seg, Plain):
280
- abm.message_str += seg.text
281
- case 51: # 帐号消息同步?
282
- logger.info("消息类型(51):帐号消息同步?")
283
- case 10000: # 被踢出群聊/更换群主/修改群名称
284
- logger.info("消息类型(10000):被踢出群聊/更换群主/修改群名称")
285
- case 10002: # 撤回/拍一拍/成员邀请/被移出群聊/解散群聊/群公告/群待办
286
- logger.info(
287
- "消息类型(10002):撤回/拍一拍/成员邀请/被移出群聊/解散群聊/群公告/群待办"
288
- )
289
-
290
- case _:
291
- logger.info(f"未实现的消息类型: {d['MsgType']}")
292
- abm.raw_message = d
293
-
294
- logger.debug(f"abm: {abm}")
295
- return abm
296
-
297
- async def _callback(self):
298
- data = await quart.request.json
299
- logger.debug(f"收到 gewechat 回调: {data}")
300
-
301
- if data.get("testMsg", None):
302
- return quart.jsonify({"r": "AstrBot ACK"})
303
-
304
- abm = None
305
- try:
306
- abm = await self._convert(data)
307
- except BaseException as e:
308
- logger.warning(
309
- f"尝试解析 GeweChat 下发的消息时遇到问题: {e}。下发消息内容: {data}。"
310
- )
311
-
312
- if abm:
313
- coro = getattr(self, "on_event_received")
314
- if coro:
315
- await coro(abm)
316
-
317
- return quart.jsonify({"r": "AstrBot ACK"})
318
-
319
- async def _register_file(self, file_path: str) -> str:
320
- """向 AstrBot 回调服务器 注册一个允许外部访问的文件。
321
-
322
- Args:
323
- file_path (str): 文件路径。
324
- Returns:
325
- str: 返回一个 auth_token,文件路径为 file_path。通过 /astrbot-gewechat/file/auth_token 得到文件。
326
- """
327
- async with self.lock:
328
- if not os.path.exists(file_path):
329
- raise Exception(f"文件不存在: {file_path}")
330
-
331
- file_token = str(uuid.uuid4())
332
- self.staged_files[file_token] = file_path
333
- return file_token
334
-
335
- async def _handle_file(self, file_token):
336
- async with self.lock:
337
- if file_token not in self.staged_files:
338
- logger.warning(f"请求的文件 {file_token} 不存在。")
339
- return quart.abort(404)
340
- if not os.path.exists(self.staged_files[file_token]):
341
- logger.warning(f"请求的文件 {self.staged_files[file_token]} 不存在。")
342
- return quart.abort(404)
343
- file_path = self.staged_files[file_token]
344
- self.staged_files.pop(file_token, None)
345
- return await quart.send_file(file_path)
346
-
347
- async def _set_callback_url(self):
348
- logger.info("设置回调,请等待...")
349
- await asyncio.sleep(3)
350
- async with aiohttp.ClientSession() as session:
351
- async with session.post(
352
- f"{self.base_url}/tools/setCallback",
353
- headers=self.headers,
354
- json={"token": self.token, "callbackUrl": self.callback_url},
355
- ) as resp:
356
- json_blob = await resp.json()
357
- logger.info(f"设置回调结果: {json_blob}")
358
- if json_blob["ret"] != 200:
359
- raise Exception(f"设置回调失败: {json_blob}")
360
- logger.info(
361
- f"将在 {self.callback_url} 上接收 gewechat 下发的消息。如果一直没收到消息请先尝试重启 AstrBot。如果仍没收到请到管理面板聊天页输入 /gewe_logout 重新登录。"
362
- )
363
-
364
- async def start_polling(self):
365
- threading.Thread(target=asyncio.run, args=(self._set_callback_url(),)).start()
366
- await self.server.run_task(
367
- host="0.0.0.0",
368
- port=self.port,
369
- shutdown_trigger=self.shutdown_trigger,
370
- )
371
-
372
- async def shutdown_trigger(self):
373
- await self.shutdown_event.wait()
374
-
375
- async def check_online(self, appid: str):
376
- """检查 APPID 对应的设备是否在线。"""
377
- async with aiohttp.ClientSession() as session:
378
- async with session.post(
379
- f"{self.base_url}/login/checkOnline",
380
- headers=self.headers,
381
- json={"appId": appid},
382
- ) as resp:
383
- json_blob = await resp.json()
384
- return json_blob["data"]
385
-
386
- async def logout(self):
387
- """登出 gewechat。"""
388
- if self.appid:
389
- online = await self.check_online(self.appid)
390
- if online:
391
- async with aiohttp.ClientSession() as session:
392
- async with session.post(
393
- f"{self.base_url}/login/logout",
394
- headers=self.headers,
395
- json={"appId": self.appid},
396
- ) as resp:
397
- json_blob = await resp.json()
398
- logger.info(f"登出结果: {json_blob}")
399
-
400
- async def login(self):
401
- """登录 gewechat。一般来说插件用不到这个方法。"""
402
- if self.token is None:
403
- await self.get_token_id()
404
-
405
- self.multimedia_downloader = GeweDownloader(
406
- self.base_url, self.download_base_url, self.token
407
- )
408
-
409
- if self.appid:
410
- try:
411
- online = await self.check_online(self.appid)
412
- if online:
413
- logger.info(f"APPID: {self.appid} 已在线")
414
- return
415
- except Exception as e:
416
- logger.error(f"检查在线状态失败: {e}")
417
- sp.put(f"gewechat-appid-{self.nickname}", "")
418
- self.appid = None
419
-
420
- payload = {"appId": self.appid}
421
-
422
- if self.appid:
423
- logger.info(f"使用 APPID: {self.appid}, {self.nickname}")
424
-
425
- try:
426
- async with aiohttp.ClientSession() as session:
427
- async with session.post(
428
- f"{self.base_url}/login/getLoginQrCode",
429
- headers=self.headers,
430
- json=payload,
431
- ) as resp:
432
- json_blob = await resp.json()
433
- if json_blob["ret"] != 200:
434
- error_msg = json_blob.get("data", {}).get("msg", "")
435
- if "设备不存在" in error_msg:
436
- logger.error(
437
- f"检测到无效的appid: {self.appid},将清除并重新登录。"
438
- )
439
- sp.put(f"gewechat-appid-{self.nickname}", "")
440
- self.appid = None
441
- return await self.login()
442
- else:
443
- raise Exception(f"获取二维码失败: {json_blob}")
444
- qr_data = json_blob["data"]["qrData"]
445
- qr_uuid = json_blob["data"]["uuid"]
446
- appid = json_blob["data"]["appId"]
447
- logger.info(f"APPID: {appid}")
448
- logger.warning(
449
- f"请打开该网址,然后使用微信扫描二维码登录: https://api.cl2wm.cn/api/qrcode/code?text={qr_data}"
450
- )
451
- except Exception as e:
452
- raise e
453
-
454
- # 执行登录
455
- retry_cnt = 64
456
- payload.update({"uuid": qr_uuid, "appId": appid})
457
- while retry_cnt > 0:
458
- retry_cnt -= 1
459
-
460
- # 需要验证码
461
- if os.path.exists("data/temp/gewe_code"):
462
- with open("data/temp/gewe_code", "r") as f:
463
- code = f.read().strip()
464
- if not code:
465
- logger.warning(
466
- "未找到验证码,请在管理面板聊天页输入 /gewe_code 验证码 来验证,如 /gewe_code 123456"
467
- )
468
- await asyncio.sleep(5)
469
- continue
470
- payload["captchCode"] = code
471
- logger.info(f"使用验证码: {code}")
472
- try:
473
- os.remove("data/temp/gewe_code")
474
- except Exception:
475
- logger.warning("删除验证码文件 data/temp/gewe_code 失败。")
476
-
477
- async with aiohttp.ClientSession() as session:
478
- async with session.post(
479
- f"{self.base_url}/login/checkLogin",
480
- headers=self.headers,
481
- json=payload,
482
- ) as resp:
483
- json_blob = await resp.json()
484
- logger.info(f"检查登录状态: {json_blob}")
485
-
486
- ret = json_blob["ret"]
487
- msg = ""
488
- if json_blob["data"] and "msg" in json_blob["data"]:
489
- msg = json_blob["data"]["msg"]
490
- if ret == 500 and "安全验证码" in msg:
491
- logger.warning(
492
- "此次登录需要安全验证码,请在管理面板聊天页输入 /gewe_code 验证码 来验证,如 /gewe_code 123456"
493
- )
494
- else:
495
- if "status" in json_blob["data"]:
496
- status = json_blob["data"]["status"]
497
- nickname = json_blob["data"].get("nickName", "")
498
- if status == 1:
499
- logger.info(f"等待确认...{nickname}")
500
- elif status == 2:
501
- logger.info(f"绿泡泡平台登录成功: {nickname}")
502
- break
503
- elif status == 0:
504
- logger.info("等待扫码...")
505
- else:
506
- logger.warning(f"未知状态: {status}")
507
- await asyncio.sleep(5)
508
-
509
- if appid:
510
- sp.put(f"gewechat-appid-{self.nickname}", appid)
511
- self.appid = appid
512
- logger.info(f"已保存 APPID: {appid}")
513
-
514
- """API 部分。Gewechat 的 API 文档请参考: https://apifox.com/apidoc/shared/69ba62ca-cb7d-437e-85e4-6f3d3df271b1
515
- """
516
-
517
- async def get_chatroom_member_list(self, chatroom_wxid: str) -> dict:
518
- """获取群成员列表。
519
-
520
- Args:
521
- chatroom_wxid (str): 微信群聊的id。可以通过 event.get_group_id() 获取。
522
-
523
- Returns:
524
- dict: 返回群成员列表字典。其中键为 memberList 的值为群成员列表。
525
- """
526
- payload = {"appId": self.appid, "chatroomId": chatroom_wxid}
527
-
528
- async with aiohttp.ClientSession() as session:
529
- async with session.post(
530
- f"{self.base_url}/group/getChatroomMemberList",
531
- headers=self.headers,
532
- json=payload,
533
- ) as resp:
534
- json_blob = await resp.json()
535
- return json_blob["data"]
536
-
537
- async def post_text(self, to_wxid, content: str, ats: str = ""):
538
- """发送纯文本消息"""
539
- payload = {
540
- "appId": self.appid,
541
- "toWxid": to_wxid,
542
- "content": content,
543
- }
544
- if ats:
545
- payload["ats"] = ats
546
-
547
- async with aiohttp.ClientSession() as session:
548
- async with session.post(
549
- f"{self.base_url}/message/postText", headers=self.headers, json=payload
550
- ) as resp:
551
- json_blob = await resp.json()
552
- logger.debug(f"发送消息结果: {json_blob}")
553
-
554
- async def post_image(self, to_wxid, image_url: str):
555
- """发送图片消息"""
556
- payload = {
557
- "appId": self.appid,
558
- "toWxid": to_wxid,
559
- "imgUrl": image_url,
560
- }
561
-
562
- async with aiohttp.ClientSession() as session:
563
- async with session.post(
564
- f"{self.base_url}/message/postImage", headers=self.headers, json=payload
565
- ) as resp:
566
- json_blob = await resp.json()
567
- logger.debug(f"发送图片结果: {json_blob}")
568
-
569
- async def post_emoji(self, to_wxid, emoji_md5, emoji_size, cdnurl=""):
570
- """发送emoji消息"""
571
- payload = {
572
- "appId": self.appid,
573
- "toWxid": to_wxid,
574
- "emojiMd5": emoji_md5,
575
- "emojiSize": emoji_size,
576
- }
577
-
578
- # 优先表情包,若拿不到表情包的md5,就用当作图片发
579
- try:
580
- if emoji_md5 != "" and emoji_size != "":
581
- async with aiohttp.ClientSession() as session:
582
- async with session.post(
583
- f"{self.base_url}/message/postEmoji",
584
- headers=self.headers,
585
- json=payload,
586
- ) as resp:
587
- json_blob = await resp.json()
588
- logger.info(
589
- f"发送emoji消息结果: {json_blob.get('msg', '操作失败')}"
590
- )
591
- else:
592
- await self.post_image(to_wxid, cdnurl)
593
-
594
- except Exception as e:
595
- logger.error(e)
596
-
597
- async def post_video(
598
- self, to_wxid, video_url: str, thumb_url: str, video_duration: int
599
- ):
600
- payload = {
601
- "appId": self.appid,
602
- "toWxid": to_wxid,
603
- "videoUrl": video_url,
604
- "thumbUrl": thumb_url,
605
- "videoDuration": video_duration,
606
- }
607
- async with aiohttp.ClientSession() as session:
608
- async with session.post(
609
- f"{self.base_url}/message/postVideo", headers=self.headers, json=payload
610
- ) as resp:
611
- json_blob = await resp.json()
612
- logger.debug(f"发送视频结果: {json_blob}")
613
-
614
- async def forward_video(self, to_wxid, cnd_xml: str):
615
- """转发视频
616
-
617
- Args:
618
- to_wxid (str): 发送给谁
619
- cnd_xml (str): 视频消息的cdn信息
620
- """
621
- payload = {
622
- "appId": self.appid,
623
- "toWxid": to_wxid,
624
- "xml": cnd_xml,
625
- }
626
- async with aiohttp.ClientSession() as session:
627
- async with session.post(
628
- f"{self.base_url}/message/forwardVideo",
629
- headers=self.headers,
630
- json=payload,
631
- ) as resp:
632
- json_blob = await resp.json()
633
- logger.debug(f"转发视频结果: {json_blob}")
634
-
635
- async def post_voice(self, to_wxid, voice_url: str, voice_duration: int):
636
- """发送语音信息
637
-
638
- Args:
639
- voice_url (str): 语音文件的网络链接
640
- voice_duration (int): 语音时长,毫秒
641
- """
642
- payload = {
643
- "appId": self.appid,
644
- "toWxid": to_wxid,
645
- "voiceUrl": voice_url,
646
- "voiceDuration": voice_duration,
647
- }
648
-
649
- logger.debug(f"发送语音: {payload}")
650
-
651
- async with aiohttp.ClientSession() as session:
652
- async with session.post(
653
- f"{self.base_url}/message/postVoice", headers=self.headers, json=payload
654
- ) as resp:
655
- json_blob = await resp.json()
656
- logger.info(f"发送语音结果: {json_blob.get('msg', '操作失败')}")
657
-
658
- async def post_file(self, to_wxid, file_url: str, file_name: str):
659
- """发送文件
660
-
661
- Args:
662
- to_wxid (string): 微信ID
663
- file_url (str): 文件的网络链接
664
- file_name (str): 文件名
665
- """
666
- payload = {
667
- "appId": self.appid,
668
- "toWxid": to_wxid,
669
- "fileUrl": file_url,
670
- "fileName": file_name,
671
- }
672
-
673
- async with aiohttp.ClientSession() as session:
674
- async with session.post(
675
- f"{self.base_url}/message/postFile", headers=self.headers, json=payload
676
- ) as resp:
677
- json_blob = await resp.json()
678
- logger.debug(f"发送文件结果: {json_blob}")
679
-
680
- async def add_friend(self, v3: str, v4: str, content: str):
681
- """申请添加好友"""
682
- payload = {
683
- "appId": self.appid,
684
- "scene": 3,
685
- "content": content,
686
- "v4": v4,
687
- "v3": v3,
688
- "option": 2,
689
- }
690
-
691
- async with aiohttp.ClientSession() as session:
692
- async with session.post(
693
- f"{self.base_url}/contacts/addContacts",
694
- headers=self.headers,
695
- json=payload,
696
- ) as resp:
697
- json_blob = await resp.json()
698
- logger.debug(f"申请添加好友结果: {json_blob}")
699
- return json_blob
700
-
701
- async def get_group(self, group_id: str):
702
- payload = {
703
- "appId": self.appid,
704
- "chatroomId": group_id,
705
- }
706
-
707
- async with aiohttp.ClientSession() as session:
708
- async with session.post(
709
- f"{self.base_url}/group/getChatroomInfo",
710
- headers=self.headers,
711
- json=payload,
712
- ) as resp:
713
- json_blob = await resp.json()
714
- logger.debug(f"获取群信息结果: {json_blob}")
715
- return json_blob
716
-
717
- async def get_group_member(self, group_id: str):
718
- payload = {
719
- "appId": self.appid,
720
- "chatroomId": group_id,
721
- }
722
-
723
- async with aiohttp.ClientSession() as session:
724
- async with session.post(
725
- f"{self.base_url}/group/getChatroomMemberList",
726
- headers=self.headers,
727
- json=payload,
728
- ) as resp:
729
- json_blob = await resp.json()
730
- logger.debug(f"获取群信息结果: {json_blob}")
731
- return json_blob
732
-
733
- async def accept_group_invite(self, url: str):
734
- """同意进群"""
735
- payload = {"appId": self.appid, "url": url}
736
-
737
- async with aiohttp.ClientSession() as session:
738
- async with session.post(
739
- f"{self.base_url}/group/agreeJoinRoom",
740
- headers=self.headers,
741
- json=payload,
742
- ) as resp:
743
- json_blob = await resp.json()
744
- logger.debug(f"获取群信息结果: {json_blob}")
745
- return json_blob
746
-
747
- async def add_group_member_to_friend(
748
- self, group_id: str, to_wxid: str, content: str
749
- ):
750
- payload = {
751
- "appId": self.appid,
752
- "chatroomId": group_id,
753
- "content": content,
754
- "memberWxid": to_wxid,
755
- }
756
-
757
- async with aiohttp.ClientSession() as session:
758
- async with session.post(
759
- f"{self.base_url}/group/addGroupMemberAsFriend",
760
- headers=self.headers,
761
- json=payload,
762
- ) as resp:
763
- json_blob = await resp.json()
764
- logger.debug(f"获取群信息结果: {json_blob}")
765
- return json_blob
766
-
767
- async def get_user_or_group_info(self, *ids):
768
- """
769
- 获取用户或群组信息。
770
-
771
- :param ids: 可变数量的 wxid 参数
772
- """
773
-
774
- wxids_str = list(ids)
775
-
776
- payload = {
777
- "appId": self.appid,
778
- "wxids": wxids_str, # 使用逗号分隔的字符串
779
- }
780
-
781
- async with aiohttp.ClientSession() as session:
782
- async with session.post(
783
- f"{self.base_url}/contacts/getDetailInfo",
784
- headers=self.headers,
785
- json=payload,
786
- ) as resp:
787
- json_blob = await resp.json()
788
- logger.debug(f"获取群信息结果: {json_blob}")
789
- return json_blob
790
-
791
- async def get_contacts_list(self):
792
- """
793
- 获取通讯录列表
794
- 见 https://apifox.com/apidoc/shared/69ba62ca-cb7d-437e-85e4-6f3d3df271b1/api-196794504
795
- """
796
- payload = {"appId": self.appid}
797
-
798
- async with aiohttp.ClientSession() as session:
799
- async with session.post(
800
- f"{self.base_url}/contacts/fetchContactsList",
801
- headers=self.headers,
802
- json=payload,
803
- ) as resp:
804
- json_blob = await resp.json()
805
- logger.debug(f"获取通讯录列表结果: {json_blob}")
806
- return json_blob