Merge branch 'master' of https://github.com/MoFox-Studio/MoFox_Bot
This commit is contained in:
19
plugins/hello_world_plugin/_manifest.json
Normal file
19
plugins/hello_world_plugin/_manifest.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"manifest_version": 1,
|
||||
"name": "Hello World 插件",
|
||||
"version": "1.0.1",
|
||||
"description": "一个包含四大核心组件和高级配置功能的入门示例插件。",
|
||||
"author": {
|
||||
"name": "Kilo Code"
|
||||
},
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"example",
|
||||
"tutorial",
|
||||
"hello world"
|
||||
],
|
||||
"categories": [
|
||||
"official",
|
||||
"example"
|
||||
]
|
||||
}
|
||||
129
plugins/hello_world_plugin/plugin.py
Normal file
129
plugins/hello_world_plugin/plugin.py
Normal file
@@ -0,0 +1,129 @@
|
||||
from typing import List, Tuple, Type, Dict, Any, Optional
|
||||
import logging
|
||||
import random
|
||||
|
||||
from src.plugin_system import (
|
||||
BasePlugin,
|
||||
register_plugin,
|
||||
ComponentInfo,
|
||||
BaseEventHandler,
|
||||
EventType,
|
||||
BaseTool,
|
||||
PlusCommand,
|
||||
CommandArgs,
|
||||
ChatType,
|
||||
BaseAction,
|
||||
ActionActivationType,
|
||||
ConfigField,
|
||||
)
|
||||
from src.plugin_system.base.base_event import HandlerResult
|
||||
|
||||
|
||||
class StartupMessageHandler(BaseEventHandler):
|
||||
"""启动时打印消息的事件处理器。"""
|
||||
|
||||
handler_name = "hello_world_startup_handler"
|
||||
handler_description = "在机器人启动时打印一条日志。"
|
||||
init_subscribe = [EventType.ON_START]
|
||||
|
||||
async def execute(self, params: dict) -> HandlerResult:
|
||||
logging.info("🎉 Hello World 插件已启动,准备就绪!")
|
||||
return HandlerResult(success=True, continue_process=True)
|
||||
|
||||
|
||||
class GetSystemInfoTool(BaseTool):
|
||||
"""一个提供系统信息的示例工具。"""
|
||||
|
||||
name = "get_system_info"
|
||||
description = "获取当前系统的模拟版本和状态信息。"
|
||||
available_for_llm = True
|
||||
parameters = []
|
||||
|
||||
async def execute(self, function_args: Dict[str, Any]) -> Dict[str, Any]:
|
||||
return {"name": self.name, "content": "系统版本: 1.0.1, 状态: 运行正常"}
|
||||
|
||||
|
||||
class HelloCommand(PlusCommand):
|
||||
"""一个简单的 /hello 命令,使用配置文件中的问候语。"""
|
||||
|
||||
command_name = "hello"
|
||||
command_description = "向机器人发送一个简单的问候。"
|
||||
command_aliases = ["hi", "你好"]
|
||||
chat_type_allow = ChatType.ALL
|
||||
|
||||
async def execute(self, args: CommandArgs) -> Tuple[bool, Optional[str], bool]:
|
||||
greeting = str(self.get_config("greeting.message", "Hello, World! 我是一个由 MoFox_Bot 驱动的插件。"))
|
||||
await self.send_text(greeting)
|
||||
return True, "成功发送问候", True
|
||||
|
||||
|
||||
class RandomEmojiAction(BaseAction):
|
||||
"""一个随机发送表情的动作。"""
|
||||
|
||||
action_name = "random_emoji"
|
||||
action_description = "随机发送一个表情符号,增加聊天的趣味性。"
|
||||
activation_type = ActionActivationType.RANDOM
|
||||
random_activation_probability = 0.1
|
||||
action_require = ["当对话气氛轻松时", "可以用来回应简单的情感表达"]
|
||||
associated_types = ["text"]
|
||||
|
||||
async def execute(self) -> Tuple[bool, str]:
|
||||
emojis = ["😊", "😂", "👍", "🎉", "🤔", "🤖"]
|
||||
await self.send_text(random.choice(emojis))
|
||||
return True, "成功发送了一个随机表情"
|
||||
|
||||
|
||||
@register_plugin
|
||||
class HelloWorldPlugin(BasePlugin):
|
||||
"""一个包含四大核心组件和高级配置功能的入门示例插件。"""
|
||||
|
||||
plugin_name = "hello_world_plugin"
|
||||
enable_plugin = True
|
||||
dependencies = []
|
||||
python_dependencies = []
|
||||
config_file_name = "config.toml"
|
||||
enable_plugin = False
|
||||
|
||||
config_schema = {
|
||||
"meta": {
|
||||
"config_version": ConfigField(
|
||||
type=int,
|
||||
default=1,
|
||||
description="配置文件版本,请勿手动修改。"
|
||||
),
|
||||
},
|
||||
"greeting": {
|
||||
"message": ConfigField(
|
||||
type=str,
|
||||
default="这是来自配置文件的问候!👋",
|
||||
description="HelloCommand 使用的问候语。"
|
||||
),
|
||||
},
|
||||
"components": {
|
||||
"hello_command_enabled": ConfigField(
|
||||
type=bool,
|
||||
default=True,
|
||||
description="是否启用 /hello 命令。"
|
||||
),
|
||||
"random_emoji_action_enabled": ConfigField(
|
||||
type=bool,
|
||||
default=True,
|
||||
description="是否启用随机表情动作。"
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]:
|
||||
"""根据配置文件动态注册插件的功能组件。"""
|
||||
components: List[Tuple[ComponentInfo, Type]] = []
|
||||
|
||||
components.append((StartupMessageHandler.get_handler_info(), StartupMessageHandler))
|
||||
components.append((GetSystemInfoTool.get_tool_info(), GetSystemInfoTool))
|
||||
|
||||
if self.get_config("components.hello_command_enabled", True):
|
||||
components.append((HelloCommand.get_command_info(), HelloCommand))
|
||||
|
||||
if self.get_config("components.random_emoji_action_enabled", True):
|
||||
components.append((RandomEmojiAction.get_action_info(), RandomEmojiAction))
|
||||
|
||||
return components
|
||||
@@ -1,6 +1,550 @@
|
||||
from typing import List, Tuple
|
||||
|
||||
from src.plugin_system import BasePlugin, BaseEventHandler, register_plugin, EventType, ConfigField, BaseAction, ActionActivationType
|
||||
from src.plugin_system import BaseEventHandler
|
||||
from src.plugin_system.base.base_event import HandlerResult
|
||||
from src.plugin_system.core.event_manager import event_manager
|
||||
|
||||
from .src.send_handler import send_handler
|
||||
from .event_types import *
|
||||
|
||||
from src.common.logger import get_logger
|
||||
logger = get_logger("napcat_adapter")
|
||||
|
||||
|
||||
class SetProfileHandler(BaseEventHandler):
|
||||
handler_name: str = "napcat_set_qq_profile_handler"
|
||||
handler_description: str = "设置账号信息"
|
||||
weight: int = 100
|
||||
intercept_message: bool = False
|
||||
init_subscribe = [NapcatEvent.ACCOUNT.SET_PROFILE]
|
||||
|
||||
async def execute(self,params:dict):
|
||||
raw = params.get("raw",{})
|
||||
nickname = params.get("nickname","")
|
||||
personal_note = params.get("personal_note","")
|
||||
sex = params.get("sex","")
|
||||
|
||||
if params.get("raw",""):
|
||||
nickname = raw.get("nickname","")
|
||||
personal_note = raw.get("personal_note","")
|
||||
sex = raw.get("sex","")
|
||||
|
||||
if not nickname:
|
||||
logger.error("事件 napcat_set_qq_profile 缺少必要参数: nickname ")
|
||||
return HandlerResult(False,False,{"status":"error"})
|
||||
|
||||
payload = {
|
||||
"nickname": nickname,
|
||||
"personal_note": personal_note,
|
||||
"sex": sex
|
||||
}
|
||||
response = await send_handler.send_message_to_napcat(action="set_qq_profile",params=payload)
|
||||
if response.get("status","") == "ok":
|
||||
if response.get("data","").get("result","") == 0:
|
||||
return HandlerResult(True,True,response)
|
||||
else:
|
||||
logger.error(f"事件 napcat_set_qq_profile 请求失败!err={response.get("data","").get("errMsg","")}")
|
||||
return HandlerResult(False,False,response)
|
||||
else:
|
||||
logger.error("事件 napcat_set_qq_profile 请求失败!")
|
||||
return HandlerResult(False,False,{"status":"error"})
|
||||
|
||||
|
||||
class GetOnlineClientsHandler(BaseEventHandler):
|
||||
handler_name: str = "napcat_get_online_clients_handler"
|
||||
handler_description: str = "获取当前账号在线客户端列表"
|
||||
weight: int = 100
|
||||
intercept_message: bool = False
|
||||
init_subscribe = [NapcatEvent.ACCOUNT.GET_ONLINE_CLIENTS]
|
||||
|
||||
async def execute(self, params: dict):
|
||||
raw = params.get("raw", {})
|
||||
no_cache = params.get("no_cache", False)
|
||||
|
||||
if params.get("raw", ""):
|
||||
no_cache = raw.get("no_cache", False)
|
||||
|
||||
payload = {
|
||||
"no_cache": no_cache
|
||||
}
|
||||
response = await send_handler.send_message_to_napcat(action="get_online_clients", params=payload)
|
||||
if response.get("status", "") == "ok":
|
||||
return HandlerResult(True, True, response)
|
||||
else:
|
||||
logger.error("事件 napcat_get_online_clients 请求失败!")
|
||||
return HandlerResult(False, False, {"status": "error"})
|
||||
|
||||
|
||||
class SetOnlineStatusHandler(BaseEventHandler):
|
||||
handler_name: str = "napcat_set_online_status_handler"
|
||||
handler_description: str = "设置在线状态"
|
||||
weight: int = 100
|
||||
intercept_message: bool = False
|
||||
init_subscribe = [NapcatEvent.ACCOUNT.SET_ONLINE_STATUS]
|
||||
|
||||
async def execute(self, params: dict):
|
||||
raw = params.get("raw", {})
|
||||
status = params.get("status", "")
|
||||
ext_status = params.get("ext_status", "0")
|
||||
battery_status = params.get("battery_status", "0")
|
||||
|
||||
if params.get("raw", ""):
|
||||
status = raw.get("status", "")
|
||||
ext_status = raw.get("ext_status", "0")
|
||||
battery_status = raw.get("battery_status", "0")
|
||||
|
||||
if not status:
|
||||
logger.error("事件 napcat_set_online_status 缺少必要参数: status")
|
||||
return HandlerResult(False, False, {"status": "error"})
|
||||
|
||||
payload = {
|
||||
"status": status,
|
||||
"ext_status": ext_status,
|
||||
"battery_status": battery_status
|
||||
}
|
||||
response = await send_handler.send_message_to_napcat(action="set_online_status", params=payload)
|
||||
if response.get("status", "") == "ok":
|
||||
return HandlerResult(True, True, response)
|
||||
else:
|
||||
logger.error("事件 napcat_set_online_status 请求失败!")
|
||||
return HandlerResult(False, False, {"status": "error"})
|
||||
|
||||
|
||||
class GetFriendsWithCategoryHandler(BaseEventHandler):
|
||||
handler_name: str = "napcat_get_friends_with_category_handler"
|
||||
handler_description: str = "获取好友分组列表"
|
||||
weight: int = 100
|
||||
intercept_message: bool = False
|
||||
init_subscribe = [NapcatEvent.ACCOUNT.GET_FRIENDS_WITH_CATEGORY]
|
||||
|
||||
async def execute(self, params: dict):
|
||||
payload = {}
|
||||
response = await send_handler.send_message_to_napcat(action="get_friends_with_category", params=payload)
|
||||
if response.get("status", "") == "ok":
|
||||
return HandlerResult(True, True, response)
|
||||
else:
|
||||
logger.error("事件 napcat_get_friends_with_category 请求失败!")
|
||||
return HandlerResult(False, False, {"status": "error"})
|
||||
|
||||
|
||||
class SetAvatarHandler(BaseEventHandler):
|
||||
handler_name: str = "napcat_set_qq_avatar_handler"
|
||||
handler_description: str = "设置头像"
|
||||
weight: int = 100
|
||||
intercept_message: bool = False
|
||||
init_subscribe = [NapcatEvent.ACCOUNT.SET_AVATAR]
|
||||
|
||||
async def execute(self, params: dict):
|
||||
raw = params.get("raw", {})
|
||||
file = params.get("file", "")
|
||||
|
||||
if params.get("raw", ""):
|
||||
file = raw.get("file", "")
|
||||
|
||||
if not file:
|
||||
logger.error("事件 napcat_set_qq_avatar 缺少必要参数: file")
|
||||
return HandlerResult(False, False, {"status": "error"})
|
||||
|
||||
payload = {
|
||||
"file": file
|
||||
}
|
||||
response = await send_handler.send_message_to_napcat(action="set_qq_avatar", params=payload)
|
||||
if response.get("status", "") == "ok":
|
||||
return HandlerResult(True, True, response)
|
||||
else:
|
||||
logger.error("事件 napcat_set_qq_avatar 请求失败!")
|
||||
return HandlerResult(False, False, {"status": "error"})
|
||||
|
||||
|
||||
class SendLikeHandler(BaseEventHandler):
|
||||
handler_name: str = "napcat_send_like_handler"
|
||||
handler_description: str = "点赞"
|
||||
weight: int = 100
|
||||
intercept_message: bool = False
|
||||
init_subscribe = [NapcatEvent.ACCOUNT.SEND_LIKE]
|
||||
|
||||
async def execute(self, params: dict):
|
||||
raw = params.get("raw", {})
|
||||
user_id = params.get("user_id", "")
|
||||
times = params.get("times", 1)
|
||||
|
||||
if params.get("raw", ""):
|
||||
user_id = raw.get("user_id", "")
|
||||
times = raw.get("times", 1)
|
||||
|
||||
if not user_id:
|
||||
logger.error("事件 napcat_send_like 缺少必要参数: user_id")
|
||||
return HandlerResult(False, False, {"status": "error"})
|
||||
|
||||
payload = {
|
||||
"user_id": str(user_id),
|
||||
"times": times
|
||||
}
|
||||
response = await send_handler.send_message_to_napcat(action="send_like", params=payload)
|
||||
if response.get("status", "") == "ok":
|
||||
return HandlerResult(True, True, response)
|
||||
else:
|
||||
logger.error("事件 napcat_send_like 请求失败!")
|
||||
return HandlerResult(False, False, {"status": "error"})
|
||||
|
||||
|
||||
class SetFriendAddRequestHandler(BaseEventHandler):
|
||||
handler_name: str = "napcat_set_friend_add_request_handler"
|
||||
handler_description: str = "处理好友请求"
|
||||
weight: int = 100
|
||||
intercept_message: bool = False
|
||||
init_subscribe = [NapcatEvent.ACCOUNT.SET_FRIEND_ADD_REQUEST]
|
||||
|
||||
async def execute(self, params: dict):
|
||||
raw = params.get("raw", {})
|
||||
flag = params.get("flag", "")
|
||||
approve = params.get("approve", True)
|
||||
remark = params.get("remark", "")
|
||||
|
||||
if params.get("raw", ""):
|
||||
flag = raw.get("flag", "")
|
||||
approve = raw.get("approve", True)
|
||||
remark = raw.get("remark", "")
|
||||
|
||||
if not flag or approve is None or remark is None:
|
||||
logger.error("事件 napcat_set_friend_add_request 缺少必要参数")
|
||||
return HandlerResult(False, False, {"status": "error"})
|
||||
|
||||
payload = {
|
||||
"flag": flag,
|
||||
"approve": approve,
|
||||
"remark": remark
|
||||
}
|
||||
response = await send_handler.send_message_to_napcat(action="set_friend_add_request", params=payload)
|
||||
if response.get("status", "") == "ok":
|
||||
return HandlerResult(True, True, response)
|
||||
else:
|
||||
logger.error("事件 napcat_set_friend_add_request 请求失败!")
|
||||
return HandlerResult(False, False, {"status": "error"})
|
||||
|
||||
|
||||
class SetSelfLongnickHandler(BaseEventHandler):
|
||||
handler_name: str = "napcat_set_self_longnick_handler"
|
||||
handler_description: str = "设置个性签名"
|
||||
weight: int = 100
|
||||
intercept_message: bool = False
|
||||
init_subscribe = [NapcatEvent.ACCOUNT.SET_SELF_LONGNICK]
|
||||
|
||||
async def execute(self, params: dict):
|
||||
raw = params.get("raw", {})
|
||||
longNick = params.get("longNick", "")
|
||||
|
||||
if params.get("raw", ""):
|
||||
longNick = raw.get("longNick", "")
|
||||
|
||||
if not longNick:
|
||||
logger.error("事件 napcat_set_self_longnick 缺少必要参数: longNick")
|
||||
return HandlerResult(False, False, {"status": "error"})
|
||||
|
||||
payload = {
|
||||
"longNick": longNick
|
||||
}
|
||||
response = await send_handler.send_message_to_napcat(action="set_self_longnick", params=payload)
|
||||
if response.get("status", "") == "ok":
|
||||
if response.get("data", {}).get("result", "") == 0:
|
||||
return HandlerResult(True, True, response)
|
||||
else:
|
||||
logger.error(f"事件 napcat_set_self_longnick 请求失败!err={response.get('data', {}).get('errMsg', '')}")
|
||||
return HandlerResult(False, False, response)
|
||||
else:
|
||||
logger.error("事件 napcat_set_self_longnick 请求失败!")
|
||||
return HandlerResult(False, False, {"status": "error"})
|
||||
|
||||
|
||||
class GetLoginInfoHandler(BaseEventHandler):
|
||||
handler_name: str = "napcat_get_login_info_handler"
|
||||
handler_description: str = "获取登录号信息"
|
||||
weight: int = 100
|
||||
intercept_message: bool = False
|
||||
init_subscribe = [NapcatEvent.ACCOUNT.GET_LOGIN_INFO]
|
||||
|
||||
async def execute(self, params: dict):
|
||||
payload = {}
|
||||
response = await send_handler.send_message_to_napcat(action="get_login_info", params=payload)
|
||||
if response.get("status", "") == "ok":
|
||||
return HandlerResult(True, True, response)
|
||||
else:
|
||||
logger.error("事件 napcat_get_login_info 请求失败!")
|
||||
return HandlerResult(False, False, {"status": "error"})
|
||||
|
||||
|
||||
class GetRecentContactHandler(BaseEventHandler):
|
||||
handler_name: str = "napcat_get_recent_contact_handler"
|
||||
handler_description: str = "最近消息列表"
|
||||
weight: int = 100
|
||||
intercept_message: bool = False
|
||||
init_subscribe = [NapcatEvent.ACCOUNT.GET_RECENT_CONTACT]
|
||||
|
||||
async def execute(self, params: dict):
|
||||
raw = params.get("raw", {})
|
||||
count = params.get("count", 20)
|
||||
|
||||
if params.get("raw", ""):
|
||||
count = raw.get("count", 20)
|
||||
|
||||
payload = {
|
||||
"count": count
|
||||
}
|
||||
response = await send_handler.send_message_to_napcat(action="get_recent_contact", params=payload)
|
||||
if response.get("status", "") == "ok":
|
||||
return HandlerResult(True, True, response)
|
||||
else:
|
||||
logger.error("事件 napcat_get_recent_contact 请求失败!")
|
||||
return HandlerResult(False, False, {"status": "error"})
|
||||
|
||||
|
||||
class GetStrangerInfoHandler(BaseEventHandler):
|
||||
handler_name: str = "napcat_get_stranger_info_handler"
|
||||
handler_description: str = "获取(指定)账号信息"
|
||||
weight: int = 100
|
||||
intercept_message: bool = False
|
||||
init_subscribe = [NapcatEvent.ACCOUNT.GET_STRANGER_INFO]
|
||||
|
||||
async def execute(self, params: dict):
|
||||
raw = params.get("raw", {})
|
||||
user_id = params.get("user_id", "")
|
||||
|
||||
if params.get("raw", ""):
|
||||
user_id = raw.get("user_id", "")
|
||||
|
||||
if not user_id:
|
||||
logger.error("事件 napcat_get_stranger_info 缺少必要参数: user_id")
|
||||
return HandlerResult(False, False, {"status": "error"})
|
||||
|
||||
payload = {
|
||||
"user_id": str(user_id)
|
||||
}
|
||||
response = await send_handler.send_message_to_napcat(action="get_stranger_info", params=payload)
|
||||
if response.get("status", "") == "ok":
|
||||
return HandlerResult(True, True, response)
|
||||
else:
|
||||
logger.error("事件 napcat_get_stranger_info 请求失败!")
|
||||
return HandlerResult(False, False, {"status": "error"})
|
||||
|
||||
|
||||
class GetFriendListHandler(BaseEventHandler):
|
||||
handler_name: str = "napcat_get_friend_list_handler"
|
||||
handler_description: str = "获取好友列表"
|
||||
weight: int = 100
|
||||
intercept_message: bool = False
|
||||
init_subscribe = [NapcatEvent.ACCOUNT.GET_FRIEND_LIST]
|
||||
|
||||
async def execute(self, params: dict):
|
||||
raw = params.get("raw", {})
|
||||
no_cache = params.get("no_cache", False)
|
||||
|
||||
if params.get("raw", ""):
|
||||
no_cache = raw.get("no_cache", False)
|
||||
|
||||
payload = {
|
||||
"no_cache": no_cache
|
||||
}
|
||||
response = await send_handler.send_message_to_napcat(action="get_friend_list", params=payload)
|
||||
if response.get("status", "") == "ok":
|
||||
return HandlerResult(True, True, response)
|
||||
else:
|
||||
logger.error("事件 napcat_get_friend_list 请求失败!")
|
||||
return HandlerResult(False, False, {"status": "error"})
|
||||
|
||||
|
||||
class GetProfileLikeHandler(BaseEventHandler):
|
||||
handler_name: str = "napcat_get_profile_like_handler"
|
||||
handler_description: str = "获取点赞列表"
|
||||
weight: int = 100
|
||||
intercept_message: bool = False
|
||||
init_subscribe = [NapcatEvent.ACCOUNT.GET_PROFILE_LIKE]
|
||||
|
||||
async def execute(self, params: dict):
|
||||
raw = params.get("raw", {})
|
||||
user_id = params.get("user_id", "")
|
||||
start = params.get("start", 0)
|
||||
count = params.get("count", 10)
|
||||
|
||||
if params.get("raw", ""):
|
||||
user_id = raw.get("user_id", "")
|
||||
start = raw.get("start", 0)
|
||||
count = raw.get("count", 10)
|
||||
|
||||
payload = {
|
||||
"start": start,
|
||||
"count": count
|
||||
}
|
||||
if user_id:
|
||||
payload["user_id"] = str(user_id)
|
||||
|
||||
response = await send_handler.send_message_to_napcat(action="get_profile_like", params=payload)
|
||||
if response.get("status", "") == "ok":
|
||||
return HandlerResult(True, True, response)
|
||||
else:
|
||||
logger.error("事件 napcat_get_profile_like 请求失败!")
|
||||
return HandlerResult(False, False, {"status": "error"})
|
||||
|
||||
|
||||
class DeleteFriendHandler(BaseEventHandler):
|
||||
handler_name: str = "napcat_delete_friend_handler"
|
||||
handler_description: str = "删除好友"
|
||||
weight: int = 100
|
||||
intercept_message: bool = False
|
||||
init_subscribe = [NapcatEvent.ACCOUNT.DELETE_FRIEND]
|
||||
|
||||
async def execute(self, params: dict):
|
||||
raw = params.get("raw", {})
|
||||
user_id = params.get("user_id", "")
|
||||
temp_block = params.get("temp_block", False)
|
||||
temp_both_del = params.get("temp_both_del", False)
|
||||
|
||||
if params.get("raw", ""):
|
||||
user_id = raw.get("user_id", "")
|
||||
temp_block = raw.get("temp_block", False)
|
||||
temp_both_del = raw.get("temp_both_del", False)
|
||||
|
||||
if not user_id or temp_block is None or temp_both_del is None:
|
||||
logger.error("事件 napcat_delete_friend 缺少必要参数")
|
||||
return HandlerResult(False, False, {"status": "error"})
|
||||
|
||||
payload = {
|
||||
"user_id": str(user_id),
|
||||
"temp_block": temp_block,
|
||||
"temp_both_del": temp_both_del
|
||||
}
|
||||
response = await send_handler.send_message_to_napcat(action="delete_friend", params=payload)
|
||||
if response.get("status", "") == "ok":
|
||||
if response.get("data", {}).get("result", "") == 0:
|
||||
return HandlerResult(True, True, response)
|
||||
else:
|
||||
logger.error(f"事件 napcat_delete_friend 请求失败!err={response.get('data', {}).get('errMsg', '')}")
|
||||
return HandlerResult(False, False, response)
|
||||
else:
|
||||
logger.error("事件 napcat_delete_friend 请求失败!")
|
||||
return HandlerResult(False, False, {"status": "error"})
|
||||
|
||||
|
||||
class GetUserStatusHandler(BaseEventHandler):
|
||||
handler_name: str = "napcat_get_user_status_handler"
|
||||
handler_description: str = "获取(指定)用户状态"
|
||||
weight: int = 100
|
||||
intercept_message: bool = False
|
||||
init_subscribe = [NapcatEvent.ACCOUNT.GET_USER_STATUS]
|
||||
|
||||
async def execute(self, params: dict):
|
||||
raw = params.get("raw", {})
|
||||
user_id = params.get("user_id", "")
|
||||
|
||||
if params.get("raw", ""):
|
||||
user_id = raw.get("user_id", "")
|
||||
|
||||
if not user_id:
|
||||
logger.error("事件 napcat_get_user_status 缺少必要参数: user_id")
|
||||
return HandlerResult(False, False, {"status": "error"})
|
||||
|
||||
payload = {
|
||||
"user_id": str(user_id)
|
||||
}
|
||||
response = await send_handler.send_message_to_napcat(action="get_user_status", params=payload)
|
||||
if response.get("status", "") == "ok":
|
||||
return HandlerResult(True, True, response)
|
||||
else:
|
||||
logger.error("事件 napcat_get_user_status 请求失败!")
|
||||
return HandlerResult(False, False, {"status": "error"})
|
||||
|
||||
|
||||
class GetStatusHandler(BaseEventHandler):
|
||||
handler_name: str = "napcat_get_status_handler"
|
||||
handler_description: str = "获取状态"
|
||||
weight: int = 100
|
||||
intercept_message: bool = False
|
||||
init_subscribe = [NapcatEvent.ACCOUNT.GET_STATUS]
|
||||
|
||||
async def execute(self, params: dict):
|
||||
payload = {}
|
||||
response = await send_handler.send_message_to_napcat(action="get_status", params=payload)
|
||||
if response.get("status", "") == "ok":
|
||||
return HandlerResult(True, True, response)
|
||||
else:
|
||||
logger.error("事件 napcat_get_status 请求失败!")
|
||||
return HandlerResult(False, False, {"status": "error"})
|
||||
|
||||
|
||||
class GetMiniAppArkHandler(BaseEventHandler):
|
||||
handler_name: str = "napcat_get_mini_app_ark_handler"
|
||||
handler_description: str = "获取小程序卡片"
|
||||
weight: int = 100
|
||||
intercept_message: bool = False
|
||||
init_subscribe = [NapcatEvent.ACCOUNT.GET_MINI_APP_ARK]
|
||||
|
||||
async def execute(self, params: dict):
|
||||
raw = params.get("raw", {})
|
||||
type = params.get("type", "")
|
||||
title = params.get("title", "")
|
||||
desc = params.get("desc", "")
|
||||
picUrl = params.get("picUrl", "")
|
||||
jumpUrl = params.get("jumpUrl", "")
|
||||
webUrl = params.get("webUrl", "")
|
||||
rawArkData = params.get("rawArkData", False)
|
||||
|
||||
if params.get("raw", ""):
|
||||
type = raw.get("type", "")
|
||||
title = raw.get("title", "")
|
||||
desc = raw.get("desc", "")
|
||||
picUrl = raw.get("picUrl", "")
|
||||
jumpUrl = raw.get("jumpUrl", "")
|
||||
webUrl = raw.get("webUrl", "")
|
||||
rawArkData = raw.get("rawArkData", False)
|
||||
|
||||
if not type or not title or not desc or not picUrl or not jumpUrl:
|
||||
logger.error("事件 napcat_get_mini_app_ark 缺少必要参数")
|
||||
return HandlerResult(False, False, {"status": "error"})
|
||||
|
||||
payload = {
|
||||
"type": type,
|
||||
"title": title,
|
||||
"desc": desc,
|
||||
"picUrl": picUrl,
|
||||
"jumpUrl": jumpUrl,
|
||||
"webUrl": webUrl,
|
||||
"rawArkData": rawArkData
|
||||
}
|
||||
response = await send_handler.send_message_to_napcat(action="get_mini_app_ark", params=payload)
|
||||
if response.get("status", "") == "ok":
|
||||
return HandlerResult(True, True, response)
|
||||
else:
|
||||
logger.error("事件 napcat_get_mini_app_ark 请求失败!")
|
||||
return HandlerResult(False, False, {"status": "error"})
|
||||
|
||||
|
||||
class SetDiyOnlineStatusHandler(BaseEventHandler):
|
||||
handler_name: str = "napcat_set_diy_online_status_handler"
|
||||
handler_description: str = "设置自定义在线状态"
|
||||
weight: int = 100
|
||||
intercept_message: bool = False
|
||||
init_subscribe = [NapcatEvent.ACCOUNT.SET_DIY_ONLINE_STATUS]
|
||||
|
||||
async def execute(self, params: dict):
|
||||
raw = params.get("raw", {})
|
||||
face_id = params.get("face_id", "")
|
||||
face_type = params.get("face_type", "0")
|
||||
wording = params.get("wording", "")
|
||||
|
||||
if params.get("raw", ""):
|
||||
face_id = raw.get("face_id", "")
|
||||
face_type = raw.get("face_type", "0")
|
||||
wording = raw.get("wording", "")
|
||||
|
||||
if not face_id:
|
||||
logger.error("事件 napcat_set_diy_online_status 缺少必要参数: face_id")
|
||||
return HandlerResult(False, False, {"status": "error"})
|
||||
|
||||
payload = {
|
||||
"face_id": str(face_id),
|
||||
"face_type": str(face_type),
|
||||
"wording": wording
|
||||
}
|
||||
response = await send_handler.send_message_to_napcat(action="set_diy_online_status", params=payload)
|
||||
if response.get("status", "") == "ok":
|
||||
return HandlerResult(True, True, response)
|
||||
else:
|
||||
logger.error("事件 napcat_set_diy_online_status 请求失败!")
|
||||
return HandlerResult(False, False, {"status": "error"})
|
||||
|
||||
@@ -1,69 +1,663 @@
|
||||
from enum import Enum
|
||||
|
||||
class NapcatEvent(Enum):
|
||||
# napcat插件事件枚举类
|
||||
"""
|
||||
napcat插件事件枚举类
|
||||
"""
|
||||
class ON_RECEIVED(Enum):
|
||||
"""
|
||||
该分类下均为消息接受事件,只能由napcat_plugin触发
|
||||
"""
|
||||
TEXT = "napcat_on_received_text" # 接收到文本消息
|
||||
FACE = "napcat_on_received_face" # 接收到表情消息
|
||||
REPLY = "napcat_on_received_reply" # 接收到回复消息
|
||||
IMAGE = "napcat_on_received_image" # 接收到图像消息
|
||||
RECORD = "napcat_on_received_record" # 接收到语音消息
|
||||
VIDEO = "napcat_on_received_video" # 接收到视频消息
|
||||
AT = "napcat_on_received_at" # 接收到at消息
|
||||
DICE = "napcat_on_received_dice" # 接收到骰子消息
|
||||
SHAKE = "napcat_on_received_shake" # 接收到屏幕抖动消息
|
||||
JSON = "napcat_on_received_json" # 接收到JSON消息
|
||||
RPS = "napcat_on_received_rps" # 接收到魔法猜拳消息
|
||||
FRIEND_INPUT = "napcat_on_friend_input" # 好友正在输入
|
||||
TEXT = "napcat_on_received_text"
|
||||
'''接收到文本消息'''
|
||||
FACE = "napcat_on_received_face"
|
||||
'''接收到表情消息'''
|
||||
REPLY = "napcat_on_received_reply"
|
||||
'''接收到回复消息'''
|
||||
IMAGE = "napcat_on_received_image"
|
||||
'''接收到图像消息'''
|
||||
RECORD = "napcat_on_received_record"
|
||||
'''接收到语音消息'''
|
||||
VIDEO = "napcat_on_received_video"
|
||||
'''接收到视频消息'''
|
||||
AT = "napcat_on_received_at"
|
||||
'''接收到at消息'''
|
||||
DICE = "napcat_on_received_dice"
|
||||
'''接收到骰子消息'''
|
||||
SHAKE = "napcat_on_received_shake"
|
||||
'''接收到屏幕抖动消息'''
|
||||
JSON = "napcat_on_received_json"
|
||||
'''接收到JSON消息'''
|
||||
RPS = "napcat_on_received_rps"
|
||||
'''接收到魔法猜拳消息'''
|
||||
FRIEND_INPUT = "napcat_on_friend_input"
|
||||
'''好友正在输入'''
|
||||
|
||||
class ACCOUNT(Enum):
|
||||
"""
|
||||
该分类是对账户相关的操作,只能由外部触发,napcat_plugin负责处理
|
||||
"""
|
||||
SET_PROFILE = "napcat_set_qq_profile" # 设置账号信息
|
||||
GET_ONLINE_CLIENTS = "napcat_get_online_clients" # 获取当前账号在线客户端列表
|
||||
SET_ONLINE_STATUS = "napcat_set_online_status" # 设置在线状态
|
||||
GET_FRIENDS_WITH_CATEGORY = "napcat_get_friends_with_category" # 获取好友分组列表
|
||||
SET_AVATAR = "napcat_set_qq_avatar" # 设置头像
|
||||
SEND_LIKE = "napcat_send_like" # 点赞
|
||||
SET_FRIEND_ADD_REQUEST = "napcat_set_friend_add_request" # 处理好友请求
|
||||
SET_SELF_LONGNICK = "napcat_set_self_longnick" # 设置个性签名
|
||||
GET_LOGIN_INFO = "napcat_get_login_info" # 获取登录号信息
|
||||
GET_RECENT_CONTACT = "napcat_get_recent_contact" # 最近消息列表
|
||||
GET_STRANGER_INFO = "napcat_get_stranger_info" # 获取(指定)账号信息
|
||||
GET_FRIEND_LIST = "napcat_get_friend_list" # 获取好友列表
|
||||
GET_PROFILE_LIKE = "napcat_get_profile_like" # 获取点赞列表
|
||||
DELETE_FRIEND = "napcat_delete_friend" # 删除好友
|
||||
GET_USER_STATUS = "napcat_get_user_status" # 获取用户状态
|
||||
GET_STATUS = "napcat_get_status" # 获取状态
|
||||
GET_MINI_APP_ARK = "napcat_get_mini_app_ark" # 获取小程序卡片
|
||||
SET_DIY_ONLINE_STATUS = "napcat_set_diy_online_status" # 设置自定义在线状态
|
||||
SET_PROFILE = "napcat_set_qq_profile"
|
||||
'''设置账号信息
|
||||
|
||||
Args:
|
||||
nickname (Optional[str]): 名称(必须)
|
||||
personal_note (Optional[str]): 个性签名
|
||||
sex ('0'|'1'|'2'): 性别
|
||||
raw (Optional[dict]): 原始请求体
|
||||
|
||||
Returns:
|
||||
dict: {
|
||||
"status": "ok",
|
||||
"retcode": 0,
|
||||
"data": {
|
||||
"result": 0,
|
||||
"errMsg": "string"
|
||||
},
|
||||
"message": "string",
|
||||
"wording": "string",
|
||||
"echo": "string"
|
||||
}
|
||||
|
||||
'''
|
||||
GET_ONLINE_CLIENTS = "napcat_get_online_clients"
|
||||
'''获取当前账号在线客户端列表
|
||||
|
||||
Args:
|
||||
no_cache (Optional[bool]): 是否不使用缓存
|
||||
raw (Optional[dict]): 原始请求体
|
||||
|
||||
Returns:
|
||||
dict: {
|
||||
"status": "ok",
|
||||
"retcode": 0,
|
||||
"data": [
|
||||
"string"
|
||||
],
|
||||
"message": "string",
|
||||
"wording": "string",
|
||||
"echo": "string"
|
||||
}
|
||||
'''
|
||||
SET_ONLINE_STATUS = "napcat_set_online_status"
|
||||
'''设置在线状态
|
||||
|
||||
Args:
|
||||
status (Optional[str]): 状态代码(必须)
|
||||
ext_status (Optional[str]): 额外状态代码,默认为0
|
||||
battery_status (Optional[str]): 电池信息,默认为0
|
||||
raw (Optional[dict]): 原始请求体
|
||||
|
||||
Returns:
|
||||
dict: {
|
||||
"status": "ok",
|
||||
"retcode": 0,
|
||||
"data": null,
|
||||
"message": "string",
|
||||
"wording": "string",
|
||||
"echo": "string"
|
||||
}
|
||||
'''
|
||||
GET_FRIENDS_WITH_CATEGORY = "napcat_get_friends_with_category"
|
||||
'''获取好友分组列表
|
||||
|
||||
Returns:
|
||||
dict: {
|
||||
"status": "ok",
|
||||
"retcode": 0,
|
||||
"data": [
|
||||
{
|
||||
"categoryId": 0,
|
||||
"categorySortId": 0,
|
||||
"categoryName": "string",
|
||||
"categoryMbCount": 0,
|
||||
"onlineCount": 0,
|
||||
"buddyList": [
|
||||
{
|
||||
"birthday_year": 0,
|
||||
"birthday_month": 0,
|
||||
"birthday_day": 0,
|
||||
"user_id": 0,
|
||||
"age": 0,
|
||||
"phone_num": "string",
|
||||
"email": "string",
|
||||
"category_id": 0,
|
||||
"nickname": "string",
|
||||
"remark": "string",
|
||||
"sex": "string",
|
||||
"level": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"message": "string",
|
||||
"wording": "string",
|
||||
"echo": "string"
|
||||
}
|
||||
'''
|
||||
SET_AVATAR = "napcat_set_qq_avatar"
|
||||
'''设置头像
|
||||
|
||||
Args:
|
||||
file (Optional[str]): 文件路径或base64(必需)
|
||||
raw (Optional[dict]): 原始请求体
|
||||
|
||||
Returns:
|
||||
dict: {
|
||||
"status": "ok",
|
||||
"retcode": 0,
|
||||
"data": null,
|
||||
"message": "string",
|
||||
"wording": "string",
|
||||
"echo": "string"
|
||||
}
|
||||
'''
|
||||
SEND_LIKE = "napcat_send_like"
|
||||
'''点赞
|
||||
|
||||
Args:
|
||||
user_id (Optional[str|int]): 用户id(必需)
|
||||
times (Optional[int]): 点赞次数,默认1
|
||||
raw (Optional[dict]): 原始请求体
|
||||
|
||||
Returns:
|
||||
dict: {
|
||||
"status": "ok",
|
||||
"retcode": 0,
|
||||
"data": null,
|
||||
"message": "string",
|
||||
"wording": "string",
|
||||
"echo": "string"
|
||||
}
|
||||
'''
|
||||
SET_FRIEND_ADD_REQUEST = "napcat_set_friend_add_request"
|
||||
'''处理好友请求
|
||||
|
||||
Args:
|
||||
flag (Optional[str]): 请求id(必需)
|
||||
approve (Optional[bool]): 是否同意(必需)
|
||||
remark (Optional[str]): 好友备注(必需)
|
||||
raw (Optional[dict]): 原始请求体
|
||||
|
||||
Returns:
|
||||
dict: {
|
||||
"status": "ok",
|
||||
"retcode": 0,
|
||||
"data": null,
|
||||
"message": "string",
|
||||
"wording": "string",
|
||||
"echo": "string"
|
||||
}
|
||||
'''
|
||||
SET_SELF_LONGNICK = "napcat_set_self_longnick"
|
||||
'''设置个性签名
|
||||
|
||||
Args:
|
||||
longNick (Optional[str]): 内容(必需)
|
||||
raw (Optional[dict]): 原始请求体
|
||||
|
||||
Returns:
|
||||
dict: {
|
||||
"status": "ok",
|
||||
"retcode": 0,
|
||||
"data": {
|
||||
"result": 0,
|
||||
"errMsg": "string"
|
||||
},
|
||||
"message": "string",
|
||||
"wording": "string",
|
||||
"echo": "string"
|
||||
}
|
||||
'''
|
||||
GET_LOGIN_INFO = "napcat_get_login_info"
|
||||
'''获取登录号信息
|
||||
|
||||
Returns:
|
||||
dict: {
|
||||
"status": "ok",
|
||||
"retcode": 0,
|
||||
"data": {
|
||||
"user_id": 0,
|
||||
"nickname": "string"
|
||||
},
|
||||
"message": "string",
|
||||
"wording": "string",
|
||||
"echo": "string"
|
||||
}
|
||||
'''
|
||||
GET_RECENT_CONTACT = "napcat_get_recent_contact"
|
||||
'''最近消息列表
|
||||
|
||||
Args:
|
||||
count (Optional[int]): 会话数量
|
||||
raw (Optional[dict]): 原始请求体
|
||||
|
||||
Returns:
|
||||
dict: {
|
||||
"status": "ok",
|
||||
"retcode": 0,
|
||||
"data": [
|
||||
{
|
||||
"lastestMsg": {
|
||||
"self_id": 0,
|
||||
"user_id": 0,
|
||||
"time": 0,
|
||||
"real_seq": "string",
|
||||
"message_type": "string",
|
||||
"sender": {
|
||||
"user_id": 0,
|
||||
"nickname": "string",
|
||||
"sex": "male",
|
||||
"age": 0,
|
||||
"card": "string",
|
||||
"role": "owner"
|
||||
},
|
||||
"raw_message": "string",
|
||||
"font": 0,
|
||||
"sub_type": "string",
|
||||
"message": [
|
||||
{
|
||||
"type": "text",
|
||||
"data": {
|
||||
"text": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"message_format": "string",
|
||||
"post_type": "string",
|
||||
"group_id": 0
|
||||
},
|
||||
"peerUin": "string",
|
||||
"remark": "string",
|
||||
"msgTime": "string",
|
||||
"chatType": 0,
|
||||
"msgId": "string",
|
||||
"sendNickName": "string",
|
||||
"sendMemberName": "string",
|
||||
"peerName": "string"
|
||||
}
|
||||
],
|
||||
"message": "string",
|
||||
"wording": "string",
|
||||
"echo": "string"
|
||||
}
|
||||
'''
|
||||
GET_STRANGER_INFO = "napcat_get_stranger_info"
|
||||
'''获取(指定)账号信息
|
||||
|
||||
Args:
|
||||
user_id (Optional[str|int]): 用户id(必需)
|
||||
raw (Optional[dict]): 原始请求体
|
||||
|
||||
Returns:
|
||||
dict: {
|
||||
"status": "ok",
|
||||
"retcode": 0,
|
||||
"data": {
|
||||
"user_id": 0,
|
||||
"uid": "string",
|
||||
"uin": "string",
|
||||
"nickname": "string",
|
||||
"age": 0,
|
||||
"qid": "string",
|
||||
"qqLevel": 0,
|
||||
"sex": "string",
|
||||
"long_nick": "string",
|
||||
"reg_time": 0,
|
||||
"is_vip": true,
|
||||
"is_years_vip": true,
|
||||
"vip_level": 0,
|
||||
"remark": "string",
|
||||
"status": 0,
|
||||
"login_days": 0
|
||||
},
|
||||
"message": "string",
|
||||
"wording": "string",
|
||||
"echo": "string"
|
||||
}
|
||||
'''
|
||||
GET_FRIEND_LIST = "napcat_get_friend_list"
|
||||
'''获取好友列表
|
||||
|
||||
Args:
|
||||
no_cache (Opetional[bool]): 是否不使用缓存
|
||||
raw (Optional[dict]): 原始请求体
|
||||
|
||||
Returns:
|
||||
dict: {
|
||||
"status": "ok",
|
||||
"retcode": 0,
|
||||
"data": [
|
||||
{
|
||||
"birthday_year": 0,
|
||||
"birthday_month": 0,
|
||||
"birthday_day": 0,
|
||||
"user_id": 0,
|
||||
"age": 0,
|
||||
"phone_num": "string",
|
||||
"email": "string",
|
||||
"category_id": 0,
|
||||
"nickname": "string",
|
||||
"remark": "string",
|
||||
"sex": "string",
|
||||
"level": 0
|
||||
}
|
||||
],
|
||||
"message": "string",
|
||||
"wording": "string",
|
||||
"echo": "string"
|
||||
}
|
||||
'''
|
||||
GET_PROFILE_LIKE = "napcat_get_profile_like"
|
||||
'''获取点赞列表
|
||||
|
||||
Args:
|
||||
user_id (Opetional[str|int]): 用户id,指定用户,不填为获取所有
|
||||
start (Opetional[int]): 起始值
|
||||
count (Opetional[int]): 返回数量
|
||||
raw (Optional[dict]): 原始请求体
|
||||
|
||||
Returns:
|
||||
dict: {
|
||||
"status": "ok",
|
||||
"retcode": 0,
|
||||
"data": {
|
||||
"uid": "string",
|
||||
"time": 0,
|
||||
"favoriteInfo": {
|
||||
"total_count": 0,
|
||||
"last_time": 0,
|
||||
"today_count": 0,
|
||||
"userInfos": [
|
||||
{
|
||||
"uid": "string",
|
||||
"src": 0,
|
||||
"latestTime": 0,
|
||||
"count": 0,
|
||||
"giftCount": 0,
|
||||
"customId": 0,
|
||||
"lastCharged": 0,
|
||||
"bAvailableCnt": 0,
|
||||
"bTodayVotedCnt": 0,
|
||||
"nick": "string",
|
||||
"gender": 0,
|
||||
"age": 0,
|
||||
"isFriend": true,
|
||||
"isvip": true,
|
||||
"isSvip": true,
|
||||
"uin": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
"voteInfo": {
|
||||
"total_count": 0,
|
||||
"new_count": 0,
|
||||
"new_nearby_count": 0,
|
||||
"last_visit_time": 0,
|
||||
"userInfos": [
|
||||
{
|
||||
"uid": "string",
|
||||
"src": 0,
|
||||
"latestTime": 0,
|
||||
"count": 0,
|
||||
"giftCount": 0,
|
||||
"customId": 0,
|
||||
"lastCharged": 0,
|
||||
"bAvailableCnt": 0,
|
||||
"bTodayVotedCnt": 0,
|
||||
"nick": "string",
|
||||
"gender": 0,
|
||||
"age": 0,
|
||||
"isFriend": true,
|
||||
"isvip": true,
|
||||
"isSvip": true,
|
||||
"uin": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"message": "string",
|
||||
"wording": "string",
|
||||
"echo": "string"
|
||||
}
|
||||
'''
|
||||
DELETE_FRIEND = "napcat_delete_friend"
|
||||
'''删除好友
|
||||
|
||||
Args:
|
||||
user_id (Opetional[str|int]): 用户id(必需)
|
||||
temp_block (Opetional[bool]): 拉黑(必需)
|
||||
temp_both_del (Opetional[bool]): 双向删除(必需)
|
||||
raw (Optional[dict]): 原始请求体
|
||||
|
||||
Returns:
|
||||
dict: {
|
||||
"status": "ok",
|
||||
"retcode": 0,
|
||||
"data": {
|
||||
"result": 0,
|
||||
"errMsg": "string"
|
||||
},
|
||||
"message": "string",
|
||||
"wording": "string",
|
||||
"echo": "string"
|
||||
}
|
||||
'''
|
||||
GET_USER_STATUS = "napcat_get_user_status"
|
||||
'''获取(指定)用户状态
|
||||
|
||||
Args:
|
||||
user_id (Opetional[str|int]): 用户id(必需)
|
||||
raw (Optional[dict]): 原始请求体
|
||||
|
||||
Returns:
|
||||
dict: {
|
||||
"status": "ok",
|
||||
"retcode": 0,
|
||||
"data": {
|
||||
"status": 0,
|
||||
"ext_status": 0
|
||||
},
|
||||
"message": "string",
|
||||
"wording": "string",
|
||||
"echo": "string"
|
||||
}
|
||||
'''
|
||||
GET_STATUS = "napcat_get_status"
|
||||
'''获取状态
|
||||
|
||||
Returns:
|
||||
dict: {
|
||||
"status": "ok",
|
||||
"retcode": 0,
|
||||
"data": {
|
||||
"online": true,
|
||||
"good": true,
|
||||
"stat": {}
|
||||
},
|
||||
"message": "string",
|
||||
"wording": "string",
|
||||
"echo": "string"
|
||||
}
|
||||
'''
|
||||
GET_MINI_APP_ARK = "napcat_get_mini_app_ark"
|
||||
'''获取小程序卡片
|
||||
|
||||
Args:
|
||||
type (Optional[str]): 类型(如bili、weibo,必需)
|
||||
title (Optional[str]): 标题(必需)
|
||||
desc (Optional[str]): 描述(必需)
|
||||
picUrl (Optional[str]): 图片URL(必需)
|
||||
jumpUrl (Optional[str]): 跳转URL(必需)
|
||||
webUrl (Optional[str]): 网页URL
|
||||
rawArkData (Optional[bool]): 是否返回原始ark数据
|
||||
raw (Optional[dict]): 原始请求体
|
||||
|
||||
Returns:
|
||||
dict: {
|
||||
"status": "ok",
|
||||
"retcode": 0,
|
||||
"data": {
|
||||
"appName": "string",
|
||||
"appView": "string",
|
||||
"ver": "string",
|
||||
"desc": "string",
|
||||
"prompt": "string",
|
||||
"metaData": {
|
||||
"detail_1": {
|
||||
"appid": "string",
|
||||
"appType": 0,
|
||||
"title": "string",
|
||||
"desc": "string",
|
||||
"icon": "string",
|
||||
"preview": "string",
|
||||
"url": "string",
|
||||
"scene": 0,
|
||||
"host": {
|
||||
"uin": 0,
|
||||
"nick": "string"
|
||||
},
|
||||
"shareTemplateId": "string",
|
||||
"shareTemplateData": {},
|
||||
"showLittleTail": "string",
|
||||
"gamePoints": "string",
|
||||
"gamePointsUrl": "string",
|
||||
"shareOrigin": 0
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"type": "string",
|
||||
"width": 0,
|
||||
"height": 0,
|
||||
"forward": 0,
|
||||
"autoSize": 0,
|
||||
"ctime": 0,
|
||||
"token": "string"
|
||||
}
|
||||
},
|
||||
"message": "string",
|
||||
"wording": "string",
|
||||
"echo": "string"
|
||||
}
|
||||
'''
|
||||
SET_DIY_ONLINE_STATUS = "napcat_set_diy_online_status"
|
||||
'''设置自定义在线状态
|
||||
|
||||
Args:
|
||||
face_id (Optional[str|int]): 表情ID(必需)
|
||||
face_type (Optional[str|int]): 表情类型
|
||||
wording (Optional[str]):描述文本
|
||||
raw (Optional[dict]): 原始请求体
|
||||
|
||||
Returns:
|
||||
dict: {
|
||||
"status": "ok",
|
||||
"retcode": 0,
|
||||
"data": "string",
|
||||
"message": "string",
|
||||
"wording": "string",
|
||||
"echo": "string"
|
||||
}
|
||||
'''
|
||||
|
||||
class MESSAGE(Enum):
|
||||
"""
|
||||
该分类是对信息相关的操作,只能由外部触发,napcat_plugin负责处理
|
||||
"""
|
||||
SEND_GROUP_POKE = "napcat_send_group_poke" # 发送群聊戳一戳
|
||||
SEND_PRIVATE_MSG = "napcat_send_private_msg" # 发送私聊消息
|
||||
SEND_POKE = "napcat_send_friend_poke" # 发送戳一戳
|
||||
DELETE_MSG = "napcat_delete_msg" # 撤回消息
|
||||
GET_GROUP_MSG_HISTORY = "napcat_get_group_msg_history" # 获取群历史消息
|
||||
GET_MSG = "napcat_get_msg" # 获取消息详情
|
||||
GET_FORWARD_MSG = "napcat_get_forward_msg" # 获取合并转发消息
|
||||
SET_MSG_EMOJI_LIKE = "napcat_set_msg_emoji_like" # 贴表情
|
||||
GET_FRIEND_MSG_HISTORY = "napcat_get_friend_msg_history" # 获取好友历史消息
|
||||
FETCH_EMOJI_LIKE = "napcat_fetch_emoji_like" # 获取贴表情详情
|
||||
SEND_FORWARF_MSG = "napcat_send_forward_msg" # 发送合并转发消息
|
||||
GET_RECOED = "napcat_get_record" # 获取语音消息详情
|
||||
SEND_GROUP_AI_RECORD = "napcat_send_group_ai_record" # 发送群AI语音
|
||||
SEND_GROUP_POKE = "napcat_send_group_poke"
|
||||
'''发送群聊戳一戳'''
|
||||
SEND_PRIVATE_MSG = "napcat_send_private_msg"
|
||||
'''发送私聊消息'''
|
||||
SEND_POKE = "napcat_send_friend_poke"
|
||||
'''发送戳一戳'''
|
||||
DELETE_MSG = "napcat_delete_msg"
|
||||
'''撤回消息'''
|
||||
GET_GROUP_MSG_HISTORY = "napcat_get_group_msg_history"
|
||||
'''获取群历史消息'''
|
||||
GET_MSG = "napcat_get_msg"
|
||||
'''获取消息详情'''
|
||||
GET_FORWARD_MSG = "napcat_get_forward_msg"
|
||||
'''获取合并转发消息'''
|
||||
SET_MSG_EMOJI_LIKE = "napcat_set_msg_emoji_like"
|
||||
'''贴表情'''
|
||||
GET_FRIEND_MSG_HISTORY = "napcat_get_friend_msg_history"
|
||||
'''获取好友历史消息'''
|
||||
FETCH_EMOJI_LIKE = "napcat_fetch_emoji_like"
|
||||
'''获取贴表情详情'''
|
||||
SEND_FORWARF_MSG = "napcat_send_forward_msg"
|
||||
'''发送合并转发消息'''
|
||||
GET_RECOED = "napcat_get_record"
|
||||
'''获取语音消息详情'''
|
||||
SEND_GROUP_AI_RECORD = "napcat_send_group_ai_record"
|
||||
'''发送群AI语音'''
|
||||
|
||||
class GROUP(Enum):
|
||||
"""
|
||||
该分类是对群聊相关的操作,只能由外部触发,napcat_plugin负责处理
|
||||
"""
|
||||
|
||||
SET_GROUP_SEARCH = "napcat_set_group_search"
|
||||
'''设置群搜索'''
|
||||
GET_GROUP_DETAIL_INFO = "napcat_get_group_detail_info"
|
||||
'''获取群详细信息'''
|
||||
SET_GROUP_ADD_OPTION = "napcat_set_group_add_option"
|
||||
'''设置群添加选项'''
|
||||
SET_GROUP_ROBOT_ADD_OPTION = "napcat_set_group_robot_add_option"
|
||||
'''设置群机器人添加选项'''
|
||||
SET_GROUP_KICK_MEMBERS = "napcat_set_group_kick_members"
|
||||
'''批量踢出群成员'''
|
||||
SET_GROUP_KICK = "napcat_set_group_kick"
|
||||
'''群踢人'''
|
||||
GET_GROUP_SYSTEM_MSG = "napcat_get_group_system_msg"
|
||||
'''获取群系统消息'''
|
||||
SET_GROUP_BAN = "napcat_set_group_ban"
|
||||
'''群禁言'''
|
||||
GET_ESSENCE_MSG_LIST = "napcat_get_essence_msg_list"
|
||||
'''获取群精华消息'''
|
||||
SET_GROUP_WHOLE_BAN = "napcat_set_group_whole_ban"
|
||||
'''全体禁言'''
|
||||
SET_GROUP_PORTRAINT = "napcat_set_group_portrait"
|
||||
'''设置群头像'''
|
||||
SET_GROUP_ADMIN = "napcat_set_group_admin"
|
||||
'''设置群管理'''
|
||||
SET_GROUP_CARD = "napcat_group_card"
|
||||
'''设置群成员名片'''
|
||||
SET_ESSENCE_MSG = "napcat_set_essence_msg"
|
||||
'''设置群精华消息'''
|
||||
SET_GROUP_NAME = "napcat_set_group_name"
|
||||
'''设置群名'''
|
||||
DELETE_ESSENCE_MSG = "napcat_delete_essence_msg"
|
||||
'''删除群精华消息'''
|
||||
SET_GROUP_LEAVE = "napcat_set_group_leave"
|
||||
'''退群'''
|
||||
SEND_GROUP_NOTICE = "napcat_group_notice"
|
||||
'''发送群公告'''
|
||||
SET_GROUP_SPECIAL_TITLE = "napcat_set_group_special_title"
|
||||
'''设置群头衔'''
|
||||
GET_GROUP_NOTICE = "napcat_get_group_notice"
|
||||
'''获取群公告'''
|
||||
SET_GROUP_ADD_REQUEST = "napcat_set_group_add_request"
|
||||
'''处理加群请求'''
|
||||
GET_GROUP_INFO = "napcat_get_group_info"
|
||||
'''获取群信息'''
|
||||
GET_GROUP_LIST = "napcat_get_group_list"
|
||||
'''获取群列表'''
|
||||
DELETE_GROUP_NOTICE = "napcat_del_group_notice"
|
||||
'''删除群公告'''
|
||||
GET_GROUP_MEMBER_INFO = "napcat_get_group_member_info"
|
||||
'''获取群成员信息'''
|
||||
GET_GROUP_MEMBER_LIST = "napcat_get_group_member_list"
|
||||
'''获取群成员列表'''
|
||||
GET_GROUP_HONOR_INFO = "napcat_get_group_honor_info"
|
||||
'''获取群荣誉'''
|
||||
GET_GROUP_INFO_EX = "napcat_get_group_info_ex"
|
||||
'''获取群信息ex'''
|
||||
GET_GROUP_AT_ALL_REMAIN = "napcat_get_group_at_all_remain"
|
||||
'''获取群 @全体成员 剩余次数'''
|
||||
GET_GROUP_SHUT_LIST = "napcat_get_group_shut_list"
|
||||
'''获取群禁言列表'''
|
||||
GET_GROUP_IGNORED_NOTIFIES = "napcat_get_group_ignored_notifies"
|
||||
'''获取群过滤系统消息'''
|
||||
SET_GROUP_SIGN = "napcat_set_group_sign"
|
||||
'''群打卡'''
|
||||
|
||||
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import sys
|
||||
import asyncio
|
||||
import json
|
||||
import inspect
|
||||
import websockets as Server
|
||||
from . import event_types,CONSTS
|
||||
from . import event_types,CONSTS,event_handlers
|
||||
|
||||
from typing import List, Tuple
|
||||
from typing import List
|
||||
|
||||
from src.plugin_system import BasePlugin, BaseEventHandler, register_plugin, EventType, ConfigField, BaseAction, ActionActivationType
|
||||
from src.plugin_system import BasePlugin, BaseEventHandler, register_plugin, EventType, ConfigField
|
||||
from src.plugin_system.base.base_event import HandlerResult
|
||||
from src.plugin_system.core.event_manager import event_manager
|
||||
|
||||
@@ -26,12 +27,19 @@ from .src.send_handler import send_handler
|
||||
from .src.config import global_config
|
||||
from .src.config.features_config import features_manager
|
||||
from .src.config.migrate_features import auto_migrate_features
|
||||
from .src.mmc_com_layer import mmc_start_com, mmc_stop_com, router
|
||||
from .src.mmc_com_layer import mmc_start_com, router
|
||||
from .src.response_pool import put_response, check_timeout_response
|
||||
from .src.websocket_manager import websocket_manager
|
||||
|
||||
message_queue = asyncio.Queue()
|
||||
|
||||
def get_classes_in_module(module):
|
||||
classes = []
|
||||
for name, member in inspect.getmembers(module):
|
||||
if inspect.isclass(member):
|
||||
classes.append(member)
|
||||
return classes
|
||||
|
||||
class LauchNapcatAdapterHandler(BaseEventHandler):
|
||||
"""自动启动Adapter"""
|
||||
|
||||
@@ -98,6 +106,103 @@ class LauchNapcatAdapterHandler(BaseEventHandler):
|
||||
asyncio.create_task(self.message_process())
|
||||
asyncio.create_task(check_timeout_response())
|
||||
|
||||
class APITestHandler(BaseEventHandler):
|
||||
handler_name: str = "napcat_api_test_handler"
|
||||
handler_description: str = "接口测试"
|
||||
weight: int = 100
|
||||
intercept_message: bool = False
|
||||
init_subscribe = [EventType.ON_MESSAGE]
|
||||
|
||||
async def execute(self,_):
|
||||
logger.info("5s后开始测试napcat接口...")
|
||||
await asyncio.sleep(5)
|
||||
'''
|
||||
# 测试获取登录信息
|
||||
logger.info("测试获取登录信息...")
|
||||
res = await event_manager.trigger_event(
|
||||
event_types.NapcatEvent.ACCOUNT.GET_LOGIN_INFO
|
||||
)
|
||||
logger.info(f"GET_LOGIN_INFO: {res.get_message_result()}")
|
||||
|
||||
# 测试获取状态
|
||||
logger.info("测试获取状态...")
|
||||
res = await event_manager.trigger_event(
|
||||
event_types.NapcatEvent.ACCOUNT.GET_STATUS
|
||||
)
|
||||
logger.info(f"GET_STATUS: {res.get_message_result()}")
|
||||
|
||||
# 测试获取好友列表
|
||||
logger.info("测试获取好友列表...")
|
||||
res = await event_manager.trigger_event(
|
||||
event_types.NapcatEvent.ACCOUNT.GET_FRIEND_LIST,
|
||||
no_cache=False
|
||||
)
|
||||
logger.info(f"GET_FRIEND_LIST: {res.get_message_result()}")
|
||||
|
||||
# 测试获取好友分组列表
|
||||
logger.info("测试获取好友分组列表...")
|
||||
res = await event_manager.trigger_event(
|
||||
event_types.NapcatEvent.ACCOUNT.GET_FRIENDS_WITH_CATEGORY
|
||||
)
|
||||
logger.info(f"GET_FRIENDS_WITH_CATEGORY: {res.get_message_result()}")
|
||||
|
||||
# 测试获取在线客户端
|
||||
logger.info("测试获取在线客户端...")
|
||||
res = await event_manager.trigger_event(
|
||||
event_types.NapcatEvent.ACCOUNT.GET_ONLINE_CLIENTS,
|
||||
no_cache=True
|
||||
)
|
||||
logger.info(f"GET_ONLINE_CLIENTS: {res.get_message_result()}")
|
||||
|
||||
# 测试获取最近联系人
|
||||
logger.info("测试获取最近联系人...")
|
||||
res = await event_manager.trigger_event(
|
||||
event_types.NapcatEvent.ACCOUNT.GET_RECENT_CONTACT,
|
||||
count=5
|
||||
)
|
||||
logger.info(f"GET_RECENT_CONTACT: {res.get_message_result()}")
|
||||
|
||||
# 测试设置个性签名
|
||||
logger.info("测试设置个性签名...")
|
||||
res = await event_manager.trigger_event(
|
||||
event_types.NapcatEvent.ACCOUNT.SET_SELF_LONGNICK,
|
||||
longNick="测试个性签名 - 来自APITestHandler"
|
||||
)
|
||||
logger.info(f"SET_SELF_LONGNICK: {res.get_message_result()}")
|
||||
|
||||
# 测试设置在线状态
|
||||
logger.info("测试设置在线状态...")
|
||||
res = await event_manager.trigger_event(
|
||||
event_types.NapcatEvent.ACCOUNT.SET_ONLINE_STATUS,
|
||||
status="11",
|
||||
ext_status="0",
|
||||
battery_status="0"
|
||||
)
|
||||
logger.info(f"SET_ONLINE_STATUS: {res.get_message_result()}")
|
||||
|
||||
# 测试设置自定义在线状态
|
||||
logger.info("测试设置自定义在线状态...")
|
||||
res = await event_manager.trigger_event(
|
||||
event_types.NapcatEvent.ACCOUNT.SET_DIY_ONLINE_STATUS,
|
||||
face_id="358",
|
||||
face_type="1",
|
||||
wording="测试中..."
|
||||
)
|
||||
logger.info(f"SET_DIY_ONLINE_STATUS: {res.get_message_result()}")
|
||||
|
||||
# 测试获取点赞列表
|
||||
logger.info("测试获取点赞列表...")
|
||||
res = await event_manager.trigger_event(
|
||||
event_types.NapcatEvent.ACCOUNT.GET_PROFILE_LIKE,
|
||||
start=0,
|
||||
count=5
|
||||
)
|
||||
logger.info(f"GET_PROFILE_LIKE: {res.get_message_result()}")
|
||||
|
||||
logger.info("所有ACCOUNT接口测试完成!")
|
||||
'''
|
||||
return HandlerResult(True,True,"所有接口测试完成")
|
||||
|
||||
@register_plugin
|
||||
class NapcatAdapterPlugin(BasePlugin):
|
||||
plugin_name = CONSTS.PLUGIN_NAME
|
||||
@@ -121,11 +226,23 @@ class NapcatAdapterPlugin(BasePlugin):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
for e in event_types.NapcatEvent.ON_RECEIVED:
|
||||
event_manager.register_event(e ,allowed_triggers=[self.plugin_name])
|
||||
|
||||
|
||||
for e in event_types.NapcatEvent.ACCOUNT:
|
||||
event_manager.register_event(e,allowed_subscribers=[f"{e.value}_handler"])
|
||||
|
||||
for e in event_types.NapcatEvent.GROUP:
|
||||
event_manager.register_event(e,allowed_subscribers=[f"{e.value}_handler"])
|
||||
|
||||
for e in event_types.NapcatEvent.MESSAGE:
|
||||
event_manager.register_event(e,allowed_subscribers=[f"{e.value}_handler"])
|
||||
|
||||
def get_plugin_components(self):
|
||||
components = []
|
||||
components.append((LauchNapcatAdapterHandler.get_handler_info(), LauchNapcatAdapterHandler))
|
||||
components.append((APITestHandler.get_handler_info(), APITestHandler))
|
||||
for handler in get_classes_in_module(event_handlers):
|
||||
if issubclass(handler,BaseEventHandler):
|
||||
components.append((handler.get_handler_info(), handler))
|
||||
return components
|
||||
|
||||
@@ -351,15 +351,15 @@ class MessageHandler:
|
||||
else:
|
||||
logger.warning("reply处理失败")
|
||||
case RealMessageType.image:
|
||||
logger.debug(f"开始处理图片消息段")
|
||||
logger.debug("开始处理图片消息段")
|
||||
ret_seg = await self.handle_image_message(sub_message)
|
||||
if ret_seg:
|
||||
await event_manager.trigger_event(NapcatEvent.ON_RECEIVED.IMAGE,plugin_name=PLUGIN_NAME,message_seg=ret_seg)
|
||||
seg_message.append(ret_seg)
|
||||
logger.debug(f"图片处理成功,添加到消息段")
|
||||
logger.debug("图片处理成功,添加到消息段")
|
||||
else:
|
||||
logger.warning("image处理失败")
|
||||
logger.debug(f"图片消息段处理完成")
|
||||
logger.debug("图片消息段处理完成")
|
||||
case RealMessageType.record:
|
||||
ret_seg = await self.handle_record_message(sub_message)
|
||||
if ret_seg:
|
||||
|
||||
@@ -133,7 +133,7 @@ class CycleProcessor:
|
||||
await stop_typing()
|
||||
|
||||
# 在一轮动作执行完毕后,增加睡眠压力
|
||||
if self.context.energy_manager and global_config.wakeup_system.enable_insomnia_system:
|
||||
if self.context.energy_manager and global_config.sleep_system.enable_insomnia_system:
|
||||
if action_type not in ["no_reply", "no_action"]:
|
||||
self.context.energy_manager.increase_sleep_pressure()
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ class EnergyManager:
|
||||
|
||||
if is_sleeping:
|
||||
# 睡眠中:减少睡眠压力
|
||||
decay_per_10s = global_config.wakeup_system.sleep_pressure_decay_rate / 6
|
||||
decay_per_10s = global_config.sleep_system.sleep_pressure_decay_rate / 6
|
||||
self.context.sleep_pressure -= decay_per_10s
|
||||
self.context.sleep_pressure = max(self.context.sleep_pressure, 0)
|
||||
self._log_sleep_pressure_change("睡眠压力释放")
|
||||
@@ -145,7 +145,7 @@ class EnergyManager:
|
||||
"""
|
||||
在执行动作后增加睡眠压力
|
||||
"""
|
||||
increment = global_config.wakeup_system.sleep_pressure_increment
|
||||
increment = global_config.sleep_system.sleep_pressure_increment
|
||||
self.context.sleep_pressure += increment
|
||||
self.context.sleep_pressure = min(self.context.sleep_pressure, 100.0) # 设置一个100的上限
|
||||
self._log_sleep_pressure_change("执行动作,睡眠压力累积")
|
||||
|
||||
@@ -8,7 +8,7 @@ from src.config.config import global_config
|
||||
from src.person_info.relationship_builder_manager import relationship_builder_manager
|
||||
from src.chat.express.expression_learner import expression_learner_manager
|
||||
from src.plugin_system.base.component_types import ChatMode
|
||||
from src.schedule.schedule_manager import schedule_manager
|
||||
from src.schedule.schedule_manager import schedule_manager, SleepState
|
||||
from src.plugin_system.apis import message_api
|
||||
|
||||
from .hfc_context import HfcContext
|
||||
@@ -196,30 +196,14 @@ class HeartFChatting:
|
||||
- FOCUS模式:直接处理所有消息并检查退出条件
|
||||
- NORMAL模式:检查进入FOCUS模式的条件,并通过normal_mode_handler处理消息
|
||||
"""
|
||||
is_sleeping = schedule_manager.is_sleeping(self.wakeup_manager)
|
||||
|
||||
# --- 失眠状态管理 ---
|
||||
if self.context.is_in_insomnia and time.time() > self.context.insomnia_end_time:
|
||||
# 失眠状态结束
|
||||
self.context.is_in_insomnia = False
|
||||
await self.proactive_thinker.trigger_goodnight_thinking()
|
||||
|
||||
if is_sleeping and not self.context.was_sleeping:
|
||||
# 刚刚进入睡眠状态,进行一次入睡检查
|
||||
if self.wakeup_manager and self.wakeup_manager.check_for_insomnia():
|
||||
# 触发失眠
|
||||
self.context.is_in_insomnia = True
|
||||
duration = global_config.wakeup_system.insomnia_duration_minutes * 60
|
||||
self.context.insomnia_end_time = time.time() + duration
|
||||
|
||||
# 判断失眠原因并触发思考
|
||||
reason = "random"
|
||||
if self.context.sleep_pressure < global_config.wakeup_system.sleep_pressure_threshold:
|
||||
reason = "low_pressure"
|
||||
await self.proactive_thinker.trigger_insomnia_thinking(reason)
|
||||
# --- 核心状态更新 ---
|
||||
await schedule_manager.update_sleep_state(self.wakeup_manager)
|
||||
current_sleep_state = schedule_manager.get_current_sleep_state()
|
||||
is_sleeping = current_sleep_state == SleepState.SLEEPING
|
||||
is_in_insomnia = current_sleep_state == SleepState.INSOMNIA
|
||||
|
||||
# 核心修复:在睡眠模式(包括失眠)下获取消息时,不过滤命令消息,以确保@消息能被接收
|
||||
filter_command_flag = not is_sleeping
|
||||
filter_command_flag = not (is_sleeping or is_in_insomnia)
|
||||
|
||||
recent_messages = message_api.get_messages_by_time_in_chat(
|
||||
chat_id=self.context.stream_id,
|
||||
@@ -239,18 +223,18 @@ class HeartFChatting:
|
||||
self.context.last_read_time = time.time()
|
||||
|
||||
# 处理唤醒度逻辑
|
||||
if is_sleeping:
|
||||
if current_sleep_state in [SleepState.SLEEPING, SleepState.PREPARING_SLEEP, SleepState.INSOMNIA]:
|
||||
self._handle_wakeup_messages(recent_messages)
|
||||
# 再次检查睡眠状态,因为_handle_wakeup_messages可能会触发唤醒
|
||||
current_is_sleeping = schedule_manager.is_sleeping(self.wakeup_manager)
|
||||
|
||||
if not self.context.is_in_insomnia and current_is_sleeping:
|
||||
# 仍然在睡眠,跳过本轮的消息处理
|
||||
# 再次获取最新状态,因为 handle_wakeup 可能导致状态变为 WOKEN_UP
|
||||
current_sleep_state = schedule_manager.get_current_sleep_state()
|
||||
|
||||
if current_sleep_state == SleepState.SLEEPING:
|
||||
# 只有在纯粹的 SLEEPING 状态下才跳过消息处理
|
||||
return has_new_messages
|
||||
else:
|
||||
# 从睡眠中被唤醒,需要继续处理本轮消息
|
||||
|
||||
if current_sleep_state == SleepState.WOKEN_UP:
|
||||
logger.info(f"{self.context.log_prefix} 从睡眠中被唤醒,将处理积压的消息。")
|
||||
self.context.last_wakeup_time = time.time()
|
||||
|
||||
# 根据聊天模式处理新消息
|
||||
if self.context.loop_mode == ChatMode.FOCUS:
|
||||
@@ -273,12 +257,12 @@ class HeartFChatting:
|
||||
|
||||
# --- 重新入睡逻辑 ---
|
||||
# 如果被吵醒了,并且在一定时间内没有新消息,则尝试重新入睡
|
||||
if schedule_manager._is_woken_up and not has_new_messages:
|
||||
re_sleep_delay = global_config.wakeup_system.re_sleep_delay_minutes * 60
|
||||
if schedule_manager.get_current_sleep_state() == SleepState.WOKEN_UP and not has_new_messages:
|
||||
re_sleep_delay = global_config.sleep_system.re_sleep_delay_minutes * 60
|
||||
# 使用 last_message_time 来判断空闲时间
|
||||
if time.time() - self.context.last_message_time > re_sleep_delay:
|
||||
logger.info(f"{self.context.log_prefix} 已被唤醒且超过 {re_sleep_delay / 60} 分钟无新消息,尝试重新入睡。")
|
||||
schedule_manager.reset_wakeup_state()
|
||||
schedule_manager.reset_sleep_state_after_wakeup()
|
||||
|
||||
# 保存HFC上下文状态
|
||||
self.context.save_context_state()
|
||||
|
||||
@@ -46,11 +46,6 @@ class HfcContext:
|
||||
self.sleep_pressure = 0.0
|
||||
self.was_sleeping = False # 用于检测睡眠状态的切换
|
||||
|
||||
# 失眠状态
|
||||
self.is_in_insomnia: bool = False
|
||||
self.insomnia_end_time: float = 0.0
|
||||
self.last_wakeup_time: float = 0.0 # 被吵醒的时间
|
||||
|
||||
self.last_message_time = time.time()
|
||||
self.last_read_time = time.time() - 10
|
||||
|
||||
@@ -78,8 +73,6 @@ class HfcContext:
|
||||
if state and isinstance(state, dict):
|
||||
self.energy_value = state.get("energy_value", 5.0)
|
||||
self.sleep_pressure = state.get("sleep_pressure", 0.0)
|
||||
self.is_in_insomnia = state.get("is_in_insomnia", False)
|
||||
self.insomnia_end_time = state.get("insomnia_end_time", 0.0)
|
||||
logger = get_logger("hfc_context")
|
||||
logger.info(f"{self.log_prefix} 成功从本地存储加载HFC上下文状态: {state}")
|
||||
else:
|
||||
@@ -91,9 +84,6 @@ class HfcContext:
|
||||
state = {
|
||||
"energy_value": self.energy_value,
|
||||
"sleep_pressure": self.sleep_pressure,
|
||||
"is_in_insomnia": self.is_in_insomnia,
|
||||
"insomnia_end_time": self.insomnia_end_time,
|
||||
"last_wakeup_time": self.last_wakeup_time,
|
||||
}
|
||||
local_storage[self._get_storage_key()] = state
|
||||
logger = get_logger("hfc_context")
|
||||
|
||||
@@ -31,22 +31,15 @@ class WakeUpManager:
|
||||
self.log_interval = 30
|
||||
|
||||
# 从配置文件获取参数
|
||||
wakeup_config = global_config.wakeup_system
|
||||
self.wakeup_threshold = wakeup_config.wakeup_threshold
|
||||
self.private_message_increment = wakeup_config.private_message_increment
|
||||
self.group_mention_increment = wakeup_config.group_mention_increment
|
||||
self.decay_rate = wakeup_config.decay_rate
|
||||
self.decay_interval = wakeup_config.decay_interval
|
||||
self.angry_duration = wakeup_config.angry_duration
|
||||
self.enabled = wakeup_config.enable
|
||||
self.angry_prompt = wakeup_config.angry_prompt
|
||||
|
||||
# 失眠系统参数
|
||||
self.insomnia_enabled = wakeup_config.enable_insomnia_system
|
||||
self.sleep_pressure_threshold = wakeup_config.sleep_pressure_threshold
|
||||
self.deep_sleep_threshold = wakeup_config.deep_sleep_threshold
|
||||
self.insomnia_chance_low_pressure = wakeup_config.insomnia_chance_low_pressure
|
||||
self.insomnia_chance_normal_pressure = wakeup_config.insomnia_chance_normal_pressure
|
||||
sleep_config = global_config.sleep_system
|
||||
self.wakeup_threshold = sleep_config.wakeup_threshold
|
||||
self.private_message_increment = sleep_config.private_message_increment
|
||||
self.group_mention_increment = sleep_config.group_mention_increment
|
||||
self.decay_rate = sleep_config.decay_rate
|
||||
self.decay_interval = sleep_config.decay_interval
|
||||
self.angry_duration = sleep_config.angry_duration
|
||||
self.enabled = sleep_config.enable
|
||||
self.angry_prompt = sleep_config.angry_prompt
|
||||
|
||||
self._load_wakeup_state()
|
||||
|
||||
@@ -144,7 +137,9 @@ class WakeUpManager:
|
||||
|
||||
# 只有在休眠且非失眠状态下才累积唤醒度
|
||||
from src.schedule.schedule_manager import schedule_manager
|
||||
if not schedule_manager.is_sleeping() or self.context.is_in_insomnia:
|
||||
from src.schedule.sleep_manager import SleepState
|
||||
current_sleep_state = schedule_manager.get_current_sleep_state()
|
||||
if current_sleep_state != SleepState.SLEEPING:
|
||||
return False
|
||||
|
||||
old_value = self.wakeup_value
|
||||
@@ -220,39 +215,4 @@ class WakeUpManager:
|
||||
"wakeup_threshold": self.wakeup_threshold,
|
||||
"is_angry": self.is_angry,
|
||||
"angry_remaining_time": max(0, self.angry_duration - (time.time() - self.angry_start_time)) if self.is_angry else 0
|
||||
}
|
||||
|
||||
def check_for_insomnia(self) -> bool:
|
||||
"""
|
||||
在尝试入睡时检查是否会失眠
|
||||
|
||||
Returns:
|
||||
bool: 如果失眠则返回 True,否则返回 False
|
||||
"""
|
||||
if not self.insomnia_enabled:
|
||||
return False
|
||||
|
||||
import random
|
||||
|
||||
pressure = self.context.sleep_pressure
|
||||
|
||||
# 压力过高,深度睡眠,极难失眠
|
||||
if pressure > self.deep_sleep_threshold:
|
||||
return False
|
||||
|
||||
# 根据睡眠压力决定失眠概率
|
||||
from src.schedule.schedule_manager import schedule_manager
|
||||
if pressure < self.sleep_pressure_threshold:
|
||||
# 压力不足型失眠
|
||||
if schedule_manager._is_in_voluntary_delay:
|
||||
logger.debug(f"{self.context.log_prefix} 处于主动延迟睡眠期间,跳过压力不足型失眠判断。")
|
||||
elif random.random() < self.insomnia_chance_low_pressure:
|
||||
logger.info(f"{self.context.log_prefix} 睡眠压力不足 ({pressure:.1f}),触发失眠!")
|
||||
return True
|
||||
else:
|
||||
# 压力正常,随机失眠
|
||||
if random.random() < self.insomnia_chance_normal_pressure:
|
||||
logger.info(f"{self.context.log_prefix} 睡眠压力正常 ({pressure:.1f}),触发随机失眠!")
|
||||
return True
|
||||
|
||||
return False
|
||||
}
|
||||
@@ -41,7 +41,7 @@ from src.config.official_configs import (
|
||||
WebSearchConfig,
|
||||
AntiPromptInjectionConfig,
|
||||
PluginsConfig,
|
||||
WakeUpSystemConfig,
|
||||
SleepSystemConfig,
|
||||
MonthlyPlanSystemConfig,
|
||||
CrossContextConfig,
|
||||
PermissionConfig,
|
||||
@@ -390,7 +390,7 @@ class Config(ValidatedConfigBase):
|
||||
dependency_management: DependencyManagementConfig = Field(default_factory=lambda: DependencyManagementConfig(), description="依赖管理配置")
|
||||
web_search: WebSearchConfig = Field(default_factory=lambda: WebSearchConfig(), description="网络搜索配置")
|
||||
plugins: PluginsConfig = Field(default_factory=lambda: PluginsConfig(), description="插件配置")
|
||||
wakeup_system: WakeUpSystemConfig = Field(default_factory=lambda: WakeUpSystemConfig(), description="唤醒度系统配置")
|
||||
sleep_system: SleepSystemConfig = Field(default_factory=lambda: SleepSystemConfig(), description="睡眠系统配置")
|
||||
monthly_plan_system: MonthlyPlanSystemConfig = Field(default_factory=lambda: MonthlyPlanSystemConfig(), description="月层计划系统配置")
|
||||
cross_context: CrossContextConfig = Field(default_factory=lambda: CrossContextConfig(), description="跨群聊上下文共享配置")
|
||||
maizone_intercom: MaizoneIntercomConfig = Field(default_factory=lambda: MaizoneIntercomConfig(), description="Maizone互通组配置")
|
||||
|
||||
@@ -529,16 +529,6 @@ class ScheduleConfig(ValidatedConfigBase):
|
||||
|
||||
enable: bool = Field(default=True, description="启用")
|
||||
guidelines: Optional[str] = Field(default=None, description="指导方针")
|
||||
enable_is_sleep: bool = Field(default=True, description="让AI会根据日程表睡觉和苏醒")
|
||||
|
||||
enable_flexible_sleep: bool = Field(default=True, description="是否启用弹性睡眠")
|
||||
flexible_sleep_pressure_threshold: float = Field(default=40.0, description="触发弹性睡眠的睡眠压力阈值,低于该值可能延迟入睡")
|
||||
max_sleep_delay_minutes: int = Field(default=60, description="单日最大延迟入睡分钟数")
|
||||
|
||||
enable_pre_sleep_notification: bool = Field(default=True, description="是否启用睡前消息")
|
||||
pre_sleep_notification_groups: List[str] = Field(default_factory=list, description="接收睡前消息的群号列表, 格式: [\"platform:group_id1\", \"platform:group_id2\"]")
|
||||
pre_sleep_prompt: str = Field(default="我准备睡觉了,请生成一句简短自然的晚安问候。", description="用于生成睡前消息的提示")
|
||||
|
||||
|
||||
|
||||
class DependencyManagementConfig(ValidatedConfigBase):
|
||||
@@ -617,10 +607,10 @@ class PluginsConfig(ValidatedConfigBase):
|
||||
centralized_config: bool = Field(default=True, description="是否启用插件配置集中化管理")
|
||||
|
||||
|
||||
class WakeUpSystemConfig(ValidatedConfigBase):
|
||||
"""唤醒度与失眠系统配置类"""
|
||||
class SleepSystemConfig(ValidatedConfigBase):
|
||||
"""睡眠系统配置类"""
|
||||
|
||||
enable: bool = Field(default=True, description="是否启用唤醒度系统")
|
||||
enable: bool = Field(default=True, description="是否启用睡眠系统")
|
||||
wakeup_threshold: float = Field(default=15.0, ge=1.0, description="唤醒阈值,达到此值时会被唤醒")
|
||||
private_message_increment: float = Field(default=3.0, ge=0.1, description="私聊消息增加的唤醒度")
|
||||
group_mention_increment: float = Field(default=2.0, ge=0.1, description="群聊艾特增加的唤醒度")
|
||||
@@ -640,6 +630,14 @@ class WakeUpSystemConfig(ValidatedConfigBase):
|
||||
sleep_pressure_increment: float = Field(default=1.5, ge=0.0, description="每次AI执行动作后,增加的睡眠压力值")
|
||||
sleep_pressure_decay_rate: float = Field(default=1.5, ge=0.0, description="睡眠时,每分钟衰减的睡眠压力值")
|
||||
|
||||
# --- 弹性睡眠与睡前消息 ---
|
||||
enable_flexible_sleep: bool = Field(default=True, description="是否启用弹性睡眠")
|
||||
flexible_sleep_pressure_threshold: float = Field(default=40.0, description="触发弹性睡眠的睡眠压力阈值,低于该值可能延迟入睡")
|
||||
max_sleep_delay_minutes: int = Field(default=60, description="单日最大延迟入睡分钟数")
|
||||
enable_pre_sleep_notification: bool = Field(default=True, description="是否启用睡前消息")
|
||||
pre_sleep_notification_groups: List[str] = Field(default_factory=list, description="接收睡前消息的群号列表, 格式: [\"platform:group_id1\", \"platform:group_id2\"]")
|
||||
pre_sleep_prompt: str = Field(default="我准备睡觉了,请生成一句简短自然的晚安问候。", description="用于生成睡前消息的提示")
|
||||
|
||||
|
||||
class MonthlyPlanSystemConfig(ValidatedConfigBase):
|
||||
"""月度计划系统配置类"""
|
||||
|
||||
@@ -9,7 +9,7 @@ class HandlerResult:
|
||||
|
||||
所有事件处理器必须返回此类的实例
|
||||
"""
|
||||
def __init__(self, success: bool, continue_process: bool, message: Any = {}, handler_name: str = ""):
|
||||
def __init__(self, success: bool, continue_process: bool, message: Any = None, handler_name: str = ""):
|
||||
self.success = success
|
||||
self.continue_process = continue_process
|
||||
self.message = message
|
||||
@@ -40,6 +40,19 @@ class HandlerResultsCollection:
|
||||
"""获取continue_process为False的handler结果"""
|
||||
return [result for result in self.results if not result.continue_process]
|
||||
|
||||
def get_message_result(self) -> Any:
|
||||
"""获取handler的message
|
||||
|
||||
当只有一个handler的结果时,直接返回那个handler结果中的message字段
|
||||
否则用字典的形式{handler_name:message}返回
|
||||
"""
|
||||
if len(self.results) == 0:
|
||||
return {}
|
||||
elif len(self.results) == 1:
|
||||
return self.results[0].message
|
||||
else:
|
||||
return {result.handler_name: result.message for result in self.results}
|
||||
|
||||
def get_handler_result(self, handler_name: str) -> Optional[HandlerResult]:
|
||||
"""获取指定handler的结果"""
|
||||
for result in self.results:
|
||||
@@ -70,8 +83,8 @@ class BaseEvent:
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
allowed_subscribers: List[str]=[],
|
||||
allowed_triggers: List[str]=[]
|
||||
allowed_subscribers: List[str] = None,
|
||||
allowed_triggers: List[str] = None
|
||||
):
|
||||
self.name = name
|
||||
self.enabled = True
|
||||
|
||||
@@ -348,7 +348,6 @@ class PluginHotReloadManager:
|
||||
|
||||
def _force_clear_plugin_modules(self, plugin_name: str):
|
||||
"""强制清理插件相关的模块缓存"""
|
||||
import sys
|
||||
|
||||
# 找到所有相关的模块名
|
||||
modules_to_remove = []
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import asyncio
|
||||
import os
|
||||
import traceback
|
||||
import sys
|
||||
import importlib
|
||||
|
||||
from typing import Dict, List, Optional, Tuple, Type, Any
|
||||
|
||||
@@ -6,9 +6,9 @@ from typing import Tuple
|
||||
|
||||
from src.common.logger import get_logger
|
||||
from src.plugin_system import BaseAction, ActionActivationType, ChatMode
|
||||
from src.plugin_system.apis import person_api, generator_api
|
||||
from src.plugin_system.apis import generator_api
|
||||
from src.plugin_system.apis.permission_api import permission_api
|
||||
from ..services.manager import get_qzone_service, get_config_getter
|
||||
from ..services.manager import get_qzone_service
|
||||
|
||||
logger = get_logger("MaiZone.ReadFeedAction")
|
||||
|
||||
|
||||
@@ -6,9 +6,9 @@ from typing import Tuple
|
||||
|
||||
from src.common.logger import get_logger
|
||||
from src.plugin_system import BaseAction, ActionActivationType, ChatMode
|
||||
from src.plugin_system.apis import person_api, generator_api
|
||||
from src.plugin_system.apis import generator_api
|
||||
from src.plugin_system.apis.permission_api import permission_api
|
||||
from ..services.manager import get_qzone_service, get_config_getter
|
||||
from ..services.manager import get_qzone_service
|
||||
|
||||
logger = get_logger("MaiZone.SendFeedAction")
|
||||
|
||||
|
||||
@@ -3,15 +3,12 @@ import asyncio
|
||||
from typing import List, Tuple, Type
|
||||
from src.plugin_system import (
|
||||
BasePlugin,
|
||||
BaseCommand,
|
||||
CommandInfo,
|
||||
ConfigField,
|
||||
register_plugin,
|
||||
plugin_manage_api,
|
||||
component_manage_api,
|
||||
ComponentInfo,
|
||||
ComponentType,
|
||||
send_api,
|
||||
)
|
||||
from src.plugin_system.base.plus_command import PlusCommand
|
||||
from src.plugin_system.base.command_args import CommandArgs
|
||||
|
||||
@@ -1,23 +1,24 @@
|
||||
import orjson
|
||||
import asyncio
|
||||
import random
|
||||
from datetime import datetime, time, timedelta
|
||||
from typing import Optional, List, Dict, Any
|
||||
from typing import Optional, List, Dict, Any, TYPE_CHECKING
|
||||
from lunar_python import Lunar
|
||||
from pydantic import BaseModel, ValidationError, validator
|
||||
|
||||
from src.common.database.sqlalchemy_models import Schedule, get_db_session
|
||||
from src.common.database.monthly_plan_db import (
|
||||
get_smart_plans_for_daily_schedule,
|
||||
update_plan_usage # 保留兼容性
|
||||
update_plan_usage, # 保留兼容性
|
||||
)
|
||||
from src.config.config import global_config, model_config
|
||||
from src.llm_models.utils_model import LLMRequest
|
||||
from src.common.logger import get_logger
|
||||
from json_repair import repair_json
|
||||
from src.manager.async_task_manager import AsyncTask, async_task_manager
|
||||
from src.manager.local_store_manager import local_storage
|
||||
from src.plugin_system.apis import send_api, generator_api
|
||||
from .sleep_manager import SleepManager, SleepState
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from src.chat.chat_loop.wakeup_manager import WakeUpManager
|
||||
|
||||
|
||||
logger = get_logger("schedule_manager")
|
||||
@@ -31,81 +32,85 @@ DEFAULT_SCHEDULE_GUIDELINES = """
|
||||
另外,请保证充足的休眠时间来处理和整合一天的数据。
|
||||
"""
|
||||
|
||||
|
||||
class ScheduleItem(BaseModel):
|
||||
"""单个日程项的Pydantic模型"""
|
||||
|
||||
time_range: str
|
||||
activity: str
|
||||
|
||||
@validator('time_range')
|
||||
|
||||
@validator("time_range")
|
||||
def validate_time_range(cls, v):
|
||||
"""验证时间范围格式"""
|
||||
if not v or '-' not in v:
|
||||
if not v or "-" not in v:
|
||||
raise ValueError("时间范围必须包含'-'分隔符")
|
||||
|
||||
|
||||
try:
|
||||
start_str, end_str = v.split('-', 1)
|
||||
start_str, end_str = v.split("-", 1)
|
||||
start_str = start_str.strip()
|
||||
end_str = end_str.strip()
|
||||
|
||||
|
||||
# 验证时间格式
|
||||
datetime.strptime(start_str, "%H:%M")
|
||||
datetime.strptime(end_str, "%H:%M")
|
||||
|
||||
|
||||
return v
|
||||
except ValueError as e:
|
||||
raise ValueError(f"时间格式无效,应为HH:MM-HH:MM格式: {e}") from e
|
||||
|
||||
@validator('activity')
|
||||
|
||||
@validator("activity")
|
||||
def validate_activity(cls, v):
|
||||
"""验证活动描述"""
|
||||
if not v or not v.strip():
|
||||
raise ValueError("活动描述不能为空")
|
||||
return v.strip()
|
||||
|
||||
|
||||
class ScheduleData(BaseModel):
|
||||
"""完整日程数据的Pydantic模型"""
|
||||
|
||||
schedule: List[ScheduleItem]
|
||||
|
||||
@validator('schedule')
|
||||
|
||||
@validator("schedule")
|
||||
def validate_schedule_completeness(cls, v):
|
||||
"""验证日程是否覆盖24小时"""
|
||||
if not v:
|
||||
raise ValueError("日程不能为空")
|
||||
|
||||
|
||||
# 收集所有时间段
|
||||
time_ranges = []
|
||||
for item in v:
|
||||
try:
|
||||
start_str, end_str = item.time_range.split('-', 1)
|
||||
start_str, end_str = item.time_range.split("-", 1)
|
||||
start_time = datetime.strptime(start_str.strip(), "%H:%M").time()
|
||||
end_time = datetime.strptime(end_str.strip(), "%H:%M").time()
|
||||
time_ranges.append((start_time, end_time))
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
|
||||
# 检查是否覆盖24小时
|
||||
if not cls._check_24_hour_coverage(time_ranges):
|
||||
raise ValueError("日程必须覆盖完整的24小时")
|
||||
|
||||
|
||||
return v
|
||||
|
||||
|
||||
@staticmethod
|
||||
def _check_24_hour_coverage(time_ranges: List[tuple]) -> bool:
|
||||
"""检查时间段是否覆盖24小时"""
|
||||
if not time_ranges:
|
||||
return False
|
||||
|
||||
|
||||
# 将时间转换为分钟数进行计算
|
||||
def time_to_minutes(t: time) -> int:
|
||||
return t.hour * 60 + t.minute
|
||||
|
||||
|
||||
# 创建覆盖情况数组 (1440分钟 = 24小时)
|
||||
covered = [False] * 1440
|
||||
|
||||
|
||||
for start_time, end_time in time_ranges:
|
||||
start_min = time_to_minutes(start_time)
|
||||
end_min = time_to_minutes(end_time)
|
||||
|
||||
|
||||
if start_min <= end_min:
|
||||
# 同一天内的时间段
|
||||
for i in range(start_min, end_min):
|
||||
@@ -117,30 +122,19 @@ class ScheduleData(BaseModel):
|
||||
covered[i] = True
|
||||
for i in range(0, end_min):
|
||||
covered[i] = True
|
||||
|
||||
|
||||
# 检查是否所有分钟都被覆盖
|
||||
return all(covered)
|
||||
|
||||
|
||||
class ScheduleManager:
|
||||
def __init__(self):
|
||||
self.today_schedule: Optional[List[Dict[str, Any]]] = None
|
||||
self.llm = LLMRequest(model_set=model_config.model_task_config.schedule_generator, request_type="schedule")
|
||||
self.max_retries = -1 # 无限重试,直到成功生成标准日程表
|
||||
self.daily_task_started = False
|
||||
self.last_sleep_log_time = 0
|
||||
self.sleep_log_interval = 35 # 日志记录间隔,单位秒
|
||||
self.schedule_generation_running = False # 防止重复生成任务
|
||||
|
||||
# 弹性睡眠相关状态
|
||||
self._is_preparing_sleep: bool = False
|
||||
self._sleep_buffer_end_time: Optional[datetime] = None
|
||||
self._total_delayed_minutes_today: int = 0
|
||||
self._last_sleep_check_date: Optional[datetime.date] = None
|
||||
self._last_fully_slept_log_time: float = 0
|
||||
self._is_in_voluntary_delay: bool = False # 新增:标记是否处于主动延迟睡眠状态
|
||||
self._is_woken_up: bool = False # 新增:标记是否被吵醒
|
||||
|
||||
self._load_sleep_state()
|
||||
self.sleep_manager = SleepManager(self)
|
||||
|
||||
async def start_daily_schedule_generation(self):
|
||||
"""启动每日零点自动生成新日程的任务"""
|
||||
@@ -165,10 +159,10 @@ class ScheduleManager:
|
||||
schedule_record = session.query(Schedule).filter(Schedule.date == today_str).first()
|
||||
if schedule_record:
|
||||
logger.info(f"从数据库加载今天的日程 ({today_str})。")
|
||||
|
||||
|
||||
try:
|
||||
schedule_data = orjson.loads(str(schedule_record.schedule_data))
|
||||
|
||||
|
||||
# 使用Pydantic验证日程数据
|
||||
if self._validate_schedule_with_pydantic(schedule_data):
|
||||
self.today_schedule = schedule_data
|
||||
@@ -197,15 +191,15 @@ class ScheduleManager:
|
||||
if self.schedule_generation_running:
|
||||
logger.info("日程生成任务已在运行中,跳过重复启动")
|
||||
return
|
||||
|
||||
|
||||
# 创建异步任务进行日程生成,不阻塞主程序
|
||||
asyncio.create_task(self._async_generate_and_save_schedule())
|
||||
logger.info("已启动异步日程生成任务")
|
||||
|
||||
|
||||
async def _async_generate_and_save_schedule(self):
|
||||
"""异步生成并保存日程的内部方法"""
|
||||
self.schedule_generation_running = True
|
||||
|
||||
|
||||
try:
|
||||
now = datetime.now()
|
||||
today_str = now.strftime("%Y-%m-%d")
|
||||
@@ -217,7 +211,7 @@ class ScheduleManager:
|
||||
festivals = lunar.getFestivals()
|
||||
other_festivals = lunar.getOtherFestivals()
|
||||
all_festivals = festivals + other_festivals
|
||||
|
||||
|
||||
festival_block = ""
|
||||
if all_festivals:
|
||||
festival_text = "、".join(all_festivals)
|
||||
@@ -228,33 +222,28 @@ class ScheduleManager:
|
||||
used_plan_ids = []
|
||||
if global_config.monthly_plan_system and global_config.monthly_plan_system.enable:
|
||||
# 使用新的智能抽取逻辑
|
||||
avoid_days = getattr(global_config.monthly_plan_system, 'avoid_repetition_days', 7)
|
||||
avoid_days = getattr(global_config.monthly_plan_system, "avoid_repetition_days", 7)
|
||||
# 使用新的智能抽取逻辑
|
||||
avoid_days = getattr(global_config.monthly_plan_system, 'avoid_repetition_days', 7)
|
||||
avoid_days = getattr(global_config.monthly_plan_system, "avoid_repetition_days", 7)
|
||||
sampled_plans = get_smart_plans_for_daily_schedule(
|
||||
current_month_str,
|
||||
max_count=3,
|
||||
avoid_days=avoid_days
|
||||
current_month_str, max_count=3, avoid_days=avoid_days
|
||||
)
|
||||
|
||||
# 如果计划耗尽,则触发补充生成
|
||||
if not sampled_plans:
|
||||
logger.info("可用的月度计划已耗尽或不足,尝试进行补充生成...")
|
||||
from mmc.src.schedule.monthly_plan_manager import monthly_plan_manager
|
||||
|
||||
success = await monthly_plan_manager.generate_monthly_plans(current_month_str)
|
||||
if success:
|
||||
logger.info("补充生成完成,重新抽取月度计划...")
|
||||
sampled_plans = get_smart_plans_for_daily_schedule(
|
||||
current_month_str,
|
||||
max_count=3,
|
||||
avoid_days=avoid_days
|
||||
current_month_str, max_count=3, avoid_days=avoid_days
|
||||
)
|
||||
else:
|
||||
logger.warning("月度计划补充生成失败。")
|
||||
|
||||
|
||||
if sampled_plans:
|
||||
used_plan_ids = [plan.id for plan in sampled_plans] # SQLAlchemy 对象的 id 属性会自动返回实际值
|
||||
|
||||
plan_texts = "\n".join([f"- {plan.plan_text}" for plan in sampled_plans])
|
||||
monthly_plans_block = f"""
|
||||
**我这个月的一些小目标/计划 (请在今天的日程中适当体现)**:
|
||||
@@ -294,33 +283,33 @@ class ScheduleManager:
|
||||
|
||||
请你扮演我,以我的身份和口吻,为我生成一份完整的24小时日程表。
|
||||
"""
|
||||
|
||||
|
||||
# 无限重试直到生成成功的标准日程表
|
||||
attempt = 0
|
||||
while True:
|
||||
attempt += 1
|
||||
try:
|
||||
logger.info(f"正在生成日程 (第 {attempt} 次尝试)")
|
||||
|
||||
|
||||
# 构建当前尝试的prompt,增加压力提示
|
||||
prompt = base_prompt
|
||||
if attempt > 1:
|
||||
failure_hint = f"""
|
||||
|
||||
**重要提醒 (第{attempt}次尝试)**:
|
||||
- 前面{attempt-1}次生成都失败了,请务必严格按照要求生成完整的24小时日程
|
||||
- 前面{attempt - 1}次生成都失败了,请务必严格按照要求生成完整的24小时日程
|
||||
- 确保JSON格式正确,所有时间段连续覆盖24小时
|
||||
- 时间格式必须为HH:MM-HH:MM,不能有时间间隙或重叠
|
||||
- 不要输出任何解释文字,只输出纯JSON数组
|
||||
- 确保输出完整,不要被截断
|
||||
"""
|
||||
prompt += failure_hint
|
||||
|
||||
|
||||
response, _ = await self.llm.generate_response_async(prompt)
|
||||
|
||||
|
||||
# 尝试解析和验证JSON(项目内置的反截断机制会自动处理截断问题)
|
||||
schedule_data = orjson.loads(repair_json(response))
|
||||
|
||||
|
||||
# 使用Pydantic验证生成的日程数据
|
||||
if self._validate_schedule_with_pydantic(schedule_data):
|
||||
# 验证通过,保存到数据库
|
||||
@@ -329,46 +318,49 @@ class ScheduleManager:
|
||||
existing_schedule = session.query(Schedule).filter(Schedule.date == today_str).first()
|
||||
if existing_schedule:
|
||||
# 更新现有日程
|
||||
session.query(Schedule).filter(Schedule.date == today_str).update({
|
||||
Schedule.schedule_data: orjson.dumps(schedule_data).decode('utf-8'),
|
||||
Schedule.updated_at: datetime.now()
|
||||
})
|
||||
session.query(Schedule).filter(Schedule.date == today_str).update(
|
||||
{
|
||||
Schedule.schedule_data: orjson.dumps(schedule_data).decode("utf-8"),
|
||||
Schedule.updated_at: datetime.now(),
|
||||
}
|
||||
)
|
||||
else:
|
||||
# 创建新日程
|
||||
new_schedule = Schedule(
|
||||
date=today_str,
|
||||
schedule_data=orjson.dumps(schedule_data).decode('utf-8')
|
||||
date=today_str, schedule_data=orjson.dumps(schedule_data).decode("utf-8")
|
||||
)
|
||||
session.add(new_schedule)
|
||||
session.commit()
|
||||
|
||||
|
||||
# 美化输出
|
||||
schedule_str = f"✅ 经过 {attempt} 次尝试,成功生成并保存今天的日程 ({today_str}):\n"
|
||||
for item in schedule_data:
|
||||
schedule_str += f" - {item.get('time_range', '未知时间')}: {item.get('activity', '未知活动')}\n"
|
||||
schedule_str += (
|
||||
f" - {item.get('time_range', '未知时间')}: {item.get('activity', '未知活动')}\n"
|
||||
)
|
||||
logger.info(schedule_str)
|
||||
|
||||
|
||||
self.today_schedule = schedule_data
|
||||
|
||||
|
||||
# 成功生成日程后,更新使用过的月度计划的统计信息
|
||||
if used_plan_ids and global_config.monthly_plan_system:
|
||||
logger.info(f"更新使用过的月度计划 {used_plan_ids} 的统计信息。")
|
||||
update_plan_usage(used_plan_ids, today_str) # type: ignore
|
||||
|
||||
|
||||
# 成功生成,退出无限循环
|
||||
break
|
||||
|
||||
|
||||
else:
|
||||
logger.warning(f"第 {attempt} 次生成的日程验证失败,继续重试...")
|
||||
# 添加短暂延迟,避免过于频繁的请求
|
||||
await asyncio.sleep(2)
|
||||
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"第 {attempt} 次生成日程失败: {e}")
|
||||
logger.info("继续重试...")
|
||||
# 添加短暂延迟,避免过于频繁的请求
|
||||
await asyncio.sleep(3)
|
||||
|
||||
|
||||
finally:
|
||||
self.schedule_generation_running = False
|
||||
logger.info("日程生成任务结束")
|
||||
@@ -386,12 +378,12 @@ class ScheduleManager:
|
||||
try:
|
||||
time_range = event.get("time_range")
|
||||
activity = event.get("activity")
|
||||
|
||||
|
||||
if not time_range or not activity:
|
||||
logger.warning(f"日程事件缺少必要字段: {event}")
|
||||
continue
|
||||
|
||||
start_str, end_str = time_range.split('-')
|
||||
|
||||
start_str, end_str = time_range.split("-")
|
||||
start_time = datetime.strptime(start_str.strip(), "%H:%M").time()
|
||||
end_time = datetime.strptime(end_str.strip(), "%H:%M").time()
|
||||
|
||||
@@ -406,249 +398,21 @@ class ScheduleManager:
|
||||
continue
|
||||
return None
|
||||
|
||||
def is_sleeping(self, wakeup_manager: Optional["WakeUpManager"] = None) -> bool:
|
||||
"""
|
||||
通过关键词匹配、唤醒度、睡眠压力等综合判断是否处于休眠时间。
|
||||
新增弹性睡眠机制,允许在压力低时延迟入睡,并在入睡前发送通知。
|
||||
"""
|
||||
# --- 基础检查 ---
|
||||
if not global_config.schedule.enable_is_sleep:
|
||||
return False
|
||||
if not self.today_schedule:
|
||||
return False
|
||||
def get_current_sleep_state(self) -> SleepState:
|
||||
"""获取当前的睡眠状态"""
|
||||
return self.sleep_manager.get_current_sleep_state()
|
||||
|
||||
now = datetime.now()
|
||||
today = now.date()
|
||||
def is_sleeping(self) -> bool:
|
||||
"""检查当前是否处于正式休眠状态"""
|
||||
return self.sleep_manager.is_sleeping()
|
||||
|
||||
# --- 每日状态重置 ---
|
||||
if self._last_sleep_check_date != today:
|
||||
logger.info(f"新的一天 ({today}),重置弹性睡眠状态。")
|
||||
self._total_delayed_minutes_today = 0
|
||||
self._is_preparing_sleep = False
|
||||
self._sleep_buffer_end_time = None
|
||||
self._last_sleep_check_date = today
|
||||
self._is_in_voluntary_delay = False
|
||||
self._save_sleep_state()
|
||||
|
||||
# --- 检查是否在“准备入睡”的缓冲期 ---
|
||||
if self._is_preparing_sleep and self._sleep_buffer_end_time:
|
||||
if now >= self._sleep_buffer_end_time:
|
||||
current_timestamp = now.timestamp()
|
||||
if current_timestamp - self._last_fully_slept_log_time > 45:
|
||||
logger.info("睡眠缓冲期结束,正式进入休眠状态。")
|
||||
self._last_fully_slept_log_time = current_timestamp
|
||||
return True
|
||||
else:
|
||||
remaining_seconds = (self._sleep_buffer_end_time - now).total_seconds()
|
||||
logger.debug(f"处于入睡缓冲期,剩余 {remaining_seconds:.1f} 秒。")
|
||||
return False
|
||||
|
||||
# --- 判断当前是否为理论上的睡眠时间 ---
|
||||
is_in_theoretical_sleep, activity = self._is_in_theoretical_sleep_time(now.time())
|
||||
|
||||
if not is_in_theoretical_sleep:
|
||||
# 如果不在理论睡眠时间,确保重置准备状态
|
||||
if self._is_preparing_sleep:
|
||||
logger.info("已离开理论休眠时间,取消“准备入睡”状态。")
|
||||
self._is_preparing_sleep = False
|
||||
self._sleep_buffer_end_time = None
|
||||
self._is_in_voluntary_delay = False
|
||||
self._is_woken_up = False # 离开睡眠时间,重置唤醒状态
|
||||
self._save_sleep_state()
|
||||
return False
|
||||
|
||||
# --- 处理唤醒状态 ---
|
||||
if self._is_woken_up:
|
||||
current_timestamp = now.timestamp()
|
||||
if current_timestamp - self.last_sleep_log_time > self.sleep_log_interval:
|
||||
logger.info(f"在休眠活动 '{activity}' 期间,但已被唤醒,保持清醒状态。")
|
||||
self.last_sleep_log_time = current_timestamp
|
||||
return False
|
||||
|
||||
# --- 核心:弹性睡眠逻辑 ---
|
||||
if global_config.schedule.enable_flexible_sleep and not self._is_preparing_sleep:
|
||||
# 首次进入理论睡眠时间,触发弹性判断
|
||||
logger.info(f"进入理论休眠时间 '{activity}',开始弹性睡眠判断...")
|
||||
|
||||
# 1. 获取睡眠压力
|
||||
sleep_pressure = wakeup_manager.context.sleep_pressure if wakeup_manager else 999
|
||||
pressure_threshold = global_config.schedule.flexible_sleep_pressure_threshold
|
||||
|
||||
# 2. 判断是否延迟
|
||||
if sleep_pressure < pressure_threshold and self._total_delayed_minutes_today < global_config.schedule.max_sleep_delay_minutes:
|
||||
delay_minutes = 15 # 每次延迟15分钟
|
||||
self._total_delayed_minutes_today += delay_minutes
|
||||
self._sleep_buffer_end_time = now + timedelta(minutes=delay_minutes)
|
||||
self._is_in_voluntary_delay = True # 标记进入主动延迟
|
||||
logger.info(f"睡眠压力 ({sleep_pressure:.1f}) 低于阈值 ({pressure_threshold}),延迟入睡 {delay_minutes} 分钟。今日已累计延迟 {self._total_delayed_minutes_today} 分钟。")
|
||||
else:
|
||||
# 3. 计算5-10分钟的入睡缓冲
|
||||
self._is_in_voluntary_delay = False # 非主动延迟
|
||||
buffer_seconds = random.randint(5 * 60, 10 * 60)
|
||||
self._sleep_buffer_end_time = now + timedelta(seconds=buffer_seconds)
|
||||
logger.info(f"睡眠压力正常或已达今日最大延迟,将在 {buffer_seconds / 60:.1f} 分钟内入睡。")
|
||||
|
||||
# 4. 发送睡前通知
|
||||
if global_config.schedule.enable_pre_sleep_notification:
|
||||
asyncio.create_task(self._send_pre_sleep_notification())
|
||||
|
||||
self._is_preparing_sleep = True
|
||||
self._save_sleep_state()
|
||||
return False # 进入准备阶段,但尚未正式入睡
|
||||
|
||||
# --- 经典模式或已在弹性睡眠流程中 ---
|
||||
current_timestamp = now.timestamp()
|
||||
if current_timestamp - self.last_sleep_log_time > self.sleep_log_interval:
|
||||
logger.info(f"当前处于休眠活动 '{activity}' 中 (经典模式)。")
|
||||
self.last_sleep_log_time = current_timestamp
|
||||
return True
|
||||
async def update_sleep_state(self, wakeup_manager: Optional["WakeUpManager"] = None):
|
||||
"""更新睡眠状态"""
|
||||
await self.sleep_manager.update_sleep_state(wakeup_manager)
|
||||
|
||||
def reset_sleep_state_after_wakeup(self):
|
||||
"""被唤醒后重置睡眠状态"""
|
||||
if self._is_preparing_sleep or self.is_sleeping():
|
||||
logger.info("被唤醒,重置所有睡眠准备状态,恢复清醒!")
|
||||
self._is_preparing_sleep = False
|
||||
self._sleep_buffer_end_time = None
|
||||
self._is_in_voluntary_delay = False
|
||||
self._is_woken_up = True # 标记为已被唤醒
|
||||
self._save_sleep_state()
|
||||
|
||||
def _is_in_theoretical_sleep_time(self, now_time: time) -> (bool, Optional[str]):
|
||||
"""检查当前时间是否落在日程表的任何一个睡眠活动中"""
|
||||
sleep_keywords = ["休眠", "睡觉", "梦乡"]
|
||||
|
||||
for event in self.today_schedule:
|
||||
try:
|
||||
activity = event.get("activity", "").strip()
|
||||
time_range = event.get("time_range")
|
||||
|
||||
if not activity or not time_range:
|
||||
continue
|
||||
|
||||
if any(keyword in activity for keyword in sleep_keywords):
|
||||
start_str, end_str = time_range.split('-')
|
||||
start_time = datetime.strptime(start_str.strip(), "%H:%M").time()
|
||||
end_time = datetime.strptime(end_str.strip(), "%H:%M").time()
|
||||
|
||||
if start_time <= end_time: # 同一天
|
||||
if start_time <= now_time < end_time:
|
||||
return True, activity
|
||||
else: # 跨天
|
||||
if now_time >= start_time or now_time < end_time:
|
||||
return True, activity
|
||||
except (ValueError, KeyError, AttributeError) as e:
|
||||
logger.warning(f"解析日程事件时出错: {event}, 错误: {e}")
|
||||
continue
|
||||
|
||||
return False, None
|
||||
|
||||
async def _send_pre_sleep_notification(self):
|
||||
"""异步生成并发送睡前通知"""
|
||||
try:
|
||||
groups = global_config.schedule.pre_sleep_notification_groups
|
||||
prompt = global_config.schedule.pre_sleep_prompt
|
||||
|
||||
if not groups:
|
||||
logger.info("未配置睡前通知的群组,跳过发送。")
|
||||
return
|
||||
|
||||
if not prompt:
|
||||
logger.warning("睡前通知的prompt为空,跳过发送。")
|
||||
return
|
||||
|
||||
# 为防止消息风暴,稍微延迟一下
|
||||
await asyncio.sleep(random.uniform(5, 15))
|
||||
|
||||
for group_id_str in groups:
|
||||
try:
|
||||
# 格式 "platform:group_id"
|
||||
parts = group_id_str.split(":")
|
||||
if len(parts) != 2:
|
||||
logger.warning(f"无效的群组ID格式: {group_id_str}")
|
||||
continue
|
||||
|
||||
platform, group_id = parts
|
||||
|
||||
# 使用与 ChatStream.get_stream_id 相同的逻辑生成 stream_id
|
||||
import hashlib
|
||||
key = "_".join([platform, group_id])
|
||||
stream_id = hashlib.md5(key.encode()).hexdigest()
|
||||
|
||||
logger.info(f"正在为群组 {group_id_str} (Stream ID: {stream_id}) 生成睡前消息...")
|
||||
|
||||
# 调用 generator_api 生成回复
|
||||
success, reply_set, _ = await generator_api.generate_reply(
|
||||
chat_id=stream_id,
|
||||
extra_info=prompt,
|
||||
request_type="schedule.pre_sleep_notification"
|
||||
)
|
||||
|
||||
if success and reply_set:
|
||||
# 提取文本内容并发送
|
||||
reply_text = "".join([content for msg_type, content in reply_set if msg_type == "text"])
|
||||
if reply_text:
|
||||
logger.info(f"向群组 {group_id_str} 发送睡前消息: {reply_text}")
|
||||
await send_api.text_to_stream(text=reply_text, stream_id=stream_id)
|
||||
else:
|
||||
logger.warning(f"为群组 {group_id_str} 生成的回复内容为空。")
|
||||
else:
|
||||
logger.error(f"为群组 {group_id_str} 生成睡前消息失败。")
|
||||
|
||||
await asyncio.sleep(random.uniform(2, 5)) # 避免发送过快
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"向群组 {group_id_str} 发送睡前消息失败: {e}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"发送睡前通知任务失败: {e}")
|
||||
|
||||
def _save_sleep_state(self):
|
||||
"""将当前弹性睡眠状态保存到本地存储"""
|
||||
try:
|
||||
state = {
|
||||
"is_preparing_sleep": self._is_preparing_sleep,
|
||||
"sleep_buffer_end_time_ts": self._sleep_buffer_end_time.timestamp() if self._sleep_buffer_end_time else None,
|
||||
"total_delayed_minutes_today": self._total_delayed_minutes_today,
|
||||
"last_sleep_check_date_str": self._last_sleep_check_date.isoformat() if self._last_sleep_check_date else None,
|
||||
"is_in_voluntary_delay": self._is_in_voluntary_delay,
|
||||
"is_woken_up": self._is_woken_up,
|
||||
}
|
||||
local_storage["schedule_sleep_state"] = state
|
||||
logger.debug(f"已保存睡眠状态: {state}")
|
||||
except Exception as e:
|
||||
logger.error(f"保存睡眠状态失败: {e}")
|
||||
|
||||
def _load_sleep_state(self):
|
||||
"""从本地存储加载弹性睡眠状态"""
|
||||
try:
|
||||
state = local_storage["schedule_sleep_state"]
|
||||
if state and isinstance(state, dict):
|
||||
self._is_preparing_sleep = state.get("is_preparing_sleep", False)
|
||||
|
||||
end_time_ts = state.get("sleep_buffer_end_time_ts")
|
||||
if end_time_ts:
|
||||
self._sleep_buffer_end_time = datetime.fromtimestamp(end_time_ts)
|
||||
|
||||
self._total_delayed_minutes_today = state.get("total_delayed_minutes_today", 0)
|
||||
self._is_in_voluntary_delay = state.get("is_in_voluntary_delay", False)
|
||||
self._is_woken_up = state.get("is_woken_up", False)
|
||||
|
||||
date_str = state.get("last_sleep_check_date_str")
|
||||
if date_str:
|
||||
self._last_sleep_check_date = datetime.fromisoformat(date_str).date()
|
||||
|
||||
logger.info(f"成功从本地存储加载睡眠状态: {state}")
|
||||
except Exception as e:
|
||||
logger.warning(f"加载睡眠状态失败,将使用默认值: {e}")
|
||||
|
||||
def reset_wakeup_state(self):
|
||||
"""重置被唤醒的状态,允许重新尝试入睡"""
|
||||
if self._is_woken_up:
|
||||
logger.info("重置唤醒状态,将重新尝试入睡。")
|
||||
self._is_woken_up = False
|
||||
self._is_preparing_sleep = False # 允许重新进入弹性睡眠判断
|
||||
self._sleep_buffer_end_time = None
|
||||
self._save_sleep_state()
|
||||
"""被唤醒后,将状态切换到 WOKEN_UP"""
|
||||
self.sleep_manager.reset_sleep_state_after_wakeup()
|
||||
|
||||
def _validate_schedule_with_pydantic(self, schedule_data) -> bool:
|
||||
"""使用Pydantic验证日程数据格式和完整性"""
|
||||
@@ -669,22 +433,21 @@ class ScheduleManager:
|
||||
if not isinstance(schedule_data, list):
|
||||
logger.warning("日程数据不是列表格式")
|
||||
return False
|
||||
|
||||
|
||||
for item in schedule_data:
|
||||
if not isinstance(item, dict):
|
||||
logger.warning(f"日程项不是字典格式: {item}")
|
||||
return False
|
||||
|
||||
if 'time_range' not in item or 'activity' not in item:
|
||||
|
||||
if "time_range" not in item or "activity" not in item:
|
||||
logger.warning(f"日程项缺少必要字段 (time_range 或 activity): {item}")
|
||||
return False
|
||||
|
||||
if not isinstance(item['time_range'], str) or not isinstance(item['activity'], str):
|
||||
|
||||
if not isinstance(item["time_range"], str) or not isinstance(item["activity"], str):
|
||||
logger.warning(f"日程项字段类型不正确: {item}")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class DailyScheduleGenerationTask(AsyncTask):
|
||||
@@ -703,15 +466,17 @@ class DailyScheduleGenerationTask(AsyncTask):
|
||||
midnight = datetime.combine(tomorrow, time.min)
|
||||
sleep_seconds = (midnight - now).total_seconds()
|
||||
|
||||
logger.info(f"下一次日程生成任务将在 {sleep_seconds:.2f} 秒后运行 (北京时间 {midnight.strftime('%Y-%m-%d %H:%M:%S')})")
|
||||
|
||||
logger.info(
|
||||
f"下一次日程生成任务将在 {sleep_seconds:.2f} 秒后运行 (北京时间 {midnight.strftime('%Y-%m-%d %H:%M:%S')})"
|
||||
)
|
||||
|
||||
# 2. 等待直到零点
|
||||
await asyncio.sleep(sleep_seconds)
|
||||
|
||||
# 3. 执行异步日程生成
|
||||
logger.info("到达每日零点,开始异步生成新的一天日程...")
|
||||
await self.schedule_manager.generate_and_save_schedule()
|
||||
|
||||
|
||||
except asyncio.CancelledError:
|
||||
logger.info("每日日程生成任务被取消。")
|
||||
break
|
||||
@@ -721,4 +486,4 @@ class DailyScheduleGenerationTask(AsyncTask):
|
||||
await asyncio.sleep(300)
|
||||
|
||||
|
||||
schedule_manager = ScheduleManager()
|
||||
schedule_manager = ScheduleManager()
|
||||
|
||||
334
src/schedule/sleep_manager.py
Normal file
334
src/schedule/sleep_manager.py
Normal file
@@ -0,0 +1,334 @@
|
||||
import asyncio
|
||||
import random
|
||||
from datetime import datetime, timedelta, date, time
|
||||
from enum import Enum, auto
|
||||
from typing import Optional, TYPE_CHECKING
|
||||
|
||||
from src.common.logger import get_logger
|
||||
from src.config.config import global_config
|
||||
from src.manager.local_store_manager import local_storage
|
||||
from src.plugin_system.apis import send_api, generator_api
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from src.chat.chat_loop.wakeup_manager import WakeUpManager
|
||||
|
||||
logger = get_logger("sleep_manager")
|
||||
|
||||
|
||||
class SleepState(Enum):
|
||||
"""睡眠状态枚举"""
|
||||
AWAKE = auto() # 完全清醒
|
||||
INSOMNIA = auto() # 失眠(在理论睡眠时间内保持清醒)
|
||||
PREPARING_SLEEP = auto() # 准备入睡(缓冲期)
|
||||
SLEEPING = auto() # 正在休眠
|
||||
WOKEN_UP = auto() # 被吵醒
|
||||
|
||||
|
||||
class SleepManager:
|
||||
def __init__(self, schedule_manager):
|
||||
self.schedule_manager = schedule_manager
|
||||
self.last_sleep_log_time = 0
|
||||
self.sleep_log_interval = 35 # 日志记录间隔,单位秒
|
||||
|
||||
# --- 统一睡眠状态管理 ---
|
||||
self._current_state: SleepState = SleepState.AWAKE
|
||||
self._sleep_buffer_end_time: Optional[datetime] = None
|
||||
self._total_delayed_minutes_today: int = 0
|
||||
self._last_sleep_check_date: Optional[date] = None
|
||||
self._last_fully_slept_log_time: float = 0
|
||||
self._re_sleep_attempt_time: Optional[datetime] = None # 新增:重新入睡的尝试时间
|
||||
|
||||
self._load_sleep_state()
|
||||
|
||||
def get_current_sleep_state(self) -> SleepState:
|
||||
"""获取当前的睡眠状态"""
|
||||
return self._current_state
|
||||
|
||||
def is_sleeping(self) -> bool:
|
||||
"""检查当前是否处于正式休眠状态"""
|
||||
return self._current_state == SleepState.SLEEPING
|
||||
|
||||
async def update_sleep_state(self, wakeup_manager: Optional["WakeUpManager"] = None):
|
||||
"""
|
||||
核心状态机:根据当前情况更新睡眠状态
|
||||
"""
|
||||
# --- 基础检查 ---
|
||||
if not global_config.sleep_system.enable or not self.schedule_manager.today_schedule:
|
||||
if self._current_state != SleepState.AWAKE:
|
||||
logger.debug("睡眠系统禁用或无日程,强制设为 AWAKE")
|
||||
self._current_state = SleepState.AWAKE
|
||||
return
|
||||
|
||||
now = datetime.now()
|
||||
today = now.date()
|
||||
|
||||
# --- 每日状态重置 ---
|
||||
if self._last_sleep_check_date != today:
|
||||
logger.info(f"新的一天 ({today}),重置睡眠状态为 AWAKE。")
|
||||
self._total_delayed_minutes_today = 0
|
||||
self._current_state = SleepState.AWAKE
|
||||
self._sleep_buffer_end_time = None
|
||||
self._last_sleep_check_date = today
|
||||
self._save_sleep_state()
|
||||
|
||||
# --- 判断当前是否为理论上的睡眠时间 ---
|
||||
is_in_theoretical_sleep, activity = self._is_in_theoretical_sleep_time(now.time())
|
||||
|
||||
# ===================================
|
||||
# 状态机核心逻辑
|
||||
# ===================================
|
||||
|
||||
# 状态:清醒 (AWAKE)
|
||||
if self._current_state == SleepState.AWAKE:
|
||||
if is_in_theoretical_sleep:
|
||||
logger.info(f"进入理论休眠时间 '{activity}',开始进行睡眠决策...")
|
||||
|
||||
# --- 合并后的失眠与弹性睡眠决策逻辑 ---
|
||||
sleep_pressure = wakeup_manager.context.sleep_pressure if wakeup_manager else 999
|
||||
pressure_threshold = global_config.sleep_system.flexible_sleep_pressure_threshold
|
||||
|
||||
# 决策1:因睡眠压力低而延迟入睡(原弹性睡眠)
|
||||
if sleep_pressure < pressure_threshold and self._total_delayed_minutes_today < global_config.sleep_system.max_sleep_delay_minutes:
|
||||
delay_minutes = 15
|
||||
self._total_delayed_minutes_today += delay_minutes
|
||||
self._sleep_buffer_end_time = now + timedelta(minutes=delay_minutes)
|
||||
self._current_state = SleepState.INSOMNIA
|
||||
logger.info(f"睡眠压力 ({sleep_pressure:.1f}) 低于阈值 ({pressure_threshold}),进入失眠状态,延迟入睡 {delay_minutes} 分钟。")
|
||||
|
||||
# 发送睡前通知
|
||||
if global_config.sleep_system.enable_pre_sleep_notification:
|
||||
asyncio.create_task(self._send_pre_sleep_notification())
|
||||
|
||||
# 决策2:进入正常的入睡准备流程
|
||||
else:
|
||||
buffer_seconds = random.randint(5 * 60, 10 * 60)
|
||||
self._sleep_buffer_end_time = now + timedelta(seconds=buffer_seconds)
|
||||
self._current_state = SleepState.PREPARING_SLEEP
|
||||
logger.info(f"睡眠压力正常或已达今日最大延迟,进入准备入睡状态,将在 {buffer_seconds / 60:.1f} 分钟内入睡。")
|
||||
|
||||
# 发送睡前通知
|
||||
if global_config.sleep_system.enable_pre_sleep_notification:
|
||||
asyncio.create_task(self._send_pre_sleep_notification())
|
||||
|
||||
self._save_sleep_state()
|
||||
|
||||
# 状态:失眠 (INSOMNIA)
|
||||
elif self._current_state == SleepState.INSOMNIA:
|
||||
if not is_in_theoretical_sleep:
|
||||
logger.info("已离开理论休眠时间,失眠结束,恢复清醒。")
|
||||
self._current_state = SleepState.AWAKE
|
||||
self._save_sleep_state()
|
||||
elif self._sleep_buffer_end_time and now >= self._sleep_buffer_end_time:
|
||||
logger.info("失眠状态下的延迟时间已过,重新评估是否入睡...")
|
||||
sleep_pressure = wakeup_manager.context.sleep_pressure if wakeup_manager else 999
|
||||
pressure_threshold = global_config.sleep_system.flexible_sleep_pressure_threshold
|
||||
|
||||
if sleep_pressure >= pressure_threshold or self._total_delayed_minutes_today >= global_config.sleep_system.max_sleep_delay_minutes:
|
||||
logger.info("睡眠压力足够或已达最大延迟,从失眠状态转换到准备入睡。")
|
||||
buffer_seconds = random.randint(5 * 60, 10 * 60)
|
||||
self._sleep_buffer_end_time = now + timedelta(seconds=buffer_seconds)
|
||||
self._current_state = SleepState.PREPARING_SLEEP
|
||||
else:
|
||||
logger.info(f"睡眠压力({sleep_pressure:.1f})仍然较低,再延迟15分钟。")
|
||||
delay_minutes = 15
|
||||
self._total_delayed_minutes_today += delay_minutes
|
||||
self._sleep_buffer_end_time = now + timedelta(minutes=delay_minutes)
|
||||
|
||||
self._save_sleep_state()
|
||||
|
||||
# 状态:准备入睡 (PREPARING_SLEEP)
|
||||
elif self._current_state == SleepState.PREPARING_SLEEP:
|
||||
if not is_in_theoretical_sleep:
|
||||
logger.info("准备入睡期间离开理论休眠时间,取消入睡,恢复清醒。")
|
||||
self._current_state = SleepState.AWAKE
|
||||
self._sleep_buffer_end_time = None
|
||||
self._save_sleep_state()
|
||||
elif self._sleep_buffer_end_time and now >= self._sleep_buffer_end_time:
|
||||
logger.info("睡眠缓冲期结束,正式进入休眠状态。")
|
||||
self._current_state = SleepState.SLEEPING
|
||||
self._last_fully_slept_log_time = now.timestamp()
|
||||
self._save_sleep_state()
|
||||
|
||||
# 状态:休眠中 (SLEEPING)
|
||||
elif self._current_state == SleepState.SLEEPING:
|
||||
if not is_in_theoretical_sleep:
|
||||
logger.info("理论休眠时间结束,自然醒来。")
|
||||
self._current_state = SleepState.AWAKE
|
||||
self._save_sleep_state()
|
||||
else:
|
||||
# 记录日志
|
||||
current_timestamp = now.timestamp()
|
||||
if current_timestamp - self.last_sleep_log_time > self.sleep_log_interval:
|
||||
logger.info(f"当前处于休眠活动 '{activity}' 中。")
|
||||
self.last_sleep_log_time = current_timestamp
|
||||
|
||||
# 状态:被吵醒 (WOKEN_UP)
|
||||
elif self._current_state == SleepState.WOKEN_UP:
|
||||
if not is_in_theoretical_sleep:
|
||||
logger.info("理论休眠时间结束,被吵醒的状态自动结束。")
|
||||
self._current_state = SleepState.AWAKE
|
||||
self._re_sleep_attempt_time = None
|
||||
self._save_sleep_state()
|
||||
elif self._re_sleep_attempt_time and now >= self._re_sleep_attempt_time:
|
||||
logger.info("被吵醒后经过一段时间,尝试重新入睡...")
|
||||
|
||||
sleep_pressure = wakeup_manager.context.sleep_pressure if wakeup_manager else 999
|
||||
pressure_threshold = global_config.sleep_system.flexible_sleep_pressure_threshold
|
||||
|
||||
if sleep_pressure >= pressure_threshold:
|
||||
logger.info("睡眠压力足够,从被吵醒状态转换到准备入睡。")
|
||||
buffer_seconds = random.randint(3 * 60, 8 * 60) # 重新入睡的缓冲期可以短一些
|
||||
self._sleep_buffer_end_time = now + timedelta(seconds=buffer_seconds)
|
||||
self._current_state = SleepState.PREPARING_SLEEP
|
||||
self._re_sleep_attempt_time = None
|
||||
else:
|
||||
delay_minutes = 15
|
||||
self._re_sleep_attempt_time = now + timedelta(minutes=delay_minutes)
|
||||
logger.info(f"睡眠压力({sleep_pressure:.1f})仍然较低,暂时保持清醒,在 {delay_minutes} 分钟后再次尝试。")
|
||||
|
||||
self._save_sleep_state()
|
||||
|
||||
def reset_sleep_state_after_wakeup(self):
|
||||
"""被唤醒后,将状态切换到 WOKEN_UP"""
|
||||
if self._current_state in [SleepState.PREPARING_SLEEP, SleepState.SLEEPING, SleepState.INSOMNIA]:
|
||||
logger.info("被唤醒,进入 WOKEN_UP 状态!")
|
||||
self._current_state = SleepState.WOKEN_UP
|
||||
self._sleep_buffer_end_time = None
|
||||
|
||||
# 设置一个延迟,之后再尝试重新入睡
|
||||
re_sleep_delay_minutes = getattr(global_config.sleep_system, 're_sleep_delay_minutes', 10)
|
||||
self._re_sleep_attempt_time = datetime.now() + timedelta(minutes=re_sleep_delay_minutes)
|
||||
logger.info(f"将在 {re_sleep_delay_minutes} 分钟后尝试重新入睡。")
|
||||
|
||||
self._save_sleep_state()
|
||||
|
||||
def _is_in_theoretical_sleep_time(self, now_time: time) -> tuple[bool, Optional[str]]:
|
||||
"""检查当前时间是否落在日程表的任何一个睡眠活动中"""
|
||||
sleep_keywords = ["休眠", "睡觉", "梦乡"]
|
||||
if self.schedule_manager.today_schedule:
|
||||
for event in self.schedule_manager.today_schedule:
|
||||
try:
|
||||
activity = event.get("activity", "").strip()
|
||||
time_range = event.get("time_range")
|
||||
|
||||
if not activity or not time_range:
|
||||
continue
|
||||
|
||||
if any(keyword in activity for keyword in sleep_keywords):
|
||||
start_str, end_str = time_range.split('-')
|
||||
start_time = datetime.strptime(start_str.strip(), "%H:%M").time()
|
||||
end_time = datetime.strptime(end_str.strip(), "%H:%M").time()
|
||||
|
||||
if start_time <= end_time: # 同一天
|
||||
if start_time <= now_time < end_time:
|
||||
return True, activity
|
||||
else: # 跨天
|
||||
if now_time >= start_time or now_time < end_time:
|
||||
return True, activity
|
||||
except (ValueError, KeyError, AttributeError) as e:
|
||||
logger.warning(f"解析日程事件时出错: {event}, 错误: {e}")
|
||||
continue
|
||||
|
||||
return False, None
|
||||
|
||||
async def _send_pre_sleep_notification(self):
|
||||
"""异步生成并发送睡前通知"""
|
||||
try:
|
||||
groups = global_config.sleep_system.pre_sleep_notification_groups
|
||||
prompt = global_config.sleep_system.pre_sleep_prompt
|
||||
|
||||
if not groups:
|
||||
logger.info("未配置睡前通知的群组,跳过发送。")
|
||||
return
|
||||
|
||||
if not prompt:
|
||||
logger.warning("睡前通知的prompt为空,跳过发送。")
|
||||
return
|
||||
|
||||
# 为防止消息风暴,稍微延迟一下
|
||||
await asyncio.sleep(random.uniform(5, 15))
|
||||
|
||||
for group_id_str in groups:
|
||||
try:
|
||||
# 格式 "platform:group_id"
|
||||
parts = group_id_str.split(":")
|
||||
if len(parts) != 2:
|
||||
logger.warning(f"无效的群组ID格式: {group_id_str}")
|
||||
continue
|
||||
|
||||
platform, group_id = parts
|
||||
|
||||
# 使用与 ChatStream.get_stream_id 相同的逻辑生成 stream_id
|
||||
import hashlib
|
||||
key = "_".join([platform, group_id])
|
||||
stream_id = hashlib.md5(key.encode()).hexdigest()
|
||||
|
||||
logger.info(f"正在为群组 {group_id_str} (Stream ID: {stream_id}) 生成睡前消息...")
|
||||
|
||||
# 调用 generator_api 生成回复
|
||||
success, reply_set, _ = await generator_api.generate_reply(
|
||||
chat_id=stream_id,
|
||||
extra_info=prompt,
|
||||
request_type="schedule.pre_sleep_notification"
|
||||
)
|
||||
|
||||
if success and reply_set:
|
||||
# 提取文本内容并发送
|
||||
reply_text = "".join([content for msg_type, content in reply_set if msg_type == "text"])
|
||||
if reply_text:
|
||||
logger.info(f"向群组 {group_id_str} 发送睡前消息: {reply_text}")
|
||||
await send_api.text_to_stream(text=reply_text, stream_id=stream_id)
|
||||
else:
|
||||
logger.warning(f"为群组 {group_id_str} 生成的回复内容为空。")
|
||||
else:
|
||||
logger.error(f"为群组 {group_id_str} 生成睡前消息失败。")
|
||||
|
||||
await asyncio.sleep(random.uniform(2, 5)) # 避免发送过快
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"向群组 {group_id_str} 发送睡前消息失败: {e}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"发送睡前通知任务失败: {e}")
|
||||
|
||||
def _save_sleep_state(self):
|
||||
"""将当前睡眠状态保存到本地存储"""
|
||||
try:
|
||||
state = {
|
||||
"current_state": self._current_state.name,
|
||||
"sleep_buffer_end_time_ts": self._sleep_buffer_end_time.timestamp() if self._sleep_buffer_end_time else None,
|
||||
"total_delayed_minutes_today": self._total_delayed_minutes_today,
|
||||
"last_sleep_check_date_str": self._last_sleep_check_date.isoformat() if self._last_sleep_check_date else None,
|
||||
"re_sleep_attempt_time_ts": self._re_sleep_attempt_time.timestamp() if self._re_sleep_attempt_time else None,
|
||||
}
|
||||
local_storage["schedule_sleep_state"] = state
|
||||
logger.debug(f"已保存睡眠状态: {state}")
|
||||
except Exception as e:
|
||||
logger.error(f"保存睡眠状态失败: {e}")
|
||||
|
||||
def _load_sleep_state(self):
|
||||
"""从本地存储加载睡眠状态"""
|
||||
try:
|
||||
state = local_storage["schedule_sleep_state"]
|
||||
if state and isinstance(state, dict):
|
||||
state_name = state.get("current_state")
|
||||
if state_name and hasattr(SleepState, state_name):
|
||||
self._current_state = SleepState[state_name]
|
||||
|
||||
end_time_ts = state.get("sleep_buffer_end_time_ts")
|
||||
if end_time_ts:
|
||||
self._sleep_buffer_end_time = datetime.fromtimestamp(end_time_ts)
|
||||
|
||||
re_sleep_ts = state.get("re_sleep_attempt_time_ts")
|
||||
if re_sleep_ts:
|
||||
self._re_sleep_attempt_time = datetime.fromtimestamp(re_sleep_ts)
|
||||
|
||||
self._total_delayed_minutes_today = state.get("total_delayed_minutes_today", 0)
|
||||
|
||||
date_str = state.get("last_sleep_check_date_str")
|
||||
if date_str:
|
||||
self._last_sleep_check_date = datetime.fromisoformat(date_str).date()
|
||||
|
||||
logger.info(f"成功从本地存储加载睡眠状态: {state}")
|
||||
except Exception as e:
|
||||
logger.warning(f"加载睡眠状态失败,将使用默认值: {e}")
|
||||
@@ -89,6 +89,7 @@ learning_strength = 1.0
|
||||
|
||||
[[expression.rules]]
|
||||
chat_stream_id = "qq:1919810:group"
|
||||
group = "group_A"
|
||||
use_expression = true
|
||||
learn_expression = true
|
||||
learning_strength = 1.5
|
||||
@@ -100,29 +101,18 @@ use_expression = true
|
||||
learn_expression = false
|
||||
learning_strength = 0.5
|
||||
|
||||
[[expression.rules]]
|
||||
chat_stream_id = "qq:1919810:private"
|
||||
group = "group_A"
|
||||
use_expression = true
|
||||
learn_expression = true
|
||||
learning_strength = 1.0
|
||||
|
||||
|
||||
|
||||
[chat] #MoFox-Bot的聊天通用设置
|
||||
# 群聊聊天模式设置
|
||||
group_chat_mode = "auto" # 群聊聊天模式:auto-自动切换,normal-强制普通模式,focus-强制专注模式
|
||||
focus_value = 1
|
||||
# MoFox-Bot的专注思考能力,越高越容易专注,可能消耗更多token
|
||||
# MoFox-Bot的专注思考能力,越高越容易专注,可能消耗更多token,仅限自动切换模式下使用哦
|
||||
# 专注时能更好把握发言时机,能够进行持久的连续对话
|
||||
|
||||
talk_frequency = 1 # MoFox-Bot活跃度,越高,MoFox-Bot回复越频繁
|
||||
talk_frequency = 1 # MoFox-Bot活跃度,越高,MoFox-Bot回复越频繁,仅限normal/或者自动切换的normal模式下使用哦
|
||||
|
||||
# 强制私聊专注模式
|
||||
force_focus_private = false # 是否强制私聊进入专注模式,开启后私聊将始终保持专注状态
|
||||
|
||||
# 群聊聊天模式设置
|
||||
group_chat_mode = "auto" # 群聊聊天模式:auto-自动切换,normal-强制普通模式,focus-强制专注模式
|
||||
|
||||
|
||||
max_context_size = 25 # 上下文长度
|
||||
thinking_timeout = 40 # MoFox-Bot一次回复最长思考规划时间,超过这个时间的思考会放弃(往往是api反应太慢)
|
||||
replyer_random_probability = 0.5 # 首要replyer模型被选择的概率
|
||||
@@ -273,7 +263,7 @@ enable_vector_instant_memory = true # 是否启用基于向量的瞬时记忆
|
||||
memory_ban_words = [ "表情包", "图片", "回复", "聊天记录" ]
|
||||
|
||||
[voice]
|
||||
enable_asr = false # 是否启用语音识别,启用后MoFox-Bot可以识别语音消息,启用该功能需要配置语音识别模型[model.voice]s
|
||||
enable_asr = false # 是否启用语音识别,启用后MoFox-Bot可以识别语音消息,启用该功能需要配置语音识别模型[model.voice]
|
||||
|
||||
[lpmm_knowledge] # lpmm知识库配置
|
||||
enable = false # 是否启用lpmm知识库
|
||||
@@ -347,8 +337,6 @@ auto_install_timeout = 300
|
||||
# 是否使用PyPI镜像源(推荐,可加速下载)
|
||||
use_mirror = true
|
||||
mirror_url = "https://pypi.tuna.tsinghua.edu.cn/simple" # PyPI镜像源URL,如: "https://pypi.tuna.tsinghua.edu.cn/simple"
|
||||
# 安装前是否提示用户(暂未实现)
|
||||
prompt_before_install = false
|
||||
# 依赖安装日志级别
|
||||
install_log_level = "INFO"
|
||||
|
||||
@@ -376,23 +364,6 @@ guidelines = """
|
||||
晚上我希望你能多和朋友们交流,维系好彼此的关系。
|
||||
另外,请保证充足的休眠时间来处理和整合一天的数据。
|
||||
"""
|
||||
enable_is_sleep = false
|
||||
|
||||
# --- 弹性睡眠与睡前消息 ---
|
||||
# 是否启用弹性睡眠。启用后,AI不会到点立刻入睡,而是会根据睡眠压力增加5-10分钟的缓冲,并可能因为压力不足而推迟睡眠。
|
||||
enable_flexible_sleep = true
|
||||
# 触发弹性睡眠的睡眠压力阈值。当AI的睡眠压力低于此值时,可能会推迟入睡。
|
||||
flexible_sleep_pressure_threshold = 40.0
|
||||
# 每日最大可推迟入睡的总分钟数。
|
||||
max_sleep_delay_minutes = 60
|
||||
|
||||
# 是否在进入“准备入睡”状态时发送一条消息通知。
|
||||
enable_pre_sleep_notification = true
|
||||
# 接收睡前消息的群组列表。格式为: ["platform:group_id1", "platform:group_id2"],例如 ["qq:12345678"]
|
||||
pre_sleep_notification_groups = []
|
||||
# 用于生成睡前消息的提示。AI会根据这个提示生成一句晚安问候。
|
||||
pre_sleep_prompt = "我准备睡觉了,请生成一句简短自然的晚安问候。"
|
||||
|
||||
[video_analysis] # 视频分析配置
|
||||
enable = true # 是否启用视频分析功能
|
||||
analysis_mode = "batch_frames" # 分析模式:"frame_by_frame"(逐帧分析,非常慢)、"batch_frames"(批量分析,推荐)或 "auto"(自动选择)
|
||||
@@ -463,8 +434,8 @@ guidelines = """
|
||||
请确保计划既有挑战性又不会过于繁重,保持生活的平衡和乐趣。
|
||||
"""
|
||||
|
||||
[wakeup_system]
|
||||
enable = false #"是否启用唤醒度系统"
|
||||
[sleep_system]
|
||||
enable = false #"是否启用睡眠系统"
|
||||
wakeup_threshold = 15.0 #唤醒阈值,达到此值时会被唤醒"
|
||||
private_message_increment = 3.0 #"私聊消息增加的唤醒度"
|
||||
group_mention_increment = 2.0 #"群聊艾特增加的唤醒度"
|
||||
@@ -489,6 +460,21 @@ sleep_pressure_increment = 1.5
|
||||
sleep_pressure_decay_rate = 1.5
|
||||
insomnia_duration_minutes = 30 # 单次失眠状态的持续时间(分钟)
|
||||
|
||||
# --- 弹性睡眠与睡前消息 ---
|
||||
# 是否启用弹性睡眠。启用后,AI不会到点立刻入睡,而是会根据睡眠压力增加5-10分钟的缓冲,并可能因为压力不足而推迟睡眠。
|
||||
enable_flexible_sleep = false
|
||||
# 触发弹性睡眠的睡眠压力阈值。当AI的睡眠压力低于此值时,可能会推迟入睡。
|
||||
flexible_sleep_pressure_threshold = 40.0
|
||||
# 每日最大可推迟入睡的总分钟数。
|
||||
max_sleep_delay_minutes = 60
|
||||
|
||||
# 是否在进入“准备入睡”状态时发送一条消息通知。
|
||||
enable_pre_sleep_notification = false
|
||||
# 接收睡前消息的群组列表。格式为: ["platform:group_id1", "platform:group_id2"],例如 ["qq:12345678"]
|
||||
pre_sleep_notification_groups = []
|
||||
# 用于生成睡前消息的提示。AI会根据这个提示生成一句晚安问候。
|
||||
pre_sleep_prompt = "我准备睡觉了,请生成一句简短自然的晚安问候。"
|
||||
|
||||
[cross_context] # 跨群聊上下文共享配置
|
||||
# 这是总开关,用于一键启用或禁用此功能
|
||||
enable = false
|
||||
|
||||
Reference in New Issue
Block a user