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
Undefined/config.py ADDED
@@ -0,0 +1,371 @@
1
+ """配置加载模块"""
2
+
3
+ import json
4
+ import logging
5
+ import os
6
+ from dataclasses import dataclass
7
+ from pathlib import Path
8
+ from typing import Optional
9
+
10
+ from dotenv import load_dotenv
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+ # 本地配置文件路径
15
+ LOCAL_CONFIG_PATH = Path("config.local.json")
16
+
17
+
18
+ @dataclass
19
+ class ChatModelConfig:
20
+ """对话模型配置"""
21
+
22
+ api_url: str
23
+ api_key: str
24
+ model_name: str
25
+ max_tokens: int
26
+ thinking_enabled: bool = False # 是否启用 thinking
27
+ thinking_budget_tokens: int = 20000 # 思维预算 token 数量
28
+
29
+
30
+ @dataclass
31
+ class VisionModelConfig:
32
+ """视觉模型配置"""
33
+
34
+ api_url: str
35
+ api_key: str
36
+ model_name: str
37
+ thinking_enabled: bool = False # 是否启用 thinking
38
+ thinking_budget_tokens: int = 20000 # 思维预算 token 数量
39
+
40
+
41
+ @dataclass
42
+ class SecurityModelConfig:
43
+ """安全模型配置(用于防注入检测和注入后的回复生成)"""
44
+
45
+ api_url: str
46
+ api_key: str
47
+ model_name: str
48
+ max_tokens: int
49
+ thinking_enabled: bool = False # 是否启用 thinking
50
+ thinking_budget_tokens: int = 0 # 思维预算 token 数量
51
+
52
+
53
+ @dataclass
54
+ class AgentModelConfig:
55
+ """Agent 模型配置(用于执行 agents)"""
56
+
57
+ api_url: str
58
+ api_key: str
59
+ model_name: str
60
+ max_tokens: int = 4096
61
+ thinking_enabled: bool = False # 是否启用 thinking
62
+ thinking_budget_tokens: int = 0 # 思维预算 token 数量
63
+
64
+
65
+ def load_local_admins() -> list[int]:
66
+ """从本地配置文件加载动态管理员列表"""
67
+ if not LOCAL_CONFIG_PATH.exists():
68
+ return []
69
+
70
+ try:
71
+ with open(LOCAL_CONFIG_PATH, "r", encoding="utf-8") as f:
72
+ data = json.load(f)
73
+ admin_qqs: list[int] = data.get("admin_qqs", [])
74
+ return admin_qqs
75
+ except Exception as e:
76
+ logger.warning(f"读取本地配置失败: {e}")
77
+ return []
78
+
79
+
80
+ def save_local_admins(admin_qqs: list[int]) -> None:
81
+ """保存动态管理员列表到本地配置文件"""
82
+ try:
83
+ data: dict[str, list[int]] = {}
84
+ if LOCAL_CONFIG_PATH.exists():
85
+ with open(LOCAL_CONFIG_PATH, "r", encoding="utf-8") as f:
86
+ data = json.load(f)
87
+
88
+ data["admin_qqs"] = admin_qqs
89
+
90
+ with open(LOCAL_CONFIG_PATH, "w", encoding="utf-8") as f:
91
+ json.dump(data, f, ensure_ascii=False, indent=2)
92
+
93
+ logger.info(f"已保存管理员列表到 {LOCAL_CONFIG_PATH}")
94
+ except Exception as e:
95
+ logger.error(f"保存本地配置失败: {e}")
96
+ raise
97
+
98
+
99
+ @dataclass
100
+ class Config:
101
+ """应用配置"""
102
+
103
+ bot_qq: int
104
+ superadmin_qq: int # 超级管理员(唯一)
105
+ admin_qqs: list[int] # 管理员列表(来自 .env + 本地配置)
106
+ forward_proxy_qq: int | None # 音频转发代理QQ号(可选,用于群聊音频转发)
107
+ onebot_ws_url: str
108
+ onebot_token: str
109
+ chat_model: ChatModelConfig
110
+ vision_model: VisionModelConfig
111
+ security_model: SecurityModelConfig # 安全模型(防注入检测和回复生成)
112
+ agent_model: AgentModelConfig # Agent 模型(用于执行各种 Agent)
113
+ log_file_path: str
114
+ log_max_size: int
115
+ log_backup_count: int
116
+
117
+ @classmethod
118
+ def load(cls) -> "Config":
119
+ """从环境变量和本地配置加载配置"""
120
+ load_dotenv()
121
+
122
+ # 验证必需的环境变量
123
+ required_vars = [
124
+ "BOT_QQ",
125
+ "SUPERADMIN_QQ",
126
+ "ONEBOT_WS_URL",
127
+ "CHAT_MODEL_API_URL",
128
+ "CHAT_MODEL_API_KEY",
129
+ "CHAT_MODEL_NAME",
130
+ "CHAT_MODEL_MAX_TOKENS",
131
+ "VISION_MODEL_API_URL",
132
+ "VISION_MODEL_API_KEY",
133
+ "VISION_MODEL_NAME",
134
+ "AGENT_MODEL_API_URL",
135
+ "AGENT_MODEL_API_KEY",
136
+ "AGENT_MODEL_NAME",
137
+ ]
138
+
139
+ missing = [var for var in required_vars if not os.getenv(var)]
140
+ if missing:
141
+ raise ValueError(f"缺少必需的环境变量: {', '.join(missing)}")
142
+
143
+ chat_model = ChatModelConfig(
144
+ api_url=os.getenv("CHAT_MODEL_API_URL", ""),
145
+ api_key=os.getenv("CHAT_MODEL_API_KEY", ""),
146
+ model_name=os.getenv("CHAT_MODEL_NAME", ""),
147
+ max_tokens=int(os.getenv("CHAT_MODEL_MAX_TOKENS", "8000")),
148
+ thinking_enabled=os.getenv("CHAT_MODEL_THINKING_ENABLED", "false").lower()
149
+ == "true",
150
+ thinking_budget_tokens=int(
151
+ os.getenv("CHAT_MODEL_THINKING_BUDGET_TOKENS", "20000")
152
+ ),
153
+ )
154
+
155
+ vision_model = VisionModelConfig(
156
+ api_url=os.getenv("VISION_MODEL_API_URL", ""),
157
+ api_key=os.getenv("VISION_MODEL_API_KEY", ""),
158
+ model_name=os.getenv("VISION_MODEL_NAME", ""),
159
+ thinking_enabled=os.getenv("VISION_MODEL_THINKING_ENABLED", "false").lower()
160
+ == "true",
161
+ thinking_budget_tokens=int(
162
+ os.getenv("VISION_MODEL_THINKING_BUDGET_TOKENS", "20000")
163
+ ),
164
+ )
165
+
166
+ # 安全模型配置(防注入检测和回复生成)
167
+ # 如果没有配置,则使用对话模型作为后备
168
+ security_model_api_url = os.getenv("SECURITY_MODEL_API_URL", "")
169
+ security_model_api_key = os.getenv("SECURITY_MODEL_API_KEY", "")
170
+ security_model_name = os.getenv("SECURITY_MODEL_NAME", "")
171
+
172
+ if security_model_api_url and security_model_api_key and security_model_name:
173
+ # 使用独立的安全模型配置
174
+ security_model = SecurityModelConfig(
175
+ api_url=security_model_api_url,
176
+ api_key=security_model_api_key,
177
+ model_name=security_model_name,
178
+ max_tokens=int(os.getenv("SECURITY_MODEL_MAX_TOKENS", "100")),
179
+ thinking_enabled=os.getenv(
180
+ "SECURITY_MODEL_THINKING_ENABLED", "false"
181
+ ).lower()
182
+ == "true",
183
+ thinking_budget_tokens=int(
184
+ os.getenv("SECURITY_MODEL_THINKING_BUDGET_TOKENS", "0")
185
+ ),
186
+ )
187
+ else:
188
+ # 如果没有配置,则使用对话模型作为后备
189
+ logger.warning("未配置安全模型,将使用对话模型作为后备")
190
+ security_model = SecurityModelConfig(
191
+ api_url=chat_model.api_url,
192
+ api_key=chat_model.api_key,
193
+ model_name=chat_model.model_name,
194
+ max_tokens=chat_model.max_tokens,
195
+ thinking_enabled=False,
196
+ thinking_budget_tokens=0,
197
+ )
198
+
199
+ # Agent 模型配置
200
+ agent_model = AgentModelConfig(
201
+ api_url=os.getenv("AGENT_MODEL_API_URL", ""),
202
+ api_key=os.getenv("AGENT_MODEL_API_KEY", ""),
203
+ model_name=os.getenv("AGENT_MODEL_NAME", ""),
204
+ max_tokens=int(os.getenv("AGENT_MODEL_MAX_TOKENS", "4096")),
205
+ thinking_enabled=os.getenv("AGENT_MODEL_THINKING_ENABLED", "false").lower()
206
+ == "true",
207
+ thinking_budget_tokens=int(
208
+ os.getenv("AGENT_MODEL_THINKING_BUDGET_TOKENS", "0")
209
+ ),
210
+ )
211
+
212
+ # 解析超级管理员
213
+ superadmin_qq = int(os.getenv("SUPERADMIN_QQ", "0"))
214
+
215
+ # 解析 .env 中的管理员列表
216
+ admin_qq_str = os.getenv("ADMIN_QQ", "")
217
+ env_admins: list[int] = []
218
+ if admin_qq_str:
219
+ try:
220
+ env_admins = [
221
+ int(qq.strip()) for qq in admin_qq_str.split(",") if qq.strip()
222
+ ]
223
+ except ValueError:
224
+ raise ValueError("ADMIN_QQ 格式错误,应为逗号分隔的数字")
225
+
226
+ # 合并本地配置的管理员
227
+ local_admins = load_local_admins()
228
+ all_admins = list(set(env_admins + local_admins))
229
+
230
+ # 确保超级管理员也在管理员列表中
231
+ if superadmin_qq and superadmin_qq not in all_admins:
232
+ all_admins.append(superadmin_qq)
233
+
234
+ # 日志配置
235
+ log_file_path = os.getenv("LOG_FILE_PATH", "logs/bot.log")
236
+ log_max_size = (
237
+ int(os.getenv("LOG_MAX_SIZE_MB", "10")) * 1024 * 1024
238
+ ) # 转换为字节
239
+ log_backup_count = int(os.getenv("LOG_BACKUP_COUNT", "5"))
240
+
241
+ # 音频转发代理QQ号(可选)
242
+ forward_proxy_qq_str = os.getenv("FORWARD_PROXY_QQ")
243
+ forward_proxy_qq = None
244
+ if forward_proxy_qq_str and forward_proxy_qq_str.strip():
245
+ try:
246
+ forward_proxy_qq = int(forward_proxy_qq_str.strip())
247
+ except ValueError:
248
+ logger.warning(f"FORWARD_PROXY_QQ 格式错误: {forward_proxy_qq_str}")
249
+
250
+ return cls(
251
+ bot_qq=int(os.getenv("BOT_QQ", "0")),
252
+ superadmin_qq=superadmin_qq,
253
+ admin_qqs=all_admins,
254
+ forward_proxy_qq=forward_proxy_qq,
255
+ onebot_ws_url=os.getenv("ONEBOT_WS_URL", ""),
256
+ onebot_token=os.getenv("ONEBOT_TOKEN", ""),
257
+ chat_model=chat_model,
258
+ vision_model=vision_model,
259
+ security_model=security_model,
260
+ agent_model=agent_model,
261
+ log_file_path=log_file_path,
262
+ log_max_size=log_max_size,
263
+ log_backup_count=log_backup_count,
264
+ )
265
+
266
+ def reload(self) -> None:
267
+ """热重载配置(重新加载管理员列表等动态配置)"""
268
+ # 重新加载本地管理员
269
+ local_admins = load_local_admins()
270
+
271
+ # 重新读取 .env 中的管理员
272
+ load_dotenv(override=True)
273
+ admin_qq_str = os.getenv("ADMIN_QQ", "")
274
+ env_admins: list[int] = []
275
+ if admin_qq_str:
276
+ try:
277
+ env_admins = [
278
+ int(qq.strip()) for qq in admin_qq_str.split(",") if qq.strip()
279
+ ]
280
+ except ValueError:
281
+ logger.warning("ADMIN_QQ 格式错误")
282
+
283
+ # 合并管理员列表
284
+ all_admins = list(set(env_admins + local_admins))
285
+ if self.superadmin_qq and self.superadmin_qq not in all_admins:
286
+ all_admins.append(self.superadmin_qq)
287
+
288
+ self.admin_qqs = all_admins
289
+ logger.info(f"配置已重载,管理员: {self.admin_qqs}")
290
+
291
+ def add_admin(self, qq: int) -> bool:
292
+ """添加管理员(保存到本地配置)
293
+
294
+ 参数:
295
+ qq: 要添加的 QQ 号
296
+
297
+ 返回:
298
+ 是否添加成功(已存在返回 False)
299
+ """
300
+ if qq in self.admin_qqs:
301
+ return False
302
+
303
+ self.admin_qqs.append(qq)
304
+
305
+ # 获取当前本地管理员并添加新的
306
+ local_admins = load_local_admins()
307
+ if qq not in local_admins:
308
+ local_admins.append(qq)
309
+ save_local_admins(local_admins)
310
+
311
+ return True
312
+
313
+ def remove_admin(self, qq: int) -> bool:
314
+ """移除管理员(从本地配置中移除)
315
+
316
+ 参数:
317
+ qq: 要移除的 QQ 号
318
+
319
+ 返回:
320
+ 是否移除成功
321
+ """
322
+ # 不能移除超级管理员
323
+ if qq == self.superadmin_qq:
324
+ return False
325
+
326
+ if qq not in self.admin_qqs:
327
+ return False
328
+
329
+ self.admin_qqs.remove(qq)
330
+
331
+ # 从本地配置中移除
332
+ local_admins = load_local_admins()
333
+ if qq in local_admins:
334
+ local_admins.remove(qq)
335
+ save_local_admins(local_admins)
336
+
337
+ return True
338
+
339
+ def is_superadmin(self, qq: int) -> bool:
340
+ """检查是否为超级管理员
341
+
342
+ 参数:
343
+ qq: QQ 号
344
+
345
+ 返回:
346
+ 是否为超级管理员
347
+ """
348
+ return qq == self.superadmin_qq
349
+
350
+ def is_admin(self, qq: int) -> bool:
351
+ """检查是否为管理员
352
+
353
+ 参数:
354
+ qq: QQ 号
355
+
356
+ 返回:
357
+ 是否为管理员
358
+ """
359
+ return qq in self.admin_qqs
360
+
361
+
362
+ # 全局配置实例
363
+ _config: Optional[Config] = None
364
+
365
+
366
+ def get_config() -> Config:
367
+ """获取配置实例(单例模式)"""
368
+ global _config
369
+ if _config is None:
370
+ _config = Config.load()
371
+ return _config
@@ -0,0 +1,48 @@
1
+ """End 摘要持久化存储模块"""
2
+
3
+ import json
4
+ import logging
5
+ from pathlib import Path
6
+ from typing import List
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+ # End 摘要数据存储路径
11
+ END_SUMMARIES_FILE_PATH = Path("data/end_summaries.json")
12
+
13
+
14
+ class EndSummaryStorage:
15
+ """End 摘要存储管理器"""
16
+
17
+ def __init__(self) -> None:
18
+ """初始化存储"""
19
+ pass
20
+
21
+ def load(self) -> List[str]:
22
+ """从文件加载所有摘要"""
23
+ if not END_SUMMARIES_FILE_PATH.exists():
24
+ return []
25
+
26
+ try:
27
+ with open(END_SUMMARIES_FILE_PATH, "r", encoding="utf-8") as f:
28
+ data = json.load(f)
29
+ if isinstance(data, list):
30
+ return data
31
+ else:
32
+ logger.warning(
33
+ f"End 摘要数据格式异常,期望 list,实际得到 {type(data)}"
34
+ )
35
+ return []
36
+ except Exception as e:
37
+ logger.error(f"加载 End 摘要数据失败: {e}")
38
+ return []
39
+
40
+ def save(self, summaries: List[str]) -> None:
41
+ """保存所有摘要到文件"""
42
+ try:
43
+ END_SUMMARIES_FILE_PATH.parent.mkdir(parents=True, exist_ok=True)
44
+ with open(END_SUMMARIES_FILE_PATH, "w", encoding="utf-8") as f:
45
+ json.dump(summaries, f, ensure_ascii=False, indent=2)
46
+ logger.debug(f"已保存 {len(summaries)} 条 End 摘要")
47
+ except Exception as e:
48
+ logger.error(f"保存 End 摘要数据失败: {e}")
Undefined/faq.py ADDED
@@ -0,0 +1,244 @@
1
+ """FAQ 存储管理"""
2
+
3
+ import json
4
+ import logging
5
+ from dataclasses import dataclass, asdict
6
+ from datetime import datetime
7
+ from pathlib import Path
8
+ from typing import Optional
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ @dataclass
14
+ class FAQ:
15
+ """FAQ 条目"""
16
+
17
+ id: str
18
+ group_id: int
19
+ target_qq: int
20
+ start_time: str
21
+ end_time: str
22
+ created_at: str
23
+ title: str
24
+ content: str
25
+
26
+ def to_dict(self) -> dict[str, str | int]:
27
+ """转换为字典"""
28
+ return asdict(self)
29
+
30
+ @classmethod
31
+ def from_dict(cls, data: dict[str, str | int]) -> "FAQ":
32
+ """从字典创建 FAQ"""
33
+ return cls(**data) # type: ignore
34
+
35
+
36
+ class FAQStorage:
37
+ """FAQ 本地存储管理"""
38
+
39
+ def __init__(self, base_dir: str = "data/faq") -> None:
40
+ self.base_dir = Path(base_dir)
41
+ self.base_dir.mkdir(parents=True, exist_ok=True)
42
+
43
+ def _get_group_dir(self, group_id: int) -> Path:
44
+ """获取群组的 FAQ 存储目录
45
+
46
+ 参数:
47
+ group_id: 群号
48
+
49
+ 返回:
50
+ 群组 FAQ 目录路径
51
+ """
52
+ group_dir = self.base_dir / str(group_id)
53
+ group_dir.mkdir(parents=True, exist_ok=True)
54
+ return group_dir
55
+
56
+ def _generate_id(self, group_id: int) -> str:
57
+ """生成新的 FAQ ID
58
+
59
+ 参数:
60
+ group_id: 群号
61
+
62
+ 返回:
63
+ 新的 FAQ ID
64
+ """
65
+ group_dir = self._get_group_dir(group_id)
66
+ existing = list(group_dir.glob("*.json"))
67
+
68
+ # 使用时间戳 + 序号
69
+ timestamp = datetime.now().strftime("%Y%m%d")
70
+ count = sum(1 for f in existing if f.stem.startswith(timestamp))
71
+ return f"{timestamp}-{count + 1:03d}"
72
+
73
+ def save(self, faq: FAQ) -> str:
74
+ """保存 FAQ
75
+
76
+ 参数:
77
+ faq: FAQ 条目
78
+
79
+ 返回:
80
+ FAQ ID
81
+ """
82
+ group_dir = self._get_group_dir(faq.group_id)
83
+ file_path = group_dir / f"{faq.id}.json"
84
+
85
+ with open(file_path, "w", encoding="utf-8") as f:
86
+ json.dump(faq.to_dict(), f, ensure_ascii=False, indent=2)
87
+
88
+ logger.info(f"FAQ 已保存: {file_path}")
89
+ return faq.id
90
+
91
+ def create(
92
+ self,
93
+ group_id: int,
94
+ target_qq: int,
95
+ start_time: str,
96
+ end_time: str,
97
+ title: str,
98
+ content: str,
99
+ ) -> FAQ:
100
+ """创建新的 FAQ 条目
101
+
102
+ 参数:
103
+ group_id: 群号
104
+ target_qq: 目标 QQ 号
105
+ start_time: 开始时间
106
+ end_time: 结束时间
107
+ title: 标题
108
+ content: 内容
109
+
110
+ 返回:
111
+ 创建的 FAQ 对象
112
+ """
113
+ faq = FAQ(
114
+ id=self._generate_id(group_id),
115
+ group_id=group_id,
116
+ target_qq=target_qq,
117
+ start_time=start_time,
118
+ end_time=end_time,
119
+ created_at=datetime.now().isoformat(),
120
+ title=title,
121
+ content=content,
122
+ )
123
+ self.save(faq)
124
+ return faq
125
+
126
+ def get(self, group_id: int, faq_id: str) -> Optional[FAQ]:
127
+ """获取指定 FAQ
128
+
129
+ 参数:
130
+ group_id: 群号
131
+ faq_id: FAQ ID
132
+
133
+ 返回:
134
+ FAQ 对象,不存在则返回 None
135
+ """
136
+ group_dir = self._get_group_dir(group_id)
137
+ file_path = group_dir / f"{faq_id}.json"
138
+
139
+ if not file_path.exists():
140
+ return None
141
+
142
+ try:
143
+ with open(file_path, "r", encoding="utf-8") as f:
144
+ data = json.load(f)
145
+ return FAQ.from_dict(data)
146
+ except Exception as e:
147
+ logger.error(f"读取 FAQ 失败: {e}")
148
+ return None
149
+
150
+ def list_all(self, group_id: int) -> list[FAQ]:
151
+ """列出群组的所有 FAQ
152
+
153
+ 参数:
154
+ group_id: 群号
155
+
156
+ 返回:
157
+ FAQ 列表
158
+ """
159
+ group_dir = self._get_group_dir(group_id)
160
+ faqs: list[FAQ] = []
161
+
162
+ for file_path in sorted(group_dir.glob("*.json"), reverse=True):
163
+ try:
164
+ with open(file_path, "r", encoding="utf-8") as f:
165
+ data = json.load(f)
166
+ faqs.append(FAQ.from_dict(data))
167
+ except Exception as e:
168
+ logger.error(f"读取 FAQ 失败 {file_path}: {e}")
169
+
170
+ return faqs
171
+
172
+ def search(self, group_id: int, keyword: str) -> list[FAQ]:
173
+ """搜索 FAQ
174
+
175
+ 根据关键词在标题和内容中搜索匹配的 FAQ
176
+
177
+ 参数:
178
+ group_id: 群号
179
+ keyword: 搜索关键词
180
+
181
+ 返回:
182
+ 匹配的 FAQ 列表
183
+ """
184
+ keyword_lower = keyword.lower()
185
+ all_faqs = self.list_all(group_id)
186
+
187
+ matched: list[FAQ] = []
188
+ for faq in all_faqs:
189
+ # 在标题和内容中搜索
190
+ if (
191
+ keyword_lower in faq.title.lower()
192
+ or keyword_lower in faq.content.lower()
193
+ ):
194
+ matched.append(faq)
195
+
196
+ return matched
197
+
198
+ def delete(self, group_id: int, faq_id: str) -> bool:
199
+ """删除 FAQ
200
+
201
+ 参数:
202
+ group_id: 群号
203
+ faq_id: FAQ ID
204
+
205
+ 返回:
206
+ 是否成功删除
207
+ """
208
+ group_dir = self._get_group_dir(group_id)
209
+ file_path = group_dir / f"{faq_id}.json"
210
+
211
+ if file_path.exists():
212
+ file_path.unlink()
213
+ logger.info(f"FAQ 已删除: {file_path}")
214
+ return True
215
+ return False
216
+
217
+
218
+ def extract_faq_title(content: str) -> str:
219
+ """从分析内容中提取 FAQ 标题
220
+
221
+ 参数:
222
+ content: 分析内容
223
+
224
+ 返回:
225
+ 提取的标题
226
+ """
227
+ # 尝试从 FAQ 条目中提取问题
228
+ for line in content.split("\n"):
229
+ line = line.strip()
230
+ if line.startswith("**问题**:") or line.startswith("**问题**:"):
231
+ title = line.split(":", 1)[-1].split(":", 1)[-1].strip()
232
+ return title[:100] # 限制长度
233
+
234
+ # 尝试从 Bug 问题描述中提取
235
+ in_bug_section = False
236
+ for line in content.split("\n"):
237
+ line = line.strip()
238
+ if "Bug 问题描述" in line:
239
+ in_bug_section = True
240
+ continue
241
+ if in_bug_section and line and not line.startswith("#"):
242
+ return line[:100]
243
+
244
+ return "未命名问题"