koishi-plugin-chat-patch 6.0.0 → 6.1.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.
- package/client/web/dist/assets/Chat-BduEIlJM.js +34 -0
- package/client/web/dist/assets/{Chat-D4tCqRrO.css → Chat-DgkvjRIh.css} +1 -1
- package/client/web/dist/assets/{MsgBody-BaoDyXUz.js → MsgBody-CVtXorvl.js} +1 -1
- package/client/web/dist/assets/MsgBody.vue_vue_type_script_setup_true_lang-D_70Txdf.js +67 -0
- package/client/web/dist/assets/{index-X9hFOAza.css → index-BW47RWcA.css} +1 -1
- package/client/web/dist/assets/{index--7-eCx0r.js → index-N2_GDY3v.js} +5 -8
- package/client/web/dist/index.html +2 -2
- package/client/web/src/assets/css/chat.css +24 -2
- 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 +12 -6
- package/client/web/src/function/custom-element.ts +31 -0
- package/client/web/src/function/msg.ts +2 -0
- package/client/web/src/function/satori-model.ts +2 -0
- package/client/web/src/function/utils/msgUtil.ts +66 -44
- package/client/web/src/pages/Chat.vue +139 -15
- package/lib/index.js +91 -31
- package/lib/types.d.ts +142 -0
- package/package.json +1 -1
- package/src/database.ts +27 -19
- package/src/recorder.ts +2 -1
- package/src/self-message.ts +4 -1
- package/src/types.ts +4 -0
- package/src/web-error-filter.ts +30 -0
- package/src/web.ts +107 -77
- package/client/web/dist/assets/Chat-BkRnAj_r.js +0 -28
- package/client/web/dist/assets/MsgBody.vue_vue_type_script_setup_true_lang-CgpEB8mj.js +0 -67
package/src/database.ts
CHANGED
|
@@ -7,11 +7,17 @@ import { Config } from './config'
|
|
|
7
7
|
import { ContactCacheItem, MessageRecord, PinnedState, SelfMessageRecord } from './types'
|
|
8
8
|
import { PluginLogger } from './logger'
|
|
9
9
|
|
|
10
|
-
function encodeKeyPart(value: string): string {
|
|
11
|
-
return encodeURIComponent(value)
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function
|
|
10
|
+
function encodeKeyPart(value: string): string {
|
|
11
|
+
return encodeURIComponent(value)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function normalizeTimestampMs(value: unknown): number {
|
|
15
|
+
const num = Number(value ?? 0)
|
|
16
|
+
if (!Number.isFinite(num) || num <= 0) return 0
|
|
17
|
+
return num > 1e12 ? num : num * 1000
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function decodeKeyPart(value: string): string {
|
|
15
21
|
try {
|
|
16
22
|
return decodeURIComponent(value)
|
|
17
23
|
} catch {
|
|
@@ -27,11 +33,11 @@ function legacyMessagePrefix(platform: string, selfId: string, channelId: string
|
|
|
27
33
|
return `m:${platform}:${selfId}:${channelId}:`
|
|
28
34
|
}
|
|
29
35
|
|
|
30
|
-
function messageKey(record: MessageRecord): string {
|
|
31
|
-
// 统一用毫秒排序,前端 beforeTime 也传毫秒,避免按秒存储时分页取到同一批消息
|
|
32
|
-
const time = String(
|
|
33
|
-
record.
|
|
34
|
-
).padStart(16, '0')
|
|
36
|
+
function messageKey(record: MessageRecord): string {
|
|
37
|
+
// 统一用毫秒排序,前端 beforeTime 也传毫秒,避免按秒存储时分页取到同一批消息
|
|
38
|
+
const time = String(
|
|
39
|
+
normalizeTimestampMs(record.timestampMs ?? record.timestamp ?? record.receivedAt),
|
|
40
|
+
).padStart(16, '0')
|
|
35
41
|
return `${messagePrefix(record.platform, record.selfId, record.channelId || '')}${time}:${encodeKeyPart(record.id || 'unknown')}`
|
|
36
42
|
}
|
|
37
43
|
|
|
@@ -40,8 +46,10 @@ function selfMessagePrefix(platform: string, selfId: string, channelId: string):
|
|
|
40
46
|
return `sm:${encodeKeyPart(platform)}:${encodeKeyPart(selfId)}:${encodeKeyPart(channelId)}:`
|
|
41
47
|
}
|
|
42
48
|
|
|
43
|
-
function selfMessageKey(record: SelfMessageRecord): string {
|
|
44
|
-
const time = String(
|
|
49
|
+
function selfMessageKey(record: SelfMessageRecord): string {
|
|
50
|
+
const time = String(
|
|
51
|
+
normalizeTimestampMs(record.timestampMs ?? record.timestamp ?? record.sentAt),
|
|
52
|
+
).padStart(16, '0')
|
|
45
53
|
return `${selfMessagePrefix(record.platform, record.selfId, record.channelId)}${time}:${encodeKeyPart(record.id || 'unknown')}`
|
|
46
54
|
}
|
|
47
55
|
|
|
@@ -932,12 +940,12 @@ export class ChatDatabase {
|
|
|
932
940
|
this.logger.logInfo(`频道历史已裁剪 ${keys.length} 条`)
|
|
933
941
|
}
|
|
934
942
|
|
|
935
|
-
private extractRecordTime(value: string): number {
|
|
936
|
-
try {
|
|
937
|
-
const parsed = JSON.parse(value) as Record<string, unknown>
|
|
938
|
-
return
|
|
939
|
-
} catch {
|
|
940
|
-
return 0
|
|
941
|
-
}
|
|
943
|
+
private extractRecordTime(value: string): number {
|
|
944
|
+
try {
|
|
945
|
+
const parsed = JSON.parse(value) as Record<string, unknown>
|
|
946
|
+
return normalizeTimestampMs(parsed.timestampMs ?? parsed.timestamp ?? parsed.sentAt ?? parsed.receivedAt)
|
|
947
|
+
} catch {
|
|
948
|
+
return 0
|
|
949
|
+
}
|
|
942
950
|
}
|
|
943
951
|
}
|
package/src/recorder.ts
CHANGED
|
@@ -65,6 +65,7 @@ export class Recorder {
|
|
|
65
65
|
const user = getObject(body.user)
|
|
66
66
|
const sn = getNumber(body.sn)
|
|
67
67
|
const timestamp = getNumber(body.timestamp) || Date.now()
|
|
68
|
+
const timestampMs = timestamp > 1e12 ? timestamp : timestamp * 1000
|
|
68
69
|
const record: MessageRecord = {
|
|
69
70
|
id: getString(message.id) || `satori-${sn}`,
|
|
70
71
|
sequence: sn,
|
|
@@ -75,7 +76,7 @@ export class Recorder {
|
|
|
75
76
|
guildId: getString(guild.id) || undefined,
|
|
76
77
|
userId: getString(user.id) || undefined,
|
|
77
78
|
timestamp,
|
|
78
|
-
timestampMs
|
|
79
|
+
timestampMs,
|
|
79
80
|
receivedAt: Date.now(),
|
|
80
81
|
content: getString(message.content) || getString(message.raw_message) || undefined,
|
|
81
82
|
elements: Array.isArray(message.elements) ? message.elements as unknown[] : undefined,
|
package/src/self-message.ts
CHANGED
|
@@ -442,6 +442,7 @@ export class SelfMessageRecorder {
|
|
|
442
442
|
const contentText = typeof content === 'string'
|
|
443
443
|
? content
|
|
444
444
|
: h.toElementArray(content).join('')
|
|
445
|
+
const sentAt = Date.now()
|
|
445
446
|
const fingerprint = createHash('sha256')
|
|
446
447
|
.update([
|
|
447
448
|
platform,
|
|
@@ -463,7 +464,9 @@ export class SelfMessageRecorder {
|
|
|
463
464
|
message: toSegments(elements, (attrs) => this.resolveI18nElement(attrs)),
|
|
464
465
|
forwardId: forwardId(elements),
|
|
465
466
|
forwardContent: toForwardNodes(elements, (attrs) => this.resolveI18nElement(attrs)),
|
|
466
|
-
sentAt
|
|
467
|
+
sentAt,
|
|
468
|
+
timestamp: Math.floor(sentAt / 1000),
|
|
469
|
+
timestampMs: sentAt,
|
|
467
470
|
sequence: ++this.sequence,
|
|
468
471
|
source,
|
|
469
472
|
kind: detectKind(elements),
|
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
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Context } from 'koishi'
|
|
2
|
+
import {} from '@koishijs/plugin-server'
|
|
3
|
+
|
|
4
|
+
type KoaErrorListener = (error: Error) => void
|
|
5
|
+
|
|
6
|
+
// 仅过滤客户端断开和超大历史响应导致的已知错误。
|
|
7
|
+
function isIgnoredWebError(error: Error): boolean {
|
|
8
|
+
if (error.name === 'RangeError' && error.message === 'Invalid string length') return true
|
|
9
|
+
return (error as NodeJS.ErrnoException).code === 'ECONNRESET'
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function registerWebErrorFilter(ctx: Context) {
|
|
13
|
+
const app = ctx.server._koa
|
|
14
|
+
const defaultHandler = app.onerror
|
|
15
|
+
const hasDefaultHandler = app.listeners('error').includes(defaultHandler)
|
|
16
|
+
if (hasDefaultHandler) app.off('error', defaultHandler)
|
|
17
|
+
|
|
18
|
+
const errorHandler: KoaErrorListener = (error) => {
|
|
19
|
+
if (isIgnoredWebError(error)) return
|
|
20
|
+
if (hasDefaultHandler) defaultHandler.call(app, error)
|
|
21
|
+
}
|
|
22
|
+
app.on('error', errorHandler)
|
|
23
|
+
|
|
24
|
+
ctx.on('dispose', () => {
|
|
25
|
+
app.off('error', errorHandler)
|
|
26
|
+
if (hasDefaultHandler && !app.listeners('error').includes(defaultHandler)) {
|
|
27
|
+
app.on('error', defaultHandler)
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
}
|
package/src/web.ts
CHANGED
|
@@ -15,6 +15,7 @@ import { ChatDatabase } from './database'
|
|
|
15
15
|
import { MediaManager } from './media'
|
|
16
16
|
import { PluginLogger } from './logger'
|
|
17
17
|
import { ContactCacheItem, MessageRecord, SelfMessagePayload, SelfMessageRecord } from './types'
|
|
18
|
+
import { registerWebErrorFilter } from './web-error-filter'
|
|
18
19
|
|
|
19
20
|
interface ViteConsoleServer {
|
|
20
21
|
config?: {
|
|
@@ -37,11 +38,13 @@ export function registerWeb(
|
|
|
37
38
|
media: MediaManager,
|
|
38
39
|
logger: PluginLogger,
|
|
39
40
|
) {
|
|
40
|
-
|
|
41
|
+
registerWebErrorFilter(ctx)
|
|
42
|
+
const webRoot = path.resolve(__dirname, '..', 'client', 'web', 'dist')
|
|
41
43
|
const webSource = path.resolve(__dirname, '..', 'client', 'web', 'src')
|
|
42
44
|
const webPublic = path.resolve(__dirname, '..', 'client', 'web', 'public')
|
|
43
|
-
const webIndex = path.resolve(__dirname, '..', 'client', 'web', 'index.html')
|
|
44
|
-
const uploadDir = path.resolve(ctx.baseDir, 'data', 'chat-patch', 'upload-media')
|
|
45
|
+
const webIndex = path.resolve(__dirname, '..', 'client', 'web', 'index.html')
|
|
46
|
+
const uploadDir = path.resolve(ctx.baseDir, 'data', 'chat-patch', 'upload-media')
|
|
47
|
+
const mediaDir = path.resolve(ctx.baseDir, 'data', 'chat-patch', 'media')
|
|
45
48
|
|
|
46
49
|
type WebContext = Router.RouterContext<DefaultState, DefaultContext>
|
|
47
50
|
const cacheType = (type: string) => type === 'user' ? 'friend' : type
|
|
@@ -106,6 +109,22 @@ export function registerWeb(
|
|
|
106
109
|
const historyChannelCandidates = (channelId: string): string[] => {
|
|
107
110
|
return channelId ? [channelId] : []
|
|
108
111
|
}
|
|
112
|
+
|
|
113
|
+
const messageSortTime = (item: MessageRecord): number => {
|
|
114
|
+
const timestampMs = Number(item.timestampMs ?? 0)
|
|
115
|
+
if (timestampMs > 0) return timestampMs > 1e12 ? timestampMs : timestampMs * 1000
|
|
116
|
+
const timestamp = Number(item.timestamp ?? 0)
|
|
117
|
+
if (timestamp > 0) return timestamp * 1000
|
|
118
|
+
return Number(item.receivedAt ?? 0)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const selfMessageSortTime = (item: SelfMessageRecord): number => {
|
|
122
|
+
const timestampMs = Number(item.timestampMs ?? item.sentAt ?? 0)
|
|
123
|
+
if (timestampMs > 0) return timestampMs > 1e12 ? timestampMs : timestampMs * 1000
|
|
124
|
+
const timestamp = Number(item.timestamp ?? 0)
|
|
125
|
+
if (timestamp > 0) return timestamp * 1000
|
|
126
|
+
return item.sentAt
|
|
127
|
+
}
|
|
109
128
|
|
|
110
129
|
const mimeToExt: Record<string, string> = {
|
|
111
130
|
'audio/mpeg': '.mp3',
|
|
@@ -242,66 +261,68 @@ export function registerWeb(
|
|
|
242
261
|
}
|
|
243
262
|
})
|
|
244
263
|
|
|
245
|
-
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
264
|
+
const sendLocalFile = async (koa: WebContext, filePath: string) => {
|
|
265
|
+
const size = statSync(filePath).size
|
|
266
|
+
const fileName = path.basename(filePath)
|
|
267
|
+
koa.set('Accept-Ranges', 'bytes')
|
|
268
|
+
if (fileName.toLowerCase().endsWith('.webm')) {
|
|
269
|
+
koa.type = 'audio/webm'
|
|
270
|
+
} else {
|
|
271
|
+
koa.type = fileName
|
|
272
|
+
}
|
|
273
|
+
const range = koa.headers.range
|
|
274
|
+
if (range) {
|
|
275
|
+
const match = /^bytes=(\d*)-(\d*)$/.exec(String(range))
|
|
276
|
+
if (!match) {
|
|
277
|
+
koa.status = 416
|
|
278
|
+
koa.set('Content-Range', `bytes */${size}`)
|
|
279
|
+
koa.body = ''
|
|
280
|
+
return
|
|
281
|
+
}
|
|
282
|
+
let start = 0
|
|
283
|
+
let end = size - 1
|
|
284
|
+
if (match[1] === '' && match[2]) {
|
|
285
|
+
start = Math.max(0, size - Number(match[2]))
|
|
286
|
+
} else if (match[1]) {
|
|
287
|
+
start = Number(match[1])
|
|
288
|
+
end = match[2] ? Number(match[2]) : end
|
|
289
|
+
}
|
|
290
|
+
if (!Number.isFinite(start) || !Number.isFinite(end) || start < 0 || start >= size) {
|
|
291
|
+
koa.status = 416
|
|
292
|
+
koa.set('Content-Range', `bytes */${size}`)
|
|
293
|
+
koa.body = ''
|
|
294
|
+
return
|
|
295
|
+
}
|
|
296
|
+
if (end >= size) end = size - 1
|
|
297
|
+
if (end < start) end = start
|
|
298
|
+
koa.status = 206
|
|
299
|
+
koa.set('Content-Range', `bytes ${start}-${end}/${size}`)
|
|
300
|
+
koa.set('Content-Length', String(end - start + 1))
|
|
301
|
+
koa.body = createReadStream(filePath, { start, end })
|
|
302
|
+
return
|
|
303
|
+
}
|
|
304
|
+
koa.set('Content-Length', String(size))
|
|
305
|
+
koa.body = createReadStream(filePath)
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
ctx.server.get(`${config.basePath}/api/media`, async (koa) => {
|
|
309
|
+
const fileName = path.basename(String(koa.query.file ?? koa.query.name ?? ''))
|
|
310
|
+
if (!fileName || fileName === '.' || fileName === '..') {
|
|
311
|
+
koa.status = 400
|
|
312
|
+
koa.body = { error: 'missing media file' }
|
|
313
|
+
return
|
|
314
|
+
}
|
|
315
|
+
for (const root of [uploadDir, mediaDir]) {
|
|
316
|
+
const filePath = path.resolve(root, fileName)
|
|
317
|
+
if (filePath !== root && filePath.startsWith(`${root}${path.sep}`)
|
|
318
|
+
&& existsSync(filePath) && statSync(filePath).isFile()) {
|
|
319
|
+
await sendLocalFile(koa, filePath)
|
|
320
|
+
return
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
koa.status = 404
|
|
324
|
+
koa.body = { error: 'media file not found' }
|
|
325
|
+
})
|
|
305
326
|
|
|
306
327
|
ctx.server.get(`${config.basePath}/api/cache/all`, async (koa) => {
|
|
307
328
|
const entries = await database.getAllContacts()
|
|
@@ -362,13 +383,13 @@ export function registerWeb(
|
|
|
362
383
|
selfMessages = await querySelfMessages(candidate)
|
|
363
384
|
if (selfMessages.length) break
|
|
364
385
|
}
|
|
365
|
-
// 统一按本地收到时间排序,避免平台时间不准导致顺序颠倒
|
|
366
|
-
messages.sort((a, b) => {
|
|
367
|
-
const timeA =
|
|
368
|
-
const timeB =
|
|
369
|
-
return timeA - timeB
|
|
386
|
+
// 统一按本地收到时间排序,避免平台时间不准导致顺序颠倒
|
|
387
|
+
messages.sort((a, b) => {
|
|
388
|
+
const timeA = messageSortTime(a)
|
|
389
|
+
const timeB = messageSortTime(b)
|
|
390
|
+
return timeA - timeB
|
|
370
391
|
})
|
|
371
|
-
selfMessages.sort((a, b) => a
|
|
392
|
+
selfMessages.sort((a, b) => selfMessageSortTime(a) - selfMessageSortTime(b))
|
|
372
393
|
// 合并两条记录流后只返回 limit 条,保证前端分页/结束判断不会因自消息被放大
|
|
373
394
|
const combined: Array<{
|
|
374
395
|
kind: 'message' | 'self'
|
|
@@ -378,12 +399,12 @@ export function registerWeb(
|
|
|
378
399
|
for (const item of messages) {
|
|
379
400
|
combined.push({
|
|
380
401
|
kind: 'message',
|
|
381
|
-
time:
|
|
402
|
+
time: messageSortTime(item),
|
|
382
403
|
record: item,
|
|
383
404
|
})
|
|
384
405
|
}
|
|
385
406
|
for (const item of selfMessages) {
|
|
386
|
-
combined.push({ kind: 'self', time: item
|
|
407
|
+
combined.push({ kind: 'self', time: selfMessageSortTime(item), record: item })
|
|
387
408
|
}
|
|
388
409
|
combined.sort((a, b) => a.time - b.time)
|
|
389
410
|
const capped = combined.slice(-limit)
|
|
@@ -416,7 +437,7 @@ export function registerWeb(
|
|
|
416
437
|
: await database.listSelfMessages(platform, selfId, candidate, limit)
|
|
417
438
|
if (messages.length) break
|
|
418
439
|
}
|
|
419
|
-
messages.sort((a, b) => a
|
|
440
|
+
messages.sort((a, b) => selfMessageSortTime(a) - selfMessageSortTime(b))
|
|
420
441
|
koa.body = { messages }
|
|
421
442
|
})
|
|
422
443
|
|
|
@@ -438,9 +459,15 @@ export function registerWeb(
|
|
|
438
459
|
message: Array.isArray(body.message) ? body.message as unknown[] : undefined,
|
|
439
460
|
forwardId: typeof body.forwardId === 'string' ? body.forwardId : undefined,
|
|
440
461
|
forwardContent: Array.isArray(body.forwardContent) ? body.forwardContent as unknown[] : undefined,
|
|
441
|
-
sentAt: typeof body.sentAt === 'number' && Number.isFinite(body.sentAt)
|
|
442
|
-
? body.sentAt
|
|
443
|
-
: Date.now(),
|
|
462
|
+
sentAt: typeof body.sentAt === 'number' && Number.isFinite(body.sentAt)
|
|
463
|
+
? body.sentAt
|
|
464
|
+
: Date.now(),
|
|
465
|
+
timestamp: typeof body.timestamp === 'number' && Number.isFinite(body.timestamp)
|
|
466
|
+
? body.timestamp
|
|
467
|
+
: undefined,
|
|
468
|
+
timestampMs: typeof body.timestampMs === 'number' && Number.isFinite(body.timestampMs)
|
|
469
|
+
? body.timestampMs
|
|
470
|
+
: undefined,
|
|
444
471
|
sequence: typeof body.sequence === 'number' && Number.isFinite(body.sequence)
|
|
445
472
|
? body.sequence
|
|
446
473
|
: 0,
|
|
@@ -456,7 +483,8 @@ export function registerWeb(
|
|
|
456
483
|
koa.body = { error: 'missing self-message params' }
|
|
457
484
|
return
|
|
458
485
|
}
|
|
459
|
-
const
|
|
486
|
+
const sentAt = payload.sentAt ?? Date.now()
|
|
487
|
+
const record: SelfMessageRecord = {
|
|
460
488
|
id: payload.id || `web-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
|
461
489
|
platform: payload.platform,
|
|
462
490
|
selfId: payload.selfId,
|
|
@@ -469,7 +497,9 @@ export function registerWeb(
|
|
|
469
497
|
message: payload.message,
|
|
470
498
|
forwardId: payload.forwardId,
|
|
471
499
|
forwardContent: payload.forwardContent,
|
|
472
|
-
sentAt
|
|
500
|
+
sentAt,
|
|
501
|
+
timestamp: payload.timestamp ?? Math.floor(sentAt / 1000),
|
|
502
|
+
timestampMs: payload.timestampMs ?? sentAt,
|
|
473
503
|
sequence: payload.sequence ?? 0,
|
|
474
504
|
source: payload.source ?? 'webui',
|
|
475
505
|
kind: payload.kind ?? 'text',
|