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
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
<link rel="stylesheet" href="./bcui/css/style.css">
|
|
12
12
|
<link rel="stylesheet" href="./bcui/css/color-light.css">
|
|
13
13
|
<link rel="stylesheet" href="./css/append-light.css">
|
|
14
|
-
<script type="module" crossorigin src="./assets/index
|
|
14
|
+
<script type="module" crossorigin src="./assets/index-DOJoilEy.js"></script>
|
|
15
15
|
<link rel="stylesheet" crossorigin href="./assets/index-X9hFOAza.css">
|
|
16
16
|
</head>
|
|
17
17
|
|
|
@@ -103,17 +103,17 @@
|
|
|
103
103
|
class="msg-md" />
|
|
104
104
|
<img v-else-if="item.type == 'image' && item.file == 'marketface'"
|
|
105
105
|
:class=" imgStyle(data.message.length, Number(index), true) + ' msg-mface'"
|
|
106
|
-
:src="item
|
|
106
|
+
:src="getImageUrl(item)"
|
|
107
107
|
:alt="item.summary"
|
|
108
108
|
@load="imageLoaded"
|
|
109
|
-
@click="preImgClick(item
|
|
109
|
+
@click="preImgClick(getImageUrl(item))"
|
|
110
110
|
@error="imgLoadFail">
|
|
111
111
|
<img v-else-if="item.type == 'mface'"
|
|
112
112
|
:class=" imgStyle(data.message.length, Number(index), true) + ' msg-mface'"
|
|
113
|
-
:src="item
|
|
113
|
+
:src="getImageUrl(item)"
|
|
114
114
|
:alt="item.summary"
|
|
115
115
|
@load="imageLoaded"
|
|
116
|
-
@click="preImgClick(item
|
|
116
|
+
@click="preImgClick(getImageUrl(item))"
|
|
117
117
|
@error="imgLoadFail">
|
|
118
118
|
<template v-else-if="item.type == 'image'">
|
|
119
119
|
<div v-show="shouldShowImagePlaceholder(item, Number(index))"
|
|
@@ -121,21 +121,21 @@
|
|
|
121
121
|
:class="imgStyle(data.message.length, Number(index), isFace(item)) + ' msg-img-placeholder'"
|
|
122
122
|
@click="loadImage(item, Number(index), $event)">
|
|
123
123
|
<font-awesome-icon
|
|
124
|
-
:icon="['fas', imageLoading(getImageKey(Number(index), item
|
|
125
|
-
:spin="imageLoading(getImageKey(Number(index), item
|
|
124
|
+
:icon="['fas', imageLoading(getImageKey(Number(index), getImageUrl(item))) ? 'spinner' : 'image']"
|
|
125
|
+
:spin="imageLoading(getImageKey(Number(index), getImageUrl(item)))" />
|
|
126
126
|
<span>
|
|
127
|
-
{{ imageLoading(getImageKey(Number(index), item
|
|
127
|
+
{{ imageLoading(getImageKey(Number(index), getImageUrl(item))) ? $t('加载中') : $t('点击加载图片') }}
|
|
128
128
|
</span>
|
|
129
129
|
</div>
|
|
130
130
|
<img v-show="!shouldShowImagePlaceholder(item, Number(index))"
|
|
131
131
|
:title="(!item.summary || item.summary == '') ? $t('预览图片') : item.summary"
|
|
132
132
|
:alt="$t('图片')"
|
|
133
133
|
:class=" imgStyle(data.message.length, Number(index), isFace(item))"
|
|
134
|
-
:src="getImgSrc(item
|
|
134
|
+
:src="getImgSrc(getImageUrl(item))"
|
|
135
135
|
data-type="image"
|
|
136
136
|
@load="imageLoaded"
|
|
137
137
|
@error="imgLoadFail"
|
|
138
|
-
@click="imgClick(item
|
|
138
|
+
@click="imgClick(getImageUrl(item), $event)">
|
|
139
139
|
</template>
|
|
140
140
|
<template v-else-if="item.type == 'face'">
|
|
141
141
|
<EmojiFace :emoji="Emoji.get(Number(item.id))" class="msg-face" />
|
|
@@ -173,7 +173,8 @@
|
|
|
173
173
|
<div v-if="data.fileView && Object.keys(data.fileView).length > 0"
|
|
174
174
|
class="file-view">
|
|
175
175
|
<img v-if="['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'].includes(data.fileView.ext)"
|
|
176
|
-
:src="getMediaSrc(data.fileView.url)"
|
|
176
|
+
:src="getMediaSrc(data.fileView.url)"
|
|
177
|
+
@click="preImgClick(getMediaSrc(data.fileView.url))">
|
|
177
178
|
<video v-else-if="['mp4', 'avi', 'mkv', 'flv'].includes(data.fileView.ext)"
|
|
178
179
|
playsinline controls muted
|
|
179
180
|
autoplay>
|
|
@@ -570,11 +571,16 @@ async function loadCachedImages() {
|
|
|
570
571
|
const selfId = authStore.loginInfo?.uin
|
|
571
572
|
if (!selfId) return
|
|
572
573
|
for (const seg of data.message) {
|
|
573
|
-
|
|
574
|
-
|
|
574
|
+
const url = getImageUrl(seg)
|
|
575
|
+
if (seg.type !== 'image' || !url) continue
|
|
576
|
+
await loadCachedImage(url)
|
|
575
577
|
}
|
|
576
578
|
}
|
|
577
579
|
|
|
580
|
+
function getImageUrl(item: { url?: string; file?: string }): string {
|
|
581
|
+
return String(item?.url || item?.file || '')
|
|
582
|
+
}
|
|
583
|
+
|
|
578
584
|
async function loadCachedImage(url: string) {
|
|
579
585
|
if (resolvedImages.value[url]) return resolvedImages.value[url]
|
|
580
586
|
|
|
@@ -593,6 +599,7 @@ async function loadCachedImage(url: string) {
|
|
|
593
599
|
}
|
|
594
600
|
|
|
595
601
|
function getImgSrc(url: string): string {
|
|
602
|
+
if (!url) return ''
|
|
596
603
|
if (url.startsWith('base64://')) {
|
|
597
604
|
return `data:image/png;base64,${url.slice(9)}`
|
|
598
605
|
}
|
|
@@ -617,11 +624,11 @@ function imageLoading(key: string) {
|
|
|
617
624
|
return pendingImageLoads.value[key] === true
|
|
618
625
|
}
|
|
619
626
|
|
|
620
|
-
function shouldShowImagePlaceholder(item: { type: string, url
|
|
627
|
+
function shouldShowImagePlaceholder(item: { type: string, url?: string, file?: string }, index: number) {
|
|
621
628
|
if (item.type !== 'image') return false
|
|
622
629
|
if (settingsStore.sysConfig.opt_no_auto_load_image !== true) return false
|
|
623
630
|
|
|
624
|
-
return manualImageLoads.value[getImageKey(index, item
|
|
631
|
+
return manualImageLoads.value[getImageKey(index, getImageUrl(item))] !== true
|
|
625
632
|
}
|
|
626
633
|
|
|
627
634
|
function getAtClass(who: number | string) {
|
|
@@ -717,8 +724,9 @@ function buildMessageImageList(): Img | undefined {
|
|
|
717
724
|
const collect = (msg: any) => {
|
|
718
725
|
if (!Array.isArray(msg?.message)) return
|
|
719
726
|
for (const item of msg.message) {
|
|
720
|
-
if (item?.type === 'image' && item?.file !== 'marketface'
|
|
721
|
-
|
|
727
|
+
if (item?.type === 'image' && item?.file !== 'marketface') {
|
|
728
|
+
const url = getImageUrl(item)
|
|
729
|
+
if (url) urls.push(url)
|
|
722
730
|
}
|
|
723
731
|
}
|
|
724
732
|
}
|
|
@@ -729,15 +737,16 @@ function buildMessageImageList(): Img | undefined {
|
|
|
729
737
|
return Img.fromList([...new Set(urls)])
|
|
730
738
|
}
|
|
731
739
|
|
|
732
|
-
async function loadImage(item: { url
|
|
740
|
+
async function loadImage(item: { url?: string; file?: string }, index: number, _event?: MouseEvent) {
|
|
733
741
|
if (props.selecting) return
|
|
734
|
-
const
|
|
742
|
+
const url = getImageUrl(item)
|
|
743
|
+
const key = getImageKey(index, url)
|
|
735
744
|
if (manualImageLoads.value[key] || pendingImageLoads.value[key]) return
|
|
736
745
|
|
|
737
746
|
pendingImageLoads.value[key] = true
|
|
738
747
|
try {
|
|
739
748
|
if (data._from_local_db) {
|
|
740
|
-
await loadCachedImage(
|
|
749
|
+
await loadCachedImage(url)
|
|
741
750
|
}
|
|
742
751
|
manualImageLoads.value[key] = true
|
|
743
752
|
} finally {
|
|
@@ -1252,7 +1261,7 @@ async function openMerge(event?: MouseEvent) {
|
|
|
1252
1261
|
imgList.push({
|
|
1253
1262
|
index: index,
|
|
1254
1263
|
message_id: item.message_id,
|
|
1255
|
-
img_url: msg.url,
|
|
1264
|
+
img_url: msg.url || msg.file,
|
|
1256
1265
|
})
|
|
1257
1266
|
index++
|
|
1258
1267
|
}
|
|
@@ -1303,6 +1303,8 @@ export interface SaveSelfMessagePayload {
|
|
|
1303
1303
|
forwardId?: string
|
|
1304
1304
|
forwardContent?: unknown[]
|
|
1305
1305
|
sentAt?: number
|
|
1306
|
+
timestamp?: number
|
|
1307
|
+
timestampMs?: number
|
|
1306
1308
|
source?: 'webui' | 'bot' | 'plugin'
|
|
1307
1309
|
kind?: string
|
|
1308
1310
|
}
|
|
@@ -1330,6 +1332,8 @@ function selfMessageToOneBot(record: unknown): Record<string, unknown> | null {
|
|
|
1330
1332
|
const selfId = getString(obj.selfId)
|
|
1331
1333
|
const channelId = getString(obj.channelId)
|
|
1332
1334
|
const sentAt = Number(obj.sentAt ?? Date.now())
|
|
1335
|
+
const timestamp = Number(obj.timestamp) || Math.floor(sentAt / 1000)
|
|
1336
|
+
const timestampMs = Number(obj.timestampMs ?? sentAt)
|
|
1333
1337
|
const messageId = getString(obj.messageId)
|
|
1334
1338
|
const localId = getString(obj.id) || `self-${sentAt}`
|
|
1335
1339
|
// 只按记录里的显式 channelType 判断,不根据频道 ID 前缀推断
|
|
@@ -1399,10 +1403,11 @@ function selfMessageToOneBot(record: unknown): Record<string, unknown> | null {
|
|
|
1399
1403
|
channel_id: channelId,
|
|
1400
1404
|
guild_id: guildId || undefined,
|
|
1401
1405
|
target_id: selfId,
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
+
timestamp,
|
|
1407
|
+
time: timestamp,
|
|
1408
|
+
local_time: timestampMs,
|
|
1409
|
+
timestamp_ms: timestampMs,
|
|
1410
|
+
time_ms: timestampMs,
|
|
1406
1411
|
sender: {
|
|
1407
1412
|
user_id: selfId,
|
|
1408
1413
|
nickname: getString(login?.name) || selfId,
|
|
@@ -1469,8 +1474,9 @@ export async function loadChatHistoryFromCache(params: {
|
|
|
1469
1474
|
const raw = getObject(recordObj.raw)
|
|
1470
1475
|
const msg = satoriEventToOneBot(raw, params.platform)
|
|
1471
1476
|
if (!msg || !msg.message_id) continue
|
|
1472
|
-
const
|
|
1473
|
-
|
|
1477
|
+
const rawTime = Number(recordObj.timestampMs ?? recordObj.timestamp ?? recordObj.receivedAt ?? 0)
|
|
1478
|
+
const localTime = rawTime > 1e12 ? rawTime : rawTime * 1000
|
|
1479
|
+
if (localTime > 0) {
|
|
1474
1480
|
msg.local_time = localTime
|
|
1475
1481
|
msg.timestamp_ms = localTime
|
|
1476
1482
|
msg.time_ms = localTime
|
|
@@ -1039,6 +1039,8 @@ const msgFunctions = {
|
|
|
1039
1039
|
message: Array.isArray(sentItem?.message) ? sentItem.message : [],
|
|
1040
1040
|
source: 'webui',
|
|
1041
1041
|
sentAt: Number(sentItem?.local_time ?? sentItem?.timestamp_ms ?? Date.now()),
|
|
1042
|
+
timestamp: Number(sentItem?.time) || Math.floor(Date.now() / 1000),
|
|
1043
|
+
timestampMs: Number(sentItem?.local_time ?? sentItem?.timestamp_ms ?? Date.now()),
|
|
1042
1044
|
kind: String(sentItem?.message?.[0]?.type ?? 'text'),
|
|
1043
1045
|
})
|
|
1044
1046
|
}
|
|
@@ -456,6 +456,7 @@ export function satoriEventToOneBot(
|
|
|
456
456
|
self_id: selfId,
|
|
457
457
|
platform,
|
|
458
458
|
sn: getNumber(event.sn),
|
|
459
|
+
timestamp: Math.floor(normalizeTimestampMs(event.timestamp) / 1000),
|
|
459
460
|
timestamp_ms: getNumber(event.timestamp) ? normalizeTimestampMs(event.timestamp) : 0,
|
|
460
461
|
time: Math.floor(normalizeTimestampMs(event.timestamp) / 1000),
|
|
461
462
|
message_seq: getNumber(event.sn),
|
|
@@ -733,6 +734,7 @@ function messageListFromResponse(data: unknown): unknown[] {
|
|
|
733
734
|
return {
|
|
734
735
|
message_id: getString(message.id),
|
|
735
736
|
sn: getNumber(message.sn),
|
|
737
|
+
timestamp: Math.floor(normalizeTimestampMs(message.timestamp) / 1000),
|
|
736
738
|
timestamp_ms: getNumber(message.timestamp) ? normalizeTimestampMs(message.timestamp) : 0,
|
|
737
739
|
time: Math.floor(normalizeTimestampMs(message.timestamp) / 1000),
|
|
738
740
|
message_seq: getNumber(message.sn) || getNumber(message.seq),
|
|
@@ -518,13 +518,24 @@ function getBase64Source(item: any): string {
|
|
|
518
518
|
return ''
|
|
519
519
|
}
|
|
520
520
|
|
|
521
|
-
function isBase64Source(source: string): boolean {
|
|
522
|
-
return source.startsWith('base64://') || source.startsWith('data:')
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
521
|
+
function isBase64Source(source: string): boolean {
|
|
522
|
+
return source.startsWith('base64://') || source.startsWith('data:')
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
function toPreviewImageSrc(item: any): string {
|
|
526
|
+
const source = String(item?.url ?? item?.file ?? '')
|
|
527
|
+
if (source.startsWith('base64://')) {
|
|
528
|
+
return `data:image/png;base64,${source.slice(9)}`
|
|
529
|
+
}
|
|
530
|
+
if (source.startsWith('data:') || source.startsWith('http:') || source.startsWith('https:')) {
|
|
531
|
+
return source
|
|
532
|
+
}
|
|
533
|
+
return getLocalMediaUrl(source)
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
export function getLocalMediaUrl(value: string): string {
|
|
537
|
+
if (!value) return value
|
|
538
|
+
const isLocalPath = value.startsWith('file:') ||
|
|
528
539
|
/^[a-z]:[\\/]/i.test(value) ||
|
|
529
540
|
value.startsWith('\\\\')
|
|
530
541
|
if (!isLocalPath) return value
|
|
@@ -637,20 +648,15 @@ export async function sendMsgRaw(
|
|
|
637
648
|
const preShowMsg = typeof msg === 'string'
|
|
638
649
|
? parsePreviewMarkup(msg)
|
|
639
650
|
: JSON.parse(JSON.stringify(msg));
|
|
640
|
-
preShowMsg.forEach((item: any) => {
|
|
641
|
-
// 对 base64 图片做特殊处理
|
|
642
|
-
if (item.type == 'image') {
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
}
|
|
650
|
-
if (item.type == 'record' && item.file.startsWith('base64://')) {
|
|
651
|
-
const b64Str = (item.file as string).substring(9)
|
|
652
|
-
item.url = 'data:audio/webm;base64,' + b64Str
|
|
653
|
-
}
|
|
651
|
+
preShowMsg.forEach((item: any) => {
|
|
652
|
+
// 对 base64 图片做特殊处理
|
|
653
|
+
if (item.type == 'image') {
|
|
654
|
+
item.url = toPreviewImageSrc(item)
|
|
655
|
+
}
|
|
656
|
+
if (item.type == 'record' && String(item?.file ?? '').startsWith('base64://')) {
|
|
657
|
+
const b64Str = (item.file as string).substring(9)
|
|
658
|
+
item.url = 'data:audio/webm;base64,' + b64Str
|
|
659
|
+
}
|
|
654
660
|
})
|
|
655
661
|
const showMsg = {
|
|
656
662
|
revoke: true,
|
|
@@ -502,13 +502,6 @@
|
|
|
502
502
|
<div><font-awesome-icon :icon="['fas', 'trash-can']" /></div>
|
|
503
503
|
<a>{{ $t('移出群聊') }}</a>
|
|
504
504
|
</div>
|
|
505
|
-
<div v-show="tags.menuDisplay.config"
|
|
506
|
-
@click="openChatInfoPan();
|
|
507
|
-
($refs.infoRef as any).openMoreConfig(selectedMsg?.sender.user_id);
|
|
508
|
-
closeMsgMenu();">
|
|
509
|
-
<div><font-awesome-icon :icon="['fas', 'cog']" /></div>
|
|
510
|
-
<a>{{ $t('成员设置') }}</a>
|
|
511
|
-
</div>
|
|
512
505
|
<div v-show="tags.menuDisplay.jumpToMsg" @click="jumpSearchMsg">
|
|
513
506
|
<div><font-awesome-icon :icon="['fas', 'arrow-up-right-from-square']" /></div>
|
|
514
507
|
<a>{{ $t('跳转到消息') }}</a>
|
|
@@ -2089,6 +2082,8 @@ function forwardMsg(data: UserFriendElem & UserGroupElem) {
|
|
|
2089
2082
|
forwardContent: msgBody,
|
|
2090
2083
|
source: 'webui',
|
|
2091
2084
|
sentAt: sentTime,
|
|
2085
|
+
timestamp: Math.floor(sentTime / 1000),
|
|
2086
|
+
timestampMs: sentTime,
|
|
2092
2087
|
kind: 'forward',
|
|
2093
2088
|
})
|
|
2094
2089
|
}
|
|
@@ -2996,7 +2991,7 @@ function updateList(newLength: number, oldLength: number) {
|
|
|
2996
2991
|
msgItem.type === 'image' &&
|
|
2997
2992
|
msgItem.file != 'marketface'
|
|
2998
2993
|
) {
|
|
2999
|
-
getImgList.push(msgItem.url)
|
|
2994
|
+
getImgList.push(msgItem.url || msgItem.file)
|
|
3000
2995
|
}
|
|
3001
2996
|
}
|
|
3002
2997
|
}
|
package/lib/index.js
CHANGED
|
@@ -5142,6 +5142,12 @@ function encodeKeyPart2(value) {
|
|
|
5142
5142
|
return encodeURIComponent(value);
|
|
5143
5143
|
}
|
|
5144
5144
|
__name(encodeKeyPart2, "encodeKeyPart");
|
|
5145
|
+
function normalizeTimestampMs(value) {
|
|
5146
|
+
const num = Number(value ?? 0);
|
|
5147
|
+
if (!Number.isFinite(num) || num <= 0) return 0;
|
|
5148
|
+
return num > 1e12 ? num : num * 1e3;
|
|
5149
|
+
}
|
|
5150
|
+
__name(normalizeTimestampMs, "normalizeTimestampMs");
|
|
5145
5151
|
function decodeKeyPart(value) {
|
|
5146
5152
|
try {
|
|
5147
5153
|
return decodeURIComponent(value);
|
|
@@ -5160,7 +5166,7 @@ function legacyMessagePrefix(platform, selfId, channelId) {
|
|
|
5160
5166
|
__name(legacyMessagePrefix, "legacyMessagePrefix");
|
|
5161
5167
|
function messageKey(record) {
|
|
5162
5168
|
const time = String(
|
|
5163
|
-
record.
|
|
5169
|
+
normalizeTimestampMs(record.timestampMs ?? record.timestamp ?? record.receivedAt)
|
|
5164
5170
|
).padStart(16, "0");
|
|
5165
5171
|
return `${messagePrefix(record.platform, record.selfId, record.channelId || "")}${time}:${encodeKeyPart2(record.id || "unknown")}`;
|
|
5166
5172
|
}
|
|
@@ -5170,7 +5176,9 @@ function selfMessagePrefix(platform, selfId, channelId) {
|
|
|
5170
5176
|
}
|
|
5171
5177
|
__name(selfMessagePrefix, "selfMessagePrefix");
|
|
5172
5178
|
function selfMessageKey(record) {
|
|
5173
|
-
const time = String(
|
|
5179
|
+
const time = String(
|
|
5180
|
+
normalizeTimestampMs(record.timestampMs ?? record.timestamp ?? record.sentAt)
|
|
5181
|
+
).padStart(16, "0");
|
|
5174
5182
|
return `${selfMessagePrefix(record.platform, record.selfId, record.channelId)}${time}:${encodeKeyPart2(record.id || "unknown")}`;
|
|
5175
5183
|
}
|
|
5176
5184
|
__name(selfMessageKey, "selfMessageKey");
|
|
@@ -5824,7 +5832,7 @@ var _ChatDatabase = class _ChatDatabase {
|
|
|
5824
5832
|
extractRecordTime(value) {
|
|
5825
5833
|
try {
|
|
5826
5834
|
const parsed = JSON.parse(value);
|
|
5827
|
-
return
|
|
5835
|
+
return normalizeTimestampMs(parsed.timestampMs ?? parsed.timestamp ?? parsed.sentAt ?? parsed.receivedAt);
|
|
5828
5836
|
} catch {
|
|
5829
5837
|
return 0;
|
|
5830
5838
|
}
|
|
@@ -5890,6 +5898,7 @@ var _Recorder = class _Recorder {
|
|
|
5890
5898
|
const user = getObject(body.user);
|
|
5891
5899
|
const sn = getNumber(body.sn);
|
|
5892
5900
|
const timestamp = getNumber(body.timestamp) || Date.now();
|
|
5901
|
+
const timestampMs = timestamp > 1e12 ? timestamp : timestamp * 1e3;
|
|
5893
5902
|
const record = {
|
|
5894
5903
|
id: getString2(message2.id) || `satori-${sn}`,
|
|
5895
5904
|
sequence: sn,
|
|
@@ -5900,7 +5909,7 @@ var _Recorder = class _Recorder {
|
|
|
5900
5909
|
guildId: getString2(guild.id) || void 0,
|
|
5901
5910
|
userId: getString2(user.id) || void 0,
|
|
5902
5911
|
timestamp,
|
|
5903
|
-
timestampMs
|
|
5912
|
+
timestampMs,
|
|
5904
5913
|
receivedAt: Date.now(),
|
|
5905
5914
|
content: getString2(message2.content) || getString2(message2.raw_message) || void 0,
|
|
5906
5915
|
elements: Array.isArray(message2.elements) ? message2.elements : void 0,
|
|
@@ -7823,6 +7832,7 @@ var _SelfMessageRecorder = class _SelfMessageRecorder {
|
|
|
7823
7832
|
const channelType = mode === "private" ? "user" : "group";
|
|
7824
7833
|
const elements = this.normalizeElements(content);
|
|
7825
7834
|
const contentText = typeof content === "string" ? content : import_koishi.h.toElementArray(content).join("");
|
|
7835
|
+
const sentAt = Date.now();
|
|
7826
7836
|
const fingerprint = (0, import_node_crypto3.createHash)("sha256").update([
|
|
7827
7837
|
platform,
|
|
7828
7838
|
selfId,
|
|
@@ -7842,7 +7852,9 @@ var _SelfMessageRecorder = class _SelfMessageRecorder {
|
|
|
7842
7852
|
message: toSegments(elements, (attrs) => this.resolveI18nElement(attrs)),
|
|
7843
7853
|
forwardId: forwardId(elements),
|
|
7844
7854
|
forwardContent: toForwardNodes(elements, (attrs) => this.resolveI18nElement(attrs)),
|
|
7845
|
-
sentAt
|
|
7855
|
+
sentAt,
|
|
7856
|
+
timestamp: Math.floor(sentAt / 1e3),
|
|
7857
|
+
timestampMs: sentAt,
|
|
7846
7858
|
sequence: ++this.sequence,
|
|
7847
7859
|
source,
|
|
7848
7860
|
kind: detectKind(elements),
|
|
@@ -7932,6 +7944,7 @@ function registerWeb(ctx, config, database, contactCache, media, logger) {
|
|
|
7932
7944
|
const webPublic = import_node_path3.default.resolve(__dirname, "..", "client", "web", "public");
|
|
7933
7945
|
const webIndex = import_node_path3.default.resolve(__dirname, "..", "client", "web", "index.html");
|
|
7934
7946
|
const uploadDir = import_node_path3.default.resolve(ctx.baseDir, "data", "chat-patch", "upload-media");
|
|
7947
|
+
const mediaDir = import_node_path3.default.resolve(ctx.baseDir, "data", "chat-patch", "media");
|
|
7935
7948
|
const cacheType = /* @__PURE__ */ __name((type) => type === "user" ? "friend" : type, "cacheType");
|
|
7936
7949
|
const getVite = /* @__PURE__ */ __name(() => {
|
|
7937
7950
|
return ctx.console.vite;
|
|
@@ -7981,6 +7994,20 @@ function registerWeb(ctx, config, database, contactCache, media, logger) {
|
|
|
7981
7994
|
const historyChannelCandidates = /* @__PURE__ */ __name((channelId) => {
|
|
7982
7995
|
return channelId ? [channelId] : [];
|
|
7983
7996
|
}, "historyChannelCandidates");
|
|
7997
|
+
const messageSortTime = /* @__PURE__ */ __name((item) => {
|
|
7998
|
+
const timestampMs = Number(item.timestampMs ?? 0);
|
|
7999
|
+
if (timestampMs > 0) return timestampMs > 1e12 ? timestampMs : timestampMs * 1e3;
|
|
8000
|
+
const timestamp = Number(item.timestamp ?? 0);
|
|
8001
|
+
if (timestamp > 0) return timestamp * 1e3;
|
|
8002
|
+
return Number(item.receivedAt ?? 0);
|
|
8003
|
+
}, "messageSortTime");
|
|
8004
|
+
const selfMessageSortTime = /* @__PURE__ */ __name((item) => {
|
|
8005
|
+
const timestampMs = Number(item.timestampMs ?? item.sentAt ?? 0);
|
|
8006
|
+
if (timestampMs > 0) return timestampMs > 1e12 ? timestampMs : timestampMs * 1e3;
|
|
8007
|
+
const timestamp = Number(item.timestamp ?? 0);
|
|
8008
|
+
if (timestamp > 0) return timestamp * 1e3;
|
|
8009
|
+
return item.sentAt;
|
|
8010
|
+
}, "selfMessageSortTime");
|
|
7984
8011
|
const mimeToExt = {
|
|
7985
8012
|
"audio/mpeg": ".mp3",
|
|
7986
8013
|
"audio/mp3": ".mp3",
|
|
@@ -8105,26 +8132,9 @@ function registerWeb(ctx, config, database, contactCache, media, logger) {
|
|
|
8105
8132
|
localPath: filePath
|
|
8106
8133
|
};
|
|
8107
8134
|
});
|
|
8108
|
-
|
|
8109
|
-
const fileName = import_node_path3.default.basename(String(koa.query.file ?? koa.query.name ?? ""));
|
|
8110
|
-
if (!fileName || fileName === "." || fileName === "..") {
|
|
8111
|
-
koa.status = 400;
|
|
8112
|
-
koa.body = { error: "missing media file" };
|
|
8113
|
-
return;
|
|
8114
|
-
}
|
|
8115
|
-
const filePath = import_node_path3.default.resolve(uploadDir, fileName);
|
|
8116
|
-
const root = import_node_path3.default.resolve(uploadDir);
|
|
8117
|
-
if (filePath !== root && !filePath.startsWith(`${root}${import_node_path3.default.sep}`)) {
|
|
8118
|
-
koa.status = 400;
|
|
8119
|
-
koa.body = { error: "invalid media file" };
|
|
8120
|
-
return;
|
|
8121
|
-
}
|
|
8122
|
-
if (!(0, import_node_fs3.existsSync)(filePath) || !(0, import_node_fs3.statSync)(filePath).isFile()) {
|
|
8123
|
-
koa.status = 404;
|
|
8124
|
-
koa.body = { error: "media file not found" };
|
|
8125
|
-
return;
|
|
8126
|
-
}
|
|
8135
|
+
const sendLocalFile = /* @__PURE__ */ __name(async (koa, filePath) => {
|
|
8127
8136
|
const size = (0, import_node_fs3.statSync)(filePath).size;
|
|
8137
|
+
const fileName = import_node_path3.default.basename(filePath);
|
|
8128
8138
|
koa.set("Accept-Ranges", "bytes");
|
|
8129
8139
|
if (fileName.toLowerCase().endsWith(".webm")) {
|
|
8130
8140
|
koa.type = "audio/webm";
|
|
@@ -8164,6 +8174,23 @@ function registerWeb(ctx, config, database, contactCache, media, logger) {
|
|
|
8164
8174
|
}
|
|
8165
8175
|
koa.set("Content-Length", String(size));
|
|
8166
8176
|
koa.body = (0, import_node_fs3.createReadStream)(filePath);
|
|
8177
|
+
}, "sendLocalFile");
|
|
8178
|
+
ctx.server.get(`${config.basePath}/api/media`, async (koa) => {
|
|
8179
|
+
const fileName = import_node_path3.default.basename(String(koa.query.file ?? koa.query.name ?? ""));
|
|
8180
|
+
if (!fileName || fileName === "." || fileName === "..") {
|
|
8181
|
+
koa.status = 400;
|
|
8182
|
+
koa.body = { error: "missing media file" };
|
|
8183
|
+
return;
|
|
8184
|
+
}
|
|
8185
|
+
for (const root of [uploadDir, mediaDir]) {
|
|
8186
|
+
const filePath = import_node_path3.default.resolve(root, fileName);
|
|
8187
|
+
if (filePath !== root && filePath.startsWith(`${root}${import_node_path3.default.sep}`) && (0, import_node_fs3.existsSync)(filePath) && (0, import_node_fs3.statSync)(filePath).isFile()) {
|
|
8188
|
+
await sendLocalFile(koa, filePath);
|
|
8189
|
+
return;
|
|
8190
|
+
}
|
|
8191
|
+
}
|
|
8192
|
+
koa.status = 404;
|
|
8193
|
+
koa.body = { error: "media file not found" };
|
|
8167
8194
|
});
|
|
8168
8195
|
ctx.server.get(`${config.basePath}/api/cache/all`, async (koa) => {
|
|
8169
8196
|
const entries = await database.getAllContacts();
|
|
@@ -8215,21 +8242,21 @@ function registerWeb(ctx, config, database, contactCache, media, logger) {
|
|
|
8215
8242
|
if (selfMessages.length) break;
|
|
8216
8243
|
}
|
|
8217
8244
|
messages.sort((a, b) => {
|
|
8218
|
-
const timeA =
|
|
8219
|
-
const timeB =
|
|
8245
|
+
const timeA = messageSortTime(a);
|
|
8246
|
+
const timeB = messageSortTime(b);
|
|
8220
8247
|
return timeA - timeB;
|
|
8221
8248
|
});
|
|
8222
|
-
selfMessages.sort((a, b) => a
|
|
8249
|
+
selfMessages.sort((a, b) => selfMessageSortTime(a) - selfMessageSortTime(b));
|
|
8223
8250
|
const combined = [];
|
|
8224
8251
|
for (const item of messages) {
|
|
8225
8252
|
combined.push({
|
|
8226
8253
|
kind: "message",
|
|
8227
|
-
time:
|
|
8254
|
+
time: messageSortTime(item),
|
|
8228
8255
|
record: item
|
|
8229
8256
|
});
|
|
8230
8257
|
}
|
|
8231
8258
|
for (const item of selfMessages) {
|
|
8232
|
-
combined.push({ kind: "self", time: item
|
|
8259
|
+
combined.push({ kind: "self", time: selfMessageSortTime(item), record: item });
|
|
8233
8260
|
}
|
|
8234
8261
|
combined.sort((a, b) => a.time - b.time);
|
|
8235
8262
|
const capped = combined.slice(-limit);
|
|
@@ -8255,7 +8282,7 @@ function registerWeb(ctx, config, database, contactCache, media, logger) {
|
|
|
8255
8282
|
messages = Number.isFinite(beforeTime) && beforeTime > 0 ? await database.listSelfMessagesBefore(platform, selfId, candidate, beforeTime, limit) : await database.listSelfMessages(platform, selfId, candidate, limit);
|
|
8256
8283
|
if (messages.length) break;
|
|
8257
8284
|
}
|
|
8258
|
-
messages.sort((a, b) => a
|
|
8285
|
+
messages.sort((a, b) => selfMessageSortTime(a) - selfMessageSortTime(b));
|
|
8259
8286
|
koa.body = { messages };
|
|
8260
8287
|
});
|
|
8261
8288
|
ctx.server.post(`${config.basePath}/api/self-messages`, async (koa) => {
|
|
@@ -8275,6 +8302,8 @@ function registerWeb(ctx, config, database, contactCache, media, logger) {
|
|
|
8275
8302
|
forwardId: typeof body.forwardId === "string" ? body.forwardId : void 0,
|
|
8276
8303
|
forwardContent: Array.isArray(body.forwardContent) ? body.forwardContent : void 0,
|
|
8277
8304
|
sentAt: typeof body.sentAt === "number" && Number.isFinite(body.sentAt) ? body.sentAt : Date.now(),
|
|
8305
|
+
timestamp: typeof body.timestamp === "number" && Number.isFinite(body.timestamp) ? body.timestamp : void 0,
|
|
8306
|
+
timestampMs: typeof body.timestampMs === "number" && Number.isFinite(body.timestampMs) ? body.timestampMs : void 0,
|
|
8278
8307
|
sequence: typeof body.sequence === "number" && Number.isFinite(body.sequence) ? body.sequence : 0,
|
|
8279
8308
|
source: body.source === "plugin" ? "plugin" : "webui",
|
|
8280
8309
|
kind: typeof body.kind === "string" ? body.kind : "text",
|
|
@@ -8286,6 +8315,7 @@ function registerWeb(ctx, config, database, contactCache, media, logger) {
|
|
|
8286
8315
|
koa.body = { error: "missing self-message params" };
|
|
8287
8316
|
return;
|
|
8288
8317
|
}
|
|
8318
|
+
const sentAt = payload.sentAt ?? Date.now();
|
|
8289
8319
|
const record = {
|
|
8290
8320
|
id: payload.id || `web-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
|
8291
8321
|
platform: payload.platform,
|
|
@@ -8299,7 +8329,9 @@ function registerWeb(ctx, config, database, contactCache, media, logger) {
|
|
|
8299
8329
|
message: payload.message,
|
|
8300
8330
|
forwardId: payload.forwardId,
|
|
8301
8331
|
forwardContent: payload.forwardContent,
|
|
8302
|
-
sentAt
|
|
8332
|
+
sentAt,
|
|
8333
|
+
timestamp: payload.timestamp ?? Math.floor(sentAt / 1e3),
|
|
8334
|
+
timestampMs: payload.timestampMs ?? sentAt,
|
|
8303
8335
|
sequence: payload.sequence ?? 0,
|
|
8304
8336
|
source: payload.source ?? "webui",
|
|
8305
8337
|
kind: payload.kind ?? "text",
|