feat(sleep): 为睡眠系统添加随机时间偏移功能

- 新增每日睡眠和起床时间随机偏移量配置选项
- 实现缓存机制确保同一天内使用相同的偏移量
- 重构睡眠时间检查逻辑以支持动态时间偏移
- 更新相关配置类和插件清单格式
This commit is contained in:
雅诺狐
2025-09-07 08:20:39 +08:00
parent 653599b7e7
commit cb994a4e17
6 changed files with 55 additions and 13 deletions

1
.gitignore vendored
View File

@@ -337,3 +337,4 @@ MaiBot.code-workspace
/tests /tests
.kilocode/rules/MoFox.md .kilocode/rules/MoFox.md
src/chat/planner_actions/planner (2).py src/chat/planner_actions/planner (2).py
rust_video/Cargo.lock

View File

@@ -1,5 +1,6 @@
from datetime import datetime, time from datetime import datetime, time, timedelta
from typing import Optional from typing import Optional
import random
from src.common.logger import get_logger from src.common.logger import get_logger
from src.config.config import global_config from src.config.config import global_config
@@ -10,15 +11,37 @@ logger = get_logger("time_checker")
class TimeChecker: class TimeChecker:
def __init__(self, schedule_source): def __init__(self, schedule_source):
self.schedule_source = schedule_source self.schedule_source = schedule_source
# 缓存当天的偏移量,确保一天内使用相同的偏移量
self._daily_sleep_offset = None
self._daily_wake_offset = None
self._offset_date = None
def _get_daily_offsets(self):
"""获取当天的睡眠和起床时间偏移量,每天生成一次"""
today = datetime.now().date()
# 如果是新的一天,重新生成偏移量
if self._offset_date != today:
sleep_offset_range = global_config.sleep_system.sleep_time_offset_minutes
wake_offset_range = global_config.sleep_system.wake_up_time_offset_minutes
# 生成 ±offset_range 范围内的随机偏移量
self._daily_sleep_offset = random.randint(-sleep_offset_range, sleep_offset_range)
self._daily_wake_offset = random.randint(-wake_offset_range, wake_offset_range)
self._offset_date = today
logger.debug(f"生成新的每日偏移量 - 睡觉时间偏移: {self._daily_sleep_offset}分钟, 起床时间偏移: {self._daily_wake_offset}分钟")
return self._daily_sleep_offset, self._daily_wake_offset
def is_in_theoretical_sleep_time(self, now_time: time) -> tuple[bool, Optional[str]]: def is_in_theoretical_sleep_time(self, now_time: time) -> tuple[bool, Optional[str]]:
if global_config.sleep_system.sleep_by_schedule: if global_config.sleep_system.sleep_by_schedule:
if self.schedule_source.get_today_schedule(): if self.schedule_source.get_today_schedule():
return self._is_in_schedule_sleep_time(now_time) return self._is_in_schedule_sleep_time(now_time)
else: else:
return self._is_in_fixed_sleep_time(now_time) return self._is_in_sleep_time(now_time)
else: else:
return self._is_in_fixed_sleep_time(now_time) return self._is_in_sleep_time(now_time)
def _is_in_schedule_sleep_time(self, now_time: time) -> tuple[bool, Optional[str]]: def _is_in_schedule_sleep_time(self, now_time: time) -> tuple[bool, Optional[str]]:
"""检查当前时间是否落在日程表的任何一个睡眠活动中""" """检查当前时间是否落在日程表的任何一个睡眠活动中"""
@@ -49,20 +72,33 @@ class TimeChecker:
continue continue
return False, None return False, None
def _is_in_fixed_sleep_time(self, now_time: time) -> tuple[bool, Optional[str]]: def _is_in_sleep_time(self, now_time: time) -> tuple[bool, Optional[str]]:
"""检查当前时间是否在固定的睡眠时间内""" """检查当前时间是否在固定的睡眠时间内(应用偏移量)"""
try: try:
start_time_str = global_config.sleep_system.fixed_sleep_time start_time_str = global_config.sleep_system.fixed_sleep_time
end_time_str = global_config.sleep_system.fixed_wake_up_time end_time_str = global_config.sleep_system.fixed_wake_up_time
start_time = datetime.strptime(start_time_str, "%H:%M").time()
end_time = datetime.strptime(end_time_str, "%H:%M").time()
if start_time <= end_time: # 获取当天的偏移量
if start_time <= now_time < end_time: sleep_offset, wake_offset = self._get_daily_offsets()
return True, "固定睡眠时间"
# 解析基础时间
base_start_time = datetime.strptime(start_time_str, "%H:%M")
base_end_time = datetime.strptime(end_time_str, "%H:%M")
# 应用偏移量
actual_start_time = (base_start_time + timedelta(minutes=sleep_offset)).time()
actual_end_time = (base_end_time + timedelta(minutes=wake_offset)).time()
logger.debug(f"固定睡眠时间检查 - 基础时间: {start_time_str}-{end_time_str}, "
f"偏移后时间: {actual_start_time.strftime('%H:%M')}-{actual_end_time.strftime('%H:%M')}, "
f"当前时间: {now_time.strftime('%H:%M')}")
if actual_start_time <= actual_end_time:
if actual_start_time <= now_time < actual_end_time:
return True, f"固定睡眠时间(偏移后: {actual_start_time.strftime('%H:%M')}-{actual_end_time.strftime('%H:%M')})"
else: else:
if now_time >= start_time or now_time < end_time: if now_time >= actual_start_time or now_time < actual_end_time:
return True, "固定睡眠时间" return True, f"固定睡眠时间(偏移后: {actual_start_time.strftime('%H:%M')}-{actual_end_time.strftime('%H:%M')})"
except ValueError as e: except ValueError as e:
logger.error(f"固定的睡眠时间格式不正确,请使用 HH:MM 格式: {e}") logger.error(f"固定的睡眠时间格式不正确,请使用 HH:MM 格式: {e}")
return False, None return False, None

View File

@@ -283,6 +283,7 @@ class DefaultReplyer:
return False, None, None return False, None, None
from src.plugin_system.core.event_manager import event_manager from src.plugin_system.core.event_manager import event_manager
# 触发 POST_LLM 事件(请求 LLM 之前)
if not from_plugin: if not from_plugin:
result = await event_manager.trigger_event( result = await event_manager.trigger_event(
EventType.POST_LLM, plugin_name="SYSTEM", prompt=prompt, stream_id=stream_id EventType.POST_LLM, plugin_name="SYSTEM", prompt=prompt, stream_id=stream_id
@@ -304,6 +305,7 @@ class DefaultReplyer:
"model": model_name, "model": model_name,
"tool_calls": tool_call, "tool_calls": tool_call,
} }
# 触发 AFTER_LLM 事件 # 触发 AFTER_LLM 事件
if not from_plugin: if not from_plugin:
result = await event_manager.trigger_event( result = await event_manager.trigger_event(

View File

@@ -613,6 +613,8 @@ class SleepSystemConfig(ValidatedConfigBase):
sleep_by_schedule: bool = Field(default=True, description="是否根据日程表进行睡觉") sleep_by_schedule: bool = Field(default=True, description="是否根据日程表进行睡觉")
fixed_sleep_time: str = Field(default="23:00", description="固定的睡觉时间") fixed_sleep_time: str = Field(default="23:00", description="固定的睡觉时间")
fixed_wake_up_time: str = Field(default="07:00", description="固定的起床时间") fixed_wake_up_time: str = Field(default="07:00", description="固定的起床时间")
sleep_time_offset_minutes: int = Field(default=15, ge=0, le=60, description="睡觉时间随机偏移量范围(分钟),实际睡觉时间会在±该值范围内随机")
wake_up_time_offset_minutes: int = Field(default=15, ge=0, le=60, description="起床时间随机偏移量范围(分钟),实际起床时间会在±该值范围内随机")
wakeup_threshold: float = Field(default=15.0, ge=1.0, description="唤醒阈值,达到此值时会被唤醒") wakeup_threshold: float = Field(default=15.0, ge=1.0, description="唤醒阈值,达到此值时会被唤醒")
private_message_increment: float = Field(default=3.0, ge=0.1, 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="群聊艾特增加的唤醒度") group_mention_increment: float = Field(default=2.0, ge=0.1, description="群聊艾特增加的唤醒度")

View File

@@ -1,4 +1,6 @@
{ {
"manifest_version": 1,
"name": "Set Typing Status", "name": "Set Typing Status",
"description": "一个在LLM生成回复时设置私聊输入状态的插件。", "description": "一个在LLM生成回复时设置私聊输入状态的插件。",
"version": "1.0.0", "version": "1.0.0",

View File

@@ -29,7 +29,6 @@ class SetTypingStatusHandler(BaseEventHandler):
user_id = message.message_info.user_info.user_id user_id = message.message_info.user_info.user_id
if not user_id: if not user_id:
return HandlerResult(success=False, continue_process=True, message="无法获取用户ID") return HandlerResult(success=False, continue_process=True, message="无法获取用户ID")
try: try:
params = {"user_id": user_id, "event_type": 1} params = {"user_id": user_id, "event_type": 1}
await send_api.adapter_command_to_stream( await send_api.adapter_command_to_stream(