This commit is contained in:
Windpicker-owo
2025-08-31 20:56:35 +08:00
12 changed files with 174 additions and 164 deletions

124
README.md
View File

@@ -1,9 +1,7 @@
<div align="center">
# 🌟 MoFox_Bot
**🚀 基于 MaiCore 的增强版 AI 智能体,提供更完善的功能和更好的使用体验**
<p>
<a href="https://www.python.org/">
<img src="https://img.shields.io/badge/Python-3.10+-3776ab?logo=python&logoColor=white&style=for-the-badge" alt="Python">
@@ -21,46 +19,46 @@
<img src="https://img.shields.io/github/v/release/MoFox-Studio/MoFox_Bot?style=for-the-badge&logo=github&logoColor=white&color=orange" alt="Release">
</a>
</p>
</div>
---
<div align="center">
## 📖 项目介绍
**MoFox_Bot** 是一个基于 [MaiCore](https://github.com/MaiM-with-u/MaiBot) `0.10.0 snapshot.5` 版本的增强型 `fork` 项目。
我们在保留原版所有功能的基础上,进行了一系列的改进和功能拓展,致力于提供更强的稳定性、更丰富的功能和更流畅的用户体验。
> [!IMPORTANT]
> **第三方项目声明**
>
> 本项目是由 **MoFox Studio** 独立维护的第三方项目,并非 MaiBot 官方版本。
> 所有后续更新和维护均由我们团队负责,与 MaiBot 官方无直接关系。
> [!WARNING]
> **迁移风险提示**
>
> 由于我们对数据库结构进行了重构和优化,从 MaiBot 官方版本直接迁移到 MoFox_Bot **可能会遇到数据不兼容的问题**。
> 在迁移前,请务必做好数据备份。
</div>
---
<div align="center">
## ✨ 功能特性
</div>
<table>
<tr>
<td width="50%">
### 🔧 原版功能(全部保留)
- 🧠 **智能对话系统** - 基于 LLM 的自然语言交互,支持 normal 和 focus 统一化处理
- 🔌 **强大插件系统** - 全面重构的插件架构,支持完整的管理 API 和权限控制
- 💭 **实时思维系统** - 模拟人类思考过程
@@ -69,12 +67,12 @@
- 🧠 **持久记忆系统** - 基于图的长期记忆存储
- 🎭 **动态人格系统** - 自适应的性格特征和表达方式
- 📊 **数据分析** - 内置数据统计和分析功能,更好了解麦麦状态
</td>
<td width="50%">
### 🚀 拓展功能
- 🔄 **数据库切换** - 支持 SQLite 与 MySQL 自由切换,采用 SQLAlchemy 2.0 重新构建
- 🛡️ **反注入集成** - 内置一整套回复前注入过滤系统,为人格保驾护航
- 🎥 **视频分析** - 支持多种视频识别模式,拓展原版视觉
@@ -84,118 +82,120 @@
- 🎪 **完善的 Event** - 支持动态事件注册和处理器订阅,并实现了聚合结果管理
- 🔍 **内嵌魔改插件** - 内置联网搜索等诸多功能,等你来探索
- 🌟 **还有更多** - 请参阅详细修改 [commits](https://github.com/MoFox-Studio/MoFox_Bot/commits)
</td>
</tr>
</table>
---
<div align="center">
## 🔧 系统要求
### 💻 基础环境
| 项目 | 要求 |
| ------------ | ---------------------------------------------------- |
| 🖥️ 操作系统 | Windows 10/11, macOS 10.14+, Linux (Ubuntu 18.04+) |
| 🐍 Python 版本 | Python 3.10 或更高版本 |
| 💾 内存 | 建议 4GB 以上可用内存 |
| 💿 存储空间 | 至少 2GB 可用空间 |
### 🛠️ 依赖服务
| 服务 | 描述 |
| ------------ | ------------------------------------------ |
| 🤖 QQ 协议端 | [NapCatQQ](https://github.com/NapNeko/NapCatQQ) 或其他兼容协议端 |
| 🗃️ 数据库 | SQLite (内置) 或 MySQL (可选) |
| 🔧 管理工具 | Chat2DB (可选,用于数据库管理) |
</div>
---
<div align="center">
## 🏁 快速开始
### 📦 安装与部署
</div>
> [!NOTE]
> 详细的安装和配置步骤,请务必参考我们的官方文档:
> * **Windows 用户部署指南**: [https://mofox-studio.github.io/MoFox-Bot-Docs/docs/deployment_guide.html](https://mofox-studio.github.io/MoFox-Bot-Docs/docs/deployment_guide.html)
> * **`bot_config.toml` 究极详细教程**: [https://mofox-studio.github.io/MoFox-Bot-Docs/docs/guides/bot_config_guide.html](https://mofox-studio.github.io/MoFox-Bot-Docs/docs/guides/bot_config_guide.html)
<div align="center">
### ⚙️ 配置要点
1. 📝 **核心配置**: 修改 `config/bot_config.toml` 中的基础设置,如 LLM API Key 等。
2. 🤖 **协议端配置**: 设置 NapCatQQ 或其他兼容的 QQ 协议端,确保通信正常。
3. 🗃️ **数据库配置**: 根据需求选择 SQLite 或配置你的 MySQL 服务器。
4. 🔌 **插件配置**: 在 `config/plugins/` 目录下按需配置插件。
</div>
---
<div align="center">
## 🙏 致谢
我们衷心感谢以下优秀的开源项目,没有它们,就没有 MoFox_Bot。
| 项目 | 描述 | 贡献 |
| ------------------------------------------ | -------------------- | ---------------- |
| 🎯 [MaiM-with-u/MaiBot](https://github.com/MaiM-with-u/MaiBot) | 原版 MaiBot 项目 | 提供优秀的基础框架 |
| 🐱 [NapNeko/NapCatQQ](https://github.com/NapNeko/NapCatQQ) | 基于 NTQQ 的 Bot 协议端 | 现代化的 QQ 协议实现 |
| 🌌 [internetsb/Maizone](https://github.com/internetsb/Maizone) | 魔改空间插件 | 插件部分功能借鉴 |
</div>
---
<div align="center">
## ⚠️ 注意事项
> [!CAUTION]
> **重要提醒**
>
> - 使用本项目前,你必须阅读并同意 [**📋 用户协议 (EULA.md)**](EULA.md)。
> - 本应用生成的内容来自人工智能大模型,请仔细甄别其准确性,并请勿用于任何违反法律法规的用途。
> - AI 生成的所有内容不代表本项目团队的任何观点和立场。
</div>
---
<div align="center">
## 📄 开源协议
本项目基于 **[GPL-3.0](LICENSE)** 协议开源。
[![GPL-3.0](https://img.shields.io/badge/License-GPL--3.0-blue.svg?style=for-the-badge&logo=gnu)](LICENSE)
```
Copyright © 2025 MoFox Studio
Licensed under the GNU General Public License v3.0
```
</div>
---
<div align="center">
**🌟 如果这个项目对你有帮助,请给我们一个 Star**
**💬 有任何问题或建议?欢迎提交 Issue 或 Pull Request**
*Made with ❤️ by [MoFox Studio](https://github.com/MoFox-Studio)*
**💬 [点击加入 QQ 交流群](https://qm.qq.com/q/jfeu7Dq7VS)**
_Made with ❤️ by [MoFox Studio](https://github.com/MoFox-Studio)_
</div>

View File

@@ -103,6 +103,7 @@ class EnergyManager:
self.context.sleep_pressure -= decay_per_10s
self.context.sleep_pressure = max(self.context.sleep_pressure, 0)
self._log_sleep_pressure_change("睡眠压力释放")
self.context.save_context_state()
else:
# 清醒时:处理能量衰减
is_group_chat = self.context.chat_stream.group_info is not None
@@ -123,6 +124,7 @@ class EnergyManager:
self.context.energy_value = max(self.context.energy_value, 0.3)
self._log_energy_change("能量值衰减")
self.context.save_context_state()
def _should_log_energy(self) -> bool:
"""
@@ -150,6 +152,7 @@ class EnergyManager:
self.context.sleep_pressure += increment
self.context.sleep_pressure = min(self.context.sleep_pressure, 100.0) # 设置一个100的上限
self._log_sleep_pressure_change("执行动作,睡眠压力累积")
self.context.save_context_state()
def _log_energy_change(self, action: str, reason: str = ""):
"""

View File

@@ -2,7 +2,6 @@ from typing import List, Optional, TYPE_CHECKING
import time
from src.chat.message_receive.chat_stream import ChatStream, get_chat_manager
from src.common.logger import get_logger
from src.manager.local_store_manager import local_storage
from src.person_info.relationship_builder_manager import RelationshipBuilder
from src.chat.express.expression_learner import ExpressionLearner
from src.plugin_system.base.component_types import ChatMode
@@ -43,10 +42,10 @@ class HfcContext:
self.expression_learner: Optional[ExpressionLearner] = None
self.loop_mode = ChatMode.NORMAL
self.energy_value = 5.0
self.sleep_pressure = 0.0
self.was_sleeping = False # 用于检测睡眠状态的切换
self.energy_value = self.chat_stream.energy_value
self.sleep_pressure = self.chat_stream.sleep_pressure
self.was_sleeping = False # 用于检测睡眠状态的切换
self.last_message_time = time.time()
self.last_read_time = time.time() - 10
@@ -62,30 +61,8 @@ class HfcContext:
self.wakeup_manager: Optional["WakeUpManager"] = None
self.energy_manager: Optional["EnergyManager"] = None
self._load_context_state()
def _get_storage_key(self) -> str:
"""获取当前聊天流的本地存储键"""
return f"hfc_context_state_{self.stream_id}"
def _load_context_state(self):
"""从本地存储加载状态"""
state = local_storage[self._get_storage_key()]
if state and isinstance(state, dict):
self.energy_value = state.get("energy_value", 5.0)
self.sleep_pressure = state.get("sleep_pressure", 0.0)
logger = get_logger("hfc_context")
logger.info(f"{self.log_prefix} 成功从本地存储加载HFC上下文状态: {state}")
else:
logger = get_logger("hfc_context")
logger.info(f"{self.log_prefix} 未找到本地HFC上下文状态将使用默认值初始化。")
def save_context_state(self):
"""将当前状态保存到本地存储"""
state = {
"energy_value": self.energy_value,
"sleep_pressure": self.sleep_pressure,
}
local_storage[self._get_storage_key()] = state
logger = get_logger("hfc_context")
logger.debug(f"{self.log_prefix} 已将HFC上下文状态保存到本地存储: {state}")
"""将当前状态保存到聊天流"""
if self.chat_stream:
self.chat_stream.energy_value = self.energy_value
self.chat_stream.sleep_pressure = self.sleep_pressure

View File

@@ -273,9 +273,21 @@ class ProactiveThinker:
# 如果决策不是 do_nothing则执行
if action_result and action_result.get("action_type") != "do_nothing":
logger.info(
f"{self.context.log_prefix} 主动思考决策: {action_result.get('action_type')}, 原因: {action_result.get('reasoning')}"
)
logger.info(f"{self.context.log_prefix} 主动思考决策: {action_result.get('action_type')}, 原因: {action_result.get('reasoning')}")
# 在主动思考时,如果 target_message 为 None则默认选取最新 message 作为 target_message
if target_message is None and self.context.chat_stream and self.context.chat_stream.context:
from src.chat.message_receive.message import MessageRecv
latest_message = self.context.chat_stream.context.get_last_message()
if isinstance(latest_message, MessageRecv):
user_info = latest_message.message_info.user_info
target_message = {
"chat_info_platform": latest_message.message_info.platform,
"user_platform": user_info.platform if user_info else None,
"user_id": user_info.user_id if user_info else None,
"processed_plain_text": latest_message.processed_plain_text,
"is_mentioned": latest_message.is_mentioned,
}
# 将决策结果交给 cycle_processor 的后续流程处理
await self.cycle_processor.execute_plan(action_result, target_message)
else:

View File

@@ -79,6 +79,8 @@ class ChatStream:
self.group_info = group_info
self.create_time = data.get("create_time", time.time()) if data else time.time()
self.last_active_time = data.get("last_active_time", self.create_time) if data else self.create_time
self.energy_value = data.get("energy_value", 5.0) if data else 5.0
self.sleep_pressure = data.get("sleep_pressure", 0.0) if data else 0.0
self.saved = False
self.context: ChatMessageContext = None # type: ignore # 用于存储该聊天的上下文信息
@@ -91,6 +93,8 @@ class ChatStream:
"group_info": self.group_info.to_dict() if self.group_info else None,
"create_time": self.create_time,
"last_active_time": self.last_active_time,
"energy_value": self.energy_value,
"sleep_pressure": self.sleep_pressure,
}
@classmethod
@@ -251,7 +255,7 @@ class ChatManager:
"user_cardname": model_instance.user_cardname or "",
}
group_info_data = None
if model_instance.group_id: # 假设 group_id 为空字符串表示没有群组信息
if model_instance.group_id:
group_info_data = {
"platform": model_instance.group_platform,
"group_id": model_instance.group_id,
@@ -265,6 +269,8 @@ class ChatManager:
"group_info": group_info_data,
"create_time": model_instance.create_time,
"last_active_time": model_instance.last_active_time,
"energy_value": model_instance.energy_value,
"sleep_pressure": model_instance.sleep_pressure,
}
stream = ChatStream.from_dict(data_for_from_dict)
# 更新用户信息和群组信息
@@ -348,6 +354,8 @@ class ChatManager:
"group_platform": group_info_d["platform"] if group_info_d else "",
"group_id": group_info_d["group_id"] if group_info_d else "",
"group_name": group_info_d["group_name"] if group_info_d else "",
"energy_value": s_data_dict.get("energy_value", 5.0),
"sleep_pressure": s_data_dict.get("sleep_pressure", 0.0),
}
# 根据数据库类型选择插入语句
@@ -407,6 +415,8 @@ class ChatManager:
"group_info": group_info_data,
"create_time": model_instance.create_time,
"last_active_time": model_instance.last_active_time,
"energy_value": model_instance.energy_value,
"sleep_pressure": model_instance.sleep_pressure,
}
loaded_streams_data.append(data_for_from_dict)
session.commit()

View File

@@ -62,11 +62,10 @@ def get_active_plans_for_month(month: str) -> List[MonthlyPlan]:
"""
with get_db_session() as session:
try:
plans = (
session.query(MonthlyPlan)
.filter(MonthlyPlan.target_month == month, MonthlyPlan.status == "active")
.all()
)
plans = session.query(MonthlyPlan).filter(
MonthlyPlan.target_month == month,
MonthlyPlan.status == 'active'
).order_by(MonthlyPlan.created_at.desc()).all()
return plans
except Exception as e:
logger.error(f"查询 {month} 的有效月度计划时发生错误: {e}")

View File

@@ -51,6 +51,8 @@ class ChatStreams(Base):
user_id = Column(get_string_field(100), nullable=False, index=True)
user_nickname = Column(Text, nullable=False)
user_cardname = Column(Text, nullable=True)
energy_value = Column(Float, nullable=True, default=5.0)
sleep_pressure = Column(Float, nullable=True, default=0.0)
__table_args__ = (
Index("idx_chatstreams_stream_id", "stream_id"),

View File

@@ -220,8 +220,6 @@ MoFox_Bot(第三方修改版)
await async_memory_manager.initialize()
logger.info("记忆管理器初始化成功")
except ImportError:
logger.warning("异步记忆优化方法不可用,将回退使用同步模式")
except Exception as e:
logger.error(f"记忆管理器初始化失败: {e}")
else:

View File

@@ -67,8 +67,8 @@ class EventManager:
event = BaseEvent(event_name, allowed_subscribers, allowed_triggers)
self._events[event_name] = event
logger.info(f"事件 {event_name} 注册成功")
logger.debug(f"事件 {event_name} 注册成功")
# 检查是否有缓存的订阅需要处理
self._process_pending_subscriptions(event_name)

View File

@@ -53,9 +53,7 @@ class MaiZoneRefactoredPlugin(BasePlugin):
"enable_reply": ConfigField(type=bool, default=True, description="完成后是否回复"),
"ai_image_number": ConfigField(type=int, default=1, description="AI生成图片数量"),
"image_number": ConfigField(type=int, default=1, description="本地配图数量1-9张"),
"image_directory": ConfigField(
type=str, default=str(Path(__file__).parent / "images"), description="图片存储目录"
),
"image_directory": ConfigField(type=str, default=(Path(__file__).parent / "images").as_posix(), description="图片存储目录")
},
"read": {
"permission": ConfigField(type=list, default=[], description="阅读权限QQ号列表"),
@@ -77,9 +75,7 @@ class MaiZoneRefactoredPlugin(BasePlugin):
"forbidden_hours_end": ConfigField(type=int, default=6, description="禁止发送的结束小时(24小时制)"),
},
"cookie": {
"http_fallback_host": ConfigField(
type=str, default="172.20.130.55", description="备用Cookie获取服务的主机地址"
),
"http_fallback_host": ConfigField(type=str, default="127.0.0.1", description="备用Cookie获取服务的主机地址"),
"http_fallback_port": ConfigField(type=int, default=9999, description="备用Cookie获取服务的端口"),
"napcat_token": ConfigField(type=str, default="", description="Napcat服务的认证Token可选"),
},

View File

@@ -64,8 +64,9 @@ class MonthlyPlanManager:
target_month = datetime.now().strftime("%Y-%m")
if not has_active_plans(target_month):
logger.info(f" {target_month} 没有任何有效的月度计划,将立即生成。")
return await self.generate_monthly_plans(target_month)
logger.info(f" {target_month} 没有任何有效的月度计划,将触发同步生成。")
generation_successful = await self._generate_monthly_plans_logic(target_month)
return generation_successful
else:
logger.info(f"{target_month} 已存在有效的月度计划。")
plans = get_active_plans_for_month(target_month)
@@ -74,8 +75,8 @@ class MonthlyPlanManager:
max_plans = global_config.monthly_plan_system.max_plans_per_month
if len(plans) > max_plans:
logger.warning(f"当前月度计划数量 ({len(plans)}) 超出上限 ({max_plans}),将自动删除多余的计划。")
# 按创建时间序排序(的在前),然后删除超出上限的部分(新的)
plans_to_delete = sorted(plans, key=lambda p: p.created_at, reverse=True)[: len(plans) - max_plans]
# 数据库查询结果已按创建时间序排序(的在前),直接截取超出上限的部分进行删除
plans_to_delete = plans[:len(plans)-max_plans]
delete_ids = [p.id for p in plans_to_delete]
delete_plans_by_ids(delete_ids)
# 重新获取计划列表
@@ -86,10 +87,21 @@ class MonthlyPlanManager:
logger.info(f"当前月度计划内容:\n{plan_texts}")
return True # 已经有计划,也算成功
async def generate_monthly_plans(self, target_month: Optional[str] = None) -> bool:
async def generate_monthly_plans(self, target_month: Optional[str] = None):
"""
生成指定月份的月度计划
启动月度计划生成。
"""
if self.generation_running:
logger.info("月度计划生成任务已在运行中,跳过重复启动")
return
logger.info(f"已触发 {target_month or '当前月份'} 的月度计划生成任务。")
await self._generate_monthly_plans_logic(target_month)
async def _generate_monthly_plans_logic(self, target_month: Optional[str] = None) -> bool:
"""
生成指定月份的月度计划的核心逻辑
:param target_month: 目标月份,格式为 "YYYY-MM"。如果为 None则为当前月份。
:return: 是否生成成功
"""
@@ -136,14 +148,6 @@ class MonthlyPlanManager:
finally:
self.generation_running = False
def trigger_generate_monthly_plans(self, target_month: Optional[str] = None):
"""
以非阻塞的方式启动月度计划生成任务。
这允许其他模块如ScheduleManager触发计划生成而无需等待其完成。
"""
logger.info(f"已触发 {target_month or '当前月份'} 的非阻塞月度计划生成任务。")
asyncio.create_task(self.generate_monthly_plans(target_month))
def _get_previous_month(self, current_month: str) -> str:
"""获取上个月的月份字符串"""
try:
@@ -287,8 +291,6 @@ class MonthlyPlanManager:
except Exception as e:
logger.error(f" 归档 {target_month} 月度计划时发生错误: {e}")
class MonthlyPlanGenerationTask(AsyncTask):
"""每月初自动生成新月度计划的任务"""
@@ -324,8 +326,8 @@ class MonthlyPlanGenerationTask(AsyncTask):
# 生成新月份的计划
current_month = next_month.strftime("%Y-%m")
logger.info(f" 到达月初,开始生成 {current_month} 的月度计划...")
await self.monthly_plan_manager.generate_monthly_plans(current_month)
await self.monthly_plan_manager._generate_monthly_plans_logic(current_month)
except asyncio.CancelledError:
logger.info(" 每月月度计划生成任务被取消。")
break

View File

@@ -159,42 +159,37 @@ 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
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("数据库中的日程数据格式无效,将异步重新生成日程")
await self.generate_and_save_schedule()
except orjson.JSONDecodeError as e:
logger.error(f"日程数据JSON解析失败: {e},将异步重新生成日程")
await self.generate_and_save_schedule()
schedule_data = orjson.loads(str(schedule_record.schedule_data))
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)
return # 成功加载,直接返回
else:
logger.warning("数据库中的日程数据格式无效,将重新生成日程")
else:
logger.info(f"数据库中未找到今天的日程 ({today_str}),将异步调用 LLM 生成。")
await self.generate_and_save_schedule()
logger.info(f"数据库中未找到今天的日程 ({today_str}),将调用 LLM 生成。")
# 仅在需要时生成
await self.generate_and_save_schedule()
except Exception as e:
logger.error(f"加载或生成日程时出错: {e}")
# 出错时也尝试异步生成
logger.info("尝试异步生成日程作为备用方案...")
logger.info("尝试生成日程作为备用方案...")
await self.generate_and_save_schedule()
async def generate_and_save_schedule(self):
"""启动异步日程生成任务,避免阻塞主程序"""
"""日程生成任务提交到后台执行"""
if self.schedule_generation_running:
logger.info("日程生成任务已在运行中,跳过重复启动")
return
# 创建异步任务进行日程生成,不阻塞主程序
asyncio.create_task(self._async_generate_and_save_schedule())
logger.info("已启动异步日程生成任务")
logger.info("检测到需要生成日程,已提交后台任务。")
task = OnDemandScheduleGenerationTask(self)
await async_task_manager.add_task(task)
async def _async_generate_and_save_schedule(self):
"""异步生成并保存日程的内部方法"""
@@ -234,12 +229,14 @@ class ScheduleManager:
logger.info("可用的月度计划已耗尽或不足,触发后台补充生成...")
from mmc.src.schedule.monthly_plan_manager import monthly_plan_manager
# 以非阻塞方式触发月度计划生成
monthly_plan_manager.trigger_generate_monthly_plans(current_month_str)
# 等待月度计划生成完成
await monthly_plan_manager.ensure_and_generate_plans_if_needed(current_month_str)
# 注意:这里不再等待生成结果,因此后续代码不会立即获得新计划
# 日程将基于当前可用的信息生成,新计划将在下一次日程生成时可用。
logger.info("月度计划的后台生成任务已启动,本次日程将不包含新计划。")
# 重新获取月度计划
sampled_plans = get_smart_plans_for_daily_schedule(
current_month_str, max_count=3, avoid_days=avoid_days
)
logger.info("月度计划补充生成完毕,继续日程生成任务。")
if sampled_plans:
plan_texts = "\n".join([f"- {plan.plan_text}" for plan in sampled_plans])
@@ -448,6 +445,20 @@ class ScheduleManager:
return True
class OnDemandScheduleGenerationTask(AsyncTask):
"""按需生成日程的后台任务"""
def __init__(self, schedule_manager: "ScheduleManager"):
task_name = f"OnDemandScheduleGenerationTask-{datetime.now().strftime('%Y%m%d%H%M%S')}"
super().__init__(task_name=task_name)
self.schedule_manager = schedule_manager
async def run(self):
logger.info(f"后台任务 {self.task_name} 开始执行日程生成。")
await self.schedule_manager._async_generate_and_save_schedule()
logger.info(f"后台任务 {self.task_name} 完成。")
class DailyScheduleGenerationTask(AsyncTask):
"""每日零点自动生成新日程的任务"""
@@ -471,9 +482,9 @@ class DailyScheduleGenerationTask(AsyncTask):
# 2. 等待直到零点
await asyncio.sleep(sleep_seconds)
# 3. 执行异步日程生成
logger.info("到达每日零点,开始异步生成新的一天日程...")
await self.schedule_manager.generate_and_save_schedule()
# 3. 执行日程生成
logger.info("到达每日零点,开始生成新的一天日程...")
await self.schedule_manager._async_generate_and_save_schedule()
except asyncio.CancelledError:
logger.info("每日日程生成任务被取消。")