AstrBot 3.5.6__py3-none-any.whl → 4.7.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 (288) hide show
  1. astrbot/api/__init__.py +16 -4
  2. astrbot/api/all.py +2 -1
  3. astrbot/api/event/__init__.py +5 -6
  4. astrbot/api/event/filter/__init__.py +37 -34
  5. astrbot/api/platform/__init__.py +7 -8
  6. astrbot/api/provider/__init__.py +8 -7
  7. astrbot/api/star/__init__.py +3 -4
  8. astrbot/api/util/__init__.py +2 -2
  9. astrbot/cli/__init__.py +1 -0
  10. astrbot/cli/__main__.py +18 -197
  11. astrbot/cli/commands/__init__.py +6 -0
  12. astrbot/cli/commands/cmd_conf.py +209 -0
  13. astrbot/cli/commands/cmd_init.py +56 -0
  14. astrbot/cli/commands/cmd_plug.py +245 -0
  15. astrbot/cli/commands/cmd_run.py +62 -0
  16. astrbot/cli/utils/__init__.py +18 -0
  17. astrbot/cli/utils/basic.py +76 -0
  18. astrbot/cli/utils/plugin.py +246 -0
  19. astrbot/cli/utils/version_comparator.py +90 -0
  20. astrbot/core/__init__.py +17 -19
  21. astrbot/core/agent/agent.py +14 -0
  22. astrbot/core/agent/handoff.py +38 -0
  23. astrbot/core/agent/hooks.py +30 -0
  24. astrbot/core/agent/mcp_client.py +385 -0
  25. astrbot/core/agent/message.py +175 -0
  26. astrbot/core/agent/response.py +14 -0
  27. astrbot/core/agent/run_context.py +22 -0
  28. astrbot/core/agent/runners/__init__.py +3 -0
  29. astrbot/core/agent/runners/base.py +65 -0
  30. astrbot/core/agent/runners/coze/coze_agent_runner.py +367 -0
  31. astrbot/core/agent/runners/coze/coze_api_client.py +324 -0
  32. astrbot/core/agent/runners/dashscope/dashscope_agent_runner.py +403 -0
  33. astrbot/core/agent/runners/dify/dify_agent_runner.py +336 -0
  34. astrbot/core/agent/runners/dify/dify_api_client.py +195 -0
  35. astrbot/core/agent/runners/tool_loop_agent_runner.py +400 -0
  36. astrbot/core/agent/tool.py +285 -0
  37. astrbot/core/agent/tool_executor.py +17 -0
  38. astrbot/core/astr_agent_context.py +19 -0
  39. astrbot/core/astr_agent_hooks.py +36 -0
  40. astrbot/core/astr_agent_run_util.py +80 -0
  41. astrbot/core/astr_agent_tool_exec.py +246 -0
  42. astrbot/core/astrbot_config_mgr.py +275 -0
  43. astrbot/core/config/__init__.py +2 -2
  44. astrbot/core/config/astrbot_config.py +60 -20
  45. astrbot/core/config/default.py +1972 -453
  46. astrbot/core/config/i18n_utils.py +110 -0
  47. astrbot/core/conversation_mgr.py +285 -75
  48. astrbot/core/core_lifecycle.py +167 -62
  49. astrbot/core/db/__init__.py +305 -102
  50. astrbot/core/db/migration/helper.py +69 -0
  51. astrbot/core/db/migration/migra_3_to_4.py +357 -0
  52. astrbot/core/db/migration/migra_45_to_46.py +44 -0
  53. astrbot/core/db/migration/migra_webchat_session.py +131 -0
  54. astrbot/core/db/migration/shared_preferences_v3.py +48 -0
  55. astrbot/core/db/migration/sqlite_v3.py +497 -0
  56. astrbot/core/db/po.py +259 -55
  57. astrbot/core/db/sqlite.py +773 -528
  58. astrbot/core/db/vec_db/base.py +73 -0
  59. astrbot/core/db/vec_db/faiss_impl/__init__.py +3 -0
  60. astrbot/core/db/vec_db/faiss_impl/document_storage.py +392 -0
  61. astrbot/core/db/vec_db/faiss_impl/embedding_storage.py +93 -0
  62. astrbot/core/db/vec_db/faiss_impl/sqlite_init.sql +17 -0
  63. astrbot/core/db/vec_db/faiss_impl/vec_db.py +204 -0
  64. astrbot/core/event_bus.py +26 -22
  65. astrbot/core/exceptions.py +9 -0
  66. astrbot/core/file_token_service.py +98 -0
  67. astrbot/core/initial_loader.py +19 -10
  68. astrbot/core/knowledge_base/chunking/__init__.py +9 -0
  69. astrbot/core/knowledge_base/chunking/base.py +25 -0
  70. astrbot/core/knowledge_base/chunking/fixed_size.py +59 -0
  71. astrbot/core/knowledge_base/chunking/recursive.py +161 -0
  72. astrbot/core/knowledge_base/kb_db_sqlite.py +301 -0
  73. astrbot/core/knowledge_base/kb_helper.py +642 -0
  74. astrbot/core/knowledge_base/kb_mgr.py +330 -0
  75. astrbot/core/knowledge_base/models.py +120 -0
  76. astrbot/core/knowledge_base/parsers/__init__.py +13 -0
  77. astrbot/core/knowledge_base/parsers/base.py +51 -0
  78. astrbot/core/knowledge_base/parsers/markitdown_parser.py +26 -0
  79. astrbot/core/knowledge_base/parsers/pdf_parser.py +101 -0
  80. astrbot/core/knowledge_base/parsers/text_parser.py +42 -0
  81. astrbot/core/knowledge_base/parsers/url_parser.py +103 -0
  82. astrbot/core/knowledge_base/parsers/util.py +13 -0
  83. astrbot/core/knowledge_base/prompts.py +65 -0
  84. astrbot/core/knowledge_base/retrieval/__init__.py +14 -0
  85. astrbot/core/knowledge_base/retrieval/hit_stopwords.txt +767 -0
  86. astrbot/core/knowledge_base/retrieval/manager.py +276 -0
  87. astrbot/core/knowledge_base/retrieval/rank_fusion.py +142 -0
  88. astrbot/core/knowledge_base/retrieval/sparse_retriever.py +136 -0
  89. astrbot/core/log.py +21 -15
  90. astrbot/core/message/components.py +413 -287
  91. astrbot/core/message/message_event_result.py +35 -24
  92. astrbot/core/persona_mgr.py +192 -0
  93. astrbot/core/pipeline/__init__.py +14 -14
  94. astrbot/core/pipeline/content_safety_check/stage.py +13 -9
  95. astrbot/core/pipeline/content_safety_check/strategies/__init__.py +1 -2
  96. astrbot/core/pipeline/content_safety_check/strategies/baidu_aip.py +13 -14
  97. astrbot/core/pipeline/content_safety_check/strategies/keywords.py +2 -1
  98. astrbot/core/pipeline/content_safety_check/strategies/strategy.py +6 -6
  99. astrbot/core/pipeline/context.py +7 -1
  100. astrbot/core/pipeline/context_utils.py +107 -0
  101. astrbot/core/pipeline/preprocess_stage/stage.py +63 -36
  102. astrbot/core/pipeline/process_stage/method/agent_request.py +48 -0
  103. astrbot/core/pipeline/process_stage/method/agent_sub_stages/internal.py +464 -0
  104. astrbot/core/pipeline/process_stage/method/agent_sub_stages/third_party.py +202 -0
  105. astrbot/core/pipeline/process_stage/method/star_request.py +26 -32
  106. astrbot/core/pipeline/process_stage/stage.py +21 -15
  107. astrbot/core/pipeline/process_stage/utils.py +125 -0
  108. astrbot/core/pipeline/rate_limit_check/stage.py +34 -36
  109. astrbot/core/pipeline/respond/stage.py +142 -101
  110. astrbot/core/pipeline/result_decorate/stage.py +124 -57
  111. astrbot/core/pipeline/scheduler.py +21 -16
  112. astrbot/core/pipeline/session_status_check/stage.py +37 -0
  113. astrbot/core/pipeline/stage.py +11 -76
  114. astrbot/core/pipeline/waking_check/stage.py +69 -33
  115. astrbot/core/pipeline/whitelist_check/stage.py +10 -7
  116. astrbot/core/platform/__init__.py +6 -6
  117. astrbot/core/platform/astr_message_event.py +107 -129
  118. astrbot/core/platform/astrbot_message.py +32 -12
  119. astrbot/core/platform/manager.py +62 -18
  120. astrbot/core/platform/message_session.py +30 -0
  121. astrbot/core/platform/platform.py +16 -24
  122. astrbot/core/platform/platform_metadata.py +9 -4
  123. astrbot/core/platform/register.py +12 -7
  124. astrbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py +136 -60
  125. astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py +126 -46
  126. astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py +63 -31
  127. astrbot/core/platform/sources/dingtalk/dingtalk_event.py +30 -26
  128. astrbot/core/platform/sources/discord/client.py +129 -0
  129. astrbot/core/platform/sources/discord/components.py +139 -0
  130. astrbot/core/platform/sources/discord/discord_platform_adapter.py +473 -0
  131. astrbot/core/platform/sources/discord/discord_platform_event.py +313 -0
  132. astrbot/core/platform/sources/lark/lark_adapter.py +27 -18
  133. astrbot/core/platform/sources/lark/lark_event.py +39 -13
  134. astrbot/core/platform/sources/misskey/misskey_adapter.py +770 -0
  135. astrbot/core/platform/sources/misskey/misskey_api.py +964 -0
  136. astrbot/core/platform/sources/misskey/misskey_event.py +163 -0
  137. astrbot/core/platform/sources/misskey/misskey_utils.py +550 -0
  138. astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py +149 -33
  139. astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py +41 -26
  140. astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_adapter.py +36 -17
  141. astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_event.py +3 -1
  142. astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_server.py +14 -8
  143. astrbot/core/platform/sources/satori/satori_adapter.py +792 -0
  144. astrbot/core/platform/sources/satori/satori_event.py +432 -0
  145. astrbot/core/platform/sources/slack/client.py +164 -0
  146. astrbot/core/platform/sources/slack/slack_adapter.py +416 -0
  147. astrbot/core/platform/sources/slack/slack_event.py +253 -0
  148. astrbot/core/platform/sources/telegram/tg_adapter.py +100 -43
  149. astrbot/core/platform/sources/telegram/tg_event.py +136 -36
  150. astrbot/core/platform/sources/webchat/webchat_adapter.py +72 -22
  151. astrbot/core/platform/sources/webchat/webchat_event.py +46 -22
  152. astrbot/core/platform/sources/webchat/webchat_queue_mgr.py +35 -0
  153. astrbot/core/platform/sources/wechatpadpro/wechatpadpro_adapter.py +926 -0
  154. astrbot/core/platform/sources/wechatpadpro/wechatpadpro_message_event.py +178 -0
  155. astrbot/core/platform/sources/wechatpadpro/xml_data_parser.py +159 -0
  156. astrbot/core/platform/sources/wecom/wecom_adapter.py +169 -27
  157. astrbot/core/platform/sources/wecom/wecom_event.py +162 -77
  158. astrbot/core/platform/sources/wecom/wecom_kf.py +279 -0
  159. astrbot/core/platform/sources/wecom/wecom_kf_message.py +196 -0
  160. astrbot/core/platform/sources/wecom_ai_bot/WXBizJsonMsgCrypt.py +297 -0
  161. astrbot/core/platform/sources/wecom_ai_bot/__init__.py +15 -0
  162. astrbot/core/platform/sources/wecom_ai_bot/ierror.py +19 -0
  163. astrbot/core/platform/sources/wecom_ai_bot/wecomai_adapter.py +472 -0
  164. astrbot/core/platform/sources/wecom_ai_bot/wecomai_api.py +417 -0
  165. astrbot/core/platform/sources/wecom_ai_bot/wecomai_event.py +152 -0
  166. astrbot/core/platform/sources/wecom_ai_bot/wecomai_queue_mgr.py +153 -0
  167. astrbot/core/platform/sources/wecom_ai_bot/wecomai_server.py +168 -0
  168. astrbot/core/platform/sources/wecom_ai_bot/wecomai_utils.py +209 -0
  169. astrbot/core/platform/sources/weixin_official_account/weixin_offacc_adapter.py +306 -0
  170. astrbot/core/platform/sources/weixin_official_account/weixin_offacc_event.py +186 -0
  171. astrbot/core/platform_message_history_mgr.py +49 -0
  172. astrbot/core/provider/__init__.py +2 -3
  173. astrbot/core/provider/entites.py +8 -8
  174. astrbot/core/provider/entities.py +154 -98
  175. astrbot/core/provider/func_tool_manager.py +446 -458
  176. astrbot/core/provider/manager.py +345 -207
  177. astrbot/core/provider/provider.py +188 -73
  178. astrbot/core/provider/register.py +9 -7
  179. astrbot/core/provider/sources/anthropic_source.py +295 -115
  180. astrbot/core/provider/sources/azure_tts_source.py +224 -0
  181. astrbot/core/provider/sources/bailian_rerank_source.py +236 -0
  182. astrbot/core/provider/sources/dashscope_tts.py +138 -14
  183. astrbot/core/provider/sources/edge_tts_source.py +24 -19
  184. astrbot/core/provider/sources/fishaudio_tts_api_source.py +58 -13
  185. astrbot/core/provider/sources/gemini_embedding_source.py +61 -0
  186. astrbot/core/provider/sources/gemini_source.py +310 -132
  187. astrbot/core/provider/sources/gemini_tts_source.py +81 -0
  188. astrbot/core/provider/sources/groq_source.py +15 -0
  189. astrbot/core/provider/sources/gsv_selfhosted_source.py +151 -0
  190. astrbot/core/provider/sources/gsvi_tts_source.py +14 -7
  191. astrbot/core/provider/sources/minimax_tts_api_source.py +159 -0
  192. astrbot/core/provider/sources/openai_embedding_source.py +40 -0
  193. astrbot/core/provider/sources/openai_source.py +241 -145
  194. astrbot/core/provider/sources/openai_tts_api_source.py +18 -7
  195. astrbot/core/provider/sources/sensevoice_selfhosted_source.py +13 -11
  196. astrbot/core/provider/sources/vllm_rerank_source.py +71 -0
  197. astrbot/core/provider/sources/volcengine_tts.py +115 -0
  198. astrbot/core/provider/sources/whisper_api_source.py +18 -13
  199. astrbot/core/provider/sources/whisper_selfhosted_source.py +19 -12
  200. astrbot/core/provider/sources/xinference_rerank_source.py +116 -0
  201. astrbot/core/provider/sources/xinference_stt_provider.py +197 -0
  202. astrbot/core/provider/sources/zhipu_source.py +6 -73
  203. astrbot/core/star/__init__.py +43 -11
  204. astrbot/core/star/config.py +17 -18
  205. astrbot/core/star/context.py +362 -138
  206. astrbot/core/star/filter/__init__.py +4 -3
  207. astrbot/core/star/filter/command.py +111 -35
  208. astrbot/core/star/filter/command_group.py +46 -34
  209. astrbot/core/star/filter/custom_filter.py +6 -5
  210. astrbot/core/star/filter/event_message_type.py +4 -2
  211. astrbot/core/star/filter/permission.py +4 -2
  212. astrbot/core/star/filter/platform_adapter_type.py +45 -12
  213. astrbot/core/star/filter/regex.py +4 -2
  214. astrbot/core/star/register/__init__.py +19 -15
  215. astrbot/core/star/register/star.py +41 -13
  216. astrbot/core/star/register/star_handler.py +236 -86
  217. astrbot/core/star/session_llm_manager.py +280 -0
  218. astrbot/core/star/session_plugin_manager.py +170 -0
  219. astrbot/core/star/star.py +36 -43
  220. astrbot/core/star/star_handler.py +47 -85
  221. astrbot/core/star/star_manager.py +442 -260
  222. astrbot/core/star/star_tools.py +167 -45
  223. astrbot/core/star/updator.py +17 -20
  224. astrbot/core/umop_config_router.py +106 -0
  225. astrbot/core/updator.py +38 -13
  226. astrbot/core/utils/astrbot_path.py +39 -0
  227. astrbot/core/utils/command_parser.py +1 -1
  228. astrbot/core/utils/io.py +119 -60
  229. astrbot/core/utils/log_pipe.py +1 -1
  230. astrbot/core/utils/metrics.py +11 -10
  231. astrbot/core/utils/migra_helper.py +73 -0
  232. astrbot/core/utils/path_util.py +63 -62
  233. astrbot/core/utils/pip_installer.py +37 -15
  234. astrbot/core/utils/session_lock.py +29 -0
  235. astrbot/core/utils/session_waiter.py +19 -20
  236. astrbot/core/utils/shared_preferences.py +174 -34
  237. astrbot/core/utils/t2i/__init__.py +4 -1
  238. astrbot/core/utils/t2i/local_strategy.py +386 -238
  239. astrbot/core/utils/t2i/network_strategy.py +109 -49
  240. astrbot/core/utils/t2i/renderer.py +29 -14
  241. astrbot/core/utils/t2i/template/astrbot_powershell.html +184 -0
  242. astrbot/core/utils/t2i/template_manager.py +111 -0
  243. astrbot/core/utils/tencent_record_helper.py +115 -1
  244. astrbot/core/utils/version_comparator.py +10 -13
  245. astrbot/core/zip_updator.py +112 -65
  246. astrbot/dashboard/routes/__init__.py +20 -13
  247. astrbot/dashboard/routes/auth.py +20 -9
  248. astrbot/dashboard/routes/chat.py +297 -141
  249. astrbot/dashboard/routes/config.py +652 -55
  250. astrbot/dashboard/routes/conversation.py +107 -37
  251. astrbot/dashboard/routes/file.py +26 -0
  252. astrbot/dashboard/routes/knowledge_base.py +1244 -0
  253. astrbot/dashboard/routes/log.py +27 -2
  254. astrbot/dashboard/routes/persona.py +202 -0
  255. astrbot/dashboard/routes/plugin.py +197 -139
  256. astrbot/dashboard/routes/route.py +27 -7
  257. astrbot/dashboard/routes/session_management.py +354 -0
  258. astrbot/dashboard/routes/stat.py +85 -18
  259. astrbot/dashboard/routes/static_file.py +5 -2
  260. astrbot/dashboard/routes/t2i.py +233 -0
  261. astrbot/dashboard/routes/tools.py +184 -120
  262. astrbot/dashboard/routes/update.py +59 -36
  263. astrbot/dashboard/server.py +96 -36
  264. astrbot/dashboard/utils.py +165 -0
  265. astrbot-4.7.0.dist-info/METADATA +294 -0
  266. astrbot-4.7.0.dist-info/RECORD +274 -0
  267. {astrbot-3.5.6.dist-info → astrbot-4.7.0.dist-info}/WHEEL +1 -1
  268. astrbot/core/db/plugin/sqlite_impl.py +0 -112
  269. astrbot/core/db/sqlite_init.sql +0 -50
  270. astrbot/core/pipeline/platform_compatibility/stage.py +0 -56
  271. astrbot/core/pipeline/process_stage/method/llm_request.py +0 -606
  272. astrbot/core/platform/sources/gewechat/client.py +0 -806
  273. astrbot/core/platform/sources/gewechat/downloader.py +0 -55
  274. astrbot/core/platform/sources/gewechat/gewechat_event.py +0 -255
  275. astrbot/core/platform/sources/gewechat/gewechat_platform_adapter.py +0 -103
  276. astrbot/core/platform/sources/gewechat/xml_data_parser.py +0 -110
  277. astrbot/core/provider/sources/dashscope_source.py +0 -203
  278. astrbot/core/provider/sources/dify_source.py +0 -281
  279. astrbot/core/provider/sources/llmtuner_source.py +0 -132
  280. astrbot/core/rag/embedding/openai_source.py +0 -20
  281. astrbot/core/rag/knowledge_db_mgr.py +0 -94
  282. astrbot/core/rag/store/__init__.py +0 -9
  283. astrbot/core/rag/store/chroma_db.py +0 -42
  284. astrbot/core/utils/dify_api_client.py +0 -152
  285. astrbot-3.5.6.dist-info/METADATA +0 -249
  286. astrbot-3.5.6.dist-info/RECORD +0 -158
  287. {astrbot-3.5.6.dist-info → astrbot-4.7.0.dist-info}/entry_points.txt +0 -0
  288. {astrbot-3.5.6.dist-info → astrbot-4.7.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,357 @@
1
+ import datetime
2
+ import json
3
+
4
+ from sqlalchemy import text
5
+ from sqlalchemy.ext.asyncio import AsyncSession
6
+
7
+ from astrbot.api import logger, sp
8
+ from astrbot.core.config import AstrBotConfig
9
+ from astrbot.core.config.default import DB_PATH
10
+ from astrbot.core.db.po import ConversationV2, PlatformMessageHistory
11
+ from astrbot.core.platform.astr_message_event import MessageSesion
12
+
13
+ from .. import BaseDatabase
14
+ from .shared_preferences_v3 import sp as sp_v3
15
+ from .sqlite_v3 import SQLiteDatabase as SQLiteV3DatabaseV3
16
+
17
+ """
18
+ 1. 迁移旧的 webchat_conversation 表到新的 conversation 表。
19
+ 2. 迁移旧的 platform 到新的 platform_stats 表。
20
+ """
21
+
22
+
23
+ def get_platform_id(
24
+ platform_id_map: dict[str, dict[str, str]],
25
+ old_platform_name: str,
26
+ ) -> str:
27
+ return platform_id_map.get(
28
+ old_platform_name,
29
+ {"platform_id": old_platform_name, "platform_type": old_platform_name},
30
+ ).get("platform_id", old_platform_name)
31
+
32
+
33
+ def get_platform_type(
34
+ platform_id_map: dict[str, dict[str, str]],
35
+ old_platform_name: str,
36
+ ) -> str:
37
+ return platform_id_map.get(
38
+ old_platform_name,
39
+ {"platform_id": old_platform_name, "platform_type": old_platform_name},
40
+ ).get("platform_type", old_platform_name)
41
+
42
+
43
+ async def migration_conversation_table(
44
+ db_helper: BaseDatabase,
45
+ platform_id_map: dict[str, dict[str, str]],
46
+ ):
47
+ db_helper_v3 = SQLiteV3DatabaseV3(
48
+ db_path=DB_PATH.replace("data_v4.db", "data_v3.db"),
49
+ )
50
+ conversations, total_cnt = db_helper_v3.get_all_conversations(
51
+ page=1,
52
+ page_size=10000000,
53
+ )
54
+ logger.info(f"迁移 {total_cnt} 条旧的会话数据到新的表中...")
55
+
56
+ async with db_helper.get_db() as dbsession:
57
+ dbsession: AsyncSession
58
+ async with dbsession.begin():
59
+ for idx, conversation in enumerate(conversations):
60
+ if total_cnt > 0 and (idx + 1) % max(1, total_cnt // 10) == 0:
61
+ progress = int((idx + 1) / total_cnt * 100)
62
+ if progress % 10 == 0:
63
+ logger.info(f"进度: {progress}% ({idx + 1}/{total_cnt})")
64
+ try:
65
+ conv = db_helper_v3.get_conversation_by_user_id(
66
+ user_id=conversation.get("user_id", "unknown"),
67
+ cid=conversation.get("cid", "unknown"),
68
+ )
69
+ if not conv:
70
+ logger.info(
71
+ f"未找到该条旧会话对应的具体数据: {conversation}, 跳过。",
72
+ )
73
+ if ":" not in conv.user_id:
74
+ continue
75
+ session = MessageSesion.from_str(session_str=conv.user_id)
76
+ platform_id = get_platform_id(
77
+ platform_id_map,
78
+ session.platform_name,
79
+ )
80
+ session.platform_id = platform_id # 更新平台名称为新的 ID
81
+ conv_v2 = ConversationV2(
82
+ user_id=str(session),
83
+ content=json.loads(conv.history) if conv.history else [],
84
+ platform_id=platform_id,
85
+ title=conv.title,
86
+ persona_id=conv.persona_id,
87
+ conversation_id=conv.cid,
88
+ created_at=datetime.datetime.fromtimestamp(conv.created_at),
89
+ updated_at=datetime.datetime.fromtimestamp(conv.updated_at),
90
+ )
91
+ dbsession.add(conv_v2)
92
+ except Exception as e:
93
+ logger.error(
94
+ f"迁移旧会话 {conversation.get('cid', 'unknown')} 失败: {e}",
95
+ exc_info=True,
96
+ )
97
+ logger.info(f"成功迁移 {total_cnt} 条旧的会话数据到新表。")
98
+
99
+
100
+ async def migration_platform_table(
101
+ db_helper: BaseDatabase,
102
+ platform_id_map: dict[str, dict[str, str]],
103
+ ):
104
+ db_helper_v3 = SQLiteV3DatabaseV3(
105
+ db_path=DB_PATH.replace("data_v4.db", "data_v3.db"),
106
+ )
107
+ secs_from_2023_4_10_to_now = (
108
+ datetime.datetime.now(datetime.timezone.utc)
109
+ - datetime.datetime(2023, 4, 10, tzinfo=datetime.timezone.utc)
110
+ ).total_seconds()
111
+ offset_sec = int(secs_from_2023_4_10_to_now)
112
+ logger.info(f"迁移旧平台数据,offset_sec: {offset_sec} 秒。")
113
+ stats = db_helper_v3.get_base_stats(offset_sec=offset_sec)
114
+ logger.info(f"迁移 {len(stats.platform)} 条旧的平台数据到新的表中...")
115
+ platform_stats_v3 = stats.platform
116
+
117
+ if not platform_stats_v3:
118
+ logger.info("没有找到旧平台数据,跳过迁移。")
119
+ return
120
+
121
+ first_time_stamp = platform_stats_v3[0].timestamp
122
+ end_time_stamp = platform_stats_v3[-1].timestamp
123
+ start_time = first_time_stamp - (first_time_stamp % 3600) # 向下取整到小时
124
+ end_time = end_time_stamp + (3600 - (end_time_stamp % 3600)) # 向上取整到小时
125
+
126
+ idx = 0
127
+
128
+ async with db_helper.get_db() as dbsession:
129
+ dbsession: AsyncSession
130
+ async with dbsession.begin():
131
+ total_buckets = (end_time - start_time) // 3600
132
+ for bucket_idx, bucket_end in enumerate(range(start_time, end_time, 3600)):
133
+ if bucket_idx % 500 == 0:
134
+ progress = int((bucket_idx + 1) / total_buckets * 100)
135
+ logger.info(f"进度: {progress}% ({bucket_idx + 1}/{total_buckets})")
136
+ cnt = 0
137
+ while (
138
+ idx < len(platform_stats_v3)
139
+ and platform_stats_v3[idx].timestamp < bucket_end
140
+ ):
141
+ cnt += platform_stats_v3[idx].count
142
+ idx += 1
143
+ if cnt == 0:
144
+ continue
145
+ platform_id = get_platform_id(
146
+ platform_id_map,
147
+ platform_stats_v3[idx].name,
148
+ )
149
+ platform_type = get_platform_type(
150
+ platform_id_map,
151
+ platform_stats_v3[idx].name,
152
+ )
153
+ try:
154
+ await dbsession.execute(
155
+ text("""
156
+ INSERT INTO platform_stats (timestamp, platform_id, platform_type, count)
157
+ VALUES (:timestamp, :platform_id, :platform_type, :count)
158
+ ON CONFLICT(timestamp, platform_id, platform_type) DO UPDATE SET
159
+ count = platform_stats.count + EXCLUDED.count
160
+ """),
161
+ {
162
+ "timestamp": datetime.datetime.fromtimestamp(
163
+ bucket_end,
164
+ tz=datetime.timezone.utc,
165
+ ),
166
+ "platform_id": platform_id,
167
+ "platform_type": platform_type,
168
+ "count": cnt,
169
+ },
170
+ )
171
+ except Exception:
172
+ logger.error(
173
+ f"迁移平台统计数据失败: {platform_id}, {platform_type}, 时间戳: {bucket_end}",
174
+ exc_info=True,
175
+ )
176
+ logger.info(f"成功迁移 {len(platform_stats_v3)} 条旧的平台数据到新表。")
177
+
178
+
179
+ async def migration_webchat_data(
180
+ db_helper: BaseDatabase,
181
+ platform_id_map: dict[str, dict[str, str]],
182
+ ):
183
+ """迁移 WebChat 的历史记录到新的 PlatformMessageHistory 表中"""
184
+ db_helper_v3 = SQLiteV3DatabaseV3(
185
+ db_path=DB_PATH.replace("data_v4.db", "data_v3.db"),
186
+ )
187
+ conversations, total_cnt = db_helper_v3.get_all_conversations(
188
+ page=1,
189
+ page_size=10000000,
190
+ )
191
+ logger.info(f"迁移 {total_cnt} 条旧的 WebChat 会话数据到新的表中...")
192
+
193
+ async with db_helper.get_db() as dbsession:
194
+ dbsession: AsyncSession
195
+ async with dbsession.begin():
196
+ for idx, conversation in enumerate(conversations):
197
+ if total_cnt > 0 and (idx + 1) % max(1, total_cnt // 10) == 0:
198
+ progress = int((idx + 1) / total_cnt * 100)
199
+ if progress % 10 == 0:
200
+ logger.info(f"进度: {progress}% ({idx + 1}/{total_cnt})")
201
+ try:
202
+ conv = db_helper_v3.get_conversation_by_user_id(
203
+ user_id=conversation.get("user_id", "unknown"),
204
+ cid=conversation.get("cid", "unknown"),
205
+ )
206
+ if not conv:
207
+ logger.info(
208
+ f"未找到该条旧会话对应的具体数据: {conversation}, 跳过。",
209
+ )
210
+ if ":" in conv.user_id:
211
+ continue
212
+ platform_id = "webchat"
213
+ history = json.loads(conv.history) if conv.history else []
214
+ for msg in history:
215
+ type_ = msg.get("type") # user type, "bot" or "user"
216
+ new_history = PlatformMessageHistory(
217
+ platform_id=platform_id,
218
+ user_id=conv.cid, # we use conv.cid as user_id for webchat
219
+ content=msg,
220
+ sender_id=type_,
221
+ sender_name=type_,
222
+ )
223
+ dbsession.add(new_history)
224
+
225
+ except Exception:
226
+ logger.error(
227
+ f"迁移旧 WebChat 会话 {conversation.get('cid', 'unknown')} 失败",
228
+ exc_info=True,
229
+ )
230
+
231
+ logger.info(f"成功迁移 {total_cnt} 条旧的 WebChat 会话数据到新表。")
232
+
233
+
234
+ async def migration_persona_data(
235
+ db_helper: BaseDatabase,
236
+ astrbot_config: AstrBotConfig,
237
+ ):
238
+ """迁移 Persona 数据到新的表中。
239
+ 旧的 Persona 数据存储在 preference 中,新的 Persona 数据存储在 persona 表中。
240
+ """
241
+ v3_persona_config: list[dict] = astrbot_config.get("persona", [])
242
+ total_personas = len(v3_persona_config)
243
+ logger.info(f"迁移 {total_personas} 个 Persona 配置到新表中...")
244
+
245
+ for idx, persona in enumerate(v3_persona_config):
246
+ if total_personas > 0 and (idx + 1) % max(1, total_personas // 10) == 0:
247
+ progress = int((idx + 1) / total_personas * 100)
248
+ if progress % 10 == 0:
249
+ logger.info(f"进度: {progress}% ({idx + 1}/{total_personas})")
250
+ try:
251
+ begin_dialogs = persona.get("begin_dialogs", [])
252
+ mood_imitation_dialogs = persona.get("mood_imitation_dialogs", [])
253
+ parts = []
254
+ user_turn = True
255
+ for mood_dialog in mood_imitation_dialogs:
256
+ if user_turn:
257
+ parts.append(f"A: {mood_dialog}\n")
258
+ else:
259
+ parts.append(f"B: {mood_dialog}\n")
260
+ user_turn = not user_turn
261
+ mood_prompt = "".join(parts)
262
+ system_prompt = persona.get("prompt", "")
263
+ if mood_prompt:
264
+ system_prompt += f"Here are few shots of dialogs, you need to imitate the tone of 'B' in the following dialogs to respond:\n {mood_prompt}"
265
+ persona_new = await db_helper.insert_persona(
266
+ persona_id=persona["name"],
267
+ system_prompt=system_prompt,
268
+ begin_dialogs=begin_dialogs,
269
+ )
270
+ logger.info(
271
+ f"迁移 Persona {persona['name']}({persona_new.system_prompt[:30]}...) 到新表成功。",
272
+ )
273
+ except Exception as e:
274
+ logger.error(f"解析 Persona 配置失败:{e}")
275
+
276
+
277
+ async def migration_preferences(
278
+ db_helper: BaseDatabase,
279
+ platform_id_map: dict[str, dict[str, str]],
280
+ ):
281
+ # 1. global scope migration
282
+ keys = [
283
+ "inactivated_llm_tools",
284
+ "inactivated_plugins",
285
+ "curr_provider",
286
+ "curr_provider_tts",
287
+ "curr_provider_stt",
288
+ "alter_cmd",
289
+ ]
290
+ for key in keys:
291
+ value = sp_v3.get(key)
292
+ if value is not None:
293
+ await sp.put_async("global", "global", key, value)
294
+ logger.info(f"迁移全局偏好设置 {key} 成功,值: {value}")
295
+
296
+ # 2. umo scope migration
297
+ session_conversation = sp_v3.get("session_conversation", default={})
298
+ for umo, conversation_id in session_conversation.items():
299
+ if not umo or not conversation_id:
300
+ continue
301
+ try:
302
+ session = MessageSesion.from_str(session_str=umo)
303
+ platform_id = get_platform_id(platform_id_map, session.platform_name)
304
+ session.platform_id = platform_id
305
+ await sp.put_async("umo", str(session), "sel_conv_id", conversation_id)
306
+ logger.info(f"迁移会话 {umo} 的对话数据到新表成功,平台 ID: {platform_id}")
307
+ except Exception as e:
308
+ logger.error(f"迁移会话 {umo} 的对话数据失败: {e}", exc_info=True)
309
+
310
+ session_service_config = sp_v3.get("session_service_config", default={})
311
+ for umo, config in session_service_config.items():
312
+ if not umo or not config:
313
+ continue
314
+ try:
315
+ session = MessageSesion.from_str(session_str=umo)
316
+ platform_id = get_platform_id(platform_id_map, session.platform_name)
317
+ session.platform_id = platform_id
318
+
319
+ await sp.put_async("umo", str(session), "session_service_config", config)
320
+
321
+ logger.info(f"迁移会话 {umo} 的服务配置到新表成功,平台 ID: {platform_id}")
322
+ except Exception as e:
323
+ logger.error(f"迁移会话 {umo} 的服务配置失败: {e}", exc_info=True)
324
+
325
+ session_variables = sp_v3.get("session_variables", default={})
326
+ for umo, variables in session_variables.items():
327
+ if not umo or not variables:
328
+ continue
329
+ try:
330
+ session = MessageSesion.from_str(session_str=umo)
331
+ platform_id = get_platform_id(platform_id_map, session.platform_name)
332
+ session.platform_id = platform_id
333
+ await sp.put_async("umo", str(session), "session_variables", variables)
334
+ except Exception as e:
335
+ logger.error(f"迁移会话 {umo} 的变量失败: {e}", exc_info=True)
336
+
337
+ session_provider_perf = sp_v3.get("session_provider_perf", default={})
338
+ for umo, perf in session_provider_perf.items():
339
+ if not umo or not perf:
340
+ continue
341
+ try:
342
+ session = MessageSesion.from_str(session_str=umo)
343
+ platform_id = get_platform_id(platform_id_map, session.platform_name)
344
+ session.platform_id = platform_id
345
+
346
+ for provider_type, provider_id in perf.items():
347
+ await sp.put_async(
348
+ "umo",
349
+ str(session),
350
+ f"provider_perf_{provider_type}",
351
+ provider_id,
352
+ )
353
+ logger.info(
354
+ f"迁移会话 {umo} 的提供商偏好到新表成功,平台 ID: {platform_id}",
355
+ )
356
+ except Exception as e:
357
+ logger.error(f"迁移会话 {umo} 的提供商偏好失败: {e}", exc_info=True)
@@ -0,0 +1,44 @@
1
+ from astrbot.api import logger, sp
2
+ from astrbot.core.astrbot_config_mgr import AstrBotConfigManager
3
+ from astrbot.core.umop_config_router import UmopConfigRouter
4
+
5
+
6
+ async def migrate_45_to_46(acm: AstrBotConfigManager, ucr: UmopConfigRouter):
7
+ abconf_data = acm.abconf_data
8
+
9
+ if not isinstance(abconf_data, dict):
10
+ # should be unreachable
11
+ logger.warning(
12
+ f"migrate_45_to_46: abconf_data is not a dict (type={type(abconf_data)}). Value: {abconf_data!r}",
13
+ )
14
+ return
15
+
16
+ # 如果任何一项带有 umop,则说明需要迁移
17
+ need_migration = False
18
+ for conf_id, conf_info in abconf_data.items():
19
+ if isinstance(conf_info, dict) and "umop" in conf_info:
20
+ need_migration = True
21
+ break
22
+
23
+ if not need_migration:
24
+ return
25
+
26
+ logger.info("Starting migration from version 4.5 to 4.6")
27
+
28
+ # extract umo->conf_id mapping
29
+ umo_to_conf_id = {}
30
+ for conf_id, conf_info in abconf_data.items():
31
+ if isinstance(conf_info, dict) and "umop" in conf_info:
32
+ umop_ls = conf_info.pop("umop")
33
+ if not isinstance(umop_ls, list):
34
+ continue
35
+ for umo in umop_ls:
36
+ if isinstance(umo, str) and umo not in umo_to_conf_id:
37
+ umo_to_conf_id[umo] = conf_id
38
+
39
+ # update the abconf data
40
+ await sp.global_put("abconf_mapping", abconf_data)
41
+ # update the umop config router
42
+ await ucr.update_routing_data(umo_to_conf_id)
43
+
44
+ logger.info("Migration from version 45 to 46 completed successfully")
@@ -0,0 +1,131 @@
1
+ """Migration script for WebChat sessions.
2
+
3
+ This migration creates PlatformSession from existing platform_message_history records.
4
+
5
+ Changes:
6
+ - Creates platform_sessions table
7
+ - Adds platform_id field (default: 'webchat')
8
+ - Adds display_name field
9
+ - Session_id format: {platform_id}_{uuid}
10
+ """
11
+
12
+ from sqlalchemy import func, select
13
+ from sqlmodel import col
14
+
15
+ from astrbot.api import logger, sp
16
+ from astrbot.core.db import BaseDatabase
17
+ from astrbot.core.db.po import ConversationV2, PlatformMessageHistory, PlatformSession
18
+
19
+
20
+ async def migrate_webchat_session(db_helper: BaseDatabase):
21
+ """Create PlatformSession records from platform_message_history.
22
+
23
+ This migration extracts all unique user_ids from platform_message_history
24
+ where platform_id='webchat' and creates corresponding PlatformSession records.
25
+ """
26
+ # 检查是否已经完成迁移
27
+ migration_done = await db_helper.get_preference(
28
+ "global", "global", "migration_done_webchat_session_1"
29
+ )
30
+ if migration_done:
31
+ return
32
+
33
+ logger.info("开始执行数据库迁移(WebChat 会话迁移)...")
34
+
35
+ try:
36
+ async with db_helper.get_db() as session:
37
+ # 从 platform_message_history 创建 PlatformSession
38
+ query = (
39
+ select(
40
+ col(PlatformMessageHistory.user_id),
41
+ col(PlatformMessageHistory.sender_name),
42
+ func.min(PlatformMessageHistory.created_at).label("earliest"),
43
+ func.max(PlatformMessageHistory.updated_at).label("latest"),
44
+ )
45
+ .where(col(PlatformMessageHistory.platform_id) == "webchat")
46
+ .where(col(PlatformMessageHistory.sender_id) != "bot")
47
+ .group_by(col(PlatformMessageHistory.user_id))
48
+ )
49
+
50
+ result = await session.execute(query)
51
+ webchat_users = result.all()
52
+
53
+ if not webchat_users:
54
+ logger.info("没有找到需要迁移的 WebChat 数据")
55
+ await sp.put_async(
56
+ "global", "global", "migration_done_webchat_session_1", True
57
+ )
58
+ return
59
+
60
+ logger.info(f"找到 {len(webchat_users)} 个 WebChat 会话需要迁移")
61
+
62
+ # 检查已存在的会话
63
+ existing_query = select(col(PlatformSession.session_id))
64
+ existing_result = await session.execute(existing_query)
65
+ existing_session_ids = {row[0] for row in existing_result.fetchall()}
66
+
67
+ # 查询 Conversations 表中的 title,用于设置 display_name
68
+ # 对于每个 user_id,对应的 conversation user_id 格式为: webchat:FriendMessage:webchat!astrbot!{user_id}
69
+ user_ids_to_query = [
70
+ f"webchat:FriendMessage:webchat!astrbot!{user_id}"
71
+ for user_id, _, _, _ in webchat_users
72
+ ]
73
+ conv_query = select(
74
+ col(ConversationV2.user_id), col(ConversationV2.title)
75
+ ).where(col(ConversationV2.user_id).in_(user_ids_to_query))
76
+ conv_result = await session.execute(conv_query)
77
+ # 创建 user_id -> title 的映射字典
78
+ title_map = {
79
+ user_id.replace("webchat:FriendMessage:webchat!astrbot!", ""): title
80
+ for user_id, title in conv_result.fetchall()
81
+ }
82
+
83
+ # 批量创建 PlatformSession 记录
84
+ sessions_to_add = []
85
+ skipped_count = 0
86
+
87
+ for user_id, sender_name, created_at, updated_at in webchat_users:
88
+ # user_id 就是 webchat_conv_id (session_id)
89
+ session_id = user_id
90
+
91
+ # sender_name 通常是 username,但可能为 None
92
+ creator = sender_name if sender_name else "guest"
93
+
94
+ # 检查是否已经存在该会话
95
+ if session_id in existing_session_ids:
96
+ logger.debug(f"会话 {session_id} 已存在,跳过")
97
+ skipped_count += 1
98
+ continue
99
+
100
+ # 从 Conversations 表中获取 display_name
101
+ display_name = title_map.get(user_id)
102
+
103
+ # 创建新的 PlatformSession(保留原有的时间戳)
104
+ new_session = PlatformSession(
105
+ session_id=session_id,
106
+ platform_id="webchat",
107
+ creator=creator,
108
+ is_group=0,
109
+ created_at=created_at,
110
+ updated_at=updated_at,
111
+ display_name=display_name,
112
+ )
113
+ sessions_to_add.append(new_session)
114
+
115
+ # 批量插入
116
+ if sessions_to_add:
117
+ session.add_all(sessions_to_add)
118
+ await session.commit()
119
+
120
+ logger.info(
121
+ f"WebChat 会话迁移完成!成功迁移: {len(sessions_to_add)}, 跳过: {skipped_count}",
122
+ )
123
+ else:
124
+ logger.info("没有新会话需要迁移")
125
+
126
+ # 标记迁移完成
127
+ await sp.put_async("global", "global", "migration_done_webchat_session_1", True)
128
+
129
+ except Exception as e:
130
+ logger.error(f"迁移过程中发生错误: {e}", exc_info=True)
131
+ raise
@@ -0,0 +1,48 @@
1
+ import json
2
+ import os
3
+ from typing import TypeVar
4
+
5
+ from astrbot.core.utils.astrbot_path import get_astrbot_data_path
6
+
7
+ _VT = TypeVar("_VT")
8
+
9
+
10
+ class SharedPreferences:
11
+ def __init__(self, path=None):
12
+ if path is None:
13
+ path = os.path.join(get_astrbot_data_path(), "shared_preferences.json")
14
+ self.path = path
15
+ self._data = self._load_preferences()
16
+
17
+ def _load_preferences(self):
18
+ if os.path.exists(self.path):
19
+ try:
20
+ with open(self.path) as f:
21
+ return json.load(f)
22
+ except json.JSONDecodeError:
23
+ os.remove(self.path)
24
+ return {}
25
+
26
+ def _save_preferences(self):
27
+ with open(self.path, "w") as f:
28
+ json.dump(self._data, f, indent=4, ensure_ascii=False)
29
+ f.flush()
30
+
31
+ def get(self, key, default: _VT = None) -> _VT:
32
+ return self._data.get(key, default)
33
+
34
+ def put(self, key, value):
35
+ self._data[key] = value
36
+ self._save_preferences()
37
+
38
+ def remove(self, key):
39
+ if key in self._data:
40
+ del self._data[key]
41
+ self._save_preferences()
42
+
43
+ def clear(self):
44
+ self._data.clear()
45
+ self._save_preferences()
46
+
47
+
48
+ sp = SharedPreferences()