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