Undefined-bot 2.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- Undefined/__init__.py +3 -0
- Undefined/__main__.py +6 -0
- Undefined/ai.py +1215 -0
- Undefined/config.py +371 -0
- Undefined/end_summary_storage.py +48 -0
- Undefined/faq.py +244 -0
- Undefined/handlers.py +1247 -0
- Undefined/injection_response_agent.py +131 -0
- Undefined/main.py +126 -0
- Undefined/memory.py +120 -0
- Undefined/onebot.py +512 -0
- Undefined/rate_limit.py +130 -0
- Undefined/render.py +123 -0
- Undefined/scheduled_task_storage.py +88 -0
- Undefined/services/__init__.py +1 -0
- Undefined/services/queue_manager.py +206 -0
- Undefined/skills/README.md +53 -0
- Undefined/skills/__init__.py +10 -0
- Undefined/skills/agents/README.md +144 -0
- Undefined/skills/agents/__init__.py +116 -0
- Undefined/skills/agents/entertainment_agent/config.json +17 -0
- Undefined/skills/agents/entertainment_agent/handler.py +220 -0
- Undefined/skills/agents/entertainment_agent/intro.md +25 -0
- Undefined/skills/agents/entertainment_agent/prompt.md +20 -0
- Undefined/skills/agents/entertainment_agent/tools/__init__.py +1 -0
- Undefined/skills/agents/entertainment_agent/tools/ai_draw_one/config.json +34 -0
- Undefined/skills/agents/entertainment_agent/tools/ai_draw_one/handler.py +62 -0
- Undefined/skills/agents/entertainment_agent/tools/ai_study_helper/config.json +22 -0
- Undefined/skills/agents/entertainment_agent/tools/ai_study_helper/handler.py +35 -0
- Undefined/skills/agents/entertainment_agent/tools/get_current_time/config.json +12 -0
- Undefined/skills/agents/entertainment_agent/tools/get_current_time/handler.py +5 -0
- Undefined/skills/agents/entertainment_agent/tools/horoscope/config.json +24 -0
- Undefined/skills/agents/entertainment_agent/tools/horoscope/handler.py +141 -0
- Undefined/skills/agents/entertainment_agent/tools/minecraft_skin/config.json +43 -0
- Undefined/skills/agents/entertainment_agent/tools/minecraft_skin/handler.py +55 -0
- Undefined/skills/agents/entertainment_agent/tools/novel_search/config.json +25 -0
- Undefined/skills/agents/entertainment_agent/tools/novel_search/handler.py +31 -0
- Undefined/skills/agents/entertainment_agent/tools/renjian/config.json +12 -0
- Undefined/skills/agents/entertainment_agent/tools/renjian/handler.py +30 -0
- Undefined/skills/agents/entertainment_agent/tools/wenchang_dijun/config.json +12 -0
- Undefined/skills/agents/entertainment_agent/tools/wenchang_dijun/handler.py +44 -0
- Undefined/skills/agents/file_analysis_agent/__init__.py +1 -0
- Undefined/skills/agents/file_analysis_agent/config.json +21 -0
- Undefined/skills/agents/file_analysis_agent/handler.py +248 -0
- Undefined/skills/agents/file_analysis_agent/intro.md +22 -0
- Undefined/skills/agents/file_analysis_agent/prompt.md +36 -0
- Undefined/skills/agents/file_analysis_agent/tools/__init__.py +1 -0
- Undefined/skills/agents/file_analysis_agent/tools/analyze_code/config.json +17 -0
- Undefined/skills/agents/file_analysis_agent/tools/analyze_code/handler.py +427 -0
- Undefined/skills/agents/file_analysis_agent/tools/analyze_multimodal/config.json +25 -0
- Undefined/skills/agents/file_analysis_agent/tools/analyze_multimodal/handler.py +178 -0
- Undefined/skills/agents/file_analysis_agent/tools/cleanup_temp/config.json +16 -0
- Undefined/skills/agents/file_analysis_agent/tools/cleanup_temp/handler.py +35 -0
- Undefined/skills/agents/file_analysis_agent/tools/detect_file_type/config.json +17 -0
- Undefined/skills/agents/file_analysis_agent/tools/detect_file_type/handler.py +221 -0
- Undefined/skills/agents/file_analysis_agent/tools/download_file/config.json +21 -0
- Undefined/skills/agents/file_analysis_agent/tools/download_file/handler.py +124 -0
- Undefined/skills/agents/file_analysis_agent/tools/extract_archive/config.json +25 -0
- Undefined/skills/agents/file_analysis_agent/tools/extract_archive/handler.py +190 -0
- Undefined/skills/agents/file_analysis_agent/tools/extract_docx/config.json +17 -0
- Undefined/skills/agents/file_analysis_agent/tools/extract_docx/handler.py +78 -0
- Undefined/skills/agents/file_analysis_agent/tools/extract_pdf/config.json +21 -0
- Undefined/skills/agents/file_analysis_agent/tools/extract_pdf/handler.py +67 -0
- Undefined/skills/agents/file_analysis_agent/tools/extract_pptx/config.json +17 -0
- Undefined/skills/agents/file_analysis_agent/tools/extract_pptx/handler.py +73 -0
- Undefined/skills/agents/file_analysis_agent/tools/extract_xlsx/config.json +17 -0
- Undefined/skills/agents/file_analysis_agent/tools/extract_xlsx/handler.py +101 -0
- Undefined/skills/agents/file_analysis_agent/tools/get_current_time/config.json +12 -0
- Undefined/skills/agents/file_analysis_agent/tools/get_current_time/handler.py +5 -0
- Undefined/skills/agents/file_analysis_agent/tools/read_text_file/config.json +21 -0
- Undefined/skills/agents/file_analysis_agent/tools/read_text_file/handler.py +90 -0
- Undefined/skills/agents/info_agent/config.json +17 -0
- Undefined/skills/agents/info_agent/handler.py +220 -0
- Undefined/skills/agents/info_agent/intro.md +22 -0
- Undefined/skills/agents/info_agent/prompt.md +27 -0
- Undefined/skills/agents/info_agent/tools/__init__.py +1 -0
- Undefined/skills/agents/info_agent/tools/baiduhot/config.json +18 -0
- Undefined/skills/agents/info_agent/tools/baiduhot/handler.py +49 -0
- Undefined/skills/agents/info_agent/tools/base64/config.json +22 -0
- Undefined/skills/agents/info_agent/tools/base64/handler.py +44 -0
- Undefined/skills/agents/info_agent/tools/douyinhot/config.json +18 -0
- Undefined/skills/agents/info_agent/tools/douyinhot/handler.py +53 -0
- Undefined/skills/agents/info_agent/tools/get_current_time/config.json +12 -0
- Undefined/skills/agents/info_agent/tools/get_current_time/handler.py +5 -0
- Undefined/skills/agents/info_agent/tools/gold_price/config.json +12 -0
- Undefined/skills/agents/info_agent/tools/gold_price/handler.py +58 -0
- Undefined/skills/agents/info_agent/tools/hash/config.json +22 -0
- Undefined/skills/agents/info_agent/tools/hash/handler.py +43 -0
- Undefined/skills/agents/info_agent/tools/history/config.json +12 -0
- Undefined/skills/agents/info_agent/tools/history/handler.py +37 -0
- Undefined/skills/agents/info_agent/tools/net_check/config.json +17 -0
- Undefined/skills/agents/info_agent/tools/net_check/handler.py +117 -0
- Undefined/skills/agents/info_agent/tools/news_tencent/config.json +17 -0
- Undefined/skills/agents/info_agent/tools/news_tencent/handler.py +38 -0
- Undefined/skills/agents/info_agent/tools/qq_level_query/config.json +29 -0
- Undefined/skills/agents/info_agent/tools/qq_level_query/handler.py +48 -0
- Undefined/skills/agents/info_agent/tools/speed/config.json +17 -0
- Undefined/skills/agents/info_agent/tools/speed/handler.py +37 -0
- Undefined/skills/agents/info_agent/tools/tcping/config.json +21 -0
- Undefined/skills/agents/info_agent/tools/tcping/handler.py +53 -0
- Undefined/skills/agents/info_agent/tools/weather_query/config.json +22 -0
- Undefined/skills/agents/info_agent/tools/weather_query/handler.py +207 -0
- Undefined/skills/agents/info_agent/tools/weibohot/config.json +18 -0
- Undefined/skills/agents/info_agent/tools/weibohot/handler.py +49 -0
- Undefined/skills/agents/info_agent/tools/whois/config.json +17 -0
- Undefined/skills/agents/info_agent/tools/whois/handler.py +63 -0
- Undefined/skills/agents/naga_code_analysis_agent/config.json +17 -0
- Undefined/skills/agents/naga_code_analysis_agent/handler.py +222 -0
- Undefined/skills/agents/naga_code_analysis_agent/intro.md +17 -0
- Undefined/skills/agents/naga_code_analysis_agent/prompt.md +19 -0
- Undefined/skills/agents/naga_code_analysis_agent/tools/__init__.py +1 -0
- Undefined/skills/agents/naga_code_analysis_agent/tools/get_current_time/config.json +12 -0
- Undefined/skills/agents/naga_code_analysis_agent/tools/get_current_time/handler.py +5 -0
- Undefined/skills/agents/naga_code_analysis_agent/tools/glob/config.json +17 -0
- Undefined/skills/agents/naga_code_analysis_agent/tools/glob/handler.py +37 -0
- Undefined/skills/agents/naga_code_analysis_agent/tools/list_directory/config.json +17 -0
- Undefined/skills/agents/naga_code_analysis_agent/tools/list_directory/handler.py +31 -0
- Undefined/skills/agents/naga_code_analysis_agent/tools/read_file/config.json +17 -0
- Undefined/skills/agents/naga_code_analysis_agent/tools/read_file/handler.py +66 -0
- Undefined/skills/agents/naga_code_analysis_agent/tools/read_naga_intro/config.json +12 -0
- Undefined/skills/agents/naga_code_analysis_agent/tools/read_naga_intro/handler.py +327 -0
- Undefined/skills/agents/naga_code_analysis_agent/tools/search_file_content/config.json +25 -0
- Undefined/skills/agents/naga_code_analysis_agent/tools/search_file_content/handler.py +46 -0
- Undefined/skills/agents/scheduler_agent/__init__.py +1 -0
- Undefined/skills/agents/scheduler_agent/config.json +17 -0
- Undefined/skills/agents/scheduler_agent/handler.py +218 -0
- Undefined/skills/agents/scheduler_agent/intro.md +17 -0
- Undefined/skills/agents/scheduler_agent/prompt.md +67 -0
- Undefined/skills/agents/scheduler_agent/tools/__init__.py +1 -0
- Undefined/skills/agents/scheduler_agent/tools/create_schedule_task/config.json +37 -0
- Undefined/skills/agents/scheduler_agent/tools/create_schedule_task/handler.py +68 -0
- Undefined/skills/agents/scheduler_agent/tools/delete_schedule_task/config.json +19 -0
- Undefined/skills/agents/scheduler_agent/tools/delete_schedule_task/handler.py +26 -0
- Undefined/skills/agents/scheduler_agent/tools/get_current_time/config.json +12 -0
- Undefined/skills/agents/scheduler_agent/tools/get_current_time/handler.py +5 -0
- Undefined/skills/agents/scheduler_agent/tools/list_schedule_tasks/config.json +11 -0
- Undefined/skills/agents/scheduler_agent/tools/list_schedule_tasks/handler.py +47 -0
- Undefined/skills/agents/scheduler_agent/tools/update_schedule_task/config.json +39 -0
- Undefined/skills/agents/scheduler_agent/tools/update_schedule_task/handler.py +46 -0
- Undefined/skills/agents/social_agent/config.json +17 -0
- Undefined/skills/agents/social_agent/handler.py +220 -0
- Undefined/skills/agents/social_agent/intro.md +17 -0
- Undefined/skills/agents/social_agent/prompt.md +19 -0
- Undefined/skills/agents/social_agent/tools/__init__.py +1 -0
- Undefined/skills/agents/social_agent/tools/bilibili_search/config.json +21 -0
- Undefined/skills/agents/social_agent/tools/bilibili_search/handler.py +68 -0
- Undefined/skills/agents/social_agent/tools/bilibili_user_info/config.json +17 -0
- Undefined/skills/agents/social_agent/tools/bilibili_user_info/handler.py +68 -0
- Undefined/skills/agents/social_agent/tools/get_current_time/config.json +12 -0
- Undefined/skills/agents/social_agent/tools/get_current_time/handler.py +5 -0
- Undefined/skills/agents/social_agent/tools/music_global_search/config.json +21 -0
- Undefined/skills/agents/social_agent/tools/music_global_search/handler.py +47 -0
- Undefined/skills/agents/social_agent/tools/music_info_get/config.json +22 -0
- Undefined/skills/agents/social_agent/tools/music_info_get/handler.py +35 -0
- Undefined/skills/agents/social_agent/tools/music_lyrics/config.json +22 -0
- Undefined/skills/agents/social_agent/tools/music_lyrics/handler.py +26 -0
- Undefined/skills/agents/social_agent/tools/video_random_recommend/config.json +21 -0
- Undefined/skills/agents/social_agent/tools/video_random_recommend/handler.py +21 -0
- Undefined/skills/agents/web_agent/config.json +17 -0
- Undefined/skills/agents/web_agent/handler.py +221 -0
- Undefined/skills/agents/web_agent/intro.md +14 -0
- Undefined/skills/agents/web_agent/prompt.md +16 -0
- Undefined/skills/agents/web_agent/tools/__init__.py +1 -0
- Undefined/skills/agents/web_agent/tools/crawl_webpage/config.json +21 -0
- Undefined/skills/agents/web_agent/tools/crawl_webpage/handler.py +102 -0
- Undefined/skills/agents/web_agent/tools/get_current_time/config.json +12 -0
- Undefined/skills/agents/web_agent/tools/get_current_time/handler.py +5 -0
- Undefined/skills/agents/web_agent/tools/web_search/config.json +21 -0
- Undefined/skills/agents/web_agent/tools/web_search/handler.py +29 -0
- Undefined/skills/tools/README.md +85 -0
- Undefined/skills/tools/__init__.py +120 -0
- Undefined/skills/tools/debug/config.json +17 -0
- Undefined/skills/tools/debug/handler.py +35 -0
- Undefined/skills/tools/end/config.json +17 -0
- Undefined/skills/tools/end/handler.py +24 -0
- Undefined/skills/tools/get_current_time/config.json +12 -0
- Undefined/skills/tools/get_current_time/handler.py +5 -0
- Undefined/skills/tools/get_forward_msg/config.json +17 -0
- Undefined/skills/tools/get_forward_msg/handler.py +131 -0
- Undefined/skills/tools/get_group_member_info/config.json +38 -0
- Undefined/skills/tools/get_group_member_info/handler.py +142 -0
- Undefined/skills/tools/get_messages_by_time/config.json +30 -0
- Undefined/skills/tools/get_messages_by_time/handler.py +128 -0
- Undefined/skills/tools/get_picture/config.json +45 -0
- Undefined/skills/tools/get_picture/handler.py +191 -0
- Undefined/skills/tools/get_recent_messages/config.json +30 -0
- Undefined/skills/tools/get_recent_messages/handler.py +88 -0
- Undefined/skills/tools/qq_like/config.json +22 -0
- Undefined/skills/tools/qq_like/handler.py +58 -0
- Undefined/skills/tools/render_html/config.json +26 -0
- Undefined/skills/tools/render_html/handler.py +39 -0
- Undefined/skills/tools/render_latex/config.json +26 -0
- Undefined/skills/tools/render_latex/handler.py +78 -0
- Undefined/skills/tools/render_markdown/config.json +26 -0
- Undefined/skills/tools/render_markdown/handler.py +63 -0
- Undefined/skills/tools/save_memory/config.json +17 -0
- Undefined/skills/tools/save_memory/handler.py +17 -0
- Undefined/skills/tools/send_message/config.json +21 -0
- Undefined/skills/tools/send_message/handler.py +60 -0
- Undefined/skills/tools/send_private_message/config.json +21 -0
- Undefined/skills/tools/send_private_message/handler.py +35 -0
- Undefined/utils/__init__.py +0 -0
- Undefined/utils/common.py +186 -0
- Undefined/utils/history.py +284 -0
- Undefined/utils/scheduler.py +286 -0
- Undefined/utils/sender.py +140 -0
- undefined_bot-2.1.0.dist-info/METADATA +259 -0
- undefined_bot-2.1.0.dist-info/RECORD +211 -0
- undefined_bot-2.1.0.dist-info/WHEEL +4 -0
- undefined_bot-2.1.0.dist-info/entry_points.txt +2 -0
- undefined_bot-2.1.0.dist-info/licenses/LICENSE +7 -0
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
"""历史记录管理"""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
import os
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
# 历史记录文件路径
|
|
12
|
+
HISTORY_DIR = os.path.join("data", "history")
|
|
13
|
+
MAX_HISTORY = 10000 # 统一 10000 条限制
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class MessageHistoryManager:
|
|
17
|
+
"""消息历史管理器"""
|
|
18
|
+
|
|
19
|
+
def __init__(self) -> None:
|
|
20
|
+
self._message_history: dict[str, list[dict[str, Any]]] = {}
|
|
21
|
+
self._private_message_history: dict[str, list[dict[str, Any]]] = {}
|
|
22
|
+
# 确保目录存在
|
|
23
|
+
os.makedirs(HISTORY_DIR, exist_ok=True)
|
|
24
|
+
# 加载历史
|
|
25
|
+
self._load_all_histories()
|
|
26
|
+
|
|
27
|
+
def _get_group_history_path(self, group_id: int) -> str:
|
|
28
|
+
"""获取群消息历史文件路径"""
|
|
29
|
+
return os.path.join(HISTORY_DIR, f"group_{group_id}.json")
|
|
30
|
+
|
|
31
|
+
def _get_private_history_path(self, user_id: int) -> str:
|
|
32
|
+
"""获取私聊消息历史文件路径"""
|
|
33
|
+
return os.path.join(HISTORY_DIR, f"private_{user_id}.json")
|
|
34
|
+
|
|
35
|
+
def _save_history_to_file(self, history: list[dict[str, Any]], path: str) -> None:
|
|
36
|
+
"""保存历史记录到文件(最多 10000 条)"""
|
|
37
|
+
try:
|
|
38
|
+
# 只保留最近的 MAX_HISTORY 条
|
|
39
|
+
truncated_history = (
|
|
40
|
+
history[-MAX_HISTORY:] if len(history) > MAX_HISTORY else history
|
|
41
|
+
)
|
|
42
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
43
|
+
json.dump(truncated_history, f, ensure_ascii=False, indent=2)
|
|
44
|
+
logger.debug(f"[历史记录] 已成功保存历史到 {path}")
|
|
45
|
+
except Exception as e:
|
|
46
|
+
logger.error(f"[历史记录错误] 保存历史记录失败 {path}: {e}")
|
|
47
|
+
|
|
48
|
+
def _load_history_from_file(self, path: str) -> list[dict[str, Any]]:
|
|
49
|
+
"""从文件加载历史记录"""
|
|
50
|
+
try:
|
|
51
|
+
if os.path.exists(path):
|
|
52
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
53
|
+
history = json.load(f)
|
|
54
|
+
if isinstance(history, list):
|
|
55
|
+
# 兼容旧格式:补充缺失的字段
|
|
56
|
+
for msg in history:
|
|
57
|
+
if "type" not in msg:
|
|
58
|
+
msg["type"] = (
|
|
59
|
+
"private" if "private" in path else "group"
|
|
60
|
+
)
|
|
61
|
+
if "chat_id" not in msg:
|
|
62
|
+
if "group_" in path:
|
|
63
|
+
msg["chat_id"] = msg.get("user_id", "")
|
|
64
|
+
else:
|
|
65
|
+
msg["chat_id"] = msg.get("user_id", "")
|
|
66
|
+
if "timestamp" not in msg:
|
|
67
|
+
msg["timestamp"] = ""
|
|
68
|
+
if "chat_name" not in msg:
|
|
69
|
+
if msg["type"] == "group":
|
|
70
|
+
msg["chat_name"] = f"群{msg.get('chat_id', '')}"
|
|
71
|
+
else:
|
|
72
|
+
msg["chat_name"] = f"QQ用户{msg.get('chat_id', '')}"
|
|
73
|
+
# 只保留最近的 MAX_HISTORY 条
|
|
74
|
+
return (
|
|
75
|
+
history[-MAX_HISTORY:]
|
|
76
|
+
if len(history) > MAX_HISTORY
|
|
77
|
+
else history
|
|
78
|
+
)
|
|
79
|
+
except Exception as e:
|
|
80
|
+
logger.error(f"加载历史记录失败 {path}: {e}")
|
|
81
|
+
return []
|
|
82
|
+
|
|
83
|
+
def _load_all_histories(self) -> None:
|
|
84
|
+
"""启动时加载所有历史记录"""
|
|
85
|
+
if not os.path.exists(HISTORY_DIR):
|
|
86
|
+
logger.info("历史消息目录不存在,跳过加载")
|
|
87
|
+
return
|
|
88
|
+
|
|
89
|
+
# 加载群消息历史
|
|
90
|
+
group_count = 0
|
|
91
|
+
for filename in os.listdir(HISTORY_DIR):
|
|
92
|
+
if filename.startswith("group_") and filename.endswith(".json"):
|
|
93
|
+
try:
|
|
94
|
+
group_id_str = filename[6:-5] # 提取群号
|
|
95
|
+
path = os.path.join(HISTORY_DIR, filename)
|
|
96
|
+
self._message_history[group_id_str] = self._load_history_from_file(
|
|
97
|
+
path
|
|
98
|
+
)
|
|
99
|
+
group_count += 1
|
|
100
|
+
logger.debug(
|
|
101
|
+
f"[历史记录] 已加载群 {group_id_str} 历史消息: {len(self._message_history[group_id_str])} 条"
|
|
102
|
+
)
|
|
103
|
+
except Exception as e:
|
|
104
|
+
logger.error(f"[历史记录错误] 加载群历史失败 {filename}: {e}")
|
|
105
|
+
|
|
106
|
+
logger.info(f"[历史记录] 共加载了 {group_count} 个群聊的历史记录")
|
|
107
|
+
|
|
108
|
+
# 加载私聊消息历史
|
|
109
|
+
private_count = 0
|
|
110
|
+
for filename in os.listdir(HISTORY_DIR):
|
|
111
|
+
if filename.startswith("private_") and filename.endswith(".json"):
|
|
112
|
+
try:
|
|
113
|
+
user_id_str = filename[8:-5] # 提取用户ID
|
|
114
|
+
path = os.path.join(HISTORY_DIR, filename)
|
|
115
|
+
self._private_message_history[user_id_str] = (
|
|
116
|
+
self._load_history_from_file(path)
|
|
117
|
+
)
|
|
118
|
+
private_count += 1
|
|
119
|
+
logger.debug(
|
|
120
|
+
f"[历史记录] 已加载私聊 {user_id_str} 历史消息: {len(self._private_message_history[user_id_str])} 条"
|
|
121
|
+
)
|
|
122
|
+
except Exception as e:
|
|
123
|
+
logger.error(f"[历史记录错误] 加载私聊历史失败 {filename}: {e}")
|
|
124
|
+
|
|
125
|
+
logger.info(f"[历史记录] 共加载了 {private_count} 个私聊会话的历史记录")
|
|
126
|
+
|
|
127
|
+
def add_group_message(
|
|
128
|
+
self,
|
|
129
|
+
group_id: int,
|
|
130
|
+
sender_id: int,
|
|
131
|
+
text_content: str,
|
|
132
|
+
sender_card: str = "",
|
|
133
|
+
sender_nickname: str = "",
|
|
134
|
+
group_name: str = "",
|
|
135
|
+
) -> None:
|
|
136
|
+
"""保存群消息到历史记录"""
|
|
137
|
+
group_id_str = str(group_id)
|
|
138
|
+
sender_id_str = str(sender_id)
|
|
139
|
+
|
|
140
|
+
if group_id_str not in self._message_history:
|
|
141
|
+
self._message_history[group_id_str] = []
|
|
142
|
+
|
|
143
|
+
display_name = sender_card or sender_nickname or sender_id_str
|
|
144
|
+
|
|
145
|
+
self._message_history[group_id_str].append(
|
|
146
|
+
{
|
|
147
|
+
"type": "group",
|
|
148
|
+
"chat_id": group_id_str,
|
|
149
|
+
"chat_name": group_name or f"群{group_id_str}",
|
|
150
|
+
"user_id": sender_id_str,
|
|
151
|
+
"display_name": display_name,
|
|
152
|
+
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
|
153
|
+
"message": text_content,
|
|
154
|
+
}
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
if len(self._message_history[group_id_str]) > MAX_HISTORY:
|
|
158
|
+
self._message_history[group_id_str] = self._message_history[group_id_str][
|
|
159
|
+
-MAX_HISTORY:
|
|
160
|
+
]
|
|
161
|
+
|
|
162
|
+
self._save_history_to_file(
|
|
163
|
+
self._message_history[group_id_str], self._get_group_history_path(group_id)
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
def add_private_message(
|
|
167
|
+
self,
|
|
168
|
+
user_id: int,
|
|
169
|
+
text_content: str,
|
|
170
|
+
display_name: str = "",
|
|
171
|
+
user_name: str = "",
|
|
172
|
+
) -> None:
|
|
173
|
+
"""保存私聊消息到历史记录"""
|
|
174
|
+
user_id_str = str(user_id)
|
|
175
|
+
|
|
176
|
+
if user_id_str not in self._private_message_history:
|
|
177
|
+
self._private_message_history[user_id_str] = []
|
|
178
|
+
|
|
179
|
+
self._private_message_history[user_id_str].append(
|
|
180
|
+
{
|
|
181
|
+
"type": "private",
|
|
182
|
+
"chat_id": user_id_str,
|
|
183
|
+
"chat_name": user_name or f"QQ用户{user_id_str}",
|
|
184
|
+
"user_id": user_id_str,
|
|
185
|
+
"display_name": display_name or user_name or user_id_str,
|
|
186
|
+
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
|
187
|
+
"message": text_content,
|
|
188
|
+
}
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
if len(self._private_message_history[user_id_str]) > MAX_HISTORY:
|
|
192
|
+
self._private_message_history[user_id_str] = self._private_message_history[
|
|
193
|
+
user_id_str
|
|
194
|
+
][-MAX_HISTORY:]
|
|
195
|
+
|
|
196
|
+
self._save_history_to_file(
|
|
197
|
+
self._private_message_history[user_id_str],
|
|
198
|
+
self._get_private_history_path(user_id),
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
def get_recent(
|
|
202
|
+
self,
|
|
203
|
+
chat_id: str,
|
|
204
|
+
msg_type: str,
|
|
205
|
+
start: int,
|
|
206
|
+
end: int,
|
|
207
|
+
) -> list[dict[str, Any]]:
|
|
208
|
+
"""获取指定的历史消息"""
|
|
209
|
+
if msg_type == "group":
|
|
210
|
+
if chat_id not in self._message_history:
|
|
211
|
+
return []
|
|
212
|
+
history = self._message_history[chat_id]
|
|
213
|
+
elif msg_type == "private":
|
|
214
|
+
if chat_id not in self._private_message_history:
|
|
215
|
+
return []
|
|
216
|
+
history = self._private_message_history[chat_id]
|
|
217
|
+
else:
|
|
218
|
+
return []
|
|
219
|
+
|
|
220
|
+
total = len(history)
|
|
221
|
+
if total == 0:
|
|
222
|
+
return []
|
|
223
|
+
|
|
224
|
+
actual_start = total - end
|
|
225
|
+
actual_end = total - start
|
|
226
|
+
|
|
227
|
+
if actual_start < 0:
|
|
228
|
+
actual_start = 0
|
|
229
|
+
if actual_end > total:
|
|
230
|
+
actual_end = total
|
|
231
|
+
if actual_start >= actual_end:
|
|
232
|
+
return []
|
|
233
|
+
|
|
234
|
+
return history[actual_start:actual_end]
|
|
235
|
+
|
|
236
|
+
def get_recent_private(self, user_id: int, count: int) -> list[dict[str, Any]]:
|
|
237
|
+
"""获取最近的私聊消息"""
|
|
238
|
+
user_id_str = str(user_id)
|
|
239
|
+
if user_id_str not in self._private_message_history:
|
|
240
|
+
return []
|
|
241
|
+
return self._private_message_history[user_id_str][-count:] if count > 0 else []
|
|
242
|
+
|
|
243
|
+
def modify_last_group_message(
|
|
244
|
+
self,
|
|
245
|
+
group_id: int,
|
|
246
|
+
sender_id: int,
|
|
247
|
+
new_message: str,
|
|
248
|
+
) -> None:
|
|
249
|
+
"""修改群聊历史记录中指定用户的最后一条消息"""
|
|
250
|
+
group_id_str = str(group_id)
|
|
251
|
+
sender_id_str = str(sender_id)
|
|
252
|
+
|
|
253
|
+
if group_id_str not in self._message_history:
|
|
254
|
+
return
|
|
255
|
+
|
|
256
|
+
for i in range(len(self._message_history[group_id_str]) - 1, -1, -1):
|
|
257
|
+
msg = self._message_history[group_id_str][i]
|
|
258
|
+
if msg.get("user_id") == sender_id_str:
|
|
259
|
+
msg["message"] = new_message
|
|
260
|
+
self._save_history_to_file(
|
|
261
|
+
self._message_history[group_id_str],
|
|
262
|
+
self._get_group_history_path(group_id),
|
|
263
|
+
)
|
|
264
|
+
logger.info(f"已修改群聊 {group_id} 用户 {sender_id} 的最后一条消息")
|
|
265
|
+
break
|
|
266
|
+
|
|
267
|
+
def modify_last_private_message(
|
|
268
|
+
self,
|
|
269
|
+
user_id: int,
|
|
270
|
+
new_message: str,
|
|
271
|
+
) -> None:
|
|
272
|
+
"""修改私聊历史记录中最后一条消息"""
|
|
273
|
+
user_id_str = str(user_id)
|
|
274
|
+
|
|
275
|
+
if user_id_str not in self._private_message_history:
|
|
276
|
+
return
|
|
277
|
+
|
|
278
|
+
if self._private_message_history[user_id_str]:
|
|
279
|
+
self._private_message_history[user_id_str][-1]["message"] = new_message
|
|
280
|
+
self._save_history_to_file(
|
|
281
|
+
self._private_message_history[user_id_str],
|
|
282
|
+
self._get_private_history_path(user_id),
|
|
283
|
+
)
|
|
284
|
+
logger.info(f"已修改私聊用户 {user_id} 的最后一条消息")
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
"""
|
|
2
|
+
任务调度器
|
|
3
|
+
用于定时执行 AI 工具
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
import time
|
|
8
|
+
from typing import Any, Optional
|
|
9
|
+
|
|
10
|
+
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
|
11
|
+
from apscheduler.triggers.cron import CronTrigger
|
|
12
|
+
|
|
13
|
+
from ..scheduled_task_storage import ScheduledTaskStorage
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class TaskScheduler:
|
|
19
|
+
"""任务调度器"""
|
|
20
|
+
|
|
21
|
+
def __init__(
|
|
22
|
+
self,
|
|
23
|
+
ai_client: Any,
|
|
24
|
+
sender: Any,
|
|
25
|
+
onebot_client: Any,
|
|
26
|
+
history_manager: Any,
|
|
27
|
+
task_storage: Optional[ScheduledTaskStorage] = None,
|
|
28
|
+
) -> None:
|
|
29
|
+
"""初始化调度器
|
|
30
|
+
|
|
31
|
+
参数:
|
|
32
|
+
ai_client: AI 客户端实例 (AIClient)
|
|
33
|
+
sender: 消息发送器实例 (MessageSender)
|
|
34
|
+
onebot_client: OneBot 客户端实例
|
|
35
|
+
history_manager: 历史记录管理器
|
|
36
|
+
task_storage: 任务持久化存储器
|
|
37
|
+
"""
|
|
38
|
+
self.scheduler = AsyncIOScheduler()
|
|
39
|
+
self.ai = ai_client
|
|
40
|
+
self.sender = sender
|
|
41
|
+
self.onebot = onebot_client
|
|
42
|
+
self.history_manager = history_manager
|
|
43
|
+
self.storage = task_storage or ScheduledTaskStorage()
|
|
44
|
+
|
|
45
|
+
# 从存储加载任务
|
|
46
|
+
self.tasks: dict[str, Any] = self.storage.load_tasks()
|
|
47
|
+
|
|
48
|
+
# 确保 scheduler 在 event loop 中运行
|
|
49
|
+
if not self.scheduler.running:
|
|
50
|
+
self.scheduler.start()
|
|
51
|
+
logger.info("[任务调度] 任务调度服务已启动")
|
|
52
|
+
|
|
53
|
+
# 恢复已有的任务
|
|
54
|
+
self._recover_tasks()
|
|
55
|
+
|
|
56
|
+
def _recover_tasks(self) -> None:
|
|
57
|
+
"""从存储中恢复任务并添加到调度器"""
|
|
58
|
+
if not self.tasks:
|
|
59
|
+
logger.info("[任务调度] 没有需要恢复的定时任务")
|
|
60
|
+
return
|
|
61
|
+
|
|
62
|
+
count = 0
|
|
63
|
+
for task_id, info in list(self.tasks.items()):
|
|
64
|
+
try:
|
|
65
|
+
trigger = CronTrigger.from_crontab(info["cron"])
|
|
66
|
+
self.scheduler.add_job(
|
|
67
|
+
self._execute_tool_wrapper,
|
|
68
|
+
trigger=trigger,
|
|
69
|
+
id=task_id,
|
|
70
|
+
args=[
|
|
71
|
+
task_id,
|
|
72
|
+
info["tool_name"],
|
|
73
|
+
info["tool_args"],
|
|
74
|
+
info["target_id"],
|
|
75
|
+
info["target_type"],
|
|
76
|
+
],
|
|
77
|
+
replace_existing=True,
|
|
78
|
+
)
|
|
79
|
+
count += 1
|
|
80
|
+
logger.debug(f"[任务调度] 已恢复任务: {task_id} ({info['tool_name']})")
|
|
81
|
+
except Exception as e:
|
|
82
|
+
logger.error(f"[任务调度错误] 恢复定时任务 {task_id} 失败: {e}")
|
|
83
|
+
# 如果任务恢复失败(如格式错误),保留在 self.tasks 中还是删除?
|
|
84
|
+
# 目前保留,由用户或后续逻辑处理
|
|
85
|
+
|
|
86
|
+
if count > 0:
|
|
87
|
+
logger.info(f"成功恢复 {count} 个定时任务")
|
|
88
|
+
|
|
89
|
+
def add_task(
|
|
90
|
+
self,
|
|
91
|
+
task_id: str,
|
|
92
|
+
tool_name: str,
|
|
93
|
+
tool_args: dict[str, Any],
|
|
94
|
+
cron_expression: str,
|
|
95
|
+
target_id: int | None = None,
|
|
96
|
+
target_type: str = "group",
|
|
97
|
+
task_name: str | None = None,
|
|
98
|
+
max_executions: int | None = None,
|
|
99
|
+
) -> bool:
|
|
100
|
+
"""添加定时任务
|
|
101
|
+
|
|
102
|
+
参数:
|
|
103
|
+
task_id: 任务唯一标识(用户指定或自动生成)
|
|
104
|
+
tool_name: 要执行的工具名称
|
|
105
|
+
tool_args: 工具参数
|
|
106
|
+
cron_expression: crontab 表达式 (分 时 日 月 周)
|
|
107
|
+
target_id: 结果发送目标 ID
|
|
108
|
+
target_type: 结果发送目标类型 (group/private)
|
|
109
|
+
task_name: 任务名称(用于标识,可读名称)
|
|
110
|
+
max_executions: 最大执行次数(None 表示无限)
|
|
111
|
+
|
|
112
|
+
返回:
|
|
113
|
+
是否添加成功
|
|
114
|
+
"""
|
|
115
|
+
try:
|
|
116
|
+
trigger = CronTrigger.from_crontab(cron_expression)
|
|
117
|
+
|
|
118
|
+
self.scheduler.add_job(
|
|
119
|
+
self._execute_tool_wrapper,
|
|
120
|
+
trigger=trigger,
|
|
121
|
+
id=task_id,
|
|
122
|
+
args=[task_id, tool_name, tool_args, target_id, target_type],
|
|
123
|
+
replace_existing=True,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
self.tasks[task_id] = {
|
|
127
|
+
"task_id": task_id,
|
|
128
|
+
"tool_name": tool_name,
|
|
129
|
+
"tool_args": tool_args,
|
|
130
|
+
"cron": cron_expression,
|
|
131
|
+
"target_id": target_id,
|
|
132
|
+
"target_type": target_type,
|
|
133
|
+
"task_name": task_name or "",
|
|
134
|
+
"max_executions": max_executions,
|
|
135
|
+
"current_executions": 0,
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
# 持久化保存
|
|
139
|
+
self.storage.save_all(self.tasks)
|
|
140
|
+
|
|
141
|
+
logger.info(
|
|
142
|
+
f"添加定时任务成功: {task_id} -> {tool_name} ({cron_expression})"
|
|
143
|
+
)
|
|
144
|
+
return True
|
|
145
|
+
except Exception as e:
|
|
146
|
+
logger.error(f"添加定时任务失败: {e}")
|
|
147
|
+
return False
|
|
148
|
+
|
|
149
|
+
def update_task(
|
|
150
|
+
self,
|
|
151
|
+
task_id: str,
|
|
152
|
+
cron_expression: str | None = None,
|
|
153
|
+
tool_name: str | None = None,
|
|
154
|
+
tool_args: dict[str, Any] | None = None,
|
|
155
|
+
task_name: str | None = None,
|
|
156
|
+
max_executions: int | None = None,
|
|
157
|
+
) -> bool:
|
|
158
|
+
"""修改定时任务(不支持修改 task_id)
|
|
159
|
+
|
|
160
|
+
参数:
|
|
161
|
+
task_id: 要修改的任务 ID
|
|
162
|
+
cron_expression: 新的 crontab 表达式
|
|
163
|
+
tool_name: 新的工具名称
|
|
164
|
+
tool_args: 新的工具参数
|
|
165
|
+
task_name: 新的任务名称
|
|
166
|
+
max_executions: 新的最大执行次数
|
|
167
|
+
|
|
168
|
+
返回:
|
|
169
|
+
是否修改成功
|
|
170
|
+
"""
|
|
171
|
+
if task_id not in self.tasks:
|
|
172
|
+
logger.warning(f"修改定时任务失败: 任务不存在 {task_id}")
|
|
173
|
+
return False
|
|
174
|
+
|
|
175
|
+
try:
|
|
176
|
+
task_info = self.tasks[task_id]
|
|
177
|
+
|
|
178
|
+
if cron_expression is not None:
|
|
179
|
+
trigger = CronTrigger.from_crontab(cron_expression)
|
|
180
|
+
self.scheduler.reschedule_job(task_id, trigger=trigger)
|
|
181
|
+
task_info["cron"] = cron_expression
|
|
182
|
+
|
|
183
|
+
if tool_name is not None:
|
|
184
|
+
task_info["tool_name"] = tool_name
|
|
185
|
+
|
|
186
|
+
if tool_args is not None:
|
|
187
|
+
task_info["tool_args"] = tool_args
|
|
188
|
+
|
|
189
|
+
if task_name is not None:
|
|
190
|
+
task_info["task_name"] = task_name
|
|
191
|
+
|
|
192
|
+
if max_executions is not None:
|
|
193
|
+
task_info["max_executions"] = max_executions
|
|
194
|
+
|
|
195
|
+
# 持久化保存
|
|
196
|
+
self.storage.save_all(self.tasks)
|
|
197
|
+
|
|
198
|
+
logger.info(f"修改定时任务成功: {task_id}")
|
|
199
|
+
return True
|
|
200
|
+
except Exception as e:
|
|
201
|
+
logger.error(f"修改定时任务失败: {e}")
|
|
202
|
+
return False
|
|
203
|
+
|
|
204
|
+
def remove_task(self, task_id: str) -> bool:
|
|
205
|
+
"""移除定时任务"""
|
|
206
|
+
try:
|
|
207
|
+
self.scheduler.remove_job(task_id)
|
|
208
|
+
if task_id in self.tasks:
|
|
209
|
+
del self.tasks[task_id]
|
|
210
|
+
# 持久化保存
|
|
211
|
+
self.storage.save_all(self.tasks)
|
|
212
|
+
logger.info(f"移除定时任务成功: {task_id}")
|
|
213
|
+
return True
|
|
214
|
+
except Exception as e:
|
|
215
|
+
logger.warning(f"移除定时任务失败 (可能不存在): {e}")
|
|
216
|
+
return False
|
|
217
|
+
|
|
218
|
+
def list_tasks(self) -> dict[str, Any]:
|
|
219
|
+
"""列出所有任务"""
|
|
220
|
+
return self.tasks
|
|
221
|
+
|
|
222
|
+
async def _execute_tool_wrapper(
|
|
223
|
+
self,
|
|
224
|
+
task_id: str,
|
|
225
|
+
tool_name: str,
|
|
226
|
+
tool_args: dict[str, Any],
|
|
227
|
+
target_id: int | None,
|
|
228
|
+
target_type: str,
|
|
229
|
+
) -> None:
|
|
230
|
+
"""任务执行包装器"""
|
|
231
|
+
logger.info(f"[任务触发] 定时任务开始执行: ID={task_id}, 工具={tool_name}")
|
|
232
|
+
logger.debug(f"[任务详情] 参数={tool_args}, 目标={target_id}({target_type})")
|
|
233
|
+
|
|
234
|
+
try:
|
|
235
|
+
context = {
|
|
236
|
+
"scheduler": self,
|
|
237
|
+
"ai_client": self.ai,
|
|
238
|
+
"sender": self.sender,
|
|
239
|
+
"onebot_client": self.onebot,
|
|
240
|
+
"history_manager": self.history_manager,
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
start_time = time.perf_counter()
|
|
244
|
+
result = await self.ai._execute_tool(tool_name, tool_args, context)
|
|
245
|
+
duration = time.perf_counter() - start_time
|
|
246
|
+
|
|
247
|
+
if result and target_id:
|
|
248
|
+
msg = f"【定时任务执行结果】\n工具: {tool_name}\n结果:\n{result}"
|
|
249
|
+
if target_type == "group":
|
|
250
|
+
await self.sender.send_group_message(target_id, msg)
|
|
251
|
+
else:
|
|
252
|
+
await self.sender.send_private_message(target_id, msg)
|
|
253
|
+
|
|
254
|
+
logger.info(
|
|
255
|
+
f"[任务完成] 定时任务执行成功: ID={task_id}, 耗时={duration:.2f}s"
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
if task_id in self.tasks:
|
|
259
|
+
task_info = self.tasks[task_id]
|
|
260
|
+
task_info["current_executions"] = (
|
|
261
|
+
task_info.get("current_executions", 0) + 1
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
# 持久化保存执行次数
|
|
265
|
+
self.storage.save_all(self.tasks)
|
|
266
|
+
|
|
267
|
+
max_executions = task_info.get("max_executions")
|
|
268
|
+
current_executions = task_info.get("current_executions", 0)
|
|
269
|
+
|
|
270
|
+
if max_executions is not None and current_executions >= max_executions:
|
|
271
|
+
self.remove_task(task_id)
|
|
272
|
+
logger.info(
|
|
273
|
+
f"定时任务 {task_id} 已达到最大执行次数 {max_executions},已自动删除"
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
except Exception as e:
|
|
277
|
+
logger.exception(f"定时任务执行出错: {e}")
|
|
278
|
+
if target_id:
|
|
279
|
+
try:
|
|
280
|
+
err_msg = f"【定时任务执行失败】\n工具: {tool_name}\n错误: {e}"
|
|
281
|
+
if target_type == "group":
|
|
282
|
+
await self.sender.send_group_message(target_id, err_msg)
|
|
283
|
+
else:
|
|
284
|
+
await self.sender.send_private_message(target_id, err_msg)
|
|
285
|
+
except Exception:
|
|
286
|
+
pass
|