feat: 添加消息大小限制和消息发送处理
- 在 message_sending.py 中引入了 95MB 的最大消息大小限制,以防止连接中断。 - 为超出大小限制的消息添加了调试和错误日志记录。 - 增强了 meta_event_handler.py 中的心跳处理,提供了更详细的机器人状态日志。 - 重构了 notice_handler.py 中的表情符号处理,使用集中式 qq_face 映射代替硬编码值。 - 更新了 qq_emoji_list.py 以修正格式并添加新的表情符号映射。 - 改进了 main_send_handler.py 中的命令处理,以便向平台发送结构化响应。 - 扩展了 send_command_handler.py 中的命令处理,添加了用于设置组名和管理组成员的新命令。 - 增强了 send_message_handler.py 中的文件消息处理,以支持文件路径和详细的文件信息。pull/76/head
parent
616ab2b9d6
commit
7b6b0d9593
432
command_args.md
432
command_args.md
|
|
@ -1,8 +1,28 @@
|
|||
# Command Arguments
|
||||
|
||||
```python
|
||||
Seg.type = "command"
|
||||
```
|
||||
## 群聊禁言
|
||||
|
||||
所有命令执行后都会通过自定义消息类型 `command_response` 返回响应,格式如下:
|
||||
|
||||
```python
|
||||
{
|
||||
"command_name": "命令名称",
|
||||
"success": True/False, # 是否执行成功
|
||||
"timestamp": 1234567890.123, # 时间戳
|
||||
"data": {...}, # 返回数据(成功时)
|
||||
"error": "错误信息" # 错误信息(失败时)
|
||||
}
|
||||
```
|
||||
|
||||
插件需要注册 `command_response` 自定义消息处理器来接收命令响应。
|
||||
|
||||
---
|
||||
|
||||
## 操作类命令
|
||||
|
||||
### 群聊禁言
|
||||
```python
|
||||
Seg.data: Dict[str, Any] = {
|
||||
"name": "GROUP_BAN",
|
||||
|
|
@ -15,7 +35,8 @@ Seg.data: Dict[str, Any] = {
|
|||
其中,群聊ID将会通过Group_Info.group_id自动获取。
|
||||
|
||||
**当`duration`为 0 时相当于解除禁言。**
|
||||
## 群聊全体禁言
|
||||
|
||||
### 群聊全体禁言
|
||||
```python
|
||||
Seg.data: Dict[str, Any] = {
|
||||
"name": "GROUP_WHOLE_BAN",
|
||||
|
|
@ -27,18 +48,36 @@ Seg.data: Dict[str, Any] = {
|
|||
其中,群聊ID将会通过Group_Info.group_id自动获取。
|
||||
|
||||
`enable`的参数需要为boolean类型,True表示开启全体禁言,False表示关闭全体禁言。
|
||||
## 群聊踢人
|
||||
|
||||
### 群聊踢人
|
||||
将指定成员从群聊中踢出,可选拉黑。
|
||||
|
||||
```python
|
||||
Seg.data: Dict[str, Any] = {
|
||||
"name": "GROUP_KICK",
|
||||
"args": {
|
||||
"qq_id": "用户QQ号",
|
||||
"group_id": 123456789, # 可选,如果在群聊上下文中可从 group_info 自动获取
|
||||
"user_id": 12345678, # 必需,用户QQ号
|
||||
"reject_add_request": False # 可选,是否群拉黑,默认 False
|
||||
},
|
||||
}
|
||||
```
|
||||
其中,群聊ID将会通过Group_Info.group_id自动获取。
|
||||
|
||||
## 戳一戳
|
||||
### 批量踢出群成员
|
||||
批量将多个成员从群聊中踢出,可选拉黑。
|
||||
|
||||
```python
|
||||
Seg.data: Dict[str, Any] = {
|
||||
"name": "GROUP_KICK_MEMBERS",
|
||||
"args": {
|
||||
"group_id": 123456789, # 可选,如果在群聊上下文中可从 group_info 自动获取
|
||||
"user_id": [12345678, 87654321], # 必需,用户QQ号数组
|
||||
"reject_add_request": False # 可选,是否群拉黑,默认 False
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### 戳一戳
|
||||
```python
|
||||
Seg.data: Dict[str, Any] = {
|
||||
"name": "SEND_POKE",
|
||||
|
|
@ -48,7 +87,7 @@ Seg.data: Dict[str, Any] = {
|
|||
}
|
||||
```
|
||||
|
||||
## 撤回消息
|
||||
### 撤回消息
|
||||
```python
|
||||
Seg.data: Dict[str, Any] = {
|
||||
"name": "DELETE_MSG",
|
||||
|
|
@ -57,4 +96,381 @@ Seg.data: Dict[str, Any] = {
|
|||
}
|
||||
}
|
||||
```
|
||||
其中message_id是消息的实际qq_id,于新版的mmc中可以从数据库获取(如果工作正常的话)
|
||||
其中message_id是消息的实际qq_id,于新版的mmc中可以从数据库获取(如果工作正常的话)
|
||||
|
||||
### 给消息贴表情
|
||||
```python
|
||||
Seg.data: Dict[str, Any] = {
|
||||
"name": "MESSAGE_LIKE",
|
||||
"args": {
|
||||
"message_id": "消息ID",
|
||||
"emoji_id": "表情ID"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 设置群名
|
||||
设置指定群的群名称。
|
||||
|
||||
```python
|
||||
Seg.data: Dict[str, Any] = {
|
||||
"name": "SET_GROUP_NAME",
|
||||
"args": {
|
||||
"group_id": 123456789, # 可选,如果在群聊上下文中可从 group_info 自动获取
|
||||
"group_name": "新群名" # 必需,新的群名称
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 设置账号信息
|
||||
设置Bot自己的QQ账号资料。
|
||||
|
||||
```python
|
||||
Seg.data: Dict[str, Any] = {
|
||||
"name": "SET_QQ_PROFILE",
|
||||
"args": {
|
||||
"nickname": "新昵称", # 必需,昵称
|
||||
"personal_note": "个性签名", # 可选,个性签名
|
||||
"sex": "male" # 可选,性别:"male" | "female" | "unknown"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**返回数据示例:**
|
||||
```python
|
||||
{
|
||||
"result": 0, # 结果码,0为成功
|
||||
"errMsg": "" # 错误信息
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 查询类命令
|
||||
|
||||
### 获取登录号信息
|
||||
获取Bot自身的账号信息。
|
||||
|
||||
```python
|
||||
Seg.data: Dict[str, Any] = {
|
||||
"name": "GET_LOGIN_INFO",
|
||||
"args": {}
|
||||
}
|
||||
```
|
||||
|
||||
**返回数据示例:**
|
||||
```python
|
||||
{
|
||||
"user_id": 12345678,
|
||||
"nickname": "Bot昵称"
|
||||
}
|
||||
```
|
||||
|
||||
### 获取陌生人信息
|
||||
```python
|
||||
Seg.data: Dict[str, Any] = {
|
||||
"name": "GET_STRANGER_INFO",
|
||||
"args": {
|
||||
"user_id": "用户QQ号"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**返回数据示例:**
|
||||
```python
|
||||
{
|
||||
"user_id": 12345678,
|
||||
"nickname": "用户昵称",
|
||||
"sex": "male/female/unknown",
|
||||
"age": 0
|
||||
}
|
||||
```
|
||||
|
||||
### 获取好友列表
|
||||
获取Bot的好友列表。
|
||||
|
||||
```python
|
||||
Seg.data: Dict[str, Any] = {
|
||||
"name": "GET_FRIEND_LIST",
|
||||
"args": {
|
||||
"no_cache": False # 可选,是否不使用缓存,默认 False
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**返回数据示例:**
|
||||
```python
|
||||
[
|
||||
{
|
||||
"user_id": 12345678,
|
||||
"nickname": "好友昵称",
|
||||
"remark": "备注名",
|
||||
"sex": "male", # "male" | "female" | "unknown"
|
||||
"age": 18,
|
||||
"qid": "QID字符串",
|
||||
"level": 64,
|
||||
"login_days": 365,
|
||||
"birthday_year": 2000,
|
||||
"birthday_month": 1,
|
||||
"birthday_day": 1,
|
||||
"phone_num": "电话号码",
|
||||
"email": "邮箱",
|
||||
"category_id": 0, # 分组ID
|
||||
"categoryName": "我的好友", # 分组名称
|
||||
"categoryId": 0
|
||||
},
|
||||
...
|
||||
]
|
||||
```
|
||||
|
||||
### 获取群信息
|
||||
获取指定群的详细信息。
|
||||
|
||||
```python
|
||||
Seg.data: Dict[str, Any] = {
|
||||
"name": "GET_GROUP_INFO",
|
||||
"args": {
|
||||
"group_id": 123456789 # 可选,如果在群聊上下文中可从 group_info 自动获取
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**返回数据示例:**
|
||||
```python
|
||||
{
|
||||
"group_id": "123456789", # 群号(字符串)
|
||||
"group_name": "群名称",
|
||||
"group_remark": "群备注",
|
||||
"group_all_shut": 0, # 群全员禁言状态(0=未禁言)
|
||||
"member_count": 100, # 当前成员数量
|
||||
"max_member_count": 500 # 最大成员数量
|
||||
}
|
||||
```
|
||||
|
||||
### 获取群详细信息
|
||||
获取指定群的详细信息(与 GET_GROUP_INFO 类似,可能提供更实时的数据)。
|
||||
|
||||
```python
|
||||
Seg.data: Dict[str, Any] = {
|
||||
"name": "GET_GROUP_DETAIL_INFO",
|
||||
"args": {
|
||||
"group_id": 123456789 # 可选,如果在群聊上下文中可从 group_info 自动获取
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**返回数据示例:**
|
||||
```python
|
||||
{
|
||||
"group_id": 123456789, # 群号(数字)
|
||||
"group_name": "群名称",
|
||||
"group_remark": "群备注",
|
||||
"group_all_shut": 0, # 群全员禁言状态(0=未禁言)
|
||||
"member_count": 100, # 当前成员数量
|
||||
"max_member_count": 500 # 最大成员数量
|
||||
}
|
||||
```
|
||||
|
||||
### 获取群列表
|
||||
获取Bot加入的所有群列表。
|
||||
|
||||
```python
|
||||
Seg.data: Dict[str, Any] = {
|
||||
"name": "GET_GROUP_LIST",
|
||||
"args": {
|
||||
"no_cache": False # 可选,是否不使用缓存,默认 False
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**返回数据示例:**
|
||||
```python
|
||||
[
|
||||
{
|
||||
"group_id": "123456789", # 群号(字符串)
|
||||
"group_name": "群名称",
|
||||
"group_remark": "群备注",
|
||||
"group_all_shut": 0, # 群全员禁言状态
|
||||
"member_count": 100, # 当前成员数量
|
||||
"max_member_count": 500 # 最大成员数量
|
||||
},
|
||||
...
|
||||
]
|
||||
```
|
||||
|
||||
### 获取群@全体成员剩余次数
|
||||
查询指定群的@全体成员剩余使用次数。
|
||||
|
||||
```python
|
||||
Seg.data: Dict[str, Any] = {
|
||||
"name": "GET_GROUP_AT_ALL_REMAIN",
|
||||
"args": {
|
||||
"group_id": 123456789 # 可选,如果在群聊上下文中可从 group_info 自动获取
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**返回数据示例:**
|
||||
```python
|
||||
{
|
||||
"can_at_all": True, # 是否可以@全体成员
|
||||
"remain_at_all_count_for_group": 10, # 群剩余@全体成员次数
|
||||
"remain_at_all_count_for_uin": 5 # Bot剩余@全体成员次数
|
||||
}
|
||||
```
|
||||
|
||||
### 获取群成员信息
|
||||
获取指定群成员的详细信息。
|
||||
|
||||
```python
|
||||
Seg.data: Dict[str, Any] = {
|
||||
"name": "GET_GROUP_MEMBER_INFO",
|
||||
"args": {
|
||||
"group_id": 123456789, # 可选,如果在群聊上下文中可从 group_info 自动获取
|
||||
"user_id": 12345678, # 必需,用户QQ号
|
||||
"no_cache": False # 可选,是否不使用缓存,默认 False
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**返回数据示例:**
|
||||
```python
|
||||
{
|
||||
"group_id": 123456789,
|
||||
"user_id": 12345678,
|
||||
"nickname": "昵称",
|
||||
"card": "群名片",
|
||||
"sex": "male", # "male" | "female" | "unknown"
|
||||
"age": 18,
|
||||
"join_time": 1234567890, # 加群时间戳
|
||||
"last_sent_time": 1234567890, # 最后发言时间戳
|
||||
"level": 1, # 群等级
|
||||
"qq_level": 64, # QQ等级
|
||||
"role": "member", # "owner" | "admin" | "member"
|
||||
"title": "专属头衔",
|
||||
"area": "地区",
|
||||
"unfriendly": False, # 是否不友好
|
||||
"title_expire_time": 1234567890, # 头衔过期时间
|
||||
"card_changeable": True, # 名片是否可修改
|
||||
"shut_up_timestamp": 0, # 禁言时间戳
|
||||
"is_robot": False, # 是否机器人
|
||||
"qage": "10年" # Q龄
|
||||
}
|
||||
```
|
||||
|
||||
### 获取群成员列表
|
||||
获取指定群的所有成员列表。
|
||||
|
||||
```python
|
||||
Seg.data: Dict[str, Any] = {
|
||||
"name": "GET_GROUP_MEMBER_LIST",
|
||||
"args": {
|
||||
"group_id": 123456789, # 可选,如果在群聊上下文中可从 group_info 自动获取
|
||||
"no_cache": False # 可选,是否不使用缓存,默认 False
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**返回数据示例:**
|
||||
```python
|
||||
[
|
||||
{
|
||||
"group_id": 123456789,
|
||||
"user_id": 12345678,
|
||||
"nickname": "昵称",
|
||||
"card": "群名片",
|
||||
"sex": "male", # "male" | "female" | "unknown"
|
||||
"age": 18,
|
||||
"join_time": 1234567890,
|
||||
"last_sent_time": 1234567890,
|
||||
"level": 1,
|
||||
"qq_level": 64,
|
||||
"role": "member", # "owner" | "admin" | "member"
|
||||
"title": "专属头衔",
|
||||
"area": "地区",
|
||||
"unfriendly": False,
|
||||
"title_expire_time": 1234567890,
|
||||
"card_changeable": True,
|
||||
"shut_up_timestamp": 0,
|
||||
"is_robot": False,
|
||||
"qage": "10年"
|
||||
},
|
||||
...
|
||||
]
|
||||
```
|
||||
|
||||
### 获取消息详情
|
||||
获取指定消息的完整详情信息。
|
||||
|
||||
```python
|
||||
Seg.data: Dict[str, Any] = {
|
||||
"name": "GET_MSG",
|
||||
"args": {
|
||||
"message_id": 123456 # 必需,消息ID
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**返回数据示例:**
|
||||
```python
|
||||
{
|
||||
"self_id": 12345678, # Bot自身ID
|
||||
"user_id": 87654321, # 发送者ID
|
||||
"time": 1234567890, # 时间戳
|
||||
"message_id": 123456, # 消息ID
|
||||
"message_seq": 123456, # 消息序列号
|
||||
"real_id": 123456, # 真实消息ID
|
||||
"real_seq": "123456", # 真实序列号(字符串)
|
||||
"message_type": "group", # "private" | "group"
|
||||
"sub_type": "normal", # 子类型
|
||||
"message_format": "array", # 消息格式
|
||||
"post_type": "message", # 事件类型
|
||||
"group_id": 123456789, # 群号(群消息时存在)
|
||||
"sender": {
|
||||
"user_id": 87654321,
|
||||
"nickname": "昵称",
|
||||
"sex": "male", # "male" | "female" | "unknown"
|
||||
"age": 18,
|
||||
"card": "群名片", # 群消息时存在
|
||||
"level": "1", # 群等级(字符串)
|
||||
"role": "member" # "owner" | "admin" | "member"
|
||||
},
|
||||
"message": [...], # 消息段数组
|
||||
"raw_message": "消息文本内容", # 原始消息文本
|
||||
"font": 0 # 字体
|
||||
}
|
||||
```
|
||||
|
||||
### 获取合并转发消息
|
||||
获取合并转发消息的所有子消息内容。
|
||||
|
||||
```python
|
||||
Seg.data: Dict[str, Any] = {
|
||||
"name": "GET_FORWARD_MSG",
|
||||
"args": {
|
||||
"message_id": "7123456789012345678" # 必需,合并转发消息ID(字符串)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**返回数据示例:**
|
||||
```python
|
||||
{
|
||||
"messages": [
|
||||
{
|
||||
"sender": {
|
||||
"user_id": 87654321,
|
||||
"nickname": "昵称",
|
||||
"sex": "male",
|
||||
"age": 18,
|
||||
"card": "群名片",
|
||||
"level": "1",
|
||||
"role": "member"
|
||||
},
|
||||
"time": 1234567890,
|
||||
"message": [...] # 消息段数组
|
||||
},
|
||||
...
|
||||
]
|
||||
}
|
||||
```
|
||||
|
|
@ -7,13 +7,30 @@ from .logger import logger
|
|||
class CommandType(Enum):
|
||||
"""命令类型"""
|
||||
|
||||
# 操作类命令
|
||||
GROUP_BAN = "set_group_ban" # 禁言用户
|
||||
GROUP_WHOLE_BAN = "set_group_whole_ban" # 群全体禁言
|
||||
GROUP_KICK = "set_group_kick" # 踢出群聊
|
||||
GROUP_KICK_MEMBERS = "set_group_kick_members" # 批量踢出群成员
|
||||
SET_GROUP_NAME = "set_group_name" # 设置群名
|
||||
SEND_POKE = "send_poke" # 戳一戳
|
||||
DELETE_MSG = "delete_msg" # 撤回消息
|
||||
AI_VOICE_SEND = "send_group_ai_record" # 发送群AI语音
|
||||
MESSAGE_LIKE = "message_like" # 给消息贴表情
|
||||
SET_QQ_PROFILE = "set_qq_profile" # 设置账号信息
|
||||
|
||||
# 查询类命令
|
||||
GET_LOGIN_INFO = "get_login_info" # 获取登录号信息
|
||||
GET_STRANGER_INFO = "get_stranger_info" # 获取陌生人信息
|
||||
GET_FRIEND_LIST = "get_friend_list" # 获取好友列表
|
||||
GET_GROUP_INFO = "get_group_info" # 获取群信息
|
||||
GET_GROUP_DETAIL_INFO = "get_group_detail_info" # 获取群详细信息
|
||||
GET_GROUP_LIST = "get_group_list" # 获取群列表
|
||||
GET_GROUP_AT_ALL_REMAIN = "get_group_at_all_remain" # 获取群@全体成员剩余次数
|
||||
GET_GROUP_MEMBER_INFO = "get_group_member_info" # 获取群成员信息
|
||||
GET_GROUP_MEMBER_LIST = "get_group_member_list" # 获取群成员列表
|
||||
GET_MSG = "get_msg" # 获取消息
|
||||
GET_FORWARD_MSG = "get_forward_msg" # 获取合并转发消息
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.value
|
||||
|
|
|
|||
|
|
@ -307,9 +307,9 @@ class MessageHandler:
|
|||
else:
|
||||
logger.warning("video处理失败")
|
||||
case RealMessageType.json:
|
||||
ret_seg = await self.handle_json_message(sub_message)
|
||||
if ret_seg:
|
||||
seg_message.append(ret_seg)
|
||||
ret_segs = await self.handle_json_message(sub_message)
|
||||
if ret_segs:
|
||||
seg_message.extend(ret_segs)
|
||||
else:
|
||||
logger.warning("json处理失败")
|
||||
case RealMessageType.file:
|
||||
|
|
@ -486,13 +486,13 @@ class MessageHandler:
|
|||
"url": url
|
||||
})
|
||||
|
||||
async def handle_json_message(self, raw_message: dict) -> Seg | None:
|
||||
async def handle_json_message(self, raw_message: dict) -> List[Seg] | None:
|
||||
"""
|
||||
处理JSON卡片消息(小程序、分享、群公告等)
|
||||
Parameters:
|
||||
raw_message: dict: 原始消息
|
||||
Returns:
|
||||
seg_data: Seg: 处理后的消息段
|
||||
seg_data: List[Seg]: 处理后的消息段列表(可能包含文本和图片)
|
||||
"""
|
||||
message_data: dict = raw_message.get("data")
|
||||
json_data: str = message_data.get("data")
|
||||
|
|
@ -505,90 +505,241 @@ class MessageHandler:
|
|||
# 尝试解析JSON获取详细信息
|
||||
parsed_json = json.loads(json_data)
|
||||
app = parsed_json.get("app", "")
|
||||
meta = parsed_json.get("meta", {})
|
||||
|
||||
# 检查是否为群公告
|
||||
# 群公告(由于图片URL是加密的,因此无法读取)
|
||||
if app == "com.tencent.mannounce":
|
||||
meta = parsed_json.get("meta", {})
|
||||
mannounce = meta.get("mannounce", {})
|
||||
title_encoded = mannounce.get("title", "")
|
||||
text_encoded = mannounce.get("text", "")
|
||||
|
||||
# 解码Base64编码的标题和内容
|
||||
title = ""
|
||||
text = ""
|
||||
try:
|
||||
if title_encoded:
|
||||
title = base64.b64decode(title_encoded).decode("utf-8")
|
||||
if text_encoded:
|
||||
text = base64.b64decode(text_encoded).decode("utf-8")
|
||||
except Exception as e:
|
||||
logger.warning(f"群公告Base64解码失败: {e}")
|
||||
# 降级使用原始值
|
||||
title = title_encoded
|
||||
text = text_encoded
|
||||
|
||||
# 构建群公告文本
|
||||
announce_text = "[群公告]"
|
||||
if title:
|
||||
announce_text += f"\n标题: {title}"
|
||||
if text:
|
||||
announce_text += f"\n内容: {text}"
|
||||
|
||||
return Seg(type="text", data=announce_text)
|
||||
title = mannounce.get("title", "")
|
||||
text = mannounce.get("text", "")
|
||||
encode_flag = mannounce.get("encode", 0)
|
||||
if encode_flag == 1:
|
||||
try:
|
||||
if title:
|
||||
title = base64.b64decode(title).decode("utf-8", errors="ignore")
|
||||
if text:
|
||||
text = base64.b64decode(text).decode("utf-8", errors="ignore")
|
||||
except Exception as e:
|
||||
logger.warning(f"群公告Base64解码失败: {e}")
|
||||
if title and text:
|
||||
content = f"[{title}]:{text}"
|
||||
elif title:
|
||||
content = f"[{title}]"
|
||||
elif text:
|
||||
content = f"{text}"
|
||||
else:
|
||||
content = "[群公告]"
|
||||
return [Seg(type="text", data=content)]
|
||||
|
||||
# 检查是否为音乐卡片
|
||||
# 音乐卡片
|
||||
if app in ("com.tencent.music.lua", "com.tencent.structmsg"):
|
||||
meta = parsed_json.get("meta", {})
|
||||
music = meta.get("music", {})
|
||||
|
||||
# 尝试从music字段提取信息
|
||||
if music:
|
||||
title = music.get("title", "")
|
||||
singer = music.get("desc", "") or music.get("singer", "")
|
||||
jump_url = music.get("jumpUrl", "") or music.get("jump_url", "")
|
||||
music_url = music.get("musicUrl", "") or music.get("music_url", "")
|
||||
tag = music.get("tag", "") # 音乐来源标签,如"网易云音乐"
|
||||
preview = music.get("preview", "") # 封面图URL
|
||||
tag = music.get("tag", "")
|
||||
preview = music.get("preview", "")
|
||||
|
||||
# 返回结构化的音乐卡片数据
|
||||
return Seg(type="music_card", data={
|
||||
return [Seg(type="music_card", data={
|
||||
"title": title,
|
||||
"singer": singer,
|
||||
"jump_url": jump_url,
|
||||
"music_url": music_url,
|
||||
"tag": tag,
|
||||
"preview": preview
|
||||
})
|
||||
})]
|
||||
|
||||
# 检查是否为小程序分享(如B站视频分享)
|
||||
# QQ小程序分享(含预览图)
|
||||
if app == "com.tencent.miniapp_01":
|
||||
meta = parsed_json.get("meta", {})
|
||||
detail = meta.get("detail_1", {})
|
||||
|
||||
if detail:
|
||||
title = detail.get("title", "") # 小程序名称,如"哔哩哔哩"
|
||||
desc = detail.get("desc", "") # 分享内容描述
|
||||
url = detail.get("url", "") # 小程序链接
|
||||
qqdocurl = detail.get("qqdocurl", "") # 原始链接(如B站链接)
|
||||
preview = detail.get("preview", "") # 预览图
|
||||
icon = detail.get("icon", "") # 小程序图标
|
||||
title = detail.get("title", "")
|
||||
desc = detail.get("desc", "")
|
||||
url = detail.get("url", "")
|
||||
qqdocurl = detail.get("qqdocurl", "")
|
||||
preview_url = detail.get("preview", "")
|
||||
icon = detail.get("icon", "")
|
||||
|
||||
# 返回结构化的小程序卡片数据
|
||||
return Seg(type="miniapp_card", data={
|
||||
seg_list = [Seg(type="miniapp_card", data={
|
||||
"title": title,
|
||||
"desc": desc,
|
||||
"url": url,
|
||||
"source_url": qqdocurl,
|
||||
"preview": preview,
|
||||
"preview": preview_url,
|
||||
"icon": icon
|
||||
})
|
||||
})]
|
||||
|
||||
# 下载预览图
|
||||
if preview_url:
|
||||
try:
|
||||
image_base64 = await get_image_base64(preview_url)
|
||||
seg_list.append(Seg(type="image", data=image_base64))
|
||||
except Exception as e:
|
||||
logger.error(f"QQ小程序预览图下载失败: {e}")
|
||||
|
||||
return seg_list
|
||||
|
||||
# 礼物消息
|
||||
if app == "com.tencent.giftmall.giftark":
|
||||
giftark = meta.get("giftark", {})
|
||||
if giftark:
|
||||
gift_name = giftark.get("title", "礼物")
|
||||
desc = giftark.get("desc", "")
|
||||
gift_text = f"[赠送礼物: {gift_name}]"
|
||||
if desc:
|
||||
gift_text += f"\n{desc}"
|
||||
return [Seg(type="text", data=gift_text)]
|
||||
|
||||
# 推荐联系人
|
||||
if app == "com.tencent.contact.lua":
|
||||
contact_info = meta.get("contact", {})
|
||||
name = contact_info.get("nickname", "未知联系人")
|
||||
tag = contact_info.get("tag", "推荐联系人")
|
||||
return [Seg(type="text", data=f"[{tag}] {name}")]
|
||||
|
||||
# 推荐群聊
|
||||
if app == "com.tencent.troopsharecard":
|
||||
contact_info = meta.get("contact", {})
|
||||
name = contact_info.get("nickname", "未知群聊")
|
||||
tag = contact_info.get("tag", "推荐群聊")
|
||||
return [Seg(type="text", data=f"[{tag}] {name}")]
|
||||
|
||||
# 图文分享(如 哔哩哔哩HD、网页、群精华等)
|
||||
if app == "com.tencent.tuwen.lua":
|
||||
news = meta.get("news", {})
|
||||
title = news.get("title", "未知标题")
|
||||
desc = (news.get("desc", "") or "").replace("[图片]", "").strip()
|
||||
tag = news.get("tag", "图文分享")
|
||||
preview_url = news.get("preview", "")
|
||||
if tag and title and tag in title:
|
||||
title = title.replace(tag, "", 1).strip(":: -— ")
|
||||
text_content = f"[{tag}] {title}:{desc}"
|
||||
seg_list = [Seg(type="text", data=text_content)]
|
||||
|
||||
# 下载预览图
|
||||
if preview_url:
|
||||
try:
|
||||
image_base64 = await get_image_base64(preview_url)
|
||||
seg_list.append(Seg(type="image", data=image_base64))
|
||||
except Exception as e:
|
||||
logger.error(f"图文预览图下载失败: {e}")
|
||||
|
||||
return seg_list
|
||||
|
||||
# 群相册(含预览图)
|
||||
if app == "com.tencent.feed.lua":
|
||||
feed = meta.get("feed", {})
|
||||
title = feed.get("title", "群相册")
|
||||
tag = feed.get("tagName", "群相册")
|
||||
desc = feed.get("forwardMessage", "")
|
||||
cover_url = feed.get("cover", "")
|
||||
if tag and title and tag in title:
|
||||
title = title.replace(tag, "", 1).strip(":: -— ")
|
||||
text_content = f"[{tag}] {title}:{desc}"
|
||||
seg_list = [Seg(type="text", data=text_content)]
|
||||
|
||||
# 下载封面图
|
||||
if cover_url:
|
||||
try:
|
||||
image_base64 = await get_image_base64(cover_url)
|
||||
seg_list.append(Seg(type="image", data=image_base64))
|
||||
except Exception as e:
|
||||
logger.error(f"群相册封面下载失败: {e}")
|
||||
|
||||
return seg_list
|
||||
|
||||
# QQ收藏分享(含预览图)
|
||||
if app == "com.tencent.template.qqfavorite.share":
|
||||
news = meta.get("news", {})
|
||||
desc = news.get("desc", "").replace("[图片]", "").strip()
|
||||
tag = news.get("tag", "QQ收藏")
|
||||
preview_url = news.get("preview", "")
|
||||
seg_list = [Seg(type="text", data=f"[{tag}] {desc}")]
|
||||
|
||||
# 下载预览图
|
||||
if preview_url:
|
||||
try:
|
||||
image_base64 = await get_image_base64(preview_url)
|
||||
seg_list.append(Seg(type="image", data=image_base64))
|
||||
except Exception as e:
|
||||
logger.error(f"QQ收藏预览图下载失败: {e}")
|
||||
|
||||
return seg_list
|
||||
|
||||
# QQ空间分享(含预览图)
|
||||
if app == "com.tencent.miniapp.lua":
|
||||
miniapp = meta.get("miniapp", {})
|
||||
title = miniapp.get("title", "未知标题")
|
||||
tag = miniapp.get("tag", "QQ空间")
|
||||
preview_url = miniapp.get("preview", "")
|
||||
seg_list = [Seg(type="text", data=f"[{tag}] {title}")]
|
||||
|
||||
# 下载预览图
|
||||
if preview_url:
|
||||
try:
|
||||
image_base64 = await get_image_base64(preview_url)
|
||||
seg_list.append(Seg(type="image", data=image_base64))
|
||||
except Exception as e:
|
||||
logger.error(f"QQ空间预览图下载失败: {e}")
|
||||
|
||||
return seg_list
|
||||
|
||||
# QQ频道分享(含预览图)
|
||||
if app == "com.tencent.forum":
|
||||
detail = meta.get("detail") if isinstance(meta, dict) else None
|
||||
if detail:
|
||||
feed = detail.get("feed", {})
|
||||
poster = detail.get("poster", {})
|
||||
channel_info = detail.get("channel_info", {})
|
||||
guild_name = channel_info.get("guild_name", "")
|
||||
nick = poster.get("nick", "QQ用户")
|
||||
title = feed.get("title", {}).get("contents", [{}])[0].get("text_content", {}).get("text", "帖子")
|
||||
face_content = ""
|
||||
for item in feed.get("contents", {}).get("contents", []):
|
||||
emoji = item.get("emoji_content")
|
||||
if emoji:
|
||||
eid = emoji.get("id")
|
||||
if eid in qq_face:
|
||||
face_content += qq_face.get(eid, "")
|
||||
|
||||
seg_list = [Seg(type="text", data=f"[频道帖子] [{guild_name}]{nick}:{title}{face_content}")]
|
||||
|
||||
# 下载帖子中的图片
|
||||
pic_urls = [img.get("pic_url") for img in feed.get("images", []) if img.get("pic_url")]
|
||||
for pic_url in pic_urls:
|
||||
try:
|
||||
image_base64 = await get_image_base64(pic_url)
|
||||
seg_list.append(Seg(type="image", data=image_base64))
|
||||
except Exception as e:
|
||||
logger.error(f"QQ频道图片下载失败: {e}")
|
||||
|
||||
return seg_list
|
||||
|
||||
# QQ地图位置分享
|
||||
if app == "com.tencent.map":
|
||||
location = meta.get("Location.Search", {})
|
||||
name = location.get("name", "未知地点")
|
||||
address = location.get("address", "")
|
||||
return [Seg(type="text", data=f"[位置] {address} · {name}")]
|
||||
|
||||
# QQ一起听歌
|
||||
if app == "com.tencent.together":
|
||||
invite = (meta or {}).get("invite", {})
|
||||
title = invite.get("title") or "一起听歌"
|
||||
summary = invite.get("summary") or ""
|
||||
return [Seg(type="text", data=f"[{title}] {summary}")]
|
||||
|
||||
# 其他卡片消息使用prompt字段
|
||||
prompt = parsed_json.get("prompt", "[卡片消息]")
|
||||
return Seg(type="text", data=prompt)
|
||||
return [Seg(type="text", data=prompt)]
|
||||
except json.JSONDecodeError:
|
||||
logger.warning("JSON消息解析失败")
|
||||
return Seg(type="text", data="[卡片消息]")
|
||||
return [Seg(type="text", data="[卡片消息]")]
|
||||
except Exception as e:
|
||||
logger.error(f"JSON消息处理异常: {e}")
|
||||
return [Seg(type="text", data="[卡片消息]")]
|
||||
|
||||
async def handle_file_message(self, raw_message: dict) -> Seg | None:
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -4,6 +4,13 @@ from src.logger import logger
|
|||
from maim_message import MessageBase, Router
|
||||
|
||||
|
||||
# 消息大小限制 (字节)
|
||||
# WebSocket 服务端限制为 100MB,这里设置 95MB 留一点余量
|
||||
MAX_MESSAGE_SIZE_BYTES = 95 * 1024 * 1024 # 95MB
|
||||
MAX_MESSAGE_SIZE_KB = MAX_MESSAGE_SIZE_BYTES / 1024
|
||||
MAX_MESSAGE_SIZE_MB = MAX_MESSAGE_SIZE_KB / 1024
|
||||
|
||||
|
||||
class MessageSending:
|
||||
"""
|
||||
负责把消息发送到麦麦
|
||||
|
|
@ -24,10 +31,27 @@ class MessageSending:
|
|||
# 计算消息大小用于调试
|
||||
msg_dict = message_base.to_dict()
|
||||
msg_json = json.dumps(msg_dict, ensure_ascii=False)
|
||||
msg_size_kb = len(msg_json.encode('utf-8')) / 1024
|
||||
msg_size_bytes = len(msg_json.encode('utf-8'))
|
||||
msg_size_kb = msg_size_bytes / 1024
|
||||
msg_size_mb = msg_size_kb / 1024
|
||||
|
||||
logger.debug(f"发送消息大小: {msg_size_kb:.2f} KB")
|
||||
|
||||
# 检查消息是否超过大小限制
|
||||
if msg_size_bytes > MAX_MESSAGE_SIZE_BYTES:
|
||||
logger.error(
|
||||
f"消息大小 ({msg_size_mb:.2f} MB) 超过限制 ({MAX_MESSAGE_SIZE_MB:.0f} MB),"
|
||||
f"消息已被丢弃以避免连接断开"
|
||||
)
|
||||
logger.warning(
|
||||
f"被丢弃的消息来源: platform={message_base.message_info.platform}, "
|
||||
f"group_id={message_base.message_info.group_info.group_id if message_base.message_info.group_info else 'N/A'}, "
|
||||
f"user_id={message_base.message_info.user_info.user_id if message_base.message_info.user_info else 'N/A'}"
|
||||
)
|
||||
return False
|
||||
|
||||
if msg_size_kb > 1024: # 超过 1MB 时警告
|
||||
logger.warning(f"发送的消息较大 ({msg_size_kb:.2f} KB),可能导致传输问题")
|
||||
logger.warning(f"发送的消息较大 ({msg_size_mb:.2f} MB),可能导致传输延迟")
|
||||
|
||||
send_status = await self.maibot_router.send_message(message_base)
|
||||
if not send_status:
|
||||
|
|
@ -37,6 +61,7 @@ class MessageSending:
|
|||
except Exception as e:
|
||||
logger.error(f"发送消息失败: {str(e)}")
|
||||
logger.error("请检查与MaiBot之间的连接")
|
||||
return False
|
||||
|
||||
async def send_custom_message(self, custom_message: Dict, platform: str, message_type: str) -> bool:
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -25,14 +25,26 @@ class MetaEventHandler:
|
|||
logger.success(f"Bot {self_id} 连接成功")
|
||||
asyncio.create_task(self.check_heartbeat(self_id))
|
||||
elif event_type == MetaEventType.heartbeat:
|
||||
if message["status"].get("online") and message["status"].get("good"):
|
||||
self_id = message.get("self_id")
|
||||
status = message.get("status", {})
|
||||
is_online = status.get("online", False)
|
||||
is_good = status.get("good", False)
|
||||
|
||||
if is_online and is_good:
|
||||
# 正常心跳
|
||||
if not self._interval_checking:
|
||||
asyncio.create_task(self.check_heartbeat())
|
||||
asyncio.create_task(self.check_heartbeat(self_id))
|
||||
self.last_heart_beat = time.time()
|
||||
self.interval = message.get("interval") / 1000
|
||||
self.interval = message.get("interval", 30000) / 1000
|
||||
else:
|
||||
self_id = message.get("self_id")
|
||||
logger.warning(f"Bot {self_id} Napcat 端异常!")
|
||||
# Bot 离线或状态异常
|
||||
if not is_online:
|
||||
logger.error(f"🔴 Bot {self_id} 已下线 (online=false)")
|
||||
logger.warning("Bot 可能被踢下线、网络断开或主动退出登录")
|
||||
elif not is_good:
|
||||
logger.warning(f"⚠️ Bot {self_id} 状态异常 (good=false)")
|
||||
else:
|
||||
logger.warning(f"Bot {self_id} Napcat 端异常!")
|
||||
|
||||
async def check_heartbeat(self, id: int) -> None:
|
||||
self._interval_checking = True
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ from src.database import BanUser, db_manager, is_identical
|
|||
from . import NoticeType, ACCEPT_FORMAT
|
||||
from .message_sending import message_send_instance
|
||||
from .message_handler import message_handler
|
||||
from .qq_emoji_list import qq_face
|
||||
from maim_message import FormatInfo, UserInfo, GroupInfo, Seg, BaseMessageInfo, MessageBase
|
||||
|
||||
from src.utils import (
|
||||
|
|
@ -402,185 +403,13 @@ class NoticeHandler:
|
|||
likes = raw_message.get("likes", [])
|
||||
message_id = raw_message.get("message_id")
|
||||
|
||||
# 构建表情文本
|
||||
# 构建表情文本,直接使用 qq_face 映射
|
||||
emoji_texts = []
|
||||
# QQ 官方表情映射表 (EmojiType=1 为 QQ 系统表情,EmojiType=2 为 Emoji Unicode)
|
||||
emoji_map = {
|
||||
# QQ 系统表情 (Type 1)
|
||||
"4": "得意",
|
||||
"5": "流泪",
|
||||
"8": "睡",
|
||||
"9": "大哭",
|
||||
"10": "尴尬",
|
||||
"12": "调皮",
|
||||
"14": "微笑",
|
||||
"16": "酷",
|
||||
"21": "可爱",
|
||||
"23": "傲慢",
|
||||
"24": "饥饿",
|
||||
"25": "困",
|
||||
"26": "惊恐",
|
||||
"27": "流汗",
|
||||
"28": "憨笑",
|
||||
"29": "悠闲",
|
||||
"30": "奋斗",
|
||||
"32": "疑问",
|
||||
"33": "嘘",
|
||||
"34": "晕",
|
||||
"38": "敲打",
|
||||
"39": "再见",
|
||||
"41": "发抖",
|
||||
"42": "爱情",
|
||||
"43": "跳跳",
|
||||
"49": "拥抱",
|
||||
"53": "蛋糕",
|
||||
"60": "咖啡",
|
||||
"63": "玫瑰",
|
||||
"66": "爱心",
|
||||
"74": "太阳",
|
||||
"75": "月亮",
|
||||
"76": "赞",
|
||||
"78": "握手",
|
||||
"79": "胜利",
|
||||
"85": "飞吻",
|
||||
"89": "西瓜",
|
||||
"96": "冷汗",
|
||||
"97": "擦汗",
|
||||
"98": "抠鼻",
|
||||
"99": "鼓掌",
|
||||
"100": "糗大了",
|
||||
"101": "坏笑",
|
||||
"102": "左哼哼",
|
||||
"103": "右哼哼",
|
||||
"104": "哈欠",
|
||||
"106": "委屈",
|
||||
"109": "左亲亲",
|
||||
"111": "可怜",
|
||||
"116": "示爱",
|
||||
"118": "抱拳",
|
||||
"120": "拳头",
|
||||
"122": "爱你",
|
||||
"123": "NO",
|
||||
"124": "OK",
|
||||
"125": "转圈",
|
||||
"129": "挥手",
|
||||
"144": "喝彩",
|
||||
"147": "棒棒糖",
|
||||
"171": "茶",
|
||||
"173": "泪奔",
|
||||
"174": "无奈",
|
||||
"175": "卖萌",
|
||||
"176": "小纠结",
|
||||
"179": "doge",
|
||||
"180": "惊喜",
|
||||
"181": "骚扰",
|
||||
"182": "笑哭",
|
||||
"183": "我最美",
|
||||
"201": "点赞",
|
||||
"203": "托脸",
|
||||
"212": "托腮",
|
||||
"214": "啵啵",
|
||||
"219": "蹭一蹭",
|
||||
"222": "抱抱",
|
||||
"227": "拍手",
|
||||
"232": "佛系",
|
||||
"240": "喷脸",
|
||||
"243": "甩头",
|
||||
"246": "加油抱抱",
|
||||
"262": "脑阔疼",
|
||||
"264": "捂脸",
|
||||
"265": "辣眼睛",
|
||||
"266": "哦哟",
|
||||
"267": "头秃",
|
||||
"268": "问号脸",
|
||||
"269": "暗中观察",
|
||||
"270": "emm",
|
||||
"271": "吃瓜",
|
||||
"272": "呵呵哒",
|
||||
"273": "我酸了",
|
||||
"277": "汪汪",
|
||||
"278": "汗",
|
||||
"281": "无眼笑",
|
||||
"282": "敬礼",
|
||||
"284": "面无表情",
|
||||
"285": "摸鱼",
|
||||
"287": "哦",
|
||||
"289": "睁眼",
|
||||
"290": "敲开心",
|
||||
"293": "摸锦鲤",
|
||||
"294": "期待",
|
||||
"297": "拜谢",
|
||||
"298": "元宝",
|
||||
"299": "牛啊",
|
||||
"305": "右亲亲",
|
||||
"306": "牛气冲天",
|
||||
"307": "喵喵",
|
||||
"314": "仔细分析",
|
||||
"315": "加油",
|
||||
"318": "崇拜",
|
||||
"319": "比心",
|
||||
"320": "庆祝",
|
||||
"322": "拒绝",
|
||||
"324": "吃糖",
|
||||
"326": "生气",
|
||||
# Unicode Emoji (Type 2)
|
||||
"9728": "☀",
|
||||
"9749": "☕",
|
||||
"9786": "☺",
|
||||
"10024": "✨",
|
||||
"10060": "❌",
|
||||
"10068": "❔",
|
||||
"127801": "🌹",
|
||||
"127817": "🍉",
|
||||
"127822": "🍎",
|
||||
"127827": "🍓",
|
||||
"127836": "🍜",
|
||||
"127838": "🍞",
|
||||
"127847": "🍧",
|
||||
"127866": "🍺",
|
||||
"127867": "🍻",
|
||||
"127881": "🎉",
|
||||
"128027": "🐛",
|
||||
"128046": "🐮",
|
||||
"128051": "🐳",
|
||||
"128053": "🐵",
|
||||
"128074": "👊",
|
||||
"128076": "👌",
|
||||
"128077": "👍",
|
||||
"128079": "👏",
|
||||
"128089": "👙",
|
||||
"128102": "👦",
|
||||
"128104": "👨",
|
||||
"128147": "💓",
|
||||
"128157": "💝",
|
||||
"128164": "💤",
|
||||
"128166": "💦",
|
||||
"128168": "💨",
|
||||
"128170": "💪",
|
||||
"128235": "📫",
|
||||
"128293": "🔥",
|
||||
"128513": "😁",
|
||||
"128514": "😂",
|
||||
"128516": "😄",
|
||||
"128522": "😊",
|
||||
"128524": "😌",
|
||||
"128527": "😏",
|
||||
"128530": "😒",
|
||||
"128531": "😓",
|
||||
"128532": "😔",
|
||||
"128536": "😘",
|
||||
"128538": "😚",
|
||||
"128540": "😜",
|
||||
"128541": "😝",
|
||||
"128557": "😭",
|
||||
"128560": "😰",
|
||||
"128563": "😳",
|
||||
}
|
||||
|
||||
for like in likes:
|
||||
emoji_id = like.get("emoji_id", "")
|
||||
emoji_id = str(like.get("emoji_id", ""))
|
||||
count = like.get("count", 1)
|
||||
emoji = emoji_map.get(emoji_id, f"表情{emoji_id}")
|
||||
# 使用 qq_face 字典获取表情描述
|
||||
emoji = qq_face.get(emoji_id, f"[表情:未知{emoji_id}]")
|
||||
if count > 1:
|
||||
emoji_texts.append(f"{emoji}x{count}")
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ qq_face: dict = {
|
|||
"30": "[表情:奋斗]",
|
||||
"31": "[表情:咒骂]",
|
||||
"32": "[表情:疑问]",
|
||||
"33": "[表情: 嘘]",
|
||||
"33": "[表情:嘘]",
|
||||
"34": "[表情:晕]",
|
||||
"35": "[表情:折磨]",
|
||||
"36": "[表情:衰]",
|
||||
|
|
@ -117,7 +117,7 @@ qq_face: dict = {
|
|||
"268": "[表情:问号脸]",
|
||||
"269": "[表情:暗中观察]",
|
||||
"270": "[表情:emm]",
|
||||
"271": "[表情:吃 瓜]",
|
||||
"271": "[表情:吃瓜]",
|
||||
"272": "[表情:呵呵哒]",
|
||||
"273": "[表情:我酸了]",
|
||||
"277": "[表情:汪汪]",
|
||||
|
|
@ -146,7 +146,7 @@ qq_face: dict = {
|
|||
"314": "[表情:仔细分析]",
|
||||
"317": "[表情:菜汪]",
|
||||
"318": "[表情:崇拜]",
|
||||
"319": "[表情: 比心]",
|
||||
"319": "[表情:比心]",
|
||||
"320": "[表情:庆祝]",
|
||||
"323": "[表情:嫌弃]",
|
||||
"324": "[表情:吃糖]",
|
||||
|
|
@ -175,13 +175,65 @@ qq_face: dict = {
|
|||
"355": "[表情:耶]",
|
||||
"356": "[表情:666]",
|
||||
"357": "[表情:裂开]",
|
||||
"392": "[表情:龙年 快乐]",
|
||||
"392": "[表情:龙年快乐]",
|
||||
"393": "[表情:新年中龙]",
|
||||
"394": "[表情:新年大龙]",
|
||||
"395": "[表情:略略略]",
|
||||
"128522": "[表情:嘿嘿]",
|
||||
"128524": "[表情:羞涩]",
|
||||
"128538": "[表情:亲亲]",
|
||||
"128531": "[表情:汗]",
|
||||
"128560": "[表情:紧张]",
|
||||
"128541": "[表情:吐舌]",
|
||||
"128513": "[表情:呲牙]",
|
||||
"128540": "[表情:淘气]",
|
||||
"9786": "[表情:可爱]",
|
||||
"128532": "[表情:失落]",
|
||||
"128516": "[表情:高兴]",
|
||||
"128527": "[表情:哼哼]",
|
||||
"128530": "[表情:不屑]",
|
||||
"128563": "[表情:瞪眼]",
|
||||
"128536": "[表情:飞吻]",
|
||||
"128557": "[表情:大哭]",
|
||||
"128514": "[表情:激动]",
|
||||
"128170": "[表情:肌肉]",
|
||||
"128074": "[表情:拳头]",
|
||||
"128077": "[表情:厉害]",
|
||||
"128079": "[表情:鼓掌]",
|
||||
"128076": "[表情:好的]",
|
||||
"127836": "[表情:拉面]",
|
||||
"127847": "[表情:刨冰]",
|
||||
"127838": "[表情:面包]",
|
||||
"127866": "[表情:啤酒]",
|
||||
"127867": "[表情:干杯]",
|
||||
"9749": "[表情:咖啡]",
|
||||
"127822": "[表情:苹果]",
|
||||
"127827": "[表情:草莓]",
|
||||
"127817": "[表情:西瓜]",
|
||||
"127801": "[表情:玫瑰]",
|
||||
"127881": "[表情:庆祝]",
|
||||
"128157": "[表情:礼物]",
|
||||
"10024": "[表情:闪光]",
|
||||
"128168": "[表情:吹气]",
|
||||
"128166": "[表情:水]",
|
||||
"128293": "[表情:火]",
|
||||
"128164": "[表情:睡觉]",
|
||||
"128235": "[表情:邮箱]",
|
||||
"128103": "[表情:女孩]",
|
||||
"128102": "[表情:男孩]",
|
||||
"128053": "[表情:猴]",
|
||||
"128046": "[表情:牛]",
|
||||
"128027": "[表情:虫]",
|
||||
"128051": "[表情:鲸鱼]",
|
||||
"9728": "[表情:晴天]",
|
||||
"10068": "[表情:问号]",
|
||||
"128147": "[表情:爱心]",
|
||||
"10060": "[表情:错误]",
|
||||
"128089": "[表情:内衣]",
|
||||
"128104": "[表情:爸爸]",
|
||||
"😊": "[表情:嘿嘿]",
|
||||
"😌": "[表情:羞涩]",
|
||||
"😚": "[ 表情:亲亲]",
|
||||
"😚": "[表情:亲亲]",
|
||||
"😓": "[表情:汗]",
|
||||
"😰": "[表情:紧张]",
|
||||
"😝": "[表情:吐舌]",
|
||||
|
|
@ -200,7 +252,7 @@ qq_face: dict = {
|
|||
"😂": "[表情:激动]",
|
||||
"💪": "[表情:肌肉]",
|
||||
"👊": "[表情:拳头]",
|
||||
"👍": "[表情 :厉害]",
|
||||
"👍": "[表情:厉害]",
|
||||
"👏": "[表情:鼓掌]",
|
||||
"👎": "[表情:鄙视]",
|
||||
"🙏": "[表情:合十]",
|
||||
|
|
@ -245,6 +297,6 @@ qq_face: dict = {
|
|||
"☀": "[表情:晴天]",
|
||||
"❔": "[表情:问号]",
|
||||
"🔫": "[表情:手枪]",
|
||||
"💓": "[表情:爱 心]",
|
||||
"💓": "[表情:爱心]",
|
||||
"🏪": "[表情:便利店]",
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
from typing import Any, Dict
|
||||
from typing import Any, Dict, Optional
|
||||
import time
|
||||
from maim_message import (
|
||||
UserInfo,
|
||||
GroupInfo,
|
||||
|
|
@ -10,6 +11,7 @@ from src.logger import logger
|
|||
from .send_command_handler import SendCommandHandleClass
|
||||
from .send_message_handler import SendMessageHandleClass
|
||||
from .nc_sending import nc_message_sender
|
||||
from src.recv_handler.message_sending import message_send_instance
|
||||
|
||||
|
||||
class SendHandler:
|
||||
|
|
@ -34,21 +36,89 @@ class SendHandler:
|
|||
message_segment: Seg = raw_message_base.message_segment
|
||||
group_info: GroupInfo = message_info.group_info
|
||||
seg_data: Dict[str, Any] = message_segment.data
|
||||
command_name = seg_data.get('name', 'UNKNOWN')
|
||||
|
||||
try:
|
||||
command, args_dict = SendCommandHandleClass.handle_command(seg_data, group_info)
|
||||
except Exception as e:
|
||||
logger.error(f"处理命令时出错: {str(e)}")
|
||||
# 发送错误响应给麦麦
|
||||
await self._send_command_response(
|
||||
platform=message_info.platform,
|
||||
command_name=command_name,
|
||||
success=False,
|
||||
error=str(e)
|
||||
)
|
||||
return
|
||||
|
||||
if not command or not args_dict:
|
||||
logger.error("命令或参数缺失")
|
||||
await self._send_command_response(
|
||||
platform=message_info.platform,
|
||||
command_name=command_name,
|
||||
success=False,
|
||||
error="命令或参数缺失"
|
||||
)
|
||||
return None
|
||||
|
||||
response = await nc_message_sender.send_message_to_napcat(command, args_dict)
|
||||
|
||||
# 根据响应状态发送结果给麦麦
|
||||
if response.get("status") == "ok":
|
||||
logger.info(f"命令 {seg_data.get('name')} 执行成功")
|
||||
logger.info(f"命令 {command_name} 执行成功")
|
||||
await self._send_command_response(
|
||||
platform=message_info.platform,
|
||||
command_name=command_name,
|
||||
success=True,
|
||||
data=response.get("data")
|
||||
)
|
||||
else:
|
||||
logger.warning(f"命令 {seg_data.get('name')} 执行失败,napcat返回:{str(response)}")
|
||||
logger.warning(f"命令 {command_name} 执行失败,napcat返回:{str(response)}")
|
||||
await self._send_command_response(
|
||||
platform=message_info.platform,
|
||||
command_name=command_name,
|
||||
success=False,
|
||||
error=str(response),
|
||||
data=response.get("data") # 有些错误响应也可能包含部分数据
|
||||
)
|
||||
|
||||
async def _send_command_response(
|
||||
self,
|
||||
platform: str,
|
||||
command_name: str,
|
||||
success: bool,
|
||||
data: Optional[Dict] = None,
|
||||
error: Optional[str] = None
|
||||
) -> None:
|
||||
"""发送命令响应回麦麦
|
||||
|
||||
Args:
|
||||
platform: 平台标识
|
||||
command_name: 命令名称
|
||||
success: 是否执行成功
|
||||
data: 返回数据(成功时)
|
||||
error: 错误信息(失败时)
|
||||
"""
|
||||
response_data = {
|
||||
"command_name": command_name,
|
||||
"success": success,
|
||||
"timestamp": time.time()
|
||||
}
|
||||
|
||||
if data is not None:
|
||||
response_data["data"] = data
|
||||
if error:
|
||||
response_data["error"] = error
|
||||
|
||||
try:
|
||||
await message_send_instance.send_custom_message(
|
||||
custom_message=response_data,
|
||||
platform=platform,
|
||||
message_type="command_response"
|
||||
)
|
||||
logger.debug(f"已发送命令响应: {command_name}, success={success}")
|
||||
except Exception as e:
|
||||
logger.error(f"发送命令响应失败: {e}")
|
||||
|
||||
async def send_normal_message(self, raw_message_base: MessageBase) -> None:
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -123,29 +123,108 @@ class SendCommandHandleClass:
|
|||
)
|
||||
|
||||
@staticmethod
|
||||
@register_command(CommandType.GROUP_KICK, require_group=True)
|
||||
@register_command(CommandType.GROUP_KICK, require_group=False)
|
||||
def handle_kick_command(args: Dict[str, Any], group_info: Optional[GroupInfo]) -> Tuple[str, Dict[str, Any]]:
|
||||
"""处理群成员踢出命令
|
||||
|
||||
Args:
|
||||
args: 参数字典 {"qq_id": int}
|
||||
group_info: 群聊信息(对应目标群聊)
|
||||
args: 参数字典 {"group_id": int, "user_id": int, "reject_add_request": bool (可选)}
|
||||
group_info: 群聊信息(可选,可自动获取 group_id)
|
||||
|
||||
Returns:
|
||||
Tuple[str, Dict[str, Any]]: (action, params)
|
||||
"""
|
||||
user_id: int = int(args["qq_id"])
|
||||
group_id: int = int(group_info.group_id)
|
||||
if not args:
|
||||
raise ValueError("群踢人命令缺少参数")
|
||||
|
||||
# 优先从 args 获取 group_id,否则从 group_info 获取
|
||||
group_id = args.get("group_id")
|
||||
if not group_id and group_info:
|
||||
group_id = int(group_info.group_id)
|
||||
|
||||
user_id = args.get("user_id")
|
||||
|
||||
if not group_id:
|
||||
raise ValueError("群踢人命令缺少必要参数: group_id")
|
||||
if not user_id:
|
||||
raise ValueError("群踢人命令缺少必要参数: user_id")
|
||||
|
||||
group_id = int(group_id)
|
||||
user_id = int(user_id)
|
||||
if group_id <= 0:
|
||||
raise ValueError("群组ID无效")
|
||||
if user_id <= 0:
|
||||
raise ValueError("用户ID无效")
|
||||
|
||||
# reject_add_request 是可选参数,默认 False
|
||||
reject_add_request = args.get("reject_add_request", False)
|
||||
|
||||
return (
|
||||
CommandType.GROUP_KICK.value,
|
||||
{
|
||||
"group_id": group_id,
|
||||
"user_id": user_id,
|
||||
"reject_add_request": False, # 不拒绝加群请求
|
||||
"reject_add_request": bool(reject_add_request),
|
||||
},
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@register_command(CommandType.GROUP_KICK_MEMBERS, require_group=False)
|
||||
def handle_kick_members_command(args: Dict[str, Any], group_info: Optional[GroupInfo]) -> Tuple[str, Dict[str, Any]]:
|
||||
"""处理批量踢出群成员命令
|
||||
|
||||
Args:
|
||||
args: 参数字典 {"group_id": int, "user_id": List[int], "reject_add_request": bool (可选)}
|
||||
group_info: 群聊信息(可选,可自动获取 group_id)
|
||||
|
||||
Returns:
|
||||
Tuple[str, Dict[str, Any]]: (action, params)
|
||||
"""
|
||||
if not args:
|
||||
raise ValueError("批量踢人命令缺少参数")
|
||||
|
||||
# 优先从 args 获取 group_id,否则从 group_info 获取
|
||||
group_id = args.get("group_id")
|
||||
if not group_id and group_info:
|
||||
group_id = int(group_info.group_id)
|
||||
|
||||
user_id = args.get("user_id")
|
||||
|
||||
if not group_id:
|
||||
raise ValueError("批量踢人命令缺少必要参数: group_id")
|
||||
if not user_id:
|
||||
raise ValueError("批量踢人命令缺少必要参数: user_id")
|
||||
|
||||
# 验证 user_id 是数组
|
||||
if not isinstance(user_id, list):
|
||||
raise ValueError("user_id 必须是数组类型")
|
||||
if len(user_id) == 0:
|
||||
raise ValueError("user_id 数组不能为空")
|
||||
|
||||
# 转换并验证每个 user_id
|
||||
user_id_list = []
|
||||
for uid in user_id:
|
||||
try:
|
||||
uid_int = int(uid)
|
||||
if uid_int <= 0:
|
||||
raise ValueError(f"用户ID无效: {uid}")
|
||||
user_id_list.append(uid_int)
|
||||
except (ValueError, TypeError) as e:
|
||||
raise ValueError(f"用户ID格式错误: {uid} - {str(e)}") from None
|
||||
|
||||
group_id = int(group_id)
|
||||
if group_id <= 0:
|
||||
raise ValueError("群组ID无效")
|
||||
|
||||
# reject_add_request 是可选参数,默认 False
|
||||
reject_add_request = args.get("reject_add_request", False)
|
||||
|
||||
return (
|
||||
CommandType.GROUP_KICK_MEMBERS.value,
|
||||
{
|
||||
"group_id": group_id,
|
||||
"user_id": user_id_list,
|
||||
"reject_add_request": bool(reject_add_request),
|
||||
},
|
||||
)
|
||||
|
||||
|
|
@ -178,6 +257,45 @@ class SendCommandHandleClass:
|
|||
},
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@register_command(CommandType.SET_GROUP_NAME, require_group=False)
|
||||
def handle_set_group_name_command(args: Dict[str, Any], group_info: Optional[GroupInfo]) -> Tuple[str, Dict[str, Any]]:
|
||||
"""设置群名
|
||||
|
||||
Args:
|
||||
args: 参数字典 {"group_id": int, "group_name": str}
|
||||
group_info: 群聊信息(可选,可自动获取 group_id)
|
||||
|
||||
Returns:
|
||||
Tuple[str, Dict[str, Any]]: (action, params)
|
||||
"""
|
||||
if not args:
|
||||
raise ValueError("设置群名命令缺少参数")
|
||||
|
||||
# 优先从 args 获取 group_id,否则从 group_info 获取
|
||||
group_id = args.get("group_id")
|
||||
if not group_id and group_info:
|
||||
group_id = int(group_info.group_id)
|
||||
|
||||
group_name = args.get("group_name")
|
||||
|
||||
if not group_id:
|
||||
raise ValueError("设置群名命令缺少必要参数: group_id")
|
||||
if not group_name:
|
||||
raise ValueError("设置群名命令缺少必要参数: group_name")
|
||||
|
||||
group_id = int(group_id)
|
||||
if group_id <= 0:
|
||||
raise ValueError("群组ID无效")
|
||||
|
||||
return (
|
||||
CommandType.SET_GROUP_NAME.value,
|
||||
{
|
||||
"group_id": group_id,
|
||||
"group_name": str(group_name),
|
||||
},
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@register_command(CommandType.DELETE_MSG, require_group=False)
|
||||
def delete_msg_command(args: Dict[str, Any], group_info: Optional[GroupInfo]) -> Tuple[str, Dict[str, Any]]:
|
||||
|
|
@ -199,12 +317,40 @@ class SendCommandHandleClass:
|
|||
except (ValueError, TypeError) as e:
|
||||
raise ValueError(f"消息ID无效: {args['message_id']} - {str(e)}") from None
|
||||
|
||||
return (
|
||||
CommandType.DELETE_MSG.value,
|
||||
{
|
||||
"message_id": message_id,
|
||||
},
|
||||
)
|
||||
return (CommandType.DELETE_MSG.value, {"message_id": message_id})
|
||||
|
||||
@staticmethod
|
||||
@register_command(CommandType.SET_QQ_PROFILE, require_group=False)
|
||||
def handle_set_qq_profile_command(args: Dict[str, Any], group_info: Optional[GroupInfo]) -> Tuple[str, Dict[str, Any]]:
|
||||
"""设置账号信息
|
||||
|
||||
Args:
|
||||
args: 参数字典 {"nickname": str, "personal_note": str (可选), "sex": str (可选)}
|
||||
group_info: 群聊信息(不使用)
|
||||
|
||||
Returns:
|
||||
Tuple[str, Dict[str, Any]]: (action, params)
|
||||
"""
|
||||
if not args:
|
||||
raise ValueError("设置账号信息命令缺少参数")
|
||||
|
||||
nickname = args.get("nickname")
|
||||
if not nickname:
|
||||
raise ValueError("设置账号信息命令缺少必要参数: nickname")
|
||||
|
||||
params = {"nickname": str(nickname)}
|
||||
|
||||
# 可选参数
|
||||
if "personal_note" in args:
|
||||
params["personal_note"] = str(args["personal_note"])
|
||||
|
||||
if "sex" in args:
|
||||
sex = str(args["sex"]).lower()
|
||||
if sex not in ["male", "female", "unknown"]:
|
||||
raise ValueError(f"性别参数无效: {sex},必须为 male/female/unknown 之一")
|
||||
params["sex"] = sex
|
||||
|
||||
return (CommandType.SET_QQ_PROFILE.value, params)
|
||||
|
||||
@staticmethod
|
||||
@register_command(CommandType.AI_VOICE_SEND, require_group=True)
|
||||
|
|
@ -276,3 +422,298 @@ class SendCommandHandleClass:
|
|||
"set": True,
|
||||
},
|
||||
)
|
||||
|
||||
# ============ 查询类命令处理器 ============
|
||||
|
||||
@staticmethod
|
||||
@register_command(CommandType.GET_LOGIN_INFO, require_group=False)
|
||||
def handle_get_login_info_command(args: Dict[str, Any], group_info: Optional[GroupInfo]) -> Tuple[str, Dict[str, Any]]:
|
||||
"""获取登录号信息(Bot自身信息)
|
||||
|
||||
Args:
|
||||
args: 参数字典(无需参数)
|
||||
group_info: 群聊信息(不使用)
|
||||
|
||||
Returns:
|
||||
Tuple[str, Dict[str, Any]]: (action, params)
|
||||
"""
|
||||
return (CommandType.GET_LOGIN_INFO.value, {})
|
||||
|
||||
@staticmethod
|
||||
@register_command(CommandType.GET_STRANGER_INFO, require_group=False)
|
||||
def handle_get_stranger_info_command(args: Dict[str, Any], group_info: Optional[GroupInfo]) -> Tuple[str, Dict[str, Any]]:
|
||||
"""获取陌生人信息
|
||||
|
||||
Args:
|
||||
args: 参数字典 {"user_id": int}
|
||||
group_info: 群聊信息(不使用)
|
||||
|
||||
Returns:
|
||||
Tuple[str, Dict[str, Any]]: (action, params)
|
||||
"""
|
||||
if not args:
|
||||
raise ValueError("获取陌生人信息命令缺少参数")
|
||||
|
||||
user_id = args.get("user_id")
|
||||
if not user_id:
|
||||
raise ValueError("获取陌生人信息命令缺少必要参数: user_id")
|
||||
|
||||
user_id = int(user_id)
|
||||
if user_id <= 0:
|
||||
raise ValueError("用户ID无效")
|
||||
|
||||
return (
|
||||
CommandType.GET_STRANGER_INFO.value,
|
||||
{"user_id": user_id},
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@register_command(CommandType.GET_FRIEND_LIST, require_group=False)
|
||||
def handle_get_friend_list_command(args: Dict[str, Any], group_info: Optional[GroupInfo]) -> Tuple[str, Dict[str, Any]]:
|
||||
"""获取好友列表
|
||||
|
||||
Args:
|
||||
args: 参数字典 {"no_cache": bool} (可选,默认 false)
|
||||
group_info: 群聊信息(不使用)
|
||||
|
||||
Returns:
|
||||
Tuple[str, Dict[str, Any]]: (action, params)
|
||||
"""
|
||||
# no_cache 参数是可选的,默认为 false
|
||||
no_cache = args.get("no_cache", False) if args else False
|
||||
|
||||
return (CommandType.GET_FRIEND_LIST.value, {"no_cache": bool(no_cache)})
|
||||
|
||||
@staticmethod
|
||||
@register_command(CommandType.GET_GROUP_INFO, require_group=False)
|
||||
def handle_get_group_info_command(args: Dict[str, Any], group_info: Optional[GroupInfo]) -> Tuple[str, Dict[str, Any]]:
|
||||
"""获取群信息
|
||||
|
||||
Args:
|
||||
args: 参数字典 {"group_id": int} 或从 group_info 自动获取
|
||||
group_info: 群聊信息(可选)
|
||||
|
||||
Returns:
|
||||
Tuple[str, Dict[str, Any]]: (action, params)
|
||||
"""
|
||||
# 优先从 args 获取,否则从 group_info 获取
|
||||
group_id = args.get("group_id") if args else None
|
||||
if not group_id and group_info:
|
||||
group_id = int(group_info.group_id)
|
||||
|
||||
if not group_id:
|
||||
raise ValueError("获取群信息命令缺少必要参数: group_id")
|
||||
|
||||
group_id = int(group_id)
|
||||
if group_id <= 0:
|
||||
raise ValueError("群组ID无效")
|
||||
|
||||
return (
|
||||
CommandType.GET_GROUP_INFO.value,
|
||||
{"group_id": group_id},
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@register_command(CommandType.GET_GROUP_DETAIL_INFO, require_group=False)
|
||||
def handle_get_group_detail_info_command(args: Dict[str, Any], group_info: Optional[GroupInfo]) -> Tuple[str, Dict[str, Any]]:
|
||||
"""获取群详细信息
|
||||
|
||||
Args:
|
||||
args: 参数字典 {"group_id": int} 或从 group_info 自动获取
|
||||
group_info: 群聊信息(可选)
|
||||
|
||||
Returns:
|
||||
Tuple[str, Dict[str, Any]]: (action, params)
|
||||
"""
|
||||
# 优先从 args 获取,否则从 group_info 获取
|
||||
group_id = args.get("group_id") if args else None
|
||||
if not group_id and group_info:
|
||||
group_id = int(group_info.group_id)
|
||||
|
||||
if not group_id:
|
||||
raise ValueError("获取群详细信息命令缺少必要参数: group_id")
|
||||
|
||||
group_id = int(group_id)
|
||||
if group_id <= 0:
|
||||
raise ValueError("群组ID无效")
|
||||
|
||||
return (
|
||||
CommandType.GET_GROUP_DETAIL_INFO.value,
|
||||
{"group_id": group_id},
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@register_command(CommandType.GET_GROUP_LIST, require_group=False)
|
||||
def handle_get_group_list_command(args: Dict[str, Any], group_info: Optional[GroupInfo]) -> Tuple[str, Dict[str, Any]]:
|
||||
"""获取群列表
|
||||
|
||||
Args:
|
||||
args: 参数字典 {"no_cache": bool} (可选,默认 false)
|
||||
group_info: 群聊信息(不使用)
|
||||
|
||||
Returns:
|
||||
Tuple[str, Dict[str, Any]]: (action, params)
|
||||
"""
|
||||
# no_cache 参数是可选的,默认为 false
|
||||
no_cache = args.get("no_cache", False) if args else False
|
||||
|
||||
return (CommandType.GET_GROUP_LIST.value, {"no_cache": bool(no_cache)})
|
||||
|
||||
@staticmethod
|
||||
@register_command(CommandType.GET_GROUP_AT_ALL_REMAIN, require_group=False)
|
||||
def handle_get_group_at_all_remain_command(args: Dict[str, Any], group_info: Optional[GroupInfo]) -> Tuple[str, Dict[str, Any]]:
|
||||
"""获取群@全体成员剩余次数
|
||||
|
||||
Args:
|
||||
args: 参数字典 {"group_id": int} 或从 group_info 自动获取
|
||||
group_info: 群聊信息(可选)
|
||||
|
||||
Returns:
|
||||
Tuple[str, Dict[str, Any]]: (action, params)
|
||||
"""
|
||||
# 优先从 args 获取,否则从 group_info 获取
|
||||
group_id = args.get("group_id") if args else None
|
||||
if not group_id and group_info:
|
||||
group_id = int(group_info.group_id)
|
||||
|
||||
if not group_id:
|
||||
raise ValueError("获取群@全体成员剩余次数命令缺少必要参数: group_id")
|
||||
|
||||
group_id = int(group_id)
|
||||
if group_id <= 0:
|
||||
raise ValueError("群组ID无效")
|
||||
|
||||
return (
|
||||
CommandType.GET_GROUP_AT_ALL_REMAIN.value,
|
||||
{"group_id": group_id},
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@register_command(CommandType.GET_GROUP_MEMBER_INFO, require_group=False)
|
||||
def handle_get_group_member_info_command(args: Dict[str, Any], group_info: Optional[GroupInfo]) -> Tuple[str, Dict[str, Any]]:
|
||||
"""获取群成员信息
|
||||
|
||||
Args:
|
||||
args: 参数字典 {"group_id": int, "user_id": int, "no_cache": bool} 或 group_id 从 group_info 自动获取
|
||||
group_info: 群聊信息(可选)
|
||||
|
||||
Returns:
|
||||
Tuple[str, Dict[str, Any]]: (action, params)
|
||||
"""
|
||||
if not args:
|
||||
raise ValueError("获取群成员信息命令缺少参数")
|
||||
|
||||
# 优先从 args 获取,否则从 group_info 获取
|
||||
group_id = args.get("group_id")
|
||||
if not group_id and group_info:
|
||||
group_id = int(group_info.group_id)
|
||||
|
||||
user_id = args.get("user_id")
|
||||
no_cache = args.get("no_cache", False)
|
||||
|
||||
if not group_id:
|
||||
raise ValueError("获取群成员信息命令缺少必要参数: group_id")
|
||||
if not user_id:
|
||||
raise ValueError("获取群成员信息命令缺少必要参数: user_id")
|
||||
|
||||
group_id = int(group_id)
|
||||
user_id = int(user_id)
|
||||
if group_id <= 0:
|
||||
raise ValueError("群组ID无效")
|
||||
if user_id <= 0:
|
||||
raise ValueError("用户ID无效")
|
||||
|
||||
return (
|
||||
CommandType.GET_GROUP_MEMBER_INFO.value,
|
||||
{
|
||||
"group_id": group_id,
|
||||
"user_id": user_id,
|
||||
"no_cache": bool(no_cache),
|
||||
},
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@register_command(CommandType.GET_GROUP_MEMBER_LIST, require_group=False)
|
||||
def handle_get_group_member_list_command(args: Dict[str, Any], group_info: Optional[GroupInfo]) -> Tuple[str, Dict[str, Any]]:
|
||||
"""获取群成员列表
|
||||
|
||||
Args:
|
||||
args: 参数字典 {"group_id": int, "no_cache": bool} 或 group_id 从 group_info 自动获取
|
||||
group_info: 群聊信息(可选)
|
||||
|
||||
Returns:
|
||||
Tuple[str, Dict[str, Any]]: (action, params)
|
||||
"""
|
||||
# 优先从 args 获取,否则从 group_info 获取
|
||||
group_id = args.get("group_id") if args else None
|
||||
if not group_id and group_info:
|
||||
group_id = int(group_info.group_id)
|
||||
|
||||
no_cache = args.get("no_cache", False) if args else False
|
||||
|
||||
if not group_id:
|
||||
raise ValueError("获取群成员列表命令缺少必要参数: group_id")
|
||||
|
||||
group_id = int(group_id)
|
||||
if group_id <= 0:
|
||||
raise ValueError("群组ID无效")
|
||||
|
||||
return (
|
||||
CommandType.GET_GROUP_MEMBER_LIST.value,
|
||||
{
|
||||
"group_id": group_id,
|
||||
"no_cache": bool(no_cache),
|
||||
},
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@register_command(CommandType.GET_MSG, require_group=False)
|
||||
def handle_get_msg_command(args: Dict[str, Any], group_info: Optional[GroupInfo]) -> Tuple[str, Dict[str, Any]]:
|
||||
"""获取消息详情
|
||||
|
||||
Args:
|
||||
args: 参数字典 {"message_id": int}
|
||||
group_info: 群聊信息(不使用)
|
||||
|
||||
Returns:
|
||||
Tuple[str, Dict[str, Any]]: (action, params)
|
||||
"""
|
||||
if not args:
|
||||
raise ValueError("获取消息命令缺少参数")
|
||||
|
||||
message_id = args.get("message_id")
|
||||
if not message_id:
|
||||
raise ValueError("获取消息命令缺少必要参数: message_id")
|
||||
|
||||
message_id = int(message_id)
|
||||
if message_id <= 0:
|
||||
raise ValueError("消息ID无效")
|
||||
|
||||
return (
|
||||
CommandType.GET_MSG.value,
|
||||
{"message_id": message_id},
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@register_command(CommandType.GET_FORWARD_MSG, require_group=False)
|
||||
def handle_get_forward_msg_command(args: Dict[str, Any], group_info: Optional[GroupInfo]) -> Tuple[str, Dict[str, Any]]:
|
||||
"""获取合并转发消息
|
||||
|
||||
Args:
|
||||
args: 参数字典 {"message_id": str}
|
||||
group_info: 群聊信息(不使用)
|
||||
|
||||
Returns:
|
||||
Tuple[str, Dict[str, Any]]: (action, params)
|
||||
"""
|
||||
if not args:
|
||||
raise ValueError("获取合并转发消息命令缺少参数")
|
||||
|
||||
message_id = args.get("message_id")
|
||||
if not message_id:
|
||||
raise ValueError("获取合并转发消息命令缺少必要参数: message_id")
|
||||
|
||||
return (
|
||||
CommandType.GET_FORWARD_MSG.value,
|
||||
{"message_id": str(message_id)},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -216,12 +216,61 @@ class SendMessageHandleClass:
|
|||
}
|
||||
|
||||
@staticmethod
|
||||
def handle_file_message(file_path: str) -> dict:
|
||||
"""处理文件消息"""
|
||||
return {
|
||||
"type": "file",
|
||||
"data": {"file": f"file://{file_path}"},
|
||||
}
|
||||
def handle_file_message(file_data) -> dict:
|
||||
"""处理文件消息
|
||||
|
||||
Args:
|
||||
file_data: 可以是字符串(文件路径)或字典(完整文件信息)
|
||||
- 字符串:简单的文件路径
|
||||
- 字典:包含 file, name, path, thumb, url 等字段
|
||||
|
||||
Returns:
|
||||
NapCat 格式的文件消息段
|
||||
"""
|
||||
# 如果是简单的字符串路径(兼容旧版本)
|
||||
if isinstance(file_data, str):
|
||||
return {
|
||||
"type": "file",
|
||||
"data": {"file": f"file://{file_data}"},
|
||||
}
|
||||
|
||||
# 如果是完整的字典数据
|
||||
if isinstance(file_data, dict):
|
||||
data = {}
|
||||
|
||||
# file 字段是必需的
|
||||
if "file" in file_data:
|
||||
file_value = file_data["file"]
|
||||
# 如果是本地路径且没有协议前缀,添加 file:// 前缀
|
||||
if not any(file_value.startswith(prefix) for prefix in ["file://", "http://", "https://", "base64://"]):
|
||||
data["file"] = f"file://{file_value}"
|
||||
else:
|
||||
data["file"] = file_value
|
||||
else:
|
||||
# 没有 file 字段,尝试使用 path 或 url
|
||||
if "path" in file_data:
|
||||
data["file"] = f"file://{file_data['path']}"
|
||||
elif "url" in file_data:
|
||||
data["file"] = file_data["url"]
|
||||
else:
|
||||
logger.warning("文件消息缺少必要的 file/path/url 字段")
|
||||
return None
|
||||
|
||||
# 添加可选字段
|
||||
if "name" in file_data:
|
||||
data["name"] = file_data["name"]
|
||||
if "thumb" in file_data:
|
||||
data["thumb"] = file_data["thumb"]
|
||||
if "url" in file_data and "file" not in file_data:
|
||||
data["file"] = file_data["url"]
|
||||
|
||||
return {
|
||||
"type": "file",
|
||||
"data": data,
|
||||
}
|
||||
|
||||
logger.warning(f"不支持的文件数据类型: {type(file_data)}")
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def handle_imageurl_message(image_url: str) -> dict:
|
||||
|
|
|
|||
Loading…
Reference in New Issue