koishi-plugin-chat-patch 5.6.1 → 6.1.0
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.
- package/client/web/dist/assets/Chat-BLcuHxzh.js +28 -0
- package/client/web/dist/assets/{Chat-BXVWj5Bd.css → Chat-Cee07eDL.css} +1 -1
- package/client/web/dist/assets/{MsgBody-CzrxyiFO.js → MsgBody-krZlLzRL.js} +1 -1
- package/client/web/dist/assets/MsgBody.vue_vue_type_script_setup_true_lang-CLmUjKws.js +67 -0
- package/client/web/dist/assets/{index-EJ2CcODr.js → index-DOJoilEy.js} +57 -60
- package/client/web/dist/index.html +1 -1
- package/client/web/src/assets/l10n/zh-CN.po +0 -3
- package/client/web/src/components/MsgBody.vue +29 -20
- package/client/web/src/function/connect.ts +17 -18
- package/client/web/src/function/msg.ts +2 -0
- package/client/web/src/function/satori-model.ts +2 -0
- package/client/web/src/function/satori.ts +18 -12
- package/client/web/src/function/utils/appUtil.ts +27 -6
- package/client/web/src/function/utils/msgUtil.ts +27 -21
- package/client/web/src/pages/Chat.vue +35 -32
- package/lib/database.d.ts +5 -3
- package/lib/gateway.d.ts +2 -4
- package/lib/index.d.ts +17 -0
- package/lib/index.js +5409 -473
- package/lib/media.d.ts +1 -1
- package/lib/recorder.d.ts +1 -4
- package/lib/self-message.d.ts +1 -4
- package/lib/types.d.ts +4 -0
- package/package.json +25 -10
- package/src/bootstrap.ts +3 -2
- package/src/config.ts +1 -5
- package/src/database.ts +313 -139
- package/src/gateway.ts +21 -13
- package/src/index.ts +59 -54
- package/src/media.ts +43 -12
- package/src/recorder.ts +2 -12
- package/src/self-message.ts +7 -14
- package/src/server.d.ts +17 -3
- package/src/types.ts +4 -0
- package/src/web.ts +245 -151
- package/client/web/dist/assets/Chat-m4-U5uHa.js +0 -28
- package/client/web/dist/assets/MsgBody.vue_vue_type_script_setup_true_lang-BBF1uxm6.js +0 -67
- package/lib/bootstrap.d.ts +0 -6
- package/lib/satori.d.ts +0 -3
- package/lib/web.d.ts +0 -7
package/src/gateway.ts
CHANGED
|
@@ -4,7 +4,6 @@ import {} from '@koishijs/plugin-http'
|
|
|
4
4
|
import {} from '@koishijs/plugin-server'
|
|
5
5
|
import {} from '@satorijs/plugin-server'
|
|
6
6
|
|
|
7
|
-
import { Config } from './config'
|
|
8
7
|
import { ChatDatabase } from './database'
|
|
9
8
|
import { PluginLogger } from './logger'
|
|
10
9
|
import { Recorder } from './recorder'
|
|
@@ -45,10 +44,10 @@ export class SatoriGateway {
|
|
|
45
44
|
private sequence = 0
|
|
46
45
|
private logins: SatoriLoginInfo[] = []
|
|
47
46
|
private online = false
|
|
47
|
+
private lastPongAt = 0
|
|
48
48
|
|
|
49
49
|
constructor(
|
|
50
50
|
private ctx: Context,
|
|
51
|
-
private config: Config,
|
|
52
51
|
private database: ChatDatabase,
|
|
53
52
|
private recorder: Recorder,
|
|
54
53
|
private logger: PluginLogger,
|
|
@@ -99,6 +98,7 @@ export class SatoriGateway {
|
|
|
99
98
|
}
|
|
100
99
|
this.retry = 0
|
|
101
100
|
this.setOnline(true)
|
|
101
|
+
this.lastPongAt = Date.now()
|
|
102
102
|
socket.send(JSON.stringify({
|
|
103
103
|
op: Opcode.IDENTIFY,
|
|
104
104
|
body: {
|
|
@@ -109,6 +109,11 @@ export class SatoriGateway {
|
|
|
109
109
|
if (!this.pingDispose) {
|
|
110
110
|
this.pingDispose = this.ctx.setInterval(() => {
|
|
111
111
|
if (this.socket?.readyState === WS_OPEN) {
|
|
112
|
+
if (Date.now() - this.lastPongAt > 30000) {
|
|
113
|
+
this.logger.logInfo('Satori 心跳超时,主动重连')
|
|
114
|
+
this.socket.close()
|
|
115
|
+
return
|
|
116
|
+
}
|
|
112
117
|
this.socket.send(JSON.stringify({ op: Opcode.PING, body: {} }))
|
|
113
118
|
}
|
|
114
119
|
}, 10000)
|
|
@@ -160,14 +165,27 @@ export class SatoriGateway {
|
|
|
160
165
|
|
|
161
166
|
if (payload.op === Opcode.READY) {
|
|
162
167
|
const body = getObject(payload.body)
|
|
168
|
+
this.lastPongAt = Date.now()
|
|
163
169
|
this.logins = this.normalizeLogins(body.logins)
|
|
164
170
|
this.onPayload({ kind: 'ready', logins: this.getLogins() })
|
|
165
171
|
return
|
|
166
172
|
}
|
|
167
173
|
|
|
174
|
+
if (payload.op === Opcode.PING) {
|
|
175
|
+
this.lastPongAt = Date.now()
|
|
176
|
+
if (this.socket?.readyState === WS_OPEN) {
|
|
177
|
+
this.socket.send(JSON.stringify({ op: Opcode.PONG, body: {} }))
|
|
178
|
+
}
|
|
179
|
+
return
|
|
180
|
+
}
|
|
181
|
+
if (payload.op === Opcode.PONG) {
|
|
182
|
+
this.lastPongAt = Date.now()
|
|
183
|
+
return
|
|
184
|
+
}
|
|
168
185
|
if (payload.op !== Opcode.EVENT) return
|
|
169
186
|
|
|
170
187
|
const body = getObject(payload.body)
|
|
188
|
+
this.lastPongAt = Date.now()
|
|
171
189
|
const sn = getNumber(body.sn)
|
|
172
190
|
if (sn) {
|
|
173
191
|
this.sequence = sn
|
|
@@ -212,8 +230,6 @@ export class SatoriGateway {
|
|
|
212
230
|
sn,
|
|
213
231
|
body,
|
|
214
232
|
}
|
|
215
|
-
if (this.isBlocked(platform)) return
|
|
216
|
-
|
|
217
233
|
try {
|
|
218
234
|
await this.recorder.handleEvent(body)
|
|
219
235
|
} catch (error) {
|
|
@@ -230,7 +246,7 @@ export class SatoriGateway {
|
|
|
230
246
|
const user = getObject(login.user)
|
|
231
247
|
const platform = getString(login.platform) || getString(user.platform)
|
|
232
248
|
const selfId = getString(login.self_id) || getString(login.selfId) || getString(user.id)
|
|
233
|
-
if (!platform || !selfId
|
|
249
|
+
if (!platform || !selfId) continue
|
|
234
250
|
result.push({
|
|
235
251
|
platform,
|
|
236
252
|
selfId,
|
|
@@ -243,14 +259,6 @@ export class SatoriGateway {
|
|
|
243
259
|
return result
|
|
244
260
|
}
|
|
245
261
|
|
|
246
|
-
private isBlocked(platform: string): boolean {
|
|
247
|
-
return (this.config.blockedPlatforms ?? []).some((item) => {
|
|
248
|
-
return item.exactMatch
|
|
249
|
-
? platform === item.platformName
|
|
250
|
-
: platform.includes(item.platformName)
|
|
251
|
-
})
|
|
252
|
-
}
|
|
253
|
-
|
|
254
262
|
private setOnline(online: boolean) {
|
|
255
263
|
if (this.online === online) return
|
|
256
264
|
this.online = online
|
package/src/index.ts
CHANGED
|
@@ -1,60 +1,65 @@
|
|
|
1
|
-
import { Context } from 'koishi'
|
|
2
|
-
import { Console } from '@koishijs/console'
|
|
3
|
-
import path from 'node:path'
|
|
4
|
-
|
|
5
|
-
import { Config } from './config'
|
|
6
|
-
import { createPluginLogger } from './logger'
|
|
7
|
-
import { ContactCacheService } from './cache'
|
|
8
|
-
import { ChatDatabase } from './database'
|
|
1
|
+
import { Context } from 'koishi'
|
|
2
|
+
import { Console } from '@koishijs/console'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
|
|
5
|
+
import { Config } from './config'
|
|
6
|
+
import { createPluginLogger } from './logger'
|
|
7
|
+
import { ContactCacheService } from './cache'
|
|
8
|
+
import { ChatDatabase } from './database'
|
|
9
9
|
import { Recorder } from './recorder'
|
|
10
10
|
import { SatoriGateway } from './gateway'
|
|
11
11
|
import { MediaManager } from './media'
|
|
12
12
|
import { SelfMessageRecorder } from './self-message'
|
|
13
13
|
import { registerBootstrap } from './bootstrap'
|
|
14
14
|
import { registerWeb } from './web'
|
|
15
|
-
|
|
16
|
-
export const name = 'chat-patch'
|
|
17
|
-
export const reusable = false
|
|
18
|
-
export const filter = false
|
|
15
|
+
|
|
16
|
+
export const name = 'chat-patch'
|
|
17
|
+
export const reusable = false
|
|
18
|
+
export const filter = false
|
|
19
19
|
export const inject = {
|
|
20
|
-
required: ['console', 'server', 'http', 'satori.server'],
|
|
20
|
+
required: ['console', 'server', 'http', 'satori.server', 'node'],
|
|
21
21
|
}
|
|
22
|
-
|
|
23
|
-
declare module 'koishi' {
|
|
24
|
-
interface Context {
|
|
25
|
-
console: Console
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export const usage = `
|
|
30
|
-
---
|
|
31
|
-
|
|
32
|
-
基于 Satori 协议的 Koishi 后台聊天室。
|
|
33
|
-
需要在 Koishi 中启用 server-satori,并在插件内构建 web 应用。
|
|
34
|
-
|
|
35
|
-
---
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
|
|
22
|
+
|
|
23
|
+
declare module 'koishi' {
|
|
24
|
+
interface Context {
|
|
25
|
+
console: Console
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const usage = `
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
基于 Satori 协议的 Koishi 后台聊天室。
|
|
33
|
+
需要在 Koishi 中启用 server-satori,并在插件内构建 web 应用。
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
需要安装 w-node 插件,并开启 w-node、market、server-satori 插件。
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
`
|
|
41
|
+
|
|
42
|
+
export { Config } from './config'
|
|
43
|
+
|
|
44
|
+
export async function apply(ctx: Context, config: Config) {
|
|
45
|
+
const pluginLogger = createPluginLogger(ctx.logger('chat-patch'), config)
|
|
46
|
+
|
|
47
|
+
const database = new ChatDatabase(ctx, config, pluginLogger)
|
|
48
|
+
await database.initialize()
|
|
49
|
+
await database.cleanupExcess()
|
|
50
|
+
const contactCache = new ContactCacheService(ctx, database, pluginLogger)
|
|
51
|
+
|
|
52
|
+
const media = new MediaManager(ctx, config, database, pluginLogger)
|
|
53
|
+
media.start()
|
|
54
|
+
|
|
55
|
+
const recorder = new Recorder(database, media, contactCache, pluginLogger)
|
|
56
|
+
const gateway = new SatoriGateway(ctx, database, recorder, pluginLogger, (payload) => {
|
|
52
57
|
void ctx.console.broadcast('chat-patch/event', payload).catch((error) => {
|
|
53
58
|
pluginLogger.warn('推送前端事件失败:', error)
|
|
54
59
|
})
|
|
55
60
|
})
|
|
56
61
|
|
|
57
|
-
const selfMessages = new SelfMessageRecorder(ctx,
|
|
62
|
+
const selfMessages = new SelfMessageRecorder(ctx, database, media, pluginLogger)
|
|
58
63
|
selfMessages.start()
|
|
59
64
|
|
|
60
65
|
void gateway.start().catch((error) => {
|
|
@@ -63,17 +68,17 @@ export async function apply(ctx: Context, config: Config) {
|
|
|
63
68
|
|
|
64
69
|
registerBootstrap(ctx, config, database, pluginLogger, () => gateway.getLogins())
|
|
65
70
|
registerWeb(ctx, config, database, contactCache, media, pluginLogger)
|
|
66
|
-
|
|
67
|
-
ctx.console.addEntry({
|
|
68
|
-
dev: path.resolve(__dirname, '../client/index.ts'),
|
|
69
|
-
prod: path.resolve(__dirname, '../dist'),
|
|
70
|
-
})
|
|
71
|
-
|
|
71
|
+
|
|
72
|
+
ctx.console.addEntry({
|
|
73
|
+
dev: path.resolve(__dirname, '../client/index.ts'),
|
|
74
|
+
prod: path.resolve(__dirname, '../dist'),
|
|
75
|
+
})
|
|
76
|
+
|
|
72
77
|
ctx.on('dispose', async () => {
|
|
73
78
|
gateway.dispose()
|
|
74
79
|
selfMessages.dispose()
|
|
75
80
|
media.dispose()
|
|
76
|
-
await database.dispose()
|
|
77
|
-
pluginLogger.logInfo('chat-patch 已卸载')
|
|
78
|
-
})
|
|
79
|
-
}
|
|
81
|
+
await database.dispose()
|
|
82
|
+
pluginLogger.logInfo('chat-patch 已卸载')
|
|
83
|
+
})
|
|
84
|
+
}
|
package/src/media.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { Context } from 'koishi'
|
|
2
2
|
import {} from '@koishijs/plugin-server'
|
|
3
3
|
import { createHash } from 'node:crypto'
|
|
4
|
-
import { createReadStream, promises as fs } from 'node:fs'
|
|
4
|
+
import { createReadStream, createWriteStream, promises as fs } from 'node:fs'
|
|
5
5
|
import path from 'node:path'
|
|
6
|
+
import { Readable } from 'node:stream'
|
|
7
|
+
import { pipeline } from 'node:stream/promises'
|
|
6
8
|
import { fileURLToPath } from 'node:url'
|
|
7
9
|
import FileType from 'file-type'
|
|
8
10
|
|
|
@@ -103,6 +105,23 @@ function collectMediaUrls(source: unknown, result: Set<string>) {
|
|
|
103
105
|
}
|
|
104
106
|
}
|
|
105
107
|
|
|
108
|
+
// 限制同时下载的媒体数量,避免一条含大量图片/视频的消息一次性吃满内存和连接
|
|
109
|
+
async function forEachConcurrent<T>(
|
|
110
|
+
items: readonly T[],
|
|
111
|
+
limit: number,
|
|
112
|
+
task: (item: T) => Promise<unknown>,
|
|
113
|
+
) {
|
|
114
|
+
let cursor = 0
|
|
115
|
+
const workers = Array.from({ length: Math.min(limit, items.length) }, async () => {
|
|
116
|
+
while (cursor < items.length) {
|
|
117
|
+
const current = cursor
|
|
118
|
+
cursor += 1
|
|
119
|
+
await task(items[current]).catch(() => undefined)
|
|
120
|
+
}
|
|
121
|
+
})
|
|
122
|
+
await Promise.all(workers)
|
|
123
|
+
}
|
|
124
|
+
|
|
106
125
|
export class MediaManager {
|
|
107
126
|
private mediaDir: string
|
|
108
127
|
private uploadDir: string
|
|
@@ -212,7 +231,7 @@ export class MediaManager {
|
|
|
212
231
|
async cacheMessageMedia(source: unknown, channelId = ''): Promise<string[]> {
|
|
213
232
|
const urls = new Set<string>()
|
|
214
233
|
collectMediaUrls(source, urls)
|
|
215
|
-
await
|
|
234
|
+
await forEachConcurrent([...urls], 4, (url) => this.cacheUrl(url, channelId))
|
|
216
235
|
if (urls.size) {
|
|
217
236
|
this.logger.logInfo('消息媒体已预缓存:', urls.size, '个 URL')
|
|
218
237
|
}
|
|
@@ -256,33 +275,45 @@ export class MediaManager {
|
|
|
256
275
|
if (!response.ok) {
|
|
257
276
|
return null
|
|
258
277
|
}
|
|
259
|
-
|
|
260
|
-
const
|
|
278
|
+
if (!response.body) return null
|
|
279
|
+
const detected = await FileType.stream(Readable.fromWeb(response.body))
|
|
280
|
+
const ext = detected.fileType?.ext
|
|
281
|
+
? toExtension(detected.fileType.ext)
|
|
282
|
+
: (urlExt || '.bin')
|
|
261
283
|
const filename = `${hash}${ext}`
|
|
262
284
|
const filePath = path.join(this.mediaDir, filename)
|
|
263
|
-
await
|
|
264
|
-
|
|
285
|
+
if (await this.exists(filePath)) {
|
|
286
|
+
return this.migrateCachedMedia(url, filePath, channelId)
|
|
287
|
+
}
|
|
288
|
+
const tempPath = path.join(this.mediaDir, `${hash}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`)
|
|
289
|
+
try {
|
|
290
|
+
await pipeline(detected, createWriteStream(tempPath))
|
|
291
|
+
await fs.rename(tempPath, filePath)
|
|
292
|
+
} catch (error) {
|
|
293
|
+
await fs.unlink(tempPath).catch(() => undefined)
|
|
294
|
+
throw error
|
|
295
|
+
}
|
|
296
|
+
this.logger.logInfo('媒体已缓存:', filename)
|
|
265
297
|
return filePath
|
|
266
298
|
} catch (error) {
|
|
267
299
|
return null
|
|
268
300
|
}
|
|
269
301
|
}
|
|
270
302
|
|
|
271
|
-
private async
|
|
303
|
+
private async detectExtensionFromFile(filePath: string): Promise<string> {
|
|
272
304
|
try {
|
|
273
|
-
const detected = await FileType.
|
|
305
|
+
const detected = await FileType.fromStream(createReadStream(filePath))
|
|
274
306
|
if (detected?.ext) return toExtension(detected.ext)
|
|
275
307
|
} catch (error) {
|
|
276
|
-
this.logger.logInfo('
|
|
308
|
+
this.logger.logInfo('媒体类型识别失败,回退文件后缀:', filePath, error)
|
|
277
309
|
}
|
|
278
|
-
const ext = path.extname(
|
|
310
|
+
const ext = path.extname(filePath).toLowerCase()
|
|
279
311
|
return ext && ext !== '.bin' ? ext.toLowerCase() : '.bin'
|
|
280
312
|
}
|
|
281
313
|
|
|
282
314
|
private async migrateCachedMedia(url: string, filePath: string, channelId: string): Promise<string> {
|
|
283
315
|
try {
|
|
284
|
-
const
|
|
285
|
-
const ext = await this.detectExtension(url, buffer)
|
|
316
|
+
const ext = await this.detectExtensionFromFile(filePath)
|
|
286
317
|
const currentExt = path.extname(filePath).toLowerCase()
|
|
287
318
|
if (ext === '.bin' || ext === currentExt) return filePath
|
|
288
319
|
|
package/src/recorder.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { Config } from './config'
|
|
2
1
|
import { ContactCacheService } from './cache'
|
|
3
2
|
import { ChatDatabase } from './database'
|
|
4
3
|
import { MediaManager } from './media'
|
|
@@ -35,7 +34,6 @@ function isPrivateChannelType(value: unknown): boolean {
|
|
|
35
34
|
|
|
36
35
|
export class Recorder {
|
|
37
36
|
constructor(
|
|
38
|
-
private config: Config,
|
|
39
37
|
private database: ChatDatabase,
|
|
40
38
|
private media: MediaManager,
|
|
41
39
|
private contactCache: ContactCacheService,
|
|
@@ -47,7 +45,6 @@ export class Recorder {
|
|
|
47
45
|
const type = getString(body.type)
|
|
48
46
|
const login = getObject(body.login)
|
|
49
47
|
const platform = getString(body.platform) || getString(login.platform)
|
|
50
|
-
if (this.isBlocked(platform)) return false
|
|
51
48
|
if (type === 'message-deleted') {
|
|
52
49
|
await this.handleMessageDeleted(body)
|
|
53
50
|
return true
|
|
@@ -57,14 +54,6 @@ export class Recorder {
|
|
|
57
54
|
return true
|
|
58
55
|
}
|
|
59
56
|
|
|
60
|
-
private isBlocked(platform: string): boolean {
|
|
61
|
-
return (this.config.blockedPlatforms ?? []).some((item) => {
|
|
62
|
-
return item.exactMatch
|
|
63
|
-
? platform === item.platformName
|
|
64
|
-
: platform.includes(item.platformName)
|
|
65
|
-
})
|
|
66
|
-
}
|
|
67
|
-
|
|
68
57
|
private async handleMessageCreated(body: Record<string, unknown>) {
|
|
69
58
|
const login = getObject(body.login)
|
|
70
59
|
const loginUser = getObject(login.user)
|
|
@@ -76,6 +65,7 @@ export class Recorder {
|
|
|
76
65
|
const user = getObject(body.user)
|
|
77
66
|
const sn = getNumber(body.sn)
|
|
78
67
|
const timestamp = getNumber(body.timestamp) || Date.now()
|
|
68
|
+
const timestampMs = timestamp > 1e12 ? timestamp : timestamp * 1000
|
|
79
69
|
const record: MessageRecord = {
|
|
80
70
|
id: getString(message.id) || `satori-${sn}`,
|
|
81
71
|
sequence: sn,
|
|
@@ -86,7 +76,7 @@ export class Recorder {
|
|
|
86
76
|
guildId: getString(guild.id) || undefined,
|
|
87
77
|
userId: getString(user.id) || undefined,
|
|
88
78
|
timestamp,
|
|
89
|
-
timestampMs
|
|
79
|
+
timestampMs,
|
|
90
80
|
receivedAt: Date.now(),
|
|
91
81
|
content: getString(message.content) || getString(message.raw_message) || undefined,
|
|
92
82
|
elements: Array.isArray(message.elements) ? message.elements as unknown[] : undefined,
|
package/src/self-message.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { Bot, Context, h, Session } from 'koishi'
|
|
2
2
|
import { createHash, randomUUID } from 'node:crypto'
|
|
3
3
|
|
|
4
|
-
import { Config } from './config'
|
|
5
4
|
import { ChatDatabase } from './database'
|
|
6
5
|
import { MediaManager } from './media'
|
|
7
6
|
import { PluginLogger } from './logger'
|
|
@@ -213,7 +212,6 @@ export class SelfMessageRecorder {
|
|
|
213
212
|
|
|
214
213
|
constructor(
|
|
215
214
|
private ctx: Context,
|
|
216
|
-
private config: Config,
|
|
217
215
|
private database: ChatDatabase,
|
|
218
216
|
private media: MediaManager,
|
|
219
217
|
private logger: PluginLogger,
|
|
@@ -236,14 +234,6 @@ export class SelfMessageRecorder {
|
|
|
236
234
|
}
|
|
237
235
|
}
|
|
238
236
|
|
|
239
|
-
private isBlocked(platform: string): boolean {
|
|
240
|
-
return (this.config.blockedPlatforms ?? []).some((item) => {
|
|
241
|
-
return item.exactMatch
|
|
242
|
-
? platform === item.platformName
|
|
243
|
-
: platform.includes(item.platformName)
|
|
244
|
-
})
|
|
245
|
-
}
|
|
246
|
-
|
|
247
237
|
// 包装三个发送入口,覆盖 Satori message.create 和插件直接发送
|
|
248
238
|
private wrapBot(bot: Bot) {
|
|
249
239
|
if (this.wrapped.has(bot)) return
|
|
@@ -337,7 +327,7 @@ export class SelfMessageRecorder {
|
|
|
337
327
|
mode: 'create' | 'send' | 'private',
|
|
338
328
|
localId: string,
|
|
339
329
|
) {
|
|
340
|
-
if (!channelId
|
|
330
|
+
if (!channelId) return
|
|
341
331
|
if (!ids.length) {
|
|
342
332
|
this.logger.warn('发送未返回消息 ID,判定为发送失败:', bot.platform, channelId)
|
|
343
333
|
return
|
|
@@ -362,7 +352,7 @@ export class SelfMessageRecorder {
|
|
|
362
352
|
}
|
|
363
353
|
|
|
364
354
|
private async markSelfMessageRevoked(bot: Bot, channelId: string, messageId: string) {
|
|
365
|
-
if (!messageId || !channelId
|
|
355
|
+
if (!messageId || !channelId) return
|
|
366
356
|
const updated = await this.database.updateSelfMessageByMessageId(
|
|
367
357
|
bot.platform ?? '',
|
|
368
358
|
bot.selfId,
|
|
@@ -384,7 +374,7 @@ export class SelfMessageRecorder {
|
|
|
384
374
|
const eventChannel = getObject(event.channel)
|
|
385
375
|
const eventMessage = getObject(event.message)
|
|
386
376
|
const channelId = session.channelId || getString(eventChannel.id)
|
|
387
|
-
if (
|
|
377
|
+
if (!platform || !selfId || !channelId) return
|
|
388
378
|
|
|
389
379
|
const messageId = session.messageId || getMessageId(eventMessage)
|
|
390
380
|
if (messageId) {
|
|
@@ -452,6 +442,7 @@ export class SelfMessageRecorder {
|
|
|
452
442
|
const contentText = typeof content === 'string'
|
|
453
443
|
? content
|
|
454
444
|
: h.toElementArray(content).join('')
|
|
445
|
+
const sentAt = Date.now()
|
|
455
446
|
const fingerprint = createHash('sha256')
|
|
456
447
|
.update([
|
|
457
448
|
platform,
|
|
@@ -473,7 +464,9 @@ export class SelfMessageRecorder {
|
|
|
473
464
|
message: toSegments(elements, (attrs) => this.resolveI18nElement(attrs)),
|
|
474
465
|
forwardId: forwardId(elements),
|
|
475
466
|
forwardContent: toForwardNodes(elements, (attrs) => this.resolveI18nElement(attrs)),
|
|
476
|
-
sentAt
|
|
467
|
+
sentAt,
|
|
468
|
+
timestamp: Math.floor(sentAt / 1000),
|
|
469
|
+
timestampMs: sentAt,
|
|
477
470
|
sequence: ++this.sequence,
|
|
478
471
|
source,
|
|
479
472
|
kind: detectKind(elements),
|
package/src/server.d.ts
CHANGED
|
@@ -1,8 +1,22 @@
|
|
|
1
1
|
import type { DefaultContext, DefaultState, ParameterizedContext } from 'koa'
|
|
2
2
|
|
|
3
|
-
type ServerContext = ParameterizedContext<DefaultState, DefaultContext>
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
type ServerContext = ParameterizedContext<DefaultState, DefaultContext>
|
|
4
|
+
|
|
5
|
+
interface WNodeService {
|
|
6
|
+
import<T>(packageName: string, options?: {
|
|
7
|
+
allowInstall?: boolean
|
|
8
|
+
useRequire?: boolean
|
|
9
|
+
version?: string
|
|
10
|
+
}): Promise<T>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
declare module 'koishi' {
|
|
14
|
+
interface Context {
|
|
15
|
+
node: WNodeService
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
declare module '@satorijs/core' {
|
|
6
20
|
interface Satori {
|
|
7
21
|
server: {
|
|
8
22
|
url: string
|
package/src/types.ts
CHANGED
|
@@ -66,6 +66,8 @@ export interface SelfMessageRecord {
|
|
|
66
66
|
forwardId?: string
|
|
67
67
|
forwardContent?: unknown[]
|
|
68
68
|
sentAt: number
|
|
69
|
+
timestamp?: number
|
|
70
|
+
timestampMs?: number
|
|
69
71
|
sequence: number
|
|
70
72
|
source: SelfMessageSource
|
|
71
73
|
kind: string
|
|
@@ -88,6 +90,8 @@ export interface SelfMessagePayload {
|
|
|
88
90
|
forwardId?: string
|
|
89
91
|
forwardContent?: unknown[]
|
|
90
92
|
sentAt?: number
|
|
93
|
+
timestamp?: number
|
|
94
|
+
timestampMs?: number
|
|
91
95
|
sequence?: number
|
|
92
96
|
source?: SelfMessageSource
|
|
93
97
|
kind?: string
|