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.
Files changed (211) hide show
  1. Undefined/__init__.py +3 -0
  2. Undefined/__main__.py +6 -0
  3. Undefined/ai.py +1215 -0
  4. Undefined/config.py +371 -0
  5. Undefined/end_summary_storage.py +48 -0
  6. Undefined/faq.py +244 -0
  7. Undefined/handlers.py +1247 -0
  8. Undefined/injection_response_agent.py +131 -0
  9. Undefined/main.py +126 -0
  10. Undefined/memory.py +120 -0
  11. Undefined/onebot.py +512 -0
  12. Undefined/rate_limit.py +130 -0
  13. Undefined/render.py +123 -0
  14. Undefined/scheduled_task_storage.py +88 -0
  15. Undefined/services/__init__.py +1 -0
  16. Undefined/services/queue_manager.py +206 -0
  17. Undefined/skills/README.md +53 -0
  18. Undefined/skills/__init__.py +10 -0
  19. Undefined/skills/agents/README.md +144 -0
  20. Undefined/skills/agents/__init__.py +116 -0
  21. Undefined/skills/agents/entertainment_agent/config.json +17 -0
  22. Undefined/skills/agents/entertainment_agent/handler.py +220 -0
  23. Undefined/skills/agents/entertainment_agent/intro.md +25 -0
  24. Undefined/skills/agents/entertainment_agent/prompt.md +20 -0
  25. Undefined/skills/agents/entertainment_agent/tools/__init__.py +1 -0
  26. Undefined/skills/agents/entertainment_agent/tools/ai_draw_one/config.json +34 -0
  27. Undefined/skills/agents/entertainment_agent/tools/ai_draw_one/handler.py +62 -0
  28. Undefined/skills/agents/entertainment_agent/tools/ai_study_helper/config.json +22 -0
  29. Undefined/skills/agents/entertainment_agent/tools/ai_study_helper/handler.py +35 -0
  30. Undefined/skills/agents/entertainment_agent/tools/get_current_time/config.json +12 -0
  31. Undefined/skills/agents/entertainment_agent/tools/get_current_time/handler.py +5 -0
  32. Undefined/skills/agents/entertainment_agent/tools/horoscope/config.json +24 -0
  33. Undefined/skills/agents/entertainment_agent/tools/horoscope/handler.py +141 -0
  34. Undefined/skills/agents/entertainment_agent/tools/minecraft_skin/config.json +43 -0
  35. Undefined/skills/agents/entertainment_agent/tools/minecraft_skin/handler.py +55 -0
  36. Undefined/skills/agents/entertainment_agent/tools/novel_search/config.json +25 -0
  37. Undefined/skills/agents/entertainment_agent/tools/novel_search/handler.py +31 -0
  38. Undefined/skills/agents/entertainment_agent/tools/renjian/config.json +12 -0
  39. Undefined/skills/agents/entertainment_agent/tools/renjian/handler.py +30 -0
  40. Undefined/skills/agents/entertainment_agent/tools/wenchang_dijun/config.json +12 -0
  41. Undefined/skills/agents/entertainment_agent/tools/wenchang_dijun/handler.py +44 -0
  42. Undefined/skills/agents/file_analysis_agent/__init__.py +1 -0
  43. Undefined/skills/agents/file_analysis_agent/config.json +21 -0
  44. Undefined/skills/agents/file_analysis_agent/handler.py +248 -0
  45. Undefined/skills/agents/file_analysis_agent/intro.md +22 -0
  46. Undefined/skills/agents/file_analysis_agent/prompt.md +36 -0
  47. Undefined/skills/agents/file_analysis_agent/tools/__init__.py +1 -0
  48. Undefined/skills/agents/file_analysis_agent/tools/analyze_code/config.json +17 -0
  49. Undefined/skills/agents/file_analysis_agent/tools/analyze_code/handler.py +427 -0
  50. Undefined/skills/agents/file_analysis_agent/tools/analyze_multimodal/config.json +25 -0
  51. Undefined/skills/agents/file_analysis_agent/tools/analyze_multimodal/handler.py +178 -0
  52. Undefined/skills/agents/file_analysis_agent/tools/cleanup_temp/config.json +16 -0
  53. Undefined/skills/agents/file_analysis_agent/tools/cleanup_temp/handler.py +35 -0
  54. Undefined/skills/agents/file_analysis_agent/tools/detect_file_type/config.json +17 -0
  55. Undefined/skills/agents/file_analysis_agent/tools/detect_file_type/handler.py +221 -0
  56. Undefined/skills/agents/file_analysis_agent/tools/download_file/config.json +21 -0
  57. Undefined/skills/agents/file_analysis_agent/tools/download_file/handler.py +124 -0
  58. Undefined/skills/agents/file_analysis_agent/tools/extract_archive/config.json +25 -0
  59. Undefined/skills/agents/file_analysis_agent/tools/extract_archive/handler.py +190 -0
  60. Undefined/skills/agents/file_analysis_agent/tools/extract_docx/config.json +17 -0
  61. Undefined/skills/agents/file_analysis_agent/tools/extract_docx/handler.py +78 -0
  62. Undefined/skills/agents/file_analysis_agent/tools/extract_pdf/config.json +21 -0
  63. Undefined/skills/agents/file_analysis_agent/tools/extract_pdf/handler.py +67 -0
  64. Undefined/skills/agents/file_analysis_agent/tools/extract_pptx/config.json +17 -0
  65. Undefined/skills/agents/file_analysis_agent/tools/extract_pptx/handler.py +73 -0
  66. Undefined/skills/agents/file_analysis_agent/tools/extract_xlsx/config.json +17 -0
  67. Undefined/skills/agents/file_analysis_agent/tools/extract_xlsx/handler.py +101 -0
  68. Undefined/skills/agents/file_analysis_agent/tools/get_current_time/config.json +12 -0
  69. Undefined/skills/agents/file_analysis_agent/tools/get_current_time/handler.py +5 -0
  70. Undefined/skills/agents/file_analysis_agent/tools/read_text_file/config.json +21 -0
  71. Undefined/skills/agents/file_analysis_agent/tools/read_text_file/handler.py +90 -0
  72. Undefined/skills/agents/info_agent/config.json +17 -0
  73. Undefined/skills/agents/info_agent/handler.py +220 -0
  74. Undefined/skills/agents/info_agent/intro.md +22 -0
  75. Undefined/skills/agents/info_agent/prompt.md +27 -0
  76. Undefined/skills/agents/info_agent/tools/__init__.py +1 -0
  77. Undefined/skills/agents/info_agent/tools/baiduhot/config.json +18 -0
  78. Undefined/skills/agents/info_agent/tools/baiduhot/handler.py +49 -0
  79. Undefined/skills/agents/info_agent/tools/base64/config.json +22 -0
  80. Undefined/skills/agents/info_agent/tools/base64/handler.py +44 -0
  81. Undefined/skills/agents/info_agent/tools/douyinhot/config.json +18 -0
  82. Undefined/skills/agents/info_agent/tools/douyinhot/handler.py +53 -0
  83. Undefined/skills/agents/info_agent/tools/get_current_time/config.json +12 -0
  84. Undefined/skills/agents/info_agent/tools/get_current_time/handler.py +5 -0
  85. Undefined/skills/agents/info_agent/tools/gold_price/config.json +12 -0
  86. Undefined/skills/agents/info_agent/tools/gold_price/handler.py +58 -0
  87. Undefined/skills/agents/info_agent/tools/hash/config.json +22 -0
  88. Undefined/skills/agents/info_agent/tools/hash/handler.py +43 -0
  89. Undefined/skills/agents/info_agent/tools/history/config.json +12 -0
  90. Undefined/skills/agents/info_agent/tools/history/handler.py +37 -0
  91. Undefined/skills/agents/info_agent/tools/net_check/config.json +17 -0
  92. Undefined/skills/agents/info_agent/tools/net_check/handler.py +117 -0
  93. Undefined/skills/agents/info_agent/tools/news_tencent/config.json +17 -0
  94. Undefined/skills/agents/info_agent/tools/news_tencent/handler.py +38 -0
  95. Undefined/skills/agents/info_agent/tools/qq_level_query/config.json +29 -0
  96. Undefined/skills/agents/info_agent/tools/qq_level_query/handler.py +48 -0
  97. Undefined/skills/agents/info_agent/tools/speed/config.json +17 -0
  98. Undefined/skills/agents/info_agent/tools/speed/handler.py +37 -0
  99. Undefined/skills/agents/info_agent/tools/tcping/config.json +21 -0
  100. Undefined/skills/agents/info_agent/tools/tcping/handler.py +53 -0
  101. Undefined/skills/agents/info_agent/tools/weather_query/config.json +22 -0
  102. Undefined/skills/agents/info_agent/tools/weather_query/handler.py +207 -0
  103. Undefined/skills/agents/info_agent/tools/weibohot/config.json +18 -0
  104. Undefined/skills/agents/info_agent/tools/weibohot/handler.py +49 -0
  105. Undefined/skills/agents/info_agent/tools/whois/config.json +17 -0
  106. Undefined/skills/agents/info_agent/tools/whois/handler.py +63 -0
  107. Undefined/skills/agents/naga_code_analysis_agent/config.json +17 -0
  108. Undefined/skills/agents/naga_code_analysis_agent/handler.py +222 -0
  109. Undefined/skills/agents/naga_code_analysis_agent/intro.md +17 -0
  110. Undefined/skills/agents/naga_code_analysis_agent/prompt.md +19 -0
  111. Undefined/skills/agents/naga_code_analysis_agent/tools/__init__.py +1 -0
  112. Undefined/skills/agents/naga_code_analysis_agent/tools/get_current_time/config.json +12 -0
  113. Undefined/skills/agents/naga_code_analysis_agent/tools/get_current_time/handler.py +5 -0
  114. Undefined/skills/agents/naga_code_analysis_agent/tools/glob/config.json +17 -0
  115. Undefined/skills/agents/naga_code_analysis_agent/tools/glob/handler.py +37 -0
  116. Undefined/skills/agents/naga_code_analysis_agent/tools/list_directory/config.json +17 -0
  117. Undefined/skills/agents/naga_code_analysis_agent/tools/list_directory/handler.py +31 -0
  118. Undefined/skills/agents/naga_code_analysis_agent/tools/read_file/config.json +17 -0
  119. Undefined/skills/agents/naga_code_analysis_agent/tools/read_file/handler.py +66 -0
  120. Undefined/skills/agents/naga_code_analysis_agent/tools/read_naga_intro/config.json +12 -0
  121. Undefined/skills/agents/naga_code_analysis_agent/tools/read_naga_intro/handler.py +327 -0
  122. Undefined/skills/agents/naga_code_analysis_agent/tools/search_file_content/config.json +25 -0
  123. Undefined/skills/agents/naga_code_analysis_agent/tools/search_file_content/handler.py +46 -0
  124. Undefined/skills/agents/scheduler_agent/__init__.py +1 -0
  125. Undefined/skills/agents/scheduler_agent/config.json +17 -0
  126. Undefined/skills/agents/scheduler_agent/handler.py +218 -0
  127. Undefined/skills/agents/scheduler_agent/intro.md +17 -0
  128. Undefined/skills/agents/scheduler_agent/prompt.md +67 -0
  129. Undefined/skills/agents/scheduler_agent/tools/__init__.py +1 -0
  130. Undefined/skills/agents/scheduler_agent/tools/create_schedule_task/config.json +37 -0
  131. Undefined/skills/agents/scheduler_agent/tools/create_schedule_task/handler.py +68 -0
  132. Undefined/skills/agents/scheduler_agent/tools/delete_schedule_task/config.json +19 -0
  133. Undefined/skills/agents/scheduler_agent/tools/delete_schedule_task/handler.py +26 -0
  134. Undefined/skills/agents/scheduler_agent/tools/get_current_time/config.json +12 -0
  135. Undefined/skills/agents/scheduler_agent/tools/get_current_time/handler.py +5 -0
  136. Undefined/skills/agents/scheduler_agent/tools/list_schedule_tasks/config.json +11 -0
  137. Undefined/skills/agents/scheduler_agent/tools/list_schedule_tasks/handler.py +47 -0
  138. Undefined/skills/agents/scheduler_agent/tools/update_schedule_task/config.json +39 -0
  139. Undefined/skills/agents/scheduler_agent/tools/update_schedule_task/handler.py +46 -0
  140. Undefined/skills/agents/social_agent/config.json +17 -0
  141. Undefined/skills/agents/social_agent/handler.py +220 -0
  142. Undefined/skills/agents/social_agent/intro.md +17 -0
  143. Undefined/skills/agents/social_agent/prompt.md +19 -0
  144. Undefined/skills/agents/social_agent/tools/__init__.py +1 -0
  145. Undefined/skills/agents/social_agent/tools/bilibili_search/config.json +21 -0
  146. Undefined/skills/agents/social_agent/tools/bilibili_search/handler.py +68 -0
  147. Undefined/skills/agents/social_agent/tools/bilibili_user_info/config.json +17 -0
  148. Undefined/skills/agents/social_agent/tools/bilibili_user_info/handler.py +68 -0
  149. Undefined/skills/agents/social_agent/tools/get_current_time/config.json +12 -0
  150. Undefined/skills/agents/social_agent/tools/get_current_time/handler.py +5 -0
  151. Undefined/skills/agents/social_agent/tools/music_global_search/config.json +21 -0
  152. Undefined/skills/agents/social_agent/tools/music_global_search/handler.py +47 -0
  153. Undefined/skills/agents/social_agent/tools/music_info_get/config.json +22 -0
  154. Undefined/skills/agents/social_agent/tools/music_info_get/handler.py +35 -0
  155. Undefined/skills/agents/social_agent/tools/music_lyrics/config.json +22 -0
  156. Undefined/skills/agents/social_agent/tools/music_lyrics/handler.py +26 -0
  157. Undefined/skills/agents/social_agent/tools/video_random_recommend/config.json +21 -0
  158. Undefined/skills/agents/social_agent/tools/video_random_recommend/handler.py +21 -0
  159. Undefined/skills/agents/web_agent/config.json +17 -0
  160. Undefined/skills/agents/web_agent/handler.py +221 -0
  161. Undefined/skills/agents/web_agent/intro.md +14 -0
  162. Undefined/skills/agents/web_agent/prompt.md +16 -0
  163. Undefined/skills/agents/web_agent/tools/__init__.py +1 -0
  164. Undefined/skills/agents/web_agent/tools/crawl_webpage/config.json +21 -0
  165. Undefined/skills/agents/web_agent/tools/crawl_webpage/handler.py +102 -0
  166. Undefined/skills/agents/web_agent/tools/get_current_time/config.json +12 -0
  167. Undefined/skills/agents/web_agent/tools/get_current_time/handler.py +5 -0
  168. Undefined/skills/agents/web_agent/tools/web_search/config.json +21 -0
  169. Undefined/skills/agents/web_agent/tools/web_search/handler.py +29 -0
  170. Undefined/skills/tools/README.md +85 -0
  171. Undefined/skills/tools/__init__.py +120 -0
  172. Undefined/skills/tools/debug/config.json +17 -0
  173. Undefined/skills/tools/debug/handler.py +35 -0
  174. Undefined/skills/tools/end/config.json +17 -0
  175. Undefined/skills/tools/end/handler.py +24 -0
  176. Undefined/skills/tools/get_current_time/config.json +12 -0
  177. Undefined/skills/tools/get_current_time/handler.py +5 -0
  178. Undefined/skills/tools/get_forward_msg/config.json +17 -0
  179. Undefined/skills/tools/get_forward_msg/handler.py +131 -0
  180. Undefined/skills/tools/get_group_member_info/config.json +38 -0
  181. Undefined/skills/tools/get_group_member_info/handler.py +142 -0
  182. Undefined/skills/tools/get_messages_by_time/config.json +30 -0
  183. Undefined/skills/tools/get_messages_by_time/handler.py +128 -0
  184. Undefined/skills/tools/get_picture/config.json +45 -0
  185. Undefined/skills/tools/get_picture/handler.py +191 -0
  186. Undefined/skills/tools/get_recent_messages/config.json +30 -0
  187. Undefined/skills/tools/get_recent_messages/handler.py +88 -0
  188. Undefined/skills/tools/qq_like/config.json +22 -0
  189. Undefined/skills/tools/qq_like/handler.py +58 -0
  190. Undefined/skills/tools/render_html/config.json +26 -0
  191. Undefined/skills/tools/render_html/handler.py +39 -0
  192. Undefined/skills/tools/render_latex/config.json +26 -0
  193. Undefined/skills/tools/render_latex/handler.py +78 -0
  194. Undefined/skills/tools/render_markdown/config.json +26 -0
  195. Undefined/skills/tools/render_markdown/handler.py +63 -0
  196. Undefined/skills/tools/save_memory/config.json +17 -0
  197. Undefined/skills/tools/save_memory/handler.py +17 -0
  198. Undefined/skills/tools/send_message/config.json +21 -0
  199. Undefined/skills/tools/send_message/handler.py +60 -0
  200. Undefined/skills/tools/send_private_message/config.json +21 -0
  201. Undefined/skills/tools/send_private_message/handler.py +35 -0
  202. Undefined/utils/__init__.py +0 -0
  203. Undefined/utils/common.py +186 -0
  204. Undefined/utils/history.py +284 -0
  205. Undefined/utils/scheduler.py +286 -0
  206. Undefined/utils/sender.py +140 -0
  207. undefined_bot-2.1.0.dist-info/METADATA +259 -0
  208. undefined_bot-2.1.0.dist-info/RECORD +211 -0
  209. undefined_bot-2.1.0.dist-info/WHEEL +4 -0
  210. undefined_bot-2.1.0.dist-info/entry_points.txt +2 -0
  211. 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