koishi-plugin-chat-patch 3.0.2 → 5.0.1

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 (237) hide show
  1. package/client/vue/index.vue +60 -634
  2. package/client/web/dist/assets/AppIcon-DIku6-h0.png +0 -0
  3. package/client/web/dist/assets/AppIconDark-BYymN71t.png +0 -0
  4. package/client/web/dist/assets/CapacitorAppIcon-CSZwNhvi.png +0 -0
  5. package/client/web/dist/assets/CapacitorAppIconDark-BMvMOzsg.png +0 -0
  6. package/client/web/dist/assets/Chat-CTdyeaX2.css +1 -0
  7. package/client/web/dist/assets/Chat-DkZvD7d8.js +28 -0
  8. package/client/web/dist/assets/MealHungryPan-Beos3lRI.js +1 -0
  9. package/client/web/dist/assets/MsgBody-aZ4_aija.js +1 -0
  10. package/client/web/dist/assets/MsgBody.vue_vue_type_script_setup_true_lang-CPZjRLp6.js +67 -0
  11. package/client/web/dist/assets/NapcatAppIcon-C7VOAkW9.png +0 -0
  12. package/client/web/dist/assets/Vue-lEhHouxB.png +0 -0
  13. package/client/web/dist/assets/append_darwin-CqBNU62J.css +1 -0
  14. package/client/web/dist/assets/append_full_vibrancy-j0D2LM9m.css +1 -0
  15. package/client/web/dist/assets/append_linux-uhxeBqeC.css +1 -0
  16. package/client/web/dist/assets/append_linux_vibrancy-COiZq8MS.css +1 -0
  17. package/client/web/dist/assets/append_napcat-DV8CAyAd.css +1 -0
  18. package/client/web/dist/assets/append_new-407Lo5_V.css +1 -0
  19. package/client/web/dist/assets/append_vibrancy-6FHctkOY.css +1 -0
  20. package/client/web/dist/assets/append_win32-srmD-78y.css +1 -0
  21. package/client/web/dist/assets/cakes-CxTVKdnK.svg +189 -0
  22. package/client/web/dist/assets/core-DhEqZVGG.js +1 -0
  23. package/client/web/dist/assets/event-Cadp6m6J.js +1 -0
  24. package/client/web/dist/assets/index-CPTXiemn.js +4045 -0
  25. package/client/web/dist/assets/index-Cg_esKB0.js +1 -0
  26. package/client/web/dist/assets/index-Cln012sP.css +1 -0
  27. package/client/web/dist/assets//346/226/271/346/255/243/345/226/265/345/221/234/344/275/223-CJKIKBn3.ttf +0 -0
  28. package/client/web/dist/assets//346/234/237/345/276/205-C90_OY60.png +0 -0
  29. package/client/web/dist/bcui/css/color-dark.css +35 -0
  30. package/client/web/dist/bcui/css/color-light.css +35 -0
  31. package/client/web/dist/bcui/css/style.css +468 -0
  32. package/client/web/dist/bcui/js/main.js +193 -0
  33. package/client/web/dist/css/append-dark.css +13 -0
  34. package/client/web/dist/css/append-light.css +13 -0
  35. package/client/web/dist/css/append-napcat-dark.css +14 -0
  36. package/client/web/dist/css/append-napcat-light.css +14 -0
  37. package/client/web/dist/favicon.ico +0 -0
  38. package/client/web/dist/img/icons/icon-for-fuck-apple.png +0 -0
  39. package/client/web/dist/img/icons/icon-maskable.png +0 -0
  40. package/client/web/dist/img/icons/icon.png +0 -0
  41. package/client/web/dist/img/icons/icon.svg +10 -0
  42. package/client/web/dist/index.html +22 -0
  43. package/client/web/dist/robots.txt +2 -0
  44. package/client/web/dist/sw.js +58 -0
  45. package/client/web/env.d.ts +56 -0
  46. package/client/web/index.html +21 -0
  47. package/client/web/package-lock.json +3419 -0
  48. package/client/web/package.json +62 -0
  49. package/client/web/public/bcui/css/color-dark.css +35 -0
  50. package/client/web/public/bcui/css/color-light.css +35 -0
  51. package/client/web/public/bcui/css/style.css +468 -0
  52. package/client/web/public/bcui/js/main.js +193 -0
  53. package/client/web/public/css/append-dark.css +13 -0
  54. package/client/web/public/css/append-light.css +13 -0
  55. package/client/web/public/css/append-napcat-dark.css +14 -0
  56. package/client/web/public/css/append-napcat-light.css +14 -0
  57. package/client/web/public/favicon.ico +0 -0
  58. package/client/web/public/img/icons/icon-for-fuck-apple.png +0 -0
  59. package/client/web/public/img/icons/icon-maskable.png +0 -0
  60. package/client/web/public/img/icons/icon.png +0 -0
  61. package/client/web/public/img/icons/icon.svg +10 -0
  62. package/client/web/public/robots.txt +2 -0
  63. package/client/web/public/sw.js +58 -0
  64. package/client/web/scripts/copy-bcui.mjs +17 -0
  65. package/client/web/src/App.vue +1127 -0
  66. package/client/web/src/assets/css/append/append_darwin.css +68 -0
  67. package/client/web/src/assets/css/append/append_full_vibrancy.css +290 -0
  68. package/client/web/src/assets/css/append/append_linux.css +125 -0
  69. package/client/web/src/assets/css/append/append_linux_vibrancy.css +22 -0
  70. package/client/web/src/assets/css/append/append_napcat.css +35 -0
  71. package/client/web/src/assets/css/append/append_new.css +843 -0
  72. package/client/web/src/assets/css/append/append_vibrancy.css +113 -0
  73. package/client/web/src/assets/css/append/append_win32.css +109 -0
  74. package/client/web/src/assets/css/append/mobile/append_mobile_horizontal.css +349 -0
  75. package/client/web/src/assets/css/append/mobile/append_mobile_vertical.css +96 -0
  76. package/client/web/src/assets/css/chat.css +2104 -0
  77. package/client/web/src/assets/css/msg.css +941 -0
  78. package/client/web/src/assets/css/options.css +811 -0
  79. package/client/web/src/assets/css/sys_notice.css +62 -0
  80. package/client/web/src/assets/css/view.css +2105 -0
  81. package/client/web/src/assets/font//346/226/271/346/255/243/345/226/265/345/221/234/344/275/223.ttf +0 -0
  82. package/client/web/src/assets/img/Vue.png +0 -0
  83. package/client/web/src/assets/img/cakes.svg +189 -0
  84. package/client/web/src/assets/img/icons/AppIcon.png +0 -0
  85. package/client/web/src/assets/img/icons/AppIconDark.png +0 -0
  86. package/client/web/src/assets/img/icons/CapacitorAppIcon.png +0 -0
  87. package/client/web/src/assets/img/icons/CapacitorAppIconDark.png +0 -0
  88. package/client/web/src/assets/img/icons/NapcatAppIcon.png +0 -0
  89. package/client/web/src/assets/img/icons/NapcatAppIconDark.png +0 -0
  90. package/client/web/src/assets/img/icons/exit.png +0 -0
  91. package/client/web/src/assets/img/icons/pen.png +0 -0
  92. package/client/web/src/assets/img/stars.svg +89 -0
  93. package/client/web/src/assets/img//346/234/237/345/276/205.png +0 -0
  94. package/client/web/src/assets/l10n/_About I10n.md +35 -0
  95. package/client/web/src/assets/l10n/_l10nconfig.json +7 -0
  96. package/client/web/src/assets/l10n/zh-CN.po +1941 -0
  97. package/client/web/src/assets/logo.png +0 -0
  98. package/client/web/src/assets/pathMap/LLOneBot.yaml +2 -0
  99. package/client/web/src/assets/pathMap/Lagrange.OneBot.yaml +180 -0
  100. package/client/web/src/assets/pathMap/NapCat.Onebot.yaml +224 -0
  101. package/client/web/src/assets/pathMap/SnowLuma.yaml +80 -0
  102. package/client/web/src/assets/qed.txt +1 -0
  103. package/client/web/src/components/AboutPan.vue +197 -0
  104. package/client/web/src/components/BulletinBody.vue +129 -0
  105. package/client/web/src/components/DepPan.vue +310 -0
  106. package/client/web/src/components/EmojiFace.vue +32 -0
  107. package/client/web/src/components/FacePan.vue +461 -0
  108. package/client/web/src/components/FileBody.vue +129 -0
  109. package/client/web/src/components/FileManager.vue +532 -0
  110. package/client/web/src/components/FriendBody.vue +54 -0
  111. package/client/web/src/components/GlobalSessionSearchBar.vue +301 -0
  112. package/client/web/src/components/History.vue +49 -0
  113. package/client/web/src/components/LazyLottie.vue +82 -0
  114. package/client/web/src/components/MergePan.vue +127 -0
  115. package/client/web/src/components/MsgBody.vue +1478 -0
  116. package/client/web/src/components/MusicPlayer.vue +549 -0
  117. package/client/web/src/components/NoticeBody.vue +168 -0
  118. package/client/web/src/components/QzonePermissionPan.vue +150 -0
  119. package/client/web/src/components/RawMsgRenderPreviewPan.vue +190 -0
  120. package/client/web/src/components/ThemeColorPickerPan.vue +420 -0
  121. package/client/web/src/components/TinySessionBody.vue +51 -0
  122. package/client/web/src/components/UmamiInfoPan.vue +1549 -0
  123. package/client/web/src/components/UpdatePan.vue +263 -0
  124. package/client/web/src/components/ViewerCom.vue +1360 -0
  125. package/client/web/src/components/VoiceMsg.vue +626 -0
  126. package/client/web/src/components/WelPan.vue +894 -0
  127. package/client/web/src/components/msg-body/.gitkeep +0 -0
  128. package/client/web/src/components/msg-component/JsonSegComp.vue +138 -0
  129. package/client/web/src/components/msg-component/XmlSegComp.vue +132 -0
  130. package/client/web/src/components/msg-component/jsonComp/AutoReply.vue +97 -0
  131. package/client/web/src/components/msg-component/jsonComp/Contact.lua.vue +129 -0
  132. package/client/web/src/components/msg-component/jsonComp/Feed.lua.vue +94 -0
  133. package/client/web/src/components/msg-component/jsonComp/Forum.vue +84 -0
  134. package/client/web/src/components/msg-component/jsonComp/Mannounce.vue +111 -0
  135. package/client/web/src/components/msg-component/jsonComp/Map.vue +52 -0
  136. package/client/web/src/components/msg-component/jsonComp/Miniapp.lua.vue +53 -0
  137. package/client/web/src/components/msg-component/jsonComp/Miniapp.vue +69 -0
  138. package/client/web/src/components/msg-component/jsonComp/Music.lua.vue +135 -0
  139. package/client/web/src/components/msg-component/jsonComp/Tuwen.lua.vue +56 -0
  140. package/client/web/src/components/notice-component/.gitkeep +0 -0
  141. package/client/web/src/components/notice-component/MealHungryPan.vue +119 -0
  142. package/client/web/src/components/tooltip/CustomFaceTooltip.vue +22 -0
  143. package/client/web/src/components/tooltip/Tooltip.vue +50 -0
  144. package/client/web/src/components/tooltip/Tooltips.vue +14 -0
  145. package/client/web/src/components/tooltip/UserInfoTooltip.vue +69 -0
  146. package/client/web/src/function/base.ts +238 -0
  147. package/client/web/src/function/connect.ts +2042 -0
  148. package/client/web/src/function/debug.ts +10 -0
  149. package/client/web/src/function/elements/information.ts +141 -0
  150. package/client/web/src/function/elements/system.ts +101 -0
  151. package/client/web/src/function/elements/vueComp.ts +62 -0
  152. package/client/web/src/function/favicon.ts +76 -0
  153. package/client/web/src/function/input.ts +69 -0
  154. package/client/web/src/function/model/emoji.ts +127 -0
  155. package/client/web/src/function/model/img.ts +150 -0
  156. package/client/web/src/function/model/msg-body.ts +51 -0
  157. package/client/web/src/function/msg.ts +2674 -0
  158. package/client/web/src/function/notify.ts +225 -0
  159. package/client/web/src/function/option.ts +749 -0
  160. package/client/web/src/function/satori-model.ts +818 -0
  161. package/client/web/src/function/satori.ts +325 -0
  162. package/client/web/src/function/sender.ts +178 -0
  163. package/client/web/src/function/tooltip.ts +49 -0
  164. package/client/web/src/function/utils/appUtil.ts +2288 -0
  165. package/client/web/src/function/utils/avatarUtil.ts +31 -0
  166. package/client/web/src/function/utils/backgroundUtil.ts +110 -0
  167. package/client/web/src/function/utils/linkViewUtil.ts +101 -0
  168. package/client/web/src/function/utils/localHistoryUtil.ts +517 -0
  169. package/client/web/src/function/utils/msgUtil.ts +1242 -0
  170. package/client/web/src/function/utils/pinyin.ts +141 -0
  171. package/client/web/src/function/utils/recordUtil.ts +187 -0
  172. package/client/web/src/function/utils/sessionUtil.ts +114 -0
  173. package/client/web/src/function/utils/systemUtil.ts +664 -0
  174. package/client/web/src/main.ts +91 -0
  175. package/client/web/src/pages/Chat.vue +3453 -0
  176. package/client/web/src/pages/Friends.vue +636 -0
  177. package/client/web/src/pages/Info.vue +717 -0
  178. package/client/web/src/pages/Messages.vue +875 -0
  179. package/client/web/src/pages/Options.vue +82 -0
  180. package/client/web/src/pages/Qzone.vue +1178 -0
  181. package/client/web/src/pages/chat-view/ChatDanmaku.vue +759 -0
  182. package/client/web/src/pages/chat-view/ChatGlagame.vue +642 -0
  183. package/client/web/src/pages/chat-view/ChatTerminal.vue +1044 -0
  184. package/client/web/src/pages/chat-view/SystemNotice.vue +171 -0
  185. package/client/web/src/pages/options/OptAccount.vue +107 -0
  186. package/client/web/src/pages/options/OptAddon.vue +185 -0
  187. package/client/web/src/pages/options/OptDev.vue +704 -0
  188. package/client/web/src/pages/options/OptFunction.vue +403 -0
  189. package/client/web/src/pages/options/OptInfo.vue +165 -0
  190. package/client/web/src/pages/options/OptView.vue +846 -0
  191. package/client/web/src/runtime/backend.ts +310 -0
  192. package/client/web/src/state/auth.ts +14 -0
  193. package/client/web/src/state/chat.ts +36 -0
  194. package/client/web/src/state/connection.ts +20 -0
  195. package/client/web/src/state/contact.ts +37 -0
  196. package/client/web/src/state/qzone.ts +18 -0
  197. package/client/web/src/state/sessionHistory.ts +25 -0
  198. package/client/web/src/state/settings.ts +18 -0
  199. package/client/web/src/state/sticker.ts +10 -0
  200. package/client/web/src/state/ui.ts +56 -0
  201. package/client/web/tsconfig.json +27 -0
  202. package/client/web/tsconfig.tsbuildinfo +1 -0
  203. package/client/web/vite.config.ts +33 -0
  204. package/dist/index.js +1 -12
  205. package/dist/style.css +1 -1
  206. package/lib/index.js +2070 -2077
  207. package/package.json +15 -2
  208. package/readme.md +0 -22
  209. package/src/bootstrap.ts +71 -0
  210. package/src/cache.ts +319 -0
  211. package/src/config.ts +23 -35
  212. package/src/database.ts +650 -0
  213. package/src/index.ts +27 -59
  214. package/src/media.ts +302 -0
  215. package/src/recorder.ts +175 -0
  216. package/src/self-message.ts +456 -0
  217. package/src/server.d.ts +26 -0
  218. package/src/types.ts +108 -48
  219. package/src/web.ts +712 -0
  220. package/client/vue/chat-logic.ts +0 -683
  221. package/client/vue/composables/useChatActions.ts +0 -61
  222. package/client/vue/composables/useChatData.ts +0 -185
  223. package/client/vue/composables/useImageCache.ts +0 -281
  224. package/client/vue/composables/useVideoCache.ts +0 -125
  225. package/client/vue/types.ts +0 -72
  226. package/lib/api-handlers.d.ts +0 -25
  227. package/lib/config.d.ts +0 -14
  228. package/lib/file-manager.d.ts +0 -124
  229. package/lib/index.d.ts +0 -17
  230. package/lib/logger.d.ts +0 -9
  231. package/lib/message-handler.d.ts +0 -35
  232. package/lib/types.d.ts +0 -57
  233. package/lib/utils.d.ts +0 -18
  234. package/src/api-handlers.ts +0 -646
  235. package/src/file-manager.ts +0 -1436
  236. package/src/message-handler.ts +0 -507
  237. package/src/utils.ts +0 -189
@@ -1,646 +0,0 @@
1
- import { FileManager } from './file-manager'
2
- import { MessageHandler } from './message-handler'
3
- import { Context, h, Universal } from 'koishi'
4
- import { Config } from './config'
5
- import { PluginLogger } from './logger'
6
- import { } from '@koishijs/plugin-console'
7
- import { URL, pathToFileURL, fileURLToPath } from 'node:url'
8
- import { promises as fs } from 'node:fs'
9
- import path from 'node:path'
10
- import { createHash } from 'node:crypto'
11
- import * as mime from 'mime-types'
12
-
13
- export class ApiHandlers {
14
- private currentTempVideo: string | null = null
15
-
16
- constructor(
17
- private ctx: Context,
18
- private config: Config,
19
- private fileManager: FileManager,
20
- private messageHandler: MessageHandler,
21
- private logger: PluginLogger
22
- ) { }
23
-
24
- registerApiHandlers() {
25
- this.ctx.console.addListener('clear-all-indexeddb-data' as any, async () => {
26
- try {
27
- this.logInfo('收到清空 IndexedDB 数据请求')
28
-
29
- return { success: true, message: '可以清空 IndexedDB' }
30
- } catch (error: any) {
31
- this.logger.error('清空 IndexedDB 数据失败:', error)
32
- return { success: false, error: error?.message || String(error) }
33
- }
34
- })
35
-
36
- this.ctx.console.addListener('get-chat-data' as any, async () => {
37
- try {
38
- // 只读取元数据,不加载消息到内存
39
- const data = await this.fileManager.readMetadataOnly()
40
-
41
- this.logInfo('获取基础聊天数据(仅元数据)')
42
-
43
- return {
44
- success: true,
45
- data: {
46
- bots: data.bots || {},
47
- channels: data.channels || {},
48
- pinnedBots: data.pinnedBots || [],
49
- pinnedChannels: data.pinnedChannels || [],
50
- // 不返回消息数据,由前端按需加载
51
- messages: {}
52
- }
53
- }
54
- } catch (error: any) {
55
- this.logger.error('获取聊天数据失败:', error)
56
- return { success: false, error: error?.message || String(error) }
57
- }
58
- })
59
-
60
- this.ctx.console.addListener('get-history-messages' as any, async (requestData: {
61
- selfId: string
62
- channelId: string
63
- limit?: number
64
- offset?: number
65
- }) => {
66
- try {
67
- const limit = Math.max(1, requestData.limit ?? this.config.messageChunkSize)
68
- const result = await this.fileManager.readChannelMessagesPage(
69
- requestData.selfId,
70
- requestData.channelId,
71
- limit,
72
- requestData.offset || 0
73
- )
74
-
75
- this.logInfo('获取历史消息:', `${requestData.selfId}:${requestData.channelId}`, '共', result.messages.length, '条消息')
76
-
77
- return {
78
- success: true,
79
- messages: result.messages,
80
- total: result.total
81
- }
82
- } catch (error: any) {
83
- this.logger.error('获取历史消息失败:', error)
84
- return { success: false, error: error?.message || String(error), messages: [], total: 0 }
85
- }
86
- })
87
-
88
- this.ctx.console.addListener('get-all-channel-message-counts' as any, async () => {
89
- try {
90
- const counts = await this.fileManager.getAllChannelMessageCounts()
91
-
92
- this.logInfo('获取所有频道消息数量:', {
93
- 频道数: Object.keys(counts).length,
94
- 总消息数: Object.values(counts).reduce((total, count) => total + count, 0)
95
- })
96
-
97
- return {
98
- success: true,
99
- counts: counts
100
- }
101
- } catch (error: any) {
102
- this.logger.error('获取频道消息数量失败:', error)
103
- return { success: false, error: error?.message || String(error), counts: {} }
104
- }
105
- })
106
-
107
- this.ctx.console.addListener('fetch-image' as any, async (data: { url: string }) => {
108
- try {
109
- // 如果已经是 Vite @fs 路径,直接返回
110
- if (data.url.includes('/vite/@fs/')) {
111
- return {
112
- success: true,
113
- viteUrl: data.url
114
- }
115
- }
116
-
117
- // 如果是本地文件 URL,转换为 Vite @fs 路径
118
- if (this.isFileUrl(data.url)) {
119
- this.logInfo('处理本地文件请求:', data.url)
120
- const filePath = require('node:url').fileURLToPath(data.url)
121
- const normalizedPath = filePath.replace(/\\/g, '/')
122
- return {
123
- success: true,
124
- viteUrl: `/vite/@fs/${normalizedPath}`
125
- }
126
- }
127
-
128
- // 网络图片:下载并缓存到本地,返回 Vite @fs 路径
129
- const dir = path.join(this.ctx.baseDir, 'data', 'chat-patch', 'persist-media', 'images')
130
- await fs.mkdir(dir, { recursive: true })
131
-
132
- const hash = createHash('md5').update(data.url).digest('hex')
133
- const ext = path.extname(new URL(data.url).pathname) || '.jpg'
134
- const filename = `${hash}${ext}`
135
- const filePath = path.join(dir, filename)
136
-
137
- // 如果文件不存在,下载并保存
138
- if (!(await this.fileExists(filePath))) {
139
- const response = await fetch(data.url, {
140
- headers: {
141
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
142
- 'Referer': ''
143
- }
144
- })
145
-
146
- if (!response.ok) {
147
- throw new Error(`HTTP ${response.status}: ${response.statusText}`)
148
- }
149
-
150
- const buffer = await response.arrayBuffer()
151
- await fs.writeFile(filePath, Buffer.from(buffer))
152
- }
153
-
154
- // 返回 Vite @fs 路径
155
- const normalizedPath = filePath.replace(/\\/g, '/')
156
- return {
157
- success: true,
158
- viteUrl: `/vite/@fs/${normalizedPath}`
159
- }
160
- } catch (error: any) {
161
- return { success: false, error: this.getClientErrorMessage(error) }
162
- }
163
- })
164
-
165
- // 清理历史记录 API 已废弃,现在直接通过右键删除频道数据
166
- this.ctx.console.addListener('clear-channel-history' as any, async (data: {
167
- selfId: string
168
- channelId: string
169
- }) => {
170
- try {
171
- this.logInfo('收到清理历史记录请求(已废弃,建议使用删除频道数据):', data)
172
-
173
- const channelKey = `${data.selfId}:${data.channelId}`
174
-
175
- const { deletedMessages } = await this.fileManager.deleteChannelData(data.selfId, data.channelId)
176
- if (!deletedMessages) {
177
- return { success: true, message: '频道没有历史消息' }
178
- }
179
-
180
- this.logInfo(`频道 ${channelKey} 历史记录已清空:`, {
181
- 清理消息数: deletedMessages
182
- })
183
-
184
- return {
185
- success: true,
186
- message: `成功清理 ${deletedMessages} 条历史消息`,
187
- clearedCount: deletedMessages,
188
- keptCount: 0
189
- }
190
- } catch (error: any) {
191
- this.logger.error('清理频道历史记录失败:', error)
192
- return { success: false, error: error?.message || String(error) }
193
- }
194
- })
195
-
196
- this.ctx.console.addListener('send-message' as any, async (data: {
197
- selfId: string
198
- channelId: string
199
- content: string
200
- images?: Array<{
201
- tempId: string
202
- filename: string
203
- }>
204
- }) => {
205
- try {
206
- this.logInfo('收到发送消息请求:', data)
207
-
208
- const bot = this.ctx.bots.find((bot: any) => bot.selfId === data.selfId || bot.user?.id === data.selfId)
209
- if (!bot) {
210
- this.logger.error('未找到机器人:', data.selfId, '当前可用机器人:', this.ctx.bots.map((b: any) => b.selfId))
211
- return { success: false, error: `未找到机器人 ${data.selfId},请检查机器人是否在线` }
212
- }
213
-
214
- if (bot.status !== Universal.Status.ONLINE) {
215
- this.logger.error('机器人离线:', data.selfId, '状态:', bot.status)
216
- return { success: false, error: `机器人 ${data.selfId} 当前离线` }
217
- }
218
-
219
- let messageContent = data.content
220
-
221
- if (data.images && data.images.length > 0) {
222
- const tempDir = path.join(this.ctx.baseDir, 'data', 'chat-patch', 'temp')
223
- const tempFiles = await this.safeReadDir(tempDir)
224
-
225
- for (const image of data.images) {
226
- const files = tempFiles.filter((file) => file.includes(`temp_${image.tempId}`))
227
-
228
- if (files.length > 0) {
229
- const imagePath = path.join(tempDir, files[0])
230
-
231
- const fileUrl = this.createFileUrl(imagePath)
232
- messageContent += h.image(fileUrl).toString()
233
- this.logInfo('添加图片到消息:', { imagePath, fileUrl })
234
- }
235
- }
236
- }
237
-
238
- const parsedContent = h.parse(messageContent)
239
-
240
- this.messageHandler.setCorrectChannelId(data.selfId, data.channelId)
241
-
242
- const result = await bot.sendMessage(data.channelId, parsedContent)
243
- this.logInfo('消息发送成功:', result)
244
-
245
- const messageId = Array.isArray(result) ? result[0] : result;
246
-
247
- if (messageId) {
248
- const channelKey = `${data.selfId}:${data.channelId}`
249
- const msg = await this.fileManager.markLatestBotMessageAsSent(data.selfId, data.channelId, messageId)
250
- if (msg) {
251
- this.ctx.console.broadcast('bot-message-updated', {
252
- channelKey,
253
- tempId: msg.id,
254
- realId: messageId
255
- })
256
- }
257
- }
258
-
259
- return {
260
- success: !!messageId,
261
- messageId: messageId,
262
- tempImageIds: data.images?.map(img => img.tempId) || []
263
- }
264
- } catch (error: any) {
265
- this.logger.error('发送消息失败:', error)
266
- return { success: false, error: error?.message || String(error) }
267
- }
268
- })
269
-
270
- this.ctx.console.addListener('cleanup-temp-images' as any, async (data: {
271
- tempImageIds: string[]
272
- }) => {
273
- try {
274
- this.logInfo('收到清理临时图片请求:', data.tempImageIds)
275
-
276
- this.logInfo('临时图片将由定时任务清理,保持文件可用性')
277
-
278
- return { success: true, cleanedCount: 0 }
279
- } catch (error: any) {
280
- this.logger.error('清理临时图片失败:', error)
281
- return { success: false, error: error?.message || String(error) }
282
- }
283
- })
284
-
285
- this.ctx.console.addListener('delete-bot-data' as any, async (data: {
286
- selfId: string
287
- }) => {
288
- try {
289
- this.logInfo('收到删除机器人数据请求:', data)
290
-
291
- const { deletedChannels, deletedMessages } = await this.fileManager.deleteBotData(data.selfId)
292
-
293
- this.logInfo(`机器人 ${data.selfId} 数据删除完成:`, {
294
- 删除频道数: deletedChannels,
295
- 删除消息数: deletedMessages
296
- })
297
-
298
- return {
299
- success: true,
300
- message: `成功删除机器人数据:${deletedChannels} 个频道,${deletedMessages} 条消息`,
301
- deletedChannels,
302
- deletedMessages
303
- }
304
- } catch (error: any) {
305
- this.logger.error('删除机器人数据失败:', error)
306
- return { success: false, error: error?.message || String(error) }
307
- }
308
- })
309
-
310
- this.ctx.console.addListener('delete-channel-data' as any, async (data: {
311
- selfId: string
312
- channelId: string
313
- }) => {
314
- try {
315
- this.logInfo('收到删除频道数据请求:', data)
316
-
317
- const channelKey = `${data.selfId}:${data.channelId}`
318
- const { deletedMessages } = await this.fileManager.deleteChannelData(data.selfId, data.channelId)
319
-
320
- this.logInfo(`频道 ${channelKey} 数据删除完成:`, {
321
- 删除消息数: deletedMessages
322
- })
323
-
324
- return {
325
- success: true,
326
- message: `成功删除频道数据:${deletedMessages} 条消息`,
327
- deletedMessages
328
- }
329
- } catch (error: any) {
330
- this.logger.error('删除频道数据失败:', error)
331
- return { success: false, error: error?.message || String(error) }
332
- }
333
- })
334
-
335
- this.ctx.console.addListener('set-pinned-bots' as any, async (data: {
336
- pinnedBots: string[]
337
- }) => {
338
- try {
339
- this.logInfo('收到设置置顶机器人请求:', data.pinnedBots)
340
- await this.fileManager.setPinnedBots(data.pinnedBots)
341
- return { success: true }
342
- } catch (error: any) {
343
- this.logger.error('设置置顶机器人失败:', error)
344
- return { success: false, error: error?.message || String(error) }
345
- }
346
- })
347
-
348
- this.ctx.console.addListener('set-pinned-channels' as any, async (data: {
349
- pinnedChannels: string[]
350
- }) => {
351
- try {
352
- this.logInfo('收到设置置顶频道请求:', data.pinnedChannels)
353
- await this.fileManager.setPinnedChannels(data.pinnedChannels)
354
- return { success: true }
355
- } catch (error: any) {
356
- this.logger.error('设置置顶频道失败:', error)
357
- return { success: false, error: error?.message || String(error) }
358
- }
359
- })
360
-
361
- this.ctx.console.addListener('upload-image' as any, async (data: {
362
- file: string
363
- filename: string
364
- mimeType: string
365
- isGif?: boolean
366
- }) => {
367
- try {
368
- this.logInfo('收到图片上传请求:', { filename: data.filename, mimeType: data.mimeType, isGif: data.isGif })
369
-
370
- const base64Data = data.file.replace(/^data:image\/\w+;base64,/, '')
371
- const buffer = Buffer.from(base64Data, 'base64')
372
-
373
- const tempId = Date.now() + '_' + Math.random().toString(36).substring(2, 11)
374
- const extension = data.filename.split('.').pop()?.toLowerCase() || (data.isGif ? 'gif' : 'jpg')
375
- const tempFilename = `temp_${tempId}.${extension}`
376
-
377
- const tempDir = path.join(this.ctx.baseDir, 'data', 'chat-patch', 'temp')
378
- await fs.mkdir(tempDir, { recursive: true })
379
-
380
- const tempPath = path.join(tempDir, tempFilename)
381
-
382
- await fs.writeFile(tempPath, buffer)
383
-
384
- this.logInfo('图片上传成功:', { tempPath, size: buffer.length, isGif: data.isGif })
385
-
386
- return {
387
- success: true,
388
- tempId: tempId,
389
- tempPath: tempPath,
390
- filename: data.filename,
391
- size: buffer.length,
392
- isGif: data.isGif
393
- }
394
- } catch (error: any) {
395
- this.logger.error('图片上传失败:', error)
396
- return { success: false, error: error?.message || String(error) }
397
- }
398
- })
399
-
400
- this.ctx.console.addListener('delete-temp-image' as any, async (data: {
401
- tempId: string
402
- }) => {
403
- try {
404
- const tempDir = path.join(this.ctx.baseDir, 'data', 'chat-patch', 'temp')
405
- const files = (await this.safeReadDir(tempDir)).filter((file) => file.includes(`temp_${data.tempId}`))
406
-
407
- for (const file of files) {
408
- const filePath = path.join(tempDir, file)
409
- if (await this.fileExists(filePath)) {
410
- await fs.unlink(filePath)
411
- this.logInfo('删除临时图片:', filePath)
412
- }
413
- }
414
-
415
- return { success: true }
416
- } catch (error: any) {
417
- this.logger.error('删除临时图片失败:', error)
418
- return { success: false, error: error?.message || String(error) }
419
- }
420
- })
421
-
422
- this.setupTempFileCleanup()
423
-
424
- this.ctx.console.addListener('get-plugin-config' as any, async () => {
425
- try {
426
- return {
427
- success: true,
428
- config: {
429
- maxMessagesPerChannel: this.config.maxMessagesPerChannel,
430
- messageChunkSize: this.config.messageChunkSize,
431
- channelCacheLimit: this.config.channelCacheLimit,
432
- maxPersistImages: this.config.maxPersistImages,
433
- loggerinfo: this.config.loggerinfo,
434
- blockedPlatforms: this.config.blockedPlatforms || [],
435
- clearIndexedDBOnStart: this.config.clearIndexedDBOnStart
436
- }
437
- }
438
- } catch (error: any) {
439
- this.logger.error('获取插件配置失败:', error)
440
- return { success: false, error: error?.message || String(error) }
441
- }
442
- })
443
-
444
- this.ctx.console.addListener('get-user-info' as any, async (data: { selfId: string, userId: string, guildId?: string }) => {
445
- try {
446
- const bot = this.ctx.bots.find(b => b.selfId === data.selfId)
447
- if (!bot) return { success: false, error: '机器人不存在' }
448
-
449
- if (!bot.getUser || typeof bot.getUser !== 'function') {
450
- return { success: false, error: '此平台不支持查看用户信息' }
451
- }
452
-
453
- const user = await bot.getUser(data.userId, data.guildId)
454
-
455
- if (user) {
456
-
457
- if (user.avatar) {
458
- user.avatar = await this.messageHandler.downloadAndCacheMedia(user.avatar, 'avatar')
459
- }
460
-
461
- const changed = await this.fileManager.updateUserProfileInBotData(data.selfId, data.userId, user.name, user.avatar)
462
- if (changed) {
463
- this.ctx.console.broadcast('chat-data-updated', {})
464
- }
465
- }
466
-
467
- return { success: true, data: user }
468
- } catch (error: any) {
469
- return { success: false, error: error?.message || '获取用户信息失败' }
470
- }
471
- })
472
-
473
- this.ctx.console.addListener('fetch-video-temp' as any, async (data: { url: string }) => {
474
- try {
475
- this.logInfo('收到视频临时加载请求:', data.url)
476
-
477
- // 如果已经是 Vite @fs 路径,直接返回
478
- if (data.url.includes('/vite/@fs/')) {
479
- return {
480
- success: true,
481
- viteUrl: data.url
482
- }
483
- }
484
-
485
- // 如果是本地文件 URL,转换为 Vite @fs 路径
486
- if (this.isFileUrl(data.url)) {
487
- const filePath = require('node:url').fileURLToPath(data.url)
488
- const normalizedPath = filePath.replace(/\\/g, '/')
489
- return {
490
- success: true,
491
- viteUrl: `/vite/@fs/${normalizedPath}`
492
- }
493
- }
494
-
495
- // 网络视频:下载并缓存到本地,返回 Vite @fs 路径
496
- const dir = path.join(this.ctx.baseDir, 'data', 'chat-patch', 'persist-media', 'media')
497
- await fs.mkdir(dir, { recursive: true })
498
-
499
- const hash = createHash('md5').update(data.url).digest('hex')
500
- const ext = path.extname(new URL(data.url).pathname) || '.mp4'
501
- const filename = `${hash}${ext}`
502
- const filePath = path.join(dir, filename)
503
-
504
- // 如果文件不存在,下载并保存
505
- if (!(await this.fileExists(filePath))) {
506
- const response = await fetch(data.url, {
507
- headers: {
508
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
509
- 'Referer': ''
510
- }
511
- })
512
-
513
- if (!response.ok) {
514
- throw new Error(`HTTP ${response.status}: ${response.statusText}`)
515
- }
516
-
517
- const buffer = await response.arrayBuffer()
518
- await fs.writeFile(filePath, Buffer.from(buffer))
519
- this.logInfo('视频下载成功:', { size: buffer.byteLength, path: filePath })
520
- }
521
-
522
- // 返回 Vite @fs 路径
523
- const normalizedPath = filePath.replace(/\\/g, '/')
524
- return {
525
- success: true,
526
- viteUrl: `/vite/@fs/${normalizedPath}`
527
- }
528
- } catch (error: any) {
529
- return { success: false, error: this.getClientErrorMessage(error) }
530
- }
531
- })
532
- }
533
-
534
- private isFileUrl(url: string): boolean {
535
- try {
536
- const parsedUrl = new URL(url)
537
- return parsedUrl.protocol === 'file:'
538
- } catch {
539
- return false
540
- }
541
- }
542
-
543
- private createFileUrl(filePath: string): string {
544
- try {
545
- return pathToFileURL(filePath).href
546
- } catch (error) {
547
- this.logger.error('创建文件URL失败:', { filePath, error })
548
-
549
- return `file://${filePath}`
550
- }
551
- }
552
-
553
- private async handleLocalFileRequest(fileUrl: string) {
554
- try {
555
-
556
- const filePath = fileURLToPath(fileUrl)
557
-
558
- const buffer = await fs.readFile(filePath)
559
- const base64 = buffer.toString('base64')
560
-
561
- const contentType = mime.lookup(filePath) || 'application/octet-stream'
562
-
563
- this.logInfo('成功读取本地文件:', { fileUrl, filePath, contentType })
564
-
565
- return {
566
- success: true,
567
- base64: base64,
568
- contentType: contentType,
569
- dataUrl: `data:${contentType};base64,${base64}`
570
- }
571
- } catch (error: any) {
572
- return {
573
- success: false,
574
- error: `读取本地文件失败: ${error.message}`
575
- }
576
- }
577
- }
578
-
579
- private setupTempFileCleanup() {
580
-
581
- this.ctx.setInterval(() => {
582
- this.cleanupMediaCache()
583
- }, 5 * 60 * 1000)
584
- }
585
-
586
- private async cleanupMediaCache() {
587
- const baseDir = path.join(this.ctx.baseDir, 'data', 'chat-patch', 'persist-media')
588
-
589
- await this.cleanupMediaCacheDir(path.join(baseDir, 'images'), 100)
590
- await this.cleanupMediaCacheDir(path.join(baseDir, 'media'), 20)
591
- }
592
-
593
- private logInfo(...args: any[]) {
594
- if (this.config.loggerinfo) {
595
- Reflect.apply(this.logger.info, this.logger, args)
596
- }
597
- }
598
-
599
- private async fileExists(filePath: string) {
600
- try {
601
- await fs.access(filePath)
602
- return true
603
- } catch {
604
- return false
605
- }
606
- }
607
-
608
- private async safeReadDir(dirPath: string): Promise<string[]> {
609
- try {
610
- return await fs.readdir(dirPath)
611
- } catch {
612
- return []
613
- }
614
- }
615
-
616
- private async cleanupMediaCacheDir(dirPath: string, limit: number) {
617
- const files = await this.safeReadDir(dirPath)
618
- if (!files.length) {
619
- return
620
- }
621
-
622
- const fileStats = await Promise.all(files.map(async (fileName) => {
623
- const filePath = path.join(dirPath, fileName)
624
- const stats = await fs.stat(filePath)
625
- return { path: filePath, mtime: stats.mtimeMs }
626
- }))
627
-
628
- fileStats.sort((left, right) => right.mtime - left.mtime)
629
-
630
- for (const file of fileStats.slice(limit)) {
631
- try {
632
- await fs.unlink(file.path)
633
- } catch {
634
- continue
635
- }
636
- }
637
- }
638
-
639
- private getClientErrorMessage(error: unknown) {
640
- if (typeof error === 'object' && error !== null && 'message' in error && typeof error.message === 'string') {
641
- return error.message
642
- }
643
-
644
- return String(error)
645
- }
646
- }