koishi-plugin-chat-patch 6.0.0 → 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-BkRnAj_r.js → Chat-BLcuHxzh.js} +5 -5
- package/client/web/dist/assets/{Chat-D4tCqRrO.css → Chat-Cee07eDL.css} +1 -1
- package/client/web/dist/assets/{MsgBody-BaoDyXUz.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--7-eCx0r.js → index-DOJoilEy.js} +5 -8
- 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 +12 -6
- 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 +27 -21
- package/client/web/src/pages/Chat.vue +3 -8
- package/lib/index.js +63 -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.ts +104 -76
- package/client/web/dist/assets/MsgBody.vue_vue_type_script_setup_true_lang-CgpEB8mj.js +0 -67
package/lib/types.d.ts
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import type { Awaitable } from 'koishi';
|
|
2
|
+
export interface SatoriBootstrap {
|
|
3
|
+
endpoint: string;
|
|
4
|
+
token: string;
|
|
5
|
+
basePath: string;
|
|
6
|
+
logins: SatoriLoginInfo[];
|
|
7
|
+
blockedPlatforms: Array<{
|
|
8
|
+
platformName: string;
|
|
9
|
+
exactMatch: boolean;
|
|
10
|
+
}>;
|
|
11
|
+
}
|
|
12
|
+
export interface SatoriLoginInfo {
|
|
13
|
+
platform: string;
|
|
14
|
+
selfId: string;
|
|
15
|
+
name: string;
|
|
16
|
+
avatar?: string;
|
|
17
|
+
status: number;
|
|
18
|
+
features?: string[];
|
|
19
|
+
}
|
|
20
|
+
export interface SatoriEventPayload {
|
|
21
|
+
type: string;
|
|
22
|
+
platform: string;
|
|
23
|
+
selfId: string;
|
|
24
|
+
timestamp: number;
|
|
25
|
+
sn: number;
|
|
26
|
+
body: Record<string, unknown>;
|
|
27
|
+
}
|
|
28
|
+
export interface MessageRecord {
|
|
29
|
+
id?: string;
|
|
30
|
+
sequence?: number;
|
|
31
|
+
type: string;
|
|
32
|
+
platform: string;
|
|
33
|
+
selfId: string;
|
|
34
|
+
channelId?: string;
|
|
35
|
+
guildId?: string;
|
|
36
|
+
userId?: string;
|
|
37
|
+
timestamp: number;
|
|
38
|
+
timestampMs?: number;
|
|
39
|
+
receivedAt?: number;
|
|
40
|
+
content?: string;
|
|
41
|
+
elements?: unknown[];
|
|
42
|
+
raw?: unknown;
|
|
43
|
+
revoked?: boolean;
|
|
44
|
+
revokedAt?: number;
|
|
45
|
+
}
|
|
46
|
+
export type SelfMessageChannelType = 'group' | 'user';
|
|
47
|
+
export type SelfMessageSource = 'webui' | 'bot' | 'plugin';
|
|
48
|
+
export interface SelfMessageRecord {
|
|
49
|
+
id: string;
|
|
50
|
+
platform: string;
|
|
51
|
+
selfId: string;
|
|
52
|
+
channelId: string;
|
|
53
|
+
guildId?: string;
|
|
54
|
+
channelType?: SelfMessageChannelType;
|
|
55
|
+
messageId?: string;
|
|
56
|
+
content?: string;
|
|
57
|
+
elements?: unknown[];
|
|
58
|
+
message?: unknown[];
|
|
59
|
+
forwardId?: string;
|
|
60
|
+
forwardContent?: unknown[];
|
|
61
|
+
sentAt: number;
|
|
62
|
+
timestamp?: number;
|
|
63
|
+
timestampMs?: number;
|
|
64
|
+
sequence: number;
|
|
65
|
+
source: SelfMessageSource;
|
|
66
|
+
kind: string;
|
|
67
|
+
fingerprint?: string;
|
|
68
|
+
revoked?: boolean;
|
|
69
|
+
revokedAt?: number;
|
|
70
|
+
}
|
|
71
|
+
export interface SelfMessagePayload {
|
|
72
|
+
id?: string;
|
|
73
|
+
platform: string;
|
|
74
|
+
selfId: string;
|
|
75
|
+
channelId: string;
|
|
76
|
+
guildId?: string;
|
|
77
|
+
channelType?: SelfMessageChannelType;
|
|
78
|
+
messageId?: string;
|
|
79
|
+
content?: string;
|
|
80
|
+
elements?: unknown[];
|
|
81
|
+
message?: unknown[];
|
|
82
|
+
forwardId?: string;
|
|
83
|
+
forwardContent?: unknown[];
|
|
84
|
+
sentAt?: number;
|
|
85
|
+
timestamp?: number;
|
|
86
|
+
timestampMs?: number;
|
|
87
|
+
sequence?: number;
|
|
88
|
+
source?: SelfMessageSource;
|
|
89
|
+
kind?: string;
|
|
90
|
+
revoked?: boolean;
|
|
91
|
+
revokedAt?: number;
|
|
92
|
+
}
|
|
93
|
+
export interface HistoryQuery {
|
|
94
|
+
platform: string;
|
|
95
|
+
selfId: string;
|
|
96
|
+
channelId: string;
|
|
97
|
+
limit?: number;
|
|
98
|
+
}
|
|
99
|
+
export interface HistoryResult {
|
|
100
|
+
messages: MessageRecord[];
|
|
101
|
+
}
|
|
102
|
+
export interface PinnedState {
|
|
103
|
+
bots: string[];
|
|
104
|
+
channels: string[];
|
|
105
|
+
}
|
|
106
|
+
export interface PluginConfigPayload {
|
|
107
|
+
basePath: string;
|
|
108
|
+
maxMessagesPerChannel: number;
|
|
109
|
+
historyPageSize: number;
|
|
110
|
+
maxMediaFiles: number;
|
|
111
|
+
blockedPlatforms: Array<{
|
|
112
|
+
platformName: string;
|
|
113
|
+
exactMatch: boolean;
|
|
114
|
+
}>;
|
|
115
|
+
loggerinfo: boolean;
|
|
116
|
+
}
|
|
117
|
+
export interface ContactCacheItem {
|
|
118
|
+
id: string;
|
|
119
|
+
name: string;
|
|
120
|
+
avatar?: string;
|
|
121
|
+
channelId?: string;
|
|
122
|
+
guildId?: string;
|
|
123
|
+
raw?: unknown;
|
|
124
|
+
}
|
|
125
|
+
export interface ContactCacheQuery {
|
|
126
|
+
platform: string;
|
|
127
|
+
selfId: string;
|
|
128
|
+
type: string;
|
|
129
|
+
contacts?: ContactCacheItem[];
|
|
130
|
+
append?: boolean;
|
|
131
|
+
}
|
|
132
|
+
export interface ContactCacheResult {
|
|
133
|
+
contacts?: ContactCacheItem[];
|
|
134
|
+
}
|
|
135
|
+
declare module '@koishijs/console' {
|
|
136
|
+
interface Events {
|
|
137
|
+
'chat-patch/bootstrap'(): SatoriBootstrap;
|
|
138
|
+
'chat-patch/history'(query: HistoryQuery): Awaitable<HistoryResult>;
|
|
139
|
+
'chat-patch/config'(): PluginConfigPayload;
|
|
140
|
+
'chat-patch/contact-cache'(query: ContactCacheQuery): Awaitable<ContactCacheResult>;
|
|
141
|
+
}
|
|
142
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "koishi-plugin-chat-patch",
|
|
3
3
|
"description": "[<ruby>chat-patch<rp>(</rp><rt>点我预览效果</rt><rp>)</rp></ruby>](https://i0.hdslb.com/bfs/openplatform/71074dfc9e5256fc3333d8bd8478bec1af874046.png) 视奸小插件((bushi( (低性能警告)。手机端适配。灵感来自 chat 插件。",
|
|
4
|
-
"version": "6.
|
|
4
|
+
"version": "6.1.0",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"build:web": "npm --prefix client/web run build",
|
|
7
7
|
"prepack": "npm run build:web",
|
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
|
package/src/web.ts
CHANGED
|
@@ -40,8 +40,9 @@ export function registerWeb(
|
|
|
40
40
|
const webRoot = path.resolve(__dirname, '..', 'client', 'web', 'dist')
|
|
41
41
|
const webSource = path.resolve(__dirname, '..', 'client', 'web', 'src')
|
|
42
42
|
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')
|
|
43
|
+
const webIndex = path.resolve(__dirname, '..', 'client', 'web', 'index.html')
|
|
44
|
+
const uploadDir = path.resolve(ctx.baseDir, 'data', 'chat-patch', 'upload-media')
|
|
45
|
+
const mediaDir = path.resolve(ctx.baseDir, 'data', 'chat-patch', 'media')
|
|
45
46
|
|
|
46
47
|
type WebContext = Router.RouterContext<DefaultState, DefaultContext>
|
|
47
48
|
const cacheType = (type: string) => type === 'user' ? 'friend' : type
|
|
@@ -106,6 +107,22 @@ export function registerWeb(
|
|
|
106
107
|
const historyChannelCandidates = (channelId: string): string[] => {
|
|
107
108
|
return channelId ? [channelId] : []
|
|
108
109
|
}
|
|
110
|
+
|
|
111
|
+
const messageSortTime = (item: MessageRecord): number => {
|
|
112
|
+
const timestampMs = Number(item.timestampMs ?? 0)
|
|
113
|
+
if (timestampMs > 0) return timestampMs > 1e12 ? timestampMs : timestampMs * 1000
|
|
114
|
+
const timestamp = Number(item.timestamp ?? 0)
|
|
115
|
+
if (timestamp > 0) return timestamp * 1000
|
|
116
|
+
return Number(item.receivedAt ?? 0)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const selfMessageSortTime = (item: SelfMessageRecord): number => {
|
|
120
|
+
const timestampMs = Number(item.timestampMs ?? item.sentAt ?? 0)
|
|
121
|
+
if (timestampMs > 0) return timestampMs > 1e12 ? timestampMs : timestampMs * 1000
|
|
122
|
+
const timestamp = Number(item.timestamp ?? 0)
|
|
123
|
+
if (timestamp > 0) return timestamp * 1000
|
|
124
|
+
return item.sentAt
|
|
125
|
+
}
|
|
109
126
|
|
|
110
127
|
const mimeToExt: Record<string, string> = {
|
|
111
128
|
'audio/mpeg': '.mp3',
|
|
@@ -242,66 +259,68 @@ export function registerWeb(
|
|
|
242
259
|
}
|
|
243
260
|
})
|
|
244
261
|
|
|
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
|
-
|
|
262
|
+
const sendLocalFile = async (koa: WebContext, filePath: string) => {
|
|
263
|
+
const size = statSync(filePath).size
|
|
264
|
+
const fileName = path.basename(filePath)
|
|
265
|
+
koa.set('Accept-Ranges', 'bytes')
|
|
266
|
+
if (fileName.toLowerCase().endsWith('.webm')) {
|
|
267
|
+
koa.type = 'audio/webm'
|
|
268
|
+
} else {
|
|
269
|
+
koa.type = fileName
|
|
270
|
+
}
|
|
271
|
+
const range = koa.headers.range
|
|
272
|
+
if (range) {
|
|
273
|
+
const match = /^bytes=(\d*)-(\d*)$/.exec(String(range))
|
|
274
|
+
if (!match) {
|
|
275
|
+
koa.status = 416
|
|
276
|
+
koa.set('Content-Range', `bytes */${size}`)
|
|
277
|
+
koa.body = ''
|
|
278
|
+
return
|
|
279
|
+
}
|
|
280
|
+
let start = 0
|
|
281
|
+
let end = size - 1
|
|
282
|
+
if (match[1] === '' && match[2]) {
|
|
283
|
+
start = Math.max(0, size - Number(match[2]))
|
|
284
|
+
} else if (match[1]) {
|
|
285
|
+
start = Number(match[1])
|
|
286
|
+
end = match[2] ? Number(match[2]) : end
|
|
287
|
+
}
|
|
288
|
+
if (!Number.isFinite(start) || !Number.isFinite(end) || start < 0 || start >= size) {
|
|
289
|
+
koa.status = 416
|
|
290
|
+
koa.set('Content-Range', `bytes */${size}`)
|
|
291
|
+
koa.body = ''
|
|
292
|
+
return
|
|
293
|
+
}
|
|
294
|
+
if (end >= size) end = size - 1
|
|
295
|
+
if (end < start) end = start
|
|
296
|
+
koa.status = 206
|
|
297
|
+
koa.set('Content-Range', `bytes ${start}-${end}/${size}`)
|
|
298
|
+
koa.set('Content-Length', String(end - start + 1))
|
|
299
|
+
koa.body = createReadStream(filePath, { start, end })
|
|
300
|
+
return
|
|
301
|
+
}
|
|
302
|
+
koa.set('Content-Length', String(size))
|
|
303
|
+
koa.body = createReadStream(filePath)
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
ctx.server.get(`${config.basePath}/api/media`, async (koa) => {
|
|
307
|
+
const fileName = path.basename(String(koa.query.file ?? koa.query.name ?? ''))
|
|
308
|
+
if (!fileName || fileName === '.' || fileName === '..') {
|
|
309
|
+
koa.status = 400
|
|
310
|
+
koa.body = { error: 'missing media file' }
|
|
311
|
+
return
|
|
312
|
+
}
|
|
313
|
+
for (const root of [uploadDir, mediaDir]) {
|
|
314
|
+
const filePath = path.resolve(root, fileName)
|
|
315
|
+
if (filePath !== root && filePath.startsWith(`${root}${path.sep}`)
|
|
316
|
+
&& existsSync(filePath) && statSync(filePath).isFile()) {
|
|
317
|
+
await sendLocalFile(koa, filePath)
|
|
318
|
+
return
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
koa.status = 404
|
|
322
|
+
koa.body = { error: 'media file not found' }
|
|
323
|
+
})
|
|
305
324
|
|
|
306
325
|
ctx.server.get(`${config.basePath}/api/cache/all`, async (koa) => {
|
|
307
326
|
const entries = await database.getAllContacts()
|
|
@@ -362,13 +381,13 @@ export function registerWeb(
|
|
|
362
381
|
selfMessages = await querySelfMessages(candidate)
|
|
363
382
|
if (selfMessages.length) break
|
|
364
383
|
}
|
|
365
|
-
// 统一按本地收到时间排序,避免平台时间不准导致顺序颠倒
|
|
366
|
-
messages.sort((a, b) => {
|
|
367
|
-
const timeA =
|
|
368
|
-
const timeB =
|
|
369
|
-
return timeA - timeB
|
|
384
|
+
// 统一按本地收到时间排序,避免平台时间不准导致顺序颠倒
|
|
385
|
+
messages.sort((a, b) => {
|
|
386
|
+
const timeA = messageSortTime(a)
|
|
387
|
+
const timeB = messageSortTime(b)
|
|
388
|
+
return timeA - timeB
|
|
370
389
|
})
|
|
371
|
-
selfMessages.sort((a, b) => a
|
|
390
|
+
selfMessages.sort((a, b) => selfMessageSortTime(a) - selfMessageSortTime(b))
|
|
372
391
|
// 合并两条记录流后只返回 limit 条,保证前端分页/结束判断不会因自消息被放大
|
|
373
392
|
const combined: Array<{
|
|
374
393
|
kind: 'message' | 'self'
|
|
@@ -378,12 +397,12 @@ export function registerWeb(
|
|
|
378
397
|
for (const item of messages) {
|
|
379
398
|
combined.push({
|
|
380
399
|
kind: 'message',
|
|
381
|
-
time:
|
|
400
|
+
time: messageSortTime(item),
|
|
382
401
|
record: item,
|
|
383
402
|
})
|
|
384
403
|
}
|
|
385
404
|
for (const item of selfMessages) {
|
|
386
|
-
combined.push({ kind: 'self', time: item
|
|
405
|
+
combined.push({ kind: 'self', time: selfMessageSortTime(item), record: item })
|
|
387
406
|
}
|
|
388
407
|
combined.sort((a, b) => a.time - b.time)
|
|
389
408
|
const capped = combined.slice(-limit)
|
|
@@ -416,7 +435,7 @@ export function registerWeb(
|
|
|
416
435
|
: await database.listSelfMessages(platform, selfId, candidate, limit)
|
|
417
436
|
if (messages.length) break
|
|
418
437
|
}
|
|
419
|
-
messages.sort((a, b) => a
|
|
438
|
+
messages.sort((a, b) => selfMessageSortTime(a) - selfMessageSortTime(b))
|
|
420
439
|
koa.body = { messages }
|
|
421
440
|
})
|
|
422
441
|
|
|
@@ -438,9 +457,15 @@ export function registerWeb(
|
|
|
438
457
|
message: Array.isArray(body.message) ? body.message as unknown[] : undefined,
|
|
439
458
|
forwardId: typeof body.forwardId === 'string' ? body.forwardId : undefined,
|
|
440
459
|
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(),
|
|
460
|
+
sentAt: typeof body.sentAt === 'number' && Number.isFinite(body.sentAt)
|
|
461
|
+
? body.sentAt
|
|
462
|
+
: Date.now(),
|
|
463
|
+
timestamp: typeof body.timestamp === 'number' && Number.isFinite(body.timestamp)
|
|
464
|
+
? body.timestamp
|
|
465
|
+
: undefined,
|
|
466
|
+
timestampMs: typeof body.timestampMs === 'number' && Number.isFinite(body.timestampMs)
|
|
467
|
+
? body.timestampMs
|
|
468
|
+
: undefined,
|
|
444
469
|
sequence: typeof body.sequence === 'number' && Number.isFinite(body.sequence)
|
|
445
470
|
? body.sequence
|
|
446
471
|
: 0,
|
|
@@ -456,7 +481,8 @@ export function registerWeb(
|
|
|
456
481
|
koa.body = { error: 'missing self-message params' }
|
|
457
482
|
return
|
|
458
483
|
}
|
|
459
|
-
const
|
|
484
|
+
const sentAt = payload.sentAt ?? Date.now()
|
|
485
|
+
const record: SelfMessageRecord = {
|
|
460
486
|
id: payload.id || `web-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
|
461
487
|
platform: payload.platform,
|
|
462
488
|
selfId: payload.selfId,
|
|
@@ -469,7 +495,9 @@ export function registerWeb(
|
|
|
469
495
|
message: payload.message,
|
|
470
496
|
forwardId: payload.forwardId,
|
|
471
497
|
forwardContent: payload.forwardContent,
|
|
472
|
-
sentAt
|
|
498
|
+
sentAt,
|
|
499
|
+
timestamp: payload.timestamp ?? Math.floor(sentAt / 1000),
|
|
500
|
+
timestampMs: payload.timestampMs ?? sentAt,
|
|
473
501
|
sequence: payload.sequence ?? 0,
|
|
474
502
|
source: payload.source ?? 'webui',
|
|
475
503
|
kind: payload.kind ?? 'text',
|