Merge branch 'AstrBotDevs:master' into branch-1
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
<p align="center">
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
</p>
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ from astrbot import logger
|
||||
from typing import Union, AsyncGenerator
|
||||
from astrbot.core.platform.astr_message_event import AstrMessageEvent
|
||||
from astrbot.core.message.message_event_result import MessageEventResult, MessageChain
|
||||
from astrbot.core.message.components import At, AtAll
|
||||
from astrbot.core.message.components import At, AtAll, Reply
|
||||
from astrbot.core.star.star_handler import star_handlers_registry, EventType
|
||||
from astrbot.core.star.star import star_map
|
||||
from astrbot.core.star.filter.permission import PermissionTypeFilter
|
||||
@@ -80,11 +80,19 @@ class WakingCheckStage(Stage):
|
||||
event.message_str = event.message_str[len(wake_prefix) :].strip()
|
||||
break
|
||||
if not is_wake:
|
||||
# 检查是否有 at 消息
|
||||
# 检查是否有at消息 / at全体成员消息 / 引用了bot的消息
|
||||
for message in messages:
|
||||
if (isinstance(message, At) and (
|
||||
str(message.qq) == str(event.get_self_id())
|
||||
)) or (isinstance(message, AtAll) and not self.ignore_at_all):
|
||||
if (
|
||||
(
|
||||
isinstance(message, At)
|
||||
and (str(message.qq) == str(event.get_self_id()))
|
||||
)
|
||||
or (isinstance(message, AtAll) and not self.ignore_at_all)
|
||||
or (
|
||||
isinstance(message, Reply)
|
||||
and str(message.sender_id) == str(event.get_self_id())
|
||||
)
|
||||
):
|
||||
is_wake = True
|
||||
event.is_wake = True
|
||||
wake_prefix = ""
|
||||
|
||||
@@ -32,31 +32,31 @@ class DingtalkMessageEvent(AstrMessageEvent):
|
||||
)
|
||||
elif isinstance(segment, Comp.Image):
|
||||
markdown_str = ""
|
||||
if segment.file and segment.file.startswith("file:///"):
|
||||
logger.warning(
|
||||
"dingtalk only support url image, not: " + segment.file
|
||||
)
|
||||
continue
|
||||
elif segment.file and segment.file.startswith("http"):
|
||||
markdown_str += f"\n\n"
|
||||
elif segment.file and segment.file.startswith("base64://"):
|
||||
logger.warning("dingtalk only support url image, not base64")
|
||||
continue
|
||||
else:
|
||||
logger.warning(
|
||||
"dingtalk only support url image, not: " + segment.file
|
||||
)
|
||||
continue
|
||||
|
||||
ret = await asyncio.get_event_loop().run_in_executor(
|
||||
None,
|
||||
client.reply_markdown,
|
||||
"😄",
|
||||
markdown_str,
|
||||
self.message_obj.raw_message,
|
||||
)
|
||||
logger.debug(f"send image: {ret}")
|
||||
try:
|
||||
if not segment.file:
|
||||
logger.warning("钉钉图片 segment 缺少 file 字段,跳过")
|
||||
continue
|
||||
if segment.file.startswith(("http://", "https://")):
|
||||
image_url = segment.file
|
||||
else:
|
||||
image_url = await segment.register_to_file_service()
|
||||
|
||||
markdown_str = f"\n\n"
|
||||
|
||||
ret = await asyncio.get_event_loop().run_in_executor(
|
||||
None,
|
||||
client.reply_markdown,
|
||||
"😄",
|
||||
markdown_str,
|
||||
self.message_obj.raw_message,
|
||||
)
|
||||
logger.debug(f"send image: {ret}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"钉钉图片处理失败: {e}")
|
||||
logger.warning(f"跳过图片发送: {image_path}")
|
||||
continue
|
||||
async def send(self, message: MessageChain):
|
||||
await self.send_with_client(self.client, message)
|
||||
await super().send(message)
|
||||
|
||||
@@ -2,6 +2,7 @@ import asyncio
|
||||
import base64
|
||||
import json
|
||||
import os
|
||||
import traceback
|
||||
import time
|
||||
from typing import Optional
|
||||
|
||||
@@ -158,7 +159,6 @@ class WeChatPadProAdapter(Platform):
|
||||
os.makedirs(data_dir, exist_ok=True)
|
||||
with open(self.credentials_file, "w") as f:
|
||||
json.dump(credentials, f)
|
||||
logger.info("成功保存 WeChatPadPro 凭据。")
|
||||
except Exception as e:
|
||||
logger.error(f"保存 WeChatPadPro 凭据失败: {e}")
|
||||
|
||||
@@ -166,6 +166,8 @@ class WeChatPadProAdapter(Platform):
|
||||
"""
|
||||
检查 WeChatPadPro 设备是否在线。
|
||||
"""
|
||||
if not self.auth_key:
|
||||
return False
|
||||
url = f"{self.base_url}/login/GetLoginStatus"
|
||||
params = {"key": self.auth_key}
|
||||
|
||||
@@ -184,12 +186,16 @@ class WeChatPadProAdapter(Platform):
|
||||
logger.info("WeChatPadPro 设备不在线。")
|
||||
return False
|
||||
else:
|
||||
logger.error(f"未知的在线状态: {login_state:}")
|
||||
logger.error(f"未知的在线状态: {response_data}")
|
||||
return False
|
||||
# Code == 300 为微信退出状态。
|
||||
elif response.status == 200 and response_data.get("Code") == 300:
|
||||
logger.info("WeChatPadPro 设备已退出。")
|
||||
return False
|
||||
elif response.status == 200 and response_data.get("Code") == -2:
|
||||
# 该链接不存在
|
||||
self.auth_key = None
|
||||
return False
|
||||
else:
|
||||
logger.error(
|
||||
f"检查在线状态失败: {response.status}, {response_data}"
|
||||
@@ -201,6 +207,7 @@ class WeChatPadProAdapter(Platform):
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.error(f"检查在线状态时发生错误: {e}")
|
||||
logger.error(traceback.format_exc())
|
||||
return False
|
||||
|
||||
async def generate_auth_key(self):
|
||||
@@ -224,7 +231,7 @@ class WeChatPadProAdapter(Platform):
|
||||
and len(response_data["Data"]) > 0
|
||||
):
|
||||
self.auth_key = response_data["Data"][0]
|
||||
logger.info("成功获取授权码")
|
||||
logger.info(f"成功获取授权码 {self.auth_key[:8]}...")
|
||||
else:
|
||||
logger.error(
|
||||
f"生成授权码成功但未找到授权码: {response_data}"
|
||||
@@ -250,7 +257,6 @@ class WeChatPadProAdapter(Platform):
|
||||
try:
|
||||
async with session.post(url, params=params, json=payload) as response:
|
||||
response_data = await response.json()
|
||||
# 修正成功判断条件和数据提取路径
|
||||
if response.status == 200 and response_data.get("Code") == 200:
|
||||
# 二维码地址在 Data.QrCodeUrl 字段中
|
||||
if response_data.get("Data") and response_data["Data"].get(
|
||||
@@ -262,6 +268,13 @@ class WeChatPadProAdapter(Platform):
|
||||
f"获取登录二维码成功但未找到二维码地址: {response_data}"
|
||||
)
|
||||
return None
|
||||
elif "该 key 无效" in response_data.get("Text"):
|
||||
logger.error(
|
||||
"授权码无效,已经清除。请重新启动 AstrBot 或者本消息适配器。原因也可能是 WeChatPadPro 的 MySQL 服务没有启动成功,请检查 WeChatPadPro 服务的日志。"
|
||||
)
|
||||
self.auth_key = None
|
||||
self.save_credentials()
|
||||
return None
|
||||
else:
|
||||
logger.error(
|
||||
f"获取登录二维码失败: {response.status}, {response_data}"
|
||||
@@ -354,7 +367,7 @@ class WeChatPadProAdapter(Platform):
|
||||
while True:
|
||||
try:
|
||||
async with websockets.connect(ws_url) as websocket:
|
||||
logger.info("WebSocket 连接成功。")
|
||||
logger.debug("WebSocket 连接成功。")
|
||||
# 设置空闲超时重连
|
||||
wait_time = (
|
||||
self.active_message_poll_interval
|
||||
@@ -369,7 +382,7 @@ class WeChatPadProAdapter(Platform):
|
||||
# logger.debug(message) # 不显示原始消息内容
|
||||
asyncio.create_task(self.handle_websocket_message(message))
|
||||
except asyncio.TimeoutError:
|
||||
logger.warning(f"WebSocket 连接空闲超过 {wait_time} s")
|
||||
logger.debug(f"WebSocket 连接空闲超过 {wait_time} s")
|
||||
break
|
||||
except websockets.exceptions.ConnectionClosedOK:
|
||||
logger.info("WebSocket 连接正常关闭。")
|
||||
@@ -492,7 +505,7 @@ class WeChatPadProAdapter(Platform):
|
||||
|
||||
# 对于群聊,session_id 可以是群聊 ID 或发送者 ID + 群聊 ID (如果 unique_session 为 True)
|
||||
if self.unique_session:
|
||||
abm.session_id = f"{from_user_name}_{to_user_name}"
|
||||
abm.session_id = f"{from_user_name}#{abm.sender.user_id}"
|
||||
else:
|
||||
abm.session_id = from_user_name
|
||||
|
||||
@@ -631,7 +644,11 @@ class WeChatPadProAdapter(Platform):
|
||||
# wechatpadpro 的格式: <atuserlist>wxid</atuserlist>
|
||||
# gewechat 的格式: <atuserlist><![CDATA[wxid]]></atuserlist>
|
||||
msg_source = raw_message.get("msg_source", "")
|
||||
if f"<atuserlist>{abm.self_id}</atuserlist>" in msg_source or f"<atuserlist>{abm.self_id}," in msg_source or f",{abm.self_id}</atuserlist>" in msg_source:
|
||||
if (
|
||||
f"<atuserlist>{abm.self_id}</atuserlist>" in msg_source
|
||||
or f"<atuserlist>{abm.self_id}," in msg_source
|
||||
or f",{abm.self_id}</atuserlist>" in msg_source
|
||||
):
|
||||
at_me = True
|
||||
|
||||
# 也检查 push_content 中是否有@提示
|
||||
@@ -641,19 +658,28 @@ class WeChatPadProAdapter(Platform):
|
||||
|
||||
if at_me:
|
||||
# 被@了,在消息开头插入At组件(参考gewechat的做法)
|
||||
bot_nickname = await self._get_group_member_nickname(abm.group_id, abm.self_id)
|
||||
abm.message.insert(0, At(qq=abm.self_id, name=bot_nickname or abm.self_id))
|
||||
bot_nickname = await self._get_group_member_nickname(
|
||||
abm.group_id, abm.self_id
|
||||
)
|
||||
abm.message.insert(
|
||||
0, At(qq=abm.self_id, name=bot_nickname or abm.self_id)
|
||||
)
|
||||
|
||||
# 只有当消息内容不仅仅是@时才添加Plain组件
|
||||
if "\u2005" in message_content:
|
||||
# 检查@之后是否还有其他内容
|
||||
parts = message_content.split("\u2005")
|
||||
if len(parts) > 1 and any(part.strip() for part in parts[1:]):
|
||||
if len(parts) > 1 and any(
|
||||
part.strip() for part in parts[1:]
|
||||
):
|
||||
abm.message.append(Plain(message_content))
|
||||
else:
|
||||
# 检查是否只包含@机器人
|
||||
is_pure_at = False
|
||||
if bot_nickname and message_content.strip() == f"@{bot_nickname}":
|
||||
if (
|
||||
bot_nickname
|
||||
and message_content.strip() == f"@{bot_nickname}"
|
||||
):
|
||||
is_pure_at = True
|
||||
if not is_pure_at:
|
||||
abm.message.append(Plain(message_content))
|
||||
@@ -806,7 +832,10 @@ class WeChatPadProAdapter(Platform):
|
||||
# 根据 session_id 判断消息类型
|
||||
if "@chatroom" in session.session_id:
|
||||
dummy_message_obj.type = MessageType.GROUP_MESSAGE
|
||||
dummy_message_obj.group_id = session.session_id
|
||||
if "#" in session.session_id:
|
||||
dummy_message_obj.group_id = session.session_id.split("#")[0]
|
||||
else:
|
||||
dummy_message_obj.group_id = session.session_id
|
||||
dummy_message_obj.sender = MessageMember(user_id="", nickname="")
|
||||
else:
|
||||
dummy_message_obj.type = MessageType.FRIEND_MESSAGE
|
||||
|
||||
@@ -81,12 +81,16 @@ class WeChatPadProMessageEvent(AstrMessageEvent):
|
||||
# logger.info(f"已添加 @ 信息: {message_text}")
|
||||
else:
|
||||
message_text = text
|
||||
if self.get_group_id() and "#" in self.session_id:
|
||||
session_id = self.session_id.split("#")[0]
|
||||
else:
|
||||
session_id = self.session_id
|
||||
payload = {
|
||||
"MsgItem": [
|
||||
{
|
||||
"MsgType": 1,
|
||||
"TextContent": message_text,
|
||||
"ToUserName": self.session_id,
|
||||
"ToUserName": session_id,
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import logging
|
||||
import asyncio
|
||||
import sys
|
||||
|
||||
logger = logging.getLogger("astrbot")
|
||||
|
||||
@@ -31,7 +32,10 @@ class PipInstaller:
|
||||
logger.info(f"Pip 包管理器: pip {' '.join(args)}")
|
||||
try:
|
||||
process = await asyncio.create_subprocess_exec(
|
||||
"pip", *args,
|
||||
sys.executable,
|
||||
"-m",
|
||||
"pip",
|
||||
*args,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.STDOUT,
|
||||
)
|
||||
@@ -47,6 +51,7 @@ class PipInstaller:
|
||||
except FileNotFoundError:
|
||||
# 没有 pip
|
||||
from pip import main as pip_main
|
||||
|
||||
result_code = await asyncio.to_thread(pip_main, args)
|
||||
|
||||
# 清除 pip.main 导致的多余的 logging handlers
|
||||
|
||||
Reference in New Issue
Block a user