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
|
@@ -37,7 +37,10 @@ else:
|
|
|
37
37
|
@register_platform_adapter("telegram", "telegram 适配器")
|
|
38
38
|
class TelegramPlatformAdapter(Platform):
|
|
39
39
|
def __init__(
|
|
40
|
-
self,
|
|
40
|
+
self,
|
|
41
|
+
platform_config: dict,
|
|
42
|
+
platform_settings: dict,
|
|
43
|
+
event_queue: asyncio.Queue,
|
|
41
44
|
) -> None:
|
|
42
45
|
super().__init__(event_queue)
|
|
43
46
|
self.config = platform_config
|
|
@@ -45,13 +48,15 @@ class TelegramPlatformAdapter(Platform):
|
|
|
45
48
|
self.client_self_id = uuid.uuid4().hex[:8]
|
|
46
49
|
|
|
47
50
|
base_url = self.config.get(
|
|
48
|
-
"telegram_api_base_url",
|
|
51
|
+
"telegram_api_base_url",
|
|
52
|
+
"https://api.telegram.org/bot",
|
|
49
53
|
)
|
|
50
54
|
if not base_url:
|
|
51
55
|
base_url = "https://api.telegram.org/bot"
|
|
52
56
|
|
|
53
57
|
file_base_url = self.config.get(
|
|
54
|
-
"telegram_file_base_url",
|
|
58
|
+
"telegram_file_base_url",
|
|
59
|
+
"https://api.telegram.org/file/bot",
|
|
55
60
|
)
|
|
56
61
|
if not file_base_url:
|
|
57
62
|
file_base_url = "https://api.telegram.org/file/bot"
|
|
@@ -59,10 +64,12 @@ class TelegramPlatformAdapter(Platform):
|
|
|
59
64
|
self.base_url = base_url
|
|
60
65
|
|
|
61
66
|
self.enable_command_register = self.config.get(
|
|
62
|
-
"telegram_command_register",
|
|
67
|
+
"telegram_command_register",
|
|
68
|
+
True,
|
|
63
69
|
)
|
|
64
70
|
self.enable_command_refresh = self.config.get(
|
|
65
|
-
"telegram_command_auto_refresh",
|
|
71
|
+
"telegram_command_auto_refresh",
|
|
72
|
+
True,
|
|
66
73
|
)
|
|
67
74
|
self.last_command_hash = None
|
|
68
75
|
|
|
@@ -85,19 +92,22 @@ class TelegramPlatformAdapter(Platform):
|
|
|
85
92
|
|
|
86
93
|
@override
|
|
87
94
|
async def send_by_session(
|
|
88
|
-
self,
|
|
95
|
+
self,
|
|
96
|
+
session: MessageSesion,
|
|
97
|
+
message_chain: MessageChain,
|
|
89
98
|
):
|
|
90
99
|
from_username = session.session_id
|
|
91
100
|
await TelegramPlatformEvent.send_with_client(
|
|
92
|
-
self.client,
|
|
101
|
+
self.client,
|
|
102
|
+
message_chain,
|
|
103
|
+
from_username,
|
|
93
104
|
)
|
|
94
105
|
await super().send_by_session(session, message_chain)
|
|
95
106
|
|
|
96
107
|
@override
|
|
97
108
|
def meta(self) -> PlatformMetadata:
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
)
|
|
109
|
+
id_ = self.config.get("id") or "telegram"
|
|
110
|
+
return PlatformMetadata(name="telegram", description="telegram 适配器", id=id_)
|
|
101
111
|
|
|
102
112
|
@override
|
|
103
113
|
async def run(self):
|
|
@@ -117,6 +127,10 @@ class TelegramPlatformAdapter(Platform):
|
|
|
117
127
|
)
|
|
118
128
|
self.scheduler.start()
|
|
119
129
|
|
|
130
|
+
if not self.application.updater:
|
|
131
|
+
logger.error("Telegram Updater is not initialized. Cannot start polling.")
|
|
132
|
+
return
|
|
133
|
+
|
|
120
134
|
queue = self.application.updater.start_polling()
|
|
121
135
|
logger.info("Telegram Platform Adapter is running.")
|
|
122
136
|
await queue
|
|
@@ -128,7 +142,7 @@ class TelegramPlatformAdapter(Platform):
|
|
|
128
142
|
|
|
129
143
|
if commands:
|
|
130
144
|
current_hash = hash(
|
|
131
|
-
tuple((cmd.command, cmd.description) for cmd in commands)
|
|
145
|
+
tuple((cmd.command, cmd.description) for cmd in commands),
|
|
132
146
|
)
|
|
133
147
|
if current_hash == self.last_command_hash:
|
|
134
148
|
return
|
|
@@ -144,13 +158,15 @@ class TelegramPlatformAdapter(Platform):
|
|
|
144
158
|
command_dict = {}
|
|
145
159
|
skip_commands = {"start"}
|
|
146
160
|
|
|
147
|
-
for handler_md in star_handlers_registry
|
|
148
|
-
handler_metadata = handler_md
|
|
161
|
+
for handler_md in star_handlers_registry:
|
|
162
|
+
handler_metadata = handler_md
|
|
149
163
|
if not star_map[handler_metadata.handler_module_path].activated:
|
|
150
164
|
continue
|
|
151
165
|
for event_filter in handler_metadata.event_filters:
|
|
152
166
|
cmd_info = self._extract_command_info(
|
|
153
|
-
event_filter,
|
|
167
|
+
event_filter,
|
|
168
|
+
handler_metadata,
|
|
169
|
+
skip_commands,
|
|
154
170
|
)
|
|
155
171
|
if cmd_info:
|
|
156
172
|
cmd_name, description = cmd_info
|
|
@@ -161,7 +177,9 @@ class TelegramPlatformAdapter(Platform):
|
|
|
161
177
|
|
|
162
178
|
@staticmethod
|
|
163
179
|
def _extract_command_info(
|
|
164
|
-
event_filter,
|
|
180
|
+
event_filter,
|
|
181
|
+
handler_metadata,
|
|
182
|
+
skip_commands: set,
|
|
165
183
|
) -> tuple[str, str] | None:
|
|
166
184
|
"""从事件过滤器中提取指令信息"""
|
|
167
185
|
cmd_name = None
|
|
@@ -183,7 +201,6 @@ class TelegramPlatformAdapter(Platform):
|
|
|
183
201
|
return None
|
|
184
202
|
|
|
185
203
|
if not re.match(r"^[a-z0-9_]+$", cmd_name) or len(cmd_name) > 32:
|
|
186
|
-
logger.debug(f"跳过无法注册的命令: {cmd_name}")
|
|
187
204
|
return None
|
|
188
205
|
|
|
189
206
|
# Build description.
|
|
@@ -195,8 +212,14 @@ class TelegramPlatformAdapter(Platform):
|
|
|
195
212
|
return cmd_name, description
|
|
196
213
|
|
|
197
214
|
async def start(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|
215
|
+
if not update.effective_chat:
|
|
216
|
+
logger.warning(
|
|
217
|
+
"Received a start command without an effective chat, skipping /start reply.",
|
|
218
|
+
)
|
|
219
|
+
return
|
|
198
220
|
await context.bot.send_message(
|
|
199
|
-
chat_id=update.effective_chat.id,
|
|
221
|
+
chat_id=update.effective_chat.id,
|
|
222
|
+
text=self.config["start_message"],
|
|
200
223
|
)
|
|
201
224
|
|
|
202
225
|
async def message_handler(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|
@@ -206,16 +229,24 @@ class TelegramPlatformAdapter(Platform):
|
|
|
206
229
|
await self.handle_msg(abm)
|
|
207
230
|
|
|
208
231
|
async def convert_message(
|
|
209
|
-
self,
|
|
210
|
-
|
|
232
|
+
self,
|
|
233
|
+
update: Update,
|
|
234
|
+
context: ContextTypes.DEFAULT_TYPE,
|
|
235
|
+
get_reply=True,
|
|
236
|
+
) -> AstrBotMessage | None:
|
|
211
237
|
"""转换 Telegram 的消息对象为 AstrBotMessage 对象。
|
|
212
238
|
|
|
213
239
|
@param update: Telegram 的 Update 对象。
|
|
214
240
|
@param context: Telegram 的 Context 对象。
|
|
215
241
|
@param get_reply: 是否获取回复消息。这个参数是为了防止多个回复嵌套。
|
|
216
242
|
"""
|
|
243
|
+
if not update.message:
|
|
244
|
+
logger.warning("Received an update without a message.")
|
|
245
|
+
return None
|
|
246
|
+
|
|
217
247
|
message = AstrBotMessage()
|
|
218
248
|
message.session_id = str(update.message.chat.id)
|
|
249
|
+
|
|
219
250
|
# 获得是群聊还是私聊
|
|
220
251
|
if update.message.chat.type == ChatType.PRIVATE:
|
|
221
252
|
message.type = MessageType.FRIEND_MESSAGE
|
|
@@ -226,10 +257,14 @@ class TelegramPlatformAdapter(Platform):
|
|
|
226
257
|
# Topic Group
|
|
227
258
|
message.group_id += "#" + str(update.message.message_thread_id)
|
|
228
259
|
message.session_id = message.group_id
|
|
229
|
-
|
|
230
260
|
message.message_id = str(update.message.message_id)
|
|
261
|
+
_from_user = update.message.from_user
|
|
262
|
+
if not _from_user:
|
|
263
|
+
logger.warning("[Telegram] Received a message without a from_user.")
|
|
264
|
+
return None
|
|
231
265
|
message.sender = MessageMember(
|
|
232
|
-
str(
|
|
266
|
+
str(_from_user.id),
|
|
267
|
+
_from_user.username or "Unknown",
|
|
233
268
|
)
|
|
234
269
|
message.self_id = str(context.bot.username)
|
|
235
270
|
message.raw_message = update
|
|
@@ -248,22 +283,32 @@ class TelegramPlatformAdapter(Platform):
|
|
|
248
283
|
)
|
|
249
284
|
reply_abm = await self.convert_message(reply_update, context, False)
|
|
250
285
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
286
|
+
if reply_abm:
|
|
287
|
+
message.message.append(
|
|
288
|
+
Comp.Reply(
|
|
289
|
+
id=reply_abm.message_id,
|
|
290
|
+
chain=reply_abm.message,
|
|
291
|
+
sender_id=reply_abm.sender.user_id,
|
|
292
|
+
sender_nickname=reply_abm.sender.nickname,
|
|
293
|
+
time=reply_abm.timestamp,
|
|
294
|
+
message_str=reply_abm.message_str,
|
|
295
|
+
text=reply_abm.message_str,
|
|
296
|
+
qq=reply_abm.sender.user_id,
|
|
297
|
+
),
|
|
261
298
|
)
|
|
262
|
-
)
|
|
263
299
|
|
|
264
300
|
if update.message.text:
|
|
265
301
|
# 处理文本消息
|
|
266
302
|
plain_text = update.message.text
|
|
303
|
+
if (
|
|
304
|
+
message.type == MessageType.GROUP_MESSAGE
|
|
305
|
+
and update.message
|
|
306
|
+
and update.message.reply_to_message
|
|
307
|
+
and update.message.reply_to_message.from_user
|
|
308
|
+
and update.message.reply_to_message.from_user.id == context.bot.id
|
|
309
|
+
):
|
|
310
|
+
plain_text2 = f"/@{context.bot.username} " + plain_text
|
|
311
|
+
plain_text = plain_text2
|
|
267
312
|
|
|
268
313
|
# 群聊场景命令特殊处理
|
|
269
314
|
if plain_text.startswith("/"):
|
|
@@ -282,10 +327,12 @@ class TelegramPlatformAdapter(Platform):
|
|
|
282
327
|
entity.offset + 1 : entity.offset + entity.length
|
|
283
328
|
]
|
|
284
329
|
message.message.append(Comp.At(qq=name, name=name))
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
330
|
+
# 如果mention是当前bot则移除;否则保留
|
|
331
|
+
if name.lower() == context.bot.username.lower():
|
|
332
|
+
plain_text = (
|
|
333
|
+
plain_text[: entity.offset]
|
|
334
|
+
+ plain_text[entity.offset + entity.length :]
|
|
335
|
+
)
|
|
289
336
|
|
|
290
337
|
if plain_text:
|
|
291
338
|
message.message.append(Comp.Plain(plain_text))
|
|
@@ -293,7 +340,7 @@ class TelegramPlatformAdapter(Platform):
|
|
|
293
340
|
|
|
294
341
|
if message.message_str.strip() == "/start":
|
|
295
342
|
await self.start(update, context)
|
|
296
|
-
return
|
|
343
|
+
return None
|
|
297
344
|
|
|
298
345
|
elif update.message.voice:
|
|
299
346
|
file = await update.message.voice.get_file()
|
|
@@ -327,15 +374,25 @@ class TelegramPlatformAdapter(Platform):
|
|
|
327
374
|
|
|
328
375
|
elif update.message.document:
|
|
329
376
|
file = await update.message.document.get_file()
|
|
330
|
-
message.
|
|
331
|
-
|
|
332
|
-
|
|
377
|
+
file_name = update.message.document.file_name or uuid.uuid4().hex
|
|
378
|
+
file_path = file.file_path
|
|
379
|
+
if file_path is None:
|
|
380
|
+
logger.warning(
|
|
381
|
+
f"Telegram document file_path is None, cannot save the file {file_name}.",
|
|
382
|
+
)
|
|
383
|
+
else:
|
|
384
|
+
message.message.append(Comp.File(file=file_path, name=file_name))
|
|
333
385
|
|
|
334
386
|
elif update.message.video:
|
|
335
387
|
file = await update.message.video.get_file()
|
|
336
|
-
message.
|
|
337
|
-
|
|
338
|
-
|
|
388
|
+
file_name = update.message.video.file_name or uuid.uuid4().hex
|
|
389
|
+
file_path = file.file_path
|
|
390
|
+
if file_path is None:
|
|
391
|
+
logger.warning(
|
|
392
|
+
f"Telegram video file_path is None, cannot save the file {file_name}.",
|
|
393
|
+
)
|
|
394
|
+
else:
|
|
395
|
+
message.message.append(Comp.Video(file=file_path, path=file.file_path))
|
|
339
396
|
|
|
340
397
|
return message
|
|
341
398
|
|
|
@@ -1,21 +1,37 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
import os
|
|
3
|
+
import re
|
|
4
|
+
|
|
2
5
|
import telegramify_markdown
|
|
6
|
+
from telegram import ReactionTypeCustomEmoji, ReactionTypeEmoji
|
|
7
|
+
from telegram.ext import ExtBot
|
|
8
|
+
|
|
9
|
+
from astrbot import logger
|
|
3
10
|
from astrbot.api.event import AstrMessageEvent, MessageChain
|
|
4
|
-
from astrbot.api.platform import AstrBotMessage, PlatformMetadata, MessageType
|
|
5
11
|
from astrbot.api.message_components import (
|
|
6
|
-
Plain,
|
|
7
|
-
Image,
|
|
8
|
-
Reply,
|
|
9
12
|
At,
|
|
10
13
|
File,
|
|
14
|
+
Image,
|
|
15
|
+
Plain,
|
|
11
16
|
Record,
|
|
17
|
+
Reply,
|
|
12
18
|
)
|
|
13
|
-
from
|
|
19
|
+
from astrbot.api.platform import AstrBotMessage, MessageType, PlatformMetadata
|
|
20
|
+
from astrbot.core.utils.astrbot_path import get_astrbot_data_path
|
|
14
21
|
from astrbot.core.utils.io import download_file
|
|
15
|
-
from astrbot import logger
|
|
16
22
|
|
|
17
23
|
|
|
18
24
|
class TelegramPlatformEvent(AstrMessageEvent):
|
|
25
|
+
# Telegram 的最大消息长度限制
|
|
26
|
+
MAX_MESSAGE_LENGTH = 4096
|
|
27
|
+
|
|
28
|
+
SPLIT_PATTERNS = {
|
|
29
|
+
"paragraph": re.compile(r"\n\n"),
|
|
30
|
+
"line": re.compile(r"\n"),
|
|
31
|
+
"sentence": re.compile(r"[.!?。!?]"),
|
|
32
|
+
"word": re.compile(r"\s"),
|
|
33
|
+
}
|
|
34
|
+
|
|
19
35
|
def __init__(
|
|
20
36
|
self,
|
|
21
37
|
message_str: str,
|
|
@@ -27,8 +43,38 @@ class TelegramPlatformEvent(AstrMessageEvent):
|
|
|
27
43
|
super().__init__(message_str, message_obj, platform_meta, session_id)
|
|
28
44
|
self.client = client
|
|
29
45
|
|
|
30
|
-
@
|
|
31
|
-
|
|
46
|
+
@classmethod
|
|
47
|
+
def _split_message(cls, text: str) -> list[str]:
|
|
48
|
+
if len(text) <= cls.MAX_MESSAGE_LENGTH:
|
|
49
|
+
return [text]
|
|
50
|
+
|
|
51
|
+
chunks = []
|
|
52
|
+
while text:
|
|
53
|
+
if len(text) <= cls.MAX_MESSAGE_LENGTH:
|
|
54
|
+
chunks.append(text)
|
|
55
|
+
break
|
|
56
|
+
|
|
57
|
+
split_point = cls.MAX_MESSAGE_LENGTH
|
|
58
|
+
segment = text[: cls.MAX_MESSAGE_LENGTH]
|
|
59
|
+
|
|
60
|
+
for _, pattern in cls.SPLIT_PATTERNS.items():
|
|
61
|
+
if matches := list(pattern.finditer(segment)):
|
|
62
|
+
last_match = matches[-1]
|
|
63
|
+
split_point = last_match.end()
|
|
64
|
+
break
|
|
65
|
+
|
|
66
|
+
chunks.append(text[:split_point])
|
|
67
|
+
text = text[split_point:].lstrip()
|
|
68
|
+
|
|
69
|
+
return chunks
|
|
70
|
+
|
|
71
|
+
@classmethod
|
|
72
|
+
async def send_with_client(
|
|
73
|
+
cls,
|
|
74
|
+
client: ExtBot,
|
|
75
|
+
message: MessageChain,
|
|
76
|
+
user_name: str,
|
|
77
|
+
):
|
|
32
78
|
image_path = None
|
|
33
79
|
|
|
34
80
|
has_reply = False
|
|
@@ -57,25 +103,33 @@ class TelegramPlatformEvent(AstrMessageEvent):
|
|
|
57
103
|
|
|
58
104
|
if isinstance(i, Plain):
|
|
59
105
|
if at_user_id and not at_flag:
|
|
60
|
-
i.text = f"@{at_user_id}
|
|
106
|
+
i.text = f"@{at_user_id} {i.text}"
|
|
61
107
|
at_flag = True
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
108
|
+
chunks = cls._split_message(i.text)
|
|
109
|
+
for chunk in chunks:
|
|
110
|
+
try:
|
|
111
|
+
md_text = telegramify_markdown.markdownify(
|
|
112
|
+
chunk,
|
|
113
|
+
max_line_length=None,
|
|
114
|
+
normalize_whitespace=False,
|
|
115
|
+
)
|
|
116
|
+
await client.send_message(
|
|
117
|
+
text=md_text,
|
|
118
|
+
parse_mode="MarkdownV2",
|
|
119
|
+
**payload,
|
|
120
|
+
)
|
|
121
|
+
except Exception as e:
|
|
122
|
+
logger.warning(
|
|
123
|
+
f"MarkdownV2 send failed: {e}. Using plain text instead.",
|
|
124
|
+
)
|
|
125
|
+
await client.send_message(text=chunk, **payload)
|
|
73
126
|
elif isinstance(i, Image):
|
|
74
127
|
image_path = await i.convert_to_file_path()
|
|
75
128
|
await client.send_photo(photo=image_path, **payload)
|
|
76
129
|
elif isinstance(i, File):
|
|
77
130
|
if i.file.startswith("https://"):
|
|
78
|
-
|
|
131
|
+
temp_dir = os.path.join(get_astrbot_data_path(), "temp")
|
|
132
|
+
path = os.path.join(temp_dir, i.name)
|
|
79
133
|
await download_file(i.file, path)
|
|
80
134
|
i.file = path
|
|
81
135
|
|
|
@@ -91,6 +145,38 @@ class TelegramPlatformEvent(AstrMessageEvent):
|
|
|
91
145
|
await self.send_with_client(self.client, message, self.get_sender_id())
|
|
92
146
|
await super().send(message)
|
|
93
147
|
|
|
148
|
+
async def react(self, emoji: str | None, big: bool = False):
|
|
149
|
+
"""给原消息添加 Telegram 反应:
|
|
150
|
+
- 普通 emoji:传入 '👍'、'😂' 等
|
|
151
|
+
- 自定义表情:传入其 custom_emoji_id(纯数字字符串)
|
|
152
|
+
- 取消本机器人的反应:传入 None 或空字符串
|
|
153
|
+
"""
|
|
154
|
+
try:
|
|
155
|
+
# 解析 chat_id(去掉超级群的 "#<thread_id>" 片段)
|
|
156
|
+
if self.get_message_type() == MessageType.GROUP_MESSAGE:
|
|
157
|
+
chat_id = (self.message_obj.group_id or "").split("#")[0]
|
|
158
|
+
else:
|
|
159
|
+
chat_id = self.get_sender_id()
|
|
160
|
+
|
|
161
|
+
message_id = int(self.message_obj.message_id)
|
|
162
|
+
|
|
163
|
+
# 组装 reaction 参数(必须是 ReactionType 的列表)
|
|
164
|
+
if not emoji: # 清空本 bot 的反应
|
|
165
|
+
reaction_param = [] # 空列表表示移除本 bot 的反应
|
|
166
|
+
elif emoji.isdigit(): # 自定义表情:传 custom_emoji_id
|
|
167
|
+
reaction_param = [ReactionTypeCustomEmoji(emoji)]
|
|
168
|
+
else: # 普通 emoji
|
|
169
|
+
reaction_param = [ReactionTypeEmoji(emoji)]
|
|
170
|
+
|
|
171
|
+
await self.client.set_message_reaction(
|
|
172
|
+
chat_id=chat_id,
|
|
173
|
+
message_id=message_id,
|
|
174
|
+
reaction=reaction_param, # 注意是列表
|
|
175
|
+
is_big=big, # 可选:大动画
|
|
176
|
+
)
|
|
177
|
+
except Exception as e:
|
|
178
|
+
logger.error(f"[Telegram] 添加反应失败: {e}")
|
|
179
|
+
|
|
94
180
|
async def send_streaming(self, generator, use_fallback: bool = False):
|
|
95
181
|
message_thread_id = None
|
|
96
182
|
|
|
@@ -116,6 +202,12 @@ class TelegramPlatformEvent(AstrMessageEvent):
|
|
|
116
202
|
|
|
117
203
|
async for chain in generator:
|
|
118
204
|
if isinstance(chain, MessageChain):
|
|
205
|
+
if chain.type == "break":
|
|
206
|
+
# 分割符
|
|
207
|
+
message_id = None # 重置消息 ID
|
|
208
|
+
delta = "" # 重置 delta
|
|
209
|
+
continue
|
|
210
|
+
|
|
119
211
|
# 处理消息链中的每个组件
|
|
120
212
|
for i in chain.chain:
|
|
121
213
|
if isinstance(i, Plain):
|
|
@@ -126,12 +218,15 @@ class TelegramPlatformEvent(AstrMessageEvent):
|
|
|
126
218
|
continue
|
|
127
219
|
elif isinstance(i, File):
|
|
128
220
|
if i.file.startswith("https://"):
|
|
129
|
-
|
|
221
|
+
temp_dir = os.path.join(get_astrbot_data_path(), "temp")
|
|
222
|
+
path = os.path.join(temp_dir, i.name)
|
|
130
223
|
await download_file(i.file, path)
|
|
131
224
|
i.file = path
|
|
132
225
|
|
|
133
226
|
await self.client.send_document(
|
|
134
|
-
document=i.file,
|
|
227
|
+
document=i.file,
|
|
228
|
+
filename=i.name,
|
|
229
|
+
**payload,
|
|
135
230
|
)
|
|
136
231
|
continue
|
|
137
232
|
elif isinstance(i, Record):
|
|
@@ -143,17 +238,7 @@ class TelegramPlatformEvent(AstrMessageEvent):
|
|
|
143
238
|
continue
|
|
144
239
|
|
|
145
240
|
# Plain
|
|
146
|
-
if
|
|
147
|
-
try:
|
|
148
|
-
msg = await self.client.send_message(text=delta, **payload)
|
|
149
|
-
current_content = delta
|
|
150
|
-
except Exception as e:
|
|
151
|
-
logger.warning(f"发送消息失败(streaming): {e!s}")
|
|
152
|
-
message_id = msg.message_id
|
|
153
|
-
last_edit_time = (
|
|
154
|
-
asyncio.get_event_loop().time()
|
|
155
|
-
) # 记录初始消息发送时间
|
|
156
|
-
else:
|
|
241
|
+
if message_id and len(delta) <= self.MAX_MESSAGE_LENGTH:
|
|
157
242
|
current_time = asyncio.get_event_loop().time()
|
|
158
243
|
time_since_last_edit = current_time - last_edit_time
|
|
159
244
|
|
|
@@ -172,12 +257,25 @@ class TelegramPlatformEvent(AstrMessageEvent):
|
|
|
172
257
|
last_edit_time = (
|
|
173
258
|
asyncio.get_event_loop().time()
|
|
174
259
|
) # 更新上次编辑的时间
|
|
260
|
+
else:
|
|
261
|
+
# delta 长度一般不会大于 4096,因此这里直接发送
|
|
262
|
+
try:
|
|
263
|
+
msg = await self.client.send_message(text=delta, **payload)
|
|
264
|
+
current_content = delta
|
|
265
|
+
except Exception as e:
|
|
266
|
+
logger.warning(f"发送消息失败(streaming): {e!s}")
|
|
267
|
+
message_id = msg.message_id
|
|
268
|
+
last_edit_time = (
|
|
269
|
+
asyncio.get_event_loop().time()
|
|
270
|
+
) # 记录初始消息发送时间
|
|
175
271
|
|
|
176
272
|
try:
|
|
177
273
|
if delta and current_content != delta:
|
|
178
274
|
try:
|
|
179
275
|
markdown_text = telegramify_markdown.markdownify(
|
|
180
|
-
delta,
|
|
276
|
+
delta,
|
|
277
|
+
max_line_length=None,
|
|
278
|
+
normalize_whitespace=False,
|
|
181
279
|
)
|
|
182
280
|
await self.client.edit_message_text(
|
|
183
281
|
text=markdown_text,
|
|
@@ -188,7 +286,9 @@ class TelegramPlatformEvent(AstrMessageEvent):
|
|
|
188
286
|
except Exception as e:
|
|
189
287
|
logger.warning(f"Markdown转换失败,使用普通文本: {e!s}")
|
|
190
288
|
await self.client.edit_message_text(
|
|
191
|
-
text=delta,
|
|
289
|
+
text=delta,
|
|
290
|
+
chat_id=payload["chat_id"],
|
|
291
|
+
message_id=message_id,
|
|
192
292
|
)
|
|
193
293
|
except Exception as e:
|
|
194
294
|
logger.warning(f"编辑消息失败(streaming): {e!s}")
|