diff --git a/src/chat/planner_actions/planner.py b/src/chat/planner_actions/planner.py index 3c8784dc1..f5b6066d5 100644 --- a/src/chat/planner_actions/planner.py +++ b/src/chat/planner_actions/planner.py @@ -21,7 +21,7 @@ from src.chat.planner_actions.action_manager import ActionManager from src.chat.message_receive.chat_stream import get_chat_manager from src.plugin_system.base.component_types import ActionInfo, ChatMode, ComponentType from src.plugin_system.core.component_registry import component_registry -from src.common.schedule_manager import schedule_manager +from src.manager.schedule_manager import schedule_manager from src.mood.mood_manager import mood_manager logger = get_logger("planner") @@ -395,7 +395,7 @@ class ActionPlanner: # 处理自定义提示词 custom_prompt_block = "" if global_config.custom_prompt.planner_custom_prompt_enable and global_config.custom_prompt.planner_custom_prompt_content: - custom_prompt_block = global_config.chat.planner_custom_prompt_content + custom_prompt_block = global_config.custom_prompt.planner_custom_prompt_content planner_prompt_template = await global_prompt_manager.get_prompt_async("planner_prompt") prompt = planner_prompt_template.format( diff --git a/src/chat/replyer/default_generator.py b/src/chat/replyer/default_generator.py index 7c410ae74..14fb07aa7 100644 --- a/src/chat/replyer/default_generator.py +++ b/src/chat/replyer/default_generator.py @@ -29,7 +29,7 @@ from src.mood.mood_manager import mood_manager from src.person_info.person_info import Person, is_person_known from src.plugin_system.base.component_types import ActionInfo, EventType from src.plugin_system.apis import llm_api -from src.common.schedule_manager import schedule_manager +from src.manager.schedule_manager import schedule_manager logger = get_logger("replyer") diff --git a/src/common/schedule_manager.py b/src/common/schedule_manager.py deleted file mode 100644 index ecce57dac..000000000 --- a/src/common/schedule_manager.py +++ /dev/null @@ -1,186 +0,0 @@ -import json -from datetime import datetime -from typing import Optional, List, Dict, Any - -from src.common.database.sqlalchemy_models import Schedule, get_db_session -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 - -logger = get_logger("schedule_manager") - -# 默认的日程生成指导原则 -DEFAULT_SCHEDULE_GUIDELINES = """ -我希望你每天都能过得充实而有趣。 -请确保你的日程里有学习新知识的时间,这是你成长的关键。 -但也不要忘记放松,可以看看视频、听听音乐或者玩玩游戏。 -晚上我希望你能多和朋友们交流,维系好彼此的关系。 -另外,请保证充足的休眠时间来处理和整合一天的数据。 -""" - -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") - - async def load_or_generate_today_schedule(self): - # 检查是否启用日程管理功能 - if not global_config.schedule.enable: - logger.info("日程管理功能已禁用,跳过日程加载和生成。") - return - - today_str = datetime.now().strftime("%Y-%m-%d") - try: - with get_db_session() as session: - schedule_record = session.query(Schedule).filter(Schedule.date == today_str).first() - if schedule_record: - logger.info(f"从数据库加载今天的日程 ({today_str})。") - # SQLAlchemy 对象属性直接访问,确保类型转换 - schedule_data_str = str(schedule_record.schedule_data) - - try: - schedule_data = json.loads(schedule_data_str) - - # 验证日程数据格式 - if self._validate_schedule_data(schedule_data): - self.today_schedule = schedule_data - schedule_str = f"已成功加载今天的日程 ({today_str}):\n" - if self.today_schedule: - for item in self.today_schedule: - schedule_str += f" - {item.get('time_range', '未知时间')}: {item.get('activity', '未知活动')}\n" - logger.info(schedule_str) - else: - logger.warning(f"数据库中的日程数据格式无效,将重新生成日程") - await self.generate_and_save_schedule() - except json.JSONDecodeError as e: - logger.error(f"日程数据JSON解析失败: {e},将重新生成日程") - await self.generate_and_save_schedule() - else: - logger.info(f"数据库中未找到今天的日程 ({today_str}),将调用 LLM 生成。") - await self.generate_and_save_schedule() - except Exception as e: - logger.error(f"加载或生成日程时出错: {e}") - - async def generate_and_save_schedule(self): - today_str = datetime.now().strftime("%Y-%m-%d") - weekday = datetime.now().strftime("%A") - - guidelines = global_config.schedule.guidelines or DEFAULT_SCHEDULE_GUIDELINES - personality = global_config.personality.personality_core - personality_side = global_config.personality.personality_side - - prompt = f""" -我,{global_config.bot.nickname},需要为自己规划一份今天({today_str},星期{weekday})的详细日程安排。 - -**关于我**: -- **核心人设**: {personality} -- **具体习惯与兴趣**: -{personality_side} - -**我今天的规划原则**: -{guidelines} - -**任务**: -请你扮演我,以我的身份和口吻,为我生成一份日程表。 -- 必须以一个完整的、有效的JSON数组格式返回。 -- 数组中的每个对象都必须包含 "time_range" 和 "activity" 两个键。 -- 时间范围必须覆盖全部24小时。 -- 不要包含任何JSON以外的解释性文字或代码块标记。 - -**示例**: -[ - {{"time_range": "00:00-07:00", "activity": "进入梦乡,处理数据"}}, - {{"time_range": "07:00-08:00", "activity": "起床伸个懒腰,看看今天有什么新闻"}} -] -""" - - try: - response, _ = await self.llm.generate_response_async(prompt) - schedule_data = json.loads(repair_json(response)) - - # 验证生成的日程数据格式 - if not self._validate_schedule_data(schedule_data): - logger.error("LLM生成的日程数据格式无效") - return - - with get_db_session() as session: - # 检查是否已存在今天的日程 - existing_schedule = session.query(Schedule).filter(Schedule.date == today_str).first() - if existing_schedule: - # 更新现有日程 - 通过setattr或直接赋值 - existing_schedule.schedule_data = json.dumps(schedule_data) - existing_schedule.updated_at = datetime.now() - else: - # 创建新日程 - new_schedule = Schedule() - new_schedule.date = today_str - new_schedule.schedule_data = json.dumps(schedule_data) - session.add(new_schedule) - - # 美化输出 - schedule_str = f"已成功生成并保存今天的日程 ({today_str}):\n" - for item in schedule_data: - schedule_str += f" - {item.get('time_range', '未知时间')}: {item.get('activity', '未知活动')}\n" - logger.info(schedule_str) - - self.today_schedule = schedule_data - - except Exception as e: - logger.error(f"调用 LLM 生成或保存日程失败: {e}") - - def get_current_activity(self) -> Optional[str]: - # 检查是否启用日程管理功能 - if not global_config.schedule.enable: - return None - - if not self.today_schedule: - return None - - now = datetime.now().time() - for event in self.today_schedule: - 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_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 < end_time: - return activity - else: # 跨天事件 - if now >= start_time or now < end_time: - return activity - except (ValueError, KeyError, AttributeError) as e: - logger.warning(f"解析日程事件失败: {event}, 错误: {e}") - continue - return None - - def _validate_schedule_data(self, schedule_data) -> bool: - """验证日程数据格式是否正确""" - 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: - logger.warning(f"日程项缺少必要字段 (time_range 或 activity): {item}") - return False - - if not isinstance(item['time_range'], str) or not isinstance(item['activity'], str): - logger.warning(f"日程项字段类型不正确: {item}") - return False - - return True - -schedule_manager = ScheduleManager() \ No newline at end of file diff --git a/src/main.py b/src/main.py index 55ae671c0..f43163dec 100644 --- a/src/main.py +++ b/src/main.py @@ -16,7 +16,7 @@ from src.individuality.individuality import get_individuality, Individuality from src.common.server import get_global_server, Server from src.mood.mood_manager import mood_manager from rich.traceback import install -from src.common.schedule_manager import schedule_manager +from src.manager.schedule_manager import schedule_manager # from src.api.main import start_api_server # 导入新的插件管理器和热重载管理器 diff --git a/src/manager/schedule_manager.py b/src/manager/schedule_manager.py new file mode 100644 index 000000000..2419e3bdd --- /dev/null +++ b/src/manager/schedule_manager.py @@ -0,0 +1,303 @@ +import json +from datetime import datetime, time +from typing import Optional, List, Dict, Any +from pydantic import BaseModel, ValidationError, validator + +from src.common.database.sqlalchemy_models import Schedule, get_db_session +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 + +logger = get_logger("schedule_manager") + +# 默认的日程生成指导原则 +DEFAULT_SCHEDULE_GUIDELINES = """ +我希望你每天都能过得充实而有趣。 +请确保你的日程里有学习新知识的时间,这是你成长的关键。 +但也不要忘记放松,可以看看视频、听听音乐或者玩玩游戏。 +晚上我希望你能多和朋友们交流,维系好彼此的关系。 +另外,请保证充足的休眠时间来处理和整合一天的数据。 +""" + +class ScheduleItem(BaseModel): + """单个日程项的Pydantic模型""" + time_range: str + activity: str + + @validator('time_range') + def validate_time_range(cls, v): + """验证时间范围格式""" + if not v or '-' not in v: + raise ValueError("时间范围必须包含'-'分隔符") + + try: + 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}") + + @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') + 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_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): + if i < 1440: + covered[i] = True + else: + # 跨天的时间段 + for i in range(start_min, 1440): + 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 = 3 # 最大重试次数 + + async def load_or_generate_today_schedule(self): + # 检查是否启用日程管理功能 + if not global_config.schedule.enable: + logger.info("日程管理功能已禁用,跳过日程加载和生成。") + return + + today_str = datetime.now().strftime("%Y-%m-%d") + try: + with get_db_session() as session: + schedule_record = session.query(Schedule).filter(Schedule.date == today_str).first() + if schedule_record: + logger.info(f"从数据库加载今天的日程 ({today_str})。") + + try: + schedule_data = json.loads(schedule_record.schedule_data) + + # 使用Pydantic验证日程数据 + if self._validate_schedule_with_pydantic(schedule_data): + self.today_schedule = schedule_data + schedule_str = f"已成功加载今天的日程 ({today_str}):\n" + if self.today_schedule: + for item in self.today_schedule: + schedule_str += f" - {item.get('time_range', '未知时间')}: {item.get('activity', '未知活动')}\n" + logger.info(schedule_str) + else: + logger.warning(f"数据库中的日程数据格式无效,将重新生成日程") + await self.generate_and_save_schedule() + except json.JSONDecodeError as e: + logger.error(f"日程数据JSON解析失败: {e},将重新生成日程") + await self.generate_and_save_schedule() + else: + logger.info(f"数据库中未找到今天的日程 ({today_str}),将调用 LLM 生成。") + await self.generate_and_save_schedule() + except Exception as e: + logger.error(f"加载或生成日程时出错: {e}") + + async def generate_and_save_schedule(self): + today_str = datetime.now().strftime("%Y-%m-%d") + weekday = datetime.now().strftime("%A") + + guidelines = global_config.schedule.guidelines or DEFAULT_SCHEDULE_GUIDELINES + personality = global_config.personality.personality_core + personality_side = global_config.personality.personality_side + + prompt = f""" +我,{global_config.bot.nickname},需要为自己规划一份今天({today_str},星期{weekday})的详细日程安排。 + +**关于我**: +- **核心人设**: {personality} +- **具体习惯与兴趣**: +{personality_side} + +**我今天的规划原则**: +{guidelines} + +**重要要求**: +1. 必须返回一个完整的、有效的JSON数组格式 +2. 数组中的每个对象都必须包含 "time_range" 和 "activity" 两个键 +3. 时间范围必须覆盖全部24小时,不能有遗漏 +4. time_range格式必须为 "HH:MM-HH:MM" (24小时制) +5. 相邻的时间段必须连续,不能有间隙 +6. 不要包含任何JSON以外的解释性文字或代码块标记 + +**示例**: +[ + {{"time_range": "00:00-07:00", "activity": "进入梦乡,处理数据"}}, + {{"time_range": "07:00-08:00", "activity": "起床伸个懒腰,看看今天有什么新闻"}}, + {{"time_range": "08:00-09:00", "activity": "享用早餐,规划今天的任务"}} +] + +请你扮演我,以我的身份和口吻,为我生成一份完整的24小时日程表。 +""" + + # 尝试生成并验证日程,最多重试max_retries次 + for attempt in range(self.max_retries): + try: + logger.info(f"正在生成日程 (尝试 {attempt + 1}/{self.max_retries})") + response, _ = await self.llm.generate_response_async(prompt) + schedule_data = json.loads(repair_json(response)) + + # 使用Pydantic验证生成的日程数据 + if self._validate_schedule_with_pydantic(schedule_data): + # 验证通过,保存到数据库 + with get_db_session() as session: + # 检查是否已存在今天的日程 + existing_schedule = session.query(Schedule).filter(Schedule.date == today_str).first() + if existing_schedule: + # 更新现有日程 + setattr(existing_schedule, 'schedule_data', json.dumps(schedule_data)) + setattr(existing_schedule, 'updated_at', datetime.now()) + else: + # 创建新日程 + new_schedule = Schedule() + setattr(new_schedule, 'date', today_str) + setattr(new_schedule, 'schedule_data', json.dumps(schedule_data)) + session.add(new_schedule) + + # 美化输出 + schedule_str = f"已成功生成并保存今天的日程 ({today_str}):\n" + for item in schedule_data: + schedule_str += f" - {item.get('time_range', '未知时间')}: {item.get('activity', '未知活动')}\n" + logger.info(schedule_str) + + self.today_schedule = schedule_data + return + else: + logger.warning(f"第 {attempt + 1} 次生成的日程验证失败,正在重试...") + if attempt < self.max_retries - 1: + # 在重试时添加更详细的错误提示 + prompt += f"\n\n**上次生成失败,请特别注意**:\n- 确保所有时间段连续覆盖24小时\n- 时间格式必须为HH:MM-HH:MM\n- 不能有时间间隙或重叠" + + except Exception as e: + logger.error(f"第 {attempt + 1} 次生成日程失败: {e}") + if attempt == self.max_retries - 1: + logger.error(f"经过 {self.max_retries} 次尝试,仍无法生成有效日程") + + def get_current_activity(self) -> Optional[str]: + # 检查是否启用日程管理功能 + if not global_config.schedule.enable: + return None + + if not self.today_schedule: + return None + + now = datetime.now().time() + for event in self.today_schedule: + 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_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 < end_time: + return activity + else: # 跨天事件 + if now >= start_time or now < end_time: + return activity + except (ValueError, KeyError, AttributeError) as e: + logger.warning(f"解析日程事件失败: {event}, 错误: {e}") + continue + return None + + def _validate_schedule_with_pydantic(self, schedule_data) -> bool: + """使用Pydantic验证日程数据格式和完整性""" + try: + # 尝试用Pydantic模型验证 + validated_schedule = ScheduleData(schedule=schedule_data) + logger.info("日程数据Pydantic验证通过") + return True + except ValidationError as e: + logger.warning(f"日程数据Pydantic验证失败: {e}") + return False + except Exception as e: + logger.error(f"日程数据验证时发生异常: {e}") + return False + + def _validate_schedule_data(self, schedule_data) -> bool: + """保留原有的基础验证方法作为备用""" + 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: + logger.warning(f"日程项缺少必要字段 (time_range 或 activity): {item}") + return False + + if not isinstance(item['time_range'], str) or not isinstance(item['activity'], str): + logger.warning(f"日程项字段类型不正确: {item}") + return False + + return True + +schedule_manager = ScheduleManager() \ No newline at end of file diff --git a/src/plugins/built_in/maizone/plugin.py b/src/plugins/built_in/maizone/plugin.py index c1d65e7e7..edf966bd6 100644 --- a/src/plugins/built_in/maizone/plugin.py +++ b/src/plugins/built_in/maizone/plugin.py @@ -738,12 +738,7 @@ class MaiZonePlugin(BasePlugin): "interval_minutes": ConfigField(type=int, default=10, description="监控间隔时间(分钟)"), }, "schedule": { - "enable_schedule": ConfigField(type=bool, default=False, description="是否启用定时发送说说"), - "schedules": ConfigField( - type=str, - default=r"""{"08:00" = "早安","22:00" = "晚安"}""", - description="定时发送任务列表, 格式为 {\"时间\"= \"主题\"}" - ), + "enable_schedule": ConfigField(type=bool, default=False, description="是否启用基于日程表的定时发送说说"), }, } diff --git a/src/plugins/built_in/maizone/qzone_utils.py b/src/plugins/built_in/maizone/qzone_utils.py index cb00c04d7..73844c202 100644 --- a/src/plugins/built_in/maizone/qzone_utils.py +++ b/src/plugins/built_in/maizone/qzone_utils.py @@ -14,6 +14,7 @@ import json5 from src.chat.utils.utils_image import get_image_manager from src.common.logger import get_logger from src.plugin_system.apis import llm_api, config_api, emoji_api, send_api +from src.chat.message_receive.chat_stream import get_chat_manager # 获取日志记录器 logger = get_logger('MaiZone-Utils') @@ -64,15 +65,24 @@ class CookieManager: logger.info(f"正在通过适配器API获取Cookie,域名: {domain}") try: - # 使用适配器命令API获取cookie - response = await send_api.adapter_command_to_stream( + if stream_id is None: + response = await send_api.adapter_command_to_stream( action="get_cookies", params={"domain": domain}, platform="qq", - stream_id=stream_id, timeout=40.0, storage_message=False ) + # 使用适配器命令API获取cookie + else: + response = await send_api.adapter_command_to_stream( + action="get_cookies", + params={"domain": domain}, + platform="qq", + stream_id=stream_id, + timeout=40.0, + storage_message=False + ) logger.info(f"适配器响应: {response}") diff --git a/src/plugins/built_in/maizone/scheduler.py b/src/plugins/built_in/maizone/scheduler.py index 266883d23..98f0182de 100644 --- a/src/plugins/built_in/maizone/scheduler.py +++ b/src/plugins/built_in/maizone/scheduler.py @@ -7,6 +7,7 @@ from typing import Dict, Any from src.common.logger import get_logger from src.plugin_system.apis import llm_api, config_api +from src.manager.schedule_manager import schedule_manager # 导入工具模块 import sys @@ -19,16 +20,16 @@ logger = get_logger('MaiZone-Scheduler') class ScheduleManager: - """定时任务管理器 - 负责定时发送说说""" + """定时任务管理器 - 根据日程表定时发送说说""" def __init__(self, plugin): """初始化定时任务管理器""" self.plugin = plugin self.is_running = False self.task = None - self.last_send_times: Dict[str, float] = {} # 记录每个时间点的最后发送时间 + self.last_activity_hash = None # 记录上次处理的活动哈希,避免重复发送 - logger.info("定时任务管理器初始化完成") + logger.info("定时任务管理器初始化完成 - 将根据日程表发送说说") async def start(self): """启动定时任务""" @@ -38,7 +39,7 @@ class ScheduleManager: self.is_running = True self.task = asyncio.create_task(self._schedule_loop()) - logger.info("定时发送说说任务已启动") + logger.info("定时发送说说任务已启动 - 基于日程表") async def stop(self): """停止定时任务""" @@ -57,7 +58,7 @@ class ScheduleManager: logger.info("定时发送说说任务已停止") async def _schedule_loop(self): - """定时任务主循环""" + """定时任务主循环 - 根据日程表检查活动""" while self.is_running: try: # 检查定时任务是否启用 @@ -66,23 +67,25 @@ class ScheduleManager: await asyncio.sleep(60) continue - # 获取当前时间 - current_time = datetime.datetime.now().strftime("%H:%M") + # 获取当前活动 + current_activity = schedule_manager.get_current_activity() - # 从插件配置中获取定时任务 - schedules = self.plugin.get_config("schedule.schedules", {}) - - if not schedules: - logger.info("未找到有效的定时任务配置") - await asyncio.sleep(60) - continue + if current_activity: + # 生成当前活动的哈希以避免重复发送 + current_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M") + activity_hash = f"{current_time}_{current_activity}" + + # 检查是否已经为这个活动发送过说说 + if activity_hash != self.last_activity_hash: + logger.info(f"检测到新的日程活动: {current_activity}") + await self._execute_schedule_based_send(current_activity) + self.last_activity_hash = activity_hash + else: + logger.debug(f"当前活动已处理过: {current_activity}") + else: + logger.debug("当前时间没有日程活动") - # 检查每个定时任务 - for time_str, topic in schedules.items(): - schedule = {"time": time_str, "topic": topic} - await self._check_and_execute_schedule(schedule, current_time) - - # 每分钟检查一次 + # 每5分钟检查一次,避免频繁检查 await asyncio.sleep(60) except asyncio.CancelledError: @@ -94,45 +97,18 @@ class ScheduleManager: # 出错后等待5分钟再重试 await asyncio.sleep(300) - async def _check_and_execute_schedule(self, schedule: Dict[str, Any], current_time: str): - """检查并执行定时任务""" + async def _execute_schedule_based_send(self, activity: str) -> bool: + """根据日程活动执行发送任务""" try: - schedule_time = schedule.get("time", "") - topic = schedule.get("topic", "") + logger.info(f"根据日程活动生成说说: {activity}") - # 检查是否到达发送时间 - if current_time == schedule_time: - # 避免同一分钟内重复发送 - last_send_time = self.last_send_times.get(schedule_time, 0) - current_timestamp = time.time() - - if current_timestamp - last_send_time > 60: # 超过1分钟才允许发送 - logger.info(f"定时任务触发: {schedule_time} - 主题: {topic}") - self.last_send_times[schedule_time] = current_timestamp - - # 执行发送任务 - success = await self._execute_scheduled_send(topic) - - if success: - logger.info(f"定时说说发送成功: {topic}") - else: - logger.error(f"定时说说发送失败: {topic}") - else: - logger.debug(f"跳过重复发送: {schedule_time}") - - except Exception as e: - logger.error(f"检查定时任务失败: {str(e)}") - - async def _execute_scheduled_send(self, topic: str) -> bool: - """执行定时发送任务""" - try: - # 生成说说内容 - story = await self._generate_story_content(topic) + # 生成基于活动的说说内容 + story = await self._generate_activity_story(activity) if not story: - logger.error("生成定时说说内容失败") + logger.error("生成活动相关说说内容失败") return False - logger.info(f"定时任务生成说说内容: '{story}'") + logger.info(f"基于日程活动生成说说内容: '{story}'") # 处理配图 await self._handle_images(story) @@ -140,14 +116,19 @@ class ScheduleManager: # 发送说说 success = await self._send_scheduled_feed(story) + if success: + logger.info(f"基于日程活动的说说发送成功: {story}") + else: + logger.error(f"基于日程活动的说说发送失败: {activity}") + return success except Exception as e: - logger.error(f"执行定时发送任务失败: {str(e)}") + logger.error(f"执行基于日程的发送任务失败: {str(e)}") return False - async def _generate_story_content(self, topic: str) -> str: - """生成定时说说内容""" + async def _generate_activity_story(self, activity: str) -> str: + """根据日程活动生成说说内容""" try: # 获取模型配置 models = llm_api.get_available_models() @@ -163,24 +144,24 @@ class ScheduleManager: bot_expression = config_api.get_global_config("expression.expression_style", "内容积极向上") qq_account = config_api.get_global_config("bot.qq_account", "") - # 构建提示词 - if topic: - prompt = f""" - 你是'{bot_personality}',你想写一条主题是'{topic}'的说说发表在qq空间上, - {bot_expression} - 不要刻意突出自身学科背景,不要浮夸,不要夸张修辞,可以适当使用颜文字, - 只输出一条说说正文的内容,不要有其他的任何正文以外的冗余输出 - """ - else: - prompt = f""" - 你是'{bot_personality}',你想写一条说说发表在qq空间上,主题不限 - {bot_expression} - 不要刻意突出自身学科背景,不要浮夸,不要夸张修辞,可以适当使用颜文字, - 只输出一条说说正文的内容,不要有其他的任何正文以外的冗余输出 - """ + # 构建基于活动的提示词 + prompt = f""" + 你是'{bot_personality}',根据你当前的日程安排,你正在'{activity}'。 + 请基于这个活动写一条说说发表在qq空间上, + {bot_expression} + 说说内容应该自然地反映你正在做的事情或你的想法, + 不要刻意突出自身学科背景,不要浮夸,不要夸张修辞,可以适当使用颜文字, + 只输出一条说说正文的内容,不要有其他的任何正文以外的冗余输出 + + 注意: + - 如果活动是学习相关的,可以分享学习心得或感受 + - 如果活动是休息相关的,可以分享放松的感受 + - 如果活动是日常生活相关的,可以分享生活感悟 + - 让说说内容贴近你当前正在做的事情,显得自然真实 + """ # 添加历史记录避免重复 - prompt += "\n以下是你最近发过的说说,写新说说时注意不要在相隔不长的时间发送相似内容的说说\n" + prompt += "\n\n以下是你最近发过的说说,写新说说时注意不要在相隔不长的时间发送相似内容的说说\n" history_block = await get_send_history(qq_account) if history_block: prompt += history_block @@ -190,18 +171,18 @@ class ScheduleManager: prompt=prompt, model_config=model_config, request_type="story.generate", - temperature=0.3, + temperature=0.7, # 稍微提高创造性 max_tokens=1000 ) if success: return story else: - logger.error("生成定时说说内容失败") + logger.error("生成基于活动的说说内容失败") return "" except Exception as e: - logger.error(f"生成定时说说内容异常: {str(e)}") + logger.error(f"生成基于活动的说说内容异常: {str(e)}") return "" async def _handle_images(self, story: str): @@ -220,15 +201,15 @@ class ScheduleManager: image_dir=image_dir, batch_size=image_num ) - logger.info("定时任务AI配图生成完成") + logger.info("基于日程活动的AI配图生成完成") elif enable_ai_image and not apikey: logger.warning('启用了AI配图但未填写API密钥') except Exception as e: - logger.error(f"处理定时说说配图失败: {str(e)}") + logger.error(f"处理基于日程的说说配图失败: {str(e)}") async def _send_scheduled_feed(self, story: str) -> bool: - """发送定时说说""" + """发送基于日程的说说""" try: # 获取配置 qq_account = config_api.get_global_config("bot.qq_account", "") @@ -240,47 +221,23 @@ class ScheduleManager: success = await qzone_manager.send_feed(story, image_dir, qq_account, enable_image) if success: - logger.info(f"定时说说发送成功: {story}") + logger.info(f"基于日程的说说发送成功: {story}") else: - logger.error("定时说说发送失败") + logger.error("基于日程的说说发送失败") return success except Exception as e: - logger.error(f"发送定时说说失败: {str(e)}") + logger.error(f"发送基于日程的说说失败: {str(e)}") return False def get_status(self) -> Dict[str, Any]: """获取定时任务状态""" + current_activity = schedule_manager.get_current_activity() return { "is_running": self.is_running, "enabled": self.plugin.get_config("schedule.enable_schedule", False), - "schedules": self.plugin.get_config("schedule.schedules", {}), - "last_send_times": self.last_send_times - } - - def add_schedule(self, time_str: str, topic: str) -> bool: - """添加定时任务""" - schedules = self.plugin.get_config("schedule.schedules", {}) - - if time_str in schedules: - logger.warning(f"时间 {time_str} 已存在定时任务") - return False - - schedules[time_str] = topic - # 注意:这里需要插件系统支持动态更新配置 - logger.info(f"添加定时任务: {time_str} - {topic}") - return True - - def remove_schedule(self, time_str: str) -> bool: - """移除定时任务""" - schedules = self.plugin.get_config("schedule.schedules", {}) - - if time_str in schedules: - del schedules[time_str] - # 注意:这里需要插件系统支持动态更新配置 - logger.info(f"移除定时任务: {time_str}") - return True - else: - logger.warning(f"未找到时间为 {time_str} 的定时任务") - return False \ No newline at end of file + "schedule_mode": "based_on_daily_schedule", + "current_activity": current_activity, + "last_activity_hash": self.last_activity_hash + } \ No newline at end of file