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.
- astrbot/api/__init__.py +16 -4
- astrbot/api/all.py +2 -1
- astrbot/api/event/__init__.py +5 -6
- astrbot/api/event/filter/__init__.py +37 -34
- astrbot/api/platform/__init__.py +7 -8
- astrbot/api/provider/__init__.py +8 -7
- astrbot/api/star/__init__.py +3 -4
- astrbot/api/util/__init__.py +2 -2
- astrbot/cli/__init__.py +1 -0
- astrbot/cli/__main__.py +18 -197
- astrbot/cli/commands/__init__.py +6 -0
- astrbot/cli/commands/cmd_conf.py +209 -0
- astrbot/cli/commands/cmd_init.py +56 -0
- astrbot/cli/commands/cmd_plug.py +245 -0
- astrbot/cli/commands/cmd_run.py +62 -0
- astrbot/cli/utils/__init__.py +18 -0
- astrbot/cli/utils/basic.py +76 -0
- astrbot/cli/utils/plugin.py +246 -0
- astrbot/cli/utils/version_comparator.py +90 -0
- astrbot/core/__init__.py +17 -19
- astrbot/core/agent/agent.py +14 -0
- astrbot/core/agent/handoff.py +38 -0
- astrbot/core/agent/hooks.py +30 -0
- astrbot/core/agent/mcp_client.py +385 -0
- astrbot/core/agent/message.py +175 -0
- astrbot/core/agent/response.py +14 -0
- astrbot/core/agent/run_context.py +22 -0
- astrbot/core/agent/runners/__init__.py +3 -0
- astrbot/core/agent/runners/base.py +65 -0
- astrbot/core/agent/runners/coze/coze_agent_runner.py +367 -0
- astrbot/core/agent/runners/coze/coze_api_client.py +324 -0
- astrbot/core/agent/runners/dashscope/dashscope_agent_runner.py +403 -0
- astrbot/core/agent/runners/dify/dify_agent_runner.py +336 -0
- astrbot/core/agent/runners/dify/dify_api_client.py +195 -0
- astrbot/core/agent/runners/tool_loop_agent_runner.py +400 -0
- astrbot/core/agent/tool.py +285 -0
- astrbot/core/agent/tool_executor.py +17 -0
- astrbot/core/astr_agent_context.py +19 -0
- astrbot/core/astr_agent_hooks.py +36 -0
- astrbot/core/astr_agent_run_util.py +80 -0
- astrbot/core/astr_agent_tool_exec.py +246 -0
- astrbot/core/astrbot_config_mgr.py +275 -0
- astrbot/core/config/__init__.py +2 -2
- astrbot/core/config/astrbot_config.py +60 -20
- astrbot/core/config/default.py +1972 -453
- astrbot/core/config/i18n_utils.py +110 -0
- astrbot/core/conversation_mgr.py +285 -75
- astrbot/core/core_lifecycle.py +167 -62
- astrbot/core/db/__init__.py +305 -102
- astrbot/core/db/migration/helper.py +69 -0
- astrbot/core/db/migration/migra_3_to_4.py +357 -0
- astrbot/core/db/migration/migra_45_to_46.py +44 -0
- astrbot/core/db/migration/migra_webchat_session.py +131 -0
- astrbot/core/db/migration/shared_preferences_v3.py +48 -0
- astrbot/core/db/migration/sqlite_v3.py +497 -0
- astrbot/core/db/po.py +259 -55
- astrbot/core/db/sqlite.py +773 -528
- astrbot/core/db/vec_db/base.py +73 -0
- astrbot/core/db/vec_db/faiss_impl/__init__.py +3 -0
- astrbot/core/db/vec_db/faiss_impl/document_storage.py +392 -0
- astrbot/core/db/vec_db/faiss_impl/embedding_storage.py +93 -0
- astrbot/core/db/vec_db/faiss_impl/sqlite_init.sql +17 -0
- astrbot/core/db/vec_db/faiss_impl/vec_db.py +204 -0
- astrbot/core/event_bus.py +26 -22
- astrbot/core/exceptions.py +9 -0
- astrbot/core/file_token_service.py +98 -0
- astrbot/core/initial_loader.py +19 -10
- astrbot/core/knowledge_base/chunking/__init__.py +9 -0
- astrbot/core/knowledge_base/chunking/base.py +25 -0
- astrbot/core/knowledge_base/chunking/fixed_size.py +59 -0
- astrbot/core/knowledge_base/chunking/recursive.py +161 -0
- astrbot/core/knowledge_base/kb_db_sqlite.py +301 -0
- astrbot/core/knowledge_base/kb_helper.py +642 -0
- astrbot/core/knowledge_base/kb_mgr.py +330 -0
- astrbot/core/knowledge_base/models.py +120 -0
- astrbot/core/knowledge_base/parsers/__init__.py +13 -0
- astrbot/core/knowledge_base/parsers/base.py +51 -0
- astrbot/core/knowledge_base/parsers/markitdown_parser.py +26 -0
- astrbot/core/knowledge_base/parsers/pdf_parser.py +101 -0
- astrbot/core/knowledge_base/parsers/text_parser.py +42 -0
- astrbot/core/knowledge_base/parsers/url_parser.py +103 -0
- astrbot/core/knowledge_base/parsers/util.py +13 -0
- astrbot/core/knowledge_base/prompts.py +65 -0
- astrbot/core/knowledge_base/retrieval/__init__.py +14 -0
- astrbot/core/knowledge_base/retrieval/hit_stopwords.txt +767 -0
- astrbot/core/knowledge_base/retrieval/manager.py +276 -0
- astrbot/core/knowledge_base/retrieval/rank_fusion.py +142 -0
- astrbot/core/knowledge_base/retrieval/sparse_retriever.py +136 -0
- astrbot/core/log.py +21 -15
- astrbot/core/message/components.py +413 -287
- astrbot/core/message/message_event_result.py +35 -24
- astrbot/core/persona_mgr.py +192 -0
- astrbot/core/pipeline/__init__.py +14 -14
- astrbot/core/pipeline/content_safety_check/stage.py +13 -9
- astrbot/core/pipeline/content_safety_check/strategies/__init__.py +1 -2
- astrbot/core/pipeline/content_safety_check/strategies/baidu_aip.py +13 -14
- astrbot/core/pipeline/content_safety_check/strategies/keywords.py +2 -1
- astrbot/core/pipeline/content_safety_check/strategies/strategy.py +6 -6
- astrbot/core/pipeline/context.py +7 -1
- astrbot/core/pipeline/context_utils.py +107 -0
- astrbot/core/pipeline/preprocess_stage/stage.py +63 -36
- astrbot/core/pipeline/process_stage/method/agent_request.py +48 -0
- astrbot/core/pipeline/process_stage/method/agent_sub_stages/internal.py +464 -0
- astrbot/core/pipeline/process_stage/method/agent_sub_stages/third_party.py +202 -0
- astrbot/core/pipeline/process_stage/method/star_request.py +26 -32
- astrbot/core/pipeline/process_stage/stage.py +21 -15
- astrbot/core/pipeline/process_stage/utils.py +125 -0
- astrbot/core/pipeline/rate_limit_check/stage.py +34 -36
- astrbot/core/pipeline/respond/stage.py +142 -101
- astrbot/core/pipeline/result_decorate/stage.py +124 -57
- astrbot/core/pipeline/scheduler.py +21 -16
- astrbot/core/pipeline/session_status_check/stage.py +37 -0
- astrbot/core/pipeline/stage.py +11 -76
- astrbot/core/pipeline/waking_check/stage.py +69 -33
- astrbot/core/pipeline/whitelist_check/stage.py +10 -7
- astrbot/core/platform/__init__.py +6 -6
- astrbot/core/platform/astr_message_event.py +107 -129
- astrbot/core/platform/astrbot_message.py +32 -12
- astrbot/core/platform/manager.py +62 -18
- astrbot/core/platform/message_session.py +30 -0
- astrbot/core/platform/platform.py +16 -24
- astrbot/core/platform/platform_metadata.py +9 -4
- astrbot/core/platform/register.py +12 -7
- astrbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py +136 -60
- astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py +126 -46
- astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py +63 -31
- astrbot/core/platform/sources/dingtalk/dingtalk_event.py +30 -26
- astrbot/core/platform/sources/discord/client.py +129 -0
- astrbot/core/platform/sources/discord/components.py +139 -0
- astrbot/core/platform/sources/discord/discord_platform_adapter.py +473 -0
- astrbot/core/platform/sources/discord/discord_platform_event.py +313 -0
- astrbot/core/platform/sources/lark/lark_adapter.py +27 -18
- astrbot/core/platform/sources/lark/lark_event.py +39 -13
- astrbot/core/platform/sources/misskey/misskey_adapter.py +770 -0
- astrbot/core/platform/sources/misskey/misskey_api.py +964 -0
- astrbot/core/platform/sources/misskey/misskey_event.py +163 -0
- astrbot/core/platform/sources/misskey/misskey_utils.py +550 -0
- astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py +149 -33
- astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py +41 -26
- astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_adapter.py +36 -17
- astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_event.py +3 -1
- astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_server.py +14 -8
- astrbot/core/platform/sources/satori/satori_adapter.py +792 -0
- astrbot/core/platform/sources/satori/satori_event.py +432 -0
- astrbot/core/platform/sources/slack/client.py +164 -0
- astrbot/core/platform/sources/slack/slack_adapter.py +416 -0
- astrbot/core/platform/sources/slack/slack_event.py +253 -0
- astrbot/core/platform/sources/telegram/tg_adapter.py +100 -43
- astrbot/core/platform/sources/telegram/tg_event.py +136 -36
- astrbot/core/platform/sources/webchat/webchat_adapter.py +72 -22
- astrbot/core/platform/sources/webchat/webchat_event.py +46 -22
- astrbot/core/platform/sources/webchat/webchat_queue_mgr.py +35 -0
- astrbot/core/platform/sources/wechatpadpro/wechatpadpro_adapter.py +926 -0
- astrbot/core/platform/sources/wechatpadpro/wechatpadpro_message_event.py +178 -0
- astrbot/core/platform/sources/wechatpadpro/xml_data_parser.py +159 -0
- astrbot/core/platform/sources/wecom/wecom_adapter.py +169 -27
- astrbot/core/platform/sources/wecom/wecom_event.py +162 -77
- astrbot/core/platform/sources/wecom/wecom_kf.py +279 -0
- astrbot/core/platform/sources/wecom/wecom_kf_message.py +196 -0
- astrbot/core/platform/sources/wecom_ai_bot/WXBizJsonMsgCrypt.py +297 -0
- astrbot/core/platform/sources/wecom_ai_bot/__init__.py +15 -0
- astrbot/core/platform/sources/wecom_ai_bot/ierror.py +19 -0
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_adapter.py +472 -0
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_api.py +417 -0
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_event.py +152 -0
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_queue_mgr.py +153 -0
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_server.py +168 -0
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_utils.py +209 -0
- astrbot/core/platform/sources/weixin_official_account/weixin_offacc_adapter.py +306 -0
- astrbot/core/platform/sources/weixin_official_account/weixin_offacc_event.py +186 -0
- astrbot/core/platform_message_history_mgr.py +49 -0
- astrbot/core/provider/__init__.py +2 -3
- astrbot/core/provider/entites.py +8 -8
- astrbot/core/provider/entities.py +154 -98
- astrbot/core/provider/func_tool_manager.py +446 -458
- astrbot/core/provider/manager.py +345 -207
- astrbot/core/provider/provider.py +188 -73
- astrbot/core/provider/register.py +9 -7
- astrbot/core/provider/sources/anthropic_source.py +295 -115
- astrbot/core/provider/sources/azure_tts_source.py +224 -0
- astrbot/core/provider/sources/bailian_rerank_source.py +236 -0
- astrbot/core/provider/sources/dashscope_tts.py +138 -14
- astrbot/core/provider/sources/edge_tts_source.py +24 -19
- astrbot/core/provider/sources/fishaudio_tts_api_source.py +58 -13
- astrbot/core/provider/sources/gemini_embedding_source.py +61 -0
- astrbot/core/provider/sources/gemini_source.py +310 -132
- astrbot/core/provider/sources/gemini_tts_source.py +81 -0
- astrbot/core/provider/sources/groq_source.py +15 -0
- astrbot/core/provider/sources/gsv_selfhosted_source.py +151 -0
- astrbot/core/provider/sources/gsvi_tts_source.py +14 -7
- astrbot/core/provider/sources/minimax_tts_api_source.py +159 -0
- astrbot/core/provider/sources/openai_embedding_source.py +40 -0
- astrbot/core/provider/sources/openai_source.py +241 -145
- astrbot/core/provider/sources/openai_tts_api_source.py +18 -7
- astrbot/core/provider/sources/sensevoice_selfhosted_source.py +13 -11
- astrbot/core/provider/sources/vllm_rerank_source.py +71 -0
- astrbot/core/provider/sources/volcengine_tts.py +115 -0
- astrbot/core/provider/sources/whisper_api_source.py +18 -13
- astrbot/core/provider/sources/whisper_selfhosted_source.py +19 -12
- astrbot/core/provider/sources/xinference_rerank_source.py +116 -0
- astrbot/core/provider/sources/xinference_stt_provider.py +197 -0
- astrbot/core/provider/sources/zhipu_source.py +6 -73
- astrbot/core/star/__init__.py +43 -11
- astrbot/core/star/config.py +17 -18
- astrbot/core/star/context.py +362 -138
- astrbot/core/star/filter/__init__.py +4 -3
- astrbot/core/star/filter/command.py +111 -35
- astrbot/core/star/filter/command_group.py +46 -34
- astrbot/core/star/filter/custom_filter.py +6 -5
- astrbot/core/star/filter/event_message_type.py +4 -2
- astrbot/core/star/filter/permission.py +4 -2
- astrbot/core/star/filter/platform_adapter_type.py +45 -12
- astrbot/core/star/filter/regex.py +4 -2
- astrbot/core/star/register/__init__.py +19 -15
- astrbot/core/star/register/star.py +41 -13
- astrbot/core/star/register/star_handler.py +236 -86
- astrbot/core/star/session_llm_manager.py +280 -0
- astrbot/core/star/session_plugin_manager.py +170 -0
- astrbot/core/star/star.py +36 -43
- astrbot/core/star/star_handler.py +47 -85
- astrbot/core/star/star_manager.py +442 -260
- astrbot/core/star/star_tools.py +167 -45
- astrbot/core/star/updator.py +17 -20
- astrbot/core/umop_config_router.py +106 -0
- astrbot/core/updator.py +38 -13
- astrbot/core/utils/astrbot_path.py +39 -0
- astrbot/core/utils/command_parser.py +1 -1
- astrbot/core/utils/io.py +119 -60
- astrbot/core/utils/log_pipe.py +1 -1
- astrbot/core/utils/metrics.py +11 -10
- astrbot/core/utils/migra_helper.py +73 -0
- astrbot/core/utils/path_util.py +63 -62
- astrbot/core/utils/pip_installer.py +37 -15
- astrbot/core/utils/session_lock.py +29 -0
- astrbot/core/utils/session_waiter.py +19 -20
- astrbot/core/utils/shared_preferences.py +174 -34
- astrbot/core/utils/t2i/__init__.py +4 -1
- astrbot/core/utils/t2i/local_strategy.py +386 -238
- astrbot/core/utils/t2i/network_strategy.py +109 -49
- astrbot/core/utils/t2i/renderer.py +29 -14
- astrbot/core/utils/t2i/template/astrbot_powershell.html +184 -0
- astrbot/core/utils/t2i/template_manager.py +111 -0
- astrbot/core/utils/tencent_record_helper.py +115 -1
- astrbot/core/utils/version_comparator.py +10 -13
- astrbot/core/zip_updator.py +112 -65
- astrbot/dashboard/routes/__init__.py +20 -13
- astrbot/dashboard/routes/auth.py +20 -9
- astrbot/dashboard/routes/chat.py +297 -141
- astrbot/dashboard/routes/config.py +652 -55
- astrbot/dashboard/routes/conversation.py +107 -37
- astrbot/dashboard/routes/file.py +26 -0
- astrbot/dashboard/routes/knowledge_base.py +1244 -0
- astrbot/dashboard/routes/log.py +27 -2
- astrbot/dashboard/routes/persona.py +202 -0
- astrbot/dashboard/routes/plugin.py +197 -139
- astrbot/dashboard/routes/route.py +27 -7
- astrbot/dashboard/routes/session_management.py +354 -0
- astrbot/dashboard/routes/stat.py +85 -18
- astrbot/dashboard/routes/static_file.py +5 -2
- astrbot/dashboard/routes/t2i.py +233 -0
- astrbot/dashboard/routes/tools.py +184 -120
- astrbot/dashboard/routes/update.py +59 -36
- astrbot/dashboard/server.py +96 -36
- astrbot/dashboard/utils.py +165 -0
- astrbot-4.7.0.dist-info/METADATA +294 -0
- astrbot-4.7.0.dist-info/RECORD +274 -0
- {astrbot-3.5.6.dist-info → astrbot-4.7.0.dist-info}/WHEEL +1 -1
- astrbot/core/db/plugin/sqlite_impl.py +0 -112
- astrbot/core/db/sqlite_init.sql +0 -50
- astrbot/core/pipeline/platform_compatibility/stage.py +0 -56
- astrbot/core/pipeline/process_stage/method/llm_request.py +0 -606
- astrbot/core/platform/sources/gewechat/client.py +0 -806
- astrbot/core/platform/sources/gewechat/downloader.py +0 -55
- astrbot/core/platform/sources/gewechat/gewechat_event.py +0 -255
- astrbot/core/platform/sources/gewechat/gewechat_platform_adapter.py +0 -103
- astrbot/core/platform/sources/gewechat/xml_data_parser.py +0 -110
- astrbot/core/provider/sources/dashscope_source.py +0 -203
- astrbot/core/provider/sources/dify_source.py +0 -281
- astrbot/core/provider/sources/llmtuner_source.py +0 -132
- astrbot/core/rag/embedding/openai_source.py +0 -20
- astrbot/core/rag/knowledge_db_mgr.py +0 -94
- astrbot/core/rag/store/__init__.py +0 -9
- astrbot/core/rag/store/chroma_db.py +0 -42
- astrbot/core/utils/dify_api_client.py +0 -152
- astrbot-3.5.6.dist-info/METADATA +0 -249
- astrbot-3.5.6.dist-info/RECORD +0 -158
- {astrbot-3.5.6.dist-info → astrbot-4.7.0.dist-info}/entry_points.txt +0 -0
- {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
|