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